fix(TP01): bug look-ahead ffill mixed-TF -> deploy a >=12h (1d), strategia DIFENSIVA
Segnalato: ffill MIXED-TIMEFRAME su barre open-labeled (resample label="left") gonfiava il 4h (~1.60 -> reale ~1.1). Ri-verifica per-SINGOLO-TF leak-free (guard prefix-recompute, leak=0 su 4h/6h/12h/1d): FULL Sh piatto ~1.3, hold-out 2025-26 MIGLIORE a 1d (Sh 0.31 / +3.5% vs buy&hold -39%). Conclusione adottata: NON scendere sotto le 12h (sotto, costi+overfit dominano senza vantaggio). - trend_portfolio.py: canonica PORT LF1d; resample_tf/resample_1d (resample_4h deprecato deploy); docstring con nota look-ahead + natura DIFENSIVA (taglia DD ~6x, non alpha). - paper_trend.py: deploy a 1d (resample_1d, build_bars). 5 test passano. - CLAUDE.md: TP01 ridescritta (>=12h/1d, gotcha ffill mixed-TF, difensiva). - tp01_lowfreq.py + diario 2026-06-19-tp01-lookahead-fix-lf.md. Gotcha: mai ffill/combine mixed-TF su timestamp open-labeled (close propagata indietro = leak). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+13
-12
@@ -1,14 +1,14 @@
|
||||
"""PAPER TRADER — TP01 Trend Portfolio (PORT LF4h), forward-only, simulato.
|
||||
"""PAPER TRADER — TP01 Trend Portfolio (PORT LF1d), forward-only, simulato.
|
||||
|
||||
Esegue la strategia VINCENTE (src/strategies/trend_portfolio.py, config CANONICAL) in
|
||||
paper trading FORWARD-ONLY su capitale virtuale (default 2000 USDT), portafoglio 50/50
|
||||
BTC+ETH a 4h. Stato persistente -> resume al riavvio.
|
||||
BTC+ETH a 1d. Stato persistente -> resume al riavvio.
|
||||
|
||||
DESIGN (onesto, niente esecuzione reale: l'esecuzione e' DISABILITATA nel progetto):
|
||||
- Legge i parquet certificati locali (data/raw, BTC/ETH 1h) e resampla a 4h.
|
||||
- Alla prima esecuzione parte dall'ultima barra 4h CHIUSA disponibile (forward-only:
|
||||
- Legge i parquet certificati locali (data/raw, BTC/ETH 1h) e resampla a 1d.
|
||||
- Alla prima esecuzione parte dall'ultima barra 1d CHIUSA disponibile (forward-only:
|
||||
NON include lo storico nel PnL di paper, traccia solo da ora in avanti).
|
||||
- Ad ogni run processa le NUOVE barre 4h chiuse dall'ultima volta: applica il rendimento
|
||||
- Ad ogni run processa le NUOVE barre 1d chiuse dall'ultima volta: applica il rendimento
|
||||
della posizione tenuta, addebita le fee sul turnover, registra i trade sui cambi di
|
||||
posizione, poi ricalcola la posizione-bersaglio (decisa con dati <= ultima barra chiusa).
|
||||
- Per avere barre fresche, aggiornare prima i dati:
|
||||
@@ -33,7 +33,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from src.backtest.harness import load
|
||||
from src.strategies.trend_portfolio import TrendPortfolio, CANONICAL, resample_4h, simple_returns
|
||||
from src.strategies.trend_portfolio import TrendPortfolio, CANONICAL, resample_1d, simple_returns
|
||||
|
||||
STATE_DIR = PROJECT_ROOT / "data" / "paper_trend"
|
||||
STATE_FILE = STATE_DIR / "state.json"
|
||||
@@ -43,8 +43,9 @@ WEIGHT = 0.5
|
||||
INITIAL_CAPITAL = 2000.0
|
||||
|
||||
|
||||
def build_4h() -> dict[str, pd.DataFrame]:
|
||||
return {a: resample_4h(load(a, "1h")) for a in ASSETS}
|
||||
def build_bars() -> dict[str, pd.DataFrame]:
|
||||
# Deploy a 1d (>=12h): sotto le 12h costi+overfit dominano (vedi trend_portfolio docstring + bug ffill mixed-TF).
|
||||
return {a: resample_1d(load(a, "1h")) for a in ASSETS}
|
||||
|
||||
|
||||
def load_state() -> dict | None:
|
||||
@@ -80,7 +81,7 @@ def init_state(dfs) -> dict:
|
||||
|
||||
|
||||
def advance(st: dict, dfs: dict) -> dict:
|
||||
"""Processa tutte le barre 4h chiuse DOPO st['last_ts']."""
|
||||
"""Processa tutte le barre 1d chiuse DOPO st['last_ts']."""
|
||||
tp = TrendPortfolio(**CANONICAL)
|
||||
# precompute per-asset: timestamps, returns, target series (causale)
|
||||
data = {}
|
||||
@@ -144,10 +145,10 @@ def print_status(st: dict, dfs: dict):
|
||||
ret = cap / st["initial_capital"] - 1
|
||||
daily = (cap - st["initial_capital"]) / days if days > 0 else 0.0
|
||||
print("=" * 72)
|
||||
print(" PAPER TRADER — TP01 Trend Portfolio (PORT LF4h, 50/50 BTC+ETH, 4h)")
|
||||
print(" PAPER TRADER — TP01 Trend Portfolio (PORT LF1d, 50/50 BTC+ETH, 1d)")
|
||||
print("=" * 72)
|
||||
print(f" start {start:%Y-%m-%d %H:%M} UTC")
|
||||
print(f" last bar {last:%Y-%m-%d %H:%M} UTC ({days:.1f} giorni, {st['n_bars']} barre 4h)")
|
||||
print(f" last bar {last:%Y-%m-%d %H:%M} UTC ({days:.1f} giorni, {st['n_bars']} barre 1d)")
|
||||
print(f" capitale {cap:,.2f} USDT (start {st['initial_capital']:,.0f})")
|
||||
print(f" ritorno {ret*100:+.2f}% | €/giorno {daily:+.2f} | maxDD {st['max_dd']*100:.1f}%")
|
||||
print(f" posizioni now { 'flat' if all(p==0 for p in st['positions'].values()) else '' }")
|
||||
@@ -168,7 +169,7 @@ def print_status(st: dict, dfs: dict):
|
||||
|
||||
def main():
|
||||
argv = sys.argv[1:]
|
||||
dfs = build_4h()
|
||||
dfs = build_bars()
|
||||
if "--reset" in argv:
|
||||
if STATE_FILE.exists():
|
||||
STATE_FILE.unlink()
|
||||
|
||||
Reference in New Issue
Block a user