Spaces:
Sleeping
Sleeping
File size: 5,203 Bytes
ba6114e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | #include "risk/risk_manager.hpp"
#include <cmath>
#include <algorithm>
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(mtx_);
auto it = positions_.find(symbol);
if (it == positions_.end()) return Position{symbol, 0, 0, 0, 0};
return it->second;
}
std::unordered_map<std::string, Position> RiskManager::all_positions() const {
std::lock_guard<std::mutex> lock(mtx_);
return positions_;
}
double RiskManager::total_pnl() const {
std::lock_guard<std::mutex> lock(mtx_);
double total = 0;
for (auto& [sym, pos] : positions_)
total += pos.realized_pnl + pos.unrealized_pnl;
return total;
}
} // namespace hft
|