#include "risk/risk_manager.hpp" #include #include namespace hft { RiskManager::RiskManager(RiskLimits limits) : limits_(std::move(limits)) {} int RiskManager::orders_in_last_second() { int64_t now = now_ns(); int64_t cutoff = now - 1'000'000'000LL; while (!order_timestamps_ns_.empty() && order_timestamps_ns_.front() < cutoff) order_timestamps_ns_.pop_front(); return (int)order_timestamps_ns_.size(); } RiskResult RiskManager::check_order(const Order& order, double current_price) { std::lock_guard lock(mtx_); ++stats_checked_; if (!limits_.enabled) { order_timestamps_ns_.push_back(now_ns()); return {true, RiskRejectReason::NONE, "OK"}; } if (order.qty <= 0) return (++stats_rejected_, RiskResult{false, RiskRejectReason::INVALID_QTY, "Quantity must be positive"}); if (order.type == OrderType::LIMIT && order.price <= 0) return (++stats_rejected_, RiskResult{false, RiskRejectReason::INVALID_PRICE, "Limit price must be positive"}); if (order.qty > limits_.max_order_qty) return (++stats_rejected_, RiskResult{false, RiskRejectReason::MAX_ORDER_QTY, "Order qty " + std::to_string(order.qty) + " > limit " + std::to_string(limits_.max_order_qty)}); double price_usd = (current_price > 0) ? current_price : from_price(order.price); double notional = price_usd * order.qty; if (notional > limits_.max_notional_usd) return (++stats_rejected_, RiskResult{false, RiskRejectReason::MAX_NOTIONAL, "Notional $" + std::to_string(notional) + " > limit $" + std::to_string(limits_.max_notional_usd)}); auto pit = positions_.find(order.symbol); Quantity cur_pos = (pit != positions_.end()) ? pit->second.net_qty : 0; Quantity projected = cur_pos + (order.side == Side::BUY ? order.qty : -order.qty); if (std::abs(projected) > limits_.max_position) return (++stats_rejected_, RiskResult{false, RiskRejectReason::MAX_POSITION, "Projected position " + std::to_string(projected) + " > limit " + std::to_string(limits_.max_position)}); // Rate check — only count approved orders toward the rate limit int rate = orders_in_last_second(); if (rate >= limits_.max_orders_per_sec) return (++stats_rejected_, RiskResult{false, RiskRejectReason::MAX_ORDER_RATE, "Order rate " + std::to_string(rate) + "/s >= limit " + std::to_string(limits_.max_orders_per_sec)}); // All checks passed — record timestamp for rate limiting order_timestamps_ns_.push_back(now_ns()); return {true, RiskRejectReason::NONE, "OK"}; } void RiskManager::on_fill(const std::string& symbol, Side side, Quantity qty, double fill_price) { std::lock_guard lock(mtx_); auto& pos = positions_[symbol]; pos.symbol = symbol; int64_t signed_qty = (side == Side::BUY) ? (int64_t)qty : -(int64_t)qty; bool adding = (pos.net_qty == 0) || (side == Side::BUY && pos.net_qty > 0) || (side == Side::SELL && pos.net_qty < 0); if (adding) { double total_cost = pos.avg_price * std::abs((double)pos.net_qty) + fill_price * qty; pos.net_qty += signed_qty; pos.avg_price = (pos.net_qty != 0) ? total_cost / std::abs((double)pos.net_qty) : 0.0; } else { Quantity closing = std::min(qty, (Quantity)std::abs(pos.net_qty)); Quantity opening = qty - closing; double realized = (fill_price - pos.avg_price) * closing * (pos.net_qty > 0 ? 1.0 : -1.0); pos.realized_pnl += realized; pos.net_qty += signed_qty; if (pos.net_qty == 0) { pos.avg_price = 0.0; } else if (opening > 0) { pos.avg_price = fill_price; } } if (pos.net_qty != 0) { double mkt = market_prices_.count(symbol) ? market_prices_.at(symbol) : fill_price; pos.unrealized_pnl = (mkt - pos.avg_price) * pos.net_qty; } else { pos.unrealized_pnl = 0.0; } } void RiskManager::update_market_price(const std::string& sym, double px) { std::lock_guard lock(mtx_); market_prices_[sym] = px; if (positions_.count(sym)) { auto& pos = positions_[sym]; pos.unrealized_pnl = (pos.net_qty != 0) ? (px - pos.avg_price) * pos.net_qty : 0.0; } } void RiskManager::on_cancel(const std::string& symbol, const Order& order) { (void)symbol; (void)order; } Position RiskManager::get_position(const std::string& symbol) const { std::lock_guard lock(mtx_); auto it = positions_.find(symbol); if (it == positions_.end()) return Position{symbol, 0, 0, 0, 0}; return it->second; } std::unordered_map RiskManager::all_positions() const { std::lock_guard lock(mtx_); return positions_; } double RiskManager::total_pnl() const { std::lock_guard lock(mtx_); double total = 0; for (auto& [sym, pos] : positions_) total += pos.realized_pnl + pos.unrealized_pnl; return total; } } // namespace hft