nx_fin_portfolio.nx source
↩ module page · 51 lines · 2606 B
1// nx_fin_portfolio.nx -- portfolio construction / DIVERSIFICATION: allocate across many names so NO single
2// position or correlated cluster can sink the account (the portfolio-level complement to R4's per-trade sizing).
3// Equal- or score-weighted allocation, each capped by a per-name concentration limit; sector-exposure and total-
4// exposure (no-leverage) checks; and a Herfindahl concentration index (lower = more diversified; 10000/N at
5// equal weight). Pure, i64 (cents/bps), data-driven caps (Cardinal 11). license_tier: ORIGINAL
6import "nx_syscalls.nx"
7const K_MAGIC_10000: i64 = 10000
8
9// equal-weight $ per position = equity/n, capped by the per-name concentration limit.
10func pf_equal_weight(equity_cents: i64, n_positions: i64, max_weight_bps: i64) -> i64 {
11 if n_positions <= 0 { return 0 }
12 var w: i64 = equity_cents / n_positions
13 let cap: i64 = equity_cents * max_weight_bps / K_MAGIC_10000
14 if w > cap { w = cap }
15 return w
16}
17
18// score-weighted $ = equity * score/total_score, capped by the per-name concentration limit.
19func pf_score_weight(equity_cents: i64, score: i64, total_score: i64, max_weight_bps: i64) -> i64 {
20 if total_score <= 0 { return 0 }
21 var w: i64 = equity_cents * score / total_score
22 let cap: i64 = equity_cents * max_weight_bps / K_MAGIC_10000
23 if w > cap { w = cap }
24 return w
25}
26
27// 1 if a position is within the per-name concentration limit.
28func pf_concentration_ok(position_cents: i64, equity_cents: i64, max_weight_bps: i64) -> i64 {
29 if position_cents <= equity_cents * max_weight_bps / K_MAGIC_10000 { return 1 }
30 return 0
31}
32
33// 1 if adding new_position keeps the sector within its exposure limit (correlation proxy).
34func pf_sector_ok(sector_exposure_cents: i64, new_position_cents: i64, equity_cents: i64, max_sector_bps: i64) -> i64 {
35 if (sector_exposure_cents + new_position_cents) <= equity_cents * max_sector_bps / K_MAGIC_10000 { return 1 }
36 return 0
37}
38
39// 1 if adding new keeps total deployed within equity (no leverage in v1).
40func pf_total_exposure_ok(deployed_cents: i64, new_position_cents: i64, equity_cents: i64) -> i64 {
41 if (deployed_cents + new_position_cents) <= equity_cents { return 1 }
42 return 0
43}
44
45// Herfindahl concentration index of weights (bps, summing to ~10000): sum(w^2)/10000. Equal-weight across N ->
46// 10000/N (lower = more diversified); all in one name -> 10000 (max concentration).
47func pf_herfindahl(weights_bps: *i64, n: i64) -> i64 {
48 var s: i64 = 0; var i: i64 = 0
49 while i < n { s = s + weights_bps[i]*weights_bps[i]; i = i + 1 }
50 return s / K_MAGIC_10000
51}