File size: 1,988 Bytes
f86fb3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#include "../../include/risk/risk_engine.hpp"
#include <cmath>

namespace quant::risk {

core::RejectReason RiskEngine::check_order(
    core::ClientId client_id,
    core::Side side,
    core::Price price,
    core::Quantity qty,
    double current_mid_price,
    uint64_t now_ns
) noexcept {
    if (now_ns == 0) {
        now_ns = core::TimeUtils::now_ns();
    }

    // 1. Quantity check
    if (qty == 0) {
        return core::RejectReason::INVALID_QUANTITY;
    }

    // 2. Notional value check (Price * Qty)
    int64_t notional = price.raw() * qty;
    if (notional > limits_.max_order_notional) {
        return core::RejectReason::RISK_NOTIONAL_EXCEEDED;
    }

    // 3. Fat-finger Price Collar check (against book mid-price)
    if (current_mid_price > 0.0 && price.is_positive()) {
        double order_p = price.to_double();
        double deviation = std::abs(order_p - current_mid_price) / current_mid_price;
        if (deviation > limits_.price_collar_pct) {
            return core::RejectReason::RISK_PRICE_COLLAR_VIOLATED;
        }
    }

    // 4. Leaky Bucket Rate Limiter check
    auto& bucket = rate_limiters_[client_id];
    bucket.refill(limits_.max_orders_per_sec, now_ns);
    if (!bucket.consume()) {
        return core::RejectReason::RISK_RATE_LIMIT_EXCEEDED;
    }

    // 5. Position Limit check
    int64_t current_pos = net_positions_[client_id];
    int64_t delta = (side == core::Side::BUY) ? static_cast<int64_t>(qty) : -static_cast<int64_t>(qty);
    int64_t projected_pos = current_pos + delta;
    if (std::abs(projected_pos) > limits_.max_net_position) {
        return core::RejectReason::RISK_POSITION_LIMIT_EXCEEDED;
    }

    return core::RejectReason::NONE;
}

void RiskEngine::on_fill(core::ClientId client_id, core::Side side, core::Quantity qty) noexcept {
    int64_t delta = (side == core::Side::BUY) ? static_cast<int64_t>(qty) : -static_cast<int64_t>(qty);
    net_positions_[client_id] += delta;
}

} // namespace quant::risk