nx_fin_risk_engine.nx source
↩ module page · 52 lines · 2950 B
1// nx_fin_risk_engine.nx -- R4: POSITION SIZING + drawdown circuit breaker = the real answer to "don't blow up".
2// All i64 (cents / basis-points), NO floats. Sizers: (1) fractional-KELLY from the backtest's edge stats,
3// (2) fixed-RISK-per-trade (the "1% rule": size so a stop-out costs a bounded % of equity), (3) Kelly-fraction ->
4// shares. Plus a max-DRAWDOWN circuit breaker that HALTS sizing once equity has fallen past a limit, and an
5// ADV cap (composes R1 md_capacity) so every size respects the scaling wall. Single-responsibility: sizes ONE
6// position; portfolio-level diversification is nx_fin_portfolio. license_tier: ORIGINAL
7import "nx_syscalls.nx"
8import "nx_fin_marketdata.nx"
9const K_MAGIC_10000: i64 = 10000
10
11// optimal Kelly fraction (bps of equity) from win-rate + avg win/loss. f* = p - (1-p)/b, b = avg_win/avg_loss.
12// No edge (f*<0) or no loss estimate -> 0 (refuse to size). Full Kelly is aggressive -> temper with re_frac_kelly.
13func re_kelly_bps(win_rate_bps: i64, avg_win_cents: i64, avg_loss_cents: i64) -> i64 {
14 if avg_loss_cents <= 0 { return 0 }
15 if avg_win_cents <= 0 { return 0 }
16 let b_bps: i64 = avg_win_cents*K_MAGIC_10000/avg_loss_cents // win/loss ratio, x10000
17 if b_bps <= 0 { return 0 }
18 let lose_bps: i64 = K_MAGIC_10000 - win_rate_bps
19 let f: i64 = win_rate_bps - (lose_bps*K_MAGIC_10000/b_bps)
20 if f < 0 { return 0 }
21 return f
22}
23
24// apply a safety fraction to Kelly (half-Kelly = 5000 bps). Both in bps.
25func re_frac_kelly(kelly_bps: i64, frac_bps: i64) -> i64 { return kelly_bps*frac_bps/K_MAGIC_10000 }
26
27// shares such that a stop-out (entry -> stop) costs exactly risk_budget_bps of equity (the "1% rule").
28func re_size_by_risk(equity_cents: i64, risk_budget_bps: i64, entry_cents: i64, stop_cents: i64) -> i64 {
29 let risk_per_share: i64 = entry_cents - stop_cents
30 if risk_per_share <= 0 { return 0 }
31 let risk_budget: i64 = equity_cents*risk_budget_bps/K_MAGIC_10000
32 return risk_budget/risk_per_share
33}
34
35// shares from a Kelly (or any) equity fraction: (equity * frac_bps/10000) / price.
36func re_size_by_kelly(equity_cents: i64, frac_bps: i64, price_cents: i64) -> i64 {
37 if price_cents <= 0 { return 0 }
38 let dollars: i64 = equity_cents*frac_bps/K_MAGIC_10000
39 return dollars/price_cents
40}
41
42// circuit breaker: 1 = trading allowed (current drawdown within limit), 0 = HALT (breaker tripped).
43func re_dd_ok(current_dd_bps: i64, limit_bps: i64) -> i64 { if current_dd_bps >= limit_bps { return 0 } return 1 }
44
45// cap a share count by the ADV scaling wall (composes R1 md_capacity). adv_cents<=0 disables the cap.
46func re_cap_shares(shares: i64, price_cents: i64, adv_cents: i64, participation_bps: i64) -> i64 {
47 if adv_cents <= 0 { return shares }
48 if price_cents <= 0 { return 0 }
49 let cap_shares: i64 = md_capacity(adv_cents, participation_bps)/price_cents
50 if cap_shares < shares { return cap_shares }
51 return shares
52}