decred.org/dcrdex@v1.0.5/server/book/book.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 book defines the order book used by each Market. 5 package book 6 7 import ( 8 "sync" 9 10 "decred.org/dcrdex/dex/order" 11 "decred.org/dcrdex/server/account" 12 ) 13 14 const ( 15 // initBookHalfCapacity is the default capacity of one side (buy or sell) of 16 // the order book. It is set to 2^16 orders (65536 orders) per book side. 17 initBookHalfCapacity uint32 = 1 << 16 18 ) 19 20 // Book is a market's order book. The Book uses a configurable lot size, of 21 // which all inserted orders must have a quantity that is a multiple. The buy 22 // and sell sides of the order book are contained in separate priority queues to 23 // allow constant time access to the best orders, and log time insertion and 24 // removal of orders. 25 type Book struct { 26 mtx sync.RWMutex 27 lotSize uint64 28 buys *OrderPQ 29 sells *OrderPQ 30 acctTracking AccountTracking 31 acctTracker *accountTracker 32 } 33 34 // New creates a new order book with the given lot size and account-tracking. 35 func New(lotSize uint64, acctTracking AccountTracking) *Book { 36 return &Book{ 37 lotSize: lotSize, 38 buys: NewMaxOrderPQ(initBookHalfCapacity), 39 sells: NewMinOrderPQ(initBookHalfCapacity), 40 acctTracking: acctTracking, 41 acctTracker: newAccountTracker(acctTracking), 42 } 43 } 44 45 // Clear reset the order book with configured capacity. 46 func (b *Book) Clear() (removedBuys, removedSells []*order.LimitOrder) { 47 b.mtx.Lock() 48 removedBuys, removedSells = b.buys.Orders(), b.sells.Orders() 49 b.buys, b.sells = nil, nil 50 b.buys = NewMaxOrderPQ(initBookHalfCapacity) 51 b.sells = NewMinOrderPQ(initBookHalfCapacity) 52 b.acctTracker = newAccountTracker(b.acctTracking) 53 b.mtx.Unlock() 54 return 55 } 56 57 // LotSize returns the Book's configured lot size in atoms of the base asset. 58 func (b *Book) LotSize() uint64 { 59 return b.lotSize 60 } 61 62 // BuyCount returns the number of buy orders. 63 func (b *Book) BuyCount() int { 64 return b.buys.Count() 65 } 66 67 // SellCount returns the number of sell orders. 68 func (b *Book) SellCount() int { 69 return b.sells.Count() 70 } 71 72 // BestSell returns a pointer to the best sell order in the order book. The 73 // order is NOT removed from the book. 74 func (b *Book) BestSell() *order.LimitOrder { 75 return b.sells.PeekBest() 76 } 77 78 // BestBuy returns a pointer to the best buy order in the order book. The 79 // order is NOT removed from the book. 80 func (b *Book) BestBuy() *order.LimitOrder { 81 return b.buys.PeekBest() 82 } 83 84 // Best returns pointers to the best buy and sell order in the order book. The 85 // orders are NOT removed from the book. 86 func (b *Book) Best() (bestBuy, bestSell *order.LimitOrder) { 87 b.mtx.RLock() 88 bestBuy = b.buys.PeekBest() 89 bestSell = b.sells.PeekBest() 90 b.mtx.RUnlock() 91 return 92 } 93 94 // Insert attempts to insert the provided order into the order book, returning a 95 // boolean indicating if the insertion was successful. If the order is not an 96 // integer multiple of the Book's lot size, the order will not be inserted. 97 func (b *Book) Insert(o *order.LimitOrder) bool { 98 if o.Quantity%b.lotSize != 0 { 99 log.Warnf("(*Book).Insert: Refusing to insert an order with a quantity that is not a multiple of lot size.") 100 return false 101 } 102 b.mtx.Lock() 103 defer b.mtx.Unlock() 104 if o.Sell { 105 if b.sells.Insert(o) { 106 b.acctTracker.add(o) 107 return true 108 } 109 return false 110 } 111 if b.buys.Insert(o) { 112 b.acctTracker.add(o) 113 return true 114 } 115 return false 116 } 117 118 // Remove attempts to remove the order with the given OrderID from the book. 119 func (b *Book) Remove(oid order.OrderID) (*order.LimitOrder, bool) { 120 b.mtx.Lock() 121 defer b.mtx.Unlock() 122 if removed, ok := b.sells.RemoveOrderID(oid); ok { 123 b.acctTracker.remove(removed) 124 return removed, true 125 } 126 if removed, ok := b.buys.RemoveOrderID(oid); ok { 127 b.acctTracker.remove(removed) 128 return removed, true 129 } 130 return nil, false 131 } 132 133 // RemoveUserOrders removes all orders from the book that belong to a user. The 134 // removed buy and sell orders are returned. 135 func (b *Book) RemoveUserOrders(user account.AccountID) (removedBuys, removedSells []*order.LimitOrder) { 136 removedBuys = b.buys.RemoveUserOrders(user) 137 for _, lo := range removedBuys { 138 b.acctTracker.remove(lo) 139 } 140 removedSells = b.sells.RemoveUserOrders(user) 141 for _, lo := range removedSells { 142 b.acctTracker.remove(lo) 143 } 144 return 145 } 146 147 // HaveOrder checks if an order is in either the buy or sell side of the book. 148 func (b *Book) HaveOrder(oid order.OrderID) bool { 149 b.mtx.RLock() 150 defer b.mtx.RUnlock() 151 return b.buys.HaveOrder(oid) || b.sells.HaveOrder(oid) 152 } 153 154 // Order attempts to retrieve an order from either the buy or sell side of the 155 // book. If the order data is not required, consider using HaveOrder. 156 func (b *Book) Order(oid order.OrderID) *order.LimitOrder { 157 b.mtx.RLock() 158 defer b.mtx.RUnlock() 159 if lo := b.buys.Order(oid); lo != nil { 160 return lo 161 } 162 return b.sells.Order(oid) 163 } 164 165 // UserOrderTotals returns the total amount in booked orders and the number of 166 // booked orders, for both the buy and sell sides of the book. Both amounts are 167 // in units of the base asset, and should be multiples of the market's lot size. 168 func (b *Book) UserOrderTotals(user account.AccountID) (buyAmt, sellAmt, buyCount, sellCount uint64) { 169 b.mtx.RLock() 170 buyAmt, buyCount = b.buys.UserOrderTotals(user) 171 sellAmt, sellCount = b.sells.UserOrderTotals(user) 172 b.mtx.RUnlock() 173 return 174 } 175 176 // SellOrders copies out all sell orders in the book, sorted. 177 func (b *Book) SellOrders() []*order.LimitOrder { 178 return b.sells.Orders() 179 } 180 181 // SellOrdersN copies out the N best sell orders in the book, sorted. 182 func (b *Book) SellOrdersN(N int) []*order.LimitOrder { 183 return b.sells.OrdersN(N) 184 } 185 186 // BuyOrders copies out all buy orders in the book, sorted. 187 func (b *Book) BuyOrders() []*order.LimitOrder { 188 return b.buys.Orders() 189 } 190 191 // BuyOrdersN copies out the N best buy orders in the book, sorted. 192 func (b *Book) BuyOrdersN(N int) []*order.LimitOrder { 193 return b.buys.OrdersN(N) 194 } 195 196 // UnfilledUserBuys retrieves all buy orders belonging to a given user that are 197 // completely unfilled. 198 func (b *Book) UnfilledUserBuys(user account.AccountID) []*order.LimitOrder { 199 b.mtx.RLock() 200 defer b.mtx.RUnlock() 201 return b.buys.UnfilledForUser(user) 202 } 203 204 // UnfilledUserSells retrieves all sell orders belonging to a given user that 205 // are completely unfilled. 206 func (b *Book) UnfilledUserSells(user account.AccountID) []*order.LimitOrder { 207 b.mtx.RLock() 208 defer b.mtx.RUnlock() 209 return b.sells.UnfilledForUser(user) 210 } 211 212 // IterateBaseAccount calls the provided function for every tracked order with 213 // a base asset corresponding to the specified account address. 214 func (b *Book) IterateBaseAccount(acctAddr string, f func(lo *order.LimitOrder)) { 215 b.mtx.RLock() 216 defer b.mtx.RUnlock() 217 b.acctTracker.iterateBaseAccount(acctAddr, f) 218 } 219 220 // IterateQuoteAccount calls the provided function for every tracked order with 221 // a quote asset corresponding to the specified account address. 222 func (b *Book) IterateQuoteAccount(acctAddr string, f func(lo *order.LimitOrder)) { 223 b.mtx.RLock() 224 defer b.mtx.RUnlock() 225 b.acctTracker.iterateQuoteAccount(acctAddr, f) 226 }