From 2444ab99b261da6284a3e5ab2cfd06f23100c717 Mon Sep 17 00:00:00 2001 From: Moshe Shababo Date: Fri, 30 Nov 2018 15:03:37 +0200 Subject: [PATCH] =?UTF-8?q?feat(orderbook):=20don=E2=80=99t=20match=20with?= =?UTF-8?q?=20quantity=20on=20hold=20(#697)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * don’t match with quantity on hold * lint * PR fixes * PR fix --- lib/orderbook/TradingPair.ts | 25 +++++++++++++--- test/unit/TradingPair.spec.ts | 55 +++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/lib/orderbook/TradingPair.ts b/lib/orderbook/TradingPair.ts index 0e588b0d3..c3f172e68 100644 --- a/lib/orderbook/TradingPair.ts +++ b/lib/orderbook/TradingPair.ts @@ -292,6 +292,7 @@ class TradingPair { let remainingOrder: OwnOrder | undefined = { ...takerOrder }; const queue = takerOrder.isBuy ? this.queues!.sell : this.queues!.buy; + const queueRemovedOrdersWithHold: OwnOrder[] = []; const getMatchingQuantity = (remainingOrder: OwnOrder, oppositeOrder: Order) => takerOrder.isBuy ? TradingPair.getMatchingQuantity(remainingOrder, oppositeOrder) : TradingPair.getMatchingQuantity(oppositeOrder, remainingOrder); @@ -300,13 +301,18 @@ class TradingPair { while (remainingOrder && !queue.isEmpty()) { // get the best available maker order from the top of the queue const makerOrder = queue.peek()!; - const matchingQuantity = getMatchingQuantity(remainingOrder, makerOrder); + const makerAvailableQuantityOrder = isOwnOrder(makerOrder) + ? { ...makerOrder, quantity: makerOrder.quantity - makerOrder.hold, hold: 0 } + : makerOrder; + + const matchingQuantity = getMatchingQuantity(remainingOrder, makerAvailableQuantityOrder); if (matchingQuantity <= 0) { // there's no match with the best available maker order, so end the matching routine break; } else { /** Whether the maker order is fully matched and should be removed from the queue. */ const makerFullyMatched = makerOrder.quantity === matchingQuantity; + const makerAvailableQuantityFullyMatched = makerAvailableQuantityOrder.quantity === matchingQuantity; const remainingFullyMatched = remainingOrder.quantity === matchingQuantity; if (makerFullyMatched && remainingFullyMatched) { @@ -317,27 +323,38 @@ class TradingPair { const matchedMakerOrder = TradingPair.splitOrderByQuantity(makerOrder, matchingQuantity); this.logger.debug(`reduced order ${makerOrder.id} by ${matchingQuantity} quantity while matching order ${takerOrder.id}`); matches.push({ maker: matchedMakerOrder, taker: remainingOrder }); - } else if (makerFullyMatched) { + } else if (makerAvailableQuantityFullyMatched) { // maker order quantity is not sufficient. taker order will split const matchedTakerOrder = TradingPair.splitOrderByQuantity(remainingOrder, matchingQuantity); - matches.push({ maker: makerOrder, taker: matchedTakerOrder }); + matches.push({ maker: makerAvailableQuantityOrder, taker: matchedTakerOrder }); } else { - assert(false, 'matchingQuantity should not be lower than both orders quantity values'); + assert(false, 'matchingQuantity should not be lower than both orders available quantity values'); } if (remainingFullyMatched) { remainingOrder = undefined; } + if (makerFullyMatched) { // maker order is fully matched, so remove it from the queue and map assert(queue.poll() === makerOrder); const map = this.getOrderMap(makerOrder)!; map.delete(makerOrder.id); this.logger.debug(`removed order ${makerOrder.id} while matching order ${takerOrder.id}`); + } else if (makerAvailableQuantityFullyMatched) { + // only an own order can be fully matched for available quantity, but not fully matched in the overall + assert(isOwnOrder(makerOrder)); + + assert(queue.poll() === makerOrder); + queueRemovedOrdersWithHold.push(makerOrder as OwnOrder); } } } + // return the removed orders with hold to the queue. + // their hold quantity might be released later + queueRemovedOrdersWithHold.forEach(order => queue.add(order)); + return { matches, remainingOrder }; } } diff --git a/test/unit/TradingPair.spec.ts b/test/unit/TradingPair.spec.ts index 2dae1622a..fbf4b6783 100644 --- a/test/unit/TradingPair.spec.ts +++ b/test/unit/TradingPair.spec.ts @@ -190,6 +190,61 @@ describe('TradingPair.match', () => { expect(peekResult).to.not.be.undefined; expect(peekResult!.quantity).to.equal(1); }); + + it('should not match maker own order hold quantity', () => { + const ownOrder = createOwnOrder(5, 5, false); + tp.addOwnOrder(ownOrder); + tp.addOrderHold(ownOrder.id, 5); + + const { matches, remainingOrder } = tp.match(createOwnOrder(5, 5, true)); + expect(remainingOrder).to.not.be.undefined; + expect(remainingOrder!.quantity).to.equal(5); + expect(matches.length).to.be.equal(0); + }); + + it('should not match maker own order hold quantity, and should split the taker order', () => { + const ownOrder = createOwnOrder(5, 5, false); + tp.addOwnOrder(ownOrder); + tp.addOrderHold(ownOrder.id, 3); + + const { matches, remainingOrder } = tp.match(createOwnOrder(5, 5, true)); + expect(remainingOrder).to.not.be.undefined; + expect(remainingOrder!.quantity).to.equal(3); + expect(matches.length).to.be.equal(1); + expect(matches[0].maker.quantity).to.be.equal(2); + expect(matches[0].taker.quantity).to.be.equal(2); + }); + + it('should not match maker own order hold quantity, and should fully match the taker order', () => { + const ownOrder = createOwnOrder(5, 5, false); + tp.addOwnOrder(ownOrder); + tp.addOrderHold(ownOrder.id, 3); + + const { matches, remainingOrder } = tp.match(createOwnOrder(5, 2, true)); + expect(remainingOrder).to.be.undefined; + expect(matches.length).to.be.equal(1); + expect(matches[0].maker.quantity).to.be.equal(2); + expect(matches[0].taker.quantity).to.be.equal(2); + }); + + it('should not match maker own order hold quantity, but keep the order for the next match', () => { + const ownOrder = createOwnOrder(5, 5, false); + tp.addOwnOrder(ownOrder); + tp.addOrderHold(ownOrder.id, 5); + + let mr = tp.match(createOwnOrder(5, 5, true)); + expect(mr.remainingOrder).to.not.be.undefined; + expect(mr.remainingOrder!.quantity).to.equal(5); + expect(mr.matches.length).to.be.equal(0); + + tp.removeOrderHold(ownOrder.id, 5); + + mr = tp.match(createOwnOrder(5, 5, true)); + expect(mr.remainingOrder).to.be.undefined; + expect(mr.matches.length).to.be.equal(1); + expect(mr.matches[0].maker.quantity).to.be.equal(5); + expect(mr.matches[0].taker.quantity).to.be.equal(5); + }); }); describe('TradingPair.removeOwnOrder', () => {