diff --git a/include/velox/core/symbol_engine.hpp b/include/velox/core/symbol_engine.hpp index feffbfe..881efc2 100644 --- a/include/velox/core/symbol_engine.hpp +++ b/include/velox/core/symbol_engine.hpp @@ -16,8 +16,8 @@ class SymbolEngine { m_engine(symbol, risk_manager, gateway, position_manager) {} // Called by Feed Handler thread to push an order from ITCH - void on_market_order(Order* order) { - m_engine.submit_order(order); + bool on_market_order(Order* order) { + return m_engine.submit_order(order); } // Called by Matching Engine thread to run matching @@ -41,6 +41,10 @@ class SymbolEngine { // Get symbol const char* symbol() { return m_book.symbol(); } + OrderBook& get_order_book() { + return m_book; + } + private: OrderBook m_book; MatchingEngine m_engine; diff --git a/include/velox/sim/strategy/bot_manager.hpp b/include/velox/sim/strategy/bot_manager.hpp new file mode 100644 index 0000000..0ab9608 --- /dev/null +++ b/include/velox/sim/strategy/bot_manager.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include "velox/sim/strategy/bots.hpp" +#include "velox/book/book_snapshot.hpp" +#include "lockfree/spsc_queue.hpp" + +namespace velox { +namespace bot { + +class BotManager { +public: + using OrderQueue = lockfree::SPSCQueue; + + BotManager(); + ~BotManager(); + + // Register a bot (takes ownership) + void register_bot(std::unique_ptr bot); + + // Called by snapshot thread to distribute snapshots to bots + void on_snapshot(const BookSnapshot& snapshot); + + // Called by matching engine thread to consume orders + bool pop_order(Order& order); + void push_order(const Order& order); + + // Statistics + size_t bot_count() const { return m_bots.size(); } + uint64_t orders_queued() const; + +private: + std::vector> m_bots; + std::unordered_map> m_bots_by_symbol; + OrderQueue m_order_queue; +}; + +} +} \ No newline at end of file diff --git a/include/velox/sim/strategy/bots.hpp b/include/velox/sim/strategy/bots.hpp new file mode 100644 index 0000000..663f7f4 --- /dev/null +++ b/include/velox/sim/strategy/bots.hpp @@ -0,0 +1,349 @@ +#pragma once +#include "velox/matching/order.hpp" +#include "velox/book/book_snapshot.hpp" +#include + +namespace velox { +namespace bot { + +class BotManager; + +// Base Trading Bot Class +class TradingBot { +public: + TradingBot(const std::string& name, const std::string& symbol) + : m_name(name), m_symbol(symbol) {} + + virtual ~TradingBot() = default; + + virtual void on_snapshot(const BookSnapshot& snapshot) = 0; + virtual void on_fill(const Order& order, uint32_t fill_qty, int64_t fill_price) { + (void)order; (void)fill_qty; (void)fill_price; + } + + const std::string& name() const { return m_name; } + const std::string& symbol() const { return m_symbol; } + + void set_manager(BotManager* manager) { m_manager = manager; } + + void submit_order(const Order& order); + + std::string m_symbol; +private: + std::string m_name; + BotManager* m_manager = nullptr; +}; + +// Market Maker Bot: provides liquidity by maintaining resting orders +class MarketMakerBot : public TradingBot { +public: + MarketMakerBot(const std::string& name, const std::string& symbol, + int64_t base_price = 10000, int64_t spread = 100, + uint32_t quantity = 500) + : TradingBot(name, symbol) + , m_base_price(base_price) + , m_spread(spread) + , m_quantity(quantity) {} + + void on_snapshot(const BookSnapshot& snapshot) override { + static int call_count = 0; + if (call_count++ < 3) { + std::cout << "[MarketMakerBot] on_snapshot called for " << snapshot.symbol + << " best_bid=" << snapshot.best_bid + << " best_ask=" << snapshot.best_ask << std::endl; + } + + static int update_count = 0; + + // Only check every 10 snapshots (every 100ms) + if (++update_count % 10 != 0) return; + + // Check if we have resting orders already + bool has_bid = (snapshot.best_bid >= m_base_price - m_spread / 2); + bool has_ask = (snapshot.best_ask <= m_base_price + m_spread / 2); + + // If no bids, place a bid + if (!has_bid) { + Order bid; + bid.order_id = ++m_order_id; + bid.side = OrderSide::BUY; + bid.price = m_base_price - m_spread / 2; + bid.quantity = m_quantity; + bid.remaining_quantity = m_quantity; + bid.type = OrderType::LIMIT; + std::strncpy(bid.symbol, m_symbol.c_str(), 7); + submit_order(bid); + + std::cout << "[MarketMaker " << name() << "] Placed BUY at " << bid.price << std::endl; + } + + // If no asks, place an ask + if (!has_ask) { + Order ask; + ask.order_id = ++m_order_id; + ask.side = OrderSide::SELL; + ask.price = m_base_price + m_spread / 2; + ask.quantity = m_quantity; + ask.remaining_quantity = m_quantity; + ask.type = OrderType::LIMIT; + std::strncpy(ask.symbol, m_symbol.c_str(), 7); + submit_order(ask); + + std::cout << "[MarketMaker " << name() << "] Placed SELL at " << ask.price << std::endl; + } + + // Gradually adjust base price based on recent trades + if (snapshot.mid_price > 0) { + m_base_price = snapshot.mid_price; + } + } + + void on_fill(const Order& order, uint32_t fill_qty, int64_t fill_price) override { + std::cout << "[MarketMaker " << name() << "] Filled " << fill_qty + << " @ " << fill_price << std::endl; + } + +private: + int64_t m_base_price; + int64_t m_spread; + uint32_t m_quantity; + uint64_t m_order_id = 50000; +}; + +// Spread Bot: trades based on bid-ask spread changes +class SpreadBot : public TradingBot { +public: + SpreadBot(const std::string& name, const std::string& symbol, + int64_t threshold = 50, uint32_t quantity = 100) + : TradingBot(name, symbol) + , m_threshold(threshold) + , m_quantity(quantity) {} + + void on_snapshot(const BookSnapshot& snapshot) override { + if (m_last_spread == 0) { + m_last_spread = snapshot.spread; + return; + } + + int64_t spread_change = snapshot.spread - m_last_spread; + + if (spread_change > m_threshold) { + // Spread widened → buy + Order order; + order.order_id = ++m_order_id; + order.side = OrderSide::BUY; + order.price = snapshot.best_ask; + order.quantity = m_quantity; + order.remaining_quantity = m_quantity; + order.type = OrderType::LIMIT; + std::strncpy(order.symbol, snapshot.symbol, 7); + submit_order(order); + } + else if (spread_change < -m_threshold) { + // Spread narrowed → sell + Order order; + order.order_id = ++m_order_id; + order.side = OrderSide::SELL; + order.price = snapshot.best_bid; + order.quantity = m_quantity; + order.remaining_quantity = m_quantity; + order.type = OrderType::LIMIT; + std::strncpy(order.symbol, snapshot.symbol, 7); + submit_order(order); + } + + m_last_spread = snapshot.spread; + } + +private: + int64_t m_threshold; + uint32_t m_quantity; + int64_t m_last_spread = 0; + uint64_t m_order_id = 10000; +}; + +// Random Walk Bot: random buy/sell decisions +class RandomWalkBot : public TradingBot { +public: + RandomWalkBot(const std::string& name, const std::string& symbol, + double buy_prob = 0.3, double sell_prob = 0.2, uint32_t quantity = 100) + : TradingBot(name, symbol) + , m_buy_prob(buy_prob) + , m_sell_prob(sell_prob) + , m_quantity(quantity) + , m_gen(m_rd()) + , m_dist(0.0, 1.0) {} + + void on_snapshot(const BookSnapshot& snapshot) override { + static int call_count = 0; + if (call_count++ < 5) { + std::cout << "[RandomWalkBot " << name() << "] Received snapshot, price=" + << snapshot.mid_price << std::endl; + } + + double r = m_dist(m_gen); + + if (r < m_buy_prob) { + Order order; + order.order_id = ++m_order_id; + order.side = OrderSide::BUY; + order.price = snapshot.best_ask; + order.quantity = m_quantity; + order.remaining_quantity = m_quantity; + order.type = OrderType::LIMIT; + std::strncpy(order.symbol, snapshot.symbol, 7); + std::cout << "[RandomWalkBot " << name() << "] SUBMITTING BUY order at " + << order.price << std::endl; + submit_order(order); + } + else if (r < m_buy_prob + m_sell_prob) { + Order order; + order.order_id = ++m_order_id; + order.side = OrderSide::SELL; + order.price = snapshot.best_bid; + order.quantity = m_quantity; + order.remaining_quantity = m_quantity; + order.type = OrderType::LIMIT; + std::strncpy(order.symbol, snapshot.symbol, 7); + submit_order(order); + } + } + +private: + double m_buy_prob; + double m_sell_prob; + uint32_t m_quantity; + std::random_device m_rd; + std::mt19937 m_gen; + std::uniform_real_distribution<> m_dist; + uint64_t m_order_id = 20000; +}; + +// Mean Reversion Bot: trades when price deviates from mean +class MeanReversionBot : public TradingBot { +public: + MeanReversionBot(const std::string& name, const std::string& symbol, + int lookback = 20, double z_threshold = 2.0, uint32_t quantity = 100) + : TradingBot(name, symbol) + , m_lookback(lookback) + , m_z_threshold(z_threshold) + , m_quantity(quantity) {} + + void on_snapshot(const BookSnapshot& snapshot) override { + // Record price + m_prices.push_back(snapshot.mid_price); + if (m_prices.size() > m_lookback) { + m_prices.erase(m_prices.begin()); + } + + if (m_prices.size() < m_lookback) return; + + double mean = calculate_mean(); + double stddev = calculate_stddev(mean); + + if (stddev < 1e-6) return; + + double z_score = (snapshot.mid_price - mean) / stddev; + + if (z_score > m_z_threshold) { + // Price too high → sell + Order order; + order.order_id = ++m_order_id; + order.side = OrderSide::SELL; + order.price = snapshot.best_bid; + order.quantity = m_quantity; + order.remaining_quantity = m_quantity; + order.type = OrderType::LIMIT; + std::strncpy(order.symbol, snapshot.symbol, 7); + submit_order(order); + } + else if (z_score < -m_z_threshold) { + // Price too low → buy + Order order; + order.order_id = ++m_order_id; + order.side = OrderSide::BUY; + order.price = snapshot.best_ask; + order.quantity = m_quantity; + order.remaining_quantity = m_quantity; + order.type = OrderType::LIMIT; + std::strncpy(order.symbol, snapshot.symbol, 7); + submit_order(order); + } + } + +private: + double calculate_mean() { + double sum = 0; + for (double p : m_prices) sum += p; + return sum / m_prices.size(); + } + + double calculate_stddev(double mean) { + double sum_sq = 0; + for (double p : m_prices) { + double diff = p - mean; + sum_sq += diff * diff; + } + return std::sqrt(sum_sq / m_prices.size()); + } + + int m_lookback; + double m_z_threshold; + uint32_t m_quantity; + std::vector m_prices; + uint64_t m_order_id = 30000; +}; + +// Momentum Bot: Trades based on price momentum +class MomentumBot : public TradingBot { +public: + MomentumBot(const std::string& name, const std::string& symbol, + int64_t momentum_threshold = 50, uint32_t quantity = 100) + : TradingBot(name, symbol) + , m_momentum_threshold(momentum_threshold) + , m_quantity(quantity) {} + + void on_snapshot(const BookSnapshot& snapshot) override { + if (m_prev_price == 0) { + m_prev_price = snapshot.mid_price; + return; + } + + int64_t change = snapshot.mid_price - m_prev_price; + m_prev_price = snapshot.mid_price; + + if (change > m_momentum_threshold) { + // Upward momentum → buy + Order order; + order.order_id = ++m_order_id; + order.side = OrderSide::BUY; + order.price = snapshot.best_ask; + order.quantity = m_quantity; + order.remaining_quantity = m_quantity; + order.type = OrderType::LIMIT; + std::strncpy(order.symbol, snapshot.symbol, 7); + submit_order(order); + } + else if (change < -m_momentum_threshold) { + // Downward momentum → sell + Order order; + order.order_id = ++m_order_id; + order.side = OrderSide::SELL; + order.price = snapshot.best_bid; + order.quantity = m_quantity; + order.remaining_quantity = m_quantity; + order.type = OrderType::LIMIT; + std::strncpy(order.symbol, snapshot.symbol, 7); + submit_order(order); + } + } + +private: + int64_t m_momentum_threshold; + uint32_t m_quantity; + int64_t m_prev_price = 0; + uint64_t m_order_id = 40000; +}; + +} +} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 618b6be..1c8b3f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,6 +17,10 @@ add_library(velox_core STATIC # Gateway gateway/execution_gateway.cpp gateway/fix_encoder.cpp + + # Strategy + strategy/bot_manager.cpp + strategy/bot.cpp ) target_link_libraries(velox_core diff --git a/src/book/book_snapshot.cpp b/src/book/book_snapshot.cpp index 49a7119..1732b69 100644 --- a/src/book/book_snapshot.cpp +++ b/src/book/book_snapshot.cpp @@ -100,6 +100,15 @@ void BookSnapshotManager::capture_snapshot(const OrderBook& book, snap.mid_price = (snap.best_ask == INT64_MAX || snap.best_bid == 0) ? 0.0 : static_cast(snap.best_bid + snap.best_ask) / 2.0; + + /* + std::cout << "[Capture] " << book.symbol() + << " best_bid=" << snap.best_bid + << " best_ask=" << snap.best_ask + << " bid_levels=" << book.get_bid_levels().size() + << " ask_levels=" << book.get_ask_levels().size() + << std::endl; + */ // Capture bid levels — walk the sorted bid vector const auto& bid_levels = book.get_bid_levels(); diff --git a/src/main.cpp b/src/main.cpp index f0376d2..38cd600 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,166 +4,215 @@ #include "velox/risk/position_manager.hpp" #include "velox/feed/feed_handler.hpp" #include "velox/matching/matching_engine.hpp" +#include "velox/sim/strategy/bot_manager.hpp" +#include "velox/sim/strategy/bots.hpp" + #include #include #include #include #include #include -#include +#include using namespace velox; static std::atomic g_running{true}; - void signal_handler(int) { g_running = false; } int main() { - // Signal handlers signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); - // Market data ingestion (entry point) FeedHandler feed_handler; - - // Shared components RiskManager risk_manager; ExecutionGateway gateway; PositionManager pos_manager; - // Add workers to Execution Gateway (1 per CPU core, tunable) - const int num_workers = std::thread::hardware_concurrency(); + auto bot_manager = std::make_unique(); + // Gateway workers + const int num_workers = std::thread::hardware_concurrency(); for (int i = 0; i < num_workers; ++i) { gateway.add_worker(); } - // Trading symbols + // Symbols std::vector symbols = {"AAPL", "MSFT", "GOOGL", "AMZN", "META"}; - // One Symbol Engine per symbol std::vector> engines; - std::vector worker_threads; - for (const auto& s : symbols) { engines.push_back(std::make_unique( s.c_str(), &risk_manager, &gateway, &pos_manager)); } - // Market data feed + // Bots + bot_manager->register_bot(std::make_unique("MM_AAPL", "AAPL", 10000, 100, 500)); + bot_manager->register_bot(std::make_unique("Spread_AAPL", "AAPL")); + bot_manager->register_bot(std::make_unique("RW_AAPL", "AAPL")); + bot_manager->register_bot(std::make_unique("MR_AAPL", "AAPL")); + bot_manager->register_bot(std::make_unique("Mom_AAPL", "AAPL")); + + // Shared pools + static lockfree::ObjectPool global_pool; + static std::vector> order_storage; + + // Feed handler routing feed_handler.on_add_order([&](const Order& order) { - - // Linear search for matching symbol - // TODO: use more efficient algorithm for larger symbol set for (auto& e : engines) { if (strcmp(e->order_book().symbol(), order.symbol) == 0) { + auto ptr = global_pool.acquire(); + *ptr = order; - // Each engine has its own pool (for now) - static lockfree::ObjectPool global_pool; - static std::vector> pending_orders; + // CRITICAL: normalize state + ptr->remaining_quantity = ptr->quantity; + ptr->filled_quantity = 0; + ptr->status = OrderStatus::NEW; - auto new_order = global_pool.acquire(); - *new_order = order; // Copy - // std::cout << "[DEBUG] Routing to engine for symbol: " << e->order_book().symbol() << std::endl; - e->on_market_order(new_order.get()); - pending_orders.push_back(std::move(new_order)); + e->on_market_order(ptr.get()); + order_storage.push_back(std::move(ptr)); break; } } }); - // Spawn a thread for each symbol's matching engine - for (size_t i = 0; i < engines.size(); ++i) { - worker_threads.emplace_back([&e = engines[i]]() { + std::cout << "[MAIN] Seeding market with extreme prices (bid=1, ask=999999)...\n"; + + static lockfree::ObjectPool seed_pool; + static std::vector> seed_storage; + + for (auto& e : engines) { + // BID at $0.01 (price=1) – extremely low, never matched by any sell order + auto bid = seed_pool.acquire(); + bid->order_id = 1000; + bid->side = OrderSide::BUY; + bid->price = 1; + bid->quantity = 1000; + bid->remaining_quantity = 1000; + bid->filled_quantity = 0; + bid->status = OrderStatus::NEW; + std::strncpy(bid->symbol, e->symbol(), 7); + e->get_order_book().add_order(bid.get()); // NOTE: populate order book WITHOUT going through matching engine (otherwise rejection) + seed_storage.push_back(std::move(bid)); + + // ASK at $9999.99 (price=999999) – extremely high, never matched by any buy order + auto ask = seed_pool.acquire(); + ask->order_id = 1001; + ask->side = OrderSide::SELL; + ask->price = 999999; + ask->quantity = 1000; + ask->remaining_quantity = 1000; + ask->filled_quantity = 0; + ask->status = OrderStatus::NEW; + std::strncpy(ask->symbol, e->symbol(), 7); + e->get_order_book().add_order(ask.get()); + seed_storage.push_back(std::move(ask)); + } + + // Verify seed orders are in the book + for (auto& e : engines) { + std::cout << "[POST-SEED] " << e->symbol() + << " bid=" << e->order_book().best_bid() + << " ask=" << e->order_book().best_ask() << "\n"; + } + + // Process seed orders + for (auto& e : engines) { + for (int i = 0; i < 10; ++i) { + e->run_match_cycle(); + } + } + + // Verification + for (auto& e : engines) { + std::cout << "[POST-SEED] " << e->symbol() + << " seq=" << e->order_book().sequence() + << " bid=" << e->order_book().best_bid() + << " ask=" << e->order_book().best_ask() << "\n"; + } + + // Worker threads + std::vector workers; + for (auto& e : engines) { + workers.emplace_back([&e]() { while (g_running) { e->run_match_cycle(); - - // Small sleep cycles if no orders - std::this_thread::sleep_for(std::chrono::microseconds(1)); } }); } - // Spawn a thread for stats reporting - std::thread stats_thread([&]() { + // Snapshot + bot loop + std::thread snapshot_thread([&]() { + std::unordered_map> snaps; + for (auto& e : engines) { + snaps[e->symbol()] = std::make_unique(5); + } + + static lockfree::ObjectPool bot_pool; + static std::vector> bot_storage; + while (g_running) { - std::this_thread::sleep_for(std::chrono::seconds(5)); // N = 5 seconds (arbitrary) - - std::cout << "\n=== Trading Engine Stats [" - << std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) - << "] ===" << std::endl; - for (auto& e : engines) { - auto stats = e->get_stats(); - int64_t position = pos_manager.get_position(e->symbol()); - int64_t realized_pnl = pos_manager.get_realized_pnl(e->symbol()); - - std::cout << " " << e->symbol() - << ": matched=" << stats.orders_matched - << ", position=" << position - << ", realized_PnL=$" << (realized_pnl / 100.0) - << ", rejected=" << stats.orders_rejected - << ", partial=" << stats.orders_partially_filled - << std::endl; + auto& sm = *snaps[e->symbol()]; + sm.update(e->order_book()); + + const auto* snap = sm.get_snapshot(); + if (snap && snap->valid()) { + bot_manager->on_snapshot(*snap); + sm.release_snapshot(snap); + } + + // Process bot orders + Order tmp; + while (bot_manager->pop_order(tmp)) { + auto ptr = bot_pool.acquire(); + *ptr = tmp; + ptr->remaining_quantity = ptr->quantity; + ptr->filled_quantity = 0; + ptr->status = OrderStatus::NEW; + for (auto& eng : engines) { + if (strcmp(eng->symbol(), ptr->symbol) == 0) { + eng->on_market_order(ptr.get()); + bot_storage.push_back(std::move(ptr)); + break; + } + } + } } - - std::cout << " Gateway: total_reports=" << gateway.total_reports_sent() - << ", total_orders=" << gateway.total_orders_sent() << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(5)); } }); - // Process market data by simulating read from ITCH file - // TODO: change to read from constant stream + // Feed thread (process ITCH file) std::thread feed_thread([&]() { - // std::cout << "[DEBUG] Parsing mock ITCH file in test_data/NASDAQ_ITCH50_sample.bin..." << std::endl; feed_handler.process_file("test_data/NASDAQ_ITCH50_sample.bin"); - g_running = false; // Stop workers when feed ends }); - // Spawn a thread to publish snapshots periodically - // TODO: implement once current event loop works - /* - std::thread snapshot_thread([&]() { - while (g_running) { - for (auto& eng : engines) { - - } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - }); - */ - - // Wait for shutdown feed_thread.join(); + + std::this_thread::sleep_for(std::chrono::seconds(2)); g_running = false; - // Fully drain each engine's queue before shutting down workers - for (auto& e : engines) { - e->run_match_cycle(); - } + snapshot_thread.join(); - // Let stats reporting thread print before yielding - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + for (auto& t : workers) t.join(); - // Join stats thread - if (stats_thread.joinable()) { - stats_thread.join(); - } - - // Join worker threads - for (auto& t : worker_threads) { - if (t.joinable()) t.join(); + // Final drain + for (auto& e : engines) { + for (int i = 0; i < 10; ++i) { + e->run_match_cycle(); + } } - // Print final stats - std::cout << "\n=== FINAL STATISTICS ===" << std::endl; + std::cout << "\n=== FINAL STATS ===\n"; for (auto& e : engines) { - auto stats = e->get_stats(); - std::cout << e->symbol() << ": matched=" << stats.orders_matched - << ", rejected=" << stats.orders_rejected - << ", partial=" << stats.orders_partially_filled - << std::endl; + auto s = e->get_stats(); + std::cout << e->symbol() + << " matched=" << s.orders_matched + << " rejected=" << s.orders_rejected + << " partial=" << s.orders_partially_filled + << "\n"; } return 0; -} \ No newline at end of file +} diff --git a/src/strategy/bot.cpp b/src/strategy/bot.cpp new file mode 100644 index 0000000..2325834 --- /dev/null +++ b/src/strategy/bot.cpp @@ -0,0 +1,14 @@ +#include "velox/sim/strategy/bots.hpp" +#include "velox/sim/strategy/bot_manager.hpp" + +namespace velox { +namespace bot { + +void TradingBot::submit_order(const Order& order) { + if (m_manager) { + m_manager->push_order(order); + } +} + +} +} \ No newline at end of file diff --git a/src/strategy/bot_manager.cpp b/src/strategy/bot_manager.cpp new file mode 100644 index 0000000..cd2abaf --- /dev/null +++ b/src/strategy/bot_manager.cpp @@ -0,0 +1,67 @@ +#include "velox/sim/strategy/bot_manager.hpp" + +namespace velox { +namespace bot { + +BotManager::BotManager() = default; +BotManager::~BotManager() = default; + +void BotManager::register_bot(std::unique_ptr bot) { + bot->set_manager(this); + std::string symbol = bot->symbol(); + m_bots_by_symbol[symbol].push_back(bot.get()); + m_bots.push_back(std::move(bot)); +} + +/* +void BotManager::on_snapshot(const BookSnapshot& snapshot) { + auto it = m_bots_by_symbol.find(snapshot.symbol); + + if (it == m_bots_by_symbol.end()) return; + + for (auto* bot : it->second) { + bot->on_snapshot(snapshot); + } +} +*/ +void BotManager::on_snapshot(const BookSnapshot& snapshot) { + static int snap_count = 0; + if (snap_count++ < 5) { + std::cout << "[BotManager] on_snapshot for " << snapshot.symbol + << " best_bid=" << snapshot.best_bid << std::endl; + } + + auto it = m_bots_by_symbol.find(snapshot.symbol); + if (it == m_bots_by_symbol.end()) { + std::cout << "[BotManager] No bots for symbol " << snapshot.symbol << std::endl; + return; + } + + std::cout << "[BotManager] Found " << it->second.size() << " bots for " + << snapshot.symbol << std::endl; + + for (auto* bot : it->second) { + bot->on_snapshot(snapshot); + } +} + +void BotManager::push_order(const Order& order) { + m_order_queue.push(order); +} + +bool BotManager::pop_order(Order& order) { + auto opt = m_order_queue.pop(); + + if (opt.has_value()) { + order = std::move(opt.value()); + return true; + } + return false; +} + +uint64_t BotManager::orders_queued() const { + return m_order_queue.size(); +} + +} +} \ No newline at end of file