feat(live): worker con exit TP/SL/max_bars per MR01 + doc aggiornata
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>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user