merge: rivalutazione strategia — 75/25 confermato, nessun cambio
This commit is contained in:
@@ -989,6 +989,41 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis
|
||||
**l'outer-join con referenze di lunghezza diversa e' una trappola ricorrente**); (f) un controllo
|
||||
positivo finito dentro il ramo `else` **non gira mai** (trovato in sessione: era esattamente il
|
||||
difetto che doveva prevenire).
|
||||
- ⚖️ **RIVALUTAZIONE DELLA STRATEGIA (2026-07-26, fine giornata) — 0 cambi, e il peso 75/25
|
||||
confermato per la TERZA volta.** Script `r0726_reeval_live_weight.py`, test
|
||||
`tests/test_reeval_live_weight.py` (6), diario `2026-07-26-reeval-strategia.md`.
|
||||
**Book, pesi, cron, config INVARIATI.**
|
||||
(1) **Solo UNA delle sei misure del giorno apriva una decisione**, e per una ragione precisa: il
|
||||
peso 75/25 fu confermato il 24/07 con la lente **hourly**, che il 26/07 e' risultata pessimistica
|
||||
su ENTRAMBI i lati di SKH01 (uscite +0.081, ingressi +0.048 di Sharpe di book) → *se SKH01 vale
|
||||
piu' di come e' stato pesato, 0.25 e' ancora giusto?* Contro tirava il LOO de-luckato dello stesso
|
||||
giorno (SKH01 = il meno affidabile dei cinque). **Due correzioni in versi opposti: si misura, non
|
||||
si deduce.**
|
||||
(2) **Misura** (path live `intra_entry=True`, 8 offset, mediana delle differenze appaiate):
|
||||
argmax a **w=0.35** e 0.30-0.40 batte 0.25 nell'**88%** degli offset — il segnale c'e' ed e' nel
|
||||
verso previsto. Ma **plateau entro 0.05 = [0.25...0.50]** (il peso live e' dentro) e
|
||||
**`weights_tilt_null` FALLISCE** (delta_insample −0.0026, gate_pass False).
|
||||
(3) ⚠️ **Il motivo vero sta in cio' che la mediana nasconde: il guadagno e' tutto nella coda
|
||||
ALTA.** p10 per peso: 1.463 (0.25) / 1.465 (0.30) / 1.455 (0.35) / 1.435 (0.40) / 1.374 (0.50)
|
||||
mentre il p90 sale monotono 1.915→2.064. **Alzare SKH01 non compra Sharpe, compra dipendenza da
|
||||
quale ancora ti e' capitata** (banda da 0.45 a 0.69). Coerente con altre due misure indipendenti:
|
||||
SKH01 ha la frazione d'ancora piu' grande da restituire (LOO) ed e' **4x** piu' fee-sensibile
|
||||
(curva fee). **Tre misure indipendenti: SKH01 e' la gamba fragile, e 0.25 sta all'estremo prudente
|
||||
della regione robusta.**
|
||||
(4) 📌 **La rivalutazione che conta non e' sulla strategia.** Ordini di grandezza a confronto:
|
||||
ottimizzare il peso = **+0.030 Sharpe** (gate fallito); versare **€250/mese invece di €0** = da
|
||||
**mai** a **16.2 anni** (P(entro 20a) 92%); perdere il conto Deribit a p=1% = **−18%** di
|
||||
probabilita' di arrivare, ripartendo da zero. **Il book e' dentro il suo plateau su ogni asse
|
||||
misurato: la ricerca ha smesso di essere il vincolo binding.** I vincoli binding oggi sono
|
||||
**capitale che entra** e **conto che non sparisce**. (Non significa che la ricerca sia finita —
|
||||
significa che il margine residuo vale centesimi di Sharpe contro leve che valgono il risultato.)
|
||||
**REGOLE:** (a) una rivalutazione parte dall'elenco di cosa **cambia una decisione**, non dal
|
||||
riassunto di cosa si e' scoperto; (b) quando due correzioni puntano in versi opposti **si misura**
|
||||
(a occhio si poteva argomentare qualunque cosa); (c) **un argmax dentro un plateau non e' una
|
||||
decisione**, e la mediana da sola puo' nascondere il fatto decisivo (qui: p90 su, p10 giu');
|
||||
(d) quando le leve hanno ordini di grandezza diversi **dirlo**, o si lavora molto senza cambiare
|
||||
niente; (e) rivalutare **non** vuol dire anticipare i gate pre-registrati — anticiparli e'
|
||||
selezione sull'hold-out.
|
||||
- 📅 **NUOVO SCHEMA FEE DERIBIT dal 2026-08-01 — misurata la CURVA, nessuna azione oggi
|
||||
(2026-07-26).** Script `r0726_fee_sensitivity.py`, test `tests/test_fee_sensitivity.py` (6),
|
||||
diario `2026-07-26-fee-deribit.md`. **Book/pesi/cron/config INVARIATI.**
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
# 2026-07-26 — Rivalutazione della strategia dopo le correzioni del giorno
|
||||
|
||||
**Richiesta:** *"in base a quanto definito rivalutiamo la strategia"*.
|
||||
|
||||
**Script:** `r0726_reeval_live_weight.py`. **Test:** `tests/test_reeval_live_weight.py` (6).
|
||||
**Book, pesi, cron, config: INVARIATI.**
|
||||
|
||||
---
|
||||
|
||||
## 0. Cosa c'era da rivalutare davvero
|
||||
|
||||
La giornata ha prodotto sei misure che toccano la strategia. Solo **una** apriva una decisione:
|
||||
|
||||
| misura di oggi | cambia una decisione? |
|
||||
|---|---|
|
||||
| fattore de-luck ×0.6 → ×0.89 | no: cambia i *numeri* (muro −45%), non cosa si fa |
|
||||
| muro come punto fisso ($273.900) | no: la mia previsione era sbagliata, il piano resta |
|
||||
| LOO de-luckato (SKH01 il meno affidabile) | **forse**: e' un argomento per *ridurre* SKH01 |
|
||||
| path live SKH01 migliore su entrambi i lati | **forse**: argomento opposto, per *aumentarlo* |
|
||||
| curva fee (SKH01 4x piu' sensibile) | no oggi: regola gia' decisa per il 1 agosto |
|
||||
| rischio venue + tripwire | no: decisione dell'operatore gia' registrata |
|
||||
|
||||
Le due righe "forse" puntano in **versi opposti** e nessuna delle due si puo' dedurre a tavolino.
|
||||
Il peso 75/25 fu confermato il **24/07** con la lente *hourly* — la stessa che il 26/07 e' risultata
|
||||
pessimistica su entrambi i lati di SKH01 (uscite **+0.081** Sharpe FULL di book, ingressi **+0.048**).
|
||||
Quindi la domanda e' legittima: **se SKH01 vale piu' di come e' stato pesato, 0.25 e' ancora giusto?**
|
||||
|
||||
## 1. La misura
|
||||
|
||||
Path live vero (`intra_entry=True`), 8 offset del sottocampione a priori, statistica = mediana
|
||||
delle differenze **appaiate** per offset.
|
||||
|
||||
```
|
||||
peso SKH Sh FULL med banda p10-p90 Sh HOLD med d vs 0.25 (appaiata)
|
||||
0.00 1.289 [1.289, 1.289] 0.296 -0.322 ( 12%)
|
||||
0.15 1.523 [1.425, 1.714] 0.705 -0.091 ( 12%)
|
||||
0.20 1.575 [1.450, 1.825] 0.825 -0.038 ( 12%)
|
||||
0.25 1.611 [1.463, 1.915] 0.928 +0.000 ( 0%) <- LIVE
|
||||
0.30 1.632 [1.465, 1.982] 1.012 +0.022 ( 88%)
|
||||
0.35 1.639 [1.455, 2.028] 1.078 +0.030 ( 88%)
|
||||
0.40 1.634 [1.435, 2.055] 1.127 +0.025 ( 88%)
|
||||
0.50 1.595 [1.374, 2.064] 1.188 -0.013 ( 38%)
|
||||
```
|
||||
|
||||
**Argmax a w=0.35**, e in **88% degli offset** 0.30-0.40 batte 0.25: il segnale c'e' ed e' nel verso
|
||||
previsto dalla correzione di lente. Ma:
|
||||
|
||||
* **plateau entro 0.05 di Sharpe = [0.25, 0.30, 0.35, 0.40, 0.50]** — il peso live e' dentro, quindi
|
||||
la differenza col massimo non e' distinguibile;
|
||||
* **`weights_tilt_null` FALLISCE** (`delta_insample −0.0026`, `gate_pass False`);
|
||||
* e soprattutto la lettura che la mediana da sola nasconde ⬇.
|
||||
|
||||
## 2. Il motivo vero per non toccarlo: il guadagno e' tutto nella coda ALTA
|
||||
|
||||
```
|
||||
peso p10 mediana p90
|
||||
0.25 1.463 1.611 1.915
|
||||
0.30 1.465 1.632 1.982
|
||||
0.35 1.455 1.639 2.028
|
||||
0.40 1.435 1.634 2.055
|
||||
0.50 1.374 1.595 2.064
|
||||
```
|
||||
|
||||
Il **p10 e' piatto fino a 0.30 e poi SCENDE**, mentre il p90 sale monotono. Alzare il peso di SKH01
|
||||
non compra Sharpe: compra **dipendenza da quale ancora ti e' capitata**. La banda si allarga da
|
||||
0.45 (a w=0.25) a 0.69 (a w=0.50).
|
||||
|
||||
E' coerente con il leave-one-out de-luckato dello stesso giorno — SKH01 e' lo sleeve con la
|
||||
frazione d'ancora piu' grande da restituire — e con la curva fee, dove SKH01 e' **4x** piu'
|
||||
sensibile di TP01. Tre misure indipendenti dicono la stessa cosa: **SKH01 e' la gamba fragile del
|
||||
book, e 0.25 sta all'estremo prudente della regione robusta.**
|
||||
|
||||
**→ Nessun cambio. Il 75/25 e' confermato per la terza volta** (24/07 lente hourly, 26/07 path live
|
||||
intra-bin, e ora il gate sui pesi).
|
||||
|
||||
## 3. La rivalutazione che conta non e' sulla strategia
|
||||
|
||||
Messi in fila, gli ordini di grandezza di oggi:
|
||||
|
||||
| leva | effetto misurato |
|
||||
|---|---|
|
||||
| ottimizzare il peso SKH01 (0.25 → 0.35) | **+0.030 Sharpe**, gate fallito |
|
||||
| versare €250/mese invece di €0 | da **mai** a **16.2 anni** (mediana), P(entro 20a) 92% |
|
||||
| perdere il conto Deribit (p=1%, 20 anni) | **−18%** di probabilita' di arrivare, e si riparte da zero |
|
||||
|
||||
**L'esito del piano non e' piu' governato dalla strategia.** Il book e' dentro il suo plateau su
|
||||
ogni asse misurato; il margine residuo di ricerca vale centesimi di Sharpe, mentre i versamenti
|
||||
valgono la differenza fra "mai" e "16 anni" e il rischio di venue vale il 18% del risultato.
|
||||
|
||||
Questo **non** dice che la ricerca sia finita — dice che la ricerca ha smesso di essere il vincolo
|
||||
binding. Il vincolo binding oggi e' **capitale che entra** e **conto che non sparisce**.
|
||||
|
||||
## 4. Cio' che la rivalutazione NON cambia
|
||||
|
||||
* **La strategia resta quella**: TP01 0.75 / SKH01 0.25 su Deribit, cron orario, cap 1.0x.
|
||||
* **I gate pre-registrati restano alle loro date** (1 ago fee, 27 set STATARB, 23 ott XSR01,
|
||||
24 ott kill DVOLSPREAD, 24 gen 2027 decisione DVOLSPREAD). Rivalutare non vuol dire anticiparli:
|
||||
anticipare un gate e' selezione sull'hold-out.
|
||||
* **La decisione venue resta $20k**, con il tripwire a coprire la parte di rischio che ha una
|
||||
finestra.
|
||||
|
||||
## 5. Regole trasferibili
|
||||
|
||||
1. **Una rivalutazione onesta parte dall'elenco di cosa CAMBIA una decisione**, non dal riassunto
|
||||
di cosa si e' scoperto. Sei misure, una sola apriva qualcosa.
|
||||
2. **Quando due correzioni puntano in versi opposti, si misura**: qui l'una (lente pessimistica)
|
||||
spingeva ad alzare SKH01, l'altra (LOO de-luckato) ad abbassarlo, e a occhio si sarebbe potuto
|
||||
argomentare qualunque cosa.
|
||||
3. **Un argmax dentro un plateau non e' una decisione.** E la mediana da sola nascondeva il fatto
|
||||
decisivo: il guadagno stava tutto nel p90, il p10 peggiorava.
|
||||
4. **Quando le leve hanno ordini di grandezza diversi, dirlo.** +0.030 di Sharpe e "da mai a 16
|
||||
anni" non sono la stessa categoria di scelta, e trattarle come tali e' il modo piu' comune di
|
||||
lavorare molto senza cambiare niente.
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python
|
||||
"""r0726_reeval_live_weight.py — il peso 75/25 regge dopo le correzioni di oggi?
|
||||
|
||||
PERCHE' RIAPRIRLO. Il peso del book live (TP01 0.75 / SKH01 0.25) fu confermato il **24/07**
|
||||
(`r0724_skh_live_weight.py`) misurando il path live con la lente **hourly**. Il 26/07 quella lente
|
||||
e' risultata **sistematicamente pessimistica su ENTRAMBI i lati** di SKH01:
|
||||
* USCITE: `resample_5m` non scarta il bin in corso e `_skyhook_positions` ci itera dentro -> il
|
||||
live rileva SL/TP intra-barra; la lente hourly sottostima **+0.081 Sharpe FULL di book**;
|
||||
* INGRESSI: il cron valuta a ogni confine orario dentro il bin 230m, non a chiusura di bin ->
|
||||
mediana appaiata **+0.112** a livello di sleeve, **+0.048** di Sharpe FULL di book.
|
||||
Se SKH01 vale piu' di come e' stato pesato, **l'ottimo potrebbe non essere piu' 0.25**. E in senso
|
||||
opposto tira il leave-one-out de-luckato dello stesso giorno, che lo da' come il **meno affidabile**
|
||||
dei cinque sleeve (+0.142 FULL, positivo nel 91% delle estrazioni, contro +0.390/100% di TP01).
|
||||
|
||||
Due correzioni misurate che puntano in versi OPPOSTI: la domanda non e' retorica, e la risposta non
|
||||
si puo' dedurre. Qui si misura.
|
||||
|
||||
METODO
|
||||
* path live vero: `intra_entry=True` (ingressi intra-bin) sui segnali gia' in cache;
|
||||
* griglia di peso 0.00-0.50, su 8 offset (sottocampione a priori `OFFSETS_SUB`);
|
||||
* statistica = **mediana delle differenze APPAIATE** per offset (mai differenza di mediane);
|
||||
* qualunque cambio proposto passa poi da `weights_tilt_null`, che e' il gate del progetto.
|
||||
|
||||
uv run python scripts/research/r0726_reeval_live_weight.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
import r0726_skh_live_book as LB # noqa: E402
|
||||
import r0726_skh_sigcache as SC # noqa: E402
|
||||
from src.portfolio.portfolio import (combine_outer, metrics, # noqa: E402
|
||||
to_daily, weights_tilt_null)
|
||||
from src.portfolio.sleeves import tp01_sleeve # noqa: E402
|
||||
|
||||
W_GRID = (0.00, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.50)
|
||||
W_LIVE = 0.25
|
||||
HOLDOUT = pd.Timestamp("2025-01-01", tz="UTC")
|
||||
|
||||
|
||||
def _sh(s: pd.Series) -> float:
|
||||
v = s.dropna()
|
||||
return float(v.mean() / v.std() * np.sqrt(365)) if len(v) > 2 and v.std() > 0 else 0.0
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 100)
|
||||
print(" r0726 — RIVALUTAZIONE del peso live TP01/SKH01 dopo le correzioni del 26/07")
|
||||
print("=" * 100)
|
||||
print(" Il 24/07 confermo' 0.25 con la lente HOURLY. Oggi quella lente e' misurata")
|
||||
print(" pessimistica su entrambi i lati di SKH01 (uscite +0.081, ingressi +0.048 di book).")
|
||||
print(" Contro: il LOO de-luckato da' SKH01 come il MENO affidabile dei cinque.")
|
||||
|
||||
have = [o for o in SC.OFFSETS_SUB
|
||||
if (SC.CACHE_DIR / f"sigtab_btc_off{o:03d}.parquet").exists()
|
||||
and (SC.CACHE_DIR / f"sigtab_eth_off{o:03d}.parquet").exists()]
|
||||
print(f"\n offset disponibili in cache: {have} ({len(have)}/23 della griglia piena)")
|
||||
if len(have) < 4:
|
||||
print(" ✋ troppo pochi offset: rilanciare r0726_skh_sigcache.py")
|
||||
return
|
||||
|
||||
tp = tp01_sleeve(weight=1.0).daily()
|
||||
|
||||
# ---- per ogni offset, il path LIVE di SKH01 e le curve di Sharpe al variare del peso
|
||||
rows_full, rows_hold = {w: [] for w in W_GRID}, {w: [] for w in W_GRID}
|
||||
for off in have:
|
||||
dailies = {}
|
||||
for a in ("BTC", "ETH"):
|
||||
dailies[a] = LB.paths_for(a, off)["live"]
|
||||
skh = LB.skh_book(dailies)
|
||||
for w in W_GRID:
|
||||
bk = combine_outer({"TP01": tp, "SKH01": skh}, {"TP01": 1.0 - w, "SKH01": w})
|
||||
rows_full[w].append(_sh(bk))
|
||||
rows_hold[w].append(_sh(bk[bk.index >= HOLDOUT]))
|
||||
|
||||
print(f"\n {'peso SKH':>9}{'Sh FULL med':>13}{'banda p10-p90':>22}"
|
||||
f"{'Sh HOLD med':>13}{'d vs 0.25 (appaiata)':>23}")
|
||||
base_f = np.array(rows_full[W_LIVE])
|
||||
base_h = np.array(rows_hold[W_LIVE])
|
||||
best_f, best_w = -9e9, None
|
||||
for w in W_GRID:
|
||||
f = np.array(rows_full[w])
|
||||
h = np.array(rows_hold[w])
|
||||
dpair = np.median(f - base_f) # mediana delle DIFFERENZE, non differenza di mediane
|
||||
mf = float(np.median(f))
|
||||
if mf > best_f:
|
||||
best_f, best_w = mf, w
|
||||
tag = " <- LIVE" if w == W_LIVE else ""
|
||||
print(f" {w:>9.2f}{mf:>13.3f}"
|
||||
f" [{np.percentile(f,10):>6.3f},{np.percentile(f,90):>6.3f}]"
|
||||
f"{float(np.median(h)):>13.3f}{dpair:>+15.3f}"
|
||||
f" ({(f>base_f).mean()*100:>3.0f}%){tag}")
|
||||
|
||||
print(f"\n argmax della mediana FULL: w = {best_w:.2f} (live = {W_LIVE:.2f})")
|
||||
|
||||
# ---- plateau: un argmax senza plateau e' rumore
|
||||
near = [w for w in W_GRID if np.median(rows_full[w]) >= best_f - 0.05]
|
||||
print(f" plateau entro 0.05 di Sharpe: {near}")
|
||||
if W_LIVE in near:
|
||||
print(f" -> il peso live E' dentro il plateau: la differenza col massimo non e'")
|
||||
print(f" distinguibile, e cambiare peso su questa base sarebbe rumore.")
|
||||
else:
|
||||
print(f" -> il peso live e' FUORI dal plateau: il cambio va valutato dal gate.")
|
||||
|
||||
# ---- gate del progetto su un eventuale cambio
|
||||
if best_w != W_LIVE:
|
||||
print(f"\n GATE `weights_tilt_null` sul cambio {W_LIVE:.2f} -> {best_w:.2f}:")
|
||||
off0 = have[0]
|
||||
skh0 = LB.skh_book({a: LB.paths_for(a, off0)["live"] for a in ("BTC", "ETH")})
|
||||
res = weights_tilt_null({"TP01": to_daily(tp), "SKH01": to_daily(skh0)},
|
||||
{"TP01": 1 - W_LIVE, "SKH01": W_LIVE},
|
||||
{"TP01": 1 - best_w, "SKH01": best_w},
|
||||
k_seen=len(W_GRID))
|
||||
for k in ("delta_insample", "pctl_hold", "gate_pass"):
|
||||
if k in res:
|
||||
print(f" {k:<16} {res[k]}")
|
||||
print(f"\n ⚠️ Anche a gate passato servirebbero finestre OOS disgiunte e un controllo di")
|
||||
print(f" eseguibilita' al peso aumentato: il gate e' NECESSARIO, non sufficiente.")
|
||||
else:
|
||||
print(f"\n Nessun cambio da valutare: l'argmax coincide col peso live.")
|
||||
|
||||
# ---- sensibilita' alla fee, che il 1 agosto puo' cambiare
|
||||
print(f"\n PROMEMORIA fee (r0726_fee_sensitivity): SKH01 e' ~4x piu' sensibile di TP01")
|
||||
print(f" (-0.69% vs -0.09% di CAGR per bps/lato). La regola gia' decisa resta:")
|
||||
print(f" taker <=5bps -> non si tocca nulla; >10bps -> rivedere PRIMA il peso di SKH01.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,73 @@
|
||||
"""Test della rivalutazione del peso live (scripts/research/r0726_reeval_live_weight.py).
|
||||
|
||||
Il punto del file non e' il numero: e' che il peso live resti **dentro il plateau** e che ogni
|
||||
proposta di cambio passi da `weights_tilt_null`. Se un domani qualcuno alza SKH01 senza il gate,
|
||||
questi test lo dicono.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
import r0726_reeval_live_weight as RW # noqa: E402
|
||||
|
||||
|
||||
def test_il_peso_live_e_nella_griglia_valutata():
|
||||
"""Una griglia che non contiene il peso corrente non puo' dire se cambiarlo."""
|
||||
assert RW.W_LIVE in RW.W_GRID
|
||||
|
||||
|
||||
def test_la_griglia_copre_entrambi_i_lati_del_peso_live():
|
||||
"""Se la griglia fosse solo verso l'alto, l'argmax sarebbe garantito >= live per costruzione."""
|
||||
assert min(RW.W_GRID) < RW.W_LIVE < max(RW.W_GRID)
|
||||
|
||||
|
||||
def test_il_peso_della_griglia_coincide_col_book_di_produzione():
|
||||
"""W_LIVE deve essere il peso VERO del book Deribit, non una costante scollegata."""
|
||||
from src.live.book import W_SKH, W_TP01
|
||||
|
||||
assert RW.W_LIVE == pytest.approx(W_SKH)
|
||||
assert W_TP01 + W_SKH == pytest.approx(1.0)
|
||||
|
||||
|
||||
def test_la_statistica_e_la_mediana_delle_differenze_appaiate():
|
||||
"""Lezione 26/07: fra offset si usa la mediana delle DIFFERENZE, non la differenza delle
|
||||
mediane (che confronta offset diversi). Su dati costruiti le due risposte divergono."""
|
||||
# Serve che l'ORDINAMENTO differisca fra le due serie, altrimenti le due statistiche
|
||||
# coincidono per caso (prima stesura di questo test: coincidevano, e non provava niente).
|
||||
a = np.array([1.0, 5.0, 6.0])
|
||||
b = np.array([4.0, 2.0, 3.0])
|
||||
appaiata = float(np.median(a - b)) # [-3, +3, +3] -> +3
|
||||
delle_mediane = float(np.median(a) - np.median(b)) # 5 - 3 -> +2
|
||||
assert appaiata == pytest.approx(3.0)
|
||||
assert delle_mediane == pytest.approx(2.0)
|
||||
assert appaiata != pytest.approx(delle_mediane)
|
||||
|
||||
|
||||
def test_gate_tilt_null_rifiuta_un_cambio_che_perde_in_sample():
|
||||
"""Controllo POSITIVO sul gate: un tilt costruito per perdere pre-holdout deve fallire.
|
||||
Un gate che approva tutto e' inutile quanto uno che rifiuta tutto."""
|
||||
from src.portfolio.portfolio import weights_tilt_null
|
||||
|
||||
idx = pd.date_range("2019-01-01", periods=1500, freq="D", tz="UTC")
|
||||
rng = np.random.default_rng(7)
|
||||
buona = pd.Series(rng.normal(0.0008, 0.01, len(idx)), index=idx)
|
||||
cattiva = pd.Series(rng.normal(-0.0008, 0.02, len(idx)), index=idx)
|
||||
res = weights_tilt_null({"BUONA": buona, "CATTIVA": cattiva},
|
||||
{"BUONA": 0.75, "CATTIVA": 0.25},
|
||||
{"BUONA": 0.35, "CATTIVA": 0.65}, k_seen=9)
|
||||
assert res["gate_pass"] is False
|
||||
|
||||
|
||||
def test_promemoria_fee_coerente_con_la_regola_gia_decisa():
|
||||
"""La soglia d'azione sulla fee vive in un solo posto: se cambia li', questo test la segue."""
|
||||
src = (ROOT / "scripts" / "research" / "r0726_reeval_live_weight.py").read_text()
|
||||
assert "taker <=5bps" in src and ">10bps" in src
|
||||
Reference in New Issue
Block a user