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) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-08 00:29:36 +02:00
parent f320c5f09f
commit 8ebb65f27e

View File

@@ -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))