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', () => {