diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cc3870..e747c5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,10 @@ cmake_minimum_required(VERSION 3.20 FATAL_ERROR) +find_program(CCACHE_PROGRAM ccache) +if (CCACHE_PROGRAM) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") +endif() + set(CPP_ORDERBOOK orderbook) project(${CPP_ORDERBOOK} LANGUAGES CXX) diff --git a/include/orderbook.hpp b/include/orderbook.hpp index 3a28a14..c5fb4d8 100644 --- a/include/orderbook.hpp +++ b/include/orderbook.hpp @@ -20,10 +20,10 @@ class OrderBook { OrderBook(NotificationInterface& n, size_t price_level_pool_size = 16384, size_t order_pool_size = 16384) : order_pool_(order_pool_size), notification_(static_cast(n)), - bids_(PriceLevel(PriceType::Bid, price_level_pool_size)), - asks_(PriceLevel(PriceType::Ask, price_level_pool_size)), - trigger_over_(PriceLevel(PriceType::Trigger, price_level_pool_size)), - trigger_under_(PriceLevel(PriceType::Trigger, price_level_pool_size)), + bids_(PriceLevel(price_level_pool_size)), + asks_(PriceLevel(price_level_pool_size)), + trigger_over_(PriceLevel(price_level_pool_size)), + trigger_under_(PriceLevel(price_level_pool_size)), orders_(OrderMap()), trig_orders_(OrderMap()){}; @@ -39,10 +39,10 @@ class OrderBook { private: pool::AdaptiveObjectPool order_pool_; - PriceLevel bids_; - PriceLevel asks_; - PriceLevel trigger_over_; - PriceLevel trigger_under_; + PriceLevel bids_; + PriceLevel asks_; + PriceLevel trigger_over_; + PriceLevel trigger_under_; OrderMap orders_; OrderMap trig_orders_; diff --git a/include/orderqueue.hpp b/include/orderqueue.hpp index 80efbee..771b28e 100644 --- a/include/orderqueue.hpp +++ b/include/orderqueue.hpp @@ -16,6 +16,7 @@ class OrderQueue : public boost::intrusive::set_base_hook(const OrderQueue &a, const OrderQueue &b) { return a.price_ > b.price_; } friend bool operator==(const OrderQueue &a, const OrderQueue &b) { return a.price_ == b.price_; } + + friend class PriceCompare; }; struct PriceCompare { diff --git a/include/pricelevel.hpp b/include/pricelevel.hpp index 641cc45..31c1158 100644 --- a/include/pricelevel.hpp +++ b/include/pricelevel.hpp @@ -20,58 +20,36 @@ class Compare { using CmpGreater = boost::intrusive::compare>; using CmpLess = boost::intrusive::compare>; -template +template class PriceLevel { pool::AdaptiveObjectPool queue_pool_; + using CompareType = std::conditional_t<(P == PriceType::Bid || P == PriceType::TriggerOver), CmpGreater, CmpLess>; using PriceTree = boost::intrusive::rbtree; PriceTree price_tree_; - PriceType price_type_; + PriceType price_type_ = P; Decimal volume_; uint64_t num_orders_ = 0; uint64_t depth_ = 0; public: - PriceLevel(PriceType price_type, size_t price_level_pool_size) : price_type_(price_type), queue_pool_(price_level_pool_size){}; + PriceLevel(size_t price_level_pool_size) : queue_pool_(price_level_pool_size){}; uint64_t len(); uint64_t depth(); Decimal volume(); - OrderQueue* getQueue(); + [[nodiscard]] OrderQueue* getQueue(); + [[nodiscard]] OrderQueue* getNextQueue(const Decimal& price); + [[nodiscard]] OrderQueue* largestLessThan(const Decimal& price); + [[nodiscard]] OrderQueue* smallestGreaterThan(const Decimal& price); + void append(Order* order); void remove(Order* order); + Decimal processMarketOrder(const TradeNotification& tn, const PostOrderFill& pf, OrderID takerOrderID, Decimal qty, Flag flag); Decimal processLimitOrder(const TradeNotification& tn, const PostOrderFill& pf, OrderID& takerOrderID, Decimal& price, Decimal qty, Flag& flag); - PriceTree& price_tree() { return price_tree_; } - - OrderQueue* LargestLessThan(const Decimal& price) { - auto it = price_tree_.lower_bound(price, PriceCompare()); - if (it != price_tree_.begin()) { - --it; - return &(*it); - } - return nullptr; - } - - OrderQueue* SmallestGreaterThan(const Decimal& price) { - auto it = price_tree_.upper_bound(price, PriceCompare()); - if (it != price_tree_.end()) { - return &(*it); - } - return nullptr; - } - - OrderQueue* GetNextQueue(const Decimal& price) { - switch (price_type_) { - case PriceType::Bid: - return LargestLessThan(price); - case PriceType::Ask: - return SmallestGreaterThan(price); - default: - throw std::runtime_error("invalid call to GetQueue"); - } - } + PriceTree& price_tree() { return price_tree_; }; }; } // namespace orderbook diff --git a/include/types.hpp b/include/types.hpp index bd3ff33..3b00716 100644 --- a/include/types.hpp +++ b/include/types.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "decimal.hpp" @@ -44,7 +45,8 @@ std::ostream& operator<<(std::ostream& os, const OrderStatus& status); enum class PriceType { Bid, Ask, - Trigger, + TriggerOver, + TriggerUnder, }; std::ostream& operator<<(std::ostream& os, const PriceType& priceType); diff --git a/src/order.cpp b/src/order.cpp index bd88215..f803e0e 100644 --- a/src/order.cpp +++ b/src/order.cpp @@ -5,7 +5,7 @@ namespace orderbook { Decimal Order::getPrice(PriceType pt) { - if (pt == PriceType::Trigger) { + if (pt == PriceType::TriggerOver || pt == PriceType::TriggerUnder) [[unlikely]] { return trig_price; } diff --git a/src/pricelevel.cpp b/src/pricelevel.cpp index 9a7bc9a..fd180a0 100644 --- a/src/pricelevel.cpp +++ b/src/pricelevel.cpp @@ -12,23 +12,23 @@ namespace orderbook { -template -uint64_t PriceLevel::len() { +template +uint64_t PriceLevel

::len() { return num_orders_; } -template -uint64_t PriceLevel::depth() { +template +uint64_t PriceLevel

::depth() { return depth_; } -template -Decimal PriceLevel::volume() { +template +Decimal PriceLevel

::volume() { return volume_; } -template -void PriceLevel::append(Order* order) { +template +void PriceLevel

::append(Order* order) { auto price = order->getPrice(price_type_); auto it = price_tree_.find(price); @@ -45,8 +45,8 @@ void PriceLevel::append(Order* order) { q->append(order); } -template -void PriceLevel::remove(Order* order) { +template +void PriceLevel

::remove(Order* order) { auto price = order->getPrice(price_type_); auto q = order->queue; @@ -64,8 +64,8 @@ void PriceLevel::remove(Order* order) { volume_ -= order->qty; } -template -OrderQueue* PriceLevel::getQueue() { +template +OrderQueue* PriceLevel

::getQueue() { auto q = price_tree_.begin(); if (q != price_tree_.end()) { return &*q; @@ -74,8 +74,8 @@ OrderQueue* PriceLevel::getQueue() { return nullptr; } -template -Decimal PriceLevel::processMarketOrder(const TradeNotification& tn, const PostOrderFill& pf, OrderID takerOrderID, Decimal qty, Flag flag) { +template +Decimal PriceLevel

::processMarketOrder(const TradeNotification& tn, const PostOrderFill& pf, OrderID takerOrderID, Decimal qty, Flag flag) { // TODO: this won't work as pricelevel volumes aren't accounted for correctly if ((flag & (AoN | FoK)) != 0 && qty > volume_) { return uint64_t(0); @@ -92,9 +92,8 @@ Decimal PriceLevel::processMarketOrder(const TradeNotification& tn, return uint64_t(0); }; -template -Decimal PriceLevel::processLimitOrder(const TradeNotification& tn, const PostOrderFill& pf, OrderID& takerOrderID, Decimal& price, Decimal qty, - Flag& flag) { +template +Decimal PriceLevel

::processLimitOrder(const TradeNotification& tn, const PostOrderFill& pf, OrderID& takerOrderID, Decimal& price, Decimal qty, Flag& flag) { Decimal qtyProcessed = {}; auto orderQueue = getQueue(); @@ -127,7 +126,7 @@ Decimal PriceLevel::processLimitOrder(const TradeNotification& tn, break; } aQty -= orderQueue->totalQty(); - orderQueue = GetNextQueue(orderQueue->price()); + orderQueue = getNextQueue(orderQueue->price()); } } else { while (orderQueue != nullptr && price > orderQueue->price()) { @@ -136,7 +135,7 @@ Decimal PriceLevel::processLimitOrder(const TradeNotification& tn, break; } aQty -= orderQueue->totalQty(); - orderQueue = GetNextQueue(orderQueue->price()); + orderQueue = getNextQueue(orderQueue->price()); } } @@ -157,7 +156,40 @@ Decimal PriceLevel::processLimitOrder(const TradeNotification& tn, return qtyProcessed; }; -template class PriceLevel; -template class PriceLevel; +template +OrderQueue* PriceLevel

::largestLessThan(const Decimal& price) { + auto it = price_tree_.lower_bound(price, PriceCompare()); + if (it != price_tree_.begin()) { + --it; + return &(*it); + } + return nullptr; +} + +template +OrderQueue* PriceLevel

::smallestGreaterThan(const Decimal& price) { + auto it = price_tree_.upper_bound(price, PriceCompare()); + if (it != price_tree_.end()) { + return &(*it); + } + return nullptr; +} + +template +OrderQueue* PriceLevel

::getNextQueue(const Decimal& price) { + switch (price_type_) { + case PriceType::Bid: + return largestLessThan(price); + case PriceType::Ask: + return smallestGreaterThan(price); + default: + throw std::runtime_error("invalid call to GetQueue"); + } +} + +template class PriceLevel; +template class PriceLevel; +template class PriceLevel; +template class PriceLevel; } // namespace orderbook diff --git a/src/types.cpp b/src/types.cpp index 8584f17..8be99bb 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -54,8 +54,10 @@ std::ostream& operator<<(std::ostream& os, const PriceType& priceType) { return os << "Bid"; case PriceType::Ask: return os << "Ask"; - case PriceType::Trigger: - return os << "Trigger"; + case PriceType::TriggerOver: + return os << "TriggerOver"; + case PriceType::TriggerUnder: + return os << "TriggerUnder"; } return os << "Unknown"; } diff --git a/test/pricelevel_test.cpp b/test/pricelevel_test.cpp index ef387d4..a0b373d 100644 --- a/test/pricelevel_test.cpp +++ b/test/pricelevel_test.cpp @@ -16,7 +16,7 @@ class PriceLevelTest : public ::testing::Test { }; TEST_F(PriceLevelTest, TestPriceLevel) { - PriceLevel bidLevel(PriceType::Bid, 10); + PriceLevel bidLevel(10); auto o1 = std::make_shared(1, Type::Limit, Side::Buy, Decimal(10, 0), Decimal(10, 0), Decimal(uint64_t(0)), Flag::None); auto o2 = std::make_shared(2, Type::Limit, Side::Buy, Decimal(10, 0), Decimal(20, 0), Decimal(uint64_t(0)), Flag::None); @@ -43,9 +43,10 @@ TEST_F(PriceLevelTest, TestPriceLevel) { ASSERT_EQ(bidLevel.depth(), 2); ASSERT_EQ(bidLevel.len(), 2); - if (tree.begin()->head() != o1.get() || tree.begin()->tail() != o1.get() || tree.rbegin()->head() != o2.get() || tree.rbegin()->tail() != o2.get()) { - FAIL() << "invalid price levels"; - } + ASSERT_EQ(tree.begin()->head(), o2.get()) << "Invalid price levels: head of the first element does not match o1"; + ASSERT_EQ(tree.begin()->tail(), o2.get()) << "Invalid price levels: tail of the first element does not match o1"; + ASSERT_EQ(tree.rbegin()->head(), o1.get()) << "Invalid price levels: head of the last element does not match o2"; + ASSERT_EQ(tree.rbegin()->tail(), o1.get()) << "Invalid price levels: tail of the last element does not match o2"; bidLevel.remove(o1.get()); @@ -62,7 +63,7 @@ TEST_F(PriceLevelTest, TestPriceLevel) { } TEST_F(PriceLevelTest, TestPriceFinding) { - PriceLevel askLevel(PriceType::Ask, 10); + PriceLevel askLevel(10); askLevel.append(new Order(1, Type::Limit, Side::Sell, Decimal(5, 0), Decimal(130, 0), Decimal(uint64_t(0)), Flag::None)); askLevel.append(new Order(2, Type::Limit, Side::Sell, Decimal(5, 0), Decimal(170, 0), Decimal(uint64_t(0)), Flag::None)); @@ -75,17 +76,17 @@ TEST_F(PriceLevelTest, TestPriceFinding) { ASSERT_EQ(askLevel.volume(), Decimal(40, 0)); - ASSERT_EQ(askLevel.LargestLessThan(Decimal(101, 0))->price(), Decimal(100, 0)); - ASSERT_EQ(askLevel.LargestLessThan(Decimal(150, 0))->price(), Decimal(140, 0)); - ASSERT_EQ(askLevel.LargestLessThan(Decimal(100, 0)), nullptr); + ASSERT_EQ(askLevel.largestLessThan(Decimal(101, 0))->price(), Decimal(100, 0)); + ASSERT_EQ(askLevel.largestLessThan(Decimal(150, 0))->price(), Decimal(140, 0)); + ASSERT_EQ(askLevel.largestLessThan(Decimal(100, 0)), nullptr); - ASSERT_EQ(askLevel.SmallestGreaterThan(Decimal(169, 0))->price(), Decimal(170, 0)); - ASSERT_EQ(askLevel.SmallestGreaterThan(Decimal(150, 0))->price(), Decimal(160, 0)); - ASSERT_EQ(askLevel.SmallestGreaterThan(Decimal(170, 0)), nullptr); + ASSERT_EQ(askLevel.smallestGreaterThan(Decimal(169, 0))->price(), Decimal(170, 0)); + ASSERT_EQ(askLevel.smallestGreaterThan(Decimal(150, 0))->price(), Decimal(160, 0)); + ASSERT_EQ(askLevel.smallestGreaterThan(Decimal(170, 0)), nullptr); } TEST_F(PriceLevelTest, TestStopQueuePriceFinding) { - PriceLevel trigLevel(PriceType::Trigger, 10); + PriceLevel trigLevel(10); trigLevel.append(new Order(1, Type::Limit, Side::Sell, Decimal(5, 0), Decimal(10, 0), Decimal(130, 0), Flag::None)); trigLevel.append(new Order(2, Type::Limit, Side::Sell, Decimal(5, 0), Decimal(20, 0), Decimal(170, 0), Flag::None)); @@ -98,13 +99,15 @@ TEST_F(PriceLevelTest, TestStopQueuePriceFinding) { ASSERT_EQ(trigLevel.volume(), Decimal(40, 0)); - ASSERT_EQ(trigLevel.LargestLessThan(Decimal(101, 0))->price(), Decimal(100, 0)); - ASSERT_EQ(trigLevel.LargestLessThan(Decimal(150, 0))->price(), Decimal(140, 0)); - ASSERT_EQ(trigLevel.LargestLessThan(Decimal(100, 0)), nullptr); + std::cout << 1 << std::endl; + ASSERT_EQ(trigLevel.largestLessThan(Decimal(101, 0))->price(), Decimal(100, 0)); + std::cout << 2 << std::endl; + ASSERT_EQ(trigLevel.largestLessThan(Decimal(150, 0))->price(), Decimal(140, 0)); + ASSERT_EQ(trigLevel.largestLessThan(Decimal(100, 0)), nullptr); - ASSERT_EQ(trigLevel.SmallestGreaterThan(Decimal(169, 0))->price(), Decimal(170, 0)); - ASSERT_EQ(trigLevel.SmallestGreaterThan(Decimal(150, 0))->price(), Decimal(160, 0)); - ASSERT_EQ(trigLevel.SmallestGreaterThan(Decimal(170, 0)), nullptr); + ASSERT_EQ(trigLevel.smallestGreaterThan(Decimal(169, 0))->price(), Decimal(170, 0)); + ASSERT_EQ(trigLevel.smallestGreaterThan(Decimal(150, 0))->price(), Decimal(160, 0)); + ASSERT_EQ(trigLevel.smallestGreaterThan(Decimal(170, 0)), nullptr); } } // namespace test