Files
Multi_Swarm_Coevolutive/scripts/compare_winners.py
T
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

140 lines
5.4 KiB
Python
Raw 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.
"""Confronto per-trade dei 4 winner cross-run (BTC/ETH × 1h/5m).
Per ogni winner: ri-esegue il backtest su 4 fold WFA expanding-window e raccoglie
trade buoni/non buoni, win-rate, avg PnL, return, max DD, Sharpe.
"""
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
# (run_name, genome_id, symbol, timeframe, label)
WINNERS = [
("phase1-btc-100-001", "238e481262c1594c", "BTC-PERPETUAL", "1h", "BTC 1h sharpshooter (Gen 7)"),
("phase1-btc-100-001", "23a24989e2ed0f84", "BTC-PERPETUAL", "1h", "BTC 1h robust (Gen 0 elite)"),
("phase1-eth-100-001", "4b45a72c13acf1d5", "ETH-PERPETUAL", "1h", "ETH 1h best-by-sharpe (killed)"),
("phase1-btc-100-5m-001", "f8ca6642adf7e0cd", "BTC-PERPETUAL", "5m", "BTC 5m robust winner"),
("phase1-eth-100-5m-001", "c04dff7086bb9588", "ETH-PERPETUAL", "5m", "ETH 5m OOS winner"),
]
def analyze_genome(run_id: str, genome_id: str, symbol: str, timeframe: str, label: str,
settings, cerbero, loader) -> None:
repo = Repository(settings.ga_db_path)
repo.init_schema()
evs = [e for e in repo.list_evaluations(run_id) if e["genome_id"] == genome_id]
if not evs:
print(f" no eval for {genome_id} in {run_id}")
return
ev = evs[0]
strat, err = _try_parse(ev.get("raw_text") or "")
if strat is None:
print(f" parse error: {err}")
return
req = OHLCVRequest(
symbol=symbol, timeframe=timeframe,
start=datetime.fromisoformat("2018-09-01T00:00:00+00:00"),
end=datetime.fromisoformat("2026-01-01T00:00:00+00:00"),
)
ohlcv = loader.load(req)
splits = expanding_walk_forward(ohlcv.index, train_ratio=0.5, n_folds=4)
engine = BacktestEngine(fees_bp=5.0)
print(f"\n>>> {label}")
print(f" {genome_id} | fit_IS={ev['fitness']:.4f} sharpe_IS={ev['sharpe']:.3f} trades_IS={ev['n_trades']}")
print(f" {'fold':<5} {'period':<26} {'trades':>7} {'wins':>5} {'losses':>7} {'win%':>6} {'avg_w':>10} {'avg_l':>10} {'ret':>8} {'maxDD':>7} {'sharpe':>7}")
sum_ret = 0.0
sum_trades = 0
sum_wins = 0
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 = 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]
nw, nl = len(wins), len(losses)
wr = (nw / n * 100) if n else 0.0
aw = (sum(wins) / nw) if nw else 0.0
al = (sum(losses) / nl) if nl else 0.0
if n > 0:
notional = float(test_df["close"].iloc[0])
eq = (bt.equity_curve / notional) + 1.0
ret = total_return(eq)
dd = max_drawdown(eq)
sr = sharpe_ratio(bt.returns, periods_per_year=8760)
else:
ret = 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:>7} {nw:>5} {nl:>7} {wr:>5.1f}% {aw:>10.1f} {al:>10.1f} {ret:>7.2%} {dd:>6.2%} {sr:>7.3f}")
sum_ret += ret
sum_trades += n
sum_wins += nw
overall_wr = (sum_wins / sum_trades * 100) if sum_trades else 0.0
print(f" {'='*5} TOTALS: {sum_trades:>7} {sum_wins:>5} {sum_trades-sum_wins:>7} {overall_wr:>5.1f}% cum_ret={sum_ret:+.2%}")
def main() -> None:
settings = load_settings()
repo = Repository(settings.ga_db_path)
repo.init_schema()
name_to_id: dict[str, str] = {}
for w in WINNERS:
run_name = w[0]
if run_name in name_to_id:
continue
runs = repo.list_runs()
for r in runs:
if r["name"] == run_name:
name_to_id[run_name] = r["id"]
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)
print(f"{'='*120}")
print(f"PER-TRADE COMPARISON — {len(WINNERS)} winner candidates × 4 folds WFA")
print(f"{'='*120}")
for run_name, genome_id, symbol, timeframe, label in WINNERS:
run_id = name_to_id.get(run_name)
if not run_id:
print(f"!!! run not found: {run_name}")
continue
analyze_genome(run_id, genome_id, symbol, timeframe, label, settings, cerbero, loader)
if __name__ == "__main__":
main()