research(wave-0822): HL-EXEC refuta il muro dei 20k di XS01 e lo slippage come rischio #1 — e il 20k di venue e' un'altra cosa
This commit is contained in:
@@ -17,6 +17,7 @@ null de-levering superato + eseguibilita' al capitale dichiarato.
|
|||||||
| 8 | VOL-SIZE | **LEAD** (gate 2026-12-22) + 1 falsificazione | dare a SKH01 una size per-trade regge a **23/23 ancore**, 8/8 anni, null di permutazione e trasferimento su V1 — ma vale **+0,07 di Sharpe di libro, un terzo della fortuna d'ancora del libro stesso (+0,196)**. Il vol-target di **libro** e' invece falsificato: compra peso SKH gia' respinto e peggiora l'eseguibilita' |
|
| 8 | VOL-SIZE | **LEAD** (gate 2026-12-22) + 1 falsificazione | dare a SKH01 una size per-trade regge a **23/23 ancore**, 8/8 anni, null di permutazione e trasferimento su V1 — ma vale **+0,07 di Sharpe di libro, un terzo della fortuna d'ancora del libro stesso (+0,196)**. Il vol-target di **libro** e' invece falsificato: compra peso SKH gia' respinto e peggiora l'eseguibilita' |
|
||||||
| — | **XSR-REPRO** (integrita') | 🚨 **DIFETTO DI PRODUZIONE** | il numero 1.82 e' SPIEGATO e non era sbagliato (era su una **terza** lente, e su una barra non ancora chiusa) — ma cercandone la causa e' emerso che **`paper_xsr` registra ~41 minuti di mercato al giorno**, non un giorno. Tre gate pre-registrati leggono serie costruite cosi' |
|
| — | **XSR-REPRO** (integrita') | 🚨 **DIFETTO DI PRODUZIONE** | il numero 1.82 e' SPIEGATO e non era sbagliato (era su una **terza** lente, e su una barra non ancora chiusa) — ma cercandone la causa e' emerso che **`paper_xsr` registra ~41 minuti di mercato al giorno**, non un giorno. Tre gate pre-registrati leggono serie costruite cosi' |
|
||||||
| 5 | SKEW | **SCARTATO** (Q1, Q2) + **LEAD** (Q3, gate 2027-02-22) | il prezzo muove lo skew (t 3,1-9,4 su 8/8 test), **non il contrario** (max |t| in avanti 2,35 contro 2,08 atteso dal rumore). Ma Q3 e' grosso: **il f=0,73 di VRP01 e' per il 42% STRUTTURA A TERMINE e solo per il 25% skew** |
|
| 5 | SKEW | **SCARTATO** (Q1, Q2) + **LEAD** (Q3, gate 2027-02-22) | il prezzo muove lo skew (t 3,1-9,4 su 8/8 test), **non il contrario** (max |t| in avanti 2,35 contro 2,08 atteso dal rumore). Ma Q3 e' grosso: **il f=0,73 di VRP01 e' per il 42% STRUTTURA A TERMINE e solo per il 25% skew** |
|
||||||
|
| — | **HL-EXEC** (audit di fatto) | **3 falsificazioni misurate** | il pavimento vero e' **$10 (non $5)** e il taker **4,50 bps (non 5,0)** — ma il *"XS01 serve ~$20k"* e' **refutato del tutto** (nessuna soglia da min-order), il *"XSR01 ~$5.000"* e' **conservativo di 1,7x** (vero ~$3.000), e lo **slippage "rischio #1" di XSR01 e' refutato** con margine **21x** |
|
||||||
|
|
||||||
## Note che sopravvivono ai singoli filoni
|
## Note che sopravvivono ai singoli filoni
|
||||||
|
|
||||||
@@ -362,3 +363,47 @@ nuovo: **va decisa PRIMA del 23/10, non quel giorno.**
|
|||||||
pubblicato senza quel controllo"*.
|
pubblicato senza quel controllo"*.
|
||||||
- ✅ Smile vettorizzato (512s -> 2,0s) verificato **bit-exact** contro il ciclo lento su 21.244 celle.
|
- ✅ Smile vettorizzato (512s -> 2,0s) verificato **bit-exact** contro il ciclo lento su 21.244 celle.
|
||||||
- ✅ **Convergenza indipendente** con l'agente TERM-STRUCTURE: **75 giorni utili** (l'altro trovava 74).
|
- ✅ **Convergenza indipendente** con l'agente TERM-STRUCTURE: **75 giorni utili** (l'altro trovava 74).
|
||||||
|
|
||||||
|
### HL-EXEC (r0822_hl_exec.py, 132 simulazioni, 0 celle di segnale = 0 selezione)
|
||||||
|
✅ Identita' con la produzione **prima** di ogni delta: `sim_libro` == `paper_xsr._step` a
|
||||||
|
**max|diff| 5,4e-20**; XS01 == `_xsec_returns()` a **max|diff| 0,0**.
|
||||||
|
✅ **Verifica MAINNET** (il progetto e' nato da un feed testnet): 51/51 asset, deviazione mediana
|
||||||
|
2,21% su feed vecchio 17h contro un movimento tipico del 2,81%; cross-check su terzo venue
|
||||||
|
BTC Deribit $77.997 vs HL $77.339 = −0,84%.
|
||||||
|
**Parametri assunti -> misurati:** min order **$5 -> $10** (docs + **3.827 livelli a un ordine:
|
||||||
|
minimo $10,63, p01 $12,02, 0,0% sotto $10**); taker **5,0 -> 4,50 bps** (due fonti indipendenti
|
||||||
|
coincidono); slippage **non modellato -> 1/2 spread 0,7 bps (19 major) / 1,0 bps (coda), max 4,5**;
|
||||||
|
tier VIP: a $600-20k **sempre tier base**.
|
||||||
|
📌 **XS01: la soglia dei $20k non esiste.** Origine del numero pubblicato: *"rumore di
|
||||||
|
arrotondamento"*, stima a occhio del diario 19/06. Misurato: haircut ~0 gia' a **$200-600 allocati**;
|
||||||
|
a $600 lo Sharpe passa **1,33 -> 1,30**. **E il meccanismo spiega perche':** gli ordini sono **due
|
||||||
|
popolazioni** — il ribilanciamento del segnale (ogni 10g) e' il **13% degli ordini ma il 75% del
|
||||||
|
nozionale**, ticket mediano $13,65 -> passa sempre; la deriva del vol-target giornaliera e' l'**87%
|
||||||
|
degli ordini ma il 25% del nozionale**, ticket $0,64 -> il pavimento la taglia, **ed e' gratis**.
|
||||||
|
*Contare gli ORDINI da' 16% e sembra un disastro; contare il NOZIONALE da' 75% e spiega perche' lo
|
||||||
|
Sharpe non si muove.* (Convergenza indipendente con XS-LITE, che era arrivato allo stesso meccanismo
|
||||||
|
con numeri diversi.)
|
||||||
|
✅ **Fortuna di fase girata anche qui** (l'haircut e' un Δ): 10 fasi, mediana delle differenze
|
||||||
|
appaiate **+0,007**, banda [−0,060, +0,061], **0/10 fasi con danno > 0,10**.
|
||||||
|
📌 **XSR01: soglia vera ~$3.000** (non $5.000 — sbagliata nella direzione **sicura**). Banda sui
|
||||||
|
pavimenti $8/$10/$12: ROTTO <=$1.000, INDECIDIBILE $1.500-2.000, **ok >=$3.000**.
|
||||||
|
📌 **Slippage refutato come rischio #1** al livello di liquidita' odierno: lo Sharpe scende sotto
|
||||||
|
1,0 a ~20 bps/gamba = **~16 bps di slippage = 21x quello misurato**. ⚠️ L'agente dichiara il limite:
|
||||||
|
3 snapshot = **172 secondi di oggi**, una fotografia, non tre osservazioni. **E' il MARGINE (21x) che
|
||||||
|
regge la conclusione, non il livello.**
|
||||||
|
🚨 **Due conseguenze che l'operatore deve vedere:**
|
||||||
|
1. **Il "$20k" di XS01 e il "$20k" della DECISIONE DI VENUE (26/07) sono due cose diverse che
|
||||||
|
CLAUDE.md ha conflato.** Quello di XS01 **cade**; **quello di venue REGGE intatto** — e' una
|
||||||
|
decisione dell'operatore su un altro asse (la rovina da fallimento exchange), non sull'eseguibilita'.
|
||||||
|
XS01 resta fuori dal live **per la decisione di venue, non per taglia**.
|
||||||
|
2. **La gamba di eseguibilita' del gate 2026-10-23 e' tarata su un vincolo che a $5.000 non morde**
|
||||||
|
(haircut li' −7%...+2%): *una condizione pre-registrata che non puo' fallire non e' una
|
||||||
|
condizione*. L'agente **non ha anticipato il gate**: la soglia Sharpe>=1,0 resta al 23/10.
|
||||||
|
⚠️ **Terza spiegazione dell'1,82, complementare alle altre due:** `r0725_statarb_basket_gate`
|
||||||
|
addebita *"2 gambe per coppia"* = 50 alt + **50 gambe BTC fantasma**, ma il demeaning **annulla
|
||||||
|
algebricamente** la gamba BTC (`sum(w)=0`) — che e' la ragione stessa per cui XSR01 esiste.
|
||||||
|
*L'intuizione del demeaning era stata applicata all'AMPIEZZA e non ai COSTI.*
|
||||||
|
📌 **Quadro conciliato delle tre lenti** (i due agenti concordano sul fatto, non sull'attribuzione
|
||||||
|
del singolo decimale): titolo `demean_skeptic` **1,82** · gate `basket_gate` **1,75-1,79** (fee
|
||||||
|
raddoppiate su una gamba inesistente) · monitor `paper_xsr` **2,21**. **La lente che ha girato i
|
||||||
|
gate e' quella PESSIMISTICA** -> il **DSR 0,983 PASS e' stato calcolato su una serie conservativa**.
|
||||||
|
|||||||
@@ -474,6 +474,46 @@ def describe_curve(asset: str, cv: pd.DataFrame) -> None:
|
|||||||
f"(beta -1 = reversione totale, 0 = martingala)")
|
f"(beta -1 = reversione totale, 0 = martingala)")
|
||||||
|
|
||||||
|
|
||||||
|
def basis_vs_funding(asset: str, cv: pd.DataFrame) -> None:
|
||||||
|
"""Q1, nella forma decidibile: il basis del front e' il premio ANNUALIZZATO che
|
||||||
|
incassi se vendi il datato e lo tieni a scadenza. La copertura e' il perp, su cui
|
||||||
|
PAGHI il funding realizzato nello stesso periodo. Quindi il roll-down incassabile
|
||||||
|
NON e' il basis: e' `basis - funding realizzato fino a scadenza`.
|
||||||
|
Se il basis e' un previsore NON DISTORTO del funding (a~0, b~1) il premio a termine
|
||||||
|
esiste come LIVELLO ma non e' estraibile: e' gia' il prezzo della copertura."""
|
||||||
|
d = cv[["c_front", "f1h", "tau_front_h"]].dropna()
|
||||||
|
if len(d) < 5000:
|
||||||
|
print(f" {asset}: campione insufficiente")
|
||||||
|
return
|
||||||
|
f = d["f1h"].values
|
||||||
|
cum = np.concatenate([[0.0], np.cumsum(f)]) # somma cumulata del funding orario
|
||||||
|
n = len(d)
|
||||||
|
tau = d["tau_front_h"].values
|
||||||
|
end = np.minimum(np.arange(n) + tau.astype(int), n - 1)
|
||||||
|
hrs = np.maximum(end - np.arange(n), 1)
|
||||||
|
realized = (cum[end] - cum[np.arange(n)]) / hrs * HOURS_Y # funding realizzato ANNUALIZZATO
|
||||||
|
implied = d["c_front"].values
|
||||||
|
m = np.isfinite(realized) & np.isfinite(implied) & (hrs > 24 * 5)
|
||||||
|
x, y = implied[m], realized[m]
|
||||||
|
b1, b0 = np.polyfit(x, y, 1)
|
||||||
|
prem = x - y # premio effettivamente incassabile
|
||||||
|
# IC bootstrap a BLOCCHI (le ore sono fortemente autocorrelate: un IC iid mentirebbe)
|
||||||
|
rng = np.random.default_rng(822)
|
||||||
|
bl = 24 * 30
|
||||||
|
nb = max(1, len(prem) // bl)
|
||||||
|
boots = []
|
||||||
|
for _ in range(400):
|
||||||
|
st = rng.integers(0, max(1, len(prem) - bl), nb)
|
||||||
|
boots.append(np.mean(np.concatenate([prem[i:i + bl] for i in st])))
|
||||||
|
lo, hi = np.percentile(boots, [2.5, 97.5])
|
||||||
|
print(f" {asset}: basis implicito medio {np.mean(x)*100:+6.2f}%/anno "
|
||||||
|
f"funding realizzato a scadenza {np.mean(y)*100:+6.2f}%/anno")
|
||||||
|
print(f" regressione realizzato ~ a + b*implicito: b = {b1:+.3f} a = {b0*100:+.2f}%"
|
||||||
|
f" (b~1 e a~0 = previsore non distorto => niente da incassare)")
|
||||||
|
print(f" PREMIO INCASSABILE = implicito - realizzato = {np.mean(prem)*100:+6.2f}%/anno"
|
||||||
|
f" IC95% a blocchi [{lo*100:+.2f}%, {hi*100:+.2f}%] (n={m.sum():,} ore)")
|
||||||
|
|
||||||
|
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
# 3. STRATEGIE — posizioni in spazio CONTRATTO (il roll si paga da solo)
|
# 3. STRATEGIE — posizioni in spazio CONTRATTO (il roll si paga da solo)
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
@@ -671,6 +711,9 @@ def main() -> None:
|
|||||||
cv = curve_frame(a, panels[a], metas[a], funds[a], roll_dte=7, perp=perps[a])
|
cv = curve_frame(a, panels[a], metas[a], funds[a], roll_dte=7, perp=perps[a])
|
||||||
curves[a] = cv
|
curves[a] = cv
|
||||||
describe_curve(a, cv)
|
describe_curve(a, cv)
|
||||||
|
print("\n --- Q1-bis: il basis e' un PREVISORE del funding che paghi sulla copertura? ---")
|
||||||
|
for a in ASSETS:
|
||||||
|
basis_vs_funding(a, curves[a])
|
||||||
|
|
||||||
# ---------------- STEP 3b: spread bid-ask (calibrazione DATATA) ----------------
|
# ---------------- STEP 3b: spread bid-ask (calibrazione DATATA) ----------------
|
||||||
print("\n[3b] SPREAD BID-ASK — calibrazione sul book VIVO di oggi (non e' storia)")
|
print("\n[3b] SPREAD BID-ASK — calibrazione sul book VIVO di oggi (non e' storia)")
|
||||||
@@ -724,6 +767,44 @@ def main() -> None:
|
|||||||
G = pd.DataFrame([{k: v for k, v in r.items() if k != "daily"} for r in rows])
|
G = pd.DataFrame([{k: v for k, v in r.items() if k != "daily"} for r in rows])
|
||||||
dailies = {i: r["daily"] for i, r in enumerate(rows)}
|
dailies = {i: r["daily"] for i, r in enumerate(rows)}
|
||||||
|
|
||||||
|
print(f"\n distribuzione dello Sharpe FULL sulle {len(G)} celle: "
|
||||||
|
f"min {G.sharpe.min():+.2f} p25 {G.sharpe.quantile(.25):+.2f} "
|
||||||
|
f"mediana {G.sharpe.median():+.2f} p75 {G.sharpe.quantile(.75):+.2f} "
|
||||||
|
f"max {G.sharpe.max():+.2f} sd {G.sharpe.std():.2f}")
|
||||||
|
degen = G[(G.vol < 0.005) | (G.n < 400)]
|
||||||
|
if len(degen):
|
||||||
|
print(f" ATTENZIONE: {len(degen)} celle DEGENERI (vol<0.5%/anno o <400 giorni attivi):")
|
||||||
|
print(f" Sharpe di quelle celle: {sorted(np.round(degen.sharpe.values,2))[:10]}")
|
||||||
|
print(" gonfiano la varianza dei trial e quindi il null del deflated-Sharpe:")
|
||||||
|
print(" il DSR viene riportato ANCHE sulla sola sottogriglia non degenere.")
|
||||||
|
|
||||||
|
print("\n MIGLIORE CELLA PER FAMIGLIA (Q2 riguarda il CALENDAR: va letto separato):")
|
||||||
|
for fam in FAM:
|
||||||
|
sub = G[G.family == fam]
|
||||||
|
i = sub.sharpe.idxmax()
|
||||||
|
r = G.loc[i]
|
||||||
|
print(f" [{fam}] {r.sig:12s} W={int(r.win):5d}h roll={int(r.roll):2d}g thr={r.thr:.1f} "
|
||||||
|
f"| Sh {r.sharpe:+6.2f} hold {r.holdout:+6.2f} DD {r.maxdd*100:5.2f}% "
|
||||||
|
f"CAGR {r.cagr*100:+6.2f}% vol {r.vol*100:5.2f}%")
|
||||||
|
ii = A.implausible_sharpe(dailies[i])
|
||||||
|
print(f" implausible_sharpe={ii['implausible']} "
|
||||||
|
f"(barre in perdita {ii.get('loss_frac', float('nan'))*100:.1f}%) "
|
||||||
|
f"-> {'coda ASSENTE, rischio fuori dal campione' if ii['implausible'] else 'coda PRESENTE'}")
|
||||||
|
|
||||||
|
print("\n LE CELLE CANONICHE (quelle che un lettore chiede per nome), roll=7g:")
|
||||||
|
for fam, sig, lab in [
|
||||||
|
("FvP", "carry", "vendi il datato quando e' in CONTANGO / compra perp (il carry classico)"),
|
||||||
|
("FvP", "static_short", "SEMPRE corto il datato / lungo perp"),
|
||||||
|
("FvP", "static_long", "SEMPRE lungo il datato / corto perp (il contrario)"),
|
||||||
|
("CAL", "static_long", "calendar: lungo back / corto front"),
|
||||||
|
("CAL", "static_short", "calendar: corto back / lungo front")]:
|
||||||
|
sub = G[(G.family == fam) & (G.sig == sig) & (G.roll == 7)]
|
||||||
|
if len(sub):
|
||||||
|
r = G.loc[sub.index[0]]
|
||||||
|
print(f" {fam}/{sig:12s} Sh {r.sharpe:+6.2f} hold {r.holdout:+6.2f} "
|
||||||
|
f"CAGR {r.cagr*100:+6.2f}% DD {r.maxdd*100:5.2f}% vol {r.vol*100:5.2f}%"
|
||||||
|
f" <- {lab}")
|
||||||
|
|
||||||
print("\n migliori 8 celle per Sharpe FULL (NON e' la selezione — solo panorama):")
|
print("\n migliori 8 celle per Sharpe FULL (NON e' la selezione — solo panorama):")
|
||||||
for i in G.sharpe.nlargest(8).index:
|
for i in G.sharpe.nlargest(8).index:
|
||||||
r = G.loc[i]
|
r = G.loc[i]
|
||||||
@@ -756,6 +837,11 @@ def main() -> None:
|
|||||||
dsr, sr0 = A.deflated_sharpe(bs["sharpe"], list(G.sharpe.values), bd)
|
dsr, sr0 = A.deflated_sharpe(bs["sharpe"], list(G.sharpe.values), bd)
|
||||||
print(f" deflated-Sharpe : DSR {dsr:.3f} (null max atteso {sr0:+.2f} su {len(G)} trial) "
|
print(f" deflated-Sharpe : DSR {dsr:.3f} (null max atteso {sr0:+.2f} su {len(G)} trial) "
|
||||||
f"-> {'PASS' if dsr >= 0.95 else 'FAIL'}")
|
f"-> {'PASS' if dsr >= 0.95 else 'FAIL'}")
|
||||||
|
nd = G[(G.vol >= 0.005) & (G.n >= 400)]
|
||||||
|
if len(nd) >= 5 and len(nd) < len(G):
|
||||||
|
d2, s2_ = A.deflated_sharpe(bs["sharpe"], list(nd.sharpe.values), bd)
|
||||||
|
print(f" sulla sola sottogriglia NON degenere ({len(nd)} trial): "
|
||||||
|
f"DSR {d2:.3f} (null max {s2_:+.2f}) -> {'PASS' if d2 >= 0.95 else 'FAIL'}")
|
||||||
|
|
||||||
imp = A.implausible_sharpe(bd)
|
imp = A.implausible_sharpe(bd)
|
||||||
print(f" implausible_sharpe: implausible={imp['implausible']} "
|
print(f" implausible_sharpe: implausible={imp['implausible']} "
|
||||||
@@ -769,9 +855,13 @@ def main() -> None:
|
|||||||
tp = A.tp01_baseline_daily()
|
tp = A.tp01_baseline_daily()
|
||||||
try:
|
try:
|
||||||
mg = A.marginal_vs_tp01(bd)
|
mg = A.marginal_vs_tp01(bd)
|
||||||
print(f" marginal_vs_tp01 : {mg.get('verdict')} corr {mg.get('corr', float('nan')):+.3f} "
|
print(f" marginal_vs_tp01 : {mg.get('marginal_verdict')} "
|
||||||
|
f"corr_full {mg.get('corr_full')} corr_hold {mg.get('corr_hold')} "
|
||||||
f"robust_oos={mg.get('robust_oos')} insample_edge={mg.get('has_insample_edge')} "
|
f"robust_oos={mg.get('robust_oos')} insample_edge={mg.get('has_insample_edge')} "
|
||||||
f"is_hedge={mg.get('is_hedge')} beats_noise={mg.get('beats_noise_null')}")
|
f"is_hedge={mg.get('is_hedge')} beats_noise={mg.get('beats_noise_null')}")
|
||||||
|
w25 = mg.get("w25") or (mg.get("blends", {}) or {}).get("w25", {})
|
||||||
|
print(f" uplift blend w=25%: full {w25.get('uplift_full')} "
|
||||||
|
f"hold {w25.get('uplift_hold')}")
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
mg = {}
|
mg = {}
|
||||||
print(f" marginal_vs_tp01 : NON GIRATO ({type(exc).__name__}: {exc})")
|
print(f" marginal_vs_tp01 : NON GIRATO ({type(exc).__name__}: {exc})")
|
||||||
@@ -848,14 +938,21 @@ def main() -> None:
|
|||||||
|
|
||||||
# ---------------- STEP 8: eseguibilita' ----------------
|
# ---------------- STEP 8: eseguibilita' ----------------
|
||||||
print("\n[8] Q3 — ESEGUIBILITA' a $600-3.000")
|
print("\n[8] Q3 — ESEGUIBILITA' a $600-3.000")
|
||||||
|
print(" lotti minimi letti dal VENUE (non da una tabella cablata):")
|
||||||
|
px = {a: float(curves[a]["index"].dropna().iloc[-1]) for a in ASSETS}
|
||||||
for a in ASSETS:
|
for a in ASSETS:
|
||||||
|
for nm in (f"{a}-25DEC26", f"{a}_USDC-25DEC26"):
|
||||||
try:
|
try:
|
||||||
sp = _api("/public/get_instrument", instrument_name=f"{a}-25DEC26")["result"]
|
sp = _api("/public/get_instrument", instrument_name=nm)["result"]
|
||||||
print(f" {a}-25DEC26: min_trade_amount ${sp['min_trade_amount']:,.0f} di nozionale, "
|
lin = sp.get("settlement_currency") == "USDC"
|
||||||
f"contract_size ${sp['contract_size']:,.0f}, tick {sp['tick_size']}, "
|
lot = (sp["min_trade_amount"] * px[a]) if lin else sp["min_trade_amount"]
|
||||||
f"taker {sp['taker_commission']*1e4:.1f} bps")
|
print(f" {nm:22s} {'LINEARE' if lin else 'INVERSE':8s} "
|
||||||
|
f"min {sp['min_trade_amount']:<10.4g} = ${lot:>8,.2f} di nozionale "
|
||||||
|
f"tick {sp['tick_size']} taker {sp['taker_commission']*1e4:.1f} bps")
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
print(f" {a}: specs non lette ({type(exc).__name__})")
|
print(f" {nm:22s} specs non lette ({type(exc).__name__})")
|
||||||
|
print(" (il libro live trada i LINEARI USDC; i trimestrali studiati qui sono gli INVERSE,")
|
||||||
|
print(" che hanno 7 anni di storia. Le due famiglie hanno lotti minimi DIVERSI.)")
|
||||||
print(f" vol ANNUA del candidato per $1 di nozionale lordo/gamba: {bs['vol']*100:.2f}%")
|
print(f" vol ANNUA del candidato per $1 di nozionale lordo/gamba: {bs['vol']*100:.2f}%")
|
||||||
if bs["vol"] > 0:
|
if bs["vol"] > 0:
|
||||||
lev = 0.20 / bs["vol"]
|
lev = 0.20 / bs["vol"]
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ def metriche(net: np.ndarray, idx: pd.DatetimeIndex) -> dict:
|
|||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
# 4. XS01 — ricostruzione dei PESI (il sleeve espone solo i ritorni) + prova d'identita'
|
# 4. XS01 — ricostruzione dei PESI (il sleeve espone solo i ritorni) + prova d'identita'
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
def xs01_pesi():
|
def xs01_pesi(phase: int = 0):
|
||||||
from src.portfolio.sleeves import XS_CFG, XS_UNIVERSE, _xsec_returns
|
from src.portfolio.sleeves import XS_CFG, XS_UNIVERSE, _xsec_returns
|
||||||
cols = {}
|
cols = {}
|
||||||
for sym in XS_UNIVERSE:
|
for sym in XS_UNIVERSE:
|
||||||
@@ -334,7 +334,7 @@ def xs01_pesi():
|
|||||||
w = np.zeros(A)
|
w = np.zeros(A)
|
||||||
hist: list[float] = []
|
hist: list[float] = []
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
if i >= mlb and i % H == 0:
|
if i >= mlb and i % H == phase:
|
||||||
rLs = [px[i] / px[i - L] - 1.0 for L in lb]
|
rLs = [px[i] / px[i - L] - 1.0 for L in lb]
|
||||||
di = float(np.mean([r.std() for r in rLs]))
|
di = float(np.mean([r.std() for r in rLs]))
|
||||||
thr = np.percentile(hist, dp) if (dp > 0 and len(hist) >= mh) else -np.inf
|
thr = np.percentile(hist, dp) if (dp > 0 and len(hist) >= mh) else -np.inf
|
||||||
@@ -361,9 +361,12 @@ def xs01_pesi():
|
|||||||
rv = s.rolling(30, min_periods=15).std().shift(1) * np.sqrt(365.25)
|
rv = s.rolling(30, min_periods=15).std().shift(1) * np.sqrt(365.25)
|
||||||
scale = np.clip(np.nan_to_num(tv / rv.replace(0, np.nan).values, nan=0.0), 0, 3.0)
|
scale = np.clip(np.nan_to_num(tv / rv.replace(0, np.nan).values, nan=0.0), 0, 3.0)
|
||||||
# prova d'identita' col sleeve UFFICIALE (max|diff| deve essere 0.0)
|
# prova d'identita' col sleeve UFFICIALE (max|diff| deve essere 0.0)
|
||||||
|
if phase == 0:
|
||||||
off = _xsec_returns()
|
off = _xsec_returns()
|
||||||
dif = float(np.abs(pd.Series(s.values * scale, index=C.index).reindex(off.index).values
|
dif = float(np.abs(pd.Series(s.values * scale, index=C.index).reindex(off.index).values
|
||||||
- off.values).max())
|
- off.values).max())
|
||||||
|
else:
|
||||||
|
dif = float("nan")
|
||||||
# posizione TENUTA durante la barra i = W[i-1]*scale[i] => posta a fine barra i: W[i]*scale[i+1]
|
# posizione TENUTA durante la barra i = W[i-1]*scale[i] => posta a fine barra i: W[i]*scale[i+1]
|
||||||
pos_scale = np.concatenate([scale[1:], scale[-1:]])
|
pos_scale = np.concatenate([scale[1:], scale[-1:]])
|
||||||
return W * pos_scale[:, None], dret, C, list(C.columns), dif
|
return W * pos_scale[:, None], dret, C, list(C.columns), dif
|
||||||
@@ -541,6 +544,29 @@ def main() -> None:
|
|||||||
f"{hc:>12.0f}%{m10['pct_eseguite']:>8.0f}% ord "
|
f"{hc:>12.0f}%{m10['pct_eseguite']:>8.0f}% ord "
|
||||||
f"{m10['pct_nozionale']:>4.0f}% ntl")
|
f"{m10['pct_nozionale']:>4.0f}% ntl")
|
||||||
|
|
||||||
|
# -------- fortuna di FASE: l'haircut e' un DELTA, e in questo progetto ogni delta misurato
|
||||||
|
# su una griglia ancorata eredita la fortuna dell'ancora (lezione 2026-07-26). XS01 ribilancia
|
||||||
|
# ogni H=10 giorni -> 10 fasi. Statistica giusta: MEDIANA DELLE DIFFERENZE APPAIATE.
|
||||||
|
print(f"\n [gate anchor] haircut per FASE del ciclo H={H} (pavimento $10, "
|
||||||
|
f"mediana delle differenze appaiate):")
|
||||||
|
print(f" {'fase':>6}{'Sh mod':>9}{'Sh $10':>9}{'haircut':>10} (cap XS01 $600)")
|
||||||
|
hb = []
|
||||||
|
for ph in range(H):
|
||||||
|
Wp, dp_, Cp, colsp, _ = xs01_pesi(ph)
|
||||||
|
lp = np.array([float(meta.loc[x, "lot_usd"]) if x in meta.index else 0.0 for x in colsp])
|
||||||
|
pp = np.array([float(meta.loc[x, "markPx"]) if x in meta.index else np.nan for x in colsp])
|
||||||
|
s0 = metriche(sim_libro(Wp, dp_, None, 600.0, None, None, None, fee_xs_mod)["net"], Cp.index)["sharpe"]
|
||||||
|
s1 = metriche(sim_libro(Wp, dp_, None, 600.0, MIN_ORDER_HL, lp, pp, fee_xs_mod)["net"], Cp.index)["sharpe"]
|
||||||
|
hb.append(s0 - s1)
|
||||||
|
print(f" {ph:>6}{s0:>9.2f}{s1:>9.2f}{s0-s1:>+10.3f}")
|
||||||
|
hb = np.array(hb)
|
||||||
|
print(f" mediana appaiata {np.median(hb):+.3f} Sharpe banda [{hb.min():+.3f}, {hb.max():+.3f}]"
|
||||||
|
f" fasi con danno >0.10: {int((hb > 0.10).sum())}/{H}")
|
||||||
|
print(f" -> il pavimento HL non e' un costo per XS01 a NESSUNA fase, non solo alla canonica.")
|
||||||
|
print(f" (sottoprodotto: la colonna 'Sh mod' REPLICA il phase-luck del 2026-07-02 — la fase")
|
||||||
|
print(f" canonica 0 sta in alto e la MEDIANA delle fasi e' ben sotto: e' l'audit di XS01,")
|
||||||
|
print(f" ritrovato per caso da un'altra strada.)")
|
||||||
|
|
||||||
# ---------------------------------------------------------------- 4. XSR01
|
# ---------------------------------------------------------------- 4. XSR01
|
||||||
print(f"\n[4] XSR01 — soglia PUBBLICATA ~$5.000, gate 2026-10-23 con haircut a $5.000 <= 40%")
|
print(f"\n[4] XSR01 — soglia PUBBLICATA ~$5.000, gate 2026-10-23 con haircut a $5.000 <= 40%")
|
||||||
from scripts.live.paper_xsr import FEE_LEG, MIN_ORDER, _book, _step, build_panel
|
from scripts.live.paper_xsr import FEE_LEG, MIN_ORDER, _book, _step, build_panel
|
||||||
|
|||||||
@@ -245,6 +245,32 @@ def main() -> dict:
|
|||||||
f"p99 {np.quantile(gap, 0.01):+.2%} peggiore {gap.min():+.2%}")
|
f"p99 {np.quantile(gap, 0.01):+.2%} peggiore {gap.min():+.2%}")
|
||||||
print(f" giorni che CHIUDONO sul minimo (m == R): {(np.abs(gap) < 1e-12).mean():.1%}")
|
print(f" giorni che CHIUDONO sul minimo (m == R): {(np.abs(gap) < 1e-12).mean():.1%}")
|
||||||
print(" -> replica superata su 4 statistiche su 4: la macchineria e' quella del 25/07.")
|
print(" -> replica superata su 4 statistiche su 4: la macchineria e' quella del 25/07.")
|
||||||
|
print("\n E il MECCANISMO del 25/07 ('il tuffo e' ~3x piu' profondo nei giorni che")
|
||||||
|
print(" finiscono BENE'), che e' cio' che rende la lente accoppiata diversa da una")
|
||||||
|
print(" marginale — gap medio per decile della CHIUSURA del giorno:")
|
||||||
|
q = pd.qcut(pd.Series(D["R_live"]).rank(method="first"), 10, labels=False)
|
||||||
|
tab = pd.DataFrame({"R": D["R_live"], "gap": gap, "q": q.values}).groupby("q").agg(
|
||||||
|
R=("R", "mean"), gap=("gap", "mean"), chiude_sul_min=("gap", lambda x: float((x > -1e-12).mean())))
|
||||||
|
print(f"\n {'decile':>8}{'chiusura media':>17}{'gap medio':>12}{'chiude sul minimo':>20}")
|
||||||
|
for i, row in tab.iterrows():
|
||||||
|
eti = " <- peggiori" if i == 0 else (" <- migliori" if i == 9 else "")
|
||||||
|
print(f" {int(i)+1:>8}{row['R']:>17.2%}{row['gap']:>12.2%}{row['chiude_sul_min']:>20.0%}{eti}")
|
||||||
|
peggio5 = gap[D["R_live"] <= np.quantile(D["R_live"], 0.05)]
|
||||||
|
print("\n ⚠️ PRECISAZIONE a una lettura pubblicata: il 26,8% di giorni con m==R NON e'")
|
||||||
|
print(" «il giorno brutto che scende tutto il giorno e chiude sul minimo» — sta nei")
|
||||||
|
print(" decili CENTRALI (56-88%), che sono i giorni in cui il libro e' FLAT e quindi")
|
||||||
|
print(" non ha nessuna escursione da fare. Nel decile peggiore chiude sul minimo solo")
|
||||||
|
print(" il 4% delle volte. Il meccanismo del 25/07 (gap ~3x nei giorni buoni) e'")
|
||||||
|
print(" replicato; la spiegazione che lo accompagna, no.")
|
||||||
|
print(f"\n gap medio nel 5% di giorni PEGGIORI: {peggio5.mean():+.2%}"
|
||||||
|
f" (contro {gap[D['R_live'] >= np.quantile(D['R_live'], 0.95)].mean():+.2%} nel 5% migliori)")
|
||||||
|
print(" 📌 CONSEGUENZA CHE VALE PER LA SEZIONE (4) DELLO SCETTICATO (lo stress di coda):")
|
||||||
|
print(" un giorno di crash CHIUDE quasi sul proprio minimo, quindi la lente accoppiata")
|
||||||
|
print(" **non peggiora** lo scenario di stress fuori campione — il tuffo extra che")
|
||||||
|
print(" aggiungerebbe a un giorno -10% e' dell'ordine di mezzo punto. Cio' che regge")
|
||||||
|
print(" k* verso il basso resta quello che dice lo scetticato (drift stimato + coda")
|
||||||
|
print(" assente dal dataset): la lente NON e' un quarto argomento, ne' nell'un verso")
|
||||||
|
print(" ne' nell'altro.")
|
||||||
|
|
||||||
print(f"\n (0b) ⚠️ IL GATE NON E' DIRETTAMENTE ESEGUIBILE — due lenti di SKH01, non una.")
|
print(f"\n (0b) ⚠️ IL GATE NON E' DIRETTAMENTE ESEGUIBILE — due lenti di SKH01, non una.")
|
||||||
for nm, x in (("recon accoppiata (SKH canonico)", R_rec), ("book_series('hourly') (SKH live)", R_live)):
|
for nm, x in (("recon accoppiata (SKH canonico)", R_rec), ("book_series('hourly') (SKH live)", R_live)):
|
||||||
|
|||||||
@@ -702,6 +702,41 @@ def main() -> None:
|
|||||||
print(" scoperta; se resta a drift 0, il vantaggio e' pura DECORRELAZIONE e non dipende da")
|
print(" scoperta; se resta a drift 0, il vantaggio e' pura DECORRELAZIONE e non dipende da")
|
||||||
print(" quell'ipotesi. Il numero, non la narrazione, dice quale delle due.")
|
print(" quell'ipotesi. Il numero, non la narrazione, dice quale delle due.")
|
||||||
|
|
||||||
|
hr("E-ter. IL BUCO CHE LA FINESTRA NON PUO' CHIUDERE — 2024-2026 non contiene un CRASH")
|
||||||
|
print(" L'ottimizzazione toglie peso a TP01 (Sharpe 0.79 su questa finestra). Ma TP01 e' lo")
|
||||||
|
print(" sleeve DIFENSIVO: il LOO del 26/07 misura il suo contributo hold-out NEGATIVO nel 99.1%")
|
||||||
|
print(" delle ancore e conclude 'uno sleeve difensivo si giudica sul SINISTRO, non sul premio'.")
|
||||||
|
print(" Su un conto funded la barriera morde PROPRIO nel sinistro. Qui la coppia crypto (che ha")
|
||||||
|
print(" storia dal 2019, quindi il 2022 dentro) e' ri-ottimizzata sulle due finestre:")
|
||||||
|
cry_full = pc.crypto_daily_tuples(1.0, 0.0).index
|
||||||
|
W2 = [{"TP01": i / 8, "SKH01": 1 - i / 8} for i in range(9)]
|
||||||
|
print(f"\n {'finestra':>26} {'giorni':>7} {'argmax TP/SKH':>15} {'leva':>6} {'J':>7} "
|
||||||
|
f"{'J del 75/25':>12} {'Sharpe argmax':>14}")
|
||||||
|
for st, lab in (("2019-03-14", "FULL 2019+ (col 2022)"), (START_WIN, "2024+ (senza crash)")):
|
||||||
|
pairs = {}
|
||||||
|
for w in W2:
|
||||||
|
c = pc.crypto_daily_tuples(w["TP01"], w["SKH01"])
|
||||||
|
c = c[c.index >= pd.Timestamp(st, tz="UTC")]
|
||||||
|
pairs[wkey(w)] = (deluck(c["R"].values), np.minimum(c["m"].values, c["R"].values) - c["R"].values)
|
||||||
|
nh = len(next(iter(pairs.values()))[0])
|
||||||
|
ev2 = boot_idx(nh, EV_H, N_PATHS, seed=8230)
|
||||||
|
fu2 = boot_idx(nh, FU_H, N_PATHS, seed=8231)
|
||||||
|
res = []
|
||||||
|
for k, (R_, G_) in pairs.items():
|
||||||
|
for lv in LEVS:
|
||||||
|
jj = (eval_sim(R_, G_, ev2, lv, "HYRO")["p_pass"]
|
||||||
|
* funded_sim(R_, G_, fu2, lv, "HYRO", 100_000.0)["p_alive"])
|
||||||
|
res.append((k, lv, jj, R_.mean() * 365 / (R_.std() * np.sqrt(365))))
|
||||||
|
bk, blv, bj, bsh = max(res, key=lambda x: x[2])
|
||||||
|
j75 = max(x[2] for x in res if x[0] == "75/25/0")
|
||||||
|
print(f" {lab:>26} {nh:>7d} {bk.rsplit('/', 1)[0]:>15} {blv:>6.3f} {bj:>7.3f} "
|
||||||
|
f"{j75:>12.3f} {bsh:>14.2f}")
|
||||||
|
print("\n Se anche sulla finestra che CONTIENE il 2022 l'ottimo scarta TP01, la sua sotto-")
|
||||||
|
print(" pesatura non e' un artefatto di finestra. Se invece li' TP01 pesa, allora la")
|
||||||
|
print(" raccomandazione del 2024+ e' una scommessa sull'assenza di crash — e su una barriera")
|
||||||
|
print(" di rovina quella scommessa la paghi una volta sola. **Questa finestra NON puo' dirlo")
|
||||||
|
print(" per XS01**, che nasce nel 2024: il buco resta aperto per lui, per costruzione.")
|
||||||
|
|
||||||
# ---------------------------------------------------------------- F. VRP01 / GTAA01
|
# ---------------------------------------------------------------- F. VRP01 / GTAA01
|
||||||
tick("E. banda ancora")
|
tick("E. banda ancora")
|
||||||
hr("F. E GLI SLEEVE 'INUTILI A $600'? — l'asimmetria del 25/07 §4 vale per XS01, non per tutti")
|
hr("F. E GLI SLEEVE 'INUTILI A $600'? — l'asimmetria del 25/07 §4 vale per XS01, non per tutti")
|
||||||
|
|||||||
@@ -50,8 +50,13 @@ della barra 5m e il fill, che vale decine di bps. La statistica giusta e' NORMAL
|
|||||||
della barra: il nostro fill e' esso stesso un print della barra 5m che lo contiene, quindi
|
della barra: il nostro fill e' esso stesso un print della barra 5m che lo contiene, quindi
|
||||||
q = (fill - low)/(high - low) per un BUY, q = 1 - quello per un SELL.
|
q = (fill - low)/(high - low) per un BUY, q = 1 - quello per un SELL.
|
||||||
Se i nostri ordini market avessero impatto, un BUY stamperebbe AL MASSIMO della barra -> q -> 1.
|
Se i nostri ordini market avessero impatto, un BUY stamperebbe AL MASSIMO della barra -> q -> 1.
|
||||||
Senza impatto q e' un'estrazione ~uniforme -> E[q] = 0.5. sigma_q = 1/sqrt(12) = 0.289 e' ~20x
|
Senza impatto E[q] = 0.5, e il null NON si assume uniforme: si MISURA su ~18.000 barre vere.
|
||||||
piu' piccolo del rumore in bps.
|
|
||||||
|
⚠️ ESITO SULLA MIA STESSA POTENZA: assumendo l'uniforme avevo dichiarato MDE_q = 0.19 di range
|
||||||
|
(~5 bps). Il null misurato e' 1.43x piu' largo (il close di una barra 5m sta spesso proprio sul
|
||||||
|
proprio estremo) -> MDE vera 0.27 = ~7.2 bps, contro 7.8 della via in bps. **La normalizzazione
|
||||||
|
non compra potenza utile.** Con n=18 nessuna via statistica risolve 1,5 bps: cio' che decide e'
|
||||||
|
l'argomento STRUTTURALE (tick, partecipazione al nastro, e l'esperimento naturale del fill #1).
|
||||||
|
|
||||||
Uso: nice -n 19 timeout 900 uv run python scripts/research/r0822_slip_audit.py
|
Uso: nice -n 19 timeout 900 uv run python scripts/research/r0822_slip_audit.py
|
||||||
(--no-net salta il fetch dello strumento lineare e lo dichiara non misurato)
|
(--no-net salta il fetch dello strumento lineare e lo dichiara non misurato)
|
||||||
@@ -268,16 +273,24 @@ def main():
|
|||||||
bad = (fills["fee"] / fills["filled"] * 1e4).median()
|
bad = (fills["fee"] / fills["filled"] * 1e4).median()
|
||||||
print(f" [se avessi usato la convenzione INVERSE: {bad:,.0f} bps -> "
|
print(f" [se avessi usato la convenzione INVERSE: {bad:,.0f} bps -> "
|
||||||
f"{bad / FEE_TAKER_BPS_SIDE:,.0f}x il vero, e senza sollevare nulla]")
|
f"{bad / FEE_TAKER_BPS_SIDE:,.0f}x il vero, e senza sollevare nulla]")
|
||||||
odd = fills[(fills["fee_bps"] - FEE_TAKER_BPS_SIDE).abs() > 0.05]
|
# Il nuovo schema fee Deribit e' entrato il 2026-08-01. La tabella `_CONTRACT`/fee_watch
|
||||||
if len(odd):
|
# legge il tier dichiarato dal venue; qui lo si legge sul NASTRO, che e' la prova migliore.
|
||||||
print(f" ⚠️ {len(odd)}/{len(fills)} fill NON al tier {FEE_TAKER_BPS_SIDE:.2f}:")
|
CUT = pd.Timestamp("2026-08-01", tz="UTC")
|
||||||
for _, o in odd.iterrows():
|
pre, post = fills[fills["fill_ts"] < CUT], fills[fills["fill_ts"] >= CUT]
|
||||||
print(f" #{o['i']} {o['fill_ts']} {o['asset']} {o['side']} "
|
print(f" ripartito sul cambio di listino del 2026-08-01:")
|
||||||
f"{o['filled']} @ {o['price']:.2f} = ${o['notional']:.2f} · "
|
for nm, g in (("PRIMA ", pre), ("DOPO ", post)):
|
||||||
f"fee {o['fee']:.8f} = {o['fee_bps']:.2f} bps")
|
if len(g):
|
||||||
print(f" -> nozionale ${odd['notional'].min():.2f}: sotto i ~$7 la fee sembra")
|
u = sorted(float(x) for x in g["fee_bps"].round(2).unique())
|
||||||
print(f" arrotondata AL RIALZO. E' un costo REALE che il modello non ha, e")
|
print(f" {nm} n={len(g):2d} fee/lato {u} "
|
||||||
print(f" morde solo gli ordini minuscoli (dove il libro vive a $600).")
|
f"(nozionale ${g['notional'].min():.0f}-${g['notional'].max():.0f})")
|
||||||
|
if len(pre) and len(post):
|
||||||
|
print(f" => il taglio {pre['fee_bps'].median():.2f} -> {post['fee_bps'].median():.2f} "
|
||||||
|
f"bps/lato e' VISIBILE sul nastro, {len(post)}/{len(post)} fill esatti al nuovo tier.")
|
||||||
|
print(f" Non e' un effetto taglia (i due fill a {pre['fee_bps'].median():.2f} bps sono da "
|
||||||
|
f"${pre['notional'].median():.0f}, sopra la mediana del campione).")
|
||||||
|
print(f" Conferma INDIPENDENTE di fee_watch (che legge il tier dichiarato, non i fill)")
|
||||||
|
print(f" e della regola pre-registrata del 26/07: taker <=5 bps/lato -> non si tocca")
|
||||||
|
print(f" nulla. Il listino si e' mosso NEL VERSO BUONO.")
|
||||||
|
|
||||||
# ---------------------------------------------------------------- 2. potenza, PRIMA
|
# ---------------------------------------------------------------- 2. potenza, PRIMA
|
||||||
print("\n[2] POTENZA DICHIARATA — calcolata PRIMA di guardare lo slippage\n" + "-" * 96)
|
print("\n[2] POTENZA DICHIARATA — calcolata PRIMA di guardare lo slippage\n" + "-" * 96)
|
||||||
@@ -396,6 +409,22 @@ def main():
|
|||||||
if len(x):
|
if len(x):
|
||||||
print(f" {a} {sd:4s} n={len(x):2d} media {x.mean():+7.2f} med {x.median():+7.2f}")
|
print(f" {a} {sd:4s} n={len(x):2d} media {x.mean():+7.2f} med {x.median():+7.2f}")
|
||||||
|
|
||||||
|
# La media di REF-B5 e' dominata da UN giorno: la si mostra con e senza, e si dice quale
|
||||||
|
# si cita. Non e' cherry-picking se si pubblicano entrambe e la regola e' dichiarata.
|
||||||
|
wild = R.nlargest(3, "bar_rng_bps")
|
||||||
|
day = wild["fill_ts"].dt.date.mode()[0]
|
||||||
|
keep = R[R["fill_ts"].dt.date != day]
|
||||||
|
print(f"\n ROBUSTEZZA: le 3 barre piu' larghe del campione ({wild['bar_rng_bps'].min():.0f}-"
|
||||||
|
f"{wild['bar_rng_bps'].max():.0f} bps di range) cadono tutte il {day}")
|
||||||
|
print(f" (ETH +14% in 6 ore). Sono {len(R) - len(keep)} fill su {len(R)} e da soli fanno")
|
||||||
|
print(f" la media: REF-B5 su tutto {ci_mean(R['slip_B5'])['mean']:+.2f} bps -> "
|
||||||
|
f"{ci_mean(keep['slip_B5'])['mean']:+.2f} bps senza quel giorno")
|
||||||
|
print(f" (IC95 [{ci_mean(keep['slip_B5'])['lo']:+.2f}, {ci_mean(keep['slip_B5'])['hi']:+.2f}], "
|
||||||
|
f"n={len(keep)}). La MEDIANA invece non si muove: "
|
||||||
|
f"{R['slip_B5'].median():+.2f} -> {keep['slip_B5'].median():+.2f}.")
|
||||||
|
print(f" => con n=18 la media in bps e' una statistica inservibile (un giorno la")
|
||||||
|
print(f" ribalta); si cita la mediana, e comunque decide l'argomento strutturale.")
|
||||||
|
|
||||||
# ---------------------------------------------------------------- 5. la statistica con potenza
|
# ---------------------------------------------------------------- 5. la statistica con potenza
|
||||||
print("\n[5] LA STATISTICA CON POTENZA — q, posizione del fill nella barra 5m\n" + "-" * 96)
|
print("\n[5] LA STATISTICA CON POTENZA — q, posizione del fill nella barra 5m\n" + "-" * 96)
|
||||||
q = R["q"].dropna()
|
q = R["q"].dropna()
|
||||||
@@ -407,8 +436,9 @@ def main():
|
|||||||
outside = (R["in_bar"] == False).sum()
|
outside = (R["in_bar"] == False).sum()
|
||||||
print(f" fill FUORI dal range della propria barra: {outside}/{dq['n']} "
|
print(f" fill FUORI dal range della propria barra: {outside}/{dq['n']} "
|
||||||
f"(un fill fuori sarebbe strumento o ora sbagliati)")
|
f"(un fill fuori sarebbe strumento o ora sbagliati)")
|
||||||
print(f" q=1.00 esatti (fill AL massimo = firma di impatto): {(q >= 0.999).sum()}/{dq['n']}"
|
hi_obs, lo_obs = int((q >= 0.99).sum()), int((q <= 0.01).sum())
|
||||||
f" · q=0.00 esatti: {(q <= 0.001).sum()}/{dq['n']}")
|
print(f" fill sull'ESTREMO avverso (q>=0.99 = firma di impatto): {hi_obs}/{dq['n']}"
|
||||||
|
f" · sull'estremo favorevole (q<=0.01): {lo_obs}/{dq['n']}")
|
||||||
rng_med = float(R["bar_rng_bps"].median())
|
rng_med = float(R["bar_rng_bps"].median())
|
||||||
print(f" range mediano di barra 5m: {rng_med:.1f} bps -> lo scostamento misurato vale")
|
print(f" range mediano di barra 5m: {rng_med:.1f} bps -> lo scostamento misurato vale")
|
||||||
print(f" ~{(dq['mean'] - 0.5) * rng_med:+.2f} bps/lato "
|
print(f" ~{(dq['mean'] - 0.5) * rng_med:+.2f} bps/lato "
|
||||||
@@ -429,17 +459,48 @@ def main():
|
|||||||
pval = float((null >= dq["mean"]).mean())
|
pval = float((null >= dq["mean"]).mean())
|
||||||
print(f"\n NULL EMPIRICO (non 'uniforme per assunzione'): posizione del close nel proprio")
|
print(f"\n NULL EMPIRICO (non 'uniforme per assunzione'): posizione del close nel proprio")
|
||||||
print(f" range, su {len(qc):,} barre 5m vere, segno randomizzato.")
|
print(f" range, su {len(qc):,} barre 5m vere, segno randomizzato.")
|
||||||
|
sd_unif = (1 / np.sqrt(12)) / np.sqrt(dq["n"])
|
||||||
print(f" null: media {null.mean():.3f} · sd {null.std():.3f} "
|
print(f" null: media {null.mean():.3f} · sd {null.std():.3f} "
|
||||||
f"(l'uniforme darebbe sd {(1 / np.sqrt(12)) / np.sqrt(dq['n']):.3f})")
|
f"(l'uniforme darebbe sd {sd_unif:.3f})")
|
||||||
print(f" p(one-sided, impatto = q piu' alto del null) = {pval:.3f}"
|
print(f" p(one-sided, impatto = q piu' alto del null) = {pval:.3f}"
|
||||||
f" -> {'IMPATTO RILEVATO' if pval < 0.05 else 'nessun impatto rilevabile'}")
|
f" -> {'IMPATTO RILEVATO' if pval < 0.05 else 'nessun impatto rilevabile'}")
|
||||||
|
# ...e quanti estremi ci si ASPETTA sotto quel null (un conteggio nudo non e' un'evidenza)
|
||||||
|
p_hi = float(((qc >= 0.99).mean() + (qc <= 0.01).mean()) / 2)
|
||||||
|
print(f" attesi sotto il null: {p_hi * dq['n']:.1f} su {dq['n']} per lato "
|
||||||
|
f"(osservati {hi_obs} avversi / {lo_obs} favorevoli)")
|
||||||
|
print(f" -> l'unico q>=0.99 e' il fill #3 (ETH sell, q=0.996) su una barra da 7.5 bps di")
|
||||||
|
print(f" range: e' 0.03 bps di prezzo. Un estremo su una barra piatta non e' impatto.")
|
||||||
|
mde_q_true = 2.80 * float(null.std())
|
||||||
|
print(f"\n ⚠️ CORREZIONE ALLA MIA STESSA POTENZA DICHIARATA. Il null vero e' "
|
||||||
|
f"{null.std() / sd_unif:.2f}x piu' largo")
|
||||||
|
print(f" dell'uniforme (il close di una barra 5m sta spesso PROPRIO sul suo estremo:")
|
||||||
|
print(f" sd per-osservazione {null.std() * np.sqrt(dq['n']):.3f} contro 0.289 dell'uniforme).")
|
||||||
|
print(f" MDE_q vera = {mde_q_true:.3f} di range = {mde_q_true * rng_med:.1f} bps, non "
|
||||||
|
f"{mde_q * rng_med:.1f}: la mia stima")
|
||||||
|
print(f" a priori era ottimista del {(mde_q_true / mde_q - 1) * 100:.0f}%. => la statistica")
|
||||||
|
print(f" normalizzata NON compra potenza utile ({mde_q_true * rng_med:.1f} bps contro "
|
||||||
|
f"{mde_bps:.1f} della via in bps):")
|
||||||
|
print(f" con n=18 NESSUNA via statistica risolve {MARGIN_BPS_SIDE:.1f} bps. Cio' che decide")
|
||||||
|
print(f" e' l'argomento strutturale qui sotto, non un intervallo di confidenza.")
|
||||||
|
|
||||||
# -- argomento FISICO, indipendente dal campione: quanto siamo grandi e quanto vale un tick
|
# -- argomento FISICO, indipendente dal campione: quanto siamo grandi e quanto vale un tick
|
||||||
from src.live.deribit import _CONTRACT, INSTRUMENT
|
from src.live.deribit import _CONTRACT, INSTRUMENT
|
||||||
print(f"\n ARGOMENTO STRUTTURALE (non dipende da n=18):")
|
print(f"\n ARGOMENTO STRUTTURALE (non dipende da n=18):")
|
||||||
pr = R["partecip"].dropna()
|
pr = R["partecip"].dropna()
|
||||||
print(f" la nostra size sul volume della barra 5m che ci contiene: mediana "
|
print(f" la nostra size sul volume della barra 5m che ci contiene: mediana "
|
||||||
f"{pr.median() * 100:.2f}% · max {pr.max() * 100:.2f}%")
|
f"{pr.median() * 100:.2f}% · p90 {pr.quantile(.9) * 100:.2f}% · max {pr.max() * 100:.2f}%")
|
||||||
|
top = R.dropna(subset=["partecip"]).nlargest(1, "partecip").iloc[0]
|
||||||
|
print(f"\n ESPERIMENTO NATURALE — il fill #{top['i']} ({top['fill_ts']:%Y-%m-%d %H:%M} "
|
||||||
|
f"{top['fill_ts']:%a}, {top['asset']} ${top['notional']:.0f}):")
|
||||||
|
print(f" e' stato il {top['partecip'] * 100:.1f}% del volume della propria barra 5m")
|
||||||
|
print(f" (weekend, nastro sottilissimo) e quella barra ha avuto un range di SOLI")
|
||||||
|
print(f" {top['bar_rng_bps']:.2f} bps, con il nostro print a meta' (q={top['q']:.2f}).")
|
||||||
|
print(f" Un ordine che prende un quinto del nastro e lascia il prezzo entro 1 bps e'")
|
||||||
|
print(f" la prova diretta che a questa taglia il libro NON cammina il book. Vale piu'")
|
||||||
|
print(f" dell'intero intervallo di confidenza: e' il caso peggiore del campione.")
|
||||||
|
print(f" ⚠️ ed e' un SABATO: la partecipazione peggiore cade nel weekend, dove il")
|
||||||
|
print(f" progetto ha misurato che TP01 fa il 38% del proprio gross. Se l'attrito")
|
||||||
|
print(f" mordera' a taglia maggiore, mordera' proprio dove sta il rendimento.")
|
||||||
for a in sorted(R["asset"].unique()):
|
for a in sorted(R["asset"].unique()):
|
||||||
tick = _CONTRACT[INSTRUMENT[a]]["tick"]
|
tick = _CONTRACT[INSTRUMENT[a]]["tick"]
|
||||||
px = float(R[R["asset"] == a]["price"].median())
|
px = float(R[R["asset"] == a]["price"].median())
|
||||||
@@ -447,8 +508,29 @@ def main():
|
|||||||
print(f" spread di 5 tick costerebbe {2.5 * tick / px * 1e4:.3f} bps di mezzo-spread")
|
print(f" spread di 5 tick costerebbe {2.5 * tick / px * 1e4:.3f} bps di mezzo-spread")
|
||||||
print(f" => il pavimento fisico del costo d'esecuzione e' ~0.1 bps, DUE ordini di")
|
print(f" => il pavimento fisico del costo d'esecuzione e' ~0.1 bps, DUE ordini di")
|
||||||
print(f" grandezza sotto il margine di {MARGIN_BPS_SIDE:.1f} bps. Perche' lo slippage")
|
print(f" grandezza sotto il margine di {MARGIN_BPS_SIDE:.1f} bps. Perche' lo slippage")
|
||||||
print(f" sfondi il margine il libro dovrebbe CAMMINARE il book — e a <1% del nastro")
|
print(f" sfondi il margine il libro dovrebbe CAMMINARE il book — e a <1% del nastro,")
|
||||||
print(f" con 0/18 fill al massimo di barra non c'e' traccia che lo faccia.")
|
print(f" con MENO estremi avversi di quanti il null ne preveda ({hi_obs} contro "
|
||||||
|
f"{p_hi * dq['n']:.1f}), non c'e'")
|
||||||
|
print(f" traccia che lo faccia.")
|
||||||
|
|
||||||
|
# -- COSA CAMBIA CON IL CAPITALE: la partecipazione scala LINEARE, il conto no.
|
||||||
|
print(f"\n SCALA — l'unica parte di questa misura che ha una data di scadenza:")
|
||||||
|
eq = 636.0 # equity reale osservata (data/live/equity_seen.json)
|
||||||
|
print(f" la partecipazione scala lineare col capitale (equity oggi ~${eq:.0f}).")
|
||||||
|
print(f" {'capitale':>10} {'partecip. mediana':>19} {'p90':>9} {'max osservato':>15}")
|
||||||
|
for cap in (600, 5_000, 20_000, 100_000, 272_000):
|
||||||
|
k = cap / eq
|
||||||
|
mx = pr.max() * k * 100
|
||||||
|
print(f" {'$' + format(cap, ','):>10} {pr.median() * k * 100:>18.2f}% "
|
||||||
|
f"{pr.quantile(.9) * k * 100:>8.2f}% "
|
||||||
|
+ (f"{mx:>14.1f}%" if mx <= 100 else f"{'>tutta la barra':>15}"))
|
||||||
|
print(f" => a $600 l'impatto e' strutturalmente impossibile e questa misura lo conferma.")
|
||||||
|
print(f" Ma il caso peggiore osservato e' gia' {pr.max() * 100:.0f}% di una barra 5m: a $5k")
|
||||||
|
print(f" diventa {pr.max() * 5000 / eq * 100:.0f}%, a $20k {pr.max() * 20000 / eq * 100:.0f}%. "
|
||||||
|
f"**La misura di oggi NON si estrapola**")
|
||||||
|
print(f" al capitale del piano: va RIFATTA a ogni salto di taglia. E' la stessa")
|
||||||
|
print(f" forma del muro di eseguibilita' gia' noto (XS01 ~$20k, XSR01 ~$5k), ma")
|
||||||
|
print(f" sull'asse dell'ATTRITO invece che del min-order.")
|
||||||
|
|
||||||
# ---------------------------------------------------------------- 6. conversione
|
# ---------------------------------------------------------------- 6. conversione
|
||||||
print("\n[6] CONVERSIONE IN SHARPE/CAGR — curva PUBBLICATA, non ricalcolata\n" + "-" * 96)
|
print("\n[6] CONVERSIONE IN SHARPE/CAGR — curva PUBBLICATA, non ricalcolata\n" + "-" * 96)
|
||||||
|
|||||||
@@ -0,0 +1,578 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""r0822_volsize_skeptic.py — SCETTICO del filone VOL-SIZE (r0822_vol_size.py).
|
||||||
|
|
||||||
|
BERSAGLIO. Un altro agente ha trovato che dare a SKH01 una SIZE PER-TRADE (cella congelata
|
||||||
|
`VTL tv20 w90`: size fissata all'ingresso = clip(0.20 / rv90(t-1), 0, 3)) vale **+0,070 di
|
||||||
|
Sharpe di libro a ISO-PESO**, positivo in 23/23 ancore e 8/8 anni. Ha dichiarato lui stesso che
|
||||||
|
il meccanismo non e' quello che sembra: il guadagno viene dalla FREQUENZA dei trade (+0,051 sul
|
||||||
|
solo filtro di attivita') e non dalla MAGNITUDINE (+0,003) -> non e' controllo del rischio, e'
|
||||||
|
un filtro di frequenza. E ha PRE-REGISTRATO senza eseguirlo il test che lo falsificherebbe.
|
||||||
|
|
||||||
|
QUATTRO ATTACCHI, dichiarati prima di misurare, con l'esito che mi aspetto:
|
||||||
|
|
||||||
|
A1 IL TEST PRE-REGISTRATO — famiglia IV (inverse-vol per-trade sulla vol dell'ASSET) applicata
|
||||||
|
a SKH01_V1. La ragione dichiarata della famiglia IV e' la GEOMETRIA DELLO STOP: su V2-DD lo
|
||||||
|
stop e' una percentuale FISSA (4% long / 2% short), quindi il rischio in unita' di sigma
|
||||||
|
varia col regime; su V1 lo stop e' in ATR, cioe' gia' scalato sulla vol -> l'effetto DEVE
|
||||||
|
essere MINORE. Previsione direzionale: |eff. su V1| < |eff. su V2-DD|, cella per cella.
|
||||||
|
Se e' UGUALE, la spiegazione e' sbagliata anche se il numero regge.
|
||||||
|
ATTESA MIA: che il test sia SENZA POTENZA, perche' sospetto che IV non faccia niente
|
||||||
|
nemmeno su V2-DD (l'agente ha promosso VTL, non IV). Un test pre-registrato su una
|
||||||
|
famiglia inerte non falsifica nulla.
|
||||||
|
|
||||||
|
A2 LA CONTABILITA' — l'agente dichiara di aver trovato e riparato un look-ahead di
|
||||||
|
contabilita': "tutto il P&L di un trade e' contabilizzato il giorno di CHIUSURA, fino a
|
||||||
|
3,83 giorni dopo l'ingresso", costo "+0,04 di Sharpe hold-out fantasma", e ne ha tratto
|
||||||
|
una REGOLA pubblicata. Il compito mi chiede di verificare la riparazione troncando i dati
|
||||||
|
alla barra d'ingresso. Lo faccio, ma prima verifico la PREMESSA: quale giorno porta davvero
|
||||||
|
il P&L. ATTESA MIA: la riparazione e' corretta e ridondante.
|
||||||
|
|
||||||
|
A3 L'ATTACCO ALLA SPIEGAZIONE — se il guadagno e' un filtro di frequenza, deve essere
|
||||||
|
riproducibile SENZA ALCUN vol-target. Controllo piu' brutale possibile: size BINARIA 0/1
|
||||||
|
sulla stessa soglia della regola congelata (L>=1 <=> rv90<=20%), piu' un filtro che scarta
|
||||||
|
i trade nei periodi di maggiore ATTIVITA' della gamba. Se un binario replica il +0,070,
|
||||||
|
la formula di sizing e' ORNAMENTO. Controllo di segno obbligatorio: il binario INVERSO
|
||||||
|
(tieni solo i trade in alta vol) deve DANNEGGIARE, o non sto misurando un filtro.
|
||||||
|
|
||||||
|
A4 L'ASSE DELLA FINESTRA — l'agente dichiara che w e' un PICCO, non un plateau (w30 negativo,
|
||||||
|
w180 ~0, w90 ottimo). Un picco su un asse continuo con 23 ancore e' la firma classica di
|
||||||
|
un max-of-k. Misuro l'asse fitto (9 finestre) e rifaccio la scelta di w IN-SAMPLE-ONLY.
|
||||||
|
|
||||||
|
DISCIPLINA. Non ricalcolo niente che esista: importo `r0822_vol_size` (extract / equity_daily /
|
||||||
|
leg_daily / size_iv / leverage_series / sizes_from_L / book) e replico BIT-EXACT contro
|
||||||
|
`sleeves._skyhook_returns()` prima di ogni delta. Il confronto e' sempre ISO-PESO (baseline
|
||||||
|
ri-scalato allo stesso peso effettivo di SKH), perche' "alzare SKH01" e' gia' stato misurato e
|
||||||
|
respinto il 26/07. Statistica = MEDIANA DELLE DIFFERENZE APPAIATE per ancora (`anchor_luck_delta`),
|
||||||
|
mai differenza delle mediane.
|
||||||
|
|
||||||
|
USO: nice -n 19 timeout 900 uv run python scripts/research/r0822_volsize_skeptic.py [--every K]
|
||||||
|
--every 8 -> 3 ancore (pilota). default: tutte e 23.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
for _p in (ROOT, ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt"):
|
||||||
|
sys.path.insert(0, str(_p))
|
||||||
|
|
||||||
|
import altlib as A # noqa: E402
|
||||||
|
import r0702_anchor_skh01 as R # noqa: E402
|
||||||
|
import r0822_vol_size as V # noqa: E402 (bersaglio: riusato, non riscritto)
|
||||||
|
from src.strategies.skyhook import SKH01_V1 # noqa: E402
|
||||||
|
|
||||||
|
ASSETS = V.ASSETS
|
||||||
|
OFFSETS_FULL = V.OFFSETS_FULL
|
||||||
|
FROZEN = dict(tv=0.20, w=90, cap=3.0, active_only=False) # la cella congelata del LEAD
|
||||||
|
sh, vol, maxdd, cagr = V.sh, V.vol, V.maxdd, V.cagr
|
||||||
|
hold, ins, book = V.hold, V.ins, V.book
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- famiglie di size
|
||||||
|
def _keep_to_sizes(ex: dict, keep: pd.Series) -> np.ndarray:
|
||||||
|
return V.sizes_from_L(ex, keep.astype(float))
|
||||||
|
|
||||||
|
|
||||||
|
def size_vtl(sp: dict):
|
||||||
|
def f(ex):
|
||||||
|
return V.sizes_from_L(ex, V.leverage_series(ex["base_daily"], **sp))
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def size_bin(sp: dict, mode: str):
|
||||||
|
"""Size BINARIA 0/1 dalla STESSA L della regola congelata. Nessuna magnitudine: o il trade
|
||||||
|
si fa a size 1, o non si fa. `ge1` usa la soglia della regola stessa (L>=1 <=> rv90<=tv);
|
||||||
|
`gemed` la mediana espandente causale di L; `lt1` e' l'INVERSO (controllo di segno)."""
|
||||||
|
def f(ex):
|
||||||
|
L = V.leverage_series(ex["base_daily"], **sp)
|
||||||
|
if mode == "ge1":
|
||||||
|
keep = pd.Series(L.values >= 1.0, index=L.index)
|
||||||
|
elif mode == "lt1":
|
||||||
|
keep = pd.Series(L.values < 1.0, index=L.index)
|
||||||
|
elif mode == "gemed":
|
||||||
|
med = L.expanding(min_periods=V.VT_WARM).median()
|
||||||
|
keep = pd.Series(np.where(med.notna().values, L.values >= med.values, True),
|
||||||
|
index=L.index)
|
||||||
|
else:
|
||||||
|
raise ValueError(mode)
|
||||||
|
return _keep_to_sizes(ex, keep)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def size_bin_grad(sp: dict):
|
||||||
|
"""BINARIO + GRADUAZIONE: fra i trade tenuti, la size continua di L. Isola quanto aggiunge
|
||||||
|
la FORMULA una volta che la SELEZIONE e' gia' stata fatta."""
|
||||||
|
def f(ex):
|
||||||
|
L = V.leverage_series(ex["base_daily"], **sp)
|
||||||
|
g = pd.Series(np.where(L.values >= 1.0, L.values, 0.0), index=L.index)
|
||||||
|
return V.sizes_from_L(ex, g)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def size_chan(mode: str, binary: bool, w: int = 90, cap: float = 3.0):
|
||||||
|
"""I DUE CANALI dentro `rv` di una serie all'88% di zeri, isolati come nel bersaglio:
|
||||||
|
mode='mag' -> vol calcolata sulle sole barre ATTIVE (MAGNITUDINE degli esiti)
|
||||||
|
mode='freq' -> conteggio di barre attive nella finestra (FREQUENZA dei trade)
|
||||||
|
binary=False riproduce le celle continue del bersaglio (`VTL neutro attive` / `ACT`);
|
||||||
|
binary=True le riduce a 0/1 alla mediana ESPANDENTE causale della propria L."""
|
||||||
|
def f(ex):
|
||||||
|
if mode == "mag":
|
||||||
|
Ls = V.leverage_series(ex["base_daily"], tv=None, w=w, cap=cap, active_only=True)
|
||||||
|
else:
|
||||||
|
Ls = V.activity_L(ex["base_daily"], w=w, cap=cap)
|
||||||
|
if not binary:
|
||||||
|
return V.sizes_from_L(ex, Ls)
|
||||||
|
med = Ls.expanding(min_periods=V.VT_WARM).median()
|
||||||
|
keep = pd.Series(np.where(med.notna().values, Ls.values >= med.values, True),
|
||||||
|
index=Ls.index)
|
||||||
|
return _keep_to_sizes(ex, keep)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def size_dropact(w: int = 90, q: float = 0.75):
|
||||||
|
"""Scarta i trade il cui giorno d'ingresso cade nei periodi in cui la gamba e' stata piu'
|
||||||
|
ATTIVA (conteggio di barre attive negli ultimi w giorni sopra il quantile ESPANDENTE causale
|
||||||
|
q). Nessun vol-target, nessuna magnitudine, nessun prezzo: solo la frequenza dei trade."""
|
||||||
|
def f(ex):
|
||||||
|
base = ex["base_daily"]
|
||||||
|
cnt = (base != 0.0).astype(float).rolling(w, min_periods=max(5, w // 3)).sum().shift(1)
|
||||||
|
thr = cnt.expanding(min_periods=V.VT_WARM).quantile(q)
|
||||||
|
keep = pd.Series(np.where(thr.notna().values & cnt.notna().values,
|
||||||
|
cnt.values <= thr.values, True), index=base.index)
|
||||||
|
return _keep_to_sizes(ex, keep)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def iv_family() -> dict:
|
||||||
|
d = {}
|
||||||
|
for p in (0.5, 1.0):
|
||||||
|
for w in (30, 90):
|
||||||
|
for cp in (2.0, 4.0):
|
||||||
|
d[f"IV p{p} w{w} cap{cp:.0f}"] = V.size_iv(p, w, cp)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- motore
|
||||||
|
def legs(EX: dict, funcs: dict) -> dict:
|
||||||
|
"""name -> serie giornaliera della gamba SKH 50/50, per un'ancora."""
|
||||||
|
out = {"BASE": V.leg_daily(EX, {a: V.size_flat(EX[a]) for a in ASSETS})}
|
||||||
|
for nm, fn in funcs.items():
|
||||||
|
out[nm] = V.leg_daily(EX, {a: fn(EX[a]) for a in ASSETS})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def iso_pair(TP: pd.Series, leg: pd.Series, leg_base: pd.Series):
|
||||||
|
"""Libro della variante e libro di CONTROLLO ISO-PESO (baseline ri-scalato alla stessa vol
|
||||||
|
di gamba -> stesso peso effettivo di SKH). E' la convenzione del bersaglio, riusata."""
|
||||||
|
vb = vol(leg_base)
|
||||||
|
m_s = vol(leg) / vb if vb > 0 else 1.0
|
||||||
|
return book(TP, leg), book(TP, leg_base * m_s), m_s
|
||||||
|
|
||||||
|
|
||||||
|
def delta_table(BK: dict, CT: dict, names, offs, label: str, extra_hold=True) -> dict:
|
||||||
|
res = {}
|
||||||
|
print(f" {'cella':<26}{'ISO dShFULL':>12}{'pos/n':>8}{'banda':>20}"
|
||||||
|
+ (f"{'ISO dShHOLD':>13}{'pos/n':>8}" if extra_hold else "") + f"{'wSKHeff':>9}")
|
||||||
|
for nm in names:
|
||||||
|
dF = A.anchor_luck_delta(lambda o, n=nm: BK[n][o], lambda o, n=nm: CT[n][o], offs,
|
||||||
|
metric=sh)
|
||||||
|
row = dict(dF=dF["median_paired"], nF=dF["n_positive"], lo=dF["lo"], hi=dF["hi"])
|
||||||
|
s = (f" {nm:<26}{row['dF']:>+12.3f}{row['nF']:>5}/{len(offs):<3}"
|
||||||
|
f" [{row['lo']:+.3f},{row['hi']:+.3f}]".ljust(20 + 26 + 12 + 8))
|
||||||
|
s = (f" {nm:<26}{row['dF']:>+12.3f}{row['nF']:>5}/{len(offs):<3}"
|
||||||
|
f"{'[%+.3f,%+.3f]' % (row['lo'], row['hi']):>20}")
|
||||||
|
if extra_hold:
|
||||||
|
dH = A.anchor_luck_delta(lambda o, n=nm: hold(BK[n][o]),
|
||||||
|
lambda o, n=nm: hold(CT[n][o]), offs, metric=sh)
|
||||||
|
row.update(dH=dH["median_paired"], nH=dH["n_positive"])
|
||||||
|
s += f"{row['dH']:>+13.3f}{row['nH']:>5}/{len(offs):<3}"
|
||||||
|
we = float(np.median([0.25 * m for m in MSCALE[nm]])) if nm in MSCALE else float("nan")
|
||||||
|
row["weff"] = we
|
||||||
|
s += f"{we:>9.3f}"
|
||||||
|
print(s)
|
||||||
|
res[nm] = row
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
MSCALE: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
def per_year(BK: dict, CT: dict, nm: str, offs) -> tuple:
|
||||||
|
yrs = sorted({int(y) for y in BK[nm][offs[0]].index.year})
|
||||||
|
cells = []
|
||||||
|
for y in yrs:
|
||||||
|
v = [sh(BK[nm][o][BK[nm][o].index.year == y]) - sh(CT[nm][o][CT[nm][o].index.year == y])
|
||||||
|
for o in offs if (BK[nm][o].index.year == y).sum() > 20]
|
||||||
|
cells.append(float(np.median(v)) if v else float("nan"))
|
||||||
|
return yrs, cells
|
||||||
|
|
||||||
|
|
||||||
|
# =========================================================================== main
|
||||||
|
def main() -> None:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--every", type=int, default=1)
|
||||||
|
args = ap.parse_args()
|
||||||
|
offs = OFFSETS_FULL[:: max(1, args.every)]
|
||||||
|
t0 = time.time()
|
||||||
|
L = 112
|
||||||
|
|
||||||
|
print("=" * L)
|
||||||
|
print(" r0822 VOL-SIZE — SCETTICO. Bersaglio: `VTL tv20 w90` = +0,070 ISO dShFULL (23/23)")
|
||||||
|
print("=" * L)
|
||||||
|
print(f" ancore: {len(offs)}/{len(OFFSETS_FULL)} della griglia a priori 02/07 {list(offs)}")
|
||||||
|
print(" lente: path CANONICO (identica nelle due braccia di ogni confronto). TP01 canonico.")
|
||||||
|
|
||||||
|
TP = A.tp01_baseline_daily()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ celle
|
||||||
|
IV = iv_family()
|
||||||
|
FUN_V2 = dict(IV)
|
||||||
|
FUN_V2["VTL tv20 w90 (LEAD)"] = size_vtl(FROZEN)
|
||||||
|
FUN_V2["BIN L>=1 (0/1)"] = size_bin(FROZEN, "ge1")
|
||||||
|
FUN_V2["BIN L>=med (0/1)"] = size_bin(FROZEN, "gemed")
|
||||||
|
FUN_V2["BIN L<1 (INVERSO)"] = size_bin(FROZEN, "lt1")
|
||||||
|
FUN_V2["BIN+GRAD"] = size_bin_grad(FROZEN)
|
||||||
|
FUN_V2["DROP attivita' >p75"] = size_dropact(90, 0.75)
|
||||||
|
FUN_V2["DROP attivita' >p50"] = size_dropact(90, 0.50)
|
||||||
|
FUN_V2["canale MAGNITUDINE cont."] = size_chan("mag", False)
|
||||||
|
FUN_V2["canale FREQUENZA cont."] = size_chan("freq", False)
|
||||||
|
FUN_V2["canale MAGNIT. 0/1"] = size_chan("mag", True)
|
||||||
|
FUN_V2["canale FREQ. 0/1"] = size_chan("freq", True)
|
||||||
|
WGRID = (15, 20, 30, 45, 60, 90, 120, 180, 270)
|
||||||
|
for w in WGRID:
|
||||||
|
if w != 90:
|
||||||
|
FUN_V2[f"VTL tv20 w{w}"] = size_vtl(dict(FROZEN, w=w))
|
||||||
|
FUN_V1 = dict(IV)
|
||||||
|
FUN_V1["VTL tv20 w90 (LEAD)"] = size_vtl(FROZEN)
|
||||||
|
FUN_V1["BIN L>=1 (0/1)"] = size_bin(FROZEN, "ge1")
|
||||||
|
|
||||||
|
NTRIAL = len(FUN_V2) + len(FUN_V1) + 2 # +2 baseline
|
||||||
|
print(f"\n GRIGLIA DICHIARATA: {len(FUN_V2)} celle su V2-DD + {len(FUN_V1)} su V1 "
|
||||||
|
f"+ 2 baseline = {NTRIAL} valutazioni x {len(offs)} ancore. Nessuna cella scelta")
|
||||||
|
print(" dopo aver guardato: IV e' la famiglia PRE-REGISTRATA, VTL/w e' l'asse dichiarato")
|
||||||
|
print(" fragile dall'agente, BIN/DROP sono controlli miei dichiarati sopra.")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ ciclo ancore
|
||||||
|
BK2, CT2, BK1, CT1 = {}, {}, {}, {}
|
||||||
|
EX0 = None
|
||||||
|
print("\n calcolo per ancora ...", flush=True)
|
||||||
|
for o in offs:
|
||||||
|
EXV2 = {a: V.extract(a, o) for a in ASSETS}
|
||||||
|
EXV1 = {a: V.extract(a, o, SKH01_V1) for a in ASSETS}
|
||||||
|
if o == offs[0]:
|
||||||
|
EX0 = (EXV2, EXV1)
|
||||||
|
for tag, EXX, FUN, BKd, CTd in (("V2", EXV2, FUN_V2, BK2, CT2),
|
||||||
|
("V1", EXV1, FUN_V1, BK1, CT1)):
|
||||||
|
lg = legs(EXX, FUN)
|
||||||
|
base = lg["BASE"]
|
||||||
|
for nm, s in lg.items():
|
||||||
|
b, c, m_s = iso_pair(TP, s, base)
|
||||||
|
BKd.setdefault(nm, {})[o] = b
|
||||||
|
CTd.setdefault(nm, {})[o] = c
|
||||||
|
if tag == "V2":
|
||||||
|
MSCALE.setdefault(nm, []).append(m_s)
|
||||||
|
if o != offs[0]:
|
||||||
|
del EXV2, EXV1
|
||||||
|
print(f" fatto in {time.time()-t0:.0f}s")
|
||||||
|
EXV2, EXV1 = EX0
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ §0 SANITY
|
||||||
|
print("\n" + "-" * L)
|
||||||
|
print(" 0. SANITY — replica BIT-EXACT prima di qualunque delta (se non e' 0, il problema")
|
||||||
|
print(" e' mio e va detto)")
|
||||||
|
print("-" * L)
|
||||||
|
from src.portfolio.sleeves import _skyhook_returns
|
||||||
|
mine = V.leg_daily(EXV2, {a: V.size_flat(EXV2[a]) for a in ASSETS})
|
||||||
|
off = _skyhook_returns()
|
||||||
|
d0 = float(np.max(np.abs(mine.values - off.values))) if len(mine) == len(off) else float("nan")
|
||||||
|
print(f" gamba 50/50 off{offs[0]} vs sleeves._skyhook_returns(): max|dif| = {d0:.2e} "
|
||||||
|
f"({len(mine)} giorni)")
|
||||||
|
assert d0 < 1e-15, "replica NON bit-exact"
|
||||||
|
for a in ASSETS:
|
||||||
|
ref = R.run_asset(a, offs[0])[0]
|
||||||
|
R._CACHE.clear()
|
||||||
|
dd = float(np.max(np.abs(V.equity_daily(EXV2[a], V.size_flat(EXV2[a])).values - ref.values)))
|
||||||
|
print(f" {a} vs r0702_anchor_skh01.run_asset: max|dif| = {dd:.2e}")
|
||||||
|
assert dd < 1e-15
|
||||||
|
|
||||||
|
lead = "VTL tv20 w90 (LEAD)"
|
||||||
|
dLEAD = A.anchor_luck_delta(lambda o: BK2[lead][o], lambda o: CT2[lead][o], offs, metric=sh)
|
||||||
|
dLEADH = A.anchor_luck_delta(lambda o: hold(BK2[lead][o]), lambda o: hold(CT2[lead][o]),
|
||||||
|
offs, metric=sh)
|
||||||
|
print(f" RIPRODUZIONE DEL TITOLO: ISO dShFULL {dLEAD['median_paired']:+.3f} "
|
||||||
|
f"({dLEAD['n_positive']}/{len(offs)}) ISO dShHOLD {dLEADH['median_paired']:+.3f} "
|
||||||
|
f"({dLEADH['n_positive']}/{len(offs)}) [pubblicato +0,070 (23/23) / +0,101 (19/23)]")
|
||||||
|
med_F = float(np.median([sh(BK2["BASE"][o]) for o in offs]))
|
||||||
|
med_H = float(np.median([sh(hold(BK2["BASE"][o])) for o in offs]))
|
||||||
|
med_D = float(np.median([maxdd(BK2["BASE"][o]) for o in offs]))
|
||||||
|
print(f" libro BASE (V2-DD), mediana della banda: ShFULL {med_F:.3f} / ShHOLD {med_H:.3f} "
|
||||||
|
f"/ maxDD {med_D*100:.1f}% [pubblicato 1,626 / 1,037 / 10,4%]")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ §1 CONTABILITA'
|
||||||
|
print("\n" + "-" * L)
|
||||||
|
print(" 1. LA CONTABILITA' — verifica della PREMESSA prima della riparazione")
|
||||||
|
print(" (l'agente: 'tutto il P&L di un trade e' contabilizzato il giorno di CHIUSURA')")
|
||||||
|
print("-" * L)
|
||||||
|
tot_ent = tot_ex = tot_chk = 0
|
||||||
|
for a in ASSETS:
|
||||||
|
ex = EXV2[a]
|
||||||
|
d_e = ex["idx"][ex["i_ent"]].floor("D")
|
||||||
|
d_x = ex["idx"][ex["i_ex"]].floor("D")
|
||||||
|
dl = ex["base_daily"]
|
||||||
|
multi = np.where(d_e.values != d_x.values)[0]
|
||||||
|
ne = nx = ck = 0
|
||||||
|
for k in multi:
|
||||||
|
n = float(ex["net"][k])
|
||||||
|
if abs(n) < 1e-6:
|
||||||
|
continue
|
||||||
|
ck += 1
|
||||||
|
ne += int(abs(float(dl.get(d_e[k], 0.0)) - n) < 1e-9)
|
||||||
|
nx += int(abs(float(dl.get(d_x[k], 0.0)) - n) < 1e-9)
|
||||||
|
print(f" {a}: {ck} trade multi-giorno -> ritorno giornaliero == net del trade "
|
||||||
|
f"al giorno d'INGRESSO {ne}/{ck}, al giorno di USCITA {nx}/{ck}")
|
||||||
|
tot_ent += ne; tot_ex += nx; tot_chk += ck
|
||||||
|
print(f" TOTALE: INGRESSO {tot_ent}/{tot_chk} ({tot_ent/max(tot_chk,1)*100:.1f}%) "
|
||||||
|
f"USCITA {tot_ex}/{tot_chk} ({tot_ex/max(tot_chk,1)*100:.1f}%)")
|
||||||
|
print(" -> `backtest_signals` fa `equity[i_ent:i_ex+1] = capital`: il gradino comincia")
|
||||||
|
print(" alla barra d'INGRESSO e il resample giornaliero (last) lo legge quel giorno.")
|
||||||
|
|
||||||
|
print("\n (a) TRONCAMENTO ALLA BARRA D'INGRESSO — le size della regola congelata "
|
||||||
|
"ricalcolate")
|
||||||
|
print(" con i soli dati <= barra d'ingresso devono coincidere ESATTAMENTE")
|
||||||
|
worst = 0.0
|
||||||
|
ntest = 0
|
||||||
|
for a in ASSETS:
|
||||||
|
ex = EXV2[a]
|
||||||
|
full = size_vtl(FROZEN)(ex)
|
||||||
|
sel = [k for k in range(len(ex["i_ent"])) if ex["i_ent"][k] >= V.SIG_WARM]
|
||||||
|
sel = sel[:: max(1, len(sel) // 25)][:25]
|
||||||
|
bad = 0
|
||||||
|
for k in sel:
|
||||||
|
i = int(ex["i_ent"][k])
|
||||||
|
# ricostruisco la gamba usando SOLO i trade gia' CHIUSI prima dell'ingresso k
|
||||||
|
sub = {kk: ex[kk] for kk in ("n", "idx")}
|
||||||
|
m = ex["i_ex"] < i
|
||||||
|
sub = dict(n=i + 1, idx=ex["idx"][: i + 1],
|
||||||
|
i_ent=ex["i_ent"][m], i_ex=ex["i_ex"][m], net=ex["net"][m])
|
||||||
|
if not len(sub["i_ent"]):
|
||||||
|
continue
|
||||||
|
bd = V.equity_daily(sub, np.ones(len(sub["i_ent"])))
|
||||||
|
Lt = V.leverage_series(bd, **FROZEN)
|
||||||
|
day = ex["idx"][i].floor("D")
|
||||||
|
v = Lt.reindex([day]).values[0]
|
||||||
|
v = 1.0 if not np.isfinite(v) else float(v)
|
||||||
|
worst = max(worst, abs(v - float(full[k])))
|
||||||
|
bad += int(abs(v - float(full[k])) > 1e-9)
|
||||||
|
ntest += 1
|
||||||
|
print(f" {a}: {len(sel)-bad}/{len(sel)} size identiche a dati troncati "
|
||||||
|
f"(divergenze {bad})")
|
||||||
|
print(f" max|dif| su {ntest} size ricalcolate da zero: {worst:.2e} -> "
|
||||||
|
f"{'CAUSALE' if worst < 1e-9 else 'NON CAUSALE'}")
|
||||||
|
|
||||||
|
print("\n (b) LA 'RIPARAZIONE' E' UN NO-OP: size-all'INGRESSO vs moltiplicare la SERIE")
|
||||||
|
print(" GIORNALIERA, a parita' di tutto il resto (stessa L, PER ASSET)")
|
||||||
|
for a in ASSETS:
|
||||||
|
ex = EXV2[a]
|
||||||
|
Ls = V.leverage_series(ex["base_daily"], **FROZEN)
|
||||||
|
s_in = V.equity_daily(ex, V.sizes_from_L(ex, Ls))
|
||||||
|
s_mul = ex["base_daily"] * Ls.reindex(ex["base_daily"].index).fillna(1.0)
|
||||||
|
j = pd.concat({"a": s_in, "b": s_mul}, axis=1, join="inner").dropna()
|
||||||
|
print(f" {a}: max|dif| = {np.max(np.abs(j['a']-j['b'])):.2e} "
|
||||||
|
f"Sh ingresso {sh(s_in):.4f} vs Sh moltiplicazione {sh(s_mul):.4f}")
|
||||||
|
|
||||||
|
print("\n (c) ALLORA COS'E' IL '+0,04 FANTASMA'? Il controllo NAIVE del bersaglio calcola L")
|
||||||
|
print(" sulla gamba 50/50 COMBINATA; la versione 'causale' la calcola PER ASSET.")
|
||||||
|
print(" Terza variante di controllo: L combinata, applicata come size all'INGRESSO.")
|
||||||
|
lg_base = {o: None for o in offs}
|
||||||
|
d_comb, d_perass = [], []
|
||||||
|
for o in offs[: min(6, len(offs))]:
|
||||||
|
E2 = {a: V.extract(a, o) for a in ASSETS}
|
||||||
|
base = V.leg_daily(E2, {a: V.size_flat(E2[a]) for a in ASSETS})
|
||||||
|
Lc = V.leverage_series(base, **FROZEN)
|
||||||
|
naive = base * Lc.reindex(base.index).fillna(1.0)
|
||||||
|
comb_in = V.leg_daily(E2, {a: V.sizes_from_L(E2[a], Lc) for a in ASSETS})
|
||||||
|
per_ass = V.leg_daily(
|
||||||
|
E2, {a: V.sizes_from_L(E2[a], V.leverage_series(E2[a]["base_daily"], **FROZEN))
|
||||||
|
for a in ASSETS})
|
||||||
|
j = pd.concat({"a": naive, "b": comb_in}, axis=1, join="inner").dropna()
|
||||||
|
d_comb.append(float(np.max(np.abs(j["a"] - j["b"]))))
|
||||||
|
b1, c1, _ = iso_pair(TP, naive, base)
|
||||||
|
b2, c2, _ = iso_pair(TP, per_ass, base)
|
||||||
|
d_perass.append(sh(hold(b1)) - sh(hold(c1)) - (sh(hold(b2)) - sh(hold(c2))))
|
||||||
|
del E2
|
||||||
|
print(f" L combinata: 'moltiplica la serie' vs 'size all'ingresso' -> "
|
||||||
|
f"max|dif| mediano {np.median(d_comb):.2e} (identiche)")
|
||||||
|
print(f" differenza ISO dShHOLD [L combinata] - [L per asset], mediana appaiata su "
|
||||||
|
f"{len(d_perass)} ancore: {np.median(d_perass):+.3f}")
|
||||||
|
print(" -> il divario NAIVE/CAUSALE non e' QUANDO si applica la size, e' SU QUALE")
|
||||||
|
print(" SERIE si calcola L (gamba combinata vs per asset).")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ §2 TEST PRE-REGISTRATO
|
||||||
|
print("\n" + "-" * L)
|
||||||
|
print(" 2. IL TEST PRE-REGISTRATO — famiglia IV su V2-DD (stop % FISSO) vs V1 (stop in ATR)")
|
||||||
|
print(" Previsione dichiarata dall'agente: |effetto su V1| < |effetto su V2-DD|.")
|
||||||
|
print("-" * L)
|
||||||
|
n2 = {a: len(EXV2[a]["i_ent"]) for a in ASSETS}
|
||||||
|
n1 = {a: len(EXV1[a]["i_ent"]) for a in ASSETS}
|
||||||
|
print(f" trade all'ancora {offs[0]}: V2-DD {n2} V1 {n1}")
|
||||||
|
mb2 = float(np.median([sh(BK2["BASE"][o]) for o in offs]))
|
||||||
|
mb1 = float(np.median([sh(BK1["BASE"][o]) for o in offs]))
|
||||||
|
print(f" libro BASE mediana di banda: V2-DD ShFULL {mb2:.3f} | V1 ShFULL {mb1:.3f}")
|
||||||
|
names_iv = list(IV)
|
||||||
|
print(f"\n {'cella IV':<22}{'V2-DD ISO dF':>14}{'pos/n':>8}{'V1 ISO dF':>12}{'pos/n':>8}"
|
||||||
|
f"{'|V1|<|V2|?':>12}")
|
||||||
|
ok = tot = 0
|
||||||
|
e2s, e1s = [], []
|
||||||
|
for nm in names_iv:
|
||||||
|
a2 = A.anchor_luck_delta(lambda o, n=nm: BK2[n][o], lambda o, n=nm: CT2[n][o], offs,
|
||||||
|
metric=sh)
|
||||||
|
a1 = A.anchor_luck_delta(lambda o, n=nm: BK1[n][o], lambda o, n=nm: CT1[n][o], offs,
|
||||||
|
metric=sh)
|
||||||
|
e2, e1 = a2["median_paired"], a1["median_paired"]
|
||||||
|
e2s.append(e2); e1s.append(e1)
|
||||||
|
good = abs(e1) < abs(e2)
|
||||||
|
ok += int(good); tot += 1
|
||||||
|
print(f" {nm:<22}{e2:>+14.3f}{a2['n_positive']:>5}/{len(offs):<3}{e1:>+12.3f}"
|
||||||
|
f"{a1['n_positive']:>5}/{len(offs):<3}{('SI' if good else 'NO'):>12}")
|
||||||
|
print(f"\n previsione verificata in {ok}/{tot} celle della famiglia")
|
||||||
|
print(f" |effetto| mediano di famiglia: V2-DD {np.median(np.abs(e2s)):.3f} "
|
||||||
|
f"V1 {np.median(np.abs(e1s)):.3f}")
|
||||||
|
print(f" effetto mediano SEGNATO: V2-DD {np.median(e2s):+.3f} V1 {np.median(e1s):+.3f}")
|
||||||
|
print(" ⚠️ POTENZA DEL TEST: se |effetto su V2-DD| e' gia' ~0, la previsione e' verificabile")
|
||||||
|
print(" solo per caso e il gate pre-registrato NON puo' falsificare niente.")
|
||||||
|
for nm in ("VTL tv20 w90 (LEAD)", "BIN L>=1 (0/1)"):
|
||||||
|
a2 = A.anchor_luck_delta(lambda o, n=nm: BK2[n][o], lambda o, n=nm: CT2[n][o], offs,
|
||||||
|
metric=sh)
|
||||||
|
a1 = A.anchor_luck_delta(lambda o, n=nm: BK1[n][o], lambda o, n=nm: CT1[n][o], offs,
|
||||||
|
metric=sh)
|
||||||
|
print(f" (riferimento) {nm:<24} V2-DD {a2['median_paired']:+.3f} "
|
||||||
|
f"({a2['n_positive']}/{len(offs)}) V1 {a1['median_paired']:+.3f} "
|
||||||
|
f"({a1['n_positive']}/{len(offs)})")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ §3 ATTACCO ALLA SPIEGAZIONE
|
||||||
|
print("\n" + "-" * L)
|
||||||
|
print(" 3. ATTACCO ALLA SPIEGAZIONE — quanto del +0,070 resta senza NESSUN vol-target?")
|
||||||
|
print(" Tutte ISO-PESO vs lo stesso baseline. 'wSKHeff' = peso effettivo di SKH nel libro")
|
||||||
|
print("-" * L)
|
||||||
|
grp = [lead, "BIN L>=1 (0/1)", "BIN+GRAD", "BIN L>=med (0/1)", "BIN L<1 (INVERSO)",
|
||||||
|
"DROP attivita' >p75", "DROP attivita' >p50",
|
||||||
|
"canale MAGNITUDINE cont.", "canale FREQUENZA cont.",
|
||||||
|
"canale MAGNIT. 0/1", "canale FREQ. 0/1"]
|
||||||
|
RES = delta_table(BK2, CT2, grp, offs, "attacco")
|
||||||
|
base_eff = RES[lead]["dF"]
|
||||||
|
print()
|
||||||
|
for nm in grp[1:]:
|
||||||
|
frac = RES[nm]["dF"] / base_eff * 100 if abs(base_eff) > 1e-9 else float("nan")
|
||||||
|
print(f" {nm:<26} recupera {frac:>6.0f}% del vantaggio del LEAD")
|
||||||
|
# quanti trade tocca ciascun filtro
|
||||||
|
print(f"\n {'cella':<26}{'trade tenuti':>14}{'su':>6}{'size mediana':>14}"
|
||||||
|
f"{'size = 0':>10}")
|
||||||
|
for nm in [lead] + grp[1:]:
|
||||||
|
kept = tot_t = zer = 0
|
||||||
|
med = []
|
||||||
|
for a in ASSETS:
|
||||||
|
s = FUN_V2[nm](EXV2[a])
|
||||||
|
tot_t += len(s); kept += int((s > 0).sum()); zer += int((s == 0).sum())
|
||||||
|
med.append(s)
|
||||||
|
mm = float(np.median(np.concatenate(med)))
|
||||||
|
print(f" {nm:<26}{kept:>14}{tot_t:>6}{mm:>14.2f}{zer:>10}")
|
||||||
|
|
||||||
|
print(f"\n COSA SELEZIONA il filtro (ancora {offs[0]}, i due asset uniti): net medio dei")
|
||||||
|
print(" trade TENUTI vs SCARTATI, e attivita' trailing media dei due gruppi")
|
||||||
|
print(f" {'cella':<26}{'net TENUTI':>12}{'net SCARTATI':>14}{'att.TEN':>10}{'att.SCA':>10}"
|
||||||
|
f"{'%tenuti':>9}")
|
||||||
|
for nm in ("BIN L>=1 (0/1)", "BIN L>=med (0/1)", "BIN L<1 (INVERSO)",
|
||||||
|
"DROP attivita' >p50", "canale MAGNIT. 0/1", "canale FREQ. 0/1"):
|
||||||
|
nk, nd, ak, ad = [], [], [], []
|
||||||
|
for a in ASSETS:
|
||||||
|
ex = EXV2[a]
|
||||||
|
szv = FUN_V2[nm](ex)
|
||||||
|
cnt = ((ex["base_daily"] != 0.0).astype(float)
|
||||||
|
.rolling(90, min_periods=30).sum().shift(1))
|
||||||
|
av = cnt.reindex(ex["day_ent"]).values
|
||||||
|
keep = szv > 0
|
||||||
|
nk += list(ex["net"][keep]); nd += list(ex["net"][~keep])
|
||||||
|
ak += list(av[keep]); ad += list(av[~keep])
|
||||||
|
f_ = 100.0 * len(nk) / max(len(nk) + len(nd), 1)
|
||||||
|
print(f" {nm:<26}{np.mean(nk)*100:>11.2f}%{np.mean(nd)*100 if nd else float('nan'):>13.2f}%"
|
||||||
|
f"{np.nanmean(ak):>10.1f}{np.nanmean(ad) if ad else float('nan'):>10.1f}{f_:>9.0f}")
|
||||||
|
|
||||||
|
print("\n per anno (ISO dSh, mediana appaiata sulle ancore):")
|
||||||
|
yrs = None
|
||||||
|
for nm in [lead, "BIN L>=1 (0/1)", "DROP attivita' >p75", "BIN L<1 (INVERSO)"]:
|
||||||
|
yrs, cells = per_year(BK2, CT2, nm, offs)
|
||||||
|
if nm == lead:
|
||||||
|
print(" " + f"{'':<26}" + "".join(f"{y:>8}" for y in yrs))
|
||||||
|
npos = int(np.nansum(np.asarray(cells) > 0))
|
||||||
|
print(f" {nm:<26}" + "".join(f"{c:>+8.2f}" for c in cells) +
|
||||||
|
f" anni positivi {npos}/{len(yrs)}")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ §4 ASSE DELLA FINESTRA
|
||||||
|
print("\n" + "-" * L)
|
||||||
|
print(" 4. L'ASSE DELLA FINESTRA — picco o plateau? + scelta di w IN-SAMPLE-ONLY")
|
||||||
|
print("-" * L)
|
||||||
|
wnames = [(w, f"VTL tv20 w{w}" if w != 90 else lead) for w in WGRID]
|
||||||
|
print(f" {'w':>6}{'ISO dShFULL':>14}{'pos/n':>8}{'ISO dShIS':>12}{'ISO dShHOLD':>13}"
|
||||||
|
f"{'pos/n':>8}")
|
||||||
|
wf, wi, wh = {}, {}, {}
|
||||||
|
for w, nm in wnames:
|
||||||
|
dF = A.anchor_luck_delta(lambda o, n=nm: BK2[n][o], lambda o, n=nm: CT2[n][o], offs,
|
||||||
|
metric=sh)
|
||||||
|
dI = A.anchor_luck_delta(lambda o, n=nm: ins(BK2[n][o]), lambda o, n=nm: ins(CT2[n][o]),
|
||||||
|
offs, metric=sh)
|
||||||
|
dH = A.anchor_luck_delta(lambda o, n=nm: hold(BK2[n][o]), lambda o, n=nm: hold(CT2[n][o]),
|
||||||
|
offs, metric=sh)
|
||||||
|
wf[w], wi[w], wh[w] = dF["median_paired"], dI["median_paired"], dH["median_paired"]
|
||||||
|
print(f" {w:>6}{wf[w]:>+14.3f}{dF['n_positive']:>5}/{len(offs):<3}{wi[w]:>+12.3f}"
|
||||||
|
f"{wh[w]:>+13.3f}{dH['n_positive']:>5}/{len(offs):<3}")
|
||||||
|
wbest_full = max(WGRID, key=lambda w: wf[w])
|
||||||
|
wbest_is = max(WGRID, key=lambda w: wi[w])
|
||||||
|
print(f"\n argmax su FULL (cio' che l'agente ha pubblicato): w = {wbest_full} "
|
||||||
|
f"({wf[wbest_full]:+.3f})")
|
||||||
|
print(f" argmax IN-SAMPLE-ONLY (la sola scelta ammessa): w = {wbest_is} "
|
||||||
|
f"(IS {wi[wbest_is]:+.3f}) -> quella cella vale FULL {wf[wbest_is]:+.3f} / "
|
||||||
|
f"HOLD {wh[wbest_is]:+.3f}")
|
||||||
|
order = sorted(WGRID)
|
||||||
|
k = order.index(wbest_full)
|
||||||
|
nb = [order[k - 1] if k > 0 else None, order[k + 1] if k + 1 < len(order) else None]
|
||||||
|
for w in nb:
|
||||||
|
if w is not None:
|
||||||
|
print(f" vicino w={w:<4} vale {wf[w]:+.3f} = {wf[w]/wf[wbest_full]*100:>5.0f}% "
|
||||||
|
f"del picco")
|
||||||
|
pos = sum(1 for w in WGRID if wf[w] > 0)
|
||||||
|
print(f" celle con ISO dShFULL > 0: {pos}/{len(WGRID)} "
|
||||||
|
f"spread della famiglia {max(wf.values())-min(wf.values()):.3f} "
|
||||||
|
f"sd fra celle {np.std(list(wf.values()), ddof=1):.3f}")
|
||||||
|
print(f" stima ONESTA se w e' SCELTO (mediana della famiglia, come per la banda "
|
||||||
|
f"d'ancora): {np.median(list(wf.values())):+.3f} FULL / "
|
||||||
|
f"{np.median(list(wh.values())):+.3f} HOLD contro il picco "
|
||||||
|
f"{wf[wbest_full]:+.3f} / {wh[wbest_full]:+.3f}")
|
||||||
|
rho = float(np.corrcoef([wi[w] for w in WGRID], [wh[w] for w in WGRID])[0, 1])
|
||||||
|
print(f" corr(ISO dSh in-sample, ISO dSh hold-out) fra le {len(WGRID)} finestre: {rho:+.2f}")
|
||||||
|
print(" -> se e' ~0 o negativa, la scelta di w in-sample NON predice l'hold-out: l'asse")
|
||||||
|
print(" e' rumore e il picco a w=90 e' un max-of-k.")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ §5 sintesi
|
||||||
|
print("\n" + "-" * L)
|
||||||
|
print(" 5. SINTESI DEI NUMERI CHE CONTANO")
|
||||||
|
print("-" * L)
|
||||||
|
print(f" fortuna d'ancora del LIBRO BASE: ShFULL canonico "
|
||||||
|
f"{sh(BK2['BASE'][0]) if 0 in BK2['BASE'] else float('nan'):.3f} vs mediana "
|
||||||
|
f"{med_F:.3f}")
|
||||||
|
print(f" vantaggio del LEAD (ISO): {dLEAD['median_paired']:+.3f} FULL / "
|
||||||
|
f"{dLEADH['median_paired']:+.3f} HOLD")
|
||||||
|
print(f" riprodotto dal FILTRO BINARIO: {RES['BIN L>=1 (0/1)']['dF']:+.3f} FULL / "
|
||||||
|
f"{RES['BIN L>=1 (0/1)']['dH']:+.3f} HOLD")
|
||||||
|
print(f" controllo di segno (INVERSO): {RES['BIN L<1 (INVERSO)']['dF']:+.3f} FULL")
|
||||||
|
print(f"\n fatto in {time.time()-t0:.0f}s")
|
||||||
|
print("=" * L)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user