Files
Multi_Swarm_Coevolutive/scripts/yearly_strategies.py
Adriano Dal Pastro 23b7273e71 feat(paper): ETH a tick 5m + tooling per-year/per-tick analysis
scripts/run_paper_trading.py: AssetConfig ETH ora usa timeframe="5m" invece
del default 1h. Il winner c04dff7086 e' stato trovato dal GA su dati 5m
e a 1h la strategia perde:
- ETH @ 5m (native): +359.50% cum 7y, 77% winrate, max DD/yr 19%
- ETH @ 1h (precedente): -33.03% cum 7y, 67% winrate, max DD 74%
BTC resta a 1h (winner 238e4812 native a 1h, +104% 7y, Sharpe 2+ in 3 anni).

Nuovi script di analisi:
- scripts/yearly_strategies.py: breakdown per anno (2019-2025) di 4
  strategie su tick di discovery (trade/winrate/return/maxDD/Sharpe).
- scripts/multi_tick_strategies.py: confronto cross-tick (5m/15m/1h)
  per i 2 winner correnti. Documenta la divergenza tick-paper di ETH.

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

113 lines
4.8 KiB
Python

"""Per-year breakdown delle 4 strategie: 2 NEW (BTC 238e4812 + ETH c04dff7086)
+ 2 OLD freezate (btc_9cf506b8 hardened-001 + eth_facd6af85d5d).
Backtest anno-per-anno (2019-2025) sul tick di discovery di ciascuna strategia.
Output: trade, wins/losses, win%, return%, max DD%, Sharpe per ogni anno.
"""
from __future__ import annotations
from datetime import datetime
from pathlib import Path
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.metrics.basic import max_drawdown, sharpe_ratio, total_return
from multi_swarm_core.protocol.compiler import compile_strategy
from multi_swarm_core.protocol.parser import parse_strategy
STRATEGIES = [
# (label, path, symbol, timeframe)
("BTC NEW (238e4812, paper attuale)", "btc_238e4812.json", "BTC-PERPETUAL", "1h"),
("BTC OLD (9cf506b8, hardened-001 prev paper)", "archive/btc_9cf506b8.json", "BTC-PERPETUAL", "1h"),
("ETH NEW (c04dff7086, paper attuale)", "eth_c04dff7086.json", "ETH-PERPETUAL", "5m"),
("ETH OLD (facd6af85d5d, prev paper)", "archive/eth_facd6af85d5d.json", "ETH-PERPETUAL", "1h"),
]
YEARS = [
("2019", "2019-01-01T00:00:00+00:00", "2020-01-01T00:00:00+00:00"),
("2020", "2020-01-01T00:00:00+00:00", "2021-01-01T00:00:00+00:00"),
("2021", "2021-01-01T00:00:00+00:00", "2022-01-01T00:00:00+00:00"),
("2022", "2022-01-01T00:00:00+00:00", "2023-01-01T00:00:00+00:00"),
("2023", "2023-01-01T00:00:00+00:00", "2024-01-01T00:00:00+00:00"),
("2024", "2024-01-01T00:00:00+00:00", "2025-01-01T00:00:00+00:00"),
("2025", "2025-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00"),
]
def main() -> None:
settings = load_settings()
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)
engine = BacktestEngine(fees_bp=5.0)
strategies_dir = Path("/app/strategies")
for label, fname, symbol, timeframe in STRATEGIES:
path = strategies_dir / fname
strat = parse_strategy(path.read_text())
# Carica intero range una volta sola
ohlcv = loader.load(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"),
))
print(f"\n{'=' * 110}")
print(f">>> {label}")
print(f" symbol={symbol} timeframe={timeframe} | {len(ohlcv)} bars total")
print(f" {'year':<6} {'bars':>6} {'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 year_label, start, end in YEARS:
mask = (ohlcv.index >= datetime.fromisoformat(start)) & (ohlcv.index < datetime.fromisoformat(end))
slice_df = ohlcv[mask]
if len(slice_df) == 0:
continue
try:
signal_fn = compile_strategy(strat)
signals = signal_fn(slice_df)
bt = engine.run(slice_df, signals)
except Exception as e:
print(f" {year_label:<6} 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(slice_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
print(f" {year_label:<6} {len(slice_df):>6} {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 7y: {sum_trades:>7} {sum_wins:>5} {sum_trades-sum_wins:>7} {overall_wr:>5.1f}% cum_ret={sum_ret:+.2%}")
if __name__ == "__main__":
main()