#include "core/simulator.hpp" #include #include #include #include namespace hft { ExchangeSimulator::ExchangeSimulator(RiskLimits limits) : risk_(limits), journal_("session.journal") {} void ExchangeSimulator::add_symbol(const std::string& symbol) { std::lock_guard lock(mtx_); if (books_.count(symbol)) return; auto book = std::make_unique(symbol); book->set_trade_callback([this](const Trade& t) { total_trades_.fetch_add(1, std::memory_order_relaxed); if (trade_cb_) trade_cb_(t); }); book->set_order_callback([this](const Order& o) { if (order_cb_) order_cb_(o); }); books_[symbol] = std::move(book); } // Fix: Now takes const Order& and safely clones it to guarantee memory persistence ExchangeSimulator::SubmitResult ExchangeSimulator::submit_order(const Order& order_in, double market_price) { int64_t t0 = now_ns(); total_orders_.fetch_add(1, std::memory_order_relaxed); auto risk_result = risk_.check_order(order_in, market_price); if (!risk_result.approved) { total_rejects_.fetch_add(1, std::memory_order_relaxed); return {false, risk_result.message, {}}; } std::lock_guard lock(mtx_); auto it = books_.find(order_in.symbol); if (it == books_.end()) { total_rejects_.fetch_add(1, std::memory_order_relaxed); return {false, "Unknown symbol: " + order_in.symbol, {}}; } // Fix: Allocate order uniquely so OrderBook holds a stable, valid pointer auto order_ptr = std::make_unique(order_in); order_ptr->timestamp = now_ns(); Order& order = *order_ptr; order_store_[order.id] = std::move(order_ptr); auto trades = it->second->add_order(order); for (auto& t : trades) { Side fill_side = (t.buy_order_id == order.id) ? Side::BUY : Side::SELL; risk_.on_fill(t.symbol, fill_side, t.qty, from_price(t.price)); } // Fix: Recorded inside the mutex scope to prevent vector corruption order_lat_.record(now_ns() - t0); return {true, "OK", trades}; } bool ExchangeSimulator::cancel_order(const std::string& symbol, OrderId id) { std::lock_guard lock(mtx_); auto it = books_.find(symbol); if (it == books_.end()) return false; return it->second->cancel_order(id); } bool ExchangeSimulator::modify_order(const std::string& symbol, OrderId id, Price new_price, Quantity new_qty) { std::lock_guard lock(mtx_); auto it = books_.find(symbol); if (it == books_.end()) return false; return it->second->modify_order(id, new_price, new_qty); } void ExchangeSimulator::on_tick(const MarketDataTick& tick) { int64_t t0 = now_ns(); std::lock_guard lock(mtx_); risk_.update_market_price(tick.symbol, from_price(tick.last_price)); tick_lat_.record(now_ns() - t0); } OrderBookSnapshot ExchangeSimulator::get_snapshot(const std::string& symbol, int depth) const { std::lock_guard lock(mtx_); auto it = books_.find(symbol); if (it == books_.end()) return {}; return it->second->snapshot(depth); } std::vector ExchangeSimulator::symbols() const { std::lock_guard lock(mtx_); std::vector s; for (auto& [k, _] : books_) s.push_back(k); return s; } Position ExchangeSimulator::get_position(const std::string& symbol) const { return risk_.get_position(symbol); } std::unordered_map ExchangeSimulator::all_positions() const { return risk_.all_positions(); } double ExchangeSimulator::total_pnl() const { return risk_.total_pnl(); } Backtester::Backtester(RiskLimits limits) : limits_(limits) {} void Backtester::set_strategy(std::unique_ptr strategy) { strategy_ = std::move(strategy); } void Backtester::add_symbol(const std::string& symbol) { symbols_.push_back(symbol); } void Backtester::process_signals(const std::vector& sigs, const MarketDataTick& tick, BacktestResult& result, std::unordered_map& live_orders) { for (auto& sig : sigs) { if (sig.action == StrategySignal::Action::NONE) continue; if (sig.action == StrategySignal::Action::CANCEL) { sim_->cancel_order(tick.symbol, sig.cancel_id); live_orders.erase(sig.cancel_id); continue; } if (sig.action == StrategySignal::Action::CANCEL_ALL) { for (auto& [id, ord] : live_orders) sim_->cancel_order(ord.symbol, id); live_orders.clear(); continue; } Order order; order.id = sim_->next_order_id(); order.symbol = sig.symbol.empty() ? tick.symbol : sig.symbol; order.side = (sig.action == StrategySignal::Action::BUY) ? Side::BUY : Side::SELL; order.type = sig.type; order.price = sig.price; order.qty = sig.qty; order.client_id = sig.reason; ++result.orders_submitted; auto res = sim_->submit_order(order, from_price(tick.last_price)); if (!res.approved) { ++result.orders_rejected; } else { if (!order.is_done()) live_orders[order.id] = order; for (auto& t : res.trades) { ++result.orders_filled; ++result.trades_count; result.trades.push_back(t); strategy_->on_trade(t); } strategy_->on_order_update(order); } } } BacktestResult Backtester::run(const std::vector& ticks, bool enable_journal, const std::string& journal_path) { BacktestResult result; if (!strategy_ || ticks.empty()) return result; result.strategy_name = strategy_->name(); sim_ = std::make_unique(limits_); for (auto& sym : symbols_) sim_->add_symbol(sym); if (symbols_.empty() && !ticks.empty()) { std::unordered_map seen; for (auto& t : ticks) { if (!seen[t.symbol]) { sim_->add_symbol(t.symbol); seen[t.symbol] = true; } } } std::unordered_set inited; for (auto& tick : ticks) { if (!inited.count(tick.symbol)) { strategy_->on_init(tick.symbol); inited.insert(tick.symbol); } } std::unordered_map live_orders; double peak_equity = std::numeric_limits::lowest(); int64_t ts_start = now_ns(); for (auto& tick : ticks) { int64_t t0 = now_ns(); sim_->on_tick(tick); auto snap = sim_->get_snapshot(tick.symbol); auto sigs = strategy_->on_tick(tick, snap); process_signals(sigs, tick, result, live_orders); if (result.ticks_processed % 100 == 0) { double equity = sim_->total_pnl(); result.equity_curve.push_back(equity); if (equity > peak_equity) peak_equity = equity; if (peak_equity > std::numeric_limits::lowest()) { double drawdown = peak_equity - equity; if (drawdown > result.max_drawdown) result.max_drawdown = drawdown; } } result.tick_latency.record(now_ns() - t0); ++result.ticks_processed; } int64_t elapsed_ns = now_ns() - ts_start; result.total_pnl = sim_->total_pnl(); auto pos = sim_->all_positions(); for (auto& [sym, p] : pos) { result.realized_pnl += p.realized_pnl; result.unrealized_pnl += p.unrealized_pnl; } result.order_latency = sim_->order_latency(); result.order_latency.compute_percentiles(); result.tick_latency.compute_percentiles(); double elapsed_s = elapsed_ns / 1e9; result.throughput_eps = elapsed_s > 0 ? result.ticks_processed / elapsed_s : 0; if (result.equity_curve.size() > 2) { std::vector returns; returns.reserve(result.equity_curve.size() - 1); for (size_t i = 1; i < result.equity_curve.size(); ++i) returns.push_back(result.equity_curve[i] - result.equity_curve[i - 1]); double mean = std::accumulate(returns.begin(), returns.end(), 0.0) / returns.size(); double var = 0; for (double r : returns) var += (r - mean) * (r - mean); double std_dev = std::sqrt(var / returns.size()); result.sharpe = (std_dev > 1e-9) ? (mean / std_dev * std::sqrt(252.0)) : 0; } return result; } } // namespace hft