Files
Encelado/Encelado/docs/ML_AND_LEARNING.md
T
Alby96andClaude Fable 5.1 9c1bab5224 5.0 Fase 9: documenti, screenshot, risposte D-28/29/36/37, piano concluso
README con gli screenshot e l'avvio rapido Docker; CLAUDE.md, architettura, runbook,
problemi noti, glossario, fonti dei dati e apprendimento riscritti per il container e
l'interfaccia web; il post-mortem spiega gli accrediti del demo (uno per ordine
ridotto, dal ledger ricevuto); le skill seguono la catena nuova. Lo stato dice che il
piano 5.0 è completo e che il rilascio 5.0.0 resta una decisione dell'utente.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 13:24:30 +02:00

11 KiB
Raw Blame History

Apprendimento: livelli 0-3

Aggiornato: 2026-09-23 (ADR-0006, 5.0). Tutto è costruito da zero nel Core (src/Encelado.Core/Baskets/Learning/), senza pacchetti: regressione logistica online, un MLP a 16 unità ReLU con Adam, un bandit di Thompson, due previsori di volatilità. Il codice del motore che li usa a runtime è src/Encelado.Engine/Baskets/LearningState.cs; lo stesso ciclo gira offline con backtest learn.

Regola che governa tutto: nessun livello cambia un parametro live da solo. Il meta-modello può soltanto rifiutare un ingresso quando è attivo; il bandit propone un preset e non lo applica (dalla 5.0; fino alla 4.0.0 lo applicava in Paper e Demo); tutto il resto finisce in knowledge/proposals.csv e passa dal forward test pre-registrato.

Stato dalla 5.0 (ADR-0006): quasi tutto in ombra

strategy.jsonlearning è {enabled: false, weeklyCycle: false, challenger: false} di fabbrica. Con enabled = false:

Componente Runtime
Ledger, orders.jsonl, baskets.csv scritti sempre: sono il dato
Livello 0 (calibrazione) generato dal ciclo, quindi fermo finché il ciclo non gira (backtest learn, o «Esegui ciclo di apprendimento» in Impostazioni ▸ Ricerca)
Previsione di volatilità attiva, deterministica, usata da z_in effettivo
Logistica (livello 1) in ombra: p_ML a ogni chiusura di barra nel ledger, apprendimento a ogni chiusura di basket; mai un cancello (mlActive = false)
MLP challenger (livello 2) non addestrato
Bandit (livello 3) riceve i premi e propone nel log; non applica
Ciclo settimanale non gira nel bot; passa a backtest learn o al pulsante di Impostazioni ▸ Ricerca

Criterio di riattivazione (learning.enabled = true, scritto dall'operatore, mai dal bot): almeno 300 basket chiusi in Demo e P&L netto forward ≥ 0 sulla metrica pre-registrata in knowledge/preregistrazione.csv. Prima di allora qualunque «adattamento automatico» è rumore: il meta-modello può solo ridurre le perdite di una strategia che il backtest dice non reggere i costi, e a poche entrate al giorno i 300 basket sono mesi. Motivazione completa in docs/adr/ADR-0006-apprendimento-in-ombra.md.

Il dataset

Una riga per basket aperto: le 28 feature scritte nel ledger nel momento della decisione (decisions.jsonl, evento ingresso), unite per basket_id all'esito scritto alla chiusura (baskets.csv: label = 1 se il P&L netto è positivo, pnl_net). Le feature non vengono mai ricostruite a posteriori: il dataset è il ledger (LearningState.BuildDataset).

# Feature Origine
0-3 z, abs_z, z_in_eff, d_pips z-score del cross sintetico, soglia effettiva dopo la scala di volatilità, divergenza in pip
4-6 rho_w, rho_20, half_life correlazione rolling (finestra W e 20 barre), semiperiodo OLS
7-10 atr_a, atr_b, sigma_x, ewma_vol_x volatilità delle gambe e del cross
11 trend_strength forza di trend (ADX-like) del cross
12-15 spread_a, spread_b, cost_pips, break_even_win_rate costi del momento
16-18 hour_sin, hour_cos, day_of_week ora UTC ciclica, giorno
19-20 minutes_to_high, minutes_since_high calendario (troncati a 24 h)
21-23 sent_1h, sent_4h, hawkish_diff sentiment (valuta lunga valuta corta del cross)
24 risk_off sentiment risk-off
25 vol_ratio σ prevista / σ media 30 giorni
26 last_outcomes media degli ultimi 10 esiti
27 buy_cross direzione

I nomi sono in LearningFeatures.Names; il test LeakTests verifica che nessun nome contenga l'esito e che un'etichetta presa dal futuro non sia apprendibile (AUC ≈ 0,5).

Livello 0 — Calibrazione

CalibrationTables.Build raggruppa i basket chiusi per basket, preset, terzile di volatilità, ora del giorno, bucket di |z| e di costo, e scrive win rate e P&L medio per bucket in knowledge/calibration.csv (colonna motivazione con il conteggio). Serve a leggere dove la strategia paga e dove no, e a niente altro: non cambia soglie.

Livello 1 — Logistica online (il campione)

OnlineLogistic: pesi su 28 feature standardizzate con statistiche rolling (RollingStandardizer, emivita 200 righe), SGD con L2 = 10⁻³ e tasso 0,01/√(1+n/100). Predice a ogni chiusura di barra (p_ML nella dashboard, "in ombra") e impara a ogni chiusura di basket. Stato in data/models/logreg_current.json; versioni datate logreg_vN.json con trained_on_until e hash del dataset.

Valutazione walk-forward (ModelEvaluator.EvaluateLogistic): sequenziale, predici-poi-aggiorna, con i primi 30 basket di burn-in esclusi dalle metriche. Metriche: AUC con intervallo bootstrap (1000 ricampionamenti), Brier, log-loss, curva di calibrazione in 10 bin, P&L di tutti i basket contro P&L dei soli basket con p ≥ mlMinProbability, Sharpe e DSR del filtrato.

Attivazione (§8.3 della specifica), tutte insieme:

  1. almeno 300 basket chiusi;
  2. AUC walk-forward ≥ 0,55 con l'intervallo bootstrap che esclude 0,50;
  3. P&L filtrato migliore del P&L non filtrato e DSR del filtrato ≥ 0,95.

Quando è attivo, un ingresso con p < mlMinProbability (0,55) viene rifiutato (ml_gate nel ledger). Disattivazione: se l'AUC mobile sugli ultimi 100 basket scende sotto 0,52 il modello torna in ombra e lo scrive in models_registry.csv.

Stato del 2026-09-16: 0 basket chiusi nel ledger → il modello è in ombra e non è valutabile. Nessuna cifra qui è un risultato.

Livello 2 — MLP challenger

SmallMlp: 28 → 16 ReLU → 1 sigmoide, inizializzazione Glorot con seme fisso, Adam (β 0,9/0,999), L2 = 10⁻⁴, mini-batch 8-64. Addestrato dal ciclo settimanale in 5 fold cronologici con purga ed embargo di 24 ore attorno al fold di test, 5 semi mediati, early stopping sull'ultimo 20 % (cronologico) dei dati di addestramento con pazienza 20 epoche. Lo standardizzatore viene adattato all'intero insieme di addestramento prima del fit (le statistiche rolling partono da zero e distorcono le prime righe: scoperto e corretto con il test sul cerchio, vedi ModelTests).

Il gradient check (SmallMlp.GradientCheck, test TheMlpGradientMatchesTheNumericalOne) confronta il gradiente analitico di ogni peso vivo del primo strato con la differenza centrale numerica: scarto relativo < 10⁻⁴.

Promozione a campione: solo se batte la logistica di almeno 0,01 di AUC walk-forward e supera gli stessi cancelli di attivazione, e comunque solo dopo il forward test. Fino ad allora è registrato come challenger in models_registry.csv.

Livello 3 — Bandit sui preset

ThompsonBandit: una Beta(α, β) per braccio = preset × terzile di volatilità prevista (3 × 3). A ogni chiusura il braccio usato riceve 1 se il basket è positivo. La proposta campiona dalle posteriori con un tetto del 10 % alle scelte esplorative (ExplorationCap; test TheBanditKeepsExplorationUnderTheCap). Dalla 5.0 la proposta viene scritta nel log e in knowledge/proposals.csv e non viene applicata in nessuna modalità (D-30); fino alla 4.0.0 in Paper e Demo cambiava il preset a caldo.

Previsione della volatilità

VolForecaster: sui rendimenti a 15 minuti del cross calcola la varianza realizzata giornaliera e mantiene due previsori a 1-4 ore, EWMA (span 100) e HAR-RV (OLS sulle medie a 1, 5 e 22 giorni, rifittato ogni giorno). Ogni giorno confronta l'errore quadratico delle due previsioni sulla finestra mobile e usa quello migliore (ActiveModel). Il rapporto σ prevista / σ media 30 giorni scala la soglia z_in (volScaleZIn) e la size, ed è la feature vol_ratio.

Deriva

L'indice di stabilità (PSI) previsto dalla specifica non è collegato a niente: il codice è stato rimosso nella pulizia della 5.0 (era una classe mai chiamata). Quando il meta-modello sarà attivabile, il controllo della deriva va reintrodotto insieme al criterio di ritorno in ombra, non prima.

Ciclo settimanale e comando learn

LearningState.RunCycle — nel bot solo con learning.weeklyCycle = true (la domenica dopo le 10 UTC o al primo avvio dopo sette giorni); altrimenti a mano, dallo strumento o dall'interfaccia:

dotnet run --project tools/Encelado.Backtest -- learn --data <cartella data del bot> [--knowledge <cartella>] [--strategy config/strategy.json]
dotnet msbuild build/Release.proj -t:Backtest -p:Dati="%USERPROFILE%\Documents\Encelado\data" -p:Comando=learn

--data è la cartella che contiene ledger/ (una copia di /data/data del container va benissimo); --knowledge è dove scrivere (default: knowledge accanto ai dati). Lo strumento stampa quante righe ha il dataset e lo stato del modello dopo il ciclo. Il pulsante «Esegui ciclo di apprendimento» in Impostazioni ▸ Ricerca fa la stessa cosa sui dati del container. Il ciclo:

  1. ricostruisce il dataset dal ledger;
  2. valuta e riaddestra logistica (walk-forward) e MLP (fold purgati);
  3. scrive knowledge/calibration.csv, knowledge/insights_YYYYWW.md (cosa ha funzionato, calibrazione, meta-modello, bandit, parametri suggeriti), knowledge/models_registry.csv, knowledge/proposals.csv (una riga per proposta, con evidenza e stato proposta);
  4. salva i modelli con versione.

Le proposte non toccano niente. Il percorso per cambiare un parametro live è: proposta → knowledge/preregistrazione.csv (metrica, soglia, periodo, N minimo, scritti prima) → forward test in Paper/Demo → forward_registry.csv → decisione umana.

Cosa è stato escluso, e perché

  • LSTM / Transformer / RL profondo: con qualche centinaio di basket l'anno per cinque coppie, un modello con migliaia di parametri impara il rumore del campione; la regressione logistica e un MLP minuscolo sono già al limite di ciò che il dataset può sostenere. Il costo (settimane di lavoro e di calcolo) non è giustificato da nessun indizio che un modello più ricco troverebbe struttura dove i test di falsificazione non ne trovano.
  • Feature ricostruite a posteriori: il ledger scrive ciò che il bot sapeva; ricostruire feature dopo è il modo più facile di introdurre look-ahead.
  • Ottimizzazione automatica dei parametri: la griglia del backtest serve a sapere, non a scegliere; ogni prova conta nel DSR.

File

File Contenuto
data/models/logreg_current.json, mlp_current.json, bandit.json stato corrente (ripreso all'avvio)
data/models/logreg_vN.json, mlp_vN.json versioni del ciclo settimanale con trained_on_until, righe, hash del dataset, nomi delle feature
data/models/learning_state.json attivo/ombra, campione, versione, ultimi 200 (p, esito) per l'AUC mobile, feature dei basket aperti
knowledge/calibration.csv, insights_YYYYWW.md, models_registry.csv, proposals.csv, forward_registry.csv, preregistrazione.csv vedi docs/LEDGER_SCHEMA.md

Test: tests/Encelado.Tests/LearningTests.cs (i: gradient check, apprendimento walk-forward, MLP contro logistica su una regola non lineare, bandit, volatilità; j: leak; l: blocchi).