5.0: apprendimento in ombra (ADR-0006), skill di progetto, documenti della sessione

Con il backtest negativo e zero basket chiusi nel forward test, un meta-modello
attivo e un bandit che cambia il preset da solo sono rumore: learning.enabled
= false di fabbrica lascia il ledger, la calibrazione, la previsione di
volatilità e la logistica in ombra, e spegne ciclo settimanale e challenger
finché non valgono 300 basket chiusi e un P&L forward non negativo. Le skill
in .claude/skills dicono come si comincia, si verifica, si diagnostica, si
chiude e si rilascia una sessione. CLAUDE.md, glossario e problemi noti
aggiornati alla 5.0; catena Verifica verde con 219 test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 11:27:46 +02:00
co-authored by Claude Fable 5.1
parent 9f040ade76
commit 21fa04690a
19 changed files with 233 additions and 10 deletions
@@ -0,0 +1,16 @@
---
name: encelado-diagnose
description: Riconcilia ledger, registro degli ordini, heartbeat e posizioni del conto eToro e produce un rapporto CSV con separatore ; e colonna motivazione.
---
# Diagnosi di una sessione di Encelado
Cartella dati: `Documenti\Encelado\data` su Windows, `/data/data` nel container. Se l'utente indica un'altra cartella, usa quella.
1. **Ledger delle decisioni** `data/ledger/decisions.jsonl` (e i mensili `decisions_YYYYMM.jsonl`): conta gli `evento` (`segnale_ingresso`, `rifiuto`, `ingresso`, `pending`, `pending_risolto`, `leg_risk_unwind`, `uscita`, `orfana_adottata`, `orfana_chiusa`, `movimento_di_cassa`, `kill_switch_*`, `recupero_*`, `comando`, `correzione`). Per ogni `rifiuto` riporta `basket`, `ts`, `unitsA`, `unitsB`, `sizing_bound`, `motivazione`.
2. **Registro degli ordini** `data/ledger/orders.jsonl` e `data/state/pending_orders.json`: per ogni `client_ref` prendi l'ultima riga; elenca gli ordini con `esito = Pending` o `stato = Unknown` (sono le anomalie), quelli con `unita_eseguite ≠ unita_richieste` (il server ha ridotto l'ordine), gli slippage oltre 1 pip.
3. **Basket** `data/ledger/baskets.csv`: numero, win rate, P&L netto, `exit_reason` (attenzione a `orphan_closed`, `bonifica_orfana`, `leg_risk_unwind`, `margin_guard`, `kill_switch`).
4. **Heartbeat** `data/state/heartbeat.json` e **stato** `data/state/baskets_state.json`: ultimo battito, run, basket aperti e in attesa, `haltResidue`, `cumulativeCashFlow`, `peakNetEquity`.
5. **Conto eToro** (solo se le chiavi sono disponibili nell'ambiente `ETORO_API_KEY`/`ETORO_USER_KEY` o via collegamento MCP, e solo in lettura): posizioni aperte (`GET api/v1/trading/info/demo/pnl`), storico (`GET api/v1/trading/info/trade/demo/history?minDate=…`). Classifica ogni posizione come `basket` (id nello stato), `orfana-bot` (id nel registro, o strumento + verso + orario entro 90 s da una riga del ledger), `esterna`.
6. Scrivi `reports/diagnosi_<data>.csv` con separatore `;`, intestazione e colonna finale `motivazione`: una riga per anomalia (`tipo;riferimento;quando;dettaglio;motivazione`) e in fondo le righe di riepilogo. Nessuna riga del ledger va modificata: le correzioni sono righe nuove.
7. Riassumi all'utente: cosa non torna, cosa è già stato gestito dal bot, cosa richiede una mano (chiusure su eToro, reset).
@@ -0,0 +1,13 @@
---
name: encelado-docs
description: Chiude una sessione di lavoro su Encelado: aggiorna STATE, CHANGELOG, ADR e QUESTIONS, poi propone il messaggio di commit.
---
# Chiusura della sessione: documenti e commit
1. `docs/STATE.md`: data, fase in corso, cosa è stato fatto in questa sessione (con i numeri: test, file, commit), prossimi passi numerati, problemi aperti, domande in attesa. È la memoria fra una sessione e l'altra: chi legge solo questo deve poter ripartire.
2. `CHANGELOG.md`: una voce in alto con la data e il titolo della sessione; punti brevi su cosa cambia e perché, non l'elenco dei file.
3. ADR: una scelta non ovvia = un file `docs/adr/ADR-NNNN-<slug>.md` con Contesto, Decisione, Alternative scartate, Conseguenze. Aggiorna la tabella in `CLAUDE.md` se compare un documento nuovo.
4. `docs/QUESTIONS.md`: ogni dubbio è una riga numerata (D-xx) con il default applicato; le risposte arrivate si segnano con la data.
5. Se sono cambiati schema del ledger, rotte dell'API, regole di rischio o file in `Documenti\Encelado`: `docs/LEDGER_SCHEMA.md`, `docs/DATA_SOURCES.md`, `docs/RISK_RULES.md`, `docs/RUNBOOK.md`, `docs/KNOWN_ISSUES.md` (togli ciò che si chiude, aggiungi ciò che resta).
6. Verifica (`/encelado-verify`), poi proponi il messaggio di commit: titolo di una riga che dice cosa cambia, corpo che dice perché; un commit per sessione, un commit per fase se la sessione ne copre più d'una. Il push lo decide l'utente.
@@ -0,0 +1,16 @@
---
name: encelado-release
description: Rilascio di Encelado: verifica, tag, installatore Windows, immagine Docker e template Unraid, in quest'ordine e solo dopo il commit e il push del ramo.
---
# Rilascio di Encelado
Presupposti: albero pulito, ultima sessione committata e pushata, `/encelado-verify` verde, versione decisa dall'utente (semver; la 5.0.0 è la prima con web UI e Docker).
1. `dotnet msbuild build/Release.proj -t:Verifica`.
2. `dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=<x.y.z>`: crea il tag git, l'installatore Inno Setup (`bin/installer/Encelado_<x.y.z>.exe`), lo zip e la release su Gitea (`build/gitea.json`, mai committato: vedi `build/gitea.example.json`). La versione viene dal tag, non da un file.
3. Docker (dalla Fase 8 della 5.0): `dotnet msbuild build/Release.proj -t:Docker -p:Versione=<x.y.z>` costruisce `encelado:<x.y.z>` e `encelado:latest`; poi `docker push <registry>/encelado:<x.y.z>` verso il registry deciso in D-29.
4. Template Unraid: aggiorna `deploy/unraid/encelado.xml` se sono cambiate variabili, porte o volumi; l'`Icon` punta al PNG raw del repository.
5. `CHANGELOG.md` e `docs/STATE.md` riportano la versione rilasciata; il tag e la release hanno lo stesso testo del CHANGELOG.
Non modificare `build/Release.proj` o `build/Encelado.iss` se non richiesto: la catena è condivisa con Mimante e AutoBidder.
@@ -0,0 +1,14 @@
---
name: encelado-start
description: Avvia una sessione di lavoro su Encelado: legge lo stato, compila, riassume fase e problemi aperti, chiede cosa fare.
---
# Avvio di una sessione su Encelado
1. Leggi, in quest'ordine: `CLAUDE.md`, `docs/STATE.md`, `docs/PIANO_5.0.md` (se la 5.0 non è conclusa), `docs/QUESTIONS.md` (le domande in attesa), `docs/KNOWN_ISSUES.md`.
2. Controlla l'albero di lavoro: `git status --short` e `git log --oneline -5`. Un albero sporco va capito prima di toccare qualcosa.
3. Compila: `dotnet build Encelado.slnx`. Se non compila, la prima cosa da fare è farlo compilare, e va detto subito.
4. Riassumi in dieci righe: fase in corso, cosa è stato fatto nell'ultima sessione, prossimi passi, domande senza risposta, problemi aperti, esito della compilazione.
5. Chiedi all'utente cosa fare in questa sessione. Non iniziare una fase del piano senza che sia chiaro quale.
Regole che valgono sempre: niente pacchetti NuGet nell'applicazione, UTC ovunque, ledger append-only, mai un ordine reale senza flag e frase `CONFERMO LIVE`, un commit a fine sessione dopo la verifica.
@@ -0,0 +1,21 @@
---
name: encelado-ui
description: Regole per toccare l'interfaccia di Encelado: i token Material 3 e le linee guida di docs/UI_GUIDELINES.md, nessuna libreria, stesse informazioni della dashboard.
---
# Lavorare sull'interfaccia di Encelado
Finché esiste il progetto WPF (`src/Encelado.Bot`, fino alla Fase 7 della 5.0): tema in `Ui/Theme.xaml`, pagine in `Ui/Pages`, nessuna decisione nella UI (legge lo snapshot, manda comandi via `IUiActions`), orari via `UiClock`, test di binding (`UiBindingTests`) e di rendering (`ENCELADO_RENDER_DIR=<cartella> dotnet test tests/Encelado.Tests --filter UiRenderTests`).
Dalla web UI (Fase 7): leggi `docs/UI_GUIDELINES.md` prima di scrivere una riga. In sintesi:
- HTML + CSS + JavaScript vanilla incorporati come `EmbeddedResource` nel server; nessun framework, nessun font remoto, nessun `<script src=http…>`.
- Ruoli di colore Material 3 (`primary`, `on-primary`, `surface`, `surface-container-*`, `outline`, `error`), palette scura di default e chiara selezionabile, contrasto ≥ 4,5:1, una sola tinta d'accento, verde e rosso solo per P&L e stati.
- Tipografia M3 (`display`, `headline`, `title`, `body`, `label`), `Roboto, "Segoe UI", system-ui`, cifre tabulari per ogni numero.
- Angoli 12-16 px, elevazione tonale, state layer su hover/focus/pressed; componenti propri: navigation rail e drawer, top app bar, cards, data table compatta, chips, buttons, switch, select, dialog di conferma (kill-switch, chiusura, reset), snackbar, linear progress, tooltip con formula e fonte su ogni numero.
- Classi di finestra M3 (compact < 600, medium < 840, expanded ≥ 840); la tabella dei basket diventa cards sotto 600 px.
- Accessibilità: tastiera completa, `aria-label`, focus visibile, `prefers-reduced-motion`, `prefers-color-scheme`.
- Aggiornamenti via Server-Sent Events (`/api/stream`, uno snapshot al secondo), comandi via `POST /api/commands/...`, token locale `ENCELADO_WEB_TOKEN`.
- Ogni importo convertito nella valuta di visualizzazione porta nel tooltip il valore in USD e il tasso usato; il ledger resta in USD.
Prima di dichiarare finita una modifica: test dello snapshot JSON dell'API, test dell'HTML incorporato, revisione visiva con l'utente.
@@ -0,0 +1,17 @@
---
name: encelado-verify
description: La verifica completa di Encelado prima di un commit: compilazione, test, controllo dei file di configurazione e catena di rilascio.
---
# Verifica di Encelado
Esegui nell'ordine e riporta l'esito di ciascun passo con i numeri (errori, avvisi, test superati/non superati), senza abbellire:
1. `dotnet build Encelado.slnx` — deve chiudere con 0 errori e 0 avvisi (`TreatWarningsAsErrors` è attivo: un avviso è un errore).
2. `dotnet test tests/Encelado.Tests --no-build` — tutti verdi. Un test rosso si legge e si corregge; non si esclude.
3. Coerenza dei file di fabbrica: `config/strategy.json` deve essere identico a `BasketStrategyConfig.DefaultJson` e `config/encelado.json` a `ConfigDefaults.Json` (ci sono test che lo verificano: se falliscono, rigenera i file dalle costanti, non il contrario).
4. Interfaccia web (dalla Fase 7 della 5.0): il test che valida l'HTML incorporato (well-formed, nessun riferimento esterno, nessun `<script src=http…>`).
5. `dotnet msbuild build/Release.proj -t:Verifica` — la catena di rilascio compila e testa nella cartella di verifica.
6. Riepilogo: una tabella `passo;esito;dettaglio`. Se un passo fallisce, fermati lì e proponi la correzione.
Non toccare `build/` se non richiesto: è condiviso con Mimante e AutoBidder.
+7
View File
@@ -2,6 +2,13 @@
Formato: una voce per sessione di lavoro, con data. Le voci più recenti in alto. Formato: una voce per sessione di lavoro, con data. Le voci più recenti in alto.
## 2026-09-23 — 5.0, apprendimento in ombra (ADR-0006) e skill di progetto
- Sezione `learning` in `strategy.json` (`enabled`, `weeklyCycle`, `challenger`, tutti `false` di fabbrica): il ciclo settimanale e il challenger non girano nel bot, il cancello ML non può attivarsi, la logistica resta in ombra con `p_ML` nel ledger, il bandit propone e basta. Criterio di riattivazione in `docs/ML_AND_LEARNING.md`.
- Skill di progetto in `.claude/skills/`: `encelado-start`, `encelado-verify`, `encelado-diagnose`, `encelado-docs`, `encelado-release`, `encelado-ui`.
- `CLAUDE.md` aggiornato (esecuzione 5.0, `--bonifica`, variabili Telegram, skill); glossario e problemi noti aggiornati.
- Catena di verifica (`build/Release.proj -t:Verifica`) verde.
## 2026-09-23 — 5.0, Fase 5: notifiche e comandi Telegram ## 2026-09-23 — 5.0, Fase 5: notifiche e comandi Telegram
- `TelegramNotifier` nel Core (solo `HttpClient`): coda non bloccante, un messaggio al secondo, tre tentativi con backoff e `retry_after`, spezzatura a 4096 caratteri, long polling `getUpdates` da un solo task, comandi accettati solo dalla chat autorizzata. - `TelegramNotifier` nel Core (solo `HttpClient`): coda non bloccante, un messaggio al secondo, tre tentativi con backoff e `retry_after`, spezzatura a 4096 caratteri, long polling `getUpdates` da un solo task, comandi accettati solo dalla chat autorizzata.
+5 -2
View File
@@ -19,6 +19,7 @@ Bot di trading in C# (.NET 10, WPF) su **eToro** con la strategia "Correlation B
| `docs/RISK_RULES.md` | regole di sicurezza con i default e chi può cambiarle | | `docs/RISK_RULES.md` | regole di sicurezza con i default e chi può cambiarle |
| `docs/RUNBOOK.md` | avvio, arresto, kill-switch, reset, riconciliazione, chiavi, errori API, checklist | | `docs/RUNBOOK.md` | avvio, arresto, kill-switch, reset, riconciliazione, chiavi, errori API, checklist |
| `docs/QUESTIONS.md` | domande poste per fase, risposte o default applicati, con data | | `docs/QUESTIONS.md` | domande poste per fase, risposte o default applicati, con data |
| `docs/PIANO_5.0.md`, `docs/POSTMORTEM_ordini_pendenti.md` | il piano della 5.0 (fasi, stime, decisioni vincolanti) e il post-mortem delle gambe orfane del 16-21/9/2026 |
| `docs/GLOSSARY.md`, `docs/KNOWN_ISSUES.md`, `CHANGELOG.md`, `docs/adr/` | glossario, problemi noti, cronologia, decisioni architetturali | | `docs/GLOSSARY.md`, `docs/KNOWN_ISSUES.md`, `CHANGELOG.md`, `docs/adr/` | glossario, problemi noti, cronologia, decisioni architetturali |
| `build/README.md` | catena di verifica, pacchetto e rilascio | | `build/README.md` | catena di verifica, pacchetto e rilascio |
@@ -30,7 +31,8 @@ Bot di trading in C# (.NET 10, WPF) su **eToro** con la strategia "Correlation B
- **Tempo**: UTC ovunque; conversione solo in UI. `CultureInfo.InvariantCulture` per ogni parsing e formattazione su file. - **Tempo**: UTC ovunque; conversione solo in UI. `CultureInfo.InvariantCulture` per ogni parsing e formattazione su file.
- **Concorrenza**: un solo thread di decisione; I/O asincrono; `Channel<T>` fra ingestion, strategia, esecuzione e UI. - **Concorrenza**: un solo thread di decisione; I/O asincrono; `Channel<T>` fra ingestion, strategia, esecuzione e UI.
- **Riproducibilità**: seed fisso 42 per ogni componente stocastica; ogni run scrive `run_id`, hash della configurazione e versione del codice nel ledger. - **Riproducibilità**: seed fisso 42 per ogni componente stocastica; ogni run scrive `run_id`, hash della configurazione e versione del codice nel ledger.
- Cartelle a runtime sotto `Documenti\Encelado\`: `data/`, `knowledge/`, `reports/`, `results/`, `logs/`. Credenziali solo in `%LOCALAPPDATA%\Encelado\etoro.dat` (DPAPI) o variabili d'ambiente `ETORO_API_KEY`, `ETORO_USER_KEY`. - Cartelle a runtime sotto `Documenti\Encelado\`: `data/`, `knowledge/`, `reports/`, `results/`, `logs/`. Credenziali solo in `%LOCALAPPDATA%\Encelado\etoro.dat` (DPAPI) o variabili d'ambiente `ETORO_API_KEY`, `ETORO_USER_KEY`; token Telegram solo in `TELEGRAM_BOT_TOKEN` (chat in `TELEGRAM_CHAT_ID`).
- **Esecuzione (5.0)**: ogni ordine entra nel registro `data/state/pending_orders.json` prima dell'HTTP e l'esito si legge per `orderId` (il server non registra il `referenceId`); una gamba senza esito porta il basket in `PendingA`/`PendingB`, mai in un rifiuto; ogni posizione del conto è `basket`, `orfana-bot` (adottata e chiusa) o `esterna` (mai toccata); la size è il minimo fra rischio e margine (`strategy.json``risk`); il kill-switch verifica la piattezza sul conto e dichiara `Halted-Residuo` se resta qualcosa; heartbeat e recupero dopo inattività (`recovery`); un solo bot per cartella dati (`instance.lock`).
- **Interfaccia**: una barra in alto (schede Dashboard / Log / Impostazioni, stato, ambiente, ora nel fuso scelto, AVVIA), pagine sotto. Nella dashboard solo le informazioni principali; i dettagli nei tooltip e nel log. Gli orari a schermo passano da `UiClock` (`ui.timeZone`); il log porta l'offset, il ledger è UTC. - **Interfaccia**: una barra in alto (schede Dashboard / Log / Impostazioni, stato, ambiente, ora nel fuso scelto, AVVIA), pagine sotto. Nella dashboard solo le informazioni principali; i dettagli nei tooltip e nel log. Gli orari a schermo passano da `UiClock` (`ui.timeZone`); il log porta l'offset, il ledger è UTC.
- **Verifica visiva**: `ENCELADO_RENDER_DIR=<cartella> dotnet test tests/Encelado.Tests --filter UiRenderTests` scrive `dashboard.png`, `log.png`, `settings.png`, `window.png`. - **Verifica visiva**: `ENCELADO_RENDER_DIR=<cartella> dotnet test tests/Encelado.Tests --filter UiRenderTests` scrive `dashboard.png`, `log.png`, `settings.png`, `window.png`.
@@ -40,7 +42,7 @@ Bot di trading in C# (.NET 10, WPF) su **eToro** con la strategia "Correlation B
dotnet build Encelado.slnx # compilazione dotnet build Encelado.slnx # compilazione
dotnet test tests/Encelado.Tests --no-restore # test (xunit) dotnet test tests/Encelado.Tests --no-restore # test (xunit)
dotnet msbuild build/Release.proj -t:Verifica # compilazione + test nella cartella di verifica dotnet msbuild build/Release.proj -t:Verifica # compilazione + test nella cartella di verifica
dotnet run --project src/Encelado.Bot -- --headless [--minutes 240] # bot senza finestra (VPS, test lunghi) dotnet run --project src/Encelado.Bot -- --headless [--minutes 240] [--bonifica] # bot senza finestra (VPS, test lunghi); --bonifica elenca e chiude le orfane con conferma
dotnet run --project tools/Encelado.Backtest -- ticks --data "A:\Download\Trading" --out "%USERPROFILE%\Documents\Encelado\data\market" dotnet run --project tools/Encelado.Backtest -- ticks --data "A:\Download\Trading" --out "%USERPROFILE%\Documents\Encelado\data\market"
dotnet run --project tools/Encelado.Backtest -- baskets --data "%USERPROFILE%\Documents\Encelado\data\market" --out results dotnet run --project tools/Encelado.Backtest -- baskets --data "%USERPROFILE%\Documents\Encelado\data\market" --out results
dotnet run --project tools/Encelado.Backtest -- falsify --data "%USERPROFILE%\Documents\Encelado\data\market" --out reports [--costs api] dotnet run --project tools/Encelado.Backtest -- falsify --data "%USERPROFILE%\Documents\Encelado\data\market" --out reports [--costs api]
@@ -56,6 +58,7 @@ dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=4.0.0 # installator
5. **Non toccare la catena di rilascio** (`build/`) se non richiesto; è condivisa con Mimante/AutoBidder. 5. **Non toccare la catena di rilascio** (`build/`) se non richiesto; è condivisa con Mimante/AutoBidder.
6. **Un commit a fine sessione**, dopo che la verifica passa, con un messaggio che dice cosa cambia e perché. Il push lo decide l'utente. 6. **Un commit a fine sessione**, dopo che la verifica passa, con un messaggio che dice cosa cambia e perché. Il push lo decide l'utente.
7. Aggiorna `docs/STATE.md` e `CHANGELOG.md` a fine sessione; un ADR per ogni scelta non ovvia. 7. Aggiorna `docs/STATE.md` e `CHANGELOG.md` a fine sessione; un ADR per ogni scelta non ovvia.
8. Le skill di progetto in `.claude/skills/` (`encelado-start`, `encelado-verify`, `encelado-diagnose`, `encelado-docs`, `encelado-release`, `encelado-ui`) dicono come si comincia, si verifica, si diagnostica, si chiude e si rilascia: usale.
## Cose da non fare ## Cose da non fare
+7
View File
@@ -86,6 +86,13 @@
"heartbeatSeconds": 30 "heartbeatSeconds": 30
}, },
"_learning": "Apprendimento (ADR-0006). Di fabbrica tutto spento: restano il ledger, le tabelle di calibrazione, la previsione di volatilità e la logistica in ombra (p_ML nel ledger, mai un cancello). enabled = true riaccende il ciclo settimanale (weeklyCycle) e il challenger MLP (challenger) solo dopo il criterio di riattivazione di docs/ML_AND_LEARNING.md: almeno 300 basket chiusi in Demo e P&L netto forward >= 0 sulla pre-registrazione. Il bandit propone soltanto, non applica mai il preset.",
"learning": {
"enabled": false,
"weeklyCycle": false,
"challenger": false
},
"_baskets": "I cinque basket della specifica. Il cross sintetico e il verso delle gambe sono derivati dai codici delle valute, non configurati.", "_baskets": "I cinque basket della specifica. Il cross sintetico e il verso delle gambe sono derivati dai codici delle valute, non configurati.",
"baskets": [ "baskets": [
{ "a": "EURUSD", "b": "USDCHF", "enabled": true, "note": "cross sintetico EURCHF" }, { "a": "EURUSD", "b": "USDCHF", "enabled": true, "note": "cross sintetico EURCHF" },
+7
View File
@@ -20,6 +20,13 @@
| **Esterna** | Una posizione sul conto senza la firma del bot: segnalata, contata, mai toccata. | | **Esterna** | Una posizione sul conto senza la firma del bot: segnalata, contata, mai toccata. |
| **Movimento di cassa** | Deposito, prelievo o accredito virtuale: un salto del saldo che nessuna chiusura spiega. Escluso dal P&L, dal picco e dal drawdown. | | **Movimento di cassa** | Deposito, prelievo o accredito virtuale: un salto del saldo che nessuna chiusura spiega. Escluso dal P&L, dal picco e dal drawdown. |
| **Bonifica** | La pulizia una tantum delle orfane con conferma per posizione (`--bonifica`). | | **Bonifica** | La pulizia una tantum delle orfane con conferma per posizione (`--bonifica`). |
| **Halted-Residuo** | Lo stato del motore dopo un kill-switch che non è riuscito ad appiattire le posizioni del bot: banner con l'elenco, reset rifiutato finché restano. |
| **Verifica di piattezza** | La rilettura del conto dopo il kill-switch finché le posizioni del bot non sono sparite; niente è «chiuso» prima. |
| **Margin guard** | Sotto equity / margine usato = 1,5 niente entrate; sotto 1,2 si chiude il basket con il P&L peggiore. |
| **sizing_bound** | Quale vincolo ha deciso la size di un basket: `risk`, `margin`, `leverage`, `units`; `min_exposure` quando nessuna size è ammessa. |
| **Heartbeat** | `data/state/heartbeat.json`, scritto ogni 30 s: misura l'inattività che fa scattare il recupero. |
| **Recupero** | La procedura dopo un'inattività oltre la soglia: ordini pendenti, riconciliazione, barre perse, ogni basket aperto rivalutato (chiudi/tieni), rapporto, riscaldamento. |
| **Lock di istanza** | `data/state/instance.lock`, tenuto in esclusiva: un solo bot per cartella dati. |
| **Equity stop** | Chiusura di tutto e blocco a un drawdown del 9 % dal picco; riparte solo con un reset motivato. | | **Equity stop** | Chiusura di tutto e blocco a un drawdown del 9 % dal picco; riparte solo con un reset motivato. |
| **Kill-switch** | Chiusura immediata di tutto e blocco delle nuove entrate: pulsante, comando o file `STOP`. | | **Kill-switch** | Chiusura immediata di tutto e blocco delle nuove entrate: pulsante, comando o file `STOP`. |
| **Paper / Demo / Live** | Simulatore locale / conto demo eToro / conto reale. Il bot opera da solo in tutte e tre (D-20). | | **Paper / Demo / Live** | Simulatore locale / conto demo eToro / conto reale. Il bot opera da solo in tutte e tre (D-20). |
+3
View File
@@ -31,6 +31,9 @@ Aggiornato: 2026-09-23. Una voce per limite, con lo stato. Quando un limite vien
- Il ledger delle sessioni Demo del 16-21/9 (le 65 righe `segnale_ingresso`/`rifiuto` del post-mortem) non è su questa macchina; l'analisi si basa sullo storico del conto letto via API (D-36). - Il ledger delle sessioni Demo del 16-21/9 (le 65 righe `segnale_ingresso`/`rifiuto` del post-mortem) non è su questa macchina; l'analisi si basa sullo storico del conto letto via API (D-36).
- La finestra WPF mostra i contatori nuovi (in attesa, orfane, esterne, P&L del conto) nei riquadri esistenti; la scheda Storico, la bonifica con pulsante e la navigazione a sinistra arrivano con la web UI (Fasi 6-7, dopo D-28). In attesa, la bonifica si lancia da headless (`--bonifica`). - La finestra WPF mostra i contatori nuovi (in attesa, orfane, esterne, P&L del conto) nei riquadri esistenti; la scheda Storico, la bonifica con pulsante e la navigazione a sinistra arrivano con la web UI (Fasi 6-7, dopo D-28). In attesa, la bonifica si lancia da headless (`--bonifica`).
- Un movimento di cassa viene riconosciuto dal salto del saldo (oltre 10 USD e 0,25 %): un accredito piccolo sotto quella soglia passa per rumore; un prelievo che coincide con una chiusura viene distinto solo se lo storico del conto risponde. - Un movimento di cassa viene riconosciuto dal salto del saldo (oltre 10 USD e 0,25 %): un accredito piccolo sotto quella soglia passa per rumore; un prelievo che coincide con una chiusura viene distinto solo se lo storico del conto risponde.
- Telegram non avvisa ancora dei feed (calendario, RSS) in errore da più di un'ora: il provider dei feed non espone l'ora dell'ultimo successo; da fare con la Fase 6-7 insieme al riquadro «Telegram» della dashboard.
- Il comando `learn` dello strumento di ricerca (ciclo settimanale offline, ADR-0006) arriva con la Fase 6: `LearningState` dipende oggi dal log e dal ledger del Bot e non è ancora nel progetto portabile.
- Le sezioni `risk`, `recovery` e `learning` di `strategy.json` non compaiono nel form di Impostazioni della finestra WPF (si modificano nel file, come tutto il resto della strategia); la web UI le mostrerà in Impostazioni → Ricerca.
- Il ciclo settimanale gira solo mentre il bot è acceso la domenica dopo le 10 UTC (o al primo avvio dopo sette giorni). - Il ciclo settimanale gira solo mentre il bot è acceso la domenica dopo le 10 UTC (o al primo avvio dopo sette giorni).
- La finestra e l'headless usano lo stesso log: il lock di istanza (5.0) impedisce che due motori girino sulla stessa cartella dati, ma la finestra aperta senza AVVIA scrive comunque nel log mentre gira l'headless. - La finestra e l'headless usano lo stesso log: il lock di istanza (5.0) impedisce che due motori girino sulla stessa cartella dati, ma la finestra aperta senza AVVIA scrive comunque nel log mentre gira l'headless.
+18 -2
View File
@@ -1,8 +1,24 @@
# Apprendimento: livelli 0-3 # Apprendimento: livelli 0-3
Aggiornato: 2026-09-16. 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à e un indice di stabilità (PSI). Il codice del Bot che li usa a runtime è `src/Encelado.Bot/Baskets/LearningState.cs`. Aggiornato: 2026-09-23 (ADR-0006). 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à e un indice di stabilità (PSI). Il codice del Bot che li usa a runtime è `src/Encelado.Bot/Baskets/LearningState.cs`.
**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 lo applica solo in Paper/Demo; tutto il resto finisce in `knowledge/proposals.csv` e passa dal forward test pre-registrato. **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.json``learning` è `{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 (strumento `learn`, Fase 6) |
| 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 `tools/Encelado.Backtest learn` (Fase 6) |
**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 ## Il dataset
+1 -1
View File
@@ -52,7 +52,7 @@ Domande del prompt «Encelado 5.0» (§15) più quelle emerse dalla verifica. D-
| D-27 | Esiste un endpoint di cancellazione ordini? | no | **Sì, verificato**: `DELETE api/v2/trading/execution/{demo/}orders/{orderId}` (quota 20/min condivisa con gli ordini). Il 200 conferma solo la richiesta; l'esito si legge con il lookup: 7 o 9 = annullato, 6 = in corso. Il kill-switch lo usa e poi attende la risoluzione (≤ 30 s). | | D-27 | Esiste un endpoint di cancellazione ordini? | no | **Sì, verificato**: `DELETE api/v2/trading/execution/{demo/}orders/{orderId}` (quota 20/min condivisa con gli ordini). Il 200 conferma solo la richiesta; l'esito si legge con il lookup: 7 o 9 = annullato, 6 = in corso. Il kill-switch lo usa e poi attende la risoluzione (≤ 30 s). |
| D-28 | Confermi il ritiro del progetto WPF a favore della web UI servita dal bot? | sì (ADR-0007) | **In attesa.** Le Fasi 6-9 non partono senza risposta. | | D-28 | Confermi il ritiro del progetto WPF a favore della web UI servita dal bot? | sì (ADR-0007) | **In attesa.** Le Fasi 6-9 non partono senza risposta. |
| D-29 | Registry per l'immagine e URL dell'icona del template Unraid? | Gitea (`build/gitea.example.json`), icona nel repo | **In attesa.** | | D-29 | Registry per l'immagine e URL dell'icona del template Unraid? | Gitea (`build/gitea.example.json`), icona nel repo | **In attesa.** |
| D-30 | Confermi la disattivazione a runtime di MLP, bandit e ciclo settimanale? | sì (ADR-0006) | **Default applicato in Fase 1 per il solo bandit**: oggi il bandit cambia il preset da solo in Demo (`ObserveOutcome`), cioè un parametro cambiato dal bot; viene spento subito. MLP e ciclo settimanale seguono in Fase 7 con ADR-0006. | | D-30 | Confermi la disattivazione a runtime di MLP, bandit e ciclo settimanale? | sì (ADR-0006) | **Default applicato** (ADR-0006, 2026-09-23): il bandit propone e non applica (Fase 1); `learning.enabled = false` di fabbrica spegne ciclo settimanale e challenger e impedisce al cancello ML di attivarsi; la logistica resta in ombra. Il comando `learn` dello strumento arriva con la Fase 6. Resta aperta solo se vuoi il contrario. |
| D-31 | Token e chat ID Telegram via variabili d'ambiente? Comandi in ingresso o solo notifiche? | env; comandi attivi | **Default applicato** (Fase 5). | | D-31 | Token e chat ID Telegram via variabili d'ambiente? Comandi in ingresso o solo notifiche? | env; comandi attivi | **Default applicato** (Fase 5). |
| D-32 | Le posizioni esterne vanno chiuse dal kill-switch di default? | no | **Default applicato**: `risk.closeForeignOnKill = false`; la conferma del kill-switch ha la spunta «chiudi anche le esterne», deselezionata. | | D-32 | Le posizioni esterne vanno chiuse dal kill-switch di default? | no | **Default applicato**: `risk.closeForeignOnKill = false`; la conferma del kill-switch ha la spunta «chiudi anche le esterne», deselezionata. |
| D-33 | Valute del selettore oltre USD/EUR? | USD, EUR, GBP, CHF, JPY, AUD, CAD, NZD | **Default applicato** (Fase 7). Per GBP e JPY il polling aggiunge GBPUSD e USDJPY. | | D-33 | Valute del selettore oltre USD/EUR? | USD, EUR, GBP, CHF, JPY, AUD, CAD, NZD | **Default applicato** (Fase 7). Per GBP e JPY il polling aggiunge GBPUSD e USDJPY. |
+2 -1
View File
@@ -9,6 +9,7 @@ Aggiornato: 2026-09-23 (sessione 5.0, Fasi 0-5 concluse).
## Fatto nell'ultima sessione (2026-09-23) ## Fatto nell'ultima sessione (2026-09-23)
- **Fase 0**: diagnosi verificata sul codice e sul conto demo via API (sola lettura): `referenceID` nullo sugli ordini v2 (ecco il 404), 21 gambe orfane del 16-21/9 chiuse a mano il 21/9, primi due ordini a 100 % del margine, poi ordini ridotti dal server a 2 000 USD di margine. Endpoint per `orderId` e cancellazione verificati (D-26, D-27). `docs/PIANO_5.0.md`, `docs/POSTMORTEM_ordini_pendenti.md`, `docs/QUESTIONS.md` D-26…D-37. - **Fase 0**: diagnosi verificata sul codice e sul conto demo via API (sola lettura): `referenceID` nullo sugli ordini v2 (ecco il 404), 21 gambe orfane del 16-21/9 chiuse a mano il 21/9, primi due ordini a 100 % del margine, poi ordini ridotti dal server a 2 000 USD di margine. Endpoint per `orderId` e cancellazione verificati (D-26, D-27). `docs/PIANO_5.0.md`, `docs/POSTMORTEM_ordini_pendenti.md`, `docs/QUESTIONS.md` D-26…D-37.
- **§8 e §14 (parte)**: ADR-0006 con `learning.enabled = false` di fabbrica; skill di progetto in `.claude/skills/`; `CLAUDE.md`, glossario, problemi noti; catena `Verifica` verde.
- **Fase 5**: `TelegramNotifier`, stato orario, eventi, riepilogo giornaliero, comandi dalla chat autorizzata, riga `comando` nel ledger; test (s)-(u); 219 verdi. - **Fase 5**: `TelegramNotifier`, stato orario, eventi, riepilogo giornaliero, comandi dalla chat autorizzata, riga `comando` nel ledger; test (s)-(u); 219 verdi.
- **Fase 4**: heartbeat, lock di istanza, sezione `recovery`, procedura di recupero dopo inattività con rapporto `recupero_<run_id>.csv`; test (r); 211 verdi. - **Fase 4**: heartbeat, lock di istanza, sezione `recovery`, procedura di recupero dopo inattività con rapporto `recupero_<run_id>.csv`; test (r); 211 verdi.
- **Fase 3**: `FlattenProcedure` (annulla, chiudi, verifica), kill-switch che chiude basket + gambe in attesa + orfane (esterne opzionali) e dichiara `Halted-Residuo` quando il conto non è piatto, comando `residuo`, reset in cinque passi rifiutato con residui, `INotifier`; test (v)-(x); 204 verdi. - **Fase 3**: `FlattenProcedure` (annulla, chiudi, verifica), kill-switch che chiude basket + gambe in attesa + orfane (esterne opzionali) e dichiara `Halted-Residuo` quando il conto non è piatto, comando `residuo`, reset in cinque passi rifiutato con residui, `INotifier`; test (v)-(x); 204 verdi.
@@ -19,7 +20,7 @@ Aggiornato: 2026-09-23 (sessione 5.0, Fasi 0-5 concluse).
1. **Rispondere a D-28 (ritiro WPF → web UI) e D-29 (registry, icona)**: senza risposta le Fasi 6-9 non partono (`docs/QUESTIONS.md`). 1. **Rispondere a D-28 (ritiro WPF → web UI) e D-29 (registry, icona)**: senza risposta le Fasi 6-9 non partono (`docs/QUESTIONS.md`).
2. Riaccendere il Demo per 24 ore di verifica: contatore «orfane» a 0, `orders.jsonl` senza `Unknown` irrisolti, `sizing_bound = margin` sui primi ingressi; provare il kill-switch a mano con la verifica di piattezza; fermare il bot un'ora con un basket aperto per vedere il recupero; ricevere i messaggi Telegram reali (serve `TELEGRAM_BOT_TOKEN`). 2. Riaccendere il Demo per 24 ore di verifica: contatore «orfane» a 0, `orders.jsonl` senza `Unknown` irrisolti, `sizing_bound = margin` sui primi ingressi; provare il kill-switch a mano con la verifica di piattezza; fermare il bot un'ora con un basket aperto per vedere il recupero; ricevere i messaggi Telegram reali (serve `TELEGRAM_BOT_TOKEN`).
3. Fasi 6-9 (Engine/Server, web UI M3 con Storico/valuta/navigazione/Ricerca, Docker, Unraid, skill, ADR-0006/0007/0008, 5.0.0). 3. Fasi 6-9 (Engine/Server, web UI M3 con Storico/valuta/navigazione/Ricerca, Docker, Unraid, ADR-0007/0008, comando `learn` dello strumento, avviso Telegram sui feed fermi, `docs/UI_GUIDELINES.md`, `docs/DOCKER.md`, 5.0.0).
4. Rifare il backtest (`backtest baskets`) con i limiti di margine e aggiornare §3 di `docs/STRATEGY.md`. 4. Rifare il backtest (`backtest baskets`) con i limiti di margine e aggiornare §3 di `docs/STRATEGY.md`.
## Problemi aperti ## Problemi aperti
@@ -0,0 +1,22 @@
# ADR-0006 — Apprendimento: livello 0 e logistica in ombra restano, MLP, bandit e ciclo settimanale si spengono a runtime
Data: 2026-09-23. Stato: accettata con il default di D-30 (§8 del piano 5.0); revocabile con una risposta diversa.
## Contesto
`docs/STRATEGY.md` dà un verdetto negativo sul backtest (7,75 anni, nessuna configurazione con P&L netto positivo, segnale di circa 2 pip contro 3 pip di costo, walk-forward Sharpe 0,91). Il forward test in Demo ha prodotto **0 basket chiusi** in sei giorni per il difetto degli ordini pendenti (post-mortem), e il ciclo settimanale ha girato a vuoto («ledger senza basket chiusi: niente da addestrare»). Un meta-modello può soltanto filtrare i basket: con un segnale sotto i costi il massimo che può fare è ridurre le perdite, e per attivarsi gli servono almeno 300 basket chiusi, cioè mesi alla frequenza osservata. Nel frattempo il bandit **applicava** il preset da solo in Demo: un parametro cambiato dal bot, contro la regola di `RISK_RULES.md`.
## Decisione
- **Restano attivi**: il ledger (è il dato), il livello 0 (tabelle di calibrazione: quasi gratis, utili a leggere il forward test), la previsione di volatilità (`VolForecaster`, usata dal decisore in modo deterministico per `z_in` effettivo), la regressione logistica **in ombra** (predice `p_ML` nel ledger a ogni chiusura di barra e impara a ogni chiusura di basket; non fa mai da cancello).
- **Si spengono a runtime** (codice conservato nel Core): l'MLP challenger, il bandit di Thompson come attuatore (dalla Fase 1 propone soltanto), il ciclo settimanale. In `strategy.json` la sezione `learning` ha `enabled = false` di fabbrica: con `false` il motore non avvia il ciclo, non addestra il challenger e non può mai attivare il cancello `ml_gate`; il bandit registra i premi e propone nel log.
- **Il ciclo settimanale passa allo strumento** `tools/Encelado.Backtest` (comando `learn` su un ledger esportato), così i modelli si valutano quando ci saranno dati, fuori dal processo che opera. Il comando arriva con la Fase 6, quando `LearningState` si sposta nel progetto portabile `Encelado.Engine` (oggi dipende dal log e dal ledger del Bot).
- **Criterio di riattivazione** (`docs/ML_AND_LEARNING.md`): almeno **300 basket chiusi in Demo** *e* **P&L netto forward ≥ 0** sulla metrica pre-registrata (`knowledge/preregistrazione.csv`). Prima di allora ogni «adattamento automatico» è rumore, e `learning.enabled = true` è una decisione dell'operatore scritta nel file, non del bot.
- **Interfaccia**: il riquadro «Apprendimento» esce dalla dashboard; lo stato del modello in ombra (basket visti, AUC mobile, PSI) e il pulsante «esegui ciclo di apprendimento» vanno in Impostazioni → Ricerca (web UI, Fase 7). Nella finestra WPF resta la riga di stato del modello in ombra, che ora dice «disattivato (ADR-0006)».
## Conseguenze
- Nessun cambiamento nelle decisioni del bot: il cancello ML non era mai stato attivo (0 basket chiusi).
- `strategy.json` cresce della sezione `learning` (`enabled`, `weeklyCycle`, `challenger`); l'hash della configurazione la include.
- I file `data/models/*` e `knowledge/*` restano dove sono e vengono letti dallo strumento.
- Quando il criterio sarà soddisfatto, la riattivazione passa da una proposta in `knowledge/proposals.csv`, dalla pre-registrazione e da `learning.enabled = true`.
@@ -98,7 +98,8 @@ public sealed partial class BasketEngine
ContextRow row = _context.Row(now); ContextRow row = _context.Row(now);
BasketSlot? first = _slots.FirstOrDefault(static s => s.Enabled && s.Vol.Count > 0); BasketSlot? first = _slots.FirstOrDefault(static s => s.Enabled && s.Vol.Count > 0);
string vol = first is null ? "in attesa di barre" : $"{first.Name}: {first.Vol.Describe()}"; string vol = first is null ? "in attesa di barre" : $"{first.Name}: {first.Vol.Describe()}";
return row with { VolForecast = vol, MlState = _learning.Describe(), BanditProposal = _learning.BanditText }; string ml = _strategy.Learning.Enabled ? _learning.Describe() : $"disattivato (ADR-0006): {_learning.Describe()}";
return row with { VolForecast = vol, MlState = ml, BanditProposal = _learning.BanditText + " (solo proposta)" };
} }
/// <summary>The picture while the engine is not running.</summary> /// <summary>The picture while the engine is not running.</summary>
@@ -551,7 +551,8 @@ public sealed partial class BasketEngine : IEngine
SaveState(); SaveState();
} }
if (_learning.CycleDue(now) && now.DayOfWeek == DayOfWeek.Sunday && now.Hour >= 10) // ADR-0006: the weekly cycle runs inside the bot only when the operator turned learning on.
if (_strategy.Learning.Enabled && _strategy.Learning.WeeklyCycle && _learning.CycleDue(now) && now.DayOfWeek == DayOfWeek.Sunday && now.Hour >= 10)
{ {
try try
{ {
@@ -870,7 +871,7 @@ public sealed partial class BasketEngine : IEngine
double[] features = LearningFeatures.From(ctx, preview, preview.Z < 0); double[] features = LearningFeatures.From(ctx, preview, preview.Z < 0);
slot.LastFeatures = features; slot.LastFeatures = features;
slot.PMl = _learning.Predict(features); slot.PMl = _learning.Predict(features);
ctx = ctx with { PMl = slot.PMl, MlActive = _learning.Active && double.IsFinite(slot.PMl) }; ctx = ctx with { PMl = slot.PMl, MlActive = _strategy.Learning.Enabled && _learning.Active && double.IsFinite(slot.PMl) };
} }
d = _decider.Evaluate(ctx); d = _decider.Evaluate(ctx);
@@ -100,6 +100,7 @@ public static class BasketTrials
MarginCallCloseRatio = c.Risk.MarginCallCloseRatio, MarginCallCloseRatio = c.Risk.MarginCallCloseRatio,
}; };
copy.Recovery = new RecoveryOptions { ThresholdMinutes = c.Recovery.ThresholdMinutes, WarmupMinutes = c.Recovery.WarmupMinutes, HeartbeatSeconds = c.Recovery.HeartbeatSeconds }; copy.Recovery = new RecoveryOptions { ThresholdMinutes = c.Recovery.ThresholdMinutes, WarmupMinutes = c.Recovery.WarmupMinutes, HeartbeatSeconds = c.Recovery.HeartbeatSeconds };
copy.Learning = new LearningOptions { Enabled = c.Learning.Enabled, WeeklyCycle = c.Learning.WeeklyCycle, Challenger = c.Learning.Challenger };
copy.Preset = c.Preset; copy.Preset = c.Preset;
copy.SignalMode = c.SignalMode; copy.SignalMode = c.SignalMode;
copy.ExitMode = c.ExitMode; copy.ExitMode = c.ExitMode;
@@ -109,6 +109,24 @@ public sealed class RecoveryOptions
} }
} }
/// <summary>
/// What of the learning stack runs at runtime (ADR-0006). Off by default: the shadow
/// logistic model keeps scoring and learning (its probability is a ledger column), the
/// weekly cycle, the MLP challenger and the bandit as an actuator do not run until the
/// operator turns them on after the reactivation criterion of <c>docs/ML_AND_LEARNING.md</c>.
/// </summary>
public sealed class LearningOptions
{
/// <summary>Master switch: false = shadow only, no weekly cycle, no challenger, the gate can never activate.</summary>
public bool Enabled { get; set; }
/// <summary>Whether the weekly cycle runs inside the bot (Sunday after 10 UTC) when learning is enabled.</summary>
public bool WeeklyCycle { get; set; }
/// <summary>Whether the MLP challenger is trained by the cycle when learning is enabled.</summary>
public bool Challenger { get; set; }
}
/// <summary>One basket: two pairs. The synthetic cross and the leg signs are derived, never configured.</summary> /// <summary>One basket: two pairs. The synthetic cross and the leg signs are derived, never configured.</summary>
public sealed class BasketDefinition public sealed class BasketDefinition
{ {
@@ -244,6 +262,9 @@ public sealed class BasketStrategyConfig
/// <summary>The recovery after an inactivity (§6 of the 5.0 plan).</summary> /// <summary>The recovery after an inactivity (§6 of the 5.0 plan).</summary>
public RecoveryOptions Recovery { get; set; } = new(); public RecoveryOptions Recovery { get; set; } = new();
/// <summary>What of the learning stack runs at runtime (ADR-0006).</summary>
public LearningOptions Learning { get; set; } = new();
// ---- overrides of the preset (NaN / 0 = take the preset's value) ---- // ---- overrides of the preset (NaN / 0 = take the preset's value) ----
public double ZInOverride { get; set; } = double.NaN; public double ZInOverride { get; set; } = double.NaN;
@@ -370,7 +391,7 @@ public sealed class BasketStrategyConfig
sb.Append(CultureInfo.InvariantCulture, $"cost={CostMultiple};spreadMed={SpreadMedianMultiple};slip={SlippagePipsPerLeg};on={OvernightPipsPerDay};blackout={BlackoutBeforeMin}/{BlackoutAfterMin};fri={FridayCutoffUtcHour};open={OpenDelayMinutes};"); sb.Append(CultureInfo.InvariantCulture, $"cost={CostMultiple};spreadMed={SpreadMedianMultiple};slip={SlippagePipsPerLeg};on={OvernightPipsPerDay};blackout={BlackoutBeforeMin}/{BlackoutAfterMin};fri={FridayCutoffUtcHour};open={OpenDelayMinutes};");
sb.Append(CultureInfo.InvariantCulture, $"lev={MaxEffectiveLeverage}/{OrderLeverage};vol={VolScaleMin}-{VolScaleMax}/{VolAverageDays};pMin={MlMinProbability};eqStop={EquityStopPct};daily={DailyLossPct};"); sb.Append(CultureInfo.InvariantCulture, $"lev={MaxEffectiveLeverage}/{OrderLeverage};vol={VolScaleMin}-{VolScaleMax}/{VolAverageDays};pMin={MlMinProbability};eqStop={EquityStopPct};daily={DailyLossPct};");
sb.Append(CultureInfo.InvariantCulture, $"margin={Risk.MaxMarginUsePct}/{Risk.MaxMarginPerBasketPct}/{Risk.MarginBufferPct};mcall={Risk.MarginCallBlockRatio}/{Risk.MarginCallCloseRatio};"); sb.Append(CultureInfo.InvariantCulture, $"margin={Risk.MaxMarginUsePct}/{Risk.MaxMarginPerBasketPct}/{Risk.MarginBufferPct};mcall={Risk.MarginCallBlockRatio}/{Risk.MarginCallCloseRatio};");
sb.Append(CultureInfo.InvariantCulture, $"recovery={Recovery.ThresholdMinutes}/{Recovery.WarmupMinutes};"); sb.Append(CultureInfo.InvariantCulture, $"recovery={Recovery.ThresholdMinutes}/{Recovery.WarmupMinutes};learning={(Learning.Enabled ? 1 : 0)}/{(Learning.WeeklyCycle ? 1 : 0)}/{(Learning.Challenger ? 1 : 0)};");
foreach (BasketDefinition b in Baskets) foreach (BasketDefinition b in Baskets)
{ {
sb.Append(CultureInfo.InvariantCulture, $"{b.A}/{b.B}={(b.Enabled ? 1 : 0)};"); sb.Append(CultureInfo.InvariantCulture, $"{b.A}/{b.B}={(b.Enabled ? 1 : 0)};");
@@ -474,6 +495,7 @@ public sealed class BasketStrategyConfig
case "baskets": c.Baskets = ReadBaskets(p.Value, warnings); break; case "baskets": c.Baskets = ReadBaskets(p.Value, warnings); break;
case "risk": c.Risk = ReadRisk(p.Value, warnings); break; case "risk": c.Risk = ReadRisk(p.Value, warnings); break;
case "recovery": c.Recovery = ReadRecovery(p.Value, warnings); break; case "recovery": c.Recovery = ReadRecovery(p.Value, warnings); break;
case "learning": c.Learning = ReadLearning(p.Value, warnings); break;
default: warnings.Add($"chiave sconosciuta '{p.Name}' in strategy.json"); break; default: warnings.Add($"chiave sconosciuta '{p.Name}' in strategy.json"); break;
} }
} }
@@ -557,6 +579,34 @@ public sealed class BasketStrategyConfig
return r; return r;
} }
private static LearningOptions ReadLearning(JsonElement e, List<string> warnings)
{
LearningOptions l = new();
if (e.ValueKind != JsonValueKind.Object)
{
warnings.Add("'learning' deve essere un oggetto: uso i valori di fabbrica (tutto spento)");
return l;
}
foreach (JsonProperty p in e.EnumerateObject())
{
if (p.Name.StartsWith('_'))
{
continue;
}
switch (p.Name.ToLowerInvariant())
{
case "enabled": l.Enabled = Bool(p); break;
case "weeklycycle": l.WeeklyCycle = Bool(p); break;
case "challenger": l.Challenger = Bool(p); break;
default: warnings.Add($"chiave sconosciuta 'learning.{p.Name}' in strategy.json"); break;
}
}
return l;
}
private static RecoveryOptions ReadRecovery(JsonElement e, List<string> warnings) private static RecoveryOptions ReadRecovery(JsonElement e, List<string> warnings)
{ {
RecoveryOptions r = new(); RecoveryOptions r = new();
@@ -721,6 +771,13 @@ public sealed class BasketStrategyConfig
"heartbeatSeconds": 30 "heartbeatSeconds": 30
}, },
"_learning": "Apprendimento (ADR-0006). Di fabbrica tutto spento: restano il ledger, le tabelle di calibrazione, la previsione di volatilità e la logistica in ombra (p_ML nel ledger, mai un cancello). enabled = true riaccende il ciclo settimanale (weeklyCycle) e il challenger MLP (challenger) solo dopo il criterio di riattivazione di docs/ML_AND_LEARNING.md: almeno 300 basket chiusi in Demo e P&L netto forward >= 0 sulla pre-registrazione. Il bandit propone soltanto, non applica mai il preset.",
"learning": {
"enabled": false,
"weeklyCycle": false,
"challenger": false
},
"_baskets": "I cinque basket della specifica. Il cross sintetico e il verso delle gambe sono derivati dai codici delle valute, non configurati.", "_baskets": "I cinque basket della specifica. Il cross sintetico e il verso delle gambe sono derivati dai codici delle valute, non configurati.",
"baskets": [ "baskets": [
{ "a": "EURUSD", "b": "USDCHF", "enabled": true, "note": "cross sintetico EURCHF" }, { "a": "EURUSD", "b": "USDCHF", "enabled": true, "note": "cross sintetico EURCHF" },