From a0cd858d820358beb750b8d44227767255bca18f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 14:15:12 +0000 Subject: [PATCH] refactor: replace putOrder/putTrade with unified onExecutionReport stream Agent-Logs-Url: https://github.com/geseq/cpp-orderbook/sessions/c6dfee74-fd68-44a8-b15e-09a20354cdaa Co-authored-by: geseq <5458743+geseq@users.noreply.github.com> --- include/orderbook.hpp | 98 ++++++++++++++++++++++++++++++++++++++----- include/types.hpp | 53 ++++++++++++++--------- src/types.cpp | 14 +++++++ test/util.cpp | 94 ++++++++++------------------------------- 4 files changed, 158 insertions(+), 101 deletions(-) diff --git a/include/orderbook.hpp b/include/orderbook.hpp index e1221a7..92f1714 100644 --- a/include/orderbook.hpp +++ b/include/orderbook.hpp @@ -53,26 +53,58 @@ class OrderBook { template void OrderBook::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; } } @@ -80,17 +112,40 @@ void OrderBook::addOrder(OrderID id, Type type, Side side, Decimal 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); } @@ -142,18 +197,41 @@ void OrderBook::processOrder(OrderID id, Type type, Side side, Dec template void OrderBook::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 void OrderBook::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 diff --git a/include/types.hpp b/include/types.hpp index 2f4aa10..b42a50c 100644 --- a/include/types.hpp +++ b/include/types.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "decimal.hpp" @@ -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{}; }; // Notification is the interface for actual implementation of a notification handler template class NotificationInterface { public: - void putOrder(MsgType msgtype, OrderStatus status, OrderID id, Decimal qty, Decimal original_qty, Error err) { - static_cast(this)->putOrder(msgtype, status, id, qty, original_qty, err); - } - - void putOrder(MsgType msgtype, OrderStatus status, OrderID id, Decimal qty, Decimal original_qty) { static_cast(this)->putOrder(msgtype, status, id, qty, original_qty); } - - void putTrade(OrderID mOrderID, OrderID tOrderID, OrderStatus mStatus, OrderStatus tStatus, Decimal qty, Decimal price) { - static_cast(this)->putTrade(mOrderID, tOrderID, mStatus, tStatus, qty, price); + void onExecutionReport(const ExecutionReport& report) { + static_cast(this)->onExecutionReport(report); } }; class EmptyNotification : public NotificationInterface { 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 diff --git a/src/types.cpp b/src/types.cpp index 326c8d2..fc7a71f 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -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 diff --git a/test/util.cpp b/test/util.cpp index ffb8559..19ff668 100644 --- a/test/util.cpp +++ b/test/util.cpp @@ -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 error_; - - OrderNotification(orderbook::MsgType msg_type, orderbook::OrderStatus status, orderbook::OrderID order_id, const orderbook::Decimal& qty, - const orderbook::Decimal& original_qty, std::optional 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 { 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(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(m, s, orderID, qty, original_qty, std::optional{}); - 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(mID, tID, mStatus, tStatus, qty, price); - n.emplace_back(notification); - } + void onExecutionReport(const orderbook::ExecutionReport& r) { reports_.emplace_back(r); } std::vector Strings() const { std::vector 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(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& expected) const { ASSERT_EQ(expected, Strings()); } private: - std::vector> n; + std::vector 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(); + } };