code wiki / (root) / nx_fin_portfolio.nx

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}