decred.org/dcrdex@v1.0.5/server/market/integ/booker_matcher_test.go (about) 1 // Package integ_test is a black-box integration test package. 2 // This file performs book-matcher integration tests. 3 package integ_test 4 5 import ( 6 "math/rand" 7 "reflect" 8 "testing" 9 "time" 10 11 "decred.org/dcrdex/dex" 12 "decred.org/dcrdex/dex/order" 13 "decred.org/dcrdex/server/account" 14 "decred.org/dcrdex/server/book" 15 "decred.org/dcrdex/server/matcher" 16 ) 17 18 // An arbitrary account ID for test orders. 19 var acct0 = account.AccountID{ 20 0x22, 0x4c, 0xba, 0xaa, 0xfa, 0x80, 0xbf, 0x3b, 21 0xd1, 0xff, 0x73, 0x15, 0x90, 0xbc, 0xbd, 0xda, 22 0x5a, 0x76, 0xf9, 0x1e, 0x60, 0xa1, 0x56, 0x99, 23 0x46, 0x34, 0xe9, 0x1c, 0xec, 0x25, 0xd5, 0x40, // 32 bytes 24 } 25 26 var rnd = rand.New(rand.NewSource(1)) 27 28 const ( 29 AssetDCR uint32 = iota 30 AssetBTC 31 32 LotSize = uint64(10 * 1e8) 33 ) 34 35 func startLogger() { 36 logger := dex.StdOutLogger("MATCHTEST - book", dex.LevelTrace) 37 book.UseLogger(logger) 38 39 logger = dex.StdOutLogger("MATCHTEST - matcher", dex.LevelTrace) 40 matcher.UseLogger(logger) 41 } 42 43 func randomPreimage() (pe order.Preimage) { 44 rnd.Read(pe[:]) 45 return 46 } 47 48 func newLimitOrder(sell bool, rate, quantityLots uint64, force order.TimeInForce, timeOffset int64) *order.LimitOrder { 49 return newLimit(sell, rate, quantityLots, force, timeOffset).Order.(*order.LimitOrder) 50 } 51 52 func newLimit(sell bool, rate, quantityLots uint64, force order.TimeInForce, timeOffset int64) *matcher.OrderRevealed { 53 addr := "DcqXswjTPnUcd4FRCkX4vRJxmVtfgGVa5ui" 54 if sell { 55 addr = "149RQGLaHf2gGiL4NXZdH7aA8nYEuLLrgm" 56 } 57 pi := randomPreimage() 58 return &matcher.OrderRevealed{ 59 Order: &order.LimitOrder{ 60 P: order.Prefix{ 61 AccountID: acct0, 62 BaseAsset: AssetDCR, 63 QuoteAsset: AssetBTC, 64 OrderType: order.LimitOrderType, 65 ClientTime: time.Unix(1566497653+timeOffset, 0), 66 ServerTime: time.Unix(1566497656+timeOffset, 0), 67 Commit: pi.Commit(), 68 }, 69 T: order.Trade{ 70 Coins: []order.CoinID{}, 71 Sell: sell, 72 Quantity: quantityLots * LotSize, 73 Address: addr, 74 }, 75 Rate: rate, 76 Force: force, 77 }, 78 Preimage: pi, 79 } 80 } 81 82 func newMarketSellOrder(quantityLots uint64, timeOffset int64) *order.MarketOrder { 83 return newMarketSell(quantityLots, timeOffset).Order.(*order.MarketOrder) 84 } 85 86 func newMarketSell(quantityLots uint64, timeOffset int64) *matcher.OrderRevealed { 87 pi := randomPreimage() 88 return &matcher.OrderRevealed{ 89 Order: &order.MarketOrder{ 90 P: order.Prefix{ 91 AccountID: acct0, 92 BaseAsset: AssetDCR, 93 QuoteAsset: AssetBTC, 94 OrderType: order.MarketOrderType, 95 ClientTime: time.Unix(1566497653+timeOffset, 0), 96 ServerTime: time.Unix(1566497656+timeOffset, 0), 97 Commit: pi.Commit(), 98 }, 99 T: order.Trade{ 100 Coins: []order.CoinID{}, 101 Sell: true, 102 Quantity: quantityLots * LotSize, 103 Address: "149RQGLaHf2gGiL4NXZdH7aA8nYEuLLrgm", 104 }, 105 }, 106 Preimage: pi, 107 } 108 } 109 110 func newMarketBuyOrder(quantityQuoteAsset uint64, timeOffset int64) *order.MarketOrder { 111 return newMarketBuy(quantityQuoteAsset, timeOffset).Order.(*order.MarketOrder) 112 } 113 114 func newMarketBuy(quantityQuoteAsset uint64, timeOffset int64) *matcher.OrderRevealed { 115 pi := randomPreimage() 116 return &matcher.OrderRevealed{ 117 Order: &order.MarketOrder{ 118 P: order.Prefix{ 119 AccountID: acct0, 120 BaseAsset: AssetDCR, 121 QuoteAsset: AssetBTC, 122 OrderType: order.MarketOrderType, 123 ClientTime: time.Unix(1566497653+timeOffset, 0), 124 ServerTime: time.Unix(1566497656+timeOffset, 0), 125 Commit: pi.Commit(), 126 }, 127 T: order.Trade{ 128 Coins: []order.CoinID{}, 129 Sell: false, 130 Quantity: quantityQuoteAsset, 131 Address: "DcqXswjTPnUcd4FRCkX4vRJxmVtfgGVa5ui", 132 }, 133 }, 134 Preimage: pi, 135 } 136 } 137 138 func newCancel(targetOrderID order.OrderID, serverTime time.Time) *matcher.OrderRevealed { 139 pi := randomPreimage() 140 return &matcher.OrderRevealed{ 141 Order: &order.CancelOrder{ 142 P: order.Prefix{ 143 ServerTime: serverTime, 144 Commit: pi.Commit(), 145 }, 146 TargetOrderID: targetOrderID, 147 }, 148 Preimage: pi, 149 } 150 } 151 152 func newCancelOrder(targetOrderID order.OrderID, serverTime time.Time) *order.CancelOrder { 153 return newCancel(targetOrderID, serverTime).Order.(*order.CancelOrder) 154 } 155 156 var ( 157 // Create a coherent order book of standing orders and sorted rates. 158 bookBuyOrders = []*order.LimitOrder{ 159 newLimitOrder(false, 2500000, 2, order.StandingTiF, 0), 160 newLimitOrder(false, 2700000, 2, order.StandingTiF, 0), 161 newLimitOrder(false, 3200000, 2, order.StandingTiF, 0), 162 newLimitOrder(false, 3300000, 1, order.StandingTiF, 2), // newer 163 newLimitOrder(false, 3300000, 2, order.StandingTiF, 0), // older 164 newLimitOrder(false, 3600000, 4, order.StandingTiF, 0), 165 newLimitOrder(false, 3900000, 2, order.StandingTiF, 0), 166 newLimitOrder(false, 4000000, 10, order.StandingTiF, 0), 167 newLimitOrder(false, 4300000, 4, order.StandingTiF, 1), // newer 168 newLimitOrder(false, 4300000, 2, order.StandingTiF, 0), // older 169 newLimitOrder(false, 4500000, 1, order.StandingTiF, 0), 170 } 171 bookSellOrders = []*order.LimitOrder{ 172 newLimitOrder(true, 6200000, 2, order.StandingTiF, 1), // newer 173 newLimitOrder(true, 6200000, 2, order.StandingTiF, 0), // older 174 newLimitOrder(true, 6100000, 2, order.StandingTiF, 0), 175 newLimitOrder(true, 6000000, 2, order.StandingTiF, 0), 176 newLimitOrder(true, 5500000, 1, order.StandingTiF, 0), 177 newLimitOrder(true, 5400000, 4, order.StandingTiF, 0), 178 newLimitOrder(true, 5000000, 2, order.StandingTiF, 0), 179 newLimitOrder(true, 4700000, 4, order.StandingTiF, 1), // newer 180 newLimitOrder(true, 4700000, 10, order.StandingTiF, 0), // older 181 newLimitOrder(true, 4600000, 2, order.StandingTiF, 0), 182 newLimitOrder(true, 4550000, 1, order.StandingTiF, 0), 183 } 184 ) 185 186 const ( 187 bookBuyLots = 32 188 bookSellLots = 32 189 initialMidGap = (4550000 + 4500000) / 2 190 ) 191 192 func newBook(t *testing.T) *book.Book { 193 resetMakers() 194 195 b := book.New(LotSize, 0) 196 197 for _, o := range bookBuyOrders { 198 if ok := b.Insert(o); !ok { 199 t.Fatalf("Failed to insert buy order %v", o) 200 } 201 } 202 for _, o := range bookSellOrders { 203 if ok := b.Insert(o); !ok { 204 t.Fatalf("Failed to insert sell order %v", o) 205 } 206 } 207 return b 208 } 209 210 func resetMakers() { 211 for _, o := range bookBuyOrders { 212 o.FillAmt = 0 213 } 214 for _, o := range bookSellOrders { 215 o.FillAmt = 0 216 } 217 } 218 219 func newMatchSet(taker order.Order, makers []*order.LimitOrder, lastPartialAmount ...uint64) *order.MatchSet { 220 amounts := make([]uint64, len(makers)) 221 rates := make([]uint64, len(makers)) 222 var total uint64 223 for i := range makers { 224 total += makers[i].Quantity 225 amounts[i] = makers[i].Quantity 226 rates[i] = makers[i].Rate 227 } 228 if len(lastPartialAmount) > 0 { 229 amounts[len(makers)-1] = lastPartialAmount[0] 230 total -= makers[len(makers)-1].Quantity - lastPartialAmount[0] 231 } 232 return &order.MatchSet{ 233 Taker: taker, 234 Makers: makers, 235 Amounts: amounts, 236 Rates: rates, 237 Total: total, 238 } 239 } 240 241 func TestMatchWithBook_limitsOnly(t *testing.T) { 242 // Setup the match package's logger. 243 startLogger() 244 245 // New matching engine. 246 me := matcher.New() 247 248 rnd.Seed(0) 249 250 badLotsizeOrder := newLimit(false, 05000000, 1, order.ImmediateTiF, 0) 251 badLotsizeOrder.Order.(*order.LimitOrder).Quantity /= 2 252 253 // takers is heterogenous w.r.t. type 254 takers := []*matcher.OrderRevealed{ 255 newLimit(false, 4550000, 1, order.ImmediateTiF, 0), // buy, 1 lot, immediate, equal rate 256 newLimit(false, 4550000, 2, order.StandingTiF, 0), // buy, 2 lot, standing, equal rate, partial taker insert to book 257 newLimit(false, 4550000, 2, order.ImmediateTiF, 0), // buy, 2 lot, immediate, equal rate, partial taker unfilled 258 newLimit(false, 4100000, 1, order.ImmediateTiF, 0), // buy, 1 lot, immediate, unfilled fail 259 newLimit(true, 4540000, 1, order.ImmediateTiF, 0), // sell, 1 lot, immediate 260 newLimit(true, 4300000, 4, order.ImmediateTiF, 0), // sell, 4 lot, immediate, partial maker 261 } 262 263 // tweak taker[4] commitment to get desired order. 264 takers[4].Preimage[0] += 0 // brute forced, could have required multiple bytes changed 265 takers[4].Order.(*order.LimitOrder).Commit = takers[4].Preimage.Commit() 266 267 resetTakers := func() { 268 for _, o := range takers { 269 switch ot := o.Order.(type) { 270 case *order.MarketOrder: 271 ot.FillAmt = 0 272 case *order.LimitOrder: 273 ot.FillAmt = 0 274 } 275 } 276 } 277 278 nSell := len(bookSellOrders) 279 nBuy := len(bookBuyOrders) 280 281 type args struct { 282 book *book.Book 283 queue []*matcher.OrderRevealed 284 } 285 tests := []struct { 286 name string 287 args args 288 doesMatch bool 289 wantMatches []*order.MatchSet 290 wantNumPassed int 291 wantNumFailed int 292 wantDoneOK int 293 wantNumPartial int 294 wantNumBooked int 295 wantNumUnbooked int 296 wantNumNomatched int 297 }{ 298 { 299 name: "limit buy immediate rate match", 300 args: args{ 301 book: newBook(t), 302 queue: []*matcher.OrderRevealed{takers[0]}, 303 }, 304 doesMatch: true, 305 wantMatches: []*order.MatchSet{ 306 newMatchSet(takers[0].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}), 307 }, 308 wantNumPassed: 1, 309 wantNumFailed: 0, 310 wantDoneOK: 1, 311 wantNumPartial: 0, 312 wantNumBooked: 0, 313 wantNumUnbooked: 1, 314 wantNumNomatched: 0, 315 }, 316 { 317 name: "limit buy standing partial taker inserted to book", 318 args: args{ 319 book: newBook(t), 320 queue: []*matcher.OrderRevealed{takers[1]}, 321 }, 322 doesMatch: true, 323 wantMatches: []*order.MatchSet{ 324 newMatchSet(takers[1].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}), 325 }, 326 wantNumPassed: 1, 327 wantNumFailed: 0, 328 wantDoneOK: 0, 329 wantNumPartial: 1, 330 wantNumBooked: 1, 331 wantNumUnbooked: 1, 332 wantNumNomatched: 0, 333 }, 334 { 335 name: "limit buy immediate partial taker unfilled", 336 args: args{ 337 book: newBook(t), 338 queue: []*matcher.OrderRevealed{takers[2]}, 339 }, 340 doesMatch: true, 341 wantMatches: []*order.MatchSet{ 342 newMatchSet(takers[2].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}), 343 }, 344 wantNumPassed: 1, 345 wantNumFailed: 0, 346 wantDoneOK: 1, 347 wantNumPartial: 1, 348 wantNumBooked: 0, 349 wantNumUnbooked: 1, 350 wantNumNomatched: 0, 351 }, 352 { 353 name: "limit buy immediate unfilled fail", 354 args: args{ 355 book: newBook(t), 356 queue: []*matcher.OrderRevealed{takers[3]}, 357 }, 358 doesMatch: false, 359 wantMatches: nil, 360 wantNumPassed: 0, 361 wantNumFailed: 1, 362 wantDoneOK: 0, 363 wantNumPartial: 0, 364 wantNumBooked: 0, 365 wantNumUnbooked: 0, 366 wantNumNomatched: 1, 367 }, 368 { 369 name: "bad lot size order", 370 args: args{ 371 book: newBook(t), 372 queue: []*matcher.OrderRevealed{badLotsizeOrder}, 373 }, 374 doesMatch: false, 375 wantMatches: nil, 376 wantNumPassed: 0, 377 wantNumFailed: 1, 378 wantDoneOK: 0, 379 wantNumPartial: 0, 380 wantNumBooked: 0, 381 wantNumUnbooked: 0, 382 wantNumNomatched: 0, 383 }, 384 { 385 name: "limit buy standing partial taker inserted to book, then filled by down-queue sell", 386 args: args{ 387 book: newBook(t), 388 queue: []*matcher.OrderRevealed{takers[1], takers[4]}, 389 }, 390 doesMatch: true, 391 wantMatches: []*order.MatchSet{ 392 newMatchSet(takers[1].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}), 393 { // the maker is reduced by matching first item in the queue 394 Taker: takers[4].Order, 395 Makers: []*order.LimitOrder{takers[1].Order.(*order.LimitOrder)}, 396 Amounts: []uint64{1 * LotSize}, // 2 - 1 397 Rates: []uint64{4550000}, 398 Total: 1 * LotSize, 399 }, 400 }, 401 wantNumPassed: 2, 402 wantNumFailed: 0, 403 wantDoneOK: 1, 404 wantNumPartial: 1, 405 wantNumBooked: 1, 406 wantNumUnbooked: 2, 407 wantNumNomatched: 0, 408 }, 409 { 410 name: "limit sell immediate rate overlap", 411 args: args{ 412 book: newBook(t), 413 queue: []*matcher.OrderRevealed{takers[5]}, 414 }, 415 doesMatch: true, 416 wantMatches: []*order.MatchSet{ 417 newMatchSet( 418 takers[5].Order, 419 []*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2], bookBuyOrders[nBuy-3]}, 420 1*LotSize), 421 }, 422 wantNumPassed: 1, 423 wantNumFailed: 0, 424 wantDoneOK: 1, 425 wantNumPartial: 0, 426 wantNumBooked: 0, 427 wantNumUnbooked: 2, 428 wantNumNomatched: 0, 429 }, 430 } 431 for _, tt := range tests { 432 t.Run(tt.name, func(t *testing.T) { 433 // Reset Filled amounts of all pre-defined orders before each test. 434 resetTakers() 435 resetMakers() 436 437 // Ignore the seed since it is tested in the matcher unit tests. 438 _, matches, passed, failed, doneOK, partial, booked, nomatched, unbooked, _, _ := me.Match(tt.args.book, tt.args.queue) 439 matchMade := len(matches) > 0 && matches[0] != nil 440 if tt.doesMatch != matchMade { 441 t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade) 442 } 443 if len(matches) != len(tt.wantMatches) { 444 t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches)) 445 } 446 for i := range matches { 447 if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) { 448 t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i]) 449 } 450 } 451 if len(passed) != tt.wantNumPassed { 452 t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed) 453 } 454 if len(failed) != tt.wantNumFailed { 455 t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed) 456 } 457 if len(doneOK) != tt.wantDoneOK { 458 t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK) 459 } 460 if len(partial) != tt.wantNumPartial { 461 t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial) 462 } 463 if len(booked) != tt.wantNumBooked { 464 t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumBooked) 465 } 466 if len(unbooked) != tt.wantNumUnbooked { 467 t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked) 468 } 469 470 if len(nomatched) != tt.wantNumNomatched { 471 t.Errorf("number nomatched %d, expected %d", len(nomatched), tt.wantNumNomatched) 472 } 473 }) 474 } 475 } 476 477 func orderInSlice(o *matcher.OrderRevealed, s []*matcher.OrderRevealed) int { 478 for i := range s { 479 if s[i].Order.ID() == o.Order.ID() { 480 return i 481 } 482 } 483 return -1 484 } 485 486 func orderInLimitSlice(o order.Order, s []*order.LimitOrder) int { 487 for i := range s { 488 if s[i].ID() == o.ID() { 489 return i 490 } 491 } 492 return -1 493 } 494 495 func TestMatchWithBook_limitsOnly_multipleQueued(t *testing.T) { 496 // Setup the match package's logger. 497 startLogger() 498 499 // New matching engine. 500 me := matcher.New() 501 502 rnd.Seed(0) 503 504 // epochQueue is heterogenous w.r.t. type 505 epochQueue := []*matcher.OrderRevealed{ 506 // buys 507 newLimit(false, 4550000, 1, order.ImmediateTiF, 0), // 0: buy, 1 lot, immediate 508 newLimit(false, 4550000, 2, order.StandingTiF, 0), // 1: buy, 2 lot, standing 509 newLimit(false, 4550000, 2, order.ImmediateTiF, 0), // 2: buy, 2 lot, immediate 510 newLimit(false, 4100000, 1, order.ImmediateTiF, 0), // 3: buy, 1 lot, immediate 511 // sells 512 newLimit(true, 4540000, 1, order.ImmediateTiF, 0), // 4: sell, 1 lot, immediate 513 newLimit(true, 4300000, 4, order.ImmediateTiF, 0), // 5: sell, 4 lot, immediate 514 newLimit(true, 4720000, 40, order.StandingTiF, 0), // 6: sell, 40 lot, standing, unfilled insert 515 } 516 epochQueue[0].Preimage = order.Preimage{ 517 0xb1, 0xcb, 0x0a, 0xc8, 0xbf, 0x2b, 0xa9, 0xa7, 518 0x05, 0xf9, 0x6d, 0x6b, 0x68, 0x21, 0x28, 0x87, 519 0x13, 0x26, 0x23, 0x80, 0xfb, 0xe6, 0xb9, 0x0f, 520 0x74, 0x39, 0xc9, 0xf1, 0xcd, 0x6e, 0x02, 0xa8} 521 epochQueue[0].Order.(*order.LimitOrder).Commit = epochQueue[0].Preimage.Commit() 522 epochQueueInit := make([]*matcher.OrderRevealed, len(epochQueue)) 523 copy(epochQueueInit, epochQueue) 524 525 /* //brute force a commitment to make changing the test less horrible 526 t.Log(epochQueue) 527 matcher.ShuffleQueue(epochQueue) 528 529 // Apply the shuffling to determine matching order that will be used. 530 wantOrder := []int{1, 6, 0, 3, 4, 5, 2} 531 var wantQueue []*matcher.OrderRevealed 532 for _, i := range wantOrder { 533 wantQueue = append(wantQueue, epochQueueInit[i]) 534 } 535 536 queuesEqual := func(q1, q2 []*matcher.OrderRevealed) bool { 537 if len(q1) != len(q2) { 538 return false 539 } 540 for i := range q1 { 541 if q1[i].Order.(*order.LimitOrder) != q2[i].Order.(*order.LimitOrder) { 542 return false 543 } 544 } 545 return true 546 } 547 548 orderX := epochQueueInit[0] 549 loX := orderX.Order.(*order.LimitOrder) 550 var pi order.Preimage 551 var i int 552 for !queuesEqual(wantQueue, epochQueue) { 553 pi = randomPreimage() 554 orderX.Preimage = pi 555 loX.Commit = pi.Commit() 556 loX.SetTime(loX.ServerTime) // force recomputation of order ID 557 matcher.ShuffleQueue(epochQueue) 558 i++ 559 } 560 t.Logf("preimage: %#v, commit: %#v", pi, loX.Commit) 561 t.Log(i, epochQueue) 562 */ 563 564 // -> Shuffles to [1, 6, 0, 3, 4, 5, 2] 565 // 1 -> partial match, inserted into book (passed, partial inserted), 1 lot @ 4550000, buyvol + 1, sellvol - 1 = 0 566 // 6 -> unmatched, inserted into book (inserted, nomatched), sellvol + 40 567 // 0 -> is unfilled (failed, nomatched) 568 // 3 -> is unfilled (failed, nomatched) 569 // 4 -> fills against order 1, which was just inserted (passed), 1 lot @ 4550000, buyvol - 1 570 // 5 -> is filled (passed) 1 @ 4500000, 3 @ 4300000, buyvol - 4 571 // 2 -> is unfilled (failed, nomatched) 572 // matches: [1, 4, 5], passed: [1, 4], failed: [0, 3, 2] 573 // partial: [1], inserted: [1, 6], nomatched: [6, 0, 3, 2] 574 575 // best remaining sell: 4600000, buy: 4300000 576 577 // order book from bookBuyOrders and bookSellOrders 578 b := newBook(t) 579 580 resetQueue := func() { 581 for _, o := range epochQueue { 582 switch ot := o.Order.(type) { 583 case *order.MarketOrder: 584 ot.FillAmt = 0 585 case *order.LimitOrder: 586 ot.FillAmt = 0 587 } 588 } 589 } 590 591 // nSell := len(bookSellOrders) 592 // nBuy := len(bookBuyOrders) 593 594 // Reset Filled amounts of all pre-defined orders before each test. 595 resetQueue() 596 resetMakers() 597 598 // Ignore the seed since it is tested in the matcher unit tests. 599 _, matches, passed, failed, doneOK, partial, booked, nomatched, unbooked, _, stats := me.Match(b, epochQueue) 600 //t.Log(matches, passed, failed, doneOK, partial, booked, unbooked) 601 602 lastMatch := matches[len(matches)-1] 603 604 compareMatchStats(t, &matcher.MatchCycleStats{ 605 BookBuys: (bookBuyLots - 4) * LotSize, 606 BookSells: (bookSellLots + 39) * LotSize, 607 MatchVolume: 6 * LotSize, 608 HighRate: 4550000, 609 LowRate: 4300000, 610 StartRate: matches[0].Makers[0].Rate, 611 EndRate: lastMatch.Makers[len(lastMatch.Makers)-1].Rate, 612 }, stats) 613 614 // PASSED orders 615 616 // epoch order 0 should be order 0 in passed slice 617 expectedLoc := 0 618 if loc := orderInSlice(epochQueueInit[1], passed); loc == -1 { 619 t.Errorf("Order not in passed slice.") 620 } else if loc != expectedLoc { 621 t.Errorf("Order not at expected location in passed slice: %d", loc) 622 } 623 624 // epoch order 5 should be order 2 in passed slice 625 expectedLoc = 2 626 if loc := orderInSlice(epochQueueInit[4], passed); loc == -1 { 627 t.Errorf("Order not in passed slice.") 628 } else if loc != expectedLoc { 629 t.Errorf("Order not at expected location in passed slice: %d", loc) 630 } 631 632 //t.Log(doneOK) 633 634 // FAILED orders 635 636 // epoch order 3 should be order 0 in failed slice 637 expectedLoc = 0 638 if loc := orderInSlice(epochQueueInit[0], failed); loc == -1 { 639 t.Errorf("Order not in failed slice.") 640 } else if loc != expectedLoc { 641 t.Errorf("Order not at expected location in failed slice: %d", loc) 642 } 643 644 // epoch order 4 should be order 1 in failed slice 645 expectedLoc = 1 646 if loc := orderInSlice(epochQueueInit[3], failed); loc == -1 { 647 t.Errorf("Order not in failed slice.") 648 } else if loc != expectedLoc { 649 t.Errorf("Order not at expected location in failed slice: %d", loc) 650 } 651 652 // epoch order 2 should be order 2 in failed slice 653 expectedLoc = 2 654 if loc := orderInSlice(epochQueueInit[2], failed); loc == -1 { 655 t.Errorf("Order not in failed slice.") 656 } else if loc != expectedLoc { 657 t.Errorf("Order not at expected location in failed slice: %d", loc) 658 } 659 660 // Done OK 661 expectedLoc = 1 662 if loc := orderInSlice(epochQueueInit[5], doneOK); loc == -1 { 663 t.Errorf("Order not in doneOK slice.") 664 } else if loc != expectedLoc { 665 t.Errorf("Order not at expected location in doneOK slice: %d", loc) 666 } 667 668 // PARTIAL fills 669 670 // epoch order 1 should be order 0 in partial slice 671 expectedLoc = 0 672 if loc := orderInSlice(epochQueueInit[1], partial); loc == -1 { 673 t.Errorf("Order not in partial slice.") 674 } else if loc != expectedLoc { 675 t.Errorf("Order not at expected location in partial slice: %d", loc) 676 } 677 678 // BOOKED orders 679 680 // epoch order 1 should be order 0 in booked slice 681 expectedLoc = 0 682 if loc := orderInSlice(epochQueueInit[1], booked); loc == -1 { 683 t.Errorf("Order not in booked slice.") 684 } else if loc != expectedLoc { 685 t.Errorf("Order not at expected location in booked slice: %d", loc) 686 } 687 688 // epoch order 6 should be order 1 in booked slice 689 expectedLoc = 1 690 if loc := orderInSlice(epochQueueInit[6], booked); loc == -1 { 691 t.Errorf("Order not in booked slice.") 692 } else if loc != expectedLoc { 693 t.Errorf("Order not at expected location in booked slice: %d", loc) 694 } 695 696 // epoch order 1 should be order 1 in unbooked slice 697 expectedLoc = 1 698 if loc := orderInLimitSlice(epochQueueInit[1].Order, unbooked); loc == -1 { 699 t.Errorf("Order not in unbooked slice.") 700 } else if loc != expectedLoc { 701 t.Errorf("Order not at expected location in unbooked slice: %d", loc) 702 } 703 704 // NOMATCHED orders 705 706 // We don't know the exact order, since there is an intermediate map used 707 // for tracking. 708 if len(nomatched) != 4 { 709 t.Errorf("Wrong number of nomatched orders. Wanted 4, got %d", len(nomatched)) 710 } else { 711 for _, i := range []int{6, 0, 3, 2} { 712 if orderInSlice(epochQueueInit[i], nomatched) == -1 { 713 t.Errorf("Epoch queue order %d not in nomatched slice", i) 714 } 715 } 716 } 717 718 // epoch order 5 (sell, 4 lots, immediate @ 4300000) is match 1, matched 719 // with 3 orders, the first of which of which is epoch order 1 (buy, 2 lots, 720 // standing @ 4550000) that was booked as a standing order. 721 if matches[1].Taker.ID() != epochQueueInit[4].Order.ID() { 722 t.Errorf("Taker order ID expected %v, got %v", 723 epochQueueInit[5].Order.UID(), matches[1].Taker.UID()) 724 } 725 if matches[1].Makers[0].ID() != epochQueueInit[1].Order.ID() { 726 t.Errorf("First match was expected to be %v, got %v", 727 epochQueueInit[1].Order.ID(), matches[1].Makers[0].ID()) 728 } 729 } 730 731 func TestMatch_cancelOnly(t *testing.T) { 732 // Setup the match package's logger. 733 startLogger() 734 735 // New matching engine. 736 me := matcher.New() 737 738 rnd.Seed(0) 739 740 fakeOrder := newLimitOrder(false, 4550000, 1, order.ImmediateTiF, 0) 741 fakeOrder.ServerTime = time.Unix(1566497654, 0) 742 743 // takers is heterogenous w.r.t. type 744 takers := []*matcher.OrderRevealed{ 745 newCancel(bookBuyOrders[3].ID(), fakeOrder.ServerTime.Add(time.Second)), 746 newCancel(fakeOrder.ID(), fakeOrder.ServerTime.Add(time.Second)), 747 } 748 749 //nSell := len(bookSellOrders) 750 //nBuy := len(bookBuyOrders) 751 752 type args struct { 753 book *book.Book 754 queue []*matcher.OrderRevealed 755 } 756 tests := []struct { 757 name string 758 args args 759 doesMatch bool 760 wantMatches []*order.MatchSet 761 wantNumPassed int 762 wantNumFailed int 763 wantDoneOK int 764 wantNumPartial int 765 wantNumBooked int 766 wantNumUnbooked int 767 }{ 768 { 769 name: "cancel standing ok", 770 args: args{ 771 book: newBook(t), 772 queue: []*matcher.OrderRevealed{takers[0]}, 773 }, 774 doesMatch: true, 775 wantMatches: []*order.MatchSet{ 776 { 777 Taker: takers[0].Order, 778 Makers: []*order.LimitOrder{bookBuyOrders[3]}, 779 Amounts: []uint64{bookBuyOrders[3].Remaining()}, 780 Rates: []uint64{bookBuyOrders[3].Rate}, 781 }, 782 }, 783 wantNumPassed: 1, 784 wantNumFailed: 0, 785 wantDoneOK: 1, 786 wantNumPartial: 0, 787 wantNumBooked: 0, 788 wantNumUnbooked: 1, 789 }, 790 { 791 name: "cancel non-existent standing", 792 args: args{ 793 book: newBook(t), 794 queue: []*matcher.OrderRevealed{takers[1]}, 795 }, 796 doesMatch: false, 797 wantMatches: nil, 798 wantNumPassed: 0, 799 wantNumFailed: 1, 800 wantDoneOK: 0, 801 wantNumPartial: 0, 802 wantNumBooked: 0, 803 wantNumUnbooked: 0, 804 }, 805 } 806 for _, tt := range tests { 807 t.Run(tt.name, func(t *testing.T) { 808 // Reset Filled amounts of all pre-defined orders before each test. 809 resetMakers() 810 811 // var cancels int 812 // for _, oi := range tt.args.queue { 813 // if oi.Type() == order.CancelOrderType { 814 // cancels++ 815 // } 816 // } 817 818 numBuys0 := tt.args.book.BuyCount() 819 820 // Ignore the seed since it is tested in the matcher unit tests. 821 _, matches, passed, failed, doneOK, partial, booked, _, unbooked, _, _ := me.Match(tt.args.book, tt.args.queue) 822 matchMade := len(matches) > 0 && matches[0] != nil 823 if tt.doesMatch != matchMade { 824 t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade) 825 } 826 if len(matches) != len(tt.wantMatches) { 827 t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches)) 828 } 829 for i := range matches { 830 if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) { 831 t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i]) 832 } 833 } 834 if len(passed) != tt.wantNumPassed { 835 t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed) 836 } 837 if len(failed) != tt.wantNumFailed { 838 t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed) 839 } 840 if len(doneOK) != tt.wantDoneOK { 841 t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK) 842 } 843 if len(partial) != tt.wantNumPartial { 844 t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial) 845 } 846 if len(booked) != tt.wantNumBooked { 847 t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumBooked) 848 } 849 if len(unbooked) != tt.wantNumUnbooked { 850 t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked) 851 } 852 853 numBuys1 := tt.args.book.BuyCount() 854 if numBuys0-len(passed) != numBuys1 { 855 t.Errorf("Buy side order book size %d, expected %d", numBuys1, numBuys0-len(passed)) 856 } 857 }) 858 } 859 } 860 861 func TestMatch_marketSellsOnly(t *testing.T) { 862 // Setup the match package's logger. 863 startLogger() 864 865 // New matching engine. 866 me := matcher.New() 867 868 rnd.Seed(0) 869 870 badLotsizeOrder := newMarketSell(1, 0) 871 badLotsizeOrder.Order.(*order.MarketOrder).Quantity /= 2 872 873 // takers is heterogenous w.r.t. type 874 takers := []*matcher.OrderRevealed{ 875 newMarketSell(1, 0), // sell, 1 lot 876 newMarketSell(3, 0), // sell, 3 lot 877 newMarketSell(5, 0), // sell, 5 lot, partial maker fill 878 newMarketSell(99, 0), // sell, 99 lot, partial taker fill 879 } 880 881 resetTakers := func() { 882 for _, o := range takers { 883 switch ot := o.Order.(type) { 884 case *order.MarketOrder: 885 ot.FillAmt = 0 886 case *order.LimitOrder: 887 ot.FillAmt = 0 888 } 889 } 890 } 891 892 //nSell := len(bookSellOrders) 893 nBuy := len(bookBuyOrders) 894 895 type args struct { 896 book *book.Book 897 queue []*matcher.OrderRevealed 898 } 899 tests := []struct { 900 name string 901 args args 902 doesMatch bool 903 wantMatches []*order.MatchSet 904 wantNumPassed int 905 wantNumFailed int 906 wantDoneOK int 907 wantNumPartial int 908 wantNumBooked int 909 wantNumUnbooked int 910 wantNumNomatched int 911 matchStats *matcher.MatchCycleStats 912 }{ 913 { 914 name: "market sell, 1 maker match", 915 args: args{ 916 book: newBook(t), 917 queue: []*matcher.OrderRevealed{takers[0]}, 918 }, 919 doesMatch: true, 920 wantMatches: []*order.MatchSet{ 921 // 1 lot @ 4500000. Leaves best buy of 4300000 behind. 922 newMatchSet(takers[0].Order, []*order.LimitOrder{bookBuyOrders[nBuy-1]}), 923 }, 924 wantNumPassed: 1, 925 wantNumFailed: 0, 926 wantDoneOK: 1, 927 wantNumPartial: 0, 928 wantNumBooked: 0, 929 wantNumUnbooked: 1, 930 wantNumNomatched: 0, 931 matchStats: &matcher.MatchCycleStats{ 932 BookBuys: (bookBuyLots - 1) * LotSize, 933 BookSells: bookSellLots * LotSize, 934 MatchVolume: LotSize, 935 HighRate: bookBuyOrders[nBuy-1].Rate, 936 LowRate: bookBuyOrders[nBuy-1].Rate, 937 StartRate: bookBuyOrders[nBuy-1].Rate, 938 EndRate: bookBuyOrders[nBuy-1].Rate, 939 }, 940 }, 941 { 942 name: "market sell, 2 maker match", 943 args: args{ 944 book: newBook(t), 945 queue: []*matcher.OrderRevealed{takers[1]}, // 3 lots 946 }, 947 doesMatch: true, 948 wantMatches: []*order.MatchSet{ 949 newMatchSet(takers[1].Order, []*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2]}), 950 }, 951 wantNumPassed: 1, 952 wantNumFailed: 0, 953 wantDoneOK: 1, 954 wantNumPartial: 0, 955 wantNumBooked: 0, 956 wantNumUnbooked: 2, 957 wantNumNomatched: 0, 958 matchStats: &matcher.MatchCycleStats{ 959 BookBuys: (bookBuyLots - 3) * LotSize, 960 BookSells: bookSellLots * LotSize, 961 MatchVolume: 3 * LotSize, 962 HighRate: bookBuyOrders[nBuy-1].Rate, 963 LowRate: bookBuyOrders[nBuy-2].Rate, 964 StartRate: bookBuyOrders[nBuy-1].Rate, 965 EndRate: bookBuyOrders[nBuy-2].Rate, 966 }, 967 // newLimitOrder(false, 4300000, 2, order.StandingTiF, 0), // older 968 // newLimitOrder(false, 4500000, 1, order.StandingTiF, 0), 969 }, 970 { 971 name: "market sell, 2 maker match, partial maker fill", 972 args: args{ 973 book: newBook(t), 974 queue: []*matcher.OrderRevealed{takers[2]}, 975 }, 976 doesMatch: true, 977 wantMatches: []*order.MatchSet{ 978 // 1 lot @ 4500000, 4 @ 4300000 979 newMatchSet(takers[2].Order, []*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2], bookBuyOrders[nBuy-3]}, 2*LotSize), 980 }, 981 wantNumPassed: 1, 982 wantNumFailed: 0, 983 wantDoneOK: 1, 984 wantNumPartial: 0, 985 wantNumBooked: 0, 986 wantNumUnbooked: 2, 987 wantNumNomatched: 0, 988 matchStats: &matcher.MatchCycleStats{ 989 990 BookBuys: (bookBuyLots - 5) * LotSize, 991 BookSells: bookSellLots * LotSize, 992 MatchVolume: 5 * LotSize, 993 994 HighRate: bookBuyOrders[nBuy-1].Rate, 995 LowRate: bookBuyOrders[nBuy-3].Rate, 996 StartRate: bookBuyOrders[nBuy-1].Rate, 997 EndRate: bookBuyOrders[nBuy-3].Rate, 998 }, 999 }, 1000 { 1001 name: "market sell bad lot size", 1002 args: args{ 1003 book: newBook(t), 1004 queue: []*matcher.OrderRevealed{badLotsizeOrder}, 1005 }, 1006 doesMatch: false, 1007 wantMatches: nil, 1008 wantNumPassed: 0, 1009 wantNumFailed: 1, 1010 wantDoneOK: 0, 1011 wantNumPartial: 0, 1012 wantNumBooked: 0, 1013 wantNumUnbooked: 0, 1014 wantNumNomatched: 0, 1015 }, 1016 { 1017 name: "market sell against empty book", 1018 args: args{ 1019 book: book.New(LotSize, 0), 1020 queue: []*matcher.OrderRevealed{takers[0]}, 1021 }, 1022 doesMatch: false, 1023 wantMatches: nil, 1024 wantNumPassed: 0, 1025 wantNumFailed: 1, 1026 wantDoneOK: 0, 1027 wantNumPartial: 0, 1028 wantNumBooked: 0, 1029 wantNumUnbooked: 0, 1030 wantNumNomatched: 1, 1031 }, 1032 } 1033 for _, tt := range tests { 1034 t.Run(tt.name, func(t *testing.T) { 1035 // Reset Filled amounts of all pre-defined orders before each test. 1036 resetTakers() 1037 resetMakers() 1038 1039 //fmt.Printf("%v\n", takers) 1040 1041 // Ignore the seed since it is tested in the matcher unit tests. 1042 _, matches, passed, failed, doneOK, partial, booked, nomatched, unbooked, _, stats := me.Match(tt.args.book, tt.args.queue) 1043 matchMade := len(matches) > 0 && matches[0] != nil 1044 if tt.doesMatch != matchMade { 1045 t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade) 1046 } 1047 if len(matches) != len(tt.wantMatches) { 1048 t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches)) 1049 } 1050 for i := range matches { 1051 if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) { 1052 t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i]) 1053 } 1054 } 1055 if len(passed) != tt.wantNumPassed { 1056 t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed) 1057 } 1058 if len(failed) != tt.wantNumFailed { 1059 t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed) 1060 } 1061 if len(doneOK) != tt.wantDoneOK { 1062 t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK) 1063 } 1064 if len(partial) != tt.wantNumPartial { 1065 t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial) 1066 } 1067 if len(booked) != tt.wantNumBooked { 1068 t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumBooked) 1069 } 1070 if len(unbooked) != tt.wantNumUnbooked { 1071 t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked) 1072 } 1073 1074 if len(nomatched) != tt.wantNumNomatched { 1075 t.Errorf("number nomatched %d, expected %d", len(nomatched), tt.wantNumNomatched) 1076 } 1077 if tt.matchStats != nil { 1078 compareMatchStats(t, tt.matchStats, stats) 1079 } 1080 }) 1081 } 1082 } 1083 1084 // marketBuyQuoteAmt gives the exact amount in the quote asset require to 1085 // purchase lots worth of the base asset given the current sell order book. 1086 func marketBuyQuoteAmt(lots uint64) uint64 { 1087 var amt uint64 1088 var i int 1089 nSell := len(bookSellOrders) 1090 for lots > 0 && i < nSell { 1091 sellOrder := bookSellOrders[nSell-1-i] 1092 orderLots := sellOrder.Quantity / LotSize 1093 if orderLots > lots { 1094 orderLots = lots 1095 } 1096 lots -= orderLots 1097 1098 amt += matcher.BaseToQuote(sellOrder.Rate, orderLots*LotSize) 1099 i++ 1100 } 1101 return amt 1102 } 1103 1104 // quoteAmt computes the required amount of the quote asset required to purchase 1105 // the specified number of lots given the current order book and required amount 1106 // buffering in the single lot case. 1107 func quoteAmt(lots uint64) uint64 { 1108 amt := marketBuyQuoteAmt(lots) 1109 if lots == 1 { 1110 amt *= 3 1111 amt /= 2 1112 } 1113 return amt 1114 } 1115 1116 func TestMatch_marketBuysOnly(t *testing.T) { 1117 // Setup the match package's logger. 1118 startLogger() 1119 1120 // New matching engine. 1121 me := matcher.New() 1122 1123 rnd.Seed(0) 1124 1125 nSell := len(bookSellOrders) 1126 //nBuy := len(bookBuyOrders) 1127 1128 // takers is heterogenous w.r.t. type 1129 takers := []*matcher.OrderRevealed{ 1130 newMarketBuy(quoteAmt(1), 0), // buy, 1 lot 1131 newMarketBuy(quoteAmt(2), 0), // buy, 2 lot 1132 newMarketBuy(quoteAmt(3), 0), // buy, 3 lot 1133 newMarketBuy(quoteAmt(99), 0), // buy, up to 99 lots, computed exactly for the book 1134 } 1135 1136 resetTakers := func() { 1137 for _, o := range takers { 1138 switch ot := o.Order.(type) { 1139 case *order.MarketOrder: 1140 ot.FillAmt = 0 1141 case *order.LimitOrder: 1142 ot.FillAmt = 0 1143 } 1144 } 1145 } 1146 1147 bookSellOrdersReverse := make([]*order.LimitOrder, len(bookSellOrders)) 1148 for i := range bookSellOrders { 1149 bookSellOrdersReverse[len(bookSellOrders)-1-i] = bookSellOrders[i] 1150 } 1151 1152 type args struct { 1153 book *book.Book 1154 queue []*matcher.OrderRevealed 1155 } 1156 tests := []struct { 1157 name string 1158 args args 1159 doesMatch bool 1160 wantMatches []*order.MatchSet 1161 remaining []uint64 1162 wantNumPassed int 1163 wantNumFailed int 1164 wantDoneOK int 1165 wantNumPartial int 1166 wantNumBooked int 1167 wantNumUnbooked int 1168 }{ 1169 { 1170 name: "market buy, 1 maker match", 1171 args: args{ 1172 book: newBook(t), 1173 queue: []*matcher.OrderRevealed{takers[0]}, 1174 }, 1175 doesMatch: true, 1176 wantMatches: []*order.MatchSet{ 1177 newMatchSet(takers[0].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}), 1178 }, 1179 remaining: []uint64{quoteAmt(1) - marketBuyQuoteAmt(1)}, 1180 wantNumPassed: 1, 1181 wantNumFailed: 0, 1182 wantDoneOK: 1, 1183 wantNumPartial: 0, 1184 wantNumBooked: 0, 1185 wantNumUnbooked: 1, 1186 }, 1187 { 1188 name: "market buy, 2 maker match", 1189 args: args{ 1190 book: newBook(t), 1191 queue: []*matcher.OrderRevealed{takers[1]}, 1192 }, 1193 doesMatch: true, 1194 wantMatches: []*order.MatchSet{ 1195 newMatchSet(takers[1].Order, 1196 []*order.LimitOrder{bookSellOrders[nSell-1], bookSellOrders[nSell-2]}, 1197 1*LotSize), 1198 }, 1199 remaining: []uint64{0}, 1200 wantNumPassed: 1, 1201 wantNumFailed: 0, 1202 wantDoneOK: 1, 1203 wantNumPartial: 0, 1204 wantNumBooked: 0, 1205 wantNumUnbooked: 1, 1206 }, 1207 { 1208 name: "market buy, 3 maker match", 1209 args: args{ 1210 book: newBook(t), 1211 queue: []*matcher.OrderRevealed{takers[2]}, 1212 }, 1213 doesMatch: true, 1214 wantMatches: []*order.MatchSet{ 1215 newMatchSet(takers[2].Order, 1216 []*order.LimitOrder{bookSellOrders[nSell-1], bookSellOrders[nSell-2]}), 1217 }, 1218 remaining: []uint64{0}, 1219 wantNumPassed: 1, 1220 wantNumFailed: 0, 1221 wantDoneOK: 1, 1222 wantNumPartial: 0, 1223 wantNumBooked: 0, 1224 wantNumUnbooked: 2, 1225 }, 1226 { 1227 name: "market buy, 99 maker match", 1228 args: args{ 1229 book: newBook(t), 1230 queue: []*matcher.OrderRevealed{takers[3]}, 1231 }, 1232 doesMatch: true, 1233 wantMatches: []*order.MatchSet{ 1234 newMatchSet(takers[3].Order, bookSellOrdersReverse), 1235 }, 1236 remaining: []uint64{0}, 1237 wantNumPassed: 1, 1238 wantNumFailed: 0, 1239 wantDoneOK: 1, 1240 wantNumPartial: 0, 1241 wantNumBooked: 0, 1242 wantNumUnbooked: 11, 1243 }, 1244 } 1245 for _, tt := range tests { 1246 t.Run(tt.name, func(t *testing.T) { 1247 // Reset Filled amounts of all pre-defined orders before each test. 1248 resetTakers() 1249 resetMakers() 1250 1251 // Ignore the seed since it is tested in the matcher unit tests. 1252 _, matches, passed, failed, doneOK, partial, booked, _, unbooked, _, _ := me.Match(tt.args.book, tt.args.queue) 1253 matchMade := len(matches) > 0 && matches[0] != nil 1254 if tt.doesMatch != matchMade { 1255 t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade) 1256 } 1257 if len(matches) != len(tt.wantMatches) { 1258 t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches)) 1259 } 1260 for i := range matches { 1261 if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) { 1262 t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i]) 1263 } 1264 if matches[i].Taker.Trade().Remaining() != tt.remaining[i] { 1265 t.Errorf("Incorrect taker order amount remaining. Expected %d, got %d", 1266 tt.remaining[i], matches[i].Taker.Trade().Remaining()) 1267 } 1268 } 1269 if len(passed) != tt.wantNumPassed { 1270 t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed) 1271 } 1272 if len(failed) != tt.wantNumFailed { 1273 t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed) 1274 } 1275 if len(doneOK) != tt.wantDoneOK { 1276 t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK) 1277 } 1278 if len(partial) != tt.wantNumPartial { 1279 t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial) 1280 } 1281 if len(booked) != tt.wantNumBooked { 1282 t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumBooked) 1283 } 1284 if len(unbooked) != tt.wantNumUnbooked { 1285 t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked) 1286 } 1287 }) 1288 } 1289 } 1290 1291 func TestMatchWithBook_everything_multipleQueued(t *testing.T) { 1292 // Setup the match package's logger. 1293 startLogger() 1294 1295 // New matching engine. 1296 me := matcher.New() 1297 1298 rnd.Seed(12) 1299 1300 nSell := len(bookSellOrders) 1301 nBuy := len(bookBuyOrders) 1302 cancelTime := time.Unix(1566497655, 0) 1303 1304 // epochQueue is heterogenous w.r.t. type 1305 epochQueue := []*matcher.OrderRevealed{ 1306 // buys 1307 newLimit(false, 4550000, 1, order.ImmediateTiF, 0), // 0: buy, 1 lot, immediate 1308 newLimit(false, 4550000, 2, order.StandingTiF, 0), // 1: buy, 2 lot, standing 1309 newLimit(false, 4550000, 2, order.ImmediateTiF, 0), // 2: buy, 2 lot, immediate, unmatched 1310 newLimit(false, 4100000, 1, order.ImmediateTiF, 0), // 3: buy, 1 lot, immediate, unmatched 1311 // sells 1312 newLimit(true, 4540000, 1, order.ImmediateTiF, 0), // 4: sell, 1 lot, immediate 1313 newLimit(true, 4800000, 4, order.StandingTiF, 0), // 5: sell, 4 lot, immediate, unmatched 1314 newLimit(true, 4300000, 4, order.ImmediateTiF, 0), // 6: sell, 4 lot, immediate 1315 newLimit(true, 4800000, 40, order.StandingTiF, 1), // 7: sell, 40 lot, standing, unfilled insert 1316 // market 1317 newMarketSell(2, 0), // 8 1318 newMarketSell(4, 0), // 9 1319 newMarketBuy(quoteAmt(1), 0), // 10 1320 newMarketBuy(quoteAmt(2), 0), // 11 1321 // cancel 1322 newCancel(bookSellOrders[6].ID(), cancelTime), // 12 1323 newCancel(bookBuyOrders[8].ID(), cancelTime), // 13 1324 newCancel(bookBuyOrders[nBuy-1].ID(), cancelTime), // 14 1325 newCancel(bookSellOrders[nSell-1].ID(), cancelTime), // 15 1326 } 1327 // cancel some the epoch queue orders too 1328 epochQueue = append(epochQueue, newCancel(epochQueue[7].Order.ID(), cancelTime)) // 16 cancels 7 (miss) 1329 epochQueue = append(epochQueue, newCancel(epochQueue[5].Order.ID(), cancelTime)) // 17 cancels 5 (miss) 1330 1331 epochQueueInit := make([]*matcher.OrderRevealed, len(epochQueue)) 1332 copy(epochQueueInit, epochQueue) 1333 1334 // var shuf []int 1335 // matcher.ShuffleQueue(epochQueue) 1336 // for i := range epochQueue { 1337 // for j := range epochQueueInit { 1338 // if epochQueue[i].Order.ID() == epochQueueInit[j].Order.ID() { 1339 // shuf = append(shuf, j) 1340 // t.Logf("%d: %p", j, epochQueueInit[j].Order) 1341 // continue 1342 // } 1343 // } 1344 // } 1345 // t.Logf("%#v", shuf) 1346 1347 // Apply the shuffling to determine matching order that will be used. 1348 // matcher.ShuffleQueue(epochQueue) 1349 // for i := range epochQueue { 1350 // t.Logf("%d: %p, %p", i, epochQueueInit[i].Order, epochQueue[i].Order) 1351 // } 1352 // Shuffles to [6, 13, 0, 14, 11, 10, 7, 1, 12, 5, 17, 4, 16, 2, 9, 8, 15, 3] 1353 1354 expectedNumMatches := 11 1355 expectedPassed := []int{5, 0, 8, 10, 13, 6, 17, 9, 7, 16, 12, 1, 4, 11} 1356 expectedFailed := []int{15, 3, 2, 14} 1357 expectedDoneOK := []int{0, 8, 10, 13, 6, 17, 9, 16, 12, 4, 11} 1358 expectedPartial := []int{} 1359 expectedBooked := []int{5, 7, 1} // all StandingTiF 1360 expectedNumUnbooked := 8 1361 expectedNumNomatched := 6 1362 1363 // order book from bookBuyOrders and bookSellOrders 1364 b := newBook(t) 1365 1366 resetQueue := func() { 1367 for _, o := range epochQueue { 1368 switch ot := o.Order.(type) { 1369 case *order.MarketOrder: 1370 ot.FillAmt = 0 1371 case *order.LimitOrder: 1372 ot.FillAmt = 0 1373 } 1374 } 1375 } 1376 1377 // Reset Filled amounts of all pre-defined orders before each test. 1378 resetQueue() 1379 resetMakers() 1380 1381 // Ignore the seed since it is tested in the matcher unit tests. 1382 _, matches, passed, failed, doneOK, partial, booked, nomatched, unbooked, _, _ := me.Match(b, epochQueue) 1383 //t.Log("Matches:", matches) 1384 // s := "Passed: " 1385 // for _, o := range passed { 1386 // s += fmt.Sprintf("%p ", o.Order) 1387 // } 1388 // t.Log(s) 1389 // s = "Failed: " 1390 // for _, o := range failed { 1391 // s += fmt.Sprintf("%p ", o.Order) 1392 // } 1393 // t.Log(s) 1394 // s = "DoneOK: " 1395 // for _, o := range doneOK { 1396 // s += fmt.Sprintf("%p ", o.Order) 1397 // } 1398 // t.Log(s) 1399 // s = "Partial: " 1400 // for _, o := range partial { 1401 // s += fmt.Sprintf("%p ", o.Order) 1402 // } 1403 // t.Log(s) 1404 // s = "Booked: " 1405 // for _, o := range booked { 1406 // s += fmt.Sprintf("%p ", o.Order) 1407 // } 1408 // t.Log(s) 1409 // s := "Nomatched: " 1410 // for _, o := range nomatched { 1411 // s += fmt.Sprintf("%p ", o.Order) 1412 // } 1413 // t.Log(s) 1414 // s = "Unbooked: " 1415 // for _, o := range unbooked { 1416 // s += fmt.Sprintf("%p ", o) 1417 // } 1418 // t.Log(s) 1419 1420 // for i := range matches { 1421 // t.Logf("Match %d: %p, [%p, ...]", i, matches[i].Taker, matches[i].Makers[0]) 1422 // } 1423 1424 // PASSED orders 1425 1426 for i, qi := range expectedPassed { 1427 if oi := orderInSlice(epochQueueInit[qi], passed); oi != i { 1428 t.Errorf("Order not at expected location in passed slice. Got %d, expected %d", 1429 oi, i) 1430 } 1431 } 1432 1433 for i, qi := range expectedFailed { 1434 if oi := orderInSlice(epochQueueInit[qi], failed); oi != i { 1435 t.Errorf("Order not at expected location in failed slice. Got %d, expected %d", 1436 oi, i) 1437 } 1438 } 1439 1440 for i, qi := range expectedDoneOK { 1441 if oi := orderInSlice(epochQueueInit[qi], doneOK); oi != i { 1442 t.Errorf("Order not at expected location in doneOK slice. Got %d, expected %d", 1443 oi, i) 1444 } 1445 } 1446 1447 for i, qi := range expectedPartial { 1448 if oi := orderInSlice(epochQueueInit[qi], partial); oi != i { 1449 t.Errorf("Order not at expected location in partial slice. Got %d, expected %d", 1450 oi, i) 1451 } 1452 } 1453 1454 for i, qi := range expectedBooked { 1455 if oi := orderInSlice(epochQueueInit[qi], booked); oi != i { 1456 t.Errorf("Order not at expected location in booked slice. Got %d, expected %d", 1457 oi, i) 1458 } 1459 } 1460 1461 if len(unbooked) != expectedNumUnbooked { 1462 t.Errorf("Incorrect number of unbooked orders. Got %d, expected %d", len(unbooked), expectedNumUnbooked) 1463 } 1464 1465 if len(nomatched) != expectedNumNomatched { 1466 t.Errorf("Incorrect number of nomatched orders. Got %d, expected %d", len(nomatched), expectedNumNomatched) 1467 } 1468 1469 if len(matches) != expectedNumMatches { 1470 t.Errorf("Incorrect number of matches. Got %d, expected %d", len(matches), expectedNumMatches) 1471 } 1472 1473 // Spot check a couple of matches. 1474 1475 // match 4 (epoch order 6) cancels a book order 1476 if matches[4].Taker.ID() != epochQueueInit[6].Order.ID() { 1477 t.Errorf("Taker order ID expected %v, got %v", 1478 epochQueueInit[6].Order.UID(), matches[4].Taker.UID()) 1479 } 1480 if matches[9].Makers[0].ID() != epochQueueInit[1].Order.ID() { 1481 t.Errorf("9th match maker was expected to be %v, got %v", 1482 epochQueueInit[1].Order.UID(), matches[9].Makers[0].ID()) 1483 } 1484 } 1485 1486 func compareMatchStats(t *testing.T, sWant, sHave *matcher.MatchCycleStats) { 1487 t.Helper() 1488 if sWant.BookBuys != sHave.BookBuys { 1489 t.Errorf("wrong BookBuys. wanted %d, got %d", sWant.BookBuys, sHave.BookBuys) 1490 } 1491 // if sWant.BookBuys5 != sHave.BookBuys5 { 1492 // t.Errorf("wrong BookBuys5. wanted %d, got %d", sWant.BookBuys5, sHave.BookBuys5) 1493 // } 1494 // if sWant.BookBuys25 != sHave.BookBuys25 { 1495 // t.Errorf("wrong BookBuys25. wanted %d, got %d", sWant.BookBuys25, sHave.BookBuys25) 1496 // } 1497 if sWant.BookSells != sHave.BookSells { 1498 t.Errorf("wrong BookSells. wanted %d, got %d", sWant.BookSells, sHave.BookSells) 1499 } 1500 // if sWant.BookSells5 != sHave.BookSells5 { 1501 // t.Errorf("wrong BookSells5. wanted %d, got %d", sWant.BookSells5, sHave.BookSells5) 1502 // } 1503 // if sWant.BookSells25 != sHave.BookSells25 { 1504 // t.Errorf("wrong BookSells25. wanted %d, got %d", sWant.BookSells25, sHave.BookSells25) 1505 // } 1506 if sWant.HighRate != sHave.HighRate { 1507 t.Errorf("wrong HighRate. wanted %d, got %d", sWant.HighRate, sHave.HighRate) 1508 } 1509 if sWant.LowRate != sHave.LowRate { 1510 t.Errorf("wrong LowRate. wanted %d, got %d", sWant.LowRate, sHave.LowRate) 1511 } 1512 if sWant.StartRate != sHave.StartRate { 1513 t.Errorf("wrong StartRate. wanted %d, got %d", sWant.StartRate, sHave.StartRate) 1514 } 1515 if sWant.EndRate != sHave.EndRate { 1516 t.Errorf("wrong EndRate. wanted %d, got %d", sWant.EndRate, sHave.EndRate) 1517 } 1518 }