Skip to content

Commit 57b0e34

Browse files
committed
perf: improve OrderSide performance by saving asks in ascending order and bids in descending order
1 parent 99ddd0b commit 57b0e34

File tree

4 files changed

+48
-50
lines changed

4 files changed

+48
-50
lines changed

src/orderbook.ts

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export class OrderBook {
2828
private bids: OrderSide
2929
private asks: OrderSide
3030
constructor() {
31-
this.bids = new OrderSide()
32-
this.asks = new OrderSide()
31+
this.bids = new OrderSide(Side.BUY)
32+
this.asks = new OrderSide(Side.SELL)
3333
}
3434

3535
/**
@@ -317,20 +317,12 @@ export class OrderBook {
317317
public depth = (): [[number, number][], [number, number][]] => {
318318
const asks: [number, number][] = []
319319
const bids: [number, number][] = []
320-
321-
let level = this.asks.maxPriceQueue()
322-
while (level) {
323-
const levelPrice = level.price()
320+
this.asks.priceTree().forEach((levelPrice, level) => {
324321
asks.push([levelPrice, level.volume()])
325-
level = this.asks.lowerThan(levelPrice)
326-
}
327-
328-
level = this.bids.maxPriceQueue()
329-
while (level) {
330-
const levelPrice = level.price()
322+
})
323+
this.bids.priceTree().forEach((levelPrice, level) => {
331324
bids.push([levelPrice, level.volume()])
332-
level = this.bids.lowerThan(levelPrice)
333-
}
325+
})
334326
return [asks, bids]
335327
}
336328

@@ -469,20 +461,13 @@ export class OrderBook {
469461
}
470462

471463
let cumulativeSize = 0
472-
let iterator = orderSide.priceTree().end
473-
let continueLoop = true
474-
while (continueLoop) {
475-
if (iterator?.node) {
476-
if (price <= iterator.node.key) {
477-
cumulativeSize += iterator.node.value.volume()
478-
if (cumulativeSize < size && iterator.hasPrev) {
479-
iterator = orderSide.priceTree().at(iterator.index - 1)
480-
} else {
481-
continueLoop = false
482-
}
483-
} else continueLoop = false
484-
} else continueLoop = false
485-
}
464+
orderSide.priceTree().forEach((_key, priceLevel) => {
465+
if (price <= priceLevel.price() && cumulativeSize < size) {
466+
cumulativeSize += priceLevel.volume()
467+
} else {
468+
return true // break the loop
469+
}
470+
})
486471
return cumulativeSize >= size
487472
}
488473
}

src/orderside.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@ import createRBTree from 'functional-red-black-tree'
22
import { CustomError, ERROR } from './errors'
33
import { Order, OrderUpdate } from './order'
44
import { OrderQueue } from './orderqueue'
5+
import { Side } from './side'
56

67
export class OrderSide {
78
private _priceTree: createRBTree.Tree<number, OrderQueue>
89
private _prices: { [key: string]: OrderQueue } = {}
9-
1010
private _volume = 0
1111
private _total = 0
1212
private _numOrders = 0
1313
private _depthSide = 0
14-
15-
constructor() {
16-
this._priceTree = createRBTree<number, OrderQueue>()
14+
private _side: Side = Side.SELL
15+
16+
constructor(side: Side) {
17+
const compare =
18+
side === Side.SELL
19+
? (a: number, b: number) => a - b
20+
: (a: number, b: number) => b - a
21+
this._priceTree = createRBTree<number, OrderQueue>(compare)
22+
this._side = side
1723
}
1824

1925
// returns amount of orders
@@ -106,31 +112,39 @@ export class OrderSide {
106112
}
107113
}
108114

109-
// returns maximal level of price
115+
// returns max level of price
110116
maxPriceQueue = (): OrderQueue | undefined => {
111117
if (this._depthSide > 0) {
112-
const max = this._priceTree.end
118+
const max =
119+
this._side === Side.SELL ? this._priceTree.end : this._priceTree.begin
113120
return max.value
114121
}
115122
}
116123

117-
// returns maximal level of price
124+
// returns min level of price
118125
minPriceQueue = (): OrderQueue | undefined => {
119126
if (this._depthSide > 0) {
120-
const min = this._priceTree.begin
127+
const min =
128+
this._side === Side.SELL ? this._priceTree.begin : this._priceTree.end
121129
return min.value
122130
}
123131
}
124132

125133
// returns nearest OrderQueue with price less than given
126134
lowerThan = (price: number): OrderQueue | undefined => {
127-
const node = this._priceTree.lt(price)
135+
const node =
136+
this._side === Side.SELL
137+
? this._priceTree.lt(price)
138+
: this._priceTree.gt(price)
128139
return node.value
129140
}
130141

131142
// returns nearest OrderQueue with price greater than given
132143
greaterThan = (price: number): OrderQueue | undefined => {
133-
const node = this._priceTree.gt(price)
144+
const node =
145+
this._side === Side.SELL
146+
? this._priceTree.gt(price)
147+
: this._priceTree.lt(price)
134148
return node.value
135149
}
136150

test/orderbook.test.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ const addDepth = (ob: OrderBook, prefix: string, quantity: number) => {
77
for (let index = 50; index < 100; index += 10) {
88
ob.limit(Side.BUY, `${prefix}buy-${index}`, quantity, index)
99
}
10-
1110
for (let index = 100; index < 150; index += 10) {
1211
ob.limit(Side.SELL, `${prefix}sell-${index}`, quantity, index)
1312
}
14-
1513
return
1614
}
1715

1816
describe('OrderBook', () => {
17+
// First test the addDepth function used by all the other test
18+
test('test addDepth testing function', () => {
19+
const ob = new OrderBook()
20+
addDepth(ob, '', 10)
21+
expect(ob.toString()).toBe(
22+
`\n140 -> 10\n130 -> 10\n120 -> 10\n110 -> 10\n100 -> 10\r\n------------------------------------\n90 -> 10\n80 -> 10\n70 -> 10\n60 -> 10\n50 -> 10`
23+
)
24+
})
1925
test('test limit place', () => {
2026
const ob = new OrderBook()
2127
const size = 2
@@ -45,7 +51,7 @@ describe('OrderBook', () => {
4551
depth.forEach((side, index) => {
4652
side.forEach((level, subIndex) => {
4753
expect(level[1]).toBe(2)
48-
let price = index === 0 ? 140 - 10 * subIndex : 90 - 10 * subIndex
54+
let price = index === 0 ? 100 + 10 * subIndex : 90 - 10 * subIndex
4955
expect(level[0]).toBe(price)
5056
})
5157
})
@@ -197,7 +203,7 @@ describe('OrderBook', () => {
197203

198204
const processFOKSell = ob.limit(
199205
Side.SELL,
200-
'order-fok-s80',
206+
'order-fok-sell-4-70',
201207
4,
202208
70,
203209
TimeInForce.FOK
@@ -342,11 +348,4 @@ describe('OrderBook', () => {
342348
expect(calc4.err?.message).toBe(ERROR.ErrInsufficientQuantity)
343349
expect(calc4.price).toBe(10500)
344350
})
345-
test('test priceCalculation', () => {
346-
const ob = new OrderBook()
347-
addDepth(ob, '', 10)
348-
expect(ob.toString()).toBe(
349-
`\n140 -> 10\n130 -> 10\n120 -> 10\n110 -> 10\n100 -> 10\r\n------------------------------------\n90 -> 10\n80 -> 10\n70 -> 10\n60 -> 10\n50 -> 10`
350-
)
351-
})
352351
})

test/orderside.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ERROR } from '../src/errors'
55

66
describe('OrderSide', () => {
77
test('it should append/update/remove orders from queue', () => {
8-
const os = new OrderSide()
8+
const os = new OrderSide(Side.SELL)
99
const order1 = new Order('order1', Side.SELL, 5, 10)
1010
const order2 = new Order('order2', Side.SELL, 5, 20)
1111

0 commit comments

Comments
 (0)