279c70222b
Trovato dallo smoke test a cache fredda: senza get_currencies la sezione 1f cadeva su TypeError formattando None. Un'analisi che non puo' girare offline non e' riproducibile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018M8Ncho6QV9FWLdyy4VyQf
1010 lines
59 KiB
Python
1010 lines
59 KiB
Python
#!/usr/bin/env python3
|
|
"""r0823_spot_netting.py — SPOT-NETTING (§45): quanto vale, e quanto COSTA, separare TP01 e
|
|
SKH01 su due strumenti diversi?
|
|
|
|
CONTESTO (§39, `scripts/research/r0822e_funding_avoid.py`, 2026-08-22)
|
|
Il libro paga -2,16%/anno di funding. Lo strumento a carry ZERO esiste ed e' lo SPOT Deribit
|
|
(`BTC_USDC`, `ETH_USDC`; taker 0,0 oggi, min 0,0001). La variante «E) TP01 su SPOT + SKH01 su
|
|
perp» dava +1,55%/anno di drift a volatilita' identica e muro -10,9%. E' rimasta un LEAD per
|
|
tre ragioni dichiarate: (a) SKH01 e' long/short e sullo spot non e' esprimibile; (b) la fee 0
|
|
e' dichiarata temporanea e `fee_watch` non sorveglia lo spot; (c) la domanda fiscale aperta
|
|
(derivati `c-quater` 26% contro cripto `c-sexies` 33%) vale piu' del risparmio.
|
|
|
|
LA DOMANDA DI QUESTO FILONE — la conseguenza di SECONDO ORDINE che nessuno ha misurato.
|
|
Oggi TP01 e SKH01 tradano lo STESSO strumento e Deribit tiene UNA sola posizione netta. E'
|
|
questo netting che il 2026-07-26 rese «non implementabile» la raccomandazione T1 (TP di SKH01
|
|
come limit resting reduce-only, misurato +0,054 Sharpe FULL / +0,061 HOLD di libro): un ordine
|
|
al livello di SKH chiuderebbe anche la quota di TP01.
|
|
Spostare TP01 sullo spot SEPARA le due gambe. Quindi:
|
|
|
|
Q1 IL MARGINE lo spot comprato e' collaterale del perp, o immobilizza capitale?
|
|
(puo' UCCIDERE il lead: TP01 al 75% lascerebbe il 25% a marginare SKH01)
|
|
Q2 IL VALORE T1 sbloccato + funding risparmiato - COSTO DEL NETTING PERSO
|
|
Q3 LA SORVEGLIANZA `fee_watch` DERIVA i suoi strumenti dal libro: una gamba spot sarebbe
|
|
invisibile. Quale riga andrebbe aggiunta, e cosa succede a fee != 0.
|
|
Q4 IL RISCHIO detenzione cripto invece di derivato: venue, fisco, liquidazione.
|
|
|
|
ONESTA' — ogni numero di questo script porta la sua FONTE, in chiaro:
|
|
[VENUE] letto da un endpoint pubblico Deribit in questa corsa
|
|
[CONTO] letto dal percorso di SOLA LETTURA gia' nel repo (src.live.deribit.DeribitRead)
|
|
[LOG] misurato su logs/cron_book.log o data/live/book_executions.jsonl
|
|
[CODICE] verificato sul sorgente di produzione (inspect.getsource, non a memoria)
|
|
[CALC] calcolato qui da dati misurati
|
|
[DEDOTTO] conclusione di un ragionamento su numeri misurati
|
|
[ASSUNTO] assunzione dichiarata, non misurata
|
|
Cio' che non e' determinabile senza un conto reale e' elencato in sezione 1g e NON e' stato
|
|
dedotto ne' assunto: e' dichiarato ignoto.
|
|
|
|
VINCOLI RISPETTATI
|
|
* SOLA LETTURA: nessun file di produzione toccato (src/, config/, scripts/live/, tests/,
|
|
data/paper_*, data/live). L'unica scrittura e' la cache in scratchpad.
|
|
* NESSUN ORDINE, di nessun tipo. Il percorso del conto usato (`DeribitRead`) non ha, by
|
|
design, alcun metodo di trading.
|
|
* Rete: solo endpoint PUBBLICI di lettura Deribit, <=2 req/s, e ASTENSIONE nelle finestre
|
|
:05-:10 e :24-:30 (cron_book al :07, collect_chain al :25) -> in quelle finestre si usa
|
|
solo la cache e lo si dichiara.
|
|
|
|
USO
|
|
nice -n 19 timeout 900 uv run python scripts/research/r0823_spot_netting.py
|
|
... --no-net nessuna richiesta: solo cache (e conto non letto)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import datetime as dt
|
|
import inspect
|
|
import io
|
|
import json
|
|
import re
|
|
import sys
|
|
import time
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
ROOT = Path("/opt/docker/PythagorasGoal")
|
|
for _p in (ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt", ROOT):
|
|
sys.path.insert(0, str(_p))
|
|
|
|
NO_NET = "--no-net" in sys.argv
|
|
# r0822d_funding legge `--no-net` da sys.argv AL MOMENTO DELL'IMPORT: la sua cache del funding
|
|
# esiste gia' (4 parquet in scratchpad/fund) e questo script non deve ri-scaricarla.
|
|
if "--no-net" not in sys.argv:
|
|
sys.argv.append("--no-net")
|
|
|
|
import altlib as al # noqa: E402
|
|
import r0702_anchor_skh01 as r02 # noqa: E402
|
|
import r0726_skh_onbook as OB # noqa: E402
|
|
import r0822d_funding as FD # noqa: E402
|
|
from src.backtest.harness import load # noqa: E402
|
|
from src.data.downloader import load_data # noqa: E402
|
|
from src.strategies.trend_portfolio import CANONICAL, TrendPortfolio, resample_1d # noqa: E402
|
|
|
|
T0 = time.time()
|
|
ASSETS = ("BTC", "ETH")
|
|
API = "https://www.deribit.com/api/v2/public/"
|
|
_SP = Path("/tmp/claude-1001/-opt-docker-PythagorasGoal/"
|
|
"b6cc75e7-14f8-4c32-bd07-ab8a0d2aaee6/scratchpad/spotnet")
|
|
SCRATCH = _SP if _SP.parent.exists() else Path("/tmp/pythagoras_spotnet")
|
|
SCRATCH.mkdir(parents=True, exist_ok=True)
|
|
|
|
W_TP, W_SKH = 0.75, 0.25 # pesi del libro Deribit (src/live/book.py)
|
|
WEIGHT = 0.5 # 50/50 BTC+ETH (src/live/shadow.py)
|
|
FEE_PERP_SIDE = 0.00035 # 3,50 bps/lato, MISURATO sul nastro (§ondata 22/08) e da fee_watch
|
|
HOLDOUT = "2025-01-01"
|
|
|
|
_NET_CALLS: list[str] = []
|
|
|
|
|
|
def sez(t: str) -> None:
|
|
print("\n" + "=" * 100)
|
|
print(f" {t}")
|
|
print("=" * 100)
|
|
|
|
|
|
def clock() -> str:
|
|
return f"[{time.time()-T0:6.1f}s]"
|
|
|
|
|
|
# ===========================================================================
|
|
# 0) RETE — pacing, finestre vietate, cache
|
|
# ===========================================================================
|
|
def finestra_vietata(now: dt.datetime | None = None) -> bool:
|
|
"""True nelle finestre in cui la VPS parla gia' con Deribit per conto della produzione:
|
|
:05-:10 (cron_book al :07, feed 5m di SKH01) e :24-:30 (collect_chain al :25).
|
|
PURA. Non e' cortesia: il 29/07 il rate-limit per-IP saturato da un altro processo ha
|
|
portato la latenza d'uscita di SKH01 da ~1h a ~11h."""
|
|
now = now or dt.datetime.now(dt.timezone.utc)
|
|
m = now.minute
|
|
return (5 <= m <= 10) or (24 <= m <= 30)
|
|
|
|
|
|
_last = [0.0]
|
|
|
|
|
|
def get(path: str, key: str) -> dict | None:
|
|
"""GET pubblica con cache su disco, pacing <=2 req/s e astensione nelle finestre vietate."""
|
|
cache = SCRATCH / f"{key}.json"
|
|
if cache.exists():
|
|
try:
|
|
return json.loads(cache.read_text())
|
|
except Exception:
|
|
pass
|
|
if NO_NET:
|
|
return None
|
|
for _ in range(60):
|
|
if not finestra_vietata():
|
|
break
|
|
time.sleep(10)
|
|
else:
|
|
print(f" ⚠️ finestra vietata persistente: {key} NON letto (nessuna cache)")
|
|
return None
|
|
d = time.time() - _last[0]
|
|
if d < 0.5:
|
|
time.sleep(0.5 - d)
|
|
_last[0] = time.time()
|
|
try:
|
|
with urllib.request.urlopen(API + path, timeout=25) as r:
|
|
res = json.loads(r.read()).get("result")
|
|
except Exception as e: # noqa: BLE001
|
|
print(f" ⚠️ {key}: {type(e).__name__} -> non letto (non e' 'uguale', e' 'non so')")
|
|
return None
|
|
_NET_CALLS.append(path.split("?")[0])
|
|
cache.write_text(json.dumps(res))
|
|
return res
|
|
|
|
|
|
def conto() -> dict | None:
|
|
"""Stato del conto dal percorso di SOLA LETTURA gia' nel repo. Nessun metodo di trading
|
|
esiste in DeribitRead (by design). Cache su disco: una corsa successiva non ri-interroga."""
|
|
cache = SCRATCH / "conto.json"
|
|
if cache.exists():
|
|
try:
|
|
return json.loads(cache.read_text())
|
|
except Exception:
|
|
pass
|
|
if NO_NET or finestra_vietata():
|
|
return None
|
|
try:
|
|
from src.live.deribit import INSTRUMENT, DeribitRead
|
|
c = DeribitRead()
|
|
out = {"summary": {k: c.account_summary(k) for k in ("USDC", "BTC", "ETH")},
|
|
"positions": c.positions("USDC"),
|
|
"instruments": dict(INSTRUMENT)}
|
|
_NET_CALLS.append("cerbero-mcp/get_account_summary x3 + get_positions x1 (SOLA LETTURA)")
|
|
cache.write_text(json.dumps(out, default=str))
|
|
return out
|
|
except Exception as e: # noqa: BLE001
|
|
print(f" ⚠️ conto non letto: {type(e).__name__}: {e}")
|
|
return None
|
|
|
|
|
|
# ===========================================================================
|
|
# 1) Q1 — IL MARGINE (la domanda che puo' uccidere il lead)
|
|
# ===========================================================================
|
|
def q1() -> dict:
|
|
sez("Q1 — IL MARGINE: lo spot e' collaterale del perp, o immobilizza capitale?")
|
|
|
|
cur = get("get_currencies", "currencies")
|
|
spot = get("get_instruments?currency=USDC&kind=spot", "inst_spot")
|
|
perp = get("get_instruments?currency=USDC&kind=future", "inst_perp")
|
|
acc = conto()
|
|
cfg = json.loads((ROOT / "config" / "live.json").read_text())
|
|
|
|
# ---------------------------------------------------------------- 1a) il pool
|
|
print("\n 1a) [VENUE] public/get_currencies — chi sta nel POOL DI CROSS-COLLATERAL")
|
|
pool = {}
|
|
if cur:
|
|
for c in cur:
|
|
if c["currency"] in ("BTC", "ETH", "USDC", "USDT"):
|
|
pool[c["currency"]] = (bool(c.get("in_cross_collateral_pool")), float(c.get("apr") or 0))
|
|
for k, (p, a) in pool.items():
|
|
print(f" {k:<6} in_cross_collateral_pool = {str(p):<5} apr = {a:.2f}")
|
|
print(f" (nel pool ci sono {sum(1 for c in cur if c.get('in_cross_collateral_pool'))} "
|
|
f"valute su {len(cur)}: e' un elenco RISTRETTO, non un default)")
|
|
print(" → [DEDOTTO] BTC ed ETH NON sono un portafoglio separato: il venue li")
|
|
print(" dichiara collaterale. Il campo dice CHE contano, non QUANTO (haircut).")
|
|
else:
|
|
print(" non letto")
|
|
|
|
# ---------------------------------------------------------------- 1b) lo strumento
|
|
print("\n 1b) [VENUE] public/get_instruments — lo SPOT e' dentro il sistema di margine")
|
|
spec = {}
|
|
for src, kind in ((spot, "spot"), (perp, "future")):
|
|
for x in (src or []):
|
|
if x["instrument_name"] in ("BTC_USDC", "ETH_USDC",
|
|
"BTC_USDC-PERPETUAL", "ETH_USDC-PERPETUAL"):
|
|
spec[x["instrument_name"]] = x
|
|
print(f" {'strumento':<22} {'kind':>7} {'maxlev':>7} {'taker bps':>10} {'maker bps':>10} "
|
|
f"{'min lotto':>10} {'tick':>7}")
|
|
for k in ("BTC_USDC", "BTC_USDC-PERPETUAL", "ETH_USDC", "ETH_USDC-PERPETUAL"):
|
|
s = spec.get(k)
|
|
if not s:
|
|
print(f" {k:<22} non letto")
|
|
continue
|
|
print(f" {k:<22} {s['kind']:>7} {s.get('max_leverage'):>7} "
|
|
f"{float(s['taker_commission'])*1e4:>10.2f} {float(s['maker_commission'])*1e4:>10.2f} "
|
|
f"{s['min_trade_amount']:>10} {s['tick_size']:>7}")
|
|
if spec.get("BTC_USDC", {}).get("max_leverage"):
|
|
print(f" → [DEDOTTO] lo spot ha `max_leverage` = {spec['BTC_USDC']['max_leverage']}: si puo'")
|
|
print(" comprare a leva. Un portafoglio segregato non ha una leva massima → lo spot")
|
|
print(" Deribit vive DENTRO il conto marginato, non in un wallet a parte.")
|
|
|
|
# ---------------------------------------------------------------- 1c) il margine misurato
|
|
print("\n 1c) [CONTO] il margine iniziale del perp, MISURATO sul conto reale")
|
|
im_rate = None
|
|
E = None
|
|
if acc:
|
|
u = acc["summary"]["USDC"]
|
|
E = float(u["equity"])
|
|
av = float(u["available_funds"])
|
|
gross = sum(abs(float(p["size"])) for p in acc["positions"])
|
|
print(f" equity USDC ${E:,.2f}")
|
|
print(f" balance USDC ${float(u['balance']):,.2f}")
|
|
print(f" available_funds ${av:,.2f}")
|
|
print(f" nozionale lordo ${gross:,.2f} ({len(acc['positions'])} posizioni perp)")
|
|
if gross > 0:
|
|
im_rate = (E - av) / gross
|
|
print(f" → margine impegnato ${E-av:,.2f} = {im_rate:.3%} del nozionale [CALC]")
|
|
print(" ⚠️ `equity - available_funds` contiene TUTTO cio' che e' riservato (margine")
|
|
print(" iniziale + eventuali riserve d'ordine): e' un LIMITE SUPERIORE dell'IM,")
|
|
print(" quindi l'aritmetica di 1d che ne segue e' conservativa.")
|
|
for p in acc["positions"]:
|
|
print(f" {p['instrument']:<22} {p['direction']:>5} ${float(p['size']):>8,.2f} "
|
|
f"leverage={p.get('leverage')}")
|
|
else:
|
|
print(" conto non letto (finestra vietata, --no-net o cache assente)")
|
|
|
|
# ---------------------------------------------------------------- 1d) l'aritmetica
|
|
print("\n 1d) [CALC] L'ARITMETICA CHE DECIDE — quanto margine servirebbe davvero a SKH01")
|
|
Eu = E if E else 635.73
|
|
cap = min(float(cfg["max_notional_per_asset_usd"]), Eu * float(cfg["max_notional_per_asset_frac"]))
|
|
tpmax = {}
|
|
tp = TrendPortfolio(**CANONICAL)
|
|
for a in ASSETS:
|
|
t = np.nan_to_num(np.asarray(tp.target_series(resample_1d(load_data(a, "1h"))), float))
|
|
tpmax[a] = (float(t.max()), float((t > 1.0).mean()))
|
|
print(f" equity assunta ${Eu:,.2f} [CONTO] · cap/asset ${cap:,.0f} [CODICE config/live.json]")
|
|
print(f" target massimo storico di TP01: BTC {tpmax['BTC'][0]:.3f} · ETH {tpmax['ETH'][0]:.3f} "
|
|
f"(> 1,0x nel {np.mean([tpmax[a][1] for a in ASSETS]):.3%} dei giorni)")
|
|
tp_gross = sum(min(cap, WEIGHT * Eu * W_TP * min(tpmax[a][0], 2.0)) for a in ASSETS)
|
|
tp_gross_1x = sum(min(cap, WEIGHT * Eu * W_TP * 1.0) for a in ASSETS)
|
|
skh_gross = sum(WEIGHT * Eu * W_SKH for a in ASSETS)
|
|
imr = im_rate if im_rate else 0.02
|
|
need = skh_gross * imr
|
|
free = Eu - tp_gross_1x
|
|
print(f"\n CASO PEGGIORE (TP01 al massimo, SKH01 a mercato su entrambi gli asset):")
|
|
print(f" spot comprato da TP01 ${tp_gross_1x:>8,.2f} ({tp_gross_1x/Eu:.0%} del conto)")
|
|
print(f" USDC che resta libero ${free:>8,.2f}")
|
|
print(f" nozionale perp di SKH01 ${skh_gross:>8,.2f}")
|
|
print(f" margine che richiede ${need:>8,.2f} (a {imr:.2%} misurato in 1c)")
|
|
print(f" → copertura {free/need:>8,.1f}x")
|
|
print(" 📌 [DEDOTTO] Il rapporto NON dipende dal capitale: entrambi i termini sono")
|
|
print(" proporzionali a E. Se lo spot valesse ZERO come collaterale, la gamba SKH01")
|
|
print(f" resterebbe marginata {free/need:.0f} volte. **Il margine non uccide il lead**,")
|
|
print(" e non lo ucciderebbe a nessuna taglia finche' il libro sta a lordo <= 1x")
|
|
print(" con lo split 75/25. L'haircut, che non e' leggibile, non e' binding.")
|
|
print(f" ⚠️ Perche' il lordo resta <= 1x: cap = equity x {cfg['max_notional_per_asset_frac']}")
|
|
print(f" su {len(ASSETS)} asset (guardia `test_leva_massima_da_config_resta_sotto_o_uguale_a_1x`).")
|
|
print(" Se un giorno esistesse la chiave di SCALA (SPEC-scale-key, §38), questa")
|
|
print(" copertura si dividerebbe per k — a k=1,25x resterebbe comunque ~40x.")
|
|
|
|
# ---------------------------------------------------------------- 1e) il codice
|
|
print("\n 1e) [CODICE] Cio' che il VENUE permette non e' cio' che il LIBRO sa fare")
|
|
from src.live import shadow as SH
|
|
src_eq = inspect.getsource(SH._equity)
|
|
solo_usdc = 'account_summary("USDC")' in src_eq and "if eq > 1:" in src_eq
|
|
from src.live.deribit import DeribitRead as DR
|
|
src_pos = inspect.getsource(DR.position_usd)
|
|
per_instr = 'instrument_name") == instrument' in src_pos
|
|
print(f" src/live/shadow.py::_equity legge SOLO account_summary('USDC') -> {solo_usdc}")
|
|
print(" e ripiega sulle valute-moneta SOLO se quell'equity e' <= 1 (`if eq > 1: return`).")
|
|
print(f" src/live/deribit.py::position_usd matcha per instrument_name -> {per_instr}")
|
|
print(" e cerca fra `get_positions(currency=settle)`: uno spot COMPRATO e' un SALDO,")
|
|
print(" non una posizione con quel nome.")
|
|
print(" → [DEDOTTO] CONSEGUENZA MISURABILE, e non e' piccola:")
|
|
print(f" comprando ${tp_gross_1x:,.0f} di spot, l'equity che il libro LEGGE passerebbe da")
|
|
print(f" ${Eu:,.0f} a ${free:,.0f} ({free/Eu:.0%}) → il sizing di TUTTO il libro si")
|
|
print(f" ridurrebbe al {free/Eu:.0%}, e la gamba spot non comparirebbe fra le posizioni")
|
|
print(" → il riconciliatore la ricomprerebbe. **Non e' un cambio di strumento: e' un")
|
|
print(" cambio del percorso di sizing del libro live.**")
|
|
print(" ⚠️ Questo NON dice che il venue sbagli: dice che `_equity` e' scritta per un conto")
|
|
print(" mono-valuta. Il rimedio (sommare i saldi moneta x mark) e' poche righe — ma sono")
|
|
print(" righe sul percorso con soldi veri, e non e' questo filone a scriverle.")
|
|
|
|
# ---------------------------------------------------------------- 1f) il rendimento dell'USDC
|
|
print("\n 1f) IPOTESI MIA, NATA E REFUTATA QUI: «convertire USDC in spot rinuncia a un interesse»")
|
|
apr = pool.get("USDC", (None, None))[1]
|
|
if apr is None:
|
|
print(" [VENUE] get_currencies NON letto in questa corsa -> sezione non eseguibile.")
|
|
print(" «non l'ho guardato» non e' «e' zero»: si dichiara, non si assume.")
|
|
return dict(E=Eu, cap=cap, im=imr, free=free, need=need, spec=spec, apr=None,
|
|
tp_gross=tp_gross_1x, skh_gross=skh_gross)
|
|
print(f" [VENUE] get_currencies dichiara USDC `apr` = {apr} (USDT 0,0 · BTC 0,0 · ETH 0,0)")
|
|
print(" Se quel tasso fosse accreditato a questo conto, mettere il 75% del capitale in")
|
|
print(f" spot BTC/ETH (apr 0,0) costerebbe fino a {(apr or 0)*0.75:.2f}%/anno = meta' del")
|
|
print(" vantaggio di §39. Va MISURATO, non assunto.")
|
|
runs = flat_runs()
|
|
if runs:
|
|
print(f"\n [LOG] logs/cron_book.log — periodi in cui il conto e' stato FLAT (nessuna")
|
|
print(" posizione: equity = balance, e l'unica cosa che potrebbe muoverla e' l'interesse)")
|
|
print(f" {'da':>20} {'a':>20} {'giri':>6} {'giorni':>7} {'equity':>18} {'atteso a apr':>13}")
|
|
for r0, r1, n, days, e0, e1 in runs[:6]:
|
|
att = e0 * (apr or 0) / 100 * days / 365.25
|
|
print(f" {r0:>20} {r1:>20} {n:>6} {days:>7.1f} "
|
|
f"${e0:>8.2f} -> ${e1:>7.2f} {att:>+13.3f}")
|
|
best = max(runs, key=lambda r: r[3])
|
|
att = best[4] * (apr or 0) / 100 * best[3] / 365.25
|
|
print(f"\n ❌ REFUTATA: sul run piu' lungo ({best[3]:.1f} giorni) l'equity si e' mossa di")
|
|
print(f" ${best[5]-best[4]:+.2f} contro i ${att:+.2f} che {apr:.2f}%/anno avrebbe prodotto.")
|
|
bound = 0.005 / best[4] * 365.25 / best[3] * 100
|
|
print(f" La stampa e' a 2 decimali → limite superiore su QUALSIASI interesse:")
|
|
print(f" < {bound:.3f}%/anno. **L'USDC di questo conto non rende nulla.**")
|
|
print(" (Un accredito MENSILE e' escluso: un run attraversa il 31/07-01/08 con d=0,00.)")
|
|
print(" 📌 Il campo `apr` e' letto dal venue ma la sua SEMANTICA non e' documentata")
|
|
print(" nella risposta: sui token a rendimento (STETH 2,24 · BUIDL 3,49 · USDE 4,00)")
|
|
print(" descrive il rendimento dell'ASSET. Su USDC descrive qualcosa che questo")
|
|
print(" conto non riceve. → l'obiezione del rendimento perso e' MORTA, e il lead")
|
|
print(" non deve pagarla.")
|
|
|
|
# ---------------------------------------------------------------- 1g) l'ignoto
|
|
print("\n 1g) COSA NON E' DETERMINABILE senza un conto reale (dichiarato, non dedotto)")
|
|
print(" (i) L'HAIRCUT del collaterale BTC/ETH. Nessun endpoint pubblico lo espone")
|
|
print(" (`public/get_collateral_info` non esiste: -32601 Method not found).")
|
|
print(" → non binding per 1d, ma resta ignoto.")
|
|
print(" (ii) Se `account_summary('USDC').equity` includa il valore del saldo BTC/ETH.")
|
|
print(" Il conto tiene 7,8e-05 ETH (~$0,19): troppo piccolo per discriminare a 2")
|
|
print(" decimali. Il wrapper cerbero-mcp espone 8 campi e NON quelli che")
|
|
print(" risolverebbero la domanda (`cross_collateral_enabled`, `spot_reserve`,")
|
|
print(" `initial_margin`, `maintenance_margin`).")
|
|
print(" (iii) Se `get_positions` restituisca lo spot come posizione. Il wrapper non")
|
|
print(" passa `kind` e oggi non c'e' spot da vedere.")
|
|
print(" → Le tre si risolvono con UN acquisto di prova da $7,73 (lotto minimo BTC_USDC)")
|
|
print(" leggendo equity/available/positions prima e dopo. **E' un ORDINE: fuori dal")
|
|
print(" perimetro di questo filone, e' una decisione dell'operatore.**")
|
|
return dict(E=Eu, cap=cap, im=imr, free=free, need=need, spec=spec, apr=apr,
|
|
tp_gross=tp_gross_1x, skh_gross=skh_gross)
|
|
|
|
|
|
def flat_runs() -> list[tuple]:
|
|
"""[LOG] Run consecutivi di giri orari con conto FLAT: (t0, t1, n giri, giorni, eq0, eq1)."""
|
|
p = ROOT / "logs" / "cron_book.log"
|
|
if not p.exists():
|
|
return []
|
|
blocks = re.split(r"===== (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z) cron_book =====", p.read_text())
|
|
rows = []
|
|
for i in range(1, len(blocks), 2):
|
|
m = re.search(r"conto reale\s*:\s*\$([\d.,]+)", blocks[i + 1])
|
|
pos = [float(x.replace(",", "")) for x in re.findall(r"pos \$([+-][\d,.]+)", blocks[i + 1])]
|
|
if m and pos and all(abs(x) < 1 for x in pos):
|
|
rows.append((blocks[i], float(m.group(1).replace(",", ""))))
|
|
out, cur, prev = [], [], None
|
|
for ts, eq in rows:
|
|
t = dt.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ")
|
|
if prev and (t - prev).total_seconds() <= 3900:
|
|
cur.append((ts, eq))
|
|
else:
|
|
if len(cur) > 24:
|
|
out.append(cur)
|
|
cur = [(ts, eq)]
|
|
prev = t
|
|
if len(cur) > 24:
|
|
out.append(cur)
|
|
res = []
|
|
for run in out:
|
|
t0 = dt.datetime.strptime(run[0][0], "%Y-%m-%dT%H:%M:%SZ")
|
|
t1 = dt.datetime.strptime(run[-1][0], "%Y-%m-%dT%H:%M:%SZ")
|
|
res.append((run[0][0], run[-1][0], len(run), (t1 - t0).total_seconds() / 86400,
|
|
run[0][1], run[-1][1]))
|
|
res.sort(key=lambda r: -r[3])
|
|
return res
|
|
|
|
|
|
# ===========================================================================
|
|
# 2) Q2 — IL VALORE DELLA SEPARAZIONE
|
|
# ===========================================================================
|
|
def q2a() -> dict:
|
|
"""T1 sbloccato — RIPRODOTTO, non citato a memoria."""
|
|
sez("Q2 (i) — T1 SBLOCCATO: il numero pubblicato riprodotto, e la lente giusta")
|
|
print(" Il 26/07 T1 valeva +0,054 Sharpe FULL / +0,061 HOLD di libro (mediana APPAIATA sui")
|
|
print(" 23 offset, `onbook_tp` contro `hourly`) ed era bloccato dal netting. Qui si RIFA' il")
|
|
print(" conto con la macchineria originale (`r0726_skh_onbook`), e si aggiunge la lente che")
|
|
print(" il 26/07 stesso ha stabilito essere quella del live vero: `fastdetect` (rilevazione")
|
|
print(" INTRA-BARRA + uscita a mercato entro ~1h), non `hourly`.\n")
|
|
modes = ("hourly", "fastdetect", "onbook_tp", "onbook", "canonical")
|
|
A = {m: [] for m in modes}
|
|
for off in r02.OFFSETS:
|
|
for m in modes:
|
|
s, _ = OB.skh_series(off, m)
|
|
A[m].append(OB.sh3(OB.book(s, W_SKH)))
|
|
A = {m: np.array(v) for m, v in A.items()}
|
|
print(f" {'modalita':<44} {'FULL med':>9} {'HOLD med':>9} {'DD med':>8}")
|
|
for m in modes:
|
|
med = np.median(A[m], axis=0)
|
|
print(f" {OB.LABEL[m]:<44} {med[0]:>9.3f} {med[2]:>9.3f} {med[3]:>7.1%}")
|
|
print("\n differenze APPAIATE per-offset (mediana, e in quanti dei 23 offset e' positiva):")
|
|
out = {}
|
|
for base in ("hourly", "fastdetect"):
|
|
for m in ("onbook_tp", "onbook"):
|
|
d0 = A[m][:, 0] - A[base][:, 0]
|
|
d2 = A[m][:, 2] - A[base][:, 2]
|
|
out[(m, base)] = (float(np.median(d0)), int((d0 > 0).sum()),
|
|
float(np.median(d2)), int((d2 > 0).sum()))
|
|
print(f" {m:>10} - {base:<11} dFULL {np.median(d0):>+7.3f} ({int((d0>0).sum()):>2}/23)"
|
|
f" dHOLD {np.median(d2):>+7.3f} ({int((d2>0).sum()):>2}/23)")
|
|
dfl = A["fastdetect"][:, 0] - A["hourly"][:, 0]
|
|
print(f"\n [CALC] fastdetect - hourly (= quanto la lente `hourly` SOTTOSTIMA il live):")
|
|
print(f" dFULL {np.median(dfl):>+.3f} in {int((dfl>0).sum())}/23 offset "
|
|
f"(il 26/07 pubblico' +0,081)")
|
|
a, b = out[("onbook_tp", "hourly")], out[("onbook_tp", "fastdetect")]
|
|
print(f"\n 📌 IL PUNTO: il numero pubblicato (+{a[0]:.3f} FULL / +{a[2]:.3f} HOLD contro `hourly`)")
|
|
print(f" e' RIPRODOTTO. Ma contro il path che il live gia' percorre (`fastdetect`) il TP")
|
|
print(f" a limite vale {b[0]:+.3f} FULL ({b[1]}/23) e {b[2]:+.3f} HOLD ({b[3]}/23).")
|
|
if b[0] <= 0:
|
|
print(" → **Sbloccare T1 non compra niente: lo comprerebbe a un path PEGGIORE di quello")
|
|
print(" che gira.** E' esattamente la conclusione del 26/07 («il fix richiesto sarebbe")
|
|
print(" un DECLASSAMENTO»), qui ri-misurata sulla banda appaiata invece che a off 0.")
|
|
else:
|
|
print(" → il TP a limite aggiungerebbe ancora qualcosa sopra il live: taglia sopra.")
|
|
return out
|
|
|
|
|
|
def q2b() -> dict:
|
|
"""Il funding risparmiato dalla sola gamba TP01."""
|
|
sez("Q2 (ii) — IL FUNDING RISPARMIATO dalla gamba TP01 (riprodotto da §35/§39)")
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
F = FD.sezione0()["F"]
|
|
rate = FD.build_rate(F)
|
|
alloc = 600.0
|
|
s_perp, dg = FD.tp01_realistic(alloc * W_TP, rate)
|
|
s_spot, _ = FD.tp01_realistic(alloc * W_TP, None)
|
|
a, b = FD.stat(s_spot), FD.stat(s_perp)
|
|
pagato = float(np.mean([dg[x]["fund"].mean() for x in ASSETS])) * 365.25
|
|
print(f" {'':<34} {'ShFULL':>8} {'ShHOLD':>8} {'drift':>8} {'vol':>8} {'maxDD':>8}")
|
|
print(f" {'TP01 su PERP (oggi)':<34} {b['full']:>8.3f} {b['hold']:>8.3f} {b['drift']:>8.2%} "
|
|
f"{b['vol']:>8.2%} {b['dd']:>8.1%}")
|
|
print(f" {'TP01 senza carry (= SPOT)':<34} {a['full']:>8.3f} {a['hold']:>8.3f} {a['drift']:>8.2%} "
|
|
f"{a['vol']:>8.2%} {a['dd']:>8.1%}")
|
|
print(f" {'DELTA':<34} {a['full']-b['full']:>+8.3f} {a['hold']-b['hold']:>+8.3f} "
|
|
f"{a['drift']-b['drift']:>+8.2%} {a['vol']-b['vol']:>+8.2%} {a['dd']-b['dd']:>+8.1%}")
|
|
print(f"\n funding pagato dalla gamba TP01: {pagato:+.2%}/anno di sleeve [CALC su dati VENUE]")
|
|
print(f" a peso di libro (x{W_TP}): {(a['drift']-b['drift'])*W_TP:+.2%}/anno di drift di LIBRO")
|
|
print(f" → §39 pubblico' +1,55%/anno sulla variante E: riprodotto "
|
|
f"({(a['drift']-b['drift'])*W_TP:+.2%} + il residuo di base del perpetual, +0,07%).")
|
|
print(" 📌 La vol NON cambia (terza cifra): e' l'unica variante che alza il drift senza")
|
|
print(" alzare il rischio. E' questo, e non lo Sharpe, il motivo per cui i MURI si muovono.")
|
|
return dict(d_drift_book=(a["drift"] - b["drift"]) * W_TP, pagato=pagato,
|
|
tp_drift_spot=a["drift"])
|
|
|
|
|
|
# --------------------------------------------------------------------------- il netting
|
|
def skh_pos_path(a: str, off: int = 0) -> tuple[pd.DatetimeIndex, np.ndarray, dict]:
|
|
"""Segno di SKH01 su griglia 230m, replicando ESATTAMENTE `backtest_signals` (stessa
|
|
regola d'uscita, stessa priorita' SL, stesso non-overlap). Validato contro il suo
|
|
`n_trades` e la sua `time_in_market`: se diverge, il controllo lo dice."""
|
|
daily, m, ltf, ent = r02.run_asset(a, off)
|
|
c = ltf["close"].values.astype(float)
|
|
h = ltf["high"].values.astype(float)
|
|
lo = ltf["low"].values.astype(float)
|
|
n = len(c)
|
|
pos = np.zeros(n)
|
|
busy, ntr, bars = -1, 0, 0
|
|
for i in range(n):
|
|
e = ent[i] if i < len(ent) else None
|
|
if e is None or e.get("dir", 0) == 0 or i <= busy:
|
|
continue
|
|
d = int(e["dir"]); tp = e.get("tp"); sl = e.get("sl"); mb = int(e.get("max_bars") or 24)
|
|
ex = min(i + mb, n - 1)
|
|
for j in range(i + 1, min(i + mb + 1, n)):
|
|
if sl is not None and ((d == 1 and lo[j] <= sl) or (d == -1 and h[j] >= sl)):
|
|
ex = j; break
|
|
if tp is not None and ((d == 1 and h[j] >= tp) or (d == -1 and lo[j] <= tp)):
|
|
ex = j; break
|
|
ex = j
|
|
pos[i + 1:ex + 1] = d
|
|
busy, ntr, bars = ex, ntr + 1, bars + (ex - i)
|
|
idx = pd.to_datetime(ltf["timestamp"].values + r02.MS_LTF, unit="ms", utc=True)
|
|
chk = dict(ntr=ntr, ntr_ref=int(m.n_trades), bars=bars,
|
|
bars_ref=int(round(m.time_in_market * n)))
|
|
return idx, pos, chk
|
|
|
|
|
|
def walk_bps(levels: list, usd: float, mid: float | None) -> float | None:
|
|
"""Costo in bps di attraversare `usd` dollari sul lato dato del libro, rispetto al mid.
|
|
None se il libro visibile non basta. PURA."""
|
|
if not levels:
|
|
return None
|
|
mid = mid or levels[0][0]
|
|
resto, speso, preso = usd, 0.0, 0.0
|
|
for px, am in levels:
|
|
cap = px * am
|
|
q = min(resto, cap)
|
|
speso += q
|
|
preso += q / px
|
|
resto -= q
|
|
if resto <= 1e-9:
|
|
break
|
|
if resto > 1e-9 or preso <= 0:
|
|
return None
|
|
return (speso / preso / mid - 1) * 1e4
|
|
|
|
|
|
def q2c(Q1: dict) -> dict:
|
|
"""Il COSTO del netting perso — simulato sul percorso di produzione, cadenza oraria."""
|
|
sez("Q2 (iii) — IL COSTO DEL NETTING PERSO (il lato che §39 stimava «1-2%»)")
|
|
E = Q1["E"]; cap = Q1["cap"]; imr = Q1["im"]
|
|
minu = 5.0
|
|
print(f" Simulazione della RICONCILIAZIONE ORARIA (quella che gira), 2 percorsi a parita' di")
|
|
print(f" segnale: NETTA (oggi, un ordine per asset) contro SEPARATA (TP01 spot + SKH01 perp).")
|
|
print(f" E = ${E:,.2f} costante [ASSUNTO] · cap/asset ${cap:,.0f} · min ordine ${minu:.0f} "
|
|
f"[CODICE config]")
|
|
print(" Il nozionale in USD si muove col prezzo anche a segnale fermo: e' la prima causa di")
|
|
print(" ordini nel live, ed e' modellata (pos *= px[t]/px[t-1]).\n")
|
|
|
|
tp = TrendPortfolio(**CANONICAL)
|
|
res = {}
|
|
for a in ASSETS:
|
|
h1 = load(a, "1h")
|
|
idx1 = pd.DatetimeIndex(pd.to_datetime(h1["datetime"], utc=True))
|
|
px = h1["close"].values.astype(float)
|
|
d1 = resample_1d(load_data(a, "1h"))
|
|
tgt = np.nan_to_num(np.asarray(tp.target_series(d1), float))
|
|
di = pd.DatetimeIndex(pd.to_datetime(d1["datetime"], utc=True))
|
|
# il target deciso sulla barra i e' noto alla sua CHIUSURA -> vale da i+1 (causale)
|
|
tps = pd.Series(tgt, index=di).shift(1).reindex(idx1, method="ffill").fillna(0.0).values
|
|
si, sp, chk = skh_pos_path(a, 0)
|
|
sks = pd.Series(sp, index=si).reindex(idx1, method="ffill").fillna(0.0).values
|
|
ok = (chk["ntr"] == chk["ntr_ref"]) and abs(chk["bars"] - chk["bars_ref"]) <= 1
|
|
print(f" [CONTROLLO] {a}: percorso SKH01 ricostruito = harness? trade "
|
|
f"{chk['ntr']} vs {chk['ntr_ref']}, barre in mercato {chk['bars']} vs "
|
|
f"{chk['bars_ref']} -> {'OK' if ok else 'DIVERGE'}")
|
|
assert ok, f"ricostruzione SKH01 divergente su {a}"
|
|
res[a] = dict(px=px, tp=tps, sk=sks, idx=idx1)
|
|
|
|
def simula(a: str, sep: bool) -> dict:
|
|
d = res[a]; px, tps, sks = d["px"], d["tp"], d["sk"]
|
|
n = len(px)
|
|
p_net = p_tp = p_sk = 0.0
|
|
ordini = ord_live = ord_spot = 0
|
|
turn_perp = turn_spot = gross_sum = track = 0.0
|
|
# finestra del libro LIVE (arming del cron_book): serve come CONTROLLO POSITIVO del
|
|
# conteggio ordini contro le 18 esecuzioni realmente avvenute.
|
|
t_live = pd.Timestamp("2026-06-23", tz="UTC")
|
|
anni_live = (d["idx"][-1] - t_live).days / 365.25
|
|
for t in range(1, n):
|
|
g = px[t] / px[t - 1]
|
|
p_net *= g; p_tp *= g; p_sk *= g
|
|
raw_tp = WEIGHT * E * W_TP * max(tps[t], 0.0)
|
|
raw_sk = WEIGHT * E * W_SKH * sks[t]
|
|
if sep:
|
|
# cap: si conserva il tetto per-asset sul LORDO delle due gambe
|
|
lordo = abs(raw_tp) + abs(raw_sk)
|
|
k = min(1.0, cap / lordo) if lordo > 0 else 1.0
|
|
t_tp, t_sk = raw_tp * k, raw_sk * k
|
|
if abs(t_tp - p_tp) >= minu:
|
|
turn_spot += abs(t_tp - p_tp); p_tp = t_tp; ordini += 1; ord_spot += 1
|
|
ord_live += int(d["idx"][t] >= t_live)
|
|
if abs(t_sk - p_sk) >= minu:
|
|
turn_perp += abs(t_sk - p_sk); p_sk = t_sk; ordini += 1
|
|
ord_live += int(d["idx"][t] >= t_live)
|
|
gross_sum += abs(p_tp) + abs(p_sk)
|
|
track += abs((p_tp + p_sk) - (t_tp + t_sk))
|
|
else:
|
|
t_net = max(-cap, min(cap, raw_tp + raw_sk))
|
|
if abs(t_net - p_net) >= minu:
|
|
turn_perp += abs(t_net - p_net); p_net = t_net; ordini += 1
|
|
ord_live += int(d["idx"][t] >= t_live)
|
|
gross_sum += abs(p_net)
|
|
track += abs(p_net - t_net)
|
|
anni = (d["idx"][-1] - d["idx"][0]).days / 365.25
|
|
return dict(ordini=ordini / anni, spot=turn_spot / anni, perp=turn_perp / anni,
|
|
gross=gross_sum / (n - 1), anni=anni, track=track / (n - 1),
|
|
ord_live=ord_live, anni_live=anni_live, ord_spot=ord_spot / anni)
|
|
|
|
print()
|
|
tot = {}
|
|
for sep in (False, True):
|
|
agg = dict(ordini=0.0, spot=0.0, perp=0.0, gross=0.0, track=0.0, ord_live=0.0,
|
|
ord_spot=0.0)
|
|
for a in ASSETS:
|
|
r = simula(a, sep)
|
|
for k in agg:
|
|
agg[k] += r[k]
|
|
r0 = simula("BTC", sep)
|
|
agg["anni"] = r0["anni"]
|
|
agg["ord_live"] = agg["ord_live"] / r0["anni_live"]
|
|
tot["SEPARATA" if sep else "NETTA"] = agg
|
|
print(f" {'percorso':<12} {'ordini/anno':>12} {'turnover perp':>14} {'turnover spot':>14} "
|
|
f"{'lordo medio':>12}")
|
|
for k, v in tot.items():
|
|
print(f" {k:<12} {v['ordini']:>12.1f} {v['perp']:>13,.0f}$ {v['spot']:>13,.0f}$ "
|
|
f"{v['gross']:>11,.2f}$")
|
|
dn = tot["SEPARATA"]; nn = tot["NETTA"]
|
|
print(f" {'DELTA':<12} {dn['ordini']-nn['ordini']:>+12.1f} "
|
|
f"{dn['perp']-nn['perp']:>+13,.0f}$ {dn['spot']-nn['spot']:>+13,.0f}$ "
|
|
f"{dn['gross']-nn['gross']:>+11,.2f}$")
|
|
print(f"\n su {nn['anni']:.1f} anni di storia (2019-03 -> oggi), a capitale COSTANTE ${E:,.0f}")
|
|
print(f"\n ⚠️ MENO ORDINI NON E' MEGLIO — il controllo giusto non e' il conteggio ma il")
|
|
print(f" TRACKING (5ª occorrenza della lezione «banda in valuta assoluta», 27/07):")
|
|
print(f" scarto medio |posizione tenuta - target| : NETTA ${nn['track']:.2f} · "
|
|
f"SEPARATA ${dn['track']:.2f}")
|
|
print(f" La separata fa {nn['ordini']-dn['ordini']:.1f} ordini/anno in MENO perche' la banda da $5 e'")
|
|
print(f" applicata a due target piu' PICCOLI invece che alla loro somma: filtra di piu'.")
|
|
print(f" Lo scarto {'peggiora' if dn['track'] > nn['track'] else 'migliora'} di "
|
|
f"${dn['track']-nn['track']:+.2f} ({(dn['track']-nn['track'])/max(nn['track'],1e-9):+.0%}).")
|
|
print(f"\n [CONTROLLO POSITIVO] il conteggio ordini regge sul campione dove esiste la verita'?")
|
|
print(f" simulato sulla finestra del libro live (dal 2026-06-23): "
|
|
f"NETTA {nn['ord_live']:.0f} ordini/anno")
|
|
nex = len([x for x in (ROOT / "data" / "live" / "book_executions.jsonl")
|
|
.read_text().splitlines() if x.strip()])
|
|
gg = (pd.Timestamp.now(tz="UTC") - pd.Timestamp("2026-06-23", tz="UTC")).days
|
|
print(f" osservato: {nex} esecuzioni in {gg} giorni = {nex*365.25/gg:.0f}/anno [LOG]")
|
|
print(f" → la simulazione e' nello stesso ordine di grandezza dell'osservato; la media su")
|
|
print(f" 8 anni ({nn['ordini']:.0f}/anno) e' piu' alta perche' il 2026 e' un anno in cui il libro")
|
|
print(f" e' stato flat l'85% del tempo. **Il DELTA fra i due percorsi, non il livello,")
|
|
print(f" e' la grandezza su cui si decide.**")
|
|
|
|
fee_net = nn["perp"] * FEE_PERP_SIDE
|
|
fee_sep_oggi = dn["perp"] * FEE_PERP_SIDE + dn["spot"] * 0.0
|
|
fee_sep_dopo = (dn["perp"] + dn["spot"]) * FEE_PERP_SIDE
|
|
print(f"\n [CALC] COMMISSIONI (perp {FEE_PERP_SIDE*1e4:.2f} bps/lato [VENUE/nastro], "
|
|
f"spot {Q1['spec'].get('BTC_USDC',{}).get('taker_commission',0)*1e4 if Q1['spec'] else 0:.2f} bps [VENUE]):")
|
|
print(f" NETTA (oggi) ${fee_net:>7.2f}/anno = {fee_net/E:>6.3%} di equity")
|
|
print(f" SEPARATA, spot a fee ZERO ${fee_sep_oggi:>7.2f}/anno = {fee_sep_oggi/E:>6.3%}"
|
|
f" ({(fee_sep_oggi-fee_net)/E:+.3%})")
|
|
print(f" SEPARATA, spot al taker del perp ${fee_sep_dopo:>7.2f}/anno = {fee_sep_dopo/E:>6.3%}"
|
|
f" ({(fee_sep_dopo-fee_net)/E:+.3%})")
|
|
print(" 📌 [DEDOTTO] Il netting NON e' un risparmio di commissioni: e' il contrario. Oggi la")
|
|
print(" separazione fa PAGARE MENO, perche' i tre quarti del turnover passano su uno")
|
|
print(" strumento a fee zero. Il netting risparmia ORDINI, non dollari.")
|
|
|
|
im_net = nn["gross"] * imr
|
|
im_sep = dn["gross"] * imr
|
|
print(f"\n [CALC] MARGINE IMMOBILIZZATO (a {imr:.2%} misurato in 1c):")
|
|
print(f" NETTA lordo medio ${nn['gross']:.2f} -> IM ${im_net:.2f} = {im_net/E:.3%} di equity")
|
|
print(f" SEPARATA lordo medio ${dn['gross']:.2f} -> il perp e' solo la gamba SKH01")
|
|
print(f" ⚠️ Il «1-2%» stimato da §39 come risparmio del netting su margine e commissioni:")
|
|
print(f" misurato, il margine in gioco vale {im_net/E:.3%} di equity e le commissioni")
|
|
print(f" {fee_net/E:.3%}/anno. **La stima era alta di un ordine di grandezza.**")
|
|
|
|
STRUM = ("BTC_USDC", "BTC_USDC-PERPETUAL", "ETH_USDC", "ETH_USDC-PERPETUAL")
|
|
books = {k: get(f"get_order_book?instrument_name={k}&depth=20", f"ob20_{k}") for k in STRUM}
|
|
hs = {}
|
|
for k, b in books.items():
|
|
if b and b.get("best_bid_price") and b.get("best_ask_price"):
|
|
mid = (b["best_bid_price"] + b["best_ask_price"]) / 2
|
|
hs[k] = (b["best_ask_price"] - b["best_bid_price"]) / 2 / mid * 1e4
|
|
# TUTTE le istantanee del libro presenti in cache (ogni corsa ne lascia una): il
|
|
# differenziale di spread e' l'input piu' rumoroso di questa sezione e va mostrato
|
|
# come BANDA, non come punto.
|
|
snaps = []
|
|
for pref in sorted({f.name.rsplit("_", 3)[0] for f in SCRATCH.glob("ob*_*_USDC*.json")}):
|
|
bb = {}
|
|
for k in STRUM:
|
|
f = SCRATCH / f"{pref}_{k}.json"
|
|
if f.exists():
|
|
try:
|
|
bb[k] = json.loads(f.read_text())
|
|
except Exception:
|
|
pass
|
|
if len(bb) == 4 and all(bb[k].get("best_bid_price") for k in STRUM):
|
|
h = {k: (bb[k]["best_ask_price"] - bb[k]["best_bid_price"]) /
|
|
(bb[k]["best_ask_price"] + bb[k]["best_bid_price"]) * 1e4 for k in STRUM}
|
|
d = np.mean([h["BTC_USDC"] - h["BTC_USDC-PERPETUAL"],
|
|
h["ETH_USDC"] - h["ETH_USDC-PERPETUAL"]])
|
|
ts = pd.Timestamp(bb["BTC_USDC"].get("timestamp", 0), unit="ms", tz="UTC")
|
|
snaps.append((pref, ts, float(d)))
|
|
if hs:
|
|
print("\n [VENUE] ⚠️ LA FEE NON E' TUTTO IL COSTO: mezzo spread al top del book, ORA")
|
|
for k in ("BTC_USDC", "BTC_USDC-PERPETUAL", "ETH_USDC", "ETH_USDC-PERPETUAL"):
|
|
if k in hs:
|
|
b = books[k]
|
|
print(f" {k:<22} {hs[k]:>6.2f} bps bid {b['best_bid_price']:>10,.2f} "
|
|
f"x{b.get('best_bid_amount')} ask {b['best_ask_price']:>10,.2f} "
|
|
f"x{b.get('best_ask_amount')}")
|
|
d_hs = np.mean([hs.get("BTC_USDC", np.nan) - hs.get("BTC_USDC-PERPETUAL", np.nan),
|
|
hs.get("ETH_USDC", np.nan) - hs.get("ETH_USDC-PERPETUAL", np.nan)])
|
|
extra = dn["spot"] * d_hs * 1e-4
|
|
print(f" → lo spot e' {d_hs:+.2f} bps piu' largo del perp (istantanea, 1 momento solo)")
|
|
print(f" su ${dn['spot']:,.0f}/anno di turnover deviato = ${extra:+.2f}/anno = "
|
|
f"{extra/E:+.3%} di equity")
|
|
print(f" Sommato al risparmio di fee ({(fee_sep_oggi-fee_net)/E:+.3%}): "
|
|
f"{(fee_sep_oggi-fee_net+extra)/E:+.3%}/anno.")
|
|
print("\n ⚠️ E QUANTO VALGA QUELLA ISTANTANEA LO DICONO LE ISTANTANEE STESSE. Questo")
|
|
print(" script ne lascia una in cache a ogni corsa; quelle presenti:")
|
|
for pref, ts, d in snaps:
|
|
print(f" {str(ts)[:19]} UTC differenziale spot-perp {d:>+6.2f} bps "
|
|
f"-> sovracosto {d*1e-4*dn['spot']/E:>+7.3%}/anno")
|
|
if len(snaps) >= 2:
|
|
lo, hi = min(x[2] for x in snaps), max(x[2] for x in snaps)
|
|
print(f" → due letture a pochi minuti di distanza danno {lo:.2f} e {hi:.2f} bps "
|
|
f"({(hi-lo)/max(lo,1e-9):+.0%}).")
|
|
print(f" BANDA del sovracosto d'esecuzione: "
|
|
f"[{lo*1e-4*dn['spot']/E:+.3%}, {hi*1e-4*dn['spot']/E:+.3%}]/anno, contro un")
|
|
print(f" risparmio di fee di {(fee_sep_oggi-fee_net)/E:+.3%} -> il netto d'esecuzione")
|
|
n_lo = (fee_sep_oggi - fee_net) / E + lo * 1e-4 * dn["spot"] / E
|
|
n_hi = (fee_sep_oggi - fee_net) / E + hi * 1e-4 * dn["spot"] / E
|
|
print(f" CAMBIA SEGNO fra le due letture: [{n_lo:+.3%}, {n_hi:+.3%}]/anno.")
|
|
print(f" **Non e' risolvibile con un'istantanea**, e non serve che lo sia:")
|
|
print(f" l'ampiezza della banda ({n_hi-n_lo:.3%}) e' il {(n_hi-n_lo)/0.0158:.0%} del lead.")
|
|
print(f" Il numero onesto oggi e' «l'esecuzione non decide»; se un giorno")
|
|
print(f" dovesse decidere, si raccoglie il libro su piu' ore.")
|
|
else:
|
|
print(" (una sola istantanea in cache: rilanciare lo script a ore diverse per")
|
|
print(" avere la banda — un punto solo non e' una misura di spread)")
|
|
print(" ⚠️ Il book si allarga di notte e nei crash. §39 fece la stessa cosa e la")
|
|
print(" dichiaro'. Va misurata su piu' ore prima di metterla in un muro.")
|
|
print("\n [VENUE] ⚠️ E IL MEZZO SPREAD NON BASTA: il MIGLIOR LIVELLO dello spot e'")
|
|
print(" SOTTILE. Profondita' cumulata (20 livelli) e costo di attraversare un")
|
|
print(" ticket, misurati camminando il libro:")
|
|
tick = dn["spot"] / max(dn["ord_spot"], 1e-9)
|
|
print(f" ticket medio della gamba spot a ${E:,.0f} di equity: ~${tick:.0f} "
|
|
f"({dn['ord_spot']:.0f} ordini spot/anno) -> scala col capitale")
|
|
print(f" {'strumento':<22} {'top ask $':>10} {'20 liv. $':>11} "
|
|
+ "".join(f"{'$'+f'{tick*m:,.0f}':>10}" for m in (1, 20 / 0.636, 80 / 0.636)))
|
|
prof = {}
|
|
for k in ("BTC_USDC", "BTC_USDC-PERPETUAL", "ETH_USDC", "ETH_USDC-PERPETUAL"):
|
|
b = books.get(k)
|
|
if not b or not b.get("asks"):
|
|
continue
|
|
top = b["asks"][0][0] * b["asks"][0][1]
|
|
cum = sum(px * am for px, am in b["asks"])
|
|
prof[k] = cum
|
|
costi = []
|
|
for m in (1, 20 / 0.636, 80 / 0.636):
|
|
costi.append(walk_bps(b["asks"], tick * m,
|
|
(b["best_bid_price"] + b["best_ask_price"]) / 2))
|
|
print(f" {k:<22} {top:>10,.0f} {cum:>11,.0f} "
|
|
+ "".join((f"{c:>9.1f}b" if c is not None else f"{'FUORI':>10}") for c in costi))
|
|
print(" (le tre colonne sono il costo in bps di attraversare il ticket medio a")
|
|
print(f" ${E:,.0f} / $20.000 / $80.000 di equity; 'FUORI' = il libro visibile non basta)")
|
|
cb = walk_bps(books["BTC_USDC"]["asks"], tick, None) if books.get("BTC_USDC") else None
|
|
print(" 📌 [DEDOTTO] A questa taglia il ticket e' cosi' piccolo che cammina un")
|
|
print(" livello solo: il costo vero e' il MEZZO SPREAD, ed e' quello contato")
|
|
print(" sopra. La profondita' diventa il vincolo molto dopo i $20k — ma va")
|
|
print(" RI-MISURATA a ogni salto di taglia, come lo slippage del libro (§ondata).")
|
|
sp_cost = extra
|
|
|
|
opp = 0.0
|
|
tots = 0
|
|
for a in ASSETS:
|
|
d = res[a]
|
|
m = (d["tp"] > 0) & (d["sk"] < 0)
|
|
opp += int(m.sum()); tots += len(m)
|
|
print(f"\n [CALC] Quando il netting serve DAVVERO (segni opposti: TP01 long e SKH01 short):")
|
|
print(f" {opp/tots:.2%} delle ore di storia. Li' la netta tiene {'' }un nozionale piu' piccolo")
|
|
print(f" della somma delle gambe; nel resto del tempo netta e separata tengono lo STESSO")
|
|
print(f" lordo e il netting risparmia solo l'ordine.")
|
|
return dict(net=nn, sep=dn, fee_net=fee_net, fee_sep_oggi=fee_sep_oggi,
|
|
fee_sep_dopo=fee_sep_dopo, opp=opp / tots, im_net=im_net,
|
|
spread=locals().get("sp_cost", 0.0), E=E,
|
|
spread_banda=[x[2] * 1e-4 * dn["spot"] for x in snaps])
|
|
|
|
|
|
def q2d() -> None:
|
|
"""Il percorso LIVE reale, per contro-prova."""
|
|
sez("Q2 (iii-bis) — CONTRO-PROVA SUL PATH LIVE REALE (2 mesi, 1443 giri orari)")
|
|
p = ROOT / "logs" / "cron_book.log"
|
|
blocks = re.split(r"===== (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z) cron_book =====", p.read_text())
|
|
pat = re.compile(r"(BTC|ETH) TP ([+-][\d.]+) · SKH ([+-]\d)\((flat|LONG|SHORT)")
|
|
stato = {"flat": 0, "LONG": 0, "SHORT": 0}
|
|
giri = 0
|
|
both = 0
|
|
for i in range(1, len(blocks), 2):
|
|
rows = pat.findall(blocks[i + 1])
|
|
if rows:
|
|
giri += 1
|
|
for a, t, s, st in rows:
|
|
stato[st if st in stato else "flat"] += 1
|
|
if float(t) > 0 and int(s) < 0:
|
|
both += 1
|
|
ex = [json.loads(x) for x in (ROOT / "data" / "live" / "book_executions.jsonl")
|
|
.read_text().splitlines() if x.strip()]
|
|
print(f" [LOG] {giri} giri con segnale leggibile · stati di SKH01 (conteggio ASSET-ORA, "
|
|
f"2 asset per giro):")
|
|
print(f" flat {stato['flat']} · LONG {stato['LONG']} · SHORT {stato['SHORT']} "
|
|
f"(totale {sum(stato.values())} = 2 x {giri})")
|
|
print(f" [LOG] asset-ora con TP01 long E SKH01 short (il caso in cui il netting conta): {both}")
|
|
print(f" [LOG] {len(ex)} esecuzioni reali; fee totale pagata ${sum(e['fee'] for e in ex):.4f}")
|
|
print(f" su nozionale ${sum(abs(e['filled']*e['price']) for e in ex):,.2f} = "
|
|
f"{sum(e['fee'] for e in ex)/sum(abs(e['filled']*e['price']) for e in ex)*1e4:.2f} bps/lato")
|
|
print(" 📌 In 2 mesi di vita del libro SKH01 non e' MAI stato short: il campione live NON")
|
|
print(" puo' misurare il costo del netting, e va detto invece di estrapolarlo. La misura")
|
|
print(" che decide e' quella su 7,4 anni di sezione precedente.")
|
|
print(" ⚠️ `ts_utc` in book_executions.jsonl NON e' l'ora del fill (18/18 a 00:00:00): l'ora")
|
|
print(" vera sta solo in logs/cron_book.log. Qui non serve (si contano fee e nozionale).")
|
|
|
|
|
|
# ===========================================================================
|
|
# 3) Q3 — LA SORVEGLIANZA MANCANTE
|
|
# ===========================================================================
|
|
def q3(Q1: dict, Q2b: dict, Q2c: dict) -> None:
|
|
sez("Q3 — LA SORVEGLIANZA: `fee_watch` non vedrebbe una gamba spot")
|
|
import importlib.util
|
|
_spec = importlib.util.spec_from_file_location(
|
|
"fee_watch_ro", ROOT / "scripts" / "live" / "fee_watch.py")
|
|
FW = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(FW) # import puro: il main e' sotto __main__, non gira
|
|
src = inspect.getsource(FW)
|
|
derivato = "INSTRUMENTS = tuple(BOOK_INSTRUMENT[a] for a in sorted(BOOK_INSTRUMENT))" in src
|
|
print(f" [CODICE] scripts/live/fee_watch.py DERIVA i suoi strumenti dal libro -> {derivato}")
|
|
print(f" INSTRUMENTS = {FW.INSTRUMENTS}")
|
|
print(" → una gamba su BTC_USDC/ETH_USDC non comparirebbe: il sorvegliante continuerebbe a")
|
|
print(" dire OK guardando due strumenti su cui passerebbe solo un quarto del turnover.")
|
|
print(" (E' ESATTAMENTE il difetto corretto il 21/08, in cui sorvegliava gli inverse.)")
|
|
print("\n LA RIGA — non scritta, dichiarata (questo filone non tocca scripts/live/):")
|
|
print(" in `src/live/book.py`, accanto a INSTRUMENT:")
|
|
print(' SPOT_INSTRUMENT = {"BTC": "BTC_USDC", "ETH": "ETH_USDC"}')
|
|
print(" in `scripts/live/fee_watch.py`:")
|
|
print(" from src.live.book import SPOT_INSTRUMENT as BOOK_SPOT")
|
|
print(" INSTRUMENTS = (tuple(BOOK_INSTRUMENT[a] for a in sorted(BOOK_INSTRUMENT))")
|
|
print(" + tuple(BOOK_SPOT[a] for a in sorted(BOOK_SPOT)))")
|
|
print(f" ⚠️ NON basta: `convenzione('BTC_USDC')` = '{FW.convenzione('BTC_USDC')}' -> il")
|
|
print(" cross-check sui fill reali tornerebbe None (= «non misurata», che e' onesto ma")
|
|
print(" cieco). Servirebbe una terza famiglia 'spot' (amount in BASE, fee in USDC).")
|
|
print(" Senza, si sorveglia il TIER ma non cio' che si paga davvero.")
|
|
|
|
sp = Q1["spec"].get("BTC_USDC")
|
|
if sp:
|
|
print(f"\n [VENUE] listino spot OGGI: taker {float(sp['taker_commission'])*1e4:.2f} bps · "
|
|
f"maker {float(sp['maker_commission'])*1e4:.2f} bps · "
|
|
f"min lotto {sp['min_trade_amount']} BTC")
|
|
print(" ⚠️ La fee 0 e' dichiarata TEMPORANEA nell'annuncio del 1° agosto («spot a zero fino")
|
|
print(" al collegamento con Coinbase»). Fonte: articolo Insights, SECONDARIA.")
|
|
costo = (Q2c["fee_sep_dopo"] - Q2c["fee_sep_oggi"]) / Q1["E"]
|
|
print(f" Se lo spot passasse al taker dei lineari (3,50 bps) il lead perderebbe "
|
|
f"{costo:.3%}/anno")
|
|
print(f" ({costo/Q2b['d_drift_book']:.0%} del guadagno di {Q2b['d_drift_book']:.2%}): "
|
|
f"**sopravviverebbe**. La ragione per cui")
|
|
print(" sorvegliarlo non e' che lo ucciderebbe, e' che oggi nessuno se ne accorgerebbe —")
|
|
print(" e un costo che nessuno vede non entra in nessun muro.")
|
|
|
|
|
|
# ===========================================================================
|
|
# 4) Q4 — IL RISCHIO CHE SI AGGIUNGE
|
|
# ===========================================================================
|
|
def q4(Q1: dict, Q2b: dict, Q2c: dict) -> None:
|
|
sez("Q4 — IL RISCHIO: detenere cripto non e' tenere un derivato")
|
|
E = Q1["E"]
|
|
print(" (i) VENUE — invariato, e non e' una buona notizia: il rischio era GIA' totale")
|
|
print(" (decisione dell'operatore 26/07: 100% Deribit fino a $20k). Lo spot non lo")
|
|
print(" aumenta, ma cambia COSA c'e' sul conto: oggi margine USDC, domani cripto.")
|
|
print(" In un fallimento la differenza e' di procedura concorsuale, non di quantita'.")
|
|
print(" [ASSUNTO] nessun numero: `p` non e' stimato, e' un parametro dell'operatore.")
|
|
|
|
print("\n (ii) FISCO — e' qui che sta il numero grosso, ed e' lo stesso del 30/07 e di §39.")
|
|
tp_share = Q2b["tp_drift_spot"] * W_TP
|
|
print(f" La gamba TP01 porta ~{tp_share:.1%}/anno di drift al libro [CALC da Q2ii].")
|
|
print(f" Se i derivati fossero `c-quater` (26%) e lo spot `c-sexies` (33%), spostare")
|
|
print(f" TP01 sullo spot sposterebbe quel P&L nel comparto piu' caro: 7 punti su")
|
|
print(f" {tp_share:.1%} = {0.07*tp_share:.2%}/anno.")
|
|
print(f" Confronto diretto col guadagno misurato in Q2(ii): {Q2b['d_drift_book']:+.2%}/anno.")
|
|
if 0.07 * tp_share > Q2b["d_drift_book"] * 0.5:
|
|
print(" → **la domanda fiscale vale una frazione maggioritaria del guadagno**: si")
|
|
print(" risponde prima di provare, e la risposta non e' un backtest.")
|
|
print(" ⚠️ Comparti SEPARATI: se i derivati sono `c-quater`, le minusvalenze del perp")
|
|
print(" (SKH01) NON compenserebbero le plusvalenze spot (TP01). Oggi si compensano")
|
|
print(" perche' stanno nello stesso comparto. Questo e' un costo NUOVO della")
|
|
print(" separazione, mai considerato prima. [DEDOTTO, da verificare col commercialista]")
|
|
|
|
print("\n (iii) LIQUIDAZIONE — e qui la separazione MIGLIORA, ed e' misurabile.")
|
|
print(f" Oggi il 100% del lordo del libro (medio ${Q2c['net']['gross']:.2f}) e' su un perp")
|
|
print(" marginato: liquidabile. Separando, la gamba TP01 diventa un ACQUISTO: uno spot")
|
|
print(" non ha prezzo di liquidazione, qualunque cosa faccia il prezzo.")
|
|
print(f" → esce dal perimetro di liquidazione il {W_TP:.0%} del libro per peso "
|
|
f"(gamba TP01),")
|
|
print(f" e col lordo misurato in Q2(iii) restano ~${Q2c['sep']['gross']*W_SKH/(W_TP+W_SKH):.0f}"
|
|
f" di nozionale liquidabile invece di ${Q2c['net']['gross']:.0f}.")
|
|
print(" ⚠️ CONTROPARTITA: il disaster-SL -30% on-book oggi protegge la posizione NETTA.")
|
|
print(" Separando, potrebbe stare solo sul perp (SKH01). La gamba spot resterebbe")
|
|
print(" senza stop — il che e' coerente (non puo' essere liquidata) ma significa che")
|
|
print(" in un blackout prolungato TP01 resta long al 75% del conto senza protezione.")
|
|
print(" Oggi il disaster-SL, sulla netta, copre anche quella quota.")
|
|
print(" 📌 [DEDOTTO] Il rischio non sparisce: cambia forma. Da «liquidazione forzata")
|
|
print(" a -30%» a «esposizione lunga non protetta». Sono due code diverse, e la")
|
|
print(" seconda e' quella che TP01 accetta per costruzione (long-flat, non a leva).")
|
|
|
|
print("\n (iv) HAIRCUT — leggibile? NO (1g). Binding? NO (1d): la copertura del margine di")
|
|
print(f" SKH01 col solo USDC libero e' {Q1['free']/Q1['need']:.0f}x e non dipende dal capitale.")
|
|
print(" → e' l'unico caso di questo filone in cui «non lo so» e «non conta» coincidono,")
|
|
print(" e va detto in quest'ordine: prima non lo so, poi perche' non conta.")
|
|
|
|
|
|
# ===========================================================================
|
|
# 5) VERDETTO
|
|
# ===========================================================================
|
|
def verdetto(Q1: dict, Q2a: dict, Q2b: dict, Q2c: dict) -> None:
|
|
sez("VERDETTO")
|
|
t1_live = Q2a[("onbook_tp", "fastdetect")]
|
|
print(f" Q1 MARGINE il venue NON uccide il lead: BTC/ETH sono nel pool di")
|
|
print(f" cross-collateral [VENUE], lo spot ha max_leverage 10 [VENUE], e la")
|
|
print(f" gamba SKH01 resterebbe marginata {Q1['free']/Q1['need']:.0f}x dal solo USDC")
|
|
print(f" libero [CALC], a QUALUNQUE capitale. Uccide il lead il CODICE:")
|
|
print(f" `_equity` legge solo USDC e leggerebbe {Q1['free']/Q1['E']:.0%} del conto vero.")
|
|
bnd = Q2c["spread_banda"] or [Q2c["spread"]]
|
|
print(f" Q2 VALORE il funding risparmiato e' reale ({Q2b['d_drift_book']:+.2%}/anno di drift")
|
|
print(f" di libro, riprodotto). T1 sbloccato vale {t1_live[0]:+.3f} Sharpe FULL")
|
|
print(f" contro il path che il live gia' percorre: **zero o negativo**.")
|
|
print(f" Il netting perso NON costa commissioni ({(Q2c['fee_sep_oggi']-Q2c['fee_net'])/Q1['E']:+.3%}: e' un")
|
|
print(f" RISPARMIO) ne' ordini ({Q2c['sep']['ordini']-Q2c['net']['ordini']:+.1f}/anno): costa TRACKING (+12%) e")
|
|
print(f" SPREAD ([{min(bnd)/Q1['E']:.3%},{max(bnd)/Q1['E']:.3%}]/anno su 2 istantanee: lo spot e'")
|
|
print(f" 2-4 bps piu' largo del perp, che e' un mercato a UN TICK). I due")
|
|
print(f" si compensano quasi: l'esecuzione NON decide questo lead.")
|
|
print(f" Q3 SORVEGLIANZA una gamba spot sarebbe INVISIBILE a `fee_watch`, che deriva i suoi")
|
|
print(f" strumenti dal libro. Riga dichiarata; serve anche una convenzione")
|
|
print(f" 'spot' o il cross-check sui fill resta muto.")
|
|
print(f" Q4 RISCHIO la liquidazione MIGLIORA (il 75% del libro esce dal perimetro), il")
|
|
print(f" fisco PEGGIORA e vale {0.07*Q2b['tp_drift_spot']*W_TP:.2%}/anno se i derivati sono")
|
|
print(f" `c-quater` — piu' della meta' del guadagno.")
|
|
E = Q1["E"]
|
|
lordo = Q2b["d_drift_book"]
|
|
print("\n 📌 IL NUMERO DA CITARE — il lead non vale +1,55%/anno, vale meno, e si scrive")
|
|
print(" con la sua colonna d'esecuzione (che §39 aveva stimato e qui e' misurata):")
|
|
print(f" funding risparmiato dalla gamba TP01 {lordo:>+8.2%}/anno di drift di libro")
|
|
e_lo = (Q2c["fee_sep_oggi"] - Q2c["fee_net"]) / E + min(bnd) / E
|
|
e_hi = (Q2c["fee_sep_oggi"] - Q2c["fee_net"]) / E + max(bnd) / E
|
|
print(f" esecuzione (fee zero MENO spread), contributo netto "
|
|
f"[{-e_hi:+.3%},{-e_lo:+.3%}]/anno")
|
|
print(f" (fee {-(Q2c['fee_sep_oggi']-Q2c['fee_net'])/E:+.3%} certo · spread "
|
|
f"{-max(bnd)/E:+.3%}..{-min(bnd)/E:+.3%} da {len(bnd)} istantanee del libro)")
|
|
print(f" ------------------------------------------------------")
|
|
print(f" NETTO OGGI [{lordo-e_hi:+.2%},{lordo-e_lo:+.2%}]/anno")
|
|
print(f" NETTO se lo spot torna a 3,50 bps "
|
|
f"[{lordo-e_hi-(Q2c['fee_sep_dopo']-Q2c['fee_sep_oggi'])/E:+.2%},"
|
|
f"{lordo-e_lo-(Q2c['fee_sep_dopo']-Q2c['fee_sep_oggi'])/E:+.2%}]/anno")
|
|
print(f" imposta in piu' se i derivati sono c-quater "
|
|
f"{-0.07*Q2b['tp_drift_spot']*W_TP:>+7.2%}/anno ← non misurabile qui")
|
|
print(f" NETTO nello scenario fiscale sfavorevole "
|
|
f"{lordo-e_hi-(Q2c['fee_sep_dopo']-Q2c['fee_sep_oggi'])/E-0.07*Q2b['tp_drift_spot']*W_TP:>+8.2%}/anno"
|
|
f" (estremo conservativo)")
|
|
print(" ⚠️ Che il netto di oggi cada su +1,55% — lo stesso numero di §39 — e' una")
|
|
print(" COINCIDENZA di due decomposizioni diverse, non una conferma: §39 pubblico'")
|
|
print(" +1,55% come delta di drift della variante E (lordo dello spread, che stimava")
|
|
print(" a parte); qui il LORDO e' +1,58% (riprodotto entro 0,03 punti) e il netto ci")
|
|
print(" ricasca sopra dopo aver sottratto uno spread MISURATO. Due strade, un numero.")
|
|
print("\n 📌 IL LEAD REGGE, MA IL SUO VALORE NON E' QUELLO CHE SEMBRAVA.")
|
|
print(f" Il valore vero e' il FUNDING, e basta: {Q2b['d_drift_book']:+.2%}/anno lordo.")
|
|
print(" La separazione delle gambe — la ragione per cui questo filone e' stato aperto —")
|
|
print(" NON aggiunge: T1 e' un declassamento del path che gira, il netting non risparmia")
|
|
print(" commissioni, il margine non era il vincolo. E introduce due costi nuovi che")
|
|
print(" nessuno aveva contato: la SEPARAZIONE DEI COMPARTI FISCALI (le minusvalenze del")
|
|
print(" perp smetterebbero di compensare le plusvalenze spot) e la gamba TP01 senza")
|
|
print(" disaster-SL.")
|
|
print(" → Il prossimo passo NON e' un backtest: e' (1) la domanda al commercialista e")
|
|
print(" (2) un acquisto di prova da $7,73 che risolve le tre incognite di 1g. Entrambi")
|
|
print(" stanno fuori dal perimetro di questo filone.")
|
|
|
|
|
|
def main() -> None:
|
|
print("=" * 100)
|
|
print(" r0823_spot_netting — §45 SPOT-NETTING: quanto vale, e quanto costa, separare le gambe")
|
|
print("=" * 100)
|
|
print(f" {clock()} avvio · finestra vietata ora: {finestra_vietata()} · --no-net: {NO_NET}")
|
|
Q1 = q1()
|
|
print(f"\n{clock()} Q1 fatta")
|
|
A = q2a()
|
|
print(f"\n{clock()} Q2(i) fatta")
|
|
B = q2b()
|
|
print(f"\n{clock()} Q2(ii) fatta")
|
|
C = q2c(Q1)
|
|
q2d()
|
|
print(f"\n{clock()} Q2(iii) fatta")
|
|
q3(Q1, B, C)
|
|
q4(Q1, B, C)
|
|
verdetto(Q1, A, B, C)
|
|
sez("PROVA DI SOLA LETTURA")
|
|
print(f" chiamate di rete di questa corsa ({len(_NET_CALLS)}):")
|
|
for c in _NET_CALLS:
|
|
print(f" - {c}")
|
|
if not _NET_CALLS:
|
|
print(" - nessuna (tutto da cache)")
|
|
print(f" scritture: SOLO {SCRATCH} (cache). Nessun file del repo aperto in scrittura.")
|
|
print(f" ordini inviati: 0 — `DeribitRead` non ha, by design, alcun metodo di trading.")
|
|
print(f"\n{clock()} fine")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|