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 }