/***************************************************************************** * 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 #include #include #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 #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; }