research(wave-0822): ledger degli esiti — ADAPTIVE-HORIZON scartato
This commit is contained in:
@@ -0,0 +1,717 @@
|
||||
"""XS-LITE — esiste una versione CONCENTRATA (2-8 gambe) degli edge cross-sectional del progetto
|
||||
che conservi una frazione utile dell'edge ed e' ESEGUIBILE OGGI a $600-5.000?
|
||||
|
||||
LA DOMANDA (mai posta in questa forma). Il progetto ha due edge cross-sectional che dichiara di non
|
||||
poter eseguire per TAGLIA, non per debolezza:
|
||||
XS01 — 19 major HL, long 5 / short 5, ribilancio ogni 10g. Dichiarato eseguibile da ~$20k.
|
||||
XSR01 — 50 alt HL, residuo OLS su BTC demeanato cross-sezionalmente. Dichiarato ~$5k.
|
||||
Il vincolo dichiarato e' il **min order** moltiplicato per il numero di gambe. Se si riducono le
|
||||
gambe il vincolo si allenta — ma l'ampiezza (il numero di scommesse indipendenti) crolla, e
|
||||
l'ampiezza e' l'unica ragione per cui una strategia cross-sectional ha uno Sharpe alto.
|
||||
Questo script misura il CAMBIO, non lo assume. E misura anche la premessa: che il muro sia il
|
||||
min-order.
|
||||
|
||||
IPOTESI PRE-REGISTRATE (scritte prima di guardare i numeri):
|
||||
(a) lo Sharpe scende con k come ~sqrt(ampiezza): k=2-3 conserva il 40-60% dell'edge;
|
||||
(b) il min-order NON e' il muro di XS01 a $600 (a k=5 il ticket e' 0.5/5 del capitale dello
|
||||
sleeve), e il "serve $20k" viene dal TURNOVER del vol-target, non dalla taglia della posizione;
|
||||
(c) XSR01 concentrato perde molto di piu', perche' il suo edge E' l'ampiezza (il progetto ha gia'
|
||||
misurato che il demeaning porta l'ampiezza 4.5 -> 37.4: e' quel numero, non il segnale, a
|
||||
fare lo Sharpe 1.82).
|
||||
Con 2.6 anni di storia monoregime il verdetto atteso e' LEAD o SCARTATO, mai CANDIDATO.
|
||||
|
||||
MECCANISMI CONGELATI — non si rifitta nulla.
|
||||
XS01 : blend lookback (30,90), gate dispersione p30 espandente causale, H=10, vol-target 20%,
|
||||
cap 3x, fee 5 bps per unita' di turnover. UNICO parametro variato: k (gambe per lato).
|
||||
Replica bit-exact di src.portfolio.sleeves._xsec_returns verificata al passo (0).
|
||||
XSR01: W=45, sgn=+1, residuo OLS rolling causale su BTC, z-score, tanh, vol-target 20%, cap 2x,
|
||||
demean cross-sezionale giornaliero, fee 5 bps/gamba. Segnale IMPORTATO da
|
||||
r0725_statarb_multi, paniere da r0725_statarb_basket_gate (nessuna reimplementazione).
|
||||
UNICO parametro variato: k.
|
||||
Il LOOKBACK NON e' stato riaperto DI PROPOSITO: riaprire un parametro riapre la sua famiglia
|
||||
(lezione VRP-tenore, 30/07) e moltiplicherebbe i trial. Qui si varia solo la CONCENTRAZIONE.
|
||||
|
||||
GRIGLIA DICHIARATA PRIMA DI MISURARE: 2 famiglie x 2 universi x 6 valori di k (+ il "pieno" di
|
||||
XSR01) = 26 celle valutabili. Le 10 fasi del ciclo H=10 di XS01 sono una banda d'ANCORA, non una
|
||||
griglia di parametri (regola del progetto: il deflated-Sharpe non le conta) -> riportate come
|
||||
mediana onesta e come differenze APPAIATE. Il deflated-Sharpe e' comunque pubblicato a N=26 E a
|
||||
N=156 (sensibilita' del verdetto al conteggio, lezione 30/07).
|
||||
|
||||
nice -n 19 timeout 900 uv run python scripts/research/r0822_xs_lite.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
|
||||
|
||||
import altlib as A # noqa: E402
|
||||
from src.portfolio.sleeves import XS_CFG, XS_UNIVERSE, _xsec_returns # noqa: E402
|
||||
from r0725_statarb_multi import BASE, load_hl, pnl, signal # noqa: E402
|
||||
from r0725_statarb_basket_gate import basket_from_positions, pair_frames # noqa: E402
|
||||
|
||||
RAW = ROOT / "data" / "raw"
|
||||
ANN = np.sqrt(365.25)
|
||||
HOLDOUT = A.HOLDOUT # 2025-01-01 (convenzione altlib)
|
||||
MIN_ORDER_HL = 10.0 # Hyperliquid: min order $10 (r0702_capital_scaling)
|
||||
MIN_ORDER_5 = 5.0 # il $5 di Deribit, citato nella missione
|
||||
KS = (1, 2, 3, 4, 5, 6) # 5 = canonico XS01
|
||||
COST_SWEEP = (0.0, 5.0, 10.0, 20.0, 30.0, 50.0) # bps per gamba TOTALI (fee + slippage)
|
||||
# capitali dello SLEEVE. Il progetto tiene XS01 al 15% del book -> a book $600 lo sleeve ha $90.
|
||||
# Si misurano entrambe le letture, perche' e' esattamente qui che nasce il numero "$20k".
|
||||
SLEEVE_CAPS = (90.0, 300.0, 600.0, 750.0, 2000.0, 5000.0)
|
||||
|
||||
|
||||
# =====================================================================================
|
||||
# misure
|
||||
# =====================================================================================
|
||||
def sh(s) -> float:
|
||||
r = np.asarray(pd.Series(s).dropna().values, float)
|
||||
return float(r.mean() / r.std() * ANN) if len(r) > 30 and r.std() > 0 else 0.0
|
||||
|
||||
|
||||
def dd(s) -> float:
|
||||
r = np.nan_to_num(np.asarray(pd.Series(s).values, float))
|
||||
eq = np.cumprod(1.0 + np.clip(r, -0.99, None))
|
||||
return float(np.min(eq / np.maximum.accumulate(eq) - 1.0))
|
||||
|
||||
|
||||
def cagr(s) -> float:
|
||||
x = pd.Series(s).dropna()
|
||||
if len(x) < 30:
|
||||
return 0.0
|
||||
yrs = (x.index[-1] - x.index[0]).days / 365.25
|
||||
eq = float(np.prod(1.0 + np.clip(x.values, -0.99, None)))
|
||||
return float(eq ** (1.0 / yrs) - 1.0) if yrs > 0.2 and eq > 0 else 0.0
|
||||
|
||||
|
||||
def split(s):
|
||||
x = pd.Series(s).dropna()
|
||||
return x[x.index < HOLDOUT], x[x.index >= HOLDOUT]
|
||||
|
||||
|
||||
HDR = f" {'cella':<20}{'ShFULL':>8}{'Sh IS':>8}{'Sh OOS':>8}{'maxDD':>9}{'CAGR':>9}"
|
||||
|
||||
|
||||
def row(name, s, extra=""):
|
||||
ins, out = split(s)
|
||||
return (f" {name:<20}{sh(s):>8.2f}{sh(ins):>8.2f}{sh(out):>8.2f}"
|
||||
f"{dd(s)*100:>8.1f}%{cagr(s)*100:>8.1f}%{extra}")
|
||||
|
||||
|
||||
# =====================================================================================
|
||||
# (A) XS01 congelato, con k e fase parametrici e universo a copertura variabile
|
||||
# =====================================================================================
|
||||
def load_closes(syms, how="inner") -> pd.DataFrame:
|
||||
cols = {}
|
||||
for s in syms:
|
||||
p = RAW / f"hl_{s.lower()}_1d.parquet"
|
||||
if not p.exists():
|
||||
continue
|
||||
d = pd.read_parquet(p, columns=["timestamp", "close"])
|
||||
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" if how == "inner" else "outer")).sort_index()
|
||||
return C.dropna() if how == "inner" else C
|
||||
|
||||
|
||||
def xs_run(C: pd.DataFrame, k: int, phase: int = 0):
|
||||
"""Replica di sleeves._xsec_returns con k e fase parametrici, tollerante ai simboli con
|
||||
copertura piu' corta (si valutano solo quelli con storia sufficiente a quella data: senza
|
||||
questo, un outer-join su 51 alt riduce la finestra comune a ~2/3 e cancella l'in-sample).
|
||||
Con tutte le colonne valide il percorso e' NUMERICAMENTE IDENTICO all'originale — provato al
|
||||
passo (0) con max|diff| = 0.
|
||||
Ritorna (serie netta lente-sleeve, W [n x A], scale [n], dret [n x A])."""
|
||||
px = C.values.astype(float)
|
||||
n, na = px.shape
|
||||
lookbacks, H, mode, tv = XS_CFG["lookbacks"], XS_CFG["H"], XS_CFG["mode"], XS_CFG["target_vol"]
|
||||
disp_pct, minhist = XS_CFG["disp_pct"], XS_CFG["disp_minhist"]
|
||||
mlb = max(lookbacks)
|
||||
fin = np.isfinite(px)
|
||||
prev = np.vstack([np.full(na, np.nan), px[:-1]])
|
||||
dret = np.where(fin & np.isfinite(prev), px / prev - 1.0, 0.0)
|
||||
dret = np.nan_to_num(dret)
|
||||
W = np.zeros((n, na))
|
||||
w = np.zeros(na)
|
||||
disp_hist = []
|
||||
for i in range(n):
|
||||
if i >= mlb and (i - phase) % H == 0:
|
||||
ok = fin[i].copy()
|
||||
for L in lookbacks:
|
||||
ok &= fin[i - L]
|
||||
if ok.sum() >= 2 * k:
|
||||
cols = np.where(ok)[0]
|
||||
rLs = [px[i, cols] / px[i - L, cols] - 1.0 for L in lookbacks]
|
||||
disp_i = float(np.mean([r.std() for r in rLs]))
|
||||
thr = (np.percentile(disp_hist, disp_pct)
|
||||
if (disp_pct > 0 and len(disp_hist) >= minhist) else -np.inf)
|
||||
if disp_i >= thr:
|
||||
score = np.zeros(len(cols)); cnt = 0
|
||||
for rL in rLs:
|
||||
sd = rL.std()
|
||||
if sd > 0:
|
||||
score += (rL - rL.mean()) / sd; cnt += 1
|
||||
if cnt:
|
||||
score /= cnt
|
||||
order = np.argsort(score)
|
||||
w = np.zeros(na)
|
||||
lo, hi = cols[order[:k]], cols[order[-k:]]
|
||||
if mode == "mom": w[hi] = 0.5 / k; w[lo] = -0.5 / k
|
||||
else: w[lo] = 0.5 / k; w[hi] = -0.5 / k
|
||||
else:
|
||||
w = np.zeros(na)
|
||||
disp_hist.append(disp_i)
|
||||
W[i] = w
|
||||
gross = np.zeros(n); gross[1:] = np.sum(W[:-1] * dret[1:], axis=1)
|
||||
turn = np.zeros(n); turn[0] = np.abs(W[0]).sum()
|
||||
turn[1:] = np.abs(np.diff(W, axis=0)).sum(axis=1)
|
||||
net = gross - turn * (0.001 / 2.0)
|
||||
s = pd.Series(net, index=C.index)
|
||||
rv = s.rolling(30, min_periods=15).std().shift(1) * np.sqrt(365.25)
|
||||
scale = np.clip(np.nan_to_num(tv / rv.replace(0, np.nan).values, nan=0.0), 0, 3.0)
|
||||
return pd.Series(s.values * scale, index=C.index), W, scale, dret
|
||||
|
||||
|
||||
def xs_effective_weights(W: np.ndarray, scale: np.ndarray) -> np.ndarray:
|
||||
"""Peso EFFETTIVO tenuto durante la barra i = W[i-1]*scale[i]. Entrambi noti a close[i-1]
|
||||
(scale usa una rolling std gia' shiftata) -> causale."""
|
||||
Weff = np.zeros_like(W)
|
||||
Weff[1:] = W[:-1] * scale[1:, None]
|
||||
return Weff
|
||||
|
||||
|
||||
# =====================================================================================
|
||||
# (B) XSR01 congelato, con k parametrico
|
||||
# =====================================================================================
|
||||
def xsr_positions(P: pd.DataFrame, k: int | None) -> pd.DataFrame:
|
||||
"""Q = posizioni demeanate cross-sezionalmente (meccanismo congelato).
|
||||
Se k e' dato: tiene le k piu' positive e le k piu' negative di ogni riga, azzera il resto e
|
||||
RI-demeana sulle sopravvissute (resta dollar-neutral)."""
|
||||
Q = P.sub(P.mean(axis=1), axis=0)
|
||||
if k is None:
|
||||
return Q
|
||||
V = Q.to_numpy(float)
|
||||
out = np.zeros_like(V)
|
||||
for i in range(V.shape[0]):
|
||||
v = V[i]
|
||||
m = np.isfinite(v)
|
||||
if m.sum() < 2 * k or not np.any(v[m] != 0.0):
|
||||
continue
|
||||
idx = np.where(m)[0]
|
||||
order = idx[np.argsort(v[idx])]
|
||||
keep = np.concatenate([order[:k], order[-k:]])
|
||||
r = np.zeros_like(v)
|
||||
r[keep] = v[keep]
|
||||
r[keep] -= r[keep].mean()
|
||||
out[i] = r
|
||||
return pd.DataFrame(out, index=P.index, columns=P.columns)
|
||||
|
||||
|
||||
def xsr_legs(Q: pd.DataFrame, S: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Matrice [data x gamba] dei pnl per gamba, nella lente PUBBLICATA (fee su 2 gambe per
|
||||
coppia: alt + copertura BTC dedicata)."""
|
||||
return pd.DataFrame({c: pnl(Q[c].to_numpy(float), S[c].to_numpy(float)) for c in Q.columns},
|
||||
index=Q.index)
|
||||
|
||||
|
||||
def xsr_basket(Q: pd.DataFrame, S: pd.DataFrame, norm) -> pd.Series:
|
||||
"""norm = 'perrow' riproduce ESATTAMENTE basket_from_positions (media per-riga sui simboli
|
||||
con dato: il numero di simboli cambia nel tempo). norm = intero -> normalizzazione COSTANTE,
|
||||
l'unica che rende confrontabili celle con k diverso."""
|
||||
M = xsr_legs(Q, S)
|
||||
if norm == "perrow":
|
||||
return M.mean(axis=1).dropna()
|
||||
return (M.fillna(0.0).sum(axis=1) / float(norm)).dropna()
|
||||
|
||||
|
||||
# =====================================================================================
|
||||
# (C) simulatore di libro con MIN-ORDER (lente eseguibile, comune alle due famiglie)
|
||||
# =====================================================================================
|
||||
def sim_book(Weff: np.ndarray, R: np.ndarray, idx, r_hedge=None, capital=None,
|
||||
min_order=0.0, cost_bps=5.0):
|
||||
"""Weff[i] = pesi TARGET da tenere durante la barra i (decisi a close[i-1]).
|
||||
R[i] = ritorni per gamba nella barra i. r_hedge = ritorno della gamba di copertura (si tiene
|
||||
-sum(w)); None = libro gia' neutrale fra le gambe. min_order>0: una variazione di NOZIONALE
|
||||
sotto la soglia NON si esegue (si tiene la vecchia). cost_bps = costo per unita' di turnover."""
|
||||
n, na = Weff.shape
|
||||
c = cost_bps / 1e4
|
||||
held = np.zeros(na)
|
||||
hedge = 0.0
|
||||
net = np.zeros(n)
|
||||
n_fill = n_skip = 0
|
||||
tickets, gross = [], []
|
||||
for i in range(n):
|
||||
tgt = Weff[i]
|
||||
d_not = np.abs(tgt - held) * (capital or 1.0)
|
||||
if min_order > 0.0 and capital:
|
||||
move = d_not >= min_order
|
||||
else:
|
||||
move = d_not > 1e-12
|
||||
new = np.where(move, tgt, held)
|
||||
n_fill += int(move.sum())
|
||||
n_skip += int(((~move) & (d_not > 1e-12)).sum())
|
||||
tickets.extend(d_not[move].tolist())
|
||||
turn = float(np.abs(new - held).sum())
|
||||
r = float(np.dot(new, R[i]))
|
||||
if r_hedge is not None:
|
||||
h = -float(new.sum())
|
||||
if min_order > 0.0 and capital and abs(h - hedge) * capital < min_order:
|
||||
h = hedge
|
||||
turn += abs(h - hedge)
|
||||
hedge = h
|
||||
r += hedge * float(r_hedge[i])
|
||||
net[i] = r - c * turn
|
||||
held = new
|
||||
gross.append(float(np.abs(new).sum()) + abs(hedge))
|
||||
return pd.Series(net, index=idx), dict(
|
||||
n_fill=n_fill, n_skip=n_skip,
|
||||
exec_share=(n_fill / (n_fill + n_skip)) if (n_fill + n_skip) else 1.0,
|
||||
med_ticket=float(np.median(tickets)) if tickets else 0.0,
|
||||
med_gross=float(np.median(gross)),
|
||||
orders_py=(n_fill / (len(idx) / 365.25)) if len(idx) else 0.0)
|
||||
|
||||
|
||||
# =====================================================================================
|
||||
# (D) ampiezza
|
||||
# =====================================================================================
|
||||
def breadth(legs: pd.DataFrame, n_simult: int, min_overlap: int = 30):
|
||||
"""Due misure, perche' una sola mente.
|
||||
(1) N_eff = N/(1+(N-1)*rbar), N = gambe SIMULTANEE, rbar = correlazione media fra i contributi
|
||||
di due gambe CONDIZIONATA a essere entrambe attive. E' la formula pubblicata dal progetto
|
||||
(4.5 -> 37.4), qui condizionata: senza condizionare, concentrando si conterebbe come
|
||||
'ampiezza' la ROTAZIONE nel tempo, che nello Sharpe sta gia' dentro sqrt(T).
|
||||
INSTABILE per costruzione: rbar leggermente NEGATIVO (normale in un libro dollar-neutral)
|
||||
la fa esplodere. Si legge insieme a rbar, mai da sola.
|
||||
(2) N_hhi = numero effettivo di gambe che PORTANO il rischio, giorno per giorno:
|
||||
1/sum(share_i^2) sulle quote |contributo|, mediato sui giorni attivi. Limitato da N,
|
||||
stabile, e non dipende dal segno delle correlazioni."""
|
||||
M = legs.to_numpy(float)
|
||||
M = np.nan_to_num(M)
|
||||
act = M != 0.0
|
||||
rs = []
|
||||
for a in range(M.shape[1]):
|
||||
for b in range(a + 1, M.shape[1]):
|
||||
m = act[:, a] & act[:, b]
|
||||
if m.sum() < min_overlap:
|
||||
continue
|
||||
xa, xb = M[m, a], M[m, b]
|
||||
if xa.std() > 0 and xb.std() > 0:
|
||||
rs.append(float(np.corrcoef(xa, xb)[0, 1]))
|
||||
rbar = float(np.mean(rs)) if rs else float("nan")
|
||||
N = float(n_simult)
|
||||
den = 1.0 + (N - 1) * rbar if rs else float("nan")
|
||||
neff = (N / den) if (rs and den > 1e-3) else float("nan")
|
||||
hh = []
|
||||
for i in range(M.shape[0]):
|
||||
v = np.abs(M[i][act[i]])
|
||||
t = v.sum()
|
||||
if t > 0 and len(v) > 0:
|
||||
sHH = float(np.sum((v / t) ** 2))
|
||||
if sHH > 0:
|
||||
hh.append(1.0 / sHH)
|
||||
return neff, rbar, (float(np.mean(hh)) if hh else float("nan")), len(rs)
|
||||
|
||||
|
||||
# =====================================================================================
|
||||
def main() -> None:
|
||||
print("=" * 108)
|
||||
print(" XS-LITE — una versione CONCENTRATA degli edge cross-sectional e' eseguibile a $600-5.000?")
|
||||
print("=" * 108)
|
||||
|
||||
maj19 = list(XS_UNIVERSE)
|
||||
all51 = sorted({p.stem.replace("hl_", "").replace("_1d", "").upper()
|
||||
for p in RAW.glob("hl_*_1d.parquet")})
|
||||
|
||||
# ---------------------------------------------------------------- (0) repliche
|
||||
print("\n" + "-" * 108)
|
||||
print(" (0) REPLICA DEI MECCANISMI CONGELATI — prima di ogni delta")
|
||||
print("-" * 108)
|
||||
C19 = load_closes(maj19, "inner")
|
||||
C51 = load_closes(all51, "outer")
|
||||
off, _, _, _ = xs_run(C19, k=XS_CFG["k"], phase=0)
|
||||
ref = _xsec_returns()
|
||||
J = pd.concat({"mio": off, "sleeve": ref}, axis=1, join="inner").dropna()
|
||||
d_xs = float(np.max(np.abs(J["mio"] - J["sleeve"])))
|
||||
print(f" XS01 k=5 fase 0 vs sleeves._xsec_returns : max|diff| = {d_xs:.3e} "
|
||||
f"({len(J)} barre) {'OK' if d_xs < 1e-12 else 'DIVERGE'}")
|
||||
|
||||
P, S = pair_frames()
|
||||
Qfull = xsr_positions(P, None)
|
||||
xsr_perrow = xsr_basket(Qfull, S, "perrow")
|
||||
xsr_ref = basket_from_positions(P, S, cols=None, demean=True)
|
||||
J2 = pd.concat({"mio": xsr_perrow, "ref": xsr_ref}, axis=1, join="inner").dropna()
|
||||
d_xsr = float(np.max(np.abs(J2["mio"] - J2["ref"])))
|
||||
print(f" XSR01 pieno vs basket_from_positions(demean): max|diff| = {d_xsr:.3e} "
|
||||
f"({len(J2)} barre) {'OK' if d_xsr < 1e-12 else 'DIVERGE'}")
|
||||
xsr_const = xsr_basket(Qfull, S, P.shape[1])
|
||||
print(f" XSR01 pieno, lente pubblicata (media per-riga sui simboli CON DATO): "
|
||||
f"Sharpe {sh(xsr_perrow):.2f} maxDD {dd(xsr_perrow)*100:.1f}% (pubblicati 1.82 / -2.6%)")
|
||||
print(f" XSR01 pieno, normalizzazione COSTANTE 1/{P.shape[1]} : "
|
||||
f"Sharpe {sh(xsr_const):.2f} maxDD {dd(xsr_const)*100:.1f}%")
|
||||
print(" ^ le due lenti coincidono qui (la copertura e' quasi piena); da qui in poi si usa la")
|
||||
print(" COSTANTE, l'unica che rende confrontabili celle con k diverso, e i numeri XSR01 vanno")
|
||||
print(f" letti contro {sh(xsr_const):.2f}.")
|
||||
disc = pd.Timestamp("2026-07-25", tz="UTC") # giorno di scoperta/inception monitor
|
||||
xsr_disc = xsr_perrow[xsr_perrow.index <= disc]
|
||||
xsr_fwd = xsr_perrow[xsr_perrow.index > disc]
|
||||
print(f" XSR01 sulla FINESTRA DI SCOPERTA (<= {disc.date()}, {len(xsr_disc)} barre): "
|
||||
f"Sharpe {sh(xsr_disc):.2f} maxDD {dd(xsr_disc)*100:.1f}%")
|
||||
print(" ⚠ IL NUMERO PUBBLICATO (1.82) NON SI RIPRODUCE OGGI, e non e' la mia implementazione:")
|
||||
print(" le due repliche qui sopra sono bit-exact col codice del progetto. Sulla finestra")
|
||||
print(f" IDENTICA a quella di scoperta la lente 'paniere' da' {sh(xsr_disc):.2f} e la lente 'libro'")
|
||||
print(" (fee 1 gamba alt + copertura BTC NETTA, convenzione di paper_xsr) da' 2.23: l'1.82 non")
|
||||
print(" e' nessuna delle due. Spiegazione piu' probabile: data/raw e' gitignored e il cron")
|
||||
print(" riscrive i parquet HL -> stesso codice, dati diversi (identico allo scoperto del 07/08")
|
||||
print(" su GTAA/ADJUSTED_LAST). NON l'ho inseguito: tutte le mie conclusioni sono confronti")
|
||||
print(" RELATIVI dentro UNA lente. Ma tocca un numero pubblicato e il gate del 23/10.")
|
||||
print(f" XSR01 dal 25/07 a oggi (forward, {len(xsr_fwd)} barre, stessa lente): "
|
||||
f"ritorno {(np.prod(1+xsr_fwd.values)-1)*100:+.2f}% "
|
||||
f"Sharpe {sh(xsr_fwd) if len(xsr_fwd) > 30 else float('nan'):.2f} "
|
||||
f"{'(campione troppo corto per uno Sharpe: si legge il ritorno)' if len(xsr_fwd) <= 30 else ''}")
|
||||
print(" ^ la differenza fra 1.82 e il numero FULL di oggi NON e' una lente diversa (le repliche")
|
||||
print(" sono bit-exact): e' un mese in piu' di dati. Il gate pre-registrato resta il 23/10.")
|
||||
print(f" finestra MAJ19: {C19.index[0].date()} -> {C19.index[-1].date()} ({len(C19)} barre); "
|
||||
f"in-sample = pre {HOLDOUT.date()} = {int((C19.index < HOLDOUT).sum())} barre")
|
||||
cov51 = np.isfinite(C51.values).sum(axis=1)
|
||||
print(f" finestra ALL51: {C51.index[0].date()} -> {C51.index[-1].date()} ({len(C51)} barre); "
|
||||
f"simboli con dato: da {cov51.min()} a {cov51.max()} (outer-join: l'inner-join")
|
||||
print(" taglierebbe la finestra a 627 barre e CANCELLEREBBE l'in-sample).")
|
||||
print(" ⚠ l'in-sample e' UN ANNO SOLO e monoregime: ogni Sh IS qui sotto va letto cosi'.")
|
||||
|
||||
# ---------------------------------------------------------------- (1) XS-LITE
|
||||
print("\n" + "-" * 108)
|
||||
print(" (1) FAMIGLIA A — XS-LITE: XS01 congelato, varia solo k (gambe per lato). k=5 = canonico")
|
||||
print("-" * 108)
|
||||
print(HDR + f"{'N_eff':>8}{'rbar':>8}{'N_hhi':>8}")
|
||||
xs_series, xs_cells = {}, {}
|
||||
for uni, Cx in (("MAJ19", C19), ("ALL51", C51)):
|
||||
for k in KS:
|
||||
s, W, sc, dr = xs_run(Cx, k=k, phase=0)
|
||||
Weff = xs_effective_weights(W, sc)
|
||||
legpnl = pd.DataFrame(Weff * dr, index=Cx.index, columns=Cx.columns)
|
||||
ne, rbar, nhhi, _ = breadth(legpnl, 2 * k)
|
||||
nm = f"{uni} k={k}" + ("*" if (uni == "MAJ19" and k == 5) else "")
|
||||
xs_series[nm] = s
|
||||
xs_cells[nm] = dict(uni=uni, k=k, C=Cx, W=W, scale=sc, dret=dr,
|
||||
neff=ne, rbar=rbar, nhhi=nhhi)
|
||||
print(row(nm, s, f"{ne:>8.1f}{rbar:>8.3f}{nhhi:>8.1f}"))
|
||||
print(" (*) = configurazione canonica del sleeve XS01")
|
||||
|
||||
# ---------------------------------------------------------------- (2) XSR-LITE
|
||||
print("\n" + "-" * 108)
|
||||
print(" (2) FAMIGLIA B — XSR-LITE: XSR01 congelato (demean), varia solo k. 'pieno' = tutte le gambe")
|
||||
print("-" * 108)
|
||||
maj18 = [s for s in maj19 if s != BASE]
|
||||
Pm = P[[c for c in maj18 if c in P.columns]]
|
||||
Sm = S[[c for c in maj18 if c in S.columns]]
|
||||
print(f" ALL50: {P.shape[1]} gambe MAJ18: {Pm.shape[1]} gambe")
|
||||
print(HDR + f"{'N_eff':>8}{'rbar':>8}{'N_hhi':>8}")
|
||||
xsr_series, xsr_cells = {}, {}
|
||||
for uni, (Pu, Su) in (("ALL50", (P, S)), ("MAJ18", (Pm, Sm))):
|
||||
for k in list(KS) + [None]:
|
||||
if k is not None and 2 * k > Pu.shape[1]:
|
||||
continue
|
||||
Q = xsr_positions(Pu, k)
|
||||
norm = (2 * k) if k is not None else Pu.shape[1]
|
||||
s = xsr_basket(Q, Su, norm)
|
||||
legs = xsr_legs(Q, Su) / float(norm)
|
||||
ne, rbar, nhhi, _ = breadth(legs, norm)
|
||||
nm = f"{uni} k={k if k else 'pieno'}" + ("*" if (uni == "ALL50" and k is None) else "")
|
||||
xsr_series[nm] = s
|
||||
xsr_cells[nm] = dict(uni=uni, k=k, Q=Q, S=Su, norm=norm,
|
||||
neff=ne, rbar=rbar, nhhi=nhhi)
|
||||
print(row(nm, s, f"{ne:>8.1f}{rbar:>8.3f}{nhhi:>8.1f}"))
|
||||
print(" (*) = XSR01 pieno (50 gambe demeanate), qui a normalizzazione costante")
|
||||
|
||||
# banda di fase calcolata PRIMA della curva: la stima onesta di uno sleeve ancorato e' la
|
||||
# mediana della banda, quindi e' quella che deve stare nella curva (la fase 0 e' un max-of-10).
|
||||
base_by_phase = {ph: xs_run(C19, k=5, phase=ph)[0] for ph in range(XS_CFG["H"])}
|
||||
phase_med, phase_rows = {}, {}
|
||||
for k in KS:
|
||||
by_ph = {ph: (base_by_phase[ph] if k == 5 else xs_run(C19, k=k, phase=ph)[0])
|
||||
for ph in range(XS_CFG["H"])}
|
||||
vals = np.array([sh(by_ph[p_]) for p_ in range(XS_CFG["H"])])
|
||||
diffs = np.array([sh(by_ph[p_]) - sh(base_by_phase[p_]) for p_ in range(XS_CFG["H"])])
|
||||
phase_med[k] = float(np.median(vals))
|
||||
phase_rows[k] = (vals, diffs)
|
||||
|
||||
# ---------------------------------------------------------------- (3) curva ampiezza
|
||||
print("\n" + "-" * 108)
|
||||
print(" (3) LA CURVA AMPIEZZA-vs-k — la domanda centrale: quanto ne resta concentrando?")
|
||||
print("-" * 108)
|
||||
sh_xs_star = sh(xs_series["MAJ19 k=5*"])
|
||||
sh_xsr_star = sh(xsr_series["ALL50 k=pieno*"])
|
||||
print(f" {'k':>4}{'gambe':>7} {'--------- XS-LITE MAJ19 ---------':>34} "
|
||||
f"{'--------- XSR-LITE ALL50 --------':>34}")
|
||||
print(f" {'':>4}{'':>7} {'N_hhi':>9}{'N_eff':>9}{'Sh f0':>8}{'Sh MED':>8}{'MED/MED*':>10} "
|
||||
f"{'N_hhi':>9}{'N_eff':>9}{'Sh':>8}{'Sh/Sh*':>9}")
|
||||
med5 = phase_med[5]
|
||||
for k in KS:
|
||||
an = f"MAJ19 k={k}" + ("*" if k == 5 else "")
|
||||
bn = f"ALL50 k={k}"
|
||||
ca, cb = xs_cells[an], xsr_cells[bn]
|
||||
sa, sb = xs_series[an], xsr_series[bn]
|
||||
print(f" {k:>4}{2*k:>7} {ca['nhhi']:>9.1f}{ca['neff']:>9.1f}{sh(sa):>8.2f}"
|
||||
f"{phase_med[k]:>8.2f}{(phase_med[k]/med5 if med5 else 0):>10.0%} "
|
||||
f"{cb['nhhi']:>9.1f}{cb['neff']:>9.1f}{sh(sb):>8.2f}"
|
||||
f"{(sh(sb)/sh_xsr_star if sh_xsr_star else 0):>9.0%}")
|
||||
cp = xsr_cells["ALL50 k=pieno*"]
|
||||
print(f" {'pieno':>4}{cp['norm']:>7} {'':>9}{'':>9}{'':>8}{'':>8}{'':>10} "
|
||||
f"{cp['nhhi']:>9.1f}{cp['neff']:>9.1f}{sh_xsr_star:>8.2f}{1.0:>9.0%}")
|
||||
print(" 'Sh MED' = mediana delle 10 fasi d'ancora di XS01 = la stima ONESTA (la fase 0 e' un")
|
||||
print(" max-of-10 non dichiarato). XSR01 e' giornaliero: non ha fase, la sua colonna e' unica.")
|
||||
print(" N_hhi = gambe che PORTANO il rischio (stabile). N_eff = formula pubblicata, esplode")
|
||||
print(" quando rbar e' negativo: si legge solo insieme a rbar, mai da sola.")
|
||||
|
||||
# ---------------------------------------------------------------- (4) banda d'ancora
|
||||
print("\n" + "-" * 108)
|
||||
print(" (4) BANDA D'ANCORA — le 10 fasi del ciclo H=10 di XS01 (XSR01 e' giornaliero: nessuna")
|
||||
print(" fase). Stima onesta = MEDIANA della banda, non la fase 0 con cui e' stato scoperto.")
|
||||
print("-" * 108)
|
||||
print(f" {'cella':<14}{'fase0':>8}{'mediana':>9}{'p10':>8}{'p90':>8}{'pctl f0':>9}"
|
||||
f" d vs k=5: mediana APPAIATA (fasi >0)")
|
||||
for k in KS:
|
||||
vals, diffs = phase_rows[k]
|
||||
pctl = float((vals <= vals[0]).mean() * 100)
|
||||
print(f" MAJ19 k={k:<6}{vals[0]:>8.2f}{phase_med[k]:>9.2f}{np.percentile(vals,10):>8.2f}"
|
||||
f"{np.percentile(vals,90):>8.2f}{pctl:>8.0f}% {np.median(diffs):>+8.3f}"
|
||||
f" {int((diffs>0).sum())}/10")
|
||||
print(" 'd vs k=5' = mediana delle DIFFERENZE APPAIATE per fase (mai differenza di mediane).")
|
||||
|
||||
# ---------------------------------------------------------------- (5) eseguibilita'
|
||||
print("\n" + "-" * 108)
|
||||
print(" (5) ESEGUIBILITA' — contabilita' min-order per gamba, lente 'libro'")
|
||||
print(" Il capitale in colonna e' quello dello SLEEVE. XS01 sta al 15% del book:")
|
||||
print(" sleeve $90 = book $600, sleeve $300 = book $2.000, sleeve $750 = book $5.000,")
|
||||
print(" sleeve $3.000 = book $20.000 (la soglia dichiarata dal progetto).")
|
||||
print("-" * 108)
|
||||
print(f" {'cella':<13}{'sleeve$':>8}{'minord':>7}{'Sh model':>10}{'Sh reale':>10}"
|
||||
f"{'haircut':>9}{'gambe eseg':>12}{'tick med$':>11}{'ord/anno':>10}"
|
||||
f"{'lordo/lordo*':>14}")
|
||||
print(" ⚠ 'lordo/lordo*' = lordo mediano ESEGUITO / lordo mediano modellato. Sotto ~0.8 il")
|
||||
print(" libro ha smesso di seguire il proprio target, e allora lo Sharpe NON e' la lettura")
|
||||
print(" giusta (puo' perfino MIGLIORARE): e' il null del de-levering in veste di 'meno")
|
||||
print(" costi'. Lezione GTAA-banda 27/07, riapplicata qui.")
|
||||
|
||||
def xs_book_inputs(nm):
|
||||
cell = xs_cells[nm]
|
||||
return xs_effective_weights(cell["W"], cell["scale"]), cell["dret"], cell["C"].index, None
|
||||
|
||||
def xsr_book_inputs(nm):
|
||||
cell = xsr_cells[nm]
|
||||
Q, Su, norm = cell["Q"], cell["S"], cell["norm"]
|
||||
Wt = np.nan_to_num(Q.to_numpy(float)) / float(norm)
|
||||
Weff = np.zeros_like(Wt); Weff[1:] = Wt[:-1]
|
||||
rb = load_hl(BASE).reindex(Q.index).pct_change().fillna(0.0).to_numpy(float)
|
||||
Ralt = np.zeros_like(Wt)
|
||||
for j, c in enumerate(Q.columns):
|
||||
Ralt[:, j] = np.nan_to_num(Su[c].to_numpy(float)) + rb
|
||||
return Weff, Ralt, Q.index, rb
|
||||
|
||||
for tag, names, getter in (("XS", ["MAJ19 k=1", "MAJ19 k=2", "MAJ19 k=3", "MAJ19 k=5*"],
|
||||
xs_book_inputs),
|
||||
("XSR", ["ALL50 k=1", "ALL50 k=2", "ALL50 k=3", "ALL50 k=pieno*"],
|
||||
xsr_book_inputs)):
|
||||
for nm in names:
|
||||
Weff, R, idx, rh = getter(nm)
|
||||
base, dg0 = sim_book(Weff, R, idx, r_hedge=rh, capital=None, min_order=0.0)
|
||||
for cap in SLEEVE_CAPS:
|
||||
for mo in (MIN_ORDER_HL,):
|
||||
real, dg = sim_book(Weff, R, idx, r_hedge=rh, capital=cap, min_order=mo)
|
||||
lbl = f"{tag} {nm.split()[1]}"
|
||||
trk = (dg["med_gross"] / dg0["med_gross"]) if dg0["med_gross"] > 0 else float("nan")
|
||||
print(f" {lbl:<13}{cap:>8.0f}{mo:>7.0f}{sh(base):>10.2f}{sh(real):>10.2f}"
|
||||
f"{sh(base)-sh(real):>9.2f}{dg['exec_share']*100:>11.0f}%"
|
||||
f"{dg['med_ticket']:>11.0f}{dg['orders_py']:>10.0f}"
|
||||
f"{trk:>12.2f}{' ⚠' if trk < 0.8 else ' '}")
|
||||
print()
|
||||
print(" 'gambe eseg' = quota delle VARIAZIONI di posizione sopra il min-order (la diagnostica")
|
||||
print(" su cui si basa la soglia '$20k'). 'haircut' = quanto ne costa in Sharpe. Sono cose")
|
||||
print(" DIVERSE: il turnover saltato e' la deriva del vol-target, non il segnale.")
|
||||
print(" Con min-order $5 invece di $10 (Deribit invece di HL) l'haircut si dimezza per")
|
||||
print(" costruzione; il caso $10 e' quello vero per Hyperliquid ed e' quello riportato.")
|
||||
|
||||
# ---------------------------------------------------------------- (6) slippage
|
||||
print("\n" + "-" * 108)
|
||||
print(" (6) SLIPPAGE — costo TOTALE per gamba (i 5 bps di fee taker sono gia' dentro).")
|
||||
print(" A che costo muore l'edge? E' il rischio #1 dichiarato di XSR01.")
|
||||
print("-" * 108)
|
||||
print(f" {'cella':<16}" + "".join(f"{int(c):>10}bps" for c in COST_SWEEP) + f"{'break-even':>13}")
|
||||
|
||||
def cost_curve(label, Weff, R, idx, rh):
|
||||
vals = []
|
||||
for c in COST_SWEEP:
|
||||
s, _ = sim_book(Weff, R, idx, r_hedge=rh, capital=None, min_order=0.0, cost_bps=c)
|
||||
vals.append(sh(s))
|
||||
be = None
|
||||
for i in range(1, len(COST_SWEEP)):
|
||||
if vals[i - 1] > 0 >= vals[i]:
|
||||
x0, x1, y0, y1 = COST_SWEEP[i-1], COST_SWEEP[i], vals[i-1], vals[i]
|
||||
be = x0 + (x1 - x0) * y0 / (y0 - y1)
|
||||
break
|
||||
print(f" {label:<16}" + "".join(f"{v:>13.2f}" for v in vals)
|
||||
+ (f"{be:>13.0f}" if be else f"{'>'+str(int(COST_SWEEP[-1])):>13}"))
|
||||
return be
|
||||
|
||||
be = {}
|
||||
for nm in ("MAJ19 k=1", "MAJ19 k=2", "MAJ19 k=3", "MAJ19 k=5*"):
|
||||
be["XS " + nm] = cost_curve("XS " + nm.split()[1], *xs_book_inputs(nm))
|
||||
for nm in ("ALL50 k=1", "ALL50 k=2", "ALL50 k=3", "ALL50 k=pieno*"):
|
||||
be["XSR " + nm] = cost_curve("XSR " + nm.split()[1], *xsr_book_inputs(nm))
|
||||
|
||||
# ---------------------------------------------------------------- (7) selezione + DSR
|
||||
print("\n" + "-" * 108)
|
||||
print(" (7) SELEZIONE IN-SAMPLE-ONLY (mai sull'hold-out) + DEFLATED SHARPE")
|
||||
print("-" * 108)
|
||||
allcells = {**{f"XS {n}": s for n, s in xs_series.items()},
|
||||
**{f"XSR {n}": s for n, s in xsr_series.items()}}
|
||||
ins_rank = sorted(allcells.items(), key=lambda kv: -sh(split(kv[1])[0]))
|
||||
print(f" celle valutate: {len(allcells)} (2 famiglie x 2 universi x 6 k, + i 2 'pieno' di")
|
||||
print(" XSR01, meno le celle impossibili 2k>gambe). Lookback NON riaperto -> nessun trial in piu'.")
|
||||
print(f"\n {'rank':>5} {'cella':<20}{'Sh IS':>8}{'Sh OOS':>8}{'Sh FULL':>9}")
|
||||
for i, (n, s) in enumerate(ins_rank[:8], 1):
|
||||
ins, out = split(s)
|
||||
print(f" {i:>5} {n:<20}{sh(ins):>8.2f}{sh(out):>8.2f}{sh(s):>9.2f}")
|
||||
best_name, best_s = ins_rank[0]
|
||||
all_sr = [sh(s) for s in allcells.values()]
|
||||
print(f"\n cella scelta AL BUIO (solo in-sample): {best_name}")
|
||||
for mult, label in ((1, "trial dichiarati = celle"),
|
||||
(6, "conteggio conservativo x6 (se avessi riaperto il lookback)")):
|
||||
pad = all_sr * mult
|
||||
d, s0 = A.deflated_sharpe(sh(best_s), pad, pd.Series(best_s).dropna())
|
||||
print(f" DSR a N={len(pad):<4} ({label}): {d:.3f} "
|
||||
f"{'PASS' if d >= 0.95 else 'FAIL'} (Sharpe-soglia del null {s0:.2f})")
|
||||
conc = {n: s for n, s in allcells.items() if any(f" k={j}" in n for j in (1, 2, 3))}
|
||||
cbest_name, cbest_s = max(conc.items(), key=lambda kv: sh(split(kv[1])[0]))
|
||||
d2, _ = A.deflated_sharpe(sh(cbest_s), all_sr, pd.Series(cbest_s).dropna())
|
||||
print(f" cella CONCENTRATA (k<=3, cioe' <=6 gambe) scelta al buio: {cbest_name} "
|
||||
f"Sh IS {sh(split(cbest_s)[0]):.2f} OOS {sh(split(cbest_s)[1]):.2f} FULL {sh(cbest_s):.2f}")
|
||||
print(f" DSR della concentrata a N={len(all_sr)}: {d2:.3f} {'PASS' if d2 >= 0.95 else 'FAIL'}")
|
||||
|
||||
# ---------------------------------------------------------------- (8) gate marginali
|
||||
print("\n" + "-" * 108)
|
||||
print(" (8) GATE MARGINALE vs TP01 + RIDONDANZA vs gli sleeve che gia' esistono")
|
||||
print("-" * 108)
|
||||
order = [best_name, cbest_name, "XS MAJ19 k=2", "XS MAJ19 k=3", "XS MAJ19 k=5*",
|
||||
"XSR ALL50 k=2", "XSR ALL50 k=3", "XSR ALL50 k=pieno*"]
|
||||
to_score = {}
|
||||
for n in order:
|
||||
if n in allcells and n not in to_score:
|
||||
to_score[n] = allcells[n]
|
||||
for n, s in to_score.items():
|
||||
ss = pd.Series(s).dropna(); ss.index = pd.to_datetime(ss.index, utc=True)
|
||||
m = A.marginal_vs_tp01(ss)
|
||||
b = m.get("blends", {}).get("w25", {})
|
||||
print(f" {n:<20} {str(m.get('marginal_verdict')):<10} corr {str(m.get('corr_full')):>7}"
|
||||
f" uplift w25 full {str(b.get('uplift_full')):>7} hold {str(b.get('uplift_hold')):>7}"
|
||||
f" robust={m.get('robust_oos')} noise_null={m.get('beats_noise_null')}"
|
||||
f" is_edge={m.get('has_insample_edge')} hedge={m.get('is_hedge')}")
|
||||
print("\n correlazione col materiale gia' in casa (un proxy eseguibile DEVE assomigliare al suo")
|
||||
print(" originale: qui una corr ALTA e' l'obiettivo, non un difetto)")
|
||||
ref_map = {"XS01 (sleeve)": ref, "XSR01 (pieno)": xsr_const, "TP01": A.tp01_baseline_daily()}
|
||||
print(f" {'cella':<20}" + "".join(f"{k:>16}" for k in ref_map))
|
||||
for n, s in to_score.items():
|
||||
line = f" {n:<20}"
|
||||
for _, r in ref_map.items():
|
||||
Jc = pd.concat({"a": pd.Series(s).dropna(), "b": r}, axis=1, join="inner").dropna()
|
||||
line += f"{(Jc['a'].corr(Jc['b']) if len(Jc) > 30 else float('nan')):>16.3f}"
|
||||
print(line)
|
||||
print(" ⚠ corr a SKH01/VRP01/GTAA01 NON misurata: SKH01 richiede i 5m di DUE asset e il brief")
|
||||
print(" vieta di caricarli insieme. Prior del progetto (25/07): XSR01 sta a |0.001|-|0.071|")
|
||||
print(" da tutti e 5 gli sleeve; un suo sottoinsieme non puo' esserne lontano.")
|
||||
|
||||
# ---------------------------------------------------------------- (9) null del de-levering
|
||||
print("\n" + "-" * 108)
|
||||
print(" (9) NULL DEL DE-LEVERING — obbligatorio su ogni confronto di maxDD (5 occorrenze note)")
|
||||
print(" Domanda: esiste una LEVA del canonico che dia lo STESSO maxDD della variante?")
|
||||
print(" Se a quel maxDD il canonico ha Sharpe >= alla variante, la variante non aggiunge.")
|
||||
print("-" * 108)
|
||||
def lev_for_dd(canon: pd.Series, target_abs: float, lo=0.01, hi=5.0):
|
||||
"""Leva k tale che |maxDD(canon*k)| = target. |maxDD| e' monotono crescente in k."""
|
||||
if abs(dd(canon * hi)) < target_abs:
|
||||
return None # nemmeno a leva 5x si arriva al DD
|
||||
for _ in range(60):
|
||||
mid = 0.5 * (lo + hi)
|
||||
if abs(dd(canon * mid)) < target_abs: lo = mid
|
||||
else: hi = mid
|
||||
return 0.5 * (lo + hi)
|
||||
|
||||
def delever_null(variants: dict, canon: pd.Series, label: str):
|
||||
print(f" {label:<22}{'maxDD':>9}{'Sh':>8} {'k_leva canonico':>16}{'Sh canonico':>13} esito")
|
||||
for nm, s in variants.items():
|
||||
kl = lev_for_dd(canon, abs(dd(s)))
|
||||
if kl is None:
|
||||
print(f" {nm:<22}{dd(s)*100:>8.1f}%{sh(s):>8.2f} {'n.d. (>5x)':>16}"
|
||||
f"{'':>13} maxDD irraggiungibile levando il canonico")
|
||||
continue
|
||||
sc_ = sh(canon * kl)
|
||||
print(f" {nm:<22}{dd(s)*100:>8.1f}%{sh(s):>8.2f} {kl:>16.3f}{sc_:>13.2f} "
|
||||
f"{'il canonico DOMINA' if sc_ >= sh(s) else 'la variante REGGE'}")
|
||||
delever_null({n: xs_series[n] for n in xs_series if n.startswith("MAJ19") and "k=5" not in n},
|
||||
xs_series["MAJ19 k=5*"], "XS-LITE vs canonico")
|
||||
print()
|
||||
delever_null({n: xsr_series[n] for n in xsr_series if n.startswith("ALL50") and "pieno" not in n},
|
||||
xsr_series["ALL50 k=pieno*"], "XSR-LITE vs pieno")
|
||||
|
||||
# ---------------------------------------------------------------- (10) implausible + causalita'
|
||||
print("\n" + "-" * 108)
|
||||
print(" (10) implausible_sharpe + CAUSALITA' (prefisso troncato + decadimento con lag)")
|
||||
print("-" * 108)
|
||||
for n in dict.fromkeys([best_name, cbest_name, "XS MAJ19 k=2", "XS MAJ19 k=5*",
|
||||
"XSR ALL50 k=2"]):
|
||||
if n not in allcells:
|
||||
continue
|
||||
ss = pd.Series(allcells[n]).dropna(); ss.index = pd.to_datetime(ss.index, utc=True)
|
||||
r = A.implausible_sharpe(ss)
|
||||
print(f" {n:<20} implausible={str(r['implausible']):<6} Sh {r.get('sharpe',0):>5.2f} "
|
||||
f"perdite su barre ATTIVE {r.get('loss_frac',float('nan'))*100:>5.1f}% "
|
||||
f"attive {r.get('active_frac',0)*100:>3.0f}% {'; '.join(r['reasons'])[:38]}")
|
||||
cut = int(len(C19) * 0.85)
|
||||
Wf = xs_run(C19, k=2, phase=0)[1]
|
||||
Wp = xs_run(C19.iloc[:cut], k=2, phase=0)[1]
|
||||
dmax = float(np.max(np.abs(Wp[cut - 80:cut] - Wf[cut - 80:cut])))
|
||||
print(f" XS k=2: pesi ricalcolati su prefisso troncato (cut={cut}) vs pieno, ultime 80 barre: "
|
||||
f"max|diff| = {dmax:.2e} {'CAUSALE' if dmax < 1e-12 else 'LOOK-AHEAD'}")
|
||||
Wq = xs_run(C19, k=2, phase=0)
|
||||
Weff2 = xs_effective_weights(Wq[1], Wq[2])
|
||||
lags = []
|
||||
for lag in (0, 1, 2, 3, 5):
|
||||
Wl = np.zeros_like(Weff2)
|
||||
if lag: Wl[lag:] = Weff2[:-lag]
|
||||
else: Wl = Weff2
|
||||
s, _ = sim_book(Wl, Wq[3], C19.index)
|
||||
lags.append(sh(s))
|
||||
print(" XS k=2 decadimento con lag (0/1/2/3/5 giorni): "
|
||||
+ " / ".join(f"{v:.2f}" for v in lags)
|
||||
+ " (dolce = segnale lento, non firma di look-ahead)")
|
||||
|
||||
# ---------------------------------------------------------------- (11) sintesi
|
||||
print("\n" + "=" * 108)
|
||||
print(" SINTESI — quanto costa concentrare, e dove sta davvero il muro")
|
||||
print("=" * 108)
|
||||
k2, k5 = xs_series["MAJ19 k=2"], xs_series["MAJ19 k=5*"]
|
||||
print(f" XS-LITE k=2 (4 gambe): Sh fase0 {sh(k2):.2f} / fase-MEDIANA {phase_med[2]:.2f}"
|
||||
f" vs canonico k=5 (10 gambe): {sh(k5):.2f} / {phase_med[5]:.2f}")
|
||||
print(f" XSR-LITE k=2 (4 gambe): Sh {sh(xsr_series['ALL50 k=2']):.2f} "
|
||||
f"(IS {sh(split(xsr_series['ALL50 k=2'])[0]):.2f}) vs pieno "
|
||||
f"{sh_xsr_star:.2f} (IS {sh(split(xsr_series['ALL50 k=pieno*'])[0]):.2f})")
|
||||
def _be(x):
|
||||
return f"{x:.0f} bps" if x else f">{int(COST_SWEEP[-1])} bps"
|
||||
print(f" break-even di costo per gamba: XS k=2 {_be(be.get('XS MAJ19 k=2'))}, "
|
||||
f"XS k=5 {_be(be.get('XS MAJ19 k=5*'))}, XSR k=2 {_be(be.get('XSR ALL50 k=2'))}, "
|
||||
f"XSR pieno {_be(be.get('XSR ALL50 k=pieno*'))}")
|
||||
print("=" * 108)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user