Skip to content

Commit ca57f50

Browse files
committed
perf(swaps/orderbook): placeOrder parallel swaps
This commit begins all swaps in parallel when placing/matching an order and retries failed swaps immediately. Any unmatched quantity is tracked and added to the orderbook (for limit orders) after matching and swaps are complete. Previously, swaps would execute one at a time and any failed quantity would wait to be retried until all first-attempt swaps completed. This should result in significantly faster order execution for orders that match with multiple peer orders and therefore require multiple swaps. Closes #654.
1 parent c41c7c9 commit ca57f50

File tree

1 file changed

+40
-33
lines changed

1 file changed

+40
-33
lines changed

lib/orderbook/OrderBook.ts

+40-33
Original file line numberDiff line numberDiff line change
@@ -288,25 +288,37 @@ class OrderBook extends EventEmitter {
288288
// perform match. maker orders will be removed from the repository
289289
const tp = this.getTradingPair(order.pairId);
290290
const matchingResult = tp.match(order);
291-
292-
// instantiate the final response object
293-
const result: PlaceOrderResult = {
294-
internalMatches: [],
295-
swapResults: [],
296-
remainingOrder: matchingResult.remainingOrder,
291+
/** Any portion of the placed order that could not be swapped or matched internally. */
292+
let { remainingOrder } = matchingResult;
293+
/** Local orders that matched with the placed order. */
294+
const internalMatches: OwnOrder[] = [];
295+
/** Successful swaps performed for the placed order. */
296+
const swapResults: SwapResult[] = [];
297+
298+
const retryFailedSwap = async (failedSwapQuantity: number) => {
299+
this.logger.debug(`repeating matching routine for ${order.id} for failed quantity of ${failedSwapQuantity}`);
300+
const orderToRetry: OwnOrder = { ...order, quantity: failedSwapQuantity };
301+
302+
// invoke placeOrder recursively, append matches/swaps and any remaining order
303+
const retryResult = await this.placeOrder(orderToRetry, true, onUpdate, maxTime);
304+
internalMatches.push(...retryResult.internalMatches);
305+
swapResults.push(...retryResult.swapResults);
306+
if (retryResult.remainingOrder) {
307+
if (remainingOrder) {
308+
remainingOrder.quantity += retryResult.remainingOrder.quantity;
309+
} else {
310+
remainingOrder = retryResult.remainingOrder;
311+
}
312+
}
297313
};
298314

299-
/** The quantity that was attempted to be swapped but failed. */
300-
let failedSwapQuantity = 0;
301-
302-
// iterate over the matches
303-
for (const { maker, taker } of matchingResult.matches) {
315+
const handleMatch = async (maker: Order, taker: OwnOrder) => {
304316
const portion: OrderPortion = { id: maker.id, pairId: maker.pairId, quantity: maker.quantity };
305317
if (isOwnOrder(maker)) {
306318
// internal match
307319
this.logger.info(`internal match executed on taker ${taker.id} and maker ${maker.id} for ${maker.quantity}`);
308320
portion.localId = maker.localId;
309-
result.internalMatches.push(maker);
321+
internalMatches.push(maker);
310322
this.emit('ownOrder.filled', portion);
311323
await this.persistTrade(portion.quantity, maker, taker);
312324
onUpdate && onUpdate({ type: PlaceOrderEventType.InternalMatch, payload: maker });
@@ -315,8 +327,7 @@ class OrderBook extends EventEmitter {
315327
// swaps should only be undefined during integration testing of the order book
316328
// for now we treat this case like a swap failure
317329
this.emit('peerOrder.invalidation', portion);
318-
failedSwapQuantity += portion.quantity;
319-
continue;
330+
return;
320331
}
321332

322333
try {
@@ -327,43 +338,39 @@ class OrderBook extends EventEmitter {
327338
// swap was only partially completed
328339
portion.quantity = swapResult.quantity;
329340
const rejectedQuantity = maker.quantity - swapResult.quantity;
330-
failedSwapQuantity += rejectedQuantity; // add unswapped quantity to failed quantity
331341
this.logger.info(`match partially executed on taker ${taker.id} and maker ${maker.id} for ${swapResult.quantity} ` +
332342
`with peer ${maker.peerPubKey}, ${rejectedQuantity} quantity not accepted and will repeat matching routine`);
343+
await retryFailedSwap(rejectedQuantity);
333344
} else {
334345
this.logger.info(`match executed on taker ${taker.id} and maker ${maker.id} for ${maker.quantity} with peer ${maker.peerPubKey}`);
335346
}
336-
result.swapResults.push(swapResult);
347+
swapResults.push(swapResult);
337348
onUpdate && onUpdate({ type: PlaceOrderEventType.SwapSuccess, payload: swapResult });
338349
} catch (err) {
339-
failedSwapQuantity += portion.quantity;
340-
onUpdate && onUpdate({ type: PlaceOrderEventType.SwapFailure, payload: maker });
341350
this.logger.warn(`swap for ${portion.quantity} failed during order matching, will repeat matching routine for failed swap quantity`);
351+
onUpdate && onUpdate({ type: PlaceOrderEventType.SwapFailure, payload: maker });
352+
await retryFailedSwap(portion.quantity);
342353
}
343354
}
344-
}
345-
346-
// if we have swap failures, attempt one retry for all available quantity. don't re-add the maker orders
347-
if (failedSwapQuantity > 0) {
348-
this.logger.debug(`${failedSwapQuantity} quantity of swaps failed for order ${order.id}, repeating matching routine for failed quantity`);
349-
// aggregate failures quantities with the remaining order
350-
const remainingOrder: OwnOrder = result.remainingOrder || { ...order, quantity: 0 };
351-
remainingOrder.quantity += failedSwapQuantity;
355+
};
352356

353-
// invoke placeOrder recursively, append matches/swaps and set the consecutive remaining order
354-
const remainingOrderResult = await this.placeOrder(remainingOrder, true, onUpdate, maxTime);
355-
result.internalMatches.push(...remainingOrderResult.internalMatches);
356-
result.swapResults.push(...remainingOrderResult.swapResults);
357-
result.remainingOrder = remainingOrderResult.remainingOrder;
357+
// iterate over the matches
358+
const matchPromises: Promise<void>[] = [];
359+
for (const { maker, taker } of matchingResult.matches) {
360+
matchPromises.push(handleMatch(maker, taker));
358361
}
362+
await Promise.all(matchPromises);
359363

360-
const { remainingOrder } = result;
361364
if (remainingOrder && !discardRemaining) {
362365
this.addOwnOrder(remainingOrder);
363366
onUpdate && onUpdate({ type: PlaceOrderEventType.RemainingOrder, payload: remainingOrder });
364367
}
365368

366-
return result;
369+
return {
370+
internalMatches,
371+
swapResults,
372+
remainingOrder,
373+
};
367374
}
368375

369376
/**

0 commit comments

Comments
 (0)