From 8ebb65f27eafaa3966eafc80ccff6c7a1d75297f Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Mon, 8 Jun 2026 00:29:36 +0200 Subject: [PATCH] fix: NaN-safe correlation de-risk in book_series (zero-variance windows) np.corrcoef on a window where a vol-normalized stream is constant (warmup / non-moving stream) divided by zero std -> RuntimeWarning + NaN in the corr matrix -> ac=NaN -> propagated via chist into the z-score and could poison leverage L and book with NaN (silent corruption). Fix: correlate only streams with std>0 in the window, nansum over the actual valid count, fall back to ac=0 when <2 vary. Verified: no RuntimeWarning (tested with warnings-as-errors), no NaN, identical L/weights. Affects all scripts importing book_series + the cluster ConfigMap. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/surfer/multistrat_paper.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/surfer/multistrat_paper.py b/scripts/surfer/multistrat_paper.py index e613abb11..665acbafc 100644 --- a/scripts/surfer/multistrat_paper.py +++ b/scripts/surfer/multistrat_paper.py @@ -88,7 +88,12 @@ def book_series(R): L = min(L, max(KELLY_FLOOR, (emu * 252) / (evar * 252 + 1e-9))) dd = eq / peak - 1.0 L *= float(np.clip(1 - DD_SENS * max(0.0, -dd - DD_DEADBAND), DD_FLOOR, 1.0)) - cm = np.corrcoef(np.nan_to_num(M[t - 63:t]).T); ac = (cm.sum() - S) / (S * S - S); chist.append(ac) + sub = np.nan_to_num(M[t - 63:t]); vmask = sub.std(0) > 1e-12 # only streams that vary (avoid /0 in corrcoef -> NaN) + if int(vmask.sum()) >= 2: + k = int(vmask.sum()); cm = np.corrcoef(sub[:, vmask].T); ac = float((np.nansum(cm) - k) / (k * k - k)) + else: + ac = 0.0 + chist.append(ac) if len(chist) > 60: h = np.array(chist[-120:]); z = (ac - h.mean()) / (h.std() + 1e-9) L *= float(np.clip(1 - 0.2 * max(0.0, z), 0.5, 1.0))