Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/order.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ using namespace boost::intrusive;

struct Order : public set_base_hook<optimize_size<false>>, list_base_hook<constant_time_size<true>> {
OrderID id;
UserID user_id;
Decimal qty;
Decimal original_qty;
Decimal price;
Expand All @@ -24,7 +25,7 @@ struct Order : public set_base_hook<optimize_size<false>>, list_base_hook<consta

OrderQueue *queue = nullptr;

Order(OrderID id, Type type, Side side, Decimal qty, Decimal price, Flag flag) : id(id), qty(qty), original_qty(qty), price(price), type(type), flag(flag), side(side){};
Order(OrderID id, UserID user_id, Type type, Side side, Decimal qty, Decimal price, Flag flag) : id(id), user_id(user_id), qty(qty), original_qty(qty), price(price), type(type), flag(flag), side(side){};

friend bool operator<(const Order &a, const Order &b) { return a.id < b.id; }
friend bool operator>(const Order &a, const Order &b) { return a.id > b.id; }
Expand Down
52 changes: 32 additions & 20 deletions include/orderbook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <cstdint>
#include <map>
#include <sstream>
#include <tuple>
#include <utility>

#include "boost/intrusive/rbtree.hpp"
Expand All @@ -24,8 +25,8 @@ class OrderBook {
asks_(PriceLevel<PriceType::Ask>(price_level_pool_size)),
orders_(OrderMap()){};

void addOrder(OrderID id, Type type, Side side, Decimal qty, Decimal price, Flag flag);
void putTradeNotification(OrderID mOrderID, OrderID tOrderID, OrderStatus mStatus, OrderStatus tStatus, Decimal qty, Decimal price);
void addOrder(OrderID id, UserID user_id, Type type, Side side, Decimal qty, Decimal price, Flag flag);
void putTradeNotification(OrderID mOrderID, OrderID tOrderID, UserID mUserID, UserID tUserID, OrderStatus mStatus, OrderStatus tStatus, Decimal qty, Decimal price);
void cancelOrder(OrderID id);
bool hasOrder(OrderID id);
void setMatching(bool matching) { matching_ = matching; }
Expand All @@ -46,17 +47,18 @@ class OrderBook {

bool matching_ = true;

std::pair<Decimal, Decimal> eraseOrder(OrderID id);
void processOrder(OrderID id, Type type, Side side, Decimal qty, Decimal price, Flag flag);
std::tuple<Decimal, Decimal, UserID> eraseOrder(OrderID id);
void processOrder(OrderID id, UserID user_id, Type type, Side side, Decimal qty, Decimal price, Flag flag);
};

template <class Notification>
void OrderBook<Notification>::addOrder(OrderID id, Type type, Side side, Decimal qty, Decimal price, Flag flag) {
void OrderBook<Notification>::addOrder(OrderID id, UserID user_id, Type type, Side side, Decimal qty, Decimal price, Flag flag) {
if (qty.is_zero()) [[unlikely]] {
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.user_id = user_id,
.status = OrderStatus::Rejected,
.qty = qty,
.original_qty = qty,
Expand All @@ -71,6 +73,7 @@ void OrderBook<Notification>::addOrder(OrderID id, Type type, Side side, Decimal
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.user_id = user_id,
.status = OrderStatus::Rejected,
.qty = qty,
.original_qty = qty,
Expand All @@ -86,6 +89,7 @@ void OrderBook<Notification>::addOrder(OrderID id, Type type, Side side, Decimal
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.user_id = user_id,
.status = OrderStatus::Rejected,
.qty = qty,
.original_qty = qty,
Expand All @@ -100,6 +104,7 @@ void OrderBook<Notification>::addOrder(OrderID id, Type type, Side side, Decimal
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.user_id = user_id,
.status = OrderStatus::Rejected,
.qty = qty,
.original_qty = qty,
Expand All @@ -116,6 +121,7 @@ void OrderBook<Notification>::addOrder(OrderID id, Type type, Side side, Decimal
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.user_id = user_id,
.status = OrderStatus::Rejected,
.qty = uint64_t(0),
.original_qty = qty,
Expand All @@ -129,6 +135,7 @@ void OrderBook<Notification>::addOrder(OrderID id, Type type, Side side, Decimal
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.user_id = user_id,
.status = OrderStatus::Rejected,
.qty = uint64_t(0),
.original_qty = qty,
Expand All @@ -142,36 +149,37 @@ void OrderBook<Notification>::addOrder(OrderID id, Type type, Side side, Decimal
.exec_type = ExecType::New,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.user_id = user_id,
.status = OrderStatus::Accepted,
.qty = qty,
.original_qty = qty,
});
processOrder(id, type, side, qty, price, flag);
processOrder(id, user_id, type, side, qty, price, flag);
}

template <class Notification>
void OrderBook<Notification>::processOrder(OrderID id, Type type, Side side, Decimal qty, Decimal price, Flag flag) {
const auto tradeNotification = [this](OrderID mOrderID, OrderID tOrderID, OrderStatus mOrderStatus, OrderStatus tOrderStatus, Decimal qty, Decimal price) {
this->putTradeNotification(mOrderID, tOrderID, mOrderStatus, tOrderStatus, qty, price);
void OrderBook<Notification>::processOrder(OrderID id, UserID user_id, Type type, Side side, Decimal qty, Decimal price, Flag flag) {
const auto tradeNotification = [this](OrderID mOrderID, OrderID tOrderID, UserID mUserID, UserID tUserID, OrderStatus mOrderStatus, OrderStatus tOrderStatus, Decimal qty, Decimal price) {
this->putTradeNotification(mOrderID, tOrderID, mUserID, tUserID, mOrderStatus, tOrderStatus, qty, price);
this->last_price = price;
};
const auto postOrderFill = [this](OrderID id) { this->eraseOrder(id); };

if (type == Type::Market) {
if (side == Side::Buy) {
asks_.processMarketOrder(tradeNotification, postOrderFill, id, qty, flag);
asks_.processMarketOrder(tradeNotification, postOrderFill, id, user_id, qty, flag);
} else {
bids_.processMarketOrder(tradeNotification, postOrderFill, id, qty, flag);
bids_.processMarketOrder(tradeNotification, postOrderFill, id, user_id, qty, flag);
}

return;
}

Decimal qtyProcessed;
if (side == Side::Buy) {
qtyProcessed = asks_.processLimitOrder(tradeNotification, postOrderFill, id, price, qty, flag);
qtyProcessed = asks_.processLimitOrder(tradeNotification, postOrderFill, id, user_id, price, qty, flag);
} else {
qtyProcessed = bids_.processLimitOrder(tradeNotification, postOrderFill, id, price, qty, flag);
qtyProcessed = bids_.processLimitOrder(tradeNotification, postOrderFill, id, user_id, price, qty, flag);
}

if ((flag & (IoC | FoK)) != 0) {
Expand All @@ -180,7 +188,7 @@ void OrderBook<Notification>::processOrder(OrderID id, Type type, Side side, Dec

auto qtyLeft = qty - qtyProcessed;
if (qtyLeft > uint64_t(0)) {
auto* o = order_pool_.acquire(id, type, side, qtyLeft, price, flag);
auto* o = order_pool_.acquire(id, user_id, type, side, qtyLeft, price, flag);
o->original_qty = qty;
if (side == Side::Buy) {
bids_.append(o);
Expand All @@ -195,11 +203,13 @@ void OrderBook<Notification>::processOrder(OrderID id, Type type, Side side, Dec
}

template <class Notification>
void OrderBook<Notification>::putTradeNotification(OrderID mOrderID, OrderID tOrderID, OrderStatus mStatus, OrderStatus tStatus, Decimal qty, Decimal price) {
void OrderBook<Notification>::putTradeNotification(OrderID mOrderID, OrderID tOrderID, UserID mUserID, UserID tUserID, OrderStatus mStatus, OrderStatus tStatus, Decimal qty, Decimal price) {
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Trade,
.maker_order_id = mOrderID,
.taker_order_id = tOrderID,
.maker_user_id = mUserID,
.taker_user_id = tUserID,
.maker_status = mStatus,
.taker_status = tStatus,
.last_qty = qty,
Expand All @@ -209,7 +219,7 @@ void OrderBook<Notification>::putTradeNotification(OrderID mOrderID, OrderID tOr

template <class Notification>
void OrderBook<Notification>::cancelOrder(OrderID id) {
auto [qty, original_qty] = eraseOrder(id);
auto [qty, original_qty, user_id] = eraseOrder(id);
if (qty.is_zero()) {
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Rejected,
Expand All @@ -227,31 +237,33 @@ void OrderBook<Notification>::cancelOrder(OrderID id) {
.exec_type = ExecType::Canceled,
.msg_type = MsgType::CancelOrder,
.order_id = id,
.user_id = user_id,
.status = OrderStatus::Canceled,
.qty = qty,
.original_qty = original_qty,
});
}

template <class Notification>
std::pair<Decimal, Decimal> OrderBook<Notification>::eraseOrder(OrderID id) {
std::tuple<Decimal, Decimal, UserID> OrderBook<Notification>::eraseOrder(OrderID id) {
auto it = orders_.find(id, OrderIDCompare());
if (it == orders_.end()) {
return {uint64_t(0), uint64_t(0)};
return {uint64_t(0), uint64_t(0), uint64_t(0)};
}

auto& pool = order_pool_;
auto* order = &*it;
scope_exit defer([&pool, &order]() { pool.release(order); });
orders_.erase(*it);

const UserID user_id = order->user_id;
if (order->side == Side::Buy) {
bids_.remove(order);
return {order->qty, order->original_qty};
return {order->qty, order->original_qty, user_id};
}

asks_.remove(order);
return {order->qty, order->original_qty};
return {order->qty, order->original_qty, user_id};
}

template <class Notification>
Expand Down
5 changes: 3 additions & 2 deletions include/orderqueue.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace orderbook {

using TradeNotification =
std::function<void(OrderID mOrderID, OrderID tOrderID, OrderStatus mOrderStatus, OrderStatus tOrderStatus, Decimal qty, Decimal price)>;
std::function<void(OrderID mOrderID, OrderID tOrderID, UserID mUserID, UserID tUserID, OrderStatus mOrderStatus, OrderStatus tOrderStatus, Decimal qty, Decimal price)>;
using PostOrderFill = std::function<void(OrderID canceledOrderID)>;

using OrderList = boost::intrusive::list<Order>;
Expand All @@ -28,7 +28,8 @@ class OrderQueue : public boost::intrusive::set_base_hook<boost::intrusive::opti
[[nodiscard]] Decimal totalQty() const;
void append(Order *o);
void remove(Order *o);
Decimal process(const TradeNotification &tn, const PostOrderFill &postFill, OrderID takerOrderID, Decimal qty);
Decimal process(const TradeNotification &tn, const PostOrderFill &postFill, OrderID takerOrderID, UserID takerUserID, Decimal qty);
[[nodiscard]] Decimal availableQty(UserID takerUserID) const;
[[nodiscard]] const OrderList &order_list() const { return orders_; }

friend bool operator<(const OrderQueue &a, const OrderQueue &b) { return a.price_ < b.price_; }
Expand Down
4 changes: 2 additions & 2 deletions include/pricelevel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ class PriceLevel {
[[nodiscard]] OrderQueue* getNextQueue(const Decimal& price);

template <PriceType Q = P>
Decimal processMarketOrder(const TradeNotification& tn, const PostOrderFill& pf, OrderID takerOrderID, Decimal qty, Flag flag);
Decimal processMarketOrder(const TradeNotification& tn, const PostOrderFill& pf, OrderID takerOrderID, UserID takerUserID, Decimal qty, Flag flag);

template <PriceType Q = P>
Decimal processLimitOrder(const TradeNotification& tn, const PostOrderFill& pf, OrderID takerOrderID, Decimal price, Decimal qty, Flag flag);
Decimal processLimitOrder(const TradeNotification& tn, const PostOrderFill& pf, OrderID takerOrderID, UserID takerUserID, Decimal price, Decimal qty, Flag flag);

const PriceTree& price_tree() const { return price_tree_; };
};
Expand Down
5 changes: 5 additions & 0 deletions include/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace orderbook {

using Decimal = decimal::U8;
using OrderID = uint64_t;
using UserID = uint64_t;

enum class Type : uint8_t {
Limit,
Expand Down Expand Up @@ -58,6 +59,7 @@ enum class Error : uint16_t {
OrderNotExists,
InsufficientQty,
NoMatching,
SelfTrade,
};

std::ostream& operator<<(std::ostream& os, const Error& error);
Expand Down Expand Up @@ -87,13 +89,16 @@ struct ExecutionReport {
// Order event fields (New, Rejected, Canceled)
MsgType msg_type{};
OrderID order_id{};
UserID user_id{};
OrderStatus status{};
Decimal qty{};
Decimal original_qty{};

// Trade event fields
OrderID maker_order_id{};
OrderID taker_order_id{};
UserID maker_user_id{};
UserID taker_user_id{};
OrderStatus maker_status{};
OrderStatus taker_status{};
Decimal last_qty{};
Expand Down
4 changes: 2 additions & 2 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ void throughput(int64_t seed, int duration, orderbook::Decimal lowerBound, order
ob->cancelOrder(sellID);
buyID = ++nextID;
sellID = ++nextID;
ob->addOrder(buyID, Type::Limit, Side::Buy, bidQty, bid, Flag::None);
ob->addOrder(sellID, Type::Limit, Side::Sell, askQty, ask, Flag::None);
ob->addOrder(buyID, 0, Type::Limit, Side::Buy, bidQty, bid, Flag::None);
ob->addOrder(sellID, 0, Type::Limit, Side::Sell, askQty, ask, Flag::None);

operations += 4; // 2 cancels + 2 adds
}
Expand Down
25 changes: 22 additions & 3 deletions src/orderqueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,41 @@ void OrderQueue::remove(Order* o) {
}
}

Decimal OrderQueue::process(const TradeNotification& tradeNotification, const PostOrderFill& postFill, OrderID takerOrderID, Decimal qty) {
Decimal OrderQueue::availableQty(UserID takerUserID) const {
// When STP is disabled (takerUserID == 0) all orders are eligible; skip the per-order scan.
if (takerUserID == 0) {
return total_qty_;
}
Decimal avail{};
for (const auto& o : orders_) {
if (o.user_id != takerUserID) {
avail += o.qty;
}
}
return avail;
}

Decimal OrderQueue::process(const TradeNotification& tradeNotification, const PostOrderFill& postFill, OrderID takerOrderID, UserID takerUserID, Decimal qty) {
Decimal qtyProcessed = {};
BOOST_ASSERT(orders_.begin() != orders_.end());
for (auto it = orders_.begin(); it != orders_.end() && qty > uint64_t(0);) {
BOOST_ASSERT(it != orders_.end());
BOOST_ASSERT(orders_.begin() != orders_.end());
auto* ho = &*it;
BOOST_ASSERT(ho != nullptr);
if (takerUserID != 0 && ho->user_id == takerUserID) {
++it;
continue;
}
if (qty < ho->qty) {
qtyProcessed += qty;
ho->qty -= qty;
total_qty_ -= qty;
tradeNotification(ho->id, takerOrderID, OrderStatus::FilledPartial, OrderStatus::FilledComplete, qty, ho->price);
tradeNotification(ho->id, takerOrderID, ho->user_id, takerUserID, OrderStatus::FilledPartial, OrderStatus::FilledComplete, qty, ho->price);
break;
} else {
const auto makerOrderID = ho->id;
const auto makerUserID = ho->user_id;
const auto makerPrice = ho->price;
auto matchedQty = ho->qty;
qtyProcessed += matchedQty;
Expand All @@ -56,7 +75,7 @@ Decimal OrderQueue::process(const TradeNotification& tradeNotification, const Po
postFill(makerOrderID);
// qty has already been decremented by matchedQty, so zero means taker is fully filled.
const auto takerStatus = qty.is_zero() ? OrderStatus::FilledComplete : OrderStatus::FilledPartial;
tradeNotification(makerOrderID, takerOrderID, OrderStatus::FilledComplete, takerStatus, matchedQty, makerPrice);
tradeNotification(makerOrderID, takerOrderID, makerUserID, takerUserID, OrderStatus::FilledComplete, takerStatus, matchedQty, makerPrice);
}
}

Expand Down
Loading