Skip to content

Commit

Permalink
Merge pull request #2 from Kautenja/api
Browse files Browse the repository at this point in the history
Api
  • Loading branch information
Kautenja authored Dec 17, 2019
2 parents 916f0f5 + 99016ff commit fae5661
Show file tree
Hide file tree
Showing 15 changed files with 1,061 additions and 467 deletions.
74 changes: 72 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,78 @@
# Limit Order Book (LOB)

[![build-status][]][build-server]
[![PackageVersion][pypi-version]][pypi-home]
[![PythonVersion][python-version]][python-home]
[![Stable][pypi-status]][pypi-home]
[![Format][pypi-format]][pypi-home]
[![License][pypi-license]](LICENSE)

[build-status]: https://travis-ci.com/Kautenja/lob.svg
[build-server]: https://travis-ci.com/Kautenja/lob
[build-status]: https://travis-ci.com/Kautenja/limit-order-book.svg
[build-server]: https://travis-ci.com/Kautenja/limit-order-book
[pypi-version]: https://badge.fury.io/py/limit-order-book.svg
[pypi-license]: https://img.shields.io/pypi/l/limit-order-book.svg
[pypi-status]: https://img.shields.io/pypi/status/limit-order-book.svg
[pypi-format]: https://img.shields.io/pypi/format/limit-order-book.svg
[pypi-home]: https://badge.fury.io/py/limit-order-book
[python-version]: https://img.shields.io/pypi/pyversions/limit-order-book.svg
[python-home]: https://python.org

This is an implementation of the limit order book structure and matching
algorithm for C++ (and Python through ctypes) for market data streaming.

![Limit order book](img/limit-order-book.svg)

## Usage

### C++

Simply add [include/*.hpp](include) to your C++ project either by copying
directly or using git submodules.

### Python

The preferred Python installation of `limit-order-book` is from `pip`:

```shell
pip install limit-order-book
```

### Windows

You'll need to install the Visual-Studio 17.0 tools for Windows installation.
The [Visual Studio Community](https://visualstudio.microsoft.com/downloads/)
package provides these tools for free.

## Testing

To run all the unit-test suites, run:

```shell
make test
```

### C++

To run the C++ unit-test suite, run:

```shell
scons test
```

### Python

To run the Python unit-test suite, run:

```shell
python -m unittest discover .
```

## Benchmarking

### C++

To run the C++ benchmark code, run:

```shell
scons benchmark
```
4 changes: 2 additions & 2 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ for main in find_source_files('main', 'build_main'):


# Create a shared library (it will add "lib" to the front automatically)
lib = PRODUCTION_ENV.SharedLibrary('_lob.so', SRC)
lib = PRODUCTION_ENV.SharedLibrary('_limit_order_book.so', SRC)
AlwaysBuild(lib)
# copy the so file to the lob package
Command(target="lob", source=lib, action=Copy("$TARGET", "$SOURCE"))
Command(target="limit_order_book", source=lib, action=Copy("$TARGET", "$SOURCE"))
16 changes: 8 additions & 8 deletions benchmark/benchmark_lob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ using Catch::Benchmark::Chronometer;
//

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

TEST_CASE("Spam new Limits") {
Expand Down Expand Up @@ -57,7 +57,7 @@ TEST_CASE("Spam new Limits") {

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

TEST_CASE("Spam new Orders") {
Expand Down Expand Up @@ -100,9 +100,9 @@ inline void spam_orders_random_cancels(
) {
auto generator = std::default_random_engine();
auto price_distribution = std::normal_distribution<double>(mean, variance);
book.limit(Side::Buy, 50, price_distribution(generator), 0);
book.limit(Side::Buy, 0, 50, price_distribution(generator), 0);
for (int i = 1; i < count; i++) {
book.limit(Side::Buy, 50, price_distribution(generator), i);
book.limit(Side::Buy, i, 50, price_distribution(generator), i);
if (i % cancel_every == 0)
book.cancel(i - cancel_every);
}
Expand Down Expand Up @@ -154,9 +154,9 @@ inline void spam_limit_random_orders(
for (int i = 1; i < count; i++) {
auto price_ = static_cast<uint64_t>(price(generator));
auto size_ = static_cast<uint32_t>(size(generator));
book.limit(Side::Buy, 100, price_, i);
book.limit(Side::Buy, i, 100, price_, i);
if (i % order_every == 0) // random submit a market order
book.market(Side::Sell, size_, i);
book.market(Side::Sell, i, size_, i);
}
}

Expand Down Expand Up @@ -189,8 +189,8 @@ inline void spam_limit_many_market_orders(
for (int i = 1; i < count; i++) {
auto price_ = static_cast<uint64_t>(price(generator));
auto size_ = static_cast<uint32_t>(size(generator));
book.limit(Side::Buy, 100, price_, i);
book.market(Side::Sell, size_, i);
book.limit(Side::Buy, i, 100, price_, i);
book.market(Side::Sell, i, size_, i);
}
}

Expand Down
72 changes: 35 additions & 37 deletions include/limit_order_book.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,69 +22,64 @@ class LimitOrderBook {
LimitTree<Side::Buy> buys;
/// a mapping of order IDs to orders (for cancellation).
UIDOrderMap orders;
/// the counter for assigning unique IDs to orders.
UID sequence;

public:
/// Initialize a new limit order book object.
LimitOrderBook() : sells(), buys(), orders(), sequence(1) { }
LimitOrderBook() : sells(), buys(), orders() { }

/// Add a new sell limit order to the book.
///
/// @param order_id the ID for the order
/// @param size the number of shares to sell
/// @param price the limit price for the order
/// @param arrival the time the order arrived at
/// @return the order ID for the order added to the book
///
UID limit_sell(Size size, Price price, Timestamp arrival) {
// put the order into the sequence map
orders.insert({sequence, {sequence, Side::Sell, size, price, arrival}});
void limit_sell(UID order_id, Size size, Price price, Timestamp arrival) {
// put the order into the map
orders.insert({order_id, {order_id, Side::Sell, size, price, arrival}});
if (buys.best != nullptr && price <= buys.best->key) { // crosses
// place a market order with the limit price
buys.market(&orders.at(sequence), [&](UID uid) { orders.erase(uid); });
if (orders.at(sequence).size == 0) { // order filled
orders.erase(sequence);
return 0;
}
buys.market(&orders.at(order_id), [&](UID uid) { orders.erase(uid); });
if (orders.at(order_id).size == 0) // order filled
orders.erase(order_id);
}
sells.limit(&orders.at(sequence));
return sequence++;
sells.limit(&orders.at(order_id));
}

/// Add a new buy limit order to the book.
///
/// @param order_id the ID for the order
/// @param size the number of shares to buy
/// @param price the limit price for the order
/// @param arrival the time the order arrived at
/// @return the order ID for the order added to the book
///
UID limit_buy(Size size, Price price, Timestamp arrival) {
// put the order into the sequence map
orders.insert({sequence, {sequence, Side::Buy, size, price, arrival}});
void limit_buy(UID order_id, Size size, Price price, Timestamp arrival) {
// put the order into the map
orders.insert({order_id, {order_id, Side::Buy, size, price, arrival}});
if (sells.best != nullptr && price >= sells.best->key) { // crosses
// place a market order with the limit price
sells.market(&orders.at(sequence), [&](UID uid) { orders.erase(uid); });
if (orders.at(sequence).size == 0) { // order filled
orders.erase(sequence);
return 0;
}
sells.market(&orders.at(order_id), [&](UID uid) { orders.erase(uid); });
if (orders.at(order_id).size == 0) // order filled
orders.erase(order_id);
}
buys.limit(&orders.at(sequence));
return sequence++;
buys.limit(&orders.at(order_id));
}

/// Add a new order to the book.
///
/// @param side whether the order is a buy (true) or sell (false)
/// @param order_id the ID for the order
/// @param size the number of shares to buy
/// @param price the limit/market price for the order
/// @param arrival the time the order arrived at
/// @return the order ID for the order added to the book
///
inline UID limit(Side side, Size size, Price price, Timestamp arrival) {
inline void limit(Side side, UID order_id, Size size, Price price, Timestamp arrival) {
switch (side) { // send the order to the appropriate side
case Side::Sell: return limit_sell(size, price, arrival);
case Side::Buy: return limit_buy(size, price, arrival);
case Side::Sell: return limit_sell(order_id, size, price, arrival);
case Side::Buy: return limit_buy(order_id, size, price, arrival);
}
}

Expand Down Expand Up @@ -117,36 +112,39 @@ class LimitOrderBook {

/// Execute a sell market order.
///
/// @param order_id the ID for the order
/// @param size the size of the market order
/// @arrival the arrival of the market order
///
void market_sell(Size size, Timestamp arrival) {
auto order = Order(sequence, Side::Sell, size, 0, arrival);
void market_sell(UID order_id, Size size, Timestamp arrival) {
auto order = Order(order_id, Side::Sell, size, 0, arrival);
order.execution = arrival;
buys.market(&order, [&](UID uid) { orders.erase(uid); });
}

/// Execute a buy market order.
///
/// @param order_id the ID for the order
/// @param size the size of the market order
/// @arrival the arrival of the market order
///
void market_buy(Size size, Timestamp arrival) {
auto order = Order(sequence, Side::Buy, size, 0, arrival);
void market_buy(UID order_id, Size size, Timestamp arrival) {
auto order = Order(order_id, Side::Buy, size, 0, arrival);
order.execution = arrival;
sells.market(&order, [&](UID uid) { orders.erase(uid); });
}

/// Execute a market order.
///
/// @param side whether the order is a sell or buy order
/// @param order_id the ID for the order
/// @param size the size of the market order
/// @arrival the arrival of the market order
///
inline void market(Side side, Size size, Timestamp arrival) {
inline void market(Side side, UID order_id, Size size, Timestamp arrival) {
switch (side) { // send the market order to the appropriate side
case Side::Sell: { market_sell(size, arrival); break; }
case Side::Buy: { market_buy(size, arrival); break; }
case Side::Sell: { market_sell(order_id, size, arrival); break; }
case Side::Buy: { market_buy(order_id, size, arrival); break; }
}
}

Expand Down Expand Up @@ -187,20 +185,20 @@ class LimitOrderBook {
/// @param price the limit price to get the volume for
/// @return the volume for the given limit price
///
inline Size volume_sell(Price price) { return sells.volume_at(price); }
inline Volume volume_sell(Price price) { return sells.volume_at(price); }

/// Return the total volume for the sell side of the book.
inline Size volume_sell() { return sells.volume; }
inline Volume volume_sell() { return sells.volume; }

/// Return the total volume for the buy side of the book.
///
/// @param price the limit price to get the volume for
/// @return the volume for the given limit price
///
inline Size volume_buy(Price price) { return buys.volume_at(price); }
inline Volume volume_buy(Price price) { return buys.volume_at(price); }

/// Return the total volume for the buy side of the book.
inline Size volume_buy() { return buys.volume; }
inline Volume volume_buy() { return buys.volume; }

/// Return the volume at the given limit price.
///
Expand Down
2 changes: 1 addition & 1 deletion lob/__init__.py → limit_order_book/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""A Limit Order Book (LOB)."""
from .lob import LimitOrderBook
from .limit_order_book import LimitOrderBook


# explicitly define the outward facing API of this package
Expand Down
Loading

0 comments on commit fae5661

Please sign in to comment.