harness(realism): codifica le 2 lezioni dell'onda intraday (day-boundary + small-cap fills)
Due gate nuovi in altlib.py (test tests/test_harness_realism.py, suite intera verde): 1. day_boundary_robust(target_fn, tf): shifta il confine del giorno UTC e ri-misura l'uplift marginale. INVARIANT (segnale di prezzo, spread 0) / ROBUST (effetto calendario vero, resta positivo) / ARTIFACT-RISK (uplift si inverte = etichettatura). Riproduce da solo il verdetto degli scettici: open_drive +0.23@00:00 -> -0.33@+8h = ARTIFACT-RISK; prevday_breakout = ROBUST. Decoupling chiave: il segnale vede il clock shiftato, il backtest usa il calendario reale. 2. eval_weights_smallcap(df, target, capital=600, min_order=5): salta i ribilanciamenti di nozionale < min_order (la finzione del micro-trading sub-dollaro che eval_weights costa come fee proporzionale su un overlay vol-target), riporta lo Sharpe haircut reale vs modellato. Vale per ogni sleeve a $600, TP01 incluso. CLAUDE.md aggiornato (sezione HARNESS REALISM). La pipeline di falsificazione ora becca da sola artefatti-calendario e finzioni-fee, oltre a hedge/regime-luck/leakage gia' codificati. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -555,6 +555,91 @@ def fmt_marginal(rep: dict) -> str:
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# HARNESS REALISM — two gates codified from the 2026-06-21 intraday wave.
|
||||
#
|
||||
# LESSON 1 (day-boundary): open_drive ("first 8h UTC predicts rest-of-day") scored a
|
||||
# +0.23 uplift but INVERTED to -0.10 when the UTC day start was shifted 4h — a calendar-
|
||||
# LABELING artifact, not an intraday effect. A real hour/session/day edge degrades
|
||||
# gracefully under a boundary shift; an artifact flips sign.
|
||||
#
|
||||
# LESSON 2 (small-cap fills): eval_weights charges fee on EVERY |Δposition|, incl. the
|
||||
# thousands of sub-dollar rebalances a vol-target overlay produces. At ~$600 real capital a
|
||||
# $0.03 trade can't execute — the modeled proportional fee is a continuous-rebalancing
|
||||
# fiction. eval_weights_smallcap skips changes below min_order and reports the Sharpe haircut.
|
||||
# ===========================================================================
|
||||
def _shift_calendar(df: pd.DataFrame, offset_hours: int) -> pd.DataFrame:
|
||||
"""Relabel the clock the SIGNAL sees by +offset_hours (datetime & timestamp), leaving
|
||||
prices/returns untouched -> the signal's .dt.hour / day-grouping shifts, the backtest
|
||||
does not. (get() is cached; copy so we never mutate the shared frame.)"""
|
||||
d = df.copy()
|
||||
dt = pd.to_datetime(d["datetime"], utc=True) + pd.Timedelta(hours=offset_hours)
|
||||
d["datetime"] = dt
|
||||
if "timestamp" in d:
|
||||
d["timestamp"] = d["timestamp"].astype("int64") + int(offset_hours * 3600 * 1000)
|
||||
return d
|
||||
|
||||
|
||||
def day_boundary_robust(target_fn, tf: str = "1h",
|
||||
offsets=(0, 3, 6, 9, 12, 15, 18, 21), w: float = 0.25) -> dict:
|
||||
"""Is a candidate's marginal uplift ROBUST to shifting the UTC day boundary? For each
|
||||
offset we relabel the calendar the signal sees, recompute its 50/50 BTC+ETH daily series
|
||||
and the blend uplift vs TP01. A datetime-independent signal is INVARIANT (spread ~0); a
|
||||
calendar signal that stays positive is ROBUST; one whose uplift flips sign is ARTIFACT-RISK
|
||||
(open_drive). Run this on ANY hour/session/day-of-week signal before believing it."""
|
||||
B = tp01_baseline_daily()
|
||||
per = {}
|
||||
for off in offsets:
|
||||
series = {}
|
||||
for a in CERTIFIED:
|
||||
df0 = get(a, tf) # ORIGINAL bars/dates
|
||||
tgt = _call_target(target_fn, _shift_calendar(df0, off), a) # signal sees shifted clock
|
||||
ev = eval_weights(df0, tgt) # backtest on the real calendar
|
||||
series[a] = pd.Series(ev["net"], index=ev["idx"])
|
||||
J = pd.concat(series, axis=1, join="inner").fillna(0.0)
|
||||
cand = _to_daily(0.5 * J[CERTIFIED[0]] + 0.5 * J[CERTIFIED[1]])
|
||||
JJ = pd.concat({"B": B, "C": cand}, axis=1, join="inner").dropna()
|
||||
per[int(off)] = round(_sh((1 - w) * JJ["B"] + w * JJ["C"]) - _sh(JJ["B"]), 3) if len(JJ) > 30 else None
|
||||
ups = [v for v in per.values() if v is not None]
|
||||
if not ups:
|
||||
return dict(per_offset=per, verdict="N/A", reason="no evaluable offsets")
|
||||
spread = round(max(ups) - min(ups), 3)
|
||||
calendar_sensitive = spread > 0.02
|
||||
robust = min(ups) > 0
|
||||
verdict = ("INVARIANT" if not calendar_sensitive else ("ROBUST" if robust else "ARTIFACT-RISK"))
|
||||
return dict(per_offset=per, base=per[offsets[0]], min=min(ups), max=max(ups),
|
||||
spread=spread, calendar_sensitive=calendar_sensitive,
|
||||
robust_to_boundary=robust, verdict=verdict)
|
||||
|
||||
|
||||
def eval_weights_smallcap(df: pd.DataFrame, target, capital: float = 600.0,
|
||||
min_order: float = 5.0, fee_side: float = FEE_SIDE) -> dict:
|
||||
"""Honest net at SMALL capital. A desired position change whose notional |Δw|*capital is
|
||||
below min_order is NOT executed (held -> tracking error, no trade) — removing the
|
||||
continuous-rebalancing fiction. Returns realistic vs modeled metrics, the Sharpe haircut,
|
||||
and the number of trades that actually execute. (Applies to ANY sleeve at this capital,
|
||||
TP01 included.)"""
|
||||
c = df["close"].values.astype(float)
|
||||
tgt = np.clip(np.nan_to_num(np.asarray(target, float)), -10, 10)
|
||||
held = np.empty(len(tgt)); cur = 0.0; n_tr = 0
|
||||
for i in range(len(tgt)):
|
||||
if abs(tgt[i] - cur) * capital >= min_order:
|
||||
cur = tgt[i]; n_tr += 1
|
||||
held[i] = cur
|
||||
r = simple_returns(c)
|
||||
pos = np.zeros(len(held)); pos[1:] = held[:-1]
|
||||
turn = np.abs(np.diff(pos, prepend=0.0))
|
||||
net = pos * r - fee_side * turn; net[0] = 0.0
|
||||
idx = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True))
|
||||
real = _metrics_from_net(net, idx)
|
||||
modeled = eval_weights(df, tgt, fee_side=fee_side)["full"]
|
||||
bpy_d = bars_per_day(df) * 365.25
|
||||
return dict(realistic=real, modeled=modeled,
|
||||
sharpe_haircut=round(modeled["sharpe"] - real["sharpe"], 3),
|
||||
n_executed_trades=int(n_tr),
|
||||
executed_turnover_per_year=round(float(turn.sum() / (len(turn) / bpy_d)), 1))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# DRIVERS — run a hypothesis across both assets, several TFs, with a fee sweep.
|
||||
# ===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user