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