research(wave-0822): HL-EXEC refuta il muro dei 20k di XS01 e lo slippage come rischio #1 — e il 20k di venue e' un'altra cosa
This commit is contained in:
@@ -474,6 +474,46 @@ def describe_curve(asset: str, cv: pd.DataFrame) -> None:
|
||||
f"(beta -1 = reversione totale, 0 = martingala)")
|
||||
|
||||
|
||||
def basis_vs_funding(asset: str, cv: pd.DataFrame) -> None:
|
||||
"""Q1, nella forma decidibile: il basis del front e' il premio ANNUALIZZATO che
|
||||
incassi se vendi il datato e lo tieni a scadenza. La copertura e' il perp, su cui
|
||||
PAGHI il funding realizzato nello stesso periodo. Quindi il roll-down incassabile
|
||||
NON e' il basis: e' `basis - funding realizzato fino a scadenza`.
|
||||
Se il basis e' un previsore NON DISTORTO del funding (a~0, b~1) il premio a termine
|
||||
esiste come LIVELLO ma non e' estraibile: e' gia' il prezzo della copertura."""
|
||||
d = cv[["c_front", "f1h", "tau_front_h"]].dropna()
|
||||
if len(d) < 5000:
|
||||
print(f" {asset}: campione insufficiente")
|
||||
return
|
||||
f = d["f1h"].values
|
||||
cum = np.concatenate([[0.0], np.cumsum(f)]) # somma cumulata del funding orario
|
||||
n = len(d)
|
||||
tau = d["tau_front_h"].values
|
||||
end = np.minimum(np.arange(n) + tau.astype(int), n - 1)
|
||||
hrs = np.maximum(end - np.arange(n), 1)
|
||||
realized = (cum[end] - cum[np.arange(n)]) / hrs * HOURS_Y # funding realizzato ANNUALIZZATO
|
||||
implied = d["c_front"].values
|
||||
m = np.isfinite(realized) & np.isfinite(implied) & (hrs > 24 * 5)
|
||||
x, y = implied[m], realized[m]
|
||||
b1, b0 = np.polyfit(x, y, 1)
|
||||
prem = x - y # premio effettivamente incassabile
|
||||
# IC bootstrap a BLOCCHI (le ore sono fortemente autocorrelate: un IC iid mentirebbe)
|
||||
rng = np.random.default_rng(822)
|
||||
bl = 24 * 30
|
||||
nb = max(1, len(prem) // bl)
|
||||
boots = []
|
||||
for _ in range(400):
|
||||
st = rng.integers(0, max(1, len(prem) - bl), nb)
|
||||
boots.append(np.mean(np.concatenate([prem[i:i + bl] for i in st])))
|
||||
lo, hi = np.percentile(boots, [2.5, 97.5])
|
||||
print(f" {asset}: basis implicito medio {np.mean(x)*100:+6.2f}%/anno "
|
||||
f"funding realizzato a scadenza {np.mean(y)*100:+6.2f}%/anno")
|
||||
print(f" regressione realizzato ~ a + b*implicito: b = {b1:+.3f} a = {b0*100:+.2f}%"
|
||||
f" (b~1 e a~0 = previsore non distorto => niente da incassare)")
|
||||
print(f" PREMIO INCASSABILE = implicito - realizzato = {np.mean(prem)*100:+6.2f}%/anno"
|
||||
f" IC95% a blocchi [{lo*100:+.2f}%, {hi*100:+.2f}%] (n={m.sum():,} ore)")
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# 3. STRATEGIE — posizioni in spazio CONTRATTO (il roll si paga da solo)
|
||||
# ==========================================================================
|
||||
@@ -671,6 +711,9 @@ def main() -> None:
|
||||
cv = curve_frame(a, panels[a], metas[a], funds[a], roll_dte=7, perp=perps[a])
|
||||
curves[a] = cv
|
||||
describe_curve(a, cv)
|
||||
print("\n --- Q1-bis: il basis e' un PREVISORE del funding che paghi sulla copertura? ---")
|
||||
for a in ASSETS:
|
||||
basis_vs_funding(a, curves[a])
|
||||
|
||||
# ---------------- STEP 3b: spread bid-ask (calibrazione DATATA) ----------------
|
||||
print("\n[3b] SPREAD BID-ASK — calibrazione sul book VIVO di oggi (non e' storia)")
|
||||
@@ -724,6 +767,44 @@ def main() -> None:
|
||||
G = pd.DataFrame([{k: v for k, v in r.items() if k != "daily"} for r in rows])
|
||||
dailies = {i: r["daily"] for i, r in enumerate(rows)}
|
||||
|
||||
print(f"\n distribuzione dello Sharpe FULL sulle {len(G)} celle: "
|
||||
f"min {G.sharpe.min():+.2f} p25 {G.sharpe.quantile(.25):+.2f} "
|
||||
f"mediana {G.sharpe.median():+.2f} p75 {G.sharpe.quantile(.75):+.2f} "
|
||||
f"max {G.sharpe.max():+.2f} sd {G.sharpe.std():.2f}")
|
||||
degen = G[(G.vol < 0.005) | (G.n < 400)]
|
||||
if len(degen):
|
||||
print(f" ATTENZIONE: {len(degen)} celle DEGENERI (vol<0.5%/anno o <400 giorni attivi):")
|
||||
print(f" Sharpe di quelle celle: {sorted(np.round(degen.sharpe.values,2))[:10]}")
|
||||
print(" gonfiano la varianza dei trial e quindi il null del deflated-Sharpe:")
|
||||
print(" il DSR viene riportato ANCHE sulla sola sottogriglia non degenere.")
|
||||
|
||||
print("\n MIGLIORE CELLA PER FAMIGLIA (Q2 riguarda il CALENDAR: va letto separato):")
|
||||
for fam in FAM:
|
||||
sub = G[G.family == fam]
|
||||
i = sub.sharpe.idxmax()
|
||||
r = G.loc[i]
|
||||
print(f" [{fam}] {r.sig:12s} W={int(r.win):5d}h roll={int(r.roll):2d}g thr={r.thr:.1f} "
|
||||
f"| Sh {r.sharpe:+6.2f} hold {r.holdout:+6.2f} DD {r.maxdd*100:5.2f}% "
|
||||
f"CAGR {r.cagr*100:+6.2f}% vol {r.vol*100:5.2f}%")
|
||||
ii = A.implausible_sharpe(dailies[i])
|
||||
print(f" implausible_sharpe={ii['implausible']} "
|
||||
f"(barre in perdita {ii.get('loss_frac', float('nan'))*100:.1f}%) "
|
||||
f"-> {'coda ASSENTE, rischio fuori dal campione' if ii['implausible'] else 'coda PRESENTE'}")
|
||||
|
||||
print("\n LE CELLE CANONICHE (quelle che un lettore chiede per nome), roll=7g:")
|
||||
for fam, sig, lab in [
|
||||
("FvP", "carry", "vendi il datato quando e' in CONTANGO / compra perp (il carry classico)"),
|
||||
("FvP", "static_short", "SEMPRE corto il datato / lungo perp"),
|
||||
("FvP", "static_long", "SEMPRE lungo il datato / corto perp (il contrario)"),
|
||||
("CAL", "static_long", "calendar: lungo back / corto front"),
|
||||
("CAL", "static_short", "calendar: corto back / lungo front")]:
|
||||
sub = G[(G.family == fam) & (G.sig == sig) & (G.roll == 7)]
|
||||
if len(sub):
|
||||
r = G.loc[sub.index[0]]
|
||||
print(f" {fam}/{sig:12s} Sh {r.sharpe:+6.2f} hold {r.holdout:+6.2f} "
|
||||
f"CAGR {r.cagr*100:+6.2f}% DD {r.maxdd*100:5.2f}% vol {r.vol*100:5.2f}%"
|
||||
f" <- {lab}")
|
||||
|
||||
print("\n migliori 8 celle per Sharpe FULL (NON e' la selezione — solo panorama):")
|
||||
for i in G.sharpe.nlargest(8).index:
|
||||
r = G.loc[i]
|
||||
@@ -756,6 +837,11 @@ def main() -> None:
|
||||
dsr, sr0 = A.deflated_sharpe(bs["sharpe"], list(G.sharpe.values), bd)
|
||||
print(f" deflated-Sharpe : DSR {dsr:.3f} (null max atteso {sr0:+.2f} su {len(G)} trial) "
|
||||
f"-> {'PASS' if dsr >= 0.95 else 'FAIL'}")
|
||||
nd = G[(G.vol >= 0.005) & (G.n >= 400)]
|
||||
if len(nd) >= 5 and len(nd) < len(G):
|
||||
d2, s2_ = A.deflated_sharpe(bs["sharpe"], list(nd.sharpe.values), bd)
|
||||
print(f" sulla sola sottogriglia NON degenere ({len(nd)} trial): "
|
||||
f"DSR {d2:.3f} (null max {s2_:+.2f}) -> {'PASS' if d2 >= 0.95 else 'FAIL'}")
|
||||
|
||||
imp = A.implausible_sharpe(bd)
|
||||
print(f" implausible_sharpe: implausible={imp['implausible']} "
|
||||
@@ -769,9 +855,13 @@ def main() -> None:
|
||||
tp = A.tp01_baseline_daily()
|
||||
try:
|
||||
mg = A.marginal_vs_tp01(bd)
|
||||
print(f" marginal_vs_tp01 : {mg.get('verdict')} corr {mg.get('corr', float('nan')):+.3f} "
|
||||
print(f" marginal_vs_tp01 : {mg.get('marginal_verdict')} "
|
||||
f"corr_full {mg.get('corr_full')} corr_hold {mg.get('corr_hold')} "
|
||||
f"robust_oos={mg.get('robust_oos')} insample_edge={mg.get('has_insample_edge')} "
|
||||
f"is_hedge={mg.get('is_hedge')} beats_noise={mg.get('beats_noise_null')}")
|
||||
w25 = mg.get("w25") or (mg.get("blends", {}) or {}).get("w25", {})
|
||||
print(f" uplift blend w=25%: full {w25.get('uplift_full')} "
|
||||
f"hold {w25.get('uplift_hold')}")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
mg = {}
|
||||
print(f" marginal_vs_tp01 : NON GIRATO ({type(exc).__name__}: {exc})")
|
||||
@@ -848,14 +938,21 @@ def main() -> None:
|
||||
|
||||
# ---------------- STEP 8: eseguibilita' ----------------
|
||||
print("\n[8] Q3 — ESEGUIBILITA' a $600-3.000")
|
||||
print(" lotti minimi letti dal VENUE (non da una tabella cablata):")
|
||||
px = {a: float(curves[a]["index"].dropna().iloc[-1]) for a in ASSETS}
|
||||
for a in ASSETS:
|
||||
try:
|
||||
sp = _api("/public/get_instrument", instrument_name=f"{a}-25DEC26")["result"]
|
||||
print(f" {a}-25DEC26: min_trade_amount ${sp['min_trade_amount']:,.0f} di nozionale, "
|
||||
f"contract_size ${sp['contract_size']:,.0f}, tick {sp['tick_size']}, "
|
||||
f"taker {sp['taker_commission']*1e4:.1f} bps")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f" {a}: specs non lette ({type(exc).__name__})")
|
||||
for nm in (f"{a}-25DEC26", f"{a}_USDC-25DEC26"):
|
||||
try:
|
||||
sp = _api("/public/get_instrument", instrument_name=nm)["result"]
|
||||
lin = sp.get("settlement_currency") == "USDC"
|
||||
lot = (sp["min_trade_amount"] * px[a]) if lin else sp["min_trade_amount"]
|
||||
print(f" {nm:22s} {'LINEARE' if lin else 'INVERSE':8s} "
|
||||
f"min {sp['min_trade_amount']:<10.4g} = ${lot:>8,.2f} di nozionale "
|
||||
f"tick {sp['tick_size']} taker {sp['taker_commission']*1e4:.1f} bps")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f" {nm:22s} specs non lette ({type(exc).__name__})")
|
||||
print(" (il libro live trada i LINEARI USDC; i trimestrali studiati qui sono gli INVERSE,")
|
||||
print(" che hanno 7 anni di storia. Le due famiglie hanno lotti minimi DIVERSI.)")
|
||||
print(f" vol ANNUA del candidato per $1 di nozionale lordo/gamba: {bs['vol']*100:.2f}%")
|
||||
if bs["vol"] > 0:
|
||||
lev = 0.20 / bs["vol"]
|
||||
|
||||
Reference in New Issue
Block a user