decred.org/dcrdex@v1.0.5/client/mm/libxc/orderbook_test.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package libxc 5 6 import ( 7 "reflect" 8 "sort" 9 "testing" 10 ) 11 12 func TestOrderbook(t *testing.T) { 13 ob := newOrderBook() 14 15 // Test vwap on empty books 16 _, _, filled := ob.vwap(true, 1) 17 if filled { 18 t.Fatalf("empty book should not be filled") 19 } 20 _, _, filled = ob.vwap(false, 1) 21 if filled { 22 t.Fatalf("empty book should not be filled") 23 } 24 25 sortedEntries := func(bids bool, entries []*obEntry) []*obEntry { 26 sorted := make([]*obEntry, len(entries)) 27 copy(sorted, entries) 28 sort.Slice(sorted, func(i, j int) bool { 29 if bids { 30 return sorted[i].rate > sorted[j].rate 31 } 32 return sorted[i].rate < sorted[j].rate 33 }) 34 return sorted 35 } 36 37 // Populate the book with some bids and asks. They both 38 // have the same values, but VWAP for asks should be 39 // calculated from the lower values first. 40 bids := []*obEntry{ 41 {qty: 30e8, rate: 4000}, 42 {qty: 30e8, rate: 5000}, 43 {qty: 80e8, rate: 400}, 44 {qty: 10e8, rate: 3000}, 45 } 46 asks := []*obEntry{ 47 {qty: 30e8, rate: 4000}, 48 {qty: 30e8, rate: 5000}, 49 {qty: 80e8, rate: 400}, 50 {qty: 10e8, rate: 3000}, 51 } 52 ob.update(bids, asks) 53 54 sortedBids := sortedEntries(true, bids) 55 sortedAsks := sortedEntries(false, asks) 56 snapBids, snapAsks := ob.snap() 57 if !reflect.DeepEqual(snapBids, sortedBids) { 58 t.Fatalf("wrong snap bids. expected %v got %v", sortedBids, snapBids) 59 } 60 if !reflect.DeepEqual(snapAsks, sortedAsks) { 61 t.Fatalf("wrong snap asks. expected %v got %v", sortedAsks, snapAsks) 62 } 63 64 type vwapFn func(bids bool, qty uint64) (vwap, extrema uint64, filled bool) 65 checkVWAPFn := func(fn vwapFn, bids bool, qty uint64, expVWAP, expExtrema uint64, expFilled bool) { 66 t.Helper() 67 vwap, extrema, filled := fn(bids, qty) 68 if filled != expFilled { 69 t.Fatalf("wrong filled. expected %v got %v", expFilled, filled) 70 } 71 if vwap != expVWAP { 72 t.Fatalf("wrong vwap. expected %d got %d", expVWAP, vwap) 73 } 74 if extrema != expExtrema { 75 t.Fatalf("wrong extrema. expected %d got %d", expExtrema, extrema) 76 } 77 } 78 checkVWAP := func(bids bool, qty uint64, expVWAP, expExtrema uint64, expFilled bool) { 79 t.Helper() 80 checkVWAPFn(ob.vwap, bids, qty, expVWAP, expExtrema, expFilled) 81 } 82 83 // Test vwap for bids and asks 84 expVWAP := (sortedBids[0].rate*30e8 + sortedBids[1].rate*30e8 + sortedBids[2].rate*5e8) / 65e8 85 checkVWAP(true, 65e8, expVWAP, 3000, true) 86 checkVWAP(false, 65e8, 400, 400, true) 87 88 // Test vwap for qty > total qty 89 checkVWAP(true, 161e8, 0, 0, false) 90 checkVWAP(false, 161e8, 0, 0, false) 91 92 // Update quantities. Setting qty to 0 should delete. 93 bids = []*obEntry{ 94 {qty: 0, rate: 5000}, 95 {qty: 50e8, rate: 4000}, 96 } 97 asks = []*obEntry{ 98 {qty: 0, rate: 400}, 99 {qty: 35e8, rate: 4000}, 100 } 101 ob.update(bids, asks) 102 103 // Make sure snap returns the correct entries 104 expSnapBids := []*obEntry{ 105 {qty: 50e8, rate: 4000}, 106 {qty: 10e8, rate: 3000}, 107 {qty: 80e8, rate: 400}, 108 } 109 expSnapAsks := []*obEntry{ 110 {qty: 10e8, rate: 3000}, 111 {qty: 35e8, rate: 4000}, 112 {qty: 30e8, rate: 5000}, 113 } 114 snapBids, snapAsks = ob.snap() 115 if !reflect.DeepEqual(snapBids, expSnapBids) { 116 t.Fatalf("wrong snap bids. expected %v got %v", expSnapBids, snapBids) 117 } 118 if !reflect.DeepEqual(snapAsks, expSnapAsks) { 119 t.Fatalf("wrong snap asks. expected %v got %v", expSnapAsks, snapAsks) 120 } 121 122 // Test vwap with updated quantities 123 expVWAP = (50e8*uint64(4000) + 10e8*uint64(3000) + 5e8*uint64(400)) / 65e8 124 checkVWAP(true, 65e8, expVWAP, 400, true) 125 expVWAP = (10e8*uint64(3000) + 35e8*uint64(4000) + 20e8*uint64(5000)) / 65e8 126 checkVWAP(false, 65e8, expVWAP, 5000, true) 127 } 128 129 // Test vwap with values that would overflow uint64. 130 func TestVWAPOverflow(t *testing.T) { 131 bids := []*obEntry{ 132 {qty: 1e15, rate: 12e9}, 133 {qty: 1e15, rate: 10e9}, 134 } 135 ob := newOrderBook() 136 ob.update(bids, nil) 137 138 vwap, extrema, filled := ob.vwap(true, 2e15) 139 if !filled { 140 t.Fatalf("should be filled") 141 } 142 if vwap != uint64(11e9) { 143 t.Fatalf("wrong vwap. expected %d got %d", uint64(11e9), vwap) 144 } 145 if extrema != uint64(10e9) { 146 t.Fatalf("wrong extrema") 147 } 148 }