code.vegaprotocol.io/vega@v0.79.0/core/execution/future/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 future_test 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "math" 23 "reflect" 24 "testing" 25 "time" 26 27 bmocks "code.vegaprotocol.io/vega/core/broker/mocks" 28 "code.vegaprotocol.io/vega/core/collateral" 29 "code.vegaprotocol.io/vega/core/datasource" 30 dstypes "code.vegaprotocol.io/vega/core/datasource/common" 31 dserrors "code.vegaprotocol.io/vega/core/datasource/errors" 32 "code.vegaprotocol.io/vega/core/datasource/external/signedoracle" 33 "code.vegaprotocol.io/vega/core/datasource/spec" 34 "code.vegaprotocol.io/vega/core/events" 35 "code.vegaprotocol.io/vega/core/execution/common" 36 "code.vegaprotocol.io/vega/core/execution/common/mocks" 37 "code.vegaprotocol.io/vega/core/execution/future" 38 "code.vegaprotocol.io/vega/core/fee" 39 fmocks "code.vegaprotocol.io/vega/core/fee/mocks" 40 "code.vegaprotocol.io/vega/core/idgeneration" 41 "code.vegaprotocol.io/vega/core/integration/stubs" 42 "code.vegaprotocol.io/vega/core/liquidity/v2" 43 "code.vegaprotocol.io/vega/core/matching" 44 "code.vegaprotocol.io/vega/core/monitor" 45 "code.vegaprotocol.io/vega/core/positions" 46 "code.vegaprotocol.io/vega/core/risk" 47 "code.vegaprotocol.io/vega/core/settlement" 48 "code.vegaprotocol.io/vega/core/types" 49 vegacontext "code.vegaprotocol.io/vega/libs/context" 50 "code.vegaprotocol.io/vega/libs/crypto" 51 vgcrypto "code.vegaprotocol.io/vega/libs/crypto" 52 "code.vegaprotocol.io/vega/libs/num" 53 "code.vegaprotocol.io/vega/logging" 54 proto "code.vegaprotocol.io/vega/protos/vega" 55 vegapb "code.vegaprotocol.io/vega/protos/vega" 56 datapb "code.vegaprotocol.io/vega/protos/vega/data/v1" 57 58 "github.com/golang/mock/gomock" 59 "github.com/stretchr/testify/assert" 60 "github.com/stretchr/testify/require" 61 ) 62 63 var ( 64 MAXMOVEUP = num.DecimalFromFloat(1000) 65 MINMOVEDOWN = num.DecimalFromFloat(500) 66 ) 67 68 func peggedOrderCounterForTest(int64) {} 69 70 var defaultCollateralAssets = []types.Asset{ 71 { 72 ID: "ETH", 73 Details: &types.AssetDetails{ 74 Symbol: "ETH", 75 Quantum: num.DecimalOne(), 76 }, 77 }, 78 { 79 ID: "VOTE", 80 Details: &types.AssetDetails{ 81 Name: "VOTE", 82 Symbol: "VOTE", 83 Decimals: 5, 84 Quantum: num.DecimalOne(), 85 Source: &types.AssetDetailsBuiltinAsset{ 86 BuiltinAsset: &types.BuiltinAsset{}, 87 }, 88 }, 89 }, 90 } 91 92 var defaultPriceMonitorSettings = &types.PriceMonitoringSettings{ 93 Parameters: &types.PriceMonitoringParameters{ 94 Triggers: []*types.PriceMonitoringTrigger{ 95 { 96 Horizon: 600, 97 HorizonDec: num.MustDecimalFromString("600"), 98 Probability: num.DecimalFromFloat(0.99), 99 AuctionExtension: 120, 100 }, 101 }, 102 }, 103 } 104 105 func newTestIDGenerator() common.IDGenerator { 106 return idgeneration.New(vgcrypto.RandomHash()) 107 } 108 109 type marketW struct { 110 *future.Market 111 } 112 113 func (m *marketW) SubmitOrder( 114 ctx context.Context, 115 order *types.Order, 116 ) (*types.OrderConfirmation, error) { 117 conf, err := m.Market.SubmitOrder(ctx, order.IntoSubmission(), order.Party, vgcrypto.RandomHash()) 118 if err == nil { 119 *order = *conf.Order.Clone() 120 } 121 return conf, err 122 } 123 124 func (m *marketW) SubmitOrderWithHash( 125 ctx context.Context, 126 order *types.Order, 127 hash string, 128 ) (*types.OrderConfirmation, error) { 129 conf, err := m.Market.SubmitOrder(ctx, order.IntoSubmission(), order.Party, hash) 130 if err == nil { 131 *order = *conf.Order.Clone() 132 } 133 return conf, err 134 } 135 136 type testMarket struct { 137 t *testing.T 138 139 market *marketW 140 log *logging.Logger 141 ctrl *gomock.Controller 142 collateralEngine *collateral.Engine 143 broker *bmocks.MockBroker 144 timeService *mocks.MockTimeService 145 now time.Time 146 asset string 147 mas *monitor.AuctionState 148 eventCount uint64 149 orderEventCount uint64 150 events []events.Event 151 orderEvents []events.Event 152 mktCfg *types.Market 153 oracleEngine *spec.Engine 154 stateVar *stubs.StateVarStub 155 builtinOracle *spec.Builtin 156 157 // Options 158 Assets []types.Asset 159 } 160 161 func newTestMarket(t *testing.T, now time.Time) *testMarket { 162 t.Helper() 163 ctrl := gomock.NewController(t) 164 tm := &testMarket{ 165 t: t, 166 ctrl: ctrl, 167 log: logging.NewTestLogger(), 168 now: now, 169 } 170 171 // Setup Mocking Expectations 172 tm.broker = bmocks.NewMockBroker(ctrl) 173 tm.timeService = mocks.NewMockTimeService(ctrl) 174 tm.timeService.EXPECT().GetTimeNow().DoAndReturn( 175 func() time.Time { 176 return tm.now 177 }).AnyTimes() 178 179 // eventFn records and count events and orderEvents 180 eventFn := func(evt events.Event) { 181 if evt.Type() == events.OrderEvent { 182 tm.orderEventCount++ 183 tm.orderEvents = append(tm.orderEvents, evt) 184 } 185 tm.eventCount++ 186 tm.events = append(tm.events, evt) 187 } 188 // eventsFn is the same as eventFn above but handles []event 189 eventsFn := func(evts []events.Event) { 190 for _, evt := range evts { 191 eventFn(evt) 192 } 193 } 194 195 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes().Do(eventFn) 196 tm.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes().Do(eventsFn) 197 tm.oracleEngine = spec.NewEngine(tm.log, spec.NewDefaultConfig(), tm.timeService, tm.broker) 198 tm.builtinOracle = spec.NewBuiltin(tm.oracleEngine, tm.timeService) 199 return tm 200 } 201 202 func (tm *testMarket) Run(ctx context.Context, mktCfg types.Market) *testMarket { 203 collateralEngine := collateral.New(tm.log, collateral.NewDefaultConfig(), tm.timeService, tm.broker) 204 // create asset with same decimal places as the market asset 205 mktAssets, _ := mktCfg.GetAssets() 206 cfgAsset := NewAssetStub(mktAssets[0], mktCfg.DecimalPlaces) 207 assets := tm.Assets 208 if len(assets) == 0 { 209 assets = defaultCollateralAssets 210 } 211 for _, asset := range assets { 212 err := collateralEngine.EnableAsset(ctx, asset) 213 require.NoError(tm.t, err) 214 } 215 216 var ( 217 riskConfig = risk.NewDefaultConfig() 218 positionConfig = positions.NewDefaultConfig() 219 settlementConfig = settlement.NewDefaultConfig() 220 matchingConfig = matching.NewDefaultConfig() 221 feeConfig = fee.NewDefaultConfig() 222 liquidityConfig = liquidity.NewDefaultConfig() 223 ) 224 positionConfig.StreamPositionVerbose = true 225 226 oracleEngine := spec.NewEngine(tm.log, spec.NewDefaultConfig(), tm.timeService, tm.broker) 227 228 mas := monitor.NewAuctionState(&mktCfg, tm.now) 229 monitor.NewAuctionState(&mktCfg, tm.now) 230 231 statevarEngine := stubs.NewStateVar() 232 epochEngine := mocks.NewMockEpochEngine(tm.ctrl) 233 epochEngine.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).Times(1) 234 235 teams := mocks.NewMockTeams(tm.ctrl) 236 bc := mocks.NewMockAccountBalanceChecker(tm.ctrl) 237 broker := bmocks.NewMockBroker(tm.ctrl) 238 marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker, collateralEngine) 239 epochEngine.NotifyOnEpoch(marketActivityTracker.OnEpochEvent, marketActivityTracker.OnEpochRestore) 240 241 referralDiscountReward := fmocks.NewMockReferralDiscountRewardService(tm.ctrl) 242 volumeDiscount := fmocks.NewMockVolumeDiscountService(tm.ctrl) 243 volumeRebate := fmocks.NewMockVolumeRebateService(tm.ctrl) 244 referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("no referrer")).AnyTimes() 245 referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() 246 referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() 247 volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() 248 volumeRebate.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() 249 banking := mocks.NewMockBanking(tm.ctrl) 250 parties := mocks.NewMockParties(tm.ctrl) 251 252 mktEngine, err := future.NewMarket(ctx, 253 tm.log, riskConfig, positionConfig, settlementConfig, matchingConfig, 254 feeConfig, liquidityConfig, collateralEngine, oracleEngine, &mktCfg, tm.timeService, tm.broker, mas, statevarEngine, marketActivityTracker, cfgAsset, 255 peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, volumeRebate, banking, parties, 256 ) 257 require.NoError(tm.t, err) 258 259 settlementAssets, err := mktCfg.GetAssets() 260 require.NoError(tm.t, err) 261 262 _, _, err = collateralEngine.CreateMarketAccounts(ctx, mktEngine.GetID(), settlementAssets[0]) 263 require.NoError(tm.t, err) 264 265 tm.market = &marketW{mktEngine} 266 tm.market.UpdateRiskFactorsForTest() 267 tm.collateralEngine = collateralEngine 268 tm.asset = settlementAssets[0] 269 tm.mas = mas 270 tm.mktCfg = &mktCfg 271 tm.stateVar = statevarEngine 272 273 // Reset event counters 274 tm.eventCount = 0 275 tm.orderEventCount = 0 276 277 return tm 278 } 279 280 func (tm *testMarket) EndOpeningAuction(t *testing.T, auctionEnd time.Time, setMarkPrice bool) { 281 t.Helper() 282 var ( 283 party0 = "clearing-auction-party0" 284 party1 = "clearing-auction-party1" 285 party2 = "lpprov-party" 286 ) 287 288 // parties used for clearing opening auction 289 tm.WithAccountAndAmount(party0, 1000000). 290 WithAccountAndAmount(party1, 1000000). 291 WithAccountAndAmount(party2, 90000000000) // LP needs a lot of balance 292 293 auctionOrders := []*types.Order{ 294 // Limit Orders 295 { 296 Type: types.OrderTypeLimit, 297 Size: 5, 298 Remaining: 5, 299 Price: num.NewUint(1000), 300 Side: types.SideBuy, 301 Party: party0, 302 TimeInForce: types.OrderTimeInForceGTC, 303 }, 304 { 305 Type: types.OrderTypeLimit, 306 Size: 5, 307 Remaining: 5, 308 Price: num.NewUint(1000), 309 Side: types.SideSell, 310 Party: party1, 311 TimeInForce: types.OrderTimeInForceGTC, 312 }, 313 { 314 Type: types.OrderTypeLimit, 315 Size: 1, 316 Remaining: 1, 317 Price: num.NewUint(900), 318 Side: types.SideBuy, 319 Party: party0, 320 TimeInForce: types.OrderTimeInForceGTC, 321 }, 322 { 323 Type: types.OrderTypeLimit, 324 Size: 1, 325 Remaining: 1, 326 Price: num.NewUint(1100), 327 Side: types.SideSell, 328 Party: party1, 329 TimeInForce: types.OrderTimeInForceGTC, 330 }, 331 } 332 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 333 // submit the auctions orders & LP 334 tm.WithSubmittedOrders(t, auctionOrders...) 335 // update the time to get out of auction 336 if setMarkPrice { 337 // now set the markprice 338 mpOrders := []*types.Order{ 339 { 340 Type: types.OrderTypeLimit, 341 Size: 1, 342 Remaining: 1, 343 Price: num.NewUint(900), 344 Side: types.SideSell, 345 Party: party1, 346 TimeInForce: types.OrderTimeInForceGTC, 347 }, 348 { 349 Type: types.OrderTypeLimit, 350 Size: 1, 351 Remaining: 1, 352 Price: num.NewUint(2500), 353 Side: types.SideBuy, 354 Party: party0, 355 TimeInForce: types.OrderTimeInForceGTC, 356 }, 357 } 358 // submit the auctions orders 359 tm.WithSubmittedOrders(t, mpOrders...) 360 } 361 362 tm.now = auctionEnd 363 tm.market.OnTick(ctx, auctionEnd) 364 365 assert.Equal(t, 366 tm.market.GetMarketData().MarketTradingMode, 367 types.MarketTradingModeContinuous, 368 ) 369 } 370 371 func (tm *testMarket) EndOpeningAuction2(t *testing.T, auctionEnd time.Time, setMarkPrice bool) { 372 t.Helper() 373 var ( 374 party0 = "clearing-auction-party0" 375 party1 = "clearing-auction-party1" 376 ) 377 378 // parties used for clearing opening auction 379 tm.WithAccountAndAmount(party0, 1000000). 380 WithAccountAndAmount(party1, 1000000) 381 382 auctionOrders := []*types.Order{ 383 // Limit Orders 384 { 385 Type: types.OrderTypeLimit, 386 Size: 5, 387 Remaining: 5, 388 Price: num.NewUint(1000), 389 Side: types.SideBuy, 390 Party: party0, 391 TimeInForce: types.OrderTimeInForceGTC, 392 }, 393 { 394 Type: types.OrderTypeLimit, 395 Size: 5, 396 Remaining: 5, 397 Price: num.NewUint(1000), 398 Side: types.SideSell, 399 Party: party1, 400 TimeInForce: types.OrderTimeInForceGTC, 401 }, 402 { 403 Type: types.OrderTypeLimit, 404 Size: 1, 405 Remaining: 1, 406 Price: num.NewUint(900), 407 Side: types.SideBuy, 408 Party: party0, 409 TimeInForce: types.OrderTimeInForceGTC, 410 }, 411 { 412 Type: types.OrderTypeLimit, 413 Size: 1, 414 Remaining: 1, 415 Price: num.NewUint(1200), 416 Side: types.SideSell, 417 Party: party1, 418 TimeInForce: types.OrderTimeInForceGTC, 419 }, 420 } 421 422 // submit the auctions orders 423 tm.WithSubmittedOrders(t, auctionOrders...) 424 425 // update the time to get out of auction 426 tm.market.OnTick(context.Background(), auctionEnd) 427 428 assert.Equal(t, 429 tm.market.GetMarketData().MarketTradingMode, 430 types.MarketTradingModeContinuous, 431 ) 432 433 if setMarkPrice { 434 // now set the markprice 435 mpOrders := []*types.Order{ 436 { 437 Type: types.OrderTypeLimit, 438 Size: 1, 439 Remaining: 1, 440 Price: num.NewUint(900), 441 Side: types.SideSell, 442 Party: party1, 443 TimeInForce: types.OrderTimeInForceGTC, 444 }, 445 { 446 Type: types.OrderTypeLimit, 447 Size: 1, 448 Remaining: 1, 449 Price: num.NewUint(1200), 450 Side: types.SideBuy, 451 Party: party0, 452 TimeInForce: types.OrderTimeInForceGTC, 453 }, 454 } 455 // submit the auctions orders 456 tm.WithSubmittedOrders(t, mpOrders...) 457 } 458 } 459 460 func mustOrderFromProto(o *vegapb.Order) *types.Order { 461 order, _ := types.OrderFromProto(o) 462 return order 463 } 464 465 func (tm *testMarket) lastOrderUpdate(id string) *types.Order { 466 var order *types.Order 467 cancel := false 468 for _, e := range tm.events { 469 switch evt := e.(type) { 470 case *events.Order: 471 ord := evt.Order() 472 if ord.Id == id { 473 order = mustOrderFromProto(ord) 474 } 475 case *events.CancelledOrders: 476 cancel = true 477 } 478 } 479 if cancel { 480 order.Status = types.OrderStatusCancelled 481 } 482 return order 483 } 484 485 func (tm *testMarket) StartOpeningAuction() *testMarket { 486 err := tm.market.StartOpeningAuction(context.Background()) 487 require.NoError(tm.t, err) 488 return tm 489 } 490 491 func (tm *testMarket) WithAccountAndAmount(id string, amount uint64) *testMarket { 492 addAccountWithAmount(tm, id, amount) 493 return tm 494 } 495 496 func (tm *testMarket) PartyGeneralAccount(t *testing.T, party string) *types.Account { 497 t.Helper() 498 acc, err := tm.collateralEngine.GetPartyGeneralAccount(party, tm.asset) 499 require.NoError(t, err) 500 require.NotNil(t, acc) 501 return acc 502 } 503 504 func (tm *testMarket) PartyMarginAccount(t *testing.T, party string) *types.Account { 505 t.Helper() 506 acc, err := tm.collateralEngine.GetPartyMarginAccount(tm.market.GetID(), party, tm.asset) 507 require.NoError(t, err) 508 require.NotNil(t, acc) 509 return acc 510 } 511 512 func getTestMarket( 513 t *testing.T, 514 now time.Time, 515 pMonitorSettings *types.PriceMonitoringSettings, 516 openingAuctionDuration *types.AuctionDuration, 517 ) *testMarket { 518 t.Helper() 519 return getTestMarket2(t, now, pMonitorSettings, openingAuctionDuration, true, 0.99) 520 } 521 522 func getTestMarketWithDP( 523 t *testing.T, 524 now time.Time, 525 pMonitorSettings *types.PriceMonitoringSettings, 526 openingAuctionDuration *types.AuctionDuration, 527 decimalPlaces uint64, 528 lpRange float64, 529 ) *testMarket { 530 t.Helper() 531 return getTestMarket2WithDP(t, now, pMonitorSettings, openingAuctionDuration, true, decimalPlaces, lpRange) 532 } 533 534 func getTestMarket2( 535 t *testing.T, 536 now time.Time, 537 pMonitorSettings *types.PriceMonitoringSettings, 538 openingAuctionDuration *types.AuctionDuration, 539 startOpeningAuction bool, 540 lpRange float64, 541 ) *testMarket { 542 t.Helper() 543 return getTestMarket2WithDP(t, now, pMonitorSettings, openingAuctionDuration, startOpeningAuction, 1, lpRange) 544 } 545 546 func getTestMarket2WithDP( 547 t *testing.T, 548 now time.Time, 549 pMonitorSettings *types.PriceMonitoringSettings, 550 openingAuctionDuration *types.AuctionDuration, 551 startOpeningAuction bool, 552 decimalPlaces uint64, 553 lpRange float64, 554 ) *testMarket { 555 t.Helper() 556 ctrl := gomock.NewController(t) 557 log := logging.NewTestLogger() 558 riskConfig := risk.NewDefaultConfig() 559 positionConfig := positions.NewDefaultConfig() 560 settlementConfig := settlement.NewDefaultConfig() 561 matchingConfig := matching.NewDefaultConfig() 562 feeConfig := fee.NewDefaultConfig() 563 liquidityConfig := liquidity.NewDefaultConfig() 564 broker := bmocks.NewMockBroker(ctrl) 565 timeService := mocks.NewMockTimeService(ctrl) 566 567 tm := &testMarket{ 568 log: log, 569 ctrl: ctrl, 570 broker: broker, 571 timeService: timeService, 572 now: now, 573 } 574 575 timeService.EXPECT().GetTimeNow().DoAndReturn( 576 func() time.Time { 577 return tm.now 578 }).AnyTimes() 579 580 handleEvent := func(evt events.Event) { 581 te := evt.Type() 582 if te == events.OrderEvent { 583 tm.orderEventCount++ 584 tm.orderEvents = append(tm.orderEvents, evt) 585 } 586 tm.eventCount++ 587 tm.events = append(tm.events, evt) 588 } 589 590 // catch all expected calls 591 broker.EXPECT().SendBatch(gomock.Any()).AnyTimes().Do( 592 func(evts []events.Event) { 593 for _, evt := range evts { 594 handleEvent(evt) 595 } 596 }, 597 ) 598 599 broker.EXPECT().Send(gomock.Any()).AnyTimes().Do(handleEvent) 600 601 collateralEngine := collateral.New(log, collateral.NewDefaultConfig(), timeService, broker) 602 err := collateralEngine.EnableAsset(context.Background(), types.Asset{ 603 ID: "ETH", 604 Details: &types.AssetDetails{ 605 Symbol: "ETH", 606 Decimals: 0, // no decimals 607 Quantum: num.DecimalOne(), 608 }, 609 }) 610 require.NoError(t, err) 611 // create asset stub to match the test asset: 612 cfgAsset := NewAssetStub("ETH", 0) 613 614 oracleEngine := spec.NewEngine(log, spec.NewDefaultConfig(), timeService, broker) 615 tm.oracleEngine = oracleEngine 616 tm.builtinOracle = spec.NewBuiltin(tm.oracleEngine, tm.timeService) 617 618 // add the token asset 619 tokAsset := types.Asset{ 620 ID: "VOTE", 621 Details: &types.AssetDetails{ 622 Name: "VOTE", 623 Symbol: "VOTE", 624 Decimals: 5, 625 Quantum: num.DecimalOne(), 626 Source: &types.AssetDetailsBuiltinAsset{ 627 BuiltinAsset: &types.BuiltinAsset{}, 628 }, 629 }, 630 } 631 632 err = collateralEngine.EnableAsset(context.Background(), tokAsset) 633 if pMonitorSettings == nil { 634 pMonitorSettings = &types.PriceMonitoringSettings{ 635 Parameters: &types.PriceMonitoringParameters{ 636 Triggers: []*types.PriceMonitoringTrigger{}, 637 }, 638 } 639 } 640 require.NoError(t, err) 641 mkt := getMarketWithDP(pMonitorSettings, openingAuctionDuration, decimalPlaces, lpRange) 642 // ensure nextMTM is happening every block 643 mktCfg := &mkt 644 mktCfg.DecimalPlaces = cfgAsset.DecimalPlaces() 645 646 mas := monitor.NewAuctionState(mktCfg, now) 647 statevar := stubs.NewStateVar() 648 649 epoch := mocks.NewMockEpochEngine(ctrl) 650 epoch.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).Times(1) 651 teams := mocks.NewMockTeams(tm.ctrl) 652 bc := mocks.NewMockAccountBalanceChecker(tm.ctrl) 653 marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker, collateralEngine) 654 epoch.NotifyOnEpoch(marketActivityTracker.OnEpochEvent, marketActivityTracker.OnEpochRestore) 655 656 referralDiscountReward := fmocks.NewMockReferralDiscountRewardService(tm.ctrl) 657 volumeDiscount := fmocks.NewMockVolumeDiscountService(tm.ctrl) 658 volumeRebate := fmocks.NewMockVolumeRebateService(tm.ctrl) 659 660 referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("no referrer")).AnyTimes() 661 referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() 662 referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() 663 volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() 664 volumeRebate.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() 665 banking := mocks.NewMockBanking(ctrl) 666 parties := mocks.NewMockParties(ctrl) 667 668 mktEngine, err := future.NewMarket(context.Background(), 669 log, riskConfig, positionConfig, settlementConfig, matchingConfig, 670 feeConfig, liquidityConfig, collateralEngine, oracleEngine, mktCfg, timeService, broker, mas, statevar, marketActivityTracker, cfgAsset, 671 peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, volumeRebate, banking, parties) 672 if err != nil { 673 t.Fatalf("couldn't create a market: %v", err) 674 } 675 // ensure MTM settlements happen every block 676 mktEngine.OnMarkPriceUpdateMaximumFrequency(context.Background(), time.Duration(0)) 677 mktEngine.UpdateRiskFactorsForTest() 678 679 if startOpeningAuction { 680 d := time.Second 681 if openingAuctionDuration != nil { 682 d = time.Duration(openingAuctionDuration.Duration) * time.Second 683 } 684 mktEngine.OnMarketAuctionMinimumDurationUpdate(context.Background(), d) 685 err := mktEngine.StartOpeningAuction(context.Background()) 686 require.NoError(t, err) 687 } 688 689 settlementAssets, err := mkt.GetAssets() 690 assert.NoError(t, err) 691 692 // ignore response ids here + this cannot fail 693 _, _, err = collateralEngine.CreateMarketAccounts(context.Background(), mktEngine.GetID(), settlementAssets[0]) 694 assert.NoError(t, err) 695 696 tm.market = &marketW{mktEngine} 697 tm.collateralEngine = collateralEngine 698 tm.asset = settlementAssets[0] 699 tm.mas = mas 700 tm.mktCfg = mktCfg 701 tm.stateVar = statevar 702 703 // Reset event counters 704 tm.eventCount = 0 705 tm.orderEventCount = 0 706 707 return tm 708 } 709 710 func getMarket(pMonitorSettings *types.PriceMonitoringSettings, openingAuctionDuration *types.AuctionDuration) types.Market { 711 return getMarketWithDP(pMonitorSettings, openingAuctionDuration, 1, 0.99) 712 } 713 714 func getMarketWithDP(pMonitorSettings *types.PriceMonitoringSettings, openingAuctionDuration *types.AuctionDuration, decimalPlaces uint64, _ float64) types.Market { 715 pubKeys := []*dstypes.Signer{ 716 dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey), 717 } 718 719 mkt := types.Market{ 720 ID: vgcrypto.RandomHash(), 721 DecimalPlaces: decimalPlaces, 722 Fees: &types.Fees{ 723 Factors: &types.FeeFactors{ 724 InfrastructureFee: num.DecimalFromFloat(0.001), 725 MakerFee: num.DecimalFromFloat(0.004), 726 }, 727 LiquidityFeeSettings: &types.LiquidityFeeSettings{ 728 Method: types.LiquidityFeeMethodMarginalCost, 729 }, 730 }, 731 TradableInstrument: &types.TradableInstrument{ 732 Instrument: &types.Instrument{ 733 ID: "Crypto/ETHUSD/Futures/Dec19", 734 Code: "CRYPTO:ETHUSD/DEC19", 735 Name: "December 2019 ETH vs USD future", 736 Metadata: &types.InstrumentMetadata{ 737 Tags: []string{ 738 "asset_class:fx/crypto", 739 "product:futures", 740 }, 741 }, 742 Product: &types.InstrumentFuture{ 743 Future: &types.Future{ 744 SettlementAsset: "ETH", 745 QuoteName: "USD", 746 DataSourceSpecForSettlementData: &datasource.Spec{ 747 ID: "1", 748 Data: datasource.NewDefinition( 749 datasource.ContentTypeOracle, 750 ).SetOracleConfig( 751 &signedoracle.SpecConfiguration{ 752 Signers: pubKeys, 753 Filters: []*dstypes.SpecFilter{ 754 { 755 Key: &dstypes.SpecPropertyKey{ 756 Name: "prices.ETH.value", 757 Type: datapb.PropertyKey_TYPE_INTEGER, 758 }, 759 Conditions: []*dstypes.SpecCondition{}, 760 }, 761 }, 762 }, 763 ), 764 }, 765 DataSourceSpecForTradingTermination: &datasource.Spec{ 766 ID: "2", 767 Data: datasource.NewDefinition( 768 datasource.ContentTypeOracle, 769 ).SetOracleConfig( 770 &signedoracle.SpecConfiguration{ 771 Signers: pubKeys, 772 Filters: []*dstypes.SpecFilter{ 773 { 774 Key: &dstypes.SpecPropertyKey{ 775 Name: "trading.terminated", 776 Type: datapb.PropertyKey_TYPE_BOOLEAN, 777 }, 778 Conditions: []*dstypes.SpecCondition{}, 779 }, 780 }, 781 }, 782 ), 783 }, 784 DataSourceSpecBinding: &datasource.SpecBindingForFuture{ 785 SettlementDataProperty: "prices.ETH.value", 786 TradingTerminationProperty: "trading.terminated", 787 }, 788 }, 789 }, 790 }, 791 MarginCalculator: &types.MarginCalculator{ 792 ScalingFactors: &types.ScalingFactors{ 793 SearchLevel: num.DecimalFromFloat(1.1), 794 InitialMargin: num.DecimalFromFloat(1.2), 795 CollateralRelease: num.DecimalFromFloat(1.4), 796 }, 797 }, 798 RiskModel: &types.TradableInstrumentSimpleRiskModel{ 799 SimpleRiskModel: &types.SimpleRiskModel{ 800 Params: &types.SimpleModelParams{ 801 FactorLong: num.DecimalFromFloat(0.15), 802 FactorShort: num.DecimalFromFloat(0.25), 803 MaxMoveUp: MAXMOVEUP, 804 MinMoveDown: MINMOVEDOWN, 805 ProbabilityOfTrading: num.DecimalFromFloat(0.1), 806 }, 807 }, 808 }, 809 }, 810 OpeningAuction: openingAuctionDuration, 811 PriceMonitoringSettings: pMonitorSettings, 812 LiquidityMonitoringParameters: &types.LiquidityMonitoringParameters{ 813 TargetStakeParameters: &types.TargetStakeParameters{ 814 TimeWindow: 3600, // seconds = 1h 815 ScalingFactor: num.DecimalFromFloat(10), 816 }, 817 }, 818 LiquiditySLAParams: &types.LiquiditySLAParams{ 819 PriceRange: num.DecimalOne(), 820 CommitmentMinTimeFraction: num.DecimalFromFloat(0.5), 821 SlaCompetitionFactor: num.DecimalOne(), 822 PerformanceHysteresisEpochs: 1, 823 }, 824 LinearSlippageFactor: num.DecimalFromFloat(0.1), 825 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 826 MarkPriceConfiguration: &types.CompositePriceConfiguration{ 827 DecayWeight: num.DecimalZero(), 828 DecayPower: num.DecimalZero(), 829 CashAmount: num.UintZero(), 830 CompositePriceType: types.CompositePriceTypeByLastTrade, 831 }, 832 TickSize: num.UintOne(), 833 } 834 835 return mkt 836 } 837 838 func addAccount(t *testing.T, market *testMarket, party string) { 839 t.Helper() 840 _, err := market.collateralEngine.Deposit(context.Background(), party, market.asset, num.NewUint(1000000000)) 841 if err != nil { 842 t.Fatalf("couldn't deposit asset \"%s\" for party \"%s\": %v", market.asset, party, err) 843 } 844 } 845 846 func addAccountWithAmount(market *testMarket, party string, amnt uint64) *types.LedgerMovement { 847 r, _ := market.collateralEngine.Deposit(context.Background(), party, market.asset, num.NewUint(amnt)) 848 return r 849 } 850 851 // WithSubmittedLiquidityProvision Submits a Liquidity Provision and asserts that it was created without errors. 852 func (tm *testMarket) WithSubmittedLiquidityProvision(t *testing.T, party string, amount uint64, fee string) *testMarket { 853 t.Helper() 854 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 855 856 f, _ := num.DecimalFromString(fee) 857 lps := &types.LiquidityProvisionSubmission{ 858 MarketID: tm.market.GetID(), 859 CommitmentAmount: num.NewUint(amount), 860 Fee: f, 861 } 862 863 require.NoError(t, 864 tm.market.SubmitLiquidityProvision(ctx, lps, party, vgcrypto.RandomHash()), 865 ) 866 867 return tm 868 } 869 870 // WithSubmittedOrder returns a market with Submitted orders defined in `orders`. 871 // If one submission fails, it will make the test fail and stop. 872 func (tm *testMarket) WithSubmittedOrders(t *testing.T, orders ...*types.Order) *testMarket { 873 t.Helper() 874 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 875 for i, order := range orders { 876 order.MarketID = tm.market.GetID() 877 _, err := tm.market.SubmitOrder(ctx, order) 878 require.NoError(t, err, "Submitting Order(@index#%d): '%s' failed", i, order.String()) 879 } 880 return tm 881 } 882 883 func (tm *testMarket) EventHasBeenEmitted(t *testing.T, e events.Event) { 884 t.Helper() 885 for _, event := range tm.events { 886 if reflect.DeepEqual(e, event) { 887 return 888 } 889 } 890 t.Fatalf("Expected event: '%s', has not been emitted", e) 891 } 892 893 func TestMarketClosing(t *testing.T) { 894 pubKeys := []*dstypes.Signer{ 895 dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey), 896 } 897 898 party1 := "party1" 899 party2 := "party2" 900 lp1 := "lp1" 901 lp2 := "lp2" 902 now := time.Unix(10, 0) 903 closingAt := time.Unix(20, 0) 904 tm := getTestMarket(t, now, nil, &types.AuctionDuration{Duration: 1}) 905 defer tm.ctrl.Finish() 906 addAccount(t, tm, party1) 907 addAccount(t, tm, party2) 908 addAccount(t, tm, lp1) 909 addAccount(t, tm, lp2) 910 911 // submit liquidity with varying fee levels 912 commitment1 := num.NewUint(30000) 913 fee1 := num.DecimalFromFloat(0.01) 914 commitment2 := num.NewUint(20000) 915 fee2 := num.DecimalFromFloat(0.02) 916 lps := &types.LiquidityProvisionSubmission{ 917 MarketID: tm.market.GetID(), 918 CommitmentAmount: commitment1, 919 Fee: fee1, 920 } 921 922 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lps, lp1, vgcrypto.RandomHash())) 923 lps.Fee = fee2 924 lps.CommitmentAmount = commitment2 925 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lps, lp2, vgcrypto.RandomHash())) 926 927 // generate trades so that fees need to be distributed among LPs 928 orders := []*types.Order{ 929 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "order1", types.SideSell, lp1, 1000, 110), 930 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "order1", types.SideBuy, lp1, 1000, 90), 931 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "order1", types.SideSell, party1, 1, 100), 932 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "order2", types.SideBuy, party2, 1, 100), 933 } 934 for _, o := range orders { 935 conf, err := tm.market.SubmitOrder(context.Background(), o) 936 require.NoError(t, err) 937 require.NotNil(t, conf) 938 } 939 940 // leave opening auction 941 now = now.Add(2 * time.Second) 942 tm.now = now 943 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 944 md := tm.market.GetMarketData() 945 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 946 947 properties := map[string]string{} 948 properties["trading.terminated"] = "true" 949 err := tm.oracleEngine.BroadcastData(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), dstypes.Data{ 950 Signers: pubKeys, 951 Data: properties, 952 }) 953 require.NoError(t, err) 954 955 closingAt = closingAt.Add(time.Second) 956 tm.now = closingAt 957 closed := tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), closingAt) 958 959 // there's no settlement data yet 960 assert.False(t, closed) 961 assert.Equal(t, types.MarketStateTradingTerminated, tm.market.State()) 962 963 // let time pass still no settlement data 964 closingAt = closingAt.Add(time.Second) 965 tm.now = closingAt 966 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), closingAt) 967 assert.False(t, closed) 968 assert.Equal(t, types.MarketStateTradingTerminated, tm.market.State()) 969 970 // now update the market with different trading terminated key 971 tm.mktCfg.TradableInstrument.Instrument.GetFuture().DataSourceSpecForTradingTermination = &datasource.Spec{ 972 ID: "2", 973 Data: datasource.NewDefinition( 974 datasource.ContentTypeOracle, 975 ).SetOracleConfig(&signedoracle.SpecConfiguration{ 976 Signers: pubKeys, 977 Filters: []*dstypes.SpecFilter{ 978 { 979 Key: &dstypes.SpecPropertyKey{ 980 Name: "tradingTerminated", 981 Type: datapb.PropertyKey_TYPE_BOOLEAN, 982 }, 983 }, 984 }, 985 }), 986 } 987 tm.mktCfg.TradableInstrument.Instrument.GetFuture().DataSourceSpecBinding.TradingTerminationProperty = "tradingTerminated" 988 err = tm.market.Update(context.Background(), tm.mktCfg, tm.oracleEngine) 989 require.NoError(t, err) 990 991 // now update the market again with the *same* spec ID 992 err = tm.market.Update(context.Background(), tm.mktCfg, tm.oracleEngine) 993 require.NoError(t, err) 994 995 properties = map[string]string{} 996 properties["tradingTerminated"] = "true" 997 err = tm.oracleEngine.BroadcastData(context.Background(), dstypes.Data{ 998 Signers: pubKeys, 999 Data: properties, 1000 }) 1001 require.NoError(t, err) 1002 1003 // let the oracle update settlement data 1004 delete(properties, "tradingTerminated") 1005 properties["prices.ETH.value"] = "100" 1006 err = tm.oracleEngine.BroadcastData(context.Background(), dstypes.Data{ 1007 Signers: pubKeys, 1008 Data: properties, 1009 }) 1010 require.NoError(t, err) 1011 1012 assert.Equal(t, closingAt.UnixNano(), tm.market.Mkt().MarketTimestamps.Close) 1013 assert.Equal(t, types.MarketStateSettled, tm.market.State()) 1014 1015 closingAt = closingAt.Add(time.Second) 1016 tm.now = closingAt 1017 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), closingAt) 1018 assert.True(t, closed) 1019 assert.Equal(t, types.MarketStateSettled, tm.market.State()) 1020 1021 // call on epoch event to replicate system behaviour 1022 tm.market.OnEpochEvent(context.Background(), types.Epoch{Action: proto.EpochAction_EPOCH_ACTION_END}) 1023 } 1024 1025 func TestMarketClosingAfterUpdate(t *testing.T) { 1026 // given 1027 party1 := "party1" 1028 party2 := "party2" 1029 now := time.Unix(10, 0) 1030 closingAt := time.Unix(20, 0) 1031 tm := getTestMarket(t, now, nil, nil) 1032 defer tm.ctrl.Finish() 1033 1034 pubKeys := []*dstypes.Signer{ 1035 dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey), 1036 } 1037 1038 // setup 1039 addAccount(t, tm, party1) 1040 addAccount(t, tm, party2) 1041 1042 // then 1043 assert.Equal(t, types.MarketStateActive.String(), tm.market.State().String()) 1044 1045 // when 1046 err := tm.oracleEngine.BroadcastData(context.Background(), dstypes.Data{ 1047 Signers: pubKeys, 1048 Data: map[string]string{ 1049 "trading.terminated": "true", 1050 }, 1051 }) 1052 1053 // then 1054 require.NoError(t, err) 1055 1056 // given 1057 closingTimePlus1Sec := closingAt.Add(1 * time.Second) 1058 1059 // when 1060 tm.now = closingTimePlus1Sec 1061 closed := tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), closingTimePlus1Sec) 1062 1063 // then 1064 require.False(t, closed) 1065 assert.Equal(t, types.MarketStateTradingTerminated.String(), tm.market.State().String()) 1066 1067 // Update the market's oracle spec for settlement data. 1068 1069 // given 1070 updatedMkt := tm.mktCfg.DeepClone() 1071 updatedMkt.TradableInstrument.Instrument.GetFuture().DataSourceSpecForSettlementData.Data.UpdateFilters( 1072 []*dstypes.SpecFilter{ 1073 { 1074 Key: &dstypes.SpecPropertyKey{ 1075 Name: "prices.ETHEREUM.value", 1076 Type: datapb.PropertyKey_TYPE_INTEGER, 1077 }, 1078 }, 1079 }, 1080 ) 1081 1082 updatedMkt.TradableInstrument.Instrument.GetFuture().DataSourceSpecBinding.SettlementDataProperty = "prices.ETHEREUM.value" 1083 1084 // when 1085 err = tm.market.Update(context.Background(), updatedMkt, tm.oracleEngine) 1086 1087 // Sending an oracle data aiming at older oracle spec of the market. 1088 1089 // then 1090 require.NoError(t, err) 1091 1092 // when 1093 err = tm.oracleEngine.BroadcastData(context.Background(), dstypes.Data{ 1094 Signers: pubKeys, 1095 Data: map[string]string{ 1096 "prices.ETH.value": "10", 1097 }, 1098 }) 1099 1100 // then 1101 require.NoError(t, err) 1102 1103 // Verify the market didn't catch the oracle data since the oracle spec has 1104 // been updated. 1105 1106 // given 1107 closingTimePlus2Sec := closingAt.Add(2 * time.Second) 1108 1109 // when 1110 tm.now = closingTimePlus2Sec 1111 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), closingTimePlus2Sec) 1112 1113 // then 1114 require.False(t, closed) 1115 assert.Equal(t, types.MarketStateTradingTerminated.String(), tm.market.State().String()) 1116 1117 // Verify the market did catch the oracle data according to the oracle spec 1118 // update. 1119 1120 // when 1121 err = tm.oracleEngine.BroadcastData(context.Background(), dstypes.Data{ 1122 Signers: pubKeys, 1123 Data: map[string]string{ 1124 "prices.ETHEREUM.value": "100", 1125 }, 1126 }) 1127 1128 // then 1129 require.NoError(t, err) 1130 1131 // given 1132 closingTimePlus3Sec := closingAt.Add(2 * time.Second) 1133 1134 // when 1135 tm.now = closingTimePlus3Sec 1136 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), closingTimePlus3Sec) 1137 1138 // then 1139 require.True(t, closed) 1140 assert.Equal(t, types.MarketStateSettled.String(), tm.market.State().String()) 1141 } 1142 1143 func TestUnsubscribeTradingTerminatedOracle(t *testing.T) { 1144 // given 1145 party1 := "party1" 1146 party2 := "party2" 1147 now := time.Unix(10, 0) 1148 tm := getTestMarket(t, now, nil, nil) 1149 defer tm.ctrl.Finish() 1150 1151 // setup 1152 addAccount(t, tm, party1) 1153 addAccount(t, tm, party2) 1154 1155 // then 1156 assert.Equal(t, types.MarketStateActive.String(), tm.market.State().String()) 1157 1158 // when 1159 err := tm.oracleEngine.BroadcastData(context.Background(), dstypes.Data{ 1160 Signers: []*dstypes.Signer{ 1161 dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey), 1162 }, 1163 Data: map[string]string{ 1164 "trading.terminated": "true", 1165 }, 1166 }) 1167 1168 // then 1169 require.NoError(t, err) 1170 1171 count := tm.eventCount 1172 1173 for i := 0; i < 10; i++ { 1174 err := tm.oracleEngine.BroadcastData(context.Background(), dstypes.Data{ 1175 Signers: []*dstypes.Signer{ 1176 dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey), 1177 }, 1178 Data: map[string]string{ 1179 "trading.terminated": "true", 1180 }, 1181 }) 1182 1183 // then 1184 require.NoError(t, err) 1185 } 1186 1187 require.Equal(t, count, tm.eventCount) 1188 } 1189 1190 func TestMarketLiquidityFeeAfterUpdate(t *testing.T) { 1191 // given 1192 now := time.Unix(10, 0) 1193 tm := getTestMarket(t, now, nil, nil) 1194 defer tm.ctrl.Finish() 1195 1196 // then 1197 // We need to ensure this has been updated 1198 require.NotEqual(t, tm.market.GetLiquidityFee(), num.DecimalZero()) 1199 1200 // given 1201 previousLiqFee := tm.market.GetLiquidityFee() 1202 updatedMkt := tm.mktCfg.DeepClone() 1203 updatedMkt.TradableInstrument.Instrument.GetFuture().DataSourceSpecForSettlementData.Data.UpdateFilters( 1204 []*dstypes.SpecFilter{ 1205 { 1206 Key: &dstypes.SpecPropertyKey{ 1207 Name: "prices.ETHEREUM.value", 1208 Type: datapb.PropertyKey_TYPE_INTEGER, 1209 }, 1210 }, 1211 }, 1212 ) 1213 1214 updatedMkt.TradableInstrument.Instrument.GetFuture().DataSourceSpecBinding.SettlementDataProperty = "prices.ETHEREUM.value" 1215 1216 // when 1217 err := tm.market.Update(context.Background(), updatedMkt, tm.oracleEngine) 1218 1219 // then 1220 require.NoError(t, err) 1221 assert.Equal(t, previousLiqFee, tm.market.GetLiquidityFee()) 1222 } 1223 1224 func TestLiquidityFeeWhenTargetStakeDropsDueToFlowOfTime(t *testing.T) { 1225 party1 := "party1" 1226 party2 := "party2" 1227 lp1 := "lp1" 1228 lp2 := "lp2" 1229 maxOI := uint64(124) 1230 matchingPrice := uint64(111) 1231 now := time.Unix(10, 0) 1232 tm := getTestMarket2(t, now, nil, &types.AuctionDuration{ 1233 Duration: 1, 1234 // increase lpRange so that LP orders don't get pushed too close to MID and test can behave as expected 1235 }, true, 1) 1236 tm.market.OnMarketTargetStakeTimeWindowUpdate(5 * time.Second) 1237 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 1238 1239 addAccount(t, tm, party1) 1240 addAccount(t, tm, party2) 1241 addAccountWithAmount(tm, lp1, 100000000000) 1242 addAccountWithAmount(tm, lp2, 100000000000) 1243 1244 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Second) 1245 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, lp1, 1, 10) 1246 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 1247 require.NotNil(t, conf) 1248 require.NoError(t, err) 1249 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 1250 1251 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, lp2, 1, 100000) 1252 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 1253 require.NotNil(t, conf) 1254 require.NoError(t, err) 1255 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 1256 1257 orders := []*types.Order{ 1258 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "ord1", types.SideSell, party1, maxOI, matchingPrice), 1259 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "ord2", types.SideBuy, party2, maxOI, matchingPrice), 1260 } 1261 for _, o := range orders { 1262 conf, err := tm.market.SubmitOrder(context.Background(), o) 1263 require.NoError(t, err) 1264 require.NotNil(t, conf) 1265 } 1266 1267 // submit liquidity with varying fee levels 1268 commitment1 := num.NewUint(30000) 1269 fee1 := num.DecimalFromFloat(0.01) 1270 commitment2 := num.NewUint(20000) 1271 fee2 := num.DecimalFromFloat(0.02) 1272 lps := &types.LiquidityProvisionSubmission{ 1273 MarketID: tm.market.GetID(), 1274 CommitmentAmount: commitment1, 1275 Fee: fee1, 1276 } 1277 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lps, lp1, vgcrypto.RandomHash())) 1278 lps.Fee = fee2 1279 lps.CommitmentAmount = commitment2 1280 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lps, lp2, vgcrypto.RandomHash())) 1281 1282 // leave opening auction 1283 now = now.Add(2 * time.Second) 1284 tm.now = now 1285 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 1286 md := tm.market.GetMarketData() 1287 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 1288 require.Equal(t, maxOI, md.OpenInterest) 1289 now = now.Add(2 * time.Second) 1290 tm.now = now 1291 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 1292 // move time and decrase open interest 1293 orders = []*types.Order{ 1294 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "ord1", types.SideBuy, party1, maxOI-100, matchingPrice), 1295 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "ord2", types.SideSell, party2, maxOI-100, matchingPrice), 1296 } 1297 for _, o := range orders { 1298 conf, err := tm.market.SubmitOrder(context.Background(), o) 1299 require.NoError(t, err) 1300 require.NotNil(t, conf) 1301 } 1302 md = tm.market.GetMarketData() 1303 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 1304 targetStake1 := md.TargetStake 1305 require.Equal(t, fee2, tm.market.GetLiquidityFee()) 1306 1307 // move time beyond taret stake window (so max OI drops and hence target stake) 1308 now = now.Add(6 * time.Second) 1309 tm.now = now 1310 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 1311 1312 md = tm.market.GetMarketData() 1313 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 1314 targetStake2 := md.TargetStake 1315 1316 require.Less(t, targetStake2, targetStake1) 1317 require.Equal(t, fee1, tm.market.GetLiquidityFee()) 1318 } 1319 1320 func TestMarketNotActive(t *testing.T) { 1321 now := time.Unix(10, 0) 1322 closingAt := time.Unix(20, 0) 1323 1324 // this will create a market in Proposed Mode 1325 tm := getTestMarket2(t, now, nil, nil, false, 0.99) 1326 defer tm.ctrl.Finish() 1327 1328 require.Equal(t, types.MarketStateProposed, tm.market.State()) 1329 1330 party1 := "party1" 1331 tm.WithAccountAndAmount(party1, 1000000) 1332 1333 hash := vgcrypto.RandomHash() 1334 order := &types.Order{ 1335 ID: hash, 1336 Type: types.OrderTypeLimit, 1337 TimeInForce: types.OrderTimeInForceGTT, 1338 Status: types.OrderStatusActive, 1339 Side: types.SideBuy, 1340 Party: party1, 1341 MarketID: tm.market.GetID(), 1342 Size: 100, 1343 Price: num.NewUint(100), 1344 OriginalPrice: num.NewUint(100), 1345 Remaining: 100, 1346 CreatedAt: now.UnixNano(), 1347 ExpiresAt: closingAt.UnixNano(), 1348 Reference: "party1-buy-order", 1349 } 1350 1351 tm.events = nil 1352 cpy := *order 1353 cpy.Status = types.OrderStatusRejected 1354 cpy.Reason = types.OrderErrorMarketClosed 1355 expectedEvent := events.NewOrderEvent(context.Background(), &cpy) 1356 1357 _, err := tm.market.SubmitOrderWithHash(context.Background(), order, hash) 1358 require.Error(t, err) 1359 tm.EventHasBeenEmitted(t, expectedEvent) 1360 } 1361 1362 func TestSubmittedOrderIdIsTheDeterministicId(t *testing.T) { 1363 now := time.Unix(10, 0) 1364 closingAt := time.Unix(20, 0) 1365 tm := getTestMarket(t, now, nil, nil) 1366 defer tm.ctrl.Finish() 1367 1368 party1 := "party1" 1369 order := &types.Order{ 1370 Type: types.OrderTypeLimit, 1371 TimeInForce: types.OrderTimeInForceGTT, 1372 Status: types.OrderStatusActive, 1373 ID: "", 1374 Side: types.SideBuy, 1375 Party: party1, 1376 MarketID: tm.market.GetID(), 1377 Size: 100, 1378 Price: num.NewUint(100), 1379 OriginalPrice: num.NewUint(100), 1380 Remaining: 100, 1381 CreatedAt: now.UnixNano(), 1382 ExpiresAt: closingAt.UnixNano(), 1383 Reference: "party1-buy-order", 1384 } 1385 addAccount(t, tm, party1) 1386 1387 deterministicID := vgcrypto.RandomHash() 1388 conf, err := tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), order.Party, deterministicID) 1389 if err != nil { 1390 t.Fatalf("failed to submit order:%s", err) 1391 } 1392 1393 assert.Equal(t, deterministicID, conf.Order.ID) 1394 1395 event := tm.orderEvents[0].(*events.Order) 1396 assert.Equal(t, event.Order().Id, deterministicID) 1397 } 1398 1399 func TestSubmitOrderWithInvalidTickSize(t *testing.T) { 1400 now := time.Unix(10, 0) 1401 closingAt := time.Unix(20, 0) 1402 tm := getTestMarket(t, now, nil, nil) 1403 tm.mktCfg.TickSize = num.NewUint(1000) 1404 defer tm.ctrl.Finish() 1405 1406 party1 := "party1" 1407 order := &types.Order{ 1408 Type: types.OrderTypeLimit, 1409 TimeInForce: types.OrderTimeInForceGTT, 1410 Status: types.OrderStatusActive, 1411 ID: "", 1412 Side: types.SideBuy, 1413 Party: party1, 1414 MarketID: tm.market.GetID(), 1415 Size: 100, 1416 Price: num.NewUint(1100), 1417 Remaining: 100, 1418 CreatedAt: now.UnixNano(), 1419 ExpiresAt: closingAt.UnixNano(), 1420 Reference: "party1-buy-order", 1421 } 1422 addAccount(t, tm, party1) 1423 1424 deterministicID := vgcrypto.RandomHash() 1425 _, err := tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), order.Party, deterministicID) 1426 require.Error(t, types.ErrOrderNotInTickSize, err) 1427 1428 tm.mktCfg.TickSize = num.NewUint(100) 1429 _, err = tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), order.Party, deterministicID) 1430 require.NoError(t, err) 1431 } 1432 1433 func TestPeggingWithTickSize(t *testing.T) { 1434 now := time.Unix(10, 0) 1435 tm := getTestMarket(t, now, nil, nil) 1436 tm.mktCfg.TickSize = num.NewUint(50) 1437 defer tm.ctrl.Finish() 1438 1439 auxParty := "auxParty" 1440 auxParty2 := "auxParty2" 1441 addAccount(t, tm, auxParty) 1442 addAccount(t, tm, auxParty2) 1443 1444 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Second) 1445 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 50) 1446 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 1447 require.NotNil(t, conf) 1448 require.NoError(t, err) 1449 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 1450 1451 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 1452 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 1453 require.NotNil(t, conf) 1454 require.NoError(t, err) 1455 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 1456 auxOrders := []*types.Order{ 1457 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 1458 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 1459 } 1460 for _, o := range auxOrders { 1461 conf, err := tm.market.SubmitOrder(context.Background(), o) 1462 require.NoError(t, err) 1463 require.NotNil(t, conf) 1464 } 1465 1466 party1 := "party1" 1467 order := &types.Order{ 1468 Type: types.OrderTypeLimit, 1469 TimeInForce: types.OrderTimeInForceGTT, 1470 Status: types.OrderStatusActive, 1471 ID: "", 1472 Side: types.SideBuy, 1473 Party: party1, 1474 MarketID: tm.market.GetID(), 1475 Size: 100, 1476 Remaining: 100, 1477 CreatedAt: now.UnixNano(), 1478 ExpiresAt: math.MaxInt64, 1479 Reference: "party1-buy-order", 1480 } 1481 addAccount(t, tm, party1) 1482 // submit a pegged order pegged to the mid 1483 order.PeggedOrder = &types.PeggedOrder{ 1484 Reference: types.PeggedReferenceMid, 1485 Offset: num.NewUint(100), 1486 } 1487 // mid price is 50025 - 100 = 49,925 => 49950 bid rounded to the nearest tick size up 1488 conf, err = tm.market.SubmitOrder(context.Background(), order) 1489 require.NoError(t, err) 1490 require.Equal(t, "49950", conf.Order.OriginalPrice.String()) 1491 1492 order.Side = types.SideSell 1493 // mid price is 50025 + 100 = 50125 => 50100 ask rounded to the nearest tick size down 1494 conf, err = tm.market.SubmitOrder(context.Background(), order) 1495 require.NoError(t, err) 1496 require.Equal(t, "50100", conf.Order.OriginalPrice.String()) 1497 1498 mkt := tm.mktCfg.DeepClone() 1499 // offset is still divisible by ticksize so nothing happens 1500 mkt.TickSize = num.NewUint(50) 1501 require.Equal(t, 2, tm.market.GetPeggedOrderCount()) 1502 require.NoError(t, tm.market.Market.Update(context.Background(), mkt, tm.oracleEngine)) 1503 require.Equal(t, 2, tm.market.GetPeggedOrderCount()) 1504 1505 mkt = tm.mktCfg.DeepClone() 1506 mkt.TickSize = num.NewUint(79) 1507 // offset is not divisible by ticksize so pegged orders get cancelled 1508 require.Equal(t, 2, tm.market.GetPeggedOrderCount()) 1509 require.NoError(t, tm.market.Market.Update(context.Background(), mkt, tm.oracleEngine)) 1510 require.Equal(t, 0, tm.market.GetPeggedOrderCount()) 1511 } 1512 1513 func TestAmendOrderWithInvalidTickSize(t *testing.T) { 1514 now := time.Unix(10, 0) 1515 closingAt := time.Unix(20, 0) 1516 tm := getTestMarket(t, now, nil, nil) 1517 tm.mktCfg.TickSize = num.NewUint(100) 1518 defer tm.ctrl.Finish() 1519 1520 party1 := "party1" 1521 order := &types.Order{ 1522 Type: types.OrderTypeLimit, 1523 TimeInForce: types.OrderTimeInForceGTT, 1524 Status: types.OrderStatusActive, 1525 ID: "", 1526 Side: types.SideBuy, 1527 Party: party1, 1528 MarketID: tm.market.GetID(), 1529 Size: 100, 1530 Price: num.NewUint(100), 1531 Remaining: 100, 1532 CreatedAt: now.UnixNano(), 1533 ExpiresAt: closingAt.UnixNano(), 1534 Reference: "party1-buy-order", 1535 } 1536 addAccount(t, tm, party1) 1537 1538 deterministicID := vgcrypto.RandomHash() 1539 conf, err := tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), order.Party, deterministicID) 1540 require.NoError(t, err) 1541 1542 orderAmendment := &types.OrderAmendment{ 1543 OrderID: conf.Order.ID, 1544 MarketID: conf.Order.MarketID, 1545 Price: num.NewUint(1150), 1546 } 1547 _, err = tm.market.Market.AmendOrder(context.Background(), orderAmendment, party1, deterministicID) 1548 require.Error(t, types.ErrOrderNotInTickSize, err) 1549 1550 tm.mktCfg.TickSize = num.NewUint(50) 1551 _, err = tm.market.Market.AmendOrder(context.Background(), orderAmendment, party1, deterministicID) 1552 require.NoError(t, err) 1553 1554 // pegged order 1555 order.Price = nil 1556 order.OriginalPrice = nil 1557 // market tick is 50, lets set a peg offset of 75 1558 order.Side = types.SideBuy 1559 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 75) 1560 _, err = tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), party1, deterministicID) 1561 require.Error(t, types.ErrOrderNotInTickSize, err) 1562 1563 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 100) 1564 _, err = tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), party1, vgcrypto.RandomHash()) 1565 require.NoError(t, err) 1566 1567 order.Side = types.SideSell 1568 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 75) 1569 _, err = tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), party1, deterministicID) 1570 require.Error(t, types.ErrOrderNotInTickSize, err) 1571 1572 order.ID = crypto.RandomHash() 1573 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 100) 1574 conf, err = tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), party1, vgcrypto.RandomHash()) 1575 require.NoError(t, err) 1576 1577 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 75) 1578 orderAmendment = &types.OrderAmendment{ 1579 OrderID: conf.Order.ID, 1580 MarketID: conf.Order.MarketID, 1581 PeggedOffset: num.NewUint(75), 1582 } 1583 _, err = tm.market.Market.AmendOrder(context.Background(), orderAmendment, party1, vgcrypto.RandomHash()) 1584 require.Error(t, types.ErrOrderNotInTickSize, err) 1585 } 1586 1587 func TestMarketWithTradeClosing(t *testing.T) { 1588 party1 := "party1" 1589 party2 := "party2" 1590 now := time.Unix(10, 0) 1591 closingAt := time.Unix(20, 0) 1592 tm := getTestMarket(t, now, nil, nil) 1593 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1594 defer tm.ctrl.Finish() 1595 // add 2 parties to the party engine 1596 // this will create 2 parties, credit their account 1597 // and move some monies to the market 1598 // this will also output the closed accounts 1599 addAccount(t, tm, party1) 1600 addAccount(t, tm, party2) 1601 pubKeys := []*dstypes.Signer{ 1602 dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey), 1603 } 1604 1605 // submit orders 1606 // party1 buys 1607 // party2 sells 1608 orderBuy := &types.Order{ 1609 Type: types.OrderTypeLimit, 1610 TimeInForce: types.OrderTimeInForceGTT, 1611 Status: types.OrderStatusActive, 1612 ID: "", 1613 Side: types.SideBuy, 1614 Party: party1, 1615 MarketID: tm.market.GetID(), 1616 Size: 100, 1617 Price: num.NewUint(100), 1618 Remaining: 100, 1619 CreatedAt: now.UnixNano(), 1620 ExpiresAt: closingAt.UnixNano(), 1621 Reference: "party1-buy-order", 1622 } 1623 orderSell := &types.Order{ 1624 Type: types.OrderTypeLimit, 1625 TimeInForce: types.OrderTimeInForceGTT, 1626 Status: types.OrderStatusActive, 1627 ID: "", 1628 Side: types.SideSell, 1629 Party: party2, 1630 MarketID: tm.market.GetID(), 1631 Size: 100, 1632 Price: num.NewUint(100), 1633 Remaining: 100, 1634 CreatedAt: now.UnixNano(), 1635 ExpiresAt: closingAt.UnixNano(), 1636 Reference: "party2-sell-order", 1637 } 1638 1639 // submit orders 1640 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 1641 // tm.transferResponseStore.EXPECT().Add(gomock.Any()).AnyTimes() 1642 1643 _, err := tm.market.SubmitOrder(ctx, orderBuy) 1644 assert.Nil(t, err) 1645 if err != nil { 1646 t.Fail() 1647 } 1648 tm.now = tm.now.Add(time.Second) 1649 tm.market.OnTick(ctx, tm.now) 1650 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 1651 1652 _, err = tm.market.SubmitOrder(ctx, orderSell) 1653 assert.Nil(t, err) 1654 if err != nil { 1655 t.Fail() 1656 } 1657 tm.now = tm.now.Add(time.Second) 1658 tm.market.OnTick(ctx, tm.now) 1659 1660 // update collateral time first, normally done by execution engine 1661 futureTime := closingAt.Add(1 * time.Second) 1662 properties := map[string]string{} 1663 properties["trading.terminated"] = "true" 1664 err = tm.oracleEngine.BroadcastData(ctx, dstypes.Data{ 1665 Signers: pubKeys, 1666 Data: properties, 1667 }) 1668 require.NoError(t, err) 1669 1670 properties = map[string]string{} 1671 properties["prices.ETH.value"] = "100" 1672 err = tm.oracleEngine.BroadcastData(ctx, dstypes.Data{ 1673 Signers: pubKeys, 1674 Data: properties, 1675 }) 1676 require.NoError(t, err) 1677 1678 tm.now = futureTime 1679 closed := tm.market.OnTick(ctx, futureTime) 1680 assert.True(t, closed) 1681 } 1682 1683 func TestUpdateMarketWithOracleSpecEarlyTermination(t *testing.T) { 1684 party1 := "party1" 1685 party2 := "party2" 1686 now := time.Unix(10, 0) 1687 closingAt := time.Unix(20, 0) 1688 tm := getTestMarket(t, now, nil, nil) 1689 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1690 defer tm.ctrl.Finish() 1691 // add 2 parties to the party engine 1692 // this will create 2 parties, credit their account 1693 // and move some monies to the market 1694 // this will also output the closed accounts 1695 addAccount(t, tm, party1) 1696 addAccount(t, tm, party2) 1697 1698 // submit orders 1699 // party1 buys 1700 // party2 sells 1701 orderBuy := &types.Order{ 1702 Type: types.OrderTypeLimit, 1703 TimeInForce: types.OrderTimeInForceGTT, 1704 Status: types.OrderStatusActive, 1705 ID: "", 1706 Side: types.SideBuy, 1707 Party: party1, 1708 MarketID: tm.market.GetID(), 1709 Size: 100, 1710 Price: num.NewUint(100), 1711 Remaining: 100, 1712 CreatedAt: now.UnixNano(), 1713 ExpiresAt: closingAt.UnixNano(), 1714 Reference: "party1-buy-order", 1715 } 1716 orderSell := &types.Order{ 1717 Type: types.OrderTypeLimit, 1718 TimeInForce: types.OrderTimeInForceGTT, 1719 Status: types.OrderStatusActive, 1720 ID: "", 1721 Side: types.SideSell, 1722 Party: party2, 1723 MarketID: tm.market.GetID(), 1724 Size: 100, 1725 Price: num.NewUint(100), 1726 Remaining: 100, 1727 CreatedAt: now.UnixNano(), 1728 ExpiresAt: closingAt.UnixNano(), 1729 Reference: "party2-sell-order", 1730 } 1731 1732 // submit orders 1733 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 1734 // tm.transferResponseStore.EXPECT().Add(gomock.Any()).AnyTimes() 1735 1736 _, err := tm.market.SubmitOrder(ctx, orderBuy) 1737 assert.Nil(t, err) 1738 if err != nil { 1739 t.Fail() 1740 } 1741 tm.now = tm.now.Add(time.Second) 1742 tm.market.OnTick(ctx, tm.now) 1743 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 1744 1745 _, err = tm.market.SubmitOrder(ctx, orderSell) 1746 assert.Nil(t, err) 1747 if err != nil { 1748 t.Fail() 1749 } 1750 tm.now = tm.now.Add(time.Second) 1751 tm.market.OnTick(ctx, tm.now) 1752 1753 // now update the market 1754 updatedMkt := tm.mktCfg.DeepClone() 1755 1756 updatedMkt.TradableInstrument.Instrument.GetFuture().DataSourceSpecForTradingTermination.Data.UpdateFilters( 1757 []*dstypes.SpecFilter{ 1758 { 1759 Key: &dstypes.SpecPropertyKey{ 1760 Name: spec.BuiltinTimestamp, 1761 Type: datapb.PropertyKey_TYPE_TIMESTAMP, 1762 }, 1763 Conditions: []*dstypes.SpecCondition{ 1764 { 1765 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 1766 Value: "0", 1767 }, 1768 }, 1769 }, 1770 }, 1771 ) 1772 1773 updatedMkt.TradableInstrument.Instrument.GetFuture().DataSourceSpecBinding.TradingTerminationProperty = spec.BuiltinTimestamp 1774 1775 err = tm.market.Update(context.Background(), updatedMkt, tm.oracleEngine) 1776 require.NoError(t, err) 1777 tm.builtinOracle.OnTick(ctx, tm.now) 1778 tm.market.OnTick(ctx, tm.now) 1779 require.Equal(t, types.MarketStateTradingTerminated, tm.market.State()) 1780 1781 pubKeys := []*dstypes.Signer{ 1782 dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey), 1783 } 1784 properties := map[string]string{} 1785 properties["prices.ETH.value"] = "100" 1786 err = tm.oracleEngine.BroadcastData(ctx, dstypes.Data{ 1787 Signers: pubKeys, 1788 Data: properties, 1789 }) 1790 require.NoError(t, err) 1791 1792 tm.now = tm.now.Add(time.Second) 1793 closed := tm.market.OnTick(ctx, tm.now) 1794 assert.True(t, closed) 1795 } 1796 1797 func Test6056(t *testing.T) { 1798 party1 := "party1" 1799 party2 := "party2" 1800 now := time.Unix(10, 0) 1801 closingAt := time.Unix(20, 0) 1802 tm := getTestMarket(t, now, nil, nil) 1803 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1804 defer tm.ctrl.Finish() 1805 // add 2 parties to the party engine 1806 // this will create 2 parties, credit their account 1807 // and move some monies to the market 1808 // this will also output the closed accounts 1809 addAccount(t, tm, party1) 1810 addAccount(t, tm, party2) 1811 1812 // submit orders 1813 // party1 buys 1814 // party2 sells 1815 orderBuy := &types.Order{ 1816 Type: types.OrderTypeLimit, 1817 TimeInForce: types.OrderTimeInForceGTT, 1818 Status: types.OrderStatusActive, 1819 ID: "", 1820 Side: types.SideBuy, 1821 Party: party1, 1822 MarketID: tm.market.GetID(), 1823 Size: 100, 1824 Price: num.NewUint(100), 1825 Remaining: 100, 1826 CreatedAt: now.UnixNano(), 1827 ExpiresAt: closingAt.UnixNano(), 1828 Reference: "party1-buy-order", 1829 } 1830 orderSell := &types.Order{ 1831 Type: types.OrderTypeLimit, 1832 TimeInForce: types.OrderTimeInForceGTT, 1833 Status: types.OrderStatusActive, 1834 ID: "", 1835 Side: types.SideSell, 1836 Party: party2, 1837 MarketID: tm.market.GetID(), 1838 Size: 100, 1839 Price: num.NewUint(100), 1840 Remaining: 100, 1841 CreatedAt: now.UnixNano(), 1842 ExpiresAt: closingAt.UnixNano(), 1843 Reference: "party2-sell-order", 1844 } 1845 1846 // submit orders 1847 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 1848 // tm.transferResponseStore.EXPECT().Add(gomock.Any()).AnyTimes() 1849 1850 _, err := tm.market.SubmitOrder(ctx, orderBuy) 1851 assert.Nil(t, err) 1852 if err != nil { 1853 t.Fail() 1854 } 1855 tm.now = tm.now.Add(time.Second) 1856 tm.market.OnTick(ctx, tm.now) 1857 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 1858 1859 _, err = tm.market.SubmitOrder(ctx, orderSell) 1860 assert.Nil(t, err) 1861 if err != nil { 1862 t.Fail() 1863 } 1864 tm.now = tm.now.Add(time.Second) 1865 tm.market.OnTick(ctx, tm.now) 1866 1867 // now update the market 1868 updatedMkt := tm.mktCfg.DeepClone() 1869 1870 updatedMkt.TradableInstrument.Instrument.GetFuture().DataSourceSpecForSettlementData.Data.UpdateFilters( 1871 []*dstypes.SpecFilter{ 1872 { 1873 Key: &dstypes.SpecPropertyKey{ 1874 Name: "prices.ETH.value", 1875 Type: datapb.PropertyKey_TYPE_INTEGER, 1876 }, 1877 Conditions: []*dstypes.SpecCondition{ 1878 { 1879 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 1880 Value: "1", 1881 }, 1882 }, 1883 }, 1884 }, 1885 ) 1886 1887 updatedMkt.TradableInstrument.Instrument.GetFuture().DataSourceSpecForTradingTermination.Data.UpdateFilters( 1888 []*dstypes.SpecFilter{ 1889 { 1890 Key: &dstypes.SpecPropertyKey{ 1891 Name: "trading.terminated", 1892 Type: datapb.PropertyKey_TYPE_BOOLEAN, 1893 }, 1894 Conditions: []*dstypes.SpecCondition{ 1895 { 1896 Operator: datapb.Condition_OPERATOR_EQUALS, 1897 Value: "false", 1898 }, 1899 }, 1900 }, 1901 }, 1902 ) 1903 1904 pubKeys := []*dstypes.Signer{ 1905 dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey), 1906 } 1907 err = tm.market.Update(context.Background(), updatedMkt, tm.oracleEngine) 1908 require.NoError(t, err) 1909 1910 properties := map[string]string{} 1911 properties["trading.terminated"] = "false" 1912 err = tm.oracleEngine.BroadcastData(ctx, dstypes.Data{ 1913 Signers: pubKeys, 1914 Data: properties, 1915 }) 1916 require.NoError(t, err) 1917 tm.market.OnTick(ctx, tm.now) 1918 require.Equal(t, types.MarketStateTradingTerminated, tm.market.State()) 1919 1920 properties = map[string]string{} 1921 properties["prices.ETH.value"] = "100" 1922 err = tm.oracleEngine.BroadcastData(ctx, dstypes.Data{ 1923 Signers: pubKeys, 1924 Data: properties, 1925 }) 1926 require.NoError(t, err) 1927 1928 tm.now = tm.now.Add(time.Second) 1929 closed := tm.market.OnTick(ctx, tm.now) 1930 assert.True(t, closed) 1931 } 1932 1933 func TestOraclesWithMultipleFilterNameFails(t *testing.T) { 1934 party1 := "party1" 1935 party2 := "party2" 1936 now := time.Unix(10, 0) 1937 closingAt := time.Unix(20, 0) 1938 tm := getTestMarket(t, now, nil, nil) 1939 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1940 defer tm.ctrl.Finish() 1941 // add 2 parties to the party engine 1942 // this will create 2 parties, credit their account 1943 // and move some monies to the market 1944 // this will also output the closed accounts 1945 addAccount(t, tm, party1) 1946 addAccount(t, tm, party2) 1947 1948 // submit orders 1949 // party1 buys 1950 // party2 sells 1951 orderBuy := &types.Order{ 1952 Type: types.OrderTypeLimit, 1953 TimeInForce: types.OrderTimeInForceGTT, 1954 Status: types.OrderStatusActive, 1955 ID: "", 1956 Side: types.SideBuy, 1957 Party: party1, 1958 MarketID: tm.market.GetID(), 1959 Size: 100, 1960 Price: num.NewUint(100), 1961 Remaining: 100, 1962 CreatedAt: now.UnixNano(), 1963 ExpiresAt: closingAt.UnixNano(), 1964 Reference: "party1-buy-order", 1965 } 1966 orderSell := &types.Order{ 1967 Type: types.OrderTypeLimit, 1968 TimeInForce: types.OrderTimeInForceGTT, 1969 Status: types.OrderStatusActive, 1970 ID: "", 1971 Side: types.SideSell, 1972 Party: party2, 1973 MarketID: tm.market.GetID(), 1974 Size: 100, 1975 Price: num.NewUint(100), 1976 Remaining: 100, 1977 CreatedAt: now.UnixNano(), 1978 ExpiresAt: closingAt.UnixNano(), 1979 Reference: "party2-sell-order", 1980 } 1981 1982 // submit orders 1983 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 1984 1985 _, err := tm.market.SubmitOrder(ctx, orderBuy) 1986 assert.Nil(t, err) 1987 if err != nil { 1988 t.Fail() 1989 } 1990 tm.now = tm.now.Add(time.Second) 1991 tm.market.OnTick(ctx, tm.now) 1992 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 1993 1994 _, err = tm.market.SubmitOrder(ctx, orderSell) 1995 assert.Nil(t, err) 1996 if err != nil { 1997 t.Fail() 1998 } 1999 tm.now = tm.now.Add(time.Second) 2000 tm.market.OnTick(ctx, tm.now) 2001 2002 // now update the market 2003 updatedMkt := tm.mktCfg.DeepClone() 2004 2005 f1 := uint64(12) 2006 f2 := uint64(21) 2007 err = updatedMkt.TradableInstrument.Instrument.GetFuture().DataSourceSpecForSettlementData.Data.UpdateFilters( 2008 []*dstypes.SpecFilter{ 2009 { 2010 Key: &dstypes.SpecPropertyKey{ 2011 Name: "prices.ETH.value", 2012 Type: datapb.PropertyKey_TYPE_INTEGER, 2013 NumberDecimalPlaces: &f1, 2014 }, 2015 Conditions: []*dstypes.SpecCondition{ 2016 { 2017 Operator: datapb.Condition_OPERATOR_GREATER_THAN, 2018 Value: "717098987000000000000000000000000000000", 2019 }, 2020 }, 2021 }, 2022 { 2023 Key: &dstypes.SpecPropertyKey{ 2024 Name: "prices.ETH.value", 2025 Type: datapb.PropertyKey_TYPE_INTEGER, 2026 NumberDecimalPlaces: &f2, 2027 }, 2028 Conditions: []*dstypes.SpecCondition{ 2029 { 2030 Operator: datapb.Condition_OPERATOR_GREATER_THAN, 2031 Value: "957586060000000000000000000000000000000000000000", 2032 }, 2033 }, 2034 }, 2035 }, 2036 ) 2037 2038 assert.ErrorIs(t, dserrors.ErrDataSourceSpecHasMultipleSameKeyNamesInFilterList, err) 2039 2040 updatedMkt.TradableInstrument.Instrument.GetFuture().DataSourceSpecForTradingTermination.Data.UpdateFilters( 2041 []*dstypes.SpecFilter{ 2042 { 2043 Key: &dstypes.SpecPropertyKey{ 2044 Name: "trading.terminated", 2045 Type: datapb.PropertyKey_TYPE_BOOLEAN, 2046 }, 2047 Conditions: []*dstypes.SpecCondition{ 2048 { 2049 Operator: datapb.Condition_OPERATOR_EQUALS, 2050 Value: "false", 2051 }, 2052 }, 2053 }, 2054 }, 2055 ) 2056 2057 pubKeys := []*dstypes.Signer{ 2058 dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey), 2059 } 2060 err = tm.market.Update(context.Background(), updatedMkt, tm.oracleEngine) 2061 require.NoError(t, err) 2062 2063 properties := map[string]string{} 2064 properties["trading.terminated"] = "false" 2065 err = tm.oracleEngine.BroadcastData(ctx, dstypes.Data{ 2066 Signers: pubKeys, 2067 Data: properties, 2068 }) 2069 require.NoError(t, err) 2070 tm.market.OnTick(ctx, tm.now) 2071 require.Equal(t, types.MarketStateTradingTerminated, tm.market.State()) 2072 2073 properties = map[string]string{} 2074 properties["prices.ETH.value"] = "100" 2075 err = tm.oracleEngine.BroadcastData(ctx, dstypes.Data{ 2076 Signers: pubKeys, 2077 Data: properties, 2078 }) 2079 require.NoError(t, err) 2080 2081 tm.now = tm.now.Add(time.Second) 2082 closed := tm.market.OnTick(ctx, tm.now) 2083 2084 // The market should be closed, because it was never updated 2085 assert.True(t, closed) 2086 } 2087 2088 func TestMarketGetMarginOnNewOrderEmptyBook(t *testing.T) { 2089 party1 := "party1" 2090 now := time.Unix(10, 0) 2091 closingAt := time.Unix(10000000000, 0) 2092 tm := getTestMarket(t, now, nil, nil) 2093 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2094 defer tm.ctrl.Finish() 2095 // add 2 parties to the party engine 2096 // this will create 2 parties, credit their account 2097 // and move some monies to the market 2098 addAccount(t, tm, party1) 2099 2100 // submit orders 2101 // party1 buys 2102 // party2 sells 2103 orderBuy := &types.Order{ 2104 Type: types.OrderTypeLimit, 2105 TimeInForce: types.OrderTimeInForceGTT, 2106 Status: types.OrderStatusActive, 2107 ID: "", 2108 Side: types.SideBuy, 2109 Party: party1, 2110 MarketID: tm.market.GetID(), 2111 Size: 100, 2112 Price: num.NewUint(100), 2113 Remaining: 100, 2114 CreatedAt: now.UnixNano(), 2115 ExpiresAt: closingAt.UnixNano(), 2116 Reference: "party1-buy-order", 2117 } 2118 2119 // submit orders 2120 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 2121 // tm.transferResponseStore.EXPECT().Add(gomock.Any()).AnyTimes() 2122 2123 _, err := tm.market.SubmitOrder(context.Background(), orderBuy) 2124 assert.Nil(t, err) 2125 if err != nil { 2126 t.Fail() 2127 } 2128 tm.now = tm.now.Add(time.Second) 2129 tm.market.OnTick(ctx, tm.now) 2130 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 2131 } 2132 2133 func TestMarketGetMarginOnFailNoFund(t *testing.T) { 2134 party1, party2, party3 := "party1", "party2", "party3" 2135 now := time.Unix(10, 0) 2136 closingAt := time.Unix(10000000000, 0) 2137 tm := getTestMarket2(t, now, nil, &types.AuctionDuration{ 2138 Duration: 1, 2139 // increase lpRange so that LP orders don't get pushed too close to MID and test can behave as expected 2140 }, true, 1) 2141 defer tm.ctrl.Finish() 2142 // add 2 parties to the party engine 2143 // this will create 2 parties, credit their account 2144 // and move some monies to the market 2145 addAccountWithAmount(tm, party1, 0) 2146 addAccountWithAmount(tm, party2, 1000000) 2147 addAccountWithAmount(tm, party3, 1000000) 2148 addAccountWithAmount(tm, "lpprov", 100000000) 2149 2150 auxParty := "auxParty" 2151 auxParty2 := "auxParty2" 2152 addAccount(t, tm, auxParty) 2153 addAccount(t, tm, auxParty2) 2154 2155 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Second) 2156 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 2157 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 2158 require.NotNil(t, conf) 2159 require.NoError(t, err) 2160 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 2161 2162 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 2163 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 2164 require.NotNil(t, conf) 2165 require.NoError(t, err) 2166 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 2167 auxOrders := []*types.Order{ 2168 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 2169 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 2170 } 2171 for _, o := range auxOrders { 2172 conf, err := tm.market.SubmitOrder(context.Background(), o) 2173 require.NoError(t, err) 2174 require.NotNil(t, conf) 2175 } 2176 lp := &types.LiquidityProvisionSubmission{ 2177 MarketID: tm.market.GetID(), 2178 CommitmentAmount: num.NewUint(500), 2179 Fee: num.DecimalFromFloat(0.01), 2180 } 2181 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2182 // leave opening auction 2183 now = now.Add(time.Second * 2) 2184 tm.now = now 2185 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 2186 2187 order1 := &types.Order{ 2188 Status: types.OrderStatusActive, 2189 Type: types.OrderTypeLimit, 2190 TimeInForce: types.OrderTimeInForceGTC, 2191 ID: "someid12", 2192 Side: types.SideBuy, 2193 Party: party2, 2194 MarketID: tm.market.GetID(), 2195 Size: 100, 2196 Price: num.NewUint(100), 2197 Remaining: 100, 2198 CreatedAt: now.UnixNano(), 2199 Reference: "party2-buy-order", 2200 } 2201 order2 := &types.Order{ 2202 Status: types.OrderStatusActive, 2203 Type: types.OrderTypeLimit, 2204 TimeInForce: types.OrderTimeInForceGTC, 2205 ID: "someid123", 2206 Side: types.SideSell, 2207 Party: party3, 2208 MarketID: tm.market.GetID(), 2209 Size: 100, 2210 Price: num.NewUint(100), 2211 Remaining: 100, 2212 CreatedAt: now.UnixNano(), 2213 Reference: "party3-buy-order", 2214 } 2215 _, err = tm.market.SubmitOrder(context.TODO(), order1) 2216 assert.NoError(t, err) 2217 confirmation, err := tm.market.SubmitOrder(context.TODO(), order2) 2218 assert.NoError(t, err) 2219 assert.Equal(t, 1, len(confirmation.Trades)) 2220 2221 // submit orders 2222 // party1 buys 2223 // party2 sells 2224 orderBuy := &types.Order{ 2225 Type: types.OrderTypeLimit, 2226 TimeInForce: types.OrderTimeInForceGTT, 2227 Status: types.OrderStatusActive, 2228 ID: "", 2229 Side: types.SideBuy, 2230 Party: party1, 2231 MarketID: tm.market.GetID(), 2232 Size: 100, 2233 Price: num.NewUint(100), 2234 Remaining: 100, 2235 CreatedAt: now.UnixNano(), 2236 ExpiresAt: closingAt.UnixNano(), 2237 Reference: "party1-buy-order", 2238 } 2239 2240 // submit orders 2241 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 2242 // tm.transferResponseStore.EXPECT().Add(gomock.Any()).AnyTimes() 2243 2244 _, err = tm.market.SubmitOrder(context.Background(), orderBuy) 2245 assert.NotNil(t, err) 2246 assert.EqualError(t, err, "margin check failed") 2247 } 2248 2249 func TestMarketGetMarginOnAmendOrderCancelReplace(t *testing.T) { 2250 party1 := "party1" 2251 now := time.Unix(100000, 0) 2252 closingAt := time.Unix(1000000, 0) 2253 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2254 tm := getTestMarket(t, now, nil, nil) 2255 defer tm.ctrl.Finish() 2256 2257 addAccount(t, tm, party1) 2258 2259 // submit orders 2260 // party1 buys 2261 // party2 sells 2262 orderBuy := &types.Order{ 2263 Type: types.OrderTypeLimit, 2264 TimeInForce: types.OrderTimeInForceGTT, 2265 Status: types.OrderStatusActive, 2266 ID: "someid", 2267 Side: types.SideBuy, 2268 Party: party1, 2269 MarketID: tm.market.GetID(), 2270 Size: 100, 2271 Price: num.NewUint(100), 2272 Remaining: 100, 2273 CreatedAt: now.UnixNano(), 2274 ExpiresAt: closingAt.UnixNano(), 2275 Reference: "party1-buy-order", 2276 Version: common.InitialOrderVersion, 2277 } 2278 2279 // submit orders 2280 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 2281 // tm.transferResponseStore.EXPECT().Add(gomock.Any()).AnyTimes() 2282 2283 _, err := tm.market.SubmitOrder(context.Background(), orderBuy) 2284 assert.Nil(t, err) 2285 if err != nil { 2286 t.Fail() 2287 } 2288 tm.now = tm.now.Add(time.Second) 2289 tm.market.OnTick(ctx, tm.now) 2290 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 2291 t.Log("amending order now") 2292 2293 // now try to amend and make sure monies are updated 2294 amendedOrder := &types.OrderAmendment{ 2295 OrderID: orderBuy.ID, 2296 Price: num.NewUint(200), 2297 SizeDelta: -50, 2298 TimeInForce: types.OrderTimeInForceGTT, 2299 ExpiresAt: &orderBuy.ExpiresAt, 2300 } 2301 2302 _, err = tm.market.AmendOrder(context.Background(), amendedOrder, party1, vgcrypto.RandomHash()) 2303 if !assert.Nil(t, err) { 2304 t.Fatalf("Error: %v", err) 2305 } 2306 tm.now = tm.now.Add(time.Second) 2307 tm.market.OnTick(ctx, tm.now) 2308 } 2309 2310 func TestTriggerByPriceNoTradesInAuction(t *testing.T) { 2311 party1 := "party1" 2312 party2 := "party2" 2313 auxParty := "auxParty" 2314 auxParty2 := "auxParty2" 2315 now := time.Unix(10, 0) 2316 closingAt := time.Unix(10000000000, 0) 2317 auctionExtensionSeconds := int64(45) 2318 openEnd := now.Add(time.Duration(auctionExtensionSeconds)*time.Second + time.Second) 2319 auctionEndTime := openEnd.Add(time.Duration(auctionExtensionSeconds) * time.Second) 2320 afterAuction := auctionEndTime.Add(time.Nanosecond) 2321 pMonitorSettings := &types.PriceMonitoringSettings{ 2322 Parameters: &types.PriceMonitoringParameters{ 2323 Triggers: []*types.PriceMonitoringTrigger{ 2324 { 2325 Horizon: 60, 2326 HorizonDec: num.DecimalFromFloat(60), 2327 Probability: num.DecimalFromFloat(0.95), 2328 AuctionExtension: auctionExtensionSeconds, 2329 }, 2330 }, 2331 }, 2332 } 2333 initialPrice := uint64(600) 2334 mmu, _ := num.UintFromDecimal(MAXMOVEUP) 2335 auctionTriggeringPrice := initialPrice + 1 + mmu.Uint64() 2336 tm := getTestMarket2(t, now, pMonitorSettings, &types.AuctionDuration{ 2337 Duration: 1, 2338 // increase lpRange so that LP orders don't get pushed too close to MID and test can behave as expected 2339 }, true, 1) 2340 2341 addAccount(t, tm, party1) 2342 addAccount(t, tm, party2) 2343 addAccount(t, tm, auxParty) 2344 addAccount(t, tm, auxParty2) 2345 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 2346 addAccountWithAmount(tm, "lpprov", 100000) 2347 2348 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Duration(auctionExtensionSeconds)*time.Second) 2349 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 2350 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 2351 require.NotNil(t, conf) 2352 require.NoError(t, err) 2353 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 2354 2355 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100*initialPrice) 2356 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 2357 require.NotNil(t, conf) 2358 require.NoError(t, err) 2359 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 2360 2361 auxOrders := []*types.Order{ 2362 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, initialPrice), 2363 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, initialPrice), 2364 } 2365 for _, o := range auxOrders { 2366 conf, err := tm.market.SubmitOrder(context.Background(), o) 2367 require.NoError(t, err) 2368 require.NotNil(t, conf) 2369 } 2370 lp := &types.LiquidityProvisionSubmission{ 2371 MarketID: tm.market.GetID(), 2372 CommitmentAmount: num.NewUint(5000), 2373 Fee: num.DecimalFromFloat(0.01), 2374 } 2375 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2376 // leave opening auction by moving time 2377 tm.now = openEnd 2378 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), openEnd) 2379 now = openEnd 2380 2381 orderBuy1 := &types.Order{ 2382 Type: types.OrderTypeLimit, 2383 TimeInForce: types.OrderTimeInForceGTT, 2384 Status: types.OrderStatusActive, 2385 ID: "someid1", 2386 Side: types.SideBuy, 2387 Party: party1, 2388 MarketID: tm.market.GetID(), 2389 Size: 100, 2390 Price: num.NewUint(initialPrice), 2391 Remaining: 100, 2392 CreatedAt: now.UnixNano(), 2393 ExpiresAt: closingAt.UnixNano(), 2394 Reference: "party1-buy-order-1", 2395 } 2396 confirmationBuy, err := tm.market.SubmitOrder(context.Background(), orderBuy1) 2397 assert.NotNil(t, confirmationBuy) 2398 assert.NoError(t, err) 2399 2400 orderSell1 := &types.Order{ 2401 Type: types.OrderTypeLimit, 2402 TimeInForce: types.OrderTimeInForceFOK, 2403 Status: types.OrderStatusActive, 2404 ID: "someid2", 2405 Side: types.SideSell, 2406 Party: party2, 2407 MarketID: tm.market.GetID(), 2408 Size: 100, 2409 Price: num.NewUint(initialPrice), 2410 Remaining: 100, 2411 CreatedAt: now.UnixNano(), 2412 Reference: "party2-sell-order-1", 2413 } 2414 confirmationSell, err := tm.market.SubmitOrder(context.Background(), orderSell1) 2415 require.NotNil(t, confirmationSell) 2416 require.NoError(t, err) 2417 2418 require.Equal(t, 1, len(confirmationSell.Trades)) 2419 2420 auctionEnd := tm.market.GetMarketData().AuctionEnd 2421 require.Equal(t, int64(0), auctionEnd) // Not in auction 2422 2423 orderBuy2 := &types.Order{ 2424 Type: types.OrderTypeLimit, 2425 TimeInForce: types.OrderTimeInForceGTT, 2426 Status: types.OrderStatusActive, 2427 ID: "someid3", 2428 Side: types.SideBuy, 2429 Party: party1, 2430 MarketID: tm.market.GetID(), 2431 Size: 100, 2432 Price: num.NewUint(auctionTriggeringPrice), 2433 Remaining: 100, 2434 CreatedAt: now.UnixNano(), 2435 ExpiresAt: closingAt.UnixNano(), 2436 Reference: "party1-buy-order-2", 2437 } 2438 confirmationBuy, err = tm.market.SubmitOrder(context.Background(), orderBuy2) 2439 assert.NotNil(t, confirmationBuy) 2440 assert.NoError(t, err) 2441 2442 orderSell2 := &types.Order{ 2443 Type: types.OrderTypeLimit, 2444 TimeInForce: types.OrderTimeInForceGTC, 2445 Status: types.OrderStatusActive, 2446 ID: "someid4", 2447 Side: types.SideSell, 2448 Party: party2, 2449 MarketID: tm.market.GetID(), 2450 Size: 100, 2451 Price: num.NewUint(auctionTriggeringPrice), 2452 Remaining: 100, 2453 CreatedAt: now.UnixNano(), 2454 Reference: "party2-sell-order-2", 2455 } 2456 confirmationSell, err = tm.market.SubmitOrder(context.Background(), orderSell2) 2457 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 2458 require.NotNil(t, confirmationSell) 2459 require.NoError(t, err) 2460 require.Equal(t, types.MarketStateSuspended, tm.market.State()) // enter auction 2461 2462 require.Empty(t, confirmationSell.Trades) 2463 2464 auctionEnd = tm.market.GetMarketData().AuctionEnd 2465 require.Equal(t, auctionEndTime.UnixNano(), auctionEnd) // In auction 2466 2467 tm.now = auctionEndTime 2468 closed := tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), auctionEndTime) 2469 assert.False(t, closed) 2470 2471 tm.now = afterAuction 2472 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), afterAuction) 2473 require.Equal(t, types.MarketStateActive, tm.market.State()) 2474 assert.False(t, closed) 2475 } 2476 2477 func TestTriggerByPriceAuctionPriceInBounds(t *testing.T) { 2478 party1 := "party1" 2479 party2 := "party2" 2480 auxParty := "auxParty" 2481 auxParty2 := "auxParty2" 2482 now := time.Unix(10, 0) 2483 closingAt := time.Unix(10000000000, 0) 2484 auctionExtensionSeconds := int64(45) 2485 openEnd := now.Add(time.Duration(auctionExtensionSeconds)*time.Second + time.Second) 2486 auctionEndTime := openEnd.Add(time.Duration(auctionExtensionSeconds) * time.Second) 2487 afterAuction := auctionEndTime.Add(time.Nanosecond) 2488 pMonitorSettings := &types.PriceMonitoringSettings{ 2489 Parameters: &types.PriceMonitoringParameters{ 2490 Triggers: []*types.PriceMonitoringTrigger{ 2491 { 2492 Horizon: 60, 2493 HorizonDec: num.DecimalFromFloat(60), 2494 Probability: num.DecimalFromFloat(0.95), 2495 AuctionExtension: auctionExtensionSeconds, 2496 }, 2497 }, 2498 }, 2499 } 2500 initialPrice := uint64(600) 2501 deltaD := MAXMOVEUP 2502 delta, _ := num.UintFromDecimal(deltaD.Add(MINMOVEDOWN).Div(num.DecimalFromFloat(2))) 2503 mmu, _ := num.UintFromDecimal(MAXMOVEUP) 2504 validPrice := initialPrice + delta.Uint64() 2505 auctionTriggeringPrice := initialPrice + mmu.Uint64() + 1 2506 // let's not start this in opening auction, it complicates the matter 2507 tm := getTestMarket2(t, now, pMonitorSettings, &types.AuctionDuration{ 2508 Duration: auctionExtensionSeconds, 2509 // increase lpRange so that LP orders don't get pushed too close to MID and test can behave as expected 2510 }, true, 1) 2511 2512 addAccount(t, tm, party1) 2513 addAccount(t, tm, party2) 2514 addAccount(t, tm, auxParty) 2515 addAccount(t, tm, auxParty2) 2516 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 2517 addAccountWithAmount(tm, "lpprov", 100000) 2518 2519 // set auction duration 2520 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Duration(auctionExtensionSeconds)*time.Second) 2521 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 2522 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 2523 require.NotNil(t, conf) 2524 require.NoError(t, err) 2525 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 2526 2527 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100*initialPrice) 2528 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 2529 require.NotNil(t, conf) 2530 require.NoError(t, err) 2531 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 2532 auxOrders := []*types.Order{ 2533 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, initialPrice), 2534 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, initialPrice), 2535 } 2536 for _, o := range auxOrders { 2537 conf, err := tm.market.SubmitOrder(context.Background(), o) 2538 require.NoError(t, err) 2539 require.NotNil(t, conf) 2540 } 2541 lp := &types.LiquidityProvisionSubmission{ 2542 MarketID: tm.market.GetID(), 2543 CommitmentAmount: num.NewUint(5000), 2544 Fee: num.DecimalFromFloat(0.01), 2545 } 2546 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2547 // leave auction 2548 tm.now = openEnd 2549 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), openEnd) 2550 now = openEnd 2551 2552 orderSell1 := &types.Order{ 2553 Type: types.OrderTypeLimit, 2554 TimeInForce: types.OrderTimeInForceGTT, 2555 Status: types.OrderStatusActive, 2556 ID: "someid2", 2557 Side: types.SideSell, 2558 Party: party2, 2559 MarketID: tm.market.GetID(), 2560 Size: 100, 2561 Price: num.NewUint(initialPrice), 2562 Remaining: 100, 2563 CreatedAt: now.UnixNano(), 2564 ExpiresAt: closingAt.UnixNano(), 2565 Reference: "party2-sell-order-1", 2566 } 2567 confirmationSell, err := tm.market.SubmitOrder(context.Background(), orderSell1) 2568 require.NotNil(t, confirmationSell) 2569 require.NoError(t, err) 2570 2571 orderBuy1 := &types.Order{ 2572 Type: types.OrderTypeLimit, 2573 TimeInForce: types.OrderTimeInForceFOK, 2574 Status: types.OrderStatusActive, 2575 ID: "someid1", 2576 Side: types.SideBuy, 2577 Party: party1, 2578 MarketID: tm.market.GetID(), 2579 Size: 100, 2580 Price: num.NewUint(initialPrice), 2581 Remaining: 100, 2582 CreatedAt: now.UnixNano(), 2583 Reference: "party1-buy-order-1", 2584 } 2585 confirmationBuy, err := tm.market.SubmitOrder(context.Background(), orderBuy1) 2586 require.NotNil(t, confirmationBuy) 2587 assert.NoError(t, err) 2588 2589 require.Equal(t, 1, len(confirmationBuy.Trades)) 2590 2591 auctionEnd := tm.market.GetMarketData().AuctionEnd 2592 require.Equal(t, int64(0), auctionEnd, "we are in auction?") // Not in auction 2593 require.Equal(t, types.MarketStateActive, tm.market.State()) 2594 2595 orderSell2 := &types.Order{ 2596 Type: types.OrderTypeLimit, 2597 TimeInForce: types.OrderTimeInForceGTT, 2598 Status: types.OrderStatusActive, 2599 ID: "someid4", 2600 Side: types.SideSell, 2601 Party: party2, 2602 MarketID: tm.market.GetID(), 2603 Size: 100, 2604 Price: num.NewUint(auctionTriggeringPrice), 2605 Remaining: 100, 2606 CreatedAt: now.UnixNano(), 2607 ExpiresAt: closingAt.UnixNano(), 2608 Reference: "party2-sell-order-2", 2609 } 2610 confirmationSell, err = tm.market.SubmitOrder(context.Background(), orderSell2) 2611 require.NotNil(t, confirmationSell) 2612 require.NoError(t, err) 2613 2614 orderBuy2 := &types.Order{ 2615 Type: types.OrderTypeLimit, 2616 TimeInForce: types.OrderTimeInForceGTC, 2617 Status: types.OrderStatusActive, 2618 ID: "someid3", 2619 Side: types.SideBuy, 2620 Party: party1, 2621 MarketID: tm.market.GetID(), 2622 Size: 100, 2623 Price: num.NewUint(auctionTriggeringPrice), 2624 Remaining: 100, 2625 CreatedAt: now.UnixNano(), 2626 Reference: "party1-buy-order-2", 2627 } 2628 confirmationBuy, err = tm.market.SubmitOrder(context.Background(), orderBuy2) 2629 require.Equal(t, types.MarketStateSuspended, tm.market.State()) // enter auction 2630 assert.NotNil(t, confirmationBuy) 2631 assert.NoError(t, err) 2632 2633 require.Empty(t, confirmationSell.Trades) 2634 2635 tm.now = auctionEndTime 2636 closed := tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), auctionEndTime) 2637 assert.False(t, closed) 2638 2639 now = auctionEndTime 2640 orderSell3 := &types.Order{ 2641 Type: types.OrderTypeLimit, 2642 TimeInForce: types.OrderTimeInForceGFA, 2643 Status: types.OrderStatusActive, 2644 ID: "someid6", 2645 Side: types.SideSell, 2646 Party: party2, 2647 MarketID: tm.market.GetID(), 2648 Size: 100, 2649 Price: num.NewUint(validPrice), 2650 Remaining: 100, 2651 CreatedAt: now.UnixNano(), 2652 Reference: "party2-sell-order-3", 2653 } 2654 confirmationSell, err = tm.market.SubmitOrder(context.Background(), orderSell3) 2655 assert.NotNil(t, confirmationSell) 2656 assert.NoError(t, err) 2657 2658 orderBuy3 := &types.Order{ 2659 Type: types.OrderTypeLimit, 2660 TimeInForce: types.OrderTimeInForceGFA, 2661 Status: types.OrderStatusActive, 2662 ID: "someid5", 2663 Side: types.SideBuy, 2664 Party: party1, 2665 MarketID: tm.market.GetID(), 2666 Size: 100, 2667 Price: num.NewUint(validPrice), 2668 Remaining: 100, 2669 CreatedAt: now.UnixNano(), 2670 ExpiresAt: closingAt.UnixNano(), 2671 Reference: "party1-buy-order-3", 2672 } 2673 confirmationBuy, err = tm.market.SubmitOrder(context.Background(), orderBuy3) 2674 assert.NotNil(t, confirmationBuy) 2675 assert.NoError(t, err) 2676 require.Empty(t, confirmationBuy.Trades) 2677 2678 auctionEnd = tm.market.GetMarketData().AuctionEnd 2679 require.Equal(t, auctionEndTime.UnixNano(), auctionEnd) // In auction 2680 require.Equal(t, types.MarketStateSuspended, tm.market.State()) // enter auction 2681 2682 tm.now = afterAuction 2683 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), afterAuction) 2684 require.Equal(t, tm.market.State(), types.MarketStateActive) 2685 assert.False(t, closed) 2686 2687 auctionEnd = tm.market.GetMarketData().AuctionEnd 2688 require.Equal(t, int64(0), auctionEnd) // Not in auction 2689 2690 // TODO: Check that `party2-sell-order-3` & `party1-buy-order-3` get matched in auction and a trade is generated 2691 2692 // Test that orders get matched as expected upon returning to continuous trading 2693 now = afterAuction.Add(time.Second) 2694 orderSell4 := &types.Order{ 2695 Type: types.OrderTypeLimit, 2696 TimeInForce: types.OrderTimeInForceGTT, 2697 Status: types.OrderStatusActive, 2698 ID: "someid8", 2699 Side: types.SideSell, 2700 Party: party2, 2701 MarketID: tm.market.GetID(), 2702 Size: 1, 2703 Price: num.NewUint(validPrice), 2704 Remaining: 1, 2705 CreatedAt: now.UnixNano(), 2706 ExpiresAt: closingAt.UnixNano(), 2707 Reference: "party2-sell-order-4", 2708 } 2709 confirmationSell, err = tm.market.SubmitOrder(context.Background(), orderSell4) 2710 assert.NotNil(t, confirmationSell) 2711 assert.NoError(t, err) 2712 2713 orderBuy4 := &types.Order{ 2714 Type: types.OrderTypeLimit, 2715 TimeInForce: types.OrderTimeInForceGTT, 2716 Status: types.OrderStatusActive, 2717 ID: "someid7", 2718 Side: types.SideBuy, 2719 Party: party1, 2720 MarketID: tm.market.GetID(), 2721 Size: 1, 2722 Price: num.NewUint(validPrice), 2723 Remaining: 1, 2724 CreatedAt: now.UnixNano(), 2725 ExpiresAt: closingAt.UnixNano(), 2726 Reference: "party1-buy-order-4", 2727 } 2728 confirmationBuy, err = tm.market.SubmitOrder(context.Background(), orderBuy4) 2729 require.NotNil(t, confirmationBuy) 2730 require.NoError(t, err) 2731 require.Equal(t, 1, len(confirmationBuy.Trades)) 2732 } 2733 2734 func TestTriggerByPriceAuctionPriceOutsideBounds(t *testing.T) { 2735 party1 := "party1" 2736 party2 := "party2" 2737 auxParty, auxParty2 := "auxParty", "auxParty2" 2738 now := time.Unix(10, 0) 2739 closingAt := time.Unix(10000000000, 0) 2740 auctionExtensionSeconds := int64(45) 2741 openingAuctionDuration := &types.AuctionDuration{Duration: auctionExtensionSeconds} 2742 openEnd := now.Add(time.Duration(openingAuctionDuration.Duration)*time.Second + time.Second) 2743 auctionEndTime := openEnd.Add(time.Duration(auctionExtensionSeconds) * time.Second) 2744 initialAuctionEnd := auctionEndTime.Add(time.Second) 2745 pMonitorSettings := &types.PriceMonitoringSettings{ 2746 Parameters: &types.PriceMonitoringParameters{ 2747 Triggers: []*types.PriceMonitoringTrigger{ 2748 { 2749 Horizon: 60, 2750 HorizonDec: num.DecimalFromFloat(60), 2751 Probability: num.DecimalFromFloat(0.95), 2752 AuctionExtension: auctionExtensionSeconds, 2753 }, 2754 }, 2755 }, 2756 } 2757 mmu, _ := num.UintFromDecimal(MAXMOVEUP) 2758 initialPrice := uint64(600) 2759 auctionTriggeringPrice := initialPrice + 1 + mmu.Uint64() 2760 // increase lpRange so that LP orders don't get pushed too close to MID and test can behave as expected 2761 tm := getTestMarket2(t, now, pMonitorSettings, openingAuctionDuration, true, 1) 2762 2763 addAccount(t, tm, party1) 2764 addAccount(t, tm, party2) 2765 addAccount(t, tm, auxParty) 2766 addAccount(t, tm, auxParty2) 2767 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 2768 addAccountWithAmount(tm, "lpprov", 100000) 2769 2770 // set auction duration 2771 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Duration(auctionExtensionSeconds)*time.Second) 2772 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 2773 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 2774 require.Equal(t, types.MarketStatePending, tm.market.State()) // enter auction 2775 require.NotNil(t, conf) 2776 require.NoError(t, err) 2777 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 2778 2779 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100*initialPrice) 2780 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 2781 require.NotNil(t, conf) 2782 require.NoError(t, err) 2783 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 2784 auxOrders := []*types.Order{ 2785 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideBuy, auxParty, 1, initialPrice), 2786 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideSell, auxParty2, 1, initialPrice), 2787 } 2788 for _, o := range auxOrders { 2789 conf, err := tm.market.SubmitOrder(context.Background(), o) 2790 require.NotNil(t, conf) 2791 require.NoError(t, err) 2792 } 2793 lp := &types.LiquidityProvisionSubmission{ 2794 MarketID: tm.market.GetID(), 2795 CommitmentAmount: num.NewUint(5000), 2796 Fee: num.DecimalFromFloat(0.01), 2797 } 2798 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2799 // increase time, so we can leave opening auction 2800 tm.now = openEnd 2801 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), openEnd) 2802 2803 md := tm.market.GetMarketData() 2804 2805 require.Equal(t, types.AuctionTriggerUnspecified, md.Trigger) 2806 2807 require.Equal(t, types.MarketStateActive, tm.market.State()) 2808 now = openEnd 2809 2810 orderSell1 := &types.Order{ 2811 Type: types.OrderTypeLimit, 2812 TimeInForce: types.OrderTimeInForceGTT, 2813 Status: types.OrderStatusActive, 2814 ID: "someid2", 2815 Side: types.SideSell, 2816 Party: party2, 2817 MarketID: tm.market.GetID(), 2818 Size: 100, 2819 Price: num.NewUint(initialPrice), 2820 Remaining: 100, 2821 CreatedAt: now.UnixNano(), 2822 ExpiresAt: closingAt.UnixNano(), 2823 Reference: "party2-sell-order-1", 2824 } 2825 confirmationSell, err := tm.market.SubmitOrder(context.Background(), orderSell1) 2826 require.NotNil(t, confirmationSell) 2827 require.NoError(t, err) 2828 2829 orderBuy1 := &types.Order{ 2830 Type: types.OrderTypeLimit, 2831 TimeInForce: types.OrderTimeInForceFOK, 2832 Status: types.OrderStatusActive, 2833 ID: "someid1", 2834 Side: types.SideBuy, 2835 Party: party1, 2836 MarketID: tm.market.GetID(), 2837 Size: 100, 2838 Price: num.NewUint(initialPrice), 2839 Remaining: 100, 2840 CreatedAt: now.UnixNano(), 2841 Reference: "party1-buy-order-1", 2842 } 2843 confirmationBuy, err := tm.market.SubmitOrder(context.Background(), orderBuy1) 2844 require.NotNil(t, confirmationBuy) 2845 assert.NoError(t, err) 2846 2847 require.Equal(t, 1, len(confirmationBuy.Trades)) 2848 2849 auctionEnd := tm.market.GetMarketData().AuctionEnd 2850 require.Equal(t, int64(0), auctionEnd) // Not in auction 2851 2852 orderSell2 := &types.Order{ 2853 Type: types.OrderTypeLimit, 2854 TimeInForce: types.OrderTimeInForceGTT, 2855 Status: types.OrderStatusActive, 2856 ID: "someid4", 2857 Side: types.SideSell, 2858 Party: party2, 2859 MarketID: tm.market.GetID(), 2860 Size: 100, 2861 Price: num.NewUint(auctionTriggeringPrice), 2862 Remaining: 100, 2863 CreatedAt: now.UnixNano(), 2864 ExpiresAt: closingAt.UnixNano(), 2865 Reference: "party2-sell-order-2", 2866 } 2867 confirmationSell, err = tm.market.SubmitOrder(context.Background(), orderSell2) 2868 require.NotNil(t, confirmationSell) 2869 require.NoError(t, err) 2870 2871 orderBuy2 := &types.Order{ 2872 Type: types.OrderTypeLimit, 2873 TimeInForce: types.OrderTimeInForceGTC, 2874 Status: types.OrderStatusActive, 2875 ID: "someid3", 2876 Side: types.SideBuy, 2877 Party: party1, 2878 MarketID: tm.market.GetID(), 2879 Size: 100, 2880 Price: num.NewUint(auctionTriggeringPrice - 1), 2881 Remaining: 100, 2882 CreatedAt: now.UnixNano(), 2883 Reference: "party1-buy-order-2", 2884 } 2885 confirmationBuy, err = tm.market.SubmitOrder(context.Background(), orderBuy2) 2886 assert.NotNil(t, confirmationBuy) 2887 assert.NoError(t, err) 2888 2889 require.Empty(t, confirmationBuy.Trades) 2890 2891 auctionEnd = tm.market.GetMarketData().AuctionEnd 2892 require.Equal(t, int64(0), auctionEnd) // Not in auction 2893 2894 amendedOrder := &types.OrderAmendment{ 2895 OrderID: orderBuy2.ID, 2896 Price: num.NewUint(auctionTriggeringPrice), 2897 SizeDelta: 0, 2898 TimeInForce: types.OrderTimeInForceGTC, 2899 } 2900 2901 conf, err = tm.market.AmendOrder(context.Background(), amendedOrder, party1, vgcrypto.RandomHash()) 2902 require.NoError(t, err) 2903 require.NotNil(t, conf) 2904 require.Equal(t, types.MarketStateSuspended, tm.market.State()) // enter auction 2905 2906 auctionEnd = tm.market.GetMarketData().AuctionEnd 2907 require.Equal(t, auctionEndTime.UnixNano(), auctionEnd) // In auction 2908 2909 tm.now = auctionEndTime 2910 closed := tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), auctionEndTime) 2911 assert.False(t, closed) 2912 2913 now = auctionEndTime 2914 orderSell3 := &types.Order{ 2915 Type: types.OrderTypeLimit, 2916 TimeInForce: types.OrderTimeInForceGFA, 2917 Status: types.OrderStatusActive, 2918 ID: "someid6", 2919 Side: types.SideSell, 2920 Party: party2, 2921 MarketID: tm.market.GetID(), 2922 Size: 100, 2923 Price: num.NewUint(auctionTriggeringPrice), 2924 Remaining: 100, 2925 CreatedAt: now.UnixNano(), 2926 Reference: "party2-sell-order-3", 2927 } 2928 confirmationSell, err = tm.market.SubmitOrder(context.Background(), orderSell3) 2929 assert.NotNil(t, confirmationSell) 2930 assert.NoError(t, err) 2931 2932 orderBuy3 := &types.Order{ 2933 Type: types.OrderTypeLimit, 2934 TimeInForce: types.OrderTimeInForceGFA, 2935 Status: types.OrderStatusActive, 2936 ID: "someid5", 2937 Side: types.SideBuy, 2938 Party: party1, 2939 MarketID: tm.market.GetID(), 2940 Size: 100, 2941 Price: num.NewUint(auctionTriggeringPrice), 2942 Remaining: 100, 2943 CreatedAt: now.UnixNano(), 2944 ExpiresAt: closingAt.UnixNano(), 2945 Reference: "party1-buy-order-3", 2946 } 2947 confirmationBuy, err = tm.market.SubmitOrder(context.Background(), orderBuy3) 2948 assert.NotNil(t, confirmationBuy) 2949 assert.NoError(t, err) 2950 require.Empty(t, confirmationBuy.Trades) 2951 2952 auctionEnd = tm.market.GetMarketData().AuctionEnd 2953 require.Equal(t, auctionEndTime.UnixNano(), auctionEnd) // In auction 2954 2955 // Expecting market to still be in auction as auction resulted in invalid price 2956 tm.now = initialAuctionEnd 2957 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), initialAuctionEnd) 2958 assert.False(t, closed) 2959 2960 auctionEnd = tm.market.GetMarketData().AuctionEnd 2961 require.Equal(t, int64(0), auctionEnd) // Not in auction (trigger can only start auction, but can't stop it from concluding at a higher price) 2962 } 2963 2964 func TestTriggerByMarketOrder(t *testing.T) { 2965 party1 := "party1" 2966 party2 := "party2" 2967 auxParty := "auxParty" 2968 auxParty2 := "auxParty2" 2969 now := time.Unix(10, 0) 2970 closingAt := time.Unix(10000000000, 0) 2971 var auctionExtensionSeconds int64 = 45 2972 openingEnd := now.Add(time.Duration(auctionExtensionSeconds+1) * time.Second) 2973 auctionEndTime := openingEnd.Add(time.Duration(auctionExtensionSeconds) * time.Second) 2974 pMonitorSettings := &types.PriceMonitoringSettings{ 2975 Parameters: &types.PriceMonitoringParameters{ 2976 Triggers: []*types.PriceMonitoringTrigger{ 2977 { 2978 Horizon: 60, 2979 HorizonDec: num.DecimalFromFloat(60), 2980 Probability: num.DecimalFromFloat(0.95), 2981 AuctionExtension: auctionExtensionSeconds, 2982 }, 2983 }, 2984 }, 2985 } 2986 mmu, _ := num.UintFromDecimal(MAXMOVEUP) 2987 initialPrice := uint64(600) 2988 auctionTriggeringPriceHigh := initialPrice + 1 + mmu.Uint64() 2989 tm := getTestMarket2(t, now, pMonitorSettings, &types.AuctionDuration{ 2990 Duration: auctionExtensionSeconds, 2991 // increase lpRange so that LP orders don't get pushed too close to MID and test can behave as expected 2992 }, true, 1) 2993 2994 addAccount(t, tm, party1) 2995 addAccount(t, tm, party2) 2996 addAccount(t, tm, auxParty) 2997 addAccount(t, tm, auxParty2) 2998 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 2999 addAccountWithAmount(tm, "lpprov", 100000) 3000 3001 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Duration(auctionExtensionSeconds)*time.Second) 3002 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 3003 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 3004 require.NotNil(t, conf) 3005 require.NoError(t, err) 3006 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3007 3008 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100*initialPrice) 3009 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 3010 require.NotNil(t, conf) 3011 require.NoError(t, err) 3012 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3013 3014 auxOrders := []*types.Order{ 3015 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, initialPrice), 3016 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, initialPrice), 3017 } 3018 for _, o := range auxOrders { 3019 conf, err := tm.market.SubmitOrder(context.Background(), o) 3020 require.NotNil(t, conf) 3021 require.NoError(t, err) 3022 } 3023 lp := &types.LiquidityProvisionSubmission{ 3024 MarketID: tm.market.GetID(), 3025 CommitmentAmount: num.NewUint(5000), 3026 Fee: num.DecimalFromFloat(0.01), 3027 } 3028 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 3029 // now leave auction 3030 tm.now = openingEnd 3031 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), openingEnd) 3032 now = openingEnd 3033 3034 orderSell1 := &types.Order{ 3035 Type: types.OrderTypeLimit, 3036 TimeInForce: types.OrderTimeInForceGTT, 3037 Status: types.OrderStatusActive, 3038 ID: "someid2", 3039 Side: types.SideSell, 3040 Party: party2, 3041 MarketID: tm.market.GetID(), 3042 Size: 100, 3043 Price: num.NewUint(initialPrice), 3044 Remaining: 100, 3045 CreatedAt: now.UnixNano(), 3046 ExpiresAt: closingAt.UnixNano(), 3047 Reference: "party2-sell-order-1", 3048 } 3049 confirmationSell, err := tm.market.SubmitOrder(context.Background(), orderSell1) 3050 require.NotNil(t, confirmationSell) 3051 require.NoError(t, err) 3052 3053 orderBuy1 := &types.Order{ 3054 Type: types.OrderTypeLimit, 3055 TimeInForce: types.OrderTimeInForceFOK, 3056 Status: types.OrderStatusActive, 3057 ID: "someid1", 3058 Side: types.SideBuy, 3059 Party: party1, 3060 MarketID: tm.market.GetID(), 3061 Size: 100, 3062 Price: num.NewUint(initialPrice), 3063 Remaining: 100, 3064 CreatedAt: now.UnixNano(), 3065 Reference: "party1-buy-order-1", 3066 } 3067 confirmationBuy, err := tm.market.SubmitOrder(context.Background(), orderBuy1) 3068 require.NotNil(t, confirmationBuy) 3069 assert.NoError(t, err) 3070 3071 require.Equal(t, 1, len(confirmationBuy.Trades)) 3072 3073 auctionEnd := tm.market.GetMarketData().AuctionEnd 3074 require.Equal(t, int64(0), auctionEnd) // Not in auction 3075 3076 orderSell2 := &types.Order{ 3077 Type: types.OrderTypeLimit, 3078 TimeInForce: types.OrderTimeInForceGTT, 3079 Status: types.OrderStatusActive, 3080 ID: "someid3", 3081 Side: types.SideSell, 3082 Party: party2, 3083 MarketID: tm.market.GetID(), 3084 Size: 3, 3085 Price: num.NewUint(auctionTriggeringPriceHigh - 1), 3086 Remaining: 3, 3087 CreatedAt: now.UnixNano(), 3088 ExpiresAt: closingAt.UnixNano(), 3089 Reference: "party2-sell-order-2", 3090 } 3091 confirmationSell, err = tm.market.SubmitOrder(context.Background(), orderSell2) 3092 require.NotNil(t, confirmationSell) 3093 require.NoError(t, err) 3094 3095 require.Empty(t, confirmationSell.Trades) 3096 3097 auctionEnd = tm.market.GetMarketData().AuctionEnd 3098 require.Equal(t, int64(0), auctionEnd) // Not in auction 3099 3100 orderSell3 := &types.Order{ 3101 Type: types.OrderTypeLimit, 3102 TimeInForce: types.OrderTimeInForceGTT, 3103 Status: types.OrderStatusActive, 3104 ID: "someid4", 3105 Side: types.SideSell, 3106 Party: party2, 3107 MarketID: tm.market.GetID(), 3108 Size: 1, 3109 Price: num.NewUint(auctionTriggeringPriceHigh), 3110 Remaining: 1, 3111 CreatedAt: now.UnixNano(), 3112 ExpiresAt: closingAt.UnixNano(), 3113 Reference: "party2-sell-order-3", 3114 } 3115 confirmationSell, err = tm.market.SubmitOrder(context.Background(), orderSell3) 3116 require.NotNil(t, confirmationSell) 3117 require.NoError(t, err) 3118 3119 require.Empty(t, confirmationSell.Trades) 3120 3121 auctionEnd = tm.market.GetMarketData().AuctionEnd 3122 require.Equal(t, int64(0), auctionEnd) // Not in auction 3123 3124 orderBuy2 := &types.Order{ 3125 Type: types.OrderTypeMarket, 3126 Status: types.OrderStatusActive, 3127 ID: "someid5", 3128 Side: types.SideBuy, 3129 Party: party1, 3130 MarketID: tm.market.GetID(), 3131 Size: 4, 3132 Price: num.UintZero(), 3133 Remaining: 4, 3134 CreatedAt: now.UnixNano(), 3135 Reference: "party1-buy-order-2", 3136 } 3137 confirmationBuy, err = tm.market.SubmitOrder(context.Background(), orderBuy2) 3138 assert.NotNil(t, confirmationBuy) 3139 assert.NoError(t, err) 3140 require.Equal(t, types.MarketStateSuspended, tm.market.State()) // enter auction 3141 3142 require.Empty(t, confirmationSell.Trades) 3143 3144 auctionEnd = tm.market.GetMarketData().AuctionEnd 3145 require.Equal(t, auctionEndTime.UnixNano(), auctionEnd) // In auction 3146 3147 tm.now = auctionEndTime 3148 closed := tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), auctionEndTime) 3149 assert.False(t, closed) 3150 3151 auctionEnd = tm.market.GetMarketData().AuctionEnd 3152 require.Equal(t, auctionEndTime.UnixNano(), auctionEnd) // Still in auction 3153 require.Equal(t, types.MarketStateSuspended, tm.market.State()) 3154 3155 tm.now = auctionEndTime.Add(time.Nanosecond) 3156 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), tm.now) 3157 require.Equal(t, types.MarketStateActive, tm.market.State()) // left auction 3158 assert.False(t, closed) 3159 3160 md := tm.market.GetMarketData() 3161 auctionEnd = md.AuctionEnd 3162 require.Equal(t, int64(0), auctionEnd) // Not in auction 3163 3164 require.True(t, md.MarkPrice.EQ(num.NewUint(initialPrice))) 3165 } 3166 3167 func TestPriceMonitoringBoundsInGetMarketData(t *testing.T) { 3168 party1 := "party1" 3169 party2 := "party2" 3170 auxParty := "auxParty" 3171 auxParty2 := "auxParty2" 3172 now := time.Unix(10, 0) 3173 closingAt := time.Unix(10000000000, 0) 3174 extension := int64(45) 3175 t1 := &types.PriceMonitoringTrigger{ 3176 Horizon: 60, 3177 HorizonDec: num.DecimalFromFloat(60), 3178 Probability: num.DecimalFromFloat(0.95), 3179 AuctionExtension: extension, 3180 } 3181 t2 := &types.PriceMonitoringTrigger{ 3182 Horizon: 120, 3183 HorizonDec: num.DecimalFromFloat(120), 3184 Probability: num.DecimalFromFloat(0.99), 3185 AuctionExtension: extension * 2, 3186 } 3187 pMonitorSettings := &types.PriceMonitoringSettings{ 3188 Parameters: &types.PriceMonitoringParameters{ 3189 Triggers: []*types.PriceMonitoringTrigger{ 3190 t1, 3191 t2, 3192 }, 3193 }, 3194 } 3195 openEnd := now.Add(time.Duration(extension)*time.Second + time.Second) 3196 mmu, _ := num.UintFromDecimal(MAXMOVEUP) 3197 initialPrice := uint64(600) 3198 auctionTriggeringPrice := initialPrice + mmu.Uint64() + 1 3199 tm := getTestMarket2(t, now, pMonitorSettings, &types.AuctionDuration{ 3200 Duration: extension, 3201 // increase lpRange so that LP orders don't get pushed too close to MID and test can behave as expected 3202 }, true, 1) 3203 3204 initDec := num.DecimalFromFloat(float64(initialPrice)) 3205 // add 1 for the ceil 3206 min, _ := num.UintFromDecimal(initDec.Sub(MINMOVEDOWN).Add(num.DecimalFromFloat(1))) 3207 max, _ := num.UintFromDecimal(initDec.Add(MAXMOVEUP).Floor()) 3208 expectedPmRange1 := types.PriceMonitoringBounds{ 3209 MinValidPrice: min, 3210 MaxValidPrice: max, 3211 Trigger: t1, 3212 ReferencePrice: initDec, 3213 } 3214 expectedPmRange2 := types.PriceMonitoringBounds{ 3215 MinValidPrice: min.Clone(), 3216 MaxValidPrice: max.Clone(), 3217 Trigger: t2, 3218 ReferencePrice: initDec, 3219 } 3220 3221 addAccount(t, tm, party1) 3222 addAccount(t, tm, party2) 3223 addAccount(t, tm, auxParty) 3224 addAccount(t, tm, auxParty2) 3225 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 3226 addAccountWithAmount(tm, "lpprov", 100000) 3227 3228 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Duration(extension)*time.Second) 3229 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 3230 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 3231 require.NotNil(t, conf) 3232 require.NoError(t, err) 3233 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3234 3235 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 3236 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 3237 require.NotNil(t, conf) 3238 require.NoError(t, err) 3239 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3240 3241 auxOrders := []*types.Order{ 3242 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, initialPrice), 3243 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, initialPrice), 3244 } 3245 for _, o := range auxOrders { 3246 conf, err := tm.market.SubmitOrder(context.Background(), o) 3247 require.NoError(t, err) 3248 require.NotNil(t, conf) 3249 } 3250 lp := &types.LiquidityProvisionSubmission{ 3251 MarketID: tm.market.GetID(), 3252 CommitmentAmount: num.NewUint(5000), 3253 Fee: num.DecimalFromFloat(0.01), 3254 } 3255 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 3256 // leave auction 3257 tm.now = openEnd 3258 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), openEnd) 3259 now = openEnd 3260 3261 orderBuy1 := &types.Order{ 3262 Type: types.OrderTypeLimit, 3263 TimeInForce: types.OrderTimeInForceGTT, 3264 Status: types.OrderStatusActive, 3265 ID: "someid1", 3266 Side: types.SideBuy, 3267 Party: party1, 3268 MarketID: tm.market.GetID(), 3269 Size: 100, 3270 Price: num.NewUint(initialPrice), 3271 Remaining: 100, 3272 CreatedAt: now.UnixNano(), 3273 ExpiresAt: closingAt.UnixNano(), 3274 Reference: "party1-buy-order-1", 3275 } 3276 confirmationBuy, err := tm.market.SubmitOrder(context.Background(), orderBuy1) 3277 assert.NotNil(t, confirmationBuy) 3278 assert.NoError(t, err) 3279 3280 orderSell1 := &types.Order{ 3281 Type: types.OrderTypeLimit, 3282 TimeInForce: types.OrderTimeInForceFOK, 3283 Status: types.OrderStatusActive, 3284 ID: "someid2", 3285 Side: types.SideSell, 3286 Party: party2, 3287 MarketID: tm.market.GetID(), 3288 Size: 100, 3289 Price: num.NewUint(initialPrice), 3290 Remaining: 100, 3291 CreatedAt: now.UnixNano(), 3292 Reference: "party2-sell-order-1", 3293 } 3294 confirmationSell, err := tm.market.SubmitOrder(context.Background(), orderSell1) 3295 require.NotNil(t, confirmationSell) 3296 require.NoError(t, err) 3297 require.Equal(t, 1, len(confirmationSell.Trades)) 3298 3299 md := tm.market.GetMarketData() 3300 require.NotNil(t, md) 3301 3302 auctionEnd := md.AuctionEnd 3303 require.Equal(t, int64(0), auctionEnd) // Not in auction 3304 3305 pmBounds := md.PriceMonitoringBounds 3306 require.Equal(t, 2, len(pmBounds)) 3307 require.True(t, expectedPmRange1.MinValidPrice.EQ(pmBounds[0].MinValidPrice), "%s != %s", expectedPmRange1.MinValidPrice, pmBounds[0].MinValidPrice) 3308 require.True(t, expectedPmRange1.MaxValidPrice.EQ(pmBounds[0].MaxValidPrice)) 3309 require.True(t, expectedPmRange1.ReferencePrice.Equals(pmBounds[0].ReferencePrice)) 3310 require.Equal(t, *expectedPmRange1.Trigger, *pmBounds[0].Trigger) 3311 3312 require.True(t, expectedPmRange2.MinValidPrice.EQ(pmBounds[1].MinValidPrice)) 3313 require.True(t, expectedPmRange2.MaxValidPrice.EQ(pmBounds[1].MaxValidPrice)) 3314 require.True(t, expectedPmRange2.ReferencePrice.Equals(pmBounds[1].ReferencePrice)) 3315 require.Equal(t, *expectedPmRange2.Trigger, *pmBounds[1].Trigger) 3316 3317 orderBuy2 := &types.Order{ 3318 Type: types.OrderTypeLimit, 3319 TimeInForce: types.OrderTimeInForceGTT, 3320 Status: types.OrderStatusActive, 3321 ID: "someid3", 3322 Side: types.SideBuy, 3323 Party: party1, 3324 MarketID: tm.market.GetID(), 3325 Size: 100, 3326 Price: num.NewUint(auctionTriggeringPrice), 3327 Remaining: 100, 3328 CreatedAt: now.UnixNano(), 3329 ExpiresAt: closingAt.UnixNano(), 3330 Reference: "party1-buy-order-2", 3331 } 3332 confirmationBuy, err = tm.market.SubmitOrder(context.Background(), orderBuy2) 3333 assert.NotNil(t, confirmationBuy) 3334 assert.NoError(t, err) 3335 3336 orderSell2 := &types.Order{ 3337 Type: types.OrderTypeLimit, 3338 TimeInForce: types.OrderTimeInForceGTC, 3339 Status: types.OrderStatusActive, 3340 ID: "someid4", 3341 Side: types.SideSell, 3342 Party: party2, 3343 MarketID: tm.market.GetID(), 3344 Size: 100, 3345 Price: num.NewUint(auctionTriggeringPrice), 3346 Remaining: 100, 3347 CreatedAt: now.UnixNano(), 3348 Reference: "party2-sell-order-2", 3349 } 3350 confirmationSell, err = tm.market.SubmitOrder(context.Background(), orderSell2) 3351 require.NotNil(t, confirmationSell) 3352 require.NoError(t, err) 3353 require.Equal(t, types.MarketStateSuspended, tm.market.State()) // enter auction 3354 3355 require.Empty(t, confirmationSell.Trades) 3356 3357 md = tm.market.GetMarketData() 3358 require.NotNil(t, md) 3359 auctionEnd = md.AuctionEnd 3360 auctionEndTime := openEnd.Add(time.Duration(t1.AuctionExtension) * time.Second) 3361 require.Equal(t, auctionEndTime.UnixNano(), auctionEnd) // In auction 3362 require.Equal(t, types.MarketStateSuspended, tm.market.State()) 3363 // 2 in total 3364 require.Equal(t, 2, len(md.PriceMonitoringBounds)) 3365 active, triggerd := 0, 0 3366 for _, v := range md.PriceMonitoringBounds { 3367 if v.Active { 3368 active++ 3369 continue 3370 } 3371 triggerd++ 3372 } 3373 // 1 active 3374 require.Equal(t, 1, active) 3375 // 1 triggered 3376 require.Equal(t, 1, triggerd) 3377 3378 tm.now = auctionEndTime 3379 closed := tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), auctionEndTime) 3380 assert.False(t, closed) 3381 3382 md = tm.market.GetMarketData() 3383 require.NotNil(t, md) 3384 auctionEnd = md.AuctionEnd 3385 require.Equal(t, auctionEndTime.UnixNano(), auctionEnd) // In auction 3386 require.Equal(t, types.MarketStateSuspended, tm.market.State()) 3387 3388 require.Equal(t, 1, len(md.PriceMonitoringBounds)) 3389 3390 tm.now = auctionEndTime.Add(time.Nanosecond) 3391 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), tm.now) 3392 assert.False(t, closed) 3393 3394 auctionEndTime = openEnd.Add(time.Duration(t1.AuctionExtension+t2.AuctionExtension) * time.Second) 3395 md = tm.market.GetMarketData() 3396 require.NotNil(t, md) 3397 auctionEnd = md.AuctionEnd 3398 require.Equal(t, auctionEndTime.UnixNano(), auctionEnd) // In auction 3399 require.Equal(t, types.MarketStateSuspended, tm.market.State()) 3400 3401 tm.now = auctionEndTime 3402 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), auctionEndTime) 3403 assert.False(t, closed) 3404 3405 md = tm.market.GetMarketData() 3406 require.NotNil(t, md) 3407 auctionEnd = md.AuctionEnd 3408 require.Equal(t, auctionEndTime.UnixNano(), auctionEnd) // In auction 3409 require.Equal(t, types.MarketStateSuspended, tm.market.State()) 3410 3411 tm.now = auctionEndTime.Add(time.Nanosecond) 3412 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), tm.now) 3413 assert.False(t, closed) 3414 3415 md = tm.market.GetMarketData() 3416 require.NotNil(t, md) 3417 auctionEnd = md.AuctionEnd 3418 require.Equal(t, int64(0), auctionEnd) // Not in auction 3419 require.Equal(t, types.MarketStateActive, tm.market.State()) 3420 3421 require.Equal(t, 2, len(md.PriceMonitoringBounds)) 3422 require.True(t, expectedPmRange1.MinValidPrice.EQ(pmBounds[0].MinValidPrice)) 3423 require.True(t, expectedPmRange1.MaxValidPrice.EQ(pmBounds[0].MaxValidPrice)) 3424 require.True(t, expectedPmRange1.ReferencePrice.Equals(pmBounds[0].ReferencePrice)) 3425 require.Equal(t, *expectedPmRange1.Trigger, *pmBounds[0].Trigger) 3426 3427 require.True(t, expectedPmRange2.MinValidPrice.EQ(pmBounds[1].MinValidPrice)) 3428 require.True(t, expectedPmRange2.MaxValidPrice.EQ(pmBounds[1].MaxValidPrice)) 3429 require.True(t, expectedPmRange2.ReferencePrice.Equals(pmBounds[1].ReferencePrice)) 3430 require.Equal(t, *expectedPmRange2.Trigger, *pmBounds[1].Trigger) 3431 } 3432 3433 func TestTargetStakeReturnedAndCorrect(t *testing.T) { 3434 party1 := "party1" 3435 party2 := "party2" 3436 auxParty := "auxParty" 3437 auxParty2 := "auxParty2" 3438 oi := uint64(124) 3439 matchingPrice := uint64(111) 3440 now := time.Unix(10, 0) 3441 closingAt := time.Unix(10000000000, 0) 3442 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 3443 Duration: 1, 3444 }) 3445 3446 rmParams := tm.mktCfg.TradableInstrument.GetSimpleRiskModel().Params 3447 expectedTargetStake := num.DecimalFromFloat(float64(matchingPrice * oi)).Mul(tm.mktCfg.LiquidityMonitoringParameters.TargetStakeParameters.ScalingFactor) 3448 if rmParams.FactorLong.GreaterThan(rmParams.FactorShort) { 3449 expectedTargetStake = expectedTargetStake.Mul(rmParams.FactorLong) 3450 } else { 3451 expectedTargetStake = expectedTargetStake.Mul(rmParams.FactorShort) 3452 } 3453 3454 addAccount(t, tm, party1) 3455 addAccount(t, tm, party2) 3456 addAccount(t, tm, auxParty) 3457 addAccount(t, tm, auxParty2) 3458 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 3459 addAccountWithAmount(tm, "lpprov", 100000000000) 3460 3461 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Second) 3462 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 3463 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 3464 require.NotNil(t, conf) 3465 require.NoError(t, err) 3466 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3467 3468 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 3469 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 3470 require.NotNil(t, conf) 3471 require.NoError(t, err) 3472 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3473 3474 auxOrders := []*types.Order{ 3475 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, matchingPrice), 3476 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, matchingPrice), 3477 } 3478 for _, o := range auxOrders { 3479 conf, err := tm.market.SubmitOrder(context.Background(), o) 3480 require.NoError(t, err) 3481 require.NotNil(t, conf) 3482 } 3483 lp := &types.LiquidityProvisionSubmission{ 3484 MarketID: tm.market.GetID(), 3485 CommitmentAmount: num.NewUint(50000), 3486 Fee: num.DecimalFromFloat(0.01), 3487 } 3488 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 3489 // leave opening auction 3490 now = now.Add(2 * time.Second) 3491 tm.now = now 3492 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3493 3494 orderSell1 := &types.Order{ 3495 Type: types.OrderTypeLimit, 3496 TimeInForce: types.OrderTimeInForceGTT, 3497 Status: types.OrderStatusActive, 3498 ID: "someid2", 3499 Side: types.SideSell, 3500 Party: party2, 3501 MarketID: tm.market.GetID(), 3502 Size: oi - 1, // -1 because we trade during opening auction 3503 Price: num.NewUint(matchingPrice), 3504 Remaining: oi - 1, 3505 CreatedAt: now.UnixNano(), 3506 ExpiresAt: closingAt.UnixNano(), 3507 Reference: "party2-sell-order-1", 3508 } 3509 confirmationSell, err := tm.market.SubmitOrder(context.Background(), orderSell1) 3510 require.NotNil(t, confirmationSell) 3511 require.NoError(t, err) 3512 3513 orderBuy1 := &types.Order{ 3514 Type: types.OrderTypeLimit, 3515 TimeInForce: types.OrderTimeInForceFOK, 3516 Status: types.OrderStatusActive, 3517 ID: "someid1", 3518 Side: types.SideBuy, 3519 Party: party1, 3520 MarketID: tm.market.GetID(), 3521 Size: oi - 1, 3522 Price: num.NewUint(matchingPrice), 3523 Remaining: oi - 1, 3524 CreatedAt: now.UnixNano(), 3525 Reference: "party1-buy-order-1", 3526 } 3527 confirmationBuy, err := tm.market.SubmitOrder(context.Background(), orderBuy1) 3528 require.NotNil(t, confirmationBuy) 3529 assert.NoError(t, err) 3530 require.NotZero(t, len(confirmationBuy.Trades)) 3531 3532 mktData := tm.market.GetMarketData() 3533 require.NotNil(t, mktData) 3534 require.Equal(t, expectedTargetStake.String(), mktData.TargetStake) 3535 } 3536 3537 func TestSuppliedStakeReturnedAndCorrect(t *testing.T) { 3538 party1 := "party1" 3539 party2 := "party2" 3540 now := time.Unix(10, 0) 3541 closingAt := time.Unix(10000000000, 0) 3542 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 3543 tm := getTestMarket(t, now, nil, nil) 3544 var matchingPrice uint64 = 111 3545 3546 addAccount(t, tm, party1) 3547 addAccount(t, tm, party2) 3548 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 3549 3550 orderSell1 := &types.Order{ 3551 Type: types.OrderTypeLimit, 3552 TimeInForce: types.OrderTimeInForceGTT, 3553 Status: types.OrderStatusActive, 3554 ID: "someid2", 3555 Side: types.SideSell, 3556 Party: party2, 3557 MarketID: tm.market.GetID(), 3558 Size: 1, 3559 Price: num.NewUint(matchingPrice + 1), 3560 Remaining: 1, 3561 CreatedAt: now.UnixNano(), 3562 ExpiresAt: closingAt.UnixNano(), 3563 Reference: "party2-sell-order-1", 3564 } 3565 confirmationSell, err := tm.market.SubmitOrder(context.Background(), orderSell1) 3566 tm.now = tm.now.Add(time.Second) 3567 tm.market.OnTick(ctx, tm.now) 3568 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 3569 require.NotNil(t, confirmationSell) 3570 require.NoError(t, err) 3571 3572 orderBuy1 := &types.Order{ 3573 Type: types.OrderTypeLimit, 3574 TimeInForce: types.OrderTimeInForceGTT, 3575 Status: types.OrderStatusActive, 3576 ID: "someid1", 3577 Side: types.SideBuy, 3578 Party: party1, 3579 MarketID: tm.market.GetID(), 3580 Size: 1, 3581 Price: num.NewUint(matchingPrice - 1), 3582 Remaining: 1, 3583 CreatedAt: now.UnixNano(), 3584 ExpiresAt: closingAt.UnixNano(), 3585 Reference: "party1-buy-order-1", 3586 } 3587 confirmationBuy, err := tm.market.SubmitOrder(context.Background(), orderBuy1) 3588 tm.now = tm.now.Add(time.Second) 3589 tm.market.OnTick(ctx, tm.now) 3590 assert.NotNil(t, confirmationBuy) 3591 assert.NoError(t, err) 3592 3593 require.Equal(t, 0, len(confirmationBuy.Trades)) 3594 3595 lp1 := &types.LiquidityProvisionSubmission{ 3596 MarketID: tm.market.GetID(), 3597 CommitmentAmount: num.NewUint(200), 3598 Fee: num.DecimalFromFloat(0.05), 3599 } 3600 3601 err = tm.market.SubmitLiquidityProvision(context.Background(), lp1, party1, vgcrypto.RandomHash()) 3602 require.NoError(t, err) 3603 tm.now = tm.now.Add(time.Second) 3604 tm.market.OnTick(ctx, tm.now) 3605 3606 lp2 := &types.LiquidityProvisionSubmission{ 3607 MarketID: tm.market.GetID(), 3608 CommitmentAmount: num.NewUint(100), 3609 Fee: num.DecimalFromFloat(0.06), 3610 } 3611 3612 err = tm.market.SubmitLiquidityProvision(context.Background(), lp2, party2, vgcrypto.RandomHash()) 3613 require.NoError(t, err) 3614 tm.now = tm.now.Add(time.Second) 3615 tm.market.OnTick(ctx, tm.now) 3616 3617 mktData := tm.market.GetMarketData() 3618 require.NotNil(t, mktData) 3619 expectedSuppliedStake := num.DecimalFromUint(num.Sum(lp1.CommitmentAmount, lp2.CommitmentAmount)) 3620 3621 require.Equal(t, expectedSuppliedStake.String(), mktData.SuppliedStake) 3622 } 3623 3624 func TestSubmitLiquidityProvisionInOpeningAuction(t *testing.T) { 3625 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 3626 mainParty := "mainParty" 3627 auxParty := "auxParty" 3628 p1, p2 := "party1", "party2" 3629 now := time.Unix(10, 0) 3630 var auctionDuration int64 = 5 3631 tm := getTestMarket2(t, now, nil, &types.AuctionDuration{Duration: auctionDuration}, true, 0.99) 3632 var midPrice uint64 = 100 3633 3634 addAccount(t, tm, mainParty) 3635 addAccount(t, tm, auxParty) 3636 addAccount(t, tm, p1) 3637 addAccount(t, tm, p2) 3638 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 3639 3640 lp1 := &types.LiquidityProvisionSubmission{ 3641 MarketID: tm.market.GetID(), 3642 CommitmentAmount: num.NewUint(250), 3643 Fee: num.DecimalFromFloat(0.05), 3644 } 3645 3646 require.Equal(t, types.MarketTradingModeOpeningAuction, tm.market.GetMarketData().MarketTradingMode) 3647 3648 err := tm.market.SubmitLiquidityProvision(ctx, lp1, mainParty, vgcrypto.RandomHash()) 3649 require.NoError(t, err) 3650 3651 tradingOrders := []*types.Order{ 3652 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "p1-sell-order", types.SideSell, p1, 1, midPrice), 3653 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "p2-buy-order", types.SideBuy, p2, 1, midPrice), 3654 } 3655 for _, o := range tradingOrders { 3656 conf, err := tm.market.SubmitOrder(ctx, o) 3657 assert.NoError(t, err) 3658 assert.NotNil(t, conf) 3659 } 3660 orderSell1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "auxParty-sell-order-1", types.SideSell, auxParty, 1, midPrice+2) 3661 3662 confirmationSell, err := tm.market.SubmitOrder(ctx, orderSell1) 3663 require.NotNil(t, confirmationSell) 3664 require.NoError(t, err) 3665 3666 orderBuy1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "auxParty-buy-order-1", types.SideBuy, auxParty, 1, midPrice-2) 3667 3668 confirmationBuy, err := tm.market.SubmitOrder(ctx, orderBuy1) 3669 assert.NotNil(t, confirmationBuy) 3670 assert.NoError(t, err) 3671 3672 now = now.Add(time.Duration((auctionDuration + 1) * time.Second.Nanoseconds())) 3673 tm.now = now 3674 tm.market.OnTick(ctx, now) 3675 3676 // Check that liquidity orders appear on the book once reference prices exist 3677 mktData := tm.market.GetMarketData() 3678 require.Equal(t, types.MarketTradingModeContinuous, mktData.MarketTradingMode) 3679 } 3680 3681 func getMarketOrder(tm *testMarket, 3682 now time.Time, 3683 orderType types.OrderType, 3684 orderTIF types.OrderTimeInForce, 3685 id string, 3686 side types.Side, 3687 partyID string, 3688 size uint64, 3689 price uint64, 3690 ) *types.Order { 3691 order := &types.Order{ 3692 Type: orderType, 3693 TimeInForce: orderTIF, 3694 Status: types.OrderStatusActive, 3695 ID: id, 3696 Side: side, 3697 Party: partyID, 3698 MarketID: tm.market.GetID(), 3699 Size: size, 3700 Price: num.NewUint(price), 3701 Remaining: size, 3702 CreatedAt: now.UnixNano(), 3703 Reference: "marketorder", 3704 } 3705 return order 3706 } 3707 3708 func newPeggedOrder(reference types.PeggedReference, offset uint64) *types.PeggedOrder { 3709 return &types.PeggedOrder{ 3710 Reference: reference, 3711 Offset: num.NewUint(offset), 3712 } 3713 } 3714 3715 func TestOrderBook_Crash2651(t *testing.T) { 3716 now := time.Unix(10, 0) 3717 tm := getTestMarket(t, now, nil, nil) 3718 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 3719 3720 addAccount(t, tm, "613f") 3721 addAccount(t, tm, "f9e7") 3722 addAccount(t, tm, "98e1") 3723 addAccount(t, tm, "qqqq") 3724 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 3725 3726 // Switch to auction mode 3727 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 3728 tm.mas.AuctionStarted(ctx, now) 3729 tm.market.EnterAuction(ctx) 3730 3731 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFA, "Order01", types.SideBuy, "613f", 5, 9000) 3732 o1conf, err := tm.market.SubmitOrder(ctx, o1) 3733 require.NotNil(t, o1conf) 3734 require.NoError(t, err) 3735 3736 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFA, "Order02", types.SideSell, "f9e7", 5, 9000) 3737 o2conf, err := tm.market.SubmitOrder(ctx, o2) 3738 require.NotNil(t, o2conf) 3739 require.NoError(t, err) 3740 3741 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFA, "Order03", types.SideBuy, "613f", 4, 8000) 3742 o3conf, err := tm.market.SubmitOrder(ctx, o3) 3743 require.NotNil(t, o3conf) 3744 require.NoError(t, err) 3745 3746 o4 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFA, "Order04", types.SideSell, "f9e7", 4, 8000) 3747 o4conf, err := tm.market.SubmitOrder(ctx, o4) 3748 require.NotNil(t, o4conf) 3749 require.NoError(t, err) 3750 3751 o5 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFA, "Order05", types.SideBuy, "613f", 4, 3000) 3752 o5conf, err := tm.market.SubmitOrder(ctx, o5) 3753 require.NotNil(t, o5conf) 3754 require.NoError(t, err) 3755 3756 o6 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFA, "Order06", types.SideSell, "f9e7", 3, 3000) 3757 o6conf, err := tm.market.SubmitOrder(ctx, o6) 3758 require.NotNil(t, o6conf) 3759 require.NoError(t, err) 3760 3761 o7 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order07", types.SideSell, "f9e7", 20, 0) 3762 o7.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 1000) 3763 o7conf, err := tm.market.SubmitOrder(ctx, o7) 3764 require.NotNil(t, o7conf) 3765 require.NoError(t, err) 3766 3767 o8 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFA, "Order08", types.SideSell, "613f", 5, 10001) 3768 o8conf, err := tm.market.SubmitOrder(ctx, o8) 3769 require.NotNil(t, o8conf) 3770 require.NoError(t, err) 3771 3772 o9 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFA, "Order09", types.SideBuy, "613f", 5, 15001) 3773 o9conf, err := tm.market.SubmitOrder(ctx, o9) 3774 require.NotNil(t, o9conf) 3775 require.NoError(t, err) 3776 3777 o10 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order10", types.SideBuy, "f9e7", 12, 0) 3778 o10.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 1000) 3779 o10conf, err := tm.market.SubmitOrder(ctx, o10) 3780 require.NotNil(t, o10conf) 3781 require.NoError(t, err) 3782 3783 o11 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order11", types.SideBuy, "613f", 21, 0) 3784 o11.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 2000) 3785 o11conf, err := tm.market.SubmitOrder(ctx, o11) 3786 require.NotNil(t, o11conf) 3787 require.NoError(t, err) 3788 3789 // Leave auction and uncross the book 3790 tm.market.LeaveAuctionWithIDGen(ctx, now.Add(time.Second*20), newTestIDGenerator()) 3791 require.Equal(t, 3, tm.market.GetPeggedOrderCount()) 3792 require.Equal(t, 3, tm.market.GetParkedOrderCount()) 3793 require.Equal(t, types.MarketStateActive, tm.market.State()) // still in auction 3794 3795 o12 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order12", types.SideSell, "613f", 22, 9023) 3796 o12conf, err := tm.market.SubmitOrder(ctx, o12) 3797 require.NotNil(t, o12conf) 3798 require.NoError(t, err) 3799 3800 o13 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order13", types.SideBuy, "98e1", 23, 11119) 3801 o13conf, err := tm.market.SubmitOrder(ctx, o13) 3802 require.NotNil(t, o13conf) 3803 require.NoError(t, err) 3804 3805 // This order should cause a crash 3806 o14 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order14", types.SideBuy, "qqqq", 34, 11513) 3807 o14conf, err := tm.market.SubmitOrder(ctx, o14) 3808 require.NotNil(t, o14conf) 3809 require.NoError(t, err) 3810 } 3811 3812 func TestOrderBook_Crash2599(t *testing.T) { 3813 now := time.Unix(10, 0) 3814 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 3815 Duration: 1, 3816 }) 3817 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 3818 3819 addAccount(t, tm, "A") 3820 addAccount(t, tm, "B") 3821 addAccount(t, tm, "C") 3822 addAccount(t, tm, "D") 3823 addAccount(t, tm, "E") 3824 addAccount(t, tm, "F") 3825 addAccount(t, tm, "G") 3826 auxParty := "auxParty" 3827 auxParty2 := "auxParty2" 3828 addAccount(t, tm, auxParty) 3829 addAccount(t, tm, auxParty2) 3830 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 3831 addAccountWithAmount(tm, "lpprov", 10000000) 3832 3833 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 3834 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 3835 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 3836 require.NotNil(t, conf) 3837 require.NoError(t, err) 3838 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3839 3840 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 3841 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 3842 require.NotNil(t, conf) 3843 require.NoError(t, err) 3844 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3845 auxOrders := []*types.Order{ 3846 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 11000), 3847 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 11000), 3848 } 3849 for _, o := range auxOrders { 3850 conf, err := tm.market.SubmitOrder(ctx, o) 3851 require.NoError(t, err) 3852 require.NotNil(t, conf) 3853 } 3854 lp := &types.LiquidityProvisionSubmission{ 3855 MarketID: tm.market.GetID(), 3856 CommitmentAmount: num.NewUint(27500), 3857 Fee: num.DecimalFromFloat(0.01), 3858 } 3859 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 3860 // leave opening auction 3861 now = now.Add(2 * time.Second) 3862 tm.now = now 3863 tm.market.OnTick(ctx, now) 3864 3865 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFN, "Order01", types.SideBuy, "A", 5, 11500) 3866 o1conf, err := tm.market.SubmitOrder(ctx, o1) 3867 require.NotNil(t, o1conf) 3868 require.NoError(t, err) 3869 now = now.Add(time.Second * 1) 3870 tm.now = now 3871 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3872 3873 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFN, "Order02", types.SideSell, "B", 25, 11000) 3874 o2conf, err := tm.market.SubmitOrder(ctx, o2) 3875 require.NotNil(t, o2conf) 3876 require.NoError(t, err) 3877 now = now.Add(time.Second * 1) 3878 tm.now = now 3879 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3880 3881 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFN, "Order03", types.SideBuy, "A", 10, 10500) 3882 o3conf, err := tm.market.SubmitOrder(ctx, o3) 3883 require.NotNil(t, o3conf) 3884 require.NoError(t, err) 3885 now = now.Add(time.Second * 1) 3886 tm.now = now 3887 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3888 3889 o4 := getMarketOrder(tm, now, types.OrderTypeMarket, types.OrderTimeInForceIOC, "Order04", types.SideSell, "C", 5, 0) 3890 o4conf, err := tm.market.SubmitOrder(ctx, o4) 3891 require.NotNil(t, o4conf) 3892 require.NoError(t, err) 3893 now = now.Add(time.Second * 1) 3894 tm.now = now 3895 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3896 3897 o5 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order05", types.SideBuy, "C", 35, 0) 3898 o5.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 500) 3899 o5conf, err := tm.market.SubmitOrder(ctx, o5) 3900 require.NotNil(t, o5conf) 3901 require.NoError(t, err) 3902 now = now.Add(time.Second * 1) 3903 tm.now = now 3904 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3905 3906 o6 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order06", types.SideBuy, "D", 16, 0) 3907 o6.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 2000) 3908 o6conf, err := tm.market.SubmitOrder(ctx, o6) 3909 require.NotNil(t, o6conf) 3910 require.NoError(t, err) 3911 now = now.Add(time.Second * 1) 3912 tm.now = now 3913 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3914 3915 o7 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order07", types.SideSell, "E", 19, 0) 3916 o7.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 3000) 3917 o7.ExpiresAt = now.Add(time.Second * 60).UnixNano() 3918 o7conf, err := tm.market.SubmitOrder(ctx, o7) 3919 require.NotNil(t, o7conf) 3920 require.NoError(t, err) 3921 now = now.Add(time.Second * 1) 3922 tm.now = now 3923 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3924 3925 o8 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order08", types.SideBuy, "F", 25, 10000) 3926 o8conf, err := tm.market.SubmitOrder(ctx, o8) 3927 require.NotNil(t, o8conf) 3928 require.NoError(t, err) 3929 now = now.Add(time.Second * 1) 3930 tm.now = now 3931 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3932 3933 // This one should crash 3934 o9 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order09", types.SideSell, "F", 25, 10250) 3935 o9conf, err := tm.market.SubmitOrder(ctx, o9) 3936 require.NotNil(t, o9conf) 3937 require.NoError(t, err) 3938 now = now.Add(time.Second * 1) 3939 tm.now = now 3940 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3941 3942 o10 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order10", types.SideBuy, "G", 45, 14000) 3943 o10conf, err := tm.market.SubmitOrder(ctx, o10) 3944 require.NotNil(t, o10conf) 3945 require.NoError(t, err) 3946 now = now.Add(time.Second * 1) 3947 tm.now = now 3948 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3949 3950 o11 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order11", types.SideSell, "G", 45, 8500) 3951 o11conf, err := tm.market.SubmitOrder(ctx, o11) 3952 require.NotNil(t, o11conf) 3953 require.NoError(t, err) 3954 now = now.Add(time.Second * 1) 3955 tm.now = now 3956 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 3957 } 3958 3959 func TestTriggerAfterOpeningAuction(t *testing.T) { 3960 party1 := "party1" 3961 party2 := "party2" 3962 party3 := "party3" 3963 party4 := "party4" 3964 auxParty := "auxParty" 3965 auxParty2 := "auxParty2" 3966 now := time.Unix(10, 0) 3967 closingAt := time.Unix(10000000000, 0) 3968 auctionExtensionSeconds := int64(45) 3969 openingAuctionDuration := &types.AuctionDuration{Duration: auctionExtensionSeconds} 3970 openingAuctionEndTime := now.Add(time.Duration(openingAuctionDuration.Duration) * time.Second) 3971 afterOpeningAuction := openingAuctionEndTime.Add(time.Nanosecond) 3972 pMonitorAuctionEndTime := afterOpeningAuction.Add(time.Duration(auctionExtensionSeconds) * time.Second) 3973 afterPMonitorAuction := pMonitorAuctionEndTime.Add(time.Nanosecond) 3974 pMonitorSettings := &types.PriceMonitoringSettings{ 3975 Parameters: &types.PriceMonitoringParameters{ 3976 Triggers: []*types.PriceMonitoringTrigger{ 3977 { 3978 Horizon: 60, 3979 HorizonDec: num.DecimalFromFloat(60), 3980 Probability: num.DecimalFromFloat(0.95), 3981 AuctionExtension: auctionExtensionSeconds, 3982 }, 3983 }, 3984 }, 3985 } 3986 mmu, _ := num.UintFromDecimal(MAXMOVEUP) 3987 initialPrice := uint64(100) 3988 auctionTriggeringPrice := initialPrice + 1 + mmu.Uint64() 3989 3990 tm := getTestMarket(t, now, pMonitorSettings, openingAuctionDuration) 3991 3992 addAccount(t, tm, party1) 3993 addAccount(t, tm, party2) 3994 addAccount(t, tm, party3) 3995 addAccount(t, tm, party4) 3996 addAccount(t, tm, auxParty) 3997 addAccount(t, tm, auxParty2) 3998 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 3999 addAccountWithAmount(tm, "lpprov", 10000000) 4000 4001 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Duration(auctionExtensionSeconds)*time.Second) 4002 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 4003 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 4004 require.NotNil(t, conf) 4005 require.NoError(t, err) 4006 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4007 4008 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 4009 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 4010 require.NotNil(t, conf) 4011 require.NoError(t, err) 4012 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4013 4014 gtcOrders := []*types.Order{ 4015 { 4016 Type: types.OrderTypeLimit, 4017 TimeInForce: types.OrderTimeInForceGTC, 4018 Status: types.OrderStatusActive, 4019 ID: "someid3", 4020 Side: types.SideBuy, 4021 Party: party3, 4022 MarketID: tm.market.GetID(), 4023 Size: 1, 4024 Price: num.NewUint(initialPrice - 5), 4025 Remaining: 1, 4026 CreatedAt: now.UnixNano(), 4027 ExpiresAt: closingAt.UnixNano(), 4028 Reference: "party3-buy-order-1", 4029 }, 4030 { 4031 Type: types.OrderTypeLimit, 4032 TimeInForce: types.OrderTimeInForceGTC, 4033 Status: types.OrderStatusActive, 4034 ID: "someid4", 4035 Side: types.SideSell, 4036 Party: party4, 4037 MarketID: tm.market.GetID(), 4038 Size: 1, 4039 Price: num.NewUint(initialPrice + 10), 4040 Remaining: 1, 4041 CreatedAt: now.UnixNano(), 4042 Reference: "party4-sell-order-1", 4043 }, 4044 } 4045 for _, o := range gtcOrders { 4046 conf, err := tm.market.SubmitOrder(context.Background(), o) 4047 assert.NotNil(t, conf) 4048 assert.NoError(t, err) 4049 } 4050 orderBuy1 := &types.Order{ 4051 Type: types.OrderTypeLimit, 4052 TimeInForce: types.OrderTimeInForceGTT, 4053 Status: types.OrderStatusActive, 4054 ID: "someid1", 4055 Side: types.SideBuy, 4056 Party: party1, 4057 MarketID: tm.market.GetID(), 4058 Size: 100, 4059 Price: num.NewUint(initialPrice), 4060 Remaining: 100, 4061 CreatedAt: now.UnixNano(), 4062 ExpiresAt: closingAt.UnixNano(), 4063 Reference: "party1-buy-order-1", 4064 } 4065 confirmationBuy, err := tm.market.SubmitOrder(context.Background(), orderBuy1) 4066 assert.NotNil(t, confirmationBuy) 4067 assert.NoError(t, err) 4068 4069 orderSell1 := &types.Order{ 4070 Type: types.OrderTypeLimit, 4071 TimeInForce: types.OrderTimeInForceGTC, 4072 Status: types.OrderStatusActive, 4073 ID: "someid2", 4074 Side: types.SideSell, 4075 Party: party2, 4076 MarketID: tm.market.GetID(), 4077 Size: 100, 4078 Price: num.NewUint(initialPrice), 4079 Remaining: 100, 4080 CreatedAt: now.UnixNano(), 4081 Reference: "party2-sell-order-1", 4082 } 4083 confirmationSell, err := tm.market.SubmitOrder(context.Background(), orderSell1) 4084 require.NotNil(t, confirmationSell) 4085 require.NoError(t, err) 4086 4087 require.Empty(t, confirmationSell.Trades) 4088 4089 auctionEnd := tm.market.GetMarketData().AuctionEnd 4090 require.Equal(t, openingAuctionEndTime.UnixNano(), auctionEnd) // In opening auction 4091 4092 lp := &types.LiquidityProvisionSubmission{ 4093 MarketID: tm.market.GetID(), 4094 CommitmentAmount: num.NewUint(25000), 4095 Fee: num.DecimalFromFloat(0.01), 4096 } 4097 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 4098 tm.now = afterOpeningAuction 4099 closed := tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), afterOpeningAuction) 4100 assert.False(t, closed) 4101 auctionEnd = tm.market.GetMarketData().AuctionEnd 4102 require.Equal(t, int64(0), auctionEnd) // Not in auction 4103 4104 // let's cancel the orders we had to place to end opening auction 4105 for _, o := range gtcOrders { 4106 _, err := tm.market.CancelOrder(context.Background(), o.Party, o.ID, vgcrypto.RandomHash()) 4107 assert.NoError(t, err) 4108 } 4109 orderBuy2 := &types.Order{ 4110 Type: types.OrderTypeLimit, 4111 TimeInForce: types.OrderTimeInForceGTT, 4112 Status: types.OrderStatusActive, 4113 ID: "someid3", 4114 Side: types.SideBuy, 4115 Party: party1, 4116 MarketID: tm.market.GetID(), 4117 Size: 100, 4118 Price: num.NewUint(auctionTriggeringPrice), 4119 Remaining: 100, 4120 CreatedAt: now.UnixNano(), 4121 ExpiresAt: closingAt.UnixNano(), 4122 Reference: "party1-buy-order-2", 4123 } 4124 confirmationBuy, err = tm.market.SubmitOrder(context.Background(), orderBuy2) 4125 assert.NotNil(t, confirmationBuy) 4126 assert.NoError(t, err) 4127 4128 orderSell2 := &types.Order{ 4129 Type: types.OrderTypeLimit, 4130 TimeInForce: types.OrderTimeInForceGTC, 4131 Status: types.OrderStatusActive, 4132 ID: "someid4", 4133 Side: types.SideSell, 4134 Party: party2, 4135 MarketID: tm.market.GetID(), 4136 Size: 100, 4137 Price: num.NewUint(auctionTriggeringPrice), 4138 Remaining: 100, 4139 CreatedAt: now.UnixNano(), 4140 Reference: "party2-sell-order-2", 4141 } 4142 confirmationSell, err = tm.market.SubmitOrder(context.Background(), orderSell2) 4143 require.NotNil(t, confirmationSell) 4144 require.NoError(t, err) 4145 4146 require.Empty(t, confirmationSell.Trades) 4147 4148 auctionEnd = tm.market.GetMarketData().AuctionEnd 4149 require.Equal(t, pMonitorAuctionEndTime.UnixNano(), auctionEnd) // In auction 4150 4151 tm.now = pMonitorAuctionEndTime 4152 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), pMonitorAuctionEndTime) 4153 assert.False(t, closed) 4154 4155 tm.now = afterPMonitorAuction 4156 closed = tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), afterPMonitorAuction) 4157 assert.False(t, closed) 4158 } 4159 4160 func TestOrderBook_Crash2718(t *testing.T) { 4161 now := time.Unix(10, 0) 4162 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 4163 Duration: 1, 4164 }) 4165 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4166 4167 addAccount(t, tm, "aaa") 4168 addAccount(t, tm, "bbb") 4169 auxParty := "auxParty" 4170 auxParty2 := "auxParty2" 4171 addAccount(t, tm, auxParty) 4172 addAccount(t, tm, auxParty2) 4173 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4174 addAccountWithAmount(tm, "lpprov", 10000000) 4175 4176 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 4177 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 4178 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 4179 require.NotNil(t, conf) 4180 require.NoError(t, err) 4181 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4182 4183 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 4184 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 4185 require.NotNil(t, conf) 4186 require.NoError(t, err) 4187 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4188 4189 auxOrders := []*types.Order{ 4190 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 4191 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 4192 } 4193 for _, o := range auxOrders { 4194 conf, err := tm.market.SubmitOrder(ctx, o) 4195 require.NoError(t, err) 4196 require.NotNil(t, conf) 4197 } 4198 lp := &types.LiquidityProvisionSubmission{ 4199 MarketID: tm.market.GetID(), 4200 CommitmentAmount: num.NewUint(5000), 4201 Fee: num.DecimalFromFloat(0.01), 4202 } 4203 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 4204 // leave opening auction 4205 now = now.Add(2 * time.Second) 4206 tm.now = now 4207 tm.market.OnTick(ctx, now) 4208 4209 // We start in continuous trading, create order to set best bid 4210 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "aaa", 1, 100) 4211 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4212 require.NotNil(t, o1conf) 4213 require.NoError(t, err) 4214 now = now.Add(time.Second * 1) 4215 tm.now = now 4216 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 4217 4218 // Now the pegged order which will be live 4219 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideBuy, "bbb", 1, 0) 4220 o2.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 10) 4221 o2conf, err := tm.market.SubmitOrder(ctx, o2) 4222 require.NotNil(t, o2conf) 4223 require.NoError(t, err) 4224 now = now.Add(time.Second * 1) 4225 tm.now = now 4226 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 4227 assert.Equal(t, types.OrderStatusActive, o2.Status, o2.Status.String()) 4228 assert.Equal(t, num.NewUint(90), o2.Price) 4229 4230 // Force the pegged order to reprice 4231 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order03", types.SideBuy, "aaa", 1, 110) 4232 o3conf, err := tm.market.SubmitOrder(ctx, o3) 4233 require.NotNil(t, o3conf) 4234 require.NoError(t, err) 4235 now = now.Add(time.Second * 1) 4236 tm.now = now 4237 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 4238 4239 o2Update := tm.lastOrderUpdate(o2.ID) 4240 assert.Equal(t, types.OrderStatusActive, o2Update.Status) 4241 assert.Equal(t, num.NewUint(100), o2Update.Price) 4242 4243 // Flip to auction so the pegged order will be parked 4244 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 4245 tm.mas.AuctionStarted(ctx, now) 4246 tm.market.EnterAuction(ctx) 4247 o2Update = tm.lastOrderUpdate(o2.ID) 4248 assert.Equal(t, types.OrderStatusParked, o2Update.Status) 4249 assert.True(t, o2Update.Price.IsZero()) 4250 4251 // Flip out of auction to un-park it 4252 tm.market.LeaveAuctionWithIDGen(ctx, now.Add(time.Second*20), newTestIDGenerator()) 4253 tm.market.LeaveAuctionWithIDGen(ctx, now.Add(time.Second*20), newTestIDGenerator()) 4254 4255 o2Update = tm.lastOrderUpdate(o2.ID) 4256 assert.Equal(t, types.OrderStatusActive, o2Update.Status) 4257 assert.Equal(t, num.NewUint(100), o2Update.Price) 4258 } 4259 4260 func TestOrderBook_AmendPriceInParkedOrder(t *testing.T) { 4261 now := time.Unix(10, 0) 4262 tm := getTestMarket(t, now, nil, nil) 4263 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4264 4265 addAccount(t, tm, "aaa") 4266 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4267 4268 // Create a parked pegged order 4269 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "aaa", 1, 0) 4270 o1.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 10) 4271 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4272 tm.now = tm.now.Add(time.Second) 4273 tm.market.OnTick(ctx, tm.now) 4274 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 4275 require.NotNil(t, o1conf) 4276 require.NoError(t, err) 4277 tm.now = tm.now.Add(time.Second) 4278 tm.market.OnTick(ctx, tm.now) 4279 assert.Equal(t, types.OrderStatusParked, o1.Status) 4280 assert.True(t, o1.Price.IsZero()) 4281 4282 // Try to amend the price 4283 amendment := &types.OrderAmendment{ 4284 OrderID: o1.ID, 4285 Price: num.NewUint(200), 4286 } 4287 4288 // This should fail as we cannot amend a pegged order price 4289 amendConf, err := tm.market.AmendOrder(ctx, amendment, "aaa", vgcrypto.RandomHash()) 4290 tm.now = tm.now.Add(time.Second) 4291 tm.market.OnTick(ctx, tm.now) 4292 require.Nil(t, amendConf) 4293 require.Error(t, types.OrderErrorUnableToAmendPriceOnPeggedOrder, err) 4294 } 4295 4296 func TestOrderBook_ExpiredOrderTriggersReprice(t *testing.T) { 4297 now := time.Unix(10, 0) 4298 tm := getTestMarket(t, now, nil, nil) 4299 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4300 4301 addAccount(t, tm, "aaa") 4302 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4303 4304 // Create an expiring order 4305 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order01", types.SideBuy, "aaa", 1, 10) 4306 o1.ExpiresAt = now.Add(5 * time.Second).UnixNano() 4307 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4308 require.NotNil(t, o1conf) 4309 require.NoError(t, err) 4310 4311 // Create a pegged order that references its price 4312 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideBuy, "aaa", 1, 0) 4313 o2.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 2) 4314 o2conf, err := tm.market.SubmitOrder(ctx, o2) 4315 require.NotNil(t, o2conf) 4316 require.NoError(t, err) 4317 4318 // Move the clock forward to expire the first order 4319 now = now.Add(time.Second * 10) 4320 tm.now = now 4321 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 4322 4323 t.Run("order is parked", func(t *testing.T) { 4324 // First collect all the orders events 4325 found := map[string]*proto.Order{} 4326 for _, e := range tm.events { 4327 switch evt := e.(type) { 4328 case *events.Order: 4329 found[evt.Order().Id] = evt.Order() 4330 case *events.ExpiredOrders: 4331 for _, oid := range evt.OrderIDs() { 4332 found[oid] = &proto.Order{ 4333 Status: types.OrderStatusExpired, 4334 } 4335 } 4336 } 4337 } 4338 4339 require.Len(t, found, 2) 4340 4341 expects := map[string]types.OrderStatus{ 4342 o1.ID: types.OrderStatusExpired, 4343 o2.ID: types.OrderStatusParked, 4344 } 4345 4346 for id, v := range found { 4347 require.Equal(t, expects[id], v.Status) 4348 } 4349 }) 4350 } 4351 4352 // This is a scenario to test issue: 2734 4353 // Party A - 100000000 4354 // 4355 // A - Buy 5@15000 GTC 4356 // 4357 // Party B - 100000000 4358 // 4359 // B - Sell 10 IOC Market 4360 // 4361 // Party C - Deposit 100000 4362 // 4363 // C - Buy GTT 6@1001 (60s) 4364 // 4365 // Party D- Fund 578 4366 // 4367 // D - Pegged 3@BA +1 4368 // 4369 // Party E - Deposit 100000 4370 // 4371 // E - Sell GTC 3@1002 4372 // 4373 // C amends order price=1002. 4374 func TestOrderBook_CrashWithDistressedPartyPeggedOrderNotRemovedFromPeggedList2734(t *testing.T) { 4375 now := time.Unix(10, 0) 4376 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 4377 Duration: 1, 4378 }) 4379 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4380 4381 addAccountWithAmount(tm, "party-A", 100000000) 4382 addAccountWithAmount(tm, "party-B", 100000000) 4383 addAccountWithAmount(tm, "party-C", 100000) 4384 addAccountWithAmount(tm, "party-D", 578) 4385 addAccountWithAmount(tm, "party-E", 100000) 4386 auxParty := "auxParty" 4387 auxParty2 := "auxParty2" 4388 addAccount(t, tm, auxParty) 4389 addAccount(t, tm, auxParty2) 4390 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4391 addAccountWithAmount(tm, "lpprov", 10000000) 4392 4393 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 4394 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 4395 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 4396 require.NotNil(t, conf) 4397 require.NoError(t, err) 4398 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4399 4400 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000) 4401 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 4402 require.NotNil(t, conf) 4403 require.NoError(t, err) 4404 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4405 auxOrders := []*types.Order{ 4406 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 1000), 4407 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 1000), 4408 } 4409 for _, o := range auxOrders { 4410 conf, err := tm.market.SubmitOrder(ctx, o) 4411 require.NoError(t, err) 4412 require.NotNil(t, conf) 4413 } 4414 // leave opening auction 4415 lp := &types.LiquidityProvisionSubmission{ 4416 MarketID: tm.market.GetID(), 4417 CommitmentAmount: num.NewUint(5000), 4418 Fee: num.DecimalFromFloat(0.01), 4419 } 4420 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 4421 now = now.Add(2 * time.Second) 4422 tm.now = now 4423 tm.market.OnTick(ctx, now) 4424 4425 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-A", 5, 15000) 4426 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4427 require.NotNil(t, o1conf) 4428 require.NoError(t, err) 4429 4430 o2 := getMarketOrder(tm, now, types.OrderTypeMarket, types.OrderTimeInForceIOC, "Order02", types.SideSell, "party-B", 10, 0) 4431 o2conf, err := tm.market.SubmitOrder(ctx, o2) 4432 require.NotNil(t, o2conf) 4433 require.NoError(t, err) 4434 tm.now = tm.now.Add(time.Second) 4435 tm.market.OnTick(ctx, tm.now) 4436 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 4437 4438 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order03", types.SideBuy, "party-C", 6, 1001) 4439 o3.ExpiresAt = now.Add(60 * time.Second).UnixNano() 4440 o3conf, err := tm.market.SubmitOrder(ctx, o3) 4441 require.NotNil(t, o3conf) 4442 require.NoError(t, err) 4443 4444 o4 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order04", types.SideSell, "party-D", 3, 0) 4445 o4.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 1) 4446 o4conf, err := tm.market.SubmitOrder(ctx, o4) 4447 require.Nil(t, o4conf) 4448 require.Error(t, err) 4449 4450 o5 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order05", types.SideSell, "party-E", 3, 1002) 4451 o5conf, err := tm.market.SubmitOrder(ctx, o5) 4452 require.NotNil(t, o5conf) 4453 require.NoError(t, err) 4454 4455 // Try to amend the price 4456 amendment := &types.OrderAmendment{ 4457 OrderID: o3.ID, 4458 Price: num.NewUint(1002), 4459 } 4460 4461 amendConf, err := tm.market.AmendOrder(ctx, amendment, "party-C", vgcrypto.RandomHash()) 4462 require.NotNil(t, amendConf) 4463 require.NoError(t, err) 4464 4465 // nothing to do we just expect no crash. 4466 } 4467 4468 func TestOrderBook_Crash2733(t *testing.T) { 4469 now := time.Unix(10, 0) 4470 tm := getTestMarket(t, now, nil, &types.AuctionDuration{Duration: 30}) 4471 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4472 4473 addAccountWithAmount(tm, "party-A", 1000000) 4474 addAccountWithAmount(tm, "party-B", 1000000) 4475 addAccountWithAmount(tm, "party-C", 100000000) 4476 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4477 4478 for i := 1; i <= 10; i++ { 4479 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, fmt.Sprintf("Order1%v", i), types.SideBuy, "party-A", uint64(i), 0) 4480 o1.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, uint64(i*15)) 4481 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4482 require.NotNil(t, o1conf) 4483 require.NoError(t, err) 4484 4485 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, fmt.Sprintf("Order2%v", i), types.SideSell, "party-A", uint64(i), 0) 4486 o2.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, uint64(i*10)) 4487 o2conf, err := tm.market.SubmitOrder(ctx, o2) 4488 require.NotNil(t, o2conf) 4489 require.NoError(t, err) 4490 4491 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, fmt.Sprintf("Order3%v", i), types.SideBuy, "party-A", uint64(i), 0) 4492 o3.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, uint64(i*5)) 4493 o3conf, err := tm.market.SubmitOrder(ctx, o3) 4494 require.NotNil(t, o3conf) 4495 require.NoError(t, err) 4496 } 4497 4498 // now move time to after auction 4499 now = now.Add(31 * time.Second) 4500 tm.now = now 4501 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 4502 4503 for i := 1; i <= 10; i++ { 4504 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, fmt.Sprintf("Order4%v", i), types.SideSell, "party-B", uint64(i), uint64(i*150)) 4505 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4506 require.NotNil(t, o1conf) 4507 require.NoError(t, err) 4508 } 4509 4510 for i := 1; i <= 20; i++ { 4511 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, fmt.Sprintf("Order5%v", i), types.SideBuy, "party-C", uint64(i), uint64(i*100)) 4512 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4513 require.NotNil(t, o1conf) 4514 require.NoError(t, err) 4515 } 4516 } 4517 4518 func TestOrderBook_Bug2747(t *testing.T) { 4519 now := time.Unix(10, 0) 4520 tm := getTestMarket(t, now, nil, nil) 4521 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4522 4523 addAccountWithAmount(tm, "party-A", 100000000) 4524 addAccountWithAmount(tm, "party-B", 100000000) 4525 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4526 4527 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-A", 100, 0) 4528 o1.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 15) 4529 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4530 tm.now = tm.now.Add(time.Second) 4531 tm.market.OnTick(ctx, tm.now) 4532 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 4533 require.NotNil(t, o1conf) 4534 require.NoError(t, err) 4535 4536 // Try to amend the price 4537 amendment := &types.OrderAmendment{ 4538 OrderID: o1.ID, 4539 PeggedOffset: num.NewUint(20), 4540 PeggedReference: types.PeggedReferenceBestAsk, 4541 } 4542 amendConf, err := tm.market.AmendOrder(ctx, amendment, "party-A", vgcrypto.RandomHash()) 4543 assert.Nil(t, amendConf) 4544 assert.EqualError(t, err, "OrderError: buy cannot reference best ask price") 4545 } 4546 4547 func TestOrderBook_AmendTIME_IN_FORCEForPeggedOrder(t *testing.T) { 4548 now := time.Unix(10, 0) 4549 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 4550 Duration: 1, 4551 }) 4552 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4553 4554 addAccount(t, tm, "aaa") 4555 auxParty := "auxParty" 4556 auxParty2 := "auxParty2" 4557 addAccount(t, tm, auxParty) 4558 addAccount(t, tm, auxParty2) 4559 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4560 addAccountWithAmount(tm, "lpprov", 10000000) 4561 4562 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 4563 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 4564 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 4565 require.NotNil(t, conf) 4566 require.NoError(t, err) 4567 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4568 4569 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000) 4570 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 4571 require.NotNil(t, conf) 4572 require.NoError(t, err) 4573 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4574 auxOrders := []*types.Order{ 4575 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 4576 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 4577 } 4578 for _, o := range auxOrders { 4579 conf, err := tm.market.SubmitOrder(ctx, o) 4580 require.NoError(t, err) 4581 require.NotNil(t, conf) 4582 } 4583 lp := &types.LiquidityProvisionSubmission{ 4584 MarketID: tm.market.GetID(), 4585 CommitmentAmount: num.NewUint(5000), 4586 Fee: num.DecimalFromFloat(0.01), 4587 } 4588 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 4589 // leave opening auction 4590 now = now.Add(time.Second * 2) 4591 tm.now = now 4592 tm.market.OnTick(ctx, now) 4593 // Create a normal order to set a BB price 4594 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "aaa", 1, 10) 4595 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4596 require.NotNil(t, o1conf) 4597 require.NoError(t, err) 4598 4599 // Create a pegged order that references the BB price with an expiry time 4600 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order02", types.SideBuy, "aaa", 1, 0) 4601 o2.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 2) 4602 o2.ExpiresAt = now.Add(5 * time.Second).UnixNano() 4603 o2conf, err := tm.market.SubmitOrder(ctx, o2) 4604 require.NotNil(t, o2conf) 4605 require.NoError(t, err) 4606 4607 // Amend the pegged order from GTT to GTC 4608 amendment := &types.OrderAmendment{ 4609 OrderID: o2.ID, 4610 TimeInForce: types.OrderTimeInForceGTC, 4611 } 4612 4613 amendConf, err := tm.market.AmendOrder(ctx, amendment, "aaa", vgcrypto.RandomHash()) 4614 require.NotNil(t, amendConf) 4615 require.NoError(t, err) 4616 assert.Equal(t, types.OrderStatusActive, o2.Status) 4617 4618 // Move the clock forward to expire any old orders 4619 now = now.Add(time.Second * 10) 4620 tm.now = now 4621 tm.events = nil 4622 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 4623 t.Run("no orders expired", func(t *testing.T) { 4624 // First collect all the orders events 4625 orders := []*types.Order{} 4626 for _, e := range tm.events { 4627 switch evt := e.(type) { 4628 case *events.Order: 4629 if evt.Order().Status == types.OrderStatusExpired { 4630 orders = append(orders, mustOrderFromProto(evt.Order())) 4631 } 4632 } 4633 } 4634 require.Equal(t, 0, len(orders)) 4635 }) 4636 4637 // The pegged order should not be expired 4638 assert.Equal(t, types.OrderStatusActive.String(), o2.Status.String()) 4639 assert.Equal(t, 0, tm.market.GetPeggedExpiryOrderCount()) 4640 } 4641 4642 func TestOrderBook_AmendTIME_IN_FORCEForPeggedOrder2(t *testing.T) { 4643 now := time.Unix(10, 0) 4644 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 4645 Duration: 1, 4646 }) 4647 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4648 4649 addAccount(t, tm, "aaa") 4650 auxParty := "auxParty" 4651 auxParty2 := "auxParty2" 4652 addAccount(t, tm, auxParty) 4653 addAccount(t, tm, auxParty2) 4654 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4655 addAccountWithAmount(tm, "lpprov", 10000000) 4656 4657 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 4658 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 4659 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 4660 require.NotNil(t, conf) 4661 require.NoError(t, err) 4662 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4663 4664 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000) 4665 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 4666 require.NotNil(t, conf) 4667 require.NoError(t, err) 4668 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4669 4670 auxOrders := []*types.Order{ 4671 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 4672 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 4673 } 4674 for _, o := range auxOrders { 4675 conf, err := tm.market.SubmitOrder(ctx, o) 4676 require.NoError(t, err) 4677 require.NotNil(t, conf) 4678 } 4679 lp := &types.LiquidityProvisionSubmission{ 4680 MarketID: tm.market.GetID(), 4681 CommitmentAmount: num.NewUint(5000), 4682 Fee: num.DecimalFromFloat(0.01), 4683 } 4684 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 4685 // leave opening auction 4686 now = now.Add(2 * time.Second) 4687 tm.now = now 4688 tm.market.OnTick(ctx, now) 4689 4690 // Create a normal order to set a BB price 4691 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "aaa", 1, 10) 4692 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4693 require.NotNil(t, o1conf) 4694 require.NoError(t, err) 4695 4696 // Create a pegged order that references the BB price 4697 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideBuy, "aaa", 1, 0) 4698 o2.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 2) 4699 o2conf, err := tm.market.SubmitOrder(ctx, o2) 4700 require.NotNil(t, o2conf) 4701 require.NoError(t, err) 4702 4703 exp := now.Add(5 * time.Second).UnixNano() 4704 // Amend the pegged order so that it has an expiry 4705 amendment := &types.OrderAmendment{ 4706 OrderID: o2.ID, 4707 TimeInForce: types.OrderTimeInForceGTT, 4708 ExpiresAt: &exp, 4709 } 4710 4711 amendConf, err := tm.market.AmendOrder(ctx, amendment, "aaa", vgcrypto.RandomHash()) 4712 require.NotNil(t, amendConf) 4713 require.NoError(t, err) 4714 assert.Equal(t, types.OrderStatusActive, o2.Status) 4715 assert.Equal(t, 1, tm.market.GetPeggedExpiryOrderCount()) 4716 4717 // Move the clock forward to expire any old orders 4718 now = now.Add(time.Second * 10) 4719 tm.now = now 4720 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 4721 t.Run("1 order expired", func(t *testing.T) { 4722 // First collect all the orders events 4723 orders := []*types.Order{} 4724 for _, e := range tm.events { 4725 switch evt := e.(type) { 4726 case *events.ExpiredOrders: 4727 for _, oid := range evt.OrderIDs() { 4728 orders = append(orders, &types.Order{ 4729 ID: oid, 4730 }) 4731 } 4732 } 4733 } 4734 require.Equal(t, 1, len(orders)) 4735 assert.Equal(t, orders[0].ID, o2.ID) 4736 }) 4737 4738 assert.Equal(t, 0, tm.market.GetPeggedExpiryOrderCount()) 4739 } 4740 4741 func TestOrderBook_AmendFilledWithActiveStatus2736(t *testing.T) { 4742 now := time.Unix(10, 0) 4743 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 4744 Duration: 1, 4745 }) 4746 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4747 4748 addAccount(t, tm, "party-A") 4749 addAccount(t, tm, "party-B") 4750 auxParty := "auxParty" 4751 auxParty2 := "auxParty2" 4752 addAccount(t, tm, auxParty) 4753 addAccount(t, tm, auxParty2) 4754 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4755 addAccountWithAmount(tm, "lpprov", 10000000) 4756 4757 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 4758 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 4759 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 4760 require.NotNil(t, conf) 4761 require.NoError(t, err) 4762 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4763 4764 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000) 4765 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 4766 require.NotNil(t, conf) 4767 require.NoError(t, err) 4768 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4769 4770 auxOrders := []*types.Order{ 4771 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 5000), 4772 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 5000), 4773 } 4774 for _, o := range auxOrders { 4775 conf, err := tm.market.SubmitOrder(ctx, o) 4776 require.NoError(t, err) 4777 require.NotNil(t, conf) 4778 } 4779 lp := &types.LiquidityProvisionSubmission{ 4780 MarketID: tm.market.GetID(), 4781 CommitmentAmount: num.NewUint(25000), 4782 Fee: num.DecimalFromFloat(0.01), 4783 } 4784 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 4785 // leave opening auction 4786 now = now.Add(2 * time.Second) 4787 tm.now = now 4788 tm.market.OnTick(ctx, now) 4789 require.Equal(t, types.MarketTradingModeContinuous, tm.market.GetMarketData().MarketTradingMode) 4790 4791 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideSell, "party-A", 5, 5000) 4792 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4793 assert.NotNil(t, o1conf) 4794 assert.NoError(t, err) 4795 4796 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideBuy, "party-B", 5, 4500) 4797 o2conf, err := tm.market.SubmitOrder(ctx, o2) 4798 assert.NotNil(t, o2conf) 4799 assert.NoError(t, err) 4800 4801 // Amend the pegged order so that it has an expiry 4802 amendment := &types.OrderAmendment{ 4803 OrderID: o2.ID, 4804 Price: num.NewUint(5000), 4805 } 4806 4807 amendConf, err := tm.market.AmendOrder(ctx, amendment, "party-B", vgcrypto.RandomHash()) 4808 assert.NotNil(t, amendConf) 4809 assert.NoError(t, err) 4810 o2Update := tm.lastOrderUpdate(o2.ID) 4811 assert.Equal(t, types.OrderStatusFilled, o2Update.Status, o2Update.Status.String()) 4812 } 4813 4814 func TestOrderBook_PeggedOrderReprice2748(t *testing.T) { 4815 now := time.Unix(10, 0) 4816 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 4817 Duration: 1, 4818 }) 4819 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4820 4821 addAccountWithAmount(tm, "party-A", 100000000) 4822 addAccountWithAmount(tm, "party-B", 100000000) 4823 addAccountWithAmount(tm, "party-C", 100000000) 4824 auxParty, auxParty2 := "aux1", "aux2" 4825 addAccount(t, tm, auxParty) 4826 addAccount(t, tm, auxParty2) 4827 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4828 addAccountWithAmount(tm, "lpprov", 10000000) 4829 4830 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 4831 auxOrders := []*types.Order{ 4832 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideBuy, auxParty, 1, 1), 4833 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 10000), 4834 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 5000), 4835 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 5000), 4836 } 4837 for _, o := range auxOrders { 4838 conf, err := tm.market.SubmitOrder(ctx, o) 4839 require.NoError(t, err) 4840 require.NotNil(t, conf) 4841 } 4842 lp := &types.LiquidityProvisionSubmission{ 4843 MarketID: tm.market.GetID(), 4844 CommitmentAmount: num.NewUint(12500), 4845 Fee: num.DecimalFromFloat(0.01), 4846 } 4847 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 4848 // leave opening auction 4849 now = now.Add(2 * time.Second) 4850 tm.now = now 4851 tm.market.OnTick(ctx, now) 4852 // set the mid-price first to 6.5k 4853 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-A", 5, 6000) 4854 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4855 require.NotNil(t, o1conf) 4856 require.NoError(t, err) 4857 4858 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideSell, "party-B", 5, 7000) 4859 o2conf, err := tm.market.SubmitOrder(ctx, o2) 4860 require.NotNil(t, o2conf) 4861 require.NoError(t, err) 4862 4863 // then place pegged order 4864 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order03", types.SideBuy, "party-C", 100, 0) 4865 o3.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 15) 4866 o3conf, err := tm.market.SubmitOrder(ctx, o3) 4867 require.NotNil(t, o3conf) 4868 require.NoError(t, err) 4869 4870 assert.Equal(t, o3conf.Order.Status, types.OrderStatusActive) 4871 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 4872 4873 // then 4874 // Amend the pegged order so that it has an expiry 4875 amendment := &types.OrderAmendment{ 4876 OrderID: o3.ID, 4877 PeggedOffset: num.NewUint(6500), 4878 } 4879 4880 amendConf, err := tm.market.AmendOrder(ctx, amendment, "party-C", vgcrypto.RandomHash()) 4881 require.NotNil(t, amendConf) 4882 require.NoError(t, err) 4883 4884 assert.Equal(t, amendConf.Order.Status, types.OrderStatusParked) 4885 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 4886 } 4887 4888 func TestOrderBook_AmendGFNToGTCOrGTTNotAllowed2486(t *testing.T) { 4889 now := time.Unix(10, 0) 4890 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 4891 Duration: 1, 4892 }) 4893 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4894 4895 addAccountWithAmount(tm, "party-A", 100000000) 4896 auxParty := "auxParty" 4897 auxParty2 := "auxParty2" 4898 addAccount(t, tm, auxParty) 4899 addAccount(t, tm, auxParty2) 4900 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4901 addAccountWithAmount(tm, "lpprov", 10000000) 4902 4903 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 4904 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 4905 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 4906 require.NotNil(t, conf) 4907 require.NoError(t, err) 4908 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4909 4910 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000) 4911 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 4912 require.NotNil(t, conf) 4913 require.NoError(t, err) 4914 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 4915 4916 auxOrders := []*types.Order{ 4917 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 6000), 4918 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 6000), 4919 } 4920 for _, o := range auxOrders { 4921 conf, err := tm.market.SubmitOrder(ctx, o) 4922 require.NoError(t, err) 4923 require.NotNil(t, conf) 4924 } 4925 lp := &types.LiquidityProvisionSubmission{ 4926 MarketID: tm.market.GetID(), 4927 CommitmentAmount: num.NewUint(25000), 4928 Fee: num.DecimalFromFloat(0.01), 4929 } 4930 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 4931 // leave opening auction 4932 now = now.Add(2 * time.Second) 4933 tm.now = now 4934 tm.market.OnTick(ctx, now) 4935 4936 // set the mid-price first to 6.5k 4937 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFN, "Order01", types.SideBuy, "party-A", 5, 6000) 4938 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4939 require.NotNil(t, o1conf) 4940 require.NoError(t, err) 4941 4942 // then 4943 // Amend the pegged order so that it has an expiry 4944 amendment := &types.OrderAmendment{ 4945 OrderID: o1.ID, 4946 TimeInForce: types.OrderTimeInForceGTC, 4947 } 4948 4949 amendConf, err := tm.market.AmendOrder(ctx, amendment, "party-A", vgcrypto.RandomHash()) 4950 assert.Nil(t, amendConf) 4951 assert.EqualError(t, err, "OrderError: Cannot amend TIF from GFA or GFN") 4952 } 4953 4954 func TestOrderBook_CancelAll2771(t *testing.T) { 4955 now := time.Unix(10, 0) 4956 tm := getTestMarket(t, now, nil, nil) 4957 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4958 4959 addAccountWithAmount(tm, "party-A", 100000000) 4960 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4961 4962 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideSell, "party-A", 1, 0) 4963 o1.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 10) 4964 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4965 require.NotNil(t, o1conf) 4966 require.NoError(t, err) 4967 tm.now = tm.now.Add(time.Second) 4968 tm.market.OnTick(ctx, tm.now) 4969 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 4970 assert.Equal(t, o1conf.Order.Status, types.OrderStatusParked) 4971 4972 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideSell, "party-A", 1, 0) 4973 o2.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 10) 4974 o2conf, err := tm.market.SubmitOrder(ctx, o2) 4975 require.NotNil(t, o2conf) 4976 require.NoError(t, err) 4977 assert.Equal(t, o2conf.Order.Status, types.OrderStatusParked) 4978 4979 confs, err := tm.market.CancelAllOrders(ctx, "party-A") 4980 assert.NoError(t, err) 4981 assert.Len(t, confs, 2) 4982 } 4983 4984 func TestOrderBook_RejectAmendPriceOnPeggedOrder2658(t *testing.T) { 4985 now := time.Unix(10, 0) 4986 tm := getTestMarket(t, now, nil, nil) 4987 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 4988 4989 addAccount(t, tm, "party-A") 4990 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 4991 4992 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-A", 5, 5000) 4993 o1.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 10) 4994 o1conf, err := tm.market.SubmitOrder(ctx, o1) 4995 assert.NotNil(t, o1conf) 4996 assert.NoError(t, err) 4997 tm.now = tm.now.Add(time.Second) 4998 tm.market.OnTick(ctx, tm.now) 4999 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 5000 5001 // Try to amend the price 5002 amendment := &types.OrderAmendment{ 5003 OrderID: o1.ID, 5004 Price: num.NewUint(4000), 5005 SizeDelta: 10, 5006 } 5007 5008 amendConf, err := tm.market.AmendOrder(ctx, amendment, "party-A", vgcrypto.RandomHash()) 5009 assert.Nil(t, amendConf) 5010 assert.Error(t, types.OrderErrorUnableToAmendPriceOnPeggedOrder, err) 5011 assert.Equal(t, types.OrderStatusParked, o1.Status) 5012 assert.Equal(t, uint64(1), o1.Version) 5013 } 5014 5015 func TestOrderBook_AmendToCancelForceReprice(t *testing.T) { 5016 now := time.Unix(10, 0) 5017 tm := getTestMarket(t, now, nil, nil) 5018 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5019 5020 addAccount(t, tm, "party-A") 5021 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 5022 5023 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideSell, "party-A", 1, 5000) 5024 o1conf, err := tm.market.SubmitOrder(ctx, o1) 5025 assert.NotNil(t, o1conf) 5026 assert.NoError(t, err) 5027 tm.now = tm.now.Add(time.Second) 5028 tm.market.OnTick(ctx, tm.now) 5029 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 5030 5031 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideSell, "party-A", 1, 0) 5032 o2.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 10) 5033 o2conf, err := tm.market.SubmitOrder(ctx, o2) 5034 assert.NotNil(t, o2conf) 5035 assert.NoError(t, err) 5036 5037 // Try to amend the price 5038 amendment := &types.OrderAmendment{ 5039 OrderID: o1.ID, 5040 SizeDelta: -1, 5041 } 5042 5043 amendConf, err := tm.market.AmendOrder(ctx, amendment, "party-A", vgcrypto.RandomHash()) 5044 assert.NotNil(t, amendConf) 5045 assert.NoError(t, err) 5046 5047 assert.Equal(t, types.OrderStatusActive, o2.Status) 5048 o1Update := tm.lastOrderUpdate(o1.ID) 5049 assert.Equal(t, types.OrderStatusCancelled, o1Update.Status) 5050 } 5051 5052 func TestOrderBook_AmendExpPersistParkPeggedOrder(t *testing.T) { 5053 now := time.Unix(10, 0) 5054 tm := getTestMarket(t, now, nil, nil) 5055 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5056 5057 addAccount(t, tm, "party-A") 5058 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 5059 5060 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideSell, "party-A", 10, 4550) 5061 o1conf, err := tm.market.SubmitOrder(ctx, o1) 5062 assert.NotNil(t, o1conf) 5063 assert.NoError(t, err) 5064 tm.now = tm.now.Add(time.Second) 5065 tm.market.OnTick(ctx, tm.now) 5066 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 5067 5068 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideSell, "party-A", 105, 0) 5069 o2.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 100) 5070 o2conf, err := tm.market.SubmitOrder(ctx, o2) 5071 assert.NotNil(t, o2conf) 5072 assert.NoError(t, err) 5073 5074 // Try to amend the price 5075 amendment := &types.OrderAmendment{ 5076 OrderID: o1.ID, 5077 SizeDelta: -10, 5078 } 5079 5080 amendConf, err := tm.market.AmendOrder(ctx, amendment, "party-A", vgcrypto.RandomHash()) 5081 assert.NotNil(t, amendConf) 5082 assert.NoError(t, err) 5083 assert.Equal(t, types.OrderStatusActive, o2.Status) 5084 assert.False(t, o2.Price.IsZero()) 5085 o1Update := tm.lastOrderUpdate(o1.ID) 5086 assert.Equal(t, types.OrderStatusCancelled, o1Update.Status) 5087 } 5088 5089 // This test is to make sure when we move into a price monitoring auction that we 5090 // do not allow the parked orders to be repriced. 5091 func TestOrderBook_ParkPeggedOrderWhenMovingToAuction(t *testing.T) { 5092 now := time.Unix(10, 0) 5093 tm := getTestMarket2(t, now, nil, &types.AuctionDuration{ 5094 Duration: 1, 5095 // increase lpRange so that LP orders don't get pushed too close to MID and test can behave as expected 5096 }, true, 1.01) 5097 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5098 5099 addAccount(t, tm, "party-A") 5100 auxParty := "auxParty" 5101 auxParty2 := "auxParty2" 5102 addAccount(t, tm, auxParty) 5103 addAccount(t, tm, auxParty2) 5104 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 5105 addAccountWithAmount(tm, "lpprov", 10000000) 5106 5107 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 5108 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 5109 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 5110 require.NotNil(t, conf) 5111 require.NoError(t, err) 5112 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 5113 5114 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000) 5115 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 5116 require.NotNil(t, conf) 5117 require.NoError(t, err) 5118 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 5119 5120 auxOrders := []*types.Order{ 5121 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 1000), 5122 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 1000), 5123 } 5124 for _, o := range auxOrders { 5125 conf, err := tm.market.SubmitOrder(ctx, o) 5126 require.NoError(t, err) 5127 require.NotNil(t, conf) 5128 } 5129 lp := &types.LiquidityProvisionSubmission{ 5130 MarketID: tm.market.GetID(), 5131 CommitmentAmount: num.NewUint(25000), 5132 Fee: num.DecimalFromFloat(0.01), 5133 } 5134 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 5135 // leave opening auction 5136 now = now.Add(2 * time.Second) 5137 tm.now = now 5138 tm.market.OnTick(ctx, now) 5139 5140 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFN, "Order01", types.SideSell, "party-A", 10, 1010) 5141 o1conf, err := tm.market.SubmitOrder(ctx, o1) 5142 require.NotNil(t, o1conf) 5143 require.NoError(t, err) 5144 5145 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGFN, "Order02", types.SideBuy, "party-A", 10, 990) 5146 o2conf, err := tm.market.SubmitOrder(ctx, o2) 5147 require.NotNil(t, o2conf) 5148 require.NoError(t, err) 5149 5150 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "PeggyWeggy", types.SideSell, "party-A", 10, 0) 5151 o3.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 100) 5152 o3conf, err := tm.market.SubmitOrder(ctx, o3) 5153 require.NotNil(t, o3conf) 5154 require.NoError(t, err) 5155 assert.Equal(t, int64(5), tm.market.GetOrdersOnBookCount()) 5156 5157 // Move into a price monitoring auction so that the pegged orders are parked and the other orders are cancelled 5158 tm.market.StartPriceAuction(now) 5159 tm.market.EnterAuction(ctx) 5160 require.Equal(t, types.MarketStateSuspended, tm.market.State()) // enter auction 5161 5162 require.Equal(t, 1, tm.market.GetPeggedOrderCount()) 5163 require.Equal(t, 1, tm.market.GetParkedOrderCount()) 5164 assert.Equal(t, int64(2), tm.market.GetOrdersOnBookCount()) 5165 } 5166 5167 func TestMarket_LeaveAuctionRepricePeggedOrdersShouldFailIfNoMargin(t *testing.T) { 5168 now := time.Unix(10, 0) 5169 tm := getTestMarket(t, now, nil, nil) 5170 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5171 5172 // Create a new party account with very little funding 5173 addAccountWithAmount(tm, "party-C", 1) 5174 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 5175 5176 // Start the opening auction 5177 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 5178 tm.mas.AuctionStarted(ctx, now) 5179 tm.market.EnterAuction(ctx) 5180 5181 lps := &types.LiquidityProvisionSubmission{ 5182 Fee: num.DecimalFromFloat(0.01), 5183 MarketID: tm.market.GetID(), 5184 CommitmentAmount: num.NewUint(1000000000), 5185 } 5186 5187 // Because we do not have enough funds to support our commitment level, we should reject this call 5188 err := tm.market.SubmitLiquidityProvision(ctx, lps, "party-C", vgcrypto.RandomHash()) 5189 require.Error(t, err) 5190 } 5191 5192 func TestMarket_LeaveAuctionAndRepricePeggedOrders(t *testing.T) { 5193 now := time.Unix(10, 0) 5194 tm := getTestMarket(t, now, nil, nil) 5195 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5196 5197 addAccount(t, tm, "party-A") 5198 addAccount(t, tm, "party-B") 5199 addAccount(t, tm, "party-C") 5200 auxParty := "auxParty" 5201 addAccount(t, tm, auxParty) 5202 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 5203 5204 // Start the opening auction 5205 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 10}) 5206 tm.mas.AuctionStarted(ctx, now) 5207 tm.market.EnterAuction(ctx) 5208 5209 // Add orders that will outlive the auction to set the reference prices 5210 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideSell, "party-A", 10, 1010) 5211 o1conf, err := tm.market.SubmitOrder(ctx, o1) 5212 require.NotNil(t, o1conf) 5213 require.NoError(t, err) 5214 5215 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideBuy, "party-A", 10, 990) 5216 o2conf, err := tm.market.SubmitOrder(ctx, o2) 5217 require.NotNil(t, o2conf) 5218 require.NoError(t, err) 5219 5220 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order03", types.SideSell, "party-A", 1, 1000) 5221 o3conf, err := tm.market.SubmitOrder(ctx, o3) 5222 require.NotNil(t, o3conf) 5223 require.NoError(t, err) 5224 5225 o4 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order04", types.SideBuy, "party-A", 1, 1000) 5226 o4conf, err := tm.market.SubmitOrder(ctx, o4) 5227 require.NotNil(t, o4conf) 5228 require.NoError(t, err) 5229 5230 require.Equal(t, int64(4), tm.market.GetOrdersOnBookCount()) 5231 5232 lps := &types.LiquidityProvisionSubmission{ 5233 Fee: num.DecimalFromFloat(0.01), 5234 MarketID: tm.market.GetID(), 5235 CommitmentAmount: num.NewUint(1000000000), 5236 } 5237 5238 err = tm.market.SubmitLiquidityProvision(ctx, lps, "party-C", vgcrypto.RandomHash()) 5239 require.NoError(t, err) 5240 5241 // Leave the auction so pegged orders are unparked 5242 tm.market.LeaveAuctionWithIDGen(ctx, now.Add(time.Second*20), newTestIDGenerator()) 5243 5244 require.Equal(t, int64(2), tm.market.GetOrdersOnBookCount()) 5245 require.Equal(t, 0, tm.market.GetPeggedOrderCount()) 5246 require.Equal(t, 0, tm.market.GetParkedOrderCount()) 5247 5248 // Remove an order to invalidate reference prices and force pegged orders to park 5249 _, err = tm.market.CancelOrder(ctx, o1.Party, o1.ID, vgcrypto.RandomHash()) 5250 require.NoError(t, err) 5251 tm.now = tm.now.Add(time.Second) 5252 tm.market.OnTick(ctx, tm.now) 5253 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 5254 5255 // 1 live orders, 1 normal 5256 // all LP have been removed as cannot be repriced. 5257 assert.Equal(t, int64(1), tm.market.GetOrdersOnBookCount()) 5258 assert.Equal(t, 0, tm.market.GetPeggedOrderCount()) 5259 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 5260 } 5261 5262 func TestOrderBook_PartiallyFilledMarketOrderThatWouldWashIOC(t *testing.T) { 5263 now := time.Unix(10, 0) 5264 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 5265 Duration: 1000, 5266 }) 5267 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5268 5269 addAccountWithAmount(tm, "party-A", 10000000) 5270 addAccountWithAmount(tm, "party-B", 10000000) 5271 auxParty := "auxParty" 5272 addAccount(t, tm, auxParty) 5273 5274 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 5275 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 5276 require.NotNil(t, conf) 5277 require.NoError(t, err) 5278 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 5279 5280 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 5281 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 5282 require.NotNil(t, conf) 5283 require.NoError(t, err) 5284 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 5285 5286 // Leave auction right away 5287 tm.market.LeaveAuctionWithIDGen(ctx, now.Add(time.Second*20), newTestIDGenerator()) 5288 5289 // Create 2 buy orders that we will try to match against 5290 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-B", 10, 100) 5291 o1conf, err := tm.market.SubmitOrder(ctx, o1) 5292 require.NotNil(t, o1conf) 5293 require.NoError(t, err) 5294 5295 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideBuy, "party-A", 10, 90) 5296 o2conf, err := tm.market.SubmitOrder(ctx, o2) 5297 require.NotNil(t, o2conf) 5298 require.NoError(t, err) 5299 5300 // Send the sell order with enough volume to match both existing trades 5301 o3 := getMarketOrder(tm, now, types.OrderTypeMarket, types.OrderTimeInForceIOC, "Order03", types.SideSell, "party-A", 20, 0) 5302 o3conf, err := tm.market.SubmitOrder(ctx, o3) 5303 require.NotNil(t, o3conf) 5304 require.NoError(t, err) 5305 assert.Equal(t, types.OrderStatusPartiallyFilled, o3.Status) 5306 assert.Equal(t, uint64(10), o3.Remaining) 5307 } 5308 5309 func TestOrderBook_PartiallyFilledMarketOrderThatWouldWashFOKSell(t *testing.T) { 5310 now := time.Unix(10, 0) 5311 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 5312 Duration: 1000, 5313 }) 5314 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5315 5316 addAccountWithAmount(tm, "party-A", 10000000) 5317 addAccountWithAmount(tm, "party-B", 10000000) 5318 auxParty := "auxParty" 5319 addAccount(t, tm, auxParty) 5320 5321 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 5322 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 5323 require.NotNil(t, conf) 5324 require.NoError(t, err) 5325 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 5326 5327 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 5328 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 5329 require.NotNil(t, conf) 5330 require.NoError(t, err) 5331 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 5332 5333 // Leave auction right away 5334 tm.market.LeaveAuctionWithIDGen(ctx, now.Add(time.Second*20), newTestIDGenerator()) 5335 5336 // Create 2 buy orders that we will try to match against 5337 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-B", 10, 100) 5338 o1conf, err := tm.market.SubmitOrder(ctx, o1) 5339 require.NotNil(t, o1conf) 5340 require.NoError(t, err) 5341 5342 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideBuy, "party-A", 10, 90) 5343 o2conf, err := tm.market.SubmitOrder(ctx, o2) 5344 require.NotNil(t, o2conf) 5345 require.NoError(t, err) 5346 5347 // Send the sell order with enough volume to match both existing trades 5348 o3 := getMarketOrder(tm, now, types.OrderTypeMarket, types.OrderTimeInForceFOK, "Order03", types.SideSell, "party-A", 20, 0) 5349 o3conf, err := tm.market.SubmitOrder(ctx, o3) 5350 require.NotNil(t, o3conf) 5351 require.NoError(t, err) 5352 5353 // A wash trade during a FOK order will stop the order fully unfilled 5354 require.Equal(t, types.OrderStatusStopped, o3.Status) 5355 assert.Equal(t, uint64(20), o3.Remaining) 5356 5357 // Send the sell order with only enough volume to match the opposite party 5358 o4 := getMarketOrder(tm, now, types.OrderTypeMarket, types.OrderTimeInForceFOK, "Order04", types.SideSell, "party-A", 5, 0) 5359 o4conf, err := tm.market.SubmitOrder(ctx, o4) 5360 require.NotNil(t, o4conf) 5361 require.NoError(t, err) 5362 5363 // Fully matches 5364 require.Equal(t, types.OrderStatusFilled, o4.Status) 5365 assert.Equal(t, uint64(0), o4.Remaining) 5366 } 5367 5368 func TestOrderBook_PartiallyFilledMarketOrderThatWouldWashFOKBuy(t *testing.T) { 5369 now := time.Unix(10, 0) 5370 tm := getTestMarket2(t, now, nil, &types.AuctionDuration{ 5371 Duration: 1, 5372 // increase lpRange so that LP orders don't get pushed too close to MID and test can behave as expected 5373 }, true, 1) 5374 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5375 5376 auxParty, auxParty2 := "auxParty", "auxParty2" 5377 addAccountWithAmount(tm, "party-A", 10000000) 5378 addAccountWithAmount(tm, "party-B", 10000000) 5379 addAccount(t, tm, auxParty) 5380 addAccount(t, tm, auxParty2) 5381 addAccountWithAmount(tm, "lpprov", 10000000) 5382 5383 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 5384 5385 auxOrders := []*types.Order{ 5386 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100000), 5387 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty, 1, 1), 5388 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux3", types.SideSell, auxParty, 1, 100), 5389 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux4", types.SideBuy, auxParty2, 1, 100), 5390 } 5391 for _, o := range auxOrders { 5392 conf, err := tm.market.SubmitOrder(ctx, o) 5393 require.NoError(t, err) 5394 require.NotNil(t, conf) 5395 } 5396 lp := &types.LiquidityProvisionSubmission{ 5397 MarketID: tm.market.GetID(), 5398 CommitmentAmount: num.NewUint(25000), 5399 Fee: num.DecimalFromFloat(0.01), 5400 } 5401 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 5402 // Leave auction 5403 now = now.Add(2 * time.Second) 5404 tm.now = now 5405 tm.market.OnTick(ctx, now) 5406 5407 // Create 2 buy orders that we will try to match against 5408 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideSell, "party-B", 10, 100) 5409 o1conf, err := tm.market.SubmitOrder(ctx, o1) 5410 require.NotNil(t, o1conf) 5411 require.NoError(t, err) 5412 5413 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideSell, "party-A", 10, 110) 5414 o2conf, err := tm.market.SubmitOrder(ctx, o2) 5415 require.NotNil(t, o2conf) 5416 require.NoError(t, err) 5417 5418 o5 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order05", types.SideBuy, "party-B", 10, 90) 5419 o5conf, err := tm.market.SubmitOrder(ctx, o5) 5420 require.NotNil(t, o5conf) 5421 require.NoError(t, err) 5422 5423 // Send the sell order with enough volume to match both existing trades 5424 o3 := getMarketOrder(tm, now, types.OrderTypeMarket, types.OrderTimeInForceFOK, "Order03", types.SideBuy, "party-A", 15, 0) 5425 o3conf, err := tm.market.SubmitOrder(ctx, o3) 5426 require.NotNil(t, o3conf) 5427 require.NoError(t, err) 5428 5429 // A wash trade during a FOK order will stop the order fully unfilled 5430 require.Equal(t, types.OrderStatusStopped, o3.Status) 5431 assert.EqualValues(t, 15, o3.Remaining) 5432 5433 // Send the sell order with only enough volume to match the opposite party 5434 o4 := getMarketOrder(tm, now, types.OrderTypeMarket, types.OrderTimeInForceFOK, "Order04", types.SideBuy, "party-A", 5, 0) 5435 o4conf, err := tm.market.SubmitOrder(ctx, o4) 5436 require.NotNil(t, o4conf) 5437 require.NoError(t, err) 5438 5439 // A wash trade during a FOK order will stop the order fully unfilled 5440 require.Equal(t, types.OrderStatusFilled, o4.Status) 5441 assert.Equal(t, uint64(0), o4.Remaining) 5442 } 5443 5444 func TestOrderBook_PartiallyFilledLimitOrderThatWouldWashFOK(t *testing.T) { 5445 now := time.Unix(10, 0) 5446 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 5447 Duration: 1000, 5448 }) 5449 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5450 5451 addAccountWithAmount(tm, "party-A", 10000000) 5452 addAccountWithAmount(tm, "party-B", 10000000) 5453 auxParty := "auxParty" 5454 addAccount(t, tm, auxParty) 5455 5456 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 5457 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 5458 require.NotNil(t, conf) 5459 require.NoError(t, err) 5460 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 5461 5462 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 5463 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 5464 require.NotNil(t, conf) 5465 require.NoError(t, err) 5466 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 5467 5468 // Leave auction right away 5469 tm.market.LeaveAuctionWithIDGen(ctx, now.Add(time.Second*20), newTestIDGenerator()) 5470 5471 md := tm.market.GetMarketData() 5472 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 5473 5474 // Create 2 buy orders that we will try to match against 5475 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order01", types.SideBuy, "party-B", 10, 100) 5476 o1conf, err := tm.market.SubmitOrder(ctx, o1) 5477 require.NotNil(t, o1conf) 5478 require.NoError(t, err) 5479 5480 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order02", types.SideBuy, "party-A", 10, 90) 5481 o2conf, err := tm.market.SubmitOrder(ctx, o2) 5482 require.NotNil(t, o2conf) 5483 require.NoError(t, err) 5484 5485 // Send the sell order with enough volume to match both existing trades 5486 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceFOK, "Order03", types.SideSell, "party-A", 20, 90) 5487 o3conf, err := tm.market.SubmitOrder(ctx, o3) 5488 require.NotNil(t, o3conf) 5489 require.NoError(t, err) 5490 5491 // A wash trade during FOK will stop the order filly unfilled 5492 require.Equal(t, types.OrderStatusStopped, o3.Status) 5493 assert.Equal(t, uint64(20), o3.Remaining) 5494 5495 // Send the sell order with only enough volume to match the opposite party 5496 o4 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceFOK, "Order04", types.SideSell, "party-A", 5, 90) 5497 o4conf, err := tm.market.SubmitOrder(ctx, o4) 5498 require.NotNil(t, o4conf) 5499 require.NoError(t, err) 5500 5501 // A wash trade during FOK will stop the order filly unfilled 5502 require.Equal(t, types.OrderStatusFilled, o4.Status) 5503 assert.Equal(t, uint64(0), o4.Remaining) 5504 } 5505 5506 func Test3008And3007CancelLiquidityProvision(t *testing.T) { 5507 now := time.Unix(10, 0) 5508 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5509 5510 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{ 5511 Duration: 10000, 5512 }) 5513 mktCfg.Fees.Factors = &types.FeeFactors{ 5514 LiquidityFee: num.DecimalFromFloat(0.001), 5515 InfrastructureFee: num.DecimalFromFloat(0.0005), 5516 MakerFee: num.DecimalFromFloat(0.00025), 5517 } 5518 mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{ 5519 LogNormalRiskModel: &types.LogNormalRiskModel{ 5520 RiskAversionParameter: num.DecimalFromFloat(0.001), 5521 Tau: num.DecimalFromFloat(0.00011407711613050422), 5522 Params: &types.LogNormalModelParams{ 5523 Mu: num.DecimalZero(), 5524 R: num.DecimalFromFloat(0.016), 5525 Sigma: num.DecimalFromFloat(20), 5526 }, 5527 }, 5528 } 5529 5530 tm := newTestMarket(t, now).Run(ctx, mktCfg) 5531 tm.StartOpeningAuction(). 5532 WithAccountAndAmount("party-0", 1000000). 5533 WithAccountAndAmount("party-1", 1000000). 5534 WithAccountAndAmount("party-2", 10000000000). 5535 // provide stake as well but will cancel 5536 WithAccountAndAmount("party-2-bis", 10000000000). 5537 WithAccountAndAmount("party-3", 1000000). 5538 WithAccountAndAmount("party-4", 1000000) 5539 5540 tm.now = now 5541 tm.market.OnTick(ctx, now) 5542 5543 orderParams := []struct { 5544 id string 5545 size uint64 5546 side types.Side 5547 tif types.OrderTimeInForce 5548 pegRef types.PeggedReference 5549 pegOffset *num.Uint 5550 }{ 5551 {"party-4", 1, types.SideBuy, types.OrderTimeInForceGTC, types.PeggedReferenceBestBid, num.NewUint(2000)}, 5552 {"party-3", 1, types.SideSell, types.OrderTimeInForceGTC, types.PeggedReferenceBestAsk, num.NewUint(1000)}, 5553 } 5554 partyA, partyB := orderParams[0], orderParams[1] 5555 5556 tpl := OrderTemplate{ 5557 Type: types.OrderTypeLimit, 5558 } 5559 orders := []*types.Order{ 5560 // Limit Orders 5561 tpl.New(types.Order{ 5562 Size: 20, 5563 Remaining: 20, 5564 Price: num.UintZero().Sub(num.NewUint(5500), partyA.pegOffset), // 3500 5565 Side: types.SideBuy, 5566 Party: "party-0", 5567 TimeInForce: types.OrderTimeInForceGFA, 5568 }), 5569 tpl.New(types.Order{ 5570 Size: 20, 5571 Remaining: 20, 5572 Price: num.UintZero().Sub(num.NewUint(5000), partyB.pegOffset), // 4000 5573 Side: types.SideSell, 5574 Party: "party-1", 5575 TimeInForce: types.OrderTimeInForceGFA, 5576 }), 5577 tpl.New(types.Order{ 5578 Size: 10, 5579 Remaining: 10, 5580 Price: num.NewUint(5500), 5581 Side: types.SideBuy, 5582 Party: "party-2", 5583 TimeInForce: types.OrderTimeInForceGFA, 5584 }), 5585 tpl.New(types.Order{ 5586 Size: 100, 5587 Remaining: 100, 5588 Price: num.NewUint(5000), 5589 Side: types.SideSell, 5590 Party: "party-2", 5591 TimeInForce: types.OrderTimeInForceGTC, 5592 }), 5593 tpl.New(types.Order{ 5594 Size: 100, 5595 Remaining: 100, 5596 Price: num.NewUint(3500), 5597 Side: types.SideBuy, 5598 Party: "party-0", 5599 TimeInForce: types.OrderTimeInForceGTC, 5600 }), 5601 tpl.New(types.Order{ 5602 Size: 20, 5603 Remaining: 20, 5604 Price: num.NewUint(8500), 5605 Side: types.SideBuy, 5606 Party: "party-0", 5607 TimeInForce: types.OrderTimeInForceGTC, 5608 }), 5609 5610 // Pegged Orders 5611 tpl.New(types.Order{ 5612 Party: partyA.id, 5613 Side: partyA.side, 5614 Size: partyA.size, 5615 Remaining: partyA.size, 5616 TimeInForce: partyA.tif, 5617 PeggedOrder: &types.PeggedOrder{Reference: partyA.pegRef, Offset: partyA.pegOffset}, 5618 }), 5619 tpl.New(types.Order{ 5620 Party: partyB.id, 5621 Side: partyB.side, 5622 Size: partyB.size, 5623 Remaining: partyB.size, 5624 TimeInForce: partyB.tif, 5625 PeggedOrder: &types.PeggedOrder{Reference: partyB.pegRef, Offset: partyB.pegOffset}, 5626 }), 5627 } 5628 5629 tm.WithSubmittedOrders(t, orders...) 5630 5631 // Add a LPSubmission 5632 // this is a log of stake, enough to cover all 5633 // the required stake for the market 5634 lp := &types.LiquidityProvisionSubmission{ 5635 MarketID: tm.market.GetID(), 5636 CommitmentAmount: num.NewUint(2000000), 5637 Fee: num.DecimalFromFloat(0.01), 5638 } 5639 5640 require.NoError(t, tm.market.SubmitLiquidityProvision(ctx, lp, "party-2", vgcrypto.RandomHash())) 5641 // Leave the auction 5642 tm.now = now.Add(10001 * time.Second) 5643 tm.market.OnTick(ctx, tm.now) 5644 5645 assert.Equal(t, 1, tm.market.GetLPSCount()) 5646 5647 // this is our second stake provider 5648 // small player 5649 lp2 := &types.LiquidityProvisionSubmission{ 5650 MarketID: tm.market.GetID(), 5651 CommitmentAmount: num.NewUint(1000), 5652 Fee: num.DecimalFromFloat(0.01), 5653 } 5654 5655 // cleanup the events, we want to make sure our orders are created 5656 tm.events = nil 5657 5658 require.NoError(t, tm.market.SubmitLiquidityProvision( 5659 ctx, lp2, "party-2-bis", vgcrypto.RandomHash())) 5660 5661 tm.market.OnEpochEvent(ctx, types.Epoch{Action: proto.EpochAction_EPOCH_ACTION_START}) 5662 5663 assert.Equal(t, 2, tm.market.GetLPSCount()) 5664 5665 tm.now = now.Add(10011 * time.Second) 5666 tm.market.OnTick(ctx, tm.now) 5667 5668 // now we do a cancellation 5669 lpCancel := &types.LiquidityProvisionCancellation{ 5670 MarketID: tm.market.GetID(), 5671 } 5672 5673 // cleanup the events before we continue 5674 tm.events = nil 5675 5676 require.NoError(t, tm.market.CancelLiquidityProvision( 5677 ctx, lpCancel, "party-2-bis")) 5678 5679 assert.Equal(t, 2, tm.market.GetLPSCount()) 5680 5681 t.Run("LiquidityProvision_CANCELLED", func(t *testing.T) { 5682 // Filter events until LP is found 5683 var found *vegapb.LiquidityProvision 5684 for _, e := range tm.events { 5685 switch evt := e.(type) { 5686 case *events.LiquidityProvision: 5687 if evt.LiquidityProvision().PartyId == "party-2-bis" { 5688 found = evt.LiquidityProvision() 5689 } 5690 } 5691 } 5692 require.NotNil(t, found) 5693 assert.Equal(t, types.LiquidityProvisionStatusPending.String(), found.Status.String()) 5694 }) 5695 } 5696 5697 func Test2963EnsureEquityShareAreInMarketData(t *testing.T) { 5698 now := time.Unix(10, 0) 5699 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5700 5701 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{ 5702 Duration: 10000, 5703 }) 5704 mktCfg.Fees.Factors = &types.FeeFactors{ 5705 LiquidityFee: num.DecimalFromFloat(0.001), 5706 InfrastructureFee: num.DecimalFromFloat(0.0005), 5707 MakerFee: num.DecimalFromFloat(0.00025), 5708 } 5709 mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{ 5710 LogNormalRiskModel: &types.LogNormalRiskModel{ 5711 RiskAversionParameter: num.DecimalFromFloat(0.001), 5712 Tau: num.DecimalFromFloat(0.00011407711613050422), 5713 Params: &types.LogNormalModelParams{ 5714 Mu: num.DecimalZero(), 5715 R: num.DecimalFromFloat(0.016), 5716 Sigma: num.DecimalFromFloat(20), 5717 }, 5718 }, 5719 } 5720 5721 tm := newTestMarket(t, now).Run(ctx, mktCfg) 5722 tm.StartOpeningAuction(). 5723 WithAccountAndAmount("party-0", 1000000). 5724 WithAccountAndAmount("party-1", 1000000). 5725 WithAccountAndAmount("party-2", 10000000000). 5726 // provide stake as well but will cancel 5727 WithAccountAndAmount("party-2-bis", 10000000000). 5728 WithAccountAndAmount("party-3", 1000000). 5729 WithAccountAndAmount("party-4", 1000000) 5730 5731 tm.now = now 5732 tm.market.OnTick(ctx, now) 5733 5734 orderParams := []struct { 5735 id string 5736 size uint64 5737 side types.Side 5738 tif types.OrderTimeInForce 5739 pegRef types.PeggedReference 5740 pegOffset *num.Uint 5741 }{ 5742 {"party-4", 1, types.SideBuy, types.OrderTimeInForceGTC, types.PeggedReferenceBestBid, num.NewUint(2000)}, 5743 {"party-3", 1, types.SideSell, types.OrderTimeInForceGTC, types.PeggedReferenceBestAsk, num.NewUint(1000)}, 5744 } 5745 partyA, partyB := orderParams[0], orderParams[1] 5746 5747 tpl := OrderTemplate{ 5748 Type: types.OrderTypeLimit, 5749 } 5750 orders := []*types.Order{ 5751 // Limit Orders 5752 tpl.New(types.Order{ 5753 Size: 20, 5754 Remaining: 20, 5755 Price: num.Sum(num.NewUint(5500), partyA.pegOffset), // 3500 5756 Side: types.SideBuy, 5757 Party: "party-0", 5758 TimeInForce: types.OrderTimeInForceGFA, 5759 }), 5760 tpl.New(types.Order{ 5761 Size: 20, 5762 Remaining: 20, 5763 Price: num.Sum(num.NewUint(5000), partyA.pegOffset), // 4000 5764 Side: types.SideSell, 5765 Party: "party-1", 5766 TimeInForce: types.OrderTimeInForceGFA, 5767 }), 5768 tpl.New(types.Order{ 5769 Size: 10, 5770 Remaining: 10, 5771 Price: num.NewUint(5500), 5772 Side: types.SideBuy, 5773 Party: "party-2", 5774 TimeInForce: types.OrderTimeInForceGFA, 5775 }), 5776 tpl.New(types.Order{ 5777 Size: 100, 5778 Remaining: 100, 5779 Price: num.NewUint(5000), 5780 Side: types.SideSell, 5781 Party: "party-2", 5782 TimeInForce: types.OrderTimeInForceGTC, 5783 }), 5784 tpl.New(types.Order{ 5785 Size: 100, 5786 Remaining: 100, 5787 Price: num.NewUint(3500), 5788 Side: types.SideBuy, 5789 Party: "party-0", 5790 TimeInForce: types.OrderTimeInForceGTC, 5791 }), 5792 tpl.New(types.Order{ 5793 Size: 20, 5794 Remaining: 20, 5795 Price: num.NewUint(8500), 5796 Side: types.SideBuy, 5797 Party: "party-0", 5798 TimeInForce: types.OrderTimeInForceGTC, 5799 }), 5800 5801 // Pegged Orders 5802 tpl.New(types.Order{ 5803 Party: partyA.id, 5804 Side: partyA.side, 5805 Size: partyA.size, 5806 Remaining: partyA.size, 5807 TimeInForce: partyA.tif, 5808 PeggedOrder: &types.PeggedOrder{Reference: partyA.pegRef, Offset: partyA.pegOffset}, 5809 }), 5810 tpl.New(types.Order{ 5811 Party: partyB.id, 5812 Side: partyB.side, 5813 Size: partyB.size, 5814 Remaining: partyB.size, 5815 TimeInForce: partyB.tif, 5816 PeggedOrder: &types.PeggedOrder{Reference: partyB.pegRef, Offset: partyB.pegOffset}, 5817 }), 5818 } 5819 5820 tm.WithSubmittedOrders(t, orders...) 5821 5822 // Add a LPSubmission 5823 // this is a log of stake, enough to cover all 5824 // the required stake for the market 5825 lp := &types.LiquidityProvisionSubmission{ 5826 MarketID: tm.market.GetID(), 5827 CommitmentAmount: num.NewUint(2000000), 5828 Fee: num.DecimalFromFloat(0.01), 5829 } 5830 5831 // Leave the auction 5832 tm.now = now.Add(10001 * time.Second) 5833 tm.market.OnTick(ctx, tm.now) 5834 5835 require.NoError(t, tm.market.SubmitLiquidityProvision(ctx, lp, "party-2", vgcrypto.RandomHash())) 5836 assert.Equal(t, 0, tm.market.GetLPSCount()) 5837 5838 // this is our second stake provider 5839 // small player 5840 lp2 := &types.LiquidityProvisionSubmission{ 5841 MarketID: tm.market.GetID(), 5842 CommitmentAmount: num.NewUint(1000), 5843 Fee: num.DecimalFromFloat(0.01), 5844 } 5845 5846 // cleanup the events, we want to make sure our orders are created 5847 tm.events = nil 5848 5849 require.NoError(t, tm.market.SubmitLiquidityProvision( 5850 ctx, lp2, "party-2-bis", vgcrypto.RandomHash())) 5851 assert.Equal(t, 0, tm.market.GetLPSCount()) 5852 5853 tm.now = now.Add(10011 * time.Second) 5854 tm.market.OnTick(ctx, tm.now) 5855 tm.market.OnEpochEvent(ctx, types.Epoch{Action: proto.EpochAction_EPOCH_ACTION_START}) 5856 5857 mktData := tm.market.GetMarketData() 5858 assert.Len(t, mktData.LiquidityProviderFeeShare, 2) 5859 } 5860 5861 func TestAverageEntryValuation(t *testing.T) { 5862 now := time.Unix(10, 0) 5863 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5864 5865 auctionEnd := now.Add(10001 * time.Second) 5866 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{ 5867 Duration: 10000, 5868 }) 5869 mktCfg.Fees.Factors = &types.FeeFactors{ 5870 LiquidityFee: num.DecimalFromFloat(0.001), 5871 InfrastructureFee: num.DecimalFromFloat(0.0005), 5872 MakerFee: num.DecimalFromFloat(0.00025), 5873 } 5874 mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{ 5875 LogNormalRiskModel: &types.LogNormalRiskModel{ 5876 RiskAversionParameter: num.DecimalFromFloat(0.001), 5877 Tau: num.DecimalFromFloat(0.00011407711613050422), 5878 Params: &types.LogNormalModelParams{ 5879 Mu: num.DecimalZero(), 5880 R: num.DecimalFromFloat(0.016), 5881 Sigma: num.DecimalFromFloat(20), 5882 }, 5883 }, 5884 } 5885 5886 lpparty := "lp-party-1" 5887 lpparty2 := "lp-party-2" 5888 lpparty3 := "lp-party-3" 5889 5890 tm := newTestMarket(t, now).Run(ctx, mktCfg) 5891 tm.StartOpeningAuction(). 5892 // the liquidity provider 5893 WithAccountAndAmount(lpparty, 500000000000). 5894 WithAccountAndAmount(lpparty2, 500000000000). 5895 WithAccountAndAmount(lpparty3, 500000000000) 5896 5897 // Add a LPSubmission 5898 // this is a log of stake, enough to cover all 5899 // the required stake for the market 5900 lpSubmission := types.LiquidityProvisionSubmission{ 5901 MarketID: tm.market.GetID(), 5902 CommitmentAmount: num.NewUint(80000), 5903 Fee: num.DecimalFromFloat(0.01), 5904 Reference: "ref-lp-submission-1", 5905 } 5906 5907 // submit our lp 5908 require.NoError(t, 5909 tm.market.SubmitLiquidityProvision( 5910 ctx, &lpSubmission, lpparty, vgcrypto.RandomHash()), 5911 ) 5912 5913 lpSubmission2 := lpSubmission 5914 lpSubmission2.CommitmentAmount = lpSubmission.CommitmentAmount.Clone() 5915 lpSubmission2.Reference = "lp-submission-2" 5916 // submit our lp 5917 require.NoError(t, 5918 tm.market.SubmitLiquidityProvision( 5919 ctx, &lpSubmission2, lpparty2, vgcrypto.RandomHash()), 5920 ) 5921 5922 lpSubmission3 := lpSubmission 5923 lpSubmission3.CommitmentAmount = lpSubmission.CommitmentAmount.Clone() 5924 lpSubmission3.Reference = "lp-submission-3" 5925 // submit our lp 5926 require.NoError(t, 5927 tm.market.SubmitLiquidityProvision( 5928 ctx, &lpSubmission3, lpparty3, vgcrypto.RandomHash()), 5929 ) 5930 // after LP submission 5931 tm.EndOpeningAuction(t, auctionEnd, false) 5932 5933 marketData := tm.market.GetMarketData() 5934 /* 5935 expects := map[string]struct { 5936 found bool 5937 value string 5938 }{ 5939 lpparty: {value: "0.5454545454545455"}, // 6/9 5940 lpparty2: {value: "0.2727272727272727"}, // 3/9 5941 lpparty3: {value: "0.1818181818181818"}, // 2/9 5942 }*/ 5943 // because we now have to submit the LP before leaving auction, all LPs provide the same 5944 expects := map[string]struct { 5945 found bool 5946 value string 5947 }{ 5948 lpparty: {value: "0.3333333333333333"}, 5949 lpparty2: {value: "0.3333333333333333"}, 5950 lpparty3: {value: "0.3333333333333333"}, 5951 } 5952 5953 for _, v := range marketData.LiquidityProviderFeeShare { 5954 expv, ok := expects[v.Party] 5955 assert.True(t, ok, "unexpected lp provider in market data", v.Party) 5956 assert.Equal(t, expv.value, v.EquityLikeShare) 5957 expv.found = true 5958 expects[v.Party] = expv 5959 } 5960 5961 // now ensure all are found 5962 for k, v := range expects { 5963 assert.True(t, v.found, "was not in the list of lp providers", k) 5964 } 5965 } 5966 5967 func TestBondAccountIsReleasedItMarketRejected(t *testing.T) { 5968 now := time.Unix(10, 0) 5969 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 5970 5971 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{ 5972 Duration: 10000, 5973 }) 5974 mktCfg.Fees.Factors = &types.FeeFactors{ 5975 LiquidityFee: num.DecimalFromFloat(0.001), 5976 InfrastructureFee: num.DecimalFromFloat(0.0005), 5977 MakerFee: num.DecimalFromFloat(0.00025), 5978 } 5979 mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{ 5980 LogNormalRiskModel: &types.LogNormalRiskModel{ 5981 RiskAversionParameter: num.DecimalFromFloat(0.001), 5982 Tau: num.DecimalFromFloat(0.00011407711613050422), 5983 Params: &types.LogNormalModelParams{ 5984 Mu: num.DecimalZero(), 5985 R: num.DecimalFromFloat(0.016), 5986 Sigma: num.DecimalFromFloat(20), 5987 }, 5988 }, 5989 } 5990 5991 lpparty := "lp-party-1" 5992 5993 tm := newTestMarket(t, now).Run(ctx, mktCfg) 5994 tm.WithAccountAndAmount(lpparty, 500000) 5995 5996 tm.now = now 5997 tm.market.OnTick(ctx, now) 5998 5999 // Add a LPSubmission 6000 // this is a log of stake, enough to cover all 6001 // the required stake for the market 6002 lpSubmission := &types.LiquidityProvisionSubmission{ 6003 MarketID: tm.market.GetID(), 6004 CommitmentAmount: num.NewUint(150000), 6005 Fee: num.DecimalFromFloat(0.01), 6006 Reference: "ref-lp-submission-1", 6007 } 6008 6009 // submit our lp 6010 require.NoError(t, 6011 tm.market.SubmitLiquidityProvision( 6012 ctx, lpSubmission, lpparty, vgcrypto.RandomHash()), 6013 ) 6014 6015 t.Run("bond account is updated with the new commitment", func(t *testing.T) { 6016 bacc, err := tm.collateralEngine.GetPartyBondAccount( 6017 tm.market.GetID(), lpparty, tm.asset) 6018 assert.NoError(t, err) 6019 assert.Equal(t, num.NewUint(150000), bacc.Balance) 6020 gacc, err := tm.collateralEngine.GetPartyGeneralAccount( 6021 lpparty, tm.asset) 6022 assert.NoError(t, err) 6023 assert.Equal(t, num.NewUint(350000), gacc.Balance) 6024 }) 6025 6026 // now we reject the network and our party bond account should be released to general 6027 assert.NoError(t, 6028 tm.market.Reject(context.Background()), 6029 ) 6030 6031 t.Run("bond is released to general account", func(t *testing.T) { 6032 // an error as the bond account is being deleted 6033 _, err := tm.collateralEngine.GetPartyBondAccount( 6034 tm.market.GetID(), lpparty, tm.asset) 6035 require.Error(t, err) 6036 assert.Contains(t, err.Error(), "account does not exist:") 6037 gacc, err := tm.collateralEngine.GetPartyGeneralAccount( 6038 lpparty, tm.asset) 6039 assert.NoError(t, err) 6040 assert.Equal(t, num.NewUint(500000), gacc.Balance) 6041 }) 6042 } 6043 6044 // TODO karel - write a test with new liquidity 6045 // func TestLiquidityMonitoring_GoIntoAndOutOfAuction(t *testing.T) { 6046 // now := time.Unix(10, 0) 6047 // openingDuration := &types.AuctionDuration{ 6048 // Duration: 1, 6049 // } 6050 // tm := getTestMarket(t, now, nil, openingDuration) 6051 // c1 := 0.7 6052 // ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 6053 // tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 6054 6055 // md := tm.market.GetMarketData() 6056 // require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode) 6057 6058 // lp1 := "lp1" 6059 // lp2 := "lp2" 6060 // party1 := "party1" 6061 // party2 := "party2" 6062 // auxParty, auxParty2 := "auxParty", "auxParty2" 6063 6064 // addAccount(t, tm, lp1) 6065 // addAccount(t, tm, lp2) 6066 // addAccount(t, tm, party1) 6067 // addAccount(t, tm, party2) 6068 // addAccount(t, tm, auxParty) 6069 // addAccount(t, tm, auxParty2) 6070 6071 // lp1Commitment := num.NewUint(50000) 6072 // lp2Commitment := num.NewUint(10000) 6073 6074 // matchingPrice := uint64(100) 6075 // // Add orders that will stay on the book thus maintaining best_bid and best_ask 6076 // buyOrder1 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "buyOrder1", types.SideBuy, party1, 1, matchingPrice-10) 6077 // buyConf1, err := tm.market.SubmitOrder(ctx, buyOrder1) 6078 // require.NoError(t, err) 6079 // require.Equal(t, types.OrderStatusActive, buyConf1.Order.Status) 6080 6081 // md = tm.market.GetMarketData() 6082 // require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode) 6083 6084 // sellOrder1 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "sellOrder1", types.SideSell, party2, 1, matchingPrice+10) 6085 // sellConf1, err := tm.market.SubmitOrder(ctx, sellOrder1) 6086 // require.NoError(t, err) 6087 // require.Equal(t, types.OrderStatusActive, sellConf1.Order.Status) 6088 6089 // md = tm.market.GetMarketData() 6090 // require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode) 6091 6092 // lp1sub := &types.LiquidityProvisionSubmission{ 6093 // MarketID: tm.market.GetID(), 6094 // CommitmentAmount: lp1Commitment, 6095 // Fee: num.DecimalFromFloat(0.05), 6096 // } 6097 6098 // lp2sub := &types.LiquidityProvisionSubmission{ 6099 // MarketID: tm.market.GetID(), 6100 // CommitmentAmount: lp2Commitment, 6101 // Fee: num.DecimalFromFloat(0.1), 6102 // } 6103 6104 // require.NoError(t, 6105 // tm.market.SubmitLiquidityProvision(ctx, lp1sub, lp1, vgcrypto.RandomHash()), 6106 // ) 6107 6108 // require.NoError(t, 6109 // tm.market.SubmitLiquidityProvision(ctx, lp2sub, lp2, vgcrypto.RandomHash()), 6110 // ) 6111 6112 // md = tm.market.GetMarketData() 6113 // require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode) 6114 6115 // buyOrder2 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "buyOrder2", types.SideBuy, party1, 1, matchingPrice) 6116 // buyConf2, err := tm.market.SubmitOrder(ctx, buyOrder2) 6117 // require.NoError(t, err) 6118 // require.Equal(t, types.OrderStatusActive, buyConf2.Order.Status) 6119 6120 // sellOrder2 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "sellOrder2", types.SideSell, party2, 1, matchingPrice) 6121 // sellConf2, err := tm.market.SubmitOrder(ctx, sellOrder2) 6122 // require.NoError(t, err) 6123 // require.Equal(t, types.OrderStatusActive, sellConf2.Order.Status) 6124 // require.Equal(t, 0, len(sellConf2.Trades)) 6125 6126 // md = tm.market.GetMarketData() 6127 // require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode) 6128 // require.Equal(t, num.Sum(lp1Commitment, lp2Commitment).String(), md.SuppliedStake) 6129 6130 // // leave opening auction 6131 // tm.now = now.Add(2 * time.Second) 6132 // closed := tm.market.OnTick(ctx, tm.now) 6133 // require.False(t, closed) 6134 // tm.stateVar.ReadyForTimeTrigger(tm.asset, tm.market.GetID()) 6135 // tm.stateVar.OnTick(context.Background(), tm.now.Add(6*time.Minute)) 6136 6137 // totalCommitment := num.Sum(lp1Commitment, lp2Commitment) 6138 // currentStake := num.DecimalFromUint(totalCommitment) 6139 // md = tm.market.GetMarketData() 6140 // require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode, md.MarketTradingMode.String()) 6141 // require.Equal(t, totalCommitment.String(), md.SuppliedStake) 6142 // require.True(t, md.MarkPrice.EQ(num.NewUint(matchingPrice))) 6143 6144 // factor := num.DecimalFromFloat(c1) 6145 // supplied, err := num.DecimalFromString(md.SuppliedStake) 6146 // require.NoError(t, err) 6147 // target, err := num.DecimalFromString(md.TargetStake) 6148 // require.NoError(t, err) 6149 // require.True(t, supplied.GreaterThan(target.Mul(factor))) 6150 6151 // // current = (target * c1) auction not triggered 6152 // riskParams := tm.mktCfg.TradableInstrument.GetSimpleRiskModel().Params 6153 // require.NotNil(t, riskParams) 6154 6155 // matchingPriceDec := num.DecimalFromFloat(float64(matchingPrice)) 6156 // if riskParams.FactorLong.GreaterThan(riskParams.FactorShort) { 6157 // matchingPriceDec = matchingPriceDec.Mul(riskParams.FactorLong) 6158 // } else { 6159 // matchingPriceDec = matchingPriceDec.Mul(riskParams.FactorShort) 6160 // } 6161 // maxOrderSizeFp := currentStake.Div(factor.Mul(matchingPriceDec).Mul(tm.mktCfg.LiquidityMonitoringParameters.TargetStakeParameters.ScalingFactor)) 6162 // maxOrderSizeFp = maxOrderSizeFp.Sub(num.DecimalFromFloat(float64(sellConf2.Order.Size))) 6163 // // maxOrderSizeFp := currentStake/(c1*float64(matchingPrice)*math.Max(riskParams.FactorShort, riskParams.FactorLong)*tm.mktCfg.LiquidityMonitoringParameters.TargetStakeParameters.ScalingFactor) - float64(sellConf2.Order.Size) 6164 // require.True(t, maxOrderSizeFp.GreaterThan(num.DecimalFromFloat(1))) 6165 // maxOSize, _ := num.UintFromDecimal(maxOrderSizeFp.Floor()) 6166 // maxOrderSize := maxOSize.Uint64() 6167 6168 // tm.stateVar.OnTick(context.Background(), tm.now.Add(11*time.Minute)) 6169 6170 // // Add orders that will trade (no auction triggered yet) 6171 // buyOrder3 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "buyOrder3", types.SideBuy, party1, maxOrderSize, matchingPrice) 6172 // buyConf3, err := tm.market.SubmitOrder(ctx, buyOrder3) 6173 // require.NoError(t, err) 6174 // require.Equal(t, types.OrderStatusActive, buyConf3.Order.Status) 6175 6176 // md = tm.market.GetMarketData() 6177 // require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6178 6179 // sellOrder3 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "sellOrder3", types.SideSell, party2, maxOrderSize, matchingPrice) 6180 // sellConf3, err := tm.market.SubmitOrder(ctx, sellOrder3) 6181 6182 // tm.now = tm.now.Add(time.Second) 6183 // tm.market.OnTick(ctx, tm.now) 6184 // require.NoError(t, err) 6185 // require.Equal(t, types.OrderStatusFilled, sellConf3.Order.Status) 6186 // require.Equal(t, 1, len(sellConf3.Trades)) 6187 6188 // md = tm.market.GetMarketData() 6189 // require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6190 6191 // supplied, err = num.DecimalFromString(md.SuppliedStake) 6192 // require.NoError(t, err) 6193 // target, err = num.DecimalFromString(md.TargetStake) 6194 // require.NoError(t, err) 6195 // require.True(t, supplied.GreaterThan(target.Mul(factor))) 6196 6197 // // Add orders that will trade and trigger liquidity auction 6198 // buyOrder4 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "buyOrder4", types.SideBuy, party1, 1, matchingPrice) 6199 // buyConf4, err := tm.market.SubmitOrder(ctx, buyOrder4) 6200 // require.NoError(t, err) 6201 // require.Equal(t, types.OrderStatusActive, buyConf4.Order.Status) 6202 6203 // md = tm.market.GetMarketData() 6204 // require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6205 6206 // sellOrder4 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "sellOrder4", types.SideSell, party2, 1, matchingPrice) 6207 // sellConf4, err := tm.market.SubmitOrder(ctx, sellOrder4) 6208 6209 // tm.now = tm.now.Add(time.Second) 6210 // tm.market.OnTick(ctx, tm.now) 6211 // require.Equal(t, types.MarketStateSuspended, tm.market.State()) // enter auction 6212 // require.NoError(t, err) 6213 // // this order will now get filled before triggering auction 6214 // require.Equal(t, types.OrderStatusFilled, sellConf4.Order.Status, sellConf4.Order.Status.String()) 6215 // require.Equal(t, 1, len(sellConf4.Trades)) 6216 6217 // md = tm.market.GetMarketData() 6218 // require.Equal(t, types.MarketTradingModeMonitoringAuction, md.MarketTradingMode) 6219 // require.Equal(t, types.AuctionTriggerLiquidityTargetNotMet, md.Trigger) 6220 6221 // // don't use AddSum, we need to keep the original amount somewhere 6222 // lpa2 := &types.LiquidityProvisionAmendment{ 6223 // CommitmentAmount: num.Sum(lp2sub.CommitmentAmount, num.NewUint(25750)), 6224 // } 6225 // require.NoError(t, 6226 // tm.market.AmendLiquidityProvision(ctx, lpa2, lp2, vgcrypto.RandomHash()), 6227 // ) 6228 6229 // // progress time so liquidity auction ends 6230 // tm.now = tm.now.Add(2 * time.Second) 6231 // tm.market.OnTick(ctx, tm.now) 6232 // require.Equal(t, types.MarketStateActive, tm.market.State()) // left auction 6233 6234 // md = tm.market.GetMarketData() 6235 // require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6236 6237 // supplied, err = num.DecimalFromString(md.SuppliedStake) 6238 // require.NoError(t, err) 6239 // target, err = num.DecimalFromString(md.TargetStake) 6240 // require.NoError(t, err) 6241 // require.True(t, supplied.GreaterThanOrEqual(target)) 6242 6243 // require.NoError(t, err) 6244 // require.Equal(t, types.OrderStatusFilled, sellConf4.Order.Status) 6245 6246 // // Bringing commitment back to old level shouldn't be allowed 6247 6248 // lpa2.CommitmentAmount = lp2Commitment.Clone() 6249 // require.Error(t, 6250 // tm.market.AmendLiquidityProvision(ctx, lpa2, lp2, vgcrypto.RandomHash()), 6251 // ) 6252 6253 // md = tm.market.GetMarketData() 6254 // var zero uint64 6255 // require.Greater(t, md.BestStaticBidVolume, zero) 6256 6257 // // Cancelling best_bid should start auction 6258 // conf, err := tm.market.CancelOrder(ctx, buyOrder1.Party, buyOrder1.ID, vgcrypto.RandomHash()) 6259 // require.NoError(t, err) 6260 // require.NotNil(t, conf) 6261 // tm.now = tm.now.Add(time.Second) 6262 // tm.market.OnTick(ctx, tm.now) 6263 // require.Equal(t, types.MarketStateSuspended, tm.market.State()) // enter auction 6264 6265 // // Submitting an order on buy side so that best_bid does exist should stop an auction 6266 // md = tm.market.GetMarketData() 6267 // require.Equal(t, zero, md.BestStaticBidVolume) 6268 // require.Equal(t, types.MarketTradingModeMonitoringAuction, md.MarketTradingMode) 6269 // require.Equal(t, types.AuctionTriggerUnableToDeployLPOrders, md.Trigger) 6270 6271 // supplied, err = num.DecimalFromString(md.SuppliedStake) 6272 // require.NoError(t, err) 6273 // target, err = num.DecimalFromString(md.TargetStake) 6274 // require.NoError(t, err) 6275 // require.True(t, supplied.GreaterThanOrEqual(target)) 6276 6277 // buyOrder5 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "buyOrder5", types.SideBuy, party1, 1, matchingPrice-10) 6278 // buyConf5, err := tm.market.SubmitOrder(ctx, buyOrder5) 6279 // require.NoError(t, err) 6280 // require.Equal(t, types.OrderStatusActive, buyConf5.Order.Status) 6281 6282 // // progress time to end auction 6283 // tm.now = tm.now.Add(2 * time.Second) 6284 // tm.market.OnTick(ctx, tm.now) 6285 // require.Equal(t, types.MarketStateActive, tm.market.State()) // left auction 6286 6287 // // Submitting an order on buy side so that best_bid does exist should stop an auction 6288 // md = tm.market.GetMarketData() 6289 // require.Equal(t, buyOrder5.Size, md.BestStaticBidVolume) 6290 // require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6291 6292 // supplied, err = num.DecimalFromString(md.SuppliedStake) 6293 // require.NoError(t, err) 6294 // target, err = num.DecimalFromString(md.TargetStake) 6295 // require.NoError(t, err) 6296 // require.True(t, supplied.GreaterThanOrEqual(target)) 6297 6298 // // Trading with best-ask, so it disappears should start an auction 6299 // buyOrder6 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "buyOrder6", types.SideBuy, party1, 1, sellOrder1.Price.Uint64()) 6300 // buyConf6, err := tm.market.SubmitOrder(ctx, buyOrder6) 6301 // require.NoError(t, err) 6302 // require.Equal(t, types.OrderStatusFilled, buyConf6.Order.Status) 6303 // require.Equal(t, 1, len(buyConf6.Trades)) 6304 // tm.now = tm.now.Add(time.Second) 6305 // tm.market.OnTick(ctx, tm.now) 6306 // require.Equal(t, types.MarketStateSuspended, tm.market.State()) // enter auction 6307 6308 // md = tm.market.GetMarketData() 6309 // require.Equal(t, zero, md.BestStaticOfferVolume) 6310 // require.Equal(t, types.MarketTradingModeMonitoringAuction, md.MarketTradingMode) 6311 // require.Equal(t, types.AuctionTriggerUnableToDeployLPOrders, md.Trigger) 6312 6313 // supplied, err = num.DecimalFromString(md.SuppliedStake) 6314 // require.NoError(t, err) 6315 // target, err = num.DecimalFromString(md.TargetStake) 6316 // require.NoError(t, err) 6317 // require.True(t, supplied.LessThan(target)) 6318 // require.True(t, supplied.GreaterThan(target.Mul(factor))) 6319 6320 // // Increasing total stake so that the new target stake is accommodated AND adding a sell so best_ask exists should stop the auction 6321 6322 // lpa1 := &types.LiquidityProvisionAmendment{ 6323 // CommitmentAmount: num.Sum(lp1Commitment, num.NewUint(10000)), 6324 // } 6325 // err = tm.market.AmendLiquidityProvision(ctx, lpa1, lp1, vgcrypto.RandomHash()) 6326 // require.NoError(t, err) 6327 6328 // md = tm.market.GetMarketData() 6329 // require.Equal(t, types.MarketTradingModeMonitoringAuction, md.MarketTradingMode) 6330 // require.Equal(t, types.AuctionTriggerUnableToDeployLPOrders, md.Trigger) 6331 6332 // supplied, err = num.DecimalFromString(md.SuppliedStake) 6333 // require.NoError(t, err) 6334 // target, err = num.DecimalFromString(md.TargetStake) 6335 // require.NoError(t, err) 6336 // require.True(t, supplied.GreaterThanOrEqual(target)) 6337 6338 // sellOrder5 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "sellOrder5", types.SideSell, party2, 1, matchingPrice-5) 6339 // sellConf5, err := tm.market.SubmitOrder(ctx, sellOrder5) 6340 6341 // require.NoError(t, err) 6342 // require.Equal(t, types.OrderStatusActive, sellConf5.Order.Status) 6343 // require.Equal(t, 0, len(sellConf5.Trades)) 6344 6345 // tm.now = tm.now.Add(2 * time.Second) 6346 // tm.market.OnTick(ctx, tm.now) 6347 // require.Equal(t, types.MarketStateActive, tm.market.State()) // left auction 6348 6349 // md = tm.market.GetMarketData() 6350 // require.Equal(t, sellOrder5.Size, md.BestStaticOfferVolume) 6351 // require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6352 6353 // supplied, err = num.DecimalFromString(md.SuppliedStake) 6354 // require.NoError(t, err) 6355 // target, err = num.DecimalFromString(md.TargetStake) 6356 // require.NoError(t, err) 6357 // require.True(t, supplied.GreaterThanOrEqual(target)) 6358 // } 6359 6360 func TestLiquidityMonitoring_BestBidAskExistAfterAuction(t *testing.T) { 6361 now := time.Unix(10, 0) 6362 openingDuration := &types.AuctionDuration{ 6363 Duration: 1, 6364 } 6365 tm := getTestMarket(t, now, nil, openingDuration) 6366 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 6367 err := tm.market.OnMarketTargetStakeScalingFactorUpdate(num.DecimalFromFloat(0.0)) 6368 require.NoError(t, err) 6369 md := tm.market.GetMarketData() 6370 require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode) 6371 6372 lp1 := "lp1" 6373 party1 := "party1" 6374 party2 := "party2" 6375 6376 addAccount(t, tm, lp1) 6377 addAccount(t, tm, party1) 6378 addAccount(t, tm, party2) 6379 6380 var lp1Commitment uint64 = 50000 6381 6382 var matchingPrice uint64 = 100 6383 // Add orders that will stay on the book thus maintaining best_bid and best_ask 6384 buyOrder1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "buyOrder1", types.SideBuy, party1, 1, matchingPrice-10) 6385 buyConf1, err := tm.market.SubmitOrder(ctx, buyOrder1) 6386 require.NoError(t, err) 6387 require.Equal(t, types.OrderStatusActive, buyConf1.Order.Status) 6388 6389 md = tm.market.GetMarketData() 6390 require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode) 6391 6392 sellOrder1 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "sellOrder1", types.SideSell, party2, 1, matchingPrice+10) 6393 sellConf1, err := tm.market.SubmitOrder(ctx, sellOrder1) 6394 require.NoError(t, err) 6395 require.Equal(t, types.OrderStatusActive, sellConf1.Order.Status) 6396 tm.market.OnTick(ctx, tm.now) 6397 6398 md = tm.market.GetMarketData() 6399 require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode) 6400 6401 lp1sub := &types.LiquidityProvisionSubmission{ 6402 MarketID: tm.market.GetID(), 6403 CommitmentAmount: num.NewUint(lp1Commitment), 6404 Fee: num.DecimalFromFloat(0.05), 6405 } 6406 6407 require.NoError(t, 6408 tm.market.SubmitLiquidityProvision(ctx, lp1sub, lp1, vgcrypto.RandomHash()), 6409 ) 6410 6411 md = tm.market.GetMarketData() 6412 require.Equal(t, types.MarketTradingModeOpeningAuction, md.MarketTradingMode) 6413 6414 buyOrder2 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "buyOrder2", types.SideBuy, party1, 1, matchingPrice) 6415 buyConf2, err := tm.market.SubmitOrder(ctx, buyOrder2) 6416 require.NoError(t, err) 6417 require.Equal(t, types.OrderStatusActive, buyConf2.Order.Status) 6418 6419 sellOrder2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "sellOrder2", types.SideSell, party2, 1, matchingPrice) 6420 sellConf2, err := tm.market.SubmitOrder(ctx, sellOrder2) 6421 require.NoError(t, err) 6422 require.Equal(t, types.OrderStatusActive, sellConf2.Order.Status) 6423 require.Equal(t, 0, len(sellConf2.Trades)) 6424 6425 tm.now = tm.now.Add(time.Second * time.Duration(openingDuration.Duration)).Add(time.Millisecond) 6426 closed := tm.market.OnTick(ctx, tm.now) 6427 require.False(t, closed) 6428 6429 md = tm.market.GetMarketData() 6430 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6431 require.True(t, md.MarkPrice.EQ(num.NewUint(matchingPrice))) 6432 require.Equal(t, "0", md.TargetStake) 6433 6434 sellOrder3 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "sellOrder3", types.SideSell, party2, 1, buyOrder1.Price.Uint64()) 6435 sellConf3, err := tm.market.SubmitOrder(ctx, sellOrder3) 6436 tm.now = tm.now.Add(time.Second) 6437 tm.market.OnTick(ctx, tm.now) 6438 require.Equal(t, types.MarketStateActive, tm.market.State()) // enter auction 6439 require.NoError(t, err) 6440 require.Equal(t, types.OrderStatusFilled, sellConf3.Order.Status) 6441 6442 md = tm.market.GetMarketData() 6443 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6444 // require.Equal(t, types.AuctionTriggerLiquidityTargetNotMet, md.Trigger) 6445 6446 buyOrder3 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "buyOrder3", types.SideBuy, party1, 1, sellOrder1.Price.Uint64()) 6447 buyConf3, err := tm.market.SubmitOrder(ctx, buyOrder3) 6448 require.NoError(t, err) 6449 require.Equal(t, types.OrderStatusFilled, buyConf3.Order.Status) 6450 6451 md = tm.market.GetMarketData() 6452 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6453 // require.Equal(t, types.AuctionTriggerLiquidityTargetNotMet, md.Trigger) 6454 6455 sellOrder4 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "sellOrder4", types.SideSell, party2, 11, sellOrder1.Price.Uint64()+1) 6456 sellConf4, err := tm.market.SubmitOrder(ctx, sellOrder4) 6457 require.NoError(t, err) 6458 require.Equal(t, types.OrderStatusActive, sellConf4.Order.Status) 6459 tm.now = tm.now.Add(time.Second) 6460 tm.market.OnTick(ctx, tm.now) 6461 6462 md = tm.market.GetMarketData() 6463 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6464 // require.Equal(t, types.AuctionTriggerLiquidityTargetNotMet, md.Trigger) 6465 6466 buyOrder4 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "buyOrder4", types.SideBuy, party1, 1, buyOrder1.Price.Uint64()-1) 6467 buyConf4, err := tm.market.SubmitOrder(ctx, buyOrder4) 6468 require.NoError(t, err) 6469 require.Equal(t, types.OrderStatusActive, buyConf4.Order.Status) 6470 6471 // we have to wait for the auction to end 6472 tm.now = tm.now.Add(2 * time.Second) 6473 tm.market.OnTick(ctx, tm.now) 6474 require.Equal(t, types.MarketStateActive, tm.market.State()) // left auction 6475 6476 md = tm.market.GetMarketData() 6477 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 6478 require.Equal(t, types.AuctionTriggerUnspecified, md.Trigger) 6479 } 6480 6481 func TestAmendTrade(t *testing.T) { 6482 now := time.Unix(10, 0) 6483 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 6484 6485 auctionEnd := now.Add(10001 * time.Second) 6486 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{ 6487 Duration: 10000, 6488 }) 6489 mktCfg.Fees.Factors = &types.FeeFactors{ 6490 LiquidityFee: num.DecimalFromFloat(0.001), 6491 InfrastructureFee: num.DecimalFromFloat(0.0005), 6492 MakerFee: num.DecimalFromFloat(0.00025), 6493 } 6494 mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{ 6495 LogNormalRiskModel: &types.LogNormalRiskModel{ 6496 RiskAversionParameter: num.DecimalFromFloat(0.001), 6497 Tau: num.DecimalFromFloat(0.00011407711613050422), 6498 Params: &types.LogNormalModelParams{ 6499 Mu: num.DecimalZero(), 6500 R: num.DecimalFromFloat(0.016), 6501 Sigma: num.DecimalFromFloat(20), 6502 }, 6503 }, 6504 } 6505 6506 lpparty := "lp-party-1" 6507 lpparty2 := "lp-party-2" 6508 lpparty3 := "lp-party-3" 6509 6510 p1 := "p1" 6511 p2 := "p2" 6512 6513 tm := newTestMarket(t, now).Run(ctx, mktCfg) 6514 tm.StartOpeningAuction(). 6515 // the liquidity provider 6516 WithAccountAndAmount(lpparty, 500000000000). 6517 WithAccountAndAmount(lpparty2, 500000000000). 6518 WithAccountAndAmount(lpparty3, 500000000000). 6519 WithAccountAndAmount(p1, 500000000000). 6520 WithAccountAndAmount(p2, 500000000000) 6521 addAccountWithAmount(tm, "lpprov", 10000000) 6522 6523 lp := &types.LiquidityProvisionSubmission{ 6524 MarketID: tm.market.GetID(), 6525 CommitmentAmount: num.NewUint(55000), 6526 Fee: num.DecimalFromFloat(0.01), 6527 } 6528 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 6529 tm.EndOpeningAuction(t, auctionEnd, false) 6530 6531 assert.Equal(t, types.MarketTradingModeContinuous, tm.market.GetMarketData().MarketTradingMode) 6532 6533 tm.events = nil 6534 6535 p1Order := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "pid1", types.SideBuy, p1, 10, 1010) 6536 p2Order := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "pid2", types.SideSell, p2, 10, 1050) 6537 6538 p1conf, err := tm.market.SubmitOrder(ctx, p1Order) 6539 assert.NoError(t, err) 6540 assert.Len(t, p1conf.Trades, 0) 6541 6542 p2conf, err := tm.market.SubmitOrder(ctx, p2Order) 6543 assert.NoError(t, err) 6544 assert.Len(t, p2conf.Trades, 0) 6545 6546 assert.Equal(t, types.MarketTradingModeContinuous, tm.market.GetMarketData().MarketTradingMode) 6547 6548 // now we 6549 amend := types.OrderAmendment{ 6550 OrderID: p1conf.Order.ID, 6551 MarketID: p1conf.Order.MarketID, 6552 Price: num.NewUint(1050), 6553 } 6554 6555 tm.events = nil 6556 amendConf, err := tm.market.AmendOrder(ctx, &amend, p1conf.Order.Party, vgcrypto.RandomHash()) 6557 assert.NoError(t, err) 6558 assert.Len(t, amendConf.Trades, 1) 6559 6560 ps := map[string]*events.PositionState{} 6561 for _, v := range tm.events { 6562 if e, ok := v.(*events.PositionState); ok { 6563 ps[e.PartyID()] = e 6564 } 6565 } 6566 6567 assert.Len(t, ps, 2) 6568 assert.Equal(t, int(ps[p1].Size()), 10) 6569 assert.Equal(t, int(ps[p2].Size()), -10) 6570 } 6571 6572 func Test_7017_UpdatingMarketDuringOpeningAuction(t *testing.T) { 6573 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 6574 pMonitorSettings := &types.PriceMonitoringSettings{ 6575 Parameters: &types.PriceMonitoringParameters{ 6576 Triggers: []*types.PriceMonitoringTrigger{}, 6577 }, 6578 } 6579 openingAuctionDuration := 10 * time.Minute 6580 mktCfg := getMarket(pMonitorSettings, &types.AuctionDuration{ 6581 Duration: int64(openingAuctionDuration.Seconds()), 6582 }) 6583 lpParty := "party-LP" 6584 trader1 := "party-trader-1" 6585 trader2 := "party-trader-2" 6586 tm := newTestMarket(t, time.Unix(10, 0)).Run(ctx, mktCfg) 6587 tm.market.OnTick(ctx, tm.now) 6588 tm.StartOpeningAuction(). 6589 WithAccountAndAmount(lpParty, 1000000). 6590 WithAccountAndAmount(trader1, 100000). 6591 WithAccountAndAmount(trader2, 100000) 6592 6593 // submit limit orders 6594 midPrice := uint64(1000) 6595 limitOrders := []*types.Order{ 6596 getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "lo-1", types.SideBuy, trader1, 10, midPrice-uint64(250)), 6597 getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "lo-2", types.SideBuy, trader1, 10, midPrice), 6598 getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "lo-3", types.SideSell, trader2, 10, midPrice), 6599 getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "lo-4", types.SideSell, trader2, 10, midPrice+uint64(250)), 6600 } 6601 for _, o := range limitOrders { 6602 conf, err := tm.market.SubmitOrder(ctx, o) 6603 require.NoError(t, err) 6604 require.NotNil(t, conf) 6605 } 6606 6607 tm.now = tm.now.Add(time.Minute) 6608 tm.market.OnTick(ctx, tm.now) 6609 6610 require.NoError(t, tm.market.Update(ctx, &mktCfg, tm.oracleEngine)) 6611 6612 tm.now = tm.now.Add(time.Minute) 6613 tm.market.OnTick(ctx, tm.now) 6614 6615 lps := &types.LiquidityProvisionSubmission{ 6616 MarketID: tm.market.GetID(), 6617 CommitmentAmount: num.NewUint(70000), 6618 Fee: num.DecimalFromFloat(0.05), 6619 Reference: "ref-lp-submission-1", 6620 } 6621 6622 require.NoError(t, 6623 tm.market.SubmitLiquidityProvision( 6624 ctx, lps, lpParty, vgcrypto.RandomHash()), 6625 ) 6626 6627 // leave opening auction 6628 tm.now = tm.now.Add(openingAuctionDuration) 6629 tm.market.OnTick(ctx, tm.now) 6630 require.Equal(t, types.MarketTradingModeContinuous, tm.market.GetMarketData().MarketTradingMode) 6631 } 6632 6633 func TestLiquidityFeeSettingsWeightedAverage(t *testing.T) { 6634 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 6635 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}) 6636 mktCfg.Fees.LiquidityFeeSettings = &types.LiquidityFeeSettings{ 6637 Method: proto.LiquidityFeeSettings_METHOD_WEIGHTED_AVERAGE, 6638 } 6639 6640 now := time.Unix(10, 0) 6641 tm := newTestMarket(t, now).Run(context.Background(), mktCfg) 6642 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 6643 6644 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 6645 addAccountWithAmount(tm, "lpprov", 10000000) 6646 addAccountWithAmount(tm, "lpprov2", 10000000) 6647 tm.StartOpeningAuction() 6648 6649 lp := &types.LiquidityProvisionSubmission{ 6650 MarketID: tm.market.GetID(), 6651 CommitmentAmount: num.NewUint(27500), 6652 Fee: num.DecimalFromFloat(0.01), 6653 } 6654 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 6655 6656 lp2 := &types.LiquidityProvisionSubmission{ 6657 MarketID: tm.market.GetID(), 6658 CommitmentAmount: num.NewUint(27500), 6659 Fee: num.DecimalFromFloat(0.02), 6660 } 6661 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp2, "lpprov2", vgcrypto.RandomHash())) 6662 6663 // leave opening auction 6664 now = now.Add(2 * time.Second) 6665 tm.now = now 6666 tm.market.OnTick(ctx, now) 6667 6668 var fee string 6669 for _, evt := range tm.events { 6670 if mup, ok := evt.(*events.MarketUpdated); ok { 6671 fee = mup.Market().Fees.Factors.LiquidityFee 6672 } 6673 } 6674 // two LPs with same comittment, fee should be the average (0.01 + 0.02) / 2 = 0.015 6675 assert.Equal(t, "0.015", fee) 6676 } 6677 6678 func TestLiquidityFeeSettingsConstantFee(t *testing.T) { 6679 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 6680 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{Duration: 1}) 6681 mktCfg.Fees.LiquidityFeeSettings = &types.LiquidityFeeSettings{ 6682 Method: proto.LiquidityFeeSettings_METHOD_CONSTANT, 6683 FeeConstant: num.NewDecimalFromFloat(0.8), 6684 } 6685 6686 now := time.Unix(10, 0) 6687 tm := newTestMarket(t, now).Run(context.Background(), mktCfg) 6688 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 6689 6690 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 6691 addAccountWithAmount(tm, "lpprov", 10000000) 6692 addAccountWithAmount(tm, "lpprov2", 10000000) 6693 tm.StartOpeningAuction() 6694 6695 lp := &types.LiquidityProvisionSubmission{ 6696 MarketID: tm.market.GetID(), 6697 CommitmentAmount: num.NewUint(27500), 6698 Fee: num.DecimalFromFloat(0.01), 6699 } 6700 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 6701 6702 lp2 := &types.LiquidityProvisionSubmission{ 6703 MarketID: tm.market.GetID(), 6704 CommitmentAmount: num.NewUint(27500), 6705 Fee: num.DecimalFromFloat(0.02), 6706 } 6707 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp2, "lpprov2", vgcrypto.RandomHash())) 6708 6709 // leave opening auction 6710 now = now.Add(2 * time.Second) 6711 tm.now = now 6712 tm.market.OnTick(ctx, now) 6713 6714 var fee string 6715 for _, evt := range tm.events { 6716 if mup, ok := evt.(*events.MarketUpdated); ok { 6717 fee = mup.Market().Fees.Factors.LiquidityFee 6718 } 6719 } 6720 // doesn't matter what the LP's set in their nomination, the fee is going to be a constant 0.8 6721 assert.Equal(t, "0.8", fee) 6722 } 6723 6724 func TestVerifyAMMBounds(t *testing.T) { 6725 tests := []struct { 6726 name string 6727 params *types.ConcentratedLiquidityParameters 6728 maxCap *num.Uint 6729 priceFactor num.Decimal 6730 expectedErr error 6731 }{ 6732 { 6733 name: "normal valid bounds", 6734 params: &types.ConcentratedLiquidityParameters{ 6735 LowerBound: num.NewUint(82), 6736 Base: num.NewUint(85), 6737 UpperBound: num.NewUint(88), 6738 }, 6739 priceFactor: num.NewDecimalFromFloat(1.1), 6740 }, 6741 { 6742 name: "lower greater than base with fewer decimals", 6743 params: &types.ConcentratedLiquidityParameters{ 6744 LowerBound: num.NewUint(80), 6745 Base: num.NewUint(85), 6746 UpperBound: num.NewUint(90), 6747 }, 6748 priceFactor: num.NewDecimalFromFloat(0.1), 6749 expectedErr: fmt.Errorf("base (8) as factored by market and asset decimals must be greater than lower bound (8)"), 6750 }, 6751 { 6752 name: "base greater than base with fewer decimals", 6753 params: &types.ConcentratedLiquidityParameters{ 6754 LowerBound: num.NewUint(80), 6755 Base: num.NewUint(85), 6756 UpperBound: num.NewUint(88), 6757 }, 6758 priceFactor: num.NewDecimalFromFloat(0.1), 6759 expectedErr: fmt.Errorf("base (8) as factored by market and asset decimals must be greater than lower bound (8)"), 6760 }, 6761 { 6762 name: "both bounds too close with fewer decimals", 6763 params: &types.ConcentratedLiquidityParameters{ 6764 LowerBound: num.NewUint(82), 6765 Base: num.NewUint(85), 6766 UpperBound: num.NewUint(88), 6767 }, 6768 priceFactor: num.NewDecimalFromFloat(0.1), 6769 expectedErr: fmt.Errorf("base (8) as factored by market and asset decimals must be greater than lower bound (8)"), 6770 }, 6771 { 6772 name: "upper bound higher than cap", 6773 params: &types.ConcentratedLiquidityParameters{ 6774 LowerBound: num.NewUint(82), 6775 Base: num.NewUint(85), 6776 UpperBound: num.NewUint(88), 6777 }, 6778 priceFactor: num.NewDecimalFromFloat(1), 6779 maxCap: num.NewUint(86), 6780 expectedErr: common.ErrAMMBoundsOutsidePriceCap, 6781 }, 6782 { 6783 name: "upper bound equal cap", 6784 params: &types.ConcentratedLiquidityParameters{ 6785 LowerBound: num.NewUint(82), 6786 Base: num.NewUint(85), 6787 UpperBound: num.NewUint(88), 6788 }, 6789 priceFactor: num.NewDecimalFromFloat(1), 6790 maxCap: num.NewUint(88), 6791 expectedErr: common.ErrAMMBoundsOutsidePriceCap, 6792 }, 6793 { 6794 name: "base higher than cap", 6795 params: &types.ConcentratedLiquidityParameters{ 6796 LowerBound: num.NewUint(82), 6797 Base: num.NewUint(100), 6798 }, 6799 priceFactor: num.NewDecimalFromFloat(1), 6800 maxCap: num.NewUint(86), 6801 expectedErr: common.ErrAMMBoundsOutsidePriceCap, 6802 }, 6803 { 6804 name: "base equal cap", 6805 params: &types.ConcentratedLiquidityParameters{ 6806 LowerBound: num.NewUint(82), 6807 Base: num.NewUint(88), 6808 }, 6809 priceFactor: num.NewDecimalFromFloat(1), 6810 maxCap: num.NewUint(88), 6811 expectedErr: common.ErrAMMBoundsOutsidePriceCap, 6812 }, 6813 } 6814 6815 for _, tt := range tests { 6816 t.Run(tt.name, func(t *testing.T) { 6817 err := future.VerifyAMMBounds(tt.params, tt.maxCap, tt.priceFactor) 6818 assert.Equal(t, tt.expectedErr, err) 6819 }) 6820 } 6821 }