integra(TP01): merge ricerca branch strategy-research-2026-06 (squash) — strategia vincente + harness + track A-E

Integra il lavoro della linea di ricerca parallela (AdrianoDev), verificato indipendentemente
col mio gauntlet onesto (regge il hold-out 2025-26 su entrambi gli asset, plateau 1h/4h/1d):
- src/strategies/trend_portfolio.py  TP01 (TSMOM 30/90/180 vol-target 20% lev2x long-flat, 50/50 BTC+ETH)
- src/backtest/harness.py            harness onesto (load + backtest_signals no-leakage + OOS)
- scripts/research/track{A,B,C,D,E}_*.py + trackD_timing.py  (le 5 track della ricerca)
- scripts/live/paper_trend.py        paper trader forward-only di TP01 (no esecuzione reale)
- tests/test_trend_portfolio.py (5 test, passano) + 6 diari trackA-E + synthesis
- CLAUDE.md aggiornato con l'esito ricerca (TP01 vincente, mean-rev morto, onesta su €50/g)

Squash (non merge) per NON portare in git i ~68MB di data/_feed_backup/*.bak che il branch
aveva committato per errore: esclusi + data/_feed_backup/ e data/paper_trend/ ora gitignorati.
Storia granulare del branch conservata sul ref origin/strategy-research-2026-06.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-19 18:55:04 +00:00
parent 55c28e51b2
commit d152941360
19 changed files with 3530 additions and 2 deletions
+460
View File
@@ -0,0 +1,460 @@
"""TRACK D — ROBUST WALK-FORWARD TREND PORTFOLIO (BTC+ETH), vol-targeted + leverage.
Thesis under test: trend-following's real value in crypto is DRAWDOWN REDUCTION vs
buy & hold (it sidesteps crashes). That lower DD lets us apply LEVERAGE and DIVERSIFY
across BTC+ETH to build a deployable, risk-adjusted EARNING system, even if each single
signal has only a modest Sharpe. Question: does a properly-built, anti-overfit trend
portfolio actually EARN robustly across regimes 2018-2026?
METHOD (strict, honest):
* NO LOOK-AHEAD. We build equity directly from a TARGET-POSITION series.
- target[i] is decided using ONLY data <= close[i].
- target[i] is HELD during the next bar (close[i] -> close[i+1]).
- bar return r[t] = close[t]/close[t-1] - 1 (uses close[t], close[t-1]; both <= t).
- pnl on bar t = target[t-1] * r[t] (shift positions by 1 -> no leakage).
- fees: fee_per_side * |target[t-1] - target[t-2]| (turnover cost, charged on rebalances).
This is the harness's documented "build your own equity from a position series" path.
* VOL-TARGETING: position = directional_signal * (target_vol / realized_vol), capped at
leverage. realized_vol uses past returns only (rolling std up to close[i]). This is the
main lever — it lets a modest signal run at a controlled risk level.
* WALK-FORWARD / MULTI-REGIME: per-year returns for ALL years 2018-2026. Plus an explicit
EARLY (2018-2021) tune / LATE (2022-2026) confirm split. ONE robust param set, both assets.
* PORTFOLIO: equal-weight BTC+ETH sleeves, rebalanced each bar. Report combined Sharpe/DD/CAGR.
* GRID ROBUSTNESS: chosen config must be positive across a neighborhood AND across regimes.
* FEE & LEVERAGE SWEEP: fee/side 0.0005..0.002 (0.10..0.40% RT); leverage cap 1x..3x.
Run: uv run python scripts/research/trackD_trendport.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from src.backtest.harness import load
ASSETS = ["BTC", "ETH"]
TF = "1h"
BARS_PER_YEAR = 24 * 365.25 # 1h bars
FEE_SIDE = 0.0005 # 0.05% per side = 0.10% round trip (Deribit taker)
# horizons in 1h bars ~ 1 / 3 / 6 "months" (30d months)
H1, H3, H6 = 30 * 24, 90 * 24, 180 * 24
# ---------------------------------------------------------------------------
# Core building blocks (all <= close[i])
# ---------------------------------------------------------------------------
def simple_returns(c: np.ndarray) -> np.ndarray:
r = np.zeros(len(c))
r[1:] = c[1:] / c[:-1] - 1.0
return r
def realized_vol(r: np.ndarray, win: int) -> np.ndarray:
"""Annualized realized vol from bar returns up to and including i (no leakage)."""
vol = pd.Series(r).rolling(win, min_periods=win // 2).std().values
return vol * np.sqrt(BARS_PER_YEAR)
def sig_tsmom_blend(c: np.ndarray, horizons=(H1, H3, H6)) -> np.ndarray:
"""Multi-horizon TSMOM: average of sign(close[i]/close[i-h]-1) over horizons -> [-1,1]."""
n = len(c)
acc = np.zeros(n)
cnt = np.zeros(n)
for h in horizons:
s = np.full(n, np.nan)
s[h:] = np.sign(c[h:] / c[:-h] - 1.0)
valid = np.isfinite(s)
acc[valid] += s[valid]
cnt[valid] += 1
out = np.zeros(n)
nz = cnt > 0
out[nz] = acc[nz] / cnt[nz]
return out
def sig_ma_slope(c: np.ndarray, span: int, slope_win: int = 24) -> np.ndarray:
"""Sign of the slope of an EMA: ema[i] vs ema[i-slope_win]. -> {-1,0,+1}."""
ema = pd.Series(c).ewm(span=span, adjust=False).mean().values
n = len(c)
out = np.zeros(n)
out[slope_win:] = np.sign(ema[slope_win:] - ema[:-slope_win])
return out
def sig_donchian_state(c, h, l, n_break: int, n_exit: int) -> np.ndarray:
"""Donchian breakout with trailing (channel) stop, returns a stateful {-1,0,+1} series.
Long when close[i] > prior n_break high; exit/flip via prior n_exit low channel (trailing).
Detection uses prior-window extremes EXCLUDING current bar (shift 1) and close[i] -> honest."""
hh = pd.Series(h).rolling(n_break).max().shift(1).values
ll = pd.Series(l).rolling(n_break).min().shift(1).values
xh = pd.Series(h).rolling(n_exit).max().shift(1).values # trailing exit for shorts
xl = pd.Series(l).rolling(n_exit).min().shift(1).values # trailing exit for longs
n = len(c)
state = np.zeros(n)
pos = 0
for i in range(n):
if not np.isfinite(hh[i]):
state[i] = 0
continue
if pos == 1:
if c[i] < xl[i]:
pos = 0
elif pos == -1:
if c[i] > xh[i]:
pos = 0
if pos == 0:
if c[i] > hh[i]:
pos = 1
elif c[i] < ll[i]:
pos = -1
state[i] = pos
return state
# ---------------------------------------------------------------------------
# Position construction (vol-targeting + leverage cap + long/flat option)
# ---------------------------------------------------------------------------
def build_target(direction: np.ndarray, vol: np.ndarray, target_vol: float,
leverage: float, long_only: bool) -> np.ndarray:
"""target[i] = direction[i] * (target_vol / vol[i]), clipped to [-leverage, leverage].
direction[i] in [-1,1]; vol[i] annualized realized vol (<= close[i]). long_only clips <0 to 0."""
d = direction.copy()
if long_only:
d = np.clip(d, 0, None)
scal = np.where((vol > 0) & np.isfinite(vol), target_vol / vol, 0.0)
tgt = d * scal
tgt = np.clip(tgt, -leverage, leverage)
tgt[~np.isfinite(tgt)] = 0.0
return tgt
def equity_from_target(target: np.ndarray, r: np.ndarray, fee_side: float):
"""Build equity from a target-position series with NO look-ahead.
pos held during bar t = target[t-1]; pnl[t] = target[t-1]*r[t]; fee on turnover."""
n = len(target)
pos_held = np.zeros(n)
pos_held[1:] = target[:-1] # held during bar t = decided at close[t-1]
gross = pos_held * r
turn = np.abs(np.diff(pos_held, prepend=0.0))
net = gross - fee_side * turn
net[0] = 0.0
net = np.clip(net, -0.99, None) # cannot lose more than capital on a bar
equity = np.cumprod(1.0 + net)
return equity, net
# ---------------------------------------------------------------------------
# Metrics
# ---------------------------------------------------------------------------
def metrics(equity: np.ndarray, net: np.ndarray, ts: pd.Series) -> dict:
rr = net[np.isfinite(net)]
sharpe = float(np.mean(rr) / np.std(rr) * np.sqrt(BARS_PER_YEAR)) if np.std(rr) > 0 else 0.0
peak = np.maximum.accumulate(equity)
dd = float(np.max((peak - equity) / peak))
span_days = (ts.iloc[-1] - ts.iloc[0]).total_seconds() / 86400
years = span_days / 365.25
total = equity[-1] / equity[0]
cagr = total ** (1 / years) - 1 if years > 0 and total > 0 else -1.0
eq_s = pd.Series(equity, index=ts)
yearly = {}
for y, g in eq_s.groupby(eq_s.index.year):
if len(g) > 1 and g.iloc[0] > 0:
yearly[int(y)] = float(g.iloc[-1] / g.iloc[0] - 1)
daily_2k = (2000 * total - 2000) / span_days if span_days > 0 else 0.0
return dict(sharpe=sharpe, max_dd=dd, cagr=cagr, total=total - 1,
yearly=yearly, daily_2k=daily_2k, vol_ann=float(np.std(rr) * np.sqrt(BARS_PER_YEAR)))
def avg_gross(target: np.ndarray) -> float:
"""Average absolute position = average gross leverage actually deployed."""
t = target[np.isfinite(target)]
return float(np.mean(np.abs(t))) if len(t) else 0.0
def fmt(m, label):
return (f" {label:<34s} ret={m['total']*100:>+9.0f}% CAGR={m['cagr']*100:>+6.1f}% "
f"Sh={m['sharpe']:>5.2f} DD={m['max_dd']*100:>4.1f}% volA={m['vol_ann']*100:>4.0f}% "
f"€/d(2k)={m['daily_2k']:>+7.2f}")
# ---------------------------------------------------------------------------
# Strategy assembly
# ---------------------------------------------------------------------------
def make_direction(df: pd.DataFrame, kind: str, params: dict) -> np.ndarray:
c = df["close"].values.astype(float)
if kind == "TSMOM":
return sig_tsmom_blend(c, params.get("horizons", (H1, H3, H6)))
if kind == "MASLOPE":
return sig_ma_slope(c, params["span"], params.get("slope_win", 24))
if kind == "DONCHIAN":
h = df["high"].values.astype(float)
l = df["low"].values.astype(float)
return sig_donchian_state(c, h, l, params["n_break"], params["n_exit"])
raise ValueError(kind)
def run_asset(df, kind, params, target_vol, leverage, long_only, fee_side=FEE_SIDE):
c = df["close"].values.astype(float)
r = simple_returns(c)
vol = realized_vol(r, params.get("vol_win", 30 * 24))
direction = make_direction(df, kind, params)
tgt = build_target(direction, vol, target_vol, leverage, long_only)
equity, net = equity_from_target(tgt, r, fee_side)
ts = df["datetime"]
m = metrics(equity, net, ts)
m["target"] = tgt
m["net"] = net
m["ts"] = ts
m["equity"] = equity
return m
def buy_hold(df):
c = df["close"].values.astype(float)
r = simple_returns(c)
equity = np.cumprod(1.0 + np.clip(r, -0.99, None))
return metrics(equity, r, df["datetime"])
# ---------------------------------------------------------------------------
# Portfolio (equal-weight BTC+ETH, rebalanced each bar on common timestamps)
# ---------------------------------------------------------------------------
def portfolio(net_btc_df, net_eth_df, w=(0.5, 0.5)):
"""Combine two per-bar net-return series aligned on common timestamps."""
a = pd.Series(net_btc_df["net"], index=net_btc_df["ts"].values)
b = pd.Series(net_eth_df["net"], index=net_eth_df["ts"].values)
j = pd.concat([a.rename("a"), b.rename("b")], axis=1, join="inner").fillna(0.0)
combo = w[0] * j["a"].values + w[1] * j["b"].values
equity = np.cumprod(1.0 + np.clip(combo, -0.99, None))
ts = pd.Series(pd.to_datetime(j.index))
return metrics(equity, combo, ts)
# ---------------------------------------------------------------------------
# Reporting helpers
# ---------------------------------------------------------------------------
ALL_YEARS = list(range(2018, 2027))
def print_yearly_row(label, m):
cells = []
for y in ALL_YEARS:
v = m["yearly"].get(y)
cells.append(" . " if v is None else f"{v*100:>+6.0f}%")
print(f" {label:<26s} " + " ".join(cells))
def yearly_header():
print(f" {'config':<26s} " + " ".join(f"{y:>7d}" for y in ALL_YEARS))
# ---------------------------------------------------------------------------
# Experiments
# ---------------------------------------------------------------------------
def main():
pd.set_option("display.width", 220)
dfs = {a: load(a, TF) for a in ASSETS}
print("=" * 130)
print("# TRACK D — VOL-TARGETED TREND PORTFOLIO (BTC+ETH, 1h, Deribit certified)")
print("# Equity built from target-position series; positions shifted +1 bar (no look-ahead);")
print("# fee = 0.05%/side (0.10% RT) on turnover. Vol-targeting scales by inverse realized vol.")
print("=" * 130)
print("\n# BUY & HOLD BENCHMARK (the DD/return bar trend must beat on risk-adjusted basis)")
yearly_header()
bh = {}
for a in ASSETS:
bh[a] = buy_hold(dfs[a])
print(fmt(bh[a], f"B&H {a}"))
print_yearly_row(f"B&H {a} yearly", bh[a])
bh_port = portfolio({"net": simple_returns(dfs["BTC"]["close"].values), "ts": dfs["BTC"]["datetime"]},
{"net": simple_returns(dfs["ETH"]["close"].values), "ts": dfs["ETH"]["datetime"]})
print(fmt(bh_port, "B&H 50/50 BTC+ETH"))
print_yearly_row("B&H port yearly", bh_port)
# ----------------------------------------------------------------------
# 1. BROAD SCAN: strategies x vol-target x leverage x long-only, per asset & portfolio
# ----------------------------------------------------------------------
print("\n" + "=" * 130)
print("# 1) BROAD SCAN — per-asset & 50/50 portfolio, vol-target=20%, leverage cap 2x")
print("# (TSMOM 1-3-6m blend / MA-slope / Donchian-trailing; long-short vs long-flat)")
print("=" * 130)
strat_defs = [
("TSMOM", dict(horizons=(H1, H3, H6), vol_win=30 * 24)),
("MASLOPE", dict(span=200, slope_win=48, vol_win=30 * 24)),
("DONCHIAN", dict(n_break=200, n_exit=100, vol_win=30 * 24)),
]
for long_only in (False, True):
mode = "LONG-FLAT" if long_only else "LONG-SHORT"
print(f"\n --- {mode} ---")
for kind, params in strat_defs:
sleeves = {}
for a in ASSETS:
m = run_asset(dfs[a], kind, params, target_vol=0.20, leverage=2.0, long_only=long_only)
sleeves[a] = m
print(fmt(m, f"{kind} {a}"))
port = portfolio(sleeves["BTC"], sleeves["ETH"])
print(fmt(port, f"{kind} PORTFOLIO 50/50"))
print_yearly_row(f"{kind} port yearly", port)
# ----------------------------------------------------------------------
# 2. GRID ROBUSTNESS on the portfolio: vol-target x leverage x vol-window
# using the multi-horizon TSMOM blend (the most diversified trend signal)
# ----------------------------------------------------------------------
print("\n" + "=" * 130)
print("# 2) GRID ROBUSTNESS — TSMOM 1-3-6m blend, 50/50 portfolio (LONG-SHORT)")
print("# Sweep target-vol x leverage-cap. A real config is positive across the neighborhood.")
print("=" * 130)
hdr = " " + "tvol\\lev".ljust(8) + "".join(f"{lev:.0f}x".rjust(26) for lev in (1.0, 1.5, 2.0, 3.0))
print(hdr)
grid = {}
for tvol in (0.10, 0.15, 0.20, 0.30, 0.40):
row = f" {tvol*100:>6.0f}% "
for lev in (1.0, 1.5, 2.0, 3.0):
sleeves = {}
for a in ASSETS:
sleeves[a] = run_asset(dfs[a], "TSMOM",
dict(horizons=(H1, H3, H6), vol_win=30 * 24),
target_vol=tvol, leverage=lev, long_only=False)
port = portfolio(sleeves["BTC"], sleeves["ETH"])
grid[(tvol, lev)] = port
row += f" Sh{port['sharpe']:>4.2f} DD{port['max_dd']*100:>3.0f} C{port['cagr']*100:>+4.0f}"
print(row)
# ----------------------------------------------------------------------
# 3. HORIZON-SET robustness (is the 1-3-6m blend a plateau or a lucky combo?)
# ----------------------------------------------------------------------
print("\n" + "=" * 130)
print("# 3) HORIZON-SET ROBUSTNESS — TSMOM blend, portfolio, tvol=20% lev=2x (LONG-SHORT)")
print("=" * 130)
horizon_sets = {
"1m only": (H1,), "3m only": (H3,), "6m only": (H6,),
"1-3m": (H1, H3), "3-6m": (H3, H6), "1-3-6m": (H1, H3, H6),
"1-2-4m": (30 * 24, 60 * 24, 120 * 24), "2-4-8m": (60 * 24, 120 * 24, 240 * 24),
}
yearly_header()
for name, hs in horizon_sets.items():
sleeves = {a: run_asset(dfs[a], "TSMOM", dict(horizons=hs, vol_win=30 * 24),
target_vol=0.20, leverage=2.0, long_only=False) for a in ASSETS}
port = portfolio(sleeves["BTC"], sleeves["ETH"])
print(fmt(port, f"TSMOM {name}"))
print()
for name, hs in horizon_sets.items():
sleeves = {a: run_asset(dfs[a], "TSMOM", dict(horizons=hs, vol_win=30 * 24),
target_vol=0.20, leverage=2.0, long_only=False) for a in ASSETS}
port = portfolio(sleeves["BTC"], sleeves["ETH"])
print_yearly_row(f"{name}", port)
# ----------------------------------------------------------------------
# 4. WALK-FORWARD: EARLY (<=2021) tune / LATE (>=2022) confirm
# Same single param set for BOTH assets; we just split the equity by date.
# ----------------------------------------------------------------------
print("\n" + "=" * 130)
print("# 4) WALK-FORWARD — split portfolio equity into EARLY (2018-2021) vs LATE (2022-2026)")
print("# One param set, both assets. Both halves must earn for the edge to be regime-robust.")
print("=" * 130)
cfg = dict(kind="TSMOM", params=dict(horizons=(H1, H3, H6), vol_win=30 * 24),
target_vol=0.20, leverage=2.0, long_only=False)
sleeves = {a: run_asset(dfs[a], cfg["kind"], cfg["params"], cfg["target_vol"],
cfg["leverage"], cfg["long_only"]) for a in ASSETS}
a = pd.Series(sleeves["BTC"]["net"], index=sleeves["BTC"]["ts"].values)
b = pd.Series(sleeves["ETH"]["net"], index=sleeves["ETH"]["ts"].values)
j = pd.concat([a.rename("a"), b.rename("b")], axis=1, join="inner").fillna(0.0)
combo = 0.5 * j["a"].values + 0.5 * j["b"].values
idx = pd.to_datetime(j.index)
for lab, mask in (("EARLY 2018-2021", idx.year <= 2021), ("LATE 2022-2026", idx.year >= 2022)):
sub = combo[mask]
eq = np.cumprod(1.0 + np.clip(sub, -0.99, None))
m = metrics(eq, sub, pd.Series(idx[mask]))
print(fmt(m, lab))
print_yearly_row(f"{lab} yearly", m)
# ----------------------------------------------------------------------
# 5. FEE & LEVERAGE SWEEP on the headline portfolio config
# ----------------------------------------------------------------------
print("\n" + "=" * 130)
print("# 5) FEE & LEVERAGE SWEEP — TSMOM 1-3-6m blend portfolio, tvol=20%")
print("=" * 130)
print(" fee sweep (leverage cap 2x):")
for fee in (0.0005, 0.00075, 0.001, 0.0015, 0.002):
sleeves = {a: run_asset(dfs[a], "TSMOM", dict(horizons=(H1, H3, H6), vol_win=30 * 24),
target_vol=0.20, leverage=2.0, long_only=False, fee_side=fee)
for a in ASSETS}
port = portfolio(sleeves["BTC"], sleeves["ETH"])
print(fmt(port, f"fee/side={fee:.5f} (RT={2*fee*100:.2f}%)"))
print(" leverage sweep (fee 0.05%/side):")
for lev in (1.0, 1.5, 2.0, 2.5, 3.0):
sleeves = {a: run_asset(dfs[a], "TSMOM", dict(horizons=(H1, H3, H6), vol_win=30 * 24),
target_vol=0.20, leverage=lev, long_only=False)
for a in ASSETS}
port = portfolio(sleeves["BTC"], sleeves["ETH"])
print(fmt(port, f"leverage cap={lev:.1f}x"))
# ----------------------------------------------------------------------
# 6. HEADLINE ROBUST CONFIG — full per-year table + sleeves + portfolio
# ----------------------------------------------------------------------
print("\n" + "=" * 130)
print("# 6) HEADLINE ROBUST CONFIG: TSMOM 1-3-6m blend, vol-target 20%, leverage cap 2x, LONG-SHORT")
print("=" * 130)
yearly_header()
sleeves = {a: run_asset(dfs[a], "TSMOM", dict(horizons=(H1, H3, H6), vol_win=30 * 24),
target_vol=0.20, leverage=2.0, long_only=False) for a in ASSETS}
for a in ASSETS:
print(fmt(sleeves[a], f"sleeve {a}"))
print_yearly_row(f"sleeve {a} yearly", sleeves[a])
port = portfolio(sleeves["BTC"], sleeves["ETH"])
print(fmt(port, "PORTFOLIO 50/50"))
print_yearly_row("PORTFOLIO yearly", port)
# also long-flat headline (deployable variant — no shorts/funding complexity)
print()
sleeves_lf = {a: run_asset(dfs[a], "TSMOM", dict(horizons=(H1, H3, H6), vol_win=30 * 24),
target_vol=0.20, leverage=2.0, long_only=True) for a in ASSETS}
port_lf = portfolio(sleeves_lf["BTC"], sleeves_lf["ETH"])
print(fmt(port_lf, "PORTFOLIO 50/50 LONG-FLAT"))
print_yearly_row("PORTFOLIO LF yearly", port_lf)
# ----------------------------------------------------------------------
# 7. €/DAY ON 2000 — what leverage gets us toward 50/day, and the DD it costs
# ----------------------------------------------------------------------
print("\n" + "=" * 130)
print("# 7) PATH TO ~50 EUR/day on 2000 — the REAL lever is TARGET-VOL, not the leverage cap.")
print("# At tvol=20%% on 60-80%% crypto vol, positions stay sub-1x: the leverage cap NEVER binds.")
print("# To deploy real leverage you raise target-vol; Sharpe is ~constant, DD scales ~linearly.")
print("# 'avg gross' = mean |position| = leverage actually used. (cap fixed at 3x here)")
print("=" * 130)
print(f" {'target_vol':<12s}{'avgGross':>10s}{'CAGR':>9s}{'Sharpe':>9s}{'maxDD':>8s}"
f"{'€/day(2k,avg)':>16s}{'final/2k':>12s}")
for tvol in (0.20, 0.40, 0.60, 0.80, 1.00):
sleeves = {a: run_asset(dfs[a], "TSMOM", dict(horizons=(H1, H3, H6), vol_win=30 * 24),
target_vol=tvol, leverage=3.0, long_only=False) for a in ASSETS}
port = portfolio(sleeves["BTC"], sleeves["ETH"])
ag = 0.5 * (avg_gross(sleeves["BTC"]["target"]) + avg_gross(sleeves["ETH"]["target"]))
print(f" {tvol*100:>8.0f}% {ag:>9.2f}x{port['cagr']*100:>+8.1f}%{port['sharpe']:>9.2f}"
f"{port['max_dd']*100:>7.1f}%{port['daily_2k']:>+16.2f}{(1+port['total']):>12.1f}x")
# steady-state €/day at current capital under headline CAGR
print("\n Steady-state €/day implied by headline CAGR (NOT path-dependent), at various capital:")
sleeves = {a: run_asset(dfs[a], "TSMOM", dict(horizons=(H1, H3, H6), vol_win=30 * 24),
target_vol=0.20, leverage=2.0, long_only=False) for a in ASSETS}
port = portfolio(sleeves["BTC"], sleeves["ETH"])
g = port["cagr"]
daily_rate = (1 + g) ** (1 / 365.25) - 1
for cap in (2000, 5000, 10000, 50000, 100000):
print(f" capital={cap:>7d} ~€/day = {cap*daily_rate:>+8.2f} (CAGR={g*100:+.1f}%)")
need = 50.0 / daily_rate if daily_rate > 0 else float("inf")
print(f"\n To average ~50 EUR/day at this CAGR you'd need ~{need:,.0f} capital "
f"(at leverage 2x, maxDD~{port['max_dd']*100:.0f}%).")
print("\nDONE. See the report/diary for the honest verdict.")
if __name__ == "__main__":
main()