decred.org/dcrdex@v1.0.5/client/orderbook/bookside_test.go (about) 1 package orderbook 2 3 import ( 4 "bytes" 5 "testing" 6 7 "decred.org/dcrdex/dex/msgjson" 8 "decred.org/dcrdex/dex/order" 9 ) 10 11 // makeBookSideDepth creates a new book side depth from the provided 12 // group and sort order. 13 func makeBookSide(groups map[uint64][]*Order, rateIndex *rateIndex, orderPref orderPreference) *bookSide { 14 return &bookSide{ 15 bins: groups, 16 rateIndex: rateIndex, 17 orderPref: orderPref, 18 } 19 } 20 21 func makeOrder(id order.OrderID, side uint8, quantity uint64, rate uint64, time uint64) *Order { 22 return &Order{ 23 OrderID: id, 24 Side: side, 25 Quantity: quantity, 26 Rate: rate, 27 Time: time, 28 } 29 } 30 31 func TestBookSideAdd(t *testing.T) { 32 tests := []struct { 33 label string 34 side *bookSide 35 entry *Order 36 expected *bookSide 37 }{ 38 { 39 label: "Add order to buy book side", 40 side: makeBookSide( 41 map[uint64][]*Order{ 42 1: { 43 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 44 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 45 }, 46 2: { 47 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10), 48 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 3, 2, 10), 49 }, 50 }, 51 makeRateIndex([]uint64{1, 2}), 52 ascending, 53 ), 54 entry: makeOrder([32]byte{'e'}, msgjson.BuyOrderNum, 1, 2, 10), 55 expected: makeBookSide( 56 map[uint64][]*Order{ 57 1: { 58 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 59 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 60 }, 61 2: { 62 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10), 63 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 3, 2, 10), 64 makeOrder([32]byte{'e'}, msgjson.BuyOrderNum, 1, 2, 10), 65 }, 66 }, 67 makeRateIndex([]uint64{1, 2}), 68 ascending, 69 ), 70 }, 71 { 72 label: "Add order to new bin of a buy book side", 73 side: makeBookSide( 74 map[uint64][]*Order{ 75 1: { 76 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 77 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 78 }, 79 }, 80 makeRateIndex([]uint64{1}), 81 ascending, 82 ), 83 entry: makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10), 84 expected: makeBookSide( 85 map[uint64][]*Order{ 86 1: { 87 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 88 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 89 }, 90 2: { 91 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10), 92 }, 93 }, 94 makeRateIndex([]uint64{1, 2}), 95 ascending, 96 ), 97 }, 98 { 99 label: "Add order to existing bin of a buy book side", 100 side: makeBookSide( 101 map[uint64][]*Order{ 102 1: { 103 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 104 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 105 }, 106 }, 107 makeRateIndex([]uint64{1}), 108 ascending, 109 ), 110 entry: makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10), 111 expected: makeBookSide( 112 map[uint64][]*Order{ 113 1: { 114 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 115 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 116 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10), 117 }, 118 }, 119 makeRateIndex([]uint64{1}), 120 ascending, 121 ), 122 }, 123 { 124 label: "Add order to existing buy book side bin sorted by order id", 125 side: makeBookSide( 126 map[uint64][]*Order{ 127 1: { 128 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 129 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10), 130 }, 131 }, 132 makeRateIndex([]uint64{1}), 133 ascending, 134 ), 135 entry: makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 136 expected: makeBookSide( 137 map[uint64][]*Order{ 138 1: { 139 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 140 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 141 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10), 142 }, 143 }, 144 makeRateIndex([]uint64{1}), 145 ascending, 146 ), 147 }, 148 { 149 label: "Add order to existing buy book side bin sorted by time", 150 side: makeBookSide( 151 map[uint64][]*Order{ 152 1: { 153 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 154 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10), 155 }, 156 }, 157 makeRateIndex([]uint64{1}), 158 ascending, 159 ), 160 entry: makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 5), 161 expected: makeBookSide( 162 map[uint64][]*Order{ 163 1: { 164 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 5), 165 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 166 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10), 167 }, 168 }, 169 makeRateIndex([]uint64{1}), 170 ascending, 171 ), 172 }, 173 { 174 label: "Add order to sell book side", 175 side: makeBookSide( 176 map[uint64][]*Order{ 177 1: { 178 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 10), 179 }, 180 }, 181 makeRateIndex([]uint64{1}), 182 descending, 183 ), 184 entry: makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 10), 185 expected: makeBookSide( 186 map[uint64][]*Order{ 187 1: { 188 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 10), 189 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 10), 190 }, 191 }, 192 makeRateIndex([]uint64{1}), 193 descending, 194 ), 195 }, 196 { 197 label: "Add order to sell book side bin sorted by time", 198 side: makeBookSide( 199 map[uint64][]*Order{ 200 1: { 201 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 10), 202 }, 203 }, 204 makeRateIndex([]uint64{1}), 205 descending, 206 ), 207 entry: makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5), 208 expected: makeBookSide( 209 map[uint64][]*Order{ 210 1: { 211 212 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5), 213 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 10), 214 }, 215 }, 216 makeRateIndex([]uint64{1}), 217 descending, 218 ), 219 }, 220 } 221 222 for idx, tc := range tests { 223 tc.side.Add(tc.entry) 224 if len(tc.side.bins) != len(tc.expected.bins) { 225 t.Fatalf("[BookSide.Add] #%d: expected bin size of %d,"+ 226 " got %d", idx+1, len(tc.expected.bins), len(tc.side.bins)) 227 } 228 229 for price, bin := range tc.side.bins { 230 expected := tc.expected.bins[price] 231 for i := 0; i < len(bin); i++ { 232 if !bytes.Equal(bin[i].OrderID[:], expected[i].OrderID[:]) { 233 t.Fatalf("[BookSide.Add] #%d: expected price bin %d "+ 234 "entry at index %d to have id %x, got %x", idx+1, 235 price, idx, expected[i].OrderID[:], bin[i].OrderID[:]) 236 } 237 238 if bin[i].Time != expected[i].Time { 239 t.Fatalf("[BookSide.Add] #%d: expected price bin %d "+ 240 "entry at index %d to have timestamp %d, got %d", idx+1, 241 price, idx, expected[i].Time, bin[i].Time) 242 } 243 244 if bin[i].Quantity != expected[i].Quantity { 245 t.Fatalf("[BookSide.Add] #%d: expected price bin %d "+ 246 "entry at index %d to have quantity %d, got %d", idx+1, 247 price, idx, expected[i].Quantity, bin[i].Quantity) 248 } 249 } 250 } 251 252 entryBin := tc.side.bins[tc.entry.Rate] 253 expectedBin := tc.expected.bins[tc.entry.Rate] 254 255 if len(entryBin) != len(expectedBin) { 256 t.Fatalf("[BookSide.Add] #%d: expected bin size of %d,"+ 257 " got %d", idx+1, len(expectedBin), len(entryBin)) 258 } 259 260 if len(tc.side.rateIndex.Rates) != len(tc.expected.rateIndex.Rates) { 261 t.Fatalf("[BookSide.Add] #%d: expected index size of %d,"+ 262 " got %d", idx+1, len(tc.expected.rateIndex.Rates), 263 len(tc.side.rateIndex.Rates)) 264 } 265 266 for i := 0; i < len(tc.side.rateIndex.Rates); i++ { 267 if tc.side.rateIndex.Rates[i] != 268 tc.expected.rateIndex.Rates[i] { 269 t.Fatalf("[BookSide.Add] #%d: expected index "+ 270 "rate value of %d at index %d, got %d", idx+1, 271 tc.expected.rateIndex.Rates[i], i, 272 tc.side.rateIndex.Rates[i]) 273 } 274 } 275 } 276 } 277 278 func TestBookSideRemove(t *testing.T) { 279 tests := []struct { 280 label string 281 side *bookSide 282 entry *Order 283 expected *bookSide 284 wantErr bool 285 }{ 286 { 287 label: "Remove order from buy book side", 288 side: makeBookSide( 289 map[uint64][]*Order{ 290 1: { 291 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 292 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 293 }, 294 2: { 295 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10), 296 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 3, 2, 10), 297 }, 298 }, 299 makeRateIndex([]uint64{1, 2}), 300 ascending, 301 ), 302 entry: makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 3, 2, 10), 303 expected: makeBookSide( 304 map[uint64][]*Order{ 305 1: { 306 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 307 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 308 }, 309 2: { 310 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10), 311 }, 312 }, 313 makeRateIndex([]uint64{1, 2}), 314 ascending, 315 ), 316 wantErr: false, 317 }, 318 { 319 label: "Remove last order from sell book side bin", 320 side: makeBookSide( 321 map[uint64][]*Order{ 322 1: { 323 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 2), 324 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5), 325 }, 326 2: { 327 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 2, 2, 10), 328 }, 329 }, 330 makeRateIndex([]uint64{1, 2}), 331 descending, 332 ), 333 entry: makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 2, 2, 10), 334 expected: makeBookSide( 335 map[uint64][]*Order{ 336 1: { 337 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 2), 338 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5), 339 }, 340 }, 341 makeRateIndex([]uint64{1}), 342 descending, 343 ), 344 wantErr: false, 345 }, 346 { 347 label: "Remove non-existing order from buy book side", 348 side: makeBookSide( 349 map[uint64][]*Order{ 350 1: { 351 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 352 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 353 }, 354 }, 355 makeRateIndex([]uint64{1}), 356 ascending, 357 ), 358 entry: makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10), 359 expected: makeBookSide( 360 map[uint64][]*Order{ 361 1: { 362 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10), 363 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10), 364 }, 365 }, 366 makeRateIndex([]uint64{1}), 367 ascending, 368 ), 369 wantErr: true, 370 }, 371 { 372 label: "Remove order from sell book side bin", 373 side: makeBookSide( 374 map[uint64][]*Order{ 375 1: { 376 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 2), 377 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5), 378 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 2, 1, 10), 379 }, 380 }, 381 makeRateIndex([]uint64{1}), 382 descending, 383 ), 384 entry: makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5), 385 expected: makeBookSide( 386 map[uint64][]*Order{ 387 1: { 388 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 2), 389 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 2, 1, 10), 390 }, 391 }, 392 makeRateIndex([]uint64{1}), 393 descending, 394 ), 395 wantErr: false, 396 }, 397 } 398 399 for idx, tc := range tests { 400 ord := tc.entry 401 err := tc.side.Remove(ord.OrderID, ord.Rate) 402 if (err != nil) != tc.wantErr { 403 t.Fatalf("[BookSide.Remove] #%d: error: %v, wantErr: %v", 404 idx+1, err, tc.wantErr) 405 } 406 407 if len(tc.side.bins) != len(tc.expected.bins) { 408 t.Fatalf("[BookSide.Remove] #%d: expected bin size of %d,"+ 409 " got %d", idx+1, len(tc.expected.bins), 410 len(tc.side.bins)) 411 } 412 413 for price, bin := range tc.side.bins { 414 expected := tc.expected.bins[price] 415 for i := 0; i < len(bin); i++ { 416 if !bytes.Equal(bin[i].OrderID[:], expected[i].OrderID[:]) { 417 t.Fatalf("[BookSide.Remove] #%d: expected price bin %d "+ 418 "entry at index %d to have id %x, got %x", idx+1, 419 price, idx, expected[i].OrderID[:], bin[i].OrderID[:]) 420 } 421 422 if bin[i].Time != expected[i].Time { 423 t.Fatalf("[BookSide.Remove] #%d: expected price bin %d "+ 424 "entry at index %d to have timestamp %d, got %d", idx+1, 425 price, idx, expected[i].Time, bin[i].Time) 426 } 427 428 if bin[i].Quantity != expected[i].Quantity { 429 t.Fatalf("[BookSide.Remove] #%d: expected price bin %d "+ 430 "entry at index %d to have quantity %d, got %d", idx+1, 431 price, idx, expected[i].Quantity, bin[i].Quantity) 432 } 433 } 434 } 435 436 entryBin := tc.side.bins[tc.entry.Rate] 437 expectedBin := tc.expected.bins[tc.entry.Rate] 438 439 if len(entryBin) != len(expectedBin) { 440 t.Fatalf("[BookSide.Remove] #%d: expected bin size of %d,"+ 441 " got %d", idx+1, len(expectedBin), len(entryBin)) 442 } 443 444 if len(tc.side.rateIndex.Rates) != len(tc.expected.rateIndex.Rates) { 445 t.Fatalf("[BookSide.Remove] #%d: expected index size of %d,"+ 446 " got %d", idx+1, len(tc.expected.rateIndex.Rates), 447 len(tc.side.rateIndex.Rates)) 448 } 449 450 for i := 0; i < len(tc.side.rateIndex.Rates); i++ { 451 if tc.side.rateIndex.Rates[i] != 452 tc.expected.rateIndex.Rates[i] { 453 t.Fatalf("[BookSide.Remove] #%d: expected "+ 454 " index value of %d at index %d, got %d", idx+1, 455 tc.expected.rateIndex.Rates[i], i, 456 tc.side.rateIndex.Rates[i]) 457 } 458 } 459 } 460 } 461 462 func TestBookSideBestNOrders(t *testing.T) { 463 tests := []struct { 464 label string 465 side *bookSide 466 n int 467 expected []*Order 468 }{ 469 { 470 label: "Fetch best N orders from buy book side sorted in ascending order", 471 side: makeBookSide( 472 map[uint64][]*Order{ 473 1: { 474 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2), 475 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 3, 1, 5), 476 }, 477 2: { 478 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2), 479 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 2, 5), 480 }, 481 }, 482 makeRateIndex([]uint64{1, 2}), 483 ascending, 484 ), 485 n: 3, 486 expected: []*Order{ 487 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2), 488 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 3, 1, 5), 489 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2), 490 }, 491 }, 492 { 493 label: "Fetch best N orders from buy book side sorted in descending order", 494 side: makeBookSide( 495 map[uint64][]*Order{ 496 1: { 497 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2), 498 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 3, 1, 5), 499 }, 500 2: { 501 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2), 502 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 2, 5), 503 }, 504 }, 505 makeRateIndex([]uint64{1, 2}), 506 descending, 507 ), 508 n: 3, 509 expected: []*Order{ 510 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2), 511 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 2, 5), 512 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2), 513 }, 514 }, 515 { 516 label: "Fetch best N orders from sell book side sorted in ascending order", 517 side: makeBookSide( 518 map[uint64][]*Order{ 519 1: { 520 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2), 521 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5), 522 }, 523 2: { 524 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2), 525 }, 526 }, 527 makeRateIndex([]uint64{1, 2}), 528 ascending, 529 ), 530 n: 10, 531 expected: []*Order{ 532 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2), 533 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5), 534 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2), 535 }, 536 }, 537 { 538 label: "Fetch best N orders from sell book side sorted in descending order", 539 side: makeBookSide( 540 map[uint64][]*Order{ 541 1: { 542 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2), 543 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5), 544 }, 545 2: { 546 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2), 547 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 2, 5), 548 }, 549 }, 550 makeRateIndex([]uint64{1, 2}), 551 descending, 552 ), 553 n: 10, 554 expected: []*Order{ 555 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2), 556 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 2, 5), 557 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2), 558 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5), 559 }, 560 }, 561 { 562 label: "Fetch best N orders from empty book side sorted in ascending order", 563 side: makeBookSide( 564 map[uint64][]*Order{}, 565 makeRateIndex([]uint64{}), 566 ascending, 567 ), 568 n: 3, 569 expected: []*Order{}, 570 }, 571 } 572 573 for idx, tc := range tests { 574 best, _ := tc.side.BestNOrders(tc.n) 575 if len(best) != len(tc.expected) { 576 t.Fatalf("[BookSide.BestNOrders] #%d: expected best "+ 577 "order size of %d, got %d", idx+1, len(tc.expected), 578 len(best)) 579 } 580 581 for i := 0; i < len(best); i++ { 582 if best[i].OrderID != tc.expected[i].OrderID { 583 t.Fatalf("[BookSide.BestNOrders] #%d: expected "+ 584 "order id %x at index of %d, got %x", idx+1, 585 tc.expected[i].OrderID[:], idx, best[i].OrderID[:]) 586 } 587 588 if best[i].Quantity != tc.expected[i].Quantity { 589 t.Fatalf("[BookSide.BestNOrders] #%d: expected "+ 590 "quantity %d at index of %d, got %d", idx+1, 591 tc.expected[i].Quantity, idx, best[i].Quantity) 592 } 593 594 if best[i].Time != tc.expected[i].Time { 595 t.Fatalf("[BookSide.BestNOrders] #%d: expected "+ 596 "timestamp %d at index of %d, got %d", idx+1, 597 tc.expected[i].Time, idx, best[i].Time) 598 } 599 } 600 } 601 } 602 603 func TestBookSideBestFill(t *testing.T) { 604 tests := []struct { 605 label string 606 side *bookSide 607 quantity uint64 608 orderPref orderPreference 609 expected []*Fill 610 filled bool 611 marketBuy bool 612 }{ 613 { 614 label: "Fetch best fill from buy book side sorted in ascending order", 615 side: makeBookSide( 616 map[uint64][]*Order{ 617 1: { 618 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2), 619 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 3, 1, 5), 620 }, 621 2: { 622 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2), 623 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 2, 5), 624 }, 625 }, 626 makeRateIndex([]uint64{1, 2}), 627 ascending, 628 ), 629 quantity: 9, 630 expected: []*Fill{ 631 { 632 Rate: 1, 633 Quantity: 5, 634 }, 635 { 636 Rate: 1, 637 Quantity: 3, 638 }, 639 { 640 Rate: 2, 641 Quantity: 1, 642 }, 643 }, 644 filled: true, 645 }, 646 { 647 label: "Fetch best fill from buy book side sorted in descending order", 648 side: makeBookSide( 649 map[uint64][]*Order{ 650 1: { 651 makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2), 652 makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 3, 1, 5), 653 }, 654 2: { 655 makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2), 656 makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 2, 5), 657 }, 658 }, 659 makeRateIndex([]uint64{1, 2}), 660 descending, 661 ), 662 quantity: 7, 663 expected: []*Fill{ 664 { 665 Rate: 2, 666 Quantity: 1, 667 }, 668 { 669 Rate: 2, 670 Quantity: 5, 671 }, 672 { 673 Rate: 1, 674 Quantity: 1, 675 }, 676 }, 677 filled: true, 678 }, 679 { 680 label: "Fetch best fill from sell book side sorted in ascending order", 681 side: makeBookSide( 682 map[uint64][]*Order{ 683 1: { 684 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2), 685 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5), 686 }, 687 2: { 688 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2), 689 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 2, 5), 690 }, 691 }, 692 makeRateIndex([]uint64{1, 2}), 693 ascending, 694 ), 695 quantity: 0, 696 expected: []*Fill{}, 697 filled: true, 698 }, 699 { 700 label: "Fetch best fill from sell book side sorted in ascending order", 701 side: makeBookSide( 702 map[uint64][]*Order{ 703 1: { 704 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 4, 1, 2), 705 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5), 706 }, 707 2: { 708 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2), 709 }, 710 }, 711 makeRateIndex([]uint64{1, 2}), 712 ascending, 713 ), 714 quantity: 9, 715 expected: []*Fill{ 716 { 717 Rate: 1, 718 Quantity: 4, 719 }, 720 { 721 Rate: 1, 722 Quantity: 3, 723 }, 724 { 725 Rate: 2, 726 Quantity: 1, 727 }, 728 }, 729 filled: false, 730 }, 731 { 732 label: "Fetch best fill from sell book side sorted in descending order", 733 side: makeBookSide( 734 map[uint64][]*Order{ 735 1: { 736 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2), 737 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5), 738 }, 739 2: { 740 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2), 741 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 2, 5), 742 }, 743 }, 744 makeRateIndex([]uint64{1, 2}), 745 descending, 746 ), 747 quantity: 50, 748 expected: []*Fill{ 749 { 750 Rate: 2, 751 Quantity: 1, 752 }, 753 { 754 Rate: 2, 755 Quantity: 5, 756 }, 757 { 758 Rate: 1, 759 Quantity: 5, 760 }, 761 { 762 Rate: 1, 763 Quantity: 3, 764 }, 765 }, 766 filled: false, 767 }, 768 { 769 label: "Fetch market buy fill", 770 side: makeBookSide( 771 map[uint64][]*Order{ 772 2e8: { 773 makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 2e8, 2), // 10 Quote asset 774 makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 2e8, 5), // 6 775 }, 776 3e8: { 777 makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 3e8, 2), // 3 778 makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 3e8, 5), // 15 779 }, 780 }, 781 makeRateIndex([]uint64{2e8, 3e8}), 782 ascending, 783 ), 784 quantity: 30, // 16 @ 2e8, 14 @ 3e8 785 expected: []*Fill{ 786 { 787 Rate: 2e8, 788 Quantity: 5, 789 }, 790 { 791 Rate: 2e8, 792 Quantity: 3, 793 }, 794 { 795 Rate: 3e8, 796 Quantity: 1, 797 }, 798 { 799 Rate: 3e8, 800 Quantity: 3, // 11 remaining only covers 3 units at rate = 3. 801 }, 802 }, 803 marketBuy: true, 804 filled: true, 805 }, 806 } 807 808 for _, tc := range tests { 809 var best []*Fill 810 var filled bool 811 if tc.marketBuy { 812 best, filled = tc.side.bestFill(tc.quantity, true, 1) 813 } else { 814 best, filled = tc.side.BestFill(tc.quantity) 815 } 816 817 if filled != tc.filled { 818 t.Fatalf("[BookSide.BestFill] %q: wrong fill. wanted %v, got %v", tc.label, tc.filled, filled) 819 } 820 821 if len(best) != len(tc.expected) { 822 t.Fatalf("[BookSide.BestFill] %q: expected best "+ 823 "order size of %d, got %d", tc.label, len(tc.expected), 824 len(best)) 825 } 826 827 for i := 0; i < len(best); i++ { 828 if best[i].Rate != tc.expected[i].Rate { 829 t.Fatalf("[BookSide.BestFill] %q: expected fill at "+ 830 "index %d to have rate %d, got %d", tc.label, i, 831 tc.expected[i].Rate, best[i].Rate) 832 } 833 if best[i].Quantity != tc.expected[i].Quantity { 834 t.Fatalf("[BookSide.BestFill] %q: expected fill at "+ 835 "index %d to have quantity %d, got %d", tc.label, i, 836 tc.expected[i].Quantity, best[i].Quantity) 837 } 838 } 839 } 840 }