Files
Adriano Dal Pastro 9c871d1d86 feat(validation): WFA tooling + multi-fold results phase1-100 runs
Aggiunge 2 script di analisi per validare i top-K genomi cross-fold:

- scripts/analyze_btc_winners.py: per-trade dump (wins/losses/winrate/
  avg_win/avg_loss/return/maxDD/Sharpe) per ogni top-K × 4 fold
  expanding-window WFA. Usato per identificare i winner robusti vs
  i lucky-shot overfit.

- scripts/compare_winners.py: cross-run comparison di 5 winner
  candidate (BTC 1h + ETH 1h + BTC 5m + ETH 5m) sui medesimi 4 fold,
  con totali cumulativi.

Risultati WFA freezati:
- validation-btc-100-001.json: BTC 1h baseline (undertrading=10)
- validation-btc-100-001-thr3.json: BTC 1h con threshold=3 (rilassato
  per strategie ultra-selettive)
- validation-btc-100-5m-thr3.json: BTC 5m con threshold=3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:48:55 +00:00

126 lines
4.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Analisi per-trade dei top-K candidate del run BTC.
Per ciascun genome top-K, ri-esegue il backtest su ogni fold WFA e raccoglie:
- n_trades, n_wins, n_losses, win_rate
- max_drawdown
- return, sharpe
- list trade pnl summary
Output stampato a stdout, non scrive su disco.
"""
from __future__ import annotations
import argparse
from datetime import datetime
import pandas as pd # type: ignore[import-untyped]
from multi_swarm_core.agents.hypothesis import _try_parse
from multi_swarm_core.backtest.engine import BacktestEngine
from multi_swarm_core.cerbero.client import CerberoClient
from multi_swarm_core.config import load_settings
from multi_swarm_core.data.cerbero_ohlcv import CerberoOHLCVLoader, OHLCVRequest
from multi_swarm_core.data.splits import expanding_walk_forward
from multi_swarm_core.metrics.basic import max_drawdown, sharpe_ratio, total_return
from multi_swarm_core.persistence.repository import Repository
from multi_swarm_core.protocol.compiler import compile_strategy
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--run-id", required=True)
ap.add_argument("--top-k", type=int, default=10)
ap.add_argument("--n-folds", type=int, default=4)
ap.add_argument("--train-ratio", type=float, default=0.5)
ap.add_argument("--symbol", default="BTC-PERPETUAL")
ap.add_argument("--timeframe", default="1h")
ap.add_argument("--start", default="2018-09-01T00:00:00+00:00")
ap.add_argument("--end", default="2026-01-01T00:00:00+00:00")
ap.add_argument("--fees-bp", type=float, default=5.0)
args = ap.parse_args()
settings = load_settings()
repo = Repository(settings.ga_db_path)
repo.init_schema()
all_evals = repo.list_evaluations(args.run_id)
parseable = [
e for e in all_evals
if e.get("raw_text") and not e.get("parse_error") and e["fitness"] > 0
]
parseable.sort(key=lambda e: e["fitness"], reverse=True)
seen: set[str] = set()
top: list[dict] = []
for e in parseable:
if e["genome_id"] in seen:
continue
seen.add(e["genome_id"])
top.append(e)
if len(top) >= args.top_k:
break
token = (
settings.cerbero_mainnet_token.get_secret_value()
if settings.cerbero_mainnet_token
else settings.cerbero_testnet_token.get_secret_value()
)
cerbero = CerberoClient(
base_url=settings.cerbero_base_url,
token=token,
bot_tag=settings.cerbero_bot_tag,
)
loader = CerberoOHLCVLoader(client=cerbero, cache_dir=settings.series_dir)
ohlcv = loader.load(OHLCVRequest(
symbol=args.symbol,
timeframe=args.timeframe,
start=datetime.fromisoformat(args.start),
end=datetime.fromisoformat(args.end),
))
splits = expanding_walk_forward(ohlcv.index, train_ratio=args.train_ratio, n_folds=args.n_folds)
engine = BacktestEngine(fees_bp=args.fees_bp)
print(f"\n{'=' * 110}")
print(f"PER-TRADE ANALYSIS — top-{len(top)} genomes × {len(splits)} folds")
print(f"{'=' * 110}")
for ev in top:
strat, err = _try_parse(ev["raw_text"] or "")
if strat is None:
print(f"\n>>> {ev['genome_id'][:16]} — parse error: {err}")
continue
print(f"\n>>> {ev['genome_id']} (fit_IS={ev['fitness']:.4f}, sharpe_IS={ev['sharpe']:.3f})")
print(f"{'fold':<5} {'period':<26} {'trades':>7} {'wins':>5} {'losses':>7} {'win%':>6} {'avg_w':>9} {'avg_l':>9} {'ret':>7} {'maxDD':>7} {'sharpe':>7}")
for s in splits:
test_df = ohlcv.loc[s.test_idx]
try:
signal_fn = compile_strategy(strat)
signals = signal_fn(test_df)
bt = engine.run(test_df, signals)
except Exception as e:
print(f" fold {s.fold}: error {e}")
continue
trades = bt.trades
n_trades = len(trades)
wins = [t.net_pnl for t in trades if t.net_pnl > 0]
losses = [t.net_pnl for t in trades if t.net_pnl <= 0]
n_wins = len(wins)
n_losses = len(losses)
win_rate = (n_wins / n_trades * 100) if n_trades else 0.0
avg_w = (sum(wins) / n_wins) if n_wins else 0.0
avg_l = (sum(losses) / n_losses) if n_losses else 0.0
# Normalize equity for DD/return
if n_trades > 0:
notional = float(test_df["close"].iloc[0])
equity_pos = (bt.equity_curve / notional) + 1.0
ret_pct = total_return(equity_pos)
dd = max_drawdown(equity_pos)
sr = sharpe_ratio(bt.returns, periods_per_year=8760)
else:
ret_pct = dd = sr = 0.0
period = f"{str(s.test_idx[0])[:10]}..{str(s.test_idx[-1])[:10]}"
print(f"{s.fold:<5} {period:<26} {n_trades:>7} {n_wins:>5} {n_losses:>7} {win_rate:>5.1f}% {avg_w:>9.1f} {avg_l:>9.1f} {ret_pct:>6.2%} {dd:>6.2%} {sr:>7.3f}")
if __name__ == "__main__":
main()