Skip to content

Latest commit

 

History

History
468 lines (371 loc) · 16.5 KB

README.md

File metadata and controls

468 lines (371 loc) · 16.5 KB

CoralME

A simple, fast and garbage-free matching engine order book that you can use as a starting point for your matching engines.

What is it?

CoralME is an order book data-structure that matches orders based on price-time priority. It maintains limit orders resting in an order book until they are either canceled or filled. Whenever an order changes its state, a callback is issued to registered listeners.

What people usually mean by the term Matching Engine?

Usually when people talk about a Matching Engine, what they are really referring to is the full solution for an electronic exchange. That would include gateways, drop copies, market data, balances, reports, monitors, margins, compliance, fees, etc. Plus the messaging middleware to tie all these pieces together. In that context, the matching engine is really just one of the many parts of an electronic exchange. It is an important part, the central nervous systems of an exchange, which maintains orders resting inside order books, and match them when liquidity takers meet liquidity providers (i.e. market makers).

For a detailed discussion of how a first-class electronic exchange can be built from the ground up using the sequencer architecture you should refer to this article.

Quick Start

Refer to Example.java for a bunch of order matching use-cases.

The OrderBookTest.java might give you some good ideas as well but I find the Example.java easier to follow.

Features

  • Fast
  • Garbage-free
  • Callback oriented
  • Price levels
  • Price improvement for fills
  • MARKET and LIMIT order types
  • IOC, GTC and DAY
  • MAKER (of liquidity) and TAKER (of liquidity) execution sides
  • NORMAL, CROSSED, LOCKED, ONESIDED and EMPTY book states
  • ClientID, ClientOrderID and OrderID
  • ExecutionID and ExecutionMatchID
  • Can optionally check and disallow trade to self

How can I check that it is zero garbage?

Check NoGCTest.java to see that it creates a book and populates this book with 10 orders one million times. And on each of these one million times it does a bunch of executions, rejects, cancelations, reduces, etc. Run this test with -verbose:gc -Xms128m -Xmx256m and you will always see zero GC activity. No matter how many iterations you perform, the gc activity is always zero. If you want to see some GC activity, you can turn on a flag that forces the creation of garbage by producing some strings in the middle of the loop.

Creating ZERO garbage
$ ./bin/runGCTest.sh
java -verbose:gc -Xms128m -Xmx256m -cp target/classes com.coralblocks.coralme.example.NoGCTest false 1000000
1000000 ... DONE!
Forcing the creation of garbage (pass true to runGCTest.sh)
$ ./bin/runGCTest.sh true
java -verbose:gc -Xms128m -Xmx256m -cp target/classes com.coralblocks.coralme.example.NoGCTest true 1000000
60870[GC (Allocation Failure)  33280K->1224K(125952K), 0.0005392 secs]
146061[GC (Allocation Failure)  34504K->1200K(125952K), 0.0005032 secs]
231254[GC (Allocation Failure)  34480K->1240K(125952K), 0.0003991 secs]
316449[GC (Allocation Failure)  34520K->1208K(125952K), 0.0004686 secs]
401642[GC (Allocation Failure)  34488K->1264K(125952K), 0.0004315 secs]
486832[GC (Allocation Failure)  34544K->1200K(129536K), 0.0004712 secs]
590373[GC (Allocation Failure)  41648K->1128K(129536K), 0.0005286 secs]
693917[GC (Allocation Failure)  41576K->1128K(128512K), 0.0002270 secs]
794836[GC (Allocation Failure)  40552K->1128K(129024K), 0.0002390 secs]
895759[GC (Allocation Failure)  40552K->1128K(129024K), 0.0002202 secs]
996679[GC (Allocation Failure)  40552K->1128K(129024K), 0.0002492 secs]
1000000 ... DONE!

Callbacks Supported

public interface OrderBookListener {
    
    public void onOrderReduced(OrderBook orderBook, long time, Order order, 
                                 long reduceNewTotalSize);
    
    public void onOrderCanceled(OrderBook orderBook, long time, Order order, 
                                  CancelReason cancelReason);
    
    public void onOrderExecuted(OrderBook orderBook, long time, Order order, 
                                  ExecuteSide executeSide, long executeSize, 
                                  long executePrice, long executeId, long executeMatchId);
    
    public void onOrderAccepted(OrderBook orderBook, long time, Order order);
    
    public void onOrderRejected(OrderBook orderBook, long time, Order order, 
                                  RejectReason rejectReason);
    
    public void onOrderRested(OrderBook orderBook, long time, Order order,
                                long restSize, long restPrice);

    public void onOrderTerminated(OrderBook orderBook, long time, Order order);
    
}

Code Snippet

final long CLIENT_ID = 1001L;

long orderId = 0;

// This OrderBookListener will print all callbacks to System.out
OrderBookLogger orderBookLogger = new OrderBookLogger();

OrderBook orderBook = new OrderBook("AAPL", orderBookLogger);

orderBook.createLimit(CLIENT_ID, String.valueOf(++orderId), orderId, Side.BUY, 200, 150.44, TimeInForce.DAY);

/*
	-----> onOrderAccepted called:
	  orderBook=AAPL
	  time=1700598048045000000
	  order=Order [id=1, clientId=1001, clientOrderId=1, side=BUY, security=AAPL, originalSize=200, openSize=200, 
					executedSize=0, canceledSize=0, price=150.44, type=LIMIT, tif=DAY]
	
	-----> onOrderRested called:
	  orderBook=AAPL
	  time=1700598048047000000
	  order=Order [id=1, clientId=1001, clientOrderId=1, side=BUY, security=AAPL, originalSize=200, openSize=200, 
					executedSize=0, canceledSize=0, price=150.44, type=LIMIT, tif=DAY]
	  restSize=200
	  restPrice=150.44
*/

orderBook.showLevels();

/*
   200 @    150.44 (orders=1)
-------- 
*/

orderBook.showOrders();

/*
   200 @    150.44 (id=1)
-------- 			  
*/

orderBook.createLimit(CLIENT_ID, String.valueOf(++orderId), orderId, Side.BUY, 500, 149.44, TimeInForce.DAY);

/* 
	-----> onOrderAccepted called:
	  orderBook=AAPL
	  time=1700598048049000000
	  order=Order [id=2, clientId=1001, clientOrderId=2, side=BUY, security=AAPL, originalSize=500, openSize=500, 
					executedSize=0, canceledSize=0, price=149.44, type=LIMIT, tif=DAY]
	
	-----> onOrderRested called:
	  orderBook=AAPL
	  time=1700598048050000000
	  order=Order [id=2, clientId=1001, clientOrderId=2, side=BUY, security=AAPL, originalSize=500, openSize=500, 
					executedSize=0, canceledSize=0, price=149.44, type=LIMIT, tif=DAY]
	  restSize=500
	  restPrice=149.44	
*/

orderBookLogger.off(); // omit callbacks output for clarity

orderBook.createLimit(CLIENT_ID, String.valueOf(++orderId), orderId, Side.BUY, 100, 149.44, TimeInForce.GTC);
orderBook.createLimit(CLIENT_ID, String.valueOf(++orderId), orderId, Side.BUY, 100, 148.14, TimeInForce.DAY);

orderBook.createLimit(CLIENT_ID, String.valueOf(++orderId), orderId, Side.SELL, 300, 153.24, TimeInForce.GTC);
orderBook.createLimit(CLIENT_ID, String.valueOf(++orderId), orderId, Side.SELL, 500, 156.43, TimeInForce.DAY);
orderBook.createLimit(CLIENT_ID, String.valueOf(++orderId), orderId, Side.SELL, 1500, 158.54, TimeInForce.DAY);

orderBook.showLevels();

/*
   100 @    148.14 (orders=1)
   600 @    149.44 (orders=2)
   200 @    150.44 (orders=1)
--------      2.80
   300 @    153.24 (orders=1)
   500 @    156.43 (orders=1)
  1500 @    158.54 (orders=1)		
*/

orderBook.showOrders();

/*
   100 @    148.14 (id=4)
   500 @    149.44 (id=2)
   100 @    149.44 (id=3)
   200 @    150.44 (id=1)
--------      2.80
   300 @    153.24 (id=5)
   500 @    156.43 (id=6)
  1500 @    158.54 (id=7)
*/

orderBookLogger.on();

// Buy 100 @ market

orderBook.createMarket(CLIENT_ID, String.valueOf(++orderId), orderId, Side.BUY, 100);

/*
	-----> onOrderAccepted called:
	  orderBook=AAPL
	  time=1700598048051000000
	  order=Order [id=8, clientId=1001, clientOrderId=8, side=BUY, security=AAPL, originalSize=100, openSize=100, 
					executedSize=0, canceledSize=0, type=MARKET]
	
	-----> onOrderExecuted called:
	  orderBook=AAPL
	  time=1700598048051000000
	  order=Order [id=5, clientId=1001, clientOrderId=5, side=SELL, security=AAPL, originalSize=300, openSize=200, 
					executedSize=100, canceledSize=0, price=153.24, type=LIMIT, tif=GTC]
	  executeSide=MAKER
	  executeSize=100
	  executePrice=153.24
	  executeId=1
	  executeMatchId=1
	
	-----> onOrderExecuted called:
	  orderBook=AAPL
	  time=1700598048051000000
	  order=Order [id=8, clientId=1001, clientOrderId=8, side=BUY, security=AAPL, originalSize=100, openSize=0, 
					executedSize=100, canceledSize=0, type=MARKET]
	  executeSide=TAKER
	  executeSize=100
	  executePrice=153.24
	  executeId=2
	  executeMatchId=1
	
	-----> onOrderTerminated called:
	  orderBook=AAPL
	  time=1700598048051000000
	  order=Order [id=8, clientId=1001, clientOrderId=8, side=BUY, security=AAPL, originalSize=100, openSize=0, 
					executedSize=100, canceledSize=0, type=MARKET]
*/

orderBook.showLevels();

/*
   100 @    148.14 (orders=1)
   600 @    149.44 (orders=2)
   200 @    150.44 (orders=1)
--------      2.80
   200 @    153.24 (orders=1)
   500 @    156.43 (orders=1)
  1500 @    158.54 (orders=1) 
*/

// reduce order with id = 1 to 100 shares

Order order = orderBook.getOrder(1);
order.reduceTo(orderBook.getTimestamper().nanoEpoch(), 100);

/*
	-----> onOrderReduced called:
	  orderBook=AAPL
	  time=1700598048053000000
	  order=Order [id=1, clientId=1001, clientOrderId=1, side=BUY, security=AAPL, originalSize=200, openSize=100, 
					executedSize=0, canceledSize=100, price=150.44, type=LIMIT, tif=DAY]
	  reduceNewTotalSize=100	
*/

orderBook.showLevels();

/*
   100 @    148.14 (orders=1)
   600 @    149.44 (orders=2)
   100 @    150.44 (orders=1)
--------      2.80
   200 @    153.24 (orders=1)
   500 @    156.43 (orders=1)
  1500 @    158.54 (orders=1)	 
*/

// now cancel the order

order.cancel(orderBook.getTimestamper().nanoEpoch());

/*
	-----> onOrderCanceled called:
	  orderBook=AAPL
	  time=1700598048053000000
	  order=Order [id=1, clientId=1001, clientOrderId=1, side=BUY, security=AAPL, originalSize=200, openSize=0, 
					executedSize=0, canceledSize=200, price=150.44, type=LIMIT, tif=DAY]
	  cancelReason=USER
	
	-----> onOrderTerminated called:
	  orderBook=AAPL
	  time=1700598048053000000
	  order=Order [id=1, clientId=1001, clientOrderId=1, side=BUY, security=AAPL, originalSize=200, openSize=0, 
					executedSize=0, canceledSize=200, price=150.44, type=LIMIT, tif=DAY]
*/

orderBook.showLevels();

/*
   100 @    148.14 (orders=1)
   600 @    149.44 (orders=2)
--------      3.80
   200 @    153.24 (orders=1)
   500 @    156.43 (orders=1)
  1500 @    158.54 (orders=1)	
*/

// hit the sell side of the book with a LIMIT IOC and notice your price improvement

orderBook.createLimit(CLIENT_ID, String.valueOf(++orderId), orderId, Side.BUY, 3000, 155.00, TimeInForce.IOC);

/*
	-----> onOrderAccepted called:
	  orderBook=AAPL
	  time=1700598048054000000
	  order=Order [id=9, clientId=1001, clientOrderId=9, side=BUY, security=AAPL, originalSize=3000, openSize=3000, 
					executedSize=0, canceledSize=0, price=155.0, type=LIMIT, tif=IOC]
	
	-----> onOrderExecuted called:
	  orderBook=AAPL
	  time=1700598048054000000
	  order=Order [id=5, clientId=1001, clientOrderId=5, side=SELL, security=AAPL, originalSize=300, openSize=0, 
					executedSize=300, canceledSize=0, price=153.24, type=LIMIT, tif=GTC]
	  executeSide=MAKER
	  executeSize=200
	  executePrice=153.24
	  executeId=3
	  executeMatchId=2
	
	-----> onOrderTerminated called:
	  orderBook=AAPL
	  time=1700598048054000000
	  order=Order [id=5, clientId=1001, clientOrderId=5, side=SELL, security=AAPL, originalSize=300, openSize=0, 
					executedSize=300, canceledSize=0, price=153.24, type=LIMIT, tif=GTC]
	
	-----> onOrderExecuted called:
	  orderBook=AAPL
	  time=1700598048054000000
	  order=Order [id=9, clientId=1001, clientOrderId=9, side=BUY, security=AAPL, originalSize=3000, openSize=2800, 
					executedSize=200, canceledSize=0, price=155.0, type=LIMIT, tif=IOC]
	  executeSide=TAKER
	  executeSize=200
	  executePrice=153.24
	  executeId=4
	  executeMatchId=2
	
	-----> onOrderCanceled called:
	  orderBook=AAPL
	  time=1700598048055000000
	  order=Order [id=9, clientId=1001, clientOrderId=9, side=BUY, security=AAPL, originalSize=3000, openSize=0, 
					executedSize=200, canceledSize=2800, price=155.0, type=LIMIT, tif=IOC]
	  cancelReason=MISSED
	
	-----> onOrderTerminated called:
	  orderBook=AAPL
	  time=1700598048055000000
	  order=Order [id=9, clientId=1001, clientOrderId=9, side=BUY, security=AAPL, originalSize=3000, openSize=0, 
					executedSize=200, canceledSize=2800, price=155.0, type=LIMIT, tif=IOC]
*/

orderBook.showLevels();

/*
   100 @    148.14 (orders=1)
   600 @    149.44 (orders=2)
--------      6.99
   500 @    156.43 (orders=1)
  1500 @    158.54 (orders=1)		 
*/

orderBookLogger.off();

orderBook.createLimit(CLIENT_ID, String.valueOf(++orderId), orderId, Side.SELL, 3000, 160.00, TimeInForce.DAY);

orderBook.showLevels();

/*
   100 @    148.14 (orders=1)
   600 @    149.44 (orders=2)
--------      6.99
   500 @    156.43 (orders=1)
  1500 @    158.54 (orders=1)
  3000 @    160.00 (orders=1)
*/

// now hit two ask levels, price improve and sit on the book at 159.00

orderBookLogger.on();

orderBook.createLimit(CLIENT_ID, String.valueOf(++orderId), orderId, Side.BUY, 3900, 159.00, TimeInForce.DAY);

/*
	-----> onOrderAccepted called:
	  orderBook=AAPL
	  time=1700598048056000000
	  order=Order [id=11, clientId=1001, clientOrderId=11, side=BUY, security=AAPL, originalSize=3900, openSize=3900, 
					executedSize=0, canceledSize=0, price=159.0, type=LIMIT, tif=DAY]
	
	-----> onOrderExecuted called:
	  orderBook=AAPL
	  time=1700598048056000000
	  order=Order [id=6, clientId=1001, clientOrderId=6, side=SELL, security=AAPL, originalSize=500, openSize=0, 
					executedSize=500, canceledSize=0, price=156.43, type=LIMIT, tif=DAY]
	  executeSide=MAKER
	  executeSize=500
	  executePrice=156.43
	  executeId=5
	  executeMatchId=3
	
	-----> onOrderTerminated called:
	  orderBook=AAPL
	  time=1700598048056000000
	  order=Order [id=6, clientId=1001, clientOrderId=6, side=SELL, security=AAPL, originalSize=500, openSize=0, 
					executedSize=500, canceledSize=0, price=156.43, type=LIMIT, tif=DAY]
	
	-----> onOrderExecuted called:
	  orderBook=AAPL
	  time=1700598048056000000
	  order=Order [id=11, clientId=1001, clientOrderId=11, side=BUY, security=AAPL, originalSize=3900, openSize=3400, 
					executedSize=500, canceledSize=0, price=159.0, type=LIMIT, tif=DAY]
	  executeSide=TAKER
	  executeSize=500
	  executePrice=156.43
	  executeId=6
	  executeMatchId=3
	
	-----> onOrderExecuted called:
	  orderBook=AAPL
	  time=1700598048056000000
	  order=Order [id=7, clientId=1001, clientOrderId=7, side=SELL, security=AAPL, originalSize=1500, openSize=0, 
					executedSize=1500, canceledSize=0, price=158.54, type=LIMIT, tif=DAY]
	  executeSide=MAKER
	  executeSize=1500
	  executePrice=158.54
	  executeId=7
	  executeMatchId=4
	
	-----> onOrderTerminated called:
	  orderBook=AAPL
	  time=1700598048056000000
	  order=Order [id=7, clientId=1001, clientOrderId=7, side=SELL, security=AAPL, originalSize=1500, openSize=0, 
					executedSize=1500, canceledSize=0, price=158.54, type=LIMIT, tif=DAY]
	
	-----> onOrderExecuted called:
	  orderBook=AAPL
	  time=1700598048056000000
	  order=Order [id=11, clientId=1001, clientOrderId=11, side=BUY, security=AAPL, originalSize=3900, openSize=1900, 
					executedSize=2000, canceledSize=0, price=159.0, type=LIMIT, tif=DAY]
	  executeSide=TAKER
	  executeSize=1500
	  executePrice=158.54
	  executeId=8
	  executeMatchId=4
	
	-----> onOrderRested called:
	  orderBook=AAPL
	  time=1700598048056000000
	  order=Order [id=11, clientId=1001, clientOrderId=11, side=BUY, security=AAPL, originalSize=3900, openSize=1900, 
					executedSize=2000, canceledSize=0, price=159.0, type=LIMIT, tif=DAY]
	  restSize=1900
	  restPrice=159.0
*/

orderBook.showOrders();

/*
   100 @    148.14 (id=4)
   500 @    149.44 (id=2)
   100 @    149.44 (id=3)
  1900 @    159.00 (id=11)    <==== You order sat here after hitting some asks...
--------      1.00
  3000 @    160.00 (id=10)	 
*/