nx_fin_pbo.nx source
↩ module page · 48 lines · 2604 B
1// nx_fin_pbo.nx -- Probability of Backtest Overfitting (Bailey/Lopez de Prado CSCV). Completes the overfit-
2// detection suite (deflated-Sharpe was the multiple-testing correction; PBO is the combinatorial one). Given an
3// M-strategy x S-group performance matrix, enumerate every C(S, S/2) train/test split (train = set bits of a
4// popcount-S/2 mask, test = the rest); for each split pick the BEST in-sample strategy and ask whether it lands
5// in the BOTTOM HALF out-of-sample. PBO = fraction of splits where it does. High PBO => your "winner" is likely
6// luck. Pure integer combinatorics -- no transcendental functions, exact + gate-checkable. license_tier: ORIGINAL
7import "nx_syscalls.nx"
8const K_MAGIC_1000000000: i64 = 1000000000
9
10func pb_popcount(x: i64) -> i64 { var c: i64 = 0; var v: i64 = x; while v > 0 { c = c + (v & 1); v = v >> 1 } return c }
11
12// PBO in permille (0..1000). perf flat = perf[strategy*S + group]. m strategies, s groups (s even).
13func pb_pbo_permille(perf: *i64, m: i64, s: i64) -> i64 {
14 let half: i64 = s / 2
15 var overfit: i64 = 0; var ncombos: i64 = 0
16 var total: i64 = 1; var pe: i64 = 0
17 while pe < s { total = total * 2; pe = pe + 1 } // 2^s (avoid << : unconfirmed .nx operator)
18 var mask: i64 = 0
19 while mask < total {
20 if pb_popcount(mask) == half {
21 let oos: *i64 = sys_mmap(8*m) as *i64
22 var best_is_val: i64 = 0 - K_MAGIC_1000000000; var best_idx: i64 = 0
23 var mi: i64 = 0
24 while mi < m {
25 var is_sum: i64 = 0; var oos_sum: i64 = 0; var g: i64 = 0
26 while g < s {
27 let val: i64 = perf[mi*s + g]
28 if ((mask >> g) & 1) == 1 { is_sum = is_sum + val } else { oos_sum = oos_sum + val }
29 g = g + 1
30 }
31 oos[mi] = oos_sum
32 if is_sum > best_is_val { best_is_val = is_sum; best_idx = mi }
33 mi = mi + 1
34 }
35 let best_oos: i64 = oos[best_idx]
36 var nhigher: i64 = 0; var k: i64 = 0
37 while k < m { if oos[k] > best_oos { nhigher = nhigher + 1 } k = k + 1 }
38 if 2*nhigher >= m { overfit = overfit + 1 } // best-IS strategy fell to the bottom half OOS
39 ncombos = ncombos + 1
40 }
41 mask = mask + 1
42 }
43 if ncombos <= 0 { return 0 }
44 return overfit * 1000 / ncombos
45}
46
47// overfit-likely iff PBO >= threshold permille (500 = 50%).
48func pb_overfit_likely(pbo_permille: i64, thresh_permille: i64) -> i64 { if pbo_permille >= thresh_permille { return 1 } return 0 }