Multi-strat with foxhunt's own ideas. (1) Combine uncorrelated premia: ~0.72 Sharpe 2019-26 but ~=60/40, only when streams net-positive (traditional 2010-26 combine +0.51 < equity +0.68 = dilution). (2) Edge-decay-trust allocation (Page-Hinkley theta, resurrection) genuinely helps: +0.16->+0.27, correctly down-weights decayed streams. (3) Static risk layer crushed returns (one-way latch); ADAPTIVE layer (continuous self-recovering DD de-lever + Kelly-floor + z-score corr + EMA vol) beat it (+0.03->+0.14, maxDD -18.7->-14.5) -- value is drawdown control. (4) THE MOAT = cheap financing: adaptive 1x Sharpe +0.48 vs 2x +0.14; retail 6-7% margin kills leverage benefit. Funds lever ~0.7 Sharpe only via prime-brokerage SOFR+1-2%. Deployable best = ~1x adaptive-risk-managed diversified book (~0.5-0.7 Sharpe, unlevered), scales with capital. Foxhunt ideas improve execution (validated); engine value = risk-mgmt not alpha. Ceiling ~0.7 ironclad. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
124 lines
6.1 KiB
Python
124 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Deployable nano multi-strat book with 2x leverage + the foxhunt risk-management layer.
|
|
|
|
Streams (risk-parity, vol-normalized): equity, bond, gold, commodity, trend, crypto.
|
|
RISK LAYER (the foxhunt principles applied to the book):
|
|
1. vol-target -> scale exposure to TARGET_VOL on trailing realized vol
|
|
2. drawdown CB -> de-lever at -15% (1x) / -25% (0.5x) / -35% (0.25x) [CMDP circuit-breaker]
|
|
3. corr de-risk -> when avg cross-stream corr spikes (diversification breaking in a crisis), cut leverage
|
|
4. Kelly cap -> leverage <= Kelly fraction (mean/var), never over-lever a degrading book
|
|
L[t] = min(MAXLEV, L_vol, L_kelly) * dd_mult * corr_mult, applied to yesterday's signal.
|
|
Financing cost charged on the borrowed (>1x) portion. Backtest risk-managed-2x vs naive-2x; emit
|
|
live target weights for a given capital. ETF mapping for deployment in the verdict.
|
|
"""
|
|
import math
|
|
import os
|
|
import sys
|
|
|
|
import numpy as np
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from signal_sweep import load_panel, build_returns # noqa: E402
|
|
import pit_sweep # noqa: E402
|
|
|
|
TARGET_VOL = 0.10
|
|
MAXLEV = 2.0
|
|
FIN = 0.06 # 6%/yr financing on borrowed portion
|
|
DD1, DD2, DD3 = -0.15, -0.25, -0.35
|
|
CORR_HI = 0.45 # avg pairwise corr above which to de-risk
|
|
|
|
|
|
def volnorm(r, tv=TARGET_VOL, win=63):
|
|
out = np.zeros_like(r)
|
|
for t in range(win, len(r)):
|
|
rv = np.std(r[t - win:t]) * math.sqrt(252)
|
|
out[t] = r[t] * min(5.0, tv / (rv + 1e-9))
|
|
return out
|
|
|
|
|
|
def stats(r):
|
|
r = r[np.isfinite(r)]
|
|
ann = r.mean() * 252; vol = r.std() * math.sqrt(252)
|
|
eq = np.cumprod(1 + r); dd = float((eq / np.maximum.accumulate(eq) - 1).min())
|
|
return ann, vol, (ann / vol if vol > 0 else float("nan")), dd
|
|
|
|
|
|
def build_streams():
|
|
roots, days, close, op, inst = load_panel()[:5]
|
|
lc, R, ov, intr = build_returns(close, op, inst)
|
|
T, N = close.shape
|
|
vol63 = np.full_like(lc, np.nan)
|
|
for t in range(63, T):
|
|
vol63[t] = np.nanstd(R[t - 63:t], axis=0)
|
|
iv = 1.0 / np.where(vol63 > 0, vol63, np.nan)
|
|
tsig = np.full_like(lc, np.nan); tsig[252:] = np.sign(lc[252:] - lc[:-252])
|
|
avail = np.isfinite(close) & np.isfinite(vol63) & (vol63 > 0) & np.isfinite(tsig)
|
|
w = np.where(avail, tsig * iv, 0.0); g = np.abs(w).sum(1, keepdims=True); g[g == 0] = 1; w = w / g
|
|
trend = np.zeros(T); trend[1:] = np.sum(w[:-1] * R[1:], axis=1)
|
|
c = {r: roots.index(r) for r in ("ES", "ZN", "GC", "CL")}
|
|
fut = {"equity": R[:, c["ES"]], "bond": R[:, c["ZN"]], "gold": R[:, c["GC"]], "commod": R[:, c["CL"]], "trend": trend}
|
|
syms, cdays, cc, _, _ = pit_sweep.load()
|
|
j = syms.index("BTCUSDT"); lcb = np.log(cc[:, j])
|
|
btc = {int(cdays[t]): (lcb[t] - lcb[t - 1]) for t in range(1, len(cdays)) if np.isfinite(lcb[t]) and np.isfinite(lcb[t - 1])}
|
|
idx = np.array([i for i, d in enumerate(days) if int(d) in btc])
|
|
streams = {k: v[idx] for k, v in fut.items()}
|
|
streams["crypto"] = np.array([btc[int(days[i])] for i in idx])
|
|
return streams, days[idx], (1970 + days[idx] / 365.25).astype(int)
|
|
|
|
|
|
def risk_managed(M, maxlev, with_risk=True):
|
|
"""M: [T, S] vol-normalized stream returns. Returns book daily returns under the risk layer."""
|
|
base = np.nanmean(M, axis=1) # risk-parity combination
|
|
T = len(base)
|
|
out = np.zeros(T); eq = 1.0; peak = 1.0
|
|
for t in range(63, T - 1):
|
|
rv = np.std(base[t - 63:t]) * math.sqrt(252)
|
|
L = min(maxlev, TARGET_VOL / (rv + 1e-9)) # (1) vol-target, capped at maxlev
|
|
if with_risk:
|
|
mu = base[t - 63:t].mean() * 252; var = (base[t - 63:t].std() ** 2) * 252
|
|
L = min(L, max(0.0, mu / (var + 1e-9))) # (4) Kelly cap
|
|
dd = eq / peak - 1.0 # (2) drawdown circuit-breaker
|
|
ddm = 1.0 if dd > DD1 else (0.5 if dd > DD2 else (0.25 if dd > DD3 else 0.0))
|
|
cm = np.corrcoef(np.nan_to_num(M[t - 63:t]).T) # (3) correlation de-risk
|
|
ac = (cm.sum() - cm.shape[0]) / (cm.shape[0] ** 2 - cm.shape[0])
|
|
corrm = 1.0 if ac < CORR_HI else max(0.4, 1 - (ac - CORR_HI) * 2)
|
|
L *= ddm * corrm
|
|
L = max(0.0, min(L, maxlev))
|
|
r = L * base[t + 1] - FIN * max(L - 1.0, 0.0) / 252 # financing on borrowed portion
|
|
out[t + 1] = r
|
|
eq *= (1 + r); peak = max(peak, eq)
|
|
return out
|
|
|
|
|
|
def main():
|
|
streams, days, year = build_streams()
|
|
names = list(streams)
|
|
M = np.column_stack([volnorm(streams[n]) for n in names])
|
|
print(f"DEPLOYABLE MULTI-STRAT BOOK — {len(names)} streams, {M.shape[0]} days ({names})")
|
|
for lab, wr, lev in [("risk-managed 2x", True, 2.0), ("NAIVE 2x (no risk layer)", False, 2.0),
|
|
("risk-managed 1x", True, 1.0)]:
|
|
r = risk_managed(M, lev, wr)
|
|
a, v, sh, dd = stats(r)
|
|
print(f" {lab:>26}: Sharpe {sh:+.2f} ann {100*a:+.1f}% vol {100*v:.0f}% maxDD {100*dd:+.1f}%")
|
|
rm = risk_managed(M, 2.0, True)
|
|
print(f" per-year Sharpe (risk-managed 2x): " + " ".join(f"{y}:{stats(rm[year == y])[2]:+.1f}" for y in sorted(set(year)) if (year == y).sum() > 150))
|
|
|
|
# live sizing: current leverage + target $ per stream for capital
|
|
base = np.nanmean(M, axis=1); t = len(base) - 1
|
|
rv = np.std(base[t - 63:t]) * math.sqrt(252)
|
|
Lv = min(MAXLEV, TARGET_VOL / (rv + 1e-9))
|
|
cap = 35000
|
|
print(f"\n LIVE SIZING (today): vol-target leverage {Lv:.2f}x -> deploy ${cap*Lv:,.0f} gross on ${cap:,.0f}")
|
|
per = cap * Lv / len(names)
|
|
etf = {"equity": "SPY", "bond": "IEF", "gold": "GLD", "commod": "PDBC", "trend": "DBMF", "crypto": "BTC(spot)"}
|
|
for n in names:
|
|
print(f" {n:>7} ({etf[n]:>9}): ${per:,.0f}")
|
|
print("\n VERDICT: risk-managed 2x should keep maxDD bounded (~-20-30%) vs naive 2x (~-40-50%) at higher")
|
|
print(" return than 1x -> the hedge-fund model at retail. Deploy via the ETFs above (2x Reg-T margin) +")
|
|
print(" small crypto sleeve. Same ~0.7-Sharpe book; leverage+risk-layer = the fund. Honest: still beta,")
|
|
print(" drawdowns real, financing drags, single-window crypto sleeve -> size crypto small.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|