Skip to content

Commit 2444ab9

Browse files
authored
feat(orderbook): don’t match with quantity on hold (#697)
* don’t match with quantity on hold * lint * PR fixes * PR fix
1 parent 1b88899 commit 2444ab9

File tree

2 files changed

+76
-4
lines changed

2 files changed

+76
-4
lines changed

lib/orderbook/TradingPair.ts

+21-4
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ class TradingPair {
292292
let remainingOrder: OwnOrder | undefined = { ...takerOrder };
293293

294294
const queue = takerOrder.isBuy ? this.queues!.sell : this.queues!.buy;
295+
const queueRemovedOrdersWithHold: OwnOrder[] = [];
295296
const getMatchingQuantity = (remainingOrder: OwnOrder, oppositeOrder: Order) => takerOrder.isBuy
296297
? TradingPair.getMatchingQuantity(remainingOrder, oppositeOrder)
297298
: TradingPair.getMatchingQuantity(oppositeOrder, remainingOrder);
@@ -300,13 +301,18 @@ class TradingPair {
300301
while (remainingOrder && !queue.isEmpty()) {
301302
// get the best available maker order from the top of the queue
302303
const makerOrder = queue.peek()!;
303-
const matchingQuantity = getMatchingQuantity(remainingOrder, makerOrder);
304+
const makerAvailableQuantityOrder = isOwnOrder(makerOrder)
305+
? { ...makerOrder, quantity: makerOrder.quantity - makerOrder.hold, hold: 0 }
306+
: makerOrder;
307+
308+
const matchingQuantity = getMatchingQuantity(remainingOrder, makerAvailableQuantityOrder);
304309
if (matchingQuantity <= 0) {
305310
// there's no match with the best available maker order, so end the matching routine
306311
break;
307312
} else {
308313
/** Whether the maker order is fully matched and should be removed from the queue. */
309314
const makerFullyMatched = makerOrder.quantity === matchingQuantity;
315+
const makerAvailableQuantityFullyMatched = makerAvailableQuantityOrder.quantity === matchingQuantity;
310316
const remainingFullyMatched = remainingOrder.quantity === matchingQuantity;
311317

312318
if (makerFullyMatched && remainingFullyMatched) {
@@ -317,27 +323,38 @@ class TradingPair {
317323
const matchedMakerOrder = TradingPair.splitOrderByQuantity(makerOrder, matchingQuantity);
318324
this.logger.debug(`reduced order ${makerOrder.id} by ${matchingQuantity} quantity while matching order ${takerOrder.id}`);
319325
matches.push({ maker: matchedMakerOrder, taker: remainingOrder });
320-
} else if (makerFullyMatched) {
326+
} else if (makerAvailableQuantityFullyMatched) {
321327
// maker order quantity is not sufficient. taker order will split
322328
const matchedTakerOrder = TradingPair.splitOrderByQuantity(remainingOrder, matchingQuantity);
323-
matches.push({ maker: makerOrder, taker: matchedTakerOrder });
329+
matches.push({ maker: makerAvailableQuantityOrder, taker: matchedTakerOrder });
324330
} else {
325-
assert(false, 'matchingQuantity should not be lower than both orders quantity values');
331+
assert(false, 'matchingQuantity should not be lower than both orders available quantity values');
326332
}
327333

328334
if (remainingFullyMatched) {
329335
remainingOrder = undefined;
330336
}
337+
331338
if (makerFullyMatched) {
332339
// maker order is fully matched, so remove it from the queue and map
333340
assert(queue.poll() === makerOrder);
334341
const map = this.getOrderMap(makerOrder)!;
335342
map.delete(makerOrder.id);
336343
this.logger.debug(`removed order ${makerOrder.id} while matching order ${takerOrder.id}`);
344+
} else if (makerAvailableQuantityFullyMatched) {
345+
// only an own order can be fully matched for available quantity, but not fully matched in the overall
346+
assert(isOwnOrder(makerOrder));
347+
348+
assert(queue.poll() === makerOrder);
349+
queueRemovedOrdersWithHold.push(makerOrder as OwnOrder);
337350
}
338351
}
339352
}
340353

354+
// return the removed orders with hold to the queue.
355+
// their hold quantity might be released later
356+
queueRemovedOrdersWithHold.forEach(order => queue.add(order));
357+
341358
return { matches, remainingOrder };
342359
}
343360
}

test/unit/TradingPair.spec.ts

+55
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,61 @@ describe('TradingPair.match', () => {
190190
expect(peekResult).to.not.be.undefined;
191191
expect(peekResult!.quantity).to.equal(1);
192192
});
193+
194+
it('should not match maker own order hold quantity', () => {
195+
const ownOrder = createOwnOrder(5, 5, false);
196+
tp.addOwnOrder(ownOrder);
197+
tp.addOrderHold(ownOrder.id, 5);
198+
199+
const { matches, remainingOrder } = tp.match(createOwnOrder(5, 5, true));
200+
expect(remainingOrder).to.not.be.undefined;
201+
expect(remainingOrder!.quantity).to.equal(5);
202+
expect(matches.length).to.be.equal(0);
203+
});
204+
205+
it('should not match maker own order hold quantity, and should split the taker order', () => {
206+
const ownOrder = createOwnOrder(5, 5, false);
207+
tp.addOwnOrder(ownOrder);
208+
tp.addOrderHold(ownOrder.id, 3);
209+
210+
const { matches, remainingOrder } = tp.match(createOwnOrder(5, 5, true));
211+
expect(remainingOrder).to.not.be.undefined;
212+
expect(remainingOrder!.quantity).to.equal(3);
213+
expect(matches.length).to.be.equal(1);
214+
expect(matches[0].maker.quantity).to.be.equal(2);
215+
expect(matches[0].taker.quantity).to.be.equal(2);
216+
});
217+
218+
it('should not match maker own order hold quantity, and should fully match the taker order', () => {
219+
const ownOrder = createOwnOrder(5, 5, false);
220+
tp.addOwnOrder(ownOrder);
221+
tp.addOrderHold(ownOrder.id, 3);
222+
223+
const { matches, remainingOrder } = tp.match(createOwnOrder(5, 2, true));
224+
expect(remainingOrder).to.be.undefined;
225+
expect(matches.length).to.be.equal(1);
226+
expect(matches[0].maker.quantity).to.be.equal(2);
227+
expect(matches[0].taker.quantity).to.be.equal(2);
228+
});
229+
230+
it('should not match maker own order hold quantity, but keep the order for the next match', () => {
231+
const ownOrder = createOwnOrder(5, 5, false);
232+
tp.addOwnOrder(ownOrder);
233+
tp.addOrderHold(ownOrder.id, 5);
234+
235+
let mr = tp.match(createOwnOrder(5, 5, true));
236+
expect(mr.remainingOrder).to.not.be.undefined;
237+
expect(mr.remainingOrder!.quantity).to.equal(5);
238+
expect(mr.matches.length).to.be.equal(0);
239+
240+
tp.removeOrderHold(ownOrder.id, 5);
241+
242+
mr = tp.match(createOwnOrder(5, 5, true));
243+
expect(mr.remainingOrder).to.be.undefined;
244+
expect(mr.matches.length).to.be.equal(1);
245+
expect(mr.matches[0].maker.quantity).to.be.equal(5);
246+
expect(mr.matches[0].taker.quantity).to.be.equal(5);
247+
});
193248
});
194249

195250
describe('TradingPair.removeOwnOrder', () => {

0 commit comments

Comments
 (0)