Skip to content
Merged
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
98 changes: 88 additions & 10 deletions include/orderbook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,44 +53,99 @@ class OrderBook {
template <class Notification>
void OrderBook<Notification>::addOrder(OrderID id, Type type, Side side, Decimal qty, Decimal price, Flag flag) {
if (qty.is_zero()) [[unlikely]] {
notification_.putOrder(MsgType::CreateOrder, OrderStatus::Rejected, id, qty, qty, Error::InvalidQty);
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.status = OrderStatus::Rejected,
.qty = qty,
.original_qty = qty,
.error = Error::InvalidQty,
});
return;
}

if (!matching_) [[unlikely]] {
if (type == Type::Market) {
notification_.putOrder(MsgType::CreateOrder, OrderStatus::Rejected, id, qty, qty, Error::NoMatching);
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.status = OrderStatus::Rejected,
.qty = qty,
.original_qty = qty,
.error = Error::NoMatching,
});
return;
}

if (side == Side::Buy) {
auto q = asks_.getQueue();
if (q != nullptr && q->price() <= price) {
notification_.putOrder(MsgType::CreateOrder, OrderStatus::Rejected, id, qty, qty, Error::NoMatching);
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.status = OrderStatus::Rejected,
.qty = qty,
.original_qty = qty,
.error = Error::NoMatching,
});
return;
}
} else {
auto q = bids_.getQueue();
if (q != nullptr && q->price() >= price) {
notification_.putOrder(MsgType::CreateOrder, OrderStatus::Rejected, id, qty, qty, Error::NoMatching);
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.status = OrderStatus::Rejected,
.qty = qty,
.original_qty = qty,
.error = Error::NoMatching,
});
return;
}
}
}

if (type != Type::Market) {
if (orders_.find(id, orderbook::OrderIDCompare()) != orders_.end()) {
notification_.putOrder(MsgType::CreateOrder, OrderStatus::Rejected, id, uint64_t(0), qty, Error::OrderExists);
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.status = OrderStatus::Rejected,
.qty = uint64_t(0),
.original_qty = qty,
.error = Error::OrderExists,
});
return;
}

if (price.is_zero()) {
notification_.putOrder(MsgType::CreateOrder, OrderStatus::Rejected, id, uint64_t(0), qty, Error::InvalidPrice);
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.status = OrderStatus::Rejected,
.qty = uint64_t(0),
.original_qty = qty,
.error = Error::InvalidPrice,
});
return;
}
}

notification_.putOrder(MsgType::CreateOrder, OrderStatus::Accepted, id, qty, qty);
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::New,
.msg_type = MsgType::CreateOrder,
.order_id = id,
.status = OrderStatus::Accepted,
.qty = qty,
.original_qty = qty,
});
processOrder(id, type, side, qty, price, flag);
}

Expand Down Expand Up @@ -142,18 +197,41 @@ 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) {
notification_.putTrade(mOrderID, tOrderID, mStatus, tStatus, qty, price);
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Trade,
.maker_order_id = mOrderID,
.taker_order_id = tOrderID,
.maker_status = mStatus,
.taker_status = tStatus,
.last_qty = qty,
.last_price = price,
});
}

template <class Notification>
void OrderBook<Notification>::cancelOrder(OrderID id) {
auto [qty, original_qty] = eraseOrder(id);
if (qty.is_zero()) {
notification_.putOrder(MsgType::CancelOrder, OrderStatus::Rejected, id, uint64_t(0), uint64_t(0), Error::OrderNotExists);
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Rejected,
.msg_type = MsgType::CancelOrder,
.order_id = id,
.status = OrderStatus::Rejected,
.qty = uint64_t(0),
.original_qty = uint64_t(0),
.error = Error::OrderNotExists,
});
return;
}

notification_.putOrder(MsgType::CancelOrder, OrderStatus::Canceled, id, qty, original_qty);
notification_.onExecutionReport(ExecutionReport{
.exec_type = ExecType::Canceled,
.msg_type = MsgType::CancelOrder,
.order_id = id,
.status = OrderStatus::Canceled,
.qty = qty,
.original_qty = original_qty,
});
}

template <class Notification>
Expand Down
53 changes: 33 additions & 20 deletions include/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <cstdint>
#include <cwchar>
#include <iostream>
#include <optional>

#include "decimal.hpp"

Expand Down Expand Up @@ -71,37 +72,49 @@ enum Flag : uint8_t {

std::ostream& operator<<(std::ostream& os, const Flag& flag);

struct Trade {
uint64_t MakerOrderID;
uint64_t TakerOrderID;
OrderStatus MakerStatus;
OrderStatus TakerStatus;
Decimal Qty;
Decimal Price;
enum class ExecType : uint8_t {
New,
Rejected,
Canceled,
Trade,
};

std::ostream& operator<<(std::ostream& os, const ExecType& execType);

struct ExecutionReport {
ExecType exec_type{};

// Order event fields (New, Rejected, Canceled)
MsgType msg_type{};
OrderID order_id{};
OrderStatus status{};
Decimal qty{};
Decimal original_qty{};

// Trade event fields
OrderID maker_order_id{};
OrderID taker_order_id{};
OrderStatus maker_status{};
OrderStatus taker_status{};
Decimal last_qty{};
Decimal last_price{};

// Rejection reason (Rejected only)
std::optional<Error> error{};
};

// Notification is the interface for actual implementation of a notification handler
template <typename Implementation>
class NotificationInterface {
public:
void putOrder(MsgType msgtype, OrderStatus status, OrderID id, Decimal qty, Decimal original_qty, Error err) {
static_cast<Implementation*>(this)->putOrder(msgtype, status, id, qty, original_qty, err);
}

void putOrder(MsgType msgtype, OrderStatus status, OrderID id, Decimal qty, Decimal original_qty) { static_cast<Implementation*>(this)->putOrder(msgtype, status, id, qty, original_qty); }

void putTrade(OrderID mOrderID, OrderID tOrderID, OrderStatus mStatus, OrderStatus tStatus, Decimal qty, Decimal price) {
static_cast<Implementation*>(this)->putTrade(mOrderID, tOrderID, mStatus, tStatus, qty, price);
void onExecutionReport(const ExecutionReport& report) {
static_cast<Implementation*>(this)->onExecutionReport(report);
}
};

class EmptyNotification : public NotificationInterface<EmptyNotification> {
public:
void putOrder(MsgType msgtype, OrderStatus status, OrderID id, Decimal qty, Decimal original_qty, Error err) {}

void putOrder(MsgType msgtype, OrderStatus status, OrderID id, Decimal qty, Decimal original_qty) {}

void putTrade(OrderID mOrderID, OrderID tOrderID, OrderStatus mStatus, OrderStatus tStatus, Decimal qty, Decimal price) {}
void onExecutionReport(const ExecutionReport&) {}
};

} // namespace orderbook
14 changes: 14 additions & 0 deletions src/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,18 @@ std::ostream& operator<<(std::ostream& os, const Flag& flag) {
return os << "Unknown";
}

std::ostream& operator<<(std::ostream& os, const ExecType& execType) {
switch (execType) {
case ExecType::New:
return os << "New";
case ExecType::Rejected:
return os << "Rejected";
case ExecType::Canceled:
return os << "Canceled";
case ExecType::Trade:
return os << "Trade";
}
return os << "Unknown";
}

} // namespace orderbook
94 changes: 23 additions & 71 deletions test/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,102 +17,54 @@ using orderbook::OrderStatus;
using orderbook::Side;
using orderbook::Type;

class NotificationBase {
public:
virtual ~NotificationBase() = default;
[[nodiscard]] virtual std::string to_string() const = 0;
};

struct OrderNotification : public NotificationBase {
orderbook::MsgType msg_type_;
orderbook::OrderStatus status_;
orderbook::OrderID order_id_;
orderbook::Decimal qty_;
orderbook::Decimal original_qty_;
std::optional<orderbook::Error> error_;

OrderNotification(orderbook::MsgType msg_type, orderbook::OrderStatus status, orderbook::OrderID order_id, const orderbook::Decimal& qty,
const orderbook::Decimal& original_qty, std::optional<orderbook::Error> error)
: msg_type_(msg_type), status_(status), order_id_(order_id), qty_(qty), original_qty_(original_qty), error_(error){};

[[nodiscard]] std::string to_string() const override {
std::ostringstream os;
if (error_.has_value()) {
os << msg_type_ << " " << status_ << " " << order_id_ << " " << qty_ << " " << original_qty_ << " Err" << *error_;
} else {
os << msg_type_ << " " << status_ << " " << order_id_ << " " << qty_ << " " << original_qty_;
}
return os.str();
}
};

struct Trade : public NotificationBase {
OrderID MakerOrderID;
OrderID TakerOrderID;
OrderStatus MakerStatus;
OrderStatus TakerStatus;
Decimal Qty;
Decimal Price;

Trade(OrderID mID, OrderID tID, OrderStatus mStatus, OrderStatus tStatus, Decimal qty, Decimal price)
: MakerOrderID(mID), TakerOrderID(tID), MakerStatus(mStatus), TakerStatus(tStatus), Qty(qty), Price(price) {}

std::string to_string() const override {
std::ostringstream os;
os << MakerOrderID << " " << TakerOrderID << " " << MakerStatus << " " << TakerStatus << " " << Qty.to_string() << " " << Price.to_string();
return os.str();
}
};

class Notification : public orderbook::NotificationInterface<Notification> {
public:
void Reset() { n.clear(); }

void putOrder(MsgType m, OrderStatus s, OrderID orderID, Decimal qty, Decimal original_qty, orderbook::Error err) {
auto notification = std::make_shared<OrderNotification>(m, s, orderID, qty, original_qty, err);
n.emplace_back(notification);
}

void putOrder(MsgType m, OrderStatus s, OrderID orderID, Decimal qty, Decimal original_qty) {
auto notification = std::make_shared<OrderNotification>(m, s, orderID, qty, original_qty, std::optional<orderbook::Error>{});
n.emplace_back(notification);
}
void Reset() { reports_.clear(); }

void putTrade(OrderID mID, OrderID tID, OrderStatus mStatus, OrderStatus tStatus, Decimal qty, Decimal price) {
auto notification = std::make_shared<Trade>(mID, tID, mStatus, tStatus, qty, price);
n.emplace_back(notification);
}
void onExecutionReport(const orderbook::ExecutionReport& r) { reports_.emplace_back(r); }

std::vector<std::string> Strings() const {
std::vector<std::string> res;
for (const auto& item : n) {
res.push_back(item->to_string());
for (const auto& r : reports_) {
res.push_back(to_string(r));
}
return res;
}

std::string String() const {
std::ostringstream oss;
for (const auto& item : n) {
oss << item->to_string() << '\n';
for (const auto& r : reports_) {
oss << to_string(r) << '\n';
}
return oss.str();
}

bool hasError() {
for (const auto& item : n) {
auto notification = dynamic_cast<OrderNotification*>(item.get());
if (notification != nullptr && notification->error_) {
for (const auto& r : reports_) {
if (r.exec_type == orderbook::ExecType::Rejected) {
return true;
}
}

return false;
}

void Verify(const std::vector<std::string>& expected) const { ASSERT_EQ(expected, Strings()); }

private:
std::vector<std::shared_ptr<NotificationBase>> n;
std::vector<orderbook::ExecutionReport> reports_;

static std::string to_string(const orderbook::ExecutionReport& r) {
std::ostringstream os;
if (r.exec_type == orderbook::ExecType::Trade) {
os << r.maker_order_id << " " << r.taker_order_id << " " << r.maker_status << " " << r.taker_status << " " << r.last_qty.to_string() << " "
<< r.last_price.to_string();
} else {
os << r.msg_type << " " << r.status << " " << r.order_id << " " << r.qty << " " << r.original_qty;
if (r.error.has_value()) {
os << " Err" << *r.error;
}
}
return os.str();
}
};

Loading