Skip to content

Instantly share code, notes, and snippets.

@Jud
Created June 1, 2012 23:56
Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 24 You must be signed in to fork a gist
  • Save Jud/2855852 to your computer and use it in GitHub Desktop.
Save Jud/2855852 to your computer and use it in GitHub Desktop.
Price-Time Matching Engine
/*****************************************************************************
* QuantCup 1: Price-Time Matching Engine
*
* Submitted by: voyager
*
* Design Overview:
* In this implementation, the limit order book is represented using
* a flat linear array (pricePoints), indexed by the numeric price value.
* Each entry in this array corresponds to a specific price point and holds
* an instance of struct pricePoint. This data structure maintains a list
* of outstanding buy/sell orders at the respective price. Each outstanding
* limit order is represented by an instance of struct orderBookEntry.
*
* askMin and bidMax are global variables that maintain starting points,
* at which the matching algorithm initiates its search.
* askMin holds the lowest price that contains at least one outstanding
* sell order. Analogously, bidMax represents the maximum price point that
* contains at least one outstanding buy order.
*
* When a Buy order arrives, we search the book for outstanding Sell orders
* that cross with the incoming order. We start the search at askMin and
* proceed upwards, incrementing askMin until:
* a) The incoming Buy order is filled.
* b) We reach a price point that no longer crosses with the incoming
* limit price (askMin > BuyOrder.price)
* In case b), we create a new orderBookEntry to record the
* remainder of the incoming Buy order and add it to the global order
* book by appending it to the list at pricePoints[BuyOrder.price].
*
* Incoming Sell orders are handled analogously, except that we start at
* bidMax and proceed downwards.
*
* Although this matching algorithm runs in linear time and may, in
* degenerate cases, require scanning a large number of array slots,
* it appears to work reasonably well in practice, at least on the
* simulated data feed (score_feed.h). The vast majority of incoming
* limit orders can be handled by examining no more than two distinct
* price points and no order requires examining more than five price points.
*
* To avoid incurring the costs of dynamic heap-based memory allocation,
* this implementation maintains the full set of orderBookEntry instances
* in a statically-allocated contiguous memory arena (arenaBookEntries).
* Allocating a new entry is simply a matter of bumping up the orderID
* counter (curOrderID) and returning a pointer to arenaBookEntries[curOrderID].
*
* To cancel an order, we simply set its size to zero. Notably, we avoid
* unhooking its orderBookEntry from the list of active orders in order to
* avoid incurring the costs of pointer manipulation and conditional branches.
* This allows us to handle order cancellation requests very efficiently; the
* current implementation requires only one memory store instruction on
* x86_64. During order matching, when we walk the list of outstanding orders,
* we simply skip these zero-sized entries.
*
* The current implementation uses a custom version of strcpy() to copy the string
* fields ("symbol" and "trader") between data structures. This custom version
* has been optimized for the case STRINGLEN=5 and implements loop unrolling
* to eliminate the use of induction variables and conditional branching.
*
* The memory layout of struct orderBookEntry has been optimized for
* efficient cache access.
*****************************************************************************/
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include "engine.h"
/* Enable/disable optimizations */
#define UNROLL_STRCPY
#define MAX_NUM_ORDERS 1010000
// #define DEBUG (enable/disable debugging)
#ifdef DEBUG
#define ASSERT(c) do { \
if (!(c)) { fprintf(stderr, "ASSERT failure at line %d\n", __LINE__); \
exit(1); }} while(0)
#else
#define ASSERT(c)
#endif
#ifdef UNROLL_STRCPY
#define COPY_STRING(dst, src) do { \
dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; \
dst[3] = src[3]; /* dst[4] = src[4]; */ \
} while(0)
#else
#include <string.h>
#define COPY_STRING(dst, src) strcpy(dst, src)
#endif
/* struct orderBookEntry: describes a single outstanding limit order
(Buy or Sell). */
typedef struct orderBookEntry {
t_size size; /* Order size */
struct orderBookEntry *next; /* Next entry in the pricePoint list */
char trader[4];
} orderBookEntry_t;
/* struct pricePoint: describes a single price point in the limit order book. */
typedef struct pricePoint {
orderBookEntry_t *listHead;
orderBookEntry_t *listTail;
} pricePoint_t;
/** Global state ***/
/* An array of pricePoint structures representing the entire limit order book */
static pricePoint_t pricePoints[MAX_PRICE + 1];
static t_orderid curOrderID; /* Monotonically-increasing orderID */
static unsigned int askMin; /* Minimum Ask price */
static unsigned int bidMax; /* Maximum Bid price */
/* Statically-allocated memory arena for order book entries. This data
structure allows us to avoid the overhead of heap-based memory allocation. */
static orderBookEntry_t arenaBookEntries[MAX_NUM_ORDERS];
static orderBookEntry_t *arenaPtr;
#define ALLOC_BOOK_ENTRY(id)
void init() {
/* Initialize the price point array */
bzero(pricePoints, (MAX_PRICE + 1) * sizeof(pricePoint_t));
/* Initialize the memory arena */
bzero(arenaBookEntries, MAX_NUM_ORDERS * sizeof(orderBookEntry_t));
arenaPtr = arenaBookEntries; // Bring the arena pointer into the cache
curOrderID = 0;
askMin = MAX_PRICE + 1;
bidMax = MIN_PRICE - 1;
}
void destroy() { }
/* Insert a new order book entry at the tail of the price point list */
void ppInsertOrder(pricePoint_t *ppEntry, orderBookEntry_t *entry) {
if (ppEntry->listHead != NULL)
ppEntry->listTail->next = entry;
else
ppEntry->listHead = entry;
ppEntry->listTail = entry;
}
/* Report trade execution */
void EXECUTE_TRADE(const char *symbol, const char *buyTrader,
const char *sellTrader, t_price tradePrice,
t_size tradeSize) {
t_execution exec;
if (tradeSize == 0) /* Skip orders that have been cancelled */
return;
COPY_STRING(exec.symbol, symbol);
exec.price = tradePrice;
exec.size = tradeSize;
exec.side = 0;
COPY_STRING(exec.trader, buyTrader);
exec.trader[4] = '\0';
execution(exec); /* Report the buy-side trade */
exec.side = 1;
COPY_STRING(exec.trader, sellTrader);
exec.trader[4] = '\0';
execution(exec); /* Report the sell-side trade */
}
/* Process an incoming limit order */
t_orderid limit(t_order order) {
orderBookEntry_t *bookEntry;
orderBookEntry_t *entry;
pricePoint_t *ppEntry;
t_price price = order.price;
t_size orderSize = order.size;
if (order.side == 0) { /* Buy order */
/* Look for outstanding sell orders that cross with the incoming order */
if (price >= askMin) {
ppEntry = pricePoints + askMin;
do {
bookEntry = ppEntry->listHead;
while(bookEntry != NULL) {
if (bookEntry->size < orderSize) {
EXECUTE_TRADE(order.symbol, order.trader,
bookEntry->trader, price, bookEntry->size);
orderSize -= bookEntry->size;
bookEntry = bookEntry->next;
} else {
EXECUTE_TRADE(order.symbol, order.trader,
bookEntry->trader, price, orderSize);
if (bookEntry->size > orderSize)
bookEntry->size -= orderSize;
else
bookEntry = bookEntry->next;
ppEntry->listHead = bookEntry;
return ++curOrderID;
}
}
/* We have exhausted all orders at the askMin price point. Move on to
the next price level. */
ppEntry->listHead = NULL;
ppEntry++;
askMin++;
} while(price >= askMin);
}
entry = arenaBookEntries + (++curOrderID);
entry->size = orderSize;
COPY_STRING(entry->trader, order.trader);
ppInsertOrder(&pricePoints[price], entry);
if (bidMax < price)
bidMax = price;
return curOrderID;
} else { /* Sell order */
/* Look for outstanding Buy orders that cross with the incoming order */
if (price <= bidMax) {
ppEntry = pricePoints + bidMax;
do {
bookEntry = ppEntry->listHead;
while(bookEntry != NULL) {
if (bookEntry->size < orderSize) {
EXECUTE_TRADE(order.symbol, bookEntry->trader,
order.trader, price, bookEntry->size);
orderSize -= bookEntry->size;
bookEntry = bookEntry->next;
} else {
EXECUTE_TRADE(order.symbol, bookEntry->trader,
order.trader,price, orderSize);
if (bookEntry->size > orderSize)
bookEntry->size -= orderSize;
else
bookEntry = bookEntry->next;
ppEntry->listHead = bookEntry;
return ++curOrderID;
}
}
/* We have exhausted all orders at the bidMax price point. Move on to
the next price level. */
ppEntry->listHead = NULL;
ppEntry--;
bidMax--;
} while (price <= bidMax);
}
entry = arenaBookEntries + (++curOrderID);
entry->size = orderSize;
COPY_STRING(entry->trader, order.trader);
ppInsertOrder(&pricePoints[price], entry);
if (askMin > price)
askMin = price;
return curOrderID;
}
}
/* Cancel an outstanding order */
void cancel(t_orderid orderid) {
arenaBookEntries[orderid].size = 0;
}
@raferbop
Copy link

raferbop commented Nov 4, 2012

Were any modifications made to this code to enhance its functionality within the last 5 months?

@dannynash
Copy link

Dear Sir,

Thanks for your sharing.
It's an awesome match engine.
What kind of open source license is it?

Thanks.

@adrianog
Copy link

Is MAX_PRICE tick-based?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment