Algorithms

Orderbook


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

🎰