From 2403936310cfb48cfd22935a8aaf5b319335cd6f Mon Sep 17 00:00:00 2001 From: John Jones Date: Mon, 9 Dec 2019 12:47:50 -0500 Subject: [PATCH] Initial Commit --- .gitignore | 3 ++ CMakeLists.txt | 3 ++ README.md | 3 ++ include/asset.hpp | 40 ++++++++++++++++++++ include/book.hpp | 91 +++++++++++++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 2 + test/functional.cpp | 40 ++++++++++++++++++++ 7 files changed, 182 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 include/asset.hpp create mode 100644 include/book.hpp create mode 100644 test/CMakeLists.txt create mode 100644 test/functional.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0704549 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode/* +build/* +Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3ae4160 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.4) +project (matching_engine) +add_subdirectory(test) diff --git a/README.md b/README.md new file mode 100644 index 0000000..96e5a1b --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# A C++ Matching Engine + +An academic exercise in code profiling. For more details, see my website at https://www.jmjatlanta.com/index.php/2019/12/09/matching-engine-requirements/ \ No newline at end of file diff --git a/include/asset.hpp b/include/asset.hpp new file mode 100644 index 0000000..1cb5419 --- /dev/null +++ b/include/asset.hpp @@ -0,0 +1,40 @@ +/*** + * An overly-simplified representation of an asset + */ +class Asset +{ +public: + Asset(int id) : id(id) {} + int id; + inline bool operator==(const Asset& in) const + { + return in.id == id; + } +}; + +/*** + * An order that will be possibly stored on the order book. + * Therefore, it must be sortable by price and ID + */ +template +class AssetOrder +{ + public: + AssetOrder() : id(0), assetToBuy(Asset(0)), assetToSell(Asset(0)), quantity(0), price(0) {} + AssetOrder(int id, const Asset& toBuy, const Asset& toSell, Quantity qty, Price price) + : id(id), assetToBuy(toBuy), assetToSell(toSell), quantity(qty), price(price) {} + AssetOrder& operator=(const AssetOrder& in) + { + id = in.id; + assetToBuy = in.assetToBuy; + assetToSell = in.assetToSell; + quantity = in.quantity; + price = in.price; + return *this; + } + int id; + Asset assetToBuy; + Asset assetToSell; + Quantity quantity; + Price price; +}; \ No newline at end of file diff --git a/include/book.hpp b/include/book.hpp new file mode 100644 index 0000000..9a7a63a --- /dev/null +++ b/include/book.hpp @@ -0,0 +1,91 @@ +#include +#include + +/*** + * A representation of an order book + */ +template +class Book +{ +public: + Book(const Asset buying, const Asset selling) : _wantToBuy(buying), _wantToSell(selling) {} + std::map> bids; + std::map> asks; +private: + auto getBestOffer() + { + return bids.rbegin(); + } + auto getBestAsk() + { + return asks.begin(); + } +public: + /*** + * Add an order to the book + * @param order the order + * @returns true if there was at least one fill + */ + bool AddOrder(const AssetOrder& order) + { + AssetOrder currOrder = order; + // From the book's point of view, is this order buying what we're selling? + bool buying = (order.assetToBuy == _wantToSell); + if (buying) + { + auto bestAskItr = getBestAsk(); + while (bestAskItr != asks.end() && (*bestAskItr).second.price <= currOrder.price && currOrder.quantity > 0) + { + take(asks, (*bestAskItr).second, currOrder); + bestAskItr = getBestAsk(); + } + placeOnBook(currOrder); + } + else + { + auto bestOfferItr = getBestOffer(); + while ( bestOfferItr != bids.rend() && (*bestOfferItr).second.price >= currOrder.price && currOrder.quantity > 0) + { + take(bids, (*bestOfferItr).second, currOrder); + bestOfferItr = getBestOffer(); + } + // now deal with the leftover + placeOnBook(currOrder); + } + } +private: + Asset _wantToBuy; + Asset _wantToSell; + void take(std::map>& collection, AssetOrder& bookItem, AssetOrder& incomingOrder) + { + if (bookItem.quantity > incomingOrder.quantity) + { + bookItem.quantity -= incomingOrder.quantity; + incomingOrder.quantity = 0; + } + else + { + incomingOrder.quantity -= bookItem.quantity; + bookItem.quantity = 0; + } + if (bookItem.quantity == 0) + { + collection.erase(bookItem.price); + } + } + void placeOnBook(AssetOrder& order) + { + if (order.quantity > 0) + { + bool buying = (order.assetToBuy == _wantToSell); + if (buying) + { + bids.insert({order.price, order}); + } + else + { + asks.insert({order.price, order}); + } + } + } +}; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..6b64caa --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(functional functional.cpp) +target_include_directories(functional PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include ) \ No newline at end of file diff --git a/test/functional.cpp b/test/functional.cpp new file mode 100644 index 0000000..5c60742 --- /dev/null +++ b/test/functional.cpp @@ -0,0 +1,40 @@ +/*** + * Functional tests for the order book + */ +#define BOOST_TEST_MODULE orderbook +#include + +#include + +BOOST_AUTO_TEST_CASE( simple_tests ) +{ + // two assets + Asset asset1(1); + Asset asset2(2); + + Book book(asset1, asset2); + // add something to the order book + AssetOrder order1(1, asset2, asset1, 10, 10); + book.AddOrder(order1); + BOOST_TEST( book.bids.size() == 1); + BOOST_TEST( book.asks.size() == 0); + // take half of the order + AssetOrder order2(2, asset1, asset2, 5, 10); + book.AddOrder(order2); + BOOST_TEST( book.bids.size() == 1); + BOOST_TEST( book.asks.size() == 0); + // take the rest of the order + AssetOrder order3(2, asset1, asset2, 5, 10); + book.AddOrder(order3); + BOOST_TEST( book.bids.size() == 0); + BOOST_TEST( book.asks.size() == 0); + // now let 2 bids sit on the books and 1 big ask swallow them up + order1 = AssetOrder(1, asset2, asset1, 10, 10); + book.AddOrder(order1); + order2 = AssetOrder(2, asset2, asset1, 10, 7.5); + book.AddOrder(order2); + order3 = AssetOrder(3, asset1, asset2, 20, 7.5); + book.AddOrder(order3); + BOOST_TEST( book.bids.size() == 0); + BOOST_TEST( book.asks.size() == 0); +} \ No newline at end of file