Files
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

38 KiB
Raw Permalink Blame History

SPEC — la CHIAVE DI SCALA del libro live (book_scale_k)

Filone SCALE-SPEC, ondata 2026-08-22, branch research/wave-0822.

IMPLEMENTATA IL 2026-09-01 — punti 1-4 della checklist §8. src/live/book.py (book_scale_k, _scala, LEVA_LORDA_MAX, SCALA_LADDER, DISASTER_SL_BUDGET, ScalaNonAutorizzata, scala nel report), scripts/live/book_execute.py (riga «scala libro» + stop-e-allerta), src/live/scale_watch.py + scripts/live/scale_watch.py in cron_daily.sh, data/live/scale_history.jsonl, tests/test_book_scale.py (T1-T11, 18 test verdi). 🚨 config/live.json NON e' stato toccato, come prescrive il punto 2: la chiave e' assente, vale 1,00, e T7 dimostra bit-exact che il libro e' quello di sempre (max|diff| = 0.0). Restano i punti 5-7: ≥30 giorni a 1,00 col sorvegliante attivo, poi GATE SCALA-01, poi r0726_fee_sensitivity rifatto. Il punto 5 non e' burocrazia — e' l'unico modo di scoprire che il sorvegliante e' rotto mentre la leva e' ancora 1,00.

Questo documento era una SPECIFICA scritta prima dell'implementazione. Il prototipo che dimostra la meccanica e' scripts/research/r0822e_scale_proto.py (isolato, non importato da niente, non scrive nulla; la sua sezione (0) verifica entrambe le cose a runtime invece di dichiararle).

Nasce da RESULTS-0822.md §33 (WORST-DAY), che ha misurato che il gradino di leva e' l'unica leva grande rimasta e che C6 (processo) FALLISCE: la regola del progetto — «ogni cambio di SCALA passa dal cap di config, non da target_vol»non e' implementabile come scritta, perche' il cap e' un CLAMP e non un moltiplicatore.


0. IL RISCHIO CHE QUESTA SPECIFICA DEVE RENDERE IMPOSSIBILE

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

Il progetto ha regole contro questa forma di deriva su ogni altro asse — weights_tilt_null per i pesi, gate pre-registrati per i candidati, select_cell_insample per le griglie, anchor_luck_band per le ancore — e nessuna per la scala. Non per dimenticanza: per aritmetica.

Lo Sharpe e' invariante alla scala. Misurato nel prototipo (sez. 7a), sulla serie vera del libro:

k Sharpe drift vol maxDD g(k) esatto
1,00 1,5051 17,11% 11,37% 12,17% 17,90%
1,25 1,5051 21,39% 14,21% 15,01% 22,61%
2,00 1,5051 34,22% 22,74% 23,10% 37,24%

deflated_sharpe e marginal_vs_tp01 leggono la prima colonna. Non falliscono: non vedono la variabile. Un raddoppio della leva passa ogni gate esistente del progetto senza far scattare nulla, e produce lo stesso identico numero di sintesi che il progetto usa per giudicare tutto il resto.

Da qui discendono i tre vincoli non negoziabili del disegno:

  1. il tetto sta nel CODICE, non in config (una config puo' essere modificata da chiunque, in qualunque momento, senza review; una costante di codice no);
  2. il tetto sta sul PRODOTTO, non sulla chiave (frac e scala moltiplicano: un tetto sulla sola chiave lascia aperta la porta accanto — vedi §4.2, tabella);
  3. il gate e' uno SCRIPT che stampa PASSA/FALLISCE, non un giudizio (§6), e c'e' un sorvegliante giornaliero che se ne accorge dopo il fatto se qualcuno lo salta (§6.4).

⚠️ Correzione dovuta al mandato di questo filone. Il gradino non vale quello che dice la frase che circola. RESULTS-0822.md §2 misura 1,00x → 1,25x = 14,7a → 12,9a, e 1,00x → 1,50x = 14,7a → 11,6a; il «~€300/mese di versamenti equivalenti» appartiene al gradino 1,50x. Ricalcolato sulla tabella netta pubblicata (r0807_piano_netto: €500/m → 14,7a, €800/m → 11,4a, interpolazione lineare dichiarata):

gradino anni al capitale-rendita versamento mensile equivalente delta
1,00x (oggi) 14,7a €500/m
1,25x 12,9a ~€664/m +€164/m
1,50x 11,6a ~€782/m +€282/m

L'interpolazione riproduce il «~€300/mese» pubblicato per 1,50x (€282) → è un controllo, non solo un conto. Questa specifica autorizza il gradino a 1,25x e vale ~€165/mese, non ~€300.


1. COSA SPECIFICA, E COSA NON FA

Specifica: dove va la chiave, in che ordine si applica rispetto al cap, quali guardie servono, quali test devono esistere prima della modifica, quale gate deve essere superato per alzarla e con quale cadenza si sorveglia.

NON fa: non autorizza il gradino, non implementa niente, non tocca config/live.json. Con la chiave assente o = 1.0 il libro e' bit-exact quello di oggi (§5, test T7): la modifica e' inerte finche' qualcuno non scrive un numero, e quel numero passa dal gate del §6.


2. DOVE VA LA CHIAVE, ESATTAMENTE

2.1 La catena, letta dal sorgente vero

Il prototipo la stampa leggendo i file (sez. 2), cosi' le righe qui sotto sono verificabili e non citate a memoria:

stadio file:riga cosa produce
segnale TP01 src/live/shadow.py:119 targets = {a: tp.current_target(dfs[a])...} una frazione ≥0, long-flat
segnale SKH01 src/live/book.py:217 skh = _skyhook_positions(...):228 sign = _skh_sign(st) un segno +1/1/0
cap per-asset src/live/book.py:107 def _cap(...):194 cap = _cap(...) il clamp, legato al watermark
TARGET NETTO src/live/book.py:138 def book_net_target(...):229 net = book_net_target(tp_frac, sign, equity, cap) ← QUI VA LA SCALA
ordine src/live/book.py:153 def build_book_order(...):232 un delta verso il target
invio scripts/live/book_execute.py:208 trader.rebalance_signed(inst, net, mark, ...) il fill

2.2 La firma proposta

# src/live/book.py
def book_net_target(tp_frac: float, skh_sign: int, equity: float, cap: float,
                    weight: float = WEIGHT, scala: float = 1.0) -> float:
    raw = weight * equity * (W_TP01 * max(tp_frac, 0.0) + W_SKH * float(skh_sign))
    return scala * max(-cap, min(cap, raw))        # <-- la scala e' FUORI dal clamp: vedi §3

più, accanto a _cap(), un lettore dedicato:

LEVA_LORDA_MAX = 1.25          # COSTANTE DI CODICE, non di config. Vedi §4.1.
SCALA_LADDER = (1.00, 1.25)    # i soli valori esprimibili. Vedi §4.1 e §6.

def _scala(real_equity, eq_fallback) -> float:
    """Scala del libro. 1.0 (= il libro di oggi) in ogni stato degradato: vedi §4.3."""
    if real_equity is None or eq_fallback:
        return 1.0
    ...  # legge config, valida contro LEVA_LORDA_MAX e SCALA_LADDER, ALZA se fuori (§4.1)

e in book_report, una riga sotto cap = _cap(...):

scala = _scala(sh.get("real_equity"), sh.get("eq_fallback"))
...
net = book_net_target(tp_frac, sign, equity, cap, scala=scala)

book_report deve esporre scala nel dict di ritorno accanto a cap_per_asset, e book_execute deve stamparla nella riga «sizing base»: un parametro che agisce sul nozionale e non compare nel report e' la forma esatta del difetto che questo progetto ha gia' pagato tre volte (docstring che dichiarano una causalita' che il codice non ha).

2.3 Perche' li' e non altrove — e la dimostrazione

book_net_target e' l'unico punto della catena in cui le due gambe si incontrano con una TAGLIA: sopra di lei TP01 da' una frazione e SKH01 da' solo un segno; sotto di lei si calcolano delta e ordini. Percio' e' l'unico punto in cui un moltiplicatore e' un puro cambio di scala.

Il prototipo (sez. 3) lo misura sulla funzione di produzione stessa — la copia e' bit-exact (max|copia produzione| = 0.00e+00 su 480 combinazioni):

k solo TP01 (tp=1, sg=0) solo SKH01 (tp=0, sg=1) rapporto TP/SKH peso implicito TP01
1,00 2.267,62 755,88 3,000000 0,750000
1,25 2.834,53 944,84 3,000000 0,750000
2,00 4.535,25 1.511,75 3,000000 0,750000

max|target(k) k·target(1)| = 0.00e+00 su 90 combinazioni. Il mix 75/25 e' invariante a ogni k: e' una scala, non un tilt.

Le alternative, e perche' cadono:

dove perche' NO
TrendPortfolio.target_vol scala il 75% del libro e lascia fermo il 25% → e' anche un tilt di pesi, e fallisce weights_tilt_null. Misurato nel prototipo come controllo positivo: a k=1,25 il peso implicito di TP01 passa da 0,750 a 0,789.
_skyhook_positions non c'e' niente da scalare: produce un segno.
WEIGHT (=0,5) e' lo split BTC/ETH, ed e' condiviso con shadow_report/build_rebalance_order (shadow.py:144) e con la dashboard, cioe' con il percorso TP01-only: cambiarlo cambierebbe anche un altro libro. E confonderebbe in un solo numero «quanti asset» e «quanta leva».
W_TP01 / W_SKH sono il rapporto 75/25 e sono legati per test ai pesi del backtest (test_weights_match_backtest_sleeves asserisce W_TP01+W_SKH == 1.0 e la parita' con deribit_book_sleeves). Moltiplicarli entrambi romperebbe quella parita' e renderebbe impossibile distinguere scala e mix.
build_book_order scalerebbe il delta, non il target: cambia la velocita' di convergenza, non la taglia, e il reconciler litigherebbe con se' stesso a ogni giro.
dentro DeribitTrader il report, il dry-run e book_executions.jsonl stamperebbero un target diverso da quello eseguito.

3. L'INTERAZIONE COL CAP — il punto sottile

3.1 Il fatto geometrico

Il cap e' un clamp: net = clamp(0.5·E·(0,75·tp + 0,25·sg), ±cap) con cap = frac·E = 0,50·E. A piena convinzione (tp=1, sg=+1) il grezzo vale esattamente 0,5·E·1,0 = 0,50·E = cap: il clamp e' tangente al libro proprio nei giorni di massima esposizione.

3.2 I due ordini, misurati (prototipo sez. 4, su 5.648 giorni-asset veri)

k ordine clampati quota k_eff medio k_eff a piena convinzione lordo max
1,25 PRIMA del clamp 32 0,57% 1,2487 1,0000 1,000
1,25 DOPO il clamp 0 0,00% 1,2500 1,2500 1,250
2,00 PRIMA 793 14,04% 1,9235 1,0000 1,000
2,00 DOPO 0 0,00% 2,0000 2,0000 2,000

L'ordine corretto e' DOPO il clamp, e la ragione non e' di comodo:

  • PRIMA: il clamp mangia la scala proprio nei giorni di massima convinzione (k_eff = 1,00 a piena convinzione, a ogni k). Il risultato non e' «il libro a leva k»: e' un libro che alza i giorni piccoli e lascia fermi i grandi, cioe' un appiattimento del profilo di convinzione. E' un cambio di FORMA travestito da cambio di taglia — e allora la curva g(k) di r0822_growth_policy / WORST-DAY non descrive quel libro, perche' quella curva e' calcolata su k·r, cioe' sull'ordine DOPO. Autorizzare il gradino con una curva e implementarne un'altra e' il modo piu' silenzioso di sbagliare che questa specifica possa produrre.
  • DOPO: k_eff = k esattamente, in ogni giorno e in ogni stato del segnale. E' l'unico ordine per cui «leva k» significa la stessa cosa nel codice e nel backtest.

3.3 Cosa implica per il SIGNIFICATO del cap — e va detto, perche' e' il prezzo

Con l'ordine DOPO, il cap smette di essere il tetto assoluto del nozionale. Diventa il tetto del libro unitario: limita la forma (nessun asset oltre frac·E di esposizione unitaria), mentre la taglia la decide la scala. Il lordo massimo passa da n_asset·frac a n_asset·frac·scala = 1,250x l'equity a k=1,25.

E il cap non smette di fare le altre due cose che fa oggi, che sono quelle che contano davvero:

  1. resta proporzionale all'equity (cap = equity·frac sul percorso fidato) → un deposito non strozza il libro e un prelievo lo rimpicciolisce da solo;
  2. resta legato al watermark sul percorso di fallback (min(fisso, watermark·frac)) → il caso che il 26/07 valeva «3,35x di leva su un conto da $597» resta chiuso (§4.3).

⚠️ Cio' che il cap NON puo' piu' fare da solo e' essere la guardia di sicurezza sulla leva lorda. Quella guardia va ricostruita esplicitamente, ed e' il §4.

3.4 Il test che si rompe — e deve rompersi

tests/test_fee_sensitivity.py::test_leva_massima_da_config_resta_sotto_o_uguale_a_1x:

leva_max = frac * len(FS.ASSETS)
assert leva_max <= 1.0, ("leva massima ...: la liquidation fee 1% torna rilevante, "
                         "rivedere r0726_fee_sensitivity e il diario 2026-07-26-fee-deribit.md")

Si rompe? No — e questo e' il punto. Con frac invariato a 0,50 il test continua a passare, perche' misura frac · n_asset mentre la grandezza vera diventa frac · n_asset · scala. Non si rompe, e smette di controllare cio' che era stato scritto per controllare.

E' esattamente la forma di difetto che il progetto ha catturato tre volte in un giorno il 2026-08-21: un controllo puntato su una configurazione diversa da quella che gira passa sempre, e non sta controllando niente (fee_watch sugli inverse, venue_watch con bitfinex, i test di book_live a libro flat).

Quindi va sostituito, non aggiustato, e la sostituzione e' una coppia:

  • T2 (§5) — test_la_leva_lorda_da_config_resta_sotto_il_tetto: asserisce len(ASSETS) * frac * scala <= LEVA_LORDA_MAX, cioe' il prodotto, e importa la scala dallo stesso posto da cui la importa la produzione (regola del 21/08: un sorvegliante deve DERIVARE il proprio bersaglio dal codice sorvegliato, mai ridichiararlo).
  • prerequisito del gate (§6): r0726_fee_sensitivity va rifatto al nuovo lordo, perche' la sua conclusione «liquidation fee 1% irrilevante» era esplicitamente condizionata a nozionale lordo ≤1x. Al gradino il lordo e' 1,25x → una liquidazione costerebbe 1,25% dell'equity, non 1,00%. Non e' un problema; e' un numero che va ricalcolato e ripubblicato, non ereditato.

Il vecchio test va cancellato con una nota che dice perche' (e rimandi a questo documento), non modificato in silenzio: un test di decisione che cambia soglia senza lasciare traccia e' un test di decisione perso.


4. LE GUARDIE

4.1 G1 — il tetto: sul PRODOTTO, in CODICE, e non silenzioso

LEVA_LORDA_MAX = 1.25      # src/live/book.py — costante di CODICE
SCALA_LADDER   = (1.00, 1.25)

Tre proprieta', tutte necessarie:

(a) Sul prodotto. frac e scala moltiplicano. Un tetto sulla sola chiave lascia aperta la porta accanto (prototipo sez. 6):

frac scala leva lorda tetto «scala ≤ 1,25» tetto sul PRODOTTO
0,500 1,25 1,250 passa passa
0,625 1,25 1,562 passa BLOCCA
0,750 1,25 1,875 passa BLOCCA
0,500 2,00 2,000 BLOCCA BLOCCA

Ed e' un rischio concreto, non teorico: WORST-DAY ha appena pubblicato che «alzare frac da 0,50 a 0,625 non produce k=1,25», il che rende frac un candidato naturale al prossimo ritocco — salvo che, combinato con la scala, lo produce eccome.

(b) In codice. Il tetto di config sarebbe modificabile dalla stessa mano, nello stesso file, nel momento in cui si modifica la chiave: sarebbe 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, quindi un gate. Il tetto e' basso apposta: e' il meccanismo del cricchetto, non un'opinione sulla leva massima sopportabile (che dalla §4.5 sarebbe 1,67x).

(c) Non silenzioso. Una scala fuori tetto (o fuori SCALA_LADDER) NON viene tagliata al tetto: book_execute si ferma, non invia ordini, e allerta. Un clamp silenzioso farebbe girare una config che dichiara un numero e un libro che ne esegue un altro — la stessa classe di difetto del §3.4. SCALA_LADDER esiste perche' una scala a gradini rende inesprimibile «solo un po'»: 1,05 non e' un valore prudente, e' un valore fuori scaletta, e viene rifiutato.

4.2 G2 — l'invariante che lega TRE chiavi

assert len(ASSETS) * frac * scala * disaster_sl_pct <= 0.50

Vedi §4.5: e' la guardia che morde per prima, e cattura chi allarga il disaster-SL.

4.3 G3/G4 — il watermark e l'equity non leggibile

Regola proposta: la scala vive SOLO sul percorso fidato.

scala_effettiva = scala  se l'equity REALE e' leggibile,  altrimenti 1.00

Motivo: una scala e' una decisione di rischio presa conoscendo il conto; quando non si sa quanto vale il conto, non si prende. Cosi' «il fallback non e' piu' permissivo» e' vero per costruzione, non per aritmetica — e non dipende dal fatto che il conto degli invarianti torni.

Misurato (prototipo sez. 6-bis; watermark reale letto da disco, $634,996; copia di _cap verificata bit-exact contro la produzione):

stato cap scala eff. lordo max / equity vera tetto
normale (equity reale letta) 317,50 1,25 793,74 1,250x si
online ma equity illeggibile 317,50 1,00 635,00 1,000x si
watermark assente (1° avvio) 300,00 (CAP_UNKNOWN_USD) 1,00 600,00 0,945x si
watermark vecchio e basso 50,00 1,00 100,00 0,157x si
dopo un deposito a $6.047 3.023,50 1,25 7.558,75 1,250x si

In nessuno stato degradato la leva supera quella dello stato normale, e il caso «watermark assente» ha due protezioni indipendenti (cap $300 e scala 1,00), non una sola.

⚠️ Prezzo dichiarato. Nei giri in cui l'equity non e' leggibile il libro gira a k=1 e al giro dopo torna a k → un ribilancio in piu' per oscillazione, di (k1)·frac·E per asset = ~$79 a testa sul conto di oggi, cioe' ~$0,056 di fee a 3,5 bps. Frequenza misurata su logs/cron_book.log: 0 giri su 1.442 (0,00%); i 21 giri «conto non leggibile» sono OFFLINE, e li' il libro non manda ordini affatto, quindi la scala non si pone. → il prezzo esiste, e' noto, ed e' nullo sul campione osservato. ⚠️ E questo e' anche il limite dichiarato della regola: se un giorno il ramo di fallback diventasse frequente, il libro girerebbe a una leva mista e la curva g(k) smetterebbe di descriverlo. → il sorvegliante del §6.4 deve riportare quella frequenza, e se supera il 2% dei giri in 90 giorni la regola va ridiscussa (soglia dichiarata qui, prima di vedere il caso).

Alternativa scartata: congelare l'ultimo target scalato durante il fallback. Aggiunge stato a un percorso con soldi veri — cioe' un nuovo modo di essere sbagliati — per risparmiare $0,056.

4.4 G5 — il min_order_usd non e' un problema, e va detto

La scala aumenta i delta, quindi allontana dal pavimento $5 invece di avvicinarcisi. Nessuna azione. (Lo si dice perche' «non e' un problema» misurato vale quanto un problema trovato: evita che la domanda torni.)

4.5 G6 — il disaster-SL: la guardia che morde per prima, e non era stata calcolata

Il disaster-SL e' un movimento di prezzo (30% sul mark), non una perdita in equity. L'impatto in equity e' quel movimento moltiplicato per il lordo, quindi cresce con k anche se disaster_sl_pct non cambia. Prototipo sez. 6, tutte le righe scalate dai numeri replicati di WORST-DAY (§7):

k lordo max A) peggior giorno possibile B) costo disaster-SL C) x_liq min intra·k margine C
1,00 0,864 14,32% 30,00% 115,8% 7,33% 15,8x
1,25 1,081 17,90% 37,50% 92,5% 9,16% 10,1x
1,50 1,297 21,48% 45,00% 76,9% 11,00% 7,0x
2,00 1,729 28,64% 60,00% ← sfonda 57,4% 14,66% 3,9x

📌 La guardia che morde per prima NON e' il peggior giorno possibile, e' il disaster-SL:

  • da (A): k ≤ 50% / 14,32% = 3,49x
  • da (B): k ≤ 50% / (2 · 0,50 · 30%) = 1,67x2,1x piu' stringente

A k=1,25 il margine di (A) e' 2,8x (il numero che WORST-DAY pubblica come C1) mentre quello di (B) e' 1,33x. Il criterio C1 di WORST-DAY guardava la grandezza sbagliata: guardava il peggior giorno del segnale, non il costo del dispositivo di emergenza che il libro ha davvero armato sul conto. Da qui l'invariante G2 (§4.2), che con sl_pct=30% e frac=0,50 da' scala ≤ 1,67x — e che scatta se qualcuno allarga il disaster-SL.

Decisione: disaster_sl_pct NON si tocca. Stringerlo a 24% per tenere invariante l'impatto in equity lo trasformerebbe da dispositivo di catastrofe in stop di strategia: verrebbe toccato dal rumore, e il progetto ha gia' misurato cosa succede quando uno stop stretto incontra un gap (SKH01, sl 2% modellato → 11/23% realizzato). Si accetta il 37,5% e lo si scrive.

⚠️ Osservazione da LETTURA DEL CODICE, non misurata, e va citata cosi'. src/live/execution.py:196-223 ri-ancora il disaster-SL al mark a ogni giro: se lo stop esistente dista piu' del 5% relativo da mark·(1sl_pct), viene cancellato e ripiazzato. Per un long questo significa che lo stop segue il prezzo anche in discesa: il 30% e' un 30% rotolante dall'ultimo piazzamento, non un pavimento assoluto sul trade. Quindi B e' il costo di UN episodio, e un ribasso lento puo' produrne piu' d'uno. Evidenza circostanziale, non prova: nel log ci sono 15 placed contro 259 ok su asset con posizione aperta — coerente con qualche ri-ancoraggio, ma i placed includono anche le prime aperture e non sono separabili dal log. → e' una proprieta' di oggi che il gradino amplifica, non una che il gradino crea, ed e' un follow-up con una domanda precisa: quante volte il bracket e' stato ri-piazzato mentre la posizione restava aperta? (misurabile aggiungendo pos_prima al log, non ricostruibile da quello attuale).


5. I TEST CHE DEVONO ESISTERE PRIMA DELLA MODIFICA

Scritti come specifiche eseguibili: nome + cosa asserisce + il caso che deve far fallire. File proposto: tests/test_book_scale.py (piu' le modifiche dichiarate a tests/test_fee_sensitivity.py).

# test asserisce deve fallire se
T1 test_la_scala_e_identica_sui_due_sleeve su una griglia di k (1,00/1,10/1,25/1,50/2,00) × tp_frac × segno, con cap disattivato: target(k) == k·target(1) e target(tp=1,sg=0)/target(tp=0,sg=1) == W_TP01/W_SKH costante qualcuno applica la scala a una sola gamba (o dentro TrendPortfolio)
T1b test_il_test_di_simmetria_smaschera_una_scala_asimmetrica CONTROLLO POSITIVO: la stessa asserzione, applicata a un'implementazione asimmetrica scritta nel test, fallisce il test T1 gira solo a k=1 (dove le due implementazioni coincidono: potenza zero — misurato nel prototipo)
T2 test_la_leva_lorda_da_config_resta_sotto_il_tetto len(ASSETS) * frac * scala <= LEVA_LORDA_MAX, con frac e scala letti da config/live.json e LEVA_LORDA_MAX/ASSETS importati dal modulo di produzione qualcuno alza frac, o la scala, o entrambe di poco
T3 test_l_invariante_disaster_sl_tiene_meta_equity len(ASSETS) * frac * scala * disaster_sl_pct <= 0.50 qualcuno allarga disaster_sl_pct o alza la scala fino a 1,67x
T4 test_il_fallback_non_e_piu_permissivo_del_percorso_fidato in ogni stato degradato (equity illeggibile / watermark assente / watermark vecchio) il lordo massimo / equity vera ≤ quello dello stato normale qualcuno applica la scala anche in fallback, o slega il cap dal watermark
T5 test_il_cap_continua_a_limitare_cio_che_limitava il target per-asset non supera mai cap·scala; e il clamp morde ancora sulle stesse osservazioni di prima (le 3 su 5.650) qualcuno inverte l'ordine (§3.2) o toglie il clamp «tanto c'e' la scala»
T6 test_una_scala_fuori_tetto_ferma_l_esecuzione_e_non_viene_tagliata con scala fuori SCALA_LADDER o fuori tetto: book_execute non invia, allerta, e il valore non viene silenziosamente clampato qualcuno «rende robusto» il lettore con un min(scala, LEVA_LORDA_MAX/…)
T7 test_senza_la_chiave_il_libro_e_quello_di_oggi con chiave assente o = 1.0, book_net_target e' bit-exact alla versione attuale su una griglia (`max diff
T8 test_il_report_e_il_log_dichiarano_la_scala book_report()['scala'] esiste e book_execute la stampa un parametro che muove il nozionale resta invisibile nel report
T9 test_la_scala_e_derivata_dal_codice_non_ridichiarata il sorvegliante e i test importano LEVA_LORDA_MAX, ASSETS, SCALA_LADDER da src/live/book e non li riscrivono due liste in due file (regola del 21/08, 4 occorrenze)
T10 test_il_sorvegliante_segnala_una_scala_non_autorizzata CONTROLLO POSITIVO del §6.4: con una config che dichiara una scala assente dal giornale, il sorvegliante allerta; con config e giornale concordi, tace il sorvegliante e' tarato per non segnalare mai (regola del 26/07: un rilevatore tarato per non segnalare e' indistinguibile da uno rotto)
T11 test_il_vecchio_test_di_leva_e_stato_sostituito_non_rilassato test_leva_massima_da_config_resta_sotto_o_uguale_a_1x non esiste piu' e T2/T3 esistono qualcuno alza la soglia del vecchio test da 1,0 a 1,25 e chiude li'

⚠️ T1b e' il test piu' importante dell'elenco, ed e' la ragione per cui questo paragrafo esiste: il prototipo (sez. 3) mostra che 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 — la stessa firma dei test di book_live scoperti senza potenza a libro flat.


6. IL GATE MANCANTE — GATE SCALA-01

6.1 Perche' serve un gate NUOVO

Vedi §0: nessun gate esistente vede la scala. Il gate deve quindi vivere sull'unico asse su cui la scala si vede: crescita e rovina (g(k), maxDD, distanza dalla liquidazione), non Sharpe.

6.2 Le condizioni — tutte necessarie

Per portare book_scale_k da k0 a k1:

condizione misurata come
A1 k1 e' il gradino successivo di SCALA_LADDER (1,00 → 1,25). Nessun valore intermedio. confronto con la costante di codice
A2 k0 e' in produzione da ≥180 giorni con il criterio passato ogni giorno giornale data/live/scale_history.jsonl (append-only) + registrazioni del sorvegliante
A3 C1C5 di WORST-DAY ricalcolati oggi a k1, tutti PASSA — non ereditati dal 22/08 ri-esecuzione di r0822c_worst_day sui dati correnti
A4 C7 = G2: n_asset · frac · k1 · disaster_sl_pct ≤ 0,50 §4.5
A5 C8 = G1: n_asset · frac · k1 ≤ LEVA_LORDA_MAX §4.1
A6 ANTI-RECENCY: il criterio deve passare anche escludendo gli ultimi 90 giorni: k_ammesso(r[:-90]) ≥ k1 §6.3
A7 r0726_fee_sensitivity rifatto al nuovo lordo e la conclusione «liquidation fee irrilevante» ri-derivata o ritirata §3.4
A8 la modifica e' una riga di config + una riga di giornale, zero codice. Se serve toccare LEVA_LORDA_MAX, non e' una decisione di config diff
A9 i test T1T11 (§5) esistono e passano prima pytest

6.3 A6 — l'anti-recency, e come si misura davvero

La forma verificabile e': il gradino dev'essere autorizzato anche dai dati che non contengono la finestra recente che ha motivato la richiesta. E' l'analogo, sull'asse della scala, di select_cell_insample.

Il prototipo (sez. 7b) misura la meccanica su k_ammesso = ½·k*(drift a 2 SE, coda troncata al bound strutturale):

finestra n drift punto drift 2SE k ammesso ≥1,25x?
tutta la storia 2.719 17,11% 8,78% 3,52 SI
esclusi ultimi 90 g 2.629 16,70% 8,15% 3,20 SI
esclusi ultimi 365 g 2.354 19,52% 10,20% 3,80 SI
solo ultimi 365 g 365 1,59% 13,69% 0,03 NO

e la sensibilita' a un tratto recente artificialmente buono (ultimi 90 giorni riscritti a un drift dato, sull'intera storia):

ultimi 90 g portati a drift 2SE k ammesso delta
0%/anno 7,82% 3,12 0,40
+50%/anno 9,47% 3,80 +0,28
+100%/anno 11,11% 4,45 +0,93
+200%/anno 14,35% 5,63 +2,10

Il 2 SE fa gran parte del lavoro (un tratto eccezionale alza insieme il drift e la sua incertezza), ma non basta: il criterio resta una funzione dei dati recenti, e la protezione vera e' la doppia valutazione di A6.

⚠️ Tre onesta' obbligatorie su questa sezione:

  1. k_ammesso del prototipo e' deliberatamente semplificato (troncatura al bound, nessuna GPD): serve a mostrare la meccanica, non a produrre il numero. Il gate reale usa la versione di r0822c_worst_day.
  2. Oggi A6 non morde: k_ammesso ≈ 3,5x contro un gradino di 1,25x. Il vincolo che morde e' quello del §4.5 (disaster-SL). A6 serve al gradino successivo, non a questo — e va scritto ora, quando non e' comodo per nessuno, non quando servira'.
  3. La prima stesura di questa sezione presentava «solo ultimi 365 giorni» come l'esempio di «un anno buono»: falso, quell'anno e' stato debole (drift 1,59%). Quella riga mostra solo che un campione corto ha un SE enorme. La riga che risponde alla domanda vera e' la tabella dello shock.

6.4 La cadenza — scale_watch

Sorvegliante giornaliero (proposto: src/live/scale_watch.py + scripts/live/scale_watch.py in cron_daily.sh; non implementato), che risponde a tre domande diverse — e sono davvero tre, perche' hanno tre azioni diverse e nessuna e' deducibile dalle altre:

domanda esito azione
la config dichiara una scala che il giornale non ha mai autorizzato? 🚨 ricostruire come ci e' arrivata; scendere a k0
il criterio che autorizzo' la scala corrente passa ancora oggi? 🚨 scendere di un gradino
il prodotto frac·scala·n_asset ha superato LEVA_LORDA_MAX? 🚨 bloccare l'esecuzione

Piu' due requisiti che il progetto ha gia' imparato a pagare:

  • tre stati, non due: OK / ALLARME / NON MISURABILE (serie troppo corta, dati mancanti). «Non vedo» non e' «va tutto bene»paper_dvolspread, venue_watch.
  • disciplina degli allarmi: una allerta per streak (lock_step), non una per giro; e il messaggio deve dire cosa (decide) e perche' (ripara), con il valore letto, mai cablato.

E deve riportare la frequenza del ramo di fallback (§4.3), che oggi e' 0/1.442 e che se salisse sopra il 2% in 90 giorni invalida la regola «scala solo sul percorso fidato».

⚠️ Cio' che il sorvegliante NON puo' fare: impedire la modifica. Chi ha accesso al file puo' scriverci dentro. Il sorvegliante rende la modifica visibile entro 24 ore e attribuibile. E' la stessa onesta' di venue_watch: non protegge il saldo, compra tempo.


7. IL COSTO DI NON FARLO, E IL RISCHIO DI FARLO

7.1 Il costo di restare a 1,00x

~€165/mese di versamenti equivalenti, a costo zero, per sempre (§0): 14,7a → 12,9a al capitale-rendita a €500/m netto fisco. Sulla scala delle leve misurate dal progetto sta sotto «versare o no» (da mai a 16 anni) e «quando versare» (fattore 6), e sopra tutto cio' che la ricerca ha prodotto in due mesi: l'ottimizzazione del peso SKH01 vale +0,030 di Sharpe e ha fallito il suo gate; il gradino vale 1,8 anni.

Ed e' l'unica leva grande che non richiede soldi nuovi.

7.2 Il rischio di farlo, in una tabella

grandezza k=1,00 k=1,25 fonte
peggior giorno possibile (chiusura) 14,32% 17,90% replicato, §4.5
peggior giorno vissuto (chiusura) 3,95% 4,94% replicato
peggior minimo intra-giorno 7,33% 9,16% WORST-DAY (lente accoppiata)
costo di un disaster-SL 30,0% 37,5% §4.5 ← la guardia che morde
margine di liquidazione 15,8x 10,1x §4.5
maxDD storico 12,17% 15,01% §0
costo funding non modellato ~1,53%/a ~1,91%/a FUNDING §35
costo di una liquidazione (fee 1%) 1,00% eq 1,25% eq §3.4, da ricalcolare

7.3 La riga che WORST-DAY ha dichiarato, e che va tenuta in cima alla decisione

Se si credono entrambe le riserve insieme — drift a 2 SE e un giorno a 10% ogni anno — il drift atteso e' 0,61%/anno, e a quel punto non e' il gradino a essere sbagliato, e' il libro: chi usa quella combinazione sta chiedendo di spegnere anche il conto di oggi.

Questa specifica non risolve quel dilemma e non deve: lo rende esplicito. Chi rifiuta il gradino in nome di entrambe le riserve deve dire cosa propone di fare del libro a k=1.

7.4 Cosa questa specifica non rende sicuro

  • La lente e' close-only dove non usa i numeri intra-giorno di WORST-DAY. Su una regola a un giorno close-only e' esattamente cieca (25/07, riconfermato dallo scettico il 22/08). Sul canale prop/funded — dove il vincolo binding e' una barriera di DD giornaliera — questa specifica non si applica e la lente accoppiata resta obbligatoria.
  • g(k) assume ritorno indipendente dalla size. A $635 e' innocuo (SLIP-AUDIT: nessun impatto misurabile, e un fill ha preso il 21,9% del volume della sua barra 5m in un range di 1,08 bps), ma quella misura ha una data di scadenza — la partecipazione scala lineare col capitale.
  • Il funding non e' in nessuna serie di questo documento. Il libro paga gia' ~1,5%/anno che nessun backtest sottrae; il gradino lo amplifica del 25%. E' dentro C5, ma il livello a k=1 resta un difetto aperto del progetto, non di questa specifica.
  • La leva non compra probabilita', compra fretta (PROP-ALLOC). Il gradino accorcia la mediana; non alza P(arrivare) in modo proporzionale, e alza la varianza del percorso.

8. PROCEDURA DI ATTIVAZIONE (checklist, in ordine)

  1. Scrivere tests/test_book_scale.py con T1T11 (§5). Prima. T7 deve passare contro il codice attuale (la chiave non esiste ancora → default 1,0 → bit-exact).
  2. Implementare §2.2 (scala in book_net_target, _scala(), LEVA_LORDA_MAX, SCALA_LADDER, scala nel report e nel print di book_execute). Senza toccare config/live.json: la chiave assente vale 1,0, il libro non cambia, i test lo dimostrano.
  3. Cancellare test_leva_massima_da_config_resta_sotto_o_uguale_a_1x con la nota (§3.4).
  4. Scrivere scale_watch (§6.4) + data/live/scale_history.jsonl con la riga iniziale {"scala": 1.00, "dal": "...", "gate": "iniziale, nessun gradino"}, e aggiungerlo a cron_daily.sh e al backup di /opt/docker/scripts/backup.sh (il giornale non e' ricostruibile).
  5. Lasciare girare a 1,00 per almeno 30 giorni con il sorvegliante attivo. Una manopola nuova si guarda funzionare prima di girarla.
  6. Solo allora: eseguire GATE SCALA-01 (§6.2). Se PASSA, una riga di config + una di giornale.
  7. Rifare r0726_fee_sensitivity al nuovo lordo (A7) e ripubblicare la conclusione.

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


9. NODI APERTI — dichiarati, non nascosti

  1. Il disaster-SL rotolante (§4.5, ⚠️): letto nel codice, non misurato. Il log non permette di separare i ri-piazzamenti dalle prime aperture. Se davvero rotola, B e' il costo di un episodio e un ribasso lento ne produce piu' d'uno. E' il nodo piu' serio di questo documento, perche' tocca la guardia che morde per prima. Follow-up: aggiungere pos_prima al log di ensure_disaster_sl e ri-misurare — non ricostruibile a posteriori.
  2. LEVA_LORDA_MAX = 1.25 e' una scelta di processo, non una misura. Dall'invariante del §4.5 il massimo sostenibile sarebbe 1,67x. Il valore basso e' il cricchetto. Chi vuole 1,50x deve toccare codice — ed e' voluto.
  3. La banda d'ancora non e' stata girata su nessun numero di questo documento. Tutte le serie sono all'ancora canonica con la correzione ×0,89 sul drift. Le grandezze usate per decidere (bound strutturale, lordo, invarianti) sono aritmetiche e non ancorate; g(k) e k_ammesso lo sono — e vanno lette con la banda del libro (Sharpe FULL de-luckato 1,95, banda [1,81; 2,12]).
  4. Il gradino non e' stato autorizzato qui. Questo documento specifica come si autorizza.

10. IL PROTOTIPO

scripts/research/r0822e_scale_proto.py0,9 s, nessuna rete, nessuna scrittura.

nice -n 19 timeout 900 uv run python scripts/research/r0822e_scale_proto.py

Sezioni: (0) isolamento verificato · (1) sanity bit-exact vs produzione · (2) la catena letta dal sorgente · (3) simmetria + controllo positivo · (4) l'ordine col clamp · (5) replica di WORST-DAY · (6) guardie · (6-bis) stati dell'equity · (7) il gate mancante.

Replica a k=1,00 (sez. 5) — il motivo per cui le righe scalate di questo documento non sono numeri copiati:

grandezza prototipo pubblicato scarto
drift de-luckato ×0,89 17,11% 17,11% 0,00%
vol annua 11,37% 11,37% 0,00%
Sharpe de-luckato 1,505 1,510 0,005
peggior giorno (chiusura) 3,95% 3,94% 0,01%
data del peggior giorno 2020-03-13 2020-03-13 coincide
morso del clamp 3 / 5.648 3 / 5.650 2 righe di bordo*
bound strutturale 14,32% 14,32% 0,00%

* differenza di costruzione: il prototipo scarta la prima barra per asset (serve vol_prev).


Documento scritto senza modificare nulla in produzione. Il branch research/wave-0822 e' quello da cui il cron esegue: ogni riga di src/, config/, scripts/live/, scripts/cron_*.sh e tests/ e' rimasta identica.