Skip to content

Commit fae5661

Browse files
authored
Merge pull request #2 from Kautenja/api
Api
2 parents 916f0f5 + 99016ff commit fae5661

15 files changed

+1061
-467
lines changed

README.md

+72-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,78 @@
11
# Limit Order Book (LOB)
22

33
[![build-status][]][build-server]
4+
[![PackageVersion][pypi-version]][pypi-home]
5+
[![PythonVersion][python-version]][python-home]
6+
[![Stable][pypi-status]][pypi-home]
7+
[![Format][pypi-format]][pypi-home]
8+
[![License][pypi-license]](LICENSE)
49

5-
[build-status]: https://travis-ci.com/Kautenja/lob.svg
6-
[build-server]: https://travis-ci.com/Kautenja/lob
10+
[build-status]: https://travis-ci.com/Kautenja/limit-order-book.svg
11+
[build-server]: https://travis-ci.com/Kautenja/limit-order-book
12+
[pypi-version]: https://badge.fury.io/py/limit-order-book.svg
13+
[pypi-license]: https://img.shields.io/pypi/l/limit-order-book.svg
14+
[pypi-status]: https://img.shields.io/pypi/status/limit-order-book.svg
15+
[pypi-format]: https://img.shields.io/pypi/format/limit-order-book.svg
16+
[pypi-home]: https://badge.fury.io/py/limit-order-book
17+
[python-version]: https://img.shields.io/pypi/pyversions/limit-order-book.svg
18+
[python-home]: https://python.org
19+
20+
This is an implementation of the limit order book structure and matching
21+
algorithm for C++ (and Python through ctypes) for market data streaming.
722

823
![Limit order book](img/limit-order-book.svg)
24+
25+
## Usage
26+
27+
### C++
28+
29+
Simply add [include/*.hpp](include) to your C++ project either by copying
30+
directly or using git submodules.
31+
32+
### Python
33+
34+
The preferred Python installation of `limit-order-book` is from `pip`:
35+
36+
```shell
37+
pip install limit-order-book
38+
```
39+
40+
### Windows
41+
42+
You'll need to install the Visual-Studio 17.0 tools for Windows installation.
43+
The [Visual Studio Community](https://visualstudio.microsoft.com/downloads/)
44+
package provides these tools for free.
45+
46+
## Testing
47+
48+
To run all the unit-test suites, run:
49+
50+
```shell
51+
make test
52+
```
53+
54+
### C++
55+
56+
To run the C++ unit-test suite, run:
57+
58+
```shell
59+
scons test
60+
```
61+
62+
### Python
63+
64+
To run the Python unit-test suite, run:
65+
66+
```shell
67+
python -m unittest discover .
68+
```
69+
70+
## Benchmarking
71+
72+
### C++
73+
74+
To run the C++ benchmark code, run:
75+
76+
```shell
77+
scons benchmark
78+
```

SConstruct

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ for main in find_source_files('main', 'build_main'):
170170

171171

172172
# Create a shared library (it will add "lib" to the front automatically)
173-
lib = PRODUCTION_ENV.SharedLibrary('_lob.so', SRC)
173+
lib = PRODUCTION_ENV.SharedLibrary('_limit_order_book.so', SRC)
174174
AlwaysBuild(lib)
175175
# copy the so file to the lob package
176-
Command(target="lob", source=lib, action=Copy("$TARGET", "$SOURCE"))
176+
Command(target="limit_order_book", source=lib, action=Copy("$TARGET", "$SOURCE"))

benchmark/benchmark_lob.cpp

+8-8
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ using Catch::Benchmark::Chronometer;
2121
//
2222

2323
inline void spam_limits(LimitOrderBook book, int count) {
24-
for (int i = 0; i < count; i++) book.limit(Side::Buy, 50, i, i);
24+
for (int i = 0; i < count; i++) book.limit(Side::Buy, i, 50, i, i);
2525
}
2626

2727
TEST_CASE("Spam new Limits") {
@@ -57,7 +57,7 @@ TEST_CASE("Spam new Limits") {
5757

5858
inline void spam_orders(LimitOrderBook book, int count, int variance = 5) {
5959
for (int i = 0; i < count; i++)
60-
book.limit(Side::Buy, 50, i % variance, i);
60+
book.limit(Side::Buy, i, 50, i % variance, i);
6161
}
6262

6363
TEST_CASE("Spam new Orders") {
@@ -100,9 +100,9 @@ inline void spam_orders_random_cancels(
100100
) {
101101
auto generator = std::default_random_engine();
102102
auto price_distribution = std::normal_distribution<double>(mean, variance);
103-
book.limit(Side::Buy, 50, price_distribution(generator), 0);
103+
book.limit(Side::Buy, 0, 50, price_distribution(generator), 0);
104104
for (int i = 1; i < count; i++) {
105-
book.limit(Side::Buy, 50, price_distribution(generator), i);
105+
book.limit(Side::Buy, i, 50, price_distribution(generator), i);
106106
if (i % cancel_every == 0)
107107
book.cancel(i - cancel_every);
108108
}
@@ -154,9 +154,9 @@ inline void spam_limit_random_orders(
154154
for (int i = 1; i < count; i++) {
155155
auto price_ = static_cast<uint64_t>(price(generator));
156156
auto size_ = static_cast<uint32_t>(size(generator));
157-
book.limit(Side::Buy, 100, price_, i);
157+
book.limit(Side::Buy, i, 100, price_, i);
158158
if (i % order_every == 0) // random submit a market order
159-
book.market(Side::Sell, size_, i);
159+
book.market(Side::Sell, i, size_, i);
160160
}
161161
}
162162

@@ -189,8 +189,8 @@ inline void spam_limit_many_market_orders(
189189
for (int i = 1; i < count; i++) {
190190
auto price_ = static_cast<uint64_t>(price(generator));
191191
auto size_ = static_cast<uint32_t>(size(generator));
192-
book.limit(Side::Buy, 100, price_, i);
193-
book.market(Side::Sell, size_, i);
192+
book.limit(Side::Buy, i, 100, price_, i);
193+
book.market(Side::Sell, i, size_, i);
194194
}
195195
}
196196

include/limit_order_book.hpp

+35-37
Original file line numberDiff line numberDiff line change
@@ -22,69 +22,64 @@ class LimitOrderBook {
2222
LimitTree<Side::Buy> buys;
2323
/// a mapping of order IDs to orders (for cancellation).
2424
UIDOrderMap orders;
25-
/// the counter for assigning unique IDs to orders.
26-
UID sequence;
2725

2826
public:
2927
/// Initialize a new limit order book object.
30-
LimitOrderBook() : sells(), buys(), orders(), sequence(1) { }
28+
LimitOrderBook() : sells(), buys(), orders() { }
3129

3230
/// Add a new sell limit order to the book.
3331
///
32+
/// @param order_id the ID for the order
3433
/// @param size the number of shares to sell
3534
/// @param price the limit price for the order
3635
/// @param arrival the time the order arrived at
3736
/// @return the order ID for the order added to the book
3837
///
39-
UID limit_sell(Size size, Price price, Timestamp arrival) {
40-
// put the order into the sequence map
41-
orders.insert({sequence, {sequence, Side::Sell, size, price, arrival}});
38+
void limit_sell(UID order_id, Size size, Price price, Timestamp arrival) {
39+
// put the order into the map
40+
orders.insert({order_id, {order_id, Side::Sell, size, price, arrival}});
4241
if (buys.best != nullptr && price <= buys.best->key) { // crosses
4342
// place a market order with the limit price
44-
buys.market(&orders.at(sequence), [&](UID uid) { orders.erase(uid); });
45-
if (orders.at(sequence).size == 0) { // order filled
46-
orders.erase(sequence);
47-
return 0;
48-
}
43+
buys.market(&orders.at(order_id), [&](UID uid) { orders.erase(uid); });
44+
if (orders.at(order_id).size == 0) // order filled
45+
orders.erase(order_id);
4946
}
50-
sells.limit(&orders.at(sequence));
51-
return sequence++;
47+
sells.limit(&orders.at(order_id));
5248
}
5349

5450
/// Add a new buy limit order to the book.
5551
///
52+
/// @param order_id the ID for the order
5653
/// @param size the number of shares to buy
5754
/// @param price the limit price for the order
5855
/// @param arrival the time the order arrived at
5956
/// @return the order ID for the order added to the book
6057
///
61-
UID limit_buy(Size size, Price price, Timestamp arrival) {
62-
// put the order into the sequence map
63-
orders.insert({sequence, {sequence, Side::Buy, size, price, arrival}});
58+
void limit_buy(UID order_id, Size size, Price price, Timestamp arrival) {
59+
// put the order into the map
60+
orders.insert({order_id, {order_id, Side::Buy, size, price, arrival}});
6461
if (sells.best != nullptr && price >= sells.best->key) { // crosses
6562
// place a market order with the limit price
66-
sells.market(&orders.at(sequence), [&](UID uid) { orders.erase(uid); });
67-
if (orders.at(sequence).size == 0) { // order filled
68-
orders.erase(sequence);
69-
return 0;
70-
}
63+
sells.market(&orders.at(order_id), [&](UID uid) { orders.erase(uid); });
64+
if (orders.at(order_id).size == 0) // order filled
65+
orders.erase(order_id);
7166
}
72-
buys.limit(&orders.at(sequence));
73-
return sequence++;
67+
buys.limit(&orders.at(order_id));
7468
}
7569

7670
/// Add a new order to the book.
7771
///
7872
/// @param side whether the order is a buy (true) or sell (false)
73+
/// @param order_id the ID for the order
7974
/// @param size the number of shares to buy
8075
/// @param price the limit/market price for the order
8176
/// @param arrival the time the order arrived at
8277
/// @return the order ID for the order added to the book
8378
///
84-
inline UID limit(Side side, Size size, Price price, Timestamp arrival) {
79+
inline void limit(Side side, UID order_id, Size size, Price price, Timestamp arrival) {
8580
switch (side) { // send the order to the appropriate side
86-
case Side::Sell: return limit_sell(size, price, arrival);
87-
case Side::Buy: return limit_buy(size, price, arrival);
81+
case Side::Sell: return limit_sell(order_id, size, price, arrival);
82+
case Side::Buy: return limit_buy(order_id, size, price, arrival);
8883
}
8984
}
9085

@@ -117,36 +112,39 @@ class LimitOrderBook {
117112

118113
/// Execute a sell market order.
119114
///
115+
/// @param order_id the ID for the order
120116
/// @param size the size of the market order
121117
/// @arrival the arrival of the market order
122118
///
123-
void market_sell(Size size, Timestamp arrival) {
124-
auto order = Order(sequence, Side::Sell, size, 0, arrival);
119+
void market_sell(UID order_id, Size size, Timestamp arrival) {
120+
auto order = Order(order_id, Side::Sell, size, 0, arrival);
125121
order.execution = arrival;
126122
buys.market(&order, [&](UID uid) { orders.erase(uid); });
127123
}
128124

129125
/// Execute a buy market order.
130126
///
127+
/// @param order_id the ID for the order
131128
/// @param size the size of the market order
132129
/// @arrival the arrival of the market order
133130
///
134-
void market_buy(Size size, Timestamp arrival) {
135-
auto order = Order(sequence, Side::Buy, size, 0, arrival);
131+
void market_buy(UID order_id, Size size, Timestamp arrival) {
132+
auto order = Order(order_id, Side::Buy, size, 0, arrival);
136133
order.execution = arrival;
137134
sells.market(&order, [&](UID uid) { orders.erase(uid); });
138135
}
139136

140137
/// Execute a market order.
141138
///
142139
/// @param side whether the order is a sell or buy order
140+
/// @param order_id the ID for the order
143141
/// @param size the size of the market order
144142
/// @arrival the arrival of the market order
145143
///
146-
inline void market(Side side, Size size, Timestamp arrival) {
144+
inline void market(Side side, UID order_id, Size size, Timestamp arrival) {
147145
switch (side) { // send the market order to the appropriate side
148-
case Side::Sell: { market_sell(size, arrival); break; }
149-
case Side::Buy: { market_buy(size, arrival); break; }
146+
case Side::Sell: { market_sell(order_id, size, arrival); break; }
147+
case Side::Buy: { market_buy(order_id, size, arrival); break; }
150148
}
151149
}
152150

@@ -187,20 +185,20 @@ class LimitOrderBook {
187185
/// @param price the limit price to get the volume for
188186
/// @return the volume for the given limit price
189187
///
190-
inline Size volume_sell(Price price) { return sells.volume_at(price); }
188+
inline Volume volume_sell(Price price) { return sells.volume_at(price); }
191189

192190
/// Return the total volume for the sell side of the book.
193-
inline Size volume_sell() { return sells.volume; }
191+
inline Volume volume_sell() { return sells.volume; }
194192

195193
/// Return the total volume for the buy side of the book.
196194
///
197195
/// @param price the limit price to get the volume for
198196
/// @return the volume for the given limit price
199197
///
200-
inline Size volume_buy(Price price) { return buys.volume_at(price); }
198+
inline Volume volume_buy(Price price) { return buys.volume_at(price); }
201199

202200
/// Return the total volume for the buy side of the book.
203-
inline Size volume_buy() { return buys.volume; }
201+
inline Volume volume_buy() { return buys.volume; }
204202

205203
/// Return the volume at the given limit price.
206204
///

lob/__init__.py limit_order_book/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""A Limit Order Book (LOB)."""
2-
from .lob import LimitOrderBook
2+
from .limit_order_book import LimitOrderBook
33

44

55
# explicitly define the outward facing API of this package

0 commit comments

Comments
 (0)