code.vegaprotocol.io/vega@v0.79.0/core/execution/spot/market_test.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package spot_test
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"testing"
    22  	"time"
    23  
    24  	bmocks "code.vegaprotocol.io/vega/core/broker/mocks"
    25  	"code.vegaprotocol.io/vega/core/collateral"
    26  	"code.vegaprotocol.io/vega/core/events"
    27  	"code.vegaprotocol.io/vega/core/execution/common"
    28  	"code.vegaprotocol.io/vega/core/execution/common/mocks"
    29  	"code.vegaprotocol.io/vega/core/execution/spot"
    30  	"code.vegaprotocol.io/vega/core/fee"
    31  	fmocks "code.vegaprotocol.io/vega/core/fee/mocks"
    32  	"code.vegaprotocol.io/vega/core/integration/stubs"
    33  	"code.vegaprotocol.io/vega/core/liquidity/v2"
    34  	"code.vegaprotocol.io/vega/core/matching"
    35  	"code.vegaprotocol.io/vega/core/monitor"
    36  	"code.vegaprotocol.io/vega/core/types"
    37  	"code.vegaprotocol.io/vega/libs/crypto"
    38  	"code.vegaprotocol.io/vega/libs/num"
    39  	"code.vegaprotocol.io/vega/libs/ptr"
    40  	"code.vegaprotocol.io/vega/logging"
    41  	"code.vegaprotocol.io/vega/protos/vega"
    42  
    43  	"github.com/golang/mock/gomock"
    44  	"github.com/stretchr/testify/require"
    45  )
    46  
    47  type testMarket struct {
    48  	market           *spot.Market
    49  	log              *logging.Logger
    50  	ctrl             *gomock.Controller
    51  	collateralEngine *collateral.Engine
    52  	broker           *bmocks.MockBroker
    53  	timeService      *mocks.MockTimeService
    54  	banking          *mocks.MockBanking
    55  	now              time.Time
    56  	baseAsset        string
    57  	quoteAsset       string
    58  	mas              *monitor.AuctionState
    59  	eventCount       uint64
    60  	orderEventCount  uint64
    61  	events           []events.Event
    62  	orderEvents      []events.Event
    63  	mktCfg           *types.Market
    64  	stateVar         *stubs.StateVarStub
    65  }
    66  
    67  var (
    68  	MAXMOVEUP   = num.DecimalFromFloat(1000)
    69  	MINMOVEDOWN = num.DecimalFromFloat(500)
    70  )
    71  
    72  func peggedOrderCounterForTest(int64) {}
    73  
    74  var defaultCollateralAssets = []types.Asset{
    75  	{
    76  		ID: "ETH",
    77  		Details: &types.AssetDetails{
    78  			Symbol:  "ETH",
    79  			Quantum: num.DecimalOne(),
    80  		},
    81  	},
    82  	{
    83  		ID: "BTC",
    84  		Details: &types.AssetDetails{
    85  			Symbol:  "BTC",
    86  			Quantum: num.DecimalOne(),
    87  		},
    88  	},
    89  	{
    90  		ID: "VOTE",
    91  		Details: &types.AssetDetails{
    92  			Name:     "VOTE",
    93  			Symbol:   "VOTE",
    94  			Decimals: 5,
    95  			Quantum:  num.DecimalOne(),
    96  			Source: &types.AssetDetailsBuiltinAsset{
    97  				BuiltinAsset: &types.BuiltinAsset{},
    98  			},
    99  		},
   100  	},
   101  }
   102  
   103  var defaultPriceMonitorSettings = &types.PriceMonitoringSettings{
   104  	Parameters: &types.PriceMonitoringParameters{
   105  		Triggers: []*types.PriceMonitoringTrigger{
   106  			{
   107  				Horizon:          600,
   108  				HorizonDec:       num.MustDecimalFromString("600"),
   109  				Probability:      num.DecimalFromFloat(0.99),
   110  				AuctionExtension: 120,
   111  			},
   112  		},
   113  	},
   114  }
   115  
   116  func getMarketWithDP(base, quote string, pMonitorSettings *types.PriceMonitoringSettings, openingAuctionDuration *types.AuctionDuration, quoteDecimalPlaces uint64, positionDP int64) types.Market {
   117  	mkt := types.Market{
   118  		ID:                    crypto.RandomHash(),
   119  		DecimalPlaces:         quoteDecimalPlaces,
   120  		PositionDecimalPlaces: positionDP,
   121  		Fees: &types.Fees{
   122  			Factors: &types.FeeFactors{
   123  				InfrastructureFee: num.DecimalFromFloat(0.001),
   124  				MakerFee:          num.DecimalFromFloat(0.004),
   125  			},
   126  			LiquidityFeeSettings: &types.LiquidityFeeSettings{
   127  				Method: vega.LiquidityFeeSettings_METHOD_MARGINAL_COST,
   128  			},
   129  		},
   130  		TradableInstrument: &types.TradableInstrument{
   131  			Instrument: &types.Instrument{
   132  				ID:   "Crypto/Base-Quote/Spot",
   133  				Code: "CRYPTO:Base-Quote",
   134  				Name: "Base-Quote spot",
   135  				Metadata: &types.InstrumentMetadata{
   136  					Tags: []string{
   137  						"asset_class:spot/crypto",
   138  						"product:spot",
   139  					},
   140  				},
   141  				Product: &types.InstrumentSpot{
   142  					Spot: &types.Spot{
   143  						BaseAsset:  base,
   144  						QuoteAsset: quote,
   145  						Name:       base + "/" + quote,
   146  					},
   147  				},
   148  			},
   149  			RiskModel: &types.TradableInstrumentSimpleRiskModel{
   150  				SimpleRiskModel: &types.SimpleRiskModel{
   151  					Params: &types.SimpleModelParams{
   152  						FactorLong:           num.DecimalFromFloat(0.15),
   153  						FactorShort:          num.DecimalFromFloat(0.25),
   154  						MaxMoveUp:            MAXMOVEUP,
   155  						MinMoveDown:          MINMOVEDOWN,
   156  						ProbabilityOfTrading: num.DecimalFromFloat(0.1),
   157  					},
   158  				},
   159  			},
   160  		},
   161  		OpeningAuction:          openingAuctionDuration,
   162  		PriceMonitoringSettings: pMonitorSettings,
   163  		LiquidityMonitoringParameters: &types.LiquidityMonitoringParameters{
   164  			TargetStakeParameters: &types.TargetStakeParameters{
   165  				TimeWindow:    3600, // seconds = 1h
   166  				ScalingFactor: num.DecimalFromFloat(10),
   167  			},
   168  		},
   169  		LiquiditySLAParams: &types.LiquiditySLAParams{
   170  			PriceRange:                  num.DecimalFromFloat(0.05),
   171  			CommitmentMinTimeFraction:   num.DecimalFromFloat(0.5),
   172  			PerformanceHysteresisEpochs: 1,
   173  			SlaCompetitionFactor:        num.DecimalFromFloat(0.5),
   174  		},
   175  		TickSize: num.UintOne(),
   176  	}
   177  
   178  	return mkt
   179  }
   180  
   181  func newTestMarket(
   182  	t *testing.T,
   183  	pMonitorSettings *types.PriceMonitoringSettings,
   184  	openingAuctionDuration *types.AuctionDuration,
   185  	now time.Time,
   186  ) *testMarket {
   187  	t.Helper()
   188  	return newTestMarketWithAllowedSellers(t, pMonitorSettings, openingAuctionDuration, now, nil)
   189  }
   190  
   191  func newTestMarketWithAllowedSellers(
   192  	t *testing.T,
   193  	pMonitorSettings *types.PriceMonitoringSettings,
   194  	openingAuctionDuration *types.AuctionDuration,
   195  	now time.Time,
   196  	allowedSellers []string,
   197  ) *testMarket {
   198  	t.Helper()
   199  	base := "BTC"
   200  	quote := "ETH"
   201  	quoteDP := uint64(0)
   202  	baseDP := uint64(0)
   203  	positionDP := int64(0)
   204  	log := logging.NewDevLogger()
   205  	ctrl := gomock.NewController(t)
   206  	ts := mocks.NewMockTimeService(ctrl)
   207  	ts.EXPECT().GetTimeNow().DoAndReturn(
   208  		func() time.Time {
   209  			return now
   210  		}).AnyTimes()
   211  	broker := bmocks.NewMockBroker(ctrl)
   212  	collateral := collateral.New(log, collateral.NewDefaultConfig(), ts, broker)
   213  	ctx := context.Background()
   214  
   215  	statevarEngine := stubs.NewStateVar()
   216  	mkt := getMarketWithDP(base, quote, pMonitorSettings, openingAuctionDuration, quoteDP, positionDP)
   217  	mkt.AllowedSellers = allowedSellers
   218  
   219  	as := monitor.NewAuctionState(&mkt, now)
   220  	epoch := mocks.NewMockEpochEngine(ctrl)
   221  	epoch.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).AnyTimes()
   222  
   223  	teams := mocks.NewMockTeams(ctrl)
   224  	bc := mocks.NewMockAccountBalanceChecker(ctrl)
   225  	broker.EXPECT().SendBatch(gomock.Any()).Times(1)
   226  	mat := common.NewMarketActivityTracker(log, teams, bc, broker, collateral)
   227  	epoch.NotifyOnEpoch(mat.OnEpochEvent, mat.OnEpochRestore)
   228  
   229  	baseAsset := NewAssetStub(base, baseDP)
   230  	quoteAsset := NewAssetStub(quote, quoteDP)
   231  
   232  	referralDiscountReward := fmocks.NewMockReferralDiscountRewardService(ctrl)
   233  	volumeDiscount := fmocks.NewMockVolumeDiscountService(ctrl)
   234  	volumeRebate := fmocks.NewMockVolumeRebateService(ctrl)
   235  	referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("no referrer")).AnyTimes()
   236  	referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes()
   237  	referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes()
   238  	volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes()
   239  	volumeRebate.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes()
   240  	banking := mocks.NewMockBanking(ctrl)
   241  
   242  	market, _ := spot.NewMarket(log, matching.NewDefaultConfig(), fee.NewDefaultConfig(), liquidity.NewDefaultConfig(), collateral, &mkt, ts, broker, as, statevarEngine, mat, baseAsset, quoteAsset, peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, volumeRebate, banking)
   243  
   244  	tm := &testMarket{
   245  		market:           market,
   246  		log:              log,
   247  		ctrl:             ctrl,
   248  		broker:           broker,
   249  		timeService:      ts,
   250  		banking:          banking,
   251  		baseAsset:        base,
   252  		quoteAsset:       quote,
   253  		mas:              as,
   254  		now:              now,
   255  		collateralEngine: collateral,
   256  		mktCfg:           &mkt,
   257  		stateVar:         statevarEngine,
   258  	}
   259  
   260  	// eventFn records and count events and orderEvents
   261  	eventFn := func(evt events.Event) {
   262  		if evt.Type() == events.OrderEvent {
   263  			tm.orderEventCount++
   264  			tm.orderEvents = append(tm.orderEvents, evt)
   265  		}
   266  		tm.eventCount++
   267  		tm.events = append(tm.events, evt)
   268  	}
   269  	// eventsFn is the same as eventFn above but handles []event
   270  	eventsFn := func(evts []events.Event) {
   271  		for _, evt := range evts {
   272  			eventFn(evt)
   273  		}
   274  	}
   275  	tm.broker.EXPECT().Send(gomock.Any()).AnyTimes().Do(eventFn)
   276  	tm.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes().Do(eventsFn)
   277  
   278  	assets := defaultCollateralAssets
   279  	for _, a := range assets {
   280  		err := collateral.EnableAsset(ctx, a)
   281  		require.NoError(t, err)
   282  	}
   283  
   284  	tm.collateralEngine.CreateSpotMarketAccounts(ctx, tm.market.GetID(), quote)
   285  
   286  	return tm
   287  }
   288  
   289  func addAccountWithAmount(market *testMarket, party string, amnt uint64, asset string) *types.LedgerMovement {
   290  	r, _ := market.collateralEngine.Deposit(context.Background(), party, asset, num.NewUint(amnt))
   291  	return r
   292  }
   293  
   294  func getGTCLimitOrder(tm *testMarket,
   295  	now time.Time,
   296  	id string,
   297  	side types.Side,
   298  	partyID string,
   299  	size uint64,
   300  	price uint64,
   301  ) *types.Order {
   302  	order := &types.Order{
   303  		Type:        types.OrderTypeLimit,
   304  		TimeInForce: types.OrderTimeInForceGTC,
   305  		Status:      types.OrderStatusActive,
   306  		ID:          id,
   307  		Side:        side,
   308  		Party:       partyID,
   309  		MarketID:    tm.market.GetID(),
   310  		Size:        size,
   311  		Price:       num.NewUint(price),
   312  		Remaining:   size,
   313  		CreatedAt:   now.UnixNano(),
   314  		Reference:   "marketorder",
   315  	}
   316  	return order
   317  }
   318  
   319  //nolint:unparam
   320  func getStopOrderSubmission(tm *testMarket,
   321  	now time.Time,
   322  	id string,
   323  	side1 types.Side,
   324  	side2 types.Side,
   325  	partyID string,
   326  	size uint64,
   327  	price uint64,
   328  ) *types.StopOrdersSubmission {
   329  	return &types.StopOrdersSubmission{
   330  		RisesAbove: &types.StopOrderSetup{
   331  			OrderSubmission: &types.OrderSubmission{
   332  				Type:        types.OrderTypeLimit,
   333  				TimeInForce: types.OrderTimeInForceGTC,
   334  				Side:        side1,
   335  				MarketID:    tm.market.GetID(),
   336  				Size:        size,
   337  				Price:       num.NewUint(price),
   338  				Reference:   "marketorder",
   339  			},
   340  			Expiry: &types.StopOrderExpiry{
   341  				ExpiryStrategy: ptr.From(types.StopOrderExpiryStrategyCancels),
   342  			},
   343  			Trigger:             types.NewTrailingStopOrderTrigger(types.StopOrderTriggerDirectionRisesAbove, num.DecimalFromFloat(0.9)),
   344  			SizeOverrideSetting: types.StopOrderSizeOverrideSettingNone,
   345  			SizeOverrideValue:   nil,
   346  		},
   347  		FallsBelow: &types.StopOrderSetup{
   348  			OrderSubmission: &types.OrderSubmission{
   349  				Type:        types.OrderTypeLimit,
   350  				TimeInForce: types.OrderTimeInForceGTC,
   351  				Side:        side2,
   352  				MarketID:    tm.market.GetID(),
   353  				Size:        size,
   354  				Price:       num.NewUint(price),
   355  				Reference:   "marketorder",
   356  			},
   357  			Expiry: &types.StopOrderExpiry{
   358  				ExpiryStrategy: ptr.From(types.StopOrderExpiryStrategyCancels),
   359  			},
   360  			Trigger:             types.NewTrailingStopOrderTrigger(types.StopOrderTriggerDirectionRisesAbove, num.DecimalFromFloat(0.9)),
   361  			SizeOverrideSetting: types.StopOrderSizeOverrideSettingNone,
   362  			SizeOverrideValue:   nil,
   363  		},
   364  	}
   365  }