I'm creating a simple yet fast OrderBook, that only adds orders and matches them (no cancelling or modifications, etc.). I'm using partial template specialization to reduce branching in the hotpath, and I'm preallocating the price ladder to avoid expensive allocation operations (that and this being a lot more cache friendly are the reasons I chose this approach over something like a balanced BST or a heap). The printing of the orderbook after each order insertion is a requirement, and of course that dominates the runtime, but the order insertion and matching itself should be as low latency as possible.
This was tested on Clang 14 (on Ubuntu 22.04 LTS), and doesn't compile with GCC due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282.
Here's the first rough implementation (just wanted something implemented quickly, and I'm going to make improvements like using a conditional for processOrder instead of doing specialisation), and I thought I'd get some feedback on it and see what I can improve in terms of performance (and maybe some code quality issues too). I've included everything all in one file to make it easier to quickly run. Also error checking isn't needed, the orders will be formatted correctly with valid values.
#include <iostream>
#include <deque>
#include <vector>
#include <iomanip>
using namespace std; // remove this
struct Order
{
int id;
short price;
int quantity;
};
enum class Side
{
Buy,
Sell
};
class OrderBook
{
using book_t = vector<deque<Order>>;
private:
book_t buys;
book_t sells;
int topOfBuys = -1;
int bottomOfBuys = 65537;
int topOfSells = 65537;
int bottomOfSells = -1;
public:
OrderBook() : buys(65536), sells(65536) {}
void displayOrderBook()
{
cout << "+" << string(65, '-') << "+\n";
cout << "| BUY | SELL |\n";
cout << "| Id | Volume | Price | Price | Volume | Id |\n";
cout << "+----------+-------------+-------+-------+-------------+----------+\n";
vector<pair<Order, Order>> orders; // buy, sell
int sellIndex = 0, buyIndex = 0;
for (int i = topOfBuys; i >= bottomOfBuys; --i)
{
if (not buys[i].empty())
{
for (auto &order : buys[i])
{
if (buyIndex < orders.size())
orders[buyIndex++].first = order;
else
orders.push_back({order, Order{}});
}
}
}
for (int i = topOfSells; i <= bottomOfSells; ++i)
{
if (not sells[i].empty())
{
for (auto &order : sells[i])
{
if (sellIndex < orders.size())
orders[sellIndex++].second = order;
else
orders.push_back({Order{}, order});
}
}
}
auto displayField = [](const int value)
{
return value != 0 ? to_string(value) : "";
};
for (const auto &[buyOrder, sellOrder] : orders)
{
cout << "| " << setw(8) << displayField(buyOrder.id) << " | "
<< setw(11) << displayField(buyOrder.quantity) << " | "
<< setw(5) << displayField(buyOrder.price) << " | "
<< setw(5) << displayField(sellOrder.price) << " | "
<< setw(11) << displayField(sellOrder.quantity) << " | "
<< setw(8) << displayField(sellOrder.id) << " |\n";
}
cout << "+" << string(65, '-') << "+\n";
}
string formatWithCommas(int value)
{
stringstream ss;
ss.imbue(locale(""));
ss << fixed << value;
return ss.str();
}
template <Side side>
void processOrder(Order &&order);
template <>
void processOrder<Side::Sell>(Order &&order)
{
if (order.price <= topOfBuys)
matchOrders<Side::Sell, Side::Buy>(move(order), buys, topOfBuys, bottomOfBuys);
else
addOrder<Side::Sell>(move(order));
displayOrderBook();
}
template <>
void processOrder<Side::Buy>(Order &&order)
{
if (order.price >= topOfSells)
matchOrders<Side::Buy, Side::Sell>(move(order), sells, topOfSells, bottomOfSells);
else
addOrder<Side::Buy>(move(order));
displayOrderBook();
}
template <Side orderSide, Side otherSide>
void matchOrders(Order &&order, book_t &otherBook, int &topOfOther, int &bottomOfOther)
{
while (compare<otherSide>() and order.quantity > 0)
{
auto &other = otherBook[topOfOther].front();
if (order.quantity < other.quantity)
{
cout << order.id << "," << other.id << "," << topOfOther << "," << order.quantity << '\n';
other.quantity -= order.quantity;
order.quantity = 0;
}
else
{
cout << order.id << "," << other.id << "," << topOfOther << "," << other.quantity << '\n';
order.quantity -= other.quantity;
otherBook[topOfOther].pop_front();
}
if (otherBook[topOfOther].empty())
findNext<otherSide>();
}
if (order.quantity > 0)
addOrder<orderSide>(move(order));
}
template <Side side>
inline bool compare();
template <>
inline bool compare<Side::Buy>()
{
return topOfBuys >= bottomOfBuys;
}
template <>
inline bool compare<Side::Sell>()
{
return topOfSells <= bottomOfSells;
}
template <Side side>
inline void findNext();
template <>
inline void findNext<Side::Sell>()
{
while (topOfSells <= bottomOfSells and sells[topOfSells].empty())
{
++topOfSells;
}
if (topOfSells > bottomOfSells)
{
topOfSells = 65537;
bottomOfSells = -1;
}
}
template <>
inline void findNext<Side::Buy>()
{
while (topOfBuys >= bottomOfBuys and buys[topOfBuys].empty())
{
--topOfBuys;
}
if (topOfBuys < bottomOfBuys)
{
topOfBuys = -1;
bottomOfBuys = 65537;
}
}
template <Side side>
void addOrder(Order &&order);
template <>
void addOrder<Side::Sell>(Order &&order)
{
topOfSells = min(topOfSells, (int)order.price);
bottomOfSells = max(bottomOfSells, (int)order.price);
sells[order.price].push_back(move(order));
}
template <>
void addOrder<Side::Buy>(Order &&order)
{
topOfBuys = max(topOfBuys, (int)order.price);
bottomOfBuys = min(bottomOfBuys, (int)order.price);
buys[order.price].push_back(move(order));
}
};
int main()
{
OrderBook orderbook;
orderbook.processOrder<Side::Sell>(move(Order{1, 5103, 100}));
orderbook.processOrder<Side::Buy>(move(Order{2, 5103, 100000}));
orderbook.processOrder<Side::Buy>(move(Order{3, 5103, 100000}));
orderbook.processOrder<Side::Sell>(move(Order{4, 5103, 100}));
orderbook.processOrder<Side::Sell>(move(Order{5, 5104, 100}));
orderbook.processOrder<Side::Sell>(move(Order{6, 5103, 100}));
orderbook.processOrder<Side::Buy>(move(Order{7, 5105, 300}));
orderbook.processOrder<Side::Sell>(move(Order{8, 5105, 300}));
return 0;
}
```