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>
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
---
|
---
|
||||||
name: encelado-release
|
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.
|
description: Rilascio di Encelado: verifica, zip portabile, immagine Docker, tag, push dell'immagine sul registro di Gitea e release con il template Unraid, in quest'ordine e solo dopo il commit e il push del ramo.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Rilascio di Encelado
|
# 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).
|
Presupposti: albero pulito, ultima sessione committata e pushata, `/encelado-verify` verde, Docker in esecuzione, `build/gitea.json` presente (mai committato: vedi `build/gitea.example.json`; il token deve avere anche `package: read and write`), versione decisa dall'utente (semver; la 5.0.0 è la prima con web UI e Docker).
|
||||||
|
|
||||||
1. `dotnet msbuild build/Release.proj -t:Verifica`.
|
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.
|
2. `dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=<x.y.z>` (note in `ENCELADO_NOTE`): pubblica la cartella portabile, costruisce l'immagine `192.168.30.23:3000/alby96/encelado:<x.y.z>` e `:latest` (i test girano anche dentro la build), crea il tag, lo spinge e lo verifica sul remoto, fa il push dell'immagine sul registro di Gitea, crea la release con lo zip portabile e il template Unraid allegati. 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.
|
3. Template Unraid: aggiorna `deploy/unraid/encelado.xml` se sono cambiate variabili, porte o volumi; l'`Icon` è `assets/encelado.png` raw su Gitea (D-29). Il file finisce comunque allegato alla release con la versione nel nome.
|
||||||
4. Template Unraid: aggiorna `deploy/unraid/encelado.xml` se sono cambiate variabili, porte o volumi; l'`Icon` punta al PNG raw del repository.
|
4. Su Unraid: *Docker ▸ Controlla aggiornamenti*; il container riparte con SIGTERM pulito e recupero dopo inattività.
|
||||||
5. `CHANGELOG.md` e `docs/STATE.md` riportano la versione rilasciata; il tag e la release hanno lo stesso testo del CHANGELOG.
|
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.
|
Senza Docker: `-p:SaltaDocker=true` produce solo lo zip (non è un rilascio completo, e va detto). Non modificare `build/Release.proj` se non richiesto: la catena è condivisa con Mimante e AutoBidder.
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
---
|
---
|
||||||
name: encelado-ui
|
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.
|
description: Regole per toccare l'interfaccia web di Encelado: i token Material 3 e le linee guida di docs/UI_GUIDELINES.md, nessuna libreria, stesse informazioni della dashboard, test dell'HTML e dello stream.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Lavorare sull'interfaccia di Encelado
|
# 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`).
|
L'interfaccia è servita dal bot (`src/Encelado.Server/Web/wwwroot/`: `index.html`, `login.html`, `app.css`, `app.js`, `icon.svg`, `manifest.webmanifest`, incorporati come `EmbeddedResource`). Le API stanno in `Web/WebHost.cs`, lo snapshot JSON in `Engine/SnapshotJson.cs`, i dati finti in `Engine/SampleSnapshot.cs`. Leggi `docs/UI_GUIDELINES.md` prima di scrivere una riga. In sintesi:
|
||||||
|
|
||||||
Dalla web UI (Fase 7): leggi `docs/UI_GUIDELINES.md` prima di scrivere una riga. In sintesi:
|
- HTML (scritto come XHTML: tag chiusi, attributi con valore) + CSS + JavaScript vanilla; nessun framework, nessun font remoto, nessun `<script src=http…>`, nessun `@import`.
|
||||||
|
- La UI non decide niente: legge lo snapshot (`/api/stream`, SSE, uno al secondo; `?once=1` per una sola lettura) e manda comandi (`POST /api/commands/<nome>`). Un campo nuovo nello snapshot passa da `BotSnapshot` → `SnapshotJson` → `SampleSnapshot` → `app.js` → test.
|
||||||
- HTML + CSS + JavaScript vanilla incorporati come `EmbeddedResource` nel server; nessun framework, nessun font remoto, nessun `<script src=http…>`.
|
- Ruoli di colore Material 3 (`--primary`, `--surface-container-*`, `--outline`, `--error`; `--up`/`--down` solo per P&L e stati), tema scuro di default e chiaro selezionabile (`ui.theme`), contrasto ≥ 4,5:1, cifre tabulari.
|
||||||
- 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.
|
- Componenti propri: navigation rail (80/256 px, drawer sotto 600 px), top app bar, cards, data table compatta (cards sotto 600 px), chips, buttons, dialoghi nativi `<dialog>` per conferme (kill-switch con «chiudi anche le esterne», reset con motivazione, `CONFERMO LIVE`), snackbar, linear progress, tooltip con formula e fonte su ogni numero.
|
||||||
- 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.
|
- Ogni importo convertito nella valuta di visualizzazione porta nel tooltip il valore in USD e il tasso usato; il ledger resta in USD.
|
||||||
|
- Accessibilità: tastiera completa, `aria-label`, focus visibile, `prefers-reduced-motion`, `prefers-color-scheme`.
|
||||||
|
|
||||||
Prima di dichiarare finita una modifica: test dello snapshot JSON dell'API, test dell'HTML incorporato, revisione visiva con l'utente.
|
Prima di dichiarare finita una modifica:
|
||||||
|
|
||||||
|
1. `dotnet test tests/Encelado.Tests --filter "FullyQualifiedName~EmbeddedUi|FullyQualifiedName~WebHost"` verde (HTML ben formato e senza riferimenti esterni, snapshot JSON, token, stream, storico).
|
||||||
|
2. `dotnet run --project src/Encelado.Server -- --sample --port 8085` e controllo a occhio nel browser (dashboard, storico, log, impostazioni, larghezza da telefono).
|
||||||
|
3. Se una pagina è cambiata, rifai gli screenshot in `docs/img/` (comando in `docs/UI_GUIDELINES.md`) e chiedi la revisione visiva all'utente.
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
---
|
---
|
||||||
name: encelado-verify
|
name: encelado-verify
|
||||||
description: La verifica completa di Encelado prima di un commit: compilazione, test, controllo dei file di configurazione e catena di rilascio.
|
description: La verifica completa di Encelado prima di un commit: compilazione, test (inclusi HTML incorporato e stream), controllo dei file di configurazione e catena di rilascio.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Verifica di Encelado
|
# Verifica di Encelado
|
||||||
|
|
||||||
Esegui nell'ordine e riporta l'esito di ciascun passo con i numeri (errori, avvisi, test superati/non superati), senza abbellire:
|
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).
|
1. `dotnet build Encelado.slnx` — deve chiudere con 0 errori e 0 avvisi (`TreatWarningsAsErrors` è attivo: un avviso è un errore). Se un server locale tiene bloccata `Encelado.Server.dll`, fermalo prima (`stop` sulla sua console o Ctrl+C).
|
||||||
2. `dotnet test tests/Encelado.Tests --no-build` — tutti verdi. Un test rosso si legge e si corregge; non si esclude.
|
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).
|
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…>`).
|
4. Interfaccia web: `EmbeddedUiTests` (HTML ben formato, nessun riferimento esterno, snapshot JSON) e `WebHostTests` (token, cookie, stream con uno snapshot al secondo, storico CSV) sono nella suite del passo 2; se hai toccato una pagina, anche il controllo a occhio con `--sample` (`/encelado-ui`).
|
||||||
5. `dotnet msbuild build/Release.proj -t:Verifica` — la catena di rilascio compila e testa nella cartella di verifica.
|
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.
|
6. Se hai toccato `Dockerfile`, `deploy/` o la catena: `dotnet msbuild build/Release.proj -t:Docker` (o `docker build .`) deve produrre l'immagine; `docker run --rm <immagine> --health` non è un test valido a freddo (il server non gira): usa `docker compose up` e `docker inspect --format '{{.State.Health.Status}}' encelado`.
|
||||||
|
7. 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.
|
Non toccare `build/` se non richiesto: è condiviso con Mimante e AutoBidder.
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
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, Fasi 6-9: solo container, interfaccia web, Docker e Unraid, pulizia
|
||||||
|
|
||||||
|
- **Ritirata la finestra WPF** (D-28, ADR-0007): `src/Encelado.Bot` diventa la libreria `src/Encelado.Engine`; l'eseguibile è `src/Encelado.Server` (Kestrel, nessun NuGet) che ospita il motore e serve l'interfaccia. Chiavi eToro da variabili d'ambiente o dal file cifrato `etoro.keys.enc` (AES-256-GCM, `ENCELADO_KEY_PASSPHRASE`) al posto di DPAPI. Cartelle `/config` e `/data` nel container, `Documenti\Encelado` fuori.
|
||||||
|
- **Interfaccia web Material 3** senza librerie (`Web/wwwroot`): navigation rail, barra con orologi UTC e fuso, valuta di visualizzazione (otto valute, tasso dalle quotazioni, tooltip in USD), AVVIA/FERMA e KILL-SWITCH con dialoghi (esterne, motivazione, `CONFERMO LIVE`); dashboard con sei KPI e la tabella dei basket (cards da telefono); **Storico ordini** (ordini, posizioni classificate `basket`/`orfana-bot`/`esterna`/`movimento di cassa`, profitti per periodo con curva dell'equity, CSV); Log; Impostazioni con configurazione validata, chiavi, ripristino in cinque passi, Ricerca (stato dell'apprendimento e ciclo a richiesta), Diagnostica (percorsi, quote, bonifica), Informazioni (versione, data di build, commit). SSE `/api/stream` a uno snapshot al secondo; token `ENCELADO_WEB_TOKEN` in cookie HttpOnly; senza token solo localhost. Tema scuro e chiaro. Regole in `docs/UI_GUIDELINES.md`, screenshot in `docs/img/`.
|
||||||
|
- **Docker e Unraid** (ADR-0008): `Dockerfile` multi-stage con i test nello stadio di build, `entrypoint.sh` con TZ e PUID/PGID, `docker-compose.yml`, `HEALTHCHECK` via `--health`; template `deploy/unraid/encelado.xml` con l'icona servita da Gitea (D-29); catena di rilascio senza Inno Setup: `Pubblica` framework-dependent, `Docker`, `Pacchetto` (zip + template + tag), `Rilascia` con push dell'immagine sul registro di Gitea. VS Code: F5 sul server (browser aperto da solo), modalità campione `--sample`, attività Docker.
|
||||||
|
- **Strumento**: comando `learn` (ciclo di apprendimento in ombra su un ledger esportato).
|
||||||
|
- **Pulizia**: rimossi WPF, DPAPI, i test di rendering e di binding, ~45 membri mai usati nel Core e nell'Engine (statistiche di ricerca, `Psi`, `EmptyContextProvider`, `EnvironmentKeyStore`, display helper dello snapshot, `INotifyPropertyChanged`), `build/Encelado.iss`. `PROMPT.md` → `docs/PROMPT_5.0.md`.
|
||||||
|
- **Documenti**: ADR-0007, ADR-0008, `docs/DOCKER.md`, `docs/UI_GUIDELINES.md`, `README.md`, `CLAUDE.md`, ARCHITECTURE, RUNBOOK, KNOWN_ISSUES, GLOSSARY, DATA_SOURCES, ML_AND_LEARNING, QUESTIONS (D-28, D-29, D-36, D-37), POSTMORTEM (gli accrediti del demo erano uno per ordine ridotto), skill, `build/README.md`.
|
||||||
|
- Test nuovi: `EmbeddedUiTests`, `WebHostTests` (token, cookie, stream, storico), `HistoryBuilderTests`, `KeyStoreTests`; 210 verdi, anche su Linux dentro `docker build`. Versione di sviluppo 5.0.0.
|
||||||
|
|
||||||
## 2026-09-23 — 5.0, apprendimento in ombra (ADR-0006) e skill di progetto
|
## 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`.
|
- 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`.
|
||||||
|
|||||||
+32
-15
@@ -4,37 +4,51 @@
|
|||||||
|
|
||||||
## Scopo
|
## Scopo
|
||||||
|
|
||||||
Bot di trading in C# (.NET 10, WPF) su **eToro** con la strategia "Correlation Baskets": cinque basket di due coppie forex correlate, ingresso quando il cross sintetico diverge (z-score), uscita quando converge o al take-profit di basket, stop di basket obbligatorio, cost gate sullo spread reale, ledger completo, feed gratuiti di calendario e notizie, livelli di apprendimento 0-3 costruiti da zero. È l'unica strategia del repository: i motori precedenti (Binance, cTrader/proba, ricerca) sono stati rimossi il 2026-09-16 (ADR-0004) e vivono solo nella storia git. Il bot opera da solo in ogni modalità (ADR-0005): `Paper`, `Demo` (default), `Live`.
|
Bot di trading in C# (.NET 10) su **eToro** con la strategia "Correlation Baskets": cinque basket di due coppie forex correlate, ingresso quando il cross sintetico diverge (z-score), uscita quando converge o al take-profit di basket, stop di basket obbligatorio, cost gate sullo spread reale, ledger completo, feed gratuiti di calendario e notizie, livelli di apprendimento 0-3 costruiti da zero. È l'unica strategia del repository: i motori precedenti (Binance, cTrader/proba, ricerca) sono stati rimossi il 2026-09-16 (ADR-0004) e vivono solo nella storia git. Il bot opera da solo in ogni modalità (ADR-0005): `Paper`, `Demo` (default), `Live`. Dalla 5.0 gira **solo in un container** (ADR-0007, ADR-0008): un processo `Encelado.Server` che ospita il motore e serve l'interfaccia web; la finestra WPF non esiste più.
|
||||||
|
|
||||||
## Mappa dei documenti
|
## Mappa dei documenti
|
||||||
|
|
||||||
| File | Contenuto |
|
| File | Contenuto |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `docs/STATE.md` | stato corrente, fase, ultima sessione, prossimi passi, problemi aperti |
|
| `docs/STATE.md` | stato corrente, fase, ultima sessione, prossimi passi, problemi aperti |
|
||||||
| `docs/ARCHITECTURE.md` | progetti, flusso dati, macchine a stati, interfacce |
|
| `docs/ARCHITECTURE.md` | progetti, flusso dati, macchine a stati, interfacce, API web |
|
||||||
| `docs/STRATEGY.md` | logica dei basket, cross sintetici, formule, preset, aspettative oneste, numeri |
|
| `docs/STRATEGY.md` | logica dei basket, cross sintetici, formule, preset, aspettative oneste, numeri |
|
||||||
| `docs/ML_AND_LEARNING.md` | livelli 0-3, feature, label, addestramento, attivazione, esclusioni |
|
| `docs/ML_AND_LEARNING.md` | livelli 0-3, feature, label, addestramento, attivazione, esclusioni, comando `learn` |
|
||||||
| `docs/DATA_SOURCES.md` | ogni fonte (URL, formato, limiti), schema dei file in `data/` |
|
| `docs/DATA_SOURCES.md` | ogni fonte (URL, formato, limiti), schema dei file in `data/` |
|
||||||
| `docs/LEDGER_SCHEMA.md` | schema di `decisions.jsonl`, `baskets.csv`, `trials.csv`, `calibration.csv`, `preregistrazione.csv`, `proposals.csv` |
|
| `docs/LEDGER_SCHEMA.md` | schema di `decisions.jsonl`, `orders.jsonl`, `baskets.csv`, `trials.csv`, `calibration.csv`, `preregistrazione.csv`, `proposals.csv` |
|
||||||
| `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/DOCKER.md`, `deploy/unraid/README.md` | immagine, volumi, variabili, healthcheck, aggiornamento; template Unraid |
|
||||||
|
| `docs/UI_GUIDELINES.md` | l'interfaccia web: token Material 3, componenti, pagine, screenshot |
|
||||||
| `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/PIANO_5.0.md`, `docs/PROMPT_5.0.md`, `docs/POSTMORTEM_ordini_pendenti.md` | il piano della 5.0 (fasi, stime, decisioni vincolanti), la specifica originale 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 (immagine Docker) e rilascio |
|
||||||
|
|
||||||
|
## Progetti
|
||||||
|
|
||||||
|
```
|
||||||
|
src/Encelado.Core logica pura, zero I/O, AOT/trim-compatibile
|
||||||
|
src/Encelado.Etoro client HTTP di eToro Public API
|
||||||
|
src/Encelado.Engine motore, ledger, feed, configurazione, impostazioni, storico, Telegram (ex Encelado.Bot senza UI)
|
||||||
|
src/Encelado.Server l'eseguibile: Kestrel, API JSON scritta a mano, SSE, UI incorporata (Web/wwwroot)
|
||||||
|
tools/Encelado.Backtest ticks, baskets, falsify, learn
|
||||||
|
tests/Encelado.Tests xunit
|
||||||
|
deploy/docker, deploy/unraid entrypoint del container, template Unraid
|
||||||
|
```
|
||||||
|
|
||||||
## Convenzioni
|
## Convenzioni
|
||||||
|
|
||||||
- **C#**, `Nullable` e `TreatWarningsAsErrors` attivi. Identificatori e commenti tecnici in inglese; documentazione, report e colonna `motivazione` in italiano.
|
- **C#**, `Nullable` e `TreatWarningsAsErrors` attivi. Identificatori e commenti tecnici in inglese; documentazione, report e colonna `motivazione` in italiano.
|
||||||
- **Nessun pacchetto NuGet.** Solo BCL e WPF nei progetti dell'applicazione; xunit nei test.
|
- **Nessun pacchetto NuGet.** Solo BCL e i framework reference dell'SDK (`Microsoft.AspNetCore.App` nel Server) nei progetti dell'applicazione; xunit nei test. Nessuna libreria JavaScript o CSS: l'interfaccia è HTML, CSS e JS vanilla incorporati.
|
||||||
- **Tabelle**: CSV con separatore `;`, header, ultima colonna `motivazione`; JSONL append-only per ledger e notizie; JSON per modelli e stato. Scritture atomiche (`.tmp` + `File.Move`), rotazione mensile. **Nessuna riga del ledger viene mai modificata**: le correzioni sono righe nuove con `evento = correzione`.
|
- **Tabelle**: CSV con separatore `;`, header, ultima colonna `motivazione`; JSONL append-only per ledger e notizie; JSON per modelli e stato. Scritture atomiche (`.tmp` + `File.Move`), rotazione mensile. **Nessuna riga del ledger viene mai modificata**: le correzioni sono righe nuove con `evento = correzione`.
|
||||||
- **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`; token Telegram solo in `TELEGRAM_BOT_TOKEN` (chat in `TELEGRAM_CHAT_ID`).
|
- **Cartelle a runtime**: nel container `/config` (encelado.json, strategy.json, instruments.json, `etoro.keys.enc`, file `STOP`) e `/data` (`data/`, `knowledge/`, `reports/`, `results/`, `logs/`); fuori dal container (solo sviluppo) `Documenti\Encelado\` o `ENCELADO_CONFIG_DIR`. Credenziali solo nelle variabili `ETORO_API_KEY`/`ETORO_USER_KEY` o nel file cifrato (AES-256-GCM, passphrase `ENCELADO_KEY_PASSPHRASE`); token Telegram solo in `TELEGRAM_BOT_TOKEN` (chat in `TELEGRAM_CHAT_ID`); token dell'interfaccia in `ENCELADO_WEB_TOKEN`.
|
||||||
- **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`).
|
- **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**: navigation rail a sinistra (Dashboard, Storico ordini, Log, Impostazioni), barra in alto (titolo, ora UTC e nel fuso, valuta, AVVIA/FERMA, KILL-SWITCH), pagine sotto. Nella dashboard solo le informazioni principali; i dettagli nei tooltip e nel log. La UI non decide niente: legge lo snapshot via SSE e manda comandi via `POST /api/commands/<nome>`. Gli orari a schermo sono nel fuso scelto (`ui.timeZone`, `TZ`); il log porta l'offset, il ledger è UTC. Regole complete in `docs/UI_GUIDELINES.md`.
|
||||||
- **Verifica visiva**: `ENCELADO_RENDER_DIR=<cartella> dotnet test tests/Encelado.Tests --filter UiRenderTests` scrive `dashboard.png`, `log.png`, `settings.png`, `window.png`.
|
- **Verifica visiva**: `dotnet run --project src/Encelado.Server -- --sample --port 8085` e screenshot con Edge headless (`docs/UI_GUIDELINES.md`); i test `EmbeddedUiTests` e `WebHostTests` coprono HTML, JSON, token e stream.
|
||||||
|
|
||||||
## Comandi
|
## Comandi
|
||||||
|
|
||||||
@@ -42,17 +56,20 @@ 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] [--bonifica] # bot senza finestra (VPS, test lunghi); --bonifica elenca e chiude le orfane con conferma
|
dotnet run --project src/Encelado.Server -- [--no-autostart] [--sample] [--port 8080] [--minutes 240] [--bonifica] [--confirm-live "CONFERMO LIVE"] # server locale; comandi da tastiera: status, close, kill, residuo, preset, reset, bonifica, stop
|
||||||
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]
|
||||||
dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=4.0.0 # installatore + zip + tag + release su Gitea (dopo il commit e il push del ramo)
|
dotnet run --project tools/Encelado.Backtest -- learn --data "%USERPROFILE%\Documents\Encelado\data" [--knowledge …] [--strategy config/strategy.json]
|
||||||
|
dotnet msbuild build/Release.proj -t:Docker # immagine 192.168.30.23:3000/alby96/encelado:<v> (test dentro la build)
|
||||||
|
dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=5.0.0 # zip + immagine + tag + push dell'immagine + release su Gitea (dopo il commit e il push del ramo)
|
||||||
|
docker compose up --build # il container in locale (deploy/local/, segreti in .env)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Regole
|
## Regole
|
||||||
|
|
||||||
1. **Chiedi se hai un dubbio.** Le domande vanno in `docs/QUESTIONS.md`, numerate, con il default che applicheresti; in assenza di risposta applica il default più prudente e annotalo.
|
1. **Chiedi se hai un dubbio.** Le domande vanno in `docs/QUESTIONS.md`, numerate, con il default che applicheresti; in assenza di risposta applica il default più prudente e annotalo.
|
||||||
2. **Mai un ordine reale senza flag e conferma.** `run.executionMode` predefinito `Demo`; `Live` richiede `run.allowLive = true` **e** la frase `CONFERMO LIVE` all'avvio. Nessuna approvazione per singolo ordine (D-20): il bot opera da solo.
|
2. **Mai un ordine reale senza flag e conferma.** `run.executionMode` predefinito `Demo`; `Live` richiede `run.allowLive = true` **e** la frase `CONFERMO LIVE` (dialogo dell'interfaccia, `--confirm-live`, o `ENCELADO_CONFIRM_LIVE`). Nessuna approvazione per singolo ordine (D-20): il bot opera da solo.
|
||||||
3. **Mai una riga del ledger modificata.**
|
3. **Mai una riga del ledger modificata.**
|
||||||
4. **Mai un risultato abbellito.** Se la strategia non regge i costi di eToro, il report lo dice con i numeri. "Nessuna configurazione profittevole" è un esito ammesso.
|
4. **Mai un risultato abbellito.** Se la strategia non regge i costi di eToro, il report lo dice con i numeri. "Nessuna configurazione profittevole" è un esito ammesso.
|
||||||
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.
|
||||||
@@ -62,9 +79,9 @@ dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=4.0.0 # installator
|
|||||||
|
|
||||||
## Cose da non fare
|
## Cose da non fare
|
||||||
|
|
||||||
- Non scrivere chiavi in chat, nel log, nel repo o in `Documenti`.
|
- Non scrivere chiavi in chat, nel log, nel repo, in `Documenti` o nel template Unraid.
|
||||||
- Non usare spread fissi nel cost gate: sempre lo spread reale letto dall'API in quel momento più il markup dell'endpoint dei costi.
|
- Non usare spread fissi nel cost gate: sempre lo spread reale letto dall'API in quel momento più il markup dell'endpoint dei costi.
|
||||||
- Non ricostruire feature a posteriori: il dataset di addestramento è il ledger scritto al momento della decisione.
|
- Non ricostruire feature a posteriori: il dataset di addestramento è il ledger scritto al momento della decisione.
|
||||||
- Non cambiare parametri live in automatico: le proposte passano da `knowledge/proposals.csv` e dal forward test.
|
- Non cambiare parametri live in automatico: le proposte passano da `knowledge/proposals.csv` e dal forward test.
|
||||||
- Non spostare l'installazione in Program Files (vedi `docs/adr/`): l'app scrive accanto alla configurazione.
|
- Non reintrodurre un'interfaccia desktop né librerie JavaScript: l'interfaccia è servita dal bot ed è vanilla (ADR-0007).
|
||||||
- Non usare heredoc lunghi o con backslash nel Bash tool: usare `Write`/`Edit` (vedi memoria `strumenti-heredoc-backslash`).
|
- Non usare heredoc lunghi o con backslash nel Bash tool: usare `Write`/`Edit` (vedi memoria `strumenti-heredoc-backslash`).
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Encelado
|
||||||
|
|
||||||
|
Bot di trading **Correlation Baskets** su eToro (Public API): cinque basket di due coppie forex correlate, ingresso quando il cross sintetico diverge (z-score), uscita quando converge o al take-profit di basket, stop di basket obbligatorio, cost gate sullo spread reale, ledger completo, calendario e notizie gratuiti, apprendimento in ombra. Dalla 5.0 gira **solo in un container** e si guarda dal browser.
|
||||||
|
|
||||||
|
> Il backtest è negativo: la strategia non regge i costi di eToro (`docs/STRATEGY.md`). Il bot è uno strumento di forward test in Demo, non un sistema da mettere sul reale.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Avvio rapido (Docker)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
docker pull 192.168.30.23:3000/alby96/encelado:latest
|
||||||
|
docker run -d --name encelado --restart unless-stopped -p 8080:8080 `
|
||||||
|
-v /mnt/user/appdata/encelado/config:/config -v /mnt/user/appdata/encelado/data:/data `
|
||||||
|
-e ENCELADO_WEB_TOKEN=<una-stringa-lunga> -e TZ=Europe/Rome `
|
||||||
|
-e ETORO_API_KEY=<x-api-key> -e ETORO_USER_KEY=<x-user-key> `
|
||||||
|
192.168.30.23:3000/alby96/encelado:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Poi `http://<ip>:8080/`, token una volta, **AVVIA**. Su Unraid: template in `deploy/unraid/` (icona, porte, volumi e variabili già descritti). Tutto il resto in `docs/DOCKER.md`.
|
||||||
|
|
||||||
|
Senza token il server ascolta solo su localhost del container. La modalità `Live` richiede `run.allowLive = true`, `ETORO_ENVIRONMENT=real` e la frase `CONFERMO LIVE` in `ENCELADO_CONFIRM_LIVE`.
|
||||||
|
|
||||||
|
## Le pagine
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
|  |  |
|
||||||
|
| **Storico ordini**: ordini (unità richieste ed eseguite, slippage, esito), posizioni classificate `basket` / `orfana-bot` / `esterna` / `movimento di cassa`, profitti per periodo con la curva dell'equity, esportazione CSV. | **Log** con filtro, ricerca e «Segui». |
|
||||||
|
|  |  |
|
||||||
|
| **Impostazioni**: configurazione validata, chiavi eToro cifrate, ripristino in cinque passi, Ricerca (modello in ombra, ciclo di apprendimento), Diagnostica (percorsi, quote, bonifica), Informazioni (versione, build, commit). | Sotto i 600 px il rail diventa un menu e i basket diventano cards. |
|
||||||
|
|
||||||
|
## Sviluppo
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet build Encelado.slnx # compilazione
|
||||||
|
dotnet test tests/Encelado.Tests --no-restore # test (xunit)
|
||||||
|
dotnet run --project src/Encelado.Server -- --no-autostart # server locale (Documenti\Encelado), poi http://localhost:8080/
|
||||||
|
dotnet run --project src/Encelado.Server -- --sample # interfaccia con dati finti, senza chiavi
|
||||||
|
dotnet msbuild build/Release.proj -t:Verifica # compilazione + test nella cartella di verifica
|
||||||
|
dotnet msbuild build/Release.proj -t:Docker # l'immagine, con i test dentro la build
|
||||||
|
```
|
||||||
|
|
||||||
|
In VS Code: **F5** (`Encelado (server)` apre il browser da solo; `Encelado (campione)` per lavorare sulle pagine), attività `verifica`, `backtest`, `immagine docker`, `pacchetto`, `rilascia su Gitea`.
|
||||||
|
|
||||||
|
```
|
||||||
|
src/Encelado.Core logica pura: basket, cross sintetici, decisore, cost gate, sizing, esecutore, registro ordini, kill-switch, apprendimento, notifiche
|
||||||
|
src/Encelado.Etoro client HTTP di eToro Public API
|
||||||
|
src/Encelado.Engine motore, ledger, feed, configurazione, impostazioni, storico, Telegram
|
||||||
|
src/Encelado.Server l'eseguibile: Kestrel, API JSON, SSE, interfaccia incorporata (Material 3, vanilla)
|
||||||
|
tools/Encelado.Backtest ticks, baskets, falsify, learn
|
||||||
|
tests/Encelado.Tests xunit
|
||||||
|
deploy/ docker (entrypoint), unraid (template), local (ignorato: volumi del compose)
|
||||||
|
build/ catena di verifica, pacchetto e rilascio (MSBuild)
|
||||||
|
docs/ STATE, ARCHITECTURE, STRATEGY, RUNBOOK, DOCKER, UI_GUIDELINES, ADR…
|
||||||
|
```
|
||||||
|
|
||||||
|
Leggi `CLAUDE.md` per le convenzioni e `docs/STATE.md` per lo stato del lavoro.
|
||||||
|
|
||||||
|
## Autore e licenza
|
||||||
|
|
||||||
|
Alberto Balbo. Uso personale; nessuna licenza di ridistribuzione. Nessuna garanzia: un bot che opera su un conto reale può perdere denaro.
|
||||||
+76
-111
@@ -1,109 +1,49 @@
|
|||||||
# Architettura di Encelado
|
# Architettura di Encelado
|
||||||
|
|
||||||
Aggiornato: 2026-09-23 (Fase 1 del piano 5.0: registro ordini, stati Pending, classificazione delle posizioni).
|
Aggiornato: 2026-09-23 (5.0: Engine + Server, interfaccia web, container). La storia della ricognizione del 2026-09-16 (l'albero che non compilava, i motori Binance/cTrader, SQLite) è nella versione precedente di questo file nella storia git e in ADR-0004.
|
||||||
|
|
||||||
## 1. Che cosa c'era prima della modifica (ricognizione)
|
## 1. Progetti
|
||||||
|
|
||||||
### 1.1 Albero dei progetti
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Encelado.slnx
|
Encelado.slnx
|
||||||
├── src/Encelado.Core libreria portabile (net10.0), zero NuGet, AOT/trim-compatibile
|
├── src/Encelado.Core libreria portabile (net10.0), zero NuGet, AOT/trim-compatibile, zero I/O
|
||||||
│ ├── Backtest/ replay su coppie cointegrate (era Binance), CsvBarSource, CrossSectional
|
│ ├── Broker/ IBroker, modelli (Instrument, QuoteSnapshot, AccountSnapshot, BrokerPosition, ClosedTrade, OrderRequest, OrderOutcome), PaperBroker, RateLimiter
|
||||||
│ ├── Indicators/ SMA, EMA, RSI, MACD, ATR, RollingStdDev, Bollinger, Donchian, RollingWindow<T>
|
│ ├── Baskets/ SyntheticCross, PipMath, BasketMath, SymbolSeries, BasketDecider, CostGate, VolParitySizing, BasketExecutor, BasketPosition (con PendingA/PendingB),
|
||||||
│ ├── Journal/ IJournalSink e record dei journal (DecisionRow, TradeRow, ProbaDecisionRow…)
|
│ │ PendingEntry, BasketStrategyConfig (strategy.json, preset, risk, recovery, learning), OrderTracker (ADR-0009), PositionClassifier, EquityTracker,
|
||||||
│ ├── Market/ Bar, Quote, Tick, Side, TimeFrame
|
│ │ CashFlowDetector, FlattenProcedure, Heartbeat/HeartbeatFile, InstanceLock, RecoveryPlanner
|
||||||
│ ├── Ml/ GBDT nativo, meta-labeling, triple barrier, PurgedCv/CPCV, Pbo (CSCV), Classification (AUC, Brier, log-loss, calibrazione), DriftMonitor (PSI/KS)
|
│ ├── Baskets/History/ OrderRecord (orders.jsonl), BasketOutcomeRow (baskets.csv), PositionRecord, PeriodStats, CashMovementRecord, EquityPoint, HistoryBuilder (la pagina Storico, pura)
|
||||||
│ ├── Research/ pipeline ProbaBot: AssetFrame, eventi CUSUM, bracci di feature B/C, EventBacktest, Trials, Gates
|
│ ├── Baskets/Data/ BidAskBar + CSV, TickToBars (tick MT5 → M15)
|
||||||
│ ├── Risk/ RiskEngine e RiskLimits (kill-switch giornaliero, esposizione, spread)
|
│ ├── Baskets/Learning/ CalibrationTables, OnlineLogistic, SmallMlp, ThompsonBandit, VolForecast, LearningFeatures, ModelEvaluator
|
||||||
│ ├── Rl/ Mlp a due strati (Adam), DqnAgent, PairEnvironment
|
│ ├── Baskets/Backtest/ BasketBacktest, BacktestBroker, BasketTrials
|
||||||
│ ├── Statistics/ Ols, DickeyFuller, Cointegration (+HalfLife), Johansen, Kalman, Pca, Performance (Sharpe, PSR, DSR, Kelly), Normal
|
│ ├── News/ CalendarParser, RssParser, SentimentLexicon, SentimentEngine
|
||||||
│ └── Strategies/ StatArbStrategy (coppie), StrategyParameters
|
│ ├── Notifications/ INotifier, NullNotifier, TelegramNotifier (solo HttpClient)
|
||||||
├── src/Encelado.Storage SQLite (Microsoft.Data.Sqlite) — l'unica dipendenza NuGet a runtime; journal in doppia scrittura, dataset, modelli, campioni
|
│ └── Statistics/ Ols, Normal, Performance (Sharpe, PSR, DSR, drawdown), Classification, Pbo
|
||||||
├── src/Encelado.CTrader adattatore cTrader Open API (NuGet cTrader.OpenAPI.Net). NON referenziato dal Bot: non è mai stato collegato
|
├── src/Encelado.Etoro EtoroOptions, EtoroHttp (x-api-key/x-user-key/x-request-id, limitatore per classe di quota, 429, scarto orologio), EtoroBroker : IBroker, Json
|
||||||
├── src/Encelado.Bot WPF (net10.0-windows), WinExe "Encelado.exe", zero NuGet
|
├── src/Encelado.Engine libreria (net10.0): il motore e tutto ciò che tocca il disco o la rete oltre al broker
|
||||||
│ ├── Configuration/ BotConfig, ConfigLoader (JsonDocument a mano, chiavi sconosciute segnalate), ConfigDefaults (JSON di fabbrica incorporato), ConfigWriter (modifica per percorso puntato, scrittura atomica), CredentialStore (DPAPI in %LOCALAPPDATA%\Encelado), CredentialResolver
|
│ ├── Baskets/ BasketEngine in otto file parziali (.cs ciclo e decisioni, .Pending, .Reconcile, .Kill, .Recovery, .State, .Commands, .Snapshot), Ledger, Feeds,
|
||||||
│ ├── Engine/ BotSupervisor (ciclo di vita, snapshot), ProbaEngine (motore cTrader, incompleto), AccountState, BotSnapshot, TradeJournal, DecisionLog
|
│ │ ContextProvider, LearningState, HistoryService (fonti della pagina Storico, con cache), TelegramReports, TelegramCommands, HeadlessRunner (console: stato, comandi, bonifica)
|
||||||
│ ├── Diagnostics/ CsvTable (tabelle ';' con header e spostamento in .old), Metrics
|
│ ├── Configuration/ BotConfig, ConfigLoader (JsonDocument, variabili d'ambiente), ConfigDefaults (JSON di fabbrica incorporato), ConfigWriter, AppPaths (/config, /data, Documenti), KeyStore (IKeyStore, EncryptedFileKeyStore, KeyStores)
|
||||||
│ ├── Logging/ Log statico non bloccante su Channel<T>, file ';' con rotazione, Sink per la UI
|
│ ├── Engine/ IEngine, BotSupervisor (ciclo di vita, snapshot, eventi di log), BotSnapshot, SnapshotJson (lo snapshot in JSON, a mano), SampleSnapshot (dati finti per --sample e test)
|
||||||
│ ├── Ui/ MainViewModel (INotifyPropertyChanged a mano), Theme.xaml (tema scuro proprio), pagine Status/Log/Settings, LoginWindow (OAuth cTrader), SettingsCatalogue, SettingField, Converters
|
│ ├── Logging/ Log statico non bloccante su Channel<T>, file ';' con rotazione, console
|
||||||
│ └── MainWindow.xaml(.cs) shell con navigazione laterale, timer 1 s che applica lo snapshot
|
│ └── Settings/ SettingsCatalogue e SettingField (il form di Impostazioni), SettingsService (descrizione e applicazione via API), UiClock (fuso orario a schermo)
|
||||||
├── tests/Encelado.Tests xunit 2.9 (framework GIÀ presente: si usa quello, niente mini-runner)
|
├── src/Encelado.Server l'eseguibile (Microsoft.NET.Sdk.Web, framework reference Microsoft.AspNetCore.App, nessun NuGet)
|
||||||
├── tools/Encelado.Backtest strumento console di ricerca ("backtest <comando>"), unico progetto con TA-Lib
|
│ ├── Program.cs argomenti e ambiente, AppPaths, log, notifier, supervisore, Telegram, LogBuffer, HistoryService, WebHost, avvio automatico, SIGTERM, --health, --sample
|
||||||
└── build/ Release.proj (verifica, pacchetto, rilascio su Gitea), Encelado.iss (Inno Setup)
|
│ └── Web/ WebHost (Kestrel, autenticazione, rotte statiche e API), Json (Utf8JsonWriter), LogBuffer (finestra del log per la pagina), LogBridge (log di ASP.NET → Log),
|
||||||
|
│ wwwroot/ (index.html, login.html, app.css, app.js, icon.svg, manifest.webmanifest — incorporati)
|
||||||
|
├── tools/Encelado.Backtest ticks, baskets, falsify, learn (referenzia Core ed Engine)
|
||||||
|
├── tests/Encelado.Tests xunit (net10.0, gira su Windows e nello stadio di build dell'immagine)
|
||||||
|
├── deploy/docker/ entrypoint.sh (TZ, PUID/PGID, gosu, exec)
|
||||||
|
├── deploy/unraid/ encelado.xml (template Container v2), README.md
|
||||||
|
├── Dockerfile, docker-compose.yml, .dockerignore
|
||||||
|
└── build/ Release.proj (Verifica, Backtest, Pubblica, Docker, Pacchetto, Rilascia), README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Target framework**: `net10.0` (Bot e test `net10.0-windows`), `Nullable` e `TreatWarningsAsErrors` attivi per tutti i progetti via `Directory.Build.props`. SDK installato: 10.0.301.
|
- **Target framework**: `net10.0` ovunque, `Nullable` e `TreatWarningsAsErrors` attivi via `Directory.Build.props`; `InvariantGlobalization` nei progetti dell'applicazione (i test lo spengono per verificare i fusi IANA e Windows). SDK 10.0.301.
|
||||||
- **Pattern**: nessun contenitore DI; oggetti costruiti a mano nel supervisore; async/await con `ConfigureAwait(false)` nel motore; `Channel<T>` per il log; `Lock` per lo stato condiviso; snapshot immutabili verso la UI; ogni tabella è CSV `;` con colonna finale `motivazione`; log strutturato `timestamp;level;source;subject;event;message;exception;stack`.
|
- **Pattern**: nessun contenitore DI; oggetti costruiti a mano in `Program.cs` e nel supervisore; async/await con `ConfigureAwait(false)` nel motore; `Channel<T>` per il log e le notifiche; `Lock` per lo stato condiviso; snapshot immutabili verso il server; ogni tabella è CSV `;` con colonna finale `motivazione`; log strutturato `timestamp;level;source;subject;event;message;exception;stack`.
|
||||||
- **Client broker esistente**: nessun client eToro. Esisteva un adattatore Binance (cancellato, non committato) e un adattatore cTrader (mai collegato al Bot). Il motore `ProbaEngine` usa i tipi cTrader direttamente.
|
- **Storage**: solo file (ADR-0002). Nel container `/config` (configurazione, chiavi cifrate) e `/data` (`data/`, `knowledge/`, `reports/`, `results/`, `logs/`); fuori dal container `Documenti\Encelado` o `ENCELADO_CONFIG_DIR`.
|
||||||
- **Storage**: SQLite in `%ProgramData%\Encelado\encelado.db` (barre, dataset, modelli, journal) più CSV nella cartella dei log. Configurazione in `Documenti\Encelado\encelado.json`; credenziali cifrate DPAPI in `%LOCALAPPDATA%\Encelado`.
|
- **Build ed esecuzione**: `dotnet build Encelado.slnx`; `dotnet run --project src/Encelado.Server`; immagine con `dotnet msbuild build/Release.proj -t:Docker`; la versione rilasciata viene dal tag git, commit e data di build da `AssemblyMetadata`.
|
||||||
- **UI**: WPF, tema scuro proprio (`Ui/Theme.xaml`: palette, `Card`, `Chip`, `Kpi`, `Label`, `Value`, `Sub`, `Head`, pulsanti `Primary`/`Danger`, `PowerButton`, `ModeBadge`), font tabulare `Cascadia Mono`. Nessuna libreria MVVM: `MainViewModel` implementa `INotifyPropertyChanged` a mano.
|
|
||||||
- **Test**: xunit con test di binding WPF (`UiBindingTests` ascolta la trace source dei binding e fallisce su ogni binding irrisolto), test di configurazione, statistica, ML, rischio.
|
|
||||||
- **Build ed esecuzione**: `dotnet build Encelado.slnx`; verifica completa `dotnet msbuild build/Release.proj -t:Verifica`; l'app legge `Documenti\Encelado\encelado.json` (creato dal JSON di fabbrica al primo avvio); la versione rilasciata viene dal tag git (`build/Release.proj`).
|
|
||||||
|
|
||||||
### 1.2 Stato dell'albero di lavoro trovato il 2026-09-16
|
## 2. Flusso dati (live)
|
||||||
|
|
||||||
L'albero **non compilava**: la sessione precedente (rework verso cTrader, 2026-09-09) era rimasta a metà e non committata.
|
|
||||||
|
|
||||||
| Problema | Dove |
|
|
||||||
|---|---|
|
|
||||||
| `Encelado.Bot` non referenzia `Encelado.CTrader`, quindi `ProbaEngine`, `BotConfig`, `CredentialResolver`, `LoginWindow`, `AccountState` non risolvono i tipi cTrader | `src/Encelado.Bot/Encelado.Bot.csproj` |
|
|
||||||
| `MainWindow.xaml.cs` referenzia `PositionsPage` (cancellata), `_config.Binance`, `ClosePairAsync`, `EnabledPairs` (era Binance) | `src/Encelado.Bot/MainWindow.xaml.cs` |
|
|
||||||
| `CsvTable.cs` usa `Side` senza `using Encelado.Core.Market` | `src/Encelado.Bot/Diagnostics/CsvTable.cs` |
|
|
||||||
| `TestSnapshots.cs` costruisce lo snapshot dell'era Binance (`PairRow`, `EquityCurve`, `OrderRow`…) | `tests/Encelado.Tests/TestSnapshots.cs` |
|
|
||||||
| `Documenti\Encelado\encelado.json` dell'utente è nel formato Binance (sezioni `binance`, `pairs`) | file dell'utente, non nel repo |
|
|
||||||
|
|
||||||
Decisione presa (vedi `docs/QUESTIONS.md`, D-09): il motore cTrader resta nel repository come modulo selezionabile (`engine.strategy = "proba"`) e viene rimesso in compilazione; il motore nuovo (`"baskets"`) è il predefinito.
|
|
||||||
|
|
||||||
### 1.3 Punti di estensione usati dalla modifica
|
|
||||||
|
|
||||||
| Cosa | Dove si aggancia |
|
|
||||||
|---|---|
|
|
||||||
| Nuova strategia | `BotSupervisor` costruisce il motore in base a `engine.strategy`; il motore espone `IEngine` (`RunAsync`, `Snapshot`, comandi) |
|
|
||||||
| Flusso dati di mercato | il motore basket interroga `IBroker.GetQuotesAsync` a polling (2-5 s) e costruisce le barre M15 in locale; le candele ufficiali servono per il riscaldamento e la riconciliazione |
|
|
||||||
| Esecuzione ordini | `IBroker.OpenAsync/CloseAsync/UpdateStopsAsync`, tre implementazioni (`EtoroBroker`, `PaperBroker`, `BacktestBroker`) |
|
|
||||||
| Log delle operazioni | `Log` (file `;`), più il ledger nuovo (`data/ledger/decisions.jsonl`, `baskets.csv`) |
|
|
||||||
| UI | pagine nuove (`BasketsPage`) selezionate dalla shell in base al motore; `Theme.xaml` riusato |
|
|
||||||
| Configurazione | `encelado.json` (sezioni `engine`, `etoro`, `logging`) + `strategy.json` (parametri e preset dei basket) letti con `JsonDocument`, modificati con `ConfigWriter` |
|
|
||||||
| Test | xunit esistente; nuove suite in `tests/Encelado.Tests/Baskets*.cs` |
|
|
||||||
|
|
||||||
## 2. Architettura della modifica (obiettivo)
|
|
||||||
|
|
||||||
### 2.1 Progetti (stato del 2026-09-16 sera, dopo la rimozione dei motori precedenti — ADR-0004)
|
|
||||||
|
|
||||||
```
|
|
||||||
src/Encelado.Core/Broker/ IBroker, modelli (Instrument, QuoteSnapshot, AccountSnapshot, BrokerPosition, OrderRequest, OrderOutcome), PaperBroker (simulatore sopra un feed reale), RateLimiter
|
|
||||||
src/Encelado.Core/Baskets/ matematica e logica pura, senza I/O:
|
|
||||||
SyntheticCross (derivazione automatica del cross e dei segni), PipMath, BasketMath (rendimenti log, ATR, EWMA vol, ρ_W/ρ_20, z-score, semiperiodo OLS, forza di trend),
|
|
||||||
SymbolSeries (barre + quote + qualità dati), BasketDecider (entrate/uscite/averaging di §5), CostGate, VolParitySizing, BasketExecutor (protocollo leg-risk con il registro),
|
|
||||||
BasketPosition (macchina a stati, con PendingA/PendingB), PendingEntry (ingresso in sospeso), BasketStrategyConfig (strategy.json, preset), ExecutionMode (Paper | Demo | Live),
|
|
||||||
OrderTracker (registro persistente degli ordini, risoluzione per orderId / riferimento / posizioni — ADR-0009), PositionClassifier (basket | orfana-bot | esterna),
|
|
||||||
EquityTracker (picco al netto dei movimenti di cassa), FlattenProcedure (kill-switch in tre passi: annulla, chiudi, verifica la piattezza), Heartbeat/HeartbeatFile, InstanceLock, RecoveryPlanner (barre trascorse, chiudi/tieni, rapporto)
|
|
||||||
src/Encelado.Core/Notifications/ INotifier, NullNotifier, TelegramNotifier (coda Channel<T>, un messaggio al secondo, retry con backoff, long polling dei comandi dalla sola chat autorizzata; solo HttpClient)
|
|
||||||
src/Encelado.Core/Baskets/History/ OrderRecord (riga di orders.jsonl); dalla Fase 7 PositionRecord, PeriodStats, HistoryBuilder
|
|
||||||
src/Encelado.Core/Baskets/Data/ BidAskBar + CSV, TickToBars (tick MT5 → M15)
|
|
||||||
src/Encelado.Core/Baskets/Learning/ livelli 0-3: CalibrationTables, OnlineLogistic (SGD+L2, standardizzazione rolling), SmallMlp (16 ReLU, Adam, early stopping, gradient check),
|
|
||||||
ThompsonBandit (Beta per preset × terzile di vol), VolForecast (EWMA vs HAR-RV, PSI), LearningFeatures (28 feature del ledger), ModelEvaluator (walk-forward, fold purgati, bootstrap, attivazione)
|
|
||||||
src/Encelado.Core/Baskets/Backtest/ BasketBacktest (event-driven su barre M15 bid/ask), BacktestBroker, BasketTrials (griglia, PSR/DSR, PBO, walk-forward 6m/1m)
|
|
||||||
src/Encelado.Core/News/ parser puri: CalendarParser (JSON/XML FairEconomy), RssParser (XmlReader), SentimentLexicon, SentimentEngine (finestre 1h/4h/24h con decadimento)
|
|
||||||
src/Encelado.Core/Ml/, Statistics/ la statistica condivisa rimasta: Classification (AUC, Brier, log-loss, calibrazione), Pbo (CSCV), Performance (Sharpe, PSR, DSR, drawdown, momenti), Ols, Normal
|
|
||||||
src/Encelado.Etoro/ EtoroOptions, EtoroHttp (HttpClient, x-api-key/x-user-key/x-request-id, limitatore per classe di quota, 429 con Retry-After, scarto orologio dall'header Date), EtoroBroker : IBroker
|
|
||||||
src/Encelado.Bot/Baskets/ BasketEngine in file parziali: BasketEngine.cs (ciclo a thread singolo, polling quote, barre locali, decisioni, esecuzione), .Pending.cs (registro ordini: risoluzione e
|
|
||||||
ripresa degli ingressi in sospeso), .Reconcile.cs (conto, classificazione delle posizioni, adozione delle orfane, movimenti di cassa, margin guard, equity stop), .Kill.cs (file STOP, kill-switch con verifica di piattezza e stato Halted-Residuo, chiusura dei residui, reset in cinque passi), .Recovery.cs (lock di istanza, heartbeat, inattività, procedura di recupero),
|
|
||||||
.State.cs (baskets_state.json), .Commands.cs (comandi, bonifica), .Snapshot.cs (snapshot per finestra e console),
|
|
||||||
Ledger (decisions.jsonl e orders.jsonl append-only, baskets.csv, firme delle decisioni, rotazione mensile, scritture atomiche), Feeds (calendario + RSS con cache su disco, robots.txt, backoff),
|
|
||||||
TelegramReports (testi dallo snapshot: stato orario, posizioni, periodo), TelegramCommands (i comandi della chat passano dal supervisore), Notifications.Create (Telegram o niente),
|
|
||||||
LearningState (modello in ombra, bandit, ciclo settimanale, knowledge/), HeadlessRunner (--headless)
|
|
||||||
src/Encelado.Bot/Configuration/ BotConfig (etoro, run, ui, logging), ConfigLoader (JsonDocument, avvisi sulle sezioni di versioni precedenti), ConfigDefaults, ConfigWriter, EtoroKeyStore (DPAPI)
|
|
||||||
src/Encelado.Bot/Engine/ IEngine, BotSupervisor (ciclo di vita, snapshot, feed di attività), BotSnapshot
|
|
||||||
src/Encelado.Bot/Ui/ Theme.xaml, MainWindow (barra in alto con le tre schede), Pages/DashboardPage (i cinque numeri, la tabella dei basket, il contesto, l'attività), LogPage, SettingsPage (SettingsCatalogue, fuso orario),
|
|
||||||
EtoroLoginWindow, PromptWindow (CONFERMO LIVE, motivazione del reset), UiClock (fuso orario della finestra), MainViewModel
|
|
||||||
tools/Encelado.Backtest `ticks` (tick MT5 → barre M15 bid/ask), `baskets` (baseline, griglia, trials, PBO, walk-forward), `falsify` (i cinque test di falsificazione); `--costs etoro|api`
|
|
||||||
```
|
|
||||||
|
|
||||||
Progetti rimossi il 2026-09-16 (ADR-0004): `Encelado.CTrader`, `Encelado.Storage`, `Core/Backtest`, `Indicators`, `Journal`, `Market`, `Portfolio`, `Research`, `Risk`, `Rl`, `Strategies`, il grosso di `Ml` e `Statistics`, `ProbaEngine`, le pagine `StatusPage`/`LoginWindow`, `ApprovalQueue`.
|
|
||||||
|
|
||||||
### 2.2 Flusso dati (live)
|
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
@@ -114,18 +54,21 @@ flowchart LR
|
|||||||
C[Calendario + RSS] --> F[Feature contesto]
|
C[Calendario + RSS] --> F[Feature contesto]
|
||||||
F --> S
|
F --> S
|
||||||
M[Meta-modello in ombra<br/>vol forecast] --> S
|
M[Meta-modello in ombra<br/>vol forecast] --> S
|
||||||
S -->|decisione| X[Executor<br/>leg-risk protocol]
|
S -->|decisione| X[Executor<br/>leg-risk + OrderTracker]
|
||||||
X --> E
|
X --> E
|
||||||
S --> L[(Ledger jsonl/csv)]
|
S --> L[(Ledger jsonl/csv)]
|
||||||
X --> L
|
X --> L
|
||||||
S --> U[Snapshot → UI / headless]
|
S --> N[Snapshot]
|
||||||
L --> K[Ciclo settimanale: L0-L3]
|
N --> W[Server: SSE /api/stream<br/>console, Telegram]
|
||||||
|
W --> U[Browser]
|
||||||
|
U -->|POST /api/commands| W --> S
|
||||||
|
L --> K[Ciclo di apprendimento<br/>backtest learn / Ricerca]
|
||||||
K --> M
|
K --> M
|
||||||
```
|
```
|
||||||
|
|
||||||
Le decisioni avvengono su un solo thread; l'I/O è asincrono; l'unico gate umano per ordine è sparito (ADR-0005): restano avvio del reale, kill-switch, reset e cambio di preset.
|
Le decisioni avvengono su un solo thread; l'I/O è asincrono; l'unico gate umano per ordine è sparito (ADR-0005): restano avvio del reale, kill-switch, reset e cambio di preset, tutti dall'interfaccia web, dalla console o da Telegram.
|
||||||
|
|
||||||
### 2.3 Macchina a stati del basket
|
## 3. Macchina a stati del basket
|
||||||
|
|
||||||
```
|
```
|
||||||
Idle ──(segnale + cancelli)──► Entering ──(A e B eseguite)──► Open ──(add)──► Adding ──► Open
|
Idle ──(segnale + cancelli)──► Entering ──(A e B eseguite)──► Open ──(add)──► Adding ──► Open
|
||||||
@@ -143,20 +86,42 @@ Idle ──(segnale + cancelli)──► Entering ──(A e B eseguite)──
|
|||||||
|
|
||||||
Stati del motore (non del basket): `Halted` dopo un kill-switch o un equity stop con il conto piatto per le posizioni del bot; **`Halted-Residuo`** quando la verifica di piattezza trova ancora posizioni del bot sul conto (banner rosso con l'elenco, reset rifiutato finché restano). Negli stati `PendingA`/`PendingB` il basket non viene valutato e non manda ordini; il registro degli ordini (`OrderTracker`) chiede l'esito al server e, quando manca, lo ricostruisce dalla posizione comparsa sul conto. Gli ingressi in sospeso sopravvivono a un riavvio (`pendingEntries` in `baskets_state.json`) e vengono risolti all'avvio prima di qualsiasi decisione.
|
Stati del motore (non del basket): `Halted` dopo un kill-switch o un equity stop con il conto piatto per le posizioni del bot; **`Halted-Residuo`** quando la verifica di piattezza trova ancora posizioni del bot sul conto (banner rosso con l'elenco, reset rifiutato finché restano). Negli stati `PendingA`/`PendingB` il basket non viene valutato e non manda ordini; il registro degli ordini (`OrderTracker`) chiede l'esito al server e, quando manca, lo ricostruisce dalla posizione comparsa sul conto. Gli ingressi in sospeso sopravvivono a un riavvio (`pendingEntries` in `baskets_state.json`) e vengono risolti all'avvio prima di qualsiasi decisione.
|
||||||
|
|
||||||
### 2.4 Interfacce
|
## 4. Interfacce
|
||||||
|
|
||||||
- `IBroker`: `Environment`, `GetInstrumentsAsync`, `GetQuotesAsync(ids)`, `GetCandlesAsync(id, interval, count)`, `GetAccountAsync`, `GetPositionsAsync`, `OpenAsync(OrderRequest)` (esito per `orderId`, poi per posizione comparsa; mai un esito inventato), `LookupOrderAsync(clientRef)`, `LookupOrderByIdAsync(orderId)`, `CancelOrderAsync(orderId)`, `CloseAsync(positionId, instrumentId)`, `UpdateStopsAsync(positionId, sl, tp)`, `GetCostAsync(OrderRequest)`, `GetClosedTradesAsync`, `ClockSkew`.
|
- `IBroker`: `Environment`, `GetInstrumentsAsync`, `GetQuotesAsync(ids)`, `GetCandlesAsync(id, interval, count)`, `GetAccountAsync`, `GetPositionsAsync`, `OpenAsync(OrderRequest)` (esito per `orderId`, poi per posizione comparsa; mai un esito inventato), `LookupOrderAsync(clientRef)`, `LookupOrderByIdAsync(orderId)`, `CancelOrderAsync(orderId)`, `CloseAsync(positionId, instrumentId)`, `UpdateStopsAsync(positionId, sl, tp)`, `GetCostAsync(OrderRequest)`, `GetClosedTradesAsync`, `ClockSkew`.
|
||||||
- `IContextProvider` (Bot): calendario, notizie e sentiment per basket (`FeedContextProvider`; `EmptyContextProvider` nei test).
|
- `IContextProvider` (Engine): calendario, notizie e sentiment per basket (`FeedContextProvider`).
|
||||||
- `IModel`: `Predict(features)`, `Update(features, label)`, JSON, implementato da `OnlineLogistic` e `SmallMlp`.
|
- `IModel`: `Predict(features)`, `Update(features, label)`, JSON, implementato da `OnlineLogistic` e `SmallMlp`.
|
||||||
- `IEngine` (Bot): `RunAsync`, `CloseAllAsync`, `ExecuteAsync(EngineCommand)` con `Close`, `KillSwitch` (argomento `esterne` per chiudere anche le posizioni esterne), `SetPreset`, `ResetEquityStop(motivazione)` (la procedura in cinque passi), `CloseResidue`, `Bonifica`, `History`, `Pause`, `Resume`, `Snapshot()`. Ogni comando eseguito è una riga `comando` nel ledger.
|
- `IEngine` (Engine): `RunAsync`, `CloseAllAsync`, `ExecuteAsync(EngineCommand)` con `Close`, `KillSwitch` (argomento `esterne`), `SetPreset`, `ResetEquityStop(motivazione)` (la procedura in cinque passi), `CloseResidue`, `Bonifica`, `History`, `Pause`, `Resume`, `Snapshot()`. Ogni comando eseguito è una riga `comando` nel ledger.
|
||||||
- `INotifier` (Core): `Notify(kind, title, text)` non bloccante e `Status`; il motore lo riceve dal supervisore (`BotSupervisor(config, factory, notifier)`), che a sua volta lo riceve dalla finestra o dall'headless (`Notifications.Create`).
|
- `INotifier` (Core): `Notify(kind, title, text)` non bloccante e `Status`; il motore lo riceve dal supervisore, che lo riceve da `Program.cs` (`Notifications.Create`).
|
||||||
- `IUiActions` (Bot): ciò che le pagine possono chiedere alla finestra (chiudi basket, kill-switch, preset, reset, chiavi, file).
|
- `IKeyStore` (Engine): `Load(demo)`, `Save`, `Clear`, `CanSave`, implementato da `EncryptedFileKeyStore` (`etoro.keys.enc`, AES-256-GCM con passphrase); le variabili `ETORO_API_KEY`/`ETORO_USER_KEY` le applica il loader e `KeyStores.Resolve` prova prima quelle poi il file.
|
||||||
|
- `WebContext` (Server): ciò che il web host vede del processo — configurazione, supervisore, finestra del log, storico, percorso del file, `Start` e `Learn` come funzioni, `Snapshot()` (finto in `--sample`).
|
||||||
|
|
||||||
### 2.5 Vincoli e limiti scoperti in Fase 0
|
## 5. API web
|
||||||
|
|
||||||
- L'endpoint candele di eToro accetta solo `count ≤ 1000` senza data di partenza: dà al massimo ~10 giorni di M15. Lo storico per il backtest viene dai tick MT5 forniti dall'utente (`A:\Download\Trading`, 2018-12 → 2026-09, UTC), convertiti in M15 bid/ask dallo strumento `backtest ticks`.
|
Tutte le rotte tranne `/api/health`, `/login`, `/icon.svg`, `/manifest.webmanifest` chiedono il token (`ENCELADO_WEB_TOKEN`) nel cookie `encelado_token` (HttpOnly, 30 giorni) o in `Authorization: Bearer`. Senza token configurato il server ascolta solo su localhost e non chiede niente. JSON scritto a mano con `Utf8JsonWriter`, mai serializzazione per reflection.
|
||||||
- Quote di mercato (`/api/v2/market-data/rates`) in batch fino a 1000 strumenti per chiamata: un polling ogni 3 s costa 20 richieste/min sulla quota condivisa di 120/min.
|
|
||||||
|
| Rotta | Cosa |
|
||||||
|
|---|---|
|
||||||
|
| `GET /`, `/index.html`, `/app.css`, `/app.js`, `/icon.svg`, `/manifest.webmanifest`, `/login`; `POST /login` | la pagina e i suoi file, incorporati; il login mette il cookie e rimanda a `/` |
|
||||||
|
| `GET /api/health` | `{ok, running, heartbeatAgeSeconds, disk, state}`; usato dal `HEALTHCHECK` del container |
|
||||||
|
| `GET /api/snapshot` | lo snapshot completo (`SnapshotJson`) |
|
||||||
|
| `GET /api/stream` | Server-Sent Events, `event: snapshot` con lo stesso JSON, uno al secondo |
|
||||||
|
| `GET /api/log?level=&q=&limit=&after=` | la finestra del log (`LogBuffer`), a incrementi per `seq` |
|
||||||
|
| `POST /api/commands/{start\|stop\|close\|kill\|preset\|reset\|residue\|pause\|resume\|bonifica\|learn}` | corpo `{argument, reason, confirm, foreign}`; risposta `{ok, message, steps[], positions[]}` |
|
||||||
|
| `GET /api/history/orders\|positions\|periods[?format=csv&from=&to=&basket=&symbol=&outcome=&origin=]` | le tre viste dello Storico (`HistoryBuilder`); `format=csv` scarica il file `;` |
|
||||||
|
| `GET/POST /api/settings` | il form (`SettingsService.Describe`) e le modifiche per percorso (`Apply`, con validazione e scrittura atomica) |
|
||||||
|
| `POST/DELETE /api/settings/keys` | verifica le chiavi contro eToro (profilo e conto) e le salva nel file cifrato; rimozione |
|
||||||
|
| `POST /api/settings/restore` | configurazione ai valori di fabbrica con backup datato (solo a bot fermo) |
|
||||||
|
| `GET /api/info` | nome, autore, versione, data di build, commit, percorsi, .NET, sistema, uptime, quote, fuso |
|
||||||
|
| `GET /api/research` | stato dell'apprendimento (abilitazioni, modello in ombra, bandit, volatilità, file in `knowledge/`) |
|
||||||
|
|
||||||
|
Il ciclo di vita del processo (`Program.cs`): carica `AppPaths` (semina `encelado.json` e `strategy.json`), inizializza il log (file + console), crea notifier e supervisore, avvia Kestrel, avvia il motore se `ENCELADO_AUTOSTART` ≠ 0 (chiavi da `KeyStores.Resolve`, frase `CONFERMO LIVE` per il Live), legge i comandi da stdin, si ferma su Ctrl+C, SIGTERM, `stop` o `--minutes`. `--health` interroga `/api/health` ed esce 0/1; `--sample` serve dati finti senza motore; `--bonifica` avvia con la bonifica interattiva.
|
||||||
|
|
||||||
|
## 6. Vincoli e limiti dell'API eToro
|
||||||
|
|
||||||
|
- L'endpoint candele accetta solo `count ≤ 1000` senza data di partenza: dà al massimo ~10 giorni di M15. Lo storico per il backtest viene dai tick MT5 forniti dall'utente (`A:\Download\Trading`, 2018-12 → 2026-09, UTC), convertiti in M15 bid/ask dallo strumento `backtest ticks`.
|
||||||
|
- Quote di mercato (`/api/v2/market-data/rates`) in batch fino a 1000 strumenti per chiamata: un polling ogni 3 s costa 20 richieste/min sulla quota condivisa di 120/min. Le sette coppie di conversione della valuta di visualizzazione (EURUSD, GBPUSD, USDCHF, USDJPY, AUDUSD, USDCAD, NZDUSD) viaggiano nella stessa richiesta.
|
||||||
- Quota ordini: 20 richieste/min (demo e reale separate). Un basket costa 2 aperture + 2 chiusure.
|
- Quota ordini: 20 richieste/min (demo e reale separate). Un basket costa 2 aperture + 2 chiusure.
|
||||||
- Le quote di `rates` sono senza markup; il costo effettivo (markup + spread di mercato + overnight) arriva da `POST /trading/info/{demo/}costs` (20/min dedicate). Il cost gate somma i due.
|
- Le quote di `rates` sono senza markup; il costo effettivo (markup + spread di mercato + overnight) arriva da `POST /trading/info/{demo/}costs` (20/min dedicate). Il cost gate somma i due.
|
||||||
- Ordini: `POST /api/v2/trading/execution/{demo/}orders` (asincrono: esito con `orders:lookup?orderId=`; il server **non** registra l'`x-request-id` come `referenceId`, verificato il 2026-09-23); `sellShort` e leva > 1 richiedono `stopLossRate`. Il server può **ridurre** un ordine invece di rifiutarlo (2 000 USD di margine a margine esaurito, 2026-09-16): le unità eseguite si leggono dalla risposta, mai date per scontate. Chiusura: `POST /api/v1/trading/execution/{demo/}market-close-orders/positions/{id}`. Cancellazione: `DELETE /api/v2/trading/execution/{demo/}orders/{id}`.
|
- Ordini: `POST /api/v2/trading/execution/{demo/}orders` (asincrono: esito con `orders:lookup?orderId=`; il server **non** registra l'`x-request-id` come `referenceId`, verificato il 2026-09-23); `sellShort` e leva > 1 richiedono `stopLossRate`. Il server può **ridurre** un ordine invece di rifiutarlo (2 000 USD di margine a margine esaurito, 2026-09-16): le unità eseguite si leggono dalla risposta, mai date per scontate. Chiusura: `POST /api/v1/trading/execution/{demo/}market-close-orders/positions/{id}`. Cancellazione: `DELETE /api/v2/trading/execution/{demo/}orders/{id}`.
|
||||||
- Esposizione minima per posizione: 1000 USD (`minPositionExposure`); leva ammessa 1-30 (majors) e 1-20 (minors). Il conto reale dell'utente vale 193,18 USD: con i limiti di rischio della strategia il reale non è praticabile oggi (vedi QUESTIONS D-05).
|
- Esposizione minima per posizione: 1000 USD (`minPositionExposure`); leva ammessa 1-30 (majors) e 1-20 (minors). Il conto reale dell'utente vale 224,90 USD: con i limiti di rischio della strategia il reale non è praticabile oggi (vedi QUESTIONS D-05).
|
||||||
|
|||||||
@@ -72,10 +72,11 @@ Copie dei feed usate dai test: `tests/fixtures/` (scaricate il 2026-09-16).
|
|||||||
|
|
||||||
Il token non è mai in un file del repository: `TELEGRAM_BOT_TOKEN` nell'ambiente (o, a scelta dell'operatore, `botToken` in `encelado.local.json`).
|
Il token non è mai in un file del repository: `TELEGRAM_BOT_TOKEN` nell'ambiente (o, a scelta dell'operatore, `botToken` in `encelado.local.json`).
|
||||||
|
|
||||||
## 4. Schema dei file in `Documenti\Encelado`
|
## 4. Schema dei file (nel container `/config` e `/data`; fuori dal container `Documenti\Encelado`)
|
||||||
|
|
||||||
```
|
```
|
||||||
encelado.json, strategy.json, instruments.json
|
/config: encelado.json, strategy.json, instruments.json, etoro.keys.enc (chiavi cifrate), STOP (kill-switch da file)
|
||||||
|
/data:
|
||||||
data/market/candles_<SYMBOL>_M15.csv timeUtc;bidOpen;bidHigh;bidLow;bidClose;askOpen;askHigh;askLow;askClose;spreadMean;ticks;motivazione
|
data/market/candles_<SYMBOL>_M15.csv timeUtc;bidOpen;bidHigh;bidLow;bidClose;askOpen;askHigh;askLow;askClose;spreadMean;ticks;motivazione
|
||||||
data/market/data_quality.csv simbolo;tick_letti;tick_scartati;barre;prima_barra;ultima_barra;buchi_feriali_oltre_1h;barre_spike;spread_mediano_pip;motivazione
|
data/market/data_quality.csv simbolo;tick_letti;tick_scartati;barre;prima_barra;ultima_barra;buchi_feriali_oltre_1h;barre_spike;spread_mediano_pip;motivazione
|
||||||
data/calendar/events.jsonl {title,country,date,impact,forecast,previous,actual}
|
data/calendar/events.jsonl {title,country,date,impact,forecast,previous,actual}
|
||||||
@@ -86,9 +87,11 @@ data/ledger/baskets.csv vedi docs/LEDGER_SCHEMA.md
|
|||||||
data/ledger/orders.jsonl una riga per ordine inviato e per cambio di stato (5.0)
|
data/ledger/orders.jsonl una riga per ordine inviato e per cambio di stato (5.0)
|
||||||
data/state/baskets_state.json posizioni aperte, ingressi in attesa, picco di equity al netto dei movimenti di cassa, blocchi
|
data/state/baskets_state.json posizioni aperte, ingressi in attesa, picco di equity al netto dei movimenti di cassa, blocchi
|
||||||
data/state/pending_orders.json il registro degli ordini (5.0)
|
data/state/pending_orders.json il registro degli ordini (5.0)
|
||||||
|
data/state/heartbeat.json ultimo battito (30 s), run, nota di arresto (5.0)
|
||||||
|
data/state/instance.lock un solo bot per cartella dati (5.0)
|
||||||
data/state/paper_state.json il conto del simulatore (solo Paper)
|
data/state/paper_state.json il conto del simulatore (solo Paper)
|
||||||
data/models/*.json modelli (livelli 1-3) e stato del bandit
|
data/models/*.json modelli (livelli 1-3) e stato del bandit
|
||||||
knowledge/*.csv, *.md calibrazione, proposte, registri, insight settimanali
|
knowledge/*.csv, *.md calibrazione, proposte, registri, insight settimanali
|
||||||
reports/*.csv qualità dati, falsificazione, bonifica_YYYYMMDD (5.0)
|
reports/*.csv qualità dati, falsificazione, bonifica_YYYYMMDD, recupero_<run_id> (5.0)
|
||||||
logs/encelado.log log applicativo (;)
|
logs/encelado.log log applicativo (;)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -43,3 +43,9 @@
|
|||||||
| **PSI** | Population Stability Index: misura la deriva della distribuzione di una feature rispetto all'addestramento. |
|
| **PSI** | Population Stability Index: misura la deriva della distribuzione di una feature rispetto all'addestramento. |
|
||||||
| **HAR-RV** | Modello eterogeneo autoregressivo della varianza realizzata (medie a 1, 5, 22 giorni). |
|
| **HAR-RV** | Modello eterogeneo autoregressivo della varianza realizzata (medie a 1, 5, 22 giorni). |
|
||||||
| **Run id** | Identificatore della sessione del bot, scritto in ogni riga del ledger con l'hash di `strategy.json`. |
|
| **Run id** | Identificatore della sessione del bot, scritto in ogni riga del ledger con l'hash di `strategy.json`. |
|
||||||
|
| **Snapshot** | La fotografia immutabile dello stato del motore (conto, basket, contatori, contesto) che il server serializza in JSON e manda alla pagina una volta al secondo. La UI non ha altro. |
|
||||||
|
| **SSE** | Server-Sent Events: la rotta `/api/stream` che spinge uno snapshot al secondo al browser. |
|
||||||
|
| **Token web** | `ENCELADO_WEB_TOKEN`: la stringa che apre l'interfaccia; senza, il server ascolta solo su localhost. |
|
||||||
|
| **Valuta di visualizzazione** | La valuta con cui la pagina mostra gli importi (`ui.displayCurrency`); conversione solo in presentazione con il tasso delle quotazioni, ledger sempre in USD. |
|
||||||
|
| **Modalità campione** | `--sample`: il server serve uno snapshot finto e non avvia il motore; per lavorare sulle pagine e fotografarle. |
|
||||||
|
| **Navigation rail** | La colonna di navigazione a sinistra (Dashboard, Storico ordini, Log, Impostazioni), 80 o 256 px. |
|
||||||
|
|||||||
@@ -28,17 +28,21 @@ Aggiornato: 2026-09-23. Una voce per limite, con lo stato. Quando un limite vien
|
|||||||
## Bot
|
## Bot
|
||||||
|
|
||||||
- Le posizioni salvate in `baskets_state.json` da una modalità diversa non vengono riprese (si riparte dalla riconciliazione del conto).
|
- Le posizioni salvate in `baskets_state.json` da una modalità diversa non vengono riprese (si riparte dalla riconciliazione del conto).
|
||||||
- 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`).
|
|
||||||
- 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.
|
- Telegram non avvisa dei feed (calendario, RSS) in errore da più di un'ora: il provider dei feed non espone l'ora dell'ultimo successo; il riquadro «Telegram» della dashboard mostra solo lo stato del canale.
|
||||||
- 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 (si modificano nel file, come tutto il resto della strategia); Impostazioni ▸ Ricerca mostra lo stato dell'apprendimento in sola lettura.
|
||||||
- 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.
|
- Un movimento di cassa del demo che coincide con un'esecuzione ridotta dal server (il caso del 16-21/9) viene riconosciuto solo se il salto del saldo non è spiegato dalle chiusure: il matching è per differenza, non per una rotta dell'API.
|
||||||
|
|
||||||
|
## Container e interfaccia web
|
||||||
|
|
||||||
|
- Il server non ha TLS: l'interfaccia è pensata per la rete locale (Unraid) o dietro un reverse proxy che lo aggiunge. Senza `ENCELADO_WEB_TOKEN` ascolta solo su localhost del container.
|
||||||
|
- Il registro dei container di Gitea è in HTTP: chi fa `docker pull` deve dichiararlo `insecure-registry` (Docker Desktop e Unraid, vedi `docs/DOCKER.md`).
|
||||||
|
- Il template Unraid non è pubblicato in un repository di template della community: si installa dall'URL raw del file (`deploy/unraid/README.md`).
|
||||||
|
- La valuta di visualizzazione usa il tasso della quotazione più recente ricevuta dal polling; con il mercato chiuso il tasso invecchia e il tooltip lo dice.
|
||||||
|
|
||||||
## Codice
|
## Codice
|
||||||
|
|
||||||
- `BasketEngine` è spezzato in sei file parziali dalla 5.0; il file principale resta di ~900 righe (loop, decisioni, esecuzione).
|
- `BasketEngine` è spezzato in sei file parziali dalla 5.0; il file principale resta di ~900 righe (loop, decisioni, esecuzione).
|
||||||
- I test dell'interfaccia rendono le pagine in memoria (`UiRenderTests`, con `ENCELADO_RENDER_DIR`), non il comportamento della finestra vera (dialoghi, timer).
|
- I test dell'interfaccia coprono l'HTML incorporato, il JSON dello snapshot, il token e lo stream (`EmbeddedUiTests`, `WebHostTests`), non il comportamento della pagina nel browser (JavaScript, dialoghi): quello si controlla a occhio con `--sample` e con gli screenshot in `docs/img/`.
|
||||||
- Il test (l) copre i blocchi nel decisore, non la simulazione completa dell'equity stop nel motore live; quella è coperta dal backtest (`EquityStops` in `BacktestResult`) e dal ledger.
|
- Il test (l) copre i blocchi nel decisore, non la simulazione completa dell'equity stop nel motore live; quella è coperta dal backtest (`EquityStops` in `BacktestResult`) e dal ledger.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Apprendimento: livelli 0-3
|
# Apprendimento: livelli 0-3
|
||||||
|
|
||||||
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`.
|
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.
|
**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.
|
||||||
|
|
||||||
@@ -11,12 +11,12 @@ Aggiornato: 2026-09-23 (ADR-0006). Tutto è costruito da zero nel Core (`src/Enc
|
|||||||
| Componente | Runtime |
|
| Componente | Runtime |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Ledger, `orders.jsonl`, `baskets.csv` | scritti sempre: sono il dato |
|
| 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) |
|
| 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 |
|
| 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`) |
|
| 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 |
|
| MLP challenger (livello 2) | non addestrato |
|
||||||
| Bandit (livello 3) | riceve i premi e **propone** nel log; non applica |
|
| 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) |
|
| 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`.
|
**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`.
|
||||||
|
|
||||||
@@ -71,19 +71,26 @@ Promozione a campione: solo se batte la logistica di almeno 0,01 di AUC walk-for
|
|||||||
|
|
||||||
## Livello 3 — Bandit sui preset
|
## 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`). In Paper e Demo la proposta viene applicata a caldo (i basket aperti non vengono toccati) e scritta nel ledger come correzione; in Live mai.
|
`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à
|
## 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`.
|
`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 (PSI)
|
## Deriva
|
||||||
|
|
||||||
`Psi.Compute` confronta la distribuzione di ogni feature nelle ultime 50 decisioni con quella del dataset di addestramento (10 bin). Sopra 0,25 la feature è in deriva; con tre feature in deriva il meta-modello, se attivo, torna in ombra fino al ciclo successivo. Il ciclo settimanale scrive il PSI nel file degli insight.
|
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
|
## Ciclo settimanale e comando `learn`
|
||||||
|
|
||||||
`LearningState.RunCycle`, la domenica dopo le 10 UTC (o al primo avvio dopo sette giorni):
|
`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:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
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;
|
1. ricostruisce il dataset dal ledger;
|
||||||
2. valuta e riaddestra logistica (walk-forward) e MLP (fold purgati);
|
2. valuta e riaddestra logistica (walk-forward) e MLP (fold purgati);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Piano 5.0 — valutazione, ordine e stima
|
# Piano 5.0 — valutazione, ordine e stima
|
||||||
|
|
||||||
Scritto: 2026-09-23 (Fase 0 del prompt "Encelado 5.0"). Ogni punto del prompt è valutato come **fattibile**, **da chiarire** (con la domanda in `docs/QUESTIONS.md`) o **da rifiutare** (con il motivo). Le stime sono in sessioni di lavoro; una sessione è mezza giornata con verifica verde e commit.
|
Scritto: 2026-09-23 (Fase 0 del prompt "Encelado 5.0"). **Stato al 2026-09-23 sera: tutte le fasi 0-9 sono eseguite e committate; resta il rilascio 5.0.0 (decisione dell'utente).** Ogni punto del prompt è valutato come **fattibile**, **da chiarire** (con la domanda in `docs/QUESTIONS.md`) o **da rifiutare** (con il motivo). Le stime sono in sessioni di lavoro; una sessione è mezza giornata con verifica verde e commit.
|
||||||
|
|
||||||
## 0. Verifica della diagnosi preliminare
|
## 0. Verifica della diagnosi preliminare
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ Osservazioni:
|
|||||||
2. `GET api/v1/trading/info/demo/orders/381739181` (l'ordine EURUSD) risponde `referenceID: 00000000-0000-0000-0000-000000000000`: il server **non ha associato** l'`x-request-id` all'ordine. La ricerca `orders:lookup?referenceId=<x-request-id>` non poteva che rispondere 404. La ricerca per `orderId` risponde 200 con `status.id = 3 (Filled)` e la posizione.
|
2. `GET api/v1/trading/info/demo/orders/381739181` (l'ordine EURUSD) risponde `referenceID: 00000000-0000-0000-0000-000000000000`: il server **non ha associato** l'`x-request-id` all'ordine. La ricerca `orders:lookup?referenceId=<x-request-id>` non poteva che rispondere 404. La ricerca per `orderId` risponde 200 con `status.id = 3 (Filled)` e la posizione.
|
||||||
3. I primi due ordini hanno impegnato **109 228 USD di margine, cioè tutta l'equity** (1 092 281 USD di nozionale a leva 10): erano le gambe A di due basket diversi, ciascuna sizata fino al tetto `maxEffectiveLeverage = 10` calcolato sul nozionale del *basket* senza guardare il margine disponibile.
|
3. I primi due ordini hanno impegnato **109 228 USD di margine, cioè tutta l'equity** (1 092 281 USD di nozionale a leva 10): erano le gambe A di due basket diversi, ciascuna sizata fino al tetto `maxEffectiveLeverage = 10` calcolato sul nozionale del *basket* senza guardare il margine disponibile.
|
||||||
4. Dal terzo ordine in poi ogni esecuzione vale **esattamente 2 000 USD di margine**: `orders:lookup?orderId=382724150` mostra `requestedAmount 2000`, `frozenAmount 2000`, `requestType byUnits`, `requestedUnits 17420.945125`. Il bot manda unità arrotondate a due decimali; sei decimali significano che il server ha **ricalcolato le unità da un importo**: con il margine esaurito eToro ha ridotto l'ordine a un importo fisso invece di rifiutarlo. Le unità eseguite non erano quelle richieste.
|
4. Dal terzo ordine in poi ogni esecuzione vale **esattamente 2 000 USD di margine**: `orders:lookup?orderId=382724150` mostra `requestedAmount 2000`, `frozenAmount 2000`, `requestType byUnits`, `requestedUnits 17420.945125`. Il bot manda unità arrotondate a due decimali; sei decimali significano che il server ha **ricalcolato le unità da un importo**: con il margine esaurito eToro ha ridotto l'ordine a un importo fisso invece di rifiutarlo. Le unità eseguite non erano quelle richieste.
|
||||||
5. Il saldo è passato da 109 228 a 139 228 USD (+30 000) il 18/9: un accredito di fondi virtuali, non un'operazione (nessuna rotta di deposito o trasferimento nel client). In dashboard l'accredito ha alzato il picco di equity e falsato drawdown e P&L del giorno.
|
5. Il saldo è passato da 109 228 a 149 517 USD fra il 16 e il 22/9. Dal ledger ricevuto il 23/9 (D-36) non è un accredito unico: l'equity registrata a ogni riga sale in **circa ventuno scatti di ≈ +2 000 USD**, in coincidenza con le ventuno esecuzioni che il server aveva ridotto a 2 000 USD di margine. Il conto demo ha cioè accreditato fondi virtuali per ogni ordine ridotto: una regola del demo, non un'operazione del bot né un deposito dell'utente. In dashboard gli accrediti hanno alzato il picco di equity e falsato drawdown e P&L del giorno; dalla 5.0 sono `movimento_di_cassa` e non contano.
|
||||||
|
|
||||||
## Causa
|
## Causa
|
||||||
|
|
||||||
@@ -78,5 +78,5 @@ Aggravante: il **sizing non guardava il margine**. Con `orderLeverage = maxEffec
|
|||||||
|
|
||||||
## Che cosa resta aperto
|
## Che cosa resta aperto
|
||||||
|
|
||||||
- Il ledger delle sessioni 16-21/9 non è su questa macchina: le righe `rifiuto` con `unitsA` direbbero quante unità il bot aveva chiesto per gli ordini che il server ha ridotto a 2 000 USD (D-36).
|
- ~~Il ledger delle sessioni 16-21/9 non è su questa macchina~~ Ricevuto il 23/9 (D-36): 1 813 righe, 65 `segnale_ingresso` e 65 `rifiuto` «esito non ancora noto», preset AGGRESSIVE impostato dalla finestra, quattro run. Conferma la catena: lo stesso segnale ripetuto a ogni barra, ogni gamba A inviata e mai seguita, `unitsA` a due decimali contro le unità a sei decimali eseguite dal server. La chiusura in blocco del 21/9 13:26 UTC l'ha fatta l'utente dalla piattaforma (D-37): +289,05 USD realizzati, per fortuna.
|
||||||
- La regola con cui eToro riduce un ordine a margine esaurito non è documentata nell'OpenAPI; il bot non deve più trovarsi in quella condizione (Fase 2), ma la registra se accade (`units_eseguite ≠ units_richieste` in `orders.jsonl`).
|
- La regola con cui eToro riduce un ordine a margine esaurito non è documentata nell'OpenAPI; il bot non deve più trovarsi in quella condizione (Fase 2), ma la registra se accade (`units_eseguite ≠ units_richieste` in `orders.jsonl`).
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
# PROMPT PER CLAUDE CODE — Encelado 5.0: bonifica esecuzione, storico ordini, navigazione laterale, valuta, recupero dopo inattività, Telegram, apprendimento, kill-switch, margine, Material Design, Docker + Unraid
|
||||||
|
|
||||||
|
> Apri il repository `Encelado` e leggi, in quest'ordine: `CLAUDE.md`, `docs/STATE.md`, `docs/ARCHITECTURE.md`, `docs/RISK_RULES.md`, `docs/RUNBOOK.md`, `docs/KNOWN_ISSUES.md`, poi questo documento **per intero**. Questo prompt aggiorna la specifica precedente ("Correlation Baskets su eToro"): dove i due si contraddicono vale questo. Le regole di `CLAUDE.md` restano tutte (C#, nessun NuGet nell'applicazione, CSV `;` con `motivazione`, UTC, ledger append-only, mai un ordine reale senza flag e frase, un ADR per ogni scelta non ovvia, `docs/STATE.md` e `CHANGELOG.md` aggiornati a fine sessione).
|
||||||
|
>
|
||||||
|
> **Metodo di lavoro richiesto:** (1) rileggi questo prompt e produci `docs/PIANO_5.0.md` con la valutazione di ogni punto (fattibile / da chiarire / da rifiutare con motivo), l'ordine di esecuzione e la stima; (2) poni in blocco le domande di §15 e attendi; (3) implementa per fasi (§16), una fase per commit, con test verdi e documenti aggiornati; (4) **se hai un dubbio, chiedi**: `docs/QUESTIONS.md`, numerazione D-26 in poi, default prudente se non arriva risposta.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Diagnosi preliminare (già fatta leggendo codice e ledger: verificala, poi correggi)
|
||||||
|
|
||||||
|
Dal ledger `data/ledger/decisions.jsonl` (16-22 settembre): **65 `segnale_ingresso` e 65 `rifiuto`**, tutti con lo stesso testo: *«gamba A (…) non eseguita: Received → esito non ancora noto»*. Nessun basket è mai passato in `Open`, `data/ledger/baskets.csv` non esiste, `baskets_state.json` ha `baskets: []`, `learning_state.json` dice «ledger senza basket chiusi». Eppure il log di stato mostra `aperto −348,91` poi `−380,24` con `basket aperti 0/5`, e l'equity in dashboard (137 901) è sotto il saldo (139 228) mentre "P&L aperto" mostra 0,00.
|
||||||
|
|
||||||
|
Catena causale nel codice:
|
||||||
|
1. `EtoroBroker.OpenAsync` (`src/Encelado.Etoro/EtoroBroker.cs`): il `POST orders` risponde con un `orderId`, che viene letto e **mai più usato**. Il polling dell'esito passa solo da `LookupOrderAsync(clientRef)` = `GET orders:lookup?referenceId=<x-request-id>`, che restituisce **404 → `null`** per tutti i 10 s di `fillTimeoutSeconds`; il metodo torna `OrderOutcome("Received", "esito non ancora noto")` costruito a mano (riga `return last ?? new OrderOutcome(...)`).
|
||||||
|
2. `BasketExecutor.SendAsync` ripete lo stesso lookup per altri `legTimeoutSec` (5 s), sempre `null`, e dichiara la gamba A «non eseguita». Il basket viene rifiutato; **l'ordine A resta sul server e viene eseguito** (o lo è già stato): nasce una gamba orfana, senza copertura, con lo stop nativo ma senza TP né gestione.
|
||||||
|
3. `BasketEngine.ReconcileAsync` vede la posizione, non la trova in `_knownPositions`, la scrive una volta nel log come «non appartiene a nessun basket: la lascio com'è» e **non la tocca mai più**. Il contatore basket resta 0/5 perché conta gli slot con `Position`, non le posizioni del conto.
|
||||||
|
4. `KillAsync → CloseAllAsync` chiude solo gli slot con `Position` (zero): il kill-switch «riesce» senza chiudere nulla. È il punto 8 dell'utente.
|
||||||
|
|
||||||
|
Conseguenze da correggere: (a) l'esito ordine va cercato **per `orderId`** (endpoint da verificare sulla documentazione: `GET api/v2/trading/info/{demo/}orders/{orderId}` o equivalente) e, in subordine, per `referenceId`, e comunque **riconciliato con l'elenco posizioni** (strumento + verso + unità ± 1 % + orario ± 90 s); (b) un ordine dall'esito ignoto **non si abbandona mai**: entra in un registro persistente di ordini pendenti e viene seguito finché non è eseguito, rifiutato o annullato; (c) una gamba eseguita a basket abortito va **chiusa subito**, non lasciata; (d) le posizioni del conto che portano la firma del bot (clientRef/orderId nel registro, oppure strumento+unità+orario coerenti con un `segnale_ingresso` del ledger) sono **del bot**, non «sconosciute»; (e) il contatore in dashboard deve distinguere «basket del bot», «gambe orfane del bot», «posizioni esterne».
|
||||||
|
|
||||||
|
Sui **«depositi» nella cronologia eToro**: il codice non chiama alcun endpoint di deposito o trasferimento (verificato: rotte usate = instruments, rates, candles, eligibility, costs, pnl, orders, orders:lookup, market-close-orders, positions, history, me, balances). Il saldo è passato da 109 228 (16/9) a 139 228 (18/9): **+30 000 esatti**, cifra tonda tipica di un accredito di fondi virtuali sul portafoglio demo, non di un trade. Le voci «deposito» sono movimenti di cassa del conto virtuale (accrediti di eToro o richiesti dall'utente), non operazioni del bot. Nella nuova scheda Storico (§1) vanno mostrate a parte, come `movimento di cassa`, mai sommate al P&L. Se dopo l'analisi dello storico via API (`api/v1/trading/info/trade/{demo/}history` e `api/v1/balances`) risultasse un'altra origine, scrivila in `docs/KNOWN_ISSUES.md`.
|
||||||
|
|
||||||
|
Sul **margine**: il sizing attuale è solo `rischio % / distanza dello stop`. Un basket Aggressive ha prodotto 1 092 281 USD di nozionale (leva 10) su 109 228 USD di equity: **una sola** operazione impegnava tutto il margine del conto; con `maxBaskets = 5` il secondo basket non poteva esistere. È il punto 10 dell'utente e va risolto in §10.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Scheda «Storico ordini» (nuova)
|
||||||
|
|
||||||
|
Nuova sezione di navigazione `Storico` con tre viste:
|
||||||
|
- **Ordini** (ultimi N, filtro per basket/strumento/verso/esito/periodo): una riga per ordine inviato (apertura, aggiunta, chiusura, unwind), con `ts, basket_id, strumento, verso, unità, prezzo richiesto, prezzo eseguito, slippage (pip), stato (eseguito/rifiutato/annullato/pendente/ignoto→risolto), orderId, positionId, fee, motivazione`. Fonte: nuovo file `data/ledger/orders.jsonl` (append-only, §3) scritto dall'esecutore **a ogni invio e a ogni cambio di stato**, più riconciliazione con lo storico eToro.
|
||||||
|
- **Posizioni** (aperte e chiuse): una riga per posizione, con origine `basket | orfana-bot | esterna | movimento di cassa`, apertura/chiusura, P&L lordo/netto in USD e in valuta di visualizzazione, pip, durata, motivo di uscita, fee e overnight; le posizioni aperte in tempo reale con P&L corrente.
|
||||||
|
- **Profitti per periodo**: tabella `periodo; n_basket; n_posizioni; vinti; persi; win_rate; pnl_lordo; fee; pnl_netto; media_per_basket; max_dd; motivazione` per `oggi, ieri, 7 giorni, 30 giorni, mese corrente, mese precedente, anno, tutto, intervallo personalizzato`, con un piccolo grafico dell'equity (SVG generato lato server o canvas lato client, nessuna libreria). Fonte di verità del realizzato: lo **storico eToro** (`GetClosedTradesAsync`), riconciliato con `baskets.csv`; i movimenti di cassa restano fuori dal P&L e compaiono in una riga separata.
|
||||||
|
- Pulsante **Esporta CSV** (`;`, colonna `motivazione`) per ciascuna vista.
|
||||||
|
|
||||||
|
Modulo: `src/Encelado.Core/Baskets/History/` (`OrderRecord`, `PositionRecord`, `PeriodStats`, `HistoryBuilder` puro e testabile) + endpoint `GET /api/history/{orders|positions|periods}`.
|
||||||
|
|
||||||
|
## 2. Navigazione verticale a sinistra, espandibile e comprimibile
|
||||||
|
|
||||||
|
Sostituisci la barra in alto con le tre schede con una **Navigation rail** Material 3 a sinistra (80 px chiusa: icona + etichetta breve; 256-300 px aperta: icona + etichetta estesa), apribile/chiudibile dal pulsante menu in alto e con stato persistito (`ui.navExpanded`). Voci: `Dashboard`, `Storico`, `Log`, `Impostazioni`; in basso, nel rail, il chip ambiente (`PAPER/DEMO/LIVE`) e lo stato (`in esecuzione / fermo / bloccato`). Su schermi stretti (< 600 px) il rail diventa un drawer modale. La barra superiore resta sottile: titolo della pagina, orologio UTC + fuso scelto, `AVVIA/FERMA`, `KILL-SWITCH`.
|
||||||
|
|
||||||
|
## 3. Versione fuori dalla schermata principale
|
||||||
|
|
||||||
|
Rimuovi dalla dashboard `v4.0.0 · strategia <hash> · run <id>`. In `Impostazioni → Informazioni`: nome, **autore (Alberto Balbo)**, versione, data di build, commit, licenza, link alla documentazione. In `Impostazioni → Diagnostica` (sezione comprimibile): hash della strategia, `run_id`, percorsi dei file, versione .NET, uptime, quote API usate.
|
||||||
|
|
||||||
|
## 4. Valuta di visualizzazione selezionabile
|
||||||
|
|
||||||
|
`ui.displayCurrency` (default = valuta del conto, `USD`) selezionabile fra `USD, EUR, GBP, CHF, JPY, AUD, CAD, NZD` in Impostazioni e con un selettore rapido nella barra. Tassi: dalle quote eToro già in polling (EURUSD id 1, ecc.; per le coppie mancanti derivali via USD) aggiornati con le quote; in assenza di quota, l'ultimo tasso noto con etichetta «tasso di N minuti fa». Conversione **solo nel livello di presentazione** (snapshot → UI/Telegram/export); ledger, `baskets.csv`, `trials.csv` e tutte le decisioni restano in USD. Ogni importo convertito porta nel tooltip il valore in USD e il tasso usato. Formattazione con `CultureInfo` invariante e simbolo/decimali per valuta (JPY senza decimali).
|
||||||
|
|
||||||
|
## 5. Conteggio basket, posizioni aperte e «depositi» (correzione del bug di §0)
|
||||||
|
|
||||||
|
Implementa nell'ordine:
|
||||||
|
1. **`OrderTracker`** (`src/Encelado.Core/Baskets/OrderTracker.cs`) con registro persistente `data/state/pending_orders.json`: `clientRef, orderId, symbol, instrumentId, isBuy, units, basket_id, leg (A|B|add|close|unwind), sentUtc, lastStatus, resolvedUtc, positionId`. Ogni invio si registra **prima** della chiamata HTTP; ogni esito lo aggiorna; all'avvio il registro viene ricaricato e i pendenti risolti prima di qualunque decisione.
|
||||||
|
2. **`EtoroBroker.OpenAsync`**: dopo il `POST`, polling **per `orderId`** (endpoint da verificare, D-26) e, in parallelo, per `referenceId`; se entrambi rispondono 404, dopo 2 s interroga le posizioni e applica la regola di matching di §0(a); se il matching riesce l'ordine è `Filled` con quel `positionId`. Restituisce `Pending` solo se davvero non c'è traccia; **mai** un `"Received"` sintetico. Verifica la mappa degli stati (`filled = 3 or 5`, `rejected = 4,7,8,9,10`) sulla documentazione e scrivila in `docs/DATA_SOURCES.md`.
|
||||||
|
3. **`BasketExecutor`**: se la gamba A resta `Pending` allo scadere di `legTimeoutSec`, il basket passa in stato **`PendingA`** (nuovo) invece di essere rifiutato: nessun nuovo ingresso su quel basket, `OrderTracker` continua a seguire l'ordine; alla risoluzione: eseguito → invia B (se il segnale è ancora valido e il cost gate passa) oppure **chiudi A subito** (`leg_risk_unwind`) se non lo è; rifiutato → `Idle`. Stesso schema per B (`PendingB`). Registra tutto in `orders.jsonl` e in `decisions.jsonl` (`evento = pending`, `pending_risolto`).
|
||||||
|
4. **`ReconcileAsync`**: classifica ogni posizione del conto in `basket` (nota), `orfana-bot` (nel registro ordini o firma coerente con un `segnale_ingresso`/`rifiuto` del ledger entro ±90 s, stesso strumento, stesso verso, unità ± 1 %), `esterna`. Le orfane-bot vengono **adottate e chiuse** (§7 recupero decide se prima provare a ricomporre il basket con la gamba mancante: default **no**, chiudi). Le esterne restano intoccate salvo `risk.closeForeignOnKill`.
|
||||||
|
5. **Bonifica una tantum** all'avvio della 5.0 (`--bonifica` in headless / pulsante in Diagnostica): elenca le posizioni orfane-bot attualmente sul conto demo, le chiude una per una con conferma, scrive per ognuna una riga `baskets.csv` con `exit_reason = bonifica_orfana` e il P&L realizzato letto dallo storico, e produce `reports/bonifica_YYYYMMDD.csv`.
|
||||||
|
6. **Dashboard**: `Basket aperti a/b` resta, affiancato da `Gambe orfane: n` (rosso se > 0) e `Posizioni esterne: n`; `P&L aperto` mostra il valore **del conto** (`_account.UnrealizedPnl`) con sotto «di cui basket: x»; `Equity − Saldo` e `P&L aperto` devono coincidere a meno delle fee: se non coincidono per più di 60 s, banner giallo «posizioni non riconciliate».
|
||||||
|
7. **Movimenti di cassa**: `HistoryBuilder` legge saldi e storico, riconosce depositi/prelievi/accrediti virtuali e li espone in Storico come `movimento di cassa`, esclusi dal P&L e dal calcolo del drawdown (il picco di equity va **ricalcolato al netto dei movimenti di cassa**: oggi un accredito di 30 000 gonfia il picco e falsa il DD).
|
||||||
|
|
||||||
|
Test obbligatori: (m) lookup 404 persistente + posizione presente → `Filled` per matching; (n) A eseguita e B rifiutata → unwind entro 5 s; (o) A pendente oltre il timeout poi eseguita → `PendingA → Open` con B, oppure unwind se il segnale è decaduto; (p) posizione orfana con firma del bot → classificata `orfana-bot` e chiusa; (q) picco di equity insensibile a un deposito.
|
||||||
|
|
||||||
|
## 6. Recupero dopo inattività
|
||||||
|
|
||||||
|
Heartbeat `data/state/heartbeat.json` scritto ogni 30 s (`utc, run_id, mode, openBaskets`). All'avvio e a ogni iterazione del ciclo: `inattività = now − ultimo heartbeat`; se `> recovery.thresholdMinutes` (default 10; copre riavvii, sospensione del PC, aggiornamenti Windows, riavvio del container) entra la **procedura di recupero**, nell'ordine:
|
||||||
|
1. Blocca le nuove entrate; scrivi `evento = recupero_avviato` con la durata dell'inattività.
|
||||||
|
2. Risolvi il registro ordini pendenti (§5.1); riconcilia e classifica le posizioni (§5.4).
|
||||||
|
3. Riscalda le serie (ultime 1000 candele M15 via API) e ricalcola per ogni basket noto `z, ρ_W, ρ_20, HL, spread, costo, barsHeld` **includendo le barre trascorse durante l'inattività**.
|
||||||
|
4. Per ogni basket aperto applica le regole di uscita di §5.4 della specifica precedente come se fosse una valutazione ordinaria (`stop_z`, `stop_max_loss`, `time_stop` con le barre di inattività, `rho_break`, spread anomalo, fine settimana/blackout) e decide **`chiudi` o `tieni`**; per ogni gamba orfana-bot: `chiudi`; per le posizioni esterne: solo rapporto.
|
||||||
|
5. Esegui le chiusure decise; per i `tieni` ripristina TP/stop di basket e riprendi il monitoraggio tick-by-tick.
|
||||||
|
6. Se il mercato è chiuso (fine settimana) programma la valutazione alla riapertura e non fare nulla di irreversibile.
|
||||||
|
7. Scrivi `reports/recupero_<run_id>.csv` (una riga per posizione: `decisione; motivo; z; ρ; barsHeld; pnl; motivazione`), riga `recupero_concluso` nel ledger, notifica Telegram con il riepilogo; sblocca le entrate solo dopo `recovery.warmupMinutes` (default 15) di quote coerenti.
|
||||||
|
Parametri in `strategy.json` → `recovery`. Test: (r) simulazione di 6 h di inattività con un basket che nel frattempo ha superato `z_stop` → chiuso; con un basket ancora dentro le soglie → tenuto e riarmato; con una gamba orfana → chiusa.
|
||||||
|
|
||||||
|
## 7. Notifiche Telegram
|
||||||
|
|
||||||
|
Modulo `src/Encelado.Core/Notifications/TelegramNotifier.cs` (Bot API via `HttpClient`, `sendMessage` con `parse_mode=HTML`, coda in `Channel<T>`, 1 messaggio/s, retry con backoff, nessuna libreria) + `INotifier` con `NullNotifier`. Configurazione in `encelado.json` → `notifications.telegram`: `enabled, chatId, hourlyStatus, eventAlerts, dailySummaryUtcHour` (token e chatId anche da `TELEGRAM_BOT_TOKEN`/`TELEGRAM_CHAT_ID`; mai nel repo).
|
||||||
|
- **Stato ogni ora** (allo scoccare dell'ora UTC): attivo/fermo/bloccato, modalità e ambiente, uptime, equity/saldo/disponibile/margine usato (valuta di visualizzazione), P&L oggi e aperto, basket aperti (per ognuno: coppie, verso, z, pip, P&L, durata), gambe orfane, posizioni esterne, DD dal picco, prossimo evento ad alto impatto, stato API (latenza, quote usate), ultimo errore.
|
||||||
|
- **Eventi**: apertura basket, aggiunta, chiusura (con motivo e P&L), unwind, ordine pendente risolto, kill-switch, equity stop, perdita giornaliera raggiunta, recupero avviato/concluso, cambio preset, avvio/arresto del bot, errori API persistenti, scarto orologio, feed in errore per > 1 h.
|
||||||
|
- **Riepilogo giornaliero** alle `dailySummaryUtcHour` (default 21): P&L del giorno, n. basket, win rate, costi, DD.
|
||||||
|
- **Comandi in ingresso** (long polling `getUpdates`, solo dal `chatId` autorizzato, ogni comando loggato nel ledger): `/stato`, `/posizioni`, `/storico 7d`, `/pausa` (blocca entrate), `/riprendi`, `/chiudi <basket>`, `/kill CONFERMO` (kill-switch), `/reset <motivazione>`; qualunque altro testo → «comando non riconosciuto». Il polling usa un solo task e non compete con le quote eToro.
|
||||||
|
Test: (s) formattazione dei messaggi su snapshot fissi; (t) throttling e retry con `HttpMessageHandler` finto; (u) rifiuto dei comandi da chatId non autorizzato.
|
||||||
|
|
||||||
|
## 8. Valutazione del sistema di autoapprendimento (decisione richiesta)
|
||||||
|
|
||||||
|
Stato di fatto: `docs/STRATEGY.md` dà **verdetto negativo** (7,75 anni, nessuna configurazione con P&L netto positivo, segnale ≈ 2 pip contro 3 pip di costo, walk-forward Sharpe −0,91); il forward test in Demo ha **0 basket chiusi** in sei giorni per il bug di §0; il ciclo settimanale ha girato a vuoto («niente da addestrare»). Un meta-modello può solo filtrare i basket, non creare un edge che non c'è: con un segnale sotto i costi il massimo che può fare è ridurre le perdite. Serve inoltre un minimo di ~300 basket chiusi per attivarsi: con la frequenza osservata (poche entrate al giorno, quando entrano) sono mesi.
|
||||||
|
**Decisione da applicare (ADR-0006), salvo diversa risposta a D-30:**
|
||||||
|
- **Tenere**: il ledger (è il dato), il Livello 0 (calibrazione: tabelle per bucket di z/ρ/ora/evento, quasi gratis e utili a leggere il forward test), la previsione di volatilità (già usata dal decisore in modo deterministico), la logistica **in ombra** (costo nullo, produce `p_ML` nel ledger).
|
||||||
|
- **Disattivare a runtime** (codice conservato nel Core, `learning.enabled = false` di fabbrica, nessun task avviato): MLP challenger, bandit sui preset, ciclo settimanale. Spostare la loro esecuzione nello strumento `tools/Encelado.Backtest` (`learn` su un ledger esportato), così restano disponibili quando ci saranno dati.
|
||||||
|
- **UI**: rimuovere il riquadro «Apprendimento» dalla dashboard; in `Impostazioni → Ricerca` una sezione comprimibile con lo stato del modello in ombra (n. basket visti, AUC mobile, PSI) e il pulsante «esegui ciclo di apprendimento» manuale.
|
||||||
|
- **Criterio di riattivazione**, scritto in `docs/ML_AND_LEARNING.md`: ≥ 300 basket chiusi in Demo **e** P&L netto forward ≥ 0 sulla pre-registrazione; prima di allora qualunque «adattamento automatico» è rumore.
|
||||||
|
|
||||||
|
## 9. Kill-switch che chiude davvero e procedura di ripristino
|
||||||
|
|
||||||
|
**Kill-switch** (`KillAsync`), nuovo comportamento, idempotente e ripetibile:
|
||||||
|
1. `halt = true`, blocco immediato di ogni nuova entrata e aggiunta; riga `kill_switch_avviato`.
|
||||||
|
2. Annulla gli ordini pendenti nel registro (endpoint di cancellazione se esiste, D-27; altrimenti attende la risoluzione entro 30 s e chiude ciò che si è eseguito).
|
||||||
|
3. Chiude **tutte le posizioni del bot**: basket noti + gambe orfane-bot (§5.4), tre tentativi per gamba con backoff, verifica tra un tentativo e l'altro sull'elenco posizioni.
|
||||||
|
4. Posizioni **esterne**: chiuse solo se `risk.closeForeignOnKill = true` **oppure** se la finestra di conferma del kill-switch ha la spunta «chiudi anche le posizioni esterne» (default deselezionata, con l'elenco delle posizioni e il loro P&L).
|
||||||
|
5. **Verifica di piattezza**: rilegge le posizioni finché non restano solo esterne non incluse (timeout 120 s); se restano residui, banner rosso con l'elenco e stato `Halted-Residuo`; nessuna dichiarazione di «tutto chiuso» senza questa verifica.
|
||||||
|
6. Persiste `killSwitched`, scrive una riga `baskets.csv` per basket e una riga `orders.jsonl` per gamba, notifica Telegram (esito, residui).
|
||||||
|
Il file `STOP` e il comando `/kill CONFERMO` seguono lo stesso percorso.
|
||||||
|
|
||||||
|
**Ripristino** (`Impostazioni → Ripristino` e comando `reset <motivazione>`), procedura guidata in cinque passi mostrati uno per volta: (1) stato attuale: posizioni residue (con pulsante «chiudi ora»), ordini pendenti, ultimo errore; (2) rimozione del file `STOP` (automatica, con avviso se ricompare); (3) motivazione obbligatoria ≥ 10 caratteri → riga `correzione`; (4) riconciliazione completa + riscaldamento serie + ricalcolo del picco di equity (al netto dei movimenti di cassa); (5) ripartenza con entrate bloccate per `recovery.warmupMinutes`; notifica Telegram. Documenta in `docs/RUNBOOK.md` con una tabella «sintomo → passo». Test: (v) kill con 2 basket + 1 orfana + 1 esterna → chiude 5 gambe, lascia l'esterna, verifica la piattezza; (w) reset senza motivazione rifiutato; (x) reset con residui → stato `Halted-Residuo`.
|
||||||
|
|
||||||
|
## 10. Saldo disponibile e coerenza delle aperture
|
||||||
|
|
||||||
|
Leggi a ogni ciclo `AccountSnapshot` completo (`balance, equity, available, marginUsed, credit`) da `api/v1/balances` + `pnl` e mostra `Margine usato / disponibile` in dashboard. Nuove regole in `strategy.json → risk` (documentate in `docs/RISK_RULES.md`):
|
||||||
|
- `maxMarginUsePct` (default **40 %** dell'equity): margine totale impegnato dopo l'apertura ≤ soglia.
|
||||||
|
- `maxMarginPerBasketPct` (default **12 %**): margine delle due gambe di un basket ≤ soglia.
|
||||||
|
- `marginBufferPct` (default 25 %): `available ≥ (marginA + marginB) × 1,25` **prima** di inviare A, e ricontrollo di `available` (con la posizione A già aperta) prima di inviare B; se B non è più coperta, unwind di A.
|
||||||
|
- Sizing = **min**(sizing a rischio, sizing a margine); il ledger scrive quale vincolo ha deciso (`sizing_bound = risk | margin | min_exposure`) e il margine impegnato previsto; l'esposizione minima eToro (1000 USD per posizione) resta il floor: se il min non la raggiunge, niente ingresso (`reasonCodes: min_exposure`).
|
||||||
|
- Ordine dei basket quando più segnali arrivano insieme: per `|z|` decrescente, uno alla volta, ricalcolando `available` dopo ogni apertura.
|
||||||
|
- Margin call preventiva: se `equity / marginUsed < 1,5` blocca le entrate; `< 1,2` chiude il basket con il peggior P&L (riga `margin_guard`).
|
||||||
|
Test: (y) con equity 100 000 e Aggressive il nozionale per basket è limitato dal margine, non dal rischio; (z) il secondo basket viene rifiutato quando `available` non copre `1,25 ×` margine.
|
||||||
|
|
||||||
|
## 11. Interfaccia secondo Material Design 3 (web, senza librerie) — e conseguenza sul progetto WPF
|
||||||
|
|
||||||
|
Docker su Unraid è Linux: **WPF non può girare nel container**. Decisione proposta (**ADR-0007**, da confermare con D-28): l'interfaccia diventa una **web app servita dal bot stesso** (Kestrel, `Microsoft.AspNetCore.App` è un framework reference dell'SDK, non un pacchetto NuGet), usata sia su Windows (`http://localhost:8080`) sia nel container; il progetto WPF viene ritirato (resta nella storia git) e il vecchio eseguibile Windows diventa `Encelado.Server` avviabile come console/servizio. Nessun framework front-end, nessun font remoto: HTML + CSS + JavaScript vanilla incorporati come risorse (`EmbeddedResource`), aggiornamenti in tempo reale via **Server-Sent Events** (`/api/stream` con lo snapshot ogni secondo), comandi via `POST /api/commands/...`, autenticazione con **token locale** (`ENCELADO_WEB_TOKEN`, cookie HttpOnly; senza token il server ascolta solo su localhost).
|
||||||
|
|
||||||
|
Linee guida M3 da applicare (scrivile in `docs/UI_GUIDELINES.md` con i token scelti):
|
||||||
|
- **Colore**: ruoli M3 (`primary/on-primary/primary-container`, `surface`, `surface-container-lowest…highest`, `outline`, `error`), palette scura di default e chiara selezionabile, contrasto ≥ 4,5:1; una sola tinta d'accento; verde/rosso solo per P&L e stati.
|
||||||
|
- **Tipografia**: scala M3 (`display, headline, title, body, label`), `Roboto, "Segoe UI", system-ui` con fallback, cifre tabulari (`font-variant-numeric: tabular-nums`) per tutti i numeri.
|
||||||
|
- **Forma ed elevazione**: angoli 12-16 px, elevazione tramite superfici tonali (non ombre pesante), state layer su hover/focus/pressed.
|
||||||
|
- **Componenti** (in CSS/JS proprio): navigation rail + drawer (§2), top app bar, cards (elevated/filled/outlined), data table con densità compatta e intestazioni fisse, chips di stato (assist/filter), buttons (filled/tonal/outlined/text), switch, select/menu, dialog per conferme (kill-switch, chiusura basket, reset), snackbar per esiti, linear progress durante le operazioni, tooltip con formula/fonte su ogni numero, badge sui contatori.
|
||||||
|
- **Layout adattivo** per classi di finestra M3 (compact < 600, medium < 840, expanded ≥ 840): rail/drawer, griglia a 4/8/12 colonne, tabella dei basket che passa a cards sotto 600 px.
|
||||||
|
- **Accessibilità**: navigazione da tastiera completa, `aria-label`, focus visibile, preferenze `prefers-reduced-motion` e `prefers-color-scheme`.
|
||||||
|
|
||||||
|
Contenuti (stesse informazioni dello screenshot attuale, riorganizzate):
|
||||||
|
- **Dashboard**: KPI cards `Equity`, `P&L oggi`, `P&L aperto (conto) / di cui basket`, `Drawdown`, `Basket aperti a/b · orfane · esterne`, `Margine usato / disponibile`; tabella basket con `Basket (cross)`, `Stato`, `z`, `ρ`, `HL`, `Pips`, `TP`, `P&L`, `Costo`, `Prossimo evento`, pulsante `Chiudi` (la colonna `p ML` sparisce dalla tabella e va nel tooltip); cards `Prossimo evento`, `Collegamento eToro`, `Telegram` (ultimo invio, coda); striscia `Attività`.
|
||||||
|
- **Storico**: §1. **Log**: come oggi, con filtro e ricerca. **Impostazioni**: come oggi + `Notifiche`, `Valuta`, `Ripristino`, `Ricerca`, `Diagnostica`, `Informazioni`.
|
||||||
|
Test: (aa) `UiRenderTests` sostituiti da test sullo snapshot JSON dell'API e da un test che valida l'HTML incorporato (well-formed, nessun riferimento esterno, nessun `<script src=http…>`); (bb) SSE che emette uno snapshot al secondo con un client finto.
|
||||||
|
|
||||||
|
## 12. Docker (esecuzione fuori da Windows)
|
||||||
|
|
||||||
|
- Ristruttura i progetti: `Encelado.Core` (invariato), `Encelado.Etoro` (invariato), nuovo **`Encelado.Engine`** (net10.0, portabile: `Baskets/`, `Configuration/`, `Engine/`, `Logging/`, `Notifications/`, `History/` oggi dentro `Encelado.Bot`), nuovo **`Encelado.Server`** (`Microsoft.NET.Sdk.Web`, host Kestrel + UI incorporata + CLI headless con gli stessi comandi di oggi). `EtoroKeyStore` (DPAPI) diventa `IKeyStore` con tre implementazioni: DPAPI (solo Windows), file cifrato con AES da `ENCELADO_KEY_PASSPHRASE`, variabili d'ambiente (default nel container). `TreatWarningsAsErrors` e `Nullable` invariati; nessun NuGet.
|
||||||
|
- `Dockerfile` multi-stage: `mcr.microsoft.com/dotnet/sdk:10.0` per build/test/publish (`-c Release`, framework-dependent), `mcr.microsoft.com/dotnet/aspnet:10.0` per l'esecuzione; utente non root con `PUID/PGID` (default 99/100 per Unraid) applicati da un entrypoint che sistema i permessi di `/config` e `/data`; `TZ` rispettato dai log (il ledger resta UTC); `HEALTHCHECK` con `dotnet Encelado.Server.dll --health` (verifica API eToro raggiungibile, heartbeat recente, disco scrivibile); arresto pulito su `SIGTERM` (`closeOnShutdown` invariato, flush di ledger e stato, heartbeat finale); log su stdout **e** su file.
|
||||||
|
- Volumi: `/config` (`encelado.json`, `strategy.json`, `instruments.json`, chiavi cifrate), `/data` (`data/`, `knowledge/`, `reports/`, `results/`, `logs/`). Percorsi in `encelado.json` relativi a `/config`; su Windows restano `Documenti\Encelado`.
|
||||||
|
- Variabili: `ETORO_API_KEY`, `ETORO_USER_KEY`, `ETORO_ENVIRONMENT` (`demo|real`), `ENCELADO_EXECUTION_MODE` (`Paper|Demo|Live`), `ENCELADO_CONFIRM_LIVE` (deve valere `CONFERMO LIVE` per il Live), `ENCELADO_WEB_PORT` (8080), `ENCELADO_WEB_TOKEN`, `ENCELADO_DISPLAY_CURRENCY`, `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`, `TZ`, `PUID`, `PGID`.
|
||||||
|
- **Una sola istanza** per cartella dati: lock file `data/state/instance.lock` con PID e orario (risolve il problema noto).
|
||||||
|
- `docker-compose.yml`, `.dockerignore`, `docs/DOCKER.md` (build, avvio, aggiornamento, backup di `/config` e `/data`, rotazione chiavi, cosa succede ai basket aperti al riavvio → §6). La catena `build/Release.proj` acquisisce il target `Docker` (build + tag `encelado:<versione>`), senza toccare il resto (regola 5 di `CLAUDE.md`); l'installatore Windows resta per `Encelado.Server`.
|
||||||
|
|
||||||
|
## 13. Template XML per Unraid
|
||||||
|
|
||||||
|
Crea `deploy/unraid/encelado.xml` (formato Community Applications, `Container version="2"`), con: `Name` Encelado, `Repository` (registry e immagine da D-29, es. `gitea.<dominio>/alberto/encelado:latest`), `Registry`, `Network bridge`, `Privileged false`, `Support`, `Project`, `Overview` (descrizione in italiano), `Category` `Tools:`, `WebUI` `http://[IP]:[PORT:8080]/`, `Icon` (URL raw di `assets/encelado.png` 256×256, da aggiungere al repo insieme all'`.ico`), `ExtraParams --restart=unless-stopped`, `Config` per: porta 8080 (`Type=Port`), `/config` → `/mnt/user/appdata/encelado/config` e `/data` → `/mnt/user/appdata/encelado/data` (`Type=Path`, `Mode=rw`), tutte le variabili di §12 (`Type=Variable`, `Mask=true` su chiavi e token, `Display=always` per modalità/ambiente/valuta/TZ, `Display=advanced` per PUID/PGID/porta), con `Description` in italiano per ogni voce. Aggiungi `deploy/unraid/README.md` (come importare il template: «Add Container → Template repositories» o copia in `/boot/config/plugins/dockerMan/templates-user/`).
|
||||||
|
|
||||||
|
## 14. Documentazione-memoria per l'AI e base di conoscenza
|
||||||
|
|
||||||
|
Aggiorna: `CLAUDE.md` (nuova struttura dei progetti, comandi Docker/web, regole invariate), `docs/STATE.md`, `docs/ARCHITECTURE.md` (OrderTracker, stati `PendingA/PendingB/Halted-Residuo`, web/SSE, Engine/Server, notifiche, recupero), `docs/RISK_RULES.md` (margine, kill-switch, esterne), `docs/RUNBOOK.md` (ripristino guidato, Docker, Telegram, bonifica), `docs/LEDGER_SCHEMA.md` (`orders.jsonl`, `pending_orders.json`, `heartbeat.json`, nuovi `evento`), `docs/DATA_SOURCES.md` (rotte eToro verificate con stati ordine, Telegram), `docs/ML_AND_LEARNING.md` (§8), `docs/KNOWN_ISSUES.md` (rimuovi ciò che si chiude, aggiungi ciò che resta), `docs/UI_GUIDELINES.md`, `docs/DOCKER.md`, `docs/GLOSSARY.md`, `CHANGELOG.md`; ADR-0006 (apprendimento), ADR-0007 (UI web al posto di WPF), ADR-0008 (Docker/Engine/Server), ADR-0009 (OrderTracker e adozione delle orfane). Genera anche **`docs/POSTMORTEM_ordini_pendenti.md`**: cosa è successo, evidenze dal ledger, causa, correzione, come si verifica che non si ripeta.
|
||||||
|
|
||||||
|
Crea inoltre le **skill di progetto per Claude Code** in `.claude/skills/<nome>/SKILL.md` (frontmatter `name`, `description`), così che il lavoro futuro parta sempre dallo stesso posto:
|
||||||
|
- `encelado-start`: leggi `docs/STATE.md` e `CLAUDE.md`, esegui `dotnet build`, riassumi fase e problemi aperti, chiedi cosa fare.
|
||||||
|
- `encelado-verify`: `dotnet build`, `dotnet test`, verifica dell'HTML incorporato, `dotnet msbuild build/Release.proj -t:Verifica`, riepilogo esiti.
|
||||||
|
- `encelado-diagnose`: legge `decisions.jsonl`, `orders.jsonl`, `pending_orders.json`, `heartbeat.json` e l'elenco posizioni (se chiavi presenti) e produce un rapporto di riconciliazione in CSV `;`.
|
||||||
|
- `encelado-docs`: aggiorna `docs/STATE.md`, `CHANGELOG.md`, ADR e `docs/QUESTIONS.md` a fine sessione, poi propone il messaggio di commit.
|
||||||
|
- `encelado-release`: catena di rilascio + build Docker + aggiornamento del template Unraid.
|
||||||
|
- `encelado-ui`: applica i token e le regole di `docs/UI_GUIDELINES.md` quando si tocca la UI.
|
||||||
|
|
||||||
|
## 15. Domande da porre prima di iniziare (in blocco, con default)
|
||||||
|
|
||||||
|
| # | Domanda | Default |
|
||||||
|
|---|---|---|
|
||||||
|
| D-26 | Qual è l'endpoint corretto per leggere un ordine per `orderId` e quali sono gli stati (id/nome)? Fornisci la pagina della documentazione o l'esito di una chiamata reale. | verifica sul portale; se non esiste, riconciliazione per posizioni entro 2 s |
|
||||||
|
| D-27 | Esiste un endpoint di cancellazione ordini? | no: attesa risoluzione ≤ 30 s |
|
||||||
|
| D-28 | Confermi il ritiro del progetto WPF a favore della web UI servita dal bot (Windows e Docker)? | sì (ADR-0007) |
|
||||||
|
| D-29 | Registry per l'immagine (Gitea del tuo server, GHCR, Docker Hub) e URL raw per l'icona del template Unraid? | Gitea (`build/gitea.example.json`), icona nel repo |
|
||||||
|
| D-30 | Confermi la disattivazione a runtime di MLP, bandit e ciclo settimanale (§8)? | sì (ADR-0006) |
|
||||||
|
| D-31 | Chat ID e token Telegram verranno forniti via variabili d'ambiente? Vuoi i comandi in ingresso o solo le notifiche? | env; comandi attivi |
|
||||||
|
| D-32 | Le posizioni esterne vanno chiuse dal kill-switch di default? | no |
|
||||||
|
| D-33 | Valute da offrire nel selettore oltre a USD/EUR? | USD, EUR, GBP, CHF, JPY, AUD, CAD, NZD |
|
||||||
|
| D-34 | Soglie di margine (40 % totale, 12 % per basket, buffer 25 %) e soglia di inattività (10 min) vanno bene? | sì |
|
||||||
|
| D-35 | Le posizioni orfane oggi sul demo: le chiudo io con la bonifica (§5.5) o le chiudi tu a mano prima? | bonifica con conferma per posizione |
|
||||||
|
|
||||||
|
## 16. Fasi e criteri di accettazione
|
||||||
|
|
||||||
|
| Fase | Contenuto | Accettazione |
|
||||||
|
|---|---|---|
|
||||||
|
| 0 | `docs/PIANO_5.0.md`, domande D-26…D-35, `docs/POSTMORTEM_ordini_pendenti.md` | piano approvato, domande risposte o default registrati |
|
||||||
|
| 1 | §5 OrderTracker, lookup per orderId, stati Pending, adozione orfane, contatori, picco al netto dei movimenti di cassa, bonifica | test (m)-(q) verdi; 24 h di Demo con ordini eseguiti riconosciuti e **zero** orfane |
|
||||||
|
| 2 | §10 margine e coerenza aperture | test (y)(z); ledger con `sizing_bound` |
|
||||||
|
| 3 | §9 kill-switch reale + ripristino guidato | test (v)-(x); prova manuale in Demo con verifica di piattezza |
|
||||||
|
| 4 | §6 recupero dopo inattività, heartbeat, lock di istanza | test (r); prova: fermare 1 h con basket aperto |
|
||||||
|
| 5 | §7 Telegram (stato orario, eventi, riepilogo, comandi) | test (s)-(u); messaggi reali ricevuti |
|
||||||
|
| 6 | §12 Engine/Server, web host, SSE, API, key store portabile | avvio su Windows e in container con la stessa `/config` |
|
||||||
|
| 7 | §11 UI Material 3 + §2 navigazione + §3 versione + §4 valuta + §1 Storico + §8 pannello Ricerca | test (aa)(bb); revisione visiva con te; stesse informazioni dello screenshot presenti |
|
||||||
|
| 8 | §12 Dockerfile/compose/docs + §13 template Unraid | immagine avviata su Unraid con template importato, healthcheck verde |
|
||||||
|
| 9 | §14 documentazione, ADR, skill di progetto, `CHANGELOG`, versione **5.0.0**, rilascio | `encelado-verify` verde; commit a fine sessione |
|
||||||
|
|
||||||
|
**Regola finale: prima la correttezza dell'esecuzione (Fasi 1-3), poi tutto il resto. Finché un ordine dall'esito ignoto può restare sul conto senza padrone, nessuna nuova funzione va in Demo.**
|
||||||
@@ -44,19 +44,19 @@ Ogni domanda è numerata per fase. Quando l'utente non ha risposto, è stato app
|
|||||||
|
|
||||||
## Fase 0 della 5.0 — 2026-09-23
|
## Fase 0 della 5.0 — 2026-09-23
|
||||||
|
|
||||||
Domande del prompt «Encelado 5.0» (§15) più quelle emerse dalla verifica. D-26 e D-27 sono state verificate via API (sola lettura, collegamento MCP dell'utente) e non aspettano risposta. Le Fasi 1-3 usano i default senza attendere; le Fasi 6-9 aspettano D-28 e D-29.
|
Domande del prompt «Encelado 5.0» (§15) più quelle emerse dalla verifica. D-26 e D-27 sono state verificate via API (sola lettura, collegamento MCP dell'utente) e non aspettano risposta. Le Fasi 1-5 hanno usato i default; D-28, D-29, D-36 e D-37 hanno avuto risposta il 2026-09-23 e le Fasi 6-9 sono state eseguite.
|
||||||
|
|
||||||
| # | Domanda | Default proposto | Stato / risposta |
|
| # | Domanda | Default proposto | Stato / risposta |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| D-26 | Endpoint per leggere un ordine per `orderId` e mappa degli stati? | verifica sul portale | **Verificato via API il 2026-09-23**: `GET api/v2/trading/info/{demo/}orders:lookup?orderId=<id>` (stessa risposta del lookup per riferimento) e `GET api/v1/trading/info/{demo/}orders/{orderId}` (risposta v1: `statusID`, `positions[]`). Stati: 1 Received, 2 Placed, 3 Filled, 4 Rejected, 5 PartiallyFilled, 6 PendingCancel, 7 Canceled, 8 Expired, 9 CanceledPartiallyFilled, 10 RejectedPartiallyFilled, 11 WaitingForMarket, 12 PendingTriggeredRate. Eseguito = 3 o 5; rifiutato = 4, 7, 8, 9, 10; in corso = 1, 2, 6, 11, 12. **Il lookup per `referenceId` non funziona per gli ordini v2**: il server ha registrato `referenceID = 00000000-…` per gli ordini del bot (verificato su 381739181). Scritto in `docs/DATA_SOURCES.md`. |
|
| D-26 | Endpoint per leggere un ordine per `orderId` e mappa degli stati? | verifica sul portale | **Verificato via API il 2026-09-23**: `GET api/v2/trading/info/{demo/}orders:lookup?orderId=<id>` (stessa risposta del lookup per riferimento) e `GET api/v1/trading/info/{demo/}orders/{orderId}` (risposta v1: `statusID`, `positions[]`). Stati: 1 Received, 2 Placed, 3 Filled, 4 Rejected, 5 PartiallyFilled, 6 PendingCancel, 7 Canceled, 8 Expired, 9 CanceledPartiallyFilled, 10 RejectedPartiallyFilled, 11 WaitingForMarket, 12 PendingTriggeredRate. Eseguito = 3 o 5; rifiutato = 4, 7, 8, 9, 10; in corso = 1, 2, 6, 11, 12. **Il lookup per `referenceId` non funziona per gli ordini v2**: il server ha registrato `referenceID = 00000000-…` per gli ordini del bot (verificato su 381739181). Scritto in `docs/DATA_SOURCES.md`. |
|
||||||
| 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) | **Risposta dell'utente (2026-09-23)**: «Sì, confermo: il bot deve essere eseguito SOLO tramite container. Elimina tutta la parte della grafica Windows». Applicata: progetto WPF rimosso, `Encelado.Engine` + `Encelado.Server`, interfaccia web Material 3, chiavi in file cifrato al posto di DPAPI, VS Code con F5 sul server (ADR-0007, ADR-0008). |
|
||||||
| 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 | **Risposta dell'utente (2026-09-23)**: «Per il registro useremo Gitea per il momento. L'icona non si può pubblicare da qualche parte su Gitea?». Applicata: immagine `192.168.30.23:3000/alby96/encelado:<versione>` sul registro dei container di Gitea (stesso token della release); icona `assets/encelado.png` servita da Gitea come file raw del repository (`Icon` del template). |
|
||||||
| 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-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. |
|
||||||
| D-34 | Soglie di margine (40 % totale, 12 % per basket, buffer 25 %) e soglia di inattività (10 min)? | sì | **Default applicato** (Fasi 2 e 4), tutte in `strategy.json` e cambiabili dall'operatore. |
|
| D-34 | Soglie di margine (40 % totale, 12 % per basket, buffer 25 %) e soglia di inattività (10 min)? | sì | **Default applicato** (Fasi 2 e 4), tutte in `strategy.json` e cambiabili dall'operatore. |
|
||||||
| D-35 | Le orfane sul demo: bonifica con conferma o chiusura manuale? | bonifica con conferma | **Superata dai fatti**: il 2026-09-23 il conto demo ha 0 posizioni e 0 ordini; le 21 gambe del bot sono state chiuse in blocco il 21/9 alle 13:26 UTC (chiusura manuale, non del bot). La bonifica resta come comando (`--bonifica`, e pulsante in Diagnostica) per il futuro e non ha niente da chiudere oggi. |
|
| D-35 | Le orfane sul demo: bonifica con conferma o chiusura manuale? | bonifica con conferma | **Superata dai fatti**: il 2026-09-23 il conto demo ha 0 posizioni e 0 ordini; le 21 gambe del bot sono state chiuse in blocco il 21/9 alle 13:26 UTC (chiusura manuale, non del bot). La bonifica resta come comando (`--bonifica`, e pulsante in Diagnostica) per il futuro e non ha niente da chiudere oggi. |
|
||||||
| D-36 | Il ledger delle sessioni 16-21/9 (65 `segnale_ingresso`/`rifiuto`) non è su questa macchina: `Documenti\Encelado\data\ledger\decisions.jsonl` ha solo il 16/9. Su quale macchina o profilo ha girato il bot? Puoi copiare qui `decisions.jsonl`, `baskets_state.json` e `learning_state.json` di quella sessione? | procedere con le evidenze dell'API | **In attesa.** Servono per leggere `unitsA` nelle righe `rifiuto` e chiudere il punto 4 del post-mortem (ordini ridotti dal server a 2 000 USD di margine). |
|
| D-36 | Il ledger delle sessioni 16-21/9 (65 `segnale_ingresso`/`rifiuto`) non è su questa macchina: `Documenti\Encelado\data\ledger\decisions.jsonl` ha solo il 16/9. Su quale macchina o profilo ha girato il bot? Puoi copiare qui `decisions.jsonl`, `baskets_state.json` e `learning_state.json` di quella sessione? | procedere con le evidenze dell'API | **Risposta dell'utente (2026-09-23)**: allegato il bundle della cartella dati dell'installazione 4.0.0. Letto: 1 813 righe dal 16/9 15:55 al 22/9 08:01, 65 `segnale_ingresso` e 65 `rifiuto` con motivazione «esito non ancora noto», preset AGGRESSIVE impostato dalla finestra, quattro run; `baskets_state.json` con `killSwitched = true` («kill-switch dalla finestra») e picco 149 552. L'equity registrata sale da 109 228 a 149 517 USD in circa ventuno scatti di ≈ +2 000 USD, uno per ogni esecuzione ridotta dal server: gli «accrediti» erano crediti virtuali del demo per ordine, non depositi. Post-mortem aggiornato. |
|
||||||
| D-37 | Confermi che la chiusura in blocco del 21/9 alle 13:26 UTC l'hai fatta tu dalla piattaforma? | sì | **In attesa.** Se non sei stato tu, va capito chi ha chiuso (la 4.0.0 non poteva: contava gli slot). |
|
| D-37 | Confermi che la chiusura in blocco del 21/9 alle 13:26 UTC l'hai fatta tu dalla piattaforma? | sì | **Risposta dell'utente (2026-09-23)**: «Sì, l'ho fatta io». Chiuso. |
|
||||||
|
|||||||
+46
-43
@@ -1,15 +1,16 @@
|
|||||||
# Runbook
|
# Runbook
|
||||||
|
|
||||||
Aggiornato: 2026-09-23 (5.0, Fase 5). Come si avvia, si ferma, si sblocca e si ripara il bot. I file dell'operatore stanno in `Documenti\Encelado\`; le chiavi in `%LOCALAPPDATA%\Encelado\etoro.dat`.
|
Aggiornato: 2026-09-23 (5.0). Come si avvia, si ferma, si sblocca e si ripara il bot. Dalla 5.0 il bot gira nel container (`docs/DOCKER.md`, `deploy/unraid/`): i file dell'operatore stanno in `/config` (configurazione, chiavi cifrate, file `STOP`) e `/data` (ledger, stato, log). Fuori dal container, solo per lo sviluppo, valgono `Documenti\Encelado` e le stesse regole.
|
||||||
|
|
||||||
## Prima volta
|
## Prima volta
|
||||||
|
|
||||||
1. Avvia `Encelado.exe`. Vengono creati `Documenti\Encelado\encelado.json` (configurazione) e `strategy.json` (strategia) dalle copie di fabbrica.
|
1. Avvia il container (template Unraid o `docker run`, vedi `docs/DOCKER.md`) con `ENCELADO_WEB_TOKEN` impostato. Al primo avvio vengono creati `encelado.json` e `strategy.json` in `/config` dalle copie di fabbrica.
|
||||||
2. La finestra chiede le due chiavi di eToro Public API (`x-api-key` e `x-user-key`, dal portale sviluppatori; demo e reale hanno chiavi diverse). Le verifica con due letture (profilo e conto) e le salva cifrate con DPAPI. Da quel momento il bot parte da solo, anche in `--headless`.
|
2. Apri `http://<ip>:8080/`, inserisci il token (resta in un cookie per trenta giorni).
|
||||||
3. Controlla in **Impostazioni**: ambiente `demo`, modalità `Demo`, fuso orario.
|
3. Chiavi eToro Public API (`x-api-key` e `x-user-key`, dal portale sviluppatori; demo e reale hanno chiavi diverse): o nelle variabili `ETORO_API_KEY`/`ETORO_USER_KEY` del container (hanno la precedenza), oppure da **Impostazioni ▸ Chiavi eToro** con `ENCELADO_KEY_PASSPHRASE` impostata: il server le verifica con due letture (profilo e conto) e le salva cifrate in `/config/etoro.keys.enc`.
|
||||||
4. Premi **AVVIA**.
|
4. Controlla in **Impostazioni**: ambiente `demo`, modalità `Demo`, fuso orario (o `TZ` del container), valuta di visualizzazione.
|
||||||
|
5. Con `ENCELADO_AUTOSTART=1` (default) il motore è già partito; altrimenti premi **AVVIA**.
|
||||||
|
|
||||||
In alternativa alle chiavi salvate: variabili d'ambiente `ETORO_API_KEY` e `ETORO_USER_KEY` (hanno la precedenza), utili su un VPS. Per Telegram: `TELEGRAM_BOT_TOKEN` e `TELEGRAM_CHAT_ID` (vedi la sezione Telegram).
|
Per Telegram: `TELEGRAM_BOT_TOKEN` e `TELEGRAM_CHAT_ID` (vedi la sezione Telegram).
|
||||||
|
|
||||||
## Modalità
|
## Modalità
|
||||||
|
|
||||||
@@ -17,28 +18,24 @@ In alternativa alle chiavi salvate: variabili d'ambiente `ETORO_API_KEY` e `ETOR
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `Paper` | simulatore locale sopra le quotazioni reali (`data/state/paper_state.json`) | nessuna |
|
| `Paper` | simulatore locale sopra le quotazioni reali (`data/state/paper_state.json`) | nessuna |
|
||||||
| `Demo` (default) | conto demo eToro, denaro virtuale | nessuna |
|
| `Demo` (default) | conto demo eToro, denaro virtuale | nessuna |
|
||||||
| `Live` | conto reale | `run.allowLive = true` **e** la frase `CONFERMO LIVE` (finestra) o `--confirm-live "CONFERMO LIVE"` (headless) |
|
| `Live` | conto reale | `run.allowLive = true` **e** la frase `CONFERMO LIVE` (dialogo di AVVIA, oppure `ENCELADO_CONFIRM_LIVE` per l'avvio automatico, oppure `--confirm-live`) |
|
||||||
|
|
||||||
In ogni modalità il bot apre e chiude da solo (decisione D-20). Il badge in alto a destra dice sempre in che ambiente sei.
|
In ogni modalità il bot apre e chiude da solo (decisione D-20). Il chip in basso a sinistra del rail dice sempre in che ambiente sei (`PAPER` blu, `DEMO` giallo, `LIVE` rosso).
|
||||||
|
|
||||||
## Headless (VPS, test lunghi)
|
## Console e comandi
|
||||||
|
|
||||||
```powershell
|
Il processo stampa il log su stdout (`docker logs -f encelado`) e una riga di stato ogni `run.statusSeconds`. Con un terminale collegato (`docker attach`, o il processo lanciato a mano) accetta comandi da tastiera: `status`, `close <basket>`, `kill`, `residuo [id]`, `preset <nome>`, `bonifica`, `reset <motivazione>`, `stop`. Gli stessi comandi esistono nell'interfaccia (pulsanti e dialoghi) e in Telegram. Argomenti: `--no-autostart`, `--minutes N` (arresto automatico), `--bonifica`, `--confirm-live "CONFERMO LIVE"`, `--port N`, `--sample`, `--health`.
|
||||||
Encelado.exe --headless [--minutes 240] [--confirm-live "CONFERMO LIVE"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Log sulla console e nel file; una riga di stato ogni `run.statusSeconds`. Comandi da tastiera: `status`, `close <basket>`, `kill`, `preset <nome>`, `reset <motivazione>`, `bonifica`, `stop`. Argomento `--bonifica`: parte senza chiudere le orfane e le propone una per una. Variabile `ENCELADO_EXECUTION_MODE` per forzare la modalità senza toccare il file.
|
**Una sola istanza per cartella dati**, imposta dal lock `data/state/instance.lock`: un secondo processo sulla stessa cartella viene rifiutato con il pid del primo.
|
||||||
|
|
||||||
**Una sola istanza per cartella di lavoro**, imposta dal lock `data/state/instance.lock`: un secondo avvio (finestra o headless) sulla stessa cartella viene rifiutato con il pid del primo.
|
|
||||||
|
|
||||||
## Fermare
|
## Fermare
|
||||||
|
|
||||||
- **FERMA** nella finestra, `stop` in headless, Ctrl+C. I basket aperti **restano sul conto** con gli stop nativi (`run.closeOnShutdown = false`): nessuno applica TP e stop di basket finché il bot non riparte, che li riprende dallo stato salvato e dalla riconciliazione.
|
- **FERMA** nell'interfaccia, `stop` da console, `docker stop` (SIGTERM, 45 s di grazia), Ctrl+C. I basket aperti **restano sul conto** con gli stop nativi (`run.closeOnShutdown = false`): nessuno applica TP e stop di basket finché il bot non riparte, che li riprende dallo stato salvato e dalla riconciliazione (e, se è passato più di dieci minuti, con il recupero dopo inattività).
|
||||||
- Con `run.closeOnShutdown = true` la fermata chiude tutto a mercato.
|
- Con `run.closeOnShutdown = true` la fermata chiude tutto a mercato.
|
||||||
|
|
||||||
## Kill-switch
|
## Kill-switch
|
||||||
|
|
||||||
Tre modi: il pulsante **KILL-SWITCH** nella dashboard (chiede conferma e, se ci sono posizioni esterne, se chiudere anche quelle: default no), `kill` in headless (stessa domanda), oppure un file chiamato `STOP` nella cartella `Documenti\Encelado` (controllato ogni 5 s; utile da remoto). Dalla 5.0 la procedura è la stessa per i tre e **non dichiara niente chiuso senza averlo riletto dal conto**:
|
Tre modi: il pulsante **KILL-SWITCH** nella barra in alto (chiede conferma e, se ci sono posizioni esterne, se chiudere anche quelle: default no), `kill` da console o `/kill CONFERMO` da Telegram, oppure un file chiamato `STOP` nella cartella `/config` (controllato ogni 5 s; utile da remoto: `touch /mnt/user/appdata/encelado/config/STOP`). La procedura è la stessa per i tre e **non dichiara niente chiuso senza averlo riletto dal conto**:
|
||||||
|
|
||||||
1. blocco immediato di ogni nuova entrata, riga `kill_switch_avviato` nel ledger;
|
1. blocco immediato di ogni nuova entrata, riga `kill_switch_avviato` nel ledger;
|
||||||
2. annullamento degli ordini nel registro senza esito (`DELETE …/orders/{id}`), poi attesa della loro risoluzione fino a 30 s: un ordine eseguito nel frattempo diventa una posizione da chiudere;
|
2. annullamento degli ordini nel registro senza esito (`DELETE …/orders/{id}`), poi attesa della loro risoluzione fino a 30 s: un ordine eseguito nel frattempo diventa una posizione da chiudere;
|
||||||
@@ -46,7 +43,7 @@ Tre modi: il pulsante **KILL-SWITCH** nella dashboard (chiede conferma e, se ci
|
|||||||
4. verifica di piattezza: il conto viene riletto finché le posizioni del bot non sono sparite (fino a 120 s);
|
4. verifica di piattezza: il conto viene riletto finché le posizioni del bot non sono sparite (fino a 120 s);
|
||||||
5. riga `kill_switch_concluso` con l'esito, stato salvato, notifica.
|
5. riga `kill_switch_concluso` con l'esito, stato salvato, notifica.
|
||||||
|
|
||||||
Se qualcosa resta sul conto lo stato è **`Halted-Residuo`**: banner rosso con l'elenco, riga di stato con `RESIDUO`, il reset è rifiutato finché non è piatto. Per riprovare: **Sblocca…** (che prima offre di chiudere i residui), `residuo` in headless (`residuo <id>` per una sola posizione), oppure chiusura a mano su eToro; la riconciliazione se ne accorge. Il blocco resta finché non fai un **reset**.
|
Se qualcosa resta sul conto lo stato è **`Halted-Residuo`**: banner rosso con l'elenco, chip «bloccato · residuo», il reset è rifiutato finché non è piatto. Per riprovare: **Sblocca…** nel banner (il ripristino offre «Chiudi ora» sui residui), `residuo` da console (`residuo <id>` per una sola posizione), oppure chiusura a mano su eToro; la riconciliazione se ne accorge. Il blocco resta finché non fai un **reset**.
|
||||||
|
|
||||||
## Equity stop
|
## Equity stop
|
||||||
|
|
||||||
@@ -54,14 +51,14 @@ Quando l'equity (al netto dei movimenti di cassa) scende del 9 % dal picco (`equ
|
|||||||
|
|
||||||
## Ripristino (reset) in cinque passi
|
## Ripristino (reset) in cinque passi
|
||||||
|
|
||||||
**Sblocca…** nel banner, `reset <motivazione>` in headless. Dalla 5.0 il reset è una procedura, non un interruttore; ogni passo viene riportato nel messaggio di esito e nel ledger:
|
**Sblocca…** nel banner o **Avvia il ripristino…** in Impostazioni, `reset <motivazione>` da console, `/reset <motivazione>` da Telegram. Il reset è una procedura, non un interruttore; il dialogo mostra i cinque passi e l'esito di ciascuno, che finisce anche nel ledger:
|
||||||
|
|
||||||
| Passo | Che cosa fa | Se fallisce |
|
| Passo | Che cosa fa | Se fallisce |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 1. stato | riporta il motivo del blocco, i residui, gli ordini senza esito, le entrate bloccate | — |
|
| 1. stato | riporta il motivo del blocco, i residui, gli ordini senza esito, le entrate bloccate | — |
|
||||||
| 2. file STOP | lo rimuove da solo; se ricompare il kill-switch riparte | «file STOP non rimovibile»: permessi della cartella |
|
| 2. file STOP | lo rimuove da solo; se ricompare il kill-switch riparte | «file STOP non rimovibile»: permessi della cartella `/config` (PUID/PGID) |
|
||||||
| 3. motivazione | almeno dieci caratteri, scritta nel ledger come `correzione` | «motivazione mancante»: riscrivila |
|
| 3. motivazione | almeno dieci caratteri, scritta nel ledger come `correzione` | «motivazione mancante»: riscrivila |
|
||||||
| 4. riconciliazione | rilegge il conto, verifica che nessuna posizione del bot sia rimasta, riscalda le serie dall'API, riporta il picco di equity all'equity corrente (al netto dei movimenti di cassa) | «residui ancora sul conto» → resta `Halted-Residuo` (riga `reset_rifiutato`): chiudili con «chiudi ora» / `residuo` / a mano e ripeti |
|
| 4. riconciliazione | rilegge il conto, verifica che nessuna posizione del bot sia rimasta, riscalda le serie dall'API, riporta il picco di equity all'equity corrente (al netto dei movimenti di cassa) | «residui ancora sul conto» → resta `Halted-Residuo` (riga `reset_rifiutato`): chiudili con «Chiudi ora» / `residuo` / a mano e ripeti |
|
||||||
| 5. ripartenza | toglie il blocco; **entrate bloccate per 15 minuti** di riscaldamento (`riscaldamento dopo il reset` nel banner giallo), poi il bot riprende da solo | — |
|
| 5. ripartenza | toglie il blocco; **entrate bloccate per 15 minuti** di riscaldamento (`riscaldamento dopo il reset` nel banner giallo), poi il bot riprende da solo | — |
|
||||||
|
|
||||||
Sintomo → passo:
|
Sintomo → passo:
|
||||||
@@ -69,27 +66,25 @@ Sintomo → passo:
|
|||||||
| Sintomo | Passo da guardare |
|
| Sintomo | Passo da guardare |
|
||||||
|---|---|
|
|---|---|
|
||||||
| banner «KILL-SWITCH CON RESIDUO» | 4: chiudi i residui, poi ripeti il reset |
|
| banner «KILL-SWITCH CON RESIDUO» | 4: chiudi i residui, poi ripeti il reset |
|
||||||
| «rimuovi prima il file STOP» / «file STOP non rimovibile» | 2: la cartella `Documenti\Encelado` non è scrivibile o il file viene ricreato da un altro processo |
|
| «rimuovi prima il file STOP» / «file STOP non rimovibile» | 2: `/config` non è scrivibile dall'utente del container o il file viene ricreato da un altro processo |
|
||||||
| «motivazione mancante» | 3 |
|
| «motivazione mancante» | 3 |
|
||||||
| dopo il reset il bot non apre per un quarto d'ora | 5: è il riscaldamento voluto |
|
| dopo il reset il bot non apre per un quarto d'ora | 5: è il riscaldamento voluto |
|
||||||
| dopo il reset compare «posizioni non riconciliate» | 4 non è andato a buon fine sul conto: confronta `pending_orders.json` e le posizioni su eToro |
|
| dopo il reset compare «posizioni non riconciliate» | 4 non è andato a buon fine sul conto: confronta `pending_orders.json` e le posizioni su eToro (pagina Storico ▸ Posizioni) |
|
||||||
|
|
||||||
## Ordini senza esito
|
## Ordini senza esito
|
||||||
|
|
||||||
eToro lavora gli ordini in modo asincrono e a volte non risponde al lookup. Il bot non dimentica mai un ordine: ogni invio è scritto in `data/state/pending_orders.json` **prima** della chiamata, l'esito viene chiesto per `orderId` e, se il server non lo trova, ricostruito dalla posizione comparsa sul conto. Un basket con una gamba senza esito compare in dashboard come «attesa gamba A/B» e nel contatore «in attesa»: non manda altri ordini finché il registro non lo risolve. Alla risoluzione: gamba A eseguita e segnale ancora valido → parte la gamba B; segnale decaduto → la gamba A viene richiusa subito (`leg_risk_unwind`); rifiutata → il basket torna libero. Se un ordine resta senza esito per più di dieci minuti il log lo ripete ogni dieci minuti: guarda `orders.jsonl` (ultima riga di quel `client_ref`) e, se serve, la posizione su eToro; non c'è niente da fare a mano finché la gamba non compare sul conto, e quando compare il bot la gestisce. All'avvio i pendenti del run precedente vengono risolti prima di qualsiasi decisione.
|
eToro lavora gli ordini in modo asincrono e a volte non risponde al lookup. Il bot non dimentica mai un ordine: ogni invio è scritto in `data/state/pending_orders.json` **prima** della chiamata, l'esito viene chiesto per `orderId` e, se il server non lo trova, ricostruito dalla posizione comparsa sul conto. Un basket con una gamba senza esito compare in dashboard come «attesa gamba A/B» e nel contatore «in attesa»: non manda altri ordini finché il registro non lo risolve. Alla risoluzione: gamba A eseguita e segnale ancora valido → parte la gamba B; segnale decaduto → la gamba A viene richiusa subito (`leg_risk_unwind`); rifiutata → il basket torna libero. Se un ordine resta senza esito per più di dieci minuti il log lo ripete ogni dieci minuti: guarda Storico ▸ Ordini (esito `Pending`) e, se serve, la posizione su eToro; non c'è niente da fare a mano finché la gamba non compare sul conto, e quando compare il bot la gestisce. All'avvio i pendenti del run precedente vengono risolti prima di qualsiasi decisione.
|
||||||
|
|
||||||
## Riconciliazione
|
## Riconciliazione
|
||||||
|
|
||||||
Ogni 20 secondi il bot rilegge conto e posizioni e **classifica ogni posizione**: `basket` (gamba nota), `orfana-bot` (aperta dal bot ma senza basket: id nel registro degli ordini, oppure strumento, verso e orario coerenti con una decisione del ledger entro 90 s), `esterna` (tutto il resto). Una gamba di basket sparita dal conto (chiusa a mano, stop nativo) fa chiudere l'altra; un'orfana-bot viene **adottata e chiusa** (riga `orfana_adottata` e `orfana_chiusa` nel ledger, riga in `baskets.csv` con `exit_reason = orphan_closed`; dopo tre tentativi falliti le entrate si bloccano e il banner lo dice: chiudila a mano su eToro); un'esterna viene segnalata una volta, contata e mai toccata. Una chiusura incompleta dopo tre tentativi mette il basket in stato `Error` e blocca le nuove entrate finché non è risolta sul conto. Se il P&L aperto del conto e quello delle posizioni non tornano per più di un minuto compare «posizioni non riconciliate»: di solito è un'esecuzione in corso; se persiste, confronta `pending_orders.json` con le posizioni su eToro.
|
Ogni 20 secondi il bot rilegge conto e posizioni e **classifica ogni posizione**: `basket` (gamba nota), `orfana-bot` (aperta dal bot ma senza basket: id nel registro degli ordini, oppure strumento, verso e orario coerenti con una decisione del ledger entro 90 s), `esterna` (tutto il resto). Una gamba di basket sparita dal conto (chiusa a mano, stop nativo) fa chiudere l'altra; un'orfana-bot viene **adottata e chiusa** (riga `orfana_adottata` e `orfana_chiusa` nel ledger, riga in `baskets.csv` con `exit_reason = orphan_closed`; dopo tre tentativi falliti le entrate si bloccano e il banner lo dice: chiudila a mano su eToro); un'esterna viene segnalata una volta, contata e mai toccata. Una chiusura incompleta dopo tre tentativi mette il basket in stato `Error` e blocca le nuove entrate finché non è risolta sul conto. Se il P&L aperto del conto e quello delle posizioni non tornano per più di un minuto compare «posizioni non riconciliate»: di solito è un'esecuzione in corso; se persiste, confronta `pending_orders.json` con le posizioni su eToro.
|
||||||
|
|
||||||
Un deposito o un prelievo sul conto (anche l'accredito di fondi virtuali del demo) viene riconosciuto dal salto del saldo non spiegato dalle chiusure e scritto nel ledger come `movimento_di_cassa`: non è P&L, non muove il picco di equity né il drawdown.
|
Un deposito o un prelievo sul conto (anche l'accredito di fondi virtuali del demo) viene riconosciuto dal salto del saldo non spiegato dalle chiusure e scritto nel ledger come `movimento_di_cassa`: non è P&L, non muove il picco di equity né il drawdown; nello Storico compare come riga «movimento di cassa» e nei periodi è una colonna a parte.
|
||||||
|
|
||||||
## Recupero dopo inattività (riavvio, sospensione, container fermo)
|
## Recupero dopo inattività (riavvio, aggiornamento dell'immagine, container fermo)
|
||||||
|
|
||||||
Il bot scrive `data/state/heartbeat.json` ogni 30 s (`recovery.heartbeatSeconds`). All'avvio legge quello precedente e a ogni ciclo misura il tempo dall'ultimo giro: oltre `recovery.thresholdMinutes` (10) parte il **recupero**, nell'ordine: entrate bloccate e riga `recupero_avviato`; ordini senza esito risolti; riconciliazione con classificazione (le orfane si chiudono, le esterne si riportano); serie riscaldate dall'API con le barre perse e `barsHeld` di ogni basket aperto aumentato delle barre trascorse; ogni basket aperto rivalutato come a una chiusura di barra ordinaria (stop di z, perdita massima, time-stop con le barre di inattività, correlazione rotta, spread anomalo): **chiudi** o **tieni**; rapporto `reports/recupero_<run_id>.csv` (una riga per posizione: `posizione;basket;decisione;motivo;z;rho;barsHeld;pnl;motivazione`), riga `recupero_concluso`, notifica; entrate riaperte dopo `recovery.warmupMinutes` (15) di quotazioni. Se il mercato è chiuso (fine settimana) il recupero aspetta le prime quotazioni fresche e lo dice ogni dieci minuti: niente di irreversibile a mercato chiuso. Un recupero fallito lascia le entrate bloccate con il motivo: guarda il log e fai un reset.
|
Il bot scrive `data/state/heartbeat.json` ogni 30 s (`recovery.heartbeatSeconds`). All'avvio legge quello precedente e a ogni ciclo misura il tempo dall'ultimo giro: oltre `recovery.thresholdMinutes` (10) parte il **recupero**, nell'ordine: entrate bloccate e riga `recupero_avviato`; ordini senza esito risolti; riconciliazione con classificazione (le orfane si chiudono, le esterne si riportano); serie riscaldate dall'API con le barre perse e `barsHeld` di ogni basket aperto aumentato delle barre trascorse; ogni basket aperto rivalutato come a una chiusura di barra ordinaria (stop di z, perdita massima, time-stop con le barre di inattività, correlazione rotta, spread anomalo): **chiudi** o **tieni**; rapporto `reports/recupero_<run_id>.csv` (una riga per posizione: `posizione;basket;decisione;motivo;z;rho;barsHeld;pnl;motivazione`), riga `recupero_concluso`, notifica; entrate riaperte dopo `recovery.warmupMinutes` (15) di quotazioni. Se il mercato è chiuso (fine settimana) il recupero aspetta le prime quotazioni fresche e lo dice ogni dieci minuti: niente di irreversibile a mercato chiuso. Un recupero fallito lascia le entrate bloccate con il motivo: guarda il log e fai un reset.
|
||||||
|
|
||||||
**Una sola istanza** per cartella dati è imposta dal file `data/state/instance.lock`, tenuto aperto in esclusiva dal motore: un secondo avvio sulla stessa cartella si ferma con «un'altra istanza di Encelado sta usando questa cartella dati» e il pid della prima. Un crash rilascia il lock con il processo: non c'è mai un lock stantio da cancellare a mano.
|
|
||||||
|
|
||||||
## Telegram
|
## Telegram
|
||||||
|
|
||||||
Configurazione in `encelado.json` → `notifications.telegram` (`enabled`, `chatId`, `hourlyStatus`, `eventAlerts`, `dailySummaryUtcHour`, `commands`) e in Impostazioni → Notifiche. Il token del bot va **solo** nella variabile d'ambiente `TELEGRAM_BOT_TOKEN` (la chat in `TELEGRAM_CHAT_ID`, che ha la precedenza sul file). Per creare il bot: `@BotFather` → `/newbot` → token; per il chat id: scrivi al bot e leggi `chat.id` da `https://api.telegram.org/bot<token>/getUpdates`, oppure `@userinfobot`. Cosa arriva:
|
Configurazione in `encelado.json` → `notifications.telegram` (`enabled`, `chatId`, `hourlyStatus`, `eventAlerts`, `dailySummaryUtcHour`, `commands`) e in Impostazioni → Notifiche. Il token del bot va **solo** nella variabile d'ambiente `TELEGRAM_BOT_TOKEN` (la chat in `TELEGRAM_CHAT_ID`, che ha la precedenza sul file). Per creare il bot: `@BotFather` → `/newbot` → token; per il chat id: scrivi al bot e leggi `chat.id` da `https://api.telegram.org/bot<token>/getUpdates`, oppure `@userinfobot`. Cosa arriva:
|
||||||
@@ -98,37 +93,45 @@ Configurazione in `encelado.json` → `notifications.telegram` (`enabled`, `chat
|
|||||||
- **eventi**: avvio e arresto del bot, basket aperto e chiuso (con motivo e P&L), gamba in attesa, ordine pendente risolto, unwind, gamba orfana, kill-switch (con esito e residui), equity stop, perdita giornaliera, margin guard, recupero dopo inattività, reset, cambio preset, API in errore persistente, scarto orologio;
|
- **eventi**: avvio e arresto del bot, basket aperto e chiuso (con motivo e P&L), gamba in attesa, ordine pendente risolto, unwind, gamba orfana, kill-switch (con esito e residui), equity stop, perdita giornaliera, margin guard, recupero dopo inattività, reset, cambio preset, API in errore persistente, scarto orologio;
|
||||||
- **riepilogo giornaliero** all'ora impostata (21 UTC): basket chiusi, vinti, lordo, costi, netto, drawdown, motivi di uscita.
|
- **riepilogo giornaliero** all'ora impostata (21 UTC): basket chiusi, vinti, lordo, costi, netto, drawdown, motivi di uscita.
|
||||||
|
|
||||||
Comandi accettati **solo dalla chat autorizzata** (ogni altro mittente viene ignorato e annotato nel log; ogni comando eseguito finisce nel ledger come `comando`): `/stato`, `/posizioni`, `/storico 7d` (anche `1d`, `30d`), `/pausa` (blocca le entrate, le uscite restano attive), `/riprendi`, `/chiudi <basket>`, `/kill CONFERMO` (senza la parola non fa niente), `/reset <motivazione>` (almeno dieci caratteri); tutto il resto risponde «comando non riconosciuto». Il polling usa un solo task con `getUpdates` e non tocca le quote di eToro; gli invii sono al massimo uno al secondo con tre tentativi (2, 5, 15 s; su 429 rispetta `retry_after`). La riga «quote» della dashboard e la riga di stato del log riportano lo stato del canale (ultimo invio, coda, falliti, ultimo errore).
|
Comandi accettati **solo dalla chat autorizzata** (ogni altro mittente viene ignorato e annotato nel log; ogni comando eseguito finisce nel ledger come `comando`): `/stato`, `/posizioni`, `/storico 7d` (anche `1d`, `30d`), `/pausa` (blocca le entrate, le uscite restano attive), `/riprendi`, `/chiudi <basket>`, `/kill CONFERMO` (senza la parola non fa niente), `/reset <motivazione>` (almeno dieci caratteri); tutto il resto risponde «comando non riconosciuto». Il polling usa un solo task con `getUpdates` e non tocca le quote di eToro; gli invii sono al massimo uno al secondo con tre tentativi (2, 5, 15 s; su 429 rispetta `retry_after`). La card «Telegram» della dashboard e la riga di stato del log riportano lo stato del canale (ultimo invio, coda, falliti, ultimo errore).
|
||||||
|
|
||||||
## Bonifica delle gambe orfane
|
## Bonifica delle gambe orfane
|
||||||
|
|
||||||
Una tantum, dopo un'anomalia: avvia il bot con `--headless --bonifica`. Il motore parte **senza** chiudere le orfane da solo, le elenca con P&L e motivo della classificazione insieme alle posizioni esterne, e per ogni orfana chiede `chiudere? [s/N]`. Ogni chiusura confermata scrive una riga in `baskets.csv` (`exit_reason = bonifica_orfana`, P&L dallo storico) e in `reports/bonifica_YYYYMMDD.csv`. Alla fine il bot torna a chiudere le orfane da solo. Lo stesso comando si lancia dalla console con `bonifica`. Al 2026-09-23 il conto demo è piatto: non c'è niente da bonificare.
|
Una tantum, dopo un'anomalia: **Impostazioni ▸ Diagnostica ▸ Bonifica: elenca le orfane** mostra ogni posizione del conto con origine, P&L e motivo della classificazione e offre **Chiudi** su ciascuna orfana-bot; oppure `--bonifica` all'avvio (il motore parte senza chiudere le orfane da solo e chiede `chiudere? [s/N]` da console) o `bonifica` da console. Ogni chiusura confermata scrive una riga in `baskets.csv` (`exit_reason = bonifica_orfana`, P&L dallo storico) e in `reports/bonifica_YYYYMMDD.csv`. Alla fine il bot torna a chiudere le orfane da solo. Al 2026-09-23 il conto demo è piatto: non c'è niente da bonificare.
|
||||||
|
|
||||||
## Errori API
|
## Errori API
|
||||||
|
|
||||||
| Sintomo | Cosa fa il bot | Cosa fare |
|
| Sintomo | Cosa fa il bot | Cosa fare |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 401/403 | avvio fallito, "eToro ha rifiutato le chiavi" | rigenera le chiavi sul portale, reinseriscile da Impostazioni |
|
| 401/403 | avvio fallito, "eToro ha rifiutato le chiavi" | rigenera le chiavi sul portale, reinseriscile da Impostazioni ▸ Chiavi eToro o nelle variabili del container |
|
||||||
| 429 | rispetta `Retry-After`, rallenta | niente; se persiste alza `run.pollSeconds` |
|
| 429 | rispetta `Retry-After`, rallenta | niente; se persiste alza `run.pollSeconds` |
|
||||||
| 5 letture consecutive fallite | banner "entrate bloccate", uscite attive | aspetta; controlla rete e stato di eToro |
|
| 5 letture consecutive fallite | banner "entrate bloccate", uscite attive | aspetta; controlla rete e stato di eToro |
|
||||||
| scarto orologio > 5 s | banner, entrate bloccate | sincronizza l'ora di Windows |
|
| scarto orologio > 5 s | banner, entrate bloccate | sincronizza l'ora dell'host (il container eredita l'orologio) |
|
||||||
| quotazioni più vecchie di 15 s | niente nuove entrate | come sopra |
|
| quotazioni più vecchie di 15 s | niente nuove entrate | come sopra |
|
||||||
|
| `/api/health` rosso (`docker inspect`) | il container viene riavviato da `--restart` se il processo muore; un healthcheck rosso con processo vivo dice cosa manca (heartbeat vecchio, disco non scrivibile) | `docker logs`, poi `docker restart` |
|
||||||
|
|
||||||
## Feed
|
## Feed
|
||||||
|
|
||||||
Calendario e notizie sono in cache su disco (`data/cache`) e vengono riletti ogni 10 minuti con attese crescenti dopo un errore. Un feed che non risponde non ferma il bot: senza calendario non c'è blackout, senza notizie il sentiment è 0, e il log lo dice. Google News blocca via `robots.txt` le ricerche RSS: quelle fonti vivono solo di cache (vedi `docs/DATA_SOURCES.md`).
|
Calendario e notizie sono in cache su disco (`data/cache`) e vengono riletti ogni 10 minuti con attese crescenti dopo un errore. Un feed che non risponde non ferma il bot: senza calendario non c'è blackout, senza notizie il sentiment è 0, e il log lo dice. Google News blocca via `robots.txt` le ricerche RSS: quelle fonti vivono solo di cache (vedi `docs/DATA_SOURCES.md`).
|
||||||
|
|
||||||
|
## Storico
|
||||||
|
|
||||||
|
**Storico ordini** nell'interfaccia: *Ordini* da `orders.jsonl` (unità richieste ed eseguite — se differiscono il server ha ridotto l'ordine —, prezzi, slippage, stato, esito, id di ordine e posizione), *Posizioni* dallo storico eToro classificate per origine, *Profitti per periodo* con la curva dell'equity realizzata. Ogni vista si esporta in CSV (`;`, colonna `motivazione`). Senza chiavi la pagina mostra solo i dati del ledger e lo dice.
|
||||||
|
|
||||||
## File utili
|
## File utili
|
||||||
|
|
||||||
| Cosa | Dove |
|
| Cosa | Dove (container) |
|
||||||
|---|---|
|
|---|---|
|
||||||
| log | `Documenti\Encelado\logs\encelado.log` (CSV `;`) |
|
| log | `/data/logs/encelado.log` (CSV `;`) e `docker logs` |
|
||||||
| ledger | `data\ledger\decisions.jsonl`, `data\ledger\baskets.csv`, `data\ledger\orders.jsonl` |
|
| ledger | `/data/data/ledger/decisions.jsonl`, `baskets.csv`, `orders.jsonl` |
|
||||||
| stato | `data\state\baskets_state.json` (ripreso all'avvio), `data\state\pending_orders.json` (registro degli ordini), `data\state\heartbeat.json`, `data\state\instance.lock` |
|
| stato | `/data/data/state/baskets_state.json` (ripreso all'avvio), `pending_orders.json` (registro degli ordini), `heartbeat.json`, `instance.lock` |
|
||||||
| barre | `data\market\candles_<SYMBOL>_M15.csv` |
|
| barre | `/data/data/market/candles_<SYMBOL>_M15.csv` |
|
||||||
| modelli | `data\models\` |
|
| modelli | `/data/data/models/` |
|
||||||
| conoscenza | `knowledge\` |
|
| conoscenza | `/data/knowledge/` |
|
||||||
| strumenti | `instruments.json` accanto alla configurazione |
|
| rapporti | `/data/reports/` (qualità dati, bonifica, recupero) |
|
||||||
|
| configurazione, strumenti, chiavi, STOP | `/config/encelado.json`, `strategy.json`, `instruments.json`, `etoro.keys.enc`, `STOP` |
|
||||||
|
|
||||||
|
Fuori dal container gli stessi percorsi stanno sotto `Documenti\Encelado` (`data\…`, `knowledge\…`, `logs\…`).
|
||||||
|
|
||||||
## Checklist prima del Live (§9.4 della specifica)
|
## Checklist prima del Live (§9.4 della specifica)
|
||||||
|
|
||||||
@@ -138,9 +141,9 @@ Tutte vere, altrimenti no:
|
|||||||
- [ ] il P&L netto del forward test è positivo con PSR ≥ 0,95 sulla metrica pre-registrata in `knowledge/preregistrazione.csv`;
|
- [ ] il P&L netto del forward test è positivo con PSR ≥ 0,95 sulla metrica pre-registrata in `knowledge/preregistrazione.csv`;
|
||||||
- [ ] nessun test di falsificazione contraddice il risultato (oggi `reports/falsificazione.csv` dice il contrario: vedi `docs/STRATEGY.md`);
|
- [ ] nessun test di falsificazione contraddice il risultato (oggi `reports/falsificazione.csv` dice il contrario: vedi `docs/STRATEGY.md`);
|
||||||
- [ ] `run.allowLive = true`, ambiente `real`, chiavi del reale inserite e verificate;
|
- [ ] `run.allowLive = true`, ambiente `real`, chiavi del reale inserite e verificate;
|
||||||
- [ ] conto reale capiente rispetto a `riskPerBasketPct` e all'esposizione minima di 1000 USD per gamba (con 193 USD non lo è);
|
- [ ] conto reale capiente rispetto a `riskPerBasketPct` e all'esposizione minima di 1000 USD per gamba (con 224,90 USD non lo è);
|
||||||
- [ ] la frase `CONFERMO LIVE` scritta all'avvio.
|
- [ ] la frase `CONFERMO LIVE` scritta all'avvio (dialogo o `ENCELADO_CONFIRM_LIVE`).
|
||||||
|
|
||||||
## Aggiornare
|
## Aggiornare
|
||||||
|
|
||||||
L'installatore conserva `Documenti\Encelado` e le chiavi. Se dopo un aggiornamento il log segnala "chiavi di una versione precedente", da Impostazioni → **Ripristina i valori predefiniti** (backup automatico con la data accanto al file).
|
`docker pull` dell'immagine nuova e riavvio del container (su Unraid: *Controlla aggiornamenti*). `/config` e `/data` restano; il riavvio è un'inattività come le altre e passa dal recupero. Se dopo un aggiornamento il log segnala una configurazione di una versione precedente, da Impostazioni → **Ripristina i valori predefiniti** (backup automatico con la data accanto al file) e poi rimetti i valori tuoi.
|
||||||
|
|||||||
+14
-16
@@ -1,32 +1,30 @@
|
|||||||
# Stato del lavoro
|
# Stato del lavoro
|
||||||
|
|
||||||
Aggiornato: 2026-09-23 (sessione 5.0, Fasi 0-5 concluse).
|
Aggiornato: 2026-09-23 (sessione 5.0, Fasi 6-9 concluse: il piano 5.0 è completo).
|
||||||
|
|
||||||
## Fase in corso
|
## Fase in corso
|
||||||
|
|
||||||
**Piano 5.0, in attesa delle risposte D-28 e D-29 per le Fasi 6-9** (`docs/PIANO_5.0.md`). Le Fasi 0-5 sono committate: post-mortem, registro degli ordini, stati `PendingA`/`PendingB`, classificazione e chiusura delle orfane, picco al netto dei movimenti di cassa, bonifica, limiti di margine, kill-switch con verifica di piattezza e reset in cinque passi. La «correttezza dell'esecuzione» richiesta dalla regola finale del piano è coperta: il Demo **può ripartire** per le 24 ore di verifica (contatore «orfane» a 0, `orders.jsonl` senza `Unknown` irrisolti, `sizing_bound = margin` sui primi ingressi), e la prova manuale del kill-switch con verifica di piattezza va fatta in quella sessione.
|
**Piano 5.0 concluso** (`docs/PIANO_5.0.md`): tutte le fasi 0-9 sono fatte. Il bot è `Encelado.Server` (Kestrel + motore + interfaccia web incorporata) e gira nel container (ADR-0007, ADR-0008); la finestra WPF non esiste più (D-28). Versione di sviluppo 5.0.0 in `Directory.Build.props`; **il rilascio 5.0.0 non è ancora stato fatto** (tag, immagine sul registro di Gitea, release): lo decide l'utente con `dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=5.0.0` dopo il push del ramo.
|
||||||
|
|
||||||
## Fatto nell'ultima sessione (2026-09-23)
|
## Fatto nell'ultima sessione (2026-09-23, Fasi 6-9)
|
||||||
|
|
||||||
- **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 6 — Engine + Server**: `src/Encelado.Bot` → `src/Encelado.Engine` (libreria, senza UI); `src/Encelado.Server` (Sdk.Web, nessun NuGet) con `WebHost` (token in cookie HttpOnly o Bearer, senza token solo localhost), API JSON scritta a mano, SSE `/api/stream` a uno snapshot al secondo, `LogBuffer`, `HistoryService`; chiavi da variabili o file cifrato `etoro.keys.enc` (AES-256-GCM, `ENCELADO_KEY_PASSPHRASE`) al posto di DPAPI; `AppPaths` con `/config` e `/data`; `HeadlessRunner` ridotto a console (stato, comandi, bonifica); `--sample`, `--health`, `--no-autostart`, `ENCELADO_AUTOSTART`, `ENCELADO_CONFIRM_LIVE`.
|
||||||
- **§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 7 — interfaccia web Material 3** (`Web/wwwroot`, vanilla): navigation rail 80/256 px (drawer sotto 600 px), barra con orologi UTC e fuso, selettore della valuta (USD, EUR, GBP, CHF, JPY, AUD, CAD, NZD con tasso dalle quotazioni e tooltip in USD), AVVIA/FERMA, KILL-SWITCH con «chiudi anche le esterne»; dashboard con sei KPI (incluso il margine), tabella dei basket (cards da telefono), card evento/eToro/Telegram, attività, banner; **Storico ordini** (ordini, posizioni classificate, profitti per periodo con curva SVG, CSV); Log con filtri; Impostazioni con configurazione validata, chiavi eToro, ripristino in cinque passi, Ricerca, Diagnostica (con bonifica), Informazioni (versione, build, commit da `AssemblyMetadata`); temi scuro/chiaro. `HistoryBuilder` nel Core; `SnapshotJson`, `SampleSnapshot`, `SettingsService` nell'Engine; comando `learn` nello strumento (`Learn.cs`); `ui.displayCurrency/theme/navExpanded` e coppie di conversione nel polling.
|
||||||
- **Fase 5**: `TelegramNotifier`, stato orario, eventi, riepilogo giornaliero, comandi dalla chat autorizzata, riga `comando` nel ledger; test (s)-(u); 219 verdi.
|
- **Fase 8 — Docker e Unraid**: `Dockerfile` multi-stage (build, **test**, publish, runtime `aspnet:10.0` + gosu + tzdata), `deploy/docker/entrypoint.sh` (TZ, PUID/PGID), `docker-compose.yml`, `.dockerignore`, `HEALTHCHECK --health`; `deploy/unraid/encelado.xml` (Container v2, icona `assets/encelado.png` raw da Gitea, D-29) e README; `build/Release.proj` senza Inno Setup: `Pubblica` framework-dependent, `Docker`, `Pacchetto` (zip + template + tag), `Rilascia` (push dell'immagine sul registro di Gitea + release); VS Code con F5 sul server (`Encelado (server)`, `Encelado (campione)`, backtest) e attività Docker. Immagine `encelado:dev` costruita in locale con i test verdi nello stadio Linux.
|
||||||
- **Fase 4**: heartbeat, lock di istanza, sezione `recovery`, procedura di recupero dopo inattività con rapporto `recupero_<run_id>.csv`; test (r); 211 verdi.
|
- **Fase 9 — documenti e pulizia**: ADR-0007, ADR-0008, `docs/DOCKER.md`, `docs/UI_GUIDELINES.md`, `README.md` con gli screenshot (`docs/img/`), `CLAUDE.md`, ARCHITECTURE, RUNBOOK, KNOWN_ISSUES, GLOSSARY, DATA_SOURCES, ML_AND_LEARNING, QUESTIONS (D-28, D-29, D-36, D-37 risposte), POSTMORTEM (gli «accrediti» erano crediti demo per ogni ordine ridotto), skill aggiornate, `build/README.md`. **Codice morto rimosso**: progetto WPF, DPAPI, test WPF, `UiRenderTests`, ~45 membri mai usati (statistiche di ricerca, `Psi`, `EmptyContextProvider`, `EnvironmentKeyStore`, display helper dello snapshot, `INotifyPropertyChanged` in `SettingField`, …). Test nuovi: `EmbeddedUiTests`, `WebHostTests`, `HistoryBuilderTests`, `KeyStoreTests`; suite a **210 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 2**: sezione `risk`, sizing = min(rischio, margine) con `sizing_bound`/`marginUsd` nel ledger, ricontrollo del disponibile prima della gamba B, esecuzione dei segnali per |z| con rilettura del conto, margin guard, backtest con gli stessi limiti; test (y), (z); 196 verdi.
|
|
||||||
- **Fase 1**: `OrderTracker` + `pending_orders.json` + `orders.jsonl`; `EtoroBroker.OpenAsync` per `orderId` con riconoscimento dalla posizione; `BasketExecutor` con `ResumeAfterAAsync`/`CompleteAfterBAsync`/`UnwindLegAsync`; stati `PendingA`/`PendingB` persistiti; `PositionClassifier` e chiusura delle orfane; `EquityTracker`; contatori e P&L del conto nello snapshot e in dashboard; `--bonifica`; bandit senza applicazione automatica; `BasketEngine` in sei file parziali; ADR-0009; 18 test nuovi, 190 verdi.
|
|
||||||
|
|
||||||
## Prossimi passi
|
## Prossimi passi
|
||||||
|
|
||||||
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. **Rilascio 5.0.0** (utente): push del ramo, `-t:Rilascia -p:Versione=5.0.0` (richiede Docker acceso, `build/gitea.json` con permesso `package`, registro `192.168.30.23:3000` dichiarato `insecure-registry`), poi installazione su Unraid dal template.
|
||||||
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 nel container per 24 ore di verifica: contatore «orfane» a 0, `orders.jsonl` senza `Unknown` irrisolti, `sizing_bound = margin` sui primi ingressi; kill-switch a mano con verifica di piattezza; fermare il container un'ora con un basket aperto per vedere il recupero; messaggi Telegram reali.
|
||||||
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).
|
3. Revisione visiva dell'interfaccia con l'utente (gli screenshot sono dal campione `--sample`); eventuali ritocchi ai testi e ai tooltip.
|
||||||
4. Rifare il backtest (`backtest baskets`) con i limiti di margine e aggiornare §3 di `docs/STRATEGY.md`.
|
4. Avviso Telegram sui feed fermi da più di un'ora (resta in KNOWN_ISSUES).
|
||||||
|
5. Rifare il backtest (`backtest baskets`) con i limiti di margine e aggiornare §3 di `docs/STRATEGY.md`.
|
||||||
|
|
||||||
## Problemi aperti
|
## Problemi aperti
|
||||||
|
|
||||||
- Backtest negativo: la strategia non regge i costi (`docs/STRATEGY.md`).
|
- Backtest negativo: la strategia non regge i costi (`docs/STRATEGY.md`).
|
||||||
- Domande in attesa: D-28 (ritiro WPF), D-29 (registry), D-36 (ledger delle sessioni 16-21/9), D-37 (chiusura manuale del 21/9).
|
|
||||||
- Il conto reale vale 224,90 USD: il Live non è praticabile.
|
- Il conto reale vale 224,90 USD: il Live non è praticabile.
|
||||||
- Google News blocca le ricerche RSS; RBA 403 a intermittenza; Fed 404 a tratti.
|
- Google News blocca le ricerche RSS; RBA 403 a intermittenza; Fed 404 a tratti.
|
||||||
- `PROMPT.md` (la specifica 5.0) e `Modifiche.txt` sono nella radice del repository e non tracciati: decidere se spostarli in `docs/`.
|
- Il server non ha TLS e il registro di Gitea è in HTTP (rete locale).
|
||||||
|
- `Modifiche.txt` nella radice è un appunto dell'utente, modificato e non committato: decidere se tenerlo nel repository.
|
||||||
|
|||||||
Reference in New Issue
Block a user