feat(portfolio): XS01 cross-sectional (Hyperliquid) BATTE il portafoglio -> TP01 70% + XS01 30%

Espansione universo (su input utente "storico da cerbero"): il Cerbero MCP col token MAINNET serve
Hyperliquid (230 perp REALI, storia nativa dal 2024). fetch_hyperliquid.py certifica 19 alt liquidi
a 1d (flat 0%, cross-venue 4-9 bps vs Binance) -> data/raw/hl_*_1d.parquet. Abilita le strategie
CROSS-SECTIONAL (impossibili a 2 asset).

XS01 = cross-sectional momentum market-neutral (long 5 forti / short 5 deboli su ret 30g, ogni 10g,
vol-target 20%). Validato onesto: plateau (config/k/subset), fee-robusto (0.3% RT), scorrelato a TP01
(-0.06), positivo OGNI anno 2024-26, meccanismo complementare (lavora nella dispersione quando TP01
e' in cash). Diverso dal regime-luck RV bocciato (19 asset, plateau, ogni anno+).

Contributo al portafoglio (outer-join + pesi rinormalizzati per sleeve a date diverse):
  TP01-solo FULL 1.30 / HOLD 0.31  ->  TP01 70% + XS01 30%: FULL 1.41 / HOLD 1.15, DD giu', ~ogni anno+.
-> XS01 BATTE il portafoglio esistente: inserito in active_sleeves.

Caveat (documentati): storia XS ~2.5 anni; STAT-MODE (book 19 gambe non eseguibile a 2k -> ~20k),
sleeve diagnostico/forward-monitor. portfolio.combine ora outer-join+renorm. 12 test passano.
Diario 2026-06-19-hyperliquid-xsec.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-19 20:05:45 +00:00
parent 18f22160b2
commit a5a61ac7e3
10 changed files with 512 additions and 15 deletions
+16 -3
View File
@@ -17,8 +17,8 @@ from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np
from src.data.downloader import load_data
from scripts.analysis.research_lab import backtest, buy_hold, mc_pvalue, VAL_START, HOLDOUT_START
import pandas as pd
from scripts.analysis.research_lab import backtest, buy_hold, mc_pvalue, load_tf, ts, _net_series, VAL_START, HOLDOUT_START
def load_signal(path):
@@ -58,7 +58,7 @@ def main():
res = {"asset": asset, "tf": tf, "sigfile": sigfile}
try:
signal = load_signal(sigfile)
df = load_data(asset, tf)
df = load_tf(asset, tf)
pos = np.asarray(signal(df, asset, tf), float)
res["n"] = int(len(df))
res["len_ok"] = bool(len(pos) == len(df))
@@ -81,6 +81,19 @@ def main():
null_p=round(p, 4),
beats_bh=bool(full.sharpe > bh.sharpe and oos.sharpe > 0),
)
# breadth per-anno (pre-hold-out): % anni positivi, anni rossi consecutivi
net, _, _, _ = _net_series(df, pos)
s = pd.Series(net, index=ts(df))
s = s[s.index < pd.Timestamp(HOLDOUT_START, tz="UTC")]
yr = {int(y): float((1 + g).prod() - 1) for y, g in s.groupby(s.index.year)}
vals = list(yr.values())
max_consec_red = 0; cur = 0
for v in vals:
cur = cur + 1 if v < 0 else 0
max_consec_red = max(max_consec_red, cur)
res["per_year_preho"] = {y: round(v, 3) for y, v in yr.items()}
res["pct_years_pos"] = round(sum(v > 0 for v in vals) / len(vals), 2) if vals else 0.0
res["max_consec_red_years"] = int(max_consec_red)
if holdout:
ho = backtest(df, pos, tf, lo=HOLDOUT_START)
res["holdout_sharpe"] = round(ho.sharpe, 3)
+91
View File
@@ -0,0 +1,91 @@
"""FETCH + CERTIFY universo Hyperliquid (Cerbero MCP MAINNET) — espansione cross-sectional.
Hyperliquid (via cerbero-mcp mainnet) offre ~230 perp liquidi, ma storia nativa REALE solo dal
2024 (pre-2024 = backfill, volume 0). Qui scarico un set liquido a 1d (2024+), e CERTIFICO ogni
asset come BTC/ETH: cross-venue vs Binance (realismo) + flat-bar (liquidita'). Scrivo SOLO i puliti
in data/raw/hl_<sym>_1d.parquet (namespace dedicato, NON mischiato col Deribit BTC/ETH).
Disciplina: Cerbero ci ha gia' bruciato (testnet) -> niente fiducia, solo certificazione.
uv run python scripts/analysis/fetch_hyperliquid.py
"""
from __future__ import annotations
import sys, time
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np, pandas as pd, requests, ccxt
RAW = PROJECT_ROOT / "data" / "raw"
START = "2024-01-01"; END = "2026-06-17"
# set liquido (volume recente alto + storia 2024); MATIC morto, HYPE 2025-only esclusi qui
SYMS = ["BTC","ETH","SOL","BNB","XRP","DOGE","AVAX","LINK","LTC","ADA","ARB","OP","SUI","APT","INJ","TIA","SEI","NEAR","AAVE"]
BINANCE = {"BTC":"BTC/USDT","ETH":"ETH/USDT","SOL":"SOL/USDT","BNB":"BNB/USDT","XRP":"XRP/USDT",
"DOGE":"DOGE/USDT","AVAX":"AVAX/USDT","LINK":"LINK/USDT","LTC":"LTC/USDT","ADA":"ADA/USDT",
"ARB":"ARB/USDT","OP":"OP/USDT","SUI":"SUI/USDT","APT":"APT/USDT","INJ":"INJ/USDT",
"TIA":"TIA/USDT","SEI":"SEI/USDT","NEAR":"NEAR/USDT","AAVE":"AAVE/USDT"}
def _h():
env={}
for ln in open(PROJECT_ROOT/".env.mainnet"):
ln=ln.strip()
if ln and not ln.startswith("#") and "=" in ln: k,v=ln.split("=",1); env[k]=v.strip()
return {"Authorization":f"Bearer {env['CERBERO_TOKEN']}","X-Bot-Tag":env.get('CERBERO_BOT_TAG','fetch'),"Content-Type":"application/json"}
def fetch_hl(sym, H, interval="1d"):
r=requests.post("https://cerbero-mcp.tielogic.xyz/mcp/tools/get_historical",
headers=H, json={"exchange":"hyperliquid","instrument":sym,"interval":interval,
"start_date":START,"end_date":END}, timeout=60)
c=r.json().get("candles",[])
if not c: return pd.DataFrame()
df=pd.DataFrame(c)[["timestamp","open","high","low","close","volume"]]
return df.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True)
def binance_daily(sym_b, start_ms, end_ms):
ex=ccxt.binance({"enableRateLimit":True})
out={}; since=start_ms
while since<=end_ms:
try: r=ex.fetch_ohlcv(sym_b,"1d",since=since,limit=500)
except Exception: break
r=[x for x in r if x[0]>=since]
if not r: break
for x in r:
if start_ms<=x[0]<=end_ms and x[4]: out[int(x[0])]=float(x[4])
nxt=int(r[-1][0])+86400000
if nxt<=since: break
since=nxt
return pd.Series(out)
def main():
H=_h()
print("="*92); print(" FETCH + CERTIFY Hyperliquid 1d (Cerbero mainnet) — cross-venue vs Binance + liquidita'"); print("="*92)
print(f" {'sym':<6}{'barre':>7}{'start':>12}{'flat%':>7}{'med_bps':>9}{'>1%':>7}{'verdetto':>12}")
certified=[]
for s in SYMS:
df=fetch_hl(s,H)
if df.empty: print(f" {s:<6} vuoto"); continue
ts=pd.to_datetime(df["timestamp"],unit="ms",utc=True)
flat=((df.open==df.high)&(df.high==df.low)&(df.low==df.close)).mean()*100
# cross-venue vs Binance USDT (daily close)
ref=binance_daily(BINANCE[s], int(df["timestamp"].iloc[0]), int(df["timestamp"].iloc[-1]))
a=df.set_index("timestamp")["close"]
m=pd.concat([a.rename("a"),ref.rename("b")],axis=1,join="inner").dropna()
if len(m)>5:
bps=(m["a"]-m["b"]).abs()/m["b"]*1e4
med=bps.median(); g1=(bps>100).mean()*100
else: med=g1=float("nan")
clean = (not np.isnan(med)) and med<60 and g1<3 and flat<5
v = "PULITO" if clean else "scarta"
print(f" {s:<6}{len(df):>7}{str(ts.iloc[0].date()):>12}{flat:>6.1f}%{med:>9.1f}{g1:>6.1f}%{v:>12}")
if clean:
df.to_parquet(RAW/f"hl_{s.lower()}_1d.parquet", index=False); certified.append(s)
print(f"\n CERTIFICATI ({len(certified)}): {certified}")
print(" Scritti in data/raw/hl_<sym>_1d.parquet (namespace dedicato). Universo per cross-sectional.")
if __name__=="__main__":
main()
+17 -1
View File
@@ -29,7 +29,23 @@ import pandas as pd
from src.data.downloader import load_data
FEE_RT = 0.001 # 0.10% round-trip taker Deribit (0.05%/lato)
BARS_PER_YEAR = {"5m": 105192.0, "15m": 35064.0, "1h": 8766.0}
BARS_PER_YEAR = {"5m": 105192.0, "15m": 35064.0, "1h": 8766.0,
"4h": 2191.5, "12h": 730.5, "1d": 365.25}
def load_tf(asset: str, tf: str):
"""Carica un TF certificato. 5m/15m/1h diretti; 4h/12h/1d DERIVATI per resample dal 1h
(confini 00:00 UTC). >=12h e' il regime raccomandato (sotto, costi+overfit dominano)."""
if tf in ("5m", "15m", "1h"):
return load_data(asset, tf)
rule = {"4h": "4h", "12h": "12h", "1d": "1D"}[tf]
df = load_data(asset, "1h").copy()
df.index = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
out = df.resample(rule, label="left", closed="left").agg(
{"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"}).dropna(subset=["open"])
epoch = pd.Timestamp("1970-01-01", tz="UTC")
out["timestamp"] = ((out.index - epoch) // pd.Timedelta(milliseconds=1)).astype("int64")
return out.reset_index(drop=True)[["timestamp", "open", "high", "low", "close", "volume"]]
# Hold-out FINALE bloccato: NIENTE ricerca/tuning lo tocca finché non è il verdetto (Fase 3).
HOLDOUT_START = "2025-01-01"
# Finestra di validazione OOS usata in ricerca (out-of-sample ma PRE hold-out).
+109
View File
@@ -0,0 +1,109 @@
"""GIUDICE DEI CONTENDER — valuta un segnale candidato a livello PORTAFOGLIO vs TP01.
Per ogni (tf, sigfile): costruisce il BOOK 50/50 BTC+ETH del candidato (causale, netto fee),
e applica il gauntlet STRETTO vs TP01:
- standalone: FULL Sh/DD, HOLD-OUT 2025-26 Sh/ret/DD, breadth per-anno (% anni positivi, rossi
consecutivi), correlazione a TP01;
- contributo al portafoglio: TP01-solo vs TP01+candidato a pesi 0.2/0.3/0.5 (Δ FULL e Δ HOLD).
VERDETTO WINNER se: (A) batte TP01 standalone (book FULL Sh>1.30, hold-out Sh>~0.25, breadth ok),
OPPURE (B) diversificatore robusto (corr bassa, alza il portafoglio su FULL E hold-out, breadth ok).
uv run python scripts/portfolio/verify_contender.py 1d /tmp/beat_sig_0.py 12h /tmp/beat_sig_10.py ...
"""
from __future__ import annotations
import sys
import importlib.util
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np
import pandas as pd
from scripts.analysis.research_lab import load_tf, _net_series
from src.portfolio.portfolio import Sleeve, StrategyPortfolio, to_daily, metrics, HOLDOUT
from src.portfolio.sleeves import tp01_sleeve
TP01_FULL_SH = 1.30
TP01_HOLD_SH = 0.31
def load_signal(path):
spec = importlib.util.spec_from_file_location("csig_" + Path(path).stem, path)
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
return m.signal
def book_perbar(signal, tf) -> pd.Series:
s = {}
for a in ("BTC", "ETH"):
df = load_tf(a, tf)
net, _, _, _ = _net_series(df, np.asarray(signal(df, a, tf), float))
s[a] = pd.Series(net, index=pd.to_datetime(df["timestamp"], unit="ms", utc=True))
J = pd.concat(s, axis=1, join="inner").fillna(0.0)
return pd.Series(0.5 * J["BTC"].values + 0.5 * J["ETH"].values, index=J.index)
def breadth(daily):
pre = daily[daily.index < HOLDOUT]
yr = [float((1 + g).prod() - 1) for _, g in pre.groupby(pre.index.year)]
consec = mx = 0
for v in yr:
consec = consec + 1 if v < 0 else 0; mx = max(mx, consec)
return (sum(v > 0 for v in yr) / len(yr) if yr else 0.0), mx, yr
def main():
args = sys.argv[1:]
pairs = [(args[i], args[i + 1]) for i in range(0, len(args) - 1, 2)]
tp = tp01_sleeve(1.0)
tp_daily = tp.daily()
base = StrategyPortfolio([tp01_sleeve(1.0)]).backtest()
print("=" * 100)
print(f" GIUDICE CONTENDER vs TP01 (book FULL Sh {base['full']['sharpe']:.2f} / HOLD {base['holdout']['sharpe']:.2f})")
print("=" * 100)
winners = []
for tf, sig in pairs:
name = Path(sig).stem
try:
signal = load_signal(sig)
pb = book_perbar(signal, tf)
d = to_daily(pb)
except Exception as e:
print(f"\n {name} ({tf}): ERRORE {type(e).__name__}: {str(e)[:80]}"); continue
f = metrics(d); h = metrics(d[d.index >= HOLDOUT])
J = pd.concat({"tp": tp_daily, "x": d}, axis=1, join="inner").dropna()
corr = float(J["tp"].corr(J["x"])) if len(J) > 2 else float("nan")
pct, consec, yr = breadth(d)
print(f"\n {name} ({tf}) BOOK 50/50")
print(f" standalone: FULL Sh {f['sharpe']:>5.2f} DD {f['maxdd']*100:>4.1f}% | HOLD Sh {h['sharpe']:>5.2f} ret {h['ret']*100:>+6.1f}% DD {h['maxdd']*100:>4.1f}%"
f" | anni+ {pct*100:>3.0f}% rossi-consec {consec} | corr_TP01 {corr:+.2f} | turn n/a")
# contributo al portafoglio
contrib = []
for w in (0.2, 0.3, 0.5):
sl = Sleeve(name, w, lambda pb=pb: pb)
bt = StrategyPortfolio([tp01_sleeve(1 - w), sl]).backtest()
dF = bt["full"]["sharpe"] - base["full"]["sharpe"]
dH = bt["holdout"]["sharpe"] - base["holdout"]["sharpe"]
contrib.append((w, bt["full"]["sharpe"], dF, bt["holdout"]["sharpe"], dH))
print(f" +TP01 w{w:.0%}: FULL {bt['full']['sharpe']:.2f} ({dF:+.2f}) | HOLD {bt['holdout']['sharpe']:.2f} ({dH:+.2f})")
breadth_ok = pct >= 0.6 and consec <= 1
standalone_beats = f["sharpe"] > TP01_FULL_SH and h["sharpe"] > 0.25 and breadth_ok
# diversificatore: corr<0.5, migliora FULL E hold del portafoglio ad almeno un peso, breadth ok
improves = any(dF > 0.05 and dH > 0.0 for _, _, dF, _, dH in contrib)
diversifier = (not np.isnan(corr) and corr < 0.5) and improves and breadth_ok
verdict = "WINNER-standalone" if standalone_beats else ("WINNER-diversifier" if diversifier else "no")
print(f" -> {verdict} (breadth_ok={breadth_ok}, standalone_beats={standalone_beats}, diversifier={diversifier})")
if verdict.startswith("WINNER"):
winners.append((name, tf, verdict))
print("\n" + "=" * 100)
print(f" WINNERS: {len(winners)}")
for n, tf, v in winners:
print(f" {n} ({tf}): {v}")
if not winners:
print(" nessuno batte TP01 con criterio onesto -> serve un'altra ondata.")
if __name__ == "__main__":
main()
+123
View File
@@ -0,0 +1,123 @@
"""CROSS-SECTIONAL su universo Hyperliquid certificato (19 alt, 1d, 2024-2026).
Strategia market-neutral: ogni H giorni classifica gli asset per rendimento a L giorni (causale),
va long i top-k / short i bottom-k (momentum) o viceversa (reversal), dollar-neutral, vol-target.
Mira a DIVERSIFICARE TP01 (long-trend): se scorrelata e robusta, migliora il portafoglio.
Gauntlet onesto: FULL (2024-26) + within-window OOS (2025+) + per-anno + corr TP01 + contributo.
Caveat: storia corta (~2.5 anni). Risultati suggestivi, non robusti come BTC/ETH 6 anni.
uv run python scripts/portfolio/xsec_research.py
"""
from __future__ import annotations
import sys, glob
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np, pandas as pd
from src.portfolio.portfolio import to_daily, metrics, HOLDOUT, Sleeve, StrategyPortfolio
from src.portfolio.sleeves import tp01_sleeve
RAW = PROJECT_ROOT / "data" / "raw"
FEE = 0.001
def load_universe():
cols = {}
for f in sorted(glob.glob(str(RAW / "hl_*_1d.parquet"))):
s = Path(f).stem.replace("hl_", "").replace("_1d", "").upper()
d = pd.read_parquet(f)
cols[s] = pd.Series(d["close"].values.astype(float), index=pd.to_datetime(d["timestamp"], unit="ms", utc=True))
C = pd.concat(cols, axis=1, join="inner").sort_index().dropna()
return C
def xs_book(C, L, H, k, mode="mom", target_vol=0.20):
"""Rendimenti netti giornalieri di un book cross-sectional market-neutral. Causale."""
assets = list(C.columns); A = len(assets)
px = C.values; n = len(px)
dret = np.vstack([np.zeros(A), px[1:] / px[:-1] - 1.0])
W = np.zeros((n, A)) # peso per asset per giorno (deciso a close[i], tenuto in i+1)
w = np.zeros(A)
for i in range(n):
if i >= L and i % H == 0:
lb = px[i] / px[i - L] - 1.0
order = np.argsort(lb)
w = np.zeros(A)
lo, hi = order[:k], order[-k:] # peggiori / migliori
if mode == "mom":
w[hi] = 0.5 / k; w[lo] = -0.5 / k # long forti / short deboli
else:
w[lo] = 0.5 / k; w[hi] = -0.5 / k # reversal
W[i] = w
# rendimento book: peso[i-1] guadagna dret[i]; fee su turnover
gross = np.zeros(n); gross[1:] = np.sum(W[:-1] * dret[1:], axis=1) # W[i-1] guadagna dret[i]
turn = np.zeros(n); turn[0] = np.abs(W[0]).sum()
turn[1:] = np.abs(np.diff(W, axis=0)).sum(axis=1) # turnover per (ri)settare W[i]
net = gross - turn * (FEE / 2.0)
s = pd.Series(net, index=C.index)
# vol-target (causale): scala per target/vol_realizzata(30) shiftata
rv = s.rolling(30, min_periods=15).std().shift(1) * np.sqrt(365.25)
scale = np.clip(np.nan_to_num(target_vol / rv.replace(0, np.nan).values, nan=0.0), 0, 3.0)
return pd.Series(s.values * scale, index=C.index)
def yr_breadth(daily):
pre = daily
yr = [float((1 + g).prod() - 1) for _, g in pre.groupby(pre.index.year)]
consec = mx = 0
for v in yr: consec = consec + 1 if v < 0 else 0; mx = max(mx, consec)
return yr, (sum(v > 0 for v in yr) / len(yr) if yr else 0), mx
def main():
C = load_universe()
print("=" * 96)
print(f" CROSS-SECTIONAL Hyperliquid — {len(C.columns)} asset, {len(C)} giorni [{C.index[0].date()} -> {C.index[-1].date()}]")
print("=" * 96)
tp = tp01_sleeve(1.0); tp_daily = tp.daily()
base = StrategyPortfolio([tp01_sleeve(1.0)]).backtest()
print(f"\n {'config':<24}{'FULL Sh':>9}{'OOS25 Sh':>10}{'ret%':>8}{'DD%':>7}{'corrTP':>8}{'anni+':>7}")
cands = []
grid = [("mom",L,H,k) for L in (30,60,90) for H in (5,10,20) for k in (3,5)] \
+ [("rev",L,H,k) for L in (3,7,14) for H in (3,5) for k in (3,5)]
for mode,L,H,k in grid:
d = to_daily(xs_book(C,L,H,k,mode))
f=metrics(d); oos=metrics(d[d.index>=HOLDOUT])
J=pd.concat({"tp":tp_daily,"x":d},axis=1,join="inner").dropna(); corr=float(J["tp"].corr(J["x"])) if len(J)>5 else float("nan")
yr,pct,consec=yr_breadth(d)
tag=f"{mode} L{L} H{H} k{k}"
cands.append((tag,mode,L,H,k,f,oos,corr,pct,consec,d))
if f["sharpe"]>0.6 or oos["sharpe"]>0.8:
print(f" {tag:<24}{f['sharpe']:>9.2f}{oos['sharpe']:>10.2f}{f['ret']*100:>+8.0f}{f['maxdd']*100:>7.1f}{corr:>+8.2f}{pct*100:>6.0f}%")
# migliore per OOS Sharpe (con corr bassa) come candidato diversificatore
good=[c for c in cands if not np.isnan(c[7]) and abs(c[7])<0.4 and c[5]["sharpe"]>0.5 and c[6]["sharpe"]>0]
good.sort(key=lambda c:-(c[6]["sharpe"]))
print(f"\n Candidati scorrelati(<0.4) e positivi (FULL>0.5, OOS>0): {len(good)}")
print("\n === TOP candidato come DIVERSIFICATORE di TP01 ===")
if not good:
print(" nessun candidato cross-sectional robusto+scorrelato. Universo corto.")
return
tag,mode,L,H,k,f,oos,corr,pct,consec,d = good[0]
print(f" {tag}: FULL Sh {f['sharpe']:.2f} ret {f['ret']*100:+.0f}% DD {f['maxdd']*100:.1f}% | OOS25 Sh {oos['sharpe']:.2f} | corr TP01 {corr:+.2f} | anni+ {pct*100:.0f}% rossi-consec {consec}")
per=[(y,round(v,3)) for y,(v) in zip([yy for yy,_ in d.groupby(d.index.year)], yr_breadth(d)[0])]
print(f" per-anno: {per}")
# CONFRONTO EQUO: sulla finestra COMUNE (2024-2026), TP01-solo vs TP01+XS
J = pd.concat({"tp": tp_daily, "xs": d}, axis=1, join="inner").dropna()
tpw, xsw = J["tp"], J["xs"]
bw_f = metrics(tpw); bw_h = metrics(tpw[tpw.index >= HOLDOUT])
print(f"\n [finestra comune {J.index[0].date()}->{J.index[-1].date()}]")
print(f" TP01 SOLO (su finestra comune): FULL Sh {bw_f['sharpe']:.2f} DD {bw_f['maxdd']*100:.1f}% | HOLD Sh {bw_h['sharpe']:.2f}")
for w in (0.2, 0.3, 0.5):
comb = (1 - w) * tpw + w * xsw
cf = metrics(comb); ch = metrics(comb[comb.index >= HOLDOUT])
print(f" +XS w{w:.0%}: FULL {cf['sharpe']:.2f} ({cf['sharpe']-bw_f['sharpe']:+.2f}) DD {cf['maxdd']*100:.1f}%"
f" | HOLD {ch['sharpe']:.2f} ({ch['sharpe']-bw_h['sharpe']:+.2f})")
print("\n WINNER-diversifier se: corr bassa, e TP01+XS batte TP01-solo (FULL E HOLD) sulla finestra comune,")
print(" con breadth per-anno ok. Altrimenti no (e attenzione: storia XS solo ~2.5 anni).")
if __name__=="__main__":
main()