#!/usr/bin/env python3 """Deflated signal sweep — a factor zoo on data in hand, judged against the FULL search. Wisdom: breadth without multiple-testing control is the fastest way to fool yourself. So every signal's Deflated Sharpe is deflated by N_trials = the number of signals tested. Batch 1 (instant, free, data already downloaded): 22-futures daily panel — cross-sectional & time-series momentum, short-term reversal, low-vol — plus structural SEASONALITY (overnight-drift, intraday, day-of-week). Each signal → daily PnL → full/IS/OOS Sharpe, CPCV(45-path) median+5th-pct, Deflated Sharpe. Ranked by OOS. Survivor = DSR>0.95 AND OOS>0 AND CPCV-median>0 (consistent + multiple-testing-honest). Validation on GPU (torch); panel/signal staging on CPU (small). Roll-zeroed close-close returns (screening; survivors get proper roll handling in deeper validation). """ import glob import math import re from itertools import combinations from statistics import NormalDist import numpy as np import torch ND = NormalDist() DEV = "cuda" if torch.cuda.is_available() else "cpu" MONTHS = "FGHJKMNQUVXZ" SPLIT_DAY = 19723 # 2024-01-01 in days-since-epoch def load_panel(): import databento as db series = {} for p in sorted(glob.glob("data/surfer/*.dbn")): root = p.split("/")[-1][:-4] pat = re.compile("^" + re.escape(root) + "[" + MONTHS + r"]\d{1,2}$") try: df = db.DBNStore.from_file(p).to_df().reset_index() except Exception: continue if df.empty or "close" not in df.columns: continue df = df[df["close"] > 0] df = df[df["symbol"].astype(str).str.match(pat)] if df.empty: continue df["day"] = (df["ts_event"].astype("int64") // (86_400 * 10**9)) idx = df.groupby("day")["volume"].idxmax() f = df.loc[idx, ["day", "instrument_id", "open", "close"]].sort_values("day") series[root] = (f["day"].to_numpy(np.int64), f["instrument_id"].to_numpy(np.int64), f["open"].to_numpy(np.float64), f["close"].to_numpy(np.float64)) roots = sorted(series) days = np.array(sorted(set().union(*[set(series[r][0].tolist()) for r in roots]))) di = {d: i for i, d in enumerate(days.tolist())} T, N = len(days), len(roots) close = np.full((T, N), np.nan); op = np.full((T, N), np.nan); inst = np.full((T, N), -1, np.int64) for j, r in enumerate(roots): d, ii, oo, cc = series[r] for k in range(len(d)): row = di[int(d[k])]; close[row, j] = cc[k]; op[row, j] = oo[k]; inst[row, j] = ii[k] weekday = (days + 3) % 7 # epoch day0 = Thursday → 0=Mon..6=Sun return roots, days, close, op, inst, weekday def build_returns(close, op, inst): T, N = close.shape lc = np.log(close) R = np.zeros((T, N)); R[1:] = lc[1:] - lc[:-1] same = np.zeros((T, N), bool); same[1:] = inst[1:] == inst[:-1] R = np.where(same & np.isfinite(R), R, 0.0) ov = np.zeros((T, N)); ov[1:] = np.log(op[1:] / close[:-1]) ov = np.where(same & np.isfinite(ov), ov, 0.0) intr = np.log(close / op); intr = np.where(np.isfinite(intr), intr, 0.0) return lc, R, ov, intr def trailing(lc, L): out = np.full_like(lc, np.nan); out[L:] = lc[L:] - lc[:-L]; return out def xs_weights(sig): # cross-sectional z-score, market-neutral, unit gross mu = np.nanmean(sig, axis=1, keepdims=True); sd = np.nanstd(sig, axis=1, keepdims=True) z = np.nan_to_num((sig - mu) / np.where(sd > 0, sd, 1)) g = np.sum(np.abs(z), axis=1, keepdims=True); g[g == 0] = 1 return z / g def pnl_w(w, R, cost_bp=2.0): p = np.sum(w[:-1] * R[1:], axis=1) turn = np.sum(np.abs(w[1:] - w[:-1]), axis=1) return p - turn * (cost_bp / 1e4) def sharpe_t(x): x = x[torch.isfinite(x)] if len(x) < 10 or float(x.std()) == 0: return float("nan") return float(x.mean() / x.std() * math.sqrt(252)) def validate(pnl, days, n_trials): x = torch.tensor(np.asarray(pnl), device=DEV, dtype=torch.float64) n = len(x); full = sharpe_t(x) dd = days[-n:] ism = torch.tensor(dd < SPLIT_DAY, device=DEV) sis = sharpe_t(x[ism]) soos = sharpe_t(x[~ism]) if int((~ism).sum()) > 30 else float("nan") nb, k = 10, 2 blk = [torch.arange(i * n // nb, (i + 1) * n // nb, device=DEV) for i in range(nb)] oos = [sharpe_t(x[torch.cat([blk[b] for b in c])]) for c in combinations(range(nb), k)] oos = np.array([o for o in oos if not math.isnan(o)]) med = float(np.median(oos)) if len(oos) else float("nan") p5 = float(np.percentile(oos, 5)) if len(oos) else float("nan") srd = full / math.sqrt(252) if n_trials > 1: sr0 = (1 / math.sqrt(n)) * ((1 - 0.5772) * ND.inv_cdf(1 - 1.0 / n_trials) + 0.5772 * ND.inv_cdf(1 - 1.0 / (n_trials * math.e))) else: sr0 = 0.0 dsr = ND.cdf((srd - sr0) * math.sqrt(n - 1)) if not math.isnan(full) else float("nan") return dict(full=full, is_=sis, oos=soos, med=med, p5=p5, dsr=dsr) def main(): roots, days, close, op, inst, weekday = load_panel() 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) invvol = 1.0 / np.where(vol63 > 0, vol63, np.nan) sig = {} for L in [21, 63, 126, 252]: sig[f"XS_mom_{L}"] = pnl_w(xs_weights(trailing(lc, L)), R) for L in [1, 5]: sig[f"XS_rev_{L}"] = pnl_w(xs_weights(-trailing(lc, L)), R) sig["XS_lowvol"] = pnl_w(xs_weights(-vol63), R) for L in [21, 63, 252]: w = np.nan_to_num(np.sign(trailing(lc, L)) * invvol) g = np.sum(np.abs(w), axis=1, keepdims=True); g[g == 0] = 1 sig[f"TS_mom_{L}"] = pnl_w(w / g, R) sig["SEAS_overnight"] = np.nanmean(ov[1:], axis=1) - 1.0 / 1e4 # EW long overnight, ~1bp cost sig["SEAS_intraday"] = np.nanmean(intr[1:], axis=1) - 1.0 / 1e4 # EW long intraday ewR = np.nanmean(R, axis=1) for d, nm in enumerate(["Mon", "Tue", "Wed", "Thu", "Fri"]): sig[f"SEAS_{nm}"] = np.where(weekday[1:] == d, ewR[1:], 0.0) NT = len(sig) rows = [(nm, validate(p, days, NT)) for nm, p in sig.items()] rows.sort(key=lambda r: -(r[1]["oos"] if not math.isnan(r[1]["oos"]) else -9)) print(f"\n===== DEFLATED SIGNAL SWEEP — Batch 1 (22 futures daily + seasonality) =====") print(f"device={DEV} roots={len(roots)} days={T} N_trials(deflation)={NT}") print(f"{'signal':>16} {'full':>7} {'IS':>7} {'OOS':>7} {'CPCVmed':>8} {'CPCV5%':>8} {'DSR':>6}") for nm, v in rows: print(f"{nm:>16} {v['full']:>+7.2f} {v['is_']:>+7.2f} {v['oos']:>+7.2f} " f"{v['med']:>+8.2f} {v['p5']:>+8.2f} {v['dsr']:>6.2f}") surv = [nm for nm, v in rows if v["dsr"] > 0.95 and v["oos"] > 0 and v["med"] > 0] print(f"\nSURVIVORS (DSR>0.95 & OOS>0 & CPCVmed>0): {surv if surv else 'NONE'}") print("DSR is deflated by N_trials → multiple-testing-honest. Survivors get deeper validation + crypto/COT batches.") if __name__ == "__main__": main()