feat(SH01): bootstrap storia full-history + last_block_only (punto-10: regime live non robusto)

Ri-validazione sh01_trainwindow_validate.py (rolling train_window == regime live):
- tw=8760 (live 365g): BTC FULL +82% ma fee-2x -42% (6/9 anni), ETH Sharpe -0.02,
  trade-rate 21.7-26.4% vs 9.8% validato -> NON robusto. Diagnosi sweep confermata
  (LogReg over-confident su train corto, th 0.58 inerte).
- progressione MONOTONA con la memoria (2y: Sh 2.05; 3y: 3.09; expanding: ROBUSTO
  8/9) -> l'edge di SH01 e' la memoria lunga.
- sweep soglia nel regime corto: instabile/incoerente fra asset -> NON ri-tunare.

Fix (decisione utente): ripristinare in live il regime validato.
- ml_wf_entries(last_block_only=True): fitta/predice solo l'ultimo blocco del WF
  (confini deterministici start+k*retrain) -> entries IDENTICHE per costruzione
  al tail del WF completo (parity test esatto); 0.6s/tick su 73k barre vs ~140 fit.
- runner._with_history: tick degli sleeve ml su parquet locale + feed live
  (dedup timestamp, gap-guard con WARN una-tantum se parquet stantio).
- _defs.py: params {last_block_only: true} sugli sleeve SHAPE (solo path live;
  il backtest canonico resta WF completo).

Effetto atteso live: trade-rate SH01 ~25% -> ~10% delle barre, selettivita'
ripristinata. Manutenzione: parquet fresco via download_all (oggi 2026-05-28,
margine ~11 mesi col feed 365g).

Test: 87/87 (4 nuovi: parity last-block esatta, merge storia, gap fallback).
Diario docs/diary/2026-06-07-sh01-trainwindow.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-07 16:48:22 +00:00
parent be85a454e0
commit 8c5372c487
8 changed files with 268 additions and 6 deletions
+11 -2
View File
@@ -245,12 +245,18 @@ def analog_feat_entries(df, W=24, H=12, K=60, rebuild=300, min_lib=1500,
def ml_wf_entries(df, W=24, H=12, model="gb", thresh=0.58,
train_min=4000, retrain=500, n_estimators=80, max_depth=3,
tp_atr=None, sl_atr=None, trend_max=None, ema_long=200,
train_window=None) -> list[dict]:
train_window=None, last_block_only=False) -> list[dict]:
"""Walk-forward: a blocchi di `retrain` barre, allena sul passato il cui esito
e' noto, predice il blocco corrente. Scaler+modello fittati solo sul train.
Entra a close[i] se proba della classe predetta >= thresh. model in {gb, logit}.
train_window: se None -> expanding (tutto il passato); se int -> ROLLING (solo le
ultime train_window barre prima del blocco) -> test di robustezza piu' severo."""
ultime train_window barre prima del blocco) -> test di robustezza piu' severo.
last_block_only: PATH LIVE (2026-06-07) — fitta e predice SOLO l'ultimo blocco
(un fit invece di ~n/retrain): al worker servono solo i segnali sulla barra
corrente e i confini dei blocchi sono deterministici (start + k*retrain), quindi
le entries dell'ultimo blocco sono IDENTICHE per costruzione al tail del
walk-forward completo (parity test in tests/portfolio/test_sh01_last_block.py).
Rende sostenibile il train EXPANDING full-history nel runner (~73k barre/tick)."""
c = df["close"].values
n = len(c)
a = atr(df, 14)
@@ -269,6 +275,9 @@ def ml_wf_entries(df, W=24, H=12, model="gb", thresh=0.58,
blk = start
while blk < n - 1:
blk_end = min(blk + retrain, n - 1)
if last_block_only and blk_end < n - 1:
blk = blk_end # salta i blocchi storici (stessi confini del WF completo)
continue
# TRAIN: finestre la cui forma E il cui esito (e+H) sono < blk
# cioe' e <= blk-1-H (esito realizzato prima del primo test del blocco)
lo_end = (blk - 1 - H - train_window) if train_window is not None else (W - 1)