Spaces:
Sleeping
Sleeping
| using namespace hft; | |
| static std::string jget(const std::string& body, const std::string& key) { | |
| auto pos = body.find("\"" + key + "\""); | |
| if (pos == std::string::npos) return ""; | |
| pos = body.find(":", pos); | |
| if (pos == std::string::npos) return ""; | |
| ++pos; | |
| while (pos < body.size() && (body[pos] == ' ' || body[pos] == '\t')) ++pos; | |
| if (pos >= body.size()) return ""; | |
| if (body[pos] == '"') { | |
| ++pos; | |
| size_t end = body.find('"', pos); | |
| return body.substr(pos, end - pos); | |
| } | |
| size_t end = body.find_first_of(",}\n", pos); | |
| return body.substr(pos, end - pos); | |
| } | |
| static RiskLimits g_limits; | |
| static std::unique_ptr<ExchangeSimulator> g_sim; | |
| static std::vector<MarketDataTick> g_tick_feed; | |
| static size_t g_feed_idx = 0; | |
| static std::deque<std::string> g_trade_log; | |
| static std::deque<std::string> g_order_log; | |
| static void init_sim() { | |
| g_sim = std::make_unique<ExchangeSimulator>(g_limits); | |
| for (auto& sym : {"AAPL", "GOOG", "MSFT", "TSLA", "NVDA"}) | |
| g_sim->add_symbol(sym); | |
| g_sim->set_trade_callback([](const Trade& t) { | |
| g_trade_log.push_back(trade_to_json(t)); | |
| if ((int)g_trade_log.size() > 500) g_trade_log.pop_front(); | |
| }); | |
| g_sim->set_order_callback([](const Order& o) { | |
| g_order_log.push_back(order_to_json(o)); | |
| if ((int)g_order_log.size() > 500) g_order_log.pop_front(); | |
| }); | |
| } | |
| static void init_feed() { | |
| const char* syms[] = {"AAPL", "GOOG", "MSFT", "TSLA", "NVDA"}; | |
| double prices[] = {185.0, 140.0, 375.0, 245.0, 485.0}; | |
| g_tick_feed.clear(); | |
| for (int i = 0; i < 5; ++i) { | |
| MarketDataParser::Config cfg; | |
| cfg.symbol = syms[i]; | |
| cfg.start_price = prices[i]; | |
| cfg.ticks = 3000; | |
| cfg.volatility = 0.0008; | |
| cfg.spread_bps = 3.0; | |
| cfg.seed = 42 + i; | |
| auto ticks = MarketDataParser::generate_synthetic(cfg); | |
| for (auto& t : ticks) g_tick_feed.push_back(t); | |
| } | |
| std::sort(g_tick_feed.begin(), g_tick_feed.end(), | |
| [](const MarketDataTick& a, const MarketDataTick& b) { | |
| return a.timestamp < b.timestamp; | |
| }); | |
| g_feed_idx = 0; | |
| } | |
| static std::string handle(const std::string& cmd, const std::string& payload) { | |
| if (cmd == "status") { | |
| g_sim->order_latency().compute_percentiles(); | |
| g_sim->tick_latency().compute_percentiles(); | |
| return "{\"total_orders\":" + J::num(g_sim->total_orders()) + | |
| ",\"total_trades\":" + J::num(g_sim->total_trades()) + | |
| ",\"total_rejects\":" + J::num(g_sim->total_rejects()) + | |
| ",\"total_pnl\":" + J::num(g_sim->total_pnl()) + | |
| ",\"order_p50_us\":" + J::num(g_sim->order_latency().p50_ns / 1000.0) + | |
| ",\"order_p99_us\":" + J::num(g_sim->order_latency().p99_ns / 1000.0) + | |
| ",\"tick_p50_us\":" + J::num(g_sim->tick_latency().p50_ns / 1000.0) + | |
| ",\"tick_p99_us\":" + J::num(g_sim->tick_latency().p99_ns / 1000.0) + | |
| ",\"risk_checked\":" + J::num(g_sim->risk().orders_checked()) + | |
| ",\"risk_rejected\":" + J::num(g_sim->risk().orders_rejected()) + | |
| ",\"feed_size\":" + J::num((int64_t)g_tick_feed.size()) + | |
| ",\"feed_idx\":" + J::num((int64_t)g_feed_idx) + "}"; | |
| } | |
| if (cmd == "reset") { | |
| g_trade_log.clear(); | |
| g_order_log.clear(); | |
| init_sim(); | |
| init_feed(); | |
| return "{\"ok\":true}"; | |
| } | |
| if (cmd == "book") { | |
| std::string sym = jget(payload, "symbol"); | |
| if (sym.empty()) sym = "AAPL"; | |
| auto snap = g_sim->get_snapshot(sym); | |
| return snapshot_to_json(snap); | |
| } | |
| if (cmd == "positions") { | |
| auto pos = g_sim->all_positions(); | |
| std::string r = "["; | |
| bool first = true; | |
| for (auto& [sym, p] : pos) { | |
| if (!first) r += ","; | |
| r += position_to_json(p); | |
| first = false; | |
| } | |
| return r + "]"; | |
| } | |
| if (cmd == "trades") { | |
| std::string r = "["; | |
| bool first = true; | |
| for (auto& t : g_trade_log) { | |
| if (!first) r += ","; | |
| r += t; | |
| first = false; | |
| } | |
| return r + "]"; | |
| } | |
| if (cmd == "orders") { | |
| std::string r = "["; | |
| bool first = true; | |
| for (auto& o : g_order_log) { | |
| if (!first) r += ","; | |
| r += o; | |
| first = false; | |
| } | |
| return r + "]"; | |
| } | |
| if (cmd == "order") { | |
| Order order; | |
| order.id = g_sim->next_order_id(); | |
| order.symbol = jget(payload, "symbol"); | |
| if (order.symbol.empty()) order.symbol = "AAPL"; | |
| std::string side_s = jget(payload, "side"); | |
| order.side = (side_s == "SELL") ? Side::SELL : Side::BUY; | |
| std::string type_s = jget(payload, "type"); | |
| if (type_s == "MARKET") order.type = OrderType::MARKET; | |
| else if (type_s == "IOC") order.type = OrderType::IOC; | |
| else if (type_s == "FOK") order.type = OrderType::FOK; | |
| else order.type = OrderType::LIMIT; | |
| try { | |
| order.price = to_price(std::stod(jget(payload, "price"))); | |
| order.qty = std::stoll(jget(payload, "qty")); | |
| } catch (...) { | |
| return "{\"error\":\"invalid price or qty\"}"; | |
| } | |
| order.client_id = jget(payload, "client_id"); | |
| double mkt_px = 0; | |
| try { mkt_px = std::stod(jget(payload, "market_price")); } catch (...) {} | |
| auto result = g_sim->submit_order(order, mkt_px); | |
| std::string r = "{\"approved\":" + J::boolean(result.approved) + | |
| ",\"order\":" + order_to_json(order) + | |
| ",\"reject_reason\":" + J::str(result.reject_reason) + | |
| ",\"trades\":["; | |
| for (size_t i = 0; i < result.trades.size(); ++i) { | |
| if (i) r += ","; | |
| r += trade_to_json(result.trades[i]); | |
| } | |
| return r + "]}"; | |
| } | |
| if (cmd == "cancel") { | |
| OrderId id = 0; | |
| std::string sym = jget(payload, "symbol"); | |
| try { id = std::stoull(jget(payload, "id")); } catch (...) {} | |
| bool ok = g_sim->cancel_order(sym, id); | |
| return "{\"cancelled\":" + J::boolean(ok) + "}"; | |
| } | |
| if (cmd == "feed_step") { | |
| int n = 10; | |
| std::string n_str = jget(payload, "n"); | |
| if (!n_str.empty()) { | |
| try { n = std::stoi(n_str); } catch (...) {} | |
| } | |
| n = std::max(1, std::min(n, 200)); | |
| std::string r = "["; | |
| bool first = true; | |
| for (int i = 0; i < n; ++i) { | |
| if (g_feed_idx >= g_tick_feed.size()) g_feed_idx = 0; | |
| auto& tick = g_tick_feed[g_feed_idx++]; | |
| g_sim->on_tick(tick); | |
| if (!first) r += ","; | |
| r += "{\"symbol\":" + J::str(tick.symbol) + | |
| ",\"bid\":" + J::num(from_price(tick.bid_price)) + | |
| ",\"ask\":" + J::num(from_price(tick.ask_price)) + | |
| ",\"last\":" + J::num(from_price(tick.last_price)) + | |
| ",\"bid_qty\":" + J::num(tick.bid_qty) + | |
| ",\"ask_qty\":" + J::num(tick.ask_qty) + | |
| ",\"ts\":" + J::num(tick.timestamp) + "}"; | |
| first = false; | |
| } | |
| return r + "]"; | |
| } | |
| if (cmd == "backtest") { | |
| std::string strategy_name = jget(payload, "strategy"); | |
| std::string symbol = jget(payload, "symbol"); | |
| if (symbol.empty()) symbol = "AAPL"; | |
| int ticks = 5000; | |
| double start_price = 150.0; | |
| try { ticks = std::stoi(jget(payload, "ticks")); } catch (...) {} | |
| try { start_price = std::stod(jget(payload, "start_price")); } catch (...) {} | |
| ticks = std::min(std::max(ticks, 200), 50000); | |
| MarketDataParser::Config cfg; | |
| cfg.symbol = symbol; | |
| cfg.start_price = start_price; | |
| cfg.ticks = ticks; | |
| cfg.volatility = 0.001; | |
| cfg.seed = 777; | |
| auto bt_ticks = MarketDataParser::generate_synthetic(cfg); | |
| RiskLimits rl; | |
| rl.max_order_qty = 10000; | |
| rl.max_position = 500000; | |
| rl.max_notional_usd = 1e9; | |
| rl.max_orders_per_sec = 10000; | |
| Backtester bt(rl); | |
| bt.add_symbol(symbol); | |
| std::unique_ptr<IStrategy> strat; | |
| if (strategy_name == "Momentum") | |
| strat = std::make_unique<MomentumStrategy>(); | |
| else if (strategy_name == "MeanReversion") | |
| strat = std::make_unique<MeanReversionStrategy>(); | |
| else | |
| strat = std::make_unique<MarketMakingStrategy>(); | |
| bt.set_strategy(std::move(strat)); | |
| auto result = bt.run(bt_ticks, false); | |
| return backtest_result_to_json(result); | |
| } | |
| if (cmd == "risk_limits") { | |
| try { | |
| g_limits.max_order_qty = std::stoll(jget(payload, "max_order_qty")); | |
| g_limits.max_position = std::stoll(jget(payload, "max_position")); | |
| g_limits.max_notional_usd = std::stod(jget(payload, "max_notional_usd")); | |
| g_limits.max_orders_per_sec = std::stoi(jget(payload, "max_orders_per_sec")); | |
| g_sim->risk().set_limits(g_limits); | |
| return "{\"ok\":true}"; | |
| } catch (...) { | |
| return "{\"error\":\"bad limits\"}"; | |
| } | |
| } | |
| if (cmd == "get_limits") { | |
| auto l = g_sim->risk().get_limits(); | |
| return "{\"max_order_qty\":" + J::num(l.max_order_qty) + | |
| ",\"max_position\":" + J::num(l.max_position) + | |
| ",\"max_notional_usd\":" + J::num(l.max_notional_usd, 0) + | |
| ",\"max_orders_per_sec\":" + J::num((int64_t)l.max_orders_per_sec) + | |
| ",\"enabled\":" + J::boolean(l.enabled) + "}"; | |
| } | |
| if (cmd == "symbols") { | |
| auto syms = g_sim->symbols(); | |
| std::string r = "["; | |
| for (size_t i = 0; i < syms.size(); ++i) { | |
| if (i) r += ","; | |
| r += J::str(syms[i]); | |
| } | |
| return r + "]"; | |
| } | |
| return "{\"error\":" + J::str("unknown command: " + cmd) + "}"; | |
| } | |
| int main() { | |
| g_limits.max_order_qty = 10000; | |
| g_limits.max_position = 500000; | |
| g_limits.max_notional_usd = 50000000.0; | |
| g_limits.max_orders_per_sec = 2000; | |
| init_sim(); | |
| init_feed(); | |
| std::string line; | |
| while (std::getline(std::cin, line)) { | |
| if (line.empty()) continue; | |
| size_t sp = line.find(' '); | |
| std::string cmd = (sp == std::string::npos) ? line : line.substr(0, sp); | |
| std::string payload = (sp == std::string::npos) ? "{}" : line.substr(sp + 1); | |
| try { | |
| std::cout << handle(cmd, payload) << "\n"; | |
| std::cout.flush(); | |
| } catch (const std::exception& e) { | |
| std::cout << "{\"error\":" << J::str(e.what()) << "}\n"; | |
| std::cout.flush(); | |
| } | |
| } | |
| return 0; | |
| } | |