├── .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 |
--------------------------------------------------------------------------------