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