code.vegaprotocol.io/vega@v0.79.0/core/positions/positions_acceptance_criteria_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 positions_test 17 18 import ( 19 "context" 20 "fmt" 21 "testing" 22 "time" 23 24 "code.vegaprotocol.io/vega/core/types" 25 "code.vegaprotocol.io/vega/libs/num" 26 27 "github.com/stretchr/testify/assert" 28 ) 29 30 func TestPositionsEngineAcceptanceCriteria(t *testing.T) { 31 t.Run("Open long position, trades occur increasing long position", testTradeOccurIncreaseShortAndLong) 32 t.Run("Open long position, trades occur decreasing long position", testTradeOccurDecreaseShortAndLong) 33 t.Run("Open short position, trades occur increasing (greater abs(size)) short position", testTradeOccurIncreaseShortAndLong) 34 t.Run("Open short position, trades occur decreasing (smaller abs(size)) short position", testTradeOccurDecreaseShortAndLong) 35 t.Run("Open short position, trades occur taking position to zero (closing it)", testTradeOccurClosingShortAndLong) 36 t.Run("Open long position, trades occur taking position to zero (closing it)", testTradeOccurClosingShortAndLong) 37 t.Run("Open short position, trades occur closing the short position and opening a long position", testTradeOccurShortBecomeLongAndLongBecomeShort) 38 t.Run("Open long position, trades occur closing the long position and opening a short position", testTradeOccurShortBecomeLongAndLongBecomeShort) 39 t.Run("No open position, trades occur opening a long position", testNoOpenPositionsTradeOccurOpenLongAndShortPosition) 40 t.Run("No open position, trades occur opening a short position", testNoOpenPositionsTradeOccurOpenLongAndShortPosition) 41 t.Run("Open position, trades occur that close it (take it to zero), in a separate transaction, trades occur and that open a new position", testOpenPosTradeOccurCloseThanOpenPositioAgain) 42 // NOTE: this will not be tested, as we do not remove a position from the engine when it reach 0 43 // Opening and closing positions for multiple partys, maintains position size for all open (non-zero) positions 44 t.Run("Does not change position size for a wash trade (buyer = seller)", testWashTradeDoNotChangePosition) 45 46 // No active buy orders, a new buy order is added to the order book 47 t.Run("Active buy orders, a new buy order is added to the order book", testNewOrderAddedToTheBook) 48 t.Run("Active sell orders, a new sell order is added to the order book", testNewOrderAddedToTheBook) 49 t.Run("Active buy order, an order initiated by another party causes a partial amount of the existing buy order to trade.", testNewTradePartialAmountOfExistingOrderTraded) 50 t.Run("Active sell order, an order initiated by another party causes a partial amount of the existing sell order to trade.", testNewTradePartialAmountOfExistingOrderTraded) 51 t.Run("Active buy order, an order initiated by another party causes the full amount of the existing buy order to trade.", testTradeCauseTheFullAmountOfOrderToTrade) 52 t.Run("Active sell order, an order initiated by another party causes the full amount of the existing sell order to trade.", testTradeCauseTheFullAmountOfOrderToTrade) 53 t.Run("Active buy orders, an existing order is cancelled", testOrderCancelled) 54 t.Run("Active sell orders, an existing order is cancelled", testOrderCancelled) 55 t.Run("Aggressive order gets partially filled", testNewTradePartialAmountOfIncomingOrderTraded) 56 57 // NOTE: these next tests needs the integration test to be ran 58 // Active buy orders, an existing buy order is amended which increases its size. 59 // Active buy orders, an existing buy order is amended which decreases its size. 60 // Active buy orders, an existing buy order's price is amended such that it trades a partial amount. 61 // Active buy orders, an existing buy order's price is amended such that it trades in full. 62 // Active buy orders, an existing order expires 63 } 64 65 func testTradeOccurIncreaseShortAndLong(t *testing.T) { 66 engine := getTestEngine(t) 67 assert.Empty(t, engine.Positions()) 68 buyer := "buyer_id" 69 seller := "seller_id" 70 cases := []struct { 71 trade types.Trade 72 expectedSizePartyA int64 73 expectedSizePartyB int64 74 }{ 75 { 76 trade: types.Trade{ 77 Type: types.TradeTypeDefault, 78 ID: "trade_id", 79 MarketID: "market_id", 80 Price: num.NewUint(100), 81 Size: 10, 82 Buyer: buyer, 83 Seller: seller, 84 BuyOrder: "buy_order_id", 85 SellOrder: "sell_order_id", 86 Timestamp: time.Now().Unix(), 87 }, 88 expectedSizePartyA: +10, 89 expectedSizePartyB: -10, 90 }, 91 { 92 trade: types.Trade{ 93 Type: types.TradeTypeDefault, 94 ID: "trade_id", 95 MarketID: "market_id", 96 Price: num.NewUint(100), 97 Size: 25, 98 Buyer: buyer, 99 Seller: seller, 100 BuyOrder: "buy_order_id", 101 SellOrder: "sell_order_id", 102 Timestamp: time.Now().Unix(), 103 }, 104 expectedSizePartyA: +35, 105 expectedSizePartyB: -35, 106 }, 107 } 108 109 for _, c := range cases { 110 // call an update on the positions with the trade 111 passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size) 112 aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size) 113 positions := engine.Update(context.Background(), &c.trade, passive, aggressive) 114 pos := engine.Positions() 115 assert.Equal(t, 2, len(pos)) 116 assert.Equal(t, 2, len(positions)) 117 118 // check size of positions 119 for _, p := range pos { 120 if p.Party() == buyer { 121 assert.Equal(t, c.expectedSizePartyA, p.Size()) 122 } else if p.Party() == seller { 123 assert.Equal(t, c.expectedSizePartyB, p.Size()) 124 } 125 } 126 } 127 } 128 129 func testTradeOccurDecreaseShortAndLong(t *testing.T) { 130 engine := getTestEngine(t) 131 assert.Empty(t, engine.Positions()) 132 partyA := "party_a" 133 partyB := "party_b" 134 cases := []struct { 135 trade types.Trade 136 expectedSizePartyA int64 137 expectedSizePartyB int64 138 }{ 139 { 140 trade: types.Trade{ 141 Type: types.TradeTypeDefault, 142 ID: "trade_i1", 143 MarketID: "market_id", 144 Price: num.NewUint(100), 145 Size: 10, 146 Buyer: partyA, 147 Seller: partyB, 148 BuyOrder: "buy_order_id1", 149 SellOrder: "sell_order_id1", 150 Timestamp: time.Now().Unix(), 151 }, 152 expectedSizePartyA: +10, 153 expectedSizePartyB: -10, 154 }, 155 // inverse buyer and seller, so it should reduce both position of 5 156 { 157 trade: types.Trade{ 158 Type: types.TradeTypeDefault, 159 ID: "trade_id2", 160 MarketID: "market_id", 161 Price: num.NewUint(100), 162 Size: 5, 163 Buyer: partyB, 164 Seller: partyA, 165 BuyOrder: "buy_order_id2", 166 SellOrder: "sell_order_id2", 167 Timestamp: time.Now().Unix(), 168 }, 169 expectedSizePartyA: +5, 170 expectedSizePartyB: -5, 171 }, 172 } 173 174 for _, c := range cases { 175 // call an update on the positions with the trade 176 passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size) 177 aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size) 178 positions := engine.Update(context.Background(), &c.trade, passive, aggressive) 179 pos := engine.Positions() 180 assert.Equal(t, 2, len(pos)) 181 assert.Equal(t, 2, len(positions)) 182 183 // check size of positions 184 for _, p := range pos { 185 if p.Party() == partyA { 186 assert.Equal(t, c.expectedSizePartyA, p.Size()) 187 } else if p.Party() == partyB { 188 assert.Equal(t, c.expectedSizePartyB, p.Size()) 189 } 190 } 191 } 192 } 193 194 func testTradeOccurClosingShortAndLong(t *testing.T) { 195 engine := getTestEngine(t) 196 assert.Empty(t, engine.Positions()) 197 partyA := "party_a" 198 partyB := "party_b" 199 cases := []struct { 200 trade types.Trade 201 expectedSizePartyA int64 202 expectedSizePartyB int64 203 }{ 204 { 205 trade: types.Trade{ 206 Type: types.TradeTypeDefault, 207 ID: "trade_i1", 208 MarketID: "market_id", 209 Price: num.NewUint(100), 210 Size: 10, 211 Buyer: partyA, 212 Seller: partyB, 213 BuyOrder: "buy_order_id1", 214 SellOrder: "sell_order_id1", 215 Timestamp: time.Now().Unix(), 216 }, 217 expectedSizePartyA: +10, 218 expectedSizePartyB: -10, 219 }, 220 // inverse buyer and seller, so it should reduce both position of 5 221 { 222 trade: types.Trade{ 223 Type: types.TradeTypeDefault, 224 ID: "trade_id2", 225 MarketID: "market_id", 226 Price: num.NewUint(100), 227 Size: 10, 228 Buyer: partyB, 229 Seller: partyA, 230 BuyOrder: "buy_order_id2", 231 SellOrder: "sell_order_id2", 232 Timestamp: time.Now().Unix(), 233 }, 234 expectedSizePartyA: 0, 235 expectedSizePartyB: 0, 236 }, 237 } 238 239 for _, c := range cases { 240 passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size) 241 aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size) 242 positions := engine.Update(context.Background(), &c.trade, passive, aggressive) 243 pos := engine.Positions() 244 assert.Equal(t, 2, len(pos)) 245 assert.Equal(t, 2, len(positions)) 246 247 // check size of positions 248 for _, p := range pos { 249 if p.Party() == partyA { 250 assert.Equal(t, c.expectedSizePartyA, p.Size()) 251 } else if p.Party() == partyB { 252 assert.Equal(t, c.expectedSizePartyB, p.Size()) 253 } 254 } 255 } 256 } 257 258 func testTradeOccurShortBecomeLongAndLongBecomeShort(t *testing.T) { 259 engine := getTestEngine(t) 260 assert.Empty(t, engine.Positions()) 261 partyA := "party_a" 262 partyB := "party_b" 263 cases := []struct { 264 trade types.Trade 265 expectedSizePartyA int64 266 expectedSizePartyB int64 267 }{ 268 { 269 trade: types.Trade{ 270 Type: types.TradeTypeDefault, 271 ID: "trade_i1", 272 MarketID: "market_id", 273 Price: num.NewUint(100), 274 Size: 10, 275 Buyer: partyA, 276 Seller: partyB, 277 BuyOrder: "buy_order_id1", 278 SellOrder: "sell_order_id1", 279 Timestamp: time.Now().Unix(), 280 }, 281 expectedSizePartyA: +10, 282 expectedSizePartyB: -10, 283 }, 284 // inverse buyer and seller, so it should reduce both position of 5 285 { 286 trade: types.Trade{ 287 Type: types.TradeTypeDefault, 288 ID: "trade_id2", 289 MarketID: "market_id", 290 Price: num.NewUint(100), 291 Size: 15, 292 Buyer: partyB, 293 Seller: partyA, 294 BuyOrder: "buy_order_id2", 295 SellOrder: "sell_order_id2", 296 Timestamp: time.Now().Unix(), 297 }, 298 expectedSizePartyA: -5, 299 expectedSizePartyB: +5, 300 }, 301 } 302 303 for _, c := range cases { 304 passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size) 305 aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size) 306 // call an update on the positions with the trade 307 positions := engine.Update(context.Background(), &c.trade, passive, aggressive) 308 pos := engine.Positions() 309 assert.Equal(t, 2, len(pos)) 310 assert.Equal(t, 2, len(positions)) 311 312 // check size of positions 313 for _, p := range pos { 314 if p.Party() == partyA { 315 assert.Equal(t, c.expectedSizePartyA, p.Size()) 316 } else if p.Party() == partyB { 317 assert.Equal(t, c.expectedSizePartyB, p.Size()) 318 } 319 } 320 } 321 } 322 323 func testNoOpenPositionsTradeOccurOpenLongAndShortPosition(t *testing.T) { 324 engine := getTestEngine(t) 325 partyA := "party_a" 326 partyB := "party_b" 327 c := struct { 328 trade types.Trade 329 expectedSizePartyA int64 330 expectedSizePartyB int64 331 }{ 332 trade: types.Trade{ 333 Type: types.TradeTypeDefault, 334 ID: "trade_i1", 335 MarketID: "market_id", 336 Price: num.NewUint(100), 337 Size: 10, 338 Buyer: partyA, 339 Seller: partyB, 340 BuyOrder: "buy_order_id1", 341 SellOrder: "sell_order_id1", 342 Timestamp: time.Now().Unix(), 343 }, 344 expectedSizePartyA: +10, 345 expectedSizePartyB: -10, 346 } 347 348 // ensure there is no open positions in the engine 349 assert.Empty(t, engine.Positions()) 350 351 // now create a trade an make sure the positions are created an correct 352 passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size) 353 aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size) 354 positions := engine.Update(context.Background(), &c.trade, passive, aggressive) 355 pos := engine.Positions() 356 assert.Equal(t, 2, len(pos)) 357 assert.Equal(t, 2, len(positions)) 358 359 // check size of positions 360 for _, p := range pos { 361 if p.Party() == partyA { 362 assert.Equal(t, c.expectedSizePartyA, p.Size()) 363 } else if p.Party() == partyB { 364 assert.Equal(t, c.expectedSizePartyB, p.Size()) 365 } 366 } 367 } 368 369 func testOpenPosTradeOccurCloseThanOpenPositioAgain(t *testing.T) { 370 engine := getTestEngine(t) 371 assert.Empty(t, engine.Positions()) 372 partyA := "party_a" 373 partyB := "party_b" 374 partyC := "party_c" 375 cases := []struct { 376 trade types.Trade 377 expectedSizePartyA int64 378 expectedSizePartyB int64 379 expectedSizePartyC int64 380 posSize int 381 }{ 382 // first trade between A and B, open a new position 383 { 384 trade: types.Trade{ 385 Type: types.TradeTypeDefault, 386 ID: "trade_i1", 387 MarketID: "market_id", 388 Price: num.NewUint(100), 389 Size: 10, 390 Buyer: partyA, 391 Seller: partyB, 392 BuyOrder: "buy_order_id1", 393 SellOrder: "sell_order_id1", 394 Timestamp: time.Now().Unix(), 395 }, 396 expectedSizePartyA: +10, 397 expectedSizePartyB: -10, 398 expectedSizePartyC: 0, 399 posSize: 2, 400 }, 401 // second trade between A and C, open C close A 402 { 403 trade: types.Trade{ 404 Type: types.TradeTypeDefault, 405 ID: "trade_id2", 406 MarketID: "market_id", 407 Price: num.NewUint(100), 408 Size: 10, 409 Buyer: partyC, 410 Seller: partyA, 411 BuyOrder: "buy_order_id2", 412 SellOrder: "sell_order_id2", 413 Timestamp: time.Now().Unix(), 414 }, 415 expectedSizePartyA: 0, 416 expectedSizePartyB: -10, 417 expectedSizePartyC: 10, 418 posSize: 3, 419 }, 420 // last trade between A and B again, re-open A, decrease B 421 { 422 trade: types.Trade{ 423 Type: types.TradeTypeDefault, 424 ID: "trade_id3", 425 MarketID: "market_id", 426 Price: num.NewUint(100), 427 Size: 3, 428 Buyer: partyB, 429 Seller: partyA, 430 BuyOrder: "buy_order_id3", 431 SellOrder: "sell_order_id3", 432 Timestamp: time.Now().Unix(), 433 }, 434 expectedSizePartyA: -3, 435 expectedSizePartyB: -7, 436 expectedSizePartyC: 10, 437 posSize: 3, 438 }, 439 } 440 441 for _, c := range cases { 442 passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size) 443 aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size) 444 positions := engine.Update(context.Background(), &c.trade, passive, aggressive) 445 pos := engine.Positions() 446 assert.Equal(t, c.posSize, len(pos), fmt.Sprintf("all pos trade: %v", c.trade.ID)) 447 assert.Equal(t, 2, len(positions), fmt.Sprintf("chan trade: %v", c.trade.ID)) 448 449 // check size of positions 450 for _, p := range pos { 451 if p.Party() == partyA { 452 assert.Equal(t, c.expectedSizePartyA, p.Size()) 453 } else if p.Party() == partyB { 454 assert.Equal(t, c.expectedSizePartyB, p.Size()) 455 } else if p.Party() == partyC { 456 assert.Equal(t, c.expectedSizePartyC, p.Size()) 457 } 458 } 459 } 460 } 461 462 func testWashTradeDoNotChangePosition(t *testing.T) { 463 engine := getTestEngine(t) 464 assert.Empty(t, engine.Positions()) 465 partyA := "party_a" 466 partyB := "party_b" 467 cases := []struct { 468 trade types.Trade 469 expectedSizePartyA int64 470 expectedSizePartyB int64 471 }{ 472 { 473 trade: types.Trade{ 474 Type: types.TradeTypeDefault, 475 ID: "trade_i1", 476 MarketID: "market_id", 477 Price: num.NewUint(100), 478 Size: 10, 479 Buyer: partyA, 480 Seller: partyB, 481 BuyOrder: "buy_order_id1", 482 SellOrder: "sell_order_id1", 483 Timestamp: time.Now().Unix(), 484 }, 485 expectedSizePartyA: +10, 486 expectedSizePartyB: -10, 487 }, 488 // party A trade with himsefl, no positions changes 489 { 490 trade: types.Trade{ 491 Type: types.TradeTypeDefault, 492 ID: "trade_id2", 493 MarketID: "market_id", 494 Price: num.NewUint(100), 495 Size: 30, 496 Buyer: partyA, 497 Seller: partyA, 498 BuyOrder: "buy_order_id2", 499 SellOrder: "sell_order_id2", 500 Timestamp: time.Now().Unix(), 501 }, 502 expectedSizePartyA: +10, 503 expectedSizePartyB: -10, 504 }, 505 } 506 507 for _, c := range cases { 508 passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size) 509 aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size) 510 // call an update on the positions with the trade 511 positions := engine.Update(context.Background(), &c.trade, passive, aggressive) 512 pos := engine.Positions() 513 assert.Equal(t, 2, len(pos)) 514 assert.Equal(t, 2, len(positions)) 515 516 // check size of positions 517 for _, p := range pos { 518 if p.Party() == partyA { 519 assert.Equal(t, c.expectedSizePartyA, p.Size()) 520 } else if p.Party() == partyB { 521 assert.Equal(t, c.expectedSizePartyB, p.Size()) 522 } 523 } 524 } 525 } 526 527 func testNewOrderAddedToTheBook(t *testing.T) { 528 engine := getTestEngine(t) 529 partyA := "party_a" 530 partyB := "party_b" 531 cases := []struct { 532 order types.Order 533 expectedBuy int64 534 expectedSell int64 535 expectedSize int64 536 }{ 537 { 538 // add an original buy order for A 539 order: types.Order{ 540 Size: 10, 541 Remaining: 10, 542 Party: partyA, 543 Side: types.SideBuy, 544 Price: num.UintZero(), 545 }, 546 expectedBuy: 10, 547 expectedSell: 0, 548 expectedSize: 0, 549 }, 550 { 551 // add and original sell order for B 552 order: types.Order{ 553 Size: 16, 554 Remaining: 16, 555 Party: partyB, 556 Side: types.SideSell, 557 Price: num.UintZero(), 558 }, 559 expectedBuy: 0, 560 expectedSell: 16, 561 expectedSize: 0, 562 }, 563 { 564 // update buy order for A 565 order: types.Order{ 566 Size: 17, 567 Remaining: 17, 568 Party: partyA, 569 Side: types.SideBuy, 570 Price: num.UintZero(), 571 }, 572 expectedBuy: 27, 573 expectedSell: 0, 574 expectedSize: 0, 575 }, 576 { 577 // update sell order for B 578 order: types.Order{ 579 Size: 5, 580 Remaining: 5, 581 Party: partyB, 582 Side: types.SideSell, 583 Price: num.UintZero(), 584 }, 585 expectedBuy: 0, 586 expectedSell: 21, 587 expectedSize: 0, 588 }, 589 } 590 591 // no potions exists at the moment: 592 assert.Empty(t, engine.Positions()) 593 594 for _, c := range cases { 595 pos := engine.RegisterOrder(context.TODO(), &c.order) 596 assert.Equal(t, c.expectedBuy, pos.Buy()) 597 assert.Equal(t, c.expectedSell, pos.Sell()) 598 assert.Equal(t, c.expectedSize, pos.Size()) 599 } 600 } 601 602 func testNewTradePartialAmountOfExistingOrderTraded(t *testing.T) { 603 engine := getTestEngine(t) 604 partyA := "party_a" 605 partyB := "party_b" 606 matchingPrice := num.NewUint(100) 607 tradeSize := uint64(3) 608 609 passive := &types.Order{ 610 Size: 7 + tradeSize, 611 Remaining: 7 + tradeSize, 612 Party: partyA, 613 Side: types.SideBuy, 614 Price: matchingPrice, 615 } 616 617 aggressive := &types.Order{ 618 Size: tradeSize, 619 Remaining: tradeSize, 620 Party: partyB, 621 Side: types.SideSell, 622 Price: matchingPrice, 623 } 624 625 cases := struct { 626 orders []*types.Order 627 expects map[string]struct { 628 expectedBuy int64 629 expectedSell int64 630 expectedSize int64 631 expectedVwBuy *num.Uint 632 expectedVwSell *num.Uint 633 } 634 }{ 635 orders: []*types.Order{ 636 passive, 637 aggressive, 638 { 639 Size: 16 - tradeSize, 640 Remaining: 16 - tradeSize, 641 Party: partyB, 642 Side: types.SideSell, 643 Price: num.NewUint(1000), 644 }, 645 }, 646 expects: map[string]struct { 647 expectedBuy int64 648 expectedSell int64 649 expectedSize int64 650 expectedVwBuy *num.Uint 651 expectedVwSell *num.Uint 652 }{ 653 partyA: { 654 expectedBuy: 10, 655 expectedSell: 0, 656 expectedSize: 0, 657 expectedVwBuy: passive.Price, 658 expectedVwSell: num.UintZero(), 659 }, 660 partyB: { 661 expectedBuy: 0, 662 expectedSell: 16, 663 expectedSize: 0, 664 expectedVwBuy: num.UintOne(), 665 expectedVwSell: num.NewUint(831), // 831.25 666 }, 667 }, 668 } 669 670 // no positions exists at the moment: 671 assert.Empty(t, engine.Positions()) 672 673 for _, c := range cases.orders { 674 engine.RegisterOrder(context.TODO(), c) 675 } 676 pos := engine.Positions() 677 assert.Len(t, pos, len(cases.expects)) 678 for _, v := range pos { 679 assert.Equal(t, cases.expects[v.Party()].expectedBuy, v.Buy()) 680 assert.Equal(t, cases.expects[v.Party()].expectedSell, v.Sell()) 681 assert.Equal(t, cases.expects[v.Party()].expectedSize, v.Size()) 682 } 683 684 // add a trade for a size of 3, 685 // potential buy should be 7, size 3 686 trade := types.Trade{ 687 Type: types.TradeTypeDefault, 688 ID: "trade_i1", 689 MarketID: "market_id", 690 Price: num.NewUint(100), 691 Size: 3, 692 Buyer: partyA, 693 Seller: partyB, 694 BuyOrder: "buy_order_id1", 695 SellOrder: "sell_order_id1", 696 Timestamp: time.Now().Unix(), 697 } 698 699 // add the trade 700 // call an update on the positions with the trade 701 positions := engine.Update(context.Background(), &trade, passive, aggressive) 702 pos = engine.Positions() 703 assert.Equal(t, 2, len(pos)) 704 assert.Equal(t, 2, len(positions)) 705 706 // check size of positions 707 for _, p := range pos { 708 if p.Party() == partyA { 709 assert.Equal(t, int64(3), p.Size()) 710 assert.Equal(t, int64(7), p.Buy()) 711 assert.Equal(t, cases.orders[0].Price, p.VWBuy()) 712 assert.Equal(t, num.UintZero(), p.VWSell()) 713 } else if p.Party() == partyB { 714 assert.Equal(t, int64(-3), p.Size()) 715 assert.Equal(t, int64(13), p.Sell()) 716 assert.Equal(t, num.UintZero(), p.VWBuy()) 717 assert.Equal(t, cases.orders[len(cases.orders)-1].Price, p.VWSell()) 718 } 719 } 720 } 721 722 func testNewTradePartialAmountOfIncomingOrderTraded(t *testing.T) { 723 engine := getTestEngine(t) 724 partyA := "party_a" 725 partyB := "party_b" 726 matchingPrice := num.NewUint(100) 727 tradeSize := uint64(3) 728 729 passive := &types.Order{ 730 Size: tradeSize, 731 Remaining: tradeSize, 732 Party: partyB, 733 Side: types.SideSell, 734 Price: matchingPrice, 735 } 736 737 aggressive := &types.Order{ 738 Size: 5 + tradeSize, 739 Remaining: 5 + tradeSize, 740 Party: partyA, 741 Side: types.SideBuy, 742 Price: matchingPrice, 743 } 744 745 cases := struct { 746 orders []*types.Order 747 expects map[string]struct { 748 expectedBuy int64 749 expectedSell int64 750 expectedSize int64 751 expectedVwBuy *num.Uint 752 expectedVwSell *num.Uint 753 } 754 }{ 755 orders: []*types.Order{ 756 { 757 Size: 16, 758 Remaining: 16, 759 Party: partyB, 760 Side: types.SideSell, 761 Price: num.NewUint(1000), 762 }, 763 passive, 764 aggressive, 765 }, 766 expects: map[string]struct { 767 expectedBuy int64 768 expectedSell int64 769 expectedSize int64 770 expectedVwBuy *num.Uint 771 expectedVwSell *num.Uint 772 }{ 773 partyA: { 774 expectedBuy: 8, 775 expectedSell: 0, 776 expectedSize: 0, 777 expectedVwBuy: aggressive.Price, 778 expectedVwSell: num.UintZero(), 779 }, 780 partyB: { 781 expectedBuy: 0, 782 expectedSell: 19, 783 expectedSize: 0, 784 expectedVwBuy: num.UintOne(), 785 expectedVwSell: num.NewUint(857), // 857.8947368421 786 }, 787 }, 788 } 789 790 // no positions exists at the moment: 791 assert.Empty(t, engine.Positions()) 792 793 for _, c := range cases.orders { 794 engine.RegisterOrder(context.TODO(), c) 795 } 796 pos := engine.Positions() 797 assert.Len(t, pos, len(cases.expects)) 798 for _, v := range pos { 799 assert.Equal(t, cases.expects[v.Party()].expectedBuy, v.Buy()) 800 assert.Equal(t, cases.expects[v.Party()].expectedSell, v.Sell()) 801 assert.Equal(t, cases.expects[v.Party()].expectedSize, v.Size()) 802 } 803 804 // add a trade for a size of 3, 805 // potential buy should be 5, size 3 806 trade := types.Trade{ 807 Type: types.TradeTypeDefault, 808 ID: "trade_i1", 809 MarketID: "market_id", 810 Price: num.NewUint(100), 811 Size: 3, 812 Buyer: partyA, 813 Seller: partyB, 814 BuyOrder: "buy_order_id1", 815 SellOrder: "sell_order_id1", 816 Timestamp: time.Now().Unix(), 817 } 818 819 // add the trade 820 // call an update on the positions with the trade 821 positions := engine.Update(context.Background(), &trade, passive, aggressive) 822 pos = engine.Positions() 823 assert.Equal(t, 2, len(pos)) 824 assert.Equal(t, 2, len(positions)) 825 826 // check size of positions 827 for _, p := range pos { 828 if p.Party() == partyA { 829 assert.Equal(t, int64(3), p.Size()) 830 assert.Equal(t, int64(5), p.Buy()) 831 assert.Equal(t, matchingPrice, p.VWBuy()) 832 assert.Equal(t, num.UintZero(), p.VWSell()) 833 } else if p.Party() == partyB { 834 assert.Equal(t, int64(-3), p.Size()) 835 assert.Equal(t, int64(16), p.Sell()) 836 assert.Equal(t, num.UintZero(), p.VWBuy()) 837 assert.Equal(t, cases.orders[0].Price, p.VWSell()) 838 } 839 } 840 } 841 842 func testTradeCauseTheFullAmountOfOrderToTrade(t *testing.T) { 843 engine := getTestEngine(t) 844 partyA := "party_a" 845 partyB := "party_b" 846 cases := struct { 847 orders []types.Order 848 expects map[string]struct { 849 expectedBuy int64 850 expectedSell int64 851 expectedSize int64 852 } 853 }{ 854 orders: []types.Order{ 855 { 856 Size: 10, 857 Remaining: 10, 858 Party: partyA, 859 Side: types.SideBuy, 860 Price: num.UintZero(), 861 }, 862 { 863 Size: 10, 864 Remaining: 10, 865 Party: partyB, 866 Side: types.SideSell, 867 Price: num.UintZero(), 868 }, 869 }, 870 expects: map[string]struct { 871 expectedBuy int64 872 expectedSell int64 873 expectedSize int64 874 }{ 875 partyA: { 876 expectedBuy: 10, 877 expectedSell: 0, 878 expectedSize: 0, 879 }, 880 partyB: { 881 expectedBuy: 0, 882 expectedSell: 10, 883 expectedSize: 0, 884 }, 885 }, 886 } 887 888 // no potions exists at the moment: 889 assert.Empty(t, engine.Positions()) 890 891 for i, c := range cases.orders { 892 engine.RegisterOrder(context.TODO(), &c) 893 // ensure we have 1 position with 1 potential buy of size 10 for partyA 894 pos := engine.Positions() 895 assert.Len(t, pos, i+1) 896 for _, v := range pos { 897 assert.Equal(t, cases.expects[v.Party()].expectedBuy, v.Buy()) 898 assert.Equal(t, cases.expects[v.Party()].expectedSell, v.Sell()) 899 assert.Equal(t, cases.expects[v.Party()].expectedSize, v.Size()) 900 } 901 } 902 // add a trade for a size of 3, 903 // potential buy should be 7, size 3 904 trade := types.Trade{ 905 Type: types.TradeTypeDefault, 906 ID: "trade_i1", 907 MarketID: "market_id", 908 Price: num.NewUint(100), 909 Size: 10, 910 Buyer: partyA, 911 Seller: partyB, 912 BuyOrder: "buy_order_id1", 913 SellOrder: "sell_order_id1", 914 Timestamp: time.Now().Unix(), 915 } 916 917 // add the trade 918 // call an update on the positions with the trade 919 positions := engine.Update(context.Background(), &trade, &cases.orders[0], &cases.orders[1]) 920 pos := engine.Positions() 921 assert.Equal(t, 2, len(pos)) 922 assert.Equal(t, 2, len(positions)) 923 924 // check size of positions 925 for _, p := range pos { 926 if p.Party() == partyA { 927 assert.Equal(t, int64(10), p.Size()) 928 assert.Equal(t, int64(0), p.Buy()) 929 } else if p.Party() == partyB { 930 assert.Equal(t, int64(-10), p.Size()) 931 assert.Equal(t, int64(0), p.Sell()) 932 } 933 } 934 } 935 936 func testOrderCancelled(t *testing.T) { 937 engine := getTestEngine(t) 938 partyA := "party_a" 939 partyB := "party_b" 940 cases := struct { 941 orders []types.Order 942 expects map[string]struct { 943 expectedBuy int64 944 expectedSell int64 945 expectedSize int64 946 } 947 }{ 948 orders: []types.Order{ 949 { 950 Size: 10, 951 Remaining: 10, 952 Party: partyA, 953 Side: types.SideBuy, 954 Price: num.UintZero(), 955 }, 956 { 957 Size: 10, 958 Remaining: 10, 959 Party: partyB, 960 Side: types.SideSell, 961 Price: num.UintZero(), 962 }, 963 }, 964 expects: map[string]struct { 965 expectedBuy int64 966 expectedSell int64 967 expectedSize int64 968 }{ 969 partyA: { 970 expectedBuy: 10, 971 expectedSell: 0, 972 expectedSize: 0, 973 }, 974 partyB: { 975 expectedBuy: 0, 976 expectedSell: 10, 977 expectedSize: 0, 978 }, 979 }, 980 } 981 982 // no potions exists at the moment: 983 assert.Empty(t, engine.Positions()) 984 985 // first add the orders 986 for i, c := range cases.orders { 987 engine.RegisterOrder(context.TODO(), &c) 988 // ensure we have 1 position with 1 potential buy of size 10 for partyA 989 pos := engine.Positions() 990 assert.Len(t, pos, i+1) 991 for _, v := range pos { 992 assert.Equal(t, cases.expects[v.Party()].expectedBuy, v.Buy()) 993 assert.Equal(t, cases.expects[v.Party()].expectedSell, v.Sell()) 994 assert.Equal(t, cases.expects[v.Party()].expectedSize, v.Size()) 995 } 996 } 997 998 // then remove them 999 cases = struct { 1000 orders []types.Order 1001 expects map[string]struct { 1002 expectedBuy int64 1003 expectedSell int64 1004 expectedSize int64 1005 } 1006 }{ 1007 orders: []types.Order{ 1008 { 1009 Size: 10, 1010 Remaining: 10, 1011 Party: partyA, 1012 Side: types.SideBuy, 1013 Price: num.UintZero(), 1014 }, 1015 { 1016 Size: 10, 1017 Remaining: 10, 1018 Party: partyB, 1019 Side: types.SideSell, 1020 Price: num.UintZero(), 1021 }, 1022 }, 1023 expects: map[string]struct { 1024 expectedBuy int64 1025 expectedSell int64 1026 expectedSize int64 1027 }{ 1028 partyA: { 1029 expectedBuy: 0, 1030 expectedSell: 0, 1031 expectedSize: 0, 1032 }, 1033 partyB: { 1034 expectedBuy: 0, 1035 expectedSell: 0, 1036 expectedSize: 0, 1037 }, 1038 }, 1039 } 1040 1041 // first add the orders 1042 for _, c := range cases.orders { 1043 _ = engine.UnregisterOrder(context.TODO(), &c) 1044 } 1045 1046 // test everything is back to 0 once orders are unregistered 1047 pos := engine.Positions() 1048 for _, v := range pos { 1049 assert.Equal(t, cases.expects[v.Party()].expectedBuy, v.Buy()) 1050 assert.Equal(t, cases.expects[v.Party()].expectedSell, v.Sell()) 1051 assert.Equal(t, cases.expects[v.Party()].expectedSize, v.Size()) 1052 } 1053 }