kevinlu1248 / CoralME

A simple, fast and garbage-free matching engine order book that you can use as a starting point for your matching engines.
Apache License 2.0
0 stars 0 forks source link

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

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)   
*/