kraken: engin: add 'Engine'

This is the brains of the operation, the matching engine.
This commit is contained in:
Bruno BELANYI 2022-03-12 05:47:34 +01:00
parent 346dfc4e17
commit 0817e7ac7e
3 changed files with 235 additions and 0 deletions

View file

@ -1,6 +1,8 @@
add_library(engine STATIC add_library(engine STATIC
csv-engine-listener.cc csv-engine-listener.cc
csv-engine-listener.hh csv-engine-listener.hh
engine.cc
engine.hh
engine-listener.cc engine-listener.cc
engine-listener.hh engine-listener.hh
) )

187
src/engine/engine.cc Normal file
View file

@ -0,0 +1,187 @@
#include "engine.hh"
#include <algorithm>
#include <utility>
#include <cassert>
#include "engine-listener.hh"
namespace kraken::engine {
// A RAII wrapper to ensure the top-of-book callback is made
struct CallbackOnTopOfBookChange {
CallbackOnTopOfBookChange(Symbol symbol, Engine& engine)
: symbol_(symbol), engine_(engine) {
top_info_ = calculate_top();
}
~CallbackOnTopOfBookChange() {
auto const new_top = calculate_top();
// Sanity check: both sides of the book are not changing at once
assert(!(new_top.asks != top_info_.asks
&& new_top.bids != top_info_.bids));
if (top_info_.asks != new_top.asks) {
if (new_top.asks) {
engine_.listener_->on_top_of_book_change(
Side::ASK, new_top.asks->first, new_top.asks->second);
} else {
engine_.listener_->on_top_of_book_change(Side::ASK);
}
} else if (top_info_.bids != new_top.bids) {
if (new_top.bids) {
engine_.listener_->on_top_of_book_change(
Side::BID, new_top.bids->first, new_top.bids->second);
} else {
engine_.listener_->on_top_of_book_change(Side::BID);
}
}
}
private:
struct TopInfo {
std::optional<std::pair<Price, Quantity>> asks;
std::optional<std::pair<Price, Quantity>> bids;
};
TopInfo calculate_top() const {
auto info = TopInfo{};
if (auto const bid_it = engine_.bids_.find(symbol_);
bid_it != engine_.bids_.end()) {
auto const& [_, bid_map] = *bid_it;
if (bid_map.size() > 0) {
auto const price = bid_map.begin()->first;
int quantity = 0;
auto const [begin, end] = bid_map.equal_range(price);
for (auto it = begin; it != end; ++it) {
quantity += int(it->second.quantity);
}
info.bids = {price, Quantity(quantity)};
}
}
if (auto const ask_it = engine_.asks_.find(symbol_);
ask_it != engine_.asks_.end()) {
auto const& [_, ask_map] = *ask_it;
if (ask_map.size() > 0) {
auto const price = ask_map.begin()->first;
int quantity = 0;
auto const [begin, end] = ask_map.equal_range(price);
for (auto it = begin; it != end; ++it) {
quantity += int(it->second.quantity);
}
info.asks = {price, Quantity(quantity)};
}
}
return info;
}
Symbol symbol_;
Engine& engine_;
TopInfo top_info_{};
};
Engine::Engine(std::shared_ptr<EngineListener> listener)
: listener_(listener) {}
void Engine::process_orders(std::vector<Order> const& orders) {
for (auto const& order : orders) {
std::visit([this](auto const& trade_order) { (*this)(trade_order); },
order);
}
}
void Engine::operator()(TradeOrder const& trade_order) {
// Set-up automatic call-back in case top-of-book changes
auto _ = CallbackOnTopOfBookChange(trade_order.symbol, *this);
// NOTE: some amount of repetition/mirroring
switch (trade_order.side) {
case Side::ASK:
if (auto bid_map_it = bids_.find(trade_order.symbol);
bid_map_it != bids_.end()) {
auto& [_, bid_map] = *bid_map_it;
if (bid_map.size() > 0
&& bid_map.begin()->first <= trade_order.price) {
// FIXME: handle matching if enabled
listener_->on_rejection(trade_order.user, trade_order.id);
return;
}
}
asks_[trade_order.symbol].insert(
{trade_order.price, OrderMetaData{trade_order.user, trade_order.id,
trade_order.quantity}});
break;
case Side::BID:
if (auto ask_map_it = asks_.find(trade_order.symbol);
ask_map_it != asks_.end()) {
auto& [_, ask_map] = *ask_map_it;
if (ask_map.size() > 0
&& ask_map.begin()->first >= trade_order.price) {
// FIXME: handle matching if enabled
listener_->on_rejection(trade_order.user, trade_order.id);
return;
}
}
bids_[trade_order.symbol].insert(
{trade_order.price, OrderMetaData{trade_order.user, trade_order.id,
trade_order.quantity}});
break;
}
listener_->on_acknowledgement(trade_order.user, trade_order.id);
}
void Engine::operator()(CancelOrder const& cancel_order) {
// Assume that the input is well-behaved,
// no duplicate (userId, userOrderId) values.
auto const matches_order = [&](auto const& data) {
return data.second.user == cancel_order.user
&& data.second.id == cancel_order.id;
};
for (auto& [symbol, bid_map] : bids_) {
auto const it = std::ranges::find_if(bid_map, matches_order);
if (it != bid_map.end()) {
// Set-up automatic call-back in case top-of-book changes
auto _ = CallbackOnTopOfBookChange(symbol, *this);
bid_map.erase(it);
listener_->on_acknowledgement(cancel_order.user, cancel_order.id);
return;
}
}
for (auto& [symbol, ask_map] : asks_) {
auto const it = std::ranges::find_if(ask_map, matches_order);
if (it != ask_map.end()) {
// Set-up automatic call-back in case top-of-book changes
auto _ = CallbackOnTopOfBookChange(symbol, *this);
ask_map.erase(it);
listener_->on_acknowledgement(cancel_order.user, cancel_order.id);
return;
}
}
listener_->on_bad_order(cancel_order.user, cancel_order.id);
}
void Engine::operator()(FlushOrder const&) {
bids_.clear();
asks_.clear();
listener_->on_flush();
}
} // namespace kraken::engine

46
src/engine/engine.hh Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include <functional>
#include <map>
#include <memory>
#include <vector>
#include "book/order.hh"
namespace kraken::engine {
struct CallbackOnTopOfBookChange;
struct EngineListener;
/// Matching engine which processes orders and keeps the book up-to-date.
struct Engine {
Engine(std::shared_ptr<EngineListener> listener);
/// Process orders, triggerring the listener on each event.
void process_orders(std::vector<Order> const& orders);
private:
void operator()(TradeOrder const& trade_order);
void operator()(CancelOrder const& cancel_order);
void operator()(FlushOrder const& flush_order);
std::shared_ptr<EngineListener> listener_;
// Symbol, price, side are implicit given the way the book is represented
struct OrderMetaData {
User user;
UserOrderId id;
Quantity quantity;
};
friend struct CallbackOnTopOfBookChange;
// Sorted by price, then by time
template <typename Order>
using instrument_side_data = std::multimap<Price, OrderMetaData, Order>;
std::map<Symbol, instrument_side_data<std::less<void>>> bids_;
std::map<Symbol, instrument_side_data<std::greater<void>>> asks_;
};
} // namespace kraken::engine