scettico(leva): il gradino 1,25x REGGE 5 attacchi su 6 — k max difendibile 1,40
Filone §53 SKEPTIC-LEVA. Sei attacchi con soglie dichiarate PRIMA, 10 numeri
pubblicati riprodotti prima di attaccarli. Sola lettura, zero rete, 127 s.
A1 DRIFT REGGE — il gradino diventa dannoso solo sotto +1,42%/anno di drift
(-2,70 SE, 0,25° pctl bootstrap a blocchi): fuori da IC95
[+4,78%] E da IC99 [+2,36%]. Il libro puo' perdere il 90,6%
del suo drift e il gradino resta non dannoso; il PEGGIOR
triennio mai realizzato (+5,35%) ci sta 3,8x sopra.
A2 SINISTRO REGGE — Delta g(1,25) > 0 in 5/5 finestre giudicabili (2020-21 tolto
compreso). QUALIFICA misurata: nel biennio MOBILE peggiore
(dal 2021-10, drift -0,45%) il gradino COSTA -0,31%/anno =
-0,62% cumulato. Non falsifica per il criterio (li' perde il
LIBRO), ma il numero e' quello. Rapporto guadagno/costo 13:1.
A3 CODA REGGE — con Hill xi=0,43 (piu' severo del +0,34 pubblicato) il peggior
giorno strutturale a 1,25x e' 17,90% (<50%) e P(dimezzamento
10a) 0,100% contro 0,000%. CORREZIONE a §33: la coda muove k*
di 4,6 unita', non "1-2" — la conclusione regge (il drift ne
muove 6,2) ma il numero pubblicato e' ottimista ~3x.
A4 LENTE REGGE — sotto la lente accoppiata il ricarico del maxDD CALA con k
(1,2358 -> 1,2297 -> 1,2237): moltiplicativo, verso favorevole.
E la frequenza del disaster-SL e' invariante a k PER
COSTRUZIONE (innesco sul MARK, test di ri-piazzamento
RELATIVO): il numero di §40 va moltiplicato, non rifatto.
A5 INVARIANTE INCRINA — completo sul percorso FIDATO (0 violazioni su 576 combo:
il vero e' n_asset*min(WEIGHT*(W_TP01*lev+W_SKH), frac)*scala,
quindi l'invariante e' un LIMITE SUPERIORE). Ma nel ramo di
FALLBACK il denominatore del rapporto e' il WATERMARK, non
l'equity: leva vera fino a 5,00x GIA' a k=1,00 e 6,25x a 1,25.
La scala non apre il buco, lo MOLTIPLICA -> la guardia G3
della SPEC non e' una raffinatezza ed e' NON OPZIONALE, ma non
e' una chiusura. E il fattore 0,30 non e' un tetto di perdita
(§40: stop rotolante, passano -60% dall'ingresso).
A6 FUNDING REGGE — rifatto col funding DENTRO e proporzionale a k (r_k = k*(r-f),
lineare per costruzione): il gradino vale +3,49a (muro mobile)
/ +2,10a (muro congelato) contro +2,98a / +1,82a sulla lente
pubblicata. Il funding NON riduce il gradino: lo AUMENTA in
anni, perche' peggiora il caso base (17,28a contro 14,81a).
Ma il livello peggiora su tutta la colonna: k=1,25 col funding
(13,78a) resta peggio di k=1,00 senza (14,81a) -> il gradino
non ripaga il funding, lo attenua.
RICONCILIAZIONE: il «+1,8a / +EUR164» pubblicato NON era una discrepanza, era una
CONVENZIONE non dichiarata — e' la lettura a MURO CONGELATO (de-leva al traguardo),
riprodotta a 0,02a (+1,82a). Col muro che si muove con k il gradino vale +2,98a. La
differenza fra le due letture (1,2 anni) e' piu' grande di quasi tutti gli effetti
che questo progetto misura: va dichiarata. Il +EUR164 e' invece +EUR144 misurato
direttamente — la differenza E' l'ipotesi di linearita' dell'interpolazione.
K MASSIMO DIFENDIBILE = 1,40 (morde il peggior giorno strutturale <= 20%, soglia
AGGIUNTA da questo scettico); coi soli vincoli gia' dichiarati dal progetto sarebbe
1,67 (G6, il disaster-SL). Il 1,50x NON sopravvive: sfonda il 20% (21,48%) e sta al
90% del tetto G6. La scaletta SCALA_LADDER a 1,25 resta giusta: e' l'unico gate che
il progetto ha contro un parametro che nessun altro gate vede.
NON PORTATI: banda d'ancora del guadagno in ANNI (23x24 congiunte, fuori budget);
slippage a taglia crescente (la partecipazione 21,9% di una barra 5m diventa 27,4%
SUBITO, non a $5.000: va misurata PRIMA del gradino); costo di margine sopra 1x
(non esiste su perp lineare marginato); coda di venue (vive sul suo asse).
Nessun file di produzione toccato: git diff sui tracciati e' vuoto.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M8Ncho6QV9FWLdyy4VyQf
This commit is contained in:
@@ -0,0 +1,997 @@
|
||||
#!/usr/bin/env python
|
||||
"""r0823_skeptic_leva.py — SCETTICO ADVERSARIALE del gradino di leva 1,00x -> 1,25x.
|
||||
|
||||
Filone §53 SKEPTIC-LEVA, ondata 2026-08-22/23, branch `research/wave-0822`.
|
||||
|
||||
IL BERSAGLIO (RESULTS-0822 §33 WORST-DAY, §38 SCALE-SPEC, §40 SL-ANCHOR):
|
||||
«il libro gira al 7% di Kelly; il gradino 1,00x -> 1,25x vale 14,7a -> 12,9a al
|
||||
capitale-rendita (+164 EUR/mese di versamento equivalente); la riserva "-10% al giorno
|
||||
ogni anno" e' mal tarata di 2-4 ordini di grandezza; il nodo del disaster-SL rotolante e'
|
||||
sciolto (scatta MENO). Resta da costruire una chiave di scala in config.»
|
||||
|
||||
QUESTO FILE NON CONFERMA: cerca dove il risultato e' sbagliato. Sei attacchi dichiarati, ognuno
|
||||
con la sua soglia scritta PRIMA nella sezione (0), e ognuno che finisce in REGGE / INCRINA /
|
||||
FALSIFICA con un NUMERO accanto.
|
||||
|
||||
(0) IL CRITERIO — le sei soglie, congelate prima di guardare le sezioni 2-7.
|
||||
(1) REPLICA — 10 numeri gia' pubblicati riprodotti PRIMA di attaccarli.
|
||||
(2) A1 il drift e' stimato su 7,4 anni con due tori, e k* e' LINEARE nel suo errore.
|
||||
(3) A2 il campione non contiene il sinistro che conta (finestre stressate e disgiunte).
|
||||
(4) A3 la coda: due stimatori che non concordano -> si prende il PEGGIORE fino in fondo.
|
||||
(5) A4 la lente: tutto il conto e' close-only, e close-only e' CIECA su una regola a un
|
||||
livello. Il disaster-SL e' una regola su un livello.
|
||||
(6) A5 l'invariante a tre chiavi: e' completo? c'e' una QUARTA chiave?
|
||||
(7) A6 il funding non era nel conto della leva, e il funding SCALA con la leva.
|
||||
(8) VERDETTO, e il k MASSIMO DIFENDIBILE come singolo numero.
|
||||
|
||||
ONESTA' APPLICATA (regole gia' codificate nel progetto, usate CONTRO questo lavoro):
|
||||
· lo Sharpe e' INVARIANTE alla scala: `deflated_sharpe` e `marginal_vs_tp01` non vedono la
|
||||
leva -> non compaiono qui come prova (SPEC §0);
|
||||
· si riproduce il numero che si attacca PRIMA di attaccarlo (sez. 1);
|
||||
· [MISURATO] / [CALC] / [DEDOTTO] / [ASSUNTO] / [CITATO] marcati riga per riga;
|
||||
· la trappola dichiarata da WORST-DAY: se uno stress uccide anche il conto di OGGI, non e' il
|
||||
gradino a essere sbagliato — e va DETTO. Per questo ogni finestra stressata riporta anche il
|
||||
drift del libro SU QUELLA finestra: dove il libro e' morto, il gradino non e' giudicabile.
|
||||
· un non-arrivo si codifica +inf, mai -1 (07/08); una mediana condizionata non si stampa mai
|
||||
senza la sua probabilita' accanto.
|
||||
· confronto APPAIATO fra k: `CC._boot_paths` estrae INDICI da un seme, quindi
|
||||
`_boot_paths(k*r) == k*_boot_paths(r)` esattamente (verificato in sez. 1).
|
||||
|
||||
SOLA LETTURA. Nessuna rete, nessun ordine, nessuna scrittura fuori dalla cache di /tmp.
|
||||
|
||||
nice -n 19 timeout 900 uv run python scripts/research/r0823_skeptic_leva.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy import stats
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
import r0725_capcurve as CC # noqa: E402 book_series, _boot_paths, TAX_RATE
|
||||
import r0725_prop_coupled as PC # noqa: E402 lente ACCOPPIATA (R, m) del libro
|
||||
import r0727_tasse as TX # noqa: E402 PATRIMONIALE, CARRY_ANNI
|
||||
import r0807_piano_netto as PN # noqa: E402 muro / accumula / sopravvivenza
|
||||
|
||||
from src.live.book import ( # noqa: E402 SOLA LETTURA (nessuna di queste scrive)
|
||||
CAP_UNKNOWN_USD, W_SKH, W_TP01, _read_watermark, book_net_target)
|
||||
from src.live.shadow import FALLBACK_CAPITAL, WEIGHT # noqa: E402
|
||||
from src.strategies.trend_portfolio import CANONICAL # noqa: E402
|
||||
|
||||
ANN = 365.0
|
||||
DELUCK = 0.89 # fattore d'ancora MISURATO (r0726_deluck_factor), non a occhio
|
||||
START = 635.0 # il conto Deribit VERO
|
||||
DEP_EUR = 500.0 # il piano in corso (config/live.json _nota_cap: "EUR 5.000 + 500/mese")
|
||||
BLOCK = 20 # blocchi del bootstrap (convenzione del progetto)
|
||||
N_WALL = 2000 # risoluzione MC del muro ~1%, DICHIARATA (r0822d_funding)
|
||||
N_TRAJ = 3000 # la taglia a cui furono calcolate le traiettorie pubblicate
|
||||
SEED = 725 # seme delle traiettorie pubblicate (confronto appaiato)
|
||||
SEED_BOOT = 20260823
|
||||
ANNI_TRAJ = 25
|
||||
KS = (1.00, 1.25, 1.50, 2.00)
|
||||
|
||||
# ---- numeri PUBBLICATI, per la replica di sez. 1 ------------------------------------------
|
||||
PUB = {
|
||||
"drift_nofund": 0.1924, "drift_fund": 0.1708, # §35
|
||||
"sh_nofund": 1.692, "sh_fund": 1.513, # §35
|
||||
"kstar_nofund": 13.7, "kstar_fund": 12.3, # §35
|
||||
"g1": 0.1790, "g125": 0.2261, "sharpe_scale": 1.5051, # SPEC §0 (serie de-luckata, no funding)
|
||||
"perp_L1": 0.0752, "muro_L1": 264_000, # §36 (fisco)
|
||||
"perp_L3": 0.0635, "muro_L3": 313_000, # §36 (fisco + funding)
|
||||
"anni_L1_500": 14.7, # §36 riga "EUR 500 -> 14,7a"
|
||||
"worst_close": -0.0394, "worst_intraday": -0.0733, # §33
|
||||
"L_strutt": 0.1432, # §33 sez.5 (bound aritmetico a k=1)
|
||||
"xi_gpd": 0.09, "xi_hill": 0.34, # §33
|
||||
"k_da_SL": 1.67, # SPEC §4.5 = 0.50/(2*0.50*0.30)
|
||||
}
|
||||
|
||||
CACHE = Path("/tmp/claude-1001/-opt-docker-PythagorasGoal/"
|
||||
"b6cc75e7-14f8-4c32-bd07-ab8a0d2aaee6/scratchpad/pianovero/book.parquet")
|
||||
T0 = time.time()
|
||||
ESITI: dict[str, tuple[str, str]] = {}
|
||||
|
||||
|
||||
def sez(t: str) -> None:
|
||||
print("\n" + "=" * 108)
|
||||
print(f" {t}")
|
||||
print("=" * 108)
|
||||
|
||||
|
||||
def clock() -> str:
|
||||
return f"[{time.time() - T0:5.0f}s]"
|
||||
|
||||
|
||||
def esito(chiave: str, verdetto: str, riga: str) -> None:
|
||||
ESITI[chiave] = (verdetto, riga)
|
||||
print(f"\n >>> {chiave}: {verdetto} — {riga}")
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
# (0) IL CRITERIO — congelato QUI, valutato in (8). Nessuna soglia usa un numero delle
|
||||
# sezioni 2-7: vengono dalle regole gia' scritte del progetto o sono dichiarate a priori.
|
||||
# ==========================================================================================
|
||||
CRITERIO = {
|
||||
"A1": ("DRIFT — il gradino 1,25x FALSIFICA se il drift di pareggio (quello a cui "
|
||||
"g(1,25) = g(1,00)) cade DENTRO l'IC95 bootstrap a blocchi del drift. "
|
||||
"INCRINA se cade fra l'IC95 e l'IC99. REGGE se e' fuori da entrambi."),
|
||||
"A2": ("SINISTRO — il gradino FALSIFICA se Delta g(1,25) < 0 in almeno una finestra "
|
||||
"stressata IN CUI IL LIBRO STESSO HA DRIFT POSITIVO. Dove il libro e' in perdita "
|
||||
"la finestra giudica il LIBRO, non il gradino, e non conta (nota dovuta di §33)."),
|
||||
"A3": ("CODA — si prende lo stimatore PEGGIORE (Hill, xi=0.34) e lo si porta fino in "
|
||||
"fondo. FALSIFICA se a k=1,25 (i) il peggior giorno strutturale supera il 50% "
|
||||
"dell'equity, oppure (ii) P(dimezzamento in 10 anni) supera il 5% E piu' del "
|
||||
"triplo del valore a k=1,00. Soglie: il 50% e' il C1 gia' dichiarato da WORST-DAY; "
|
||||
"il 5%/3x e' dichiarato qui, prima di guardare."),
|
||||
"A4": ("LENTE — FALSIFICA se sotto la lente ACCOPPIATA (i) la frequenza di tocco del "
|
||||
"disaster-SL cresce con k, oppure (ii) il ricarico del maxDD smette di essere "
|
||||
"moltiplicativo (rapporto accoppiato/close-only che si muove di piu' di 1pp fra "
|
||||
"k=1,00 e k=1,50)."),
|
||||
"A5": ("INVARIANTE — FALSIFICA se esiste una chiave RAGGIUNGIBILE, non presente in "
|
||||
"`n_asset * frac * scala * disaster_sl_pct <= 0,50`, che possa ALZARE la leva "
|
||||
"lorda vera lasciando l'invariante invariato o soddisfatto."),
|
||||
"A6": ("FUNDING — il funding SCALA col nozionale, quindi con k. Rifatto il conto con il "
|
||||
"funding dentro e proporzionale a k, il gradino FALSIFICA se il guadagno sul "
|
||||
"capitale-rendita scende sotto 1,0 anno (soglia del mandato)."),
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
# UTILITA'
|
||||
# ==========================================================================================
|
||||
def deluck(r: np.ndarray) -> np.ndarray:
|
||||
"""Toglie dal DRIFT la fortuna d'ancora misurata. La vol NON si tocca: fra le ancore era
|
||||
invariata (7,80% -> 7,76%), la fortuna sta tutta nel drift (r0726_deluck_factor)."""
|
||||
r = np.asarray(r, float)
|
||||
return r - (1.0 - DELUCK) * r.mean()
|
||||
|
||||
|
||||
def g_exact(r: np.ndarray, k: float) -> float:
|
||||
"""Crescita geometrica annua a leva k: exp(365 * E[log(1+k r)]) - 1. Forma CHIUSA: nessun
|
||||
rumore Monte Carlo, quindi i confronti fra k sono esatti."""
|
||||
x = 1.0 + k * np.asarray(r, float)
|
||||
if np.any(x <= 0):
|
||||
return -np.inf
|
||||
return float(np.expm1(ANN * np.mean(np.log(x))))
|
||||
|
||||
|
||||
def k_star(r: np.ndarray, kmax: float = 40.0, step: float = 0.01) -> tuple[float, float]:
|
||||
ks = np.arange(step, kmax + step, step)
|
||||
gs = np.array([g_exact(r, k) for k in ks])
|
||||
j = int(np.nanargmax(gs))
|
||||
return float(ks[j]), float(gs[j])
|
||||
|
||||
|
||||
def shift_drift(r: np.ndarray, mu_annuo: float) -> np.ndarray:
|
||||
"""Stessa serie, drift imposto. Cambia UN grado di liberta' per volta: la forma della
|
||||
distribuzione (vol, asimmetria, coda, clustering) resta identica."""
|
||||
r = np.asarray(r, float)
|
||||
return r - r.mean() + mu_annuo / ANN
|
||||
|
||||
|
||||
def boot_drift(r: np.ndarray, n: int = 4000, block: int = BLOCK,
|
||||
seed: int = SEED_BOOT) -> np.ndarray:
|
||||
"""Bootstrap A BLOCCHI del drift annuo. A blocchi perche' la serie ha vol-clustering e
|
||||
momentum: un bootstrap iid darebbe un IC piu' STRETTO del vero, cioe' aiuterebbe la tesi
|
||||
che questo file sta attaccando."""
|
||||
rng = np.random.default_rng(seed)
|
||||
m = len(r)
|
||||
nb = int(np.ceil(m / block))
|
||||
out = np.empty(n)
|
||||
for i in range(n):
|
||||
st = rng.integers(0, m - block, size=nb)
|
||||
idx = (st[:, None] + np.arange(block)[None, :]).ravel()[:m]
|
||||
out[i] = r[idx].mean() * ANN
|
||||
return out
|
||||
|
||||
|
||||
def muro_di(r: np.ndarray) -> tuple[float, float]:
|
||||
"""(rendita perpetua, capitale-rendita) con la convenzione COERENTE (imposte dentro il
|
||||
portafoglio, prelievo gia' netto). Bisezione identica a PN.muro, con N_WALL dichiarato."""
|
||||
netto = CC.TARGET_EUR_DAY * 365 * CC.EURUSD
|
||||
lo, hi = 0.0, 0.40
|
||||
for _ in range(13):
|
||||
mid = (lo + hi) / 2
|
||||
s = PN.sopravvivenza(r, 1e6, 1e6 * mid, 20, CC.TAX_RATE, TX.PATRIMONIALE,
|
||||
n_paths=N_WALL, seed=SEED)
|
||||
if s["p10_end"] >= 1e6:
|
||||
lo = mid
|
||||
else:
|
||||
hi = mid
|
||||
return lo, (netto / lo if lo > 0.002 else float("inf"))
|
||||
|
||||
|
||||
def anni_al_muro(r: np.ndarray, bersaglio: float, dep_eur: float = DEP_EUR,
|
||||
start: float = START) -> dict:
|
||||
"""Anni al capitale-rendita. Path APPAIATI fra k (gli indici del bootstrap dipendono solo
|
||||
dal seme e dalla lunghezza, non dai valori)."""
|
||||
rng = np.random.default_rng(SEED)
|
||||
paths = CC._boot_paths(r, N_TRAJ, ANNI_TRAJ * 365, BLOCK, rng)
|
||||
o = PN.accumula(paths, dep_eur, bersaglio, CC.TAX_RATE, TX.PATRIMONIALE, start=start)
|
||||
c = o["colpito"]
|
||||
y = np.where(c >= 0, c / 365.0, np.inf)
|
||||
arr = y[np.isfinite(y)]
|
||||
return dict(med=float(np.median(y)),
|
||||
med_cond=float(np.median(arr)) if len(arr) > 100 else float("nan"),
|
||||
p20=float((y <= 20.0).mean()), p_arr=float(np.isfinite(y).mean()))
|
||||
|
||||
|
||||
def cap_20a(r: np.ndarray, dep_eur: float = DEP_EUR, start: float = START,
|
||||
anni: int = 20) -> float:
|
||||
"""Capitale mediano a 20 anni. Serve dove gli ANNI non sono definiti: a drift bassi il
|
||||
capitale-rendita non si raggiunge in 25 anni su nessun path e la mediana degli anni e' nan
|
||||
per ENTRAMBI i k — cioe' la metrica smette di discriminare proprio dove serve. Il capitale
|
||||
e' definito sempre, ed e' APPAIATO fra k (stessi indici di bootstrap)."""
|
||||
rng = np.random.default_rng(SEED)
|
||||
paths = CC._boot_paths(r, N_TRAJ, anni * 365, BLOCK, rng)
|
||||
o = PN.accumula(paths, dep_eur, 1e18, CC.TAX_RATE, TX.PATRIMONIALE, start=start)
|
||||
return float(np.median(o["cap"]))
|
||||
|
||||
|
||||
def fmt_anni(d: dict) -> str:
|
||||
if not np.isfinite(d["med_cond"]):
|
||||
return f" n/d (P20 {d['p20']:.3f})"
|
||||
return f"{d['med_cond']:5.2f}a (P20 {d['p20']:.3f})"
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
# (1) REPLICA — prima di attaccare
|
||||
# ==========================================================================================
|
||||
def sezione1() -> dict:
|
||||
sez("(1) REPLICA — 10 numeri pubblicati, riprodotti PRIMA di attaccarli")
|
||||
if not CACHE.exists():
|
||||
raise SystemExit("cache serie assente: girare prima r0822d_piano_vero.py")
|
||||
d = pd.read_parquet(CACHE)
|
||||
r_nf, r_f = d["nofund"].values.astype(float), d["fund"].values.astype(float)
|
||||
print(f" serie: {len(r_nf)} giorni, {d.index.min().date()} -> {d.index.max().date()} "
|
||||
f"(cache di r0822d_piano_vero, zero rete)")
|
||||
R = {"nofund": r_nf, "fund": r_f, "L1": deluck(r_nf), "L3": deluck(r_f)}
|
||||
|
||||
righe = []
|
||||
for nome, x, pub in (("drift senza funding", r_nf.mean() * ANN, PUB["drift_nofund"]),
|
||||
("drift con funding", r_f.mean() * ANN, PUB["drift_fund"]),
|
||||
("Sharpe senza funding", r_nf.mean() / r_nf.std() * ANN ** .5,
|
||||
PUB["sh_nofund"]),
|
||||
("Sharpe con funding", r_f.mean() / r_f.std() * ANN ** .5,
|
||||
PUB["sh_fund"])):
|
||||
righe.append((nome, x, pub))
|
||||
ks_nf, _ = k_star(deluck(r_nf))
|
||||
ks_f, _ = k_star(deluck(r_f))
|
||||
righe += [("k* senza funding", ks_nf, PUB["kstar_nofund"]),
|
||||
("k* con funding", ks_f, PUB["kstar_fund"]),
|
||||
("g(1,00) serie SPEC", g_exact(R["L1"], 1.00), PUB["g1"]),
|
||||
("g(1,25) serie SPEC", g_exact(R["L1"], 1.25), PUB["g125"]),
|
||||
("Sharpe (invariante a k)", R["L1"].mean() / R["L1"].std() * ANN ** .5,
|
||||
PUB["sharpe_scale"])]
|
||||
|
||||
print(f"\n {'grandezza':<26}{'replica':>12}{'pubblicato':>13}{'|diff|':>11}")
|
||||
for nome, x, pub in righe:
|
||||
print(f" {nome:<26}{x:>12.4f}{pub:>13.4f}{abs(x - pub):>11.4f}")
|
||||
|
||||
p1, m1 = muro_di(R["L1"])
|
||||
p3, m3 = muro_di(R["L3"])
|
||||
print(f" {'perpetua L1 (fisco)':<26}{p1:>12.4f}{PUB['perp_L1']:>13.4f}{abs(p1-PUB['perp_L1']):>11.4f}")
|
||||
print(f" {'muro L1 ($)':<26}{m1:>12,.0f}{PUB['muro_L1']:>13,.0f}{abs(m1-PUB['muro_L1']):>11,.0f}")
|
||||
print(f" {'perpetua L3 (+funding)':<26}{p3:>12.4f}{PUB['perp_L3']:>13.4f}{abs(p3-PUB['perp_L3']):>11.4f}")
|
||||
print(f" {'muro L3 ($)':<26}{m3:>12,.0f}{PUB['muro_L3']:>13,.0f}{abs(m3-PUB['muro_L3']):>11,.0f}")
|
||||
a1 = anni_al_muro(R["L1"], m1)
|
||||
print(f" {'anni L1 @EUR500/m':<26}{a1['med_cond']:>12.2f}{PUB['anni_L1_500']:>13.2f}"
|
||||
f"{abs(a1['med_cond']-PUB['anni_L1_500']):>11.2f} (risoluzione MC dichiarata ~1%)")
|
||||
|
||||
# --- il confronto APPAIATO fra k e' esatto: lo si PROVA, non lo si dichiara -------------
|
||||
rng = np.random.default_rng(SEED)
|
||||
p_a = CC._boot_paths(R["L3"], 200, 2000, BLOCK, rng)
|
||||
rng = np.random.default_rng(SEED)
|
||||
p_b = CC._boot_paths(1.25 * R["L3"], 200, 2000, BLOCK, rng)
|
||||
dmax = float(np.abs(p_b - 1.25 * p_a).max())
|
||||
print(f"\n appaiamento fra k: max|paths(1,25 r) - 1,25*paths(r)| = {dmax:.2e} "
|
||||
f"-> i path sono gli STESSI, il confronto fra k non ha rumore MC di campionamento")
|
||||
assert dmax == 0.0
|
||||
|
||||
# --- la lente accoppiata: quale serie e' -------------------------------------------------
|
||||
Tc = PC.crypto_daily_tuples(0.75, 0.25)
|
||||
print(f" lente accoppiata (r0725_prop_coupled): {len(Tc)} giorni, peggior chiusura "
|
||||
f"{Tc['R'].min():+.2%} ({Tc['R'].idxmin().date()}), peggior minimo intra-giorno "
|
||||
f"{Tc['m'].min():+.2%} ({Tc['m'].idxmin().date()})")
|
||||
print(f" -> il minimo intra-giorno replica il {PUB['worst_intraday']:+.2%} di §33 "
|
||||
f"(|diff| {abs(Tc['m'].min()-PUB['worst_intraday']):.4f}); la chiusura NO "
|
||||
f"({Tc['R'].min():+.2%} contro {PUB['worst_close']:+.2%}) perche' §33 legge la "
|
||||
f"chiusura sulla serie del libro e questa e' la ricostruzione ORARIA — differenza di "
|
||||
f"lente gia' registrata in §33 ('aveva ragione, con la lente sbagliata'). "
|
||||
f"[DICHIARATO, non riparato: A4 usa il MINIMO, che replica.]")
|
||||
return {"R": R, "muri": {"L1": (p1, m1), "L3": (p3, m3)}, "anni_L1_k1": a1, "tuples": Tc}
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
# (2) A1 — IL DRIFT E' STIMATO SU 7,4 ANNI CON DUE TORI
|
||||
# ==========================================================================================
|
||||
def sezione2(S: dict) -> None:
|
||||
sez("(2) A1 — il drift e' stimato su 7,4 anni con due tori, e k* e' LINEARE nel suo errore")
|
||||
r = S["R"]["L3"] # la lente ONESTA: de-luckata E col funding dentro
|
||||
mu = r.mean() * ANN
|
||||
vol = r.std() * ANN ** 0.5
|
||||
dr = boot_drift(r)
|
||||
lo95, hi95 = np.percentile(dr, [2.5, 97.5])
|
||||
lo99, hi99 = np.percentile(dr, [0.5, 99.5])
|
||||
se_b = dr.std()
|
||||
se_naive = vol / np.sqrt(len(r) / ANN)
|
||||
print(f" [MISURATO] drift de-luckato col funding: {mu:+.2%}/anno, vol {vol:.2%}, "
|
||||
f"{len(r)/ANN:.1f} anni di campione")
|
||||
print(f" [MISURATO] bootstrap a blocchi ({BLOCK}g, 4000 estrazioni): "
|
||||
f"SE {se_b:.2%} (SE ingenuo iid {se_naive:.2%}: il clustering ALLARGA di "
|
||||
f"{se_b/se_naive:.2f}x -> il bootstrap a blocchi e' la scelta severa)")
|
||||
print(f" IC95 [{lo95:+.2%}, {hi95:+.2%}] IC99 [{lo99:+.2%}, {hi99:+.2%}]")
|
||||
|
||||
# k* propagato
|
||||
print(f"\n [CALC] k* propagato sul drift (la forma della distribuzione NON cambia: "
|
||||
f"si sposta solo la media)")
|
||||
print(f" {'drift':>10}{'k*':>9}{'g(1,00)':>11}{'g(1,25)':>11}{'g(1,25)-g(1,00)':>18}")
|
||||
for nome, m in (("punto", mu), ("-1 SE", mu - se_b), ("IC95 basso", lo95),
|
||||
("IC99 basso", lo99), ("-3 SE", mu - 3 * se_b)):
|
||||
x = shift_drift(r, m)
|
||||
ks, _ = k_star(x)
|
||||
g1, g125 = g_exact(x, 1.00), g_exact(x, 1.25)
|
||||
print(f" {nome:>10} {m:>+8.2%}{ks:>9.2f}{g1:>11.2%}{g125:>11.2%}{g125-g1:>+18.2%}")
|
||||
|
||||
# drift di pareggio del gradino, su g
|
||||
lo, hi = -0.20, 0.40
|
||||
for _ in range(70):
|
||||
mid = (lo + hi) / 2
|
||||
if g_exact(shift_drift(r, mid), 1.25) - g_exact(shift_drift(r, mid), 1.00) > 0:
|
||||
hi = mid
|
||||
else:
|
||||
lo = mid
|
||||
mu_be = hi
|
||||
q_be = float((dr < mu_be).mean())
|
||||
z_be = (mu_be - mu) / se_b
|
||||
print(f"\n [CALC] DRIFT DI PAREGGIO del gradino 1,25x: {mu_be:+.4%}/anno")
|
||||
print(f" = {z_be:+.2f} SE dal punto, percentile bootstrap {q_be:.4%}")
|
||||
print(f" = il libro puo' perdere il {(1-mu_be/mu):.1%} del suo drift e il gradino "
|
||||
f"resta NON dannoso")
|
||||
print(f" [CALC] (aritmetica di controllo: per g ~ mu*k - sigma^2 k^2/2 il pareggio sta a "
|
||||
f"k*=1,125, cioe' mu = 1,125*sigma^2 = {1.125*vol**2:+.4%} — "
|
||||
f"|diff| {abs(mu_be-1.125*vol**2):.4%})")
|
||||
|
||||
# controllo empirico: il peggior drift che il libro abbia MAI realizzato su 3 anni
|
||||
ser = pd.Series(r)
|
||||
roll3 = ser.rolling(3 * 365).mean().dropna() * ANN
|
||||
roll2 = ser.rolling(2 * 365).mean().dropna() * ANN
|
||||
print(f"\n [MISURATO] controllo EMPIRICO, che non dipende da un modello di incertezza: "
|
||||
f"il peggior drift realizzato")
|
||||
print(f" su finestra mobile di 3 anni = {roll3.min():+.2%} "
|
||||
f"(mediana {roll3.median():+.2%}); su 2 anni = {roll2.min():+.2%}")
|
||||
print(f" -> anche il PEGGIOR triennio mai vissuto sta {roll3.min()/mu_be:.1f}x "
|
||||
f"sopra il drift di pareggio del gradino.")
|
||||
|
||||
# la metrica che DECIDE: anni al capitale-rendita. A drift bassi NON e' definita per
|
||||
# nessuno dei due k (nessun path arriva): li' si legge il CAPITALE a 20 anni, appaiato.
|
||||
print(f"\n [MISURATO] e sulla metrica che decide davvero (EUR{DEP_EUR:.0f}/m da "
|
||||
f"${START:.0f}, muro RICALCOLATO a ogni drift e a ogni k):")
|
||||
print(f" {'drift':>12}{'muro k=1,00':>13}{'anni k=1,00':>21}{'muro k=1,25':>13}"
|
||||
f"{'anni k=1,25':>21}{'cap 20a k=1,00':>16}{'cap 20a k=1,25':>16}")
|
||||
guadagni = {}
|
||||
for nome, m in (("punto", mu), ("-1 SE", mu - se_b), ("IC95 basso", lo95),
|
||||
("meta' drift", mu / 2)):
|
||||
x = shift_drift(r, m)
|
||||
_, m1 = muro_di(x)
|
||||
a1 = anni_al_muro(x, m1)
|
||||
_, m2 = muro_di(1.25 * x)
|
||||
a2 = anni_al_muro(1.25 * x, m2)
|
||||
c1, c2 = cap_20a(x), cap_20a(1.25 * x)
|
||||
g = (a1["med_cond"] - a2["med_cond"]
|
||||
if np.isfinite(a1["med_cond"]) and np.isfinite(a2["med_cond"]) else float("nan"))
|
||||
guadagni[nome] = (g, c2 / c1)
|
||||
print(f" {nome:>12}{m1:>13,.0f}{fmt_anni(a1):>21}{m2:>13,.0f}{fmt_anni(a2):>21}"
|
||||
f"{c1:>16,.0f}{c2:>16,.0f}")
|
||||
r_ic, r_meta = guadagni["IC95 basso"][1], guadagni["meta' drift"][1]
|
||||
print(f" ⚠️ a drift bassi gli ANNI non sono definiti PER ENTRAMBI i k (nessun path "
|
||||
f"raggiunge un muro che nel frattempo e' esploso): li' discrimina solo il capitale, "
|
||||
f"e il rapporto cap(1,25)/cap(1,00) resta {r_ic:.2f}x all'estremo basso dell'IC95 "
|
||||
f"e {r_meta:.2f}x a meta' drift.")
|
||||
print(f" {clock()}")
|
||||
|
||||
if mu_be >= lo95:
|
||||
v, riga = "FALSIFICA", f"il pareggio {mu_be:+.2%} cade DENTRO l'IC95"
|
||||
elif mu_be >= lo99:
|
||||
v, riga = "INCRINA", (f"il pareggio {mu_be:+.2%} cade fra IC95 ({lo95:+.2%}) e "
|
||||
f"IC99 ({lo99:+.2%})")
|
||||
else:
|
||||
v, riga = "REGGE", (f"il gradino diventa dannoso solo sotto {mu_be:+.2%}/anno di drift "
|
||||
f"= {z_be:+.2f} SE, percentile bootstrap {q_be:.2%}, fuori da IC95 "
|
||||
f"[{lo95:+.2%}] e da IC99 [{lo99:+.2%}]; e il PEGGIOR triennio mai "
|
||||
f"realizzato ({roll3.min():+.2%}) ne sta {roll3.min()/mu_be:.1f}x "
|
||||
f"sopra. All'estremo basso dell'IC95 il capitale a 20 anni resta "
|
||||
f"{guadagni['IC95 basso'][1]:.2f}x")
|
||||
esito("A1 DRIFT", v, riga)
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
# (3) A2 — IL CAMPIONE NON CONTIENE IL SINISTRO CHE CONTA
|
||||
# ==========================================================================================
|
||||
def sezione3(S: dict) -> None:
|
||||
sez("(3) A2 — il campione non contiene il sinistro che conta: finestre stressate e disgiunte")
|
||||
d = pd.read_parquet(CACHE)
|
||||
idx = d.index
|
||||
r_full = deluck(d["fund"].values.astype(float))
|
||||
ser = pd.Series(r_full, index=idx)
|
||||
|
||||
fin = [("PIENA 2019-03 -> oggi", ser)]
|
||||
tolti = ser[(idx < "2020-01-01") | (idx > "2021-12-31")]
|
||||
fin.append(("SENZA 2020-2021 (il toro che finanzia il drift)", tolti))
|
||||
bordi = ["2019-03-14", "2021-03-14", "2023-03-14", "2025-03-14", "2026-08-23"]
|
||||
for a, b in zip(bordi[:-1], bordi[1:]):
|
||||
w = ser[(idx >= a) & (idx < b)]
|
||||
fin.append((f"disgiunta {a[:7]} -> {b[:7]}", w))
|
||||
# la finestra PEGGIORE non e' fra quelle disgiunte: si cerca il minimo su griglia MOBILE
|
||||
# (attacco piu' forte: le disgiunte sono una partizione arbitraria, non il caso peggiore)
|
||||
W = 2 * 365
|
||||
mroll = ser.rolling(W).mean()
|
||||
j = int(np.nanargmin(mroll.values))
|
||||
peggiore2a = ser.iloc[j - W + 1: j + 1]
|
||||
fin.append((f"MOBILE peggior biennio ({peggiore2a.index[0].date()})", peggiore2a))
|
||||
|
||||
print(f" ⚠️ ogni finestra riporta ANCHE il drift del libro su di essa: dove il libro e' in "
|
||||
f"perdita la finestra giudica il LIBRO, non il gradino (nota dovuta di §33).")
|
||||
print(f"\n {'finestra':<46}{'n':>6}{'drift':>10}{'vol':>9}{'Sharpe':>9}{'k*':>8}"
|
||||
f"{'g(1,00)':>10}{'g(1,25)':>10}{'Delta g':>10}{'':>4}")
|
||||
peggiori = []
|
||||
for nome, w in fin:
|
||||
x = w.values
|
||||
if len(x) < 120:
|
||||
continue
|
||||
m, v = x.mean() * ANN, x.std() * ANN ** 0.5
|
||||
ks, _ = k_star(x)
|
||||
g1, g2 = g_exact(x, 1.00), g_exact(x, 1.25)
|
||||
flag = "" if m > 0 else " <- LIBRO NEGATIVO"
|
||||
print(f" {nome:<46}{len(x):>6}{m:>+10.2%}{v:>9.2%}{m/v:>9.2f}{ks:>8.2f}"
|
||||
f"{g1:>+10.2%}{g2:>+10.2%}{g2-g1:>+10.2%}{flag}")
|
||||
peggiori.append((nome, m, g2 - g1, x))
|
||||
|
||||
giudicabili = [(n, m, dg) for n, m, dg, _ in peggiori if m > 0 and "PIENA" not in n]
|
||||
rotte = [n for n, m, dg in giudicabili if dg <= 0]
|
||||
non_giud = [n for n, m, dg, _ in peggiori if m <= 0]
|
||||
|
||||
# Delta sulle due finestre piu' avverse fra quelle giudicabili. ⚠️ Su una finestra di 2
|
||||
# anni il muro esplode (la perpetua vive sul rapporto drift/rischio di quella finestra) e
|
||||
# gli ANNI non sono definiti per NESSUN k: si riporta anche il capitale a 20 anni, appaiato.
|
||||
print(f"\n [MISURATO] le due finestre GIUDICABILI piu' avverse, muro ricalcolato DENTRO "
|
||||
f"la finestra (⚠️ su 2 anni il muro non e' un numero di pianificazione: e' una lettura "
|
||||
f"di quel regime):")
|
||||
cand = sorted([(n, m, dg, x) for n, m, dg, x in peggiori if m > 0 and "PIENA" not in n],
|
||||
key=lambda t: t[1])[:2]
|
||||
for nome, m, dg, x in cand:
|
||||
_, m1 = muro_di(x)
|
||||
a1 = anni_al_muro(x, m1)
|
||||
_, m2 = muro_di(1.25 * x)
|
||||
a2 = anni_al_muro(1.25 * x, m2)
|
||||
c1, c2 = cap_20a(x), cap_20a(1.25 * x)
|
||||
print(f" {nome:<46} muro ${m1:>9,.0f} -> ${m2:>9,.0f} {fmt_anni(a1)} -> "
|
||||
f"{fmt_anni(a2)} cap20a ${c1:>9,.0f} -> ${c2:>9,.0f} ({c2/c1:.2f}x)")
|
||||
|
||||
# il costo del gradino DOVE fa male: il biennio mobile peggiore
|
||||
nb, mb, dgb, xb = min(peggiori, key=lambda t: t[1])
|
||||
g1b, g2b = g_exact(xb, 1.00), g_exact(xb, 1.25)
|
||||
cum = (1 + g2b) ** 2 / (1 + g1b) ** 2 - 1
|
||||
print(f"\n 🚨 [MISURATO] LA FORMA PIU' FORTE DELL'ATTACCO TROVA DAVVERO UNA FINESTRA IN CUI "
|
||||
f"IL GRADINO FA MALE — ed e' quella in cui il LIBRO perde:")
|
||||
print(f" {nb}: drift {mb:+.2%}, g(1,00) {g1b:+.2%}, g(1,25) {g2b:+.2%}, "
|
||||
f"Delta {dgb:+.2%}/anno -> costo CUMULATO del gradino su quei 2 anni: {cum:+.2%} "
|
||||
f"di capitale.")
|
||||
print(f" Da mettere accanto al guadagno: sul campione pieno il gradino vale "
|
||||
f"{g_exact(ser.values,1.25)-g_exact(ser.values,1.00):+.2%}/anno. "
|
||||
f"Rapporto {abs((g_exact(ser.values,1.25)-g_exact(ser.values,1.00))/dgb):.0f}:1.")
|
||||
print(f" Per il CRITERIO dichiarato in (0) quella finestra non falsifica (il libro "
|
||||
f"stesso e' negativo: la leva amplifica un segno che li' e' gia' sbagliato, e lo "
|
||||
f"stesso vale per il gradino 1,00x -> 0,75x nel verso opposto). "
|
||||
f"**Ma la qualifica va detta col numero, non con un aggettivo.**")
|
||||
print(f"\n ⚠️ [LIMITE DICHIARATO] a parte quel biennio, NESSUNA finestra ha drift negativo: "
|
||||
f"il libro non ha mai vissuto un TRIENNIO in perdita, e il peggior biennio perde lo "
|
||||
f"{abs(mb):.2%}/anno. L'attacco nella sua forma piu' forte — «un regime in cui il "
|
||||
f"libro perde davvero» — **non e' esprimibile su questo campione**, e questo E' il "
|
||||
f"fatto che il mandato temeva. La sostituzione onesta e' A1: si IMPONE il drift "
|
||||
f"invece di cercarlo. Le due misure si incastrano — peggior biennio {mb:+.2%}, "
|
||||
f"pareggio del gradino +1,42%.")
|
||||
print(f" {clock()}")
|
||||
|
||||
if rotte:
|
||||
v, riga = "FALSIFICA", f"Delta g(1,25) <= 0 su finestre giudicabili: {', '.join(rotte)}"
|
||||
else:
|
||||
peggior = min(giudicabili, key=lambda t: t[2]) if giudicabili else ("n/d", 0, 0)
|
||||
v, riga = "REGGE", (f"Delta g(1,25) > 0 in {len(giudicabili)}/{len(giudicabili)} "
|
||||
f"finestre giudicabili (la peggiore: {peggior[0]}, {peggior[2]:+.2%}), "
|
||||
f"togliendo il toro 2020-21 compreso. QUALIFICA MISURATA: nel biennio "
|
||||
f"mobile peggiore ({nb}, drift {mb:+.2%}) il gradino COSTA "
|
||||
f"{dgb:+.2%}/anno = {cum:+.2%} cumulato — quella finestra non "
|
||||
f"falsifica per il criterio (li' e' il libro a perdere), ma il "
|
||||
f"numero e' questo, ed e' il costo del gradino nel suo caso peggiore "
|
||||
f"realizzato")
|
||||
esito("A2 SINISTRO", v, riga)
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
# (4) A3 — LA CODA PEGGIORE, PORTATA FINO IN FONDO
|
||||
# ==========================================================================================
|
||||
def hill_xi(loss: np.ndarray, k: int) -> float:
|
||||
"""Stimatore di Hill sulle k eccedenze piu' grandi (solo perdite POSITIVE)."""
|
||||
x = np.sort(loss[loss > 0])[::-1]
|
||||
k = min(k, len(x) - 1)
|
||||
return float(np.mean(np.log(x[:k])) - np.log(x[k]))
|
||||
|
||||
|
||||
def sezione4(S: dict) -> None:
|
||||
sez("(4) A3 — la coda: due stimatori che non concordano -> si prende il PEGGIORE")
|
||||
r = S["R"]["L3"]
|
||||
loss = -r
|
||||
q = 0.95
|
||||
u = float(np.quantile(loss, q))
|
||||
ex = loss[loss > u] - u
|
||||
xi_g, _, beta = stats.genpareto.fit(ex, floc=0.0)
|
||||
k_hill = int(0.05 * len(loss))
|
||||
xi_h = hill_xi(loss, k_hill)
|
||||
print(f" [MISURATO] soglia 95° pctl = {u:.2%}, {len(ex)} eccedenze")
|
||||
print(f" GPD xi = {xi_g:+.3f} (pubblicato {PUB['xi_gpd']:+.2f}) "
|
||||
f"Hill xi = {xi_h:+.3f} su k={k_hill} (pubblicato {PUB['xi_hill']:+.2f})")
|
||||
print(f" [MISURATO] Hill e' notoriamente sensibile a k -> lo sweep, invece del punto:")
|
||||
print(" " + " ".join(f"k={kk}: xi={hill_xi(loss, kk):+.3f}"
|
||||
for kk in (40, 70, 100, 135, 200, 300)))
|
||||
xi = max(xi_g, xi_h, PUB["xi_hill"])
|
||||
print(f" [SCELTA DICHIARATA] si porta avanti il PEGGIORE dei tre: xi = {xi:+.3f} "
|
||||
f"(piu' severo del {PUB['xi_hill']:+.2f} pubblicato: l'attacco si porta col numero "
|
||||
f"peggiore, non con quello citato)")
|
||||
|
||||
L = PUB["L_strutt"] # [CITATO] bound aritmetico di §33 a k=1
|
||||
print(f"\n (a) IL BOUND STRUTTURALE, scalato [CITATO §33: |L| = {L:.2%} a k=1,00]")
|
||||
print(f" {'k':>6}{'peggior giorno':>17}{'costo disaster-SL':>20}{'soglia 20%':>13}"
|
||||
f"{'soglia 50%':>13}")
|
||||
sl_pct = json.loads((ROOT / "config" / "live.json").read_text())["disaster_sl_pct"]
|
||||
frac = json.loads((ROOT / "config" / "live.json").read_text())["max_notional_per_asset_frac"]
|
||||
for k in KS:
|
||||
wd, cs = k * L, k * 2 * frac * sl_pct
|
||||
print(f" {k:>6.2f}{wd:>16.2%} {cs:>19.2%}"
|
||||
f"{'SFONDA' if wd > 0.20 else 'ok':>13}{'SFONDA' if wd > 0.50 else 'ok':>13}")
|
||||
k_20 = 0.20 / L
|
||||
k_50 = 0.50 / L
|
||||
k_sl50 = 0.50 / (2 * frac * sl_pct)
|
||||
print(f" [CALC] il peggior giorno strutturale sfonda il 20% (recupero +25%) a k = {k_20:.2f}")
|
||||
print(f" [CALC] ...e il 50% (C1 di §33) a k = {k_50:.2f}")
|
||||
print(f" [CALC] il costo del disaster-SL sfonda il 50% a k = {k_sl50:.2f} "
|
||||
f"(pubblicato {PUB['k_da_SL']:.2f}) -> ⚠️ il disaster-SL sfonda il 20% GIA' A k=1,00 "
|
||||
f"({2*frac*sl_pct:.0%}): quella soglia non separa i k, e va detto.")
|
||||
|
||||
# (b) coda peggiore dentro il campione: si SOSTITUISCONO le eccedenze storiche con
|
||||
# estrazioni GPD(xi) troncate al bound, MANTENENDO LE POSIZIONI (quindi il clustering).
|
||||
print(f"\n (b) P(DIMEZZAMENTO in 10 anni, senza prelievi) con la coda peggiore dentro")
|
||||
rng = np.random.default_rng(SEED_BOOT)
|
||||
pos = np.where(loss > u)[0]
|
||||
fmax = stats.genpareto.cdf(max(L - u, 1e-9), xi, loc=0.0, scale=beta)
|
||||
y = stats.genpareto.ppf(rng.random(len(pos)) * fmax, xi, loc=0.0, scale=beta)
|
||||
r_tail = r.copy()
|
||||
r_tail[pos] = -(u + y)
|
||||
r_tail = r_tail - r_tail.mean() + r.mean() # stesso drift: cambia SOLO la coda
|
||||
print(f" [CALC] serie a coda peggiore: drift {r_tail.mean()*ANN:+.2%} (invariato), "
|
||||
f"vol {r_tail.std()*ANN**.5:.2%} (era {r.std()*ANN**.5:.2%}), "
|
||||
f"peggior giorno {r_tail.min():+.2%} (era {r.min():+.2%})")
|
||||
ks_t, _ = k_star(r_tail)
|
||||
ks_0, _ = k_star(r)
|
||||
d_coda = ks_0 - ks_t
|
||||
d_drift = ks_0 - k_star(shift_drift(r, r.mean() * ANN / 2))[0]
|
||||
print(f" [MISURATO] k*: {ks_0:.2f} (coda storica) -> {ks_t:.2f} (coda peggiore) "
|
||||
f"= la coda muove k* di {d_coda:+.2f}; dimezzare il DRIFT lo muove di {d_drift:+.2f}")
|
||||
print(f" ⚠️ CORREZIONE a §33, che scriveva «la forma della coda muove k* di 1-2 "
|
||||
f"unita', il drift di 6,7»: con lo stimatore PEGGIORE la coda ne muove {d_coda:.1f}, "
|
||||
f"non 1-2. La CONCLUSIONE di §33 regge (il drift resta il fattore dominante, "
|
||||
f"{d_drift/d_coda:.1f}x) ma il numero pubblicato e' ottimista di ~{d_coda/1.5:.0f}x, "
|
||||
f"e a quella coda k*={ks_t:.2f} e' ancora {ks_t/1.25:.0f}x il gradino richiesto.")
|
||||
rng = np.random.default_rng(SEED)
|
||||
P = CC._boot_paths(r_tail, 4000, 10 * 365, BLOCK, rng)
|
||||
print(f" {'k':>6}{'P(dimezzamento)':>18}{'P(-80%)':>11}{'maxDD mediano':>16}"
|
||||
f"{'g(k) 10a mediano':>19}")
|
||||
p_half = {}
|
||||
for k in KS:
|
||||
eq = np.cumprod(1.0 + k * P, axis=1)
|
||||
run = np.maximum.accumulate(eq, axis=1)
|
||||
dd = (eq / run - 1.0).min(axis=1)
|
||||
half = float((eq.min(axis=1) <= 0.5).mean())
|
||||
p80 = float((eq.min(axis=1) <= 0.2).mean())
|
||||
p_half[k] = half
|
||||
print(f" {k:>6.2f}{half:>18.3%}{p80:>11.3%}{np.median(dd):>16.2%}"
|
||||
f"{np.median(eq[:, -1]) ** (1/10) - 1:>18.2%}")
|
||||
print(f" {clock()}")
|
||||
|
||||
rap = p_half[1.25] / p_half[1.00] if p_half[1.00] > 0 else float("inf")
|
||||
if 1.25 * L > 0.50 or (p_half[1.25] > 0.05 and rap > 3.0):
|
||||
v, riga = "FALSIFICA", (f"a k=1,25 il peggior giorno strutturale vale {1.25*L:.2%} "
|
||||
f"e P(dimezzamento) {p_half[1.25]:.2%}")
|
||||
else:
|
||||
v, riga = "REGGE", (f"con xi={xi:.2f} preso alla lettera: peggior giorno strutturale "
|
||||
f"{1.25*L:.2%} < 50%, P(dimezzamento 10a) {p_half[1.25]:.3%} contro "
|
||||
f"{p_half[1.00]:.3%} a k=1,00. Il bound strutturale sfonda il 50% "
|
||||
f"a k={k_50:.2f} e il 20% a k={k_20:.2f}; la coda muove k* di "
|
||||
f"{ks_0-ks_t:+.2f}, il drift di {ks_0 - k_star(shift_drift(r, r.mean()*ANN/2))[0]:+.2f}")
|
||||
esito("A3 CODA", v, riga)
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
# (5) A4 — LA LENTE ACCOPPIATA, E IL DISASTER-SL CHE E' UNA REGOLA SU UN LIVELLO
|
||||
# ==========================================================================================
|
||||
def sezione5(S: dict) -> None:
|
||||
sez("(5) A4 — la lente: close-only e' CIECA su una regola a un livello. "
|
||||
"Il disaster-SL E' una regola su un livello.")
|
||||
T = S["tuples"]
|
||||
R = T["R"].values
|
||||
m = T["m"].values
|
||||
|
||||
print(" (a) IL RICARICO DEL maxDD: e' moltiplicativo o si amplifica con k?")
|
||||
print(f" {'k':>6}{'maxDD close-only':>19}{'maxDD accoppiato':>19}{'ricarico':>11}"
|
||||
f"{'peggior giorno':>17}{'peggior minimo':>17}")
|
||||
ric = {}
|
||||
for k in KS:
|
||||
eq = np.cumprod(1.0 + k * R)
|
||||
prev = np.concatenate(([1.0], eq[:-1]))
|
||||
run_c = np.maximum.accumulate(eq)
|
||||
dd_c = float((eq / run_c - 1.0).min())
|
||||
low = prev * (1.0 + k * m) # minimo intra-giorno del PATH composto
|
||||
run_a = np.maximum.accumulate(np.maximum(eq, prev))
|
||||
dd_a = float((low / run_a - 1.0).min())
|
||||
ric[k] = dd_a / dd_c
|
||||
print(f" {k:>6.2f}{dd_c:>19.2%}{dd_a:>19.2%}{dd_a/dd_c:>11.4f}"
|
||||
f"{k*R.min():>17.2%}{k*m.min():>17.2%}")
|
||||
spread = max(ric.values()) - min(ric.values())
|
||||
print(f" [MISURATO] il ricarico si muove di {spread:.4f} fra k=1,00 e k=2,00 "
|
||||
f"(= {spread*100:.2f}pp): moltiplicativo, NON amplificante")
|
||||
|
||||
print(f"\n (b) LA FREQUENZA DEL DISASTER-SL E' INVARIANTE A k — dimostrazione dal codice, "
|
||||
f"non dai dati:")
|
||||
print(f" `execution.ensure_disaster_sl` calcola")
|
||||
print(f" want_stop = disaster_stop_price(instrument, MARK, sl_pct, long) -> un PREZZO")
|
||||
print(f" want_amount = notional_to_amount(instrument, |pos|, price=mark) -> una TAGLIA")
|
||||
print(f" e ri-piazza se |amt - want_amount| >= want_amount*0,1 OPPURE "
|
||||
f"|stp - want_stop|/want_stop >= 0,05.")
|
||||
print(f" Una scala k moltiplica `pos` (quindi `want_amount`) e NON tocca `mark`.")
|
||||
print(f" -> il livello di innesco e' invariante a k, e il test di ri-piazzamento e' "
|
||||
f"RELATIVO (`want_amount*0,1`), quindi anch'esso invariante a k.")
|
||||
print(f" ⇒ [DEDOTTO dal sorgente] a k piu' alto il disaster-SL NON viene toccato piu' "
|
||||
f"spesso: viene toccato le STESSE volte e costa k volte tanto. §40 misura la frequenza "
|
||||
f"(0-1 episodi su tutta la storia alla cadenza oraria che gira); questo file mostra che "
|
||||
f"quel numero non va rifatto per k.")
|
||||
|
||||
print(f"\n (c) CONTROLLO NUMERICO della (b): l'innesco e' un movimento del MARK, non "
|
||||
f"dell'equity. Conteggio dei giorni in cui il minimo INTRA-giorno del libro a leva k "
|
||||
f"scende sotto soglie di EQUITY (queste si', k-dipendenti):")
|
||||
print(f" {'k':>6}{'gg min < -5%':>15}{'gg min < -10%':>15}{'gg min < -20%':>15}"
|
||||
f"{'gg min < -30%':>15}")
|
||||
for k in KS:
|
||||
print(f" {k:>6.2f}{int((k*m < -0.05).sum()):>15}{int((k*m < -0.10).sum()):>15}"
|
||||
f"{int((k*m < -0.20).sum()):>15}{int((k*m < -0.30).sum()):>15}")
|
||||
print(f" -> nessuna soglia di EQUITY e' armata sul libro proprietario (il disaster-SL e' "
|
||||
f"sul PREZZO): questa tabella e' la misura di cosa succederebbe se ce ne fosse una, "
|
||||
f"ed e' la ragione per cui sul canale FUNDED — dove la regola a un giorno esiste — "
|
||||
f"la lente accoppiata resta obbligatoria (25/07).")
|
||||
|
||||
amp = ric[1.50] - ric[1.00]
|
||||
if abs(amp) > 0.01:
|
||||
v, riga = "FALSIFICA", f"il ricarico del maxDD si muove di {amp*100:+.2f}pp fra k=1,00 e 1,50"
|
||||
else:
|
||||
v, riga = "REGGE", (f"ricarico maxDD {ric[1.00]:.4f} (k=1,00) -> {ric[1.50]:.4f} (k=1,50) "
|
||||
f"-> {ric[2.00]:.4f} (k=2,00): movimento {amp*100:+.2f}pp nel range "
|
||||
f"della proposta e {(ric[2.00]-ric[1.00])*100:+.2f}pp fino a k=2, "
|
||||
f"e va nel verso FAVOREVOLE (il ricarico CALA). "
|
||||
f"La frequenza del disaster-SL e' invariante a k PER COSTRUZIONE "
|
||||
f"(innesco sul MARK, test di ri-piazzamento RELATIVO): il numero di "
|
||||
f"§40 non va rifatto per k, solo moltiplicato")
|
||||
esito("A4 LENTE", v, riga)
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
# (6) A5 — L'INVARIANTE A TRE CHIAVI: c'e' una QUARTA?
|
||||
# ==========================================================================================
|
||||
def sezione6() -> None:
|
||||
sez("(6) A5 — l'invariante `n_asset * frac * scala * disaster_sl_pct <= 0,50`: e' completo?")
|
||||
cfg = json.loads((ROOT / "config" / "live.json").read_text())
|
||||
frac = float(cfg["max_notional_per_asset_frac"])
|
||||
fixed = float(cfg["max_notional_per_asset_usd"])
|
||||
sl = float(cfg["disaster_sl_pct"])
|
||||
n_asset = 2
|
||||
wm = _read_watermark()
|
||||
lev_tp = float(CANONICAL["leverage"])
|
||||
print(f" [LETTO dal codice e da config, sola lettura]")
|
||||
print(f" config/live.json : frac={frac} fixed=${fixed:,.0f} disaster_sl_pct={sl}")
|
||||
print(f" src/live/shadow : ASSETS n={n_asset} WEIGHT={WEIGHT} "
|
||||
f"FALLBACK_CAPITAL=${FALLBACK_CAPITAL:,.0f}")
|
||||
print(f" src/live/book : W_TP01={W_TP01} W_SKH={W_SKH} "
|
||||
f"CAP_UNKNOWN_USD=${CAP_UNKNOWN_USD:,.0f} watermark=${wm:,.2f}"
|
||||
if wm else " src/live/book : watermark ASSENTE")
|
||||
print(f" trend_portfolio : CANONICAL['leverage']={lev_tp} "
|
||||
f"target_vol={CANONICAL['target_vol']}")
|
||||
|
||||
print(f"\n [CALC] la LEVA LORDA VERA, derivata dal sorgente invece che dichiarata.")
|
||||
print(f" raw = WEIGHT * E * (W_TP01*tp_frac + W_SKH*sign), tp_frac <= "
|
||||
f"CANONICAL['leverage'] = {lev_tp}")
|
||||
print(f" net = scala * clamp(raw, +-cap), cap = frac*E (percorso fidato)")
|
||||
print(f" lordo = n_asset * min(WEIGHT*(W_TP01*{lev_tp}+W_SKH), frac) * scala * E")
|
||||
raw_max = WEIGHT * (W_TP01 * lev_tp + W_SKH * 1.0)
|
||||
print(f" = {n_asset} * min({raw_max:.4f}, {frac}) * scala * E "
|
||||
f"= {n_asset*min(raw_max, frac):.4f} * scala * E")
|
||||
print(f" -> l'invariante usa `n_asset*frac*scala` = {n_asset*frac:.4f}*scala; il VERO e' "
|
||||
f"{n_asset*min(raw_max,frac):.4f}*scala. Coincidono qui perche' frac({frac}) < "
|
||||
f"WEIGHT*(...)={raw_max:.4f}. **L'invariante e' un LIMITE SUPERIORE, quindi safe.**")
|
||||
|
||||
# verifica esaustiva su una griglia di chiavi raggiungibili
|
||||
print(f"\n [MISURATO] verifica esaustiva: esiste una combinazione che SODDISFA l'invariante "
|
||||
f"e produce una leva lorda VERA superiore al suo bound?")
|
||||
viol = 0
|
||||
tot = 0
|
||||
for f_ in (0.25, 0.50, 0.625, 0.75, 1.0, 1.5):
|
||||
for w_ in (0.25, 0.5, 0.75, 1.0):
|
||||
for sc in (1.0, 1.25, 1.5, 2.0):
|
||||
for lv in (1.0, 2.0, 3.0):
|
||||
for na in (2, 3):
|
||||
tot += 1
|
||||
rmax = w_ * (W_TP01 * lv + W_SKH * 1.0)
|
||||
vero = na * min(rmax, f_) * sc
|
||||
bound = na * f_ * sc
|
||||
if vero > bound + 1e-12:
|
||||
viol += 1
|
||||
print(f" {tot} combinazioni (frac x WEIGHT x scala x leverage-TP01 x n_asset): "
|
||||
f"{viol} violazioni del bound. -> sul PERCORSO FIDATO l'invariante e' COMPLETO.")
|
||||
|
||||
print(f"\n 🚨 [MISURATO] MA IL PERCORSO FIDATO NON E' L'UNICO. Nel ramo di FALLBACK "
|
||||
f"(`eq_fallback`) `book_report` sizza su `paper_cap`, non sull'equity vera:")
|
||||
print(f" lordo_fallback = n_asset * min(WEIGHT*paper_cap*(...), min(fixed, wm*frac)) * scala")
|
||||
print(f" e la leva si misura contro l'equity VERA, che li' non e' nota: al suo posto c'e' "
|
||||
f"il WATERMARK, che e' l'ULTIMA equity osservata, non quella attuale.")
|
||||
print(f" {'scenario':<44}{'wm':>9}{'E vera':>9}{'lordo k=1,00':>14}{'/E':>8}"
|
||||
f"{'lordo k=1,25':>14}{'/E':>8}")
|
||||
righe = [("normale (equity leggibile)", 635.0, 635.0, False),
|
||||
("fallback, watermark fresco", 635.0, 635.0, True),
|
||||
("fallback dopo PRELIEVO del 50% non visto", 635.0, 317.5, True),
|
||||
("fallback dopo PRELIEVO dell'80% non visto", 635.0, 127.0, True),
|
||||
("fallback dopo perdita del 50% non vista", 635.0, 317.5, True)]
|
||||
peggio1 = peggio125 = 0.0
|
||||
for nome, w_, e_vera, fb in righe:
|
||||
if not fb:
|
||||
cap, eq_used = e_vera * frac, e_vera
|
||||
else:
|
||||
cap, eq_used = min(fixed, w_ * frac), FALLBACK_CAPITAL
|
||||
raw = WEIGHT * eq_used * (W_TP01 * lev_tp + W_SKH)
|
||||
l1 = n_asset * min(raw, cap)
|
||||
peggio1, peggio125 = max(peggio1, l1 / e_vera), max(peggio125, 1.25 * l1 / e_vera)
|
||||
print(f" {nome:<44}{w_:>9,.0f}{e_vera:>9,.0f}{l1:>14,.1f}{l1/e_vera:>7.2f}x"
|
||||
f"{1.25*l1:>14,.1f}{1.25*l1/e_vera:>7.2f}x")
|
||||
print(f"\n 🚨 IL BUCO ESISTE GIA' A k=1,00 ({peggio1:.2f}x) e NON e' creato dalla scala: "
|
||||
f"e' creato dal fatto che nel fallback il denominatore del rapporto e' il WATERMARK "
|
||||
f"(l'ultima equity OSSERVATA) e non l'equity attuale. La scala non lo apre: lo "
|
||||
f"MOLTIPLICA ({peggio1:.2f}x -> {peggio125:.2f}x) mentre l'invariante continua a "
|
||||
f"leggere {n_asset*frac:.2f}x e {n_asset*frac*1.25:.2f}x.")
|
||||
print(f" ⚠️ Percio' la guardia G3 della SPEC (`scala = 1,00 nel fallback`) **non chiude "
|
||||
f"il buco: ne toglie il moltiplicatore**. Resta NON OPZIONALE — senza di lei il "
|
||||
f"gradino peggiora del 25% uno scenario gia' fuori tetto — ma la SPEC la presenta "
|
||||
f"come «il fallback non e' piu' permissivo per costruzione», e questo e' vero solo "
|
||||
f"RISPETTO ALLA SCALA, non rispetto alla leva.")
|
||||
print(f" [DICHIARATO] frequenza misurata del ramo: 0 giri su 1.442 (§38); "
|
||||
f"`book_execute` su `eq_fallback` **allerta e NON blocca** (letto dal sorgente), "
|
||||
f"quindi lo scenario e' raro ma non impossibile. E' fuori dal perimetro del gradino: "
|
||||
f"si registra come debito, non come argomento contro.")
|
||||
peggio = peggio125
|
||||
|
||||
print(f"\n ⚠️ [DEDOTTO da §40] e c'e' una seconda incompletezza, che non e' una chiave ma un "
|
||||
f"VALORE: l'invariante moltiplica per `disaster_sl_pct`=0,30 come se fosse la perdita "
|
||||
f"massima per posizione. §40 ha misurato che NON lo e': lo stop e' ROTOLANTE "
|
||||
f"(ri-ancorato al mark a ogni giro), quindi una discesa lenta lo trascina giu' e "
|
||||
f"passano perdite fino a -60,6%/-61,5% dall'ingresso senza che scatti. "
|
||||
f"Il termine 0,30 e' il costo di UN episodio a movimento singolo, non un tetto: "
|
||||
f"l'invariante e' safe come tetto sulla LEVA e ottimista come tetto sulla PERDITA. "
|
||||
f"(k-indipendente: sposta la scala di tutta la colonna, non il confronto fra k.)")
|
||||
|
||||
esito("A5 INVARIANTE", "INCRINA",
|
||||
f"completo sul percorso FIDATO (0 violazioni su {tot} combinazioni), ma nel ramo di "
|
||||
f"FALLBACK il denominatore e' il watermark e non l'equity: leva vera fino a "
|
||||
f"{peggio1:.2f}x gia' a k=1,00 e {peggio125:.2f}x a k=1,25 contro "
|
||||
f"{n_asset*frac*1.25:.2f}x letti. La scala non apre il buco, lo moltiplica -> G3 e' "
|
||||
f"NON OPZIONALE ma non e' una chiusura. E il fattore 0,30 non e' un tetto di perdita "
|
||||
f"(§40: stop rotolante, passano -60% dall'ingresso)")
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
# (7) A6 — IL FUNDING SCALA CON LA LEVA
|
||||
# ==========================================================================================
|
||||
def sezione7(S: dict) -> None:
|
||||
sez("(7) A6 — il funding non era nel conto della leva, E IL FUNDING SCALA CON LA LEVA")
|
||||
print(f" [DEDOTTO] il funding e' `tasso x NOZIONALE`: a leva k il nozionale e' k volte, "
|
||||
f"quindi il costo e' k volte. Percio' la serie giusta e' k*(r_lordo - f), cioe' "
|
||||
f"k * (serie CON funding) — non k*r_lordo - f.")
|
||||
cf = (S["R"]["L1"].mean() - S["R"]["L3"].mean()) * ANN
|
||||
print(f" [MISURATO] verifica sul dato: costo del funding a k=1,00 = {cf:+.2%}/anno; "
|
||||
f"a k=1,25 = {cf*1.25:+.2%}/anno (esattamente 1,25x: lineare per costruzione).")
|
||||
print(f" ⚠️ {cf:.2%} e non il {-PUB['drift_fund']+PUB['drift_nofund']:.2%} "
|
||||
f"pubblicato da §35: qui la serie e' DE-LUCKATA (x0,89), e il de-luck agisce sul "
|
||||
f"drift, quindi anche sulla sua componente di funding. Stesso numero, lente diversa.")
|
||||
|
||||
print(f"\n ⚠️ UNA SCELTA DI MODELLO CHE VALE PIU' DEL FUNDING, e §33 non la dichiara: "
|
||||
f"il MURO si muove con k?")
|
||||
print(f" Se si gira a leva k anche IN PRELIEVO, la rendita perpetua sale e il muro "
|
||||
f"SCENDE -> il gradino paga due volte (accumuli prima E ti serve meno).")
|
||||
print(f" Se si de-leva al traguardo, il muro resta quello di k=1,00 -> paga una volta.")
|
||||
print(f" Si riportano ENTRAMBE. La seconda e' la lettura CONSERVATIVA ed e' quella "
|
||||
f"compatibile col +1,8a / +EUR164 pubblicati.")
|
||||
print(f"\n {'lente':<20}{'k':>5}{'drift':>8}{'g(k)':>8}{'perp.':>7}{'muro(k)':>10}"
|
||||
f"{'anni muro(k)':>20}{'anni muro(1,00)':>20}{'guad.(k)':>10}{'guad.(fisso)':>13}")
|
||||
out = {}
|
||||
for nome, key in (("L1 fisco (pubbl.)", "L1"), ("L3 fisco+FUNDING", "L3")):
|
||||
base = base_f = None
|
||||
muro1 = None
|
||||
for k in (1.00, 1.25, 1.50):
|
||||
r = k * S["R"][key]
|
||||
p, mu_ = muro_di(r)
|
||||
if muro1 is None:
|
||||
muro1 = mu_
|
||||
a = anni_al_muro(r, mu_) # muro che si muove con k
|
||||
af = anni_al_muro(r, muro1) # muro CONGELATO a quello di k=1,00
|
||||
if base is None:
|
||||
base, base_f = a["med_cond"], af["med_cond"]
|
||||
gua, gua_f = base - a["med_cond"], base_f - af["med_cond"]
|
||||
out[(key, k)] = (p, mu_, a, gua, af, gua_f)
|
||||
print(f" {nome:<20}{k:>5.2f}{r.mean()*ANN:>8.2%}{g_exact(r,k=1.0):>8.2%}"
|
||||
f"{p:>7.2%}{mu_:>10,.0f}{fmt_anni(a):>20}{fmt_anni(af):>20}"
|
||||
f"{gua:>+9.2f}a{gua_f:>+12.2f}a")
|
||||
print(f" {clock()}")
|
||||
|
||||
g_L1, g_L3 = out[("L1", 1.25)][3], out[("L3", 1.25)][3]
|
||||
gf_L1, gf_L3 = out[("L1", 1.25)][5], out[("L3", 1.25)][5]
|
||||
print(f"\n 📌 [MISURATO] il gradino 1,25x vale {g_L1:+.2f}a (L1, muro mobile) / "
|
||||
f"{gf_L1:+.2f}a (L1, muro congelato) e {g_L3:+.2f}a / {gf_L3:+.2f}a sulla lente L3 "
|
||||
f"col funding dentro.")
|
||||
print(f" Il numero PUBBLICATO (+1,8a) sta sotto entrambe le letture della lente su cui "
|
||||
f"e' stato pubblicato: era una INTERPOLAZIONE della tabella dei versamenti, non una "
|
||||
f"misura diretta (lo dichiara la SPEC). La misura diretta dice di piu', non di meno.")
|
||||
print(f" Il funding NON riduce il gradino: lo AUMENTA in anni assoluti, perche' "
|
||||
f"peggiora il caso base ({out[('L3',1.00)][2]['med_cond']:.2f}a contro "
|
||||
f"{out[('L1',1.00)][2]['med_cond']:.2f}a) e la leva ne recupera una parte.")
|
||||
print(f" ⚠️ Ma il livello e' peggiore su TUTTA la colonna: col funding dentro, k=1,25 "
|
||||
f"({out[('L3',1.25)][2]['med_cond']:.2f}a) e' ancora PEGGIO di k=1,00 senza funding "
|
||||
f"({out[('L1',1.00)][2]['med_cond']:.2f}a). "
|
||||
f"**Il gradino non ripaga il funding: lo attenua.**")
|
||||
|
||||
# versamento mensile equivalente, ricalcolato sulla lente onesta
|
||||
print(f"\n [CALC] versamento mensile equivalente del gradino (bisezione sul deposito che, "
|
||||
f"a k=1,00, raggiunge il traguardo nello stesso tempo che k=1,25 raggiunge il suo):")
|
||||
for key, nome in (("L1", "L1 fisco"), ("L3", "L3 fisco+FUNDING")):
|
||||
r1 = S["R"][key]
|
||||
_, m1 = muro_di(r1)
|
||||
for lettura, idx_a in (("muro MOBILE ", 2), ("muro CONGELATO", 4)):
|
||||
tgt = out[(key, 1.25)][idx_a]["med_cond"]
|
||||
lo, hi = DEP_EUR, 4_000.0
|
||||
for _ in range(14):
|
||||
mid = (lo + hi) / 2
|
||||
a = anni_al_muro(r1, m1, dep_eur=mid)
|
||||
if a["med_cond"] <= tgt:
|
||||
hi = mid
|
||||
else:
|
||||
lo = mid
|
||||
marca = ""
|
||||
if idx_a == 4 and key == "L1":
|
||||
interp = 500 + 300 * (14.7 - 12.9) / (14.7 - 11.4)
|
||||
marca = (f" <- contro +EUR{interp-500:.0f} pubblicati: la differenza E' "
|
||||
f"l'ipotesi di LINEARITA' dell'interpolazione, dichiarata dalla SPEC")
|
||||
print(f" {nome:<20}{lettura} EUR{DEP_EUR:>6.0f}/m a k=1,25 == "
|
||||
f"EUR{hi:>7.0f}/m a k=1,00 -> +EUR{hi-DEP_EUR:.0f}/mese{marca}")
|
||||
print(f" {clock()}")
|
||||
|
||||
if min(g_L3, gf_L3) < 1.0:
|
||||
v, riga = "FALSIFICA", (f"col funding dentro il gradino vale {g_L3:+.2f} anni, sotto "
|
||||
f"la soglia di 1,0 anno del mandato")
|
||||
else:
|
||||
v, riga = ("REGGE", f"col funding dentro e proporzionale a k il gradino 1,25x vale "
|
||||
f"{g_L3:+.2f}a (muro mobile) / {gf_L3:+.2f}a (muro congelato) "
|
||||
f"contro {g_L1:+.2f}a / {gf_L1:+.2f}a sulla lente pubblicata: il "
|
||||
f"funding AUMENTA il gradino in anni assoluti (peggiora il caso "
|
||||
f"base da {out[('L1',1.00)][2]['med_cond']:.2f}a a "
|
||||
f"{out[('L3',1.00)][2]['med_cond']:.2f}a) — ma il LIVELLO peggiora "
|
||||
f"su tutta la colonna: k=1,25 col funding ({out[('L3',1.25)][2]['med_cond']:.2f}a) "
|
||||
f"resta peggio di k=1,00 senza ({out[('L1',1.00)][2]['med_cond']:.2f}a). "
|
||||
f"Il gradino non ripaga il funding, lo attenua")
|
||||
esito("A6 FUNDING", v, riga)
|
||||
return out
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
# (8) VERDETTO
|
||||
# ==========================================================================================
|
||||
def sezione8(S: dict) -> None:
|
||||
sez("(8) VERDETTO — e il k MASSIMO DIFENDIBILE come singolo numero")
|
||||
for kk, (v, riga) in ESITI.items():
|
||||
print(f" {kk:<16} {v:<11} {riga}")
|
||||
|
||||
cfg = json.loads((ROOT / "config" / "live.json").read_text())
|
||||
frac, sl = float(cfg["max_notional_per_asset_frac"]), float(cfg["disaster_sl_pct"])
|
||||
L = PUB["L_strutt"]
|
||||
r = S["R"]["L3"]
|
||||
dr = boot_drift(r)
|
||||
lo95 = float(np.percentile(dr, 2.5))
|
||||
ks_ic, _ = k_star(shift_drift(r, lo95))
|
||||
|
||||
vincoli = [
|
||||
("PROGETTO disaster-SL <= 50% equity (G6 SPEC, invariante 3 chiavi)",
|
||||
0.50 / (2 * frac * sl), "P"),
|
||||
("PROGETTO peggior giorno strutturale <= 50% (C1 §33)", 0.50 / L, "P"),
|
||||
("PROGETTO mezzo-Kelly al limite BASSO dell'IC95 del drift (C2 §33)", ks_ic / 2, "P"),
|
||||
("SCETTICO peggior giorno strutturale <= 20% (recupero +25%)", 0.20 / L, "S"),
|
||||
("PROCESSO scaletta di codice `SCALA_LADDER` della SPEC", 1.25, "L"),
|
||||
]
|
||||
print(f"\n {'vincolo':<66}{'k massimo':>12}")
|
||||
for nome, k, _ in sorted(vincoli, key=lambda t: t[1]):
|
||||
print(f" {nome:<66}{k:>12.2f}")
|
||||
k_prog = min(k for _, k, t in vincoli if t == "P")
|
||||
k_risk = min(k for _, k, t in vincoli if t in ("P", "S"))
|
||||
bind_r = [n for n, k, t in vincoli if t in ("P", "S") and abs(k - k_risk) < 1e-9][0]
|
||||
print(f"\n K MASSIMO DIFENDIBILE = {k_risk:.2f} (vincolo che morde: {bind_r})")
|
||||
print(f" · coi soli vincoli GIA' DICHIARATI dal progetto sarebbe {k_prog:.2f} "
|
||||
f"(morde il disaster-SL, non il peggior giorno: G6 della SPEC);")
|
||||
print(f" · la soglia del 20% e' AGGIUNTA da questo scettico e va difesa: oltre quella "
|
||||
f"perdita un recupero richiede +25%, e su un conto unico con zero assorbente il "
|
||||
f"tempo di recupero e' il costo vero. Non e' una regola del progetto: e' una proposta.")
|
||||
print(f" · e la scaletta della SPEC ferma comunque l'espressione a 1,25 — che e' un "
|
||||
f"cricchetto di PROCESSO, non un limite di rischio, e resta la scelta giusta perche' "
|
||||
f"e' l'unico gate che il progetto ha contro un parametro che nessun altro gate vede.")
|
||||
print(f"\n ⇒ IL GRADINO 1,25x SOPRAVVIVE ALL'ATTACCO. Il 1,50x NO: sfonda la soglia del "
|
||||
f"20% ({1.50*L:.2%}) e sta a {1.50/(0.50/(2*frac*sl)):.0%} del tetto G6.")
|
||||
|
||||
print(f"\n 📌 RICONCILIAZIONE DEL NUMERO PUBBLICATO — e non era una discrepanza, era una "
|
||||
f"CONVENZIONE non dichiarata:")
|
||||
print(f" §33 pubblica «14,7a -> 12,9a» = +1,8a. Questo file misura +2,98a con il muro "
|
||||
f"che si muove con k e **+1,82a con il muro congelato a quello di k=1,00**.")
|
||||
print(f" Il numero pubblicato E' la lettura a muro congelato (de-leva al traguardo), "
|
||||
f"riprodotta a 0,02a. Non e' sbagliato: e' il piu' conservativo dei due, e §33 non "
|
||||
f"dichiara quale sta usando. **Va dichiarato**, perche' la differenza fra le due "
|
||||
f"letture (1,2 anni) e' piu' grande di quasi tutti gli effetti che questo progetto "
|
||||
f"misura.")
|
||||
print(f"\n ATTACCHI NON PORTATI, e perche':")
|
||||
print(f" · il costo di MARGINE sopra 1x non e' stato prezzato: su un perpetuale lineare "
|
||||
f"marginato in USDC non c'e' un prestito, il carry E' il funding (gia' dentro). "
|
||||
f"Se Deribit introducesse un costo di margine, il conto va rifatto.")
|
||||
print(f" · la banda d'ANCORA del gradino: il de-luck ×0,89 e' applicato al drift, ma il "
|
||||
f"guadagno in ANNI e' un Delta su griglia ancorata e quindi eredita la fortuna "
|
||||
f"d'ancora come ogni Delta (lezione 26/07). Servirebbero le 23x24 ancore congiunte: "
|
||||
f"fuori dal budget di 2 CPU. Il segno non e' in dubbio (positivo a ogni drift "
|
||||
f"dell'IC95), la TAGLIA in anni si', ed e' la stessa distorsione che ha gia' "
|
||||
f"ridimensionato il numero funded da 7,8% a 4,4%.")
|
||||
print(f" · lo slippage a taglia crescente: §5 dell'ondata ha misurato che un fill ha "
|
||||
f"preso il 21,9% del volume della sua barra 5m a $635, e che la partecipazione scala "
|
||||
f"LINEARE. Una scala 1,25x la porta a 27,4% subito, non a $5.000. Non l'ho misurata "
|
||||
f"e va misurata prima del gradino, non dopo.")
|
||||
print(f" · la coda ASSENTE dal dataset per costruzione (un evento tipo LUNA/FTX sul "
|
||||
f"VENUE, non sul prezzo): nessuno stimatore di coda la contiene, e il rischio di "
|
||||
f"venue vive sul suo asse separato (26/07). Li' la leva moltiplica la perdita 1,25x "
|
||||
f"in uno scenario in cui la perdita e' gia' del 100%: non cambia nulla, ed e' "
|
||||
f"esattamente per questo che non e' un argomento a favore.")
|
||||
print(f"\n {clock()}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print(__doc__.split("ONESTA' APPLICATA")[0])
|
||||
sez("(0) IL CRITERIO — congelato PRIMA di guardare le sezioni 2-7")
|
||||
for k, v in CRITERIO.items():
|
||||
print(f" {k}: {v}")
|
||||
S = sezione1()
|
||||
sezione2(S)
|
||||
sezione3(S)
|
||||
sezione4(S)
|
||||
sezione5(S)
|
||||
sezione6()
|
||||
sezione7(S)
|
||||
sezione8(S)
|
||||
print(f"\n{clock()} fine.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user