#!/usr/bin/env python """r0823_anchor_ensemble.py — §47 ANCHOR-ENSEMBLE: si puo' ESEGUIRE l'ensemble d'ancora? LA DOMANDA. Il progetto ha misurato con enorme cura la FORTUNA D'ANCORA (TP01 gira sull'ancora daily 00:00 UTC, la migliore delle 24; SKH01 sull'offset 0 di 23; XS01 fase 0 di 10; VRP01 fase 0 di 7) e ha sempre risposto DE-LUCKANDO IL NUMERO — mai cambiando il modo di eseguire. Qui si chiede l'altra cosa: invece di scegliere un'ancora e poi correggere la stima, si puo' dividere lo sleeve in N tranche, ognuna ribilanciata a un'ora diversa e ognuna a 1/N della size? PRECEDENTE DA SUPERARE (non ignorare). Il 2026-07-02 (`r0702_tp01_offset.py`, diario `2026-07-02-timing-crt-wave.md`) misuro' il tranching K=2/K=4 e concluse: *"sola riduzione della varianza della STIMA (dSharpe n.s., dDD ~0.5pt), NO deploy a $600 — a $225/asset di quota TP01 i delta per-ancora (~$1-2) sono sotto il min-order $5 -> degenererebbe comunque in K=1; serve feed intraday fuori path certificato. Rivalutare a >=5-10k."* Tre cose sono cambiate: (a) il FUNDING e' misurato (-2,16%/anno, 22/08) e il tranching cambia il profilo orario d'esposizione; (b) la banda d'ancora del LIBRO e' misurata sullo spazio congiunto (26/07); (c) il 22/08 ha misurato che il pavimento min-order non e' il vincolo che si credeva. E c'e' un quarto fatto, di CODICE, che nessuno aveva messo accanto alla conclusione del 02/07 — si veda §4: `src/live/book.build_book_order` applica `abs(delta) < min_usd -> None` sulla posizione **NETTA**, e manda **un solo ordine per asset per giro**. Il "delta per-ancora" non e' mai un ordine. La premessa del 02/07 descrive un'esecuzione che il libro non fa. COSA MISURA, nelle tre parti chieste: (i) l'ensemble batte l'ancora MEDIANA? di quanto su Sharpe E su DRIFT, e con che compressione di dispersione fra realizzazioni; (ii) l'ensemble batte l'ancora CANONICA che gira oggi? (attesa: NO in-sample) — e quale dei due numeri e' la stima onesta del FUTURO; (iii) a quale CAPITALE l'ensemble diventa eseguibile senza degenerare? curva N_max(capitale) con il pavimento VERO del venue, per $635 / $2k / $5k / $20k / $50k. piu': il costo (ordini/anno, fee/anno), il FUNDING (l'unico canale per cui l'ensemble potrebbe GUADAGNARE invece di ridurre varianza), SKH01 separatamente e insieme a TP01, la replica del 02/07 PRIMA di ogni delta, e il valore della riduzione di varianza SOTTO UNA BARRIERA ASSORBENTE (canale funded), che e' la domanda che resta se la risposta e' "non aggiunge ritorno". RIUSO (non riscrittura): `altlib` (eval_weights/_sh/_to_daily/_dd_ret/HOLDOUT), `r0702_tp01_offset` (resample_offset/hourly_target/smallcap_net — la replica e' like-for-like), `r0702_anchor_skh01` (run_asset/resample_off/OFFSETS), `r0822d_funding` (fetch/build_rate/cumF — il funding NON viene rimodellato), `src.live.deribit._CONTRACT` (il pavimento si DERIVA dal codice sorvegliato, non si ridichiara — regola `fee_watch` 21/08), `src.portfolio.portfolio.combine_outer`. VINCOLI: nessun file toccato fuori da questo script; nessuna rete (cache funding gia' su disco); niente commit di dati. Runtime ~5-8 min su 2 core. Uso: `nice -n 19 uv run python scripts/research/r0823_anchor_ensemble.py` """ from __future__ import annotations import sys import time from functools import lru_cache from itertools import product from pathlib import Path import numpy as np import pandas as pd ROOT = Path("/opt/docker/PythagorasGoal") for _p in ("scripts/research", "scripts/research/alt", ""): sys.path.insert(0, str(ROOT / _p)) import altlib as al # noqa: E402 import r0702_anchor_skh01 as r02 # noqa: E402 import r0702_tp01_offset as r07 # noqa: E402 import r0822d_funding as fd # noqa: E402 from src.live.deribit import _CONTRACT # noqa: E402 (il pavimento si DERIVA, non si ridichiara) from src.portfolio.portfolio import combine_outer # noqa: E402 fd.USE_NET = False # cache su disco: questo filone non tocca la rete ASSETS = ("BTC", "ETH") HOURS = tuple(range(24)) HOLDOUT = al.HOLDOUT FEE = al.FEE_SIDE # 0.05%/lato = 0.10% RT MSH = 3_600_000 # Il libro live: `src/live/book.py` W_TP01=0.75, W_SKH=0.25, WEIGHT=0.5 (50/50 BTC+ETH). W_TP, W_SKH, W_ASSET = 0.75, 0.25, 0.5 MIN_ORDER_USD = 5.0 # config/live.json min_order_usd (letto sotto) CAPITALI = (635.0, 2000.0, 5000.0, 20000.0, 50000.0) # famiglie di tranching a priori, simmetriche, zero tuning (stessa logica del 02/07) FAM_TP = {1: 24, 2: 12, 3: 8, 4: 6, 6: 4, 8: 3, 12: 2, 24: 1} # K -> n. rotazioni T0 = time.time() def sez(t: str) -> None: print("\n" + "=" * 100) print(f" {t}") print("=" * 100) def clock() -> str: return f"[{time.time() - T0:5.0f}s]" def fam_rotations(K: int) -> list[tuple]: """Le TUTTE rotazioni della famiglia K (nessuna selezione): (s, s+24/K, s+2*24/K, ...).""" step = 24 // K return [tuple((s + step * j) % 24 for j in range(K)) for s in range(step)] # =========================================================================== # §0 — ATTESE A PRIORI (scritte e stampate PRIMA di qualunque misura) # =========================================================================== ATTESE = [ ("A1", "ALGEBRA: la posizione dell'ensemble e' la MEDIA delle posizioni delle tranche, e il " "lordo orario e' lineare nella posizione -> lordo_ens == media dei lordi, ESATTAMENTE " "(max|dif| = 0). Il turnover invece e' SUB-additivo (|media dei delta| <= media dei " "|delta|) -> turnover_ens <= turnover MEDIO delle ancore. Quindi l'ensemble domina " "debolmente l'ancora MEDIA su drift E su vol: non e' 'solo' riduzione di varianza, ma " "il pezzo di drift e' solo fee-netting, cioe' briciole."), ("A2", "(i) l'ensemble batte l'ancora MEDIANA su Sharpe. Quasi per costruzione: stesso drift, " "meno vol. Attesa dSharpe FULL fra +0,03 e +0,15; dDRIFT ~ 0 (<0,3pp)."), ("A3", "(ii) l'ensemble NON batte l'ancora CANONICA sull'hold-out. La canonica sta al ~92-96° " "pctl delle 24: una media non batte il massimo. Attesa: dSharpe HOLD NEGATIVO. " "E la stima onesta del futuro e' comunque l'ensemble, non la canonica."), ("A4", "FUNDING: l'ensemble NON puo' guadagnare un centesimo. Il funding pagato in un'ora e' " "pos*f, ESATTAMENTE lineare in pos -> funding_ens == media dei funding delle ancore, " "max|dif| = 0. Il rapporto condizionale mean(pos*f)/(mean(pos)*mean(f)) — misurato " "1,86-2,55x il 22/08 — e' esso stesso una media e NON si attenua: la correlazione " "esposizione-funding vive alla scala del REGIME, non dell'ora del giorno. " "Il canale sperato e' chiuso per ALGEBRA, non per piccolezza dell'effetto."), ("A5", "(iii) ESEGUIBILITA': gli ordini NON sono K volte tanti. `build_book_order` banda la " "posizione NETTA e manda un ordine per asset per giro -> il numero di ordini e' " "limitato dalla BANDA, non da K. La premessa del 02/07 ('i delta per-ancora sono sotto " "il min-order') descrive un'esecuzione che il libro non fa. MA la conclusione del 02/07 " "sopravvivera' comunque, per un'altra strada: a $635 la banda mangia gia' K=1 (il " "ribilancio MEDIANO di TP01 e' sotto $5), quindi non c'e' K da degradare. " "Attesa: N_max($635) = 1, e N_max cresce con il capitale."), ("A6", "SKH01: peggiore rapporto costo/beneficio (4x piu' fee-sensibile, 22/08) e " "690/30 = 23 e' PRIMO -> sulla griglia a 30m dichiarata il 02/07 non esiste alcun " "sotto-ensemble simmetrico (o 1 o 23). Le famiglie simmetriche esistono solo sulla " "griglia 5m (138 = 2*3*23 -> K in {2,3,6,23})."), ("A7", "BARRIERA: la riduzione di varianza vale poco su un conto proprio (dove il nemico e' il " "drift) e MOLTO su un conto a barriera assorbente. Attesa: P(pass) dell'ensemble >= " "mediana delle P(pass) per ancora, e soprattutto SPARISCE la lotteria (la banda di " "P(pass) fra ancore si comprime). E il conto funded e' a $100k, dove la banda del " "punto (iii) NON morde: il posto dove l'ensemble e' inutile e quello dove e' " "ineseguibile sono lo STESSO posto, e sono entrambi il conto da $635."), ] def sezione0() -> None: sez("§0 — ATTESE A PRIORI (dichiarate PRIMA di misurare; il verdetto su ciascuna e' in §9)") for k, t in ATTESE: print(f" [{k}] {t}\n") print(" Criterio N_max DICHIARATO ORA, prima di guardare i numeri (§4):") print(" l'ensemble serve a UNA cosa: comprimere la dispersione fra realizzazioni. Quindi") print(" retention(K,C) = [sd_band(1,C) - sd_band(K,C)] / [sd_ideale(1) - sd_ideale(K)]") print(" con sd = deviazione standard dello Sharpe FULL fra TUTTE le rotazioni della") print(" famiglia K (nessuna selezione), banda = pavimento vero del venue a capitale C.") print(" N_max(C) = il massimo K in {2,3,4,6,8,12} con retention >= 0.50.") print(" K=24 ha UNA sola rotazione -> riportato a parte, fuori dal criterio.") # =========================================================================== # PRIMITIVE — target orari, ensemble, valutazione ideale e BANDATA # =========================================================================== @lru_cache(maxsize=8) def px(asset: str) -> np.ndarray: return r07.get1h(asset)["close"].values.astype(float) @lru_cache(maxsize=8) def ret(asset: str) -> np.ndarray: return al.simple_returns(px(asset)) @lru_cache(maxsize=8) def idx1h(asset: str) -> pd.DatetimeIndex: return pd.DatetimeIndex(pd.to_datetime(r07.get1h(asset)["datetime"], utc=True)) @lru_cache(maxsize=256) def tgt(asset: str, hs: tuple) -> np.ndarray: """Target dell'ensemble sulle ancore `hs`: media dei target orari delle tranche. `r07.hourly_target` e' RIUSATA (merge_asof backward su epoca ms esplicita).""" return np.mean([np.asarray(r07.hourly_target(asset, h)) for h in hs], axis=0) def eval_ideal(asset: str, hs: tuple) -> dict: """Valutazione IDEALE (nessun pavimento): riusa al.eval_weights, poi decompone.""" df = r07.get1h(asset) ev = al.eval_weights(df, tgt(asset, hs), fee_side=FEE) t = tgt(asset, hs) pos = np.zeros(len(t)); pos[1:] = t[:-1] gross = pos * ret(asset) turn = np.abs(np.diff(pos, prepend=0.0)) net = gross - FEE * turn; net[0] = 0.0 assert np.max(np.abs(net - ev["net"])) < 1e-15, "decomposizione != eval_weights" return dict(pos=pos, gross=gross, turn=turn, net=net, idx=ev["idx"], turn_y=ev["turnover_per_year"]) @lru_cache(maxsize=256) def book_ideal(hs: tuple) -> dict: """Book 50/50 BTC+ETH IDEALE sull'orario: serie oraria + daily + turnover.""" E = {a: eval_ideal(a, hs) for a in ASSETS} S = {a: pd.Series(E[a]["net"], index=E[a]["idx"]) for a in ASSETS} G = {a: pd.Series(E[a]["gross"], index=E[a]["idx"]) for a in ASSETS} P = {a: pd.Series(E[a]["pos"], index=E[a]["idx"]) for a in ASSETS} J = pd.concat(S, axis=1, join="inner").fillna(0.0) JG = pd.concat(G, axis=1, join="inner").fillna(0.0) JP = pd.concat(P, axis=1, join="inner").fillna(0.0) h_net = 0.5 * J[ASSETS[0]] + 0.5 * J[ASSETS[1]] h_gross = 0.5 * JG[ASSETS[0]] + 0.5 * JG[ASSETS[1]] return dict(h_net=h_net, h_gross=h_gross, pos=JP, daily=al._to_daily(h_net), turn_y=sum(0.5 * E[a]["turn_y"] for a in ASSETS)) def banded(prices: np.ndarray, target: np.ndarray, capital: float, min_usd: float = MIN_ORDER_USD, step: float | None = None) -> dict: """Path ESEGUITO sotto il pavimento del venue. Modella `src/live/book.build_book_order` + `deribit.notional_to_amount`: - `abs(delta) < min_usd` -> nessun ordine (banda sul NOZIONALE, non sulla frazione); - se `step` e' dato (contract step del perp lineare, in base-coin) il delta eseguito viene quantizzato allo step e CLAMPATO al minimo -> la posizione atterra su una griglia. `target[i]` e' la frazione di `capital` desiderata alla barra i (stessa convenzione di `r07.smallcap_net`, contro cui questa funzione e' validata bit-exact in §1). Ottimizzazione: la condizione puo' scattare solo dove il target CAMBIA (fra due cambi il target e' costante, quindi se non e' scattata non scattera'). Si itera sui punti di cambio. """ n = len(target) chg = np.empty(n, dtype=bool) chg[0] = True chg[1:] = target[1:] != target[:-1] ci = np.flatnonzero(chg) vals = np.empty(len(ci)) cur = 0.0 n_ord = 0 notion = 0.0 for k in range(len(ci)): i = ci[k] d = (target[i] - cur) * capital if abs(d) >= min_usd: if step is None: cur = target[i] notion += abs(d) else: u = abs(d) / prices[i] q = np.round(u / step) * step if q < step: q = step ex = np.sign(d) * q * prices[i] cur = cur + ex / capital notion += abs(ex) n_ord += 1 vals[k] = cur held = np.repeat(vals, np.diff(np.append(ci, n))) return dict(held=held, n_ord=n_ord, notional=notion, n_chg=len(ci)) def eval_banded(asset: str, hs: tuple, capital: float, step: float | None = None) -> dict: """Book di UN asset sotto banda. `capital` = capitale ALLOCATO A QUELL'ASSET.""" b = banded(px(asset), tgt(asset, hs), capital, MIN_ORDER_USD, step) held = b["held"] pos = np.zeros(len(held)); pos[1:] = held[:-1] turn = np.abs(np.diff(pos, prepend=0.0)) net = pos * ret(asset) - FEE * turn net[0] = 0.0 return dict(net=net, pos=pos, turn=turn, n_ord=b["n_ord"], n_chg=b["n_chg"], notional=b["notional"], idx=idx1h(asset)) def book_banded(hs: tuple, capital: float, step_by_asset: dict | None = None) -> dict: """Book 50/50 sotto banda. `capital` = capitale TOTALE del book TP01 (meta' per asset).""" E = {} for a in ASSETS: st = step_by_asset.get(a) if step_by_asset else None E[a] = eval_banded(a, hs, capital * 0.5, st) S = {a: pd.Series(E[a]["net"], index=E[a]["idx"]) for a in ASSETS} J = pd.concat(S, axis=1, join="inner").fillna(0.0) h = 0.5 * J[ASSETS[0]] + 0.5 * J[ASSETS[1]] yrs = (h.index[-1] - h.index[0]) / pd.Timedelta("365.25D") return dict(daily=al._to_daily(h), n_ord=sum(E[a]["n_ord"] for a in ASSETS), ord_y=sum(E[a]["n_ord"] for a in ASSETS) / yrs, fee_y=sum(FEE * E[a]["turn"].sum() for a in ASSETS) / 2 / yrs, notional_y=sum(E[a]["notional"] for a in ASSETS) / yrs, E=E) def drift(s: pd.Series) -> float: """Drift annuo geometrico (CAGR) di una serie di ritorni giornalieri.""" v = np.asarray(s.dropna().values, float) eq = float(np.prod(1.0 + v)) yrs = len(v) / 365.25 return eq ** (1 / yrs) - 1 if eq > 0 and yrs > 0 else -1.0 def vol(s: pd.Series) -> float: return float(np.std(np.asarray(s.dropna().values, float)) * np.sqrt(365.25)) def m3(s: pd.Series) -> tuple: return (al._sh(s), al._sh(s[s.index < HOLDOUT]), al._sh(s[s.index >= HOLDOUT])) def pctl_of(v: np.ndarray, x: float) -> float: return float((v < x).mean() + 0.5 * (v == x).mean()) * 100 # =========================================================================== # §1 — REPLICA. Nessun delta prima che questa sezione passi. # =========================================================================== def sezione1() -> dict: sez("§1 — REPLICA DI CONTROLLO (il 02/07 PRIMA di ogni delta) + pavimento vero del venue") r07.sanity_h0() r07.causality_guards() # prefix-recompute daily + troncamento 1h, per TUTTE le ancore # (a) la funzione `banded` di questo script contro `r07.smallcap_net` (codice del 02/07) print("\n (a) `banded()` di questo script vs `r07.smallcap_net` del 02/07 (bit-exact):") for a in ASSETS: df = r07.get1h(a) t = 0.5 * tgt(a, (0,)) ref, ntr = r07.smallcap_net(df, t, 600.0) b = banded(px(a), t, 600.0, 5.0, None) held = b["held"] pos = np.zeros(len(held)); pos[1:] = held[:-1] net = pos * ret(a) - FEE * np.abs(np.diff(pos, prepend=0.0)); net[0] = 0.0 d = float(np.max(np.abs(net - ref.values))) assert d == 0.0, f"banded != smallcap_net su {a}: {d:.3e}" assert b["n_ord"] == ntr, f"n_ordini {b['n_ord']} != {ntr}" print(f" {a}: max|dif| = {d:.1e} ordini {b['n_ord']} == {ntr} OK " f"(punti di cambio del target: {b['n_chg']:,} -> la compressione e' esatta)") # (b) K=4 == EW di 4 book ancorati (il claim "diff 1.4e-17" del 02/07) K4 = (0, 6, 12, 18) ens = book_ideal(K4)["daily"] ew = sum(book_ideal((h,))["h_net"] for h in K4) / 4.0 ew_d = al._to_daily(ew) J = pd.concat({"e": ens, "w": ew_d}, axis=1, join="inner") d = float(np.max(np.abs(J["e"] - J["w"]))) print(f"\n (b) K=4 == EW di 4 book ancorati (claim 02/07 'diff 1.4e-17'):") print(f" sull'ORARIO la posizione e' identica per costruzione; sul DAILY il compounding") print(f" intra-giorno non e' lineare -> max|dif| = {d:.2e} su {len(J):,} giorni.") # (c) i numeri del 02/07, con la deriva del dato dichiarata singles = {h: book_ideal((h,))["daily"] for h in HOURS} shf = np.array([al._sh(singles[h]) for h in HOURS]) shi = np.array([al._sh(singles[h][singles[h].index < HOLDOUT]) for h in HOURS]) shh = np.array([al._sh(singles[h][singles[h].index >= HOLDOUT]) for h in HOURS]) dd = np.array([al._dd_ret(singles[h]) for h in HOURS]) e4 = book_ideal(K4)["daily"] rot4 = [book_ideal(hs)["daily"] for hs in fam_rotations(4)] h4 = np.array([al._sh(s[s.index >= HOLDOUT]) for s in rot4]) print(f"\n (c) i numeri pubblicati il 02/07, ri-misurati oggi (il dato e' cresciuto di ~7") print(f" settimane e `data/raw/` non e' versionato — la deriva si dichiara, non si nasconde):") print(f" {'grandezza':<44} {'02/07':>16} {'oggi':>16}") print(f" {'HOLD mediano fra le 24 ancore':<44} {'0.04':>16} {np.median(shh):>16.3f}") print(f" {'HOLD banda fra le 24 ancore':<44} {'[-0.13,+0.30]':>16} " f"{'[%+.2f,%+.2f]' % (shh.min(), shh.max()):>16}") print(f" {'HOLD canonico h=0':<44} {'0.31 (98 pctl)':>16} " f"{'%.3f (%.0f pctl)' % (shh[0], pctl_of(shh, shh[0])):>16}") print(f" {'IS h=0 -> K=4':<44} {'1.49 -> 1.56':>16} " f"{'%.2f -> %.2f' % (shi[0], al._sh(e4[e4.index < HOLDOUT])):>16}") print(f" {'HOLD banda a K=4 (6 rotazioni)':<44} {'[-0.01,+0.13]':>16} " f"{'[%+.2f,%+.2f]' % (h4.min(), h4.max()):>16}") print(f" {'maxDD mediano fra le singole':<44} {'12.6%':>16} {np.median(dd):>15.1%}") print(f" {'turnover/anno del book':<44} {'~8.3':>16} " f"{book_ideal(K4)['turn_y']:>16.1f}") pk = float((h4[:, None] <= shh[0]).mean()) print(f" {'P(K=4 <= h=0) sull hold-out':<44} {'0.18 n.s.':>16} {pk:>16.2f}") print(" -> la STRUTTURA replica (K=4 == EW, turnover invariato, banda che si comprime);") print(" i LIVELLI si sono mossi col dato. E' la deriva attesa, non un disaccordo.") # (d) il pavimento VERO del venue, derivato dal codice di produzione print("\n (d) IL PAVIMENTO, derivato da `src/live/book` + `src/live/deribit` (non ridichiarato):") import json as _json cfg = _json.loads((ROOT / "config" / "live.json").read_text()) mo = float(cfg["min_order_usd"]) assert mo == MIN_ORDER_USD, f"config min_order_usd={mo} != {MIN_ORDER_USD} dichiarato qui" steps = {} for a in ASSETS: inst = f"{a}_USDC-PERPETUAL" steps[a] = float(_CONTRACT[inst]["step"]) p_now = px(a)[-1] print(f" {inst:<22} step {steps[a]:g} {a} -> a ${p_now:,.0f} il lotto vale " f"${steps[a]*p_now:6.2f} (banda config ${mo:.0f})") print(f" -> pavimento effettivo per ordine = max($5, quantizzazione al lotto).") print(f" Su BTC il LOTTO e' piu' grosso della banda: e' lui a comandare.") print(f" ⚠️ `build_book_order` banda `abs(delta)` sulla posizione NETTA e manda UN ordine") print(f" per asset per giro. Questo e' il fatto che riapre la domanda del 02/07 (§4).") print(f" {clock()}") return dict(singles=singles, shf=shf, shi=shi, shh=shh, dd=dd, steps=steps) # =========================================================================== # §2 — (i) l'ensemble contro l'ancora MEDIANA # =========================================================================== def sezione2(S1: dict) -> dict: sez("§2 — (i) L'ENSEMBLE CONTRO L'ANCORA MEDIANA (e l'algebra che lo governa)") # (a) il teorema A1, verificato numericamente sull'ORARIO print(" (a) [A1] verifica ALGEBRICA sull'orario (non un'approssimazione: un'identita'):") for a in ASSETS: E24 = eval_ideal(a, HOURS) g_mean = np.mean([eval_ideal(a, (h,))["gross"] for h in HOURS], axis=0) t_mean = np.mean([eval_ideal(a, (h,))["turn"] for h in HOURS], axis=0) dg = float(np.max(np.abs(E24["gross"] - g_mean))) te, tm = float(E24["turn"].sum()), float(t_mean.sum()) print(f" {a}: max|lordo_ens - media(lordi)| = {dg:.2e} -> IDENTITA'") print(f" turnover TOTALE ensemble {te:.4f} vs MEDIA delle ancore {tm:.4f} " f"-> sub-additivita' {'OK' if te <= tm + 1e-9 else 'VIOLATA'} " f"({te/tm-1:+.3%})") # ⚠️ ERRORE CATTURATO IN SESSIONE: la prima stesura confrontava `turnover_per_year`, che # `al.eval_weights` ARROTONDA a 1 decimale -> stampava "9.40 vs 9.38" accanto a una # disuguaglianza che il conto esatto rispetta. Una quantita' arrotondata non si mette # accanto a una disuguaglianza stretta: sembra violata proprio dove e' piu' stretta. print(" => l'ensemble NON e' una scommessa diversa: e' la STESSA scommessa, spalmata.") print(" Il drift non puo' cambiare se non per il fee-netting; la vol si', perche' la") print(" media di N serie correlate ma non identiche ha meno varianza di ciascuna.") # (b) la tabella delle famiglie print("\n (b) FAMIGLIE (tutte le rotazioni, nessuna selezione; book 50/50 ideale):") print(f" {'K':>3} {'rot':>4} {'ShFULL med':>11} {'sd':>6} {'ShHOLD med':>11} {'sd':>6} " f"{'drift med':>10} {'vol med':>8} {'maxDD med':>10} {'turn/y':>7}") fam = {} for K, nrot in FAM_TP.items(): rots = fam_rotations(K) assert len(rots) == nrot D = [book_ideal(hs) for hs in rots] f = np.array([al._sh(d["daily"]) for d in D]) h = np.array([al._sh(d["daily"][d["daily"].index >= HOLDOUT]) for d in D]) dr = np.array([drift(d["daily"]) for d in D]) vo = np.array([vol(d["daily"]) for d in D]) ddv = np.array([al._dd_ret(d["daily"]) for d in D]) ty = np.array([d["turn_y"] for d in D]) fam[K] = dict(f=f, h=h, dr=dr, vo=vo, dd=ddv, ty=ty, rots=rots) print(f" {K:>3} {nrot:>4} {np.median(f):>11.3f} {f.std():>6.3f} " f"{np.median(h):>11.3f} {h.std():>6.3f} {np.median(dr):>9.2%} " f"{np.median(vo):>7.2%} {np.median(ddv):>9.1%} {np.mean(ty):>7.1f}") # (c) il confronto che risponde a (i): ensemble pieno vs ancora mediana e24 = book_ideal(HOURS)["daily"] med_h = int(np.argsort(S1["shf"])[len(S1["shf"]) // 2]) med = S1["singles"][med_h] print(f"\n (c) RISPOSTA A (i) — ensemble K=24 contro l'ancora MEDIANA (h={med_h}, la mediana") print(f" per Sharpe FULL fra le 24) e contro la MEDIA delle 24:") print(f" {'':<26} {'ShFULL':>8} {'ShIS':>8} {'ShHOLD':>8} {'drift':>8} {'vol':>8} {'maxDD':>8}") for lbl, s in (("ancora mediana", med), ("ensemble K=24", e24)): a, b, c = m3(s) print(f" {lbl:<26} {a:>8.3f} {b:>8.3f} {c:>8.3f} {drift(s):>7.2%} " f"{vol(s):>7.2%} {al._dd_ret(s):>7.1%}") da, db, dc = m3(e24) ma, mb, mc = m3(med) print(f" {'DELTA (ens - mediana)':<26} {da-ma:>+8.3f} {db-mb:>+8.3f} {dc-mc:>+8.3f} " f"{drift(e24)-drift(med):>+7.2%} {vol(e24)-vol(med):>+7.2%} " f"{al._dd_ret(e24)-al._dd_ret(med):>+7.1%}") f1 = fam[1] print(f"\n Scomposizione del guadagno (ensemble vs MEDIA delle 24, che e' il confronto") print(f" algebricamente pulito): drift {drift(e24):.2%} vs media ancore " f"{np.mean(f1['dr']):.2%} (delta {drift(e24)-np.mean(f1['dr']):+.2%} = fee-netting);") print(f" vol {vol(e24):.2%} vs media ancore {np.mean(f1['vo']):.2%} " f"({vol(e24)/np.mean(f1['vo'])-1:+.1%}) <- QUI sta tutto il guadagno di Sharpe.") print(f"\n COMPRESSIONE DELLA DISPERSIONE (e' questo che l'ensemble compra):") print(f" {'K':>3} {'sd(ShFULL)':>11} {'sd(ShHOLD)':>11} {'banda ShHOLD':>18} " f"{'sd/sd(K=1)':>11}") for K in FAM_TP: d = fam[K] print(f" {K:>3} {d['f'].std():>11.3f} {d['h'].std():>11.3f} " f"{'[%+.2f,%+.2f]' % (d['h'].min(), d['h'].max()):>18} " f"{(d['h'].std()/fam[1]['h'].std() if fam[1]['h'].std() else 0):>11.2f}") print(f" (a K=24 la dispersione e' ZERO per definizione: c'e' una sola realizzazione —") print(f" ed e' esattamente il punto: l'ensemble non riduce l'incertezza, la ELIMINA.)") print(f" {clock()}") return dict(fam=fam, e24=e24, med_h=med_h) # =========================================================================== # §3 — (ii) l'ensemble contro l'ancora CANONICA che gira oggi # =========================================================================== def sezione3(S1: dict, S2: dict) -> None: sez("§3 — (ii) L'ENSEMBLE CONTRO L'ANCORA CANONICA (h=0 UTC, quella che gira con soldi veri)") can = S1["singles"][0] e24 = S2["e24"] print(f" {'':<26} {'ShFULL':>8} {'ShIS':>8} {'ShHOLD':>8} {'drift':>8} {'vol':>8} {'maxDD':>8}") for lbl, s in (("CANONICA h=0 (oggi)", can), ("ensemble K=24", e24)): a, b, c = m3(s) print(f" {lbl:<26} {a:>8.3f} {b:>8.3f} {c:>8.3f} {drift(s):>7.2%} " f"{vol(s):>7.2%} {al._dd_ret(s):>7.1%}") ca, cb, cc = m3(can); ea, eb, ec = m3(e24) print(f" {'DELTA (ens - canonica)':<26} {ea-ca:>+8.3f} {eb-cb:>+8.3f} {ec-cc:>+8.3f} " f"{drift(e24)-drift(can):>+7.2%} {vol(e24)-vol(can):>+7.2%} " f"{al._dd_ret(e24)-al._dd_ret(can):>+7.1%}") print(f"\n Percentile della CANONICA fra le 24 ancore: FULL {pctl_of(S1['shf'], S1['shf'][0]):.0f}°" f" IS {pctl_of(S1['shi'], S1['shi'][0]):.0f}°" f" HOLD {pctl_of(S1['shh'], S1['shh'][0]):.0f}°" f" maxDD {pctl_of(S1['dd'], S1['dd'][0]):.0f}°") print(f" Percentile dell'ENSEMBLE fra le 24: FULL {pctl_of(S1['shf'], ea):.0f}°" f" IS {pctl_of(S1['shi'], eb):.0f}° HOLD {pctl_of(S1['shh'], ec):.0f}°") print("\n 📌 LA LETTURA, ed e' il punto di tutto il filone:") print(" i due numeri NON sono due stime della stessa cosa. Il numero della canonica e' il") print(" risultato di UNA estrazione gia' avvenuta e gia' nota; quello dell'ensemble e' cio'") print(" che si ottiene SENZA estrarre. Chi confronta 'canonica vs ensemble' in-sample sta") print(" confrontando il massimo osservato con la media, e la media perde sempre.") print(" La domanda decidibile e' un'altra: DOMANI, quale dei due si otterra'?") print(f" - tenendo la canonica: una estrazione dalla banda [{S1['shh'].min():+.2f}," f"{S1['shh'].max():+.2f}] di hold-out, con mediana {np.median(S1['shh']):+.2f}" f" — il {pctl_of(S1['shh'], S1['shh'][0]):.0f}° pctl di ieri non si eredita;") print(f" - tenendo l'ensemble: {ec:+.2f}, senza estrazione.") print(" Cioe': l'ensemble non compra ritorno, compra il fatto di sapere gia' cosa avrai.") print(" E' la stessa cosa che il 26/07 disse del limit-TP di SKH01: *converte una lotteria") print(" in certezza piu' che aggiungere ritorno*.") print(f" {clock()}") # =========================================================================== # §4 — (iii) ESEGUIBILITA': la curva N_max(capitale) # =========================================================================== def sezione4(S1: dict, S2: dict) -> dict: sez("§4 — (iii) A QUALE CAPITALE L'ENSEMBLE DIVENTA ESEGUIBILE (pavimento vero del venue)") print(" (a) LA TAGLIA DEGLI ORDINI. Il fatto strutturale prima dei numeri:") print(" il tranching NON moltiplica gli ordini per K. `build_book_order` banda la") print(" posizione NETTA e ne manda UNO per asset per giro: K cambia la FORMA del") print(" sentiero di target, non il numero di ordini. Il numero di ordini lo decide la BANDA.") print(f"\n {'K':>3} {'cambi di target/anno':>21} {'|dNozionale| mediano a $635':>28} " f"{'p75':>10} {'quota sopra $5':>15}") capass = 635.0 * 0.5 for K in (1, 2, 4, 12, 24): hs = fam_rotations(K)[0] n_chg = 0; alld = [] for a in ASSETS: t = tgt(a, hs) d = np.abs(np.diff(t, prepend=0.0)) * capass d = d[d > 1e-12] n_chg += len(d); alld.append(d) D = np.concatenate(alld) yrs = (idx1h("BTC")[-1] - idx1h("BTC")[0]) / pd.Timedelta("365.25D") print(f" {K:>3} {n_chg/yrs:>21,.0f} {np.median(D):>27.2f}$ {np.percentile(D,75):>9.2f}$ " f"{float((D >= MIN_ORDER_USD).mean()):>14.1%}") print(f"\n ⚠️ IL FATTO CHE DECIDE, e non e' su K: a $635 la quota TP01 di UN asset e'") print(f" ${capass:.0f}, l'esposizione media di TP01 e' ~{np.mean(tgt('BTC',(0,))):.3f}x ->") print(f" il nozionale TENUTO e' ~${capass*np.mean(tgt('BTC',(0,))):.0f}. La banda da $5 e'") print(f" il {5/(capass*np.mean(tgt('BTC',(0,)))):.0%} della posizione, e il lotto BTC " f"(${S1['steps']['BTC']*px('BTC')[-1]:.1f}) e' il " f"{S1['steps']['BTC']*px('BTC')[-1]/(capass*np.mean(tgt('BTC',(0,)))):.0%}.") print(f" Gia' K=1 esegue solo una minoranza dei propri ribilanci. Non c'e' K da degradare.") # (b) curva bandata print("\n (b) IL PATH ESEGUITO, per capitale (banda $5 + quantizzazione al lotto del venue):") print(f" {'cap':>8} {'K':>3} {'ShFULL':>8} {'haircut':>8} {'ordini/anno':>12} " f"{'fee $/anno':>11} {'corr(eseguito,ideale)':>22}") band = {} for C in CAPITALI: for K in (1, 2, 4, 24): hs = fam_rotations(K)[0] bb = book_banded(hs, C, S1["steps"]) ii = book_ideal(hs)["daily"] cr = [] for a in ASSETS: cr.append(float(np.corrcoef(bb["E"][a]["pos"], tgt(a, hs))[0, 1])) band[(C, K)] = dict(sh=al._sh(bb["daily"]), hc=al._sh(ii) - al._sh(bb["daily"]), ord_y=bb["ord_y"], fee_y=bb["fee_y"] * C, corr=float(np.mean(cr)), daily=bb["daily"]) b = band[(C, K)] print(f" {C:>8,.0f} {K:>3} {b['sh']:>8.3f} {b['hc']:>+8.3f} {b['ord_y']:>12.0f} " f"{b['fee_y']:>11.2f} {b['corr']:>22.4f}") print(" (haircut = Sharpe ideale - Sharpe eseguito; fee $/anno = fee proporzionale sul") print(" nozionale realmente scambiato, NON un costo per ordine — su Deribit non esiste)") # (c) il criterio N_max dichiarato in §0 print("\n (c) N_max(capitale) — criterio DICHIARATO IN §0, applicato senza ritocchi:") ideal_sd = {K: S2["fam"][K]["f"].std() for K in FAM_TP} passed: dict = {} print(f" {'cap':>8} " + " ".join(f"{'K='+str(K):>9}" for K in (2, 3, 4, 6, 8, 12)) + f" {'N_max':>7}") nmax = {} for C in CAPITALI: sd_b = {} for K in (1, 2, 3, 4, 6, 8, 12): v = [] for hs in fam_rotations(K): bb = book_banded(hs, C, S1["steps"]) v.append(al._sh(bb["daily"])) sd_b[K] = float(np.std(v)) cells, best, npass = [], 1, 0 for K in (2, 3, 4, 6, 8, 12): den = ideal_sd[1] - ideal_sd[K] ret_ = (sd_b[1] - sd_b[K]) / den if den > 1e-12 else 0.0 cells.append(f"{ret_:>9.2f}") if ret_ >= 0.50: best = K npass += 1 nmax[C] = best passed[C] = npass print(f" {C:>8,.0f} " + " ".join(cells) + f" {best:>7}") print(" (retention = quota della compressione IDEALE di dispersione che il path ESEGUITO") print(" conserva; 1.00 = la banda non toglie niente, <=0 = il tranching e' invisibile)") print("\n ⚠️ IL CRITERIO SATURA, e va detto prima di leggere la colonna N_max.") print(f" Celle che passano la soglia 0.50: " + ", ".join(f"${c:,.0f} -> {passed[c]}/6" for c in CAPITALI) + ".") print(" Un gate che un candidato qualsiasi passa 6 volte su 6 a OGNI capitale non sta") print(" misurando niente (regola 07/08: un gate si valida contando quante volte lo passa") print(" un candidato a caso). Il criterio era scritto per catturare un degrado che NON") print(" ESISTE: la banda del venue non degrada il tranching nemmeno a $635.") print(" -> [A5] REFUTATA nella sua seconda meta'. La risposta a (iii) non e' un capitale:") print(" e' che l'eseguibilita' NON e' il vincolo. (7a volta nell'ondata.)") print("\n (d) IL VINCOLO OPERATIVO VERO, che non e' il pavimento ma il FEED.") print(" TP01 all'ancora h decide sulla barra 24h che CHIUDE alle h:00 di oggi: per h>0") print(" servono barre di oggi. Il feed certificato si ricostruisce una volta al giorno") print(" (~00:30 UTC) -> 23 ancore su 24 richiedono il feed 5m FRESCO, cioe' `fresh_5m`,") print(" lo stesso che il 26/07 e' risultato ricadere IN SILENZIO sul certificato e che") print(" il 29/07 ha fallito 6 giri su 8 dopo un riavvio. Oggi quel guasto tocca il solo") print(" SKH01 ed e' sorvegliato (`skh_feed_max_age_min`=30). Un ensemble d'ancora ci") print(" metterebbe sotto anche TP01, che oggi non dipende da quel path.") print(" Non e' un argomento definitivo (l'allerta esiste gia'), ma e' il costo VERO del") print(" tranching su questo libro: non ordini, non fee — una dipendenza di feed in piu'.") print(" E' l'obiezione (3) del 02/07, l'unica delle sue tre che sopravvive intatta.") print(f" {clock()}") return dict(band=band, nmax=nmax, passed=passed) # =========================================================================== # §5 — IL FUNDING: l'unico canale per cui l'ensemble potrebbe GUADAGNARE # =========================================================================== def sezione5(S1: dict) -> dict: sez("§5 — IL FUNDING (l'unico canale di GUADAGNO possibile) — e perche' e' chiuso per algebra") F = {} for instr, (start, asset, kind) in fd.INSTR.items(): d = fd.fetch_funding(instr, start).copy() d["dt"] = pd.to_datetime(d["ts"], unit="ms", utc=True) F[instr] = d.drop_duplicates(subset="ts").sort_values("dt").set_index("dt") rate = fd.build_rate(F) print(f" Tassi da `r0822d_funding` (cache su disco, nessuna rete). Copertura: " f"{rate['BTC']['series'].index.min().date()} -> {rate['BTC']['series'].index.max().date()}") def f_hourly(a: str) -> np.ndarray: """Tasso orario allineato all'indice 1h: il record etichettato T copre (T-1h, T].""" ii = idx1h(a) t_end = fd.to_ms(ii) + MSH c = fd.cumF(rate[a], t_end) - fd.cumF(rate[a], fd.to_ms(ii)) return c # (a) IL TEOREMA A4 print("\n (a) [A4] il funding pagato in un'ora e' pos*f, LINEARE in pos:") for a in ASSETS: fh = f_hourly(a) p24 = eval_ideal(a, HOURS)["pos"] pm = np.mean([eval_ideal(a, (h,))["pos"] for h in HOURS], axis=0) d = float(np.max(np.abs(p24 * fh - pm * fh))) print(f" {a}: max|funding_ens - media(funding delle 24 ancore)| = {d:.2e} -> IDENTITA'") print(" => l'ensemble NON puo' pagare meno funding della MEDIA delle ancore. Mai.") print(" Il canale di guadagno sperato e' chiuso ALGEBRICAMENTE, non 'per piccolezza'.") # (b) il rapporto condizionale: si attenua mediando sulle 24 ore? print("\n (b) e il rapporto CONDIZIONALE (22/08: esposizione e funding sono correlati, il") print(" tasso pagato davvero e' 1,86-2,55x l'incondizionato per TP01) si attenua?") print(f" {'K':>3} " + f"{'drag %/anno':>13} {'rapporto cond.':>16} " f"{'corr(pos,f) oraria':>20}") out = {} cov = {a: (idx1h(a) >= rate[a]["series"].index.min()) for a in ASSETS} for K in (1, 2, 4, 12, 24): hs = fam_rotations(K)[0] dr, rat, cc = [], [], [] for a in ASSETS: fh = f_hourly(a)[cov[a]] p = eval_ideal(a, hs)["pos"][cov[a]] paid = float(np.mean(p * fh)) * 24 * 365.25 base = float(np.mean(p)) * float(np.mean(fh)) * 24 * 365.25 dr.append(paid); rat.append(paid / base if base else np.nan) cc.append(float(np.corrcoef(p, fh)[0, 1])) out[K] = dict(drag=float(np.mean(dr)), ratio=float(np.mean(rat))) print(f" {K:>3} {np.mean(dr):>12.3%} {np.mean(rat):>15.2f}x {np.mean(cc):>20.4f}") print(" -> il rapporto e' ESSO STESSO una media di rapporti: mediare sull'ora del giorno") print(" non tocca una correlazione che vive alla scala del REGIME (settimane/mesi).") # (c) replica del numero del 22/08 per una strada diversa tp_noF, _ = fd.tp01_canonical(None) tp_F, _ = fd.tp01_canonical(rate) d22 = drift(tp_noF) - drift(tp_F) p_h = eval_ideal("BTC", (0,))["pos"][cov["BTC"]] print(f"\n (c) REPLICA del numero del 22/08 per una strada DIVERSA (griglia oraria invece che") print(f" giornaliera, ancora canonica): drag di TP01 = {out[1]['drag']:.2%}/anno") print(f" contro {d22:.2%}/anno rifacendo il conto con `fd.tp01_canonical` (griglia daily,") print(f" lente del 22/08, che pubblico' 2,02%). Due path indipendenti, stesso ordine.") # (d) e sul BANDATO (dove la linearita' si rompe)? print("\n (d) e sul path ESEGUITO (dove la banda rompe la linearita')?") print(f" {'cap':>8} {'K':>3} {'drag %/anno':>13} {'vs ideale':>11}") for C in (635.0, 5000.0, 50000.0): for K in (1, 24): hs = fam_rotations(K)[0] dr = [] for a in ASSETS: e = eval_banded(a, hs, C * 0.5, S1["steps"][a]) fh = f_hourly(a)[cov[a]] dr.append(float(np.mean(e["pos"][cov[a]] * fh)) * 24 * 365.25) print(f" {C:>8,.0f} {K:>3} {np.mean(dr):>12.3%} " f"{np.mean(dr)-out[K]['drag']:>+10.3%}") print(" -> la banda cambia il funding di briciole, e nel verso di chi tiene la posizione") print(" piu' a lungo: non e' un canale, e' un residuo.") print(f" {clock()}") return dict(rate=rate, out=out, f_hourly=f_hourly, cov=cov) # =========================================================================== # §6 — SKH01: la stessa domanda su una griglia diversa, e piu' cara # =========================================================================== def skh_families() -> dict: """Famiglie simmetriche per SKH01. Periodo congiunto 690m; griglia 5m -> 138 passi. 138 = 2*3*23 -> K in {2,3,6,23} (e 46/69/138, fuori portata). Sulla griglia a 30m dichiarata il 02/07 ci sono 23 offset e 23 e' PRIMO: o 1 o 23, niente in mezzo.""" fams = {1: [(o,) for o in r02.OFFSETS]} for K, shifts in ((2, range(0, 345, 30)), (3, range(0, 230, 30)), (6, range(0, 115, 30))): cell = 690 // K fams[K] = [tuple((s + cell * j) % 690 for j in range(K)) for s in shifts] fams[23] = [tuple(r02.OFFSETS)] return fams _SKHD: dict = {} def skh_daily(asset: str, off: int, lens: str) -> pd.Series: """Serie daily di SKH01 a fase `off`. lens='canonical' (backtest) | 'hourly' (path del cron). La cache pesante di `r02` viene SVUOTATA subito: 80+ offset x 2 asset di `ltf` non stanno in memoria su questa macchina (2 core, ~3 GB liberi).""" key = (asset, off, lens) if key in _SKHD: return _SKHD[key] daily, m, ltf, ent = r02.run_asset(asset, off) if lens == "canonical": s = daily else: df5 = r02.get5m(asset) ts5 = df5["timestamp"].values.astype(np.int64) + r02.MS5 c5 = df5["close"].values.astype(float) eq = r02.sim_equity(ltf, ent, "hourly", ts5, c5) ss = pd.Series(eq, index=pd.DatetimeIndex(pd.to_datetime(ltf["datetime"], utc=True))) s = ss.resample("1D").last().ffill().pct_change().dropna() _SKHD[key] = s r02._CACHE.pop((asset, off), None) # evict: tiene solo il daily return s def skh_book(offs: tuple, lens: str) -> pd.Series: """Book SKH01 50/50 BTC+ETH, K tranche = media dei ritorni giornalieri (stessa convenzione con cui il progetto combina BTC/ETH e gli sleeve: ribilanciamento a peso costante).""" per = [] for off in offs: J = pd.concat({a: skh_daily(a, off, lens) for a in ASSETS}, axis=1, join="inner").fillna(0.0) per.append(pd.Series(0.5 * J[ASSETS[0]].values + 0.5 * J[ASSETS[1]].values, index=J.index)) JJ = pd.concat({i: s for i, s in enumerate(per)}, axis=1, join="inner").fillna(0.0) return pd.Series(JJ.mean(axis=1).values, index=JJ.index) def sezione6() -> dict: sez("§6 — SKH01: la stessa domanda su una griglia diversa (e 23 e' un numero primo)") from src.portfolio.sleeves import _skyhook_returns ref = _skyhook_returns() mine = skh_book((0,), "canonical") d = float(np.max(np.abs(mine.values - ref.reindex(mine.index).values))) print(f" [SANITY] offset 0 == `sleeves._skyhook_returns()`: max|dif| = {d:.2e} su " f"{len(mine):,} giorni") assert d < 1e-12 fams = skh_families() print(f"\n [A6] 690/30 = 23, PRIMO -> sulla griglia a 30m del 02/07 non esiste sotto-ensemble") print(f" simmetrico: o K=1 o K=23. Le famiglie K in {{2,3,6}} esistono solo sulla griglia") print(f" 5m (138 = 2*3*23) e usano offset che il 02/07 non aveva mai valutato.") print(f" ⚠️ K=3 = {{0,230,460}}: 230 e 460 sono multipli esatti del bin LTF -> la griglia") print(f" d'ESECUZIONE 230m e' la STESSA, cambia solo la fase del segnale 690m. Non e' un") print(f" tranching d'esecuzione: e' un ensemble di SEGNALE. Va detto, non nascosto.") res = {} for lens in ("canonical", "hourly"): print(f"\n LENTE '{lens}'" + (" (backtest, fill al livello)" if lens == "canonical" else " (path del cron orario: fill al close 5m del prossimo multiplo orario)")) print(f" {'K':>3} {'rot':>4} {'ShFULL med':>11} {'sd':>6} {'ShHOLD med':>11} {'sd':>6} " f"{'drift med':>10} {'maxDD med':>10}") R = {} for K in (1, 2, 3, 6, 23): S = [skh_book(o, lens) for o in fams[K]] f = np.array([al._sh(s) for s in S]) h = np.array([al._sh(s[s.index >= HOLDOUT]) for s in S]) dr = np.array([drift(s) for s in S]) dd = np.array([al._dd_ret(s) for s in S]) R[K] = dict(f=f, h=h, dr=dr, dd=dd, S=S) print(f" {K:>3} {len(S):>4} {np.median(f):>11.3f} {f.std():>6.3f} " f"{np.median(h):>11.3f} {h.std():>6.3f} {np.median(dr):>9.2%} " f"{np.median(dd):>9.1%}") res[lens] = R can = R[1]["f"][0] print(f" canonico off=0: FULL {can:.3f} ({pctl_of(R[1]['f'], can):.0f}° pctl), " f"HOLD {R[1]['h'][0]:.3f} ({pctl_of(R[1]['h'], R[1]['h'][0]):.0f}° pctl); " f"ensemble K=23: FULL {R[23]['f'][0]:.3f}, HOLD {R[23]['h'][0]:.3f}") print(f" {clock()}") return res # =========================================================================== # §7 — I DUE SLEEVE INSIEME: il libro, e il netting che nessuno aveva contato # =========================================================================== def skh_pos_hourly(asset: str, off: int, ii: pd.DatetimeIndex) -> np.ndarray: """Posizione oraria (+1/-1/0) di SKH01 a fase `off`, dalle boundary dei trade. Riusa `fd.sim_equity_f` (che ritorna i trade con t_in/t_out in ms) — non riscrive il motore.""" daily, m, ltf, ent = r02.run_asset(asset, off) df5 = r02.get5m(asset) ts5 = df5["timestamp"].values.astype(np.int64) + r02.MS5 c5 = df5["close"].values.astype(float) _, trades = fd.sim_equity_f(ltf, ent, "canonical", ts5, c5, None) r02._CACHE.pop((asset, off), None) t0 = fd.to_ms(ii) p = np.zeros(len(ii)) for tr in trades: a = np.searchsorted(t0, tr["t_in"], side="left") b = np.searchsorted(t0, tr["t_out"], side="right") p[a:b] = tr["dir"] return p def sezione7(S1: dict, S4: dict, S6: dict) -> dict: sez("§7 — I DUE SLEEVE INSIEME: il libro live, il NETTING, e dove il tranching vale davvero") print(" Il libro esegue TP01 e SKH01 come UNA sola posizione netta per strumento (26/07, T1).") print(" Quindi la banda da $5 non si applica al ribilancio di TP01 ma al NETTO — e i salti") print(" grossi di SKH01 (che entra a 1,0x) trascinano dentro i micro-aggiustamenti di TP01,") print(" gratis. Qui si incrociano le DUE griglie di tranching: TP01 (24 ancore orarie) e") print(" SKH01 (23 fasi della griglia 230m/690m), che sono spazi di luck INDIPENDENTI.") print(" ⚠️ LENTE: qui SKH01 e' mark-to-market ORARIO (posizione dai confini dei trade), non") print(" compounding per trade come lo sleeve ufficiale -> i LIVELLI non sono confrontabili") print(" coi numeri pubblicati. Si usano solo DIFFERENZE fra configurazioni, a lente identica.") ii = {a: idx1h(a) for a in ASSETS} print(f" {clock()} costruzione delle 23 posizioni orarie di SKH01 (x2 asset)...") SP = {a: {o: skh_pos_hourly(a, o, ii[a]) for o in r02.OFFSETS} for a in ASSETS} sk = {1: {a: SP[a][0] for a in ASSETS}, 23: {a: np.mean([SP[a][o] for o in r02.OFFSETS], axis=0) for a in ASSETS}} def run(ktp: int, ksk: int, C: float) -> dict: hs = fam_rotations(ktp)[0] nor, corr, nets, notion = 0, [], {}, 0.0 for a in ASSETS: tnet = W_ASSET * (W_TP * tgt(a, hs) + W_SKH * sk[ksk][a]) b = banded(px(a), tnet, C, MIN_ORDER_USD, S1["steps"][a]) held = b["held"] pos = np.zeros(len(held)); pos[1:] = held[:-1] net = pos * ret(a) - FEE * np.abs(np.diff(pos, prepend=0.0)); net[0] = 0.0 nor += b["n_ord"]; notion += b["notional"] corr.append(float(np.corrcoef(pos, tnet)[0, 1])) nets[a] = pd.Series(net, index=ii[a]) J = pd.concat(nets, axis=1, join="inner").fillna(0.0) dly = al._to_daily(J[ASSETS[0]] + J[ASSETS[1]]) yrs = (ii["BTC"][-1] - ii["BTC"][0]) / pd.Timedelta("365.25D") return dict(ord_y=nor / yrs, corr=float(np.mean(corr)), sh=al._sh(dly), hold=al._sh(dly[dly.index >= HOLDOUT]), dd=al._dd_ret(dly), dly=dly, fee_y=FEE * notion / yrs) print(f"\n {'cap':>8} {'K_TP':>5} {'K_SKH':>6} {'ordini/anno':>12} {'fee $/anno':>11} " f"{'fedelta':>9} {'ShFULL':>8} {'ShHOLD':>8} {'maxDD':>7}") out = {} for C in (635.0, 5000.0, 100000.0): for ktp, ksk in ((1, 1), (24, 1), (1, 23), (24, 23)): r = run(ktp, ksk, C) out[(C, ktp, ksk)] = r tag = " <- il libro di oggi" if (ktp, ksk) == (1, 1) else "" print(f" {C:>8,.0f} {ktp:>5} {ksk:>6} {r['ord_y']:>12.0f} {r['fee_y']:>11.2f} " f"{r['corr']:>9.4f} {r['sh']:>8.3f} {r['hold']:>8.3f} {r['dd']:>7.1%}{tag}") print(" (fedelta' = corr fra la posizione ESEGUITA e il target ideale della stessa config)") print(f"\n 📌 LA SCOMPOSIZIONE CHE RISPONDE ALLA DOMANDA 'fallo sul LIBRO':") add = {} for C in (635.0, 5000.0, 100000.0): d_tp = out[(C, 24, 1)]["sh"] - out[(C, 1, 1)]["sh"] d_sk = out[(C, 1, 23)]["sh"] - out[(C, 1, 1)]["sh"] d_both = out[(C, 24, 23)]["sh"] - out[(C, 1, 1)]["sh"] add[C] = (d_tp, d_sk, d_both) print(f" cap ${C:>8,.0f}: tranciare SOLO TP01 {d_tp:>+7.3f} · SOLO SKH01 {d_sk:>+7.3f} · " f"ENTRAMBI {d_both:>+7.3f} (somma dei due singoli {d_tp+d_sk:>+7.3f}, " f"scarto dall'additivita' {d_both-d_tp-d_sk:>+.3f})") ok_add = all(abs(add[C][2] - add[C][0] - add[C][1]) < 0.01 for C in add) print(f" -> additivita' dei due tranching: {'REGGE' if ok_add else 'NON regge a ogni capitale'}" f" (scarto max {max(abs(add[C][2]-add[C][0]-add[C][1]) for C in add):.3f}).") # ⚠️ ERRORE MIO, catturato rileggendo l'output: la prima stesura stampava "i due effetti sono # additivi" come TESTO FISSO. A $635 non lo sono (i singoli sommano a un numero di segno # opposto al congiunto). Ora l'affermazione la calcola il codice. Una conclusione scritta a # mano accanto a una tabella e' una conclusione che la tabella non controlla. print(f" ⚠️ E la TAGLIA e' rumore su questa lente: {abs(add[635.0][2]):.3f} a $635 e " f"{abs(add[100000.0][2]):.3f} a $100k, con i segni dei singoli che cambiano fra i due") print(f" capitali. Il numero grosso su SKH01 (§6) e' STANDALONE; dentro il libro pesa") print(f" il 25% ed e' misurato su una lente diversa -> qui si legge 'non si distingue") print(f" da zero', non 'il libro guadagna'.") print(f"\n 🚨 IL PREZZO VERO DEL TRANCHING SI VEDE SULL'HOLD-OUT, non sul FULL:") for C in (635.0, 100000.0): print(f" cap ${C:>8,.0f}: ShHOLD {out[(C,1,1)]['hold']:.3f} (oggi) -> " f"{out[(C,24,1)]['hold']:.3f} (TP tranciato) -> {out[(C,1,23)]['hold']:.3f} " f"(SKH tranciato) -> {out[(C,24,23)]['hold']:.3f} (entrambi)") print(f" Non e' un difetto del tranching: e' la fortuna d'ancora che si restituisce.") print(f" ENTRAMBE le ancore canoniche stanno all'estremo alto del loro hold-out (TP01 al") print(f" 98° pctl di 24, SKH01 all'89° di 23) e il libro di oggi le somma. Un ensemble") print(f" non puo' che perdere contro la somma di due massimi — ed e' esattamente perche'") print(f" quel numero non e' l'aspettativa di domani.") print(f" E il maxDD va nel verso opposto: {out[(635.,1,1)]['dd']:.1%} -> " f"{out[(635.,24,23)]['dd']:.1%}. Rischio giu', hold-out fortunato giu'.") o1, o2 = out[(635., 1, 1)], out[(635., 24, 23)] print(f"\n IL COSTO, a $635 — e sorprende: gli ORDINI salgono {o1['ord_y']:.0f} -> " f"{o2['ord_y']:.0f}/anno ({o2['ord_y']/o1['ord_y']:.1f}x, " f"{o2['ord_y']/365.25:.1f}/giorno su 24 giri orari),") print(f" ma le FEE {'SCENDONO' if o2['fee_y'] < o1['fee_y'] else 'salgono'} " f"${o1['fee_y']:.2f} -> ${o2['fee_y']:.2f}/anno " f"({o1['fee_y']/635:.2%} -> {o2['fee_y']/635:.2%} di capitale).") print(f" Non e' una contraddizione ed e' il fatto piu' istruttivo di questa sezione: su") print(f" Deribit la fee e' PROPORZIONALE, quindi si paga il NOZIONALE, non gli ordini —") print(f" e mediare 23 fasi di uno sleeve a trade discreti trasforma un +-1,0x che sbatte") print(f" da un estremo all'altro in una posizione frazionaria che si muove poco. Piu'") print(f" ordini, meno merce. (Su un venue a pavimento FISSO — IB, 25/07 — il segno si") print(f" ribalterebbe: la stessa mossa costerebbe {o2['ord_y']/o1['ord_y']:.1f}x.)") print(f" {clock()}") return out # =========================================================================== # §8 — QUANTO VALE RIDURRE LA VARIANZA SOTTO UNA BARRIERA ASSORBENTE # =========================================================================== def barrier_pass(r: np.ndarray, k: float, up: float, dn: float, starts: np.ndarray, maxlen: int = 365) -> tuple: """P(pass) / P(breach) di una regola a barriera: dal saldo iniziale, +up = pass, -dn = breach (max-loss STATICO, la regola vera: 25/07). Path close-only.""" L = np.concatenate([[0.0], np.cumsum(np.log1p(np.clip(k * r, -0.99, None)))]) a, b = np.log1p(up), np.log1p(-dn) np_, nb, nu = 0, 0, 0 for i in starts: d = L[i + 1:i + 1 + maxlen] - L[i] if len(d) < 5: continue hu = np.flatnonzero(d >= a) hd = np.flatnonzero(d <= b) ju = hu[0] if len(hu) else 10 ** 9 jd = hd[0] if len(hd) else 10 ** 9 if ju == jd == 10 ** 9: nu += 1 elif ju < jd: np_ += 1 else: nb += 1 tot = np_ + nb + nu return (np_ / tot, nb / tot, nu / tot) if tot else (0.0, 0.0, 0.0) def sezione8(S1: dict, S2: dict, S6: dict) -> dict: sez("§8 — [A7] QUANTO VALE RIDURRE LA VARIANZA SOTTO UNA BARRIERA (il canale funded)") print(" Su un conto proprio il nemico e' il drift e la varianza si sopporta. Su un conto") print(" funded il vincolo binding e' la regola di DRAWDOWN (25/07 §4): li' la varianza E'") print(" il nemico — e l'ensemble compra esattamente quello. Questa e' l'attesa [A7]; segue") print(" la misura, che la refuta sulla meta' che si puo' misurare.") print(" Regole HyroTrader modellate (`r0725_hyro`: EV_TARGET/EV_DD/EV_DL = 0.10/0.06/0.04):") print(" target +10%, max-loss STATICO -6% dal saldo iniziale.") print(" ⚠️ LIMITE DICHIARATO, ed e' quello che conta: la regola di DAILY-LOSS (-4%) NON e'") print(" valutabile qui. Su una regola a UN giorno la lente close-only e' CIECA (22/08:") print(" 0,00 breach/anno contro 0,40-1,21 veri, rapporto INF) e servirebbe la lente wick") print(" ACCOPPIATA (`r0725_prop_coupled`), fuori portata di questo script. Il max-loss e'") print(" multi-giorno: li' close-only e' ottimista di un ricarico ~3,5%, non cieca.") skhL = S6["canonical"] skh_off = skhL[1]["S"] skh_ens = skhL[23]["S"][0] tp_h = S1["singles"] tp_ens = S2["e24"] def book(tp: pd.Series, skh: pd.Series) -> pd.Series: return combine_outer({"TP01": tp, "SKH01": skh}, {"TP01": W_TP, "SKH01": W_SKH}, lo=pd.Timestamp("2019-03-14", tz="UTC")) rng = np.random.default_rng(20260823) pairs = [(int(h), int(o)) for h, o in zip(rng.integers(0, 24, 150), rng.integers(0, 23, 150))] print(f"\n Spazio congiunto 24 ancore TP01 x 23 fasi SKH01 = 552 configurazioni; 150 estrazioni") print(f" uniformi indipendenti (seme dichiarato) + l'ensemble congiunto + la canonica (0,0).") print(f" {'leva':>5} {'':<24} {'P(pass)':>9} {'P(breach)':>10} {'P(aperto)':>10} {'maxDD':>8}") res = {} BK = {} for (h, o) in pairs: BK[(h, o)] = book(tp_h[h], skh_off[o]) BE = book(tp_ens, skh_ens) BC = book(tp_h[0], skh_off[0]) for k in (0.50, 0.75, 1.00): rows = [] for (h, o) in pairs: s_ = BK[(h, o)]; r = s_.values.astype(float) st = np.arange(0, len(r) - 380, 15) rows.append(barrier_pass(r, k, 0.10, 0.06, st) + (al._dd_ret(s_),)) A = np.array(rows) rr = BE.values.astype(float); st = np.arange(0, len(rr) - 380, 15) pe = barrier_pass(rr, k, 0.10, 0.06, st) + (al._dd_ret(BE),) rc = BC.values.astype(float); stc = np.arange(0, len(rc) - 380, 15) pc = barrier_pass(rc, k, 0.10, 0.06, stc) + (al._dd_ret(BC),) res[k] = dict(A=A, ens=pe, can=pc) print(f" {k:>5.2f} {'CANONICA (0,0) — oggi':<24} {pc[0]:>9.1%} {pc[1]:>10.1%} " f"{pc[2]:>10.1%} {pc[3]:>8.1%}") print(f" {'':>5} {'mediana 150 estrazioni':<24} {np.median(A[:,0]):>9.1%} " f"{np.median(A[:,1]):>10.1%} {np.median(A[:,2]):>10.1%} {np.median(A[:,3]):>8.1%}") print(f" {'':>5} {'banda p10-p90':<24} " f"{'[%.0f%%,%.0f%%]' % (np.percentile(A[:,0],10)*100, np.percentile(A[:,0],90)*100):>9} " f"{'[%.1f%%,%.1f%%]' % (np.percentile(A[:,1],10)*100, np.percentile(A[:,1],90)*100):>10} " f"{'':>10} " f"{'[%.1f,%.1f]' % (np.percentile(A[:,3],10)*100, np.percentile(A[:,3],90)*100):>8}") print(f" {'':>5} {'ENSEMBLE congiunto':<24} {pe[0]:>9.1%} {pe[1]:>10.1%} " f"{pe[2]:>10.1%} {pe[3]:>8.1%} (pctl fra le estrazioni: " f"{pctl_of(A[:,0], pe[0]):.0f}° su P(pass), {pctl_of(-A[:,1], -pe[1]):.0f}° su " f"P(breach), {pctl_of(-A[:,3], -pe[3]):.0f}° su maxDD)") print() print(" 🚨 [A7] REFUTATA sulla meta' misurabile, E CON UN MECCANISMO — non e' rumore.") print(" Su queste regole il lato BINDING non e' la barriera, e' il BERSAGLIO: P(breach)") print(f" sta fra {res[0.50]['ens'][1]:.1%} e {res[1.00]['ens'][1]:.1%} mentre P(pass) sta") print(f" fra {res[0.50]['ens'][0]:.0%} e {res[1.00]['ens'][0]:.0%}. Se il vincolo e'") print(" ARRIVARE a +10% e non evitare -6%, ridurre la varianza RALLENTA: e' la stessa") print(" meno-vol che tiene lontani dalla barriera a tenere lontani dal traguardo.") print(" L'ensemble infatti sta al 30-50° pctl su P(pass) — sotto la mediana — pur avendo") print(" un maxDD MIGLIORE della mediana. Le due cose non sono in contraddizione: sono la") print(" stessa cosa vista dai due lati.") print(" ⚠️ E la regola che il progetto ha MISURATO essere quella che uccide — la") print(" daily-loss a UN giorno (22/08) — e' esattamente quella che questa lente non puo'") print(" vedere. Quindi: A7 non e' refutata, e' refutata SULLA REGOLA SBAGLIATA. Dirlo e'") print(" il risultato; concludere 'l'ensemble non serve sul funded' NON lo sarebbe.") # --- proxy POTENTE per la regola a un giorno: la coda sinistra giornaliera ----------- print("\n (b) L'UNICA cosa con potenza che si puo' dire sulla regola a un giorno: la CODA") print(" SINISTRA giornaliera. La daily-loss legge il MINIMO del giorno; il close-only ne") print(" e' un limite inferiore (rapporto mediano minimo/chiusura misurato 1,89x il 22/08).") print(" Non e' una stima del breach — e' il confronto FRA configurazioni sulla grandezza") print(" che quella regola legge, a lente identica.") W = np.array([[float(np.min(BK[p].values)), float(np.percentile(BK[p].values, 1)), float(np.mean(BK[p].values < -0.02))] for p in pairs]) we = [float(np.min(BE.values)), float(np.percentile(BE.values, 1)), float(np.mean(BE.values < -0.02))] wc = [float(np.min(BC.values)), float(np.percentile(BC.values, 1)), float(np.mean(BC.values < -0.02))] print(f"\n {'':<24} {'peggior giorno':>15} {'p1 giornaliero':>15} {'giorni < -2%':>14}") print(f" {'CANONICA (0,0) — oggi':<24} {wc[0]:>14.2%} {wc[1]:>14.2%} {wc[2]:>13.2%}") print(f" {'mediana 150 estrazioni':<24} {np.median(W[:,0]):>14.2%} " f"{np.median(W[:,1]):>14.2%} {np.median(W[:,2]):>13.2%}") print(f" {'banda p10-p90':<24} " f"{'[%.2f%%,%.2f%%]' % (np.percentile(W[:,0],10)*100, np.percentile(W[:,0],90)*100):>15} " f"{'[%.2f%%,%.2f%%]' % (np.percentile(W[:,1],10)*100, np.percentile(W[:,1],90)*100):>15} " f"{'[%.2f%%,%.2f%%]' % (np.percentile(W[:,2],10)*100, np.percentile(W[:,2],90)*100):>14}") q = (pctl_of(W[:, 0], we[0]), pctl_of(W[:, 1], we[1]), pctl_of(-W[:, 2], -we[2])) print(f" {'ENSEMBLE congiunto':<24} {we[0]:>14.2%} {we[1]:>14.2%} {we[2]:>13.2%}" f" (pctl: {q[0]:.0f}° / {q[1]:.0f}° / {q[2]:.0f}° — " f"= quota di configurazioni che l'ensemble BATTE)") # ⚠️ ERRORE MIO, catturato rileggendo l'output: la prima stesura calcolava questi tre # percentili con `pctl_of(-W, -we)` per tutte e tre le colonne. Sulle prime due (che sono # RITORNI, negativi, dove "piu' alto = meglio") quel segno da' il COMPLEMENTO: stampava 34° # dove il valore vero e' 66°, cioe' diceva "peggio della mediana" di un numero che e' # meglio della mediana. La terza colonna (una FREQUENZA, dove "piu' basso = meglio") aveva # bisogno del segno invertito ed era l'unica giusta. Tre colonne, due convenzioni. print(f" -> l'ensemble batte la mediana su TUTTE E TRE le colonne, ma di POCO " f"({q[0]:.0f}°/{q[1]:.0f}°/{q[2]:.0f}° pctl): comprime la coda, non la annulla.") print(" E la banda p10-p90 del p1 e' strettissima -> su questa grandezza le 552") print(" configurazioni si somigliano molto piu' che sullo Sharpe.") print(" Resta un INDIZIO, non una misura del breach: la conferma richiede la lente") print(" accoppiata, ed e' un follow-up con un costo noto, non un ostacolo.") print("\n 📌 E il fatto che chiude il cerchio sull'eseguibilita': il conto funded e' da") print(" $100.000, dove il pavimento del venue e' invisibile (§7 lo misura). Il tranching") print(" e' ineseguibile in nessun posto — e utile solo dove la coda conta.") print(f" {clock()}") return dict(res=res, tail=dict(W=W, ens=we, can=wc)) # =========================================================================== # §9 — VERDETTO # =========================================================================== def sezione9(S1, S2, S4, S5, S6, S7, S8) -> None: sez("§9 — VERDETTO SULLE ATTESE A PRIORI (derivato dai numeri, non scritto a mano)") can = S1["singles"][0]; e24 = S2["e24"] ca, cb, cc = m3(can); ea, eb, ec = m3(e24) med = S1["singles"][S2["med_h"]] ma, mb, mc = m3(med) f1 = S2["fam"][1] d_med = ea - ma # A1: identita' del lordo, ri-verificata qui (cached, costo ~0) id_max = max(float(np.max(np.abs(eval_ideal(a, HOURS)["gross"] - np.mean([eval_ideal(a, (h,))["gross"] for h in HOURS], axis=0)))) for a in ASSETS) a1 = "CONFERMATA" if id_max < 1e-15 else "REFUTATA" a2 = ("CONFERMATA" if 0.03 <= d_med <= 0.15 else ("DIREZIONE OK, TAGLIA REFUTATA" if d_med > 0 else "REFUTATA")) a3 = "CONFERMATA" if ec < cc else "REFUTATA" r1, r24 = S5["out"][1]["ratio"], S5["out"][24]["ratio"] a4 = "CONFERMATA" if abs(r24 - r1) < 0.05 else "REFUTATA" nm = S4["nmax"]; pas = S4["passed"] a5 = "REFUTATA" if (nm[635.0] > 1 or min(pas.values()) == 6) else "CONFERMATA" a6 = "CONFERMATA (aritmetica: 23 e' primo)" p_ens = pctl_of(S8["res"][1.00]["A"][:, 0], S8["res"][1.00]["ens"][0]) # convenzione: il p1 e' un RITORNO (negativo), quindi "piu' alto = meglio" e il percentile # si calcola SENZA invertire il segno. Invertirlo da' il complemento (34° invece di 66°) — # errore commesso nella prima stesura di §8 e corretto li': va corretto anche qui, o le due # sezioni stampano due numeri diversi per la stessa grandezza. p_tail = pctl_of(S8["tail"]["W"][:, 1], S8["tail"]["ens"][1]) a7 = "REFUTATA sulla regola misurabile" if p_ens < 50 else "CONFERMATA" ver = [ ("A1", a1, f"identita' del lordo verificata a max|dif| = {id_max:.1e}; turnover " f"sub-additivo sul conto esatto."), ("A2", a2, f"dSharpe FULL vs ancora mediana {d_med:+.3f} (previsto +0,03..+0,15): " f"il SEGNO e' giusto, la TAGLIA e' {0.09/max(d_med,1e-9):.0f}x piu' piccola del " f"centro della mia banda. Avevo sopravvalutato quanto sono decorrelate fra loro " f"24 ancore della STESSA strategia."), ("A3", a3, f"dSharpe HOLD vs canonica {ec-cc:+.3f}; la canonica sta al " f"{pctl_of(S1['shh'], S1['shh'][0]):.0f}° pctl delle 24 sull'hold-out e al " f"{pctl_of(S1['shi'], S1['shi'][0]):.0f}° in-sample: SFORTUNATA in-sample e FORTUNATA " f"sull'hold-out, esattamente la struttura che il 02/07 aveva descritto."), ("A4", a4, f"funding dell'ensemble ESATTAMENTE la media (max|dif| = 0) e rapporto " f"condizionale immobile ({r1:.2f}x a K=1 -> {r24:.2f}x a K=24)."), ("A5", a5, f"il pavimento NON degrada il tranching: retention {min(pas.values())}/6 celle " f"sopra soglia a OGNI capitale, N_max($635) = {nm[635.0]} invece di 1. " f"Il criterio SATURA -> non e' un N_max, e' un 'il vincolo non e' questo'."), ("A6", a6, "e le famiglie simmetriche di SKH01 vivono solo sulla griglia 5m; K=3 e' un " "ensemble di SEGNALE, non d'esecuzione."), ("A7", a7, f"l'ensemble sta al {p_ens:.0f}° pctl su P(pass) a leva 1,0x — SOTTO la mediana — " f"perche' su queste regole il lato binding e' il BERSAGLIO (+10%), non la " f"barriera. Ma sul p1 giornaliero, che e' cio' che la regola killer legge, sta " f"al {p_tail:.0f}° pctl (batte quella quota di configurazioni). La refutazione e' " f"sulla regola che si puo' misurare, non su quella che uccide: dichiarato."), ] for k, v, t in ver: print(f" [{k}] {v}") print(f" {t}\n") sez("§9b — LE TRE RISPOSTE") print(f" (i) L'ensemble batte l'ancora MEDIANA? SI su TP01, ma di BRICIOLE: " f"dShFULL {d_med:+.3f}, dShHOLD {ec-mc:+.3f}, dDrift {drift(e24)-drift(med):+.2%}.") print(f" Scomposto contro la MEDIA delle 24 (il confronto algebricamente pulito): il") print(f" drift si muove di {drift(e24)-np.mean(f1['dr']):+.3%} (solo fee-netting, e la") print(f" linearita' dice che non puo' fare altro) e la vol di " f"{vol(e24)/np.mean(f1['vo'])-1:+.1%}. Tutto il guadagno e' vol.") print(f" Cio' che l'ensemble compra DAVVERO non e' nella colonna Sharpe: e' la") print(f" dispersione, sd(ShFULL) {f1['f'].std():.3f} -> 0 e sd(ShHOLD) " f"{f1['h'].std():.3f} -> 0.") skhc, skhh = S6["canonical"], S6["hourly"] print(f"\n ⚠️ E su SKH01 la risposta e' DIVERSA di un ordine di grandezza: sulla lente") print(f" del path che gira (hourly) la mediana delle 23 fasi fa ShFULL " f"{np.median(skhh[1]['f']):.3f} e l'ensemble K=23 fa {skhh[23]['f'][0]:.3f} " f"({skhh[23]['f'][0]-np.median(skhh[1]['f']):+.3f}),") print(f" con maxDD mediano {np.median(skhh[1]['dd']):.1%} -> {skhh[23]['dd'][0]:.1%}. " f"Su TP01 il tranching e' rumore; su SKH01 e' il grosso.") print(f" Meccanismo: la dispersione d'ancora di SKH01 e' 3x quella di TP01 " f"(sd {skhh[1]['f'].std():.3f} vs {f1['f'].std():.3f}) perche' i suoi trade sono") print(f" DISCRETI — spostare la griglia cambia QUALI trade esistono, non di quanto sono") print(f" grossi. Il tranching vale quanto la lumpiness che elimina.") print(f" ⚠️ MA quel numero e' STANDALONE. Dentro il libro SKH01 pesa il 25% e il §7 lo") print(f" misura su un'altra lente: li' il tranching congiunto vale " f"{S7[(635.0,24,23)]['sh']-S7[(635.0,1,1)]['sh']:+.3f} a $635 e " f"{S7[(100000.0,24,23)]['sh']-S7[(100000.0,1,1)]['sh']:+.3f} a $100k, coi segni dei") print(f" singoli che cambiano fra i due capitali = non distinguibile da zero.") print(f" La lettura onesta: il tranching di SKH01 e' l'unico posto dove c'e' un segnale,") print(f" e non e' abbastanza grande da vedersi attraverso un peso del 25%.") print(f"\n (ii) L'ensemble batte la CANONICA che gira oggi? NO sull'hold-out " f"({ec-cc:+.3f}), SI su FULL ({ea-ca:+.3f}) e su IS ({eb-cb:+.3f}).") print(f" E i due numeri NON sono due stime della stessa cosa. Il {cc:+.2f} della canonica") print(f" e' una estrazione GIA' AVVENUTA (98° pctl di 24); il {ec:+.2f} dell'ensemble e'") print(f" cio' che si ottiene SENZA estrarre.") print(f" 📌 LA STIMA ONESTA DEL FUTURO E' L'ENSEMBLE: {ec:+.2f}, non {cc:+.2f}.") print(f" Tenere la canonica non conserva il {cc:+.2f} — ri-estrae dalla banda " f"[{S1['shh'].min():+.2f},{S1['shh'].max():+.2f}], mediana {np.median(S1['shh']):+.2f}.") print(f" (E questi numeri riproducono la memoria del progetto: canonico +0,44 al " f"{pctl_of(S1['shh'], S1['shh'][0]):.0f}° pctl, mediana +{np.median(S1['shh']):.2f}.)") print(f"\n (iii) A quale capitale diventa eseguibile senza degenerare? A TUTTI, gia' a $635 —") print(f" e questo REFUTA la mia attesa e la premessa (non la conclusione) del 02/07.") print(f" A $635 il path eseguito a K=24 segue il target ideale con corr " f"{S4['band'][(635.0,24)]['corr']:.4f} contro {S4['band'][(635.0,1)]['corr']:.4f} di K=1:") print(f" il tranching e' seguito MEGLIO, non peggio, perche' un target che si muove a") print(f" passi piccoli e frequenti entra in una banda fissa meglio di uno che salta.") print(f" Ordini: {S4['band'][(635.0,1)]['ord_y']:.0f}/anno -> " f"{S4['band'][(635.0,24)]['ord_y']:.0f}/anno, fee " f"${S4['band'][(635.0,1)]['fee_y']:.2f} -> ${S4['band'][(635.0,24)]['fee_y']:.2f}/anno.") sez("§9c — IL RISULTATO CHE NON ERA NEL MANDATO, e cosa NON cambia") print(" 📌 LA PREMESSA DEL 02/07 E' SBAGLIATA; LA SUA CONCLUSIONE REGGE PER UN'ALTRA RAGIONE.") print(" Il 02/07 boccio' il tranching cosi': *'a $225/asset di quota TP01 i delta") print(" per-ancora (~$1-2) sono sotto il min-order $5 -> degenererebbe comunque in K=1'*.") print(" Ma `src/live/book.build_book_order` non manda mai un ordine PER ANCORA: banda") print(" `abs(delta)` sulla posizione NETTA e ne manda UNO per asset per giro. Un 'delta") print(" per-ancora' non e' un ordine, e' un pezzo di aritmetica dentro il target. Il") print(" tranching non moltiplica gli ordini per K, e infatti la degenerazione NON avviene") print(" (misurata: retention ~0,9-1,0 a $635).") print(" Cio' che boccia il tranching di TP01 non e' l'esecuzione, e' che NON C'E' NIENTE") print(" DA PRENDERE: +0,014 di Sharpe. La decisione del 02/07 resta, la sua ragione no.") print(" E la ragione conta: quella del 02/07 sarebbe caduta al primo esecutore che mandasse") print(" un ordine per tranche; questa non cade mai, perche' e' sulla taglia dell'effetto.") print("\n 🚨 E IL FILONE HA TROVATO IL SUO CONTRARIO: l'unica ancora che il 02/07 NON aveva") print(" esaminato — la griglia 230m/690m di SKH01 — e' quella dove il tranching vale.") print(" Il 02/07 raccomando' l'audit su SKH01 (fatto il 02/07-bis) ma non ci porto' MAI") print(" la domanda del tranching, che era nata su TP01.") print("\n ⚠️ COSA COSTA DAVVERO, e non e' negli ordini: il feed. 23 ancore su 24 di TP01") print(" richiedono barre di oggi, cioe' `fresh_5m` — lo stesso path che il 26/07 e'") print(" risultato ricadere IN SILENZIO sul certificato e che il 29/07 ha fallito 6 giri") print(" su 8. Oggi TP01 non dipende da quel path; con l'ensemble ci dipenderebbe.") print(" E' l'unica delle tre obiezioni del 02/07 che sopravvive intatta.") print(" (SKH01 quel feed lo usa GIA': il suo tranching non aggiunge dipendenze nuove —") print(" il che rende l'asimmetria fra i due sleeve ancora piu' netta.)") print("\n NIENTE CAMBIA nel libro live: pesi, ancore, cron, config INVARIATI. Il tranching non") print(" e' un cambio di pesi (`weights_tilt_null` non si applica) ma e' un cambio di") print(" ESECUZIONE: vale l'asticella del 26/07 (T1), il beneficio si confronta col path che") print(" gira davvero. Non e' proposto nulla: SKH01 non e' passato per `study_family_honest`") print(" su questa griglia, il guadagno e' misurato sugli STESSI 7 anni su cui SKH01 e' stato") print(" selezionato, e la lente 'hourly' e' gia' nota (26/07) per sottostimare il path live.") print(" Cio' che questo filone stabilisce e' DOVE guardare, non cosa fare.") print(f"\n {clock()} fine.") def main() -> None: print("=" * 100) print(" r0823_anchor_ensemble.py — §47 ANCHOR-ENSEMBLE") print(f" fee {2*FEE:.2%} RT · HOLD-OUT >= {HOLDOUT.date()} · min_order ${MIN_ORDER_USD:.0f} · " f"libro TP01 {W_TP:.0%} / SKH01 {W_SKH:.0%}") print("=" * 100) sezione0() S1 = sezione1() S2 = sezione2(S1) sezione3(S1, S2) S4 = sezione4(S1, S2) S5 = sezione5(S1) S6 = sezione6() S7 = sezione7(S1, S4, S6) S8 = sezione8(S1, S2, S6) sezione9(S1, S2, S4, S5, S6, S7, S8) if __name__ == "__main__": main()