Files
PythagorasGoal/docs/diary/2026-09-01b-chiave-di-scala.md
Adriano Dal Pastro ec8478308f GATE SCALA-01: la chiave di scala esiste, ed e' INERTE (SPEC §8 punti 1-4)
Chiude il debito che CLAUDE.md dichiarava da settimane: la regola "ogni cambio
di scala passa dal cap di config, non da target_vol" NON era implementabile
perche' una chiave di scala non esisteva — qualunque cambio sarebbe finito su
WEIGHT/W_TP01/W_SKH, cioe' codice su un percorso con soldi veri e per giunta
nel posto sbagliato (W_TP01/W_SKH sono il RAPPORTO 75/25, non la taglia).

Specifica gia' scritta in docs/research/SPEC-scale-key.md (618 righe, 9
condizioni di gate, prototipo). Non ho progettato: ho eseguito i punti 1-4.

🚨 config/live.json NON E' STATO TOCCATO. La chiave e' assente, vale 1,00, e
T7 dimostra bit-exact che il libro e' quello di ieri (max|diff| = 0.0).
Verificato anche a runtime: book_execute in dry-run da' gli stessi target del
cron delle 15:47 (BTC $+355, ETH $+214).

LE QUATTRO DECISIONI CHE NON SONO DI COMODO
- La scala si applica DOPO il clamp. Prima, il cap se la mangerebbe proprio
  nei giorni di massima convinzione (a tp=1/sg=+1 il grezzo vale esattamente
  cap => k_eff tornerebbe a 1,00 a ogni k): sarebbe un cambio di FORMA
  travestito da cambio di taglia, e la curva g(k) con cui il gradino viene
  autorizzato non descriverebbe quel libro. Prezzo dichiarato: il cap diventa
  il tetto del libro UNITARIO, e la guardia sulla leva lorda va ricostruita.
- Il tetto e' sul PRODOTTO e sta nel CODICE. Sulla sola chiave lascerebbe
  aperta la porta accanto (frac 0,625 x scala 1,25 = 1,562x); in config
  sarebbe un lucchetto con la chiave attaccata. LEVA_LORDA_MAX 1,25 in
  src/live/book.py => il gradino a 1,50 richiede codice, quindi review.
- Fuori scaletta o fuori tetto = STOP, non clamp. book_execute si ferma, non
  invia, allerta (ScalaNonAutorizzata). E SCALA_LADDER (1,00 · 1,25) rende
  INESPRIMIBILE "solo un po'": 1,05 non e' prudente, e' fuori scaletta.
- La scala vive solo sul percorso fidato (equity illeggibile => 1,00), cosi'
  "il fallback non e' piu' permissivo" e' vero per costruzione. Ma la
  VALIDAZIONE avviene sempre: una config rotta non si nasconde dietro un giro
  in cui l'equity non era leggibile.

LA GUARDIA CHE MORDE PER PRIMA non e' il peggior giorno (k <= 3,49x) ma il
COSTO di un disaster-SL (k <= 1,67x, 2,1x piu' stringente): l'invariante
n_asset x frac x scala x disaster_sl_pct <= 0,50 scatta anche se qualcuno
allarga lo stop invece di alzare la scala.

TEST T1-T11 (tests/test_book_scale.py, 18 verdi). Il piu' importante e' T1b:
a k=1 l'implementazione simmetrica e quella asimmetrica danno lo STESSO
numero, quindi un test di simmetria scritto sul caso di default ha potenza
ZERO. T1b verifica che le due coincidano a k=1 (il rischio e' reale) e che
fuori da k=1 l'asserzione le SEPARI, con un'implementazione asimmetrica
scritta nel test apposta perche' fallisca.

SORVEGLIANTE scale_watch (cron_daily, 3 domande / 3 azioni / 3 stati, una
allerta per streak, marcatore scritto solo dopo invio riuscito — debito #2).
Riporta la frequenza del ramo di fallback, che sopra il 2% in 90 giorni
invaliderebbe la regola: misurata 0/1.676, coi 19 giri "paper capital"
(pre-finanziamento, dove il libro non invia) contati e dichiarati a parte.
Non puo' impedire la modifica: la rende visibile entro 24h e attribuibile.

CHIUDE il debito #5 di §5: T2/T3 sostituiscono il vecchio
test_leva_massima_da_config (che misurava frac x n_asset mentre la grandezza
vera e' frac x n_asset x scala), e T11 verifica che sia rimasto cancellato.

NON FATTO, deliberato: la chiave in config (punto 2 lo vieta), GATE SCALA-01
(A2 richiede >=30 giorni a 1,00 col sorvegliante attivo — "l'unico modo di
scoprire che il sorvegliante e' rotto mentre la leva e' ancora 1,00"),
r0726_fee_sensitivity rifatto (A7: serve solo al gradino; a 1,25x una
liquidazione costerebbe 1,25% non 1,00%, e ereditarlo sarebbe l'errore).

Nessun ordine. Suite: 825 passati.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 17:44:02 +00:00

9.9 KiB
Raw Permalink Blame History

2026-09-01 — La chiave di scala esiste, ed è inerte

Scritto il 2026-09-01. Implementazione dei punti 1-4 di docs/research/SPEC-scale-key.md §8. Nessun ordine. config/live.json NON è stato toccato: il libro che gira è bit-exact quello di ieri.

0. Da dove nasce

Il progetto aveva una regola — «ogni cambio di scala passa dal cap di config, non da target_vol» — e CLAUDE.md la accompagnava da settimane con questa nota:

⚠️ NON È IMPLEMENTABILE COME SCRITTA: in config/live.json non esiste una chiave di scala.

Era un debito preciso: la regola prescriveva un percorso che non esisteva, quindi qualunque cambio di scala sarebbe finito su WEIGHT/W_TP01/W_SKH in src/live/book.py — cioè codice su un percorso con soldi veri, e per giunta nel posto sbagliato (W_TP01/W_SKH sono il rapporto 75/25, non la taglia: moltiplicarli romperebbe la parità coi pesi del backtest).

La specifica era già scritta, 618 righe, con nove condizioni di gate e il prototipo che ne dimostra la meccanica. Non ho progettato niente: ho eseguito.

1. Il rischio che l'implementazione doveva rendere impossibile

La §0 della specifica lo dice meglio di come lo direi io:

Una chiave di scala è esattamente il tipo di parametro che si alza «solo un po'» dopo un mese buono. È un numero, sta in un file di config, non richiede di capire niente per cambiarlo, e il suo effetto è immediato e piacevole.

E il motivo per cui il progetto non aveva già una guardia non è dimenticanza, è aritmetica: lo Sharpe è invariante alla scala. deflated_sharpe e marginal_vs_tp01 leggono un numero che a k=1,00 e a k=2,00 è identico. Non falliscono: non vedono.

2. Cosa ho costruito

pezzo dove cosa fa
book_scale_k config/live.jsonassente la chiave. Assente = 1,00 = libro di sempre
_scala() src/live/book.py legge, valida sempre, applica solo sul percorso fidato
book_net_target(..., scala=) src/live/book.py applica la scala DOPO il clamp
LEVA_LORDA_MAX 1,25 · SCALA_LADDER (1,00 · 1,25) · DISASTER_SL_BUDGET 0,50 src/live/book.py costanti di CODICE
ScalaNonAutorizzata src/live/book.py eccezione, non un clamp
riga «scala libro» + stop-e-allerta scripts/live/book_execute.py la scala è visibile a ogni giro
scale_watch src/live/ + scripts/live/ + cron_daily.sh 3 domande, 3 azioni, 3 stati
scale_history.jsonl data/live/ giornale append-only (già nel backup)
T1-T11 tests/test_book_scale.py 18 test, tutti verdi

2.1 Le quattro decisioni che non sono di comodo

(a) La scala si applica DOPO il clamp. Applicandola prima, il cap se la mangerebbe proprio nei giorni di massima convinzione: a tp=1, sg=+1 il grezzo vale esattamente cap, quindi k_eff tornerebbe a 1,00 a ogni k. Il risultato non sarebbe «il libro a leva k» ma un libro che alza i giorni piccoli e lascia fermi i grandi — un cambio di forma travestito da cambio di taglia. E allora la curva g(k) con cui il gradino viene autorizzato non descriverebbe quel libro. Autorizzare con una curva e implementarne un'altra è il modo più silenzioso di sbagliare. Il prezzo, dichiarato: il cap smette di essere il tetto assoluto del nozionale e diventa il tetto del libro unitario. La guardia sulla leva lorda è ricostruita esplicitamente altrove.

(b) Il tetto è sul PRODOTTO, e sta nel CODICE. Un tetto sulla sola chiave lascia aperta la porta accanto: frac 0,625 × scala 1,25 = 1,562x, che passerebbe. E un tetto in config sarebbe modificabile dalla stessa mano, nello stesso file, nello stesso momento — un lucchetto con la chiave attaccata. Con LEVA_LORDA_MAX in src/live/book.py, il gradino 1,25 → 1,50 richiede una modifica di codice, quindi una review. Il tetto è basso apposta: è il meccanismo del cricchetto.

(c) Fuori scaletta o fuori tetto = STOP, non clamp. book_execute si ferma, non invia, allerta. Tagliare silenziosamente farebbe girare una config che dichiara un numero e un libro che ne esegue un altro. E SCALA_LADDER esiste perché una scala a gradini rende inesprimibile «solo un po'»: 1,05 non è un valore prudente, è un valore fuori scaletta, e viene rifiutato.

(d) La scala vive solo sul percorso fidato. Equity reale illeggibile ⇒ scala 1,00. Una scala è una decisione di rischio presa conoscendo il conto; quando non si sa quanto vale il conto, non si prende. Così «il fallback non è più permissivo» è vero per costruzione, non per aritmetica. ⚠️ Ma la validazione avviene comunque, anche sul percorso degradato: una config rotta è un errore di configurazione, non una condizione di mercato, e non deve nascondersi dietro un giro in cui l'equity non era leggibile.

2.2 La guardia che morde per prima, e non è quella che sembra

Il disaster-SL è un movimento di prezzo (30% sul mark), ma il suo impatto in equity è quel movimento moltiplicato per il lordo — quindi cresce con k anche se disaster_sl_pct non cambia:

  • dal peggior giorno possibile: k ≤ 3,49x
  • dal costo di un disaster-SL: k ≤ 1,67x2,1× più stringente

Da qui l'invariante n_asset · frac · scala · disaster_sl_pct ≤ 0,50, che scatta anche se qualcuno allarga lo stop invece di alzare la scala.

3. Il test che conta più degli altri

T1b, e la ragione per cui la specifica gli dedica un paragrafo: a k=1 l'implementazione simmetrica e quella asimmetrica danno lo stesso identico numero. Un test di simmetria scritto sul caso di default passa sempre e non controlla niente — potenza zero, la stessa firma dei test di book_live scoperti senza potenza a libro flat.

T1b quindi verifica due cose: (a) che a k=1 le due implementazioni siano davvero indistinguibili (cioè che il rischio sia reale), e (b) che fuori da k=1 l'asserzione di T1 le separi. Nel test c'è scritta un'implementazione asimmetrica apposta perché fallisca.

Gli altri: T2/T3 i due tetti letti da config coi bersagli importati dalla produzione; T4 il fallback mai più permessivo in ogni stato degradato; T5 il cap che morde ancora; T6 lo stop non clampato (più T6b sul prodotto e T6c sul disaster-SL); T7 l'inerzia bit-exact; T8 il report che la dichiara; T9 i bersagli derivati e non ridichiarati; T10 il controllo positivo del sorvegliante (con una config non autorizzata deve parlare, con config e giornale concordi deve tacere) più T10b sulla disciplina degli allarmi e T10c sui tre stati; T11 che il vecchio test sia stato sostituito, non rilassato.

4. Il sorvegliante, e cosa non può fare

scale_watch gira in cron_daily e risponde a tre domande che hanno tre azioni diverse:

domanda azione se 🚨
la config dichiara una scala che il giornale non ha mai autorizzato? ricostruire come ci si è arrivati, scendere a k0
il criterio che autorizzò la scala corrente passa ancora oggi? scendere di un gradino
il prodotto frac · scala · n_asset ha superato il tetto? bloccare l'esecuzione

Tre stati (OK / ALLARME / NON MISURABILE), una allerta per streak, e il marcatore «già detto» scritto solo dopo un invio riuscito (il debito #2 del 28/08: un 🚨 perso prima era perso per l'episodio intero).

Riporta anche la frequenza del ramo di fallback, che se superasse il 2% in 90 giorni invaliderebbe la regola (d): il libro girerebbe a una leva mista e g(k) smetterebbe di descriverlo. Misurata oggi: 0 giri su 1.676. ⚠️ I 19 giri senza equity reale sono tutti «paper capital», cioè pre-finanziamento, dove il libro non invia affatto — il sorvegliante li conta e li dichiara separatamente invece di gonfiare la quota.

🚨 Ciò che il sorvegliante NON può fare: impedire la modifica. Chi ha accesso al file può scriverci dentro. Rende la modifica visibile entro 24 ore e attribuibile. È la stessa onestà di venue_watch: non protegge il saldo, compra tempo.

5. Verifica che il libro non è cambiato

  sizing base     : $2,052.22 | cap/asset $1026 | min $5 | disaster-SL -30%
  scala libro     : 1.00x (tetto 1.25x) | leva lorda max 1.000x dell'equity  [chiave assente o 1,00 = libro invariato]
  BTC TP +0.461 · SKH +0(flat) -> net $+355 | pos $+357 -> HOLD (a target)
  ETH TP +0.278 · SKH +0(flat) -> net $+214 | pos $+212 -> HOLD (a target)

Gli stessi target del cron delle 15:47. Suite: 825 passati (807 + 18 nuovi).

6. Cosa NON ho fatto, ed è deliberato

Non ho scritto la chiave in config. Il punto 2 della checklist lo prescrive esplicitamente, e T7 dimostra bit-exact che senza la chiave il libro è quello di prima.

Non ho eseguito GATE SCALA-01, e non era possibile: A2 richiede che k0 sia in produzione con il criterio passato ogni giorno, e il punto 5 impone ≥30 giorni a 1,00 col sorvegliante attivo prima di eseguire il gate. Cito la specifica perché la frase è il motivo:

Il punto 5 non è burocrazia: è l'unico modo di scoprire che il sorvegliante è rotto mentre la leva è ancora 1,00.

Non ho rifatto r0726_fee_sensitivity (A7). Serve solo se e quando il gradino viene chiesto: la sua conclusione «liquidation fee 1% irrilevante» era condizionata a lordo ≤1x, e a 1,25x una liquidazione costerebbe 1,25% dell'equity. Non è un problema, è un numero da ricalcolare — e ereditarlo sarebbe l'errore.

📌 A6 (anti-recency) oggi non morde: k_ammesso ≈ 3,5x contro un gradino di 1,25x, e il vincolo che morde è il disaster-SL. È scritto adesso perché adesso non è comodo per nessuno — che è l'unico momento in cui una regola del genere si può scrivere onestamente.

7. Stato del gate

GATE SCALA-01 è ora in CLAUDE.md §4 con data non prima del 2026-10-01. A8 e A9 sono fatti; A2 matura col tempo; A1/A4/A5 sono aritmetica già cablata; A3/A6/A7 si eseguono il giorno del gate, sui dati di quel giorno, non su questi.