├── .gitattributes ├── .gitignore ├── Order-Book-Matching-Engine.iml ├── README.md ├── src └── orderbook │ ├── BBO.java │ ├── Main.java │ ├── OrderBookEngine.java │ ├── Pair.java │ ├── RandomDouble.java │ └── RandomInt.java └── test └── orderbook └── OrderBookEngineTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /Order-Book-Matching-Engine.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Order Book Matching Engine (Low Latency) 2 | 3 | Latency to match an order with a thick order book: *~1us*. 4 | 5 | No dependencies required (except Java). 6 | 7 | ### Example 8 | 9 | ```java 10 | OrderBookEngine engine = new OrderBookEngine(); 11 | // Bid 12 | // <99.95, 100> 13 | //<99.90, 50> 14 | //<99.85,50> 15 | engine.onOrder(99.95, 100, OrderBookEngine.Side.BUY); 16 | engine.onOrder(99.90, 50, OrderBookEngine.Side.BUY); 17 | engine.onOrder(99.85, 50, OrderBookEngine.Side.BUY); 18 | 19 | // Ask 20 | // <100.00, 1000> 21 | // <100.05, 50> 22 | // <100.10, 90> 23 | engine.onOrder(100.0, 1000, OrderBookEngine.Side.SELL); 24 | engine.onOrder(100.05, 50, OrderBookEngine.Side.SELL); 25 | engine.onOrder(100.10, 90, OrderBookEngine.Side.SELL); 26 | 27 | engine.printOrderBook(); 28 | 29 | engine.onOrder(99.97, 1, OrderBookEngine.Side.BUY); 30 | engine.onOrder(100.0, 1, OrderBookEngine.Side.BUY); 31 | 32 | engine.printOrderBook(); 33 | System.out.println(engine.getBBO()); 34 | ``` 35 | 36 | ``` 37 | ___ ORDER BOOK ___ 38 | Asks: 39 | 90 @ 100.1 40 | 50 @ 100.05 41 | 999 @ 100.0 42 | Bids: 43 | 1 @ 99.97 44 | 100 @ 99.95 45 | 50 @ 99.9 46 | 50 @ 99.85 47 | _________________ 48 | BBO{bid_price=99.97, bid_quantity=1.0, ask_price=100.0, ask_quantity=999.0} 49 | ``` 50 | -------------------------------------------------------------------------------- /src/orderbook/BBO.java: -------------------------------------------------------------------------------- 1 | package orderbook; 2 | 3 | public class BBO { 4 | 5 | public double bid_price = 0.0; 6 | public double bid_quantity = 0.0; 7 | public double ask_price = 0.0; 8 | public double ask_quantity = 0.0; 9 | 10 | @Override 11 | public String toString() { 12 | return "BBO{" + 13 | "bid_price=" + bid_price + 14 | ", bid_quantity=" + bid_quantity + 15 | ", ask_price=" + ask_price + 16 | ", ask_quantity=" + ask_quantity + 17 | '}'; 18 | } 19 | } -------------------------------------------------------------------------------- /src/orderbook/Main.java: -------------------------------------------------------------------------------- 1 | package orderbook; 2 | 3 | public class Main { 4 | 5 | static RandomDouble randomDouble = new RandomDouble(); 6 | static RandomInt randomInt = new RandomInt(); 7 | 8 | static { 9 | randomDouble.initialize(); 10 | randomInt.initialize( ); 11 | } 12 | 13 | public static void show() { 14 | 15 | OrderBookEngine engine = new OrderBookEngine(); 16 | engine.addBidRestingOrder(9.9, 1000); 17 | engine.addBidRestingOrder(9.8, 3000); 18 | engine.addBidRestingOrder(9.7, 5000); 19 | 20 | engine.addAskRestingOffer(10.1, 3000); 21 | engine.addAskRestingOffer(10.2, 3000); 22 | engine.addAskRestingOffer(10.3, 10000); 23 | 24 | engine.printOrderBook(); 25 | 26 | engine.onOrder(10.5, 17000, OrderBookEngine.Side.BUY); 27 | 28 | engine.printOrderBook(); 29 | 30 | engine.onOrder(9.8, 60000, OrderBookEngine.Side.SELL); 31 | 32 | engine.printOrderBook(); 33 | 34 | engine.onOrder(9.8, 55000, OrderBookEngine.Side.BUY); 35 | 36 | engine.printOrderBook(); 37 | 38 | engine.onOrder(1, 5000, OrderBookEngine.Side.SELL); 39 | 40 | engine.printOrderBook(); 41 | 42 | } 43 | 44 | public static void stress() { 45 | OrderBookEngine engine = new OrderBookEngine(); 46 | long n1 = System.currentTimeMillis(); 47 | int ITERATIONS = 10000000; 48 | System.out.println("Running for " + ITERATIONS + " iterations."); 49 | for (int i = 0; i < ITERATIONS; i++) { 50 | 51 | if(i % 100000 == 0) { 52 | System.out.println(i + " orders sent"); 53 | } 54 | 55 | if (randomDouble.nextDouble() > 50) { 56 | double price = randomDouble.nextDouble(); 57 | int qty = randomInt.nextInt(); 58 | engine.onOrder(price, qty, OrderBookEngine.Side.BUY); 59 | } else { 60 | double price = randomDouble.nextDouble(); 61 | int qty = randomInt.nextInt(); 62 | engine.onOrder(price, qty, OrderBookEngine.Side.SELL); 63 | } 64 | 65 | } 66 | 67 | long elapsedTimeMillis = System.currentTimeMillis() - n1; 68 | long elapsedTimeMicros = elapsedTimeMillis * 1000; 69 | 70 | System.out.println(((double)elapsedTimeMicros) / ITERATIONS + " us on average."); 71 | engine.reset(); 72 | 73 | } 74 | /** 75 | * -Xmx4096m -Xms4096m -server 76 | */ 77 | public static void main(String[] args) { 78 | stress(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/orderbook/OrderBookEngine.java: -------------------------------------------------------------------------------- 1 | package orderbook; 2 | 3 | import java.util.*; 4 | 5 | public class OrderBookEngine { 6 | 7 | public enum Side {BUY, SELL} 8 | 9 | private Map bidOffers = new TreeMap<>(Comparator.reverseOrder()); 10 | 11 | private Map askOffers = new TreeMap<>(); 12 | 13 | 14 | public void printOrderBook() { 15 | System.out.println("___ ORDER BOOK ___"); 16 | System.out.println("Asks:"); 17 | List ask_prices = new ArrayList<>(askOffers.keySet()); 18 | Collections.reverse(ask_prices); 19 | for (double ask_price : ask_prices) { 20 | System.out.println(askOffers.get(ask_price) + " @ " + ask_price); 21 | } 22 | 23 | List bid_prices = new ArrayList<>(bidOffers.keySet()); 24 | System.out.println("Bids:"); 25 | for (Double bid_price : bid_prices) { 26 | System.out.println(bidOffers.get(bid_price) + " @ " + bid_price); 27 | } 28 | System.out.println("_________________"); 29 | } 30 | 31 | public void onOrder(double price, int quantity, Side side) { 32 | if (side == Side.BUY) { 33 | Set ask_prices = askOffers.keySet(); 34 | List ask_prices_list = new ArrayList<>(ask_prices); 35 | for (double ask_price : ask_prices_list) { 36 | if (quantity > 0 && price >= ask_price) { 37 | int ask_quantity = askOffers.get(ask_price); 38 | if (quantity >= ask_quantity) { 39 | quantity = quantity - ask_quantity; 40 | removeAskOrder(ask_price, ask_quantity); 41 | } else { 42 | removeAskOrder(ask_price, quantity); 43 | quantity = 0; 44 | } 45 | if (quantity == 0) { 46 | break; 47 | } 48 | } 49 | } 50 | if (quantity > 0) { 51 | addBidRestingOrder(price, quantity); 52 | } 53 | } else { 54 | Set bid_prices = bidOffers.keySet(); 55 | List bid_prices_list = new ArrayList<>(bid_prices); 56 | for (double bid_price : bid_prices_list) { 57 | if (quantity > 0 && price <= bid_price) { 58 | int bid_quantity = bidOffers.get(bid_price); 59 | if (quantity >= bid_quantity) { 60 | quantity = quantity - bid_quantity; 61 | removeBidOrder(bid_price, bid_quantity); 62 | } else { 63 | removeBidOrder(bid_price, quantity); 64 | quantity = 0; 65 | } 66 | if (quantity == 0) { 67 | break; 68 | } 69 | } 70 | 71 | } 72 | if (quantity > 0) { 73 | addAskRestingOffer(price, quantity); 74 | } 75 | } 76 | } 77 | 78 | 79 | synchronized void addBidRestingOrder(double price, int quantity) { 80 | bidOffers.put(price, quantity); 81 | } 82 | 83 | synchronized void removeBidOrder(double price, int quantity) { 84 | int lastQuantity = bidOffers.get(price); 85 | if (lastQuantity == quantity) { 86 | bidOffers.remove(price); 87 | } else { 88 | bidOffers.put(price, lastQuantity - quantity); 89 | } 90 | } 91 | 92 | synchronized void addAskRestingOffer(double price, int quantity) { 93 | askOffers.put(price, quantity); 94 | } 95 | 96 | public BBO getBBO() { 97 | BBO bbo = new BBO(); 98 | double bid_price = 0.0; 99 | double bid_quantity = 0.0; 100 | double ask_price = 0.0; 101 | double ask_quantity = 0.0; 102 | for (double price : bidOffers.keySet()) { 103 | bbo.bid_price = price; 104 | bbo.bid_quantity = bidOffers.get(price); 105 | break; 106 | } 107 | for (double price : askOffers.keySet()) { 108 | bbo.ask_price = price; 109 | bbo.ask_quantity = askOffers.get(price); 110 | break; 111 | } 112 | return bbo; 113 | } 114 | 115 | public int getAskLevel() { 116 | return askOffers.size(); 117 | } 118 | 119 | public int getBidLevel() { 120 | return bidOffers.size(); 121 | } 122 | 123 | public int getBidQuantity(double bestPrice) { 124 | int bidQuantity = 0; 125 | for (double price : bidOffers.keySet()) { 126 | if (price > bestPrice) { 127 | bidQuantity += bidOffers.get(price); 128 | } 129 | } 130 | 131 | return bidQuantity; 132 | } 133 | 134 | public int getBidQuantity() { 135 | return getBidQuantity(Integer.MIN_VALUE); 136 | } 137 | 138 | public int getAskQuantity() { 139 | return getAskQuantity(Integer.MAX_VALUE); 140 | } 141 | 142 | public int getAskQuantity(double bestPrice) { 143 | int askQuantity = 0; 144 | for (double price : askOffers.keySet()) { 145 | if (price < bestPrice) { 146 | askQuantity += askOffers.get(price); 147 | } 148 | } 149 | return askQuantity; 150 | } 151 | 152 | synchronized void removeAskOrder(double price, int quantity) { 153 | int lastQuantity = askOffers.get(price); 154 | if (lastQuantity == quantity) { 155 | askOffers.remove(price); 156 | } else { 157 | askOffers.put(price, lastQuantity - quantity); 158 | } 159 | } 160 | 161 | public void reset() { 162 | System.out.println("size ask = " + askOffers.size()); 163 | System.out.println("size bid = " + bidOffers.size()); 164 | askOffers.clear(); 165 | bidOffers.clear(); 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /src/orderbook/Pair.java: -------------------------------------------------------------------------------- 1 | package orderbook; 2 | 3 | public class Pair { 4 | 5 | private final L left; 6 | private final R right; 7 | 8 | public Pair(L left, R right) { 9 | this.left = left; 10 | this.right = right; 11 | } 12 | 13 | public L getLeft() { return left; } 14 | public R getRight() { return right; } 15 | 16 | @Override 17 | public int hashCode() { return left.hashCode() ^ right.hashCode(); } 18 | 19 | @Override 20 | public boolean equals(Object o) { 21 | if (o == null) return false; 22 | if (!(o instanceof Pair)) return false; 23 | Pair pairo = (Pair) o; 24 | return this.left.equals(pairo.getLeft()) && 25 | this.right.equals(pairo.getRight()); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/orderbook/RandomDouble.java: -------------------------------------------------------------------------------- 1 | package orderbook; 2 | 3 | import java.text.DecimalFormat; 4 | 5 | public class RandomDouble { 6 | 7 | final int MAX = 4096; 8 | private double[] randoms = new double[MAX]; 9 | 10 | public void initialize() { 11 | for(int i=0; i 68 | //<99.90, 50> 69 | //<99.85,50> 70 | engine.onOrder(99.95, 100, OrderBookEngine.Side.BUY); 71 | engine.onOrder(99.90, 50, OrderBookEngine.Side.BUY); 72 | engine.onOrder(99.85, 50, OrderBookEngine.Side.BUY); 73 | 74 | // Ask 75 | // <100.00, 1000> 76 | // <100.05, 50> 77 | // <100.10, 90> 78 | engine.onOrder(100.0, 1000, OrderBookEngine.Side.SELL); 79 | engine.onOrder(100.05, 50, OrderBookEngine.Side.SELL); 80 | engine.onOrder(100.10, 90, OrderBookEngine.Side.SELL); 81 | 82 | engine.printOrderBook(); 83 | 84 | engine.onOrder(99.97, 1, OrderBookEngine.Side.BUY); 85 | engine.onOrder(100.0, 1, OrderBookEngine.Side.BUY); 86 | 87 | engine.printOrderBook(); 88 | System.out.println(engine.getBBO()); 89 | 90 | 91 | } 92 | 93 | } 94 | --------------------------------------------------------------------------------