48435f6858
StrategyWorker ora supporta exit guidati dalla strategia via Signal.metadata (take-profit alla media / stop-loss ad ATR / time-limit), con fallback al vecchio hold_bars/stop -2% per strategie senza metadata. Usa fee_rt della strategia (MR01 = 0.10% RT reale Deribit, non piu' 0.20% hardcoded). Persistenza di tp/sl/max_bars in status.json per resume. Re-validato col worker reale (replay finestre mobili 1h, fee 0.10%): BTC 1h MR01: +196% OOS, ETH 1h: +251% OOS (nov 2023->mag 2026) — coerente col backtest. README + CLAUDE.md riscritti: squeeze = artefatto di look-ahead -> waste, MR01 mean-reversion unica attiva, metodologia anti-look-ahead e fee reali 0.10% RT. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
"""Re-validazione: il StrategyWorker REALE tradi MR01 con edge netto?
|
|
|
|
Guida il worker vero (generate_signals + nuova logica exit TP/SL/max_bars) su
|
|
finestre mobili di dati 1h storici, simulando il polling live. Conferma che
|
|
sulla finestra OOS l'edge netto (dopo fee 0.10% RT) sopravvive alla meccanica
|
|
del worker (exit su prezzo corrente, piu' conservativa del backtest high/low).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from src.data.downloader import load_data
|
|
from src.live.strategy_loader import load_strategy
|
|
from src.live.strategy_worker import StrategyWorker
|
|
|
|
OOS_FRAC = 0.30
|
|
WIN = 250 # barre per finestra di poll (warmup bb_window=50 + ATR)
|
|
|
|
|
|
def replay(asset: str, params: dict):
|
|
df = load_data(asset, "1h").reset_index(drop=True)
|
|
n = len(df)
|
|
split = int(n * (1 - OOS_FRAC))
|
|
strat = load_strategy("MR01_bollinger_fade")
|
|
w = StrategyWorker(strat, asset, "1h", capital=1000.0, position_size=0.15,
|
|
leverage=3.0, hold_bars=3, params=params,
|
|
data_dir=Path(f"/tmp/replay_{asset}"))
|
|
w._notify = lambda *a, **k: None
|
|
# stato pulito
|
|
for attr, val in dict(capital=1000.0, in_position=False, direction=0, entry_price=0,
|
|
bars_held=0, total_trades=0, total_wins=0, last_bar_ts=0,
|
|
tp=0.0, sl=0.0, max_bars=0).items():
|
|
setattr(w, attr, val)
|
|
|
|
start = max(split, WIN)
|
|
with contextlib.redirect_stdout(open(os.devnull, "w")):
|
|
for j in range(start, n):
|
|
w.tick(df.iloc[j - WIN + 1 : j + 1])
|
|
|
|
ret = (w.capital / 1000 - 1) * 100
|
|
acc = w.total_wins / w.total_trades * 100 if w.total_trades else 0.0
|
|
import pandas as pd
|
|
period = (f"{pd.to_datetime(df['timestamp'].iloc[start], unit='ms', utc=True).date()}"
|
|
f"->{pd.to_datetime(df['timestamp'].iloc[-1], unit='ms', utc=True).date()}")
|
|
return w.total_trades, acc, ret, w.capital, period
|
|
|
|
|
|
def main():
|
|
print("=" * 90)
|
|
print(" RE-VALIDAZIONE WORKER REALE su MR01 (OOS, fee 0.10% RT, leva 3x) — finestra poll 250b")
|
|
print("=" * 90)
|
|
params = dict(bb_window=50, k=2.5, sl_atr=2.0, max_bars=24)
|
|
print(f" {'Asset':>6s}{'Periodo OOS':>26s}{'Trade':>7s}{'Win%':>7s}{'Ret%':>9s}{'Cap€':>9s}")
|
|
print(" " + "-" * 80)
|
|
for asset in ["BTC", "ETH"]:
|
|
t, acc, ret, cap, period = replay(asset, params)
|
|
print(f" {asset:>6s}{period:>26s}{t:>7d}{acc:>7.1f}{ret:>+9.1f}{cap:>9.0f}")
|
|
print(" " + "-" * 80)
|
|
print(" Atteso: Ret% positivo (l'edge mean-reversion sopravvive alla meccanica del worker).")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|