bdcef09057
- data/paper_trades/ rimosso dal tracking (dati runtime, gitignored) - scripts/analysis/yearly_market_report.py: accuracy/trades/PnL per anno×mercato Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
170 lines
5.9 KiB
Python
170 lines
5.9 KiB
Python
"""Report accuracy per ANNO × MERCATO delle strategie migliori.
|
||
|
||
Esegue ogni strategia vincente su BTC e ETH e produce tabella
|
||
accuracy/trades per ogni anno. Permette di vedere robustezza temporale
|
||
e differenze tra mercati.
|
||
"""
|
||
from __future__ import annotations
|
||
import sys
|
||
sys.path.insert(0, ".")
|
||
|
||
import importlib.util
|
||
from pathlib import Path
|
||
|
||
STRATEGIES_DIR = Path("scripts/strategies")
|
||
|
||
|
||
def load_class(module_file, class_name):
|
||
path = STRATEGIES_DIR / f"{module_file}.py"
|
||
spec = importlib.util.spec_from_file_location(module_file, path)
|
||
mod = importlib.util.module_from_spec(spec)
|
||
spec.loader.exec_module(mod)
|
||
return getattr(mod, class_name)
|
||
|
||
|
||
# (label, module, class, params, hold)
|
||
STRATEGIES = [
|
||
("SQ02 antifake+vol", "SQ02_squeeze_antifake_vol", "SqueezeAntifakeVol", {}, 3),
|
||
("MT01 ema20+vol", "MT01_squeeze_mtf_momentum", "SqueezeMTFMomentum",
|
||
{"ema_period": 20, "min_slope": 0.001, "vol_filter": True}, 3),
|
||
("PD01 vtb3 vm1.3", "PD01_price_volume_divergence", "PriceVolumeDivergence",
|
||
{}, 3),
|
||
("CM01 cb6+vol", "CM01_cross_market_momentum", "CrossMarketMomentum",
|
||
{"cross_bars": 6, "mom_min": 0.001, "use_vol": True}, 3),
|
||
("AD01 lt.65 ht.95", "AD01_adaptive_squeeze", "AdaptiveSqueeze",
|
||
{"low_thr": 0.65, "high_thr": 0.95, "use_vol": True}, 3),
|
||
]
|
||
|
||
ASSETS = ["BTC", "ETH"]
|
||
TF = "15m"
|
||
ALL_YEARS = list(range(2018, 2027))
|
||
|
||
|
||
def run():
|
||
results = {} # (label, asset) -> BacktestResult
|
||
|
||
for label, module, cls_name, params, hold in STRATEGIES:
|
||
try:
|
||
cls = load_class(module, cls_name)
|
||
except Exception as e:
|
||
print(f"SKIP {label}: {e}")
|
||
continue
|
||
strat = cls()
|
||
for asset in ASSETS:
|
||
try:
|
||
r = strat.backtest(asset, TF, hold=hold, **params)
|
||
if r:
|
||
results[(label, asset)] = r
|
||
except Exception as e:
|
||
print(f" errore {label} {asset}: {e}")
|
||
|
||
# ── Tabella ACCURACY per anno × mercato ──────────────────────────
|
||
print(f"\n{'=' * 140}")
|
||
print(f" ACCURACY PER ANNO × MERCATO — {TF} (fee 0.2% RT, leva 3x, pos 15%)")
|
||
print(f"{'=' * 140}")
|
||
|
||
header = f" {'Strategia':<22s} {'Mkt':>3s}"
|
||
for y in ALL_YEARS:
|
||
header += f" {y:>7d}"
|
||
header += f" │ {'TOT':>6s} {'DD%':>5s} {'Worst':>10s}"
|
||
print(header)
|
||
print(f" {'─' * 136}")
|
||
|
||
for label, module, cls_name, params, hold in STRATEGIES:
|
||
for asset in ASSETS:
|
||
r = results.get((label, asset))
|
||
if not r:
|
||
continue
|
||
yd = {ys.year: ys for ys in r.yearly}
|
||
line = f" {label:<22s} {asset:>3s}"
|
||
for y in ALL_YEARS:
|
||
if y in yd:
|
||
line += f" {yd[y].accuracy:>5.0f}%↑" if yd[y].accuracy >= 80 else f" {yd[y].accuracy:>5.0f}% "
|
||
else:
|
||
line += f" {'—':>7s}"
|
||
worst = r.worst_year
|
||
worst_str = f"{worst.year}({worst.accuracy:.0f}%)" if worst else "N/A"
|
||
line += f" │ {r.accuracy:>5.1f}% {r.max_dd:>4.1f}% {worst_str:>10s}"
|
||
print(line)
|
||
print(f" {'·' * 136}")
|
||
|
||
# ── Tabella TRADES per anno × mercato ────────────────────────────
|
||
print(f"\n{'=' * 140}")
|
||
print(f" NUMERO TRADES PER ANNO × MERCATO")
|
||
print(f"{'=' * 140}")
|
||
|
||
header = f" {'Strategia':<22s} {'Mkt':>3s}"
|
||
for y in ALL_YEARS:
|
||
header += f" {y:>7d}"
|
||
header += f" │ {'TOT':>6s} {'€/day':>6s}"
|
||
print(header)
|
||
print(f" {'─' * 130}")
|
||
|
||
for label, module, cls_name, params, hold in STRATEGIES:
|
||
for asset in ASSETS:
|
||
r = results.get((label, asset))
|
||
if not r:
|
||
continue
|
||
yd = {ys.year: ys for ys in r.yearly}
|
||
line = f" {label:<22s} {asset:>3s}"
|
||
for y in ALL_YEARS:
|
||
if y in yd:
|
||
line += f" {yd[y].trades:>7d}"
|
||
else:
|
||
line += f" {'—':>7s}"
|
||
line += f" │ {r.trades:>6d} {r.daily_pnl:>+6.2f}"
|
||
print(line)
|
||
print(f" {'·' * 130}")
|
||
|
||
# ── Tabella PnL per anno × mercato ──────────────────────────────
|
||
print(f"\n{'=' * 140}")
|
||
print(f" PnL € PER ANNO × MERCATO (su €1000, no compounding tra anni)")
|
||
print(f"{'=' * 140}")
|
||
|
||
header = f" {'Strategia':<22s} {'Mkt':>3s}"
|
||
for y in ALL_YEARS:
|
||
header += f" {y:>7d}"
|
||
header += f" │ {'TOT€':>8s}"
|
||
print(header)
|
||
print(f" {'─' * 132}")
|
||
|
||
for label, module, cls_name, params, hold in STRATEGIES:
|
||
for asset in ASSETS:
|
||
r = results.get((label, asset))
|
||
if not r:
|
||
continue
|
||
yd = {ys.year: ys for ys in r.yearly}
|
||
line = f" {label:<22s} {asset:>3s}"
|
||
for y in ALL_YEARS:
|
||
if y in yd:
|
||
line += f" {yd[y].pnl:>+7.0f}"
|
||
else:
|
||
line += f" {'—':>7s}"
|
||
line += f" │ {r.pnl:>+8.0f}"
|
||
print(line)
|
||
print(f" {'·' * 132}")
|
||
|
||
# ── Sintesi: media per anno (tutte le strategie) ────────────────
|
||
print(f"\n{'=' * 140}")
|
||
print(f" SINTESI — Accuracy media per anno (tutte le strategie, BTC+ETH)")
|
||
print(f"{'=' * 140}")
|
||
year_acc = {y: [] for y in ALL_YEARS}
|
||
for (label, asset), r in results.items():
|
||
for ys in r.yearly:
|
||
if ys.trades >= 10:
|
||
year_acc[ys.year].append(ys.accuracy)
|
||
|
||
line_y = f" {'Anno':<22s} "
|
||
line_a = f" {'Acc media':<22s} "
|
||
for y in ALL_YEARS:
|
||
accs = year_acc[y]
|
||
avg = sum(accs) / len(accs) if accs else 0
|
||
line_y += f" {y:>7d}"
|
||
line_a += f" {avg:>6.1f}%"
|
||
print(line_y)
|
||
print(line_a)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
run()
|