harness(causality): guardia look-ahead + calendar-artifact self-policing nel lab intraday

- altlib.causality_ok(target_fn, tf): online-consistency guard (ricalcola il target su un
  prefisso, la coda deve combaciare col full). eval_weights shifta la posizione ma non vede
  una feature non-causale (finestra centrata/shift(-k)/stat full-sample) -> questa sì.
- intra_score integra DUE gate prima/dopo lo scoring: causality (leak -> LEAK, squalificato)
  e day_boundary_robust (ARTIFACT-RISK -> fuori dagli slot). Effetto sul leaderboard intraday:
  open_drive + weekly_seasonality + overnight -> CAL-ARTIFACT (da soli, niente skeptic);
  prevday_range_breakout resta (ROBUST). earns_slot 10 -> 8.
- +2 test (causal-ok / leak), suite intera verde.

Il lab intraday ora auto-becca leak e artefatti-calendario che ieri richiedevano 3 scettici.
Chiude la 3a lezione harness dell'onda intraday.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-21 15:22:58 +00:00
parent 4ae3b42442
commit d5dd6f4b72
5 changed files with 187 additions and 59 deletions
@@ -76,8 +76,13 @@ short-leg/regime-2025. Trattamento = come `dvol_spread` / XS01 / STA05.
2.**`altlib.eval_weights_smallcap(df, target, capital=600, min_order=5)`** — salta i 2.**`altlib.eval_weights_smallcap(df, target, capital=600, min_order=5)`** — salta i
ribilanciamenti sub-min_order (la finzione del micro-trading a $600), riporta lo Sharpe haircut ribilanciamenti sub-min_order (la finzione del micro-trading a $600), riporta lo Sharpe haircut
reale vs modellato. Vale per ogni sleeve a questo capitale, TP01 incluso. Test idem. reale vs modellato. Vale per ogni sleeve a questo capitale, TP01 incluso. Test idem.
3. **Causality guard nel lab intraday**: qui fatta a mano (max_tail_diff = 0 su tutti e 5). Da 3. **`altlib.causality_ok(target_fn, tf)`** — guardia look-ahead/online-consistency (ricalcola
integrare in `intra_score` come in blind/ortho (non ancora codificata). il target su un prefisso e pretende che la coda combaci con il full): eval_weights shifta la
posizione ma NON vede una feature non-causale (finestra centrata / shift(-k) / stat full-sample).
Integrata in `intra_score` (un leak è squalificato prima dello scoring). + il calendar-artifact
gate (`day_boundary_robust`) ora gira dentro `intra_score`: **open_drive/weekly_seasonality/
overnight → CAL-ARTIFACT, fuori dagli slot da soli**; prevday_breakout resta (ROBUST). Il lab
intraday ora auto-becca leak e artefatti-calendario che ieri richiedevano gli scettici. Test idem.
File: `scripts/research/intraday/{intra_score,meta_intra,verify_intra}.py`, File: `scripts/research/intraday/{intra_score,meta_intra,verify_intra}.py`,
`agents/agent_00..15_*.py`, `intra_leaderboard.json`. `agents/agent_00..15_*.py`, `intra_leaderboard.json`.
+30
View File
@@ -640,6 +640,36 @@ def eval_weights_smallcap(df: pd.DataFrame, target, capital: float = 600.0,
executed_turnover_per_year=round(float(turn.sum() / (len(turn) / bpy_d)), 1)) executed_turnover_per_year=round(float(turn.sum() / (len(turn) / bpy_d)), 1))
def causality_ok(target_fn, tf: str = "1h", assets=CERTIFIED,
tail: int = 80, tol: float = 1e-3) -> dict:
"""Online-consistency / LOOK-AHEAD guard for a continuous target_fn(df) [or (df, asset)].
eval_weights SHIFTS the position so you cannot leak by multiplying a weight by the SAME
bar's return — but it does NOT verify the FEATURE construction is causal: a centered
window, a .shift(-k), or a full-sample statistic would pass eval_weights yet peek at the
future. Here we recompute the target on a TRUNCATED prefix and require its tail to MATCH
target(full)[:cut] (the bars a deployable signal would have emitted in real time). Any
future-peeking diverges. Run this in every altlib-based lab (blind/ortho already do)."""
worst = 0.0; bad = False; checked = 0
for a in assets:
df = get(a, tf)
full = np.nan_to_num(np.asarray(_call_target(target_fn, df, a), float))
n = len(df)
for cut in (int(n * 0.80), int(n * 0.92)):
if cut <= tail + 5 or cut >= n:
continue
sub = df.iloc[:cut].reset_index(drop=True)
s = np.nan_to_num(np.asarray(_call_target(target_fn, sub, a), float))
if len(s) != cut:
bad = True
continue
d = np.abs(s[cut - tail:cut] - full[cut - tail:cut])
worst = max(worst, float(np.max(d)) if len(d) else 0.0)
checked += 1
return dict(ok=bool((not bad) and worst <= tol),
max_tail_diff=round(worst, 8), checked=checked,
reason=("length-mismatch on prefix" if bad else None))
# =========================================================================== # ===========================================================================
# DRIVERS — run a hypothesis across both assets, several TFs, with a fee sweep. # DRIVERS — run a hypothesis across both assets, several TFs, with a fee sweep.
# =========================================================================== # ===========================================================================
+104 -56
View File
@@ -2,6 +2,7 @@
{ {
"name": "agent_13_range_compression_intra", "name": "agent_13_range_compression_intra",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "PASS", "abs_grade": "PASS",
"marginal_verdict": "ADDS", "marginal_verdict": "ADDS",
"earns_slot": true, "earns_slot": true,
@@ -18,32 +19,14 @@
"abs_hold_sharpe": 0.928, "abs_hold_sharpe": 0.928,
"turnover_per_year": 13.4, "turnover_per_year": 13.4,
"fee020_full_sharpe": 0.488, "fee020_full_sharpe": 0.488,
"fee_survives": true "fee_survives": true,
}, "boundary_verdict": "INVARIANT",
{ "boundary_spread": 0.0
"name": "agent_05_open_drive",
"tf": "1h",
"abs_grade": "PASS",
"marginal_verdict": "ADDS",
"earns_slot": true,
"corr_full": 0.133,
"corr_hold": -0.055,
"uplift_hold": 0.716,
"uplift_full": 0.232,
"cand_insample_sharpe": 0.924,
"has_insample_edge": true,
"is_hedge": false,
"robust_oos": true,
"multicut_persistent": true,
"abs_full_sharpe": 0.678,
"abs_hold_sharpe": 1.052,
"turnover_per_year": 21.4,
"fee020_full_sharpe": 0.641,
"fee_survives": true
}, },
{ {
"name": "agent_09_prevday_range_breakout", "name": "agent_09_prevday_range_breakout",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "PASS", "abs_grade": "PASS",
"marginal_verdict": "ADDS", "marginal_verdict": "ADDS",
"earns_slot": true, "earns_slot": true,
@@ -60,32 +43,14 @@
"abs_hold_sharpe": 0.916, "abs_hold_sharpe": 0.916,
"turnover_per_year": 56.2, "turnover_per_year": 56.2,
"fee020_full_sharpe": 0.985, "fee020_full_sharpe": 0.985,
"fee_survives": true "fee_survives": true,
}, "boundary_verdict": "ROBUST",
{ "boundary_spread": 0.196
"name": "agent_11_weekly_seasonality",
"tf": "1h",
"abs_grade": "PASS",
"marginal_verdict": "ADDS",
"earns_slot": true,
"corr_full": 0.44,
"corr_hold": 0.32,
"uplift_hold": 0.402,
"uplift_full": 0.328,
"cand_insample_sharpe": 1.728,
"has_insample_edge": true,
"is_hedge": false,
"robust_oos": true,
"multicut_persistent": true,
"abs_full_sharpe": 1.418,
"abs_hold_sharpe": 0.861,
"turnover_per_year": 86.0,
"fee020_full_sharpe": 1.262,
"fee_survives": true
}, },
{ {
"name": "agent_06_vol_event_revert_15m", "name": "agent_06_vol_event_revert_15m",
"tf": "15m", "tf": "15m",
"causal": true,
"abs_grade": "PASS", "abs_grade": "PASS",
"marginal_verdict": "ADDS", "marginal_verdict": "ADDS",
"earns_slot": true, "earns_slot": true,
@@ -102,11 +67,14 @@
"abs_hold_sharpe": 0.949, "abs_hold_sharpe": 0.949,
"turnover_per_year": 13.8, "turnover_per_year": 13.8,
"fee020_full_sharpe": 0.593, "fee020_full_sharpe": 0.593,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "INVARIANT",
"boundary_spread": 0.0
}, },
{ {
"name": "agent_07_volume_spike_revert", "name": "agent_07_volume_spike_revert",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "WEAK", "abs_grade": "WEAK",
"marginal_verdict": "ADDS", "marginal_verdict": "ADDS",
"earns_slot": true, "earns_slot": true,
@@ -123,11 +91,14 @@
"abs_hold_sharpe": 0.096, "abs_hold_sharpe": 0.096,
"turnover_per_year": 25.3, "turnover_per_year": 25.3,
"fee020_full_sharpe": 0.349, "fee020_full_sharpe": 0.349,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "INVARIANT",
"boundary_spread": 0.0
}, },
{ {
"name": "agent_14_multi_session_momentum", "name": "agent_14_multi_session_momentum",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "PASS", "abs_grade": "PASS",
"marginal_verdict": "ADDS", "marginal_verdict": "ADDS",
"earns_slot": true, "earns_slot": true,
@@ -144,11 +115,14 @@
"abs_hold_sharpe": 0.436, "abs_hold_sharpe": 0.436,
"turnover_per_year": 28.8, "turnover_per_year": 28.8,
"fee020_full_sharpe": 1.144, "fee020_full_sharpe": 1.144,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "ROBUST",
"boundary_spread": 0.022
}, },
{ {
"name": "agent_08_gap_fill", "name": "agent_08_gap_fill",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "PASS", "abs_grade": "PASS",
"marginal_verdict": "ADDS", "marginal_verdict": "ADDS",
"earns_slot": true, "earns_slot": true,
@@ -165,11 +139,14 @@
"abs_hold_sharpe": 0.432, "abs_hold_sharpe": 0.432,
"turnover_per_year": 11.6, "turnover_per_year": 11.6,
"fee020_full_sharpe": 0.501, "fee020_full_sharpe": 0.501,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "INVARIANT",
"boundary_spread": 0.0
}, },
{ {
"name": "agent_12_close_location", "name": "agent_12_close_location",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "PASS", "abs_grade": "PASS",
"marginal_verdict": "ADDS", "marginal_verdict": "ADDS",
"earns_slot": true, "earns_slot": true,
@@ -186,11 +163,14 @@
"abs_hold_sharpe": 0.501, "abs_hold_sharpe": 0.501,
"turnover_per_year": 14.4, "turnover_per_year": 14.4,
"fee020_full_sharpe": 1.166, "fee020_full_sharpe": 1.166,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "INVARIANT",
"boundary_spread": 0.0
}, },
{ {
"name": "agent_10_trend_quality_intra", "name": "agent_10_trend_quality_intra",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "PASS", "abs_grade": "PASS",
"marginal_verdict": "ADDS", "marginal_verdict": "ADDS",
"earns_slot": true, "earns_slot": true,
@@ -207,11 +187,62 @@
"abs_hold_sharpe": 0.535, "abs_hold_sharpe": 0.535,
"turnover_per_year": 13.5, "turnover_per_year": 13.5,
"fee020_full_sharpe": 0.807, "fee020_full_sharpe": 0.807,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "INVARIANT",
"boundary_spread": 0.0
},
{
"name": "agent_05_open_drive",
"tf": "1h",
"causal": true,
"abs_grade": "PASS",
"marginal_verdict": "ADDS",
"earns_slot": false,
"corr_full": 0.133,
"corr_hold": -0.055,
"uplift_hold": 0.716,
"uplift_full": 0.232,
"cand_insample_sharpe": 0.924,
"has_insample_edge": true,
"is_hedge": false,
"robust_oos": true,
"multicut_persistent": true,
"abs_full_sharpe": 0.678,
"abs_hold_sharpe": 1.052,
"turnover_per_year": 21.4,
"fee020_full_sharpe": 0.641,
"fee_survives": true,
"boundary_verdict": "ARTIFACT-RISK",
"boundary_spread": 0.484
},
{
"name": "agent_11_weekly_seasonality",
"tf": "1h",
"causal": true,
"abs_grade": "PASS",
"marginal_verdict": "ADDS",
"earns_slot": false,
"corr_full": 0.44,
"corr_hold": 0.32,
"uplift_hold": 0.402,
"uplift_full": 0.328,
"cand_insample_sharpe": 1.728,
"has_insample_edge": true,
"is_hedge": false,
"robust_oos": true,
"multicut_persistent": true,
"abs_full_sharpe": 1.418,
"abs_hold_sharpe": 0.861,
"turnover_per_year": 86.0,
"fee020_full_sharpe": 1.262,
"fee_survives": true,
"boundary_verdict": "ARTIFACT-RISK",
"boundary_spread": 0.456
}, },
{ {
"name": "agent_04_intraday_range_size", "name": "agent_04_intraday_range_size",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "PASS", "abs_grade": "PASS",
"marginal_verdict": "REDUNDANT", "marginal_verdict": "REDUNDANT",
"earns_slot": false, "earns_slot": false,
@@ -228,11 +259,14 @@
"abs_hold_sharpe": 0.353, "abs_hold_sharpe": 0.353,
"turnover_per_year": 40.6, "turnover_per_year": 40.6,
"fee020_full_sharpe": 0.942, "fee020_full_sharpe": 0.942,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "INVARIANT",
"boundary_spread": 0.0
}, },
{ {
"name": "agent_01_session_overlay", "name": "agent_01_session_overlay",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "WEAK", "abs_grade": "WEAK",
"marginal_verdict": "REDUNDANT", "marginal_verdict": "REDUNDANT",
"earns_slot": false, "earns_slot": false,
@@ -249,11 +283,14 @@
"abs_hold_sharpe": 0.03, "abs_hold_sharpe": 0.03,
"turnover_per_year": 46.9, "turnover_per_year": 46.9,
"fee020_full_sharpe": 0.904, "fee020_full_sharpe": 0.904,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "INVARIANT",
"boundary_spread": 0.013
}, },
{ {
"name": "agent_03_funding_clock_15m", "name": "agent_03_funding_clock_15m",
"tf": "15m", "tf": "15m",
"causal": true,
"abs_grade": "FAIL", "abs_grade": "FAIL",
"marginal_verdict": "NEUTRAL", "marginal_verdict": "NEUTRAL",
"earns_slot": false, "earns_slot": false,
@@ -270,11 +307,14 @@
"abs_hold_sharpe": -0.323, "abs_hold_sharpe": -0.323,
"turnover_per_year": 56.2, "turnover_per_year": 56.2,
"fee020_full_sharpe": 0.742, "fee020_full_sharpe": 0.742,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "INVARIANT",
"boundary_spread": 0.0
}, },
{ {
"name": "agent_02_overnight_vs_intraday", "name": "agent_02_overnight_vs_intraday",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "FAIL", "abs_grade": "FAIL",
"marginal_verdict": "NEUTRAL", "marginal_verdict": "NEUTRAL",
"earns_slot": false, "earns_slot": false,
@@ -291,11 +331,14 @@
"abs_hold_sharpe": -0.41, "abs_hold_sharpe": -0.41,
"turnover_per_year": 98.2, "turnover_per_year": 98.2,
"fee020_full_sharpe": 0.497, "fee020_full_sharpe": 0.497,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "ARTIFACT-RISK",
"boundary_spread": 0.081
}, },
{ {
"name": "agent_15_intraday_meanrev_gated", "name": "agent_15_intraday_meanrev_gated",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "FAIL", "abs_grade": "FAIL",
"marginal_verdict": "NOISE", "marginal_verdict": "NOISE",
"earns_slot": false, "earns_slot": false,
@@ -312,11 +355,14 @@
"abs_hold_sharpe": -1.194, "abs_hold_sharpe": -1.194,
"turnover_per_year": 19.1, "turnover_per_year": 19.1,
"fee020_full_sharpe": -0.013, "fee020_full_sharpe": -0.013,
"fee_survives": false "fee_survives": false,
"boundary_verdict": "INVARIANT",
"boundary_spread": 0.0
}, },
{ {
"name": "agent_00_hour_of_day_bias", "name": "agent_00_hour_of_day_bias",
"tf": "1h", "tf": "1h",
"causal": true,
"abs_grade": "FAIL", "abs_grade": "FAIL",
"marginal_verdict": "NEUTRAL", "marginal_verdict": "NEUTRAL",
"earns_slot": false, "earns_slot": false,
@@ -333,6 +379,8 @@
"abs_hold_sharpe": -0.582, "abs_hold_sharpe": -0.582,
"turnover_per_year": 10.3, "turnover_per_year": 10.3,
"fee020_full_sharpe": 0.593, "fee020_full_sharpe": 0.593,
"fee_survives": true "fee_survives": true,
"boundary_verdict": "INVARIANT",
"boundary_spread": 0.0
} }
] ]
+25 -1
View File
@@ -40,6 +40,15 @@ def score(path: Path, tf: str) -> dict:
target = _target(path) target = _target(path)
except Exception as e: except Exception as e:
return {**rec, "error": f"import: {e}", "earns_slot": False} return {**rec, "error": f"import: {e}", "earns_slot": False}
# LOOK-AHEAD guard first: eval_weights' shift can't catch a non-causal FEATURE (centered
# window / shift(-k) / full-sample stat). A leak is disqualified no matter its Sharpe.
try:
caus = al.causality_ok(target, tf=tf)
rec["causal"] = bool(caus["ok"])
if not caus["ok"]:
return {**rec, "causality": caus, "marginal_verdict": "LEAK", "earns_slot": False}
except Exception as e:
return {**rec, "error": f"causality: {e}", "causal": False, "earns_slot": False}
try: try:
rep = al.study_marginal(path.stem, target, tf=tf) rep = al.study_marginal(path.stem, target, tf=tf)
except Exception as e: except Exception as e:
@@ -64,6 +73,17 @@ def score(path: Path, tf: str) -> dict:
turnover_per_year=round(turn, 1), fee020_full_sharpe=round(fee020, 3), turnover_per_year=round(turn, 1), fee020_full_sharpe=round(fee020, 3),
fee_survives=cell.get("fee_survives"), fee_survives=cell.get("fee_survives"),
) )
# calendar-artifact guard: a signal whose marginal uplift INVERTS under a UTC day-boundary
# shift is a labeling artifact (open_drive), not an intraday effect. INVARIANT (price
# signal) and ROBUST (genuine calendar effect, e.g. prevday breakout) pass.
try:
db = al.day_boundary_robust(target, tf=tf)
rec["boundary_verdict"] = db["verdict"]
rec["boundary_spread"] = db["spread"]
if db["verdict"] == "ARTIFACT-RISK":
rec["earns_slot"] = False
except Exception as e:
rec["boundary_verdict"] = f"err:{e}"
return rec return rec
@@ -85,10 +105,14 @@ def main():
for r in rows: for r in rows:
if "error" in r: if "error" in r:
print(f" {r['name'][:26]:<26}{r['tf']:>4} ERROR {r['error'][:40]}"); continue print(f" {r['name'][:26]:<26}{r['tf']:>4} ERROR {r['error'][:40]}"); continue
if r.get("causal") is False:
print(f" {r['name'][:26]:<26}{r['tf']:>4} LEAK (look-ahead, disqualified) "
f"max_tail_diff={r.get('causality', {}).get('max_tail_diff')}"); continue
bflag = " CAL-ARTIFACT" if r.get("boundary_verdict") == "ARTIFACT-RISK" else ""
print(f" {r['name'][:26]:<26}{r['tf']:>4} {str(r['marginal_verdict']):<9}" print(f" {r['name'][:26]:<26}{r['tf']:>4} {str(r['marginal_verdict']):<9}"
f"{str(r['abs_grade']):>5}{str(r.get('corr_hold')):>6}{str(r.get('uplift_hold')):>6}" f"{str(r['abs_grade']):>5}{str(r.get('corr_hold')):>6}{str(r.get('uplift_hold')):>6}"
f"{str(r.get('cand_insample_sharpe')):>6}{str(r.get('turnover_per_year')):>6}" f"{str(r.get('cand_insample_sharpe')):>6}{str(r.get('turnover_per_year')):>6}"
f"{str(r.get('fee020_full_sharpe')):>7} {'<<<' if r.get('earns_slot') else ''}") f"{str(r.get('fee020_full_sharpe')):>7} {'<<<' if r.get('earns_slot') else bflag}")
slots = [r["name"] for r in rows if r.get("earns_slot")] slots = [r["name"] for r in rows if r.get("earns_slot")]
print(f"\n EARNS SLOT: {slots or 'NONE'}") print(f"\n EARNS SLOT: {slots or 'NONE'}")
(HERE / "intra_leaderboard.json").write_text(json.dumps(rows, indent=2, default=str)) (HERE / "intra_leaderboard.json").write_text(json.dumps(rows, indent=2, default=str))
+21
View File
@@ -60,3 +60,24 @@ def test_smallcap_keeps_real_trades():
r = al.eval_weights_smallcap(df, step, capital=600.0, min_order=5.0) r = al.eval_weights_smallcap(df, step, capital=600.0, min_order=5.0)
assert r["n_executed_trades"] >= 2 assert r["n_executed_trades"] >= 2
assert abs(r["sharpe_haircut"]) < 0.2 assert abs(r["sharpe_haircut"]) < 0.2
# --- LESSON 3: look-ahead (online-consistency) guard -----------------------------------
def test_causality_passes_causal_signal():
"""A causal signal recomputed on a prefix matches the full run on its tail."""
def mom(df):
c = df["close"].values
return np.tanh(3 * (c / al.sma(c, 200) - 1))
r = al.causality_ok(mom, tf="1h")
assert r["ok"] is True
assert r["max_tail_diff"] == 0.0
def test_causality_flags_lookahead():
"""A signal that reads tomorrow's move (future peek) is disqualified."""
def leaky(df):
c = df["close"].values
f = np.zeros(len(c)); f[:-1] = np.sign(c[1:] - c[:-1]) # uses close[i+1]
return f
r = al.causality_ok(leaky, tf="1h")
assert r["ok"] is False