decred.org/dcrdex@v1.0.3/client/webserver/site/src/js/orderbook.ts (about) 1 import { 2 MarketOrderBook, 3 MiniOrder 4 } from './registry' 5 6 export default class OrderBook { 7 base: number 8 baseSymbol: string 9 quote: number 10 quoteSymbol: string 11 buys: MiniOrder[] 12 sells: MiniOrder[] 13 14 constructor (mktBook: MarketOrderBook, baseSymbol: string, quoteSymbol: string) { 15 this.base = mktBook.base 16 this.baseSymbol = baseSymbol 17 this.quote = mktBook.quote 18 this.quoteSymbol = quoteSymbol 19 // Books are sorted mid-gap first. 20 this.buys = mktBook.book.buys || [] 21 this.sells = mktBook.book.sells || [] 22 } 23 24 /* add adds an order to the order book. */ 25 add (ord: MiniOrder) { 26 if (ord.qtyAtomic === 0) { 27 // TODO: Somebody, for the love of god, figure out why the hell this helps 28 // with the ghost orders problem. As far as I know, this order is a booked 29 // order that had more than one match in an epoch and completely filled. 30 // Because the first match didn't exhaust the order, there would be a 31 // 'update_remaining' notification scheduled for the order. But by the 32 // time OrderRouter generates the notification long after matching, the 33 // order has zero qty left to fill. It's all good though, kinda, because 34 // the notification is quickly followed with an 'unbook_order' 35 // notification. I have tried my damnedest to catch an update_remaining 36 // note without an accompanying unbook_order note, and have thus failed. 37 // Yet, this fix somehow seems to work. It's infuriating, tbh. 38 window.log('zeroqty', 'zero quantity order encountered', ord) 39 return 40 } 41 const side = ord.sell ? this.sells : this.buys 42 side.splice(findIdx(side, ord.rate, !ord.sell), 0, ord) 43 } 44 45 /* remove removes an order from the order book. */ 46 remove (token: string) { 47 if (this.removeFromSide(this.sells, token)) return 48 this.removeFromSide(this.buys, token) 49 } 50 51 /* removeFromSide removes an order from the list of orders. */ 52 removeFromSide (side: MiniOrder[], token: string) { 53 const [ord, i] = this.findOrder(side, token) 54 if (ord) { 55 side.splice(i, 1) 56 return true 57 } 58 return false 59 } 60 61 /* findOrder finds an order in a specified side */ 62 findOrder (side: MiniOrder[], token: string): [MiniOrder | null, number] { 63 for (let i = 0; i < side.length; i++) { 64 if (side[i].token === token) { 65 return [side[i], i] 66 } 67 } 68 return [null, -1] 69 } 70 71 /* updates the remaining quantity of an order. */ 72 updateRemaining (token: string, qty: number, qtyAtomic: number) { 73 if (this.updateRemainingSide(this.sells, token, qty, qtyAtomic)) return 74 this.updateRemainingSide(this.buys, token, qty, qtyAtomic) 75 } 76 77 /* 78 * updateRemainingSide looks for the order in the side and updates the 79 * quantity, returning true on success, false if order not found. 80 */ 81 updateRemainingSide (side: MiniOrder[], token: string, qty: number, qtyAtomic: number) { 82 const ord = this.findOrder(side, token)[0] 83 if (ord) { 84 ord.qty = qty 85 ord.qtyAtomic = qtyAtomic 86 return true 87 } 88 return false 89 } 90 91 /* 92 * setEpoch sets the current epoch and clear any orders from previous epochs. 93 */ 94 setEpoch (epochIdx: number) { 95 const approve = (ord: MiniOrder) => ord.epoch === undefined || ord.epoch === 0 || ord.epoch === epochIdx 96 this.sells = this.sells.filter(approve) 97 this.buys = this.buys.filter(approve) 98 } 99 100 /* empty will return true if both the buys and sells lists are empty. */ 101 empty () { 102 return !this.sells.length && !this.buys.length 103 } 104 105 /* count is the total count of both buy and sell orders. */ 106 count () { 107 return this.sells.length + this.buys.length 108 } 109 110 /* bestGapOrder will return the best non-epoch order if one exists, or the 111 * best epoch order if there are only epoch orders, or null if there are no 112 * orders. 113 */ 114 bestGapOrder (side: MiniOrder[]) { 115 let best = null 116 for (const ord of side) { 117 if (!ord.epoch) return ord 118 if (!best) { 119 best = ord 120 } 121 } 122 return best 123 } 124 125 bestGapBuy () { 126 return this.bestGapOrder(this.buys) 127 } 128 129 bestGapSell () { 130 return this.bestGapOrder(this.sells) 131 } 132 } 133 134 /* 135 * findIdx find the index at which to insert the order into the list of orders. 136 */ 137 function findIdx (side: MiniOrder[], rate: number, less: boolean): number { 138 for (let i = 0; i < side.length; i++) { 139 if ((side[i].rate < rate) === less) return i 140 } 141 return side.length 142 }