decred.org/dcrdex@v1.0.5/client/mm/libxc/orderbook.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 package libxc 4 5 import ( 6 "fmt" 7 "math" 8 "math/big" 9 "sync" 10 11 "github.com/huandu/skiplist" 12 ) 13 14 type obEntry struct { 15 qty uint64 16 rate uint64 17 } 18 19 // obEntryComparable is a skiplist.Comparable implementation for 20 // obEntry. It is used to sort the orderbook entries by rate. 21 type obEntryComparable int 22 23 const bidsComparable = obEntryComparable(0) 24 const asksComparable = obEntryComparable(1) 25 26 var _ skiplist.Comparable = (*obEntryComparable)(nil) 27 28 func (o obEntryComparable) Compare(lhs, rhs interface{}) int { 29 lhsEntry := lhs.(*obEntry) 30 rhsEntry := rhs.(*obEntry) 31 32 var toReturn int 33 if lhsEntry.rate < rhsEntry.rate { 34 toReturn = -1 35 } 36 if lhsEntry.rate > rhsEntry.rate { 37 toReturn = 1 38 } 39 40 if o == bidsComparable { 41 toReturn = -toReturn 42 } 43 44 return toReturn 45 } 46 47 func (o obEntryComparable) CalcScore(key interface{}) float64 { 48 if o == bidsComparable { 49 return math.MaxFloat64 - float64(key.(*obEntry).rate) 50 } else { 51 return float64(key.(*obEntry).rate) 52 } 53 } 54 55 // orderbook is an implementation of a limit order book that allows for quick 56 // updates and calculation of the volume weighted average price (VWAP). 57 type orderbook struct { 58 mtx sync.RWMutex 59 bids skiplist.SkipList 60 asks skiplist.SkipList 61 } 62 63 func newOrderBook() *orderbook { 64 return &orderbook{ 65 bids: *skiplist.New(bidsComparable), 66 asks: *skiplist.New(asksComparable), 67 } 68 } 69 70 func (ob *orderbook) String() string { 71 ob.mtx.RLock() 72 defer ob.mtx.RUnlock() 73 74 bids := make([]obEntry, 0, ob.bids.Len()) 75 for curr := ob.bids.Front(); curr != nil; curr = curr.Next() { 76 bids = append(bids, *curr.Value.(*obEntry)) 77 } 78 79 asks := make([]obEntry, 0, ob.asks.Len()) 80 for curr := ob.asks.Front(); curr != nil; curr = curr.Next() { 81 asks = append(asks, *curr.Value.(*obEntry)) 82 } 83 84 return fmt.Sprintf("bids: %v, asks: %v", bids, asks) 85 } 86 87 // update updates the orderbook new quantities at the given rates. 88 // If the quantity is 0, the entry is removed from the orderbook. 89 func (ob *orderbook) update(bids []*obEntry, asks []*obEntry) { 90 ob.mtx.Lock() 91 defer ob.mtx.Unlock() 92 93 for _, entry := range bids { 94 if entry.qty == 0 { 95 ob.bids.Remove(entry) 96 continue 97 } 98 ob.bids.Set(entry, entry) 99 } 100 101 for _, entry := range asks { 102 if entry.qty == 0 { 103 ob.asks.Remove(entry) 104 continue 105 } 106 ob.asks.Set(entry, entry) 107 } 108 } 109 110 // clear clears the orderbook. 111 func (ob *orderbook) clear() { 112 ob.mtx.Lock() 113 defer ob.mtx.Unlock() 114 115 ob.bids = *skiplist.New(bidsComparable) 116 ob.asks = *skiplist.New(asksComparable) 117 } 118 119 func (ob *orderbook) vwap(bids bool, baseQty uint64) (vwap, extrema uint64, filled bool) { 120 if baseQty == 0 { // avoid division by zero 121 return 0, 0, false 122 } 123 124 ob.mtx.RLock() 125 defer ob.mtx.RUnlock() 126 127 var list *skiplist.SkipList 128 if bids { 129 list = &ob.bids 130 } else { 131 list = &ob.asks 132 } 133 134 weightedTotal := big.NewInt(0) 135 bigQty := big.NewInt(0) 136 bigRate := big.NewInt(0) 137 addToWeightedTotal := func(rate uint64, qty uint64) { 138 bigRate.SetUint64(rate) 139 bigQty.SetUint64(qty) 140 weightedTotal.Add(weightedTotal, bigRate.Mul(bigRate, bigQty)) 141 } 142 143 remaining := baseQty 144 for curr := list.Front(); curr != nil; curr = curr.Next() { 145 entry := curr.Value.(*obEntry) 146 if entry.qty >= remaining { 147 filled = true 148 extrema = entry.rate 149 addToWeightedTotal(entry.rate, remaining) 150 break 151 } 152 remaining -= entry.qty 153 addToWeightedTotal(entry.rate, entry.qty) 154 } 155 if !filled { 156 return 0, 0, false 157 } 158 159 return weightedTotal.Div(weightedTotal, big.NewInt(int64(baseQty))).Uint64(), extrema, filled 160 } 161 162 func (ob *orderbook) midGap() uint64 { 163 ob.mtx.RLock() 164 defer ob.mtx.RUnlock() 165 166 bestBuyI := ob.bids.Front() 167 if bestBuyI == nil { 168 return 0 169 } 170 bestSellI := ob.asks.Front() 171 if bestSellI == nil { 172 return 0 173 } 174 bestBuy, bestSell := bestBuyI.Value.(*obEntry), bestSellI.Value.(*obEntry) 175 return (bestBuy.rate + bestSell.rate) / 2 176 } 177 178 // snap generates a snapshot of the book. 179 func (ob *orderbook) snap() (bids, asks []*obEntry) { 180 ob.mtx.RLock() 181 defer ob.mtx.RUnlock() 182 183 bids = make([]*obEntry, 0, ob.bids.Len()) 184 for curr := ob.bids.Front(); curr != nil; curr = curr.Next() { 185 bids = append(bids, curr.Value.(*obEntry)) 186 } 187 188 asks = make([]*obEntry, 0, ob.asks.Len()) 189 for curr := ob.asks.Front(); curr != nil; curr = curr.Next() { 190 asks = append(asks, curr.Value.(*obEntry)) 191 } 192 193 return bids, asks 194 }