research(sol): SOL come terza gamba direzionale — SCARTATO, il guadagno e' un anno solo
Domanda dell'operatore dopo "hai gia' analizzato XRP/SOL/UNI?": si', ma sempre
dentro panieri cross-sectional su Hyperliquid. SOL e' l'UNICO dei tre eseguibile su
Deribit e TP01/SKH01 non erano mai stati misurati su di lui. Ipotesi a priori
registrata PRIMA di guardare: diluisce (trend multi-asset 19/06, corr 0.74).
Confermata.
DATO. Storico ricostruito da Deribit mainnet (SOL/USDC:USDC, 466.776 barre 5m dal
2022-03-15, 0 gap, resample maxΔ 0.00bps) e certificato: >1% da Coinbase nell'1.3%
(2022) e 0.6% (2023) delle barre, med 8.1 -> 3.9 bps dal 2022 al 2026; flat 1h 0.2%,
5m 20.8%. Conferma esatta del verdetto del 19/06. Da qui DUE LENTI dichiarate prima
di misurare: L-FULL (2022-03+) e L-PULITA (2024+).
GAMBE SOLE, meccanismi CONGELATI (nessuna ri-ottimizzazione su SOL): TP01 SOL Sh 0.91
con hold-out -0.24; SKH01 SOL Sh 0.51 con maxDD 40.5%. Quest'ultimo non e' un
dettaglio: SKH01-V2-DD fu SELEZIONATA il 23/06 sul criterio maxDD<30% (BTC 21%,
ETH 27%) -> su un asset nuovo fallisce il criterio per cui la variante esiste.
BOOK, 24 ancore, mediana delle differenze APPAIATE: dSharpe hold-out -0.166 e >0 in
0/24 ancore in ENTRAMBE le lenti. L-PULITA: dSharpe FULL -0.099 (0/24), dCAGR -1.34pp
(0/24). L-FULL: dSharpe FULL +0.094 (24/24) ma dCAGR +0.10pp.
IL DD SCENDE MA E' DE-LEVERING (5a occorrenza dopo VRP-DD, TP01xDVOL, MAT01, azioni
intere UCITS): a pari maxDD, su L-PULITA basta k=0.886 sul book a 2 gambe per avere
Sharpe 1.54 contro 1.30 e CAGR 14.5% contro 12.6%. L'unica lente in cui SOL aggiunge
e' quella costruita sui dati che la certificazione segnala.
E DENTRO QUELLA LENTE IL GUADAGNO E' UN ANNO: dSh 2022 -0.91 / 2023 +1.06 / 2024
-0.29 / 2025 -0.20 / 2026 -0.22 = 4 anni su 5 negativi. La gamba SOL da sola fa
Sh -1.99 / +2.65 / +0.45 / +0.17 / +1.46. Il 2023 e' la risalita post-FTX da ~$8 a
~$100: un evento, non un meccanismo. Corr col book +0.404 (L-FULL) / +0.561
(L-PULITA), vicina allo 0.74 che boccio' il trend multi-asset, e in salita man mano
che il dato migliora.
L'ESEGUIBILITA' NON E' IL VINCOLO: SOL_USDC-PERPETUAL ha min 0.001 SOL = $0.09 contro
il pavimento min_order $5. Primo candidato bocciato senza che il muro sia la taglia
del conto.
EFFETTO COLLATERALE TROVATO SU ME STESSO. Il "guardrail solo dati certi" dichiarato in
CLAUDE.md — load_data("SOL") -> FileNotFoundError — NON e' codice: load_data non ha
whitelist, solleva solo perche' il file non c'e'. Ricostruendo SOL in
data/raw/sol_1h.parquet il guardrail si e' disattivato in silenzio, e quel file non
viene rinfrescato dal cron (--asset BTC ETH) -> sarebbe diventato dato stantio con
l'aspetto di dato attivo. Riparato con la convenzione gia' in uso (hl_/eq_/eqx_/fut_):
SOL vive in data/raw/alt_sol_*.parquet. Congelato in due test, di cui uno DERIVA gli
asset a rischio da rebuild_history.DERIBIT_INSTR. La prima stesura di quel test
elencava i prefissi a mano e bocciava eqx_, fut_, vol_term_, fundnews_ (namespace
veri): l'invariante si deriva dal codice, non si elenca — stessa lezione di fee_watch.
Book, pesi, universo direzionale, config, cron: INVARIATI. Suite 631 verdi.
NB: test_gtaa_band_gate e' tornato VERDE da solo, senza modifiche al codice, perche' il
cron ha riscritto i parquet equity — la conferma in positivo della diagnosi del 07/08.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1934,6 +1934,52 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis
|
|||||||
Misura che ha guidato una scelta: la banda del versamento suggerito resta **±1% da 1.200 a 3.000
|
Misura che ha guidato una scelta: la banda del versamento suggerito resta **±1% da 1.200 a 3.000
|
||||||
percorsi** → non domina il Monte Carlo ma la **granularita' della bisezione** (~€5) → percorsi
|
percorsi** → non domina il Monte Carlo ma la **granularita' della bisezione** (~€5) → percorsi
|
||||||
tenuti bassi e incertezza **dichiarata**.
|
tenuti bassi e incertezza **dichiarata**.
|
||||||
|
- ❌ **SOL COME TERZA GAMBA DIREZIONALE — SCARTATO (2026-08-22).** `scripts/research/r0822_sol_leg.py`,
|
||||||
|
test `tests/test_sol_leg.py` (5), diario `2026-08-22-sol-terza-gamba.md`. **Book/pesi/universo
|
||||||
|
direzionale/cron INVARIATI.** Nato da *"hai gia' analizzato XRP/SOL/UNI?"*: si', ma sempre dentro
|
||||||
|
panieri cross-sectional su HL — **SOL e' l'unico dei tre eseguibile su Deribit** e TP01/SKH01 non
|
||||||
|
erano mai stati misurati su di lui. Ipotesi a priori registrata **prima**: diluisce (trend
|
||||||
|
multi-asset 19/06, corr 0.74). Confermata.
|
||||||
|
(1) **Dato**: storico ricostruito (`SOL/USDC:USDC`, 466.776 barre 5m dal 2022-03-15, 0 gap) e
|
||||||
|
certificato → conferma esatta del 19/06: **>1% da Coinbase nell'1.3% (2022) / 0.6% (2023) delle
|
||||||
|
barre**, med 8.1→3.9 bps dal 2022 al 2026; flat 1h 0.2% (ok), 5m **20.8%** (run 177 barre).
|
||||||
|
Da qui **due lenti dichiarate prima di misurare**: L-FULL 2022-03+ e L-PULITA 2024+.
|
||||||
|
(2) **Gambe sole** (meccanismi CONGELATI): TP01 SOL Sh 0.91 / **hold-out −0.24**; SKH01 SOL
|
||||||
|
Sh 0.51 / **maxDD 40.5%**. ⚠️ **SKH01-V2-DD fu SELEZIONATA (23/06) sul criterio maxDD<30%**
|
||||||
|
(BTC 21%, ETH 27%): su un asset nuovo **il criterio per cui la variante esiste fallisce** →
|
||||||
|
era una proprieta' dei due asset di taratura, non del meccanismo.
|
||||||
|
(3) **Book, 24 ancore, mediana delle differenze APPAIATE:** dSharpe hold-out **−0.166, >0 in
|
||||||
|
0/24 ancore in ENTRAMBE le lenti**; L-PULITA dSharpe FULL −0.099 (0/24) e dCAGR −1.34pp (0/24);
|
||||||
|
L-FULL dSharpe FULL +0.094 (24/24) ma dCAGR +0.10pp. Canonica: 2 gambe 1.44/−9.4%/14.7% →
|
||||||
|
3 gambe 1.48/−7.5%/13.6% (L-FULL), 1.54 → **1.30** (L-PULITA).
|
||||||
|
(4) **Il DD scende ma e' DE-LEVERING (5ª occorrenza** dopo VRP-DD, TP01×DVOL, MAT01, azioni
|
||||||
|
intere UCITS**):** a pari maxDD, L-PULITA k=0.886 → Sh **1.54 vs 1.30** e CAGR 14.5% vs 12.6%.
|
||||||
|
**L'unica lente in cui SOL aggiunge e' quella costruita sui dati che la certificazione segnala.**
|
||||||
|
(5) 📌 **E dentro quella lente il guadagno e' UN ANNO:** dSh per anno 2022 **−0.91** · 2023
|
||||||
|
**+1.06** · 2024 −0.29 · 2025 −0.20 · 2026 −0.22 → **4 anni su 5 negativi**; la gamba SOL da sola
|
||||||
|
fa Sh −1.99 / **+2.65** / +0.45 / +0.17 / +1.46. Il 2023 e' la risalita post-FTX da ~$8 a ~$100:
|
||||||
|
un evento, non un meccanismo. Corr col book **+0.404 L-FULL / +0.561 L-PULITA** — vicina allo
|
||||||
|
0.74 che boccio' il trend multi-asset, **e in salita man mano che il dato migliora**.
|
||||||
|
(6) **L'eseguibilita' NON e' il vincolo:** `SOL_USDC-PERPETUAL` min 0.001 SOL = **$0.09** contro
|
||||||
|
il pavimento `min_order` $5 → **primo candidato bocciato senza che il muro sia la taglia del conto.**
|
||||||
|
(7) ⚠️ **EFFETTO COLLATERALE TROVATO SU ME STESSO: il "guardrail solo dati certi" NON e' codice.**
|
||||||
|
`load_data` **non ha whitelist**: `load_data("SOL") -> FileNotFoundError` funziona solo perche' il
|
||||||
|
file non c'e'. Ricostruendo SOL in `data/raw/sol_1h.parquet` **il guardrail si e' disattivato in
|
||||||
|
silenzio**, e quel file NON viene rinfrescato dal cron (`--asset BTC ETH`) → sarebbe diventato
|
||||||
|
**dato stantio con l'aspetto di dato attivo**. Riparato con la convenzione gia' in uso
|
||||||
|
(`hl_`/`eq_`/`eqx_`/`fut_`): SOL vive in **`data/raw/alt_sol_*.parquet`**, fuori dal namespace
|
||||||
|
nudo; guardrail verificato ripristinato e congelato in due test (uno **deriva** gli asset a
|
||||||
|
rischio da `rebuild_history.DERIBIT_INSTR`, l'altro segnala se `load_data` acquisisce una
|
||||||
|
whitelist vera). ⚠️ La prima stesura del test elencava i prefissi a mano e bocciava `eqx_`,
|
||||||
|
`fut_`, `vol_term_`, `fundnews_` — namespace veri: **l'invariante si deriva dal codice, non si
|
||||||
|
elenca** (stessa lezione di `fee_watch` il giorno prima).
|
||||||
|
**REGOLE:** (a) un meccanismo si trasferisce a un asset nuovo **coi parametri congelati**, o non
|
||||||
|
e' un trasferimento ma una nuova famiglia (→ `study_family_honest` + DSR); (b) **un criterio di
|
||||||
|
SELEZIONE va riprovato sull'asset nuovo** — quello di SKH01 non sopravvive; (c) quando il
|
||||||
|
risultato dipende dalla finestra, **guardare quale finestra la CERTIFICAZIONE approva**;
|
||||||
|
(d) **un contributo positivo si scompone per ANNO prima di crederci** — 24/24 ancore positive
|
||||||
|
sembravano un plateau ed erano un anno solo; (e) **ricostruire dati per un'analisi puo'
|
||||||
|
disattivare un guardrail che vive nell'assenza di un file**: chiedersi in quale namespace atterra.
|
||||||
- **Soffitto strutturale BTC/ETH-direzionale ~1.3** superato SOLO espandendo a un meccanismo diverso:
|
- **Soffitto strutturale BTC/ETH-direzionale ~1.3** superato SOLO espandendo a un meccanismo diverso:
|
||||||
cross-sectional su universo Hyperliquid certificato (XS01) → portafoglio Sharpe ~1.55.
|
cross-sectional su universo Hyperliquid certificato (XS01) → portafoglio Sharpe ~1.55.
|
||||||
- **Sweep "strategie alternative" (2026-06-20) — 104 ipotesi / 153 agenti / NIENTE di nuovo regge.**
|
- **Sweep "strategie alternative" (2026-06-20) — 104 ipotesi / 153 agenti / NIENTE di nuovo regge.**
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
# 2026-08-22 — SOL come terza gamba: tutto il guadagno è un anno solo, e quell'anno il dato non è buono
|
||||||
|
|
||||||
|
**Richiesta:** dopo *"hai già provato ad analizzare utilizzo di XRP o SOL o UNI?"* — sì, ma sempre
|
||||||
|
dentro panieri cross-sectional su Hyperliquid. SOL è l'**unico dei tre eseguibile su Deribit** e
|
||||||
|
TP01/SKH01 non erano mai stati misurati su di lui. *"si fai una analisi"*.
|
||||||
|
|
||||||
|
**Esito: SCARTATO.** L'ipotesi a priori (*diluisce*) era registrata prima di guardare, ed è confermata.
|
||||||
|
|
||||||
|
**Toccati:** `scripts/research/r0822_sol_leg.py` (nuovo), `tests/test_sol_leg.py` (5), `CLAUDE.md`,
|
||||||
|
`data/raw/alt_sol_*.parquet` (namespace nuovo). **Book, pesi, `config/live.json`, cron: INVARIATI.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Il dato, prima della strategia
|
||||||
|
|
||||||
|
Storico ricostruito da Deribit mainnet: `SOL/USDC:USDC`, **466.776 barre 5m dal 2022-03-15**,
|
||||||
|
0 gap, coerenza resample maxΔ 0.00 bps. Poi certificato — ed è la certificazione a decidere il
|
||||||
|
disegno dell'esperimento, non il contrario.
|
||||||
|
|
||||||
|
| | med bps vs Coinbase | p95 | max | >1% |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 2022 | 8.1 | 40.1 | **2478.6** | 1.3% |
|
||||||
|
| 2023 | 7.6 | 30.0 | 1010.3 | 0.6% |
|
||||||
|
| 2024 | 5.9 | 22.7 | 109.4 | 0.0% |
|
||||||
|
| 2025 | 4.0 | 14.4 | 269.1 | 0.0% |
|
||||||
|
| 2026 | 3.9 | 12.4 | 51.7 | 0.0% |
|
||||||
|
|
||||||
|
Conferma esatta del verdetto del 19/06 (*«SOL 🟡 pulito da ~2024; 2022-23 rumoroso»*). Flat a 1h
|
||||||
|
**0,2%** (usabile), a 5m **20,8%** con run fino a 177 barre (~15h) — lontano da LTC (82%) ma non
|
||||||
|
è BTC/ETH (~0%).
|
||||||
|
|
||||||
|
Da qui **due lenti dichiarate prima di misurare**: `L-FULL` (2022-03+, usa gli anni segnalati) e
|
||||||
|
`L-PULITA` (2024+, dato buono ma quasi tutto hold-out).
|
||||||
|
|
||||||
|
## 1. Le gambe da sole (meccanismi CONGELATI, nessuna ri-ottimizzazione)
|
||||||
|
|
||||||
|
| gamba | Sharpe | maxDD | CAGR | Sh hold-out |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| TP01 BTC | 1.13 | −16.2% | 15.2% | +0.28 |
|
||||||
|
| TP01 ETH | 1.10 | −15.2% | 15.2% | +0.62 |
|
||||||
|
| **TP01 SOL** | **0.91** | −12.7% | 9.8% | **−0.24** |
|
||||||
|
| SKH01 BTC | 1.28 | −21.4% | 33.6% | +1.49 |
|
||||||
|
| SKH01 ETH | 1.05 | −27.4% | 30.9% | +1.49 |
|
||||||
|
| **SKH01 SOL** | **0.51** | **−40.5%** | 11.3% | +1.05 |
|
||||||
|
|
||||||
|
⚠️ **SKH01-V2-DD su SOL fa 40,5% di drawdown.** La variante «-DD» esiste *perché* fu selezionata,
|
||||||
|
nella 2ª ondata del 23/06, sul criterio **maxDD < 30%** (BTC 21%, ETH 27%). Su un asset nuovo il
|
||||||
|
criterio stesso per cui la variante è stata scelta **fallisce**. Non è un dettaglio: è la firma di
|
||||||
|
un parametro tarato su due asset e presentato come proprietà del meccanismo.
|
||||||
|
|
||||||
|
## 2. Il livello che conta: il book
|
||||||
|
|
||||||
|
24 ancore, **mediana delle differenze appaiate** (regola 26/07: un Δ eredita la fortuna d'ancora).
|
||||||
|
|
||||||
|
| | L-FULL (2022-03+) | L-PULITA (2024+) |
|
||||||
|
|---|---|---|
|
||||||
|
| dSharpe FULL | **+0.094** (>0 in **24/24**) | **−0.099** (>0 in **0/24**) |
|
||||||
|
| dSharpe hold-out | **−0.166** (>0 in **0/24**) | **−0.166** (>0 in **0/24**) |
|
||||||
|
| DD più basso di | +1.435 pp (24/24) | +1.470 pp (24/24) |
|
||||||
|
| d CAGR | +0.105 pp (58%) | **−1.339 pp** (0/24) |
|
||||||
|
|
||||||
|
All'ancora canonica: 2 gambe Sh 1.44 / DD −9.4% / CAGR 14.7% → 3 gambe Sh 1.48 / DD −7.5% /
|
||||||
|
CAGR 13.6% su L-FULL; su L-PULITA 1.54 → **1.30**.
|
||||||
|
|
||||||
|
## 3. Il drawdown scende — ma è de-levering (5ª occorrenza)
|
||||||
|
|
||||||
|
Aggiungere SOL abbassa DD **e** CAGR insieme. Il null codificato chiede: *lo stesso DD si compra
|
||||||
|
più a buon mercato riducendo la size?* Si cerca `k` con `maxDD(k·book_2gambe) = maxDD(book_3gambe)`:
|
||||||
|
|
||||||
|
| lente | k | Sh 3 gambe | Sh 2 gambe × k | CAGR 3g | CAGR 2g × k | verdetto |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| L-FULL | 0.793 | 1.48 | 1.44 | 13.6% | 11.5% | SOL aggiunge |
|
||||||
|
| **L-PULITA** | 0.886 | **1.30** | **1.54** | 12.6% | 14.5% | **de-levering** |
|
||||||
|
|
||||||
|
**L'unica lente in cui SOL aggiunge è quella costruita sui dati che la certificazione segnala.**
|
||||||
|
|
||||||
|
## 4. E dentro quella lente, il guadagno è un anno
|
||||||
|
|
||||||
|
| anno | 2g Sh | 3g Sh | dSh | dCAGR |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 2022 | 1.37 | 0.46 | **−0.91** | −6.0 pp |
|
||||||
|
| **2023** | 1.34 | 2.40 | **+1.06** | **+11.1 pp** |
|
||||||
|
| 2024 | 1.65 | 1.36 | −0.29 | −5.6 pp |
|
||||||
|
| 2025 | 1.00 | 0.80 | −0.20 | −2.7 pp |
|
||||||
|
| 2026 | 2.70 | 2.48 | −0.22 | −2.9 pp |
|
||||||
|
|
||||||
|
Gamba SOL da sola: 2022 **−1.99** · 2023 **+2.65** · 2024 +0.45 · 2025 +0.17 · 2026 +1.46.
|
||||||
|
|
||||||
|
**4 anni su 5 negativi.** Il 2023 è l'anno in cui SOL risalì da ~$8 a ~$100 dopo il crollo FTX: un
|
||||||
|
trend-follower su quel movimento guadagna una fortuna **una volta sola**. Non è un meccanismo che
|
||||||
|
trasferisce, è un evento.
|
||||||
|
|
||||||
|
E la correlazione col book attuale è **+0.404** (L-FULL) e **+0.561** (L-PULITA) — vicina allo
|
||||||
|
0.74 per cui il trend multi-asset fu scartato il 19/06, **e in salita** man mano che il dato
|
||||||
|
migliora.
|
||||||
|
|
||||||
|
## 5. Ciò che non è il vincolo
|
||||||
|
|
||||||
|
**L'eseguibilità non c'entra:** `SOL_USDC-PERPETUAL` ha min 0.001 SOL = **$0.09**, contro il
|
||||||
|
pavimento `min_order` di $5 del book. SOL sarebbe tradabile oggi a $644. È la prima volta in questo
|
||||||
|
progetto che un candidato viene bocciato **senza** che il muro sia la taglia del conto.
|
||||||
|
|
||||||
|
## 6. ⚠️ Effetto collaterale del mio stesso lavoro: un guardrail disattivato in silenzio
|
||||||
|
|
||||||
|
CLAUDE.md dichiara: *«`load_data("SOL", ...)` → FileNotFoundError (guardrail: solo dati certi)»*.
|
||||||
|
**Non è codice.** `load_data` non ha alcuna whitelist: solleva l'eccezione solo perché il file non
|
||||||
|
esiste. Ricostruendo lo storico in `data/raw/sol_1h.parquet` **ho disattivato il guardrail senza
|
||||||
|
che nulla lo segnalasse** — e quel file non sarebbe stato rinfrescato dal cron (`--asset BTC ETH`),
|
||||||
|
quindi sarebbe diventato dato stantio con l'aspetto di dato attivo.
|
||||||
|
|
||||||
|
È la stessa forma dei tre difetti dei giorni scorsi: un controllo che funziona per configurazione,
|
||||||
|
non per costruzione.
|
||||||
|
|
||||||
|
**Riparato con la convenzione che il progetto già usa** (`hl_`, `eq_`, `eqx_`, `fut_`): SOL vive in
|
||||||
|
`data/raw/alt_sol_*.parquet`, fuori dal namespace nudo. Guardrail verificato ripristinato.
|
||||||
|
|
||||||
|
⚠️ **Errore mio nel test che doveva sorvegliarlo:** la prima stesura elencava a mano i prefissi
|
||||||
|
leciti e bocciava `eqx_`, `fut_`, `vol_term_`, `fundnews_` — namespace veri che non conoscevo.
|
||||||
|
Riscritto per **derivare** gli asset a rischio da `rebuild_history.DERIBIT_INSTR`: l'invariante non
|
||||||
|
è «quali prefissi esistono» (lista che invecchia) ma «quali asset possono finire nel namespace
|
||||||
|
nudo», che sta nel codice. Stessa lezione di `fee_watch` il giorno prima.
|
||||||
|
|
||||||
|
## VERDETTO
|
||||||
|
|
||||||
|
**SOL come terza gamba direzionale: SCARTATO.** Hold-out negativo a 24/24 ancore in entrambe le
|
||||||
|
lenti; sulla finestra pulita peggiora tutto; il DD migliore è de-levering; il guadagno della
|
||||||
|
finestra sporca è un anno solo; la correlazione col book sale col migliorare del dato. **Book,
|
||||||
|
pesi, universo direzionale invariati.**
|
||||||
|
|
||||||
|
## REGOLE
|
||||||
|
|
||||||
|
- **Un meccanismo si trasferisce a un asset nuovo con i parametri CONGELATI, o non è un
|
||||||
|
trasferimento ma una nuova famiglia** — e allora servono `study_family_honest` e deflated-Sharpe.
|
||||||
|
- **Un criterio di selezione va riprovato sull'asset nuovo:** SKH01-V2-DD fu scelta per maxDD<30%
|
||||||
|
e su SOL fa 40,5%. Il criterio era una proprietà dei due asset su cui fu tarato, non del sistema.
|
||||||
|
- **Quando un risultato dipende dalla finestra, guardare quale finestra la CERTIFICAZIONE
|
||||||
|
approva** — qui le due si contraddicono e la lente che promuove è quella non certificabile.
|
||||||
|
- **Un contributo positivo si scompone per anno prima di crederci:** +0.094 di Sharpe mediano
|
||||||
|
sembra un plateau (24/24 ancore) ed è un anno solo con quattro anni negativi intorno.
|
||||||
|
- **Ricostruire dati per un'analisi può disattivare un guardrail** che vive nell'assenza di un
|
||||||
|
file. Prima di scaricare un asset fuori universo, chiedersi in quale namespace atterra.
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""r0822_sol_leg.py — SOL come TERZA GAMBA direzionale del book Deribit: aggiunge o diluisce?
|
||||||
|
|
||||||
|
DOMANDA (operatore, 2026-08-22): XRP/SOL/UNI sono gia' stati analizzati, ma sempre dentro panieri
|
||||||
|
cross-sectional su Hyperliquid. SOL e' l'UNICO dei tre eseguibile su Deribit (SOL_USDC-PERPETUAL,
|
||||||
|
min 0.001 SOL ~ $0.08, molto sotto il pavimento $5 del book) e TP01/SKH01 non sono mai stati
|
||||||
|
misurati su di lui. Questo script chiude il buco.
|
||||||
|
|
||||||
|
DISEGNO — cio' che NON si fa qui:
|
||||||
|
* NESSUNA ri-ottimizzazione su SOL. I meccanismi sono CONGELATI (TP01 `CANONICAL`,
|
||||||
|
SKH01 `SKH01_V2_DD`). Ritarare i parametri su un asset nuovo sarebbe una nuova famiglia e
|
||||||
|
richiederebbe `study_family_honest` + deflated-Sharpe; qui la domanda e' se il meccanismo
|
||||||
|
ESISTENTE trasferisce, che e' una domanda diversa e piu' onesta.
|
||||||
|
* NESSUNA scelta di finestra a posteriori: le due lenti sono dichiarate PRIMA (sotto).
|
||||||
|
|
||||||
|
IPOTESI A PRIORI (registrata prima di guardare): DILUISCE. Il test trend-multi-asset del
|
||||||
|
2026-06-19 trovo' corr 0.74 con TP01 -> ridondante; SOL e' un beta alto di BTC. Se il risultato
|
||||||
|
fosse "aggiunge", va guardato con piu' sospetto, non con meno.
|
||||||
|
|
||||||
|
DUE LENTI, dichiarate prima (la certificazione le impone, non le sceglie il risultato):
|
||||||
|
L-FULL finestra comune 2022-03+ -> usa 2022-23, che la certificazione segnala come rumorosi
|
||||||
|
(>1% da Coinbase nell'1.3% / 0.6% delle barre)
|
||||||
|
L-PULITA finestra 2024+ -> SOL a med 4-6 bps da Coinbase, ma e' quasi tutta hold-out
|
||||||
|
|
||||||
|
ANCORA: ogni numero daily e' calcolato su TUTTE E 24 le ancore e riportato come MEDIANA DELLE
|
||||||
|
DIFFERENZE APPAIATE (regola codificata 2026-07-26: un Delta fra due varianti eredita la fortuna
|
||||||
|
d'ancora come ogni altro numero, e la mediana delle differenze != differenza delle mediane).
|
||||||
|
|
||||||
|
uv run python scripts/research/r0822_sol_leg.py [--anchors N]
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from functools import lru_cache
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||||
|
|
||||||
|
from r0702_tp01_offset import resample_offset # noqa: E402 (importato, non riscritto)
|
||||||
|
from src.backtest.harness import backtest_signals # noqa: E402
|
||||||
|
from src.data.downloader import load_data # noqa: E402 (solo BTC/ETH: vedi _alt sotto)
|
||||||
|
from src.strategies.skyhook import SKH01_V2_DD, build_frames, skyhook_entries # noqa: E402
|
||||||
|
from src.strategies.trend_portfolio import CANONICAL, TrendPortfolio # noqa: E402
|
||||||
|
|
||||||
|
# ⚠️ SOL vive in un NAMESPACE DEDICATO `alt_sol_*.parquet`, non in `sol_*.parquet`.
|
||||||
|
# Motivo (trovato in questa sessione): il "guardrail solo dati certi" documentato in CLAUDE.md
|
||||||
|
# — `load_data("SOL", ...) -> FileNotFoundError` — non e' codice, e' l'ASSENZA del file:
|
||||||
|
# `load_data` non ha alcuna whitelist. Ricostruendo lo storico di SOL in `data/raw/sol_*.parquet`
|
||||||
|
# il guardrail si disattiva IN SILENZIO, e per giunta quel file NON viene rinfrescato dal cron
|
||||||
|
# (`cron_daily.sh` fa `--asset BTC ETH`) -> diventerebbe dato stantio che sembra attivo.
|
||||||
|
# Il namespace dedicato e' la convenzione che il progetto usa gia' per `hl_` e `eq_`.
|
||||||
|
# Per rigenerarlo: uv run python scripts/analysis/rebuild_history.py --asset SOL
|
||||||
|
# poi rinominare sol_*.parquet -> alt_sol_*.parquet
|
||||||
|
ALT_NS = "alt_"
|
||||||
|
|
||||||
|
def load_alt(asset: str, tf: str) -> pd.DataFrame:
|
||||||
|
"""Legge un asset del namespace di ricerca (fuori dal feed attivo BTC/ETH)."""
|
||||||
|
path = ROOT / "data" / "raw" / f"{ALT_NS}{asset.lower()}_{tf}.parquet"
|
||||||
|
if not path.exists():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"{path} assente. Rigenera con:\n"
|
||||||
|
f" uv run python scripts/analysis/rebuild_history.py --asset {asset}\n"
|
||||||
|
f" poi rinomina data/raw/{asset.lower()}_*.parquet -> {ALT_NS}{asset.lower()}_*.parquet")
|
||||||
|
df = pd.read_parquet(path)
|
||||||
|
df["datetime"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
def dati(asset: str, tf: str) -> pd.DataFrame:
|
||||||
|
"""BTC/ETH dal feed certificato attivo; tutto il resto dal namespace di ricerca."""
|
||||||
|
return load_data(asset, tf) if asset in ("BTC", "ETH") else load_alt(asset, tf)
|
||||||
|
|
||||||
|
|
||||||
|
DUE = ("BTC", "ETH")
|
||||||
|
TRE = ("BTC", "ETH", "SOL")
|
||||||
|
W_TP, W_SKH = 0.75, 0.25 # pesi del book live Deribit
|
||||||
|
PULITA = pd.Timestamp("2024-01-01", tz="UTC") # dalla certificazione, non dal risultato
|
||||||
|
HOLDOUT = pd.Timestamp("2025-01-01", tz="UTC")
|
||||||
|
ANNO = 365.0
|
||||||
|
|
||||||
|
|
||||||
|
def sh(s: pd.Series) -> float:
|
||||||
|
s = s.dropna()
|
||||||
|
return float(s.mean() / s.std() * np.sqrt(ANNO)) if len(s) > 30 and s.std() > 0 else float("nan")
|
||||||
|
|
||||||
|
|
||||||
|
def maxdd(s: pd.Series) -> float:
|
||||||
|
e = (1 + s.fillna(0)).cumprod()
|
||||||
|
return float((e / e.cummax() - 1).min())
|
||||||
|
|
||||||
|
|
||||||
|
def cagr(s: pd.Series) -> float:
|
||||||
|
s = s.dropna()
|
||||||
|
if len(s) < 30:
|
||||||
|
return float("nan")
|
||||||
|
return float((1 + s).prod() ** (ANNO / len(s)) - 1)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=8)
|
||||||
|
def _h1(asset: str) -> pd.DataFrame:
|
||||||
|
return dati(asset, "1h")
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=128)
|
||||||
|
def tp01_leg(asset: str, h: int) -> pd.Series:
|
||||||
|
"""TP01 CONGELATO su un asset, ancora h. Rendimenti netti giornalieri, causale."""
|
||||||
|
df = resample_offset(_h1(asset), h)
|
||||||
|
tp = TrendPortfolio(**CANONICAL)
|
||||||
|
px = df["close"].values.astype(float)
|
||||||
|
r = np.zeros(len(px)); r[1:] = px[1:] / px[:-1] - 1.0
|
||||||
|
tgt = tp.target_series(df)
|
||||||
|
held = np.zeros(len(tgt)); held[1:] = tgt[:-1]
|
||||||
|
net = held * r - tp.fee_side * np.abs(np.diff(held, prepend=0.0)); net[0] = 0.0
|
||||||
|
return pd.Series(np.clip(net, -0.99, None),
|
||||||
|
index=pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True)))
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=8)
|
||||||
|
def skh_leg(asset: str) -> pd.Series:
|
||||||
|
"""SKH01-V2-DD CONGELATO su un asset, riportato su griglia giornaliera (come lo sleeve)."""
|
||||||
|
ltf, htf = build_frames(dati(asset, "5m"))
|
||||||
|
ent = skyhook_entries(ltf, htf, SKH01_V2_DD)
|
||||||
|
m = backtest_signals(ltf, ent, fee_rt=0.001, leverage=1.0, asset=asset, tf="230m")
|
||||||
|
s = pd.Series(m.equity, index=pd.DatetimeIndex(pd.to_datetime(m.eq_index, utc=True)))
|
||||||
|
return s.resample("1D").last().ffill().pct_change().dropna()
|
||||||
|
|
||||||
|
|
||||||
|
def ew(series: dict) -> pd.Series:
|
||||||
|
"""Equal-weight sulle date COMUNI (inner): un book a N gambe si valuta dove ci sono tutte."""
|
||||||
|
J = pd.concat(series, axis=1, join="inner").dropna()
|
||||||
|
return J.mean(axis=1)
|
||||||
|
|
||||||
|
|
||||||
|
def book(assets: tuple, h: int) -> pd.Series:
|
||||||
|
tp = ew({a: tp01_leg(a, h) for a in assets})
|
||||||
|
sk = ew({a: skh_leg(a) for a in assets})
|
||||||
|
J = pd.concat({"tp": tp, "sk": sk}, axis=1, join="inner").fillna(0.0)
|
||||||
|
return W_TP * J["tp"] + W_SKH * J["sk"]
|
||||||
|
|
||||||
|
|
||||||
|
def k_iso_dd(base: pd.Series, target: pd.Series, lo: float = 0.01, hi: float = 1.0) -> float:
|
||||||
|
"""Fattore k per cui `k * base` ha lo STESSO maxDD di `target`. PURA.
|
||||||
|
|
||||||
|
E' il cuore del NULL DEL DE-LEVERING, che in questo progetto e' il primo test dovuto a ogni
|
||||||
|
claim "meno drawdown" (5a occorrenza: VRP-DD 03/07, TP01xDVOL 26/06, MAT01 25/07, azioni
|
||||||
|
intere UCITS 26/07, e SOL qui). maxDD e' monotono in k, quindi la bisezione converge.
|
||||||
|
"""
|
||||||
|
obiettivo = maxdd(target)
|
||||||
|
for _ in range(60):
|
||||||
|
k = 0.5 * (lo + hi)
|
||||||
|
if maxdd(k * base) < obiettivo: # piu' profondo del bersaglio -> ridurre la size
|
||||||
|
hi = k
|
||||||
|
else:
|
||||||
|
lo = k
|
||||||
|
return 0.5 * (lo + hi)
|
||||||
|
|
||||||
|
|
||||||
|
def taglia(s: pd.Series, start=None) -> pd.Series:
|
||||||
|
return s if start is None else s[s.index >= start]
|
||||||
|
|
||||||
|
|
||||||
|
def riga(nome: str, s: pd.Series) -> str:
|
||||||
|
return (f" {nome:<26}{len(s):>7}{sh(s):>9.2f}{maxdd(s)*100:>9.1f}%{cagr(s)*100:>9.1f}%"
|
||||||
|
f"{sh(taglia(s, HOLDOUT)):>10.2f}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
n_anc = 24
|
||||||
|
if "--anchors" in sys.argv:
|
||||||
|
n_anc = int(sys.argv[sys.argv.index("--anchors") + 1])
|
||||||
|
anchors = list(range(0, 24, max(1, 24 // n_anc)))[:n_anc]
|
||||||
|
|
||||||
|
print("=" * 104)
|
||||||
|
print(" r0822 — SOL COME TERZA GAMBA DEL BOOK DERIBIT (meccanismi CONGELATI)")
|
||||||
|
print("=" * 104)
|
||||||
|
print(" ipotesi a priori dichiarata: DILUISCE (trend multi-asset 19/06: corr 0.74 con TP01)")
|
||||||
|
print(f" ancore valutate: {len(anchors)} pesi book TP01 {W_TP} / SKH01 {W_SKH}")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 1. le gambe da sole
|
||||||
|
print("\n [1/5] LE GAMBE DA SOLE (ancora canonica h=0, finestra propria di ogni asset)")
|
||||||
|
print(f"\n {'gamba':<26}{'barre':>7}{'Sharpe':>9}{'maxDD':>10}{'CAGR':>9}{'Sh hold':>10}")
|
||||||
|
for a in TRE:
|
||||||
|
print(riga(f"TP01 {a}", tp01_leg(a, 0)))
|
||||||
|
for a in TRE:
|
||||||
|
print(riga(f"SKH01 {a}", skh_leg(a)))
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 2. SOL nelle due lenti
|
||||||
|
print("\n [2/5] LA GAMBA SOL NELLE DUE LENTI DICHIARATE")
|
||||||
|
solb = W_TP * tp01_leg("SOL", 0).reindex(skh_leg("SOL").index).fillna(0.0) \
|
||||||
|
+ W_SKH * skh_leg("SOL")
|
||||||
|
print(f"\n {'lente':<26}{'barre':>7}{'Sharpe':>9}{'maxDD':>10}{'CAGR':>9}{'Sh hold':>10}")
|
||||||
|
print(riga("SOL book-leg L-FULL", solb))
|
||||||
|
print(riga("SOL book-leg L-PULITA", taglia(solb, PULITA)))
|
||||||
|
b2 = book(DUE, 0)
|
||||||
|
comune = solb.index.intersection(b2.index)
|
||||||
|
if len(comune) > 100:
|
||||||
|
c = float(np.corrcoef(solb.loc[comune], b2.loc[comune])[0, 1])
|
||||||
|
cp = comune[comune >= PULITA]
|
||||||
|
c2 = float(np.corrcoef(solb.loc[cp], b2.loc[cp])[0, 1]) if len(cp) > 100 else float("nan")
|
||||||
|
print(f"\n corr(gamba SOL, book attuale BTC/ETH): L-FULL {c:+.3f} L-PULITA {c2:+.3f}")
|
||||||
|
print(" (riferimento: il trend multi-asset del 19/06 fu scartato a corr 0.74)")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 3. book 2 vs 3 gambe
|
||||||
|
print("\n [3/5] BOOK A 2 GAMBE vs 3 GAMBE — differenze APPAIATE per ancora")
|
||||||
|
for nome, start in (("L-FULL (2022-03+)", None), ("L-PULITA (2024+)", PULITA)):
|
||||||
|
d_sh, d_dd, d_cagr, d_hold = [], [], [], []
|
||||||
|
for h in anchors:
|
||||||
|
A, B = book(DUE, h), book(TRE, h)
|
||||||
|
idx = A.index.intersection(B.index) # stessa finestra: SOL parte dopo
|
||||||
|
A, B = taglia(A.loc[idx], start), taglia(B.loc[idx], start)
|
||||||
|
if len(A) < 200:
|
||||||
|
continue
|
||||||
|
d_sh.append(sh(B) - sh(A)); d_dd.append((maxdd(B) - maxdd(A)) * 100)
|
||||||
|
d_cagr.append((cagr(B) - cagr(A)) * 100)
|
||||||
|
d_hold.append(sh(taglia(B, HOLDOUT)) - sh(taglia(A, HOLDOUT)))
|
||||||
|
if not d_sh:
|
||||||
|
print(f"\n {nome}: campione insufficiente")
|
||||||
|
continue
|
||||||
|
print(f"\n {nome} ({len(d_sh)} ancore)")
|
||||||
|
for lab, v, u in (("dSharpe FULL", d_sh, ""), ("dSharpe hold-out", d_hold, ""),
|
||||||
|
("DD piu' BASSO di", d_dd, " pp"), ("d CAGR", d_cagr, " pp")):
|
||||||
|
v = np.array(v, float)
|
||||||
|
pos = np.mean(v > 0) * 100
|
||||||
|
print(f" {lab:<20} mediana {np.median(v):+7.3f}{u} "
|
||||||
|
f"banda [{np.percentile(v, 10):+.3f}, {np.percentile(v, 90):+.3f}] "
|
||||||
|
f">0 nel {pos:.0f}% delle ancore")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 3-bis. per anno
|
||||||
|
print("\n [3bis] DOVE NASCE IL GUADAGNO — book 2 vs 3 gambe, anno per anno (ancora h=0)")
|
||||||
|
A0, B0 = book(DUE, 0), book(TRE, 0)
|
||||||
|
i0 = A0.index.intersection(B0.index); A0, B0 = A0.loc[i0], B0.loc[i0]
|
||||||
|
print(f"\n {'anno':<7}{'2g Sh':>8}{'3g Sh':>8}{'dSh':>8}{'2g CAGR':>10}{'3g CAGR':>10}{'dCAGR':>9}")
|
||||||
|
for y in sorted(set(A0.index.year)):
|
||||||
|
a, b = A0[A0.index.year == y], B0[B0.index.year == y]
|
||||||
|
if len(a) < 60:
|
||||||
|
continue
|
||||||
|
print(f" {y:<7}{sh(a):>8.2f}{sh(b):>8.2f}{sh(b)-sh(a):>+8.2f}"
|
||||||
|
f"{cagr(a)*100:>9.1f}%{cagr(b)*100:>9.1f}%{(cagr(b)-cagr(a))*100:>+8.1f}pp")
|
||||||
|
solleg = W_TP * tp01_leg("SOL", 0).reindex(skh_leg("SOL").index).fillna(0.0) + W_SKH * skh_leg("SOL")
|
||||||
|
print(f"\n la sola gamba SOL: " + " ".join(
|
||||||
|
f"{y} Sh {sh(solleg[solleg.index.year == y]):+.2f}"
|
||||||
|
for y in sorted(set(solleg.index.year)) if len(solleg[solleg.index.year == y]) >= 60))
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 4. null de-levering
|
||||||
|
print("\n [4/5] NULL DEL DE-LEVERING — il primo test dovuto a ogni claim 'meno drawdown'")
|
||||||
|
print(" Aggiungere SOL abbassa DD *e* CAGR insieme. La domanda non e' 'ha meno DD?' ma")
|
||||||
|
print(" 'lo stesso DD si compra piu' a buon mercato semplicemente riducendo la size?'.")
|
||||||
|
print(" Si cerca k tale che maxDD(k * book_2gambe) == maxDD(book_3gambe), poi si confronta.")
|
||||||
|
print(f"\n {'lente':<20}{'k':>7}{'Sh 3gambe':>11}{'Sh 2g x k':>11}{'CAGR 3g':>10}"
|
||||||
|
f"{'CAGR 2g x k':>13}{'verdetto':>14}")
|
||||||
|
for nome, start in (("L-FULL", None), ("L-PULITA", PULITA)):
|
||||||
|
A, B = book(DUE, 0), book(TRE, 0)
|
||||||
|
idx = A.index.intersection(B.index)
|
||||||
|
A, B = taglia(A.loc[idx], start), taglia(B.loc[idx], start)
|
||||||
|
k = k_iso_dd(A, B)
|
||||||
|
sh3, sh2 = sh(B), sh(k * A)
|
||||||
|
v = "SOL AGGIUNGE" if sh3 > sh2 + 0.02 else ("de-levering" if sh2 > sh3 + 0.02 else "pari")
|
||||||
|
print(f" {nome:<20}{k:>7.3f}{sh3:>11.2f}{sh2:>11.2f}{cagr(B)*100:>9.1f}%"
|
||||||
|
f"{cagr(k*A)*100:>12.1f}%{v:>14}")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 5. eseguibilita'
|
||||||
|
print("\n [5/5] ESEGUIBILITA' a $644 (equity reale del conto)")
|
||||||
|
px = float(dati("SOL", "1h")["close"].iloc[-1])
|
||||||
|
print(f" SOL_USDC-PERPETUAL min 0.001 SOL = ${0.001*px:.3f} << pavimento min_order $5")
|
||||||
|
print(f" a 3 gambe il target per asset scende da ~1/2 a ~1/3 dell'esposizione:")
|
||||||
|
for eq in (644.0, 2000.0, 6050.0):
|
||||||
|
print(f" equity ${eq:>7,.0f}: 2 gambe ${eq*0.5*0.5:>7,.0f}/asset "
|
||||||
|
f"3 gambe ${eq*0.5/3:>7,.0f}/asset")
|
||||||
|
print("\n" + "=" * 104)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"""Test dell'analisi "SOL come terza gamba" (scripts/research/r0822_sol_leg.py, 2026-08-22).
|
||||||
|
|
||||||
|
Congela il MECCANISMO e le DECISIONI, non i numeri: i numeri stanno nel diario e li riproduce
|
||||||
|
lo script (che richiede il namespace di ricerca `data/raw/alt_sol_*.parquet`, rigenerabile).
|
||||||
|
Un test che dipendesse da quei parquet fallirebbe su una macchina pulita — e la lezione del
|
||||||
|
2026-08-07 (`test_gtaa_band_gate` rosso perche' il cron riscriveva i dati sotto) dice di non
|
||||||
|
legare un test a dati non versionati.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# NULL DEL DE-LEVERING — il pezzo riusabile (5a occorrenza nel progetto)
|
||||||
|
# ===========================================================================
|
||||||
|
def test_k_iso_dd_trova_il_fattore_che_pareggia_il_drawdown():
|
||||||
|
from r0822_sol_leg import k_iso_dd, maxdd
|
||||||
|
rng = np.random.default_rng(822)
|
||||||
|
idx = pd.date_range("2022-01-01", periods=1500, freq="D", tz="UTC")
|
||||||
|
base = pd.Series(rng.normal(0.0006, 0.02, len(idx)), index=idx)
|
||||||
|
target = 0.6 * base # per costruzione k deve tornare ~0.6
|
||||||
|
k = k_iso_dd(base, target)
|
||||||
|
assert abs(k - 0.6) < 0.02, f"k atteso ~0.60, ottenuto {k:.3f}"
|
||||||
|
assert abs(maxdd(k * base) - maxdd(target)) < 1e-4
|
||||||
|
|
||||||
|
|
||||||
|
def test_il_null_smaschera_un_falso_diversificatore():
|
||||||
|
"""Controllo POSITIVO: una serie che e' solo la stessa cosa a size ridotta DEVE risultare
|
||||||
|
'de-levering', altrimenti il null non ha potenza e assolverebbe qualunque claim."""
|
||||||
|
from r0822_sol_leg import k_iso_dd, sh
|
||||||
|
rng = np.random.default_rng(1)
|
||||||
|
idx = pd.date_range("2022-01-01", periods=1500, freq="D", tz="UTC")
|
||||||
|
A = pd.Series(rng.normal(0.0008, 0.02, len(idx)), index=idx)
|
||||||
|
finto = 0.7 * A # nessuna informazione nuova
|
||||||
|
k = k_iso_dd(A, finto)
|
||||||
|
assert abs(sh(finto) - sh(k * A)) < 0.05, "a pari DD un de-levering puro ha lo STESSO Sharpe"
|
||||||
|
|
||||||
|
# e un vero diversificatore (rumore indipendente) deve invece BATTERE il de-levering
|
||||||
|
B = pd.Series(rng.normal(0.0008, 0.02, len(idx)), index=idx)
|
||||||
|
vero = 0.5 * A + 0.5 * B
|
||||||
|
k2 = k_iso_dd(A, vero)
|
||||||
|
assert sh(vero) > sh(k2 * A) + 0.05, "il null deve lasciar passare un diversificatore vero"
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# DECISIONI congelate
|
||||||
|
# ===========================================================================
|
||||||
|
def test_l_universo_direzionale_resta_BTC_ETH():
|
||||||
|
"""SOL come terza gamba e' stato misurato il 2026-08-22 ed e' SCARTATO (diluisce).
|
||||||
|
|
||||||
|
Se qualcuno aggiunge un asset a `sleeves.ASSETS`, questo test glielo ricorda: la domanda
|
||||||
|
e' gia' stata posta e la risposta era no. Rifarla richiede r0822_sol_leg.py, non un'opinione.
|
||||||
|
"""
|
||||||
|
from src.portfolio.sleeves import ASSETS
|
||||||
|
assert tuple(ASSETS) == ("BTC", "ETH"), (
|
||||||
|
"universo direzionale cambiato: rifare l'analisi marginale (r0822_sol_leg.py) "
|
||||||
|
"e il null del de-levering prima di toccare il book live")
|
||||||
|
|
||||||
|
|
||||||
|
def test_nessun_asset_fuori_universo_nel_namespace_NUDO():
|
||||||
|
"""⚠️ Il 'guardrail solo dati certi' di CLAUDE.md — load_data('SOL') -> FileNotFoundError —
|
||||||
|
NON e' codice: `load_data` non ha whitelist, funziona solo perche' il file non c'e'.
|
||||||
|
Ricostruendo SOL in `data/raw/sol_*.parquet` il guardrail si disattiva IN SILENZIO, e quel
|
||||||
|
file non viene rinfrescato dal cron (che fa `--asset BTC ETH`) -> dato stantio che sembra
|
||||||
|
attivo. La ricerca su asset fuori universo usa un namespace prefissato (`alt_`, come
|
||||||
|
`hl_`/`eq_`/`eqx_`/`fut_`).
|
||||||
|
|
||||||
|
⚠️ La prima stesura di questo test elencava a mano i prefissi leciti e bocciava `eqx_`,
|
||||||
|
`fut_`, `vol_term_`, `fundnews_` — namespace veri che non conoscevo. L'invariante non e'
|
||||||
|
'quali prefissi esistono' (lista che invecchia) ma 'quali asset `rebuild_history` puo'
|
||||||
|
scrivere nel namespace nudo', che si DERIVA dal codice.
|
||||||
|
"""
|
||||||
|
from scripts.analysis.rebuild_history import DERIBIT_INSTR
|
||||||
|
from src.data.downloader import ASSETS as ATTIVI
|
||||||
|
|
||||||
|
raw = ROOT / "data" / "raw"
|
||||||
|
if not raw.exists():
|
||||||
|
return
|
||||||
|
scrivibili = set(DERIBIT_INSTR) | set(ATTIVI)
|
||||||
|
intrusi = sorted(
|
||||||
|
f"{a.lower()}_{tf}.parquet"
|
||||||
|
for a in scrivibili if a not in ("BTC", "ETH")
|
||||||
|
for tf in ("5m", "15m", "1h", "1d")
|
||||||
|
if (raw / f"{a.lower()}_{tf}.parquet").exists())
|
||||||
|
assert not intrusi, (
|
||||||
|
f"asset fuori universo nel namespace NUDO di data/raw: {intrusi}. Li' `load_data` li "
|
||||||
|
"raggiunge (nessuna whitelist) e il cron non li rinfresca: rinominarli con un prefisso "
|
||||||
|
"di ricerca, es. alt_<asset>_<tf>.parquet (vedi r0822_sol_leg.py).")
|
||||||
|
|
||||||
|
|
||||||
|
def test_il_guardrail_di_load_data_e_per_ASSENZA_non_per_codice():
|
||||||
|
"""Congela il FATTO, cosi' non va riscoperto: se un giorno `load_data` prendesse una
|
||||||
|
whitelist vera, questo test lo segnala e la nota qui sopra va aggiornata."""
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
from src.data.downloader import load_data
|
||||||
|
src = inspect.getsource(load_data)
|
||||||
|
assert "FileNotFoundError" in src
|
||||||
|
assert "ASSETS" not in src, (
|
||||||
|
"load_data sembra avere ora una whitelist: bene, ma aggiorna la nota di r0822_sol_leg.py "
|
||||||
|
"e di CLAUDE.md, che dichiarano il guardrail come 'per assenza del file'")
|
||||||
Reference in New Issue
Block a user