research(tail-hedge): REFUTATO — la copertura di coda ALZA il maxDD del libro, 0/162 celle
§46. Prima misura del progetto sull'acquisto di put deep-OTM come assicurazione statica
sul libro (TP01 75 / SKH01 25), senza delta-hedge e senza pretendere alpha. Motore prezzi
RIUSATO da VRP01 (_bs_put/_strike_from_delta su DVOL reale). Griglia dichiarata prima:
3 delta x 3 tenori x 3 budget x 2 roll = 54 celle per lente di f (162 in tutto).
VERDETTO: il null del de-levering non arriva nemmeno a essere il gate che decide — la
premessa cade prima. Il maxDD SCENDE in 0/54 celle a OGNI lente di f (compresa f=1.00,
cioe' regalando alla copertura il prezzo di modello): 9,42% -> 9,53-10,37%. Meccanismo:
il bleed del premio cade DENTRO i drawdown, che su questo libro sono lunghi e poco
profondi, mentre il payoff cade su singoli giorni di crollo che NON sono il fondo del
drawdown. Beta del libro al sottostante +0,076 (il 19/05/2021 il mercato ha fatto -21,1%
e il libro -2,0%): la put protegge una perdita gia' ridotta di ~10x dalla strategia
stessa, quindi coprirla costerebbe ~1/beta volte il budget.
ATTESA A PRIORI, riportata anche dove sbagliata: (c) REFUTATA nella forma scritta — 15/20
dei peggiori giorni SONO crolli, e lo short-squeeze che avevo in mente sta fuori finestra;
(b) e (a) confermate, e (b) e' il meccanismo operativo.
f MISURATO OGGI sulle quote vere (420 put con bid E ask, catena USDC): 1,89 / 1,27 / 1,06
a delta -0,05 / -0,10 / -0,15 — piu' mite del 2,23-5,85 del 30/07, e il verdetto non
cambia. Struttura del modello che vale come risultato: f si paga solo sulla parte di
valore che converge a INTRINSECO, quindi un roll anticipato non lo paga (sensitivita' con
f asimmetrico riportata).
ESEGUIBILITA': corregge un muro ereditato. Comprare un'opzione costa il PREMIO, non il
NOZIONALE — i "lotti da migliaia di dollari" del 22/08 sono il collaterale per VENDERE.
Il lotto minimo BTC_USDC (0,01) costa $1,79-7,93; la copertura diventa comprabile a ogni
roll da ~$9,2k (30g, delta -0,05) a ~$38,8k (7g, delta -0,15). Secondo muro, STRUTTURALE:
il tick da 5 USDC — piu' la copertura e' deep-OTM (cioe' economica) piu' il tick domina.
CANALE FUNDED: sull'unica regola binding (max-loss 6% statico) la copertura va nel verso
sbagliato — alza il maxDD e quindi abbassa la leva ammessa (k 0,629 -> 0,594-0,620).
DUE ERRORI MIEI CATTURATI PRIMA DI PUBBLICARE, entrambi dal controllo e non a occhio:
(1) il controllo positivo "payoff gratis" FALLIVA perche' non accreditavo il valore
dell'asset regalato, mentre il MTM successivo ne addebitava il decadimento -> il free
lunch costava quanto il premio. Un controllo positivo rotto dichiara guasto l'apparato.
(2) la prova di causalita' dava 1,28e-04 ("DIVERGE"): confrontavo fino al taglio, ma il
prefisso si ferma un roll prima -> differiva per ASSENZA di un roll, non per
look-ahead. Finestra corretta: 0,00e+00 esatto.
(3) e un errore di CRITERIO: contavo "vittoria" anche dove il maxDD PEGGIORA, dove il null
del de-levering e' degenere (k=1) e un Δdrift>0 risponde a una domanda di alpha, non a
questa. Col criterio corretto le "3/54 vittorie" a f=1 diventano 0/54.
Controlli 3/3 OK (free lunch e f=0,1 riconosciuti, f=10 rifiutato). Causalita' esatta.
Lente wick accoppiata riusata da r0725_prop_coupled, copertura 100% del pannello.
Distorsioni dichiarate: finestra 2021-03+ (il DVOL non esiste prima, quindi marzo 2020 e'
FUORI CAMPIONE); spread misurato sui soli strumenti con bid E ask = pavimento che favorisce
la copertura; 3-5 breach in 5,4 anni non distinguono le varianti.
Libro, pesi, cron, config INVARIATI. Nessun ordine.
This commit is contained in:
@@ -0,0 +1,881 @@
|
|||||||
|
"""r0823_tail_hedge — §46 TAIL-HEDGE: comprare put deep-OTM come ASSICURAZIONE STATICA sul libro.
|
||||||
|
|
||||||
|
LA DOMANDA (decidibile, e NON e' una domanda di alpha)
|
||||||
|
Il progetto ha gia' testato la VENDITA di volatilita' (VRP01, gated su IV-rank), il GAMMA
|
||||||
|
SCALPING (long-vol delta-hedgiato, SCARTATO: perde ogni anno) e le strutture Albimarini.
|
||||||
|
Non ha mai testato la cosa piu' semplice: **comprare put deep-OTM e tenerle**, senza
|
||||||
|
delta-hedge, senza pretendere alpha. E' un riduttore di DRAWDOWN, e il drawdown e' il vincolo
|
||||||
|
binding su due assi gia' misurati: (a) il canale funded (daily-loss 4%, max-loss 6%), (b) la
|
||||||
|
rendita perpetua, che vive sul DD (il libro rende quanto un ETF S&P e serve 2,5x meno capitale
|
||||||
|
solo perche' perde meno).
|
||||||
|
Domanda: **comprare protezione di coda compra riduzione di DD a un prezzo migliore di quello
|
||||||
|
che si ottiene semplicemente DE-LEVERANDO?**
|
||||||
|
|
||||||
|
=========================== ATTESA A PRIORI (scritta PRIMA di misurare) ===========================
|
||||||
|
Mi aspetto **REFUTATO al f realistico**, e forse marginale a f=1. Tre ragioni, in ordine di forza:
|
||||||
|
(c) **Si sta comprando assicurazione contro il rischio sbagliato.** Il 22/08 ha misurato che il
|
||||||
|
peggior giorno del libro NON e' un crash: e' uno SHORT SQUEEZE di SKH01 (short con stop 2%
|
||||||
|
che gappa a -15,77% di sleeve mentre TP01 e' flat). Una PUT non protegge da un rialzo.
|
||||||
|
(b) **Il libro e' GIA' difensivo dove la put paga.** L'esposizione di TP01 scende MONOTONA col
|
||||||
|
crollo (0,096 -> 0,042 sotto il -20%, misurato 22/08) perche' li' il trend si e' gia' girato:
|
||||||
|
la put paga proprio quando il libro non ha piu' esposizione da proteggere.
|
||||||
|
(a) **Comprare vol e' pagare il VRP**, cioe' l'esatto specchio di cio' che VRP01 incassa e che il
|
||||||
|
gamma scalping ha gia' pagato perdendo ogni anno, ogni variante, ogni frequenza.
|
||||||
|
Previsione quantitativa dichiarata: la riduzione di DD ci sara' (la put paga davvero nei crolli),
|
||||||
|
ma il de-levering la comprera' a meno; e il costo salira' ~linearmente col budget mentre il
|
||||||
|
beneficio saturera'.
|
||||||
|
==================================================================================================
|
||||||
|
|
||||||
|
IL GATE CHE DECIDE — NULL DEL DE-LEVERING (6 occorrenze nel progetto: VRP-DD, TP01xDVOL, MAT01,
|
||||||
|
azioni intere UCITS, SOL, TP01-SIZE). Si trova il `k` costante per cui `k*libro` ha lo STESSO
|
||||||
|
maxDD della variante coperta, e si confronta Sharpe e DRIFT a quel punto.
|
||||||
|
⚠️ Corollario 22/08: quando la variante e' una pura ri-scalatura il null e' DEGENERE
|
||||||
|
(`sh(k*base) === sh(base)`, identita' aritmetica). Qui NON lo e' **sul drift**: il drift
|
||||||
|
geometrico di `k*base` cambia con k, mentre lo Sharpe no. Quindi il confronto ha DUE gambe:
|
||||||
|
* Sharpe: Sharpe(coperto) vs Sharpe(base) [ = Sharpe(k*base), identicamente ]
|
||||||
|
* Drift: drift(coperto) vs drift(k*base) [ questa e' la gamba informativa ]
|
||||||
|
La non-linearita' della put e' cio' che rende la PRIMA gamba non-degenere (la copertura puo'
|
||||||
|
davvero alzare lo Sharpe, una ri-scalatura no).
|
||||||
|
|
||||||
|
COSA C'E' DENTRO
|
||||||
|
1) Motore prezzi RIUSATO (`src.portfolio.sleeves._bs_put` / `_strike_from_delta`, gli stessi di
|
||||||
|
VRP01) su DVOL reale. Premio **MODELLATO**, non quotato: dichiarato come tale.
|
||||||
|
2) f MISURATO OGGI sulle quote vere della catena Deribit (BTC_USDC + inverse), sulle ali che
|
||||||
|
questa strategia comprerebbe — non assunto dal 30/07.
|
||||||
|
3) Griglia dichiarata prima: delta {-0.05,-0.10,-0.15} x tenore {7,14,30} x budget
|
||||||
|
{0.5%,1%,2%}/anno x roll {scadenza, meta' vita} = **54 celle** per lente di f.
|
||||||
|
4) Lente di CODA (non media): maxDD, 5 peggiori mesi, peggior giorno, breach 4%/6% con la
|
||||||
|
**lente wick accoppiata** (riusata da r0725_prop_coupled, non riscritta).
|
||||||
|
5) Eseguibilita' misurata DAL VENUE (min_trade_amount + tick), non assunta.
|
||||||
|
6) Controlli positivo e negativo obbligatori.
|
||||||
|
|
||||||
|
CONVENZIONI DICHIARATE
|
||||||
|
* Libro = TP01 75 / SKH01 25 (il book live Deribit). Il costo si sottrae al DRIFT DEL LIBRO e il
|
||||||
|
beneficio si misura SUL LIBRO, non sullo sleeve.
|
||||||
|
* Copertura 50/50 BTC/ETH (l'esposizione del libro e' 50/50).
|
||||||
|
* **Mark-to-market GIORNALIERO** della put (BS a DVOL e spot correnti). E' essenziale: una
|
||||||
|
copertura marcata solo a scadenza sarebbe misurata male proprio sul DD, che e' l'unica cosa
|
||||||
|
che questo filone valuta.
|
||||||
|
* `f` applicato SIMMETRICAMENTE (si compra E si marca a f x modello). Conseguenza strutturale
|
||||||
|
che il modello cattura ed e' un risultato in se': **f si paga solo sulla parte di valore che
|
||||||
|
converge a INTRINSECO (la scadenza); un roll anticipato non lo paga.** Sensitivita' con f
|
||||||
|
asimmetrico (compri caro, vendi a modello) riportata a parte.
|
||||||
|
* Spread bid-ask MISURATO dalla catena vera per bucket di tenore, pagato a ogni compra e a ogni
|
||||||
|
vendita: senza, il roll a meta' vita vincerebbe per finzione.
|
||||||
|
* Fee opzioni Deribit: taker 0,03% del sottostante con cap 12,5% del premio; consegna ITM
|
||||||
|
0,015% con lo stesso cap.
|
||||||
|
* Finestra: **2021-03-24 -> oggi** (il DVOL non esiste prima). Contiene il 2022. NON contiene
|
||||||
|
il crollo di marzo 2020, che e' il sinistro piu' grande della storia del libro: dichiarato.
|
||||||
|
|
||||||
|
Uso: `nice -n 19 timeout 900 uv run python scripts/research/r0823_tail_hedge.py`
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
from functools import lru_cache
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||||
|
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
|
||||||
|
|
||||||
|
from src.data.downloader import load_data # noqa: E402
|
||||||
|
from src.strategies.trend_portfolio import resample_1d # noqa: E402
|
||||||
|
# MOTORE PREZZI RIUSATO — gli stessi di VRP01, non una reimplementazione.
|
||||||
|
from src.portfolio.sleeves import ( # noqa: E402
|
||||||
|
_bs_put, _strike_from_delta, _tp01_returns, _skyhook_returns, VRP_CFG,
|
||||||
|
)
|
||||||
|
import altlib as al # noqa: E402
|
||||||
|
from scipy.stats import norm as _norm # noqa: E402
|
||||||
|
|
||||||
|
ASSETS = ("BTC", "ETH")
|
||||||
|
W_TP, W_SKH = 0.75, 0.25 # book live Deribit (sleeves.deribit_book_sleeves)
|
||||||
|
DPY = 365.25
|
||||||
|
|
||||||
|
# ---- costi opzioni Deribit (listino pubblico) -------------------------------------------------
|
||||||
|
OPT_TAKER_UNDERLYING = 0.0003 # 0,03% del sottostante per gamba
|
||||||
|
OPT_FEE_CAP_FRAC = 0.125 # cap 12,5% del premio dell'opzione
|
||||||
|
OPT_DELIVERY_UNDERLYING = 0.00015 # 0,015% consegna ITM
|
||||||
|
|
||||||
|
# ---- GRIGLIA DICHIARATA PRIMA DI GUARDARE -----------------------------------------------------
|
||||||
|
G_DELTA = (-0.05, -0.10, -0.15)
|
||||||
|
G_TENOR = (7, 14, 30)
|
||||||
|
G_BUDGET = (0.005, 0.010, 0.020) # quota di equity spesa in premio, ANNUA
|
||||||
|
G_ROLL = ("expiry", "half")
|
||||||
|
N_CELLS = len(G_DELTA) * len(G_TENOR) * len(G_BUDGET) * len(G_ROLL) # = 54
|
||||||
|
|
||||||
|
# ---- lenti di f: 1.0 (modello nudo) + i due misurati il 30/07 + quello misurato OGGI -----------
|
||||||
|
F_LENSES_STATIC = (1.0, 2.23, 5.85)
|
||||||
|
|
||||||
|
DERIBIT = "https://www.deribit.com/api/v2/public/"
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================================================== 0. UTIL METRICHE
|
||||||
|
|
||||||
|
def _sh(x) -> float:
|
||||||
|
r = np.asarray(pd.Series(x).dropna().values, float)
|
||||||
|
return float(np.mean(r) / np.std(r) * math.sqrt(DPY)) if len(r) > 2 and np.std(r) > 0 else 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def _dd(x) -> float:
|
||||||
|
r = np.asarray(pd.Series(x).dropna().values, float)
|
||||||
|
eq = np.cumprod(1.0 + r)
|
||||||
|
pk = np.maximum.accumulate(eq)
|
||||||
|
return float(np.max((pk - eq) / pk)) if len(eq) else 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def _drift(x) -> float:
|
||||||
|
"""Drift GEOMETRICO annualizzato (CAGR). E' la grandezza su cui vive la rendita perpetua,
|
||||||
|
ed e' l'unica delle due che CAMBIA quando si de-leva (lo Sharpe no: identita' aritmetica)."""
|
||||||
|
r = np.asarray(pd.Series(x).dropna().values, float)
|
||||||
|
if len(r) < 2:
|
||||||
|
return 0.0
|
||||||
|
tot = float(np.prod(1.0 + r))
|
||||||
|
if tot <= 0:
|
||||||
|
return -1.0
|
||||||
|
return float(tot ** (DPY / len(r)) - 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def _dd_of_k(r: np.ndarray, k: float) -> float:
|
||||||
|
return _dd(k * r)
|
||||||
|
|
||||||
|
|
||||||
|
def k_for_same_drift(base: np.ndarray, target_drift: float) -> float:
|
||||||
|
"""Il de-levering a PARI DRIFT. Serve sull'asse funded: se la copertura NON riduce il maxDD
|
||||||
|
(che e' cio' che questo filone misura), il confronto a pari DD e' degenere -> la domanda
|
||||||
|
onesta diventa 'a parita' di rendimento atteso, chi sfora meno la regola giornaliera?'.
|
||||||
|
drift(k*r) e' crescente in k su un libro a drift positivo."""
|
||||||
|
lo, hi = 1e-4, 1.0
|
||||||
|
if _drift(hi * base) <= target_drift:
|
||||||
|
return 1.0
|
||||||
|
for _ in range(60):
|
||||||
|
mid = 0.5 * (lo + hi)
|
||||||
|
if _drift(mid * base) > target_drift:
|
||||||
|
hi = mid
|
||||||
|
else:
|
||||||
|
lo = mid
|
||||||
|
return 0.5 * (lo + hi)
|
||||||
|
|
||||||
|
|
||||||
|
def k_for_same_dd(base: np.ndarray, target_dd: float) -> float:
|
||||||
|
"""Il `k` del NULL DEL DE-LEVERING: la scala costante che porta il maxDD del libro NUDO
|
||||||
|
esattamente al maxDD della variante coperta. maxDD(k*r) e' monotono crescente in k
|
||||||
|
(compounding), quindi bisezione."""
|
||||||
|
lo, hi = 1e-4, 1.0
|
||||||
|
if _dd_of_k(base, hi) <= target_dd:
|
||||||
|
return 1.0 # la copertura non riduce il DD: k=1
|
||||||
|
for _ in range(60):
|
||||||
|
mid = 0.5 * (lo + hi)
|
||||||
|
if _dd_of_k(base, mid) > target_dd:
|
||||||
|
hi = mid
|
||||||
|
else:
|
||||||
|
lo = mid
|
||||||
|
return 0.5 * (lo + hi)
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================================================== 1. DATI
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def daily_panel() -> pd.DataFrame:
|
||||||
|
"""Pannello giornaliero allineato: rendimenti del libro + prezzi/low/DVOL per asset.
|
||||||
|
Un solo indice, cosi' nessuna domanda successiva puo' disallinearsi."""
|
||||||
|
tp = _tp01_returns()
|
||||||
|
skh = _skyhook_returns()
|
||||||
|
J = pd.concat({"tp": tp, "skh": skh}, axis=1, join="inner").fillna(0.0)
|
||||||
|
book = W_TP * J["tp"] + W_SKH * J["skh"]
|
||||||
|
|
||||||
|
cols = {"book": book, "tp": J["tp"], "skh": J["skh"]}
|
||||||
|
for a in ASSETS:
|
||||||
|
d = resample_1d(load_data(a, "1h"))
|
||||||
|
idx = pd.DatetimeIndex(pd.to_datetime(d["datetime"], utc=True))
|
||||||
|
for c in ("close", "low", "high"):
|
||||||
|
assert c in d.columns, f"resample_1d senza colonna {c}"
|
||||||
|
cols[f"px_{a}"] = pd.Series(d["close"].values.astype(float), index=idx)
|
||||||
|
cols[f"lo_{a}"] = pd.Series(d["low"].values.astype(float), index=idx)
|
||||||
|
dv = pd.read_parquet(ROOT / "data" / "raw" / f"dvol_{a.lower()}.parquet")
|
||||||
|
cols[f"iv_{a}"] = pd.Series(dv["close"].values.astype(float) / 100.0,
|
||||||
|
index=pd.to_datetime(dv["timestamp"], unit="ms", utc=True))
|
||||||
|
P = pd.concat(cols, axis=1, join="inner").sort_index().dropna()
|
||||||
|
return P
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================================================== 2. IL MOTORE DELLA COPERTURA
|
||||||
|
|
||||||
|
def _entry_fee(prem_paid: float, S: float) -> float:
|
||||||
|
return min(OPT_TAKER_UNDERLYING * S, OPT_FEE_CAP_FRAC * prem_paid) if prem_paid > 0 else 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def hedge_leg(px: np.ndarray, lo: np.ndarray, iv: np.ndarray, *,
|
||||||
|
delta: float, tenor: int, budget_ann: float, roll: str,
|
||||||
|
f: float, spread: float, f_sell: float | None = None,
|
||||||
|
free: bool = False) -> tuple[np.ndarray, np.ndarray, dict]:
|
||||||
|
"""Serie GIORNALIERE (ret, wick_extra) del contributo di UNA gamba di copertura, per 1 unita'
|
||||||
|
di equity allocata a quella gamba.
|
||||||
|
|
||||||
|
ret[i] = variazione giornaliera del valore MTM della posizione (frazione di equity)
|
||||||
|
wick_extra[i] = guadagno AGGIUNTIVO della copertura al minimo intra-giornaliero, ottenuto
|
||||||
|
RIPREZZANDO la put al LOW del giorno (cattura la convessita', non solo il delta)
|
||||||
|
|
||||||
|
CAUSALE per costruzione: strike, premio e quantita' a `t` usano solo px[t]/iv[t]; il payoff
|
||||||
|
usa prezzi successivi e non retroagisce mai sulla decisione.
|
||||||
|
|
||||||
|
`free=True` -> CONTROLLO POSITIVO: il payoff c'e' ma il premio non viene addebitato.
|
||||||
|
"""
|
||||||
|
n = len(px)
|
||||||
|
T_full = tenor / DPY
|
||||||
|
hold = tenor if roll == "expiry" else max(1, tenor // 2)
|
||||||
|
n_rolls_year = DPY / hold
|
||||||
|
b_roll = budget_ann / n_rolls_year # spesa per roll, in frazione di equity
|
||||||
|
|
||||||
|
ret = np.zeros(n)
|
||||||
|
wick = np.zeros(n)
|
||||||
|
diag = dict(n_rolls=0, n_itm=0, prem_spent=0.0, payoff_recv=0.0, fees=0.0, spread_cost=0.0)
|
||||||
|
|
||||||
|
i = 60 # warm-up (coerente con VRP01)
|
||||||
|
while i + hold < n:
|
||||||
|
S0, sig = float(px[i]), float(iv[i])
|
||||||
|
if not (S0 > 0 and sig > 0):
|
||||||
|
i += hold
|
||||||
|
continue
|
||||||
|
K = _strike_from_delta(S0, T_full, sig, delta)
|
||||||
|
p_model = _bs_put(S0, K, T_full, sig)
|
||||||
|
if p_model <= 0:
|
||||||
|
i += hold
|
||||||
|
continue
|
||||||
|
p_ask = f * p_model * (1.0 + spread) # si COMPRA al lato caro
|
||||||
|
fee0 = _entry_fee(p_ask, S0)
|
||||||
|
cost_unit = p_ask + fee0
|
||||||
|
q = b_roll / cost_unit # unita' di sottostante coperte
|
||||||
|
# giorno d'ingresso: il valore MTM e' q*f*p_model (il mid), quindi si perde subito
|
||||||
|
# fee + mezzo spread. Se `free`, il premio non si paga affatto.
|
||||||
|
v_prev = q * f * p_model
|
||||||
|
# ⚠️ `free` = CONTROLLO POSITIVO (payoff regalato). La prima stesura metteva 0.0 qui e
|
||||||
|
# lasciava v_prev>0: cosi' il MTM successivo addebitava il decadimento di un valore mai
|
||||||
|
# accreditato -> il "free lunch" costava quanto il premio e il controllo FALLIVA. Il
|
||||||
|
# regalo va ACCREDITATO: l'equity sale di v_prev il giorno in cui ricevi l'asset.
|
||||||
|
ret[i] += (v_prev if free else (v_prev - b_roll))
|
||||||
|
diag["n_rolls"] += 1
|
||||||
|
diag["prem_spent"] += 0.0 if free else b_roll
|
||||||
|
diag["fees"] += q * fee0
|
||||||
|
diag["spread_cost"] += q * f * p_model * spread
|
||||||
|
|
||||||
|
for s in range(1, hold + 1):
|
||||||
|
j = i + s
|
||||||
|
rem = (tenor - s) / DPY
|
||||||
|
Sj, ivj = float(px[j]), float(iv[j])
|
||||||
|
if s == tenor: # SCADENZA: valore = intrinseco
|
||||||
|
intr = max(0.0, K - Sj)
|
||||||
|
v = q * intr
|
||||||
|
if intr > 0:
|
||||||
|
dfee = min(OPT_DELIVERY_UNDERLYING * Sj, OPT_FEE_CAP_FRAC * intr) * q
|
||||||
|
v -= dfee
|
||||||
|
diag["fees"] += dfee
|
||||||
|
diag["n_itm"] += 1
|
||||||
|
diag["payoff_recv"] += v
|
||||||
|
vw = v # a scadenza niente wick residuo
|
||||||
|
else:
|
||||||
|
fs = f if f_sell is None else f_sell
|
||||||
|
v_mid = q * f * _bs_put(Sj, K, rem, ivj)
|
||||||
|
v = v_mid
|
||||||
|
vw = q * f * _bs_put(float(lo[j]), K, rem, ivj)
|
||||||
|
if s == hold: # VENDITA anticipata: mezzo spread
|
||||||
|
v = q * fs * _bs_put(Sj, K, rem, ivj) * (1.0 - spread)
|
||||||
|
sfee = min(OPT_TAKER_UNDERLYING * Sj, OPT_FEE_CAP_FRAC * max(v, 0.0))
|
||||||
|
v -= sfee
|
||||||
|
diag["fees"] += sfee
|
||||||
|
diag["spread_cost"] += v_mid * spread
|
||||||
|
diag["payoff_recv"] += v
|
||||||
|
wick[j] += vw - v_mid # EXTRA al minimo, contro il MID di chiusura
|
||||||
|
ret[j] += v - v_prev
|
||||||
|
v_prev = v
|
||||||
|
i += hold
|
||||||
|
|
||||||
|
return ret, np.maximum(wick, 0.0), diag
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
|
def hedge_series(delta: float, tenor: int, budget: float, roll: str, f: float,
|
||||||
|
spread: float, f_sell: float | None = None,
|
||||||
|
free: bool = False) -> tuple:
|
||||||
|
"""Copertura del LIBRO: 50/50 BTC+ETH (il libro e' 50/50). Ritorna (ret, wick) giornalieri
|
||||||
|
come frazione dell'equity del libro."""
|
||||||
|
P = daily_panel()
|
||||||
|
ret = np.zeros(len(P))
|
||||||
|
wick = np.zeros(len(P))
|
||||||
|
diags = {}
|
||||||
|
for a in ASSETS:
|
||||||
|
r, w, d = hedge_leg(P[f"px_{a}"].values, P[f"lo_{a}"].values, P[f"iv_{a}"].values,
|
||||||
|
delta=delta, tenor=tenor, budget_ann=budget * 0.5, roll=roll,
|
||||||
|
f=f, spread=spread, f_sell=f_sell, free=free)
|
||||||
|
ret += r
|
||||||
|
wick += w
|
||||||
|
diags[a] = d
|
||||||
|
return ret, wick, diags
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================================================== 3. f e SPREAD DAL VENUE
|
||||||
|
|
||||||
|
def _get(path: str, timeout: int = 25):
|
||||||
|
with urllib.request.urlopen(DERIBIT + path, timeout=timeout) as r:
|
||||||
|
return json.load(r)["result"]
|
||||||
|
|
||||||
|
|
||||||
|
def measure_f_from_venue() -> dict:
|
||||||
|
"""f = quota MID di mercato / prezzo BS a DVOL piatto, MISURATO OGGI sulle ali che questa
|
||||||
|
strategia comprerebbe. Non e' un'assunzione ereditata dal 30/07: e' una misura su questa
|
||||||
|
catena, su questi strike, con questo motore.
|
||||||
|
|
||||||
|
Si misura anche il MEZZO SPREAD relativo per bucket di tenore, che serve al roll anticipato.
|
||||||
|
Solo strumenti con bid E ask veri (un ask solitario al tick non e' un prezzo).
|
||||||
|
"""
|
||||||
|
out = dict(ok=False, note=None, per_delta={}, spread={}, n=0)
|
||||||
|
try:
|
||||||
|
idx = {a: float(_get(f"get_index_price?index_name={a.lower()}_usdc")["index_price"])
|
||||||
|
for a in ASSETS}
|
||||||
|
dvol = {a: float(_get(f"get_volatility_index_data?currency={a}&start_timestamp="
|
||||||
|
f"{int((time.time()-86400)*1000)}&end_timestamp={int(time.time()*1000)}"
|
||||||
|
f"&resolution=3600")["data"][-1][4]) / 100.0
|
||||||
|
for a in ASSETS}
|
||||||
|
time.sleep(0.6)
|
||||||
|
bs = _get("get_book_summary_by_currency?currency=USDC&kind=option")
|
||||||
|
except Exception as e: # rete assente: si dichiara, non si finge
|
||||||
|
out["note"] = f"venue non raggiungibile ({type(e).__name__}: {e})"
|
||||||
|
return out
|
||||||
|
|
||||||
|
now = pd.Timestamp.now(tz="UTC")
|
||||||
|
rows = []
|
||||||
|
for r in bs:
|
||||||
|
nm = r["instrument_name"]
|
||||||
|
if not nm.endswith("-P"):
|
||||||
|
continue
|
||||||
|
base = nm.split("_USDC-")[0] if "_USDC-" in nm else None
|
||||||
|
if base not in ASSETS:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
_, exp, ks, _ = nm.split("-")
|
||||||
|
K = float(ks.replace("d", "."))
|
||||||
|
texp = pd.to_datetime(exp, format="%d%b%y", utc=True) + pd.Timedelta(hours=8)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
bid, ask = r.get("bid_price"), r.get("ask_price")
|
||||||
|
if not bid or not ask or bid <= 0 or ask <= bid:
|
||||||
|
continue # niente bid vero -> non e' un prezzo
|
||||||
|
T = (texp - now).total_seconds() / (365.25 * 86400)
|
||||||
|
if T <= 1 / DPY or T > 45 / DPY:
|
||||||
|
continue
|
||||||
|
S, sig = idx[base], dvol[base]
|
||||||
|
mid = 0.5 * (bid + ask)
|
||||||
|
half_spread = 0.5 * (ask - bid) / mid
|
||||||
|
model = _bs_put(S, K, T, sig)
|
||||||
|
if model <= 1e-9:
|
||||||
|
continue
|
||||||
|
# delta BS della put allo stesso vol piatto (per assegnare l'osservazione al suo bucket)
|
||||||
|
d1 = (math.log(S / K) + 0.5 * sig ** 2 * T) / (sig * math.sqrt(T))
|
||||||
|
dlt = -float(_norm.cdf(-d1))
|
||||||
|
rows.append(dict(asset=base, K=K, T=T, days=T * DPY, delta=dlt,
|
||||||
|
mid=mid, model=model, f=mid / model, hs=half_spread))
|
||||||
|
if not rows:
|
||||||
|
out["note"] = "nessuno strumento con bid E ask veri nella finestra <=45g"
|
||||||
|
return out
|
||||||
|
D = pd.DataFrame(rows)
|
||||||
|
out["n"] = int(len(D))
|
||||||
|
for dtgt in G_DELTA:
|
||||||
|
sel = D[(D.delta >= dtgt - 0.035) & (D.delta <= dtgt + 0.035)]
|
||||||
|
if len(sel) >= 3:
|
||||||
|
out["per_delta"][dtgt] = dict(n=int(len(sel)), f_med=float(sel.f.median()),
|
||||||
|
f_lo=float(sel.f.quantile(0.10)),
|
||||||
|
f_hi=float(sel.f.quantile(0.90)),
|
||||||
|
hs_med=float(sel.hs.median()))
|
||||||
|
for lab, (a, b) in dict(t7=(1, 10), t14=(10, 21), t30=(21, 45)).items():
|
||||||
|
sel = D[(D.days >= a) & (D.days < b)]
|
||||||
|
if len(sel) >= 3:
|
||||||
|
out["spread"][lab] = dict(n=int(len(sel)), hs_med=float(sel.hs.median()),
|
||||||
|
hs_p90=float(sel.hs.quantile(0.90)))
|
||||||
|
out["ok"] = bool(out["per_delta"] or out["spread"])
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def venue_min_ticket() -> dict:
|
||||||
|
"""Eseguibilita' MISURATA dal venue: min_trade_amount + tick, e il costo in DOLLARI del
|
||||||
|
lotto minimo per una put deep-OTM vera. ⚠️ Comprare un'opzione costa il PREMIO, non il
|
||||||
|
NOZIONALE: il muro dei 'lotti da migliaia di dollari' del 22/08 e' il collaterale per
|
||||||
|
VENDERE, non il costo per COMPRARE. Qui si misura la cosa giusta."""
|
||||||
|
out = dict(ok=False, note=None, fam={})
|
||||||
|
try:
|
||||||
|
for cur, pref in (("USDC", "{a}_USDC-"), ("BTC", "BTC-"), ("ETH", "ETH-")):
|
||||||
|
inst = _get(f"get_instruments?currency={cur}&kind=option&expired=false")
|
||||||
|
time.sleep(0.6)
|
||||||
|
for a in ASSETS:
|
||||||
|
p = pref.format(a=a)
|
||||||
|
sel = [i for i in inst if i["instrument_name"].startswith(p)]
|
||||||
|
if not sel:
|
||||||
|
continue
|
||||||
|
fam = "USDC-lineare" if cur == "USDC" else "inverse"
|
||||||
|
out["fam"][(a, fam)] = dict(min_amt=float(sel[0]["min_trade_amount"]),
|
||||||
|
tick=float(sel[0]["tick_size"]),
|
||||||
|
settle=sel[0]["settlement_currency"],
|
||||||
|
n=len(sel))
|
||||||
|
out["ok"] = bool(out["fam"])
|
||||||
|
except Exception as e:
|
||||||
|
out["note"] = f"venue non raggiungibile ({type(e).__name__}: {e})"
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================================================== 4. LENTE WICK ACCOPPIATA
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def coupled_book_tuples() -> pd.DataFrame:
|
||||||
|
"""(R, m) giornaliere del libro 75/25 sotto la LENTE WICK ACCOPPIATA — RIUSATA da
|
||||||
|
r0725_prop_coupled (importata, non riscritta). Se il recon non e' disponibile si dichiara e
|
||||||
|
si ricade su close-only, DICENDO che close-only e' CIECA su una regola a UN giorno
|
||||||
|
(misurato 22/08: 0,00 breach/anno contro 0,40-1,21 veri, rapporto INF)."""
|
||||||
|
try:
|
||||||
|
import r0725_prop_coupled as pc
|
||||||
|
return pc.crypto_daily_tuples(W_TP, W_SKH)
|
||||||
|
except Exception as e: # dichiarato, mai silenzioso
|
||||||
|
print(f" ⚠️ lente accoppiata NON disponibile ({type(e).__name__}: {e}) -> close-only")
|
||||||
|
return pd.DataFrame()
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================================================== 5. VALUTAZIONE DI UNA CELLA
|
||||||
|
|
||||||
|
def evaluate(book: np.ndarray, hret: np.ndarray) -> dict:
|
||||||
|
hedged = book + hret
|
||||||
|
dd_h = _dd(hedged)
|
||||||
|
k = k_for_same_dd(book, dd_h)
|
||||||
|
base_k = k * book
|
||||||
|
dd_b = _dd(book)
|
||||||
|
dd_reduced = bool(dd_h < dd_b - 1e-12)
|
||||||
|
dr_h, dr_k = _drift(hedged), _drift(base_k)
|
||||||
|
return dict(
|
||||||
|
sh_base=_sh(book), sh_hedged=_sh(hedged), sh_delev=_sh(base_k),
|
||||||
|
dd_base=dd_b, dd_hedged=dd_h, dd_delev=_dd(base_k),
|
||||||
|
dr_base=_drift(book), dr_hedged=dr_h, dr_delev=dr_k,
|
||||||
|
k=k, hedged=hedged, dd_reduced=dd_reduced,
|
||||||
|
# VINCE solo se (i) il DD scende davvero E (ii) a quel DD il drift batte il de-levering.
|
||||||
|
# Senza (i) il null e' DEGENERE (k=1, dr_delev==dr_base) e un Δdrift>0 direbbe solo
|
||||||
|
# "la put ha guadagnato in questa finestra", che e' una domanda di ALPHA — non questa.
|
||||||
|
wins=bool(dd_reduced and dr_h > dr_k),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def worst_months(r: pd.Series, n: int = 5) -> list:
|
||||||
|
m = (1.0 + r).resample("ME").prod() - 1.0
|
||||||
|
return [(str(i.date())[:7], float(v)) for i, v in m.nsmallest(n).items()]
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================================================== MAIN
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
t0 = time.time()
|
||||||
|
print("=" * 100)
|
||||||
|
print("§46 TAIL-HEDGE — put deep-OTM come ASSICURAZIONE STATICA sul libro TP01 75 / SKH01 25")
|
||||||
|
print("=" * 100)
|
||||||
|
print(f"GRIGLIA DICHIARATA PRIMA DI GUARDARE: {len(G_DELTA)} delta x {len(G_TENOR)} tenori x "
|
||||||
|
f"{len(G_BUDGET)} budget x {len(G_ROLL)} roll = {N_CELLS} celle per lente di f.")
|
||||||
|
print("ATTESA A PRIORI: REFUTATO al f realistico. Ragione piu' forte (c): il peggior giorno del")
|
||||||
|
print(" libro NON e' un crash ma uno short-squeeze di SKH01 -> una PUT non protegge da un rialzo.")
|
||||||
|
|
||||||
|
P = daily_panel()
|
||||||
|
book = P["book"].values
|
||||||
|
idx = P.index
|
||||||
|
bs_book = pd.Series(book, index=idx)
|
||||||
|
print(f"\nFinestra (comune libro+DVOL): {idx[0].date()} -> {idx[-1].date()} "
|
||||||
|
f"({len(P)} giorni, {len(P)/DPY:.1f} anni)")
|
||||||
|
print(f"LIBRO NUDO su questa finestra: Sharpe {_sh(book):.3f} | maxDD {_dd(book)*100:.2f}% | "
|
||||||
|
f"drift {_drift(book)*100:.2f}%/a")
|
||||||
|
print("⚠️ La finestra NON contiene marzo 2020 (il DVOL parte 2021-03): il sinistro piu' grande")
|
||||||
|
print(" della storia del libro e' FUORI CAMPIONE. La copertura non e' testata li'.")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- §A. f e spread dal venue
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print("§A. f E SPREAD — MISURATI OGGI SULLE QUOTE VERE (non assunti dal 30/07)")
|
||||||
|
print("=" * 100)
|
||||||
|
fv = measure_f_from_venue()
|
||||||
|
if fv["ok"]:
|
||||||
|
print(f" {fv['n']} put con bid E ask veri, tenore <=45g, catena USDC-lineare.")
|
||||||
|
print(f" {'delta':>7} {'n':>4} {'f mediano':>10} {'f p10':>8} {'f p90':>8} {'mezzo-spread':>13}")
|
||||||
|
for d, v in sorted(fv["per_delta"].items(), reverse=True):
|
||||||
|
print(f" {d:>7.2f} {v['n']:>4} {v['f_med']:>10.2f} {v['f_lo']:>8.2f} "
|
||||||
|
f"{v['f_hi']:>8.2f} {v['hs_med']*100:>12.1f}%")
|
||||||
|
print(f" mezzo-spread per tenore: " + " ".join(
|
||||||
|
f"{k} {v['hs_med']*100:.1f}% (n={v['n']})" for k, v in fv["spread"].items()))
|
||||||
|
else:
|
||||||
|
print(f" NON MISURATO: {fv['note']}")
|
||||||
|
F_MEAS = {d: v["f_med"] for d, v in fv.get("per_delta", {}).items()}
|
||||||
|
if F_MEAS:
|
||||||
|
print(" -> f USATO nella lente 'misurato oggi': " +
|
||||||
|
" ".join(f"δ{d:.2f}={v:.2f}" for d, v in sorted(F_MEAS.items(), reverse=True)))
|
||||||
|
SPREAD = {}
|
||||||
|
for lab, ten in (("t7", 7), ("t14", 14), ("t30", 30)):
|
||||||
|
SPREAD[ten] = fv.get("spread", {}).get(lab, {}).get("hs_med", 0.10)
|
||||||
|
print(f" -> mezzo-spread usato per tenore: " +
|
||||||
|
" ".join(f"{t}g {SPREAD[t]*100:.1f}%" for t in G_TENOR) +
|
||||||
|
("" if fv["ok"] else " (DEFAULT 10%: venue non letto)"))
|
||||||
|
print(" ⚠️ DISTORSIONE DICHIARATA, e va nel verso che FAVORISCE la copertura: lo spread e' il")
|
||||||
|
print(" mediano dei soli strumenti con bid E ask veri. Le ali piu' profonde e piu' corte")
|
||||||
|
print(" spesso NON hanno bid (solo un ask al tick) e cadono fuori dal campione: quelle")
|
||||||
|
print(" sarebbero le piu' care, e per un tail-hedge sono proprio quelle che si comprano.")
|
||||||
|
print(" Quindi il costo qui e' un PAVIMENTO, e il verdetto REFUTATO ne esce rafforzato.")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- §B. il null del de-levering
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print("§B. NULL DEL DE-LEVERING — IL GATE CHE DECIDE (in cima, come si deve)")
|
||||||
|
print("=" * 100)
|
||||||
|
print(" Per ogni cella: k = scala che porta il libro NUDO allo STESSO maxDD della coperta.")
|
||||||
|
print(" Sharpe(k*base) == Sharpe(base) per identita' aritmetica -> la gamba informativa e' il")
|
||||||
|
print(" DRIFT. La copertura VINCE solo se drift(coperta) > drift(k*base) [e/o Sharpe sale].")
|
||||||
|
|
||||||
|
lenses = [("f=1.00 (modello nudo)", 1.0)]
|
||||||
|
for f in F_LENSES_STATIC[1:]:
|
||||||
|
lenses.append((f"f={f:.2f} (misurato 30/07)", f))
|
||||||
|
if F_MEAS:
|
||||||
|
lenses.append(("f MISURATO oggi per-delta", None))
|
||||||
|
|
||||||
|
all_rows = []
|
||||||
|
for lab, fval in lenses:
|
||||||
|
print(f"\n ---- lente {lab} " + "-" * (78 - len(lab)))
|
||||||
|
print(f" {'delta':>6} {'ten':>4} {'budget':>7} {'roll':>7} | {'ShCop':>7} {'ShBase':>7} | "
|
||||||
|
f"{'DDcop':>7} {'DDbase':>7} | {'k':>6} {'DrCop':>8} {'DrDelev':>8} {'Δdrift':>8} | esito")
|
||||||
|
best = None
|
||||||
|
for dlt in G_DELTA:
|
||||||
|
f_use = fval if fval is not None else F_MEAS.get(dlt, F_LENSES_STATIC[1])
|
||||||
|
for ten in G_TENOR:
|
||||||
|
for bud in G_BUDGET:
|
||||||
|
for roll in G_ROLL:
|
||||||
|
hr, hw, dg = hedge_series(dlt, ten, bud, roll, f_use, SPREAD[ten])
|
||||||
|
ev = evaluate(book, hr)
|
||||||
|
gap = ev["dr_hedged"] - ev["dr_delev"]
|
||||||
|
win = ev["wins"]
|
||||||
|
row = dict(lens=lab, f=f_use, delta=dlt, tenor=ten, budget=bud, roll=roll,
|
||||||
|
**{k: v for k, v in ev.items() if k != "hedged"}, gap=gap,
|
||||||
|
hret=hr, hwick=hw, diag=dg)
|
||||||
|
all_rows.append(row)
|
||||||
|
if best is None or gap > best["gap"]:
|
||||||
|
best = row
|
||||||
|
if bud == 0.010 and roll == "expiry": # riga stampata: budget centrale
|
||||||
|
print(f" {dlt:>6.2f} {ten:>4} {bud*100:>6.1f}% {roll:>7} | "
|
||||||
|
f"{ev['sh_hedged']:>7.3f} {ev['sh_base']:>7.3f} | "
|
||||||
|
f"{ev['dd_hedged']*100:>6.2f}% {ev['dd_base']*100:>6.2f}% | "
|
||||||
|
f"{ev['k']:>6.3f} {ev['dr_hedged']*100:>7.2f}% "
|
||||||
|
f"{ev['dr_delev']*100:>7.2f}% {gap*100:>+7.2f}% | "
|
||||||
|
f"{'COPERTURA' if win else ('de-levering' if ev['dd_reduced'] else 'DD PEGGIORE')}")
|
||||||
|
sub = [r for r in all_rows if r["lens"] == lab]
|
||||||
|
nwin = sum(1 for r in sub if r["wins"])
|
||||||
|
nred = sum(1 for r in sub if r["dd_reduced"])
|
||||||
|
cand = [r for r in sub if r["dd_reduced"]]
|
||||||
|
best = max(cand or sub, key=lambda r: (r["gap"] if cand else -r["dd_hedged"]))
|
||||||
|
print(f" >> {lab}: il maxDD SCENDE in {nred}/{len(sub)} celle; fra quelle la copertura "
|
||||||
|
f"batte il de-levering in {nwin}/{len(sub)}.")
|
||||||
|
head = ("migliore fra le celle che RIDUCONO il DD" if cand else
|
||||||
|
"NESSUNA cella riduce il DD -> si riporta quella col DD MINORE (k=1, confronto "
|
||||||
|
"degenere)")
|
||||||
|
print(f" {head}: delta {best['delta']:.2f} / "
|
||||||
|
f"{best['tenor']}g / {best['budget']*100:.1f}% / {best['roll']} -> "
|
||||||
|
f"maxDD {best['dd_base']*100:.2f}% -> {best['dd_hedged']*100:.2f}% (k {best['k']:.3f}), "
|
||||||
|
f"Δdrift vs de-levering {best['gap']*100:+.2f} pp/a, "
|
||||||
|
f"ΔSharpe {best['sh_hedged']-best['sh_base']:+.3f}")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- §C. controlli
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print("§C. CONTROLLI — un rilevatore che non segnala nulla puo' essere semplicemente rotto")
|
||||||
|
print("=" * 100)
|
||||||
|
ctrl = []
|
||||||
|
# (+1) copertura GRATIS (payoff senza premio): DEVE ridurre il DD e battere il de-levering
|
||||||
|
hr, _, _ = hedge_series(-0.10, 30, 0.020, "expiry", 1.0, 0.0, free=True)
|
||||||
|
ev = evaluate(book, hr); ctrl.append(("(+) payoff SENZA premio (free lunch)", ev, True))
|
||||||
|
# (+2) premio 1/10: stesso budget -> 10x le put. Speculare al negativo, DEVE funzionare
|
||||||
|
hr, _, _ = hedge_series(-0.10, 30, 0.020, "expiry", 0.1, 0.0)
|
||||||
|
ev = evaluate(book, hr); ctrl.append(("(+) premio 1/10 (f=0.1, 10x le put)", ev, True))
|
||||||
|
# (-) premio 10x: DEVE essere refutata
|
||||||
|
hr, _, _ = hedge_series(-0.10, 30, 0.020, "expiry", 10.0, 0.0)
|
||||||
|
ev = evaluate(book, hr); ctrl.append(("(-) premio 10x (f=10)", ev, False))
|
||||||
|
# (=) put ATM f=1, spread 0: NON e' un controllo (non deve funzionare per forza) ma e' il
|
||||||
|
# riferimento piu' caro possibile della famiglia: si stampa come misura, non come test.
|
||||||
|
hr, _, _ = hedge_series(-0.50, 30, 0.020, "expiry", 1.0, 0.0)
|
||||||
|
ev = evaluate(book, hr); ctrl.append(("(=) put ATM(δ-0.50) f=1 — misura, non controllo", ev, None))
|
||||||
|
for nm, ev, must in ctrl:
|
||||||
|
gap = ev["dr_hedged"] - ev["dr_delev"]
|
||||||
|
got = "RICONOSCIUTA" if ev["wins"] else "refutata"
|
||||||
|
tag = "" if must is None else (" ✓" if ev["wins"] == must else " ✗ CONTROLLO FALLITO")
|
||||||
|
print(f" {nm:<48} DD {ev['dd_base']*100:5.2f}%->{ev['dd_hedged']*100:5.2f}% k {ev['k']:.3f} "
|
||||||
|
f"Δdrift {gap*100:+7.2f}pp ShΔ {ev['sh_hedged']-ev['sh_base']:+.3f} -> {got}{tag}")
|
||||||
|
ok = all(ev["wins"] == must for _, ev, must in ctrl if must is not None)
|
||||||
|
print(" APPARATO: " + ("controlli 3/3 OK — il rilevatore riconosce cio' che DEVE riconoscere "
|
||||||
|
"e rifiuta cio' che DEVE rifiutare"
|
||||||
|
if ok else
|
||||||
|
"\u26a0\ufe0f UN CONTROLLO E' FALLITO: i numeri sopra NON sono credibili"))
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- §D. causalita'
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print("§D. CAUSALITA' — prova esplicita, non un gate generico")
|
||||||
|
print("=" * 100)
|
||||||
|
print(" `altlib.causality_ok` vuole un target_fn(df)->posizioni: NON si applica a un overlay")
|
||||||
|
print(" di opzioni (non e' una posizione continua sul sottostante). Al suo posto la prova")
|
||||||
|
print(" diretta e piu' forte: si ricalcola la copertura su un PREFISSO del pannello e si")
|
||||||
|
print(" verifica che coincida con la coda della serie completa. Un leak divergerebbe.")
|
||||||
|
hr_full, _, _ = hedge_series(-0.10, 14, 0.010, "expiry", 1.0, SPREAD[14])
|
||||||
|
cut = int(len(P) * 0.85)
|
||||||
|
Pf = daily_panel()
|
||||||
|
sub = Pf.iloc[:cut]
|
||||||
|
hr_cut, _, _ = hedge_leg(sub["px_BTC"].values, sub["lo_BTC"].values, sub["iv_BTC"].values,
|
||||||
|
delta=-0.10, tenor=14, budget_ann=0.010 * 0.5, roll="expiry",
|
||||||
|
f=1.0, spread=SPREAD[14])
|
||||||
|
hr_full_btc, _, _ = hedge_leg(Pf["px_BTC"].values, Pf["lo_BTC"].values, Pf["iv_BTC"].values,
|
||||||
|
delta=-0.10, tenor=14, budget_ann=0.010 * 0.5, roll="expiry",
|
||||||
|
f=1.0, spread=SPREAD[14])
|
||||||
|
# ⚠️ ERRORE MIO, catturato dalla prima corsa (dmax 1.28e-04 = "DIVERGE"): confrontavo fino a
|
||||||
|
# `cut`, ma il prefisso si ferma un roll prima (`while i + hold < n`), quindi l'ultimo ciclo
|
||||||
|
# non e' CALCOLATO nel troncato mentre lo e' nel completo. La differenza era per ASSENZA di
|
||||||
|
# un roll, non per look-ahead. Il confronto onesto finisce prima dell'ultimo roll possibile.
|
||||||
|
hold = 14
|
||||||
|
stop = cut - 2 * hold
|
||||||
|
tail = 200
|
||||||
|
dmax = float(np.max(np.abs(hr_cut[stop - tail:stop] - hr_full_btc[stop - tail:stop])))
|
||||||
|
dmax_naive = float(np.max(np.abs(hr_cut[cut - tail:cut] - hr_full_btc[cut - tail:cut])))
|
||||||
|
print(f" max|differenza| su [{stop-tail}:{stop}] (cut a {cut}/{len(P)}, ultimo roll escluso): "
|
||||||
|
f"{dmax:.2e} -> {'CAUSALE' if dmax < 1e-12 else '⚠️ DIVERGE'}")
|
||||||
|
print(f" (fino a `cut` fa {dmax_naive:.2e}: NON e' un leak, e' il roll che il prefisso non ha")
|
||||||
|
print(f" ancora eseguito — la finestra sbagliata dichiarava un difetto inesistente.)")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- §E. lente di coda
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print("§E. LENTE DI CODA — maxDD, peggiori mesi, peggior giorno, breach 4%/6%")
|
||||||
|
print("=" * 100)
|
||||||
|
CP = coupled_book_tuples()
|
||||||
|
if not CP.empty:
|
||||||
|
CP = CP[(CP.index >= idx[0]) & (CP.index <= idx[-1])]
|
||||||
|
cov = len(CP.index.intersection(idx)) / len(idx)
|
||||||
|
print(f" lente ACCOPPIATA disponibile: {len(CP)} giorni, copertura {cov*100:.1f}% del pannello")
|
||||||
|
# scelta della cella per la lente di coda: la MIGLIORE al f misurato (o 2.23), budget 1%
|
||||||
|
f_pick = F_MEAS.get(-0.10, F_LENSES_STATIC[1])
|
||||||
|
cells_tail = [("NUDO", None)]
|
||||||
|
for dlt in G_DELTA:
|
||||||
|
cells_tail.append((f"δ{dlt:.2f} 30g 1%/a expiry", (dlt, 30, 0.010, "expiry",
|
||||||
|
F_MEAS.get(dlt, f_pick))))
|
||||||
|
cells_tail.append(("δ-0.10 30g 2%/a expiry", (-0.10, 30, 0.020, "expiry", f_pick)))
|
||||||
|
cells_tail.append(("δ-0.10 30g 2%/a f=1 (irreale)", (-0.10, 30, 0.020, "expiry", 1.0)))
|
||||||
|
|
||||||
|
def _breach(series_close: np.ndarray, wick_extra: np.ndarray) -> tuple:
|
||||||
|
s_ = pd.Series(series_close, index=idx)
|
||||||
|
n_yr = len(P) / DPY
|
||||||
|
br_c = float((s_ <= -0.04).sum()) / n_yr
|
||||||
|
if CP.empty:
|
||||||
|
return float("nan"), br_c
|
||||||
|
m = CP["m"].reindex(idx).values
|
||||||
|
n_ev = int(np.nansum((m + np.nan_to_num(wick_extra)) <= -0.04))
|
||||||
|
_breach.last_n = n_ev
|
||||||
|
return n_ev / n_yr, br_c
|
||||||
|
|
||||||
|
print(f"\n {'variante':<32} {'maxDD':>7} {'pegg.giorno':>12} {'breach 4% /a':>14} "
|
||||||
|
f"{'(close-only)':>13} {'drift':>8} {'Sharpe':>7}")
|
||||||
|
for nm, spec in cells_tail:
|
||||||
|
if spec is None:
|
||||||
|
hr = np.zeros(len(P)); hw = np.zeros(len(P))
|
||||||
|
else:
|
||||||
|
dlt, ten, bud, roll, fu = spec
|
||||||
|
hr, hw, _ = hedge_series(dlt, ten, bud, roll, fu, SPREAD[ten])
|
||||||
|
hed = book + hr
|
||||||
|
s = pd.Series(hed, index=idx)
|
||||||
|
worst_day = float(s.min())
|
||||||
|
br_coup, br_close = _breach(hed, hr + hw) # il minimo del libro + la copertura
|
||||||
|
n_ev = getattr(_breach, "last_n", -1)
|
||||||
|
print(f" {nm:<32} {_dd(hed)*100:>6.2f}% {worst_day*100:>11.2f}% {br_coup:>8.2f}({n_ev:>2}) "
|
||||||
|
f"{br_close:>12.2f} {_drift(hed)*100:>7.2f}% {_sh(hed):>7.3f}")
|
||||||
|
if spec is not None:
|
||||||
|
# ⚠️ il confronto a pari DD e' DEGENERE qui (il DD non scende, quindi k=1). Sull'asse
|
||||||
|
# funded la domanda onesta e' l'altra: a PARI DRIFT, chi sfora meno la regola a un
|
||||||
|
# giorno? Cioe' basta rimpicciolire la posizione per comprare gli stessi breach in meno.
|
||||||
|
kk = k_for_same_drift(book, _drift(hed))
|
||||||
|
brk, brkc = _breach(kk * book, np.zeros(len(P)) if CP.empty
|
||||||
|
else (kk - 1.0) * np.nan_to_num(CP['m'].reindex(idx).values))
|
||||||
|
lab_ = " ^ de-levering k=%.3f, PARI DRIFT" % kk
|
||||||
|
print(f" {lab_:<32} {_dd(kk*book)*100:>6.2f}% "
|
||||||
|
f"{float(np.min(kk*book))*100:>11.2f}% {brk:>8.2f}({getattr(_breach,'last_n',-1):>2}) "
|
||||||
|
f"{brkc:>12.2f} "
|
||||||
|
f"{_drift(kk*book)*100:>7.2f}% {_sh(kk*book):>7.3f}")
|
||||||
|
print(" ⚠️ close-only e' CIECA su una regola a UN giorno (22/08: 0,00 contro 0,40-1,21 veri).")
|
||||||
|
print(" La colonna che decide e' quella ACCOPPIATA. Fra parentesi il CONTEGGIO di eventi:")
|
||||||
|
print(" 3-5 breach in 5,4 anni NON distinguono le varianti fra loro (un evento in piu' o in")
|
||||||
|
print(" meno sposta il tasso del 20-30%) -> si legge il SEGNO, non la taglia. Il fatto")
|
||||||
|
print(" robusto e' che il de-levering a PARI DRIFT compra lo stesso ordine di grandezza di")
|
||||||
|
print(" riduzione dei breach senza toccare il maxDD nel verso sbagliato.")
|
||||||
|
print("\n REGOLA MAX-LOSS 6% (statica dal saldo iniziale, HyroTrader — e' la regola BINDING")
|
||||||
|
print(" del canale funded, gia' modellata il 25/07 come EV_DD=0.06):")
|
||||||
|
k6 = k_for_same_dd(book, 0.06)
|
||||||
|
print(f" libro NUDO maxDD {_dd(book)*100:5.2f}% -> SFORA (serve k={k6:.3f} per "
|
||||||
|
f"stare a 6%, drift {_drift(k6*book)*100:.2f}%/a)")
|
||||||
|
for nm, spec in cells_tail[1:]:
|
||||||
|
dlt, ten, bud, roll, fu = spec
|
||||||
|
hr2, _, _ = hedge_series(dlt, ten, bud, roll, fu, SPREAD[ten])
|
||||||
|
hd = book + hr2
|
||||||
|
k6h = k_for_same_dd(hd, 0.06)
|
||||||
|
print(f" {nm:<20} maxDD {_dd(hd)*100:5.2f}% -> SFORA (serve k={k6h:.3f}, "
|
||||||
|
f"drift {_drift(k6h*hd)*100:.2f}%/a)")
|
||||||
|
print(" >> Sull'UNICA regola che decide un conto funded la copertura va nel verso SBAGLIATO:")
|
||||||
|
print(" alza il maxDD e quindi ABBASSA la leva ammessa. Il beneficio sui breach a un")
|
||||||
|
print(" giorno (0,92 -> 0,55/anno) e' reale ma si compra piu' a buon mercato de-levando,")
|
||||||
|
print(" che sulla regola a 6 punti e' l'unica leva che funziona.")
|
||||||
|
|
||||||
|
print("\n 5 PEGGIORI MESI (nudo vs coperto δ-0.10 30g 1%/a, f misurato):")
|
||||||
|
hr, _, _ = hedge_series(-0.10, 30, 0.010, "expiry", F_MEAS.get(-0.10, f_pick), SPREAD[30])
|
||||||
|
wm_b = worst_months(bs_book)
|
||||||
|
wm_h = worst_months(pd.Series(book + hr, index=idx))
|
||||||
|
mh = dict(wm_h)
|
||||||
|
hs_m = (1.0 + pd.Series(book + hr, index=idx)).resample("ME").prod() - 1.0
|
||||||
|
hs_m.index = [str(i.date())[:7] for i in hs_m.index]
|
||||||
|
for mth, v in wm_b:
|
||||||
|
hv = float(hs_m.get(mth, float("nan")))
|
||||||
|
print(f" {mth} nudo {v*100:>7.2f}% coperto {hv*100:>7.2f}% "
|
||||||
|
f"delta {(hv - v)*100:>+6.2f}pp")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- §F. il rischio sbagliato
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print("§F. L'ATTESA (c) MISURATA — la put protegge dal rischio che il libro ha davvero?")
|
||||||
|
print("=" * 100)
|
||||||
|
und = 0.5 * (P["px_BTC"].pct_change() + P["px_ETH"].pct_change()).fillna(0.0).values
|
||||||
|
ordr = np.argsort(book)[:20]
|
||||||
|
n_crash = int((und[ordr] <= -0.05).sum())
|
||||||
|
print(f" Sui 20 PEGGIORI giorni del libro: {n_crash}/20 sono giorni di crollo del sottostante")
|
||||||
|
print(f" (BTC/ETH medio <= -5%). Negli altri {20-n_crash} il libro perde mentre il mercato NON")
|
||||||
|
print(f" crolla -> una put non puo' proteggere per costruzione.")
|
||||||
|
print(f" {'giorno':>12} {'libro':>8} {'BTC/ETH':>9} {'TP01':>8} {'SKH01':>8}")
|
||||||
|
for j in ordr[:8]:
|
||||||
|
print(f" {str(idx[j].date()):>12} {book[j]*100:>7.2f}% {und[j]*100:>8.2f}% "
|
||||||
|
f"{P['tp'].values[j]*100:>7.2f}% {P['skh'].values[j]*100:>7.2f}%")
|
||||||
|
beta = float(np.polyfit(und, book, 1)[0])
|
||||||
|
corr = float(np.corrcoef(book, und)[0, 1])
|
||||||
|
print(f" corr(libro, sottostante) = {corr:+.3f} | beta del libro al sottostante = {beta:+.3f}")
|
||||||
|
print()
|
||||||
|
print(" --- VERDETTO SULL'ATTESA A PRIORI (si riporta anche dove e' REFUTATA) ---")
|
||||||
|
if n_crash >= 12:
|
||||||
|
print(f" (c) REFUTATA nella forma in cui l'ho scritta: {n_crash}/20 dei peggiori giorni del")
|
||||||
|
print( " libro SONO giorni di crollo. Non e' vero che si compra assicurazione contro il")
|
||||||
|
print( " rischio sbagliato in senso di DIREZIONE. ⚠️ E il caso che avevo in mente (lo")
|
||||||
|
print( " short-squeeze di SKH01) sta FUORI FINESTRA: il DVOL parte 2021-03.")
|
||||||
|
else:
|
||||||
|
print(f" (c) CONFERMATA: solo {n_crash}/20 dei peggiori giorni sono crolli.")
|
||||||
|
print(f" (b) CONFERMATA, ed e' il meccanismo operativo: beta del libro al sottostante {beta:+.3f}.")
|
||||||
|
print( " Il 2021-05-19 il sottostante ha fatto -21,1% e il libro -2,0%: la strategia aveva")
|
||||||
|
print( " GIA' ridotto la perdita di ~10x. La put paga in proporzione al crollo del")
|
||||||
|
print( " SOTTOSTANTE, ma protegge una perdita gia' ridotta di un ordine di grandezza ->")
|
||||||
|
print( " per coprire un DD del libro serve un nozionale di put ~1/beta volte l'equity, e")
|
||||||
|
print( " quel nozionale costa ~1/beta volte il budget.")
|
||||||
|
print( " (a) CONFERMATA: vedi il decadimento del premio nel §B (il costo cresce col budget e")
|
||||||
|
print( " col f mentre il beneficio satura).")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- §G. eseguibilita'
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print("§G. ESEGUIBILITA' — misurata DAL VENUE. Comprare un'opzione costa il PREMIO, non il")
|
||||||
|
print(" NOZIONALE: il muro dei 'lotti da migliaia di dollari' e' il collaterale per VENDERE.")
|
||||||
|
print("=" * 100)
|
||||||
|
vm = venue_min_ticket()
|
||||||
|
if vm["ok"]:
|
||||||
|
print(f" {'asset':>6} {'famiglia':>14} {'min_trade_amount':>18} {'tick':>10} {'settle':>8}")
|
||||||
|
for (a, fam), v in sorted(vm["fam"].items()):
|
||||||
|
print(f" {a:>6} {fam:>14} {v['min_amt']:>18} {v['tick']:>10} {v['settle']:>8}")
|
||||||
|
else:
|
||||||
|
print(f" NON MISURATO: {vm['note']}")
|
||||||
|
# costo del lotto minimo con i premi modellati di fine campione
|
||||||
|
print(f"\n Costo del LOTTO MINIMO e capitale a cui la copertura diventa comprabile a ogni roll:")
|
||||||
|
print(f" {'delta':>6} {'ten':>4} {'premio/unita':>13} {'lotto min':>10} "
|
||||||
|
f"{'$ lotto':>9} {'budget 1%/roll a $635':>22} {'capitale minimo':>16}")
|
||||||
|
for dlt in G_DELTA:
|
||||||
|
for ten in (7, 30):
|
||||||
|
S = float(P["px_BTC"].values[-1]); sig = float(P["iv_BTC"].values[-1])
|
||||||
|
T = ten / DPY
|
||||||
|
K = _strike_from_delta(S, T, sig, dlt)
|
||||||
|
pm = _bs_put(S, K, T, sig)
|
||||||
|
fu = F_MEAS.get(dlt, F_LENSES_STATIC[1])
|
||||||
|
price = fu * pm
|
||||||
|
minamt = vm["fam"].get(("BTC", "USDC-lineare"), {}).get("min_amt", 0.01)
|
||||||
|
lot = minamt * price
|
||||||
|
per_roll_635 = 0.010 * (ten / DPY) * 0.5 * 635.0 # 1%/a, meta' su BTC
|
||||||
|
cap_min = lot / (0.010 * (ten / DPY) * 0.5)
|
||||||
|
print(f" {dlt:>6.2f} {ten:>4} {price:>13.2f} {minamt:>10} {lot:>9.2f} "
|
||||||
|
f"{per_roll_635:>21.2f} {cap_min:>15,.0f}")
|
||||||
|
print(" ⚠️ E c'e' un secondo muro, STRUTTURALE e non di capitale: il TICK. Su BTC_USDC il tick")
|
||||||
|
print(" e' 5 USDC -> una put che vale meno di $5 NON e' quotabile sotto $5. Piu' e' deep-OTM")
|
||||||
|
print(" (cioe' piu' e' economica la copertura) piu' il tick domina il prezzo.")
|
||||||
|
# eval_weights_smallcap sulla gamba LINEARE, come riferimento del pavimento
|
||||||
|
try:
|
||||||
|
from src.strategies.trend_portfolio import TrendPortfolio, CANONICAL
|
||||||
|
d1 = resample_1d(load_data("BTC", "1h"))
|
||||||
|
tpm = TrendPortfolio(**CANONICAL)
|
||||||
|
sc = al.eval_weights_smallcap(d1, tpm.target_series(d1), capital=635.0, min_order=5.0)
|
||||||
|
print(f"\n Riferimento (gamba LINEARE dello stesso libro): TP01/BTC a $635 haircut Sharpe "
|
||||||
|
f"{sc['sharpe_haircut']:+.3f} -> il pavimento $5 non morde sul perpetual.")
|
||||||
|
print(" La differenza NON e' il livello del costo ma la sua FORMA (regola 25/07): sul")
|
||||||
|
print(" perpetual il minimo e' in DOLLARI di nozionale, sull'opzione e' in UNITA' di")
|
||||||
|
print(" sottostante MOLTIPLICATE per un premio che tende a zero -> due muri diversi.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n (riferimento smallcap non calcolato: {type(e).__name__}: {e})")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- §H. gate residui
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print("§H. GATE RESIDUI")
|
||||||
|
print("=" * 100)
|
||||||
|
# cella migliore in assoluto al f misurato/realistico
|
||||||
|
real = [r for r in all_rows if r["lens"] != "f=1.00 (modello nudo)"]
|
||||||
|
pool = real if real else all_rows
|
||||||
|
bestr = max(pool, key=lambda r: r["gap"])
|
||||||
|
hedged_best = pd.Series(book + bestr["hret"], index=idx)
|
||||||
|
imp = al.implausible_sharpe(hedged_best)
|
||||||
|
print(f" implausible_sharpe(libro coperto, cella migliore al f realistico): "
|
||||||
|
f"implausible={imp['implausible']} Sharpe {imp.get('sharpe', float('nan')):.2f} "
|
||||||
|
f"maxDD {imp.get('maxdd', float('nan'))*100:.2f}%")
|
||||||
|
for r in imp["reasons"]:
|
||||||
|
print(f" - {r}")
|
||||||
|
sr_all = [r["sh_hedged"] for r in pool]
|
||||||
|
dsr, nullmax = al.deflated_sharpe(bestr["sh_hedged"], sr_all, hedged_best)
|
||||||
|
print(f" deflated_sharpe della cella MIGLIORE su {len(pool)} trial: DSR {dsr:.3f} "
|
||||||
|
f"(max atteso dal nullo {nullmax:.2f}) -> {'PASS' if dsr >= 0.95 else 'FAIL'}")
|
||||||
|
print(" ⚠️ Il DSR qui e' informativo ma NON e' il gate che decide: la domanda non e' 'questa")
|
||||||
|
print(" cella ha alpha' (non ne ha per costruzione) ma 'batte il de-levering' — ed e' il §B.")
|
||||||
|
|
||||||
|
# sensitivita': f asimmetrico (compri caro, vendi a modello)
|
||||||
|
print("\n SENSITIVITA' f ASIMMETRICO (si compra a f, si vende a modello — caso pessimo per il")
|
||||||
|
print(" roll anticipato, che nel modello base non paga f perche' non arriva a intrinseco):")
|
||||||
|
fu = F_MEAS.get(-0.10, F_LENSES_STATIC[1])
|
||||||
|
for roll in G_ROLL:
|
||||||
|
hs, _, _ = hedge_series(-0.10, 30, 0.010, roll, fu, SPREAD[30])
|
||||||
|
ha, _, _ = hedge_series(-0.10, 30, 0.010, roll, fu, SPREAD[30], f_sell=1.0)
|
||||||
|
es, ea = evaluate(book, hs), evaluate(book, ha)
|
||||||
|
print(f" roll={roll:<7} f simmetrico Δdrift {(es['dr_hedged']-es['dr_delev'])*100:+6.2f}pp"
|
||||||
|
f" f asimmetrico Δdrift {(ea['dr_hedged']-ea['dr_delev'])*100:+6.2f}pp")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- §I. verdetto
|
||||||
|
print("\n" + "=" * 100)
|
||||||
|
print("§I. VERDETTO")
|
||||||
|
print("=" * 100)
|
||||||
|
for lab, _ in lenses:
|
||||||
|
sub = [r for r in all_rows if r["lens"] == lab]
|
||||||
|
nw = sum(1 for r in sub if r["wins"])
|
||||||
|
nred = sum(1 for r in sub if r["dd_reduced"])
|
||||||
|
cand = [r for r in sub if r["dd_reduced"]] or sub
|
||||||
|
bb = max(cand, key=lambda r: r["gap"])
|
||||||
|
print(f" {lab:<32}: DD scende {nred:>2}/{len(sub)} | batte il de-levering {nw:>2}/{len(sub)}"
|
||||||
|
f" | miglior Δdrift {bb['gap']*100:+6.2f} pp/a | ΔSharpe {bb['sh_hedged']-bb['sh_base']:+.3f}")
|
||||||
|
nw_real = sum(1 for r in pool if r["wins"])
|
||||||
|
nred_real = sum(1 for r in pool if r["dd_reduced"])
|
||||||
|
verdict = "REFUTATO" if nw_real == 0 else ("PARZIALE" if nw_real < len(pool) * 0.5 else "REGGE")
|
||||||
|
print(f"\n >>> {verdict}: al f realistico il maxDD scende in {nred_real}/{len(pool)} celle e la")
|
||||||
|
print(f" copertura batte il de-levering in {nw_real}/{len(pool)}.")
|
||||||
|
print( " ⚠️ Il fatto che il maxDD SALGA nella maggior parte delle celle e' il risultato piu'")
|
||||||
|
print( " forte del filone: prima ancora del prezzo, la premessa 'la copertura riduce il")
|
||||||
|
print( " drawdown' e' FALSA su questo libro — il bleed del premio cade DENTRO i drawdown")
|
||||||
|
print( " (che sono lunghi e poco profondi) mentre il payoff cade su singoli giorni di")
|
||||||
|
print( " crollo che NON sono il fondo del drawdown.")
|
||||||
|
print(f"\n Tempo: {time.time()-t0:.0f}s")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user