23b7273e71
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>
113 lines
4.8 KiB
Python
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()
|