Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jmjatlanta committed Dec 9, 2019
1 parent 57b17d2 commit 2403936
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
.vscode/*
build/*
Makefile
3 changes: 3 additions & 0 deletions CMakeLists.txt
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.4)
project (matching_engine)
add_subdirectory(test)
3 changes: 3 additions & 0 deletions 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/
40 changes: 40 additions & 0 deletions 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 Quantity, class Price>
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;
};
91 changes: 91 additions & 0 deletions include/book.hpp
@@ -0,0 +1,91 @@
#include <set>
#include <asset.hpp>

/***
* A representation of an order book
*/
template<class Size, class Price>
class Book
{
public:
Book(const Asset buying, const Asset selling) : _wantToBuy(buying), _wantToSell(selling) {}
std::map<Price, AssetOrder<Size, Price>> bids;
std::map<Price, AssetOrder<Size, Price>> 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<Size, Price>& order)
{
AssetOrder<Size, Price> 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<Price, AssetOrder<Size, Price>>& collection, AssetOrder<Size, Price>& bookItem, AssetOrder<Size, Price>& 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<Size, Price>& order)
{
if (order.quantity > 0)
{
bool buying = (order.assetToBuy == _wantToSell);
if (buying)
{
bids.insert({order.price, order});
}
else
{
asks.insert({order.price, order});
}
}
}
};
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
@@ -0,0 +1,2 @@
add_executable(functional functional.cpp)
target_include_directories(functional PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include )
40 changes: 40 additions & 0 deletions test/functional.cpp
@@ -0,0 +1,40 @@
/***
* Functional tests for the order book
*/
#define BOOST_TEST_MODULE orderbook
#include <boost/test/included/unit_test.hpp>

#include <book.hpp>

BOOST_AUTO_TEST_CASE( simple_tests )
{
// two assets
Asset asset1(1);
Asset asset2(2);

Book<int, double> book(asset1, asset2);
// add something to the order book
AssetOrder<int, double> 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<int, double> 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<int, double> 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<int, double>(1, asset2, asset1, 10, 10);
book.AddOrder(order1);
order2 = AssetOrder<int, double>(2, asset2, asset1, 10, 7.5);
book.AddOrder(order2);
order3 = AssetOrder<int, double>(3, asset1, asset2, 20, 7.5);
book.AddOrder(order3);
BOOST_TEST( book.bids.size() == 0);
BOOST_TEST( book.asks.size() == 0);
}

0 comments on commit 2403936

Please sign in to comment.