code.vegaprotocol.io/vega@v0.79.0/core/execution/future/order_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 "testing" 21 "time" 22 23 "code.vegaprotocol.io/vega/core/events" 24 "code.vegaprotocol.io/vega/core/types" 25 vegacontext "code.vegaprotocol.io/vega/libs/context" 26 vgcrypto "code.vegaprotocol.io/vega/libs/crypto" 27 "code.vegaprotocol.io/vega/libs/num" 28 29 "github.com/golang/mock/gomock" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 ) 33 34 func TestReduceOnly(t *testing.T) { 35 lpparty := "lp-party-1" 36 lpparty2 := "lp-party-2" 37 lpparty3 := "lp-party-3" 38 39 p1 := "p1" 40 p2 := "p2" 41 42 party1 := "party1" 43 44 now := time.Unix(10, 0) 45 auctionEnd := now.Add(10001 * time.Second) 46 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 47 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{ 48 Duration: 10000, 49 }) 50 tm := newTestMarket(t, now).Run(ctx, mktCfg) 51 tm.StartOpeningAuction(). 52 // the liquidity provider 53 WithAccountAndAmount(lpparty, 500000000000). 54 WithAccountAndAmount(lpparty2, 500000000000). 55 WithAccountAndAmount(lpparty3, 500000000000). 56 WithAccountAndAmount(p1, 500000000000). 57 WithAccountAndAmount(p2, 500000000000) 58 addAccountWithAmount(tm, "lpprov", 10000000) 59 60 lp := &types.LiquidityProvisionSubmission{ 61 MarketID: tm.market.GetID(), 62 CommitmentAmount: num.NewUint(55000), 63 Fee: num.DecimalFromFloat(0.01), 64 } 65 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 66 tm.EndOpeningAuction(t, auctionEnd, false) 67 68 addAccountWithAmount(tm, party1, 10000000000) 69 70 volumeOrder := &types.Order{ 71 Status: types.OrderStatusActive, 72 Type: types.OrderTypeLimit, 73 TimeInForce: types.OrderTimeInForceGTC, 74 ID: "someid", 75 Side: types.SideBuy, 76 Party: lpparty, 77 MarketID: tm.market.GetID(), 78 Size: 100, 79 Price: num.NewUint(900), 80 Remaining: 100, 81 CreatedAt: now.UnixNano(), 82 Reference: "party1-buy-order", 83 } 84 confirmation, err := tm.market.SubmitOrder(context.TODO(), volumeOrder) 85 assert.NotNil(t, confirmation) 86 assert.NoError(t, err) 87 volumeOrder.Price = num.NewUint(1100) 88 volumeOrder.Side = types.SideSell 89 90 confirmation, err = tm.market.SubmitOrder(context.TODO(), volumeOrder) 91 assert.NotNil(t, confirmation) 92 assert.NoError(t, err) 93 94 orderBuy := &types.Order{ 95 Status: types.OrderStatusActive, 96 Type: types.OrderTypeMarket, 97 TimeInForce: types.OrderTimeInForceIOC, 98 ID: "someid", 99 Side: types.SideBuy, 100 Party: party1, 101 MarketID: tm.market.GetID(), 102 Size: 5, 103 Price: num.UintZero(), 104 Remaining: 5, 105 CreatedAt: now.UnixNano(), 106 Reference: "party1-buy-order", 107 ReduceOnly: true, 108 } 109 110 // Submit the original order, it will return an error 111 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 112 assert.Nil(t, confirmation) 113 assert.EqualError(t, err, "OrderError: reduce only order would not reduce position") 114 115 orderBuy.TimeInForce = types.OrderTimeInForceFOK 116 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 117 assert.Nil(t, confirmation) 118 assert.EqualError(t, err, "OrderError: reduce only order would not reduce position") 119 120 // now open a position 121 orderBuy.ReduceOnly = false 122 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 123 assert.NotNil(t, confirmation) 124 assert.NoError(t, err) 125 126 // now try to reduce the position fully with an FOK bigger than the position 127 orderBuy.TimeInForce = types.OrderTimeInForceFOK 128 orderBuy.Side = types.SideSell 129 orderBuy.ReduceOnly = true 130 orderBuy.Size = 10 131 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 132 assert.Nil(t, confirmation) 133 assert.EqualError(t, err, "OrderError: reduce only order would not reduce position") 134 135 // now try to reduce the position a little with FOK 136 orderBuy.TimeInForce = types.OrderTimeInForceFOK 137 orderBuy.ReduceOnly = true 138 orderBuy.Size = 2 139 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 140 assert.NotNil(t, confirmation) 141 assert.NoError(t, err) 142 143 // now fully clear with FOK 144 orderBuy.TimeInForce = types.OrderTimeInForceFOK 145 orderBuy.ReduceOnly = true 146 orderBuy.Size = 3 147 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 148 assert.NotNil(t, confirmation) 149 assert.NoError(t, err) 150 151 // now fully do it again just to make sure we are done with it 152 orderBuy.TimeInForce = types.OrderTimeInForceFOK 153 orderBuy.ReduceOnly = true 154 orderBuy.Size = 1 155 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 156 assert.Nil(t, confirmation) 157 assert.EqualError(t, err, "OrderError: reduce only order would not reduce position") 158 159 // Now try again with IOC and the inverse situation 160 // we start by selling 161 orderSell := orderBuy 162 orderSell.TimeInForce = types.OrderTimeInForceIOC 163 orderSell.Side = types.SideSell 164 orderSell.Size = 5 165 orderSell.ReduceOnly = false 166 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 167 assert.NotNil(t, confirmation) 168 assert.NoError(t, err) 169 170 // now try to reduce it a little 171 // we should have a position of 2 after 172 orderSell.Side = types.SideBuy 173 orderSell.Size = 3 174 orderSell.ReduceOnly = true 175 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 176 assert.NotNil(t, confirmation) 177 assert.NoError(t, err) 178 179 // now try to reduce which would flip the position 180 // this will trade just enough to get to 0 181 // we should have a position of 0 after 182 orderSell.Side = types.SideBuy 183 orderSell.Size = 5 184 orderSell.ReduceOnly = true 185 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 186 assert.NotNil(t, confirmation) 187 assert.NoError(t, err) 188 assert.Equal(t, int(confirmation.Order.Remaining), 3) 189 } 190 191 func TestPostOnly(t *testing.T) { 192 lpparty := "lp-party-1" 193 lpparty2 := "lp-party-2" 194 lpparty3 := "lp-party-3" 195 196 p1 := "p1" 197 p2 := "p2" 198 199 party1 := "party1" 200 201 now := time.Unix(10, 0) 202 auctionEnd := now.Add(10001 * time.Second) 203 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 204 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{ 205 Duration: 10000, 206 }) 207 tm := newTestMarket(t, now).Run(ctx, mktCfg) 208 tm.StartOpeningAuction(). 209 // the liquidity provider 210 WithAccountAndAmount(lpparty, 500000000000). 211 WithAccountAndAmount(lpparty2, 500000000000). 212 WithAccountAndAmount(lpparty3, 500000000000). 213 WithAccountAndAmount(p1, 500000000000). 214 WithAccountAndAmount(p2, 500000000000) 215 addAccountWithAmount(tm, "lpprov", 10000000) 216 217 lp := &types.LiquidityProvisionSubmission{ 218 MarketID: tm.market.GetID(), 219 CommitmentAmount: num.NewUint(55000), 220 Fee: num.DecimalFromFloat(0.01), 221 } 222 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 223 tm.EndOpeningAuction(t, auctionEnd, false) 224 225 addAccountWithAmount(tm, party1, 10000000000) 226 227 orderBuy := &types.Order{ 228 Status: types.OrderStatusActive, 229 Type: types.OrderTypeLimit, 230 TimeInForce: types.OrderTimeInForceGTC, 231 ID: "someid", 232 Side: types.SideBuy, 233 Party: party1, 234 MarketID: tm.market.GetID(), 235 Size: 1, 236 Price: num.NewUint(1100), 237 Remaining: 100, 238 CreatedAt: now.UnixNano(), 239 Reference: "party1-buy-order", 240 PostOnly: true, 241 } 242 243 // Submit the original order, this would trade, so expect an error 244 confirmation, err := tm.market.SubmitOrder(context.TODO(), orderBuy) 245 assert.Nil(t, confirmation) 246 assert.EqualError(t, err, "OrderError: post only order would trade") 247 248 // Submit the original order this would not trade, so we expect a success 249 orderBuy.Price = num.NewUint(100) 250 confirmation, err = tm.market.SubmitOrder(context.TODO(), orderBuy) 251 assert.NotNil(t, confirmation) 252 assert.NoError(t, err) 253 } 254 255 func TestAmendMarginCheckFails(t *testing.T) { 256 lpparty := "lp-party-1" 257 lpparty2 := "lp-party-2" 258 lpparty3 := "lp-party-3" 259 260 p1 := "p1" 261 p2 := "p2" 262 263 party1 := "party1" 264 265 now := time.Unix(10, 0) 266 auctionEnd := now.Add(10001 * time.Second) 267 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 268 mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{ 269 Duration: 10000, 270 }) 271 tm := newTestMarket(t, now).Run(ctx, mktCfg) 272 tm.StartOpeningAuction(). 273 // the liquidity provider 274 WithAccountAndAmount(lpparty, 500000000000). 275 WithAccountAndAmount(lpparty2, 500000000000). 276 WithAccountAndAmount(lpparty3, 500000000000). 277 WithAccountAndAmount(p1, 500000000000). 278 WithAccountAndAmount(p2, 500000000000) 279 addAccountWithAmount(tm, "lpprov", 10000000) 280 281 lp := &types.LiquidityProvisionSubmission{ 282 MarketID: tm.market.GetID(), 283 CommitmentAmount: num.NewUint(55000), 284 Fee: num.DecimalFromFloat(0.01), 285 } 286 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 287 tm.EndOpeningAuction(t, auctionEnd, false) 288 289 addAccountWithAmount(tm, party1, 18001) 290 291 orderBuy := &types.Order{ 292 Status: types.OrderStatusActive, 293 Type: types.OrderTypeLimit, 294 TimeInForce: types.OrderTimeInForceGTC, 295 ID: "someid", 296 Side: types.SideBuy, 297 Party: party1, 298 MarketID: tm.market.GetID(), 299 Size: 100, 300 Price: num.NewUint(100), 301 Remaining: 100, 302 CreatedAt: now.UnixNano(), 303 Reference: "party1-buy-order", 304 } 305 306 // Submit the original order 307 confirmation, err := tm.market.SubmitOrder(context.TODO(), orderBuy) 308 assert.NotNil(t, confirmation) 309 assert.NoError(t, err) 310 311 orderID := confirmation.Order.ID 312 313 // Amend the price to force a cancel+resubmit to the order book 314 315 amend := &types.OrderAmendment{ 316 OrderID: orderID, 317 MarketID: confirmation.Order.MarketID, 318 SizeDelta: 100000, 319 } 320 321 tm.events = nil 322 amended, err := tm.market.AmendOrder(context.TODO(), amend, confirmation.Order.Party, vgcrypto.RandomHash()) 323 assert.Nil(t, amended) 324 assert.EqualError(t, err, "margin check failed") 325 // ensure no events for the update order were sent 326 assert.Len(t, tm.events, 2) 327 // first event was to update the positions 328 assert.Equal(t, int64(100100), tm.events[0].(*events.PositionState).PotentialBuys()) 329 // second to restore to the initial size as margin check failed 330 assert.Equal(t, int64(100), tm.events[1].(*events.PositionState).PotentialBuys()) 331 332 // now we just cancel it and see if all is fine. 333 tm.events = nil 334 cancelled, err := tm.market.CancelOrder(context.TODO(), confirmation.Order.Party, orderID, vgcrypto.RandomHash()) 335 assert.NotNil(t, cancelled) 336 assert.NoError(t, err) 337 assert.Equal(t, cancelled.Order.Status, types.OrderStatusCancelled) 338 339 found := false 340 for _, v := range tm.events { 341 if co, ok := v.(*events.CancelledOrders); ok { 342 for _, oid := range co.OrderIDs() { 343 if oid == orderID { 344 found = true 345 break 346 } 347 } 348 if found { 349 break 350 } 351 } 352 } 353 assert.True(t, found) 354 } 355 356 func TestOrderBufferOutputCount(t *testing.T) { 357 party1 := "party1" 358 now := time.Unix(10, 0) 359 tm := getTestMarket(t, now, nil, nil) 360 361 addAccount(t, tm, party1) 362 363 orderBuy := &types.Order{ 364 Type: types.OrderTypeLimit, 365 TimeInForce: types.OrderTimeInForceGTC, 366 Status: types.OrderStatusActive, 367 ID: "someid", 368 Side: types.SideBuy, 369 Party: party1, 370 MarketID: tm.market.GetID(), 371 Size: 100, 372 Price: num.NewUint(100), 373 Remaining: 100, 374 CreatedAt: now.UnixNano(), 375 ExpiresAt: 0, 376 Reference: "party1-buy-order", 377 } 378 orderAmend := *orderBuy 379 380 // Create an order (generates one order message) 381 confirmation, err := tm.market.SubmitOrder(context.TODO(), orderBuy) 382 assert.NotNil(t, confirmation) 383 assert.NoError(t, err) 384 385 // Create a new order (generates one order message) 386 orderAmend.ID = "amendingorder" 387 orderAmend.Reference = "amendingorderreference" 388 confirmation, err = tm.market.SubmitOrder(context.TODO(), &orderAmend) 389 assert.NotNil(t, confirmation) 390 assert.NoError(t, err) 391 392 amend := &types.OrderAmendment{ 393 MarketID: tm.market.GetID(), 394 OrderID: orderAmend.ID, 395 } 396 397 one := num.NewUint(1) 398 // Amend price down (generates one order message) 399 amend.Price = num.UintZero().Sub(orderBuy.Price, one) 400 amendConf, err := tm.market.AmendOrder(context.TODO(), amend, party1, vgcrypto.RandomHash()) 401 assert.NotNil(t, amendConf) 402 assert.NoError(t, err) 403 404 // Amend price up (generates one order message) 405 amend.Price.AddSum(one, one) // we subtracted one, add 1 to get == to orderBuy.Price, + 1 again 406 amendConf, err = tm.market.AmendOrder(context.TODO(), amend, party1, vgcrypto.RandomHash()) 407 assert.NotNil(t, amendConf) 408 assert.NoError(t, err) 409 410 // Amend size down (generates one order message) 411 amend.Price = nil 412 amend.SizeDelta = -1 413 amendConf, err = tm.market.AmendOrder(context.TODO(), amend, party1, vgcrypto.RandomHash()) 414 assert.NotNil(t, amendConf) 415 assert.NoError(t, err) 416 417 // Amend size up (generates one order message) 418 amend.SizeDelta = +1 419 amendConf, err = tm.market.AmendOrder(context.TODO(), amend, party1, vgcrypto.RandomHash()) 420 assert.NotNil(t, amendConf) 421 assert.NoError(t, err) 422 423 // Amend TIME_IN_FORCE -> GTT (generates one order message) 424 amend.SizeDelta = 0 425 amend.TimeInForce = types.OrderTimeInForceGTT 426 exp := now.UnixNano() + 100000000000 427 amend.ExpiresAt = &exp 428 amendConf, err = tm.market.AmendOrder(context.TODO(), amend, party1, vgcrypto.RandomHash()) 429 assert.NotNil(t, amendConf) 430 assert.NoError(t, err) 431 432 // Amend TIME_IN_FORCE -> GTC (generates one order message) 433 amend.TimeInForce = types.OrderTimeInForceGTC 434 amend.ExpiresAt = nil 435 amendConf, err = tm.market.AmendOrder(context.TODO(), amend, party1, vgcrypto.RandomHash()) 436 assert.NotNil(t, amendConf) 437 assert.NoError(t, err) 438 439 // Amend ExpiresAt (generates two order messages) 440 amend.TimeInForce = types.OrderTimeInForceGTT 441 exp = now.UnixNano() + 100000000000 442 amend.ExpiresAt = &exp 443 amendConf, err = tm.market.AmendOrder(context.TODO(), amend, party1, vgcrypto.RandomHash()) 444 assert.NotNil(t, amendConf) 445 assert.NoError(t, err) 446 447 exp = now.UnixNano() + 200000000000 448 amend.ExpiresAt = &exp 449 amendConf, err = tm.market.AmendOrder(context.TODO(), amend, party1, vgcrypto.RandomHash()) 450 assert.NotNil(t, amendConf) 451 assert.NoError(t, err) 452 } 453 454 func TestAmendCancelResubmit(t *testing.T) { 455 party1 := "party1" 456 now := time.Unix(10, 0) 457 tm := getTestMarket(t, now, nil, nil) 458 459 addAccount(t, tm, party1) 460 461 orderBuy := &types.Order{ 462 Status: types.OrderStatusActive, 463 Type: types.OrderTypeLimit, 464 TimeInForce: types.OrderTimeInForceGTC, 465 ID: "someid", 466 Side: types.SideBuy, 467 Party: party1, 468 MarketID: tm.market.GetID(), 469 Size: 100, 470 Price: num.NewUint(100), 471 Remaining: 100, 472 CreatedAt: now.UnixNano(), 473 Reference: "party1-buy-order", 474 } 475 // Submit the original order 476 confirmation, err := tm.market.SubmitOrder(context.TODO(), orderBuy) 477 assert.NotNil(t, confirmation) 478 assert.NoError(t, err) 479 480 orderID := confirmation.Order.ID 481 482 // Amend the price to force a cancel+resubmit to the order book 483 484 amend := &types.OrderAmendment{ 485 OrderID: orderID, 486 MarketID: confirmation.Order.MarketID, 487 Price: num.NewUint(101), 488 } 489 amended, err := tm.market.AmendOrder(context.TODO(), amend, confirmation.Order.Party, vgcrypto.RandomHash()) 490 assert.NotNil(t, amended) 491 assert.NoError(t, err) 492 493 amend = &types.OrderAmendment{ 494 OrderID: orderID, 495 MarketID: confirmation.Order.MarketID, 496 Price: num.NewUint(101), 497 SizeDelta: 1, 498 } 499 amended, err = tm.market.AmendOrder(context.TODO(), amend, confirmation.Order.Party, vgcrypto.RandomHash()) 500 assert.NotNil(t, amended) 501 assert.NoError(t, err) 502 } 503 504 func TestCancelWithWrongPartyID(t *testing.T) { 505 party1 := "party1" 506 party2 := "party2" 507 now := time.Unix(10, 0) 508 tm := getTestMarket(t, now, nil, nil) 509 510 addAccount(t, tm, party1) 511 addAccount(t, tm, party2) 512 513 orderBuy := &types.Order{ 514 Status: types.OrderStatusActive, 515 Type: types.OrderTypeLimit, 516 TimeInForce: types.OrderTimeInForceGTC, 517 ID: "someid", 518 Side: types.SideBuy, 519 Party: party1, 520 MarketID: tm.market.GetID(), 521 Size: 100, 522 Price: num.NewUint(100), 523 Remaining: 100, 524 CreatedAt: now.UnixNano(), 525 Reference: "party1-buy-order", 526 } 527 // Submit the original order 528 confirmation, err := tm.market.SubmitOrder(context.TODO(), orderBuy) 529 assert.NotNil(t, confirmation) 530 assert.NoError(t, err) 531 532 // Now attempt to cancel it with the wrong partyID 533 cancelOrder := &types.OrderCancellation{ 534 OrderID: confirmation.Order.ID, 535 MarketID: confirmation.Order.MarketID, 536 } 537 cancelconf, err := tm.market.CancelOrder(context.TODO(), party2, cancelOrder.OrderID, vgcrypto.RandomHash()) 538 assert.Nil(t, cancelconf) 539 assert.Error(t, err, types.ErrInvalidPartyID) 540 } 541 542 func TestMarkPriceUpdateAfterPartialFill(t *testing.T) { 543 party1 := "party1" 544 party2 := "party2" 545 auxParty := "auxParty" 546 auxParty2 := "auxParty2" 547 now := time.Unix(10, 0) 548 tm := getTestMarket2(t, now, nil, &types.AuctionDuration{ 549 Duration: 1, 550 }, true, 0.95) 551 552 addAccount(t, tm, party1) 553 addAccount(t, tm, party2) 554 addAccount(t, tm, auxParty) 555 addAccount(t, tm, auxParty2) 556 addAccountWithAmount(tm, "lpprov", 10000000) 557 558 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Second) 559 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 560 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 561 require.NotNil(t, conf) 562 require.NoError(t, err) 563 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 564 565 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 10000) 566 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 567 require.NotNil(t, conf) 568 require.NoError(t, err) 569 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 570 571 auxOrders := []*types.Order{ 572 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 573 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 574 } 575 for _, o := range auxOrders { 576 conf, err := tm.market.SubmitOrder(context.Background(), o) 577 require.NoError(t, err) 578 require.NotNil(t, conf) 579 } 580 lp := &types.LiquidityProvisionSubmission{ 581 MarketID: tm.market.GetID(), 582 CommitmentAmount: num.NewUint(25000), 583 Fee: num.DecimalFromFloat(0.01), 584 } 585 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 586 // leave opening auction 587 now = now.Add(2 * time.Second) 588 tm.now = now 589 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 590 591 orderBuy := &types.Order{ 592 Status: types.OrderStatusActive, 593 TimeInForce: types.OrderTimeInForceGTC, 594 ID: "someid", 595 Side: types.SideBuy, 596 Party: party1, 597 MarketID: tm.market.GetID(), 598 Size: 100, 599 Price: num.NewUint(10), 600 Remaining: 100, 601 CreatedAt: now.UnixNano(), 602 Reference: "party1-buy-order", 603 Type: types.OrderTypeLimit, 604 } 605 // Submit the original order 606 buyConfirmation, err := tm.market.SubmitOrder(context.TODO(), orderBuy) 607 assert.NotNil(t, buyConfirmation) 608 assert.NoError(t, err) 609 610 orderSell := &types.Order{ 611 Status: types.OrderStatusActive, 612 TimeInForce: types.OrderTimeInForceIOC, 613 ID: "someid", 614 Side: types.SideSell, 615 Party: party2, 616 MarketID: tm.market.GetID(), 617 Size: 50, 618 Price: num.NewUint(10), 619 Remaining: 50, 620 CreatedAt: now.UnixNano(), 621 Reference: "party2-sell-order", 622 Type: types.OrderTypeMarket, 623 } 624 // Submit an opposite order to partially fill 625 sellConfirmation, err := tm.market.SubmitOrder(context.TODO(), orderSell) 626 assert.NotNil(t, sellConfirmation) 627 assert.NoError(t, err) 628 629 // Validate that the last traded price has been updated 630 assert.True(t, tm.market.GetMarketData().LastTradedPrice.EQ(num.NewUint(10))) 631 } 632 633 func TestExpireCancelGTCOrder(t *testing.T) { 634 party1 := "party1" 635 now := time.Unix(10, 0) 636 tm := getTestMarket(t, now, nil, nil) 637 638 addAccount(t, tm, party1) 639 640 orderBuy := &types.Order{ 641 CreatedAt: int64(now.Second()), 642 Status: types.OrderStatusActive, 643 TimeInForce: types.OrderTimeInForceGTC, 644 ID: "someid", 645 Side: types.SideBuy, 646 Party: party1, 647 MarketID: tm.market.GetID(), 648 Size: 100, 649 Price: num.NewUint(10), 650 Remaining: 100, 651 Reference: "party1-buy-order", 652 Type: types.OrderTypeLimit, 653 } 654 // Submit the original order 655 buyConfirmation, err := tm.market.SubmitOrder(context.Background(), orderBuy) 656 assert.NotNil(t, buyConfirmation) 657 assert.NoError(t, err) 658 659 tm.now = time.Unix(10, 100) 660 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), time.Unix(10, 100)) 661 exp := int64(10000000010) 662 amend := &types.OrderAmendment{ 663 OrderID: buyConfirmation.Order.ID, 664 MarketID: tm.market.GetID(), 665 ExpiresAt: &exp, 666 TimeInForce: types.OrderTimeInForceGTT, 667 } 668 669 amended, err := tm.market.AmendOrder(context.Background(), amend, party1, vgcrypto.RandomHash()) 670 assert.NotNil(t, amended) 671 assert.NoError(t, err) 672 673 // Validate that the mark price has been updated 674 assert.EqualValues(t, amended.Order.TimeInForce, types.OrderTimeInForceGTT) 675 assert.EqualValues(t, amended.Order.Status, types.OrderStatusExpired) 676 assert.EqualValues(t, amended.Order.CreatedAt, 10000000000) 677 assert.EqualValues(t, amended.Order.ExpiresAt, exp) 678 assert.EqualValues(t, 10000000100, amended.Order.UpdatedAt) 679 } 680 681 func TestAmendPartialFillCancelReplace(t *testing.T) { 682 party1 := "party1" 683 party2 := "party2" 684 auxParty := "auxParty" 685 auxParty2 := "auxParty2" 686 now := time.Unix(10, 0) 687 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 688 Duration: 1, 689 }) 690 691 addAccount(t, tm, party1) 692 addAccount(t, tm, party2) 693 addAccount(t, tm, auxParty) 694 addAccount(t, tm, auxParty2) 695 addAccountWithAmount(tm, "lpprov", 10000000) 696 697 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Second) 698 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 699 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 700 require.NotNil(t, conf) 701 require.NoError(t, err) 702 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 703 704 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 30) 705 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 706 require.NotNil(t, conf) 707 require.NoError(t, err) 708 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 709 710 auxOrders := []*types.Order{ 711 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 5), 712 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 5), 713 } 714 for _, o := range auxOrders { 715 conf, err := tm.market.SubmitOrder(context.Background(), o) 716 require.NoError(t, err) 717 require.NotNil(t, conf) 718 } 719 720 md0 := tm.market.GetMarketData() 721 require.NotNil(t, md0) 722 723 lp := &types.LiquidityProvisionSubmission{ 724 MarketID: tm.market.GetID(), 725 CommitmentAmount: num.NewUint(25000), 726 Fee: num.DecimalFromFloat(0.01), 727 } 728 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 729 // leave opening auction 730 now = now.Add(2 * time.Second) 731 tm.now = now 732 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 733 734 orderBuy := &types.Order{ 735 Status: types.OrderStatusActive, 736 TimeInForce: types.OrderTimeInForceGTC, 737 Side: types.SideBuy, 738 Party: party1, 739 MarketID: tm.market.GetID(), 740 Size: 20, 741 Price: num.NewUint(5), 742 Remaining: 20, 743 Reference: "party1-buy-order", 744 Type: types.OrderTypeLimit, 745 } 746 // Place an order 747 buyConfirmation, err := tm.market.SubmitOrder(context.Background(), orderBuy) 748 assert.NotNil(t, buyConfirmation) 749 assert.Zero(t, len(buyConfirmation.Trades)) 750 assert.NoError(t, err) 751 752 marketOrderSell := &types.Order{ 753 Status: types.OrderStatusActive, 754 TimeInForce: types.OrderTimeInForceIOC, 755 Side: types.SideSell, 756 Party: party2, 757 MarketID: tm.market.GetID(), 758 Size: 10, 759 Price: num.UintZero(), 760 Remaining: 10, 761 Reference: "party2-sell-order", 762 Type: types.OrderTypeMarket, 763 } 764 // Partially fill the original order 765 sellConfirmation, err := tm.market.SubmitOrder(context.Background(), marketOrderSell) 766 assert.NotNil(t, sellConfirmation) 767 assert.NoError(t, err) 768 769 amend := &types.OrderAmendment{ 770 OrderID: buyConfirmation.Order.ID, 771 MarketID: tm.market.GetID(), 772 Price: num.NewUint(20), 773 } 774 amended, err := tm.market.AmendOrder(context.Background(), amend, party1, vgcrypto.RandomHash()) 775 assert.NotNil(t, amended) 776 assert.NoError(t, err) 777 778 // Check the values are correct 779 assert.True(t, amended.Order.Price.EQ(amend.Price)) 780 assert.EqualValues(t, amended.Order.Remaining, orderBuy.Size-marketOrderSell.Size) 781 assert.EqualValues(t, amended.Order.Size, orderBuy.Size) 782 } 783 784 func TestAmendWrongPartyID(t *testing.T) { 785 party1 := "party1" 786 party2 := "party2" 787 now := time.Unix(10, 0) 788 tm := getTestMarket(t, now, nil, nil) 789 790 addAccount(t, tm, party1) 791 addAccount(t, tm, party2) 792 793 orderBuy := &types.Order{ 794 Status: types.OrderStatusActive, 795 Type: types.OrderTypeLimit, 796 TimeInForce: types.OrderTimeInForceGTC, 797 Side: types.SideBuy, 798 Party: party1, 799 MarketID: tm.market.GetID(), 800 Size: 100, 801 Price: num.NewUint(100), 802 Remaining: 100, 803 CreatedAt: now.UnixNano(), 804 Reference: "party1-buy-order", 805 } 806 // Submit the original order 807 confirmation, err := tm.market.SubmitOrder(context.Background(), orderBuy) 808 assert.NotNil(t, confirmation) 809 assert.NoError(t, err) 810 811 // Send an amend but use the wrong partyID 812 amend := &types.OrderAmendment{ 813 OrderID: confirmation.Order.ID, 814 MarketID: confirmation.Order.MarketID, 815 Price: num.NewUint(101), 816 } 817 amended, err := tm.market.AmendOrder(context.Background(), amend, party2, vgcrypto.RandomHash()) 818 assert.Nil(t, amended) 819 assert.Error(t, err, types.ErrInvalidPartyID) 820 } 821 822 func TestPartialFilledWashTrade(t *testing.T) { 823 party1 := "party1" 824 party2 := "party2" 825 auxParty := "auxParty" 826 auxParty2 := "auxParty2" 827 now := time.Unix(10, 0) 828 tm := getTestMarket2(t, now, nil, &types.AuctionDuration{ 829 Duration: 1, 830 // increase lpRange so that LP orders don't get pushed too close to MID and test can behave as expected 831 }, true, 1.05) 832 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 833 834 addAccount(t, tm, party1) 835 addAccount(t, tm, party2) 836 addAccount(t, tm, auxParty) 837 addAccount(t, tm, auxParty2) 838 addAccountWithAmount(tm, "lpprov", 10000000) 839 840 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 841 842 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 843 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 844 require.NotNil(t, conf) 845 require.NoError(t, err) 846 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 847 848 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 10000) 849 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 850 require.NotNil(t, conf) 851 require.NoError(t, err) 852 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 853 854 auxOrders := []*types.Order{ 855 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 55), 856 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 55), 857 } 858 for _, o := range auxOrders { 859 conf, err := tm.market.SubmitOrder(ctx, o) 860 require.NoError(t, err) 861 require.NotNil(t, conf) 862 } 863 lp := &types.LiquidityProvisionSubmission{ 864 MarketID: tm.market.GetID(), 865 CommitmentAmount: num.NewUint(25000), 866 Fee: num.DecimalFromFloat(0.01), 867 } 868 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 869 // leave opening auction 870 now = now.Add(2 * time.Second) 871 tm.now = now 872 tm.market.OnTick(ctx, now) 873 874 orderSell1 := &types.Order{ 875 Status: types.OrderStatusActive, 876 Type: types.OrderTypeLimit, 877 TimeInForce: types.OrderTimeInForceGTC, 878 Side: types.SideSell, 879 Party: party1, 880 MarketID: tm.market.GetID(), 881 Size: 15, 882 Price: num.NewUint(55), 883 Remaining: 15, 884 CreatedAt: now.UnixNano(), 885 Reference: "party1-sell-order", 886 } 887 confirmation, err := tm.market.SubmitOrder(context.Background(), orderSell1) 888 assert.NotNil(t, confirmation) 889 assert.NoError(t, err) 890 891 orderSell2 := &types.Order{ 892 Status: types.OrderStatusActive, 893 Type: types.OrderTypeLimit, 894 TimeInForce: types.OrderTimeInForceGTC, 895 Side: types.SideSell, 896 Party: party2, 897 MarketID: tm.market.GetID(), 898 Size: 15, 899 Price: num.NewUint(53), 900 Remaining: 15, 901 CreatedAt: now.UnixNano(), 902 Reference: "party2-sell-order", 903 } 904 confirmation, err = tm.market.SubmitOrder(context.Background(), orderSell2) 905 assert.NotNil(t, confirmation) 906 assert.NoError(t, err) 907 908 // This order should partially fill and then be rejected 909 orderBuy1 := &types.Order{ 910 Status: types.OrderStatusActive, 911 Type: types.OrderTypeLimit, 912 TimeInForce: types.OrderTimeInForceGTC, 913 Side: types.SideBuy, 914 Party: party1, 915 MarketID: tm.market.GetID(), 916 Size: 30, 917 Price: num.NewUint(60), 918 Remaining: 30, 919 CreatedAt: now.UnixNano(), 920 Reference: "party1-buy-order", 921 } 922 923 md := tm.market.GetMarketData() 924 require.NotNil(t, md) 925 926 confirmation, err = tm.market.SubmitOrder(context.Background(), orderBuy1) 927 assert.NotNil(t, confirmation) 928 assert.NoError(t, err) 929 assert.Equal(t, confirmation.Order.Status, types.OrderStatusPartiallyFilled) 930 assert.Equal(t, confirmation.Order.Remaining, uint64(15)) 931 } 932 933 func getAmend(market string, orderID string, sizeDelta int64, price uint64, tif types.OrderTimeInForce, expiresAt int64) *types.OrderAmendment { 934 amend := &types.OrderAmendment{ 935 OrderID: orderID, 936 MarketID: market, 937 SizeDelta: sizeDelta, 938 TimeInForce: tif, 939 } 940 941 if price > 0 { 942 amend.Price = num.NewUint(price) 943 } 944 945 if expiresAt > 0 { 946 amend.ExpiresAt = &expiresAt 947 } 948 949 return amend 950 } 951 952 func amendOrder(t *testing.T, tm *testMarket, party string, orderID string, sizeDelta int64, price uint64, 953 tif types.OrderTimeInForce, expiresAt int64, pass bool, 954 ) { 955 t.Helper() 956 amend := getAmend(tm.market.GetID(), orderID, sizeDelta, price, tif, expiresAt) 957 958 amended, err := tm.market.AmendOrder(context.Background(), amend, party, vgcrypto.RandomHash()) 959 if pass { 960 assert.NotNil(t, amended) 961 assert.NoError(t, err) 962 } 963 } 964 965 //nolint:unparam 966 func getOrder(t *testing.T, tm *testMarket, now *time.Time, orderType types.OrderType, tif types.OrderTimeInForce, 967 expiresAt int64, side types.Side, party string, size uint64, price uint64, 968 ) types.Order { 969 t.Helper() 970 order := types.Order{ 971 Status: types.OrderStatusActive, 972 Type: orderType, 973 TimeInForce: tif, 974 Side: side, 975 Party: party, 976 MarketID: tm.market.GetID(), 977 Size: size, 978 Price: num.NewUint(price), 979 Remaining: size, 980 CreatedAt: now.UnixNano(), 981 Reference: "", 982 } 983 984 if expiresAt > 0 { 985 order.ExpiresAt = expiresAt 986 } 987 return order 988 } 989 990 func sendOrder(t *testing.T, tm *testMarket, now *time.Time, orderType types.OrderType, tif types.OrderTimeInForce, expiresAt int64, side types.Side, party string, 991 size uint64, price uint64, 992 ) string { 993 t.Helper() 994 order := &types.Order{ 995 Status: types.OrderStatusActive, 996 Type: orderType, 997 TimeInForce: tif, 998 Side: side, 999 Party: party, 1000 MarketID: tm.market.GetID(), 1001 Size: size, 1002 Price: num.NewUint(price), 1003 Remaining: size, 1004 CreatedAt: now.UnixNano(), 1005 Reference: "", 1006 } 1007 1008 if expiresAt > 0 { 1009 order.ExpiresAt = expiresAt 1010 } 1011 1012 confirmation, err := tm.market.SubmitOrder(context.Background(), order) 1013 require.NotNil(t, confirmation) 1014 assert.NoError(t, err) 1015 return confirmation.Order.ID 1016 } 1017 1018 func TestAmendToFill(t *testing.T) { 1019 now := time.Unix(10, 0) 1020 tm := getTestMarket(t, now, nil, nil) 1021 1022 addAccount(t, tm, "party1") 1023 addAccount(t, tm, "party2") 1024 1025 // test_AmendMarketOrderFail 1026 _ = sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 100) // 1 - a8 1027 _ = sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 110) // 1 - a8 1028 _ = sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 120) // 1 - a8 1029 orderID := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party2", 40, 50) // 1 - a8 1030 amendOrder(t, tm, "party2", orderID, 0, 500, types.OrderTimeInForceUnspecified, 0, true) 1031 } 1032 1033 func TestAmendToLosePriorityThenCancel(t *testing.T) { 1034 now := time.Unix(10, 0) 1035 tm := getTestMarket(t, now, nil, nil) 1036 1037 addAccount(t, tm, "party1") 1038 addAccount(t, tm, "party2") 1039 1040 // Create 2 orders at the same level 1041 order1 := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 100) 1042 _ = sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 100) 1043 1044 // Amend the first order to make it lose time priority 1045 amendOrder(t, tm, "party1", order1, 1, 0, types.OrderTimeInForceUnspecified, 0, true) 1046 1047 // Check we can cancel it 1048 cancelconf, _ := tm.market.CancelOrder(context.TODO(), "party1", order1, vgcrypto.RandomHash()) 1049 assert.NotNil(t, cancelconf) 1050 assert.Equal(t, types.OrderStatusCancelled, cancelconf.Order.Status) 1051 } 1052 1053 func TestUnableToAmendGFAGFN(t *testing.T) { 1054 now := time.Unix(10, 0) 1055 closeSec := int64(10000000000) 1056 tm := getTestMarket(t, now, nil, &types.AuctionDuration{Duration: 1}) 1057 mainParty := "party1" 1058 auxParty := "party2" 1059 auxParty2 := "party22" 1060 addAccount(t, tm, mainParty) 1061 addAccount(t, tm, auxParty) 1062 addAccount(t, tm, auxParty2) 1063 addAccountWithAmount(tm, "lpprov", 10000000) 1064 1065 tm.market.OnMarketAuctionMinimumDurationUpdate(context.Background(), time.Second) 1066 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 1067 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 1068 require.NotNil(t, conf) 1069 require.NoError(t, err) 1070 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 1071 1072 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 10000) 1073 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 1074 require.NotNil(t, conf) 1075 require.NoError(t, err) 1076 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 1077 1078 auxOrders := []*types.Order{ 1079 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 1080 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 1081 } 1082 for _, o := range auxOrders { 1083 conf, err := tm.market.SubmitOrder(context.Background(), o) 1084 require.NoError(t, err) 1085 require.NotNil(t, conf) 1086 } 1087 lp := &types.LiquidityProvisionSubmission{ 1088 MarketID: tm.market.GetID(), 1089 CommitmentAmount: num.NewUint(25000), 1090 Fee: num.DecimalFromFloat(0.01), 1091 } 1092 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 1093 // leave opening auction 1094 now = now.Add(2 * time.Second) 1095 tm.now = now 1096 tm.market.OnTick(vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()), now) 1097 1098 // test_AmendMarketOrderFail 1099 orderID := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, mainParty, 10, 100) 1100 amendOrder(t, tm, mainParty, orderID, 0, 0, types.OrderTimeInForceGFA, 0, false) 1101 amendOrder(t, tm, mainParty, orderID, 0, 0, types.OrderTimeInForceGFN, 0, false) 1102 1103 orderID2 := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGFN, 0, types.SideSell, mainParty, 10, 100) 1104 amendOrder(t, tm, mainParty, orderID2, 0, 0, types.OrderTimeInForceGTC, 0, false) 1105 amendOrder(t, tm, mainParty, orderID2, 0, 0, types.OrderTimeInForceGFA, 0, false) 1106 1107 // EnterAuction should actually trigger an auction here... 1108 tm.mas.StartPriceAuction(now, &types.AuctionDuration{ 1109 Duration: closeSec / 10, // some time in the future, before closing 1110 }) 1111 tm.market.EnterAuction(context.Background()) 1112 orderID3 := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGFA, 0, types.SideSell, "party1", 10, 100) 1113 amendOrder(t, tm, "party1", orderID3, 0, 0, types.OrderTimeInForceGTC, 0, false) 1114 amendOrder(t, tm, "party1", orderID3, 0, 0, types.OrderTimeInForceGFN, 0, false) 1115 } 1116 1117 func TestMarketPeggedOrders(t *testing.T) { 1118 t.Run("pegged orders must be LIMIT orders ", testPeggedOrderTypes) 1119 t.Run("pegged orders must be either GTT or GTC ", testPeggedOrderTIFs) 1120 t.Run("pegged orders buy side validation", testPeggedOrderBuys) 1121 t.Run("pegged orders sell side validation", testPeggedOrderSells) 1122 t.Run("pegged orders are parked when price below 0", testPeggedOrderParkWhenPriceBelowZero) 1123 t.Run("pegged orders are parked when price reprices below 0", testPeggedOrderParkWhenPriceRepricesBelowZero) 1124 t.Run("pegged order when there is no market prices", testPeggedOrderAddWithNoMarketPrice) 1125 t.Run("pegged order add to order book", testPeggedOrderAdd) 1126 t.Run("pegged order test when placing a pegged order forces a reprice", testPeggedOrderWithReprice) 1127 t.Run("pegged order entry during an auction", testPeggedOrderParkWhenInAuction) 1128 t.Run("Pegged orders unpark order after leaving auction", testPeggedOrderUnparkAfterLeavingAuction) 1129 t.Run("pegged order repricing", testPeggedOrderRepricing) 1130 t.Run("pegged order check that a filled pegged order is handled correctly", testPeggedOrderFilledOrder) 1131 t.Run("parked orders during normal trading are unparked when possible", testParkedOrdersAreUnparkedWhenPossible) 1132 t.Run("pegged orders are handled correctly when moving into auction", testPeggedOrdersEnteringAuction) 1133 t.Run("pegged orders are handled correctly when moving out of auction", testPeggedOrdersLeavingAuction) 1134 t.Run("pegged orders amend to move reference", testPeggedOrderAmendToMoveReference) 1135 t.Run("pegged orders are removed when expired", testPeggedOrderExpiring) 1136 t.Run("pegged orders unpark order due to reference becoming valid", testPeggedOrderUnpark) 1137 t.Run("pegged order cancel a parked order", testPeggedOrderCancelParked) 1138 t.Run("pegged order reprice when no limit orders", testPeggedOrderRepriceCrashWhenNoLimitOrders) 1139 t.Run("pegged orders cancelall", testPeggedOrderParkCancelAll) 1140 t.Run("pegged orders expiring 2", testPeggedOrderExpiring2) 1141 t.Run("pegged orders test for events produced", testPeggedOrderOutputMessages) 1142 } 1143 1144 func testPeggedOrderRepriceCrashWhenNoLimitOrders(t *testing.T) { 1145 now := time.Unix(10, 0) 1146 tm := getTestMarket(t, now, nil, nil) 1147 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1148 1149 addAccount(t, tm, "party1") 1150 addAccount(t, tm, "party2") 1151 1152 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party2", 5, 9000) 1153 1154 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party2", 10, 0) 1155 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 10) 1156 _, err := tm.market.SubmitOrder(ctx, &order) 1157 require.NoError(t, err) 1158 1159 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 5, 9000) 1160 } 1161 1162 func testPeggedOrderUnpark(t *testing.T) { 1163 now := time.Unix(10, 0) 1164 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 1165 Duration: 1, 1166 }) 1167 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1168 1169 auxParty, auxParty2 := "auxParty", "auxParty2" 1170 addAccount(t, tm, "party1") 1171 addAccount(t, tm, "party2") 1172 addAccount(t, tm, auxParty) 1173 addAccount(t, tm, auxParty2) 1174 addAccountWithAmount(tm, "lpprov", 10000000) 1175 1176 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 1177 auxOrders := []*types.Order{ 1178 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1), 1179 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000), 1180 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 1181 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 1182 } 1183 for _, o := range auxOrders { 1184 conf, err := tm.market.SubmitOrder(ctx, o) 1185 require.NoError(t, err) 1186 require.NotNil(t, conf) 1187 } 1188 1189 // Create a single buy order to give this party a valid position 1190 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 5, 11) 1191 1192 // Add a pegged order which will park due to missing reference price 1193 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 100) 1194 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 10) 1195 _, err := tm.market.SubmitOrder(ctx, &order) 1196 require.NoError(t, err) 1197 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 1198 lp := &types.LiquidityProvisionSubmission{ 1199 MarketID: tm.market.GetID(), 1200 CommitmentAmount: num.NewUint(5000), 1201 Fee: num.DecimalFromFloat(0.01), 1202 } 1203 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 1204 1205 // leave opening auction 1206 now = now.Add(2 * time.Second) 1207 tm.now = now 1208 tm.market.OnTick(ctx, now) 1209 // Send a new order to set the BEST_ASK price and force the parked order to unpark 1210 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party2", 5, 15) 1211 1212 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 1213 } 1214 1215 func testPeggedOrderAmendToMoveReference(t *testing.T) { 1216 now := time.Unix(10, 0) 1217 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 1218 Duration: 1, 1219 }) 1220 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1221 1222 auxParty, auxParty2 := "auxParty", "auxParty2" 1223 addAccount(t, tm, "party1") 1224 addAccount(t, tm, auxParty) 1225 addAccount(t, tm, auxParty2) 1226 1227 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 1228 auxOrders := []*types.Order{ 1229 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1), 1230 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000), 1231 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 1232 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 1233 } 1234 for _, o := range auxOrders { 1235 conf, err := tm.market.SubmitOrder(ctx, o) 1236 require.NoError(t, err) 1237 require.NotNil(t, conf) 1238 } 1239 // leave opening auction 1240 now = now.Add(2 * time.Second) 1241 tm.now = now 1242 tm.market.OnTick(ctx, now) 1243 1244 // Place 2 orders to create valid reference prices 1245 bestBidOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 90) 1246 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 110) 1247 1248 // Place a valid pegged order which will be added to the order book 1249 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 100) 1250 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 10) 1251 _, err := tm.market.SubmitOrder(ctx, &order) 1252 require.NoError(t, err) 1253 1254 // Amend best bid price 1255 amendOrder(t, tm, "party1", bestBidOrder, 0, 88, types.OrderTimeInForceUnspecified, 0, true) 1256 amendOrder(t, tm, "party1", bestBidOrder, 0, 86, types.OrderTimeInForceUnspecified, 0, true) 1257 } 1258 1259 func testPeggedOrderFilledOrder(t *testing.T) { 1260 now := time.Unix(10, 0) 1261 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 1262 Duration: 1, 1263 }) 1264 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1265 1266 auxParty, auxParty2 := "auxParty", "auxParty2" 1267 addAccount(t, tm, "party1") 1268 addAccount(t, tm, "party2") 1269 addAccount(t, tm, auxParty) 1270 addAccount(t, tm, auxParty2) 1271 addAccountWithAmount(tm, "lpprov", 10000000) 1272 1273 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 1274 auxOrders := []*types.Order{ 1275 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 80), 1276 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 120), 1277 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 1278 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 1279 } 1280 for _, o := range auxOrders { 1281 conf, err := tm.market.SubmitOrder(ctx, o) 1282 require.NoError(t, err) 1283 require.NotNil(t, conf) 1284 } 1285 lp := &types.LiquidityProvisionSubmission{ 1286 MarketID: tm.market.GetID(), 1287 CommitmentAmount: num.NewUint(5000), 1288 Fee: num.DecimalFromFloat(0.01), 1289 } 1290 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 1291 // leave opening auction 1292 now = now.Add(2 * time.Second) 1293 tm.now = now 1294 tm.market.OnTick(ctx, now) 1295 1296 // Place 2 orders to create valid reference prices 1297 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 90) 1298 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 110) 1299 1300 // Place a valid pegged order which will be added to the order book 1301 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 100) 1302 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 1) 1303 _, err := tm.market.SubmitOrder(ctx, &order) 1304 require.NoError(t, err) 1305 1306 // Place a valid pegged order which will be added to the order book 1307 order = getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 100) 1308 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 2) 1309 _, err = tm.market.SubmitOrder(ctx, &order) 1310 require.NoError(t, err) 1311 1312 // Place a sell MARKET order to fill the buy orders 1313 sendOrder(t, tm, &now, types.OrderTypeMarket, types.OrderTimeInForceIOC, 0, types.SideSell, "party2", 2, 0) 1314 1315 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 1316 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 1317 } 1318 1319 func testParkedOrdersAreUnparkedWhenPossible(t *testing.T) { 1320 now := time.Unix(10, 0) 1321 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 1322 Duration: 1, 1323 }) 1324 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1325 1326 auxParty, auxParty2 := "auxParty", "auxParty2" 1327 addAccount(t, tm, "party1") 1328 addAccount(t, tm, "party2") 1329 addAccount(t, tm, auxParty) 1330 addAccount(t, tm, auxParty2) 1331 addAccountWithAmount(tm, "lpprov", 10000000) 1332 1333 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 1334 auxOrders := []*types.Order{ 1335 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1), 1336 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000), 1337 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 1338 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 1339 } 1340 for _, o := range auxOrders { 1341 conf, err := tm.market.SubmitOrder(ctx, o) 1342 require.NoError(t, err) 1343 require.NotNil(t, conf) 1344 } 1345 1346 // Place 2 orders to create valid reference prices 1347 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 5) 1348 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 100) 1349 1350 // Place a valid pegged order which will be parked because it cannot be repriced 1351 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 1) 1352 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 10) 1353 _, err := tm.market.SubmitOrder(ctx, &order) 1354 require.NoError(t, err) 1355 1356 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 1357 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 1358 1359 // Send a higher buy price order to move the BEST BID price up 1360 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 50) 1361 1362 lp := &types.LiquidityProvisionSubmission{ 1363 MarketID: tm.market.GetID(), 1364 CommitmentAmount: num.NewUint(5000), 1365 Fee: num.DecimalFromFloat(0.01), 1366 } 1367 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 1368 // leave opening auction 1369 now = now.Add(2 * time.Second) 1370 tm.now = now 1371 tm.market.OnTick(ctx, now) 1372 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 1373 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 1374 } 1375 1376 func testPeggedOrdersLeavingAuction(t *testing.T) { 1377 now := time.Unix(10, 0) 1378 auctionClose := now.Add(101 * time.Second) 1379 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 1380 Duration: 100, 1381 }) 1382 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1383 1384 addAccount(t, tm, "party1") 1385 addAccount(t, tm, "party2") 1386 addAccount(t, tm, "party3") 1387 addAccountWithAmount(tm, "lpprov", 10000000) 1388 1389 // Move into auction 1390 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, 100*time.Second) 1391 1392 // Place 2 orders to create valid reference prices 1393 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 90) 1394 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 100) 1395 // place 2 more orders that will result in a mark price being set 1396 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party2", 1, 95) 1397 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party3", 1, 95) 1398 1399 // Pegged order must be a LIMIT order 1400 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1401 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 10) 1402 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1403 require.NoError(t, err) 1404 assert.NotNil(t, confirmation) 1405 assert.Equal(t, confirmation.Order.Status, types.OrderStatusParked) 1406 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 1407 // During an auction all pegged orders are parked so we don't add them to the list 1408 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 1409 1410 lp := &types.LiquidityProvisionSubmission{ 1411 MarketID: tm.market.GetID(), 1412 CommitmentAmount: num.NewUint(5000), 1413 Fee: num.DecimalFromFloat(0.01), 1414 } 1415 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 1416 // Update the time to force the auction to end 1417 tm.now = auctionClose 1418 tm.market.OnTick(ctx, auctionClose) 1419 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 1420 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 1421 } 1422 1423 func testPeggedOrdersEnteringAuction(t *testing.T) { 1424 now := time.Unix(10, 0) 1425 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 1426 Duration: 100, 1427 }) 1428 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1429 1430 auxParty, auxParty2 := "auxParty", "auxParty2" 1431 addAccount(t, tm, "party1") 1432 addAccount(t, tm, "party2") 1433 addAccount(t, tm, "party3") 1434 addAccount(t, tm, auxParty) 1435 addAccount(t, tm, auxParty2) 1436 1437 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, 100*time.Second) 1438 // Place 2 orders to create valid reference prices 1439 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 90) 1440 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 100) 1441 // place 2 more orders that will result in a mark price being set 1442 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party2", 1, 95) 1443 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party3", 1, 95) 1444 1445 // Pegged order must be a LIMIT order 1446 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1447 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 10) 1448 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1449 require.NoError(t, err) 1450 assert.NotNil(t, confirmation) 1451 assert.Equal(t, confirmation.Order.Status, types.OrderStatusParked) 1452 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 1453 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 1454 1455 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 1456 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 1457 } 1458 1459 func testPeggedOrderAddWithNoMarketPrice(t *testing.T) { 1460 now := time.Unix(10, 0) 1461 tm := getTestMarket(t, now, nil, nil) 1462 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1463 1464 addAccount(t, tm, "party1") 1465 1466 // Place a valid pegged order which will be parked 1467 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1468 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1469 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1470 assert.NotNil(t, confirmation) 1471 assert.Equal(t, confirmation.Order.Status, types.OrderStatusParked) 1472 assert.NoError(t, err) 1473 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 1474 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 1475 } 1476 1477 func testPeggedOrderAdd(t *testing.T) { 1478 now := time.Unix(10, 0) 1479 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 1480 Duration: 1, 1481 }) 1482 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1483 1484 auxParty, auxParty2 := "auxParty", "auxParty2" 1485 addAccount(t, tm, "party1") 1486 addAccount(t, tm, auxParty) 1487 addAccount(t, tm, auxParty2) 1488 addAccountWithAmount(tm, "lpprov", 10000000) 1489 1490 auxOrders := []*types.Order{ 1491 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1), 1492 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000), 1493 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 1494 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 1495 } 1496 for _, o := range auxOrders { 1497 conf, err := tm.market.SubmitOrder(ctx, o) 1498 require.NoError(t, err) 1499 require.NotNil(t, conf) 1500 } 1501 lp := &types.LiquidityProvisionSubmission{ 1502 MarketID: tm.market.GetID(), 1503 CommitmentAmount: num.NewUint(25000), 1504 Fee: num.DecimalFromFloat(0.01), 1505 } 1506 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 1507 // leave auction 1508 now = now.Add(2 * time.Second) 1509 tm.now = now 1510 tm.market.OnTick(ctx, now) 1511 1512 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1513 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1514 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1515 assert.NotNil(t, confirmation) 1516 assert.Equal(t, types.OrderStatusActive, confirmation.Order.Status) 1517 require.NoError(t, err) 1518 1519 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 100) 1520 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 102) 1521 1522 // Place a valid pegged order which will be added to the order book 1523 order = getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1524 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1525 confirmation, err = tm.market.SubmitOrder(ctx, &order) 1526 require.NoError(t, err) 1527 1528 assert.NotNil(t, confirmation) 1529 assert.Equal(t, types.OrderStatusActive, confirmation.Order.Status) 1530 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 1531 assert.Equal(t, 2, tm.market.GetPeggedOrderCount()) 1532 1533 assert.True(t, order.Price.EQ(num.NewUint(500047)), order.Price.String()) 1534 } 1535 1536 func testPeggedOrderWithReprice(t *testing.T) { 1537 now := time.Unix(10, 0) 1538 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 1539 Duration: 1, 1540 }) 1541 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1542 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 1543 1544 auxParty, auxParty2 := "auxParty", "auxParty2" 1545 addAccount(t, tm, "party1") 1546 addAccount(t, tm, auxParty) 1547 addAccount(t, tm, auxParty2) 1548 addAccountWithAmount(tm, "lpprov", 10000000) 1549 1550 auxOrders := []*types.Order{ 1551 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1), 1552 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000), 1553 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 1554 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 1555 } 1556 for _, o := range auxOrders { 1557 conf, err := tm.market.SubmitOrder(ctx, o) 1558 require.NoError(t, err) 1559 require.NotNil(t, conf) 1560 } 1561 lp := &types.LiquidityProvisionSubmission{ 1562 MarketID: tm.market.GetID(), 1563 CommitmentAmount: num.NewUint(25000), 1564 Fee: num.DecimalFromFloat(0.01), 1565 } 1566 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 1567 // leave auction 1568 now = now.Add(2 * time.Second) 1569 tm.now = now 1570 tm.market.OnTick(ctx, now) 1571 1572 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1573 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1574 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1575 assert.NotNil(t, confirmation) 1576 assert.Equal(t, types.OrderStatusActive, confirmation.Order.Status) 1577 require.NoError(t, err) 1578 1579 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 90) 1580 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 110) 1581 1582 md := tm.market.GetMarketData() 1583 assert.True(t, md.StaticMidPrice.EQ(num.NewUint(500045)), md.StaticMidPrice.String()) 1584 // MidPrice > StaticMidPrice as LP volume range pushes LP orders in front of best bid / ask 1585 assert.True(t, md.MidPrice.EQ(num.NewUint(750021)), md.MidPrice.String()) 1586 // Place a valid pegged order which will be added to the order book 1587 // This order will cause the MID price to move and thus a reprice multiple times until it settles 1588 order = getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1589 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1590 _, err = tm.market.SubmitOrder(ctx, &order) 1591 require.NoError(t, err) 1592 1593 // Check to make sure the existing pegged order is repriced correctly 1594 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 1595 assert.Equal(t, 2, tm.market.GetPeggedOrderCount()) 1596 } 1597 1598 func testPeggedOrderParkWhenInAuction(t *testing.T) { 1599 now := time.Unix(10, 0) 1600 tm := getTestMarket(t, now, nil, nil) 1601 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1602 1603 addAccount(t, tm, "party1") 1604 1605 // Move into auction 1606 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 100}) 1607 tm.market.EnterAuction(ctx) 1608 1609 // Pegged order must be a LIMIT order 1610 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1611 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1612 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1613 assert.NotNil(t, confirmation) 1614 assert.Equal(t, confirmation.Order.Status, types.OrderStatusParked) 1615 assert.NoError(t, err) 1616 } 1617 1618 func testPeggedOrderUnparkAfterLeavingAuction(t *testing.T) { 1619 now := time.Unix(10, 0) 1620 closeSec := int64(10000000000) 1621 closingAt := time.Unix(closeSec, 0) 1622 tm := getTestMarket(t, now, nil, nil) 1623 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1624 1625 addAccount(t, tm, "party1") 1626 1627 // Move into auction 1628 tm.mas.StartOpeningAuction(now, &types.AuctionDuration{Duration: 100}) 1629 tm.market.EnterAuction(ctx) 1630 1631 // Pegged order must be a LIMIT order 1632 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1633 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 3) 1634 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1635 assert.NotNil(t, confirmation) 1636 assert.Equal(t, confirmation.Order.Status, types.OrderStatusParked) 1637 assert.NoError(t, err) 1638 1639 buy := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 90) 1640 confirmation, err = tm.market.SubmitOrder(context.Background(), &buy) 1641 require.NotNil(t, confirmation) 1642 assert.NoError(t, err) 1643 1644 require.NotNil(t, buy) 1645 sell := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 110) 1646 confirmation, err = tm.market.SubmitOrder(context.Background(), &sell) 1647 require.NotNil(t, confirmation) 1648 assert.NoError(t, err) 1649 1650 tm.market.LeaveAuctionWithIDGen(ctx, closingAt, newTestIDGenerator()) 1651 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 1652 } 1653 1654 func testPeggedOrderTypes(t *testing.T) { 1655 now := time.Unix(10, 0) 1656 tm := getTestMarket(t, now, nil, nil) 1657 1658 addAccount(t, tm, "party1") 1659 1660 // Pegged order must be a LIMIT order 1661 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1662 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1663 confirmation, err := tm.market.SubmitOrder(context.Background(), &order) 1664 assert.NotNil(t, confirmation) 1665 assert.NoError(t, err) 1666 1667 // Not MARKET 1668 order.Type = types.OrderTypeMarket 1669 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1670 assert.Nil(t, confirmation) 1671 assert.Error(t, err) 1672 } 1673 1674 func testPeggedOrderCancelParked(t *testing.T) { 1675 now := time.Unix(10, 0) 1676 tm := getTestMarket(t, now, nil, nil) 1677 1678 addAccount(t, tm, "party1") 1679 1680 // Pegged order will be parked as no reference prices 1681 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1682 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1683 confirmation, err := tm.market.SubmitOrder(context.Background(), &order) 1684 require.NotNil(t, confirmation) 1685 assert.NoError(t, err) 1686 } 1687 1688 func testPeggedOrderTIFs(t *testing.T) { 1689 now := time.Unix(10, 0) 1690 tm := getTestMarket(t, now, nil, nil) 1691 1692 addAccount(t, tm, "party1") 1693 1694 // Pegged order must be a LIMIT order 1695 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1696 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1697 1698 // Only allowed GTC 1699 order.Type = types.OrderTypeLimit 1700 order.TimeInForce = types.OrderTimeInForceGTC 1701 confirmation, err := tm.market.SubmitOrder(context.Background(), &order) 1702 assert.NotNil(t, confirmation) 1703 assert.NoError(t, err) 1704 1705 // and GTT 1706 order.TimeInForce = types.OrderTimeInForceGTT 1707 order.ExpiresAt = now.UnixNano() + 1000000000 1708 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1709 assert.NotNil(t, confirmation) 1710 assert.NoError(t, err) 1711 1712 // but not IOC 1713 order.ExpiresAt = 0 1714 order.TimeInForce = types.OrderTimeInForceIOC 1715 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1716 assert.Nil(t, confirmation) 1717 assert.Error(t, err) 1718 1719 // or FOK 1720 order.TimeInForce = types.OrderTimeInForceFOK 1721 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1722 assert.Nil(t, confirmation) 1723 assert.Error(t, err) 1724 } 1725 1726 func testPeggedOrderBuys(t *testing.T) { 1727 now := time.Unix(10, 0) 1728 tm := getTestMarket(t, now, nil, nil) 1729 1730 addAccount(t, tm, "party1") 1731 1732 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 100) 1733 1734 // BEST BID peg must be >= 0 1735 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 3) 1736 confirmation, err := tm.market.SubmitOrder(context.Background(), &order) 1737 assert.NotNil(t, confirmation) 1738 assert.NoError(t, err) 1739 1740 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 0) 1741 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1742 assert.NotNil(t, confirmation) 1743 assert.NoError(t, err) 1744 1745 // MID peg must be > 0 1746 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 0) 1747 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1748 assert.Nil(t, confirmation) 1749 assert.Error(t, err) 1750 1751 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1752 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1753 assert.NotNil(t, confirmation) 1754 assert.NoError(t, err) 1755 1756 // BEST ASK peg not allowed 1757 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 3) 1758 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1759 assert.Nil(t, confirmation) 1760 assert.Error(t, err) 1761 1762 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 3) 1763 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1764 assert.Nil(t, confirmation) 1765 assert.Error(t, err) 1766 1767 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 0) 1768 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1769 assert.Nil(t, confirmation) 1770 assert.Error(t, err) 1771 } 1772 1773 func testPeggedOrderSells(t *testing.T) { 1774 now := time.Unix(10, 0) 1775 tm := getTestMarket(t, now, nil, nil) 1776 1777 addAccount(t, tm, "party1") 1778 1779 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 100) 1780 1781 // BEST BID peg not allowed 1782 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 3) 1783 confirmation, err := tm.market.SubmitOrder(context.Background(), &order) 1784 assert.Nil(t, confirmation) 1785 assert.Error(t, err) 1786 1787 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 3) 1788 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1789 assert.Nil(t, confirmation) 1790 assert.Error(t, err) 1791 1792 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 0) 1793 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1794 assert.Nil(t, confirmation) 1795 assert.Error(t, err) 1796 1797 // MID peg must be > 0 1798 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 0) 1799 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1800 assert.Nil(t, confirmation) 1801 assert.Error(t, err) 1802 1803 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 3) 1804 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1805 assert.NotNil(t, confirmation) 1806 assert.NoError(t, err) 1807 1808 // BEST ASK peg must be >= 0 1809 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 3) 1810 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1811 assert.NotNil(t, confirmation) 1812 assert.NoError(t, err) 1813 1814 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 0) 1815 confirmation, err = tm.market.SubmitOrder(context.Background(), &order) 1816 assert.NotNil(t, confirmation) 1817 assert.NoError(t, err) 1818 } 1819 1820 func testPeggedOrderParkWhenPriceBelowZero(t *testing.T) { 1821 now := time.Unix(10, 0) 1822 tm := getTestMarket(t, now, nil, nil) 1823 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1824 1825 for _, acc := range []string{"buyer", "seller", "pegged"} { 1826 addAccount(t, tm, acc) 1827 } 1828 1829 buy := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "buyer", 10, 4) 1830 _, err := tm.market.SubmitOrder(ctx, &buy) 1831 require.NoError(t, err) 1832 1833 sell := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "seller", 10, 8) 1834 _, err = tm.market.SubmitOrder(ctx, &sell) 1835 require.NoError(t, err) 1836 1837 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "pegged", 10, 4) 1838 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 10) 1839 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1840 require.NoError(t, err) 1841 assert.Equal(t, 1842 types.OrderStatusParked.String(), 1843 confirmation.Order.Status.String(), "When pegged price below zero (MIDPRICE - OFFSET) <= 0") 1844 } 1845 1846 func testPeggedOrderParkWhenPriceRepricesBelowZero(t *testing.T) { 1847 now := time.Unix(10, 0) 1848 tm := getTestMarket(t, now, nil, nil) 1849 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1850 1851 for _, acc := range []string{"buyer", "seller", "pegged"} { 1852 addAccount(t, tm, acc) 1853 } 1854 1855 buy := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "buyer", 10, 4) 1856 _, err := tm.market.SubmitOrder(ctx, &buy) 1857 require.NoError(t, err) 1858 1859 sell := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "seller", 10, 8) 1860 _, err = tm.market.SubmitOrder(ctx, &sell) 1861 require.NoError(t, err) 1862 1863 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "pegged", 10, 4) 1864 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 5) 1865 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1866 require.NoError(t, err) 1867 1868 amendOrder(t, tm, "buyer", buy.ID, 0, 1, types.OrderTimeInForceUnspecified, 0, true) 1869 1870 assert.Equal(t, types.OrderStatusParked.String(), confirmation.Order.Status.String()) 1871 } 1872 1873 /*func TestPeggedOrderCrash(t *testing.T) { 1874 now := time.Unix(10, 0) 1875 closeSec := int64(10000000000) 1876 closingAt := time.Unix(closeSec, 0) 1877 tm := getTestMarket(t, now, closingAt, nil, nil) 1878 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1879 1880 for _, acc := range []string{"user1", "user2", "user3", "user4", "user5", "user6", "user7"} { 1881 addAccount(tm, acc) 1882 } 1883 1884 // Set up the best bid/ask values 1885 sendOrder(t, tm, &now, types.Order_TYPE_LIMIT, types.Order_TIME_IN_FORCE_GTC, 0, types.Side_SIDE_BUY, "user1", 5, 10500) 1886 sendOrder(t, tm, &now, types.Order_TYPE_LIMIT, types.Order_TIME_IN_FORCE_GTC, 0, types.Side_SIDE_SELL, "user2", 20, 11000) 1887 1888 // Pegged order buy 35 MID -500 1889 order := getOrder(t, tm, &now, types.Order_TYPE_LIMIT, types.Order_TIME_IN_FORCE_GTC, 0, types.Side_SIDE_BUY, "user3", 35, 0) 1890 order.PeggedOrder = getPeggedOrder(types.PeggedReference_PEGGED_REFERENCE_MID,500) 1891 _, err := tm.market.SubmitOrder(ctx, &order) 1892 require.NoError(t, err) 1893 1894 // Pegged order buy 16 BEST_BID -2000 1895 order2 := getOrder(t, tm, &now, types.Order_TYPE_LIMIT, types.Order_TIME_IN_FORCE_GTC, 0, types.Side_SIDE_BUY, "user4", 16, 0) 1896 order2.PeggedOrder = getPeggedOrder(types.PeggedReference_PEGGED_REFERENCE_BEST_BID,2000) 1897 _, err = tm.market.SubmitOrder(ctx, &order2) 1898 require.NoError(t, err) 1899 1900 // Pegged order sell 19 BEST_ASK 3000 1901 order3 := getOrder(t, tm, &now, types.Order_TYPE_LIMIT, types.Order_TIME_IN_FORCE_GTC, 0, types.Side_SIDE_SELL, "user5", 19, 0) 1902 order3.PeggedOrder = getPeggedOrder(types.PeggedReference_PEGGED_REFERENCE_BEST_ASK,3000) 1903 _, err = tm.market.SubmitOrder(ctx, &order3) 1904 require.NoError(t, err) 1905 1906 // Buy 25 @ 10000 1907 sendOrder(t, tm, &now, types.Order_TYPE_LIMIT, types.Order_TIME_IN_FORCE_GTC, 0, types.Side_SIDE_BUY, "user6", 25, 10000) 1908 1909 // Sell 25 @ 10250 1910 sendOrder(t, tm, &now, types.Order_TYPE_LIMIT, types.Order_TIME_IN_FORCE_GTC, 0, types.Side_SIDE_SELL, "user7", 25, 10250) 1911 }*/ 1912 1913 func testPeggedOrderParkCancelAll(t *testing.T) { 1914 now := time.Unix(10, 0) 1915 tm := getTestMarket(t, now, nil, nil) 1916 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1917 1918 addAccount(t, tm, "user") 1919 1920 // Send one normal order 1921 limitOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "user", 10, 100) 1922 require.NotEmpty(t, limitOrder) 1923 1924 // Send one pegged order that is live 1925 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "user", 10, 0) 1926 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 5) 1927 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1928 require.NoError(t, err) 1929 assert.NotNil(t, confirmation) 1930 1931 // Send one pegged order that is parked 1932 order2 := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "user", 10, 0) 1933 order2.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 5) 1934 confirmation2, err := tm.market.SubmitOrder(ctx, &order2) 1935 require.NoError(t, err) 1936 assert.NotNil(t, confirmation2) 1937 1938 cancelConf, err := tm.market.CancelAllOrders(ctx, "user") 1939 require.NoError(t, err) 1940 require.NotNil(t, cancelConf) 1941 assert.Equal(t, 3, len(cancelConf)) 1942 } 1943 1944 func testPeggedOrderExpiring2(t *testing.T) { 1945 now := time.Unix(10, 0) 1946 expire := now.Add(time.Second * 100) 1947 afterexpire := now.Add(time.Second * 200) 1948 tm := getTestMarket(t, now, nil, nil) 1949 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 1950 1951 addAccount(t, tm, "user") 1952 1953 // Send one normal expiring order 1954 limitOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTT, expire.UnixNano(), types.SideBuy, "user", 10, 100) 1955 require.NotEmpty(t, limitOrder) 1956 1957 // Amend the expiry time 1958 amendOrder(t, tm, "user", limitOrder, 0, 0, types.OrderTimeInForceUnspecified, now.UnixNano(), true) 1959 1960 // Send one pegged order that will be parked 1961 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTT, expire.UnixNano(), types.SideBuy, "user", 10, 0) 1962 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 5) 1963 confirmation, err := tm.market.SubmitOrder(ctx, &order) 1964 require.NoError(t, err) 1965 assert.NotNil(t, confirmation) 1966 1967 // Send one pegged order that will also be parked (after additing liquidity monitoring to market all orders will be parked unless both best_bid and best_offer exist) 1968 order2 := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTT, expire.UnixNano(), types.SideBuy, "user", 10, 0) 1969 order2.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 5) 1970 confirmation, err = tm.market.SubmitOrder(ctx, &order2) 1971 require.NoError(t, err) 1972 assert.NotNil(t, confirmation) 1973 tm.now = tm.now.Add(time.Second) 1974 tm.market.OnTick(ctx, tm.now) 1975 1976 // the limit order has expired so the order pegged to it has to be parked! 1977 assert.Equal(t, 2, tm.market.GetParkedOrderCount()) 1978 assert.Equal(t, 2, tm.market.GetPeggedOrderCount()) 1979 1980 // Move the time forward 1981 tm.events = nil 1982 tm.market.OnTick(ctx, afterexpire) 1983 t.Run("3 orders expired", func(t *testing.T) { 1984 // First collect all the orders events 1985 orders := []string{} 1986 for _, e := range tm.events { 1987 switch evt := e.(type) { 1988 case *events.ExpiredOrders: 1989 orders = append(orders, evt.OrderIDs()...) 1990 } 1991 } 1992 require.Len(t, orders, 2) 1993 // Check that we have no pegged orders 1994 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 1995 assert.Equal(t, 0, tm.market.GetPeggedOrderCount()) 1996 }) 1997 } 1998 1999 func testPeggedOrderOutputMessages(t *testing.T) { 2000 now := time.Unix(10, 0) 2001 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 2002 Duration: 1, 2003 }) 2004 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2005 2006 addAccount(t, tm, "user1") 2007 addAccount(t, tm, "user2") 2008 addAccount(t, tm, "user3") 2009 addAccount(t, tm, "user4") 2010 addAccount(t, tm, "user5") 2011 addAccount(t, tm, "user6") 2012 auxParty := "auxParty" 2013 auxParty2 := "auxParty2" 2014 addAccount(t, tm, auxParty) 2015 addAccount(t, tm, auxParty2) 2016 addAccountWithAmount(tm, "lpprov", 10000000) 2017 2018 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 2019 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1) 2020 conf, err := tm.market.SubmitOrder(context.Background(), alwaysOnBid) 2021 require.NotNil(t, conf) 2022 require.NoError(t, err) 2023 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 2024 2025 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 100000) 2026 conf, err = tm.market.SubmitOrder(context.Background(), alwaysOnAsk) 2027 require.NotNil(t, conf) 2028 require.NoError(t, err) 2029 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 2030 2031 auxOrders := []*types.Order{ 2032 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 2033 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 2034 } 2035 for _, o := range auxOrders { 2036 conf, err := tm.market.SubmitOrder(ctx, o) 2037 require.NoError(t, err) 2038 require.NotNil(t, conf) 2039 } 2040 lp := &types.LiquidityProvisionSubmission{ 2041 MarketID: tm.market.GetID(), 2042 CommitmentAmount: num.NewUint(5000), 2043 Fee: num.DecimalFromFloat(0.01), 2044 } 2045 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2046 // leave opening auction 2047 now = now.Add(2 * time.Second) 2048 tm.now = now 2049 tm.market.OnTick(ctx, now) 2050 2051 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "user1", 10, 0) 2052 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 10) 2053 confirmation, err := tm.market.SubmitOrder(ctx, &order) 2054 require.NoError(t, err) 2055 assert.NotNil(t, confirmation) 2056 assert.Equal(t, 7, int(tm.orderEventCount)) 2057 2058 order2 := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "user2", 10, 0) 2059 order2.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 15) 2060 confirmation2, err := tm.market.SubmitOrder(ctx, &order2) 2061 require.NoError(t, err) 2062 assert.NotNil(t, confirmation2) 2063 assert.Equal(t, 8, int(tm.orderEventCount)) 2064 2065 order3 := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "user3", 10, 0) 2066 order3.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 10) 2067 confirmation3, err := tm.market.SubmitOrder(ctx, &order3) 2068 require.NoError(t, err) 2069 assert.NotNil(t, confirmation3) 2070 assert.Equal(t, 9, int(tm.orderEventCount)) 2071 2072 order4 := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "user4", 10, 0) 2073 order4.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 10) 2074 confirmation4, err := tm.market.SubmitOrder(ctx, &order4) 2075 require.NoError(t, err) 2076 assert.NotNil(t, confirmation4) 2077 assert.Equal(t, 10, int(tm.orderEventCount)) 2078 2079 limitOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "user5", 1000, 120) 2080 require.NotEmpty(t, limitOrder) 2081 // force reference price checks result in more events 2082 // assert.Equal(t, int(28), int(tm.orderEventCount)) 2083 assert.Equal(t, 14, int(tm.orderEventCount)) 2084 2085 limitOrder2 := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "user6", 1000, 80) 2086 require.NotEmpty(t, limitOrder2) 2087 // assert.Equal(t, int(35), int(tm.orderEventCount)) 2088 assert.Equal(t, 17, int(tm.orderEventCount)) 2089 } 2090 2091 func testPeggedOrderRepricing(t *testing.T) { 2092 // Create the market 2093 now := time.Unix(10, 0) 2094 2095 var ( 2096 buyPrice uint64 = 90 2097 sellPrice uint64 = 110 2098 midPrice = (sellPrice + buyPrice) / 2 2099 ) 2100 2101 tests := []struct { 2102 reference types.PeggedReference 2103 side types.Side 2104 offset uint64 2105 expectedPrice *num.Uint 2106 expectingError string 2107 }{ 2108 { 2109 reference: types.PeggedReferenceBestBid, 2110 side: types.SideBuy, 2111 offset: 3, 2112 expectedPrice: num.NewUint(buyPrice - 3), 2113 }, 2114 { 2115 reference: types.PeggedReferenceBestAsk, 2116 side: types.SideBuy, 2117 offset: 0, 2118 expectedPrice: num.UintZero(), 2119 expectingError: "offset must be greater than zero", 2120 }, 2121 { 2122 reference: types.PeggedReferenceMid, 2123 side: types.SideBuy, 2124 offset: 5, 2125 expectedPrice: num.NewUint(midPrice - 5), 2126 }, 2127 { 2128 reference: types.PeggedReferenceMid, 2129 side: types.SideSell, 2130 offset: 5, 2131 expectedPrice: num.NewUint(midPrice + 5), 2132 }, 2133 { 2134 reference: types.PeggedReferenceBestAsk, 2135 side: types.SideSell, 2136 offset: 5, 2137 expectedPrice: num.NewUint(sellPrice + 5), 2138 }, 2139 } 2140 2141 for _, test := range tests { 2142 t.Run("", func(t *testing.T) { 2143 // Create market 2144 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 2145 Duration: 1, 2146 }) 2147 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2148 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 2149 2150 auxParty, auxParty2 := "auxParty", "auxParty2" 2151 addAccount(t, tm, "party1") 2152 addAccount(t, tm, auxParty) 2153 addAccount(t, tm, auxParty2) 2154 addAccountWithAmount(tm, "lpprov", 10000000) 2155 2156 auxOrders := []*types.Order{ 2157 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 80), 2158 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 120), 2159 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 2160 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 2161 } 2162 for _, o := range auxOrders { 2163 conf, err := tm.market.SubmitOrder(ctx, o) 2164 require.NoError(t, err) 2165 require.NotNil(t, conf) 2166 } 2167 lp := &types.LiquidityProvisionSubmission{ 2168 MarketID: tm.market.GetID(), 2169 CommitmentAmount: num.NewUint(25000), 2170 Fee: num.DecimalFromFloat(0.01), 2171 } 2172 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2173 // leave auction 2174 now := now.Add(2 * time.Second) 2175 tm.now = now 2176 tm.market.OnTick(ctx, now) 2177 2178 // Create buy and sell orders 2179 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, buyPrice) 2180 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, sellPrice) 2181 2182 // Create pegged order 2183 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, test.side, "party1", 10, 0) 2184 order.PeggedOrder = newPeggedOrder(test.reference, test.offset) 2185 conf, err := tm.market.SubmitOrder(context.Background(), &order) 2186 if msg := test.expectingError; msg != "" { 2187 require.Error(t, err, msg) 2188 } else { 2189 require.NoError(t, err) 2190 assert.True(t, test.expectedPrice.EQ(conf.Order.Price), conf.Order.Price) 2191 } 2192 }) 2193 } 2194 } 2195 2196 func testPeggedOrderExpiring(t *testing.T) { 2197 // Create the market 2198 now := time.Unix(10, 0) 2199 2200 tm := getTestMarket(t, now, nil, nil) 2201 addAccount(t, tm, "party") 2202 2203 // Create buy and sell orders 2204 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party", 1, 100) 2205 sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party", 1, 200) 2206 2207 // let's create N orders with different expiration time 2208 expirations := []struct { 2209 party string 2210 expiration time.Time 2211 }{ 2212 {"party-10", now.Add(10 * time.Minute)}, 2213 {"party-20", now.Add(20 * time.Minute)}, 2214 {"party-30", now.Add(30 * time.Minute)}, 2215 } 2216 for _, test := range expirations { 2217 addAccount(t, tm, test.party) 2218 2219 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTT, 0, types.SideBuy, test.party, 10, 150) 2220 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 10) 2221 order.ExpiresAt = test.expiration.UnixNano() 2222 _, err := tm.market.SubmitOrder(context.Background(), &order) 2223 require.NoError(t, err) 2224 } 2225 assert.Equal(t, len(expirations), tm.market.GetPeggedOrderCount()) 2226 2227 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2228 tm.events = nil 2229 tm.market.OnTick(ctx, now.Add(25*time.Minute)) 2230 t.Run("2 orders expired", func(t *testing.T) { 2231 // First collect all the orders events 2232 orders := []string{} 2233 for _, e := range tm.events { 2234 switch evt := e.(type) { 2235 case *events.ExpiredOrders: 2236 orders = append(orders, evt.OrderIDs()...) 2237 } 2238 } 2239 2240 assert.Equal(t, 2, len(orders)) 2241 assert.Equal(t, 1, tm.market.GetPeggedOrderCount(), "1 order should still be in the market") 2242 }) 2243 } 2244 2245 func TestPeggedOrdersAmends(t *testing.T) { 2246 t.Run("pegged orders amend an order that is parked but becomes live ", testPeggedOrderAmendParkedToLive) 2247 t.Run("pegged orders amend an order that is parked and remains parked", testPeggedOrderAmendParkedStayParked) 2248 t.Run("pegged orders amend an order that is live but becomes parked", testPeggedOrderAmendForcesPark) 2249 t.Run("pegged orders amend an order while in auction", testPeggedOrderAmendDuringAuction) 2250 t.Run("pegged orders amend an orders pegged reference", testPeggedOrderAmendReference) 2251 t.Run("pegged orders amend an orders pegged reference during an auction", testPeggedOrderAmendReferenceInAuction) 2252 t.Run("pegged orders amend multiple fields at once", testPeggedOrderAmendMultiple) 2253 t.Run("pegged orders amend multiple fields at once in an auction", testPeggedOrderAmendMultipleInAuction) 2254 t.Run("pegged orders delete an order that has lost time priority", testPeggedOrderCanDeleteAfterLostPriority) 2255 t.Run("pegged orders validate mid price values", testPeggedOrderMidPriceCalc) 2256 } 2257 2258 // We had a case where things crashed when the orders on the same price level were not sorted 2259 // in createdAt order. Test this by creating a pegged order and repricing to make it lose it's time order. 2260 func testPeggedOrderCanDeleteAfterLostPriority(t *testing.T) { 2261 now := time.Unix(10, 0) 2262 tm := getTestMarket(t, now, nil, nil) 2263 2264 addAccount(t, tm, "party1") 2265 2266 // Place trades so we have a valid BEST_BID 2267 buyOrder1 := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 100) 2268 require.NotNil(t, buyOrder1) 2269 2270 // Place the pegged order 2271 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 10) 2272 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 10) 2273 confirmation, err := tm.market.SubmitOrder(context.Background(), &order) 2274 require.NotNil(t, confirmation) 2275 assert.NoError(t, err) 2276 2277 // Place a normal limit order behind the pegged order 2278 buyOrder2 := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 90) 2279 require.NotNil(t, buyOrder2) 2280 2281 // Amend first order to move pegged 2282 amendOrder(t, tm, "party1", buyOrder1, 0, 101, types.OrderTimeInForceUnspecified, 0, true) 2283 // Amend again to make the pegged order reprice behind the second limit order 2284 amendOrder(t, tm, "party1", buyOrder1, 0, 100, types.OrderTimeInForceUnspecified, 0, true) 2285 2286 // Try to delete the pegged order 2287 cancelconf, _ := tm.market.CancelOrder(context.TODO(), "party1", order.ID, vgcrypto.RandomHash()) 2288 assert.NotNil(t, cancelconf) 2289 assert.Equal(t, types.OrderStatusCancelled, cancelconf.Order.Status) 2290 } 2291 2292 // If we amend an order that is parked and not in auction we need to see if the amendment has caused the 2293 // order to be unparkable. If so we will have to put it back on the live book. 2294 func testPeggedOrderAmendParkedToLive(t *testing.T) { 2295 now := time.Unix(10, 0) 2296 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 2297 Duration: 1, 2298 }) 2299 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2300 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 2301 2302 auxParty, auxParty2 := "auxParty", "auxParty2" 2303 addAccount(t, tm, "party1") 2304 addAccount(t, tm, auxParty) 2305 addAccount(t, tm, auxParty2) 2306 addAccountWithAmount(tm, "lpprov", 10000000) 2307 2308 // Place 2 trades so we have a valid BEST_BID+MID+BEST_ASK price 2309 buyOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 9) 2310 require.NotNil(t, buyOrder) 2311 sellOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 11) 2312 require.NotNil(t, sellOrder) 2313 auxOrders := []*types.Order{ 2314 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 10), 2315 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 10), 2316 } 2317 for _, o := range auxOrders { 2318 conf, err := tm.market.SubmitOrder(ctx, o) 2319 assert.NoError(t, err) 2320 assert.NotNil(t, conf) 2321 } 2322 2323 // Place the pegged order which will be parked 2324 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 10) 2325 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 20) 2326 confirmation, err := tm.market.SubmitOrder(ctx, &order) 2327 require.NotNil(t, confirmation) 2328 assert.NoError(t, err) 2329 2330 // Amend offset so we can reprice 2331 amend := getAmend(tm.market.GetID(), confirmation.Order.ID, 0, 0, types.OrderTimeInForceUnspecified, 0) 2332 off := num.NewUint(5) 2333 amend.PeggedOffset = off 2334 amended, err := tm.market.AmendOrder(ctx, amend, "party1", vgcrypto.RandomHash()) 2335 require.NotNil(t, amended) 2336 assert.Equal(t, off, amended.Order.PeggedOrder.Offset) 2337 assert.NoError(t, err) 2338 2339 // Check we should have no parked orders 2340 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 2341 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 2342 lp := &types.LiquidityProvisionSubmission{ 2343 MarketID: tm.market.GetID(), 2344 CommitmentAmount: num.NewUint(5000), 2345 Fee: num.DecimalFromFloat(0.01), 2346 } 2347 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2348 // leave opening auction 2349 now = now.Add(2 * time.Second) 2350 tm.now = now 2351 tm.market.OnTick(ctx, now) 2352 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 2353 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 2354 } 2355 2356 // Amend a parked order but the order remains parked. 2357 func testPeggedOrderAmendParkedStayParked(t *testing.T) { 2358 now := time.Unix(10, 0) 2359 tm := getTestMarket(t, now, nil, nil) 2360 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2361 2362 addAccount(t, tm, "party1") 2363 2364 // Place 2 trades so we have a valid BEST_BID+MID+BEST_ASK price 2365 buyOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 9) 2366 require.NotNil(t, buyOrder) 2367 sellOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 11) 2368 require.NotNil(t, sellOrder) 2369 2370 // Place the pegged order which will be parked 2371 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 10) 2372 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 20) 2373 confirmation, err := tm.market.SubmitOrder(ctx, &order) 2374 require.NotNil(t, confirmation) 2375 assert.NoError(t, err) 2376 2377 // Amend offset so we can reprice 2378 amend := getAmend(tm.market.GetID(), confirmation.Order.ID, 0, 0, types.OrderTimeInForceUnspecified, 0) 2379 off := num.NewUint(15) 2380 amend.PeggedOffset = off 2381 amended, err := tm.market.AmendOrder(ctx, amend, "party1", vgcrypto.RandomHash()) 2382 require.NotNil(t, amended) 2383 assert.Equal(t, off, amended.Order.PeggedOrder.Offset) 2384 assert.NoError(t, err) 2385 2386 // Check we should have no parked orders 2387 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 2388 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 2389 } 2390 2391 // Take a valid live order and force it to be parked by amending it. 2392 func testPeggedOrderAmendForcesPark(t *testing.T) { 2393 now := time.Unix(10, 0) 2394 tm := getTestMarket(t, now, nil, nil) 2395 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2396 2397 addAccount(t, tm, "party1") 2398 2399 // Place 2 trades so we have a valid BEST_BID+MID+BEST_ASK price 2400 buyOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 9) 2401 require.NotNil(t, buyOrder) 2402 sellOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 11) 2403 require.NotNil(t, sellOrder) 2404 2405 // Place the pegged order 2406 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 10) 2407 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 3) 2408 confirmation, err := tm.market.SubmitOrder(ctx, &order) 2409 require.NotNil(t, confirmation) 2410 assert.NoError(t, err) 2411 2412 // Amend offset so we cannot reprice 2413 amend := getAmend(tm.market.GetID(), confirmation.Order.ID, 0, 0, types.OrderTimeInForceUnspecified, 0) 2414 amend.PeggedOffset = num.NewUint(15) 2415 amended, err := tm.market.AmendOrder(ctx, amend, "party1", vgcrypto.RandomHash()) 2416 require.NotNil(t, amended) 2417 assert.NoError(t, err) 2418 2419 // Order should be parked 2420 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 2421 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 2422 assert.Equal(t, types.OrderStatusParked, amended.Order.Status) 2423 } 2424 2425 func testPeggedOrderAmendDuringAuction(t *testing.T) { 2426 now := time.Unix(10, 0) 2427 closeSec := int64(10000000000) 2428 tm := getTestMarket(t, now, nil, nil) 2429 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2430 2431 addAccount(t, tm, "party1") 2432 2433 tm.mas.StartPriceAuction(now, &types.AuctionDuration{ 2434 Duration: closeSec / 10, // some time in the future, before closing 2435 }) 2436 tm.market.EnterAuction(ctx) 2437 2438 // Place 2 trades so we have a valid BEST_BID+MID+BEST_ASK price 2439 buyOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 9) 2440 require.NotNil(t, buyOrder) 2441 sellOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 11) 2442 require.NotNil(t, sellOrder) 2443 2444 // Place the pegged order which will park it 2445 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 10) 2446 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 3) 2447 confirmation, err := tm.market.SubmitOrder(context.Background(), &order) 2448 require.NotNil(t, confirmation) 2449 assert.NoError(t, err) 2450 2451 // Amend offset so we cannot reprice 2452 amend := getAmend(tm.market.GetID(), confirmation.Order.ID, 0, 0, types.OrderTimeInForceUnspecified, 0) 2453 amend.PeggedOffset = num.NewUint(5) 2454 amended, err := tm.market.AmendOrder(context.Background(), amend, "party1", vgcrypto.RandomHash()) 2455 require.NotNil(t, amended) 2456 assert.NoError(t, err) 2457 2458 assert.Equal(t, types.OrderStatusParked, amended.Order.Status) 2459 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 2460 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 2461 } 2462 2463 func testPeggedOrderAmendReference(t *testing.T) { 2464 now := time.Unix(10, 0) 2465 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 2466 Duration: 1, 2467 }) 2468 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2469 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 2470 2471 auxParty, auxParty2 := "auxParty", "auxParty2" 2472 addAccount(t, tm, "party1") 2473 addAccount(t, tm, auxParty) 2474 addAccount(t, tm, auxParty2) 2475 addAccountWithAmount(tm, "lpprov", 10000000) 2476 2477 auxOrders := []*types.Order{ 2478 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 10), 2479 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 10), 2480 } 2481 for _, o := range auxOrders { 2482 conf, err := tm.market.SubmitOrder(ctx, o) 2483 require.NoError(t, err) 2484 require.NotNil(t, conf) 2485 } 2486 // Place 2 trades so we have a valid BEST_BID+MID+BEST_ASK price 2487 buyOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 9) 2488 require.NotNil(t, buyOrder) 2489 sellOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 11) 2490 require.NotNil(t, sellOrder) 2491 lp := &types.LiquidityProvisionSubmission{ 2492 MarketID: tm.market.GetID(), 2493 CommitmentAmount: num.NewUint(5000), 2494 Fee: num.DecimalFromFloat(0.01), 2495 } 2496 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2497 // leave opening auction 2498 now = now.Add(2 * time.Second) 2499 tm.now = now 2500 tm.market.OnTick(ctx, now) 2501 2502 // Place the pegged order which will park it 2503 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 10) 2504 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 3) 2505 confirmation, err := tm.market.SubmitOrder(context.Background(), &order) 2506 require.NotNil(t, confirmation) 2507 assert.NoError(t, err) 2508 2509 // Amend offset so we cannot reprice 2510 amend := getAmend(tm.market.GetID(), confirmation.Order.ID, 0, 0, types.OrderTimeInForceUnspecified, 0) 2511 amend.PeggedReference = types.PeggedReferenceMid 2512 amended, err := tm.market.AmendOrder(context.Background(), amend, "party1", vgcrypto.RandomHash()) 2513 require.NotNil(t, amended) 2514 assert.NoError(t, err) 2515 2516 assert.Equal(t, types.OrderStatusActive, amended.Order.Status) 2517 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 2518 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 2519 assert.Equal(t, types.PeggedReferenceMid, amended.Order.PeggedOrder.Reference) 2520 } 2521 2522 func testPeggedOrderAmendReferenceInAuction(t *testing.T) { 2523 now := time.Unix(10, 0) 2524 closeSec := int64(10000000000) 2525 tm := getTestMarket(t, now, nil, nil) 2526 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2527 2528 addAccount(t, tm, "party1") 2529 2530 tm.mas.StartPriceAuction(now, &types.AuctionDuration{ 2531 Duration: closeSec / 10, // some time in the future, before closing 2532 }) 2533 tm.market.EnterAuction(ctx) 2534 2535 // Place 2 trades so we have a valid BEST_BID+MID+BEST_ASK price 2536 buyOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 9) 2537 require.NotNil(t, buyOrder) 2538 sellOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 11) 2539 require.NotNil(t, sellOrder) 2540 2541 // Place the pegged order which will park it 2542 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 10) 2543 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 3) 2544 confirmation, err := tm.market.SubmitOrder(context.Background(), &order) 2545 require.NotNil(t, confirmation) 2546 assert.NoError(t, err) 2547 2548 // Amend offset so we cannot reprice 2549 amend := getAmend(tm.market.GetID(), confirmation.Order.ID, 0, 0, types.OrderTimeInForceUnspecified, 0) 2550 amend.PeggedReference = types.PeggedReferenceMid 2551 amended, err := tm.market.AmendOrder(context.Background(), amend, "party1", vgcrypto.RandomHash()) 2552 require.NotNil(t, amended) 2553 assert.NoError(t, err) 2554 2555 assert.Equal(t, types.OrderStatusParked, amended.Order.Status) 2556 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 2557 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 2558 assert.Equal(t, types.PeggedReferenceMid, amended.Order.PeggedOrder.Reference) 2559 } 2560 2561 func testPeggedOrderAmendMultipleInAuction(t *testing.T) { 2562 now := time.Unix(10, 0) 2563 closeSec := int64(10000000000) 2564 tm := getTestMarket(t, now, nil, nil) 2565 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2566 2567 addAccount(t, tm, "party1") 2568 2569 tm.mas.StartPriceAuction(now, &types.AuctionDuration{ 2570 Duration: closeSec / 10, // some time in the future, before closing 2571 }) 2572 tm.market.EnterAuction(ctx) 2573 2574 // Place 2 trades so we have a valid BEST_BID+MID+BEST_ASK price 2575 buyOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 9) 2576 require.NotNil(t, buyOrder) 2577 sellOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 11) 2578 require.NotNil(t, sellOrder) 2579 2580 // Place the pegged order which will park it 2581 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 10) 2582 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 3) 2583 confirmation, err := tm.market.SubmitOrder(ctx, &order) 2584 require.NotNil(t, confirmation) 2585 assert.NoError(t, err) 2586 2587 // Amend offset so we cannot reprice 2588 amend := getAmend(tm.market.GetID(), confirmation.Order.ID, 0, 0, types.OrderTimeInForceUnspecified, 0) 2589 amend.PeggedReference = types.PeggedReferenceMid 2590 amend.TimeInForce = types.OrderTimeInForceGTT 2591 exp := int64(20000000000) 2592 amend.ExpiresAt = &exp 2593 amended, err := tm.market.AmendOrder(ctx, amend, "party1", vgcrypto.RandomHash()) 2594 require.NotNil(t, amended) 2595 assert.NoError(t, err) 2596 2597 assert.Equal(t, types.OrderStatusParked, amended.Order.Status) 2598 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 2599 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 2600 assert.Equal(t, types.PeggedReferenceMid, amended.Order.PeggedOrder.Reference) 2601 assert.Equal(t, types.OrderTimeInForceGTT, amended.Order.TimeInForce) 2602 } 2603 2604 func testPeggedOrderAmendMultiple(t *testing.T) { 2605 now := time.Unix(10, 0) 2606 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 2607 Duration: 1, 2608 }) 2609 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2610 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 2611 2612 auxParty, auxParty2 := "auxParty", "auxParty2" 2613 addAccount(t, tm, "party1") 2614 addAccount(t, tm, auxParty) 2615 addAccount(t, tm, auxParty2) 2616 addAccountWithAmount(tm, "lpprov", 10000000) 2617 2618 auxOrders := []*types.Order{ 2619 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 10), 2620 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 10), 2621 } 2622 for _, o := range auxOrders { 2623 conf, err := tm.market.SubmitOrder(ctx, o) 2624 require.NoError(t, err) 2625 require.NotNil(t, conf) 2626 } 2627 // Place 2 trades so we have a valid BEST_BID+MID+BEST_ASK price 2628 buyOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 9) 2629 require.NotNil(t, buyOrder) 2630 sellOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 11) 2631 require.NotNil(t, sellOrder) 2632 lp := &types.LiquidityProvisionSubmission{ 2633 MarketID: tm.market.GetID(), 2634 CommitmentAmount: num.NewUint(5000), 2635 Fee: num.DecimalFromFloat(0.01), 2636 } 2637 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2638 2639 // leave opening auction 2640 now = now.Add(2 * time.Second) 2641 tm.now = now 2642 tm.market.OnTick(ctx, now) 2643 2644 // Place the pegged order which will park it 2645 order := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 10) 2646 order.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 3) 2647 confirmation, err := tm.market.SubmitOrder(context.Background(), &order) 2648 require.NotNil(t, confirmation) 2649 assert.NoError(t, err) 2650 2651 // Amend offset so we cannot reprice 2652 amend := getAmend(tm.market.GetID(), confirmation.Order.ID, 0, 0, types.OrderTimeInForceUnspecified, 0) 2653 amend.PeggedReference = types.PeggedReferenceMid 2654 amend.TimeInForce = types.OrderTimeInForceGTT 2655 exp := int64(20000000000) 2656 amend.ExpiresAt = &exp 2657 amended, err := tm.market.AmendOrder(context.Background(), amend, "party1", vgcrypto.RandomHash()) 2658 require.NotNil(t, amended) 2659 assert.NoError(t, err) 2660 2661 assert.Equal(t, types.OrderStatusActive, amended.Order.Status) 2662 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 2663 assert.Equal(t, 1, tm.market.GetPeggedOrderCount()) 2664 assert.Equal(t, types.PeggedReferenceMid, amended.Order.PeggedOrder.Reference) 2665 assert.Equal(t, types.OrderTimeInForceGTT, amended.Order.TimeInForce) 2666 } 2667 2668 func testPeggedOrderMidPriceCalc(t *testing.T) { 2669 now := time.Unix(10, 0) 2670 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 2671 Duration: 1, 2672 }) 2673 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2674 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 2675 2676 auxParty, auxParty2 := "auxParty", "auxParty2" 2677 addAccount(t, tm, "party1") 2678 addAccount(t, tm, auxParty) 2679 addAccount(t, tm, auxParty2) 2680 addAccountWithAmount(tm, "lpprov", 10000000) 2681 2682 // Place 2 trades so we have a valid BEST_BID+MID+BEST_ASK price 2683 buyOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 90) 2684 require.NotNil(t, buyOrder) 2685 sellOrder := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 1, 110) 2686 require.NotNil(t, sellOrder) 2687 auxOrders := []*types.Order{ 2688 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 2689 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 2690 } 2691 for _, o := range auxOrders { 2692 conf, err := tm.market.SubmitOrder(ctx, o) 2693 require.NoError(t, err) 2694 require.NotNil(t, conf) 2695 } 2696 lp := &types.LiquidityProvisionSubmission{ 2697 MarketID: tm.market.GetID(), 2698 CommitmentAmount: num.NewUint(5000), 2699 Fee: num.DecimalFromFloat(0.01), 2700 } 2701 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2702 now = now.Add(2 * time.Second) 2703 tm.now = now 2704 tm.market.OnTick(ctx, now) 2705 2706 // Place the pegged orders 2707 order1 := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 10, 10) 2708 order1.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 20) 2709 confirmation1, err := tm.market.SubmitOrder(context.Background(), &order1) 2710 require.NotNil(t, confirmation1) 2711 assert.NoError(t, err) 2712 assert.True(t, confirmation1.Order.Price.EQ(num.NewUint(80))) 2713 2714 order2 := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, "party1", 10, 10) 2715 order2.PeggedOrder = newPeggedOrder(types.PeggedReferenceMid, 20) 2716 confirmation2, err := tm.market.SubmitOrder(context.Background(), &order2) 2717 require.NotNil(t, confirmation2) 2718 assert.NoError(t, err) 2719 assert.True(t, confirmation2.Order.Price.EQ(num.NewUint(120))) 2720 2721 // Make the mid price wonky (needs rounding) 2722 buyOrder2 := sendOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, "party1", 1, 91) 2723 require.NotNil(t, buyOrder2) 2724 2725 // Check the pegged orders have reprices properly 2726 assert.True(t, confirmation1.Order.Price.EQ(num.NewUint(81))) // Buy price gets rounded up 2727 assert.True(t, confirmation2.Order.Price.EQ(num.NewUint(120))) // Sell price gets rounded down 2728 } 2729 2730 func TestPeggedOrderUnparkAfterLeavingAuctionWithNoFunds2772(t *testing.T) { 2731 now := time.Unix(10, 0) 2732 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 2733 Duration: 1, 2734 }) 2735 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2736 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 2737 2738 auxParty1, auxParty2, lp, party1 := "auxParty", "auxParty2", "lp", "party1" 2739 addAccount(t, tm, auxParty1) 2740 addAccount(t, tm, auxParty2) 2741 addAccountWithAmount(tm, lp, 10000000) 2742 addAccount(t, tm, party1) 2743 2744 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 2745 peggedOffset := uint64(10) 2746 buyPeggedOrder := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, party1, peggedOffset, 0) 2747 buyPeggedOrder.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 10) 2748 confirmationPeggedBuy, err := tm.market.SubmitOrder(ctx, &buyPeggedOrder) 2749 assert.NotNil(t, confirmationPeggedBuy) 2750 assert.Equal(t, confirmationPeggedBuy.Order.Status, types.OrderStatusParked) 2751 assert.NoError(t, err) 2752 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 2753 2754 sellPeggedOrder := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, party1, peggedOffset, 0) 2755 sellPeggedOrder.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 10) 2756 confirmationPeggedSell, err := tm.market.SubmitOrder(ctx, &sellPeggedOrder) 2757 assert.NotNil(t, confirmationPeggedSell) 2758 assert.Equal(t, confirmationPeggedSell.Order.Status, types.OrderStatusParked) 2759 assert.NoError(t, err) 2760 assert.Equal(t, 2, tm.market.GetParkedOrderCount()) 2761 2762 bestBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 1) 2763 auxOrders := []*types.Order{ 2764 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty1, 1, 10000), 2765 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 2000), 2766 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideSell, auxParty1, 1, 2000), 2767 } 2768 bestBidConf, err := tm.market.SubmitOrder(ctx, bestBid) 2769 require.NoError(t, err) 2770 require.NotNil(t, bestBidConf) 2771 for _, o := range auxOrders { 2772 conf, err := tm.market.SubmitOrder(ctx, o) 2773 require.NoError(t, err) 2774 require.NotNil(t, conf) 2775 } 2776 lps := &types.LiquidityProvisionSubmission{ 2777 MarketID: tm.market.GetID(), 2778 CommitmentAmount: num.NewUint(5000), 2779 Fee: num.DecimalFromFloat(0.01), 2780 } 2781 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lps, lp, vgcrypto.RandomHash())) 2782 2783 // leave opening auction 2784 now = now.Add(2 * time.Second) 2785 tm.now = now 2786 tm.market.OnTick(ctx, now) 2787 2788 md := tm.market.GetMarketData() 2789 require.NotNil(t, md) 2790 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 2791 2792 // 1 pegged order should get deployed, the other one should remain parked 2793 // as its offset is such that the price would be negative with current best bid 2794 assert.Equal(t, confirmationPeggedBuy.Order.Status, types.OrderStatusParked) 2795 assert.Equal(t, confirmationPeggedSell.Order.Status, types.OrderStatusActive) 2796 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 2797 2798 amend := &types.OrderAmendment{ 2799 OrderID: bestBidConf.Order.ID, 2800 MarketID: bestBidConf.Order.MarketID, 2801 Price: num.NewUint(peggedOffset + 1), 2802 } 2803 2804 tm.events = nil 2805 amended, err := tm.market.AmendOrder(ctx, amend, bestBidConf.Order.Party, vgcrypto.RandomHash()) 2806 require.NoError(t, err) 2807 assert.Equal(t, amended.Order.Status, types.OrderStatusActive) 2808 2809 // now both orders should get unparked 2810 assert.Equal(t, confirmationPeggedBuy.Order.Status, types.OrderStatusActive) 2811 assert.Equal(t, confirmationPeggedSell.Order.Status, types.OrderStatusActive) 2812 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 2813 } 2814 2815 func TestPeggedOrderCancelledWhenPartyCannotAffordTheMarginOnceDeployed(t *testing.T) { 2816 now := time.Unix(10, 0) 2817 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 2818 Duration: 1, 2819 }) 2820 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2821 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 2822 2823 auxParty1, auxParty2, lp, party1 := "auxParty", "auxParty2", "lp", "party1" 2824 addAccount(t, tm, auxParty1) 2825 addAccount(t, tm, auxParty2) 2826 addAccountWithAmount(tm, lp, 10000000) 2827 addAccountWithAmount(tm, party1, 1) 2828 2829 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 2830 peggedOffset := uint64(10) 2831 buyPeggedOrder := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideBuy, party1, peggedOffset, 0) 2832 buyPeggedOrder.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestBid, 1) 2833 confirmationPeggedBuy, err := tm.market.SubmitOrder(ctx, &buyPeggedOrder) 2834 assert.NotNil(t, confirmationPeggedBuy) 2835 assert.Equal(t, confirmationPeggedBuy.Order.Status, types.OrderStatusParked) 2836 assert.NoError(t, err) 2837 assert.Equal(t, 1, tm.market.GetParkedOrderCount()) 2838 2839 sellPeggedOrder := getOrder(t, tm, &now, types.OrderTypeLimit, types.OrderTimeInForceGTC, 0, types.SideSell, party1, peggedOffset, 0) 2840 sellPeggedOrder.PeggedOrder = newPeggedOrder(types.PeggedReferenceBestAsk, 1) 2841 confirmationPeggedSell, err := tm.market.SubmitOrder(ctx, &sellPeggedOrder) 2842 assert.NotNil(t, confirmationPeggedSell) 2843 assert.Equal(t, confirmationPeggedSell.Order.Status, types.OrderStatusParked) 2844 assert.NoError(t, err) 2845 assert.Equal(t, 2, tm.market.GetParkedOrderCount()) 2846 2847 bestBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 10) 2848 matchingPrice := uint64(2000) 2849 auxOrders := []*types.Order{ 2850 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty1, 1, 10000), 2851 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, matchingPrice), 2852 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideSell, auxParty1, 1, matchingPrice), 2853 } 2854 bestBidConf, err := tm.market.SubmitOrder(ctx, bestBid) 2855 require.NoError(t, err) 2856 require.NotNil(t, bestBidConf) 2857 for _, o := range auxOrders { 2858 conf, err := tm.market.SubmitOrder(ctx, o) 2859 require.NoError(t, err) 2860 require.NotNil(t, conf) 2861 } 2862 lps := &types.LiquidityProvisionSubmission{ 2863 MarketID: tm.market.GetID(), 2864 CommitmentAmount: num.NewUint(5000), 2865 Fee: num.DecimalFromFloat(0.01), 2866 } 2867 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lps, lp, vgcrypto.RandomHash())) 2868 2869 // leave opening auction 2870 now = now.Add(2 * time.Second) 2871 tm.now = now 2872 tm.market.OnTick(ctx, now) 2873 2874 md := tm.market.GetMarketData() 2875 require.NotNil(t, md) 2876 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 2877 require.Equal(t, num.NewUint(matchingPrice), md.MarkPrice) 2878 2879 assert.Equal(t, confirmationPeggedBuy.Order.Status, types.OrderStatusCancelled) 2880 assert.Equal(t, confirmationPeggedSell.Order.Status, types.OrderStatusCancelled) 2881 assert.Equal(t, 0, tm.market.GetParkedOrderCount()) 2882 } 2883 2884 // test for issue 787, 2885 // segv when an GTT order is cancelled, then expires. 2886 func TestOrderBookSimple_CancelGTTOrderThenRunExpiration(t *testing.T) { 2887 now := time.Unix(5, 0) 2888 tm := getTestMarket(t, now, nil, nil) 2889 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2890 defer tm.ctrl.Finish() 2891 2892 addAccount(t, tm, "aaa") 2893 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 2894 2895 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order01", types.SideBuy, "aaa", 10, 100) 2896 o1.ExpiresAt = now.Add(5 * time.Second).UnixNano() 2897 o1conf, err := tm.market.SubmitOrder(ctx, o1) 2898 require.NoError(t, err) 2899 require.NotNil(t, o1conf) 2900 2901 cncl, err := tm.market.CancelOrder(ctx, o1.Party, o1.ID, vgcrypto.RandomHash()) 2902 require.NoError(t, err) 2903 require.NotNil(t, cncl) 2904 assert.Equal(t, 0, tm.market.GetPeggedExpiryOrderCount()) 2905 2906 tm.market.OnTick(ctx, now.Add(10*time.Second)) 2907 t.Run("no orders expired", func(t *testing.T) { 2908 // First collect all the orders events 2909 orders := []*types.Order{} 2910 for _, e := range tm.events { 2911 switch evt := e.(type) { 2912 case *events.Order: 2913 if evt.Order().Status == types.OrderStatusExpired { 2914 orders = append(orders, mustOrderFromProto(evt.Order())) 2915 } 2916 } 2917 } 2918 require.Len(t, orders, 0) 2919 }) 2920 assert.Equal(t, 0, tm.market.GetPeggedExpiryOrderCount()) 2921 } 2922 2923 func TestGTTExpiredNotFilled(t *testing.T) { 2924 now := time.Unix(5, 0) 2925 tm := getTestMarket(t, now, nil, nil) 2926 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2927 defer tm.ctrl.Finish() 2928 2929 addAccount(t, tm, "aaa") 2930 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 2931 2932 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order01", types.SideSell, "aaa", 10, 100) 2933 o1.ExpiresAt = now.Add(5 * time.Second).UnixNano() 2934 o1conf, err := tm.market.SubmitOrder(ctx, o1) 2935 require.NoError(t, err) 2936 require.NotNil(t, o1conf) 2937 2938 // then remove expired, set 1 sec after order exp time. 2939 tm.events = nil 2940 tm.market.OnTick(ctx, now.Add(10*time.Second)) 2941 t.Run("1 order expired", func(t *testing.T) { 2942 // First collect all the orders events 2943 orders := []string{} 2944 for _, e := range tm.events { 2945 switch evt := e.(type) { 2946 case *events.ExpiredOrders: 2947 orders = append(orders, evt.OrderIDs()...) 2948 } 2949 } 2950 assert.Len(t, orders, 1) 2951 }) 2952 } 2953 2954 func TestGTTExpiredPartiallyFilled(t *testing.T) { 2955 now := time.Unix(5, 0) 2956 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 2957 Duration: 1, 2958 }) 2959 defer tm.ctrl.Finish() 2960 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 2961 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 2962 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 2963 2964 auxParty, auxParty2 := "auxParty", "auxParty2" 2965 addAccount(t, tm, auxParty) 2966 addAccount(t, tm, auxParty2) 2967 addAccountWithAmount(tm, "lpprov", 10000000) 2968 2969 auxOrders := []*types.Order{ 2970 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnAsk", types.SideSell, auxParty, 1, 1000000), 2971 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "alwaysOnBid", types.SideBuy, auxParty, 1, 1), 2972 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux1", types.SideSell, auxParty, 1, 100), 2973 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "aux2", types.SideBuy, auxParty2, 1, 100), 2974 } 2975 for _, o := range auxOrders { 2976 conf, err := tm.market.SubmitOrder(ctx, o) 2977 require.NoError(t, err) 2978 require.NotNil(t, conf) 2979 } 2980 lp := &types.LiquidityProvisionSubmission{ 2981 MarketID: tm.market.GetID(), 2982 CommitmentAmount: num.NewUint(5000), 2983 Fee: num.DecimalFromFloat(0.01), 2984 } 2985 require.NoError(t, tm.market.SubmitLiquidityProvision(context.Background(), lp, "lpprov", vgcrypto.RandomHash())) 2986 // leave auction 2987 tm.now = tm.now.Add(2 * time.Second) 2988 tm.market.OnTick(ctx, tm.now) 2989 addAccount(t, tm, "aaa") 2990 addAccount(t, tm, "bbb") 2991 2992 // We probably don't need these orders anymore, but they don't do any harm 2993 // place expiring order 2994 o1 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order01", types.SideSell, "aaa", 10, 100) 2995 o1.ExpiresAt = tm.now.Add(5 * time.Second).UnixNano() 2996 o1conf, err := tm.market.SubmitOrder(ctx, o1) 2997 require.NoError(t, err) 2998 require.NotNil(t, o1conf) 2999 3000 // add matching order 3001 o2 := getMarketOrder(tm, tm.now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order02", types.SideBuy, "bbb", 1, 100) 3002 o2.ExpiresAt = now.Add(5 * time.Second).UnixNano() 3003 o2conf, err := tm.market.SubmitOrder(ctx, o2) 3004 require.NoError(t, err) 3005 require.NotNil(t, o2conf) 3006 3007 // then remove expired, set 1 sec after order exp time. 3008 tm.events = nil 3009 tm.market.OnTick(ctx, tm.now.Add(10*time.Second)) 3010 t.Run("1 order expired - the other matched", func(t *testing.T) { 3011 // First collect all the orders events 3012 orders := []string{} 3013 for _, e := range tm.events { 3014 switch evt := e.(type) { 3015 case *events.ExpiredOrders: 3016 orders = append(orders, evt.OrderIDs()...) 3017 } 3018 } 3019 assert.Len(t, orders, 1) 3020 }) 3021 } 3022 3023 func TestOrderBook_RemoveExpiredOrders(t *testing.T) { 3024 now := time.Unix(5, 0) 3025 tm := getTestMarket(t, now, nil, nil) 3026 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 3027 defer tm.ctrl.Finish() 3028 3029 addAccount(t, tm, "aaa") 3030 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 3031 3032 someTimeLater := now.Add(100 * time.Second) 3033 3034 o1 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order01", types.SideSell, "aaa", 1, 1) 3035 o1.ExpiresAt = someTimeLater.UnixNano() 3036 o1conf, err := tm.market.SubmitOrder(ctx, o1) 3037 require.NoError(t, err) 3038 require.NotNil(t, o1conf) 3039 3040 o2 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order02", types.SideSell, "aaa", 99, 3298) 3041 o2.ExpiresAt = someTimeLater.UnixNano() + 1 3042 o2conf, err := tm.market.SubmitOrder(ctx, o2) 3043 require.NoError(t, err) 3044 require.NotNil(t, o2conf) 3045 3046 o3 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order03", types.SideSell, "aaa", 19, 771) 3047 o3.ExpiresAt = someTimeLater.UnixNano() 3048 o3conf, err := tm.market.SubmitOrder(ctx, o3) 3049 require.NoError(t, err) 3050 require.NotNil(t, o3conf) 3051 3052 o4 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order04", types.SideSell, "aaa", 7, 1000) 3053 o4conf, err := tm.market.SubmitOrder(ctx, o4) 3054 require.NoError(t, err) 3055 require.NotNil(t, o4conf) 3056 3057 o5 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order05", types.SideSell, "aaa", 99999, 199) 3058 o5.ExpiresAt = someTimeLater.UnixNano() 3059 o5conf, err := tm.market.SubmitOrder(ctx, o5) 3060 require.NoError(t, err) 3061 require.NotNil(t, o5conf) 3062 3063 o6 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order06", types.SideSell, "aaa", 100, 100) 3064 o6conf, err := tm.market.SubmitOrder(ctx, o6) 3065 require.NoError(t, err) 3066 require.NotNil(t, o6conf) 3067 3068 o7 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order07", types.SideSell, "aaa", 9999, 41) 3069 o7.ExpiresAt = someTimeLater.UnixNano() + 9999 3070 o7conf, err := tm.market.SubmitOrder(ctx, o7) 3071 require.NoError(t, err) 3072 require.NotNil(t, o7conf) 3073 3074 o8 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order08", types.SideSell, "aaa", 1, 1) 3075 o8.ExpiresAt = someTimeLater.UnixNano() - 9999 3076 o8conf, err := tm.market.SubmitOrder(ctx, o8) 3077 require.NoError(t, err) 3078 require.NotNil(t, o8conf) 3079 3080 o9 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, "Order09", types.SideSell, "aaa", 12, 65) 3081 o9conf, err := tm.market.SubmitOrder(ctx, o9) 3082 require.NoError(t, err) 3083 require.NotNil(t, o9conf) 3084 3085 o10 := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTT, "Order10", types.SideSell, "aaa", 1, 1) 3086 o10.ExpiresAt = someTimeLater.UnixNano() - 1 3087 o10conf, err := tm.market.SubmitOrder(ctx, o10) 3088 require.NoError(t, err) 3089 require.NotNil(t, o10conf) 3090 3091 tm.events = nil 3092 tm.market.OnTick(ctx, someTimeLater) 3093 t.Run("5 orders expired", func(t *testing.T) { 3094 // First collect all the orders events 3095 orders := []string{} 3096 for _, e := range tm.events { 3097 switch evt := e.(type) { 3098 case *events.ExpiredOrders: 3099 orders = append(orders, evt.OrderIDs()...) 3100 } 3101 } 3102 require.Len(t, orders, 5) 3103 }) 3104 } 3105 3106 func TestMissingLP(t *testing.T) { 3107 party1 := "party1" 3108 party2 := "party2" 3109 party3 := "party3" 3110 auxParty, auxParty2 := "auxParty", "auxParty2" 3111 now := time.Unix(10, 0) 3112 tm := getTestMarket(t, now, nil, &types.AuctionDuration{ 3113 Duration: 1, 3114 }) 3115 ctx := vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 3116 3117 addAccount(t, tm, party1) 3118 addAccount(t, tm, party2) 3119 addAccount(t, tm, party3) 3120 addAccount(t, tm, auxParty) 3121 addAccount(t, tm, auxParty2) 3122 tm.broker.EXPECT().Send(gomock.Any()).AnyTimes() 3123 3124 // ensure auction durations are 1 second 3125 tm.market.OnMarketAuctionMinimumDurationUpdate(ctx, time.Second) 3126 alwaysOnBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, vgcrypto.RandomHash(), types.SideBuy, auxParty, 1, 800000) 3127 conf, err := tm.market.SubmitOrder(ctx, alwaysOnBid) 3128 require.NotNil(t, conf) 3129 require.NoError(t, err) 3130 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3131 3132 alwaysOnAsk := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, vgcrypto.RandomHash(), types.SideSell, auxParty, 1, 8200000) 3133 conf, err = tm.market.SubmitOrder(ctx, alwaysOnAsk) 3134 require.NotNil(t, conf) 3135 require.NoError(t, err) 3136 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3137 // create orders so we can leave opening auction 3138 auxOrders := []*types.Order{ 3139 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, vgcrypto.RandomHash(), types.SideBuy, auxParty, 1, 3500000), 3140 getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, vgcrypto.RandomHash(), types.SideSell, auxParty2, 1, 3500000), 3141 } 3142 for _, o := range auxOrders { 3143 conf, err := tm.market.SubmitOrder(ctx, o) 3144 require.NotNil(t, conf) 3145 require.NoError(t, err) 3146 } 3147 now = now.Add(time.Second * 2) // opening auction is 1 second, move time ahead by 2 seconds so we leave auction 3148 tm.now = now 3149 tm.market.OnTick(ctx, now) 3150 3151 // Here we are in auction 3152 assert.False(t, tm.mas.InAuction()) 3153 3154 // Send LP order 3155 lps := &types.LiquidityProvisionSubmission{ 3156 MarketID: tm.market.GetID(), 3157 CommitmentAmount: num.NewUint(10000), 3158 Fee: num.DecimalFromFloat(0.01), 3159 } 3160 3161 tm.market.SubmitLiquidityProvision(ctx, lps, party1, vgcrypto.RandomHash()) 3162 3163 assert.EqualValues(t, 2, tm.market.GetOrdersOnBookCount()) 3164 3165 // Send in a limit order to move the BEST_BID and MID price 3166 newBestBid := getMarketOrder(tm, now, types.OrderTypeLimit, types.OrderTimeInForceGTC, vgcrypto.RandomHash(), types.SideBuy, auxParty, 1, 810000) 3167 conf, err = tm.market.SubmitOrder(ctx, newBestBid) 3168 require.NotNil(t, conf) 3169 require.NoError(t, err) 3170 require.Equal(t, types.OrderStatusActive, conf.Order.Status) 3171 3172 // Check we have 3 orders in total 3173 assert.EqualValues(t, 3, tm.market.GetOrdersOnBookCount()) 3174 now = now.Add(time.Second * 2) // opening auction is 1 second, move time ahead by 2 seconds so we leave auction 3175 tm.now = now 3176 tm.market.OnTick(ctx, now) 3177 }