"""Pre-vs-post Tiingo overlay factor check for the S&P panel. Builds the panel twice in-process — Yahoo-only (pre, survivorship-biased) and with the Tiingo overlay (post) — and runs the sleeve-relevant factors through the standard top-N phase-averaged backtest on both. The delta is the survivorship effect on the factors behind the live stock sleeves (overnight_recovery: overnight_mom + rec_126; lowrisk: ivol + low_beta). PYTHONPATH=. uv run python -m research.factor_panel_check """ from __future__ import annotations import os import time import warnings import pandas as pd import research.lab.panel as pnl from research.lab.factor_scan import PERIODS, phase_avg from research.lab.factors import FACTORS from research.lab.report import forward_returns, ic_series, ic_summary warnings.filterwarnings("ignore") NAMES = ["overnight_mom", "rec_126", "mom_12_1", "low_vol", "ivol", "low_beta", "sharpe_mom", "amp_cut_mom"] RESULTS = os.path.join("data", "research", "results") def scan(p, label: str) -> pd.DataFrame: fwd5 = forward_returns(p, 5) rows = [] for name in NAMES: t0 = time.time() s = FACTORS[name](p) row = {"panel": label, "factor": name} for per, (a, b) in PERIODS["us"].items(): st = phase_avg(p, s, a, b, 20, 5) ic = ic_summary(ic_series(s.loc[a:b], fwd5.loc[a:b], p.member.loc[a:b], 5)) row.update({f"{per}_cagr": st["cagr"], f"{per}_sharpe": st["sharpe"], f"{per}_ic5": ic["ic"], f"{per}_ic5_t": ic["t"]}) rows.append(row) print(f"[{label}] {name:14s} D {row['design_cagr']:6.1%}/{row['design_sharpe']:.2f} " f"H {row['holdout_cagr']:6.1%}/{row['holdout_sharpe']:.2f} [{time.time() - t0:.0f}s]", flush=True) return pd.DataFrame(rows) def main() -> None: post = pnl.load_us() orig = pnl.apply_sp500 pnl.apply_sp500 = lambda raw, **kw: raw try: pre = pnl._build_us() finally: pnl.apply_sp500 = orig a = scan(pre, "pre") b = scan(post, "post") df = pd.concat([a, b], ignore_index=True) os.makedirs(RESULTS, exist_ok=True) df.to_csv(os.path.join(RESULTS, "factor_panel_check.csv"), index=False) m = a.merge(b, on="factor", suffixes=("_pre", "_post")) print("\nfactor D_cagr pre->post D_sh pre->post H_cagr pre->post H_sh pre->post") for _, r in m.iterrows(): print(f"{r['factor']:14s} {r['design_cagr_pre']:7.1%}->{r['design_cagr_post']:6.1%} " f"{r['design_sharpe_pre']:5.2f}->{r['design_sharpe_post']:5.2f} " f"{r['holdout_cagr_pre']:7.1%}->{r['holdout_cagr_post']:6.1%} " f"{r['holdout_sharpe_pre']:5.2f}->{r['holdout_sharpe_post']:5.2f}") if __name__ == "__main__": main()