gtaa: la decisione tenere/bloccare va a $15k — gate (A) ritirato, soglia sorvegliata

Decisione dell'operatore: GTAA01 NON si blocca oggi, si decide quando il book
arriva a $15k. Il motivo registrato non e' «non contribuisce» — la misura dice
il contrario (+0,095/+0,124 di Sharpe a iso-rischio secondo la fase, positivo in
tutte e cinque, hold-out +0,247, 6 anni su 8, correlazione col resto +0,087).
Il motivo e' che sotto $15k di book lo sleeve NON E' ACCENDIBILE:
GTAA_MIN_CAPITAL e' $3.000 allocati, che al peso 20% fa $15.000 contro i $2.068
attuali. Finora era manutenzione senza beneficio incassabile.

Registrate anche le due ragioni contro il blocco, che restano valide: N7 (uno
sleeve difensivo si giudica sul sinistro, e questa finestra non lo contiene) e
N4 (togliendolo il portafoglio di ricerca diventa 100% cripto su un venue solo).
E il costo che sarebbe andato pagato: GTAA01 e' uno dei quattro nomi di
W_DEPLOY, da cui escono i $313k del muro.

PERCHE' LA DECISIONE NON SI DIMENTICHI (N9). Una decisione parcheggiata su un
numero che nessuno sorveglia e' parcheggiata per sempre. `journal.SOGLIE_CAPITALE`
legge l'equity ogni giorno — il giornale lo fa comunque — e il giorno che supera
la soglia la voce dice quale decisione si sblocca e perche'. Seconda riga: $20k,
dove si riapre «100% Deribit fino a $20k». Una riga si TOGLIE quando la decisione
e' presa. 5 test, incluso «equity non leggibile non e' una soglia superata» (P5).

GATE (A) RITIRATO come pass/fail, congelando il MOTIVO e non l'esito — come il
07/08 col confronto fra ranghi, e per la stessa ragione: il criterio decide su un
margine piu' piccolo del rumore che lo scuote. In piu' una guardia sulla CAUSA
(`test_la_fase_di_ribilanciamento_e_ancora_ancorata_alla_POSIZIONE`) che si rompe
il giorno che qualcuno ancorasse la fase al calendario: quel giorno il gate
potrebbe tornare decidibile, ed e' un fatto da guardare, non da ignorare.
Il test sul margine assoluto si e' auto-ritirato: diceva «se scendesse sotto un
centesimo questo criterio smetterebbe di essere una misura», ed e' sceso a 0,0074.

CLAUDE.md: §3 nuova riga · §5.2 RISOLTO (trasporto allarmi) · §5.4 riscritto (la
domanda fiscale (a) ha una risposta alla fonte: derivati in c-quater al 26%, non
c-sexies al 33% — Circolare AdE 30/E del 27/10/2023, citata verbatim) · §5.13
nuovo debito, la fase che ruota — quantificata e dichiarata INNOCUA oggi (D5):
0,029 di Sharpe a livello di portafoglio, dentro la banda [1,81-2,12].

Diario: docs/diary/2026-08-28-gtaa-fase-e-allarmi.md
Suite: 789 passati, 0 falliti.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-08-28 12:54:08 +00:00
parent 21fe289383
commit 418f523be7
5 changed files with 316 additions and 29 deletions
+43 -10
View File
@@ -153,6 +153,7 @@ CLAUDE.md", la risposta e' la colonna *cosa la riapre*.
| **Non de-esporre il weekend** (porta il 38% del gross di TP01 nel 31% del tempo) | 17/07 | — | ogni proposta "risk-off weekend" parte **REFUTED** salvo null de-levering superato |
| **Peso 75/25 confermato 3 volte** (24/07, 26/07, 24/07-follow-up) | 26/07 | — | `weights_tilt_null` superato, che finora **fallisce** |
| **Fee Deribit: ≤5 bps/lato → non si tocca nulla; >10 bps → rivedere il peso SKH01** (regola decisa *prima* di vedere il numero) | 26/07 | — | il sorvegliante `fee_watch` allerta da solo |
| **GTAA01: decisione tenere/bloccare RINVIATA a $15k di book** — sotto quella soglia lo sleeve **non e' deployabile** (`GTAA_MIN_CAPITAL` $3.000 allocati al peso 20%), quindi finora e' manutenzione senza beneficio incassabile. Contributo misurato a iso-rischio **+0,095 / +0,124** di Sharpe secondo la fase: **reale e sempre positivo**, e delle stesse dimensioni dello spread fra le baseline (0,12) | 28/08 | «GTAA01 non contribuisce» — la misura dice il contrario | **il book a $15k**, che il giornale sorveglia da solo (`journal.SOGLIE_CAPITALE`) |
| **Gradino di leva NON autorizzato oggi.** 1,25x autorizzabile *a condizione*; **1,50x BOCCIATO** (peggior giorno strutturale 21,48% > 20%); **k max difendibile 1,40** | 23/08 | il 1,50x | costruire prima una **chiave di scala esplicita** in config **e** rifare `r0726_fee_sensitivity` |
⚠️ **La regola *"ogni cambio di scala passa dal cap di config, non da `target_vol`"* NON E'
@@ -202,22 +203,42 @@ d'ancora in modo diverso (nella differenza si cancella in parte, nel livello per
`paper_portfolio` NON rigenerato (GTAA su ADJUSTED_LAST: replay ≠ serie registrata, P12) —
storia pre-fix dichiarata corrotta, coda non chiusa tolta. ⚠️ Nel fix, D6 pagata di nuovo:
`asi8` in pandas 3 e' in `us`, non `ns` — blindato con test su tre risoluzioni.
2. 🚨 **Il trasporto degli allarmi e' un punto singolo di guasto** — 6,9% di invii falliti (2/29).
Fatte il 23/08: retry opzionale in `send()` e registrazione del motivo nel punto in cui veniva
ingoiato. **Resta:** `alerted=True` marcato **prima** dell'invio → un 🚨 perso e' perso per
l'episodio intero (gli episodi storici durano 200-2.324 ore). Cambiarlo **cambia il comportamento**
→ decisione dell'operatore.
2. **RISOLTO (2026-08-28, decisione dell'operatore).** Il trasporto degli allarmi era un punto
singolo di guasto — 6,9% di invii falliti (2/29) e `alerted=True` marcato **prima** dell'invio,
quindi un 🚨 perso era perso per l'**episodio intero** (200-2.324 ore). Ora `venue_watch.run_once`
riceve il **sender iniettato** e il marcatore «gia' detto» si scrive **solo dopo un invio
riuscito**; su fallimento si disfano i soli marcatori — non le misure — e per il lock **le ore
continuano a correre**, cosi' una manutenzione che sfora la grazia sale ad ALERT anche col
trasporto giu'. `notify()` accetta `tentativi` (venue_watch: 3) e l'esito finisce nel log.
⚠️ Resta scoperto **ogni altro chiamante di `notify()`**: la disciplina «commit dopo l'invio»
e' oggi solo in `venue_watch`.
3.**RISOLTO (2026-08-26, decisione dell'operatore: opzione "riscrivere adesso").** Gate
XSR01 riscritto in modo dichiarato, prima dell'esito, in direzione che stringe: capitale
$5k → **$20k** (riconcilia con la decisione 26/07, che e' posteriore e governa), gamba haircut
letta da `r0826_xsr_haircut.py` a pavimento $10 con la frazione di ordini eseguiti accanto
(misurata: haircut FULL 1,1% con **22% di eseguiti** — la guardia da sola era vacua), soglie
numeriche INVARIATE. Diario `2026-08-26-xsr01-gate-riscritto.md`.
4. **Due domande al commercialista, aperte** (valgono ~$22k di muro e ~€30/mese di versamento):
(a) i derivati Deribit (inverse, margine e regolamento in cripto, sede extra-UE) stanno in
`c-sexies` (33%) o `c-quater` (26%)? E il collaterale sconta il 2‰ al 31/12? (b) i payout di una
prop firm: **ne' `c-sexies` ne' `c-quater`** — le fonti convergono su **lavoro autonomo**, nessuna
prassi ufficiale. *Non sono pareri fiscali: sono domande da porre.*
4. **(a) RISPOSTA TROVATA ALLA FONTE (2026-08-28), (b) resta aperta.** *Non sono pareri fiscali.*
**(a) I derivati stanno in `c-quater` → 26%, non in `c-sexies` → 33%.** Circolare AdE **30/E del
27/10/2023**, §2.3.2 e §3, verbatim: «In generale, i redditi derivanti da **contratti derivati,
ancorche' aventi come sottostante cripto-attivita'**, costituiscono redditi diversi ai sensi
dell'articolo 67, comma 1, **lettera c-quater)**» e «qualora le cripto-attivita' costituiscano il
sottostante di un contratto derivato (ad es. contract for difference o **future**) i redditi
derivanti da tali strumenti finanziari costituiscono redditi diversi ai sensi dell'articolo
c-quater)». La circolare conferma esplicitamente che la riforma 2023 non sposta nulla: «tali
contratti **non rientrano** nell'ambito di applicazione della lettera c-sexies». ⚠️ Conseguenza:
**le minusvalenze su derivati NON si compensano con le plusvalenze cripto** (categorie diverse) e
non c'e' franchigia. **Il collaterale sconta il 2‰**: Deribit non e' intermediario residente,
quindi non applica il bollo e scatta l'*imposta sul valore delle cripto-attivita'* sul valore al
31/12, in RW (a $2.068 fa **~$4/anno**: obbligo dichiarativo, non un costo). 📌 E la conversione
**USDC→USDE non e' fattispecie realizzativa** — §3.1: «dal 1° gennaio 2023 non costituisce
fattispecie realizzativa lo scambio di una cripto-valuta con un'altra», col valore di carico che
si trasferisce. **Resta da chiedere:** se il perpetual *inverse* regolato in cripto sia il caso
letterale della circolare (che nomina CFD e future) o serva un passaggio in piu'.
**(b) I payout di una prop firm: ancora aperta.** Le fonti convergono su **lavoro autonomo**
(partita IVA, ATECO, forfettario 5% per i primi 5 anni poi 15%, imponibile da coefficiente di
redditivita'), **non** redditi diversi al 26%. **Nessuna prassi ufficiale**, e il coefficiente
esatto dipende dal codice ATECO.
5.**FATTO (2026-08-26):** `test_leva_massima_da_config...` cancellato con nota in
`tests/test_fee_sensitivity.py` — misurava `frac · n_asset`, con una chiave di scala avrebbe
continuato a passare smettendo di controllare. La guardia giusta e' il tetto sul PRODOTTO di
@@ -260,6 +281,18 @@ d'ancora in modo diverso (nella differenza si cancella in parte, nel livello per
definizione, patchare la costante non bastava) e `docs/journal/`; per `book_executions.jsonl`
(caricato ad-hoc via importlib, irraggiungibile da conftest) impronta a inizio suite e verifica
a fine suite, col falso positivo possibile (fill reale del cron :47) dichiarato nel messaggio.
13. ⚠️ **GTAA01 gira su una finestra che ROTOLA, e la fase di ribilanciamento cambia da sola ogni
notte** (misurato 2026-08-28, `r0828_gtaa_band_phase.py`). IB serve una finestra di **30 anni**
e SPY e' al muro: la sua prima barra avanza di un giorno di borsa ogni notte (1996-07-02 il
24/06 → 1996-09-04 il 28/08). `gtaa._gated_returns` ribilancia su `i % every == 0`, cioe' sulla
**posizione nell'array**: se la prima barra scivola si ri-fasa ogni decisione di trent'anni.
Effetto misurato: su una finestra **chiusa** (pre-2015) lo Sharpe si e' mosso fino a **0,076**
a codice fermo, e la banda scelta al buio cambia fra 25% / 40% / 60% da una notte all'altra.
**Ha gia' ucciso due criteri del gate (A)** (07/08 e 28/08). **Quantificato e innocuo OGGI**
(D5): a livello di portafoglio l'escursione sulle 5 fasi vale **0,029** di Sharpe, dentro la
banda dichiarata [1,81-2,12]. QQQ e IWM arriveranno allo stesso muro **fra 2,5 e 3,7 anni**.
Riparazione (ancorare la fase al calendario) rinviata con la decisione su GTAA01: cambierebbe
**tutti** i numeri registrati dello sleeve.
---
@@ -0,0 +1,158 @@
# 2026-08-28 — La fase che ruota, gli allarmi che si perdevano, e una risposta fiscale
*Scritto il 2026-08-28. Numeri riprodotti dagli script citati; le letture in prosa sono firmate
come tali (P13).*
## In una riga
Un test rosso su GTAA01 non parlava di GTAA01: parlava del fatto che **la finestra "congelata"
pre-2015 rotola da sola ogni notte**. Nel frattempo si e' chiuso il debito del trasporto degli
allarmi, e la prima delle due domande al commercialista ha trovato una risposta alla fonte.
---
## 1. Il gate (A) della banda GTAA01 e' una moneta — la finestra rotola
`r0828_gtaa_band_phase.py`. Replica bit-exact della produzione verificata (max|Δ| = 0.00e+00 su
7.544 barre). Codice fermo dal 07/08: nessun commit su `src/portfolio/gtaa.py` ne' su
`r0727_gtaa_band_gate.py`. Si e' mosso il DATO.
**(0) La finestra in-sample non e' ferma.** IB serve una finestra **rotolante di 30 anni**. SPY e'
l'unica gamba al muro (30,0 anni) e nel `logs/cron_daily.log` la sua data d'inizio avanza di un
giorno di borsa ogni notte: `1996-07-02` il 24/06 → `1996-09-04` oggi. QQQ e IWM arriveranno allo
stesso muro fra **2,5 e 3,7 anni**.
**(1) L'amplificatore e' la fase.** `_gated_returns` ribilancia su `i % every == 0`, dove `i` e' la
**posizione nell'array**. Se la prima barra scivola, si ri-fasa ogni decisione di ribilanciamento
di trent'anni di storia.
| misura | valore |
|---|---|
| spostamento max dello Sharpe in-sample dal 07/08 (finestra CHIUSA, codice fermo) | **0,076** |
| banda scelta al buio in 10 notti consecutive | 25% · 40% · 60% |
| notti col margine sotto il pavimento dichiarato (0,01) | **4/10** (minimo 0,0026) |
| a dato FISSO, muovendo solo la fase | 25% su 3 fasi, 60% su 2 |
Il criterio decideva su ~0,007-0,015 di Sharpe mentre il rumore che lo scuote vale fino a 0,076.
E' lo stesso modo di fallire del criterio a ranghi ritirato il 07/08 (che decideva su 0,00116), in
un costume nuovo.
## 2. Criterio (C): la mediana delle 5 fasi
`r0828_gtaa_band_median_phase.py`. ⚠️ **Scelto DOPO aver visto la tabella delle fasi**: misura lo
strumento, non valida la banda. Pavimento tenuto a 0,01, invariato.
- (i) scelta stabile su 10 notti: **SI**`60%` in tutte e dieci
- (ii) margine sempre ≥ 0,01: **SI** — minimo 0,0197
- (iii) potenza (al buio ≠ hold-out): **SI** — 60% contro 40%
Escursione dello Sharpe in-sample su 10 notti: **74/99%** per ogni banda. Lo strumento regge.
**Ma la cella che sceglie non e' la proposta:** al buio esce il 60%, il 25% vince **0 notti su 10**.
Da tenere accanto: sull'hold-out il 60% e' **penultimo** (0,7430 contro 0,9315 del 40%), coerente
con lo Spearman IS/OOS ~0 misurato il 07/08 — la scelta in-sample **non predice**.
**Contorno che vale piu' del verdetto:** le 5 serie di fase correlano **0,988****N_eff 1,01**.
La mediana di 5 fasi e' UNA osservazione: toglie l'artefatto, non compra precisione.
### Le due domande che erano state confuse
Il gate (A) puo' chiedere due cose diverse, e il 07/08 coincidevano **per fortuna di fase**:
| forma | difetto | quanto e' grave |
|---|---|---|
| «la banda e' la scelta che si fa al buio?» | **non ha risoluzione** | cambia verdetto ogni notte |
| «la banda non e' stata selezionata sull'hold-out?» | **non ha potenza** | 1 cella su 30 e' l'argmax dell'hold-out → **29/30 lo passano per costruzione** |
📌 *Lettura (agente, fallibile).* Il 25% non viene dalla griglia affatto: viene dall'argomento
**strutturale** del 27/07 — una banda in dollari assoluti degenera al variare del capitale, una
frazione no. Sotto la mediana delle fasi il 25% e' **2° in-sample e 3° sull'hold-out**: decente
ovunque, primo da nessuna parte, che e' la firma di un parametro NON selezionato sui dati. A un
parametro scelto per ragioni strutturali non si applica un gate di *selezione* ma uno di
*robustezza* — «fa danno da qualche parte?». Risposta: no.
**Esito:** gate (A) **ritirato** come pass/fail, congelando il MOTIVO (come il 07/08), piu' una
guardia sulla CAUSA che si rompe il giorno che la fase venisse ancorata al calendario.
## 3. Cosa cambia togliendo GTAA01 — e la decisione dell'operatore
`r0828_senza_gtaa.py`. Portafoglio di **ricerca** a 5 sleeve; NON il book live.
| | Sharpe | CAGR | maxDD | vol |
|---|---|---|---|---|
| CON GTAA01 | **2,28** | +19,2% | 6,1% | 7,8% |
| SENZA, iso-nozionale | 2,16 | **+23,8%** | 7,8% | 10,1% |
| SENZA, **iso-rischio** (×0,77) | 2,16 | +18,0% | 6,0% | 7,8% |
A iso-nozionale togliendolo si guadagna **drift** e si compra **volatilita'**. A iso-rischio — la
sola lente onesta per un diversificatore a basso CAGR (M6) — **Δ Sharpe +0,121**, maxDD invariato.
Hold-out 2025+: **Δ +0,247**. Aiuta in **6 anni su 8**. Correlazione col resto **+0,087**: e'
davvero altro. Standalone Sharpe 1,06, CAGR +5,8%.
⚠️ **Con la sua fascia di fase:** il Δ sulle 5 fasi va da **+0,095 a +0,124** e supera la soglia
dichiarata (0,12) in **2 fasi su 5**. Il **segno e' stabile**, il verdetto **binario** no.
### La decisione: rinviata a $15k di book
Discussa con l'operatore. L'argomento che ha deciso non e' «GTAA01 non contribuisce» — la misura
dice il contrario — ma che **a questo capitale lo sleeve non e' accendibile**: `GTAA_MIN_CAPITAL`
e' **$3.000 allocati**, che al peso 20% significa **$15.000 di book** contro i **$2.068** attuali.
Sotto quella soglia e' manutenzione senza beneficio incassabile.
Due ragioni registrate **contro** il blocco immediato, entrambe regole del progetto:
- **N7** — uno sleeve difensivo si giudica **sul sinistro, non sul premio**, e la finestra
2019-2026 non contiene il sinistro per cui esiste un GTAA a sei gambe;
- **N4** — il rischio di venue si compra con un **conto**, non con uno sleeve: togliere GTAA01
renderebbe il portafoglio di ricerca **100% cripto su un venue solo**.
E un costo che sarebbe andato pagato, non saltato: GTAA01 e' uno dei **quattro** nomi di
`r0726_wall_fixedpoint.W_DEPLOY`, da cui escono i **$313k** e tutta la pianificazione da €500/mese.
**Perche' la decisione non si dimentichi** (N9), la soglia e' sorvegliata dal giornale:
`journal.SOGLIE_CAPITALE` legge l'equity ogni giorno e il giorno che supera $15k la voce lo dice,
con la domanda da rispondere accanto. Stessa tabella, seconda riga: **$20k**, dove si riapre
«100% Deribit fino a $20k».
## 4. Allarmi: il marcatore «gia' detto» si scrive DOPO l'invio
Debito §5.2 chiuso su decisione dell'operatore. `venue_watch.run_once` salvava lo stato coi
marcatori `alerted` gia' a True e l'invio lo faceva il chiamante **dopo**: col 6,9% di invii
falliti misurato (2/29), un 🚨 perso restava perso per l'**episodio intero** (200-2.324 ore).
Ora il sender e' **iniettato** (la funzione resta testabile senza rete d'uscita, che era la ragione
del disegno precedente) e su fallimento si disfano i **soli marcatori**, non le misure:
asset in ALERT → ri-allerta l'ora dopo; lock MAINT/ALERT → flag azzerati **ma le ore continuano a
correre**, cosi' una manutenzione che sfora la grazia sale ad ALERT anche col trasporto giu';
lock RIENTRATO → stato ripristinato, perche' il rientro si annuncia una volta sola.
`notify()` accetta `tentativi` (venue_watch: 3) e l'esito finisce nel log del cron.
⚠️ Resta scoperto **ogni altro chiamante di `notify()`**: la disciplina e' oggi solo in venue_watch.
## 5. L'analista del 27/08, e la riga di diagnosi che mancava
La voce del 27/08 era senza analisi. Causa, dal transcript della sessione headless:
`Failed to authenticate: OAuth session expired and could not be refreshed`, exit 1 alle
`00:37:00.98Z`. La meccanica aveva retto (nessuna analisi vecchia spacciata per nuova, esito nel DB,
notifica partita) ma il motivo registrato era `uscita 1: ` — vuoto: `interroga()` leggeva il solo
**stderr**, e la CLI aveva scritto su **stdout**. Riparato (`motivo_uscita`), analisi recuperata.
Guasto **transitorio**: il refresh token era valido, unico caso su 24 sessioni.
## 6. Fisco: la domanda (a) ha una risposta alla fonte
Circolare AdE **30/E del 27/10/2023**, letta direttamente dal PDF ufficiale. **I derivati con
sottostante cripto stanno in `c-quater` → 26%**, non in `c-sexies` → 33%, e la circolare dice
esplicitamente che la riforma 2023 non sposta nulla. Il **collaterale** sconta il **2‰** al 31/12
(intermediario non residente → imposta sul valore delle cripto-attivita', ~$4/anno a questo
capitale). E la conversione **USDC→USDE non e' fattispecie realizzativa**. Dettaglio e citazioni
verbatim in CLAUDE.md §5.4. La (b), i payout di una prop firm, **resta aperta**.
## Cosa cambia
- `CLAUDE.md`: §3 nuova riga (GTAA01 rinviato a $15k) · §5.2 risolto · §5.4 riscritto · §5.13 nuovo
debito (la fase che ruota, quantificata e innocua oggi).
- `src/live/journal.py`: `SOGLIE_CAPITALE` + regola `soglia_capitale`.
- `src/live/venue_watch.py`, `src/live/notifier.py`, `scripts/live/venue_watch.py`: commit dopo l'invio.
- `src/live/analista.py`: `motivo_uscita` da stdout E stderr.
- `tests/test_gtaa_band_gate.py`: gate (A) ritirato, motivo e causa congelati.
- Script nuovi: `r0828_gtaa_band_phase.py`, `r0828_gtaa_band_median_phase.py`, `r0828_senza_gtaa.py`.
**Suite: 789 passati, 0 falliti.**
+35
View File
@@ -264,6 +264,37 @@ def salva(con, voce: dict, nota: str | None = None) -> None:
# (c) MUTA sul non misurato — se il numero e' None la regola tace, non arrotonda a zero.
# Il campo `nota` resta l'unico posto dove puo' finire un giudizio umano.
# =============================================================================================
# DECISIONI PARCHEGGIATE SU UNA SOGLIA DI CAPITALE
# =============================================================================================
# N9: quando una decisione viene rinviata con un motivo, il motivo va registrato — altrimenti fra
# tre mesi la stessa analisi si ripresenta come se fosse nuova. E una decisione parcheggiata su un
# numero che NESSUNO sorveglia e' parcheggiata per sempre: qui la sorveglianza e' l'equity, che il
# giornale legge comunque ogni giorno.
#
# Una riga si TOGLIE quando la decisione e' presa. Finche' c'e', la voce del giorno la ripete: e'
# voluto — non e' un allarme che chiede un'azione impossibile (P14), e' una domanda che da quel
# giorno in poi si puo' finalmente rispondere.
SOGLIE_CAPITALE: tuple[tuple[float, str], ...] = (
(15_000.0, "**GTAA01: tenere o bloccare.** Decisione rinviata a questa soglia il 2026-08-28 "
"(diario `2026-08-28-gtaa-fase-e-allarmi`). Sotto $15k di book lo sleeve NON e' "
"deployabile — `GTAA_MIN_CAPITAL` $3.000 allocati al peso 20% — quindi finora e' "
"stato manutenzione senza beneficio incassabile. Contributo misurato a iso-rischio "
"+0,095/+0,124 di Sharpe secondo la fase: reale, sempre positivo, e delle stesse "
"dimensioni dello spread fra le baseline."),
(20_000.0, "**«100% Deribit fino a $20k»** (decisione del 26/07): la soglia e' arrivata, e con "
"essa la domanda del secondo conto (N4: il rischio di venue si compra con un CONTO, "
"non con uno sleeve)."),
)
def soglie_superate(equity: float | None) -> list[tuple[float, str]]:
"""PURA. Le decisioni parcheggiate che a questa equity si possono finalmente prendere."""
if equity is None:
return []
return [(s, t) for s, t in SOGLIE_CAPITALE if equity >= s]
def _bucket_convinzione(tp: float) -> str:
"""TP01 media tre `np.sign()` e poi taglia a long-flat: la direzione vive in {0, 1/3, 1}.
@@ -469,6 +500,10 @@ def lettura(con, voce: dict) -> list[dict]:
dire("evidenza", f"Campione a oggi: **{gg} giorni, {n_rt} round-trip**. A questa taglia "
"il P&L non distingue l'edge dalla fortuna — il criterio di morte del "
"libro (`edge_watch`) lavora su una finestra di 36 mesi.", "nota")
# --- decisioni che questa equity sblocca -------------------------------------------------
for soglia, testo in soglie_superate(p.get("equity_fine")):
dire("soglia_capitale", f"📌 **Soglia ${soglia:,.0f} superata** — {testo}", "attenzione")
return out
def _n(v, fmt="{:+.2f}", vuoto="n/d"):
+46 -19
View File
@@ -101,19 +101,50 @@ def griglia_piena():
return G
def test_la_banda_proposta_e_quella_scelta_al_buio(griglia_piena):
"""Il gate (A), nella forma decidibile (2026-08-07).
# ⚠️ 2026-08-28 — SECONDO CRITERIO RITIRATO, e per la stessa ragione del primo.
# `test_la_banda_proposta_e_quella_scelta_al_buio` asseriva che la banda scelta sui soli dati
# pre-2015 fosse la proposta. Ha smesso di passare senza che nessuno toccasse il codice: al buio
# esce il 60%, con margine 0.0074 — sotto il pavimento di 0.01 che il test stesso si era dato.
# Misurato in `r0828_gtaa_band_phase.py`: NON e' un fatto sulla banda, e' un fatto sul criterio.
# · IB serve una finestra ROTOLANTE di 30 anni e SPY e' al muro: la sua prima barra avanza di
# un giorno di borsa ogni notte. Il pre-2015 — che il progetto tratta come congelato — perde
# il giorno piu' vecchio ogni notte.
# · `gtaa._gated_returns` ribilancia su `i % every == 0`, cioe' sulla POSIZIONE nell'array:
# se la prima barra scivola, si ri-fasa ogni decisione di trent'anni. La fase canonica cambia
# da sola.
# · Esito: su 10 notti consecutive la scelta al buio e' 25% / 40% / 60%, e il margine sta sotto
# il pavimento in 4 notti su 10. A dato FISSO, muovendo la sola fase, il verdetto cambia lo
# stesso (25% su 3 fasi, 60% su 2).
# Come il 07/08 si congela il MOTIVO e non l'esito. La riparazione (ancorare la fase al calendario)
# e la decisione su GTAA01 sono rinviate alla soglia di $15k di book — sotto quella soglia lo
# sleeve non e' deployabile e la decisione non ha conseguenze (`journal.SOGLIE_CAPITALE`).
La proposta del 27/07 e' una BANDA: la cadenza settimanale e' gia' `REBAL_EVERY=5` in
produzione e non era in discussione. Quindi la domanda sulla provenienza della scelta e'
«a cadenza di produzione, quale banda si sceglie guardando SOLO il pre-2015?».
"""
def test_il_gate_A_non_ha_RISOLUZIONE_ed_e_per_questo_che_e_stato_RITIRATO(griglia_piena):
"""Congela il MOTIVO del secondo ritiro: il criterio decide su un margine piu' piccolo del
rumore che lo scuote. Non asserisce QUALE cella vinca quella si muove da sola ogni notte,
ed e' esattamente il punto."""
G = griglia_piena
riga = G[G["every"] == BG.PROPOSTA[0]]
blind = riga.sort_values("sh_in", ascending=False).iloc[0]
assert blind["frac"] == BG.PROPOSTA[1], (
f"al buio si sceglierebbe la banda {blind['frac']:.0%}, non {BG.PROPOSTA[1]:.0%}: "
"la validazione del 27/07 andrebbe rifatta su quella cella")
ordinata = riga.sort_values("sh_in", ascending=False)
margine = float(ordinata["sh_in"].iloc[0] - ordinata["sh_in"].iloc[1])
spread = float(riga["sh_in"].max() - riga["sh_in"].min())
# il margine su cui il criterio decideva e' una FRAZIONE dello spread della griglia:
# a quella scala la scelta non e' una misura, e' l'ordine in cui il rumore si e' posato.
assert margine < 0.25 * spread, (
f"il margine ({margine:.4f}) e' tornato grande rispetto allo spread di griglia "
f"({spread:.4f}): se il rumore si fosse ridotto, il gate (A) andrebbe RIABILITATO "
"invece di restare ritirato — ed e' un fatto da guardare, non da ignorare")
def test_la_fase_di_ribilanciamento_e_ancora_ancorata_alla_POSIZIONE(griglia_piena):
"""La guardia sulla CAUSA. Se un giorno `_gated_returns` ancorasse la fase al calendario,
questo test si rompe e va riletto tutto: il gate (A) potrebbe tornare decidibile."""
import inspect
src = inspect.getsource(BG.gtaa_returns.__globals__["_gated_returns"])
assert "i % every" in src, (
"la fase non e' piu' ancorata alla posizione nell'array: la ragione del ritiro del "
"gate (A) e' venuta meno, rileggere r0828_gtaa_band_phase.py")
def test_chi_guardasse_l_hold_out_sceglierebbe_una_banda_DIVERSA(griglia_piena):
@@ -128,15 +159,11 @@ def test_chi_guardasse_l_hold_out_sceglierebbe_una_banda_DIVERSA(griglia_piena):
"potenza e non va citato come validazione")
def test_il_margine_del_blind_non_e_un_arrotondamento(griglia_piena):
"""La ragione per cui questo criterio ha sostituito il confronto fra ranghi: decide su
~0.015 di Sharpe invece che su ~0.001. Se il margine scendesse sotto un centesimo, anche
questo criterio smetterebbe di essere una misura e andrebbe ripensato a sua volta."""
G = griglia_piena
riga = G[G["every"] == BG.PROPOSTA[0]]
blind = riga.sort_values("sh_in", ascending=False).iloc[0]
secondo = riga[riga["frac"] != blind["frac"]]["sh_in"].max()
assert blind["sh_in"] - secondo >= 0.01
# `test_il_margine_del_blind_non_e_un_arrotondamento` (2026-08-07) chiedeva margine >= 0.01 e
# diceva, testualmente: «Se il margine scendesse sotto un centesimo, anche questo criterio
# smetterebbe di essere una misura e andrebbe ripensato a sua volta». Il 28/08 e' sceso a 0.0074.
# Il test aveva ragione e si e' auto-ritirato: la sua condizione e' ora dentro il test sul MOTIVO
# qui sopra, dove misura il margine CONTRO lo spread di griglia invece che contro un assoluto.
def test_il_confronto_fra_ranghi_e_una_moneta_ed_e_per_questo_che_e_stato_RITIRATO(griglia_piena):
+34
View File
@@ -467,3 +467,37 @@ def test_la_concentrazione_non_conta_i_versamenti_come_pnl(db, monkeypatch):
righe = J.lettura(db, _voce(pnl=p, giorno="2026-08-25"))
conc = [r for r in righe if r["id"] == "concentrazione"]
assert conc == [] # il cumulato di TRADING e' 0: la regola deve tacere
# =============================================================================================
# DECISIONI PARCHEGGIATE SU UNA SOGLIA DI CAPITALE
# =============================================================================================
# Il 2026-08-28 la decisione «GTAA01: tenere o bloccare» e' stata rinviata a $15k di book, che e'
# la soglia sotto la quale lo sleeve non e' deployabile. Una decisione parcheggiata su un numero
# che nessuno sorveglia e' parcheggiata per sempre (N9): questi test blindano la sorveglianza.
def test_sotto_soglia_non_dice_niente():
assert J.soglie_superate(2_068.0) == []
def test_la_soglia_gtaa_e_15k_e_scatta_quando_l_equity_arriva():
soglie = [s for s, _ in J.SOGLIE_CAPITALE]
assert 15_000.0 in soglie, "la soglia di GTAA01 non e' piu' dichiarata"
assert J.soglie_superate(14_999.99) == []
assert [s for s, _ in J.soglie_superate(15_000.0)] == [15_000.0]
def test_le_soglie_si_accumulano_e_restano_ordinate():
"""A $20k si devono presentare ENTRAMBE: la seconda non cancella la prima."""
assert [s for s, _ in J.soglie_superate(20_000.0)] == [15_000.0, 20_000.0]
def test_equity_non_leggibile_non_e_una_soglia_superata():
"""P5: «non vedo» non e' «va tutto bene» — e non e' nemmeno «la soglia e' arrivata»."""
assert J.soglie_superate(None) == []
def test_ogni_soglia_dice_COSA_decidere_non_solo_che_e_arrivata():
"""P4: un'allerta risponde a due domande. Una riga che dice solo «sei a $15k» non serve."""
for _, testo in J.SOGLIE_CAPITALE:
assert len(testo) > 80 and "**" in testo, "la soglia non dice quale decisione sblocca"