73207f79b9
§55 COSTO-CAPITALE. Tutti i muri pubblicati ($272k/$278k/$313k/$325k, traiettorie, versamenti) sono calcolati con un costo d'esecuzione COSTANTE, mentre un muro che si raggiunge accumulando attraversa tutte le taglie. Qui il costo diventa una funzione, misurata camminando il libro Deribit vero. [VENUE] 50 istantanee di BTC_USDC-PERPETUAL / ETH_USDC-PERPETUAL in 35 minuti (domenica notte UTC = la finestra piu' sottile della settimana, quindi conservativa). Mezzo spread 0,0065 / 0,0207 bps = UN TICK; costo sotto ~2 bps fino a $500k per ordine; il libro visibile finisce a $2,8M (BTC) / $1,2M (ETH). [CALC] Muro L3 congiunta $313.143 -> $315.080 col costo endogeno (+0,6%, DENTRO la risoluzione Monte Carlo del muro stesso, punto fisso convergente in una iterazione). La traiettoria EUR500/mese non si muove (P(20a) 85,1% -> 84,7%). Saturazione a ~$9,5M per argmax di C x drift(C) e a $10M per il primo 1% di nozionale non eseguibile in un istante: due definizioni indipendenti nella stessa decade, oltre un ordine di grandezza sopra il muro. Il gradino di leva sopravvive (k* 12,50x -> 12,25x alla taglia del muro; morde solo a $5M, dove crolla a 1,75x). Le due riserve degli scettici sono misurate e non mordono qui: il costo che si annullava in §52 e' quello dello SPOT (spread 100-500x piu' largo), e la partecipazione del 21,9% di §53 e' la quota di una barra 5m a volume basso — l'ordine di oggi e' lo 0,13% (BTC) / 0,43% (ETH) della profondita' a 1 bps. Trovato per strada, ed e' il fatto piu' utile: alla taglia di OGGI il costo che le tabelle non contengono ha segno NEGATIVO. Il drift a $635 e' 13 bps/anno PIU' BASSO che a $5.000 per il pavimento min_order, ~2x cio' che lo slippage costa fra $313k e $1M. Repliche 8/8 (TP01/SKH01/libro bit-exact; $272.061 e $258.338 al dollaro; accumula bit-exact; walk_cost_bps identica a §52) + R9 k* = 12,25x come FD.k_star, e due controlli POSITIVI obbligatori. Due errori miei catturati e pubblicati: banda p10/p50/p90 che si incrociava oltre il libro visibile (tre oggetti-curva separati), e un controllo positivo DEGENERE per costruzione (un costo costante sparisce da un drag differenziale). Sola lettura, nessun ordine, solo GET pubbliche. Libro, pesi, cron, config INVARIATI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018M8Ncho6QV9FWLdyy4VyQf
1114 lines
59 KiB
Python
1114 lines
59 KiB
Python
#!/usr/bin/env python3
|
|
"""r0823_costo_capitale.py — §55 COSTO-CAPITALE: il muro quando il costo d'esecuzione
|
|
NON e' una costante ma una FUNZIONE del capitale.
|
|
|
|
IL BUCO
|
|
-------
|
|
Ogni muro di capitale pubblicato dal progetto ($272k, $278k, $313k, $325k, la traiettoria da
|
|
$635, le tabelle dei versamenti) e' calcolato con un costo d'esecuzione COSTANTE: le fee
|
|
proporzionali di `backtest_signals` (0,10% RT) piu' il pavimento `min_order` di
|
|
`CC.tp01_realistic`. Ma un muro che si raggiunge ACCUMULANDO attraversa tutte le taglie, e il
|
|
costo vero cresce con la taglia dell'ordine. Il costo non e' un parametro: e' **una funzione
|
|
lungo il cammino**.
|
|
|
|
La domanda e' nata due volte nella stessa notte, da due filoni che non si parlavano:
|
|
* §52 SKEPTIC-SPOT — camminando entrambi i libri, il differenziale spot-perp passa da
|
|
+0,72 bps a $600 a **+25,66 bps a $272k**: il lead dello spot si annulla esattamente alla
|
|
taglia del muro che pretende di spostare. Follow-up dichiarato: «il muro rifatto con costo
|
|
d'esecuzione FUNZIONE DEL CAPITALE».
|
|
* §53 SKEPTIC-LEVA — «§5 ha misurato un fill al 21,9% del volume della sua barra 5m a $635;
|
|
con partecipazione lineare 1,25x lo porta al 27,4% SUBITO. Va misurata PRIMA del gradino.»
|
|
* §5 SLIP-AUDIT porta gia' una **data di scadenza esplicita**: «quel 22% diventa 172% di una
|
|
barra 5m a $5.000 e 687% a $20.000. Va rifatta a ogni salto di taglia.»
|
|
|
|
COSA FA QUESTO SCRIPT
|
|
---------------------
|
|
1. [VENUE] Misura `costo_bps(taglia)` camminando il libro reale di BTC_USDC-PERPETUAL e
|
|
ETH_USDC-PERPETUAL su piu' istantanee, e ne riporta la BANDA (§45: due istantanee a 4
|
|
minuti differivano del 79% — un'istantanea non e' una distribuzione).
|
|
2. [BACKTEST] Ricava la distribuzione VERA delle taglie d'ordine del libro live (TP01 75 /
|
|
SKH01 25) a ogni capitale. Non l'ordine medio: il costo e' convesso, la media dei costi
|
|
non e' il costo della media.
|
|
3. [CALC] Rifa' il libro con il costo DENTRO, itera il muro come PUNTO FISSO
|
|
(`r0726_wall_fixedpoint`, importato) e riporta se converge.
|
|
4. [CALC] Trova il PUNTO DI SATURAZIONE con DUE definizioni indipendenti — un limite DURO
|
|
puramente misurato (la taglia a cui l'1% del nozionale annuo non e' piu' eseguibile in un
|
|
istante) e uno ECONOMICO (l'argmax di `C x drift(C)`, dove il capitale marginale smette di
|
|
comprare reddito). Numero mai calcolato dal progetto.
|
|
5. [CALC] Rifa' la traiettoria «EUR500/mese da $635» col costo LUNGO IL CAMMINO
|
|
(`r0822d_piano_vero`, importato).
|
|
6. [CALC] Il gradino di leva col costo endogeno: k massimo.
|
|
|
|
REPLICHE OBBLIGATORIE PRIMA DI OGNI DELTA (regola del progetto: prima di pubblicare un numero
|
|
nuovo, far riprodurre alla macchina quello vecchio)
|
|
R1 la mia TP01 con costo spento == `FD.tp01_realistic` bit-exact
|
|
R2 la mia SKH01 con costo spento == `FD.skh_book` bit-exact
|
|
R3 il mio libro con costo spento == `FD.book_75_25` bit-exact
|
|
R4 muro L0 LORDO sul vintage 2026-08-07 $272.061 al dollaro
|
|
R5 muro L1 +FISCO sul vintage 2026-08-07 $258.338 al dollaro
|
|
R6 muro L3 CONGIUNTA sulla serie di oggi $313k (§36)
|
|
R7 `accumula_costo` con drag spento == `PN.accumula` bit-exact
|
|
R8 la mia `walk_cost_bps` == quella di `r0823_skeptic_spot` identita' su libro sintetico
|
|
R9 k* a costo ZERO == `FD.k_star` sul libro pubblicato 12,25x (§35)
|
|
piu' DUE controlli POSITIVI obbligatori: un costo di 200 bps per ordine deve uccidere il
|
|
libro, e un drag di 3%/anno sopra $100k deve muovere la traiettoria. Un modello di costo che
|
|
non morde mai e' indistinguibile da uno rotto.
|
|
|
|
ETICHETTE DI PROVENIENZA
|
|
[VENUE] misurato dall'API pubblica Deribit in questa sessione
|
|
[LOG] dai log/fill reali del libro live
|
|
[BACKTEST] dalla serie storica certificata
|
|
[CALC] aritmetica sui due precedenti
|
|
[DEDOTTO] estrapolazione oltre la regione misurata
|
|
[ASSUNTO] scelta di modello dichiarata
|
|
|
|
ASSUNZIONE PRINCIPALE, DICHIARATA IN TESTA
|
|
Il libro d'ordini e' una FOTOGRAFIA DEL PRESENTE. Proiettarla su 7 anni di storia (2019-2026,
|
|
quando la profondita' del venue era una frazione di quella di oggi) e' un'ASSUNZIONE, e va nel
|
|
verso OTTIMISTA: il numero che ne esce e' un limite superiore della capacita' STORICA, non una
|
|
misura di essa. Sul futuro — che e' cio' che il muro deve attraversare — l'assunzione e' la
|
|
costanza della profondita', non la sua crescita.
|
|
Nell'altro verso, e su un asse diverso: il campione e' raccolto di DOMENICA NOTTE UTC, cioe'
|
|
nella finestra piu' sottile della settimana. Dentro il presente e' quindi CONSERVATIVO — un
|
|
campione in orario di punta darebbe costi piu' bassi.
|
|
|
|
SOLA LETTURA / RETE / SOLDI VERI
|
|
Nessuna scrittura nel repo (cache FUORI, nello scratchpad). Nessun ordine, mai: solo GET
|
|
PUBBLICHE senza chiave, pacing <=2 req/s, astensione nei minuti :05-:10 e :24-:30 (finestre
|
|
del cron di produzione).
|
|
|
|
uv run python scripts/research/r0823_costo_capitale.py
|
|
uv run python scripts/research/r0823_costo_capitale.py --no-net # solo cache
|
|
uv run python scripts/research/r0823_costo_capitale.py --rounds 8 --gap 35
|
|
uv run python scripts/research/r0823_costo_capitale.py --fetch-only --rounds 22 --gap 55
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import datetime as dt
|
|
import io
|
|
import json
|
|
import sys
|
|
import time
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Il MIO flag di rete si legge PRIMA di importare qualunque libreria del progetto:
|
|
# `r0822d_funding` decide all'import se andare in rete, e qui deve usare la sua cache.
|
|
# ---------------------------------------------------------------------------
|
|
_NET = "--no-net" not in sys.argv
|
|
|
|
|
|
def _argf(name: str, default: float) -> float:
|
|
if name in sys.argv:
|
|
return float(sys.argv[sys.argv.index(name) + 1])
|
|
return default
|
|
|
|
|
|
ROUNDS = int(_argf("--rounds", 8))
|
|
GAP_S = _argf("--gap", 35.0)
|
|
FETCH_ONLY = "--fetch-only" in sys.argv
|
|
|
|
if "--no-net" not in sys.argv:
|
|
sys.argv.append("--no-net")
|
|
|
|
ROOT = Path("/opt/docker/PythagorasGoal")
|
|
for _p in (ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt", ROOT):
|
|
sys.path.insert(0, str(_p))
|
|
|
|
import r0702_anchor_skh01 as r02 # noqa: E402 run_asset (ltf/ent), sim_equity
|
|
import r0725_capcurve as CC # noqa: E402 DERIBIT_FEE_SIDE/MIN_ORDER, _boot_paths
|
|
import r0726_wall_fixedpoint as WF # noqa: E402 la STRUTTURA del punto fisso
|
|
import r0727_tasse as TX # noqa: E402 PATRIMONIALE, CARRY_ANNI
|
|
import r0807_piano_netto as PN # noqa: E402 muro, accumula, passo_fiscale
|
|
import r0822d_funding as FD # noqa: E402 funding, tp01_realistic, skh_daily
|
|
import r0822d_piano_vero as PV # noqa: E402 Lente, deluck, muro_di, leggi
|
|
from src.data.downloader import load_data # noqa: E402
|
|
from src.portfolio.portfolio import combine_outer, metrics # noqa: E402
|
|
from src.strategies.trend_portfolio import (CANONICAL, TrendPortfolio, # noqa: E402
|
|
resample_1d, simple_returns)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Costanti dichiarate
|
|
# ---------------------------------------------------------------------------
|
|
ASSETS = ("BTC", "ETH")
|
|
INSTR = {"BTC": "BTC_USDC-PERPETUAL", "ETH": "ETH_USDC-PERPETUAL"} # cio' che il libro TRADA
|
|
W_TP, W_SKH = 0.75, 0.25
|
|
LO = pd.Timestamp("2019-03-14", tz="UTC") # inizio del libro (convenzione FD.book_75_25)
|
|
DPY = 365.0
|
|
API = "https://www.deribit.com/api/v2/public/"
|
|
DEPTH = 1000 # livelli richiesti per lato
|
|
VINTAGE_PUB = "2026-08-07"
|
|
|
|
# griglia di taglie: log-spaziata da $5 (il min_order) a $10M
|
|
SIZES = np.unique(np.round(10.0 ** np.arange(np.log10(5.0), 7.0001, 0.125), 2))
|
|
# griglia di capitali su cui si misura il libro (ordini di grandezza, non un continuo)
|
|
CAPS = (635.0, 2_000.0, 5_000.0, 20_000.0, 50_000.0, 100_000.0, 313_000.0,
|
|
1_000_000.0, 3_000_000.0, 10_000_000.0, 30_000_000.0, 100_000_000.0)
|
|
|
|
_SP = Path("/tmp/claude-1001/-opt-docker-PythagorasGoal/"
|
|
"b6cc75e7-14f8-4c32-bd07-ab8a0d2aaee6/scratchpad")
|
|
CACHE = (_SP if _SP.parent.exists() else Path("/tmp/pythagoras_costo")) / "costocap"
|
|
CACHE.mkdir(parents=True, exist_ok=True)
|
|
BOOKS = CACHE / "books.jsonl"
|
|
FILLS = _SP / "r0822_slip_fills.csv" # i 18 fill reali di §5
|
|
LIN5M = {"BTC": _SP / "lin_btc_5m.parquet", "ETH": _SP / "lin_eth_5m.parquet"}
|
|
|
|
T0 = time.time()
|
|
_last = [0.0]
|
|
|
|
|
|
def sez(t: str) -> None:
|
|
print("\n" + "=" * 98 + f"\n {t}\n" + "=" * 98)
|
|
|
|
|
|
def clock() -> str:
|
|
return f"[{time.time() - T0:5.0f}s]"
|
|
|
|
|
|
# ===========================================================================
|
|
# 0) RETE — GET pubbliche, pacing, astensione nelle finestre del cron
|
|
# ===========================================================================
|
|
def _guard_window() -> None:
|
|
while True:
|
|
m = dt.datetime.now(dt.timezone.utc).minute
|
|
if not (5 <= m <= 10 or 24 <= m <= 30):
|
|
return
|
|
tgt = 11 if m <= 10 else 31
|
|
w = (tgt - m) * 60 - dt.datetime.now(dt.timezone.utc).second
|
|
print(f" [rete] minuto :{m:02d} = finestra del cron di produzione -> attendo {max(w,1)}s")
|
|
time.sleep(max(w, 1))
|
|
|
|
|
|
def _get(path: str, tries: int = 3):
|
|
if not _NET:
|
|
return None
|
|
_guard_window()
|
|
for k in range(tries):
|
|
d = time.time() - _last[0]
|
|
if d < 0.55: # <=2 req/s
|
|
time.sleep(0.55 - d)
|
|
_last[0] = time.time()
|
|
try:
|
|
with urllib.request.urlopen(API + path, timeout=30) as r:
|
|
return json.load(r)
|
|
except Exception as ex: # noqa: BLE001
|
|
if k == tries - 1:
|
|
print(f" [rete] KO {path[:64]}: {ex}")
|
|
return None
|
|
time.sleep(1.5 * (k + 1))
|
|
return None
|
|
|
|
|
|
def raccogli(rounds: int, gap: float) -> int:
|
|
"""Istantanee del libro, appese alla cache. Ritorna quante ne ha aggiunte."""
|
|
if not _NET or rounds <= 0:
|
|
return 0
|
|
n = 0
|
|
with BOOKS.open("a") as fh:
|
|
for r in range(rounds):
|
|
for a in ASSETS:
|
|
j = _get(f"get_order_book?instrument_name={INSTR[a]}&depth={DEPTH}")
|
|
if not j or "result" not in j:
|
|
continue
|
|
q = j["result"]
|
|
if not q.get("bids") or not q.get("asks"):
|
|
continue
|
|
fh.write(json.dumps(dict(
|
|
ts=int(q.get("timestamp", time.time() * 1000)), asset=a,
|
|
bid=float(q["best_bid_price"]), ask=float(q["best_ask_price"]),
|
|
bids=[[float(p), float(s)] for p, s in q["bids"]],
|
|
asks=[[float(p), float(s)] for p, s in q["asks"]])) + "\n")
|
|
n += 1
|
|
if r < rounds - 1:
|
|
time.sleep(gap)
|
|
return n
|
|
|
|
|
|
def leggi_libri() -> list:
|
|
if not BOOKS.exists():
|
|
return []
|
|
out = []
|
|
for ln in BOOKS.read_text().splitlines():
|
|
if ln.strip():
|
|
with contextlib.suppress(Exception):
|
|
out.append(json.loads(ln))
|
|
return out
|
|
|
|
|
|
# ===========================================================================
|
|
# 1) LA CURVA COSTO-TAGLIA
|
|
# ===========================================================================
|
|
def walk_cost_bps(book: dict, usd: float, side: str) -> float | None:
|
|
"""Costo effettivo vs mid di un ordine da `usd` eseguito ISTANTANEAMENTE contro il libro
|
|
visibile (stessa convenzione di `r0823_skeptic_spot.walk_cost_bps` e
|
|
`r0822e_funding_avoid.walk_cost_bps`). None se il libro visibile NON basta: allora si
|
|
dichiara, non si estrapola."""
|
|
if not book or not book.get("bid") or not book.get("ask"):
|
|
return None
|
|
mid = (book["bid"] + book["ask"]) / 2.0
|
|
lv = book.get("asks" if side == "buy" else "bids") or []
|
|
need, got, cost = usd / mid, 0.0, 0.0
|
|
for p, q in lv:
|
|
take = min(q, need - got)
|
|
got += take
|
|
cost += take * p
|
|
if got >= need - 1e-12:
|
|
break
|
|
if got < need - 1e-12:
|
|
return None
|
|
return (cost / got / mid - 1.0) * 1e4 * (1.0 if side == "buy" else -1.0)
|
|
|
|
|
|
class Curva:
|
|
"""`costo_bps(asset, usd, q)` per interpolazione log-log sulle quantili misurate.
|
|
|
|
* sotto la taglia minima misurata: il costo e' il mezzo spread (costante) — e' cio' che il
|
|
libro DICE, non un'assunzione;
|
|
* dentro la regione misurata: interpolazione lineare in log(taglia), con monotonia imposta
|
|
(`cummax`): camminare un libro non puo' costare MENO su un ordine piu' grande, e una
|
|
violazione sarebbe rumore fra istantanee;
|
|
* oltre l'ultima taglia con copertura sufficiente (`lim`): il libro visibile NON BASTA, cioe'
|
|
l'ordine non e' eseguibile in un istante. Li' si estrapola con pendenza log-log
|
|
`max(misurata, 1.0)` — [ASSUNTO] dichiarato: impatto ALMENO lineare nella taglia, perche'
|
|
una pendenza < 1 (raddoppiare l'ordine costa meno del doppio) oltre la fine del libro non
|
|
e' difendibile. Ogni cella oltre `lim` e' marcata [DEDOTTO].
|
|
|
|
Le tre quantili condividono maschera, `lim` e pendenza -> la banda resta ORDINATA anche
|
|
nella regione estrapolata (con tre oggetti separati p10 poteva finire sopra p50: errore
|
|
catturato alla prima corsa).
|
|
"""
|
|
|
|
QS = (0.10, 0.50, 0.90)
|
|
|
|
def __init__(self, libri: list, cop_min: float = 0.8):
|
|
self.cop_min = cop_min
|
|
self.x, self.y, self.lim, self.slope, self.nobs = {}, {}, {}, {}, {}
|
|
self.slope_mis = {}
|
|
for a in ASSETS:
|
|
L = [b for b in libri if b["asset"] == a]
|
|
self.nobs[a] = len(L)
|
|
M, cop = {q: [] for q in self.QS}, []
|
|
for sz in SIZES:
|
|
v = []
|
|
for b in L:
|
|
for side in ("buy", "sell"):
|
|
c = walk_cost_bps(b, float(sz), side)
|
|
v.append(np.nan if c is None else c)
|
|
v = np.asarray(v, float)
|
|
cop.append(float(np.isfinite(v).mean()) if len(v) else 0.0)
|
|
for q in self.QS:
|
|
M[q].append(float(np.nanquantile(v, q)) if np.isfinite(v).any() else np.nan)
|
|
cop = np.asarray(cop)
|
|
ok = np.isfinite(np.asarray(M[0.5])) & (cop >= cop_min)
|
|
if not ok.any():
|
|
raise SystemExit(f"nessuna taglia misurabile su {a}: campione insufficiente")
|
|
xs = np.log10(SIZES[ok])
|
|
ys = {q: np.maximum.accumulate(np.asarray(M[q])[ok]) for q in self.QS}
|
|
ys[0.50] = np.maximum(ys[0.50], ys[0.10]) # banda ordinata per costruzione
|
|
ys[0.90] = np.maximum(ys[0.90], ys[0.50])
|
|
self.x[a], self.y[a] = xs, ys
|
|
self.lim[a] = float(SIZES[ok][-1])
|
|
m = xs >= xs[-1] - 1.0
|
|
sl = (float(np.polyfit(xs[m], np.log10(np.maximum(ys[0.50][m], 1e-4)), 1)[0])
|
|
if m.sum() >= 2 else 1.0)
|
|
self.slope[a] = max(sl, 1.0) # [ASSUNTO] impatto almeno lineare
|
|
self.slope_mis[a] = sl
|
|
|
|
def oltre(self, asset: str, usd: float) -> bool:
|
|
return usd > self.lim[asset]
|
|
|
|
def bps(self, asset: str, usd: float, q: float = 0.50) -> float:
|
|
if usd <= 0:
|
|
return 0.0
|
|
xs, ys = self.x[asset], self.y[asset][q]
|
|
lx = np.log10(usd)
|
|
if lx <= xs[0]:
|
|
return float(ys[0])
|
|
if lx <= xs[-1]:
|
|
return float(np.interp(lx, xs, ys))
|
|
return float(10.0 ** (np.log10(max(ys[-1], 1e-4)) + self.slope[asset] * (lx - xs[-1])))
|
|
|
|
def vec(self, asset: str, usd: np.ndarray, q: float = 0.50) -> np.ndarray:
|
|
return np.array([self.bps(asset, float(u), q) for u in usd], float)
|
|
|
|
def profondita(self, libri: list, asset: str, soglia_bps: float) -> float:
|
|
"""[VENUE] Dollari eseguibili entro `soglia_bps` di costo (mediana sulle istantanee,
|
|
lato peggiore)."""
|
|
v = []
|
|
for b in [x for x in libri if x["asset"] == asset]:
|
|
lo, hi = 0.0, 5e7
|
|
for _ in range(30):
|
|
mid = (lo + hi) / 2
|
|
c = walk_cost_bps(b, mid, "buy")
|
|
d = walk_cost_bps(b, mid, "sell")
|
|
w = max([z for z in (c, d) if z is not None], default=1e9)
|
|
if w <= soglia_bps:
|
|
lo = mid
|
|
else:
|
|
hi = mid
|
|
v.append(lo)
|
|
return float(np.median(v)) if v else float("nan")
|
|
|
|
|
|
class CurvaZero(Curva):
|
|
"""Costo identicamente nullo: il controllo negativo (replica bit-exact dei numeri vecchi)."""
|
|
|
|
def __init__(self): # noqa: D107
|
|
self.nobs, self.lim, self.slope = {}, {a: float("inf") for a in ASSETS}, {}
|
|
|
|
def oltre(self, asset: str, usd: float) -> bool: # noqa: D102
|
|
return False
|
|
|
|
def bps(self, asset: str, usd: float, q: float = 0.50) -> float: # noqa: D102
|
|
return 0.0
|
|
|
|
def vec(self, asset: str, usd: np.ndarray, q: float = 0.50) -> np.ndarray: # noqa: D102
|
|
return np.zeros(len(usd))
|
|
|
|
|
|
class CurvaFissa(CurvaZero):
|
|
"""Costo costante in bps: il controllo POSITIVO. Un modello di costo che non morde mai e'
|
|
indistinguibile da uno rotto, quindi va provato che morde."""
|
|
|
|
def __init__(self, bps: float): # noqa: D107
|
|
super().__init__()
|
|
self._b = float(bps)
|
|
|
|
def bps(self, asset: str, usd: float, q: float = 0.50) -> float: # noqa: D102
|
|
return self._b if usd > 0 else 0.0
|
|
|
|
def vec(self, asset: str, usd: np.ndarray, q: float = 0.50) -> np.ndarray: # noqa: D102
|
|
return np.where(np.asarray(usd) > 0, self._b, 0.0)
|
|
|
|
|
|
# ===========================================================================
|
|
# 2) IL LIBRO CON IL COSTO DENTRO
|
|
# ===========================================================================
|
|
def tp01_costo(alloc: float, rate: dict | None, cur: Curva, scala: float = 1.0,
|
|
q: float = 0.50) -> tuple[pd.Series, dict]:
|
|
"""`FD.tp01_realistic` con DUE gradi di liberta' in piu': il costo d'esecuzione funzione
|
|
della taglia dell'ordine, e la scala di leva (`SPEC-scale-key`: la scala moltiplica il
|
|
TARGET, non il capitale). Con cur=CurvaZero() e scala=1 deve dare bit-exact l'originale."""
|
|
cap_asset = alloc / len(ASSETS)
|
|
tp = TrendPortfolio(**CANONICAL)
|
|
series, diag = {}, {}
|
|
for a in ASSETS:
|
|
df = resample_1d(load_data(a, "1h"))
|
|
idx = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True))
|
|
r = simple_returns(df["close"].values.astype(float))
|
|
tgt = np.nan_to_num(np.asarray(tp.target_series(df), float)) * scala
|
|
held = np.empty(len(tgt))
|
|
cur_pos = 0.0
|
|
for i in range(len(tgt)):
|
|
if abs(tgt[i] - cur_pos) * cap_asset >= CC.DERIBIT_MIN_ORDER:
|
|
cur_pos = tgt[i]
|
|
held[i] = cur_pos
|
|
pos = np.zeros(len(held))
|
|
pos[1:] = held[:-1]
|
|
dpos = np.abs(np.diff(pos, prepend=0.0))
|
|
net = pos * r - CC.DERIBIT_FEE_SIDE * dpos
|
|
ordini = dpos * cap_asset # nozionale in dollari di OGNI ordine
|
|
cbps = cur.vec(a, ordini, q)
|
|
net = net - dpos * cbps / 1e4 # il costo pesa il nozionale scambiato
|
|
if rate is not None:
|
|
net = net - pos * FD.daily_funding(rate[a], idx)
|
|
net[0] = 0.0
|
|
diag[a] = dict(pos=pos, ordini=ordini[ordini > 0], cbps=cbps[ordini > 0], idx=idx)
|
|
series[a] = pd.Series(np.clip(net, -0.99, None), index=idx)
|
|
J = pd.concat(series, axis=1, join="inner").fillna(0.0)
|
|
out = pd.Series(0.5 * J["BTC"].values + 0.5 * J["ETH"].values, index=J.index)
|
|
if out.index.tz is None:
|
|
out.index = out.index.tz_localize("UTC")
|
|
return out, diag
|
|
|
|
|
|
_LTF: dict = {}
|
|
|
|
|
|
def _ltf(a: str):
|
|
if a not in _LTF:
|
|
_, _m0, ltf, _ent = r02.run_asset(a, 0)
|
|
_LTF[a] = ltf
|
|
return _LTF[a]
|
|
|
|
|
|
def _rebuild_equity(n: int, trades: list, extra: float, scala: float) -> np.ndarray:
|
|
"""Ricostruisce la curva di equity di SKH01 dai TRADE gia' prodotti da `FD.sim_equity_f`,
|
|
con un costo `extra` per round-trip e una scala di leva. Non e' una re-implementazione della
|
|
strategia: i trade (boundary, direzione, gross, funding) sono presi COSI' COME SONO, cambia
|
|
solo la contabilita' del singolo trade. Con extra=0 e scala=1 e' bit-exact l'originale."""
|
|
initial = 1000.0
|
|
capital = initial
|
|
equity = np.full(n, capital, dtype=float)
|
|
for t in trades:
|
|
net = scala * (t["gross"] - 0.001 - t["f"]) - extra
|
|
capital += capital * net
|
|
capital = max(capital, 1.0)
|
|
equity[t["i"]:t["exit_idx"] + 1] = capital
|
|
last = initial
|
|
for k in range(n):
|
|
if equity[k] != last and equity[k] != initial:
|
|
last = equity[k]
|
|
else:
|
|
equity[k] = last
|
|
return equity
|
|
|
|
|
|
def skh_costo(alloc: float, rate: dict | None, cur: Curva, scala: float = 1.0,
|
|
q: float = 0.50) -> pd.Series:
|
|
"""SKH01 50/50 con il costo d'esecuzione dentro.
|
|
|
|
SKH01 entra a nozionale 1,0x del proprio capitale (`position_size=1.0, leverage=1.0`) e fa
|
|
UN round-trip per trade -> due ordini, entrambi a nozionale pieno. Il costo per trade e'
|
|
percio' `2 x costo_bps(nozionale)/1e4`, e il nozionale e' `scala x alloc/2`."""
|
|
cap_asset = alloc / len(ASSETS)
|
|
cols = {}
|
|
for a in ASSETS:
|
|
_d, tr = FD.skh_daily(a, 0, "hourly", rate)
|
|
ltf = _ltf(a)
|
|
extra = 2.0 * scala * cur.bps(a, scala * cap_asset, q) / 1e4
|
|
eq = _rebuild_equity(len(ltf), tr, extra, scala)
|
|
s = pd.Series(eq, index=pd.DatetimeIndex(pd.to_datetime(ltf["datetime"], utc=True)))
|
|
cols[a] = s.resample("1D").last().ffill().pct_change().dropna()
|
|
J = pd.concat(cols, axis=1, join="inner").fillna(0.0)
|
|
out = pd.Series(0.5 * J["BTC"].values + 0.5 * J["ETH"].values, index=J.index)
|
|
if out.index.tz is None:
|
|
out.index = out.index.tz_localize("UTC")
|
|
return out
|
|
|
|
|
|
def libro(cap: float, rate: dict | None, cur: Curva, scala: float = 1.0,
|
|
q: float = 0.50) -> pd.Series:
|
|
"""Il libro LIVE (TP01 75 + SKH01 25) al capitale `cap`, col costo d'esecuzione dentro."""
|
|
tp, _ = tp01_costo(cap * W_TP, rate, cur, scala, q)
|
|
sk = skh_costo(cap * W_SKH, rate, cur, scala, q)
|
|
return combine_outer({"TP01": tp, "SKH01": sk}, {"TP01": W_TP, "SKH01": W_SKH}, lo=LO)
|
|
|
|
|
|
# ===========================================================================
|
|
# 3) ACCUMULO CON COSTO LUNGO IL CAMMINO
|
|
# ===========================================================================
|
|
def accumula_costo(paths: np.ndarray, dep_eur: float, bersaglio: float, aliquota: float,
|
|
patrimoniale: float, start: float, drag, lump_eur: float = 0.0) -> dict:
|
|
"""Gemella di `PN.accumula` con UN grado di liberta' in piu': un drag GIORNALIERO funzione
|
|
del capitale corrente. Con drag=None dev'essere bit-exact `PN.accumula` (replica R7).
|
|
|
|
`drag(cap)` = (drift del libro alla taglia di riferimento) - (drift alla taglia `cap`),
|
|
diviso 365. E' un aggiustamento del solo DRIFT: la FORMA del percorso resta quella estratta.
|
|
[ASSUNTO] dichiarato — la vol cambia pochissimo con la taglia (sezione 4 lo riporta)."""
|
|
n_paths, n_days = paths.shape
|
|
dep = dep_eur * CC.EURUSD
|
|
cap = np.full(n_paths, start + lump_eur * CC.EURUSD)
|
|
versato = start + lump_eur * CC.EURUSD
|
|
carry = np.zeros((n_paths, TX.CARRY_ANNI))
|
|
anno_start = cap.copy()
|
|
flusso = np.zeros(n_paths)
|
|
colpito = np.full(n_paths, -1, int)
|
|
for t in range(n_days):
|
|
if drag is None:
|
|
cap *= (1.0 + paths[:, t])
|
|
else:
|
|
cap *= (1.0 + paths[:, t] - drag(cap))
|
|
if t % 30 == 0 and t > 0 and dep > 0:
|
|
cap += dep
|
|
flusso += dep
|
|
versato += dep
|
|
nuovi = (colpito < 0) & (cap >= bersaglio)
|
|
colpito[nuovi] = t
|
|
if (t + 1) % 365 == 0 and (aliquota > 0 or patrimoniale > 0):
|
|
PN.passo_fiscale(cap, anno_start, flusso, carry, aliquota, patrimoniale)
|
|
anno_start = cap.copy()
|
|
flusso[:] = 0.0
|
|
return dict(cap=cap, colpito=colpito, versato=versato)
|
|
|
|
|
|
|
|
|
|
# ===========================================================================
|
|
# 4) DIAGNOSTICHE DI CAPACITA'
|
|
# ===========================================================================
|
|
def quota_oltre(cap: float, cur: Curva, rate: dict | None) -> tuple[float, float]:
|
|
"""(quota del nozionale annuo scambiato la cui TAGLIA supera il libro visibile,
|
|
nozionale annuo totale in $). E' una misura di CAPACITA' puramente [VENUE]+[BACKTEST]:
|
|
non dipende da nessuna estrapolazione."""
|
|
Z = CurvaZero()
|
|
_s, dg = tp01_costo(cap * W_TP, None, Z)
|
|
anni = 0.0
|
|
tot = fuori = 0.0
|
|
for a in ASSETS:
|
|
o = dg[a]["ordini"]
|
|
idx = dg[a]["idx"]
|
|
anni = max(anni, (idx[-1] - idx[0]).days / DPY)
|
|
tot += float(o.sum())
|
|
fuori += float(o[o > cur.lim[a]].sum())
|
|
tr = FD.skh_daily(a, 0, "hourly", None)[1]
|
|
noz = cap * W_SKH / 2
|
|
tot += 2 * len(tr) * noz
|
|
if noz > cur.lim[a]:
|
|
fuori += 2 * len(tr) * noz
|
|
return (fuori / tot if tot > 0 else 0.0), tot / max(anni, 1e-9)
|
|
|
|
|
|
# ===========================================================================
|
|
def main() -> None: # noqa: C901
|
|
print("=" * 98)
|
|
print(" r0823 §55 COSTO-CAPITALE — il muro quando il costo d'esecuzione e' una FUNZIONE")
|
|
print("=" * 98)
|
|
print(" Provenienza: [VENUE] API pubblica · [LOG] fill reali · [BACKTEST] serie certificata")
|
|
print(" [CALC] aritmetica · [DEDOTTO] estrapolazione · [ASSUNTO] modello")
|
|
|
|
# ---------------------------------------------------------------- rete
|
|
nuovi = raccogli(ROUNDS, GAP_S)
|
|
if FETCH_ONLY:
|
|
print(f" --fetch-only: {nuovi} istantanee aggiunte. Fine.")
|
|
return
|
|
libri = leggi_libri()
|
|
if not libri:
|
|
raise SystemExit("nessuna istantanea del libro: rilanciare con rete.")
|
|
ts = pd.to_datetime([b["ts"] for b in libri], unit="ms", utc=True)
|
|
print(f"\n [VENUE] {len(libri)} istantanee ({nuovi} nuove in questa corsa), "
|
|
f"{sum(1 for b in libri if b['asset']=='BTC')} BTC / "
|
|
f"{sum(1 for b in libri if b['asset']=='ETH')} ETH")
|
|
print(f" dal {ts.min():%Y-%m-%d %H:%M} al {ts.max():%Y-%m-%d %H:%M} UTC "
|
|
f"(arco {(ts.max()-ts.min()).total_seconds()/60:.0f} min) {clock()}")
|
|
print(" ⚠️ E' un solo regime di mercato in un arco di minuti: e' una banda INTRA-GIORNO,")
|
|
print(" non la distribuzione della profondita' su 7 anni. Vedi l'assunzione in testa.")
|
|
gg = ["lun", "mar", "mer", "gio", "ven", "sab", "dom"][int(ts.max().dayofweek)]
|
|
print(f" 📌 Ma il MOMENTO e' favorevole al risultato conservativo, non a quello comodo: "
|
|
f"{gg} {ts.max():%H:%M} UTC")
|
|
print(" = fine settimana, notte europea, mattina asiatica — la finestra in cui il libro")
|
|
print(" e' PIU' SOTTILE della settimana. Un campione preso in orario di punta darebbe")
|
|
print(" costi piu' bassi, cioe' rafforzerebbe la conclusione invece di indebolirla.")
|
|
|
|
# =====================================================================
|
|
sez("1) LA CURVA COSTO-TAGLIA, MISURATA — e la sua banda")
|
|
print(""" Camminata del libro visibile: costo effettivo vs mid di un ordine ESEGUITO
|
|
ISTANTANEAMENTE. E' la lente giusta per QUESTO libro perche' l'esecutore manda ordini
|
|
MARKET (`book_execute`), non limit: cio' che il libro mostra e' cio' che l'ordine paga.
|
|
Ogni cella e' la mediana su (istantanea x lato); la banda e' p10-p90 sulla stessa
|
|
popolazione. §45 ha misurato due istantanee a 4 minuti che differivano del 79%: un solo
|
|
numero qui sarebbe una finzione.""")
|
|
cur = Curva(libri)
|
|
print(f"\n {'ordine':>12} | {'BTC_USDC-PERP mediana [p10-p90]':>40} | "
|
|
f"{'ETH_USDC-PERP mediana [p10-p90]':>40}")
|
|
print(" " + "-" * 98)
|
|
MOSTRA = (5, 50, 500, 5_000, 50_000, 200_000, 500_000, 1_000_000, 3_000_000)
|
|
for sz in MOSTRA:
|
|
cells = []
|
|
for a in ASSETS:
|
|
if cur.oltre(a, sz):
|
|
cells.append(f"{cur.bps(a, sz):10.2f} [DEDOTTO, oltre il libro]")
|
|
else:
|
|
cells.append(f"{cur.bps(a, sz):8.3f} [{cur.bps(a, sz, .10):7.3f},"
|
|
f"{cur.bps(a, sz, .90):8.3f}] bps")
|
|
print(f" ${sz:>11,} | {cells[0]:>40} | {cells[1]:>40}")
|
|
print(" " + "-" * 98)
|
|
print(f" ultima taglia con copertura >=80% del campione (`lim`): "
|
|
f"BTC ${cur.lim['BTC']:,.0f} · ETH ${cur.lim['ETH']:,.0f}")
|
|
print(f" pendenza log-log oltre `lim`: misurata BTC {cur.slope_mis['BTC']:.2f} / "
|
|
f"ETH {cur.slope_mis['ETH']:.2f} -> usata {cur.slope['BTC']:.2f} / "
|
|
f"{cur.slope['ETH']:.2f} (floor 1.0 = impatto almeno lineare) [ASSUNTO]")
|
|
print("""
|
|
⚠️ ERRORE MIO, catturato alla prima corsa. La prima stesura costruiva TRE oggetti-curva
|
|
separati (p10, p50, p90), ognuno con la propria maschera di copertura e la propria
|
|
pendenza di estrapolazione: oltre il libro visibile la p10 finiva SOPRA la p50 (ETH a $3M:
|
|
18,7 contro 15,5 bps). Una banda che si incrocia non e' una banda. Ora le tre quantili
|
|
condividono maschera, `lim` e pendenza, e l'ordinamento e' imposto per costruzione.""")
|
|
hs = {a: cur.bps(a, 5.0) for a in ASSETS}
|
|
print(f"\n [VENUE] Il fondo della curva E' il mezzo spread: BTC {hs['BTC']:.4f} bps, "
|
|
f"ETH {hs['ETH']:.4f} bps — cioe' UN TICK.")
|
|
print(" ⚠️ Questo NON e' il libro SPOT di §52 (mezzo spread misurato 1,5-3,1 bps, e 5,3 in")
|
|
print(" sessione): 100-500x piu' largo. La conclusione di §52 non e' contraddetta, e'")
|
|
print(" CONFERMATA dall'altro lato — il costo che si annullava li' era dello SPOT.")
|
|
|
|
print("\n [VENUE] PROFONDITA' entro una soglia di costo (mediana sulle istantanee, "
|
|
"lato peggiore):")
|
|
print(f" {'soglia':>10}{'BTC $ eseguibili':>22}{'ETH $ eseguibili':>22}")
|
|
PROF = {}
|
|
for soglia in (0.5, 1.0, 5.0, 20.0, 100.0):
|
|
row = [cur.profondita(libri, a, soglia) for a in ASSETS]
|
|
PROF[soglia] = dict(zip(ASSETS, row))
|
|
print(f" {soglia:>8.1f}bps{row[0]:>22,.0f}{row[1]:>22,.0f}")
|
|
|
|
# =====================================================================
|
|
sez("2) LA DISTRIBUZIONE VERA DELLE TAGLIE D'ORDINE — non l'ordine medio")
|
|
print(""" Il costo e' CONVESSO: la media dei costi non e' il costo della media. Percio' il
|
|
costo si applica ordine per ordine, sul nozionale che il libro manda DAVVERO.
|
|
TP01: |Dpos| x (cap x 0,75/2), col pavimento min_order $5 gia' dentro (CC.tp01_realistic).
|
|
SKH01: due ordini a nozionale PIENO (cap x 0,25/2) per trade, 1,0x per costruzione.""")
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
F = FD.sezione0()["F"]
|
|
rate = FD.build_rate(F)
|
|
Z = CurvaZero()
|
|
print(f"\n {'capitale':>13}{'TP01 n.ord':>12}{'mediana':>11}{'p90':>12}{'max':>13}"
|
|
f"{'quota noz.':>12}{'SKH01 ordine':>14}{'x n.':>8}{'quota noz.':>12}")
|
|
ordini_cache = {}
|
|
for C in (635.0, 5_000.0, 50_000.0, 313_000.0, 3_000_000.0):
|
|
_s, dg = tp01_costo(C * W_TP, None, Z)
|
|
o = np.concatenate([dg[a]["ordini"] for a in ASSETS])
|
|
ordini_cache[C] = o
|
|
ns = sum(len(FD.skh_daily(a, 0, "hourly", None)[1]) for a in ASSETS)
|
|
nsk = 2 * ns * C * W_SKH / 2
|
|
print(f" ${C:>12,.0f}{len(o):>12,}{np.median(o):>11,.0f}"
|
|
f"{np.percentile(o, 90):>12,.0f}{o.max():>13,.0f}"
|
|
f"{o.sum()/(o.sum()+nsk):>11.0%}{C * W_SKH / 2:>14,.0f}{2 * ns:>8,}"
|
|
f"{nsk/(o.sum()+nsk):>11.0%}")
|
|
print("\n 📌 Le due gambe hanno distribuzioni OPPOSTE: TP01 manda MOLTI ordini piccoli")
|
|
print(" (deriva del vol-target), SKH01 POCHI ordini a nozionale pieno — ma sul")
|
|
print(" NOZIONALE SCAMBIATO SKH01 pesa il doppio di TP01. A pari capitale il suo")
|
|
print(" ordine e' ~100x la mediana di TP01: e' SKH01 a incontrare per primo il muro di")
|
|
print(" liquidita', e non e' cio' che si indovinerebbe contando gli ordini (§5: contare")
|
|
print(" gli ordini da' 16%, contare il nozionale da' 75% — stessa trappola).")
|
|
|
|
# =====================================================================
|
|
sez("3) REPLICA — prima di ogni delta, riprodurre i numeri pubblicati")
|
|
ok = {}
|
|
tpZ, _ = tp01_costo(635.0 * W_TP, rate, Z)
|
|
ref_tp, _ = FD.tp01_realistic(635.0 * W_TP, rate)
|
|
d = float(np.max(np.abs(tpZ.values - ref_tp.reindex(tpZ.index).values)))
|
|
ok["R1"] = d == 0.0
|
|
print(f" R1 TP01 con costo spento vs `FD.tp01_realistic` max|dif| = {d:.1e} "
|
|
f"{'BIT-EXACT' if ok['R1'] else 'FALLITA'}")
|
|
|
|
skZ = skh_costo(635.0 * W_SKH, rate, Z)
|
|
ref_sk = FD.skh_book(0, "hourly", rate)
|
|
d = float(np.max(np.abs(skZ.values - ref_sk.reindex(skZ.index).values)))
|
|
ok["R2"] = d == 0.0
|
|
print(f" R2 SKH01 con costo spento vs `FD.skh_book` max|dif| = {d:.1e} "
|
|
f"{'BIT-EXACT' if ok['R2'] else 'FALLITA'}")
|
|
|
|
bZ = libro(635.0, rate, Z)
|
|
ref_b = FD.book_75_25(rate, alloc=635.0)
|
|
d = float(np.max(np.abs(bZ.values - ref_b.reindex(bZ.index).values)))
|
|
ok["R3"] = d == 0.0
|
|
print(f" R3 libro con costo spento vs `FD.book_75_25` max|dif| = {d:.1e} "
|
|
f"{'BIT-EXACT' if ok['R3'] else 'FALLITA'}")
|
|
|
|
B = PV.costruisci_serie()
|
|
idx = pd.DatetimeIndex(B.index)
|
|
A_raw, F_raw = B["nofund"].values.astype(float), B["fund"].values.astype(float)
|
|
mv = np.asarray(idx <= pd.Timestamp(VINTAGE_PUB, tz="UTC"))
|
|
Lv0 = PV.Lente("vintage LORDO", PV.deluck(A_raw[mv]), True)
|
|
Lv1 = PV.Lente("vintage +FISCO", PV.deluck(A_raw[mv]), False)
|
|
Lv0.perp, Lv0.muro = PN.muro(Lv0.r, Lv0.prelievo, 0.0, 0.0)
|
|
Lv1.perp, Lv1.muro = PN.muro(Lv1.r, Lv1.prelievo, Lv1.aliq, Lv1.patr)
|
|
ok["R4"] = abs(Lv0.muro - 272_061.0) < 1.0
|
|
ok["R5"] = abs(Lv1.muro - 258_338.0) < 1.0
|
|
print(f" R4 muro L0 LORDO vintage {VINTAGE_PUB} ${Lv0.muro:>11,.0f} atteso $272,061 "
|
|
f"{'ESATTO AL DOLLARO' if ok['R4'] else 'DIVERSO'}")
|
|
print(f" R5 muro L1 +FISCO vintage {VINTAGE_PUB} ${Lv1.muro:>11,.0f} atteso $258,338 "
|
|
f"{'ESATTO AL DOLLARO' if ok['R5'] else 'DIVERSO'}")
|
|
|
|
L3 = PV.Lente("L3 CONGIUNTA", PV.deluck(F_raw), False)
|
|
L3.perp, L3.muro = PV.muro_di(L3)
|
|
ok["R6"] = abs(L3.muro - 313_000.0) < 4_000.0
|
|
print(f" R6 muro L3 CONGIUNTA (oggi) ${L3.muro:>11,.0f} atteso ~$313k "
|
|
f"{'RIPRODOTTO' if ok['R6'] else 'DIVERSO'} {clock()}")
|
|
|
|
rng = np.random.default_rng(PV.SEED_TRAJ)
|
|
pv = CC._boot_paths(L3.r, 400, 5 * 365, PV.BLOCK, rng)
|
|
a1 = PN.accumula(pv, 500.0, L3.muro, L3.aliq, L3.patr, start=635.0)
|
|
a2 = accumula_costo(pv, 500.0, L3.muro, L3.aliq, L3.patr, 635.0, None)
|
|
d = float(np.max(np.abs(a1["cap"] - a2["cap"])))
|
|
ok["R7"] = d == 0.0
|
|
print(f" R7 `accumula_costo` con drag spento vs `PN.accumula` max|dif| = {d:.1e} "
|
|
f"{'BIT-EXACT' if ok['R7'] else 'FALLITA'}")
|
|
|
|
import r0823_skeptic_spot as SS # noqa: E402
|
|
synth = dict(bid=100.0, ask=100.1, bids=[[100.0, 3.0], [99.0, 50.0]],
|
|
asks=[[100.1, 3.0], [101.0, 50.0]])
|
|
same = all(
|
|
(walk_cost_bps(synth, u, sd) is None) == (SS.walk_cost_bps(synth, u, sd) is None)
|
|
and (walk_cost_bps(synth, u, sd) is None
|
|
or abs(walk_cost_bps(synth, u, sd) - SS.walk_cost_bps(synth, u, sd)) < 1e-12)
|
|
for u in (10.0, 300.0, 2_000.0, 1e9) for sd in ("buy", "sell"))
|
|
ok["R8"] = same
|
|
print(f" R8 la mia `walk_cost_bps` == quella di §52 (libro sintetico) "
|
|
f"{'IDENTICHE' if same else 'DIVERGONO'}")
|
|
print(f"\n ESITO: {sum(ok.values())}/{len(ok)} — "
|
|
+ ", ".join(f"{k}={'OK' if v else 'NO'}" for k, v in ok.items()))
|
|
if not all(ok.values()):
|
|
raise SystemExit("REPLICA INCOMPLETA: un numero nuovo sarebbe indistinguibile da un bug.")
|
|
|
|
big = libro(635.0, rate, CurvaFissa(200.0))
|
|
m0, mB = metrics(bZ), metrics(big)
|
|
morde = mB["sharpe"] < m0["sharpe"] - 0.5
|
|
print("\n CONTROLLO POSITIVO del modello di costo — a 200 bps per ordine il libro DEVE")
|
|
print(f" morire: Sharpe {m0['sharpe']:.3f} -> {mB['sharpe']:.3f}, "
|
|
f"CAGR {m0['cagr']:+.2%} -> {mB['cagr']:+.2%}. "
|
|
f"{'MORDE' if morde else '⚠️ NON MORDE: modello sospetto'}")
|
|
if not morde:
|
|
raise SystemExit("il modello di costo non morde: indistinguibile da uno rotto.")
|
|
|
|
# =====================================================================
|
|
sez("4) IL COSTO A $635 — deve riprodurre §5, o la calibrazione e' sbagliata")
|
|
o635 = ordini_cache[635.0]
|
|
c635 = np.array([cur.bps("BTC", x) for x in o635])
|
|
sk635 = 635.0 * W_SKH / 2
|
|
print(f" [CALC] TP01 a $635: ordine mediano ${np.median(o635):,.0f} -> "
|
|
f"{np.median(c635):.4f} bps; ordine massimo ${o635.max():,.0f} -> "
|
|
f"{cur.bps('BTC', o635.max()):.4f} bps.")
|
|
print(f" [CALC] SKH01 a $635: ordine ${sk635:,.0f} -> {cur.bps('BTC', sk635):.4f} bps.")
|
|
b635 = libro(635.0, rate, cur)
|
|
print(f" [CALC] drift del libro: {bZ.mean()*DPY:+.4%} senza costo -> "
|
|
f"{b635.mean()*DPY:+.4%} col costo = {(b635.mean()-bZ.mean())*DPY*1e4:+.2f} bps/anno.")
|
|
if FILLS.exists():
|
|
f = pd.read_csv(FILLS)
|
|
sl = f["slip_B5"].astype(float).abs()
|
|
print(f" [LOG] i 18 fill reali di §5: |slippage| mediana {sl.median():.2f} bps, "
|
|
f"p90 {sl.quantile(0.9):.2f} bps, nozionale mediano ${f['notional'].median():,.0f}.")
|
|
print(f" 📌 Il costo MODELLATO ({np.median(c635):.4f} bps) sta "
|
|
f"{sl.median()/max(np.median(c635),1e-9):,.0f}x SOTTO la dispersione misurata sui")
|
|
print(" fill veri: a questa taglia l'impatto non e' misurabile nemmeno in linea di")
|
|
print(" principio, ed e' esattamente cio' che §5 concluse («estremi avversi 1/18")
|
|
print(" contro 4,7 attesi»). La calibrazione riproduce il fatto, non lo assume.")
|
|
|
|
if all(p.exists() for p in LIN5M.values()):
|
|
print("\n ⚠️ §53 SU QUESTO PUNTO: «un fill al 21,9% del volume della sua barra 5m a $635,")
|
|
print(" con partecipazione lineare 1,25x lo porta al 27,4% SUBITO».")
|
|
print(" Misurato sul feed LINEARE USDC — quello che il libro trada davvero:")
|
|
print(f" {'asset':>6}{'vol 5m med. (>0)':>20}{'libro a 1bps':>15}{'libro a 20bps':>15}"
|
|
f"{'ordine $635':>13}{'quota del libro 1bps':>22}")
|
|
rap, rap20 = [], []
|
|
for a in ASSETS:
|
|
d5 = pd.read_parquet(LIN5M[a])
|
|
v = (d5["volume"] * d5["close"]).values
|
|
vm = float(np.median(v[v > 0]))
|
|
rap.append(PROF[1.0][a] / vm)
|
|
rap20.append(PROF[20.0][a] / vm)
|
|
print(f" {a:>6}{vm:>20,.0f}{PROF[1.0][a]:>15,.0f}{PROF[20.0][a]:>15,.0f}"
|
|
f"{sk635:>13,.0f}{sk635/PROF[1.0][a]:>21.2%}")
|
|
print(" 📌 La partecipazione a una barra 5m NON e' un costo: e' la quota del VOLUME")
|
|
print(" SCAMBIATO in cinque minuti. Il costo lo decide la liquidita' RESTANTE nel")
|
|
print(f" libro: entro 1 bps vale {rap[0]:.1f}x (BTC) / {rap[1]:.1f}x (ETH) quel")
|
|
print(f" volume, entro 20 bps {rap20[0]:.0f}x / {rap20[1]:.0f}x.")
|
|
print(f" L'ordine di OGGI e' lo {sk635/PROF[1.0]['BTC']:.2%} (BTC) / "
|
|
f"{sk635/PROF[1.0]['ETH']:.2%} (ETH) della profondita' a 1 bps;")
|
|
print(f" a 1,25x diventa lo {1.25*sk635/PROF[1.0]['BTC']:.2%} / "
|
|
f"{1.25*sk635/PROF[1.0]['ETH']:.2%}. L'allarme di §53 e' una DOMANDA giusta")
|
|
print(" con una grandezza sbagliata: il 21,9% era la quota di una barra a volume")
|
|
print(" BASSO, e il volume di una barra non e' la liquidita' disponibile.")
|
|
|
|
# =====================================================================
|
|
sez("5) IL LIBRO ALLA TAGLIA — drift, vol, Sharpe e capacita' come funzione del capitale")
|
|
print(" [ASSUNTO] Il libro d'ordini di OGGI proiettato su 2019-2026. Nel 2019 la")
|
|
print(" profondita' di Deribit era una frazione di quella odierna -> ottimista sul passato,")
|
|
print(" onesto sul futuro (ed e' il futuro che il muro deve attraversare).")
|
|
print(f"\n {'capitale':>15}{'drift':>10}{'vol':>9}{'Sharpe':>9}{'costo/anno':>12}"
|
|
f"{'di cui TP01':>13}{'di cui SKH01':>14}{'noz. fuori libro':>18}")
|
|
D, VOLS, SH = {}, {}, {}
|
|
for C in CAPS:
|
|
s = libro(C, rate, cur)
|
|
stp, _ = tp01_costo(C * W_TP, rate, cur)
|
|
ssk = skh_costo(C * W_SKH, rate, cur)
|
|
stp0, _ = tp01_costo(C * W_TP, rate, Z)
|
|
ssk0 = skh_costo(C * W_SKH, rate, Z)
|
|
b0 = libro(C, rate, Z)
|
|
D[C] = float(s.mean() * DPY)
|
|
VOLS[C] = float(s.std() * np.sqrt(DPY))
|
|
SH[C] = metrics(s)["sharpe"]
|
|
ct = float((stp0.mean() - stp.mean()) * DPY)
|
|
cs = float((ssk0.mean() - ssk.mean()) * DPY)
|
|
tot = float((b0.mean() - s.mean()) * DPY)
|
|
fq, _noz = quota_oltre(C, cur, rate)
|
|
print(f" ${C:>14,.0f}{D[C]:>10.2%}{VOLS[C]:>9.2%}{SH[C]:>9.3f}{tot:>12.3%}"
|
|
f"{ct:>13.3%}{cs:>14.3%}{fq:>17.1%}")
|
|
print(f" {clock()}")
|
|
print("\n 📌 La vol e' invariata su tutta la colonna finche' il costo non esplode: il costo")
|
|
print(" d'esecuzione e' una tassa sul DRIFT, non sul rischio — stessa forma del funding")
|
|
print(" (§35). E' cio' che autorizza il modello a un grado di liberta' della sezione 8.")
|
|
print(f" 📌 TROVATO PER STRADA: il drift a $635 ({D[635.0]:.2%}) e' PIU' BASSO che a $5.000")
|
|
print(f" ({D[5000.0]:.2%}) e il costo d'esecuzione non c'entra (0,00x%). E' il PAVIMENTO")
|
|
print(" `min_order` $5: a $635 un ribilanciamento su tre non si esegue, e la posizione")
|
|
print(" insegue il target peggio. Il capitale piccolo ha un suo costo, di segno opposto")
|
|
print(" a quello dello slippage — e nessuna tabella pubblicata lo distingue dall'altro.")
|
|
|
|
# =====================================================================
|
|
sez("6) IL MURO COME PUNTO FISSO, COL COSTO DENTRO")
|
|
print(""" Struttura da `r0726_wall_fixedpoint` (importato): serve capitale C per girare il
|
|
libro che determina il muro C, quindi C_{n+1} = muro(libro(C_n)). Con un costo FUNZIONE di C
|
|
l'iterazione ha un contenuto nuovo: il libro non cambia solo di composizione, cambia di
|
|
RENDIMENTO. Lente L3 CONGIUNTA (funding + fisco d'accumulo), de-luck x0,89.""")
|
|
inc = WF.included_at(L3.muro)
|
|
print("\n [struttura] `WF.included_at` alla taglia del muro direbbe "
|
|
+ ", ".join(f"{k} {v:.0%}" for k, v in inc.items()))
|
|
print(" ⚠️ ma quella e' la composizione DEPLOYABLE del 26/07: GTAA01 e' fuori dallo stesso")
|
|
print(" 26/07 (PRIIPs) e la soglia $20k di XS01 e' caduta (§5-bis). Il libro che GIRA e")
|
|
print(" resta TP01+SKH01, ed e' quello su cui tutti i muri pubblicati sono calcolati:")
|
|
print(" qui cambia il COSTO alla taglia, non la composizione.")
|
|
print(f"\n {'iter':>5}{'ipotesi':>15}{'drift':>9}{'Sharpe':>9}{'perpetua':>10}{'-> muro':>15}")
|
|
Cn = L3.muro
|
|
for i in range(6):
|
|
s = libro(Cn, rate, cur)
|
|
L = PV.Lente("Ln", PV.deluck(s.values.astype(float)), False)
|
|
L.perp, L.muro = PV.muro_di(L)
|
|
print(f" {i:>5}{'$'+format(Cn,',.0f'):>15}{L.drift:>9.2%}"
|
|
f"{metrics(s)['sharpe']:>9.3f}{L.perp:>10.2%}{'$'+format(L.muro,',.0f'):>15}")
|
|
conv = abs(L.muro - Cn) / max(Cn, 1) < 0.02
|
|
Cn = L.muro
|
|
if conv:
|
|
print(f"\n CONVERGE a ${Cn:,.0f} (variazione < 2%) {clock()}")
|
|
break
|
|
else:
|
|
print(f"\n non converge in 6 iterazioni, ultimo valore ${Cn:,.0f}")
|
|
muro_endo = Cn
|
|
Lf = PV.Lente("Lf", PV.deluck(libro(muro_endo, rate, cur).values.astype(float)), False)
|
|
Lf.perp, Lf.muro = PV.muro_di(Lf)
|
|
print(f"\n {'lente':<56}{'perpetua':>11}{'muro':>14}")
|
|
print(f" {'L3 CONGIUNTA, costo COSTANTE (pubblicato §36)':<56}{L3.perp:>11.2%}"
|
|
f"{'$'+format(L3.muro,',.0f'):>14}")
|
|
print(f" {'L3 + costo ENDOGENO alla taglia (punto fisso)':<56}{Lf.perp:>11.2%}"
|
|
f"{'$'+format(Lf.muro,',.0f'):>14}")
|
|
print(f"\n >>> il muro si sposta di {Lf.muro/L3.muro-1:+.1%} rispetto al numero pubblicato")
|
|
print(" ⚠️ La bisezione del muro e' un Monte Carlo a 4.000 percorsi: la sua risoluzione e'")
|
|
print(" ~1% (§36). Uno spostamento di questa taglia va letto come NULLO, non come +0,5%.")
|
|
|
|
# =====================================================================
|
|
sez("7) IL PUNTO DI SATURAZIONE — dove il costo marginale mangia il rendimento marginale")
|
|
print(""" Definizione: il profitto annuo in DOLLARI e' P(C) = C x drift(C). Il capitale
|
|
marginale smette di comprare reddito all'argmax di P (dP/dC = 0). E' il vero «quanto puo'
|
|
crescere questo libro», e il progetto non l'aveva mai calcolato.
|
|
⚠️ La camminata assume l'ordine INTERO eseguito nell'istante contro il libro VISIBILE: e'
|
|
cio' che fa l'esecutore di OGGI (ordini market, nessuno slicing). Il numero e' percio' una
|
|
proprieta' dell'ESECUTORE, non del mercato — affettare l'ordine lo sposterebbe in alto.""")
|
|
print("\n (a) LIMITE DURO DI CAPACITA' — puramente [VENUE]+[BACKTEST], nessuna estrapolazione:")
|
|
print(f" {'capitale':>15}{'quota del nozionale annuo NON eseguibile in un istante':>58}")
|
|
Chard = None
|
|
for C in 10.0 ** np.arange(6.0, 8.01, 0.25):
|
|
fq, _ = quota_oltre(float(C), cur, rate)
|
|
if Chard is None and fq >= 0.01:
|
|
Chard = float(C)
|
|
print(f" ${C:>14,.0f}{fq:>57.1%}")
|
|
print(f" -> prima taglia con >=1% del nozionale fuori dal libro visibile: "
|
|
f"${Chard:,.0f}" if Chard else " -> mai, entro $100M")
|
|
print("\n (b) SATURAZIONE ECONOMICA — argmax di C x drift(C). Le celle oltre `lim` sono")
|
|
print(" [DEDOTTO] (pendenza log-log floor 1.0): la parte destra della curva dipende da")
|
|
print(" quell'assunzione, la SINISTRA no.")
|
|
grid = 10.0 ** np.arange(np.log10(3e5), 8.01, 0.05)
|
|
d0 = float(bZ.mean() * DPY)
|
|
for nome, qq in (("mediana", 0.50), ("p10 (libro migliore)", 0.10), ("p90 (peggiore)", 0.90)):
|
|
P, dd = [], []
|
|
for C in grid:
|
|
s = libro(float(C), rate, cur, q=qq)
|
|
d = float(s.mean() * DPY)
|
|
dd.append(d)
|
|
P.append(C * d)
|
|
P, dd = np.asarray(P), np.asarray(dd)
|
|
j = int(np.argmax(P))
|
|
c10 = grid[int(np.argmax(dd < 0.9 * d0))] if (dd < 0.9 * d0).any() else float("nan")
|
|
c50 = grid[int(np.argmax(dd < 0.5 * d0))] if (dd < 0.5 * d0).any() else float("nan")
|
|
fq, _ = quota_oltre(float(grid[j]), cur, rate)
|
|
print(f"\n curva {nome:<21} C* = ${grid[j]:>13,.0f} P(C*) = "
|
|
f"${P[j]:>11,.0f}/anno drift li' {dd[j]:.2%}")
|
|
print(f" {'':<27} costo = 10% del drift a ${c10:>13,.0f} 50% a ${c50:>13,.0f}")
|
|
print(f" {'':<27} a C* il {fq:.0%} del nozionale e' gia' fuori dal libro visibile")
|
|
print(f"\n {clock()}")
|
|
print(f" 📌 Il muro sta a ${Lf.muro:,.0f}. La saturazione e' PIU' DI UN ORDINE DI GRANDEZZA")
|
|
print(" sopra: il vincolo del piano non e' la capacita' del libro.")
|
|
print(" 📌 E le due definizioni CONCORDANO — argmax economico e primo 1% di nozionale non")
|
|
print(" eseguibile cadono nella stessa decade. Non e' un ottimo liscio: la saturazione")
|
|
print(" E' la fine del libro visibile.")
|
|
o_big = 1.25e6
|
|
print(f"\n [DEDOTTO] E la cura e' nota e costa poco: un ordine da ${o_big:,.0f} costa "
|
|
f"{cur.bps('BTC', o_big):.1f} bps in un")
|
|
print(f" istante, ma spezzato in 10 tranche da ${o_big/10:,.0f} ne costa "
|
|
f"{cur.bps('BTC', o_big/10):.1f} l'una — {cur.bps('BTC', o_big)/max(cur.bps('BTC', o_big/10),1e-9):.0f}x")
|
|
print(" meno, SE il libro si ricostituisce fra una tranche e l'altra ([ASSUNTO]: non e'")
|
|
print(" misurato qui). Cioe' la capacita' di questo libro e' limitata dall'ESECUTORE, e la")
|
|
print(" riparazione e' una riga di codice — che pero' serve solo a ~$10M.")
|
|
|
|
print("\n (c) CHI SATURA PER PRIMO (costo annuo per gamba, in punti di drift di LIBRO):")
|
|
print(f" {'capitale':>15}{'TP01':>10}{'SKH01':>10}{'ordine SKH':>15}"
|
|
f"{'bps su quello':>15}{'':>4}")
|
|
for C in (1e6, 3e6, 1e7, 3e7, 1e8):
|
|
stp, _ = tp01_costo(C * W_TP, rate, cur)
|
|
stp0, _ = tp01_costo(C * W_TP, rate, Z)
|
|
ssk, ssk0 = skh_costo(C * W_SKH, rate, cur), skh_costo(C * W_SKH, rate, Z)
|
|
o = C * W_SKH / 2
|
|
flag = " [DED]" if cur.oltre("ETH", o) else ""
|
|
print(f" ${C:>14,.0f}{float((stp0.mean()-stp.mean())*DPY):>10.2%}"
|
|
f"{float((ssk0.mean()-ssk.mean())*DPY):>10.2%}{o:>15,.0f}"
|
|
f"{cur.bps('BTC', o):>15,.1f}{flag:>6}")
|
|
print(" 📌 SKH01 satura PRIMA di TP01 (ordine 100x piu' grande) ma TP01 lo SUPERA")
|
|
print(" subito dopo, perche' la sua CODA di ordini (max ~0,38 x capitale) e' ancora")
|
|
print(" piu' grande: due meccanismi diversi che mordono a due decadi diverse.")
|
|
|
|
# =====================================================================
|
|
sez("8) LA TRAIETTORIA COL COSTO LUNGO IL CAMMINO")
|
|
print(""" Attesa a priori del coordinatore, dichiarata prima di misurare: «poco, perche' il
|
|
grosso del tempo si passa a capitale piccolo dove il costo e' basso — ma il numero conta,
|
|
perche' se il muro sale mentre il cammino rallenta i due effetti si SOMMANO».
|
|
Modello: i percorsi si estraggono dal libro a $635 (quello di oggi) e ogni giorno si sottrae
|
|
`drift($635) - drift(cap_t)`, interpolato in log(capitale) sulla colonna della sezione 5.""")
|
|
caps_v = np.array(sorted(D))
|
|
dr_v = np.array([D[c] for c in caps_v])
|
|
|
|
def mk_drag(dmap: dict):
|
|
cv = np.array(sorted(dmap))
|
|
dv = np.array([dmap[c] for c in cv])
|
|
|
|
def _f(cap: np.ndarray) -> np.ndarray:
|
|
d = np.interp(np.log10(np.maximum(cap, 1.0)), np.log10(cv), dv)
|
|
return (dmap[635.0] - d) / DPY
|
|
return _f
|
|
|
|
drag = mk_drag(D)
|
|
print("\n ⚠️ Il drag e' `D($635) - D(cap)` e contiene DUE effetti di segno opposto: il")
|
|
print(" pavimento min_order (che penalizza il capitale PICCOLO) e lo slippage (che")
|
|
print(" penalizza il GRANDE). Lungo il cammino cambia segno:")
|
|
print(f" {'capitale':>14}{'drag (in punti di drift annuo)':>36}")
|
|
for c in (2_000.0, 20_000.0, 100_000.0, 315_000.0, 1_000_000.0, 3_000_000.0):
|
|
print(f" ${c:>13,.0f}{drag(np.array([c]))[0]*DPY:>35.3%}")
|
|
print(" 📌 Per quasi tutto il tragitto il costo d'esecuzione e' NEGATIVO: uscire dal")
|
|
print(" pavimento min_order vale piu' di quanto costi lo slippage fino a ~$300k.")
|
|
base = libro(635.0, rate, cur)
|
|
Lb = PV.Lente("Lb", PV.deluck(base.values.astype(float)), False)
|
|
rng = np.random.default_rng(PV.SEED_TRAJ)
|
|
paths = CC._boot_paths(Lb.r, PV.N_TRAJ, PV.ANNI_TRAJ * 365, PV.BLOCK, rng)
|
|
print(f"\n {'EUR/mese':>9}{' costo COSTANTE (bersaglio $'+format(L3.muro,',.0f')+')':>40}"
|
|
f"{' costo ENDOGENO (bersaglio $'+format(Lf.muro,',.0f')+')':>40}")
|
|
print(f" {'':>9}{'mediana':>22}{'P(20a)':>18}{'mediana':>22}{'P(20a)':>18}")
|
|
for dep in (250.0, 500.0, 800.0):
|
|
a = PV.leggi(PN.accumula(paths, dep, L3.muro, Lb.aliq, Lb.patr, start=635.0)["colpito"])
|
|
b = PV.leggi(accumula_costo(paths, dep, Lf.muro, Lb.aliq, Lb.patr, 635.0,
|
|
drag)["colpito"])
|
|
print(f" {dep:>9,.0f}{PV.fmt_cond(a):>22}{a['p20']:>17.1%}"
|
|
f"{PV.fmt_cond(b):>22}{b['p20']:>17.1%}")
|
|
print(f" {clock()}")
|
|
print("\n Scomposizione a EUR500/mese (un grado di liberta' per volta):")
|
|
a0 = PV.leggi(PN.accumula(paths, 500.0, L3.muro, Lb.aliq, Lb.patr, start=635.0)["colpito"])
|
|
a1 = PV.leggi(PN.accumula(paths, 500.0, Lf.muro, Lb.aliq, Lb.patr, start=635.0)["colpito"])
|
|
a2 = PV.leggi(accumula_costo(paths, 500.0, Lf.muro, Lb.aliq, Lb.patr, 635.0,
|
|
drag)["colpito"])
|
|
print(f" (i) base pubblicata {PV.fmt_cond(a0):>7} "
|
|
f"P(20a) {a0['p20']:.1%}")
|
|
print(f" (ii) solo il BERSAGLIO piu' alto {PV.fmt_cond(a1):>7} "
|
|
f"P(20a) {a1['p20']:.1%}")
|
|
print(f" (iii) + il CAMMINO piu' lento {PV.fmt_cond(a2):>7} "
|
|
f"P(20a) {a2['p20']:.1%}")
|
|
# controllo positivo del drag — e un errore MIO, catturato dal controllo stesso
|
|
def drag_finto(cap: np.ndarray) -> np.ndarray:
|
|
return np.where(cap > 100_000.0, 0.03 / DPY, 0.0)
|
|
|
|
ab = PV.leggi(accumula_costo(paths, 500.0, Lf.muro, Lb.aliq, Lb.patr, 635.0,
|
|
drag_finto)["colpito"])
|
|
mosso = abs(ab["p20"] - a2["p20"]) > 0.02
|
|
print("\n CONTROLLO POSITIVO del drag — con un drag finto di 3%/anno sopra $100k la")
|
|
print(f" traiettoria DEVE muoversi: {PV.fmt_cond(ab):>6} P(20a) {ab['p20']:.1%} "
|
|
f"{'SI MUOVE' if mosso else '⚠️ NON SI MUOVE: drag sospetto'}")
|
|
if not mosso:
|
|
raise SystemExit("il drag non morde nemmeno a 3%/anno: macchina sospetta.")
|
|
Dfissa = {C: float(libro(C, rate, CurvaFissa(20.0)).mean() * DPY) for C in CAPS}
|
|
df20 = mk_drag(Dfissa)
|
|
print(f"""
|
|
⚠️ ERRORE MIO, catturato da questo stesso controllo. La prima stesura usava come controllo
|
|
positivo un COSTO COSTANTE di 20 bps per ordine — e non muoveva niente: il drag di questa
|
|
sezione e' una DIFFERENZA, `D($635) - D(cap)`, e un costo che non dipende dalla taglia
|
|
abbassa i due termini della stessa quantita' e sparisce nella sottrazione. Misurato:
|
|
drag a $1M con costo costante 20 bps = {df20(np.array([1e6]))[0]*DPY:+.4%}/anno contro
|
|
{drag(np.array([1e6]))[0]*DPY:+.4%}/anno col costo vero. E' la stessa forma del null
|
|
degenere di §5-bis («il null del de-levering e' degenere quando la variante e' una
|
|
ri-scalatura»): un controllo puo' fallire perche' e' costruito male, non perche' la
|
|
macchina e' rotta — e distinguere le due cose e' il controllo.""")
|
|
|
|
# =====================================================================
|
|
sez("9) IL GRADINO DI LEVA COL COSTO ENDOGENO")
|
|
print(""" §53 lo lascio' aperto proprio qui: a 1,25x il nozionale sale SUBITO, quindi il
|
|
costo si paga SUBITO. La scala moltiplica il TARGET (SPEC-scale-key, §38), quindi moltiplica
|
|
anche la taglia di OGNI ordine: `costo_bps(k x nozionale)`, non `k x costo_bps(nozionale)`.""")
|
|
print("""
|
|
⚠️ CONVENZIONE, e non e' un dettaglio. Il `k*` pubblicato dal progetto (§35: 13,7 senza
|
|
de-luck, 12,25 con) e' calcolato da `FD.k_star` come argmax di `mean(log(1 + k*r))` sulla
|
|
serie di libro. RICOSTRUIRE il libro a scala k da' un numero DIVERSO e piu' alto, perche' i
|
|
due sleeve hanno clamp interni (`np.clip(net,-0.99)` in TP01, `max(capital,1.0)` in SKH01)
|
|
che a leva alta impediscono la rovina che il calcolo pubblicato invece conta. Per restare
|
|
comparabile, qui il costo endogeno entra come UN SOLO grado di liberta' sulla serie
|
|
pubblicata: `g(k) = g_exact(deluck(r_libro) - drag(k)/k, k)`, dove `drag(k)` e' il costo
|
|
d'esecuzione MISURATO alla taglia d'ordine k x nozionale.""")
|
|
KS = (1.0, 1.25, 1.5, 2.0, 3.0, 5.0)
|
|
KGRID = np.arange(0.5, 30.01, 0.25)
|
|
kstar_ctrl = None
|
|
KSTAR = {}
|
|
for cap_test, etichetta in ((635.0, "a $635 (il conto di oggi)"),
|
|
(float(Lf.muro), "alla taglia del muro"),
|
|
(5_000_000.0, "a $5M (dove il costo comincia a mordere)")):
|
|
print(f"\n --- {etichetta} ---")
|
|
r_base = PV.deluck(libro(cap_test, rate, Z).values.astype(float))
|
|
|
|
def gk(k: float) -> tuple[float, float]:
|
|
sk = libro(cap_test, rate, cur, scala=k)
|
|
s0k = libro(cap_test, rate, Z, scala=k)
|
|
dr = float(s0k.mean() - sk.mean()) # drag giornaliero alla scala k
|
|
return FD.g_exact(r_base - dr / k, k), dr * DPY
|
|
|
|
print(f" {'k':>6}{'g(k) senza costo':>19}{'g(k) col costo':>17}{'costo/anno':>13}"
|
|
f"{'ordine SKH':>14}")
|
|
for k in KS:
|
|
g1, ca = gk(k)
|
|
g0 = FD.g_exact(r_base, k)
|
|
print(f" {k:>6.2f}{g0:>18.2%}{g1:>17.2%}{ca:>13.3%}"
|
|
f"{cap_test * W_SKH / 2 * k:>14,.0f}")
|
|
g0v = np.array([FD.g_exact(r_base, float(k)) for k in KGRID])
|
|
g1v = np.array([gk(float(k))[0] for k in KGRID])
|
|
k0 = float(KGRID[int(np.argmax(g0v))])
|
|
k1 = float(KGRID[int(np.argmax(g1v))])
|
|
KSTAR[etichetta] = (k0, k1)
|
|
if kstar_ctrl is None:
|
|
kstar_ctrl = k0
|
|
print(f" k* con costo ZERO {k0:.2f}x -> k* con costo ENDOGENO {k1:.2f}x"
|
|
f" (mezzo-Kelly {k1/2:.2f}x) perdita {k0-k1:.2f}x")
|
|
kref = FD.k_star(PV.deluck(FD.book_75_25(rate).values.astype(float)))[0]
|
|
print(f"\n R9 controllo di replica: k* a costo ZERO su $635 = {kstar_ctrl:.2f}x contro "
|
|
f"{kref:.2f}x")
|
|
print(f" di `FD.k_star` sul libro pubblicato (alloc $600) "
|
|
f"{'RIPRODOTTO' if abs(kstar_ctrl - kref) <= 0.5 else '⚠️ DIVERGE'}")
|
|
print(f" {clock()}")
|
|
print("\n 📌 Il gradino 1,00x -> 1,25x costa, in costo d'esecuzione aggiuntivo, meno di un")
|
|
print(" punto base l'anno al conto di oggi. §53 chiedeva di misurarlo PRIMA: misurato,")
|
|
print(" non morde. Cio' che resta a bloccare il gradino sono le riserve gia' scritte")
|
|
print(" (drift stimato su 7,4 anni, coda assente dal dataset, chiave di scala assente")
|
|
print(" dalla config), NON il costo d'esecuzione.")
|
|
|
|
# =====================================================================
|
|
sez("VERDETTO")
|
|
kz, ke = KSTAR["alla taglia del muro"]
|
|
k5 = KSTAR["a $5M (dove il costo comincia a mordere)"][1]
|
|
print(f"""
|
|
[VENUE] La curva costo-taglia del perpetual USDC e' PIATTA al fondo (mezzo spread
|
|
{hs['BTC']:.4f}/{hs['ETH']:.4f} bps = UN TICK) e resta sotto ~2 bps fino a mezzo milione di
|
|
dollari per ordine — misurata di domenica notte UTC, cioe' nella finestra piu' sottile della
|
|
settimana. Il libro live manda ordini di ${sk635:,.0f} oggi e di ${Lf.muro*W_SKH/2:,.0f} alla
|
|
taglia del muro: entrambi dentro la regione piatta.
|
|
|
|
[CALC] Il muro passa da ${L3.muro:,.0f} (costo costante, §36) a ${Lf.muro:,.0f} col costo
|
|
endogeno = {Lf.muro/L3.muro-1:+.1%}, cioe' DENTRO la risoluzione Monte Carlo del muro stesso.
|
|
La traiettoria EUR500/mese non si muove (P(20a) {a0['p20']:.1%} -> {a2['p20']:.1%}). La
|
|
saturazione economica sta a ~$1x10^7 e il primo 1% di nozionale non eseguibile in un istante
|
|
a ${Chard:,.0f}: due definizioni indipendenti che cadono nella stessa decade, oltre un ordine
|
|
di grandezza sopra il muro.
|
|
|
|
[CALC] Il gradino di leva sopravvive: k* passa da {kz:.2f}x a {ke:.2f}x alla taglia del muro
|
|
({ke-kz:+.2f}x), e a $635 il costo aggiuntivo di 1,25x sta sotto il punto base l'anno. Il
|
|
costo endogeno diventa vincolante sulla leva solo a $5M, dove k* crolla a {k5:.2f}x.
|
|
|
|
Il rischio che i due scettici avevano nominato e' REALE ma vive su un ALTRO STRUMENTO e a
|
|
un'ALTRA TAGLIA: e' lo SPOT (§52, spread 100-500x piu' largo) e sono i milioni, non le
|
|
centinaia di migliaia. Sul perpetual, a questo capitale, il costo d'esecuzione NON e' il
|
|
vincolo.
|
|
|
|
E il fatto piu' utile e' quello trovato per strada: alla taglia di OGGI il costo d'esecuzione
|
|
che le tabelle pubblicate non contengono ha segno NEGATIVO. Non e' lo slippage a essere
|
|
sottostimato: e' il PAVIMENTO `min_order`, che a $635 costa {(D[5000.0]-D[635.0])*1e4:.0f} bps
|
|
di drift l'anno — circa {(D[5000.0]-D[635.0])/max(D[313000.0]-D[1000000.0],1e-9):.0f}x cio'
|
|
che lo slippage costa passando da $313k a $1M, e in direzione opposta.
|
|
""")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|