Files
Encelado/Encelado/docs/ARCHITECTURE.md
T
Alby96andClaude Fable 5.1 aaec965241 5.0 Fase 3: kill-switch che chiude davvero e ripristino in cinque passi
Il kill-switch della 4.0.0 chiudeva gli slot, non le posizioni, e «riusciva»
con venti gambe ancora sul conto. Ora annulla gli ordini senza esito e ne
attende la risoluzione, chiude basket, gambe in attesa e orfane (le esterne
solo su richiesta o con risk.closeForeignOnKill), rilegge il conto finché le
posizioni del bot non sono sparite e, se qualcosa resta, dichiara
Halted-Residuo con l'elenco invece di «tutto chiuso». Il reset è una procedura
in cinque passi (stato, file STOP, motivazione, riconciliazione con
riscaldamento e picco, ripartenza con entrate bloccate) rifiutata finché il
conto non è piatto. INotifier per le notifiche della Fase 5. Test (v)-(x).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 11:08:47 +02:00

18 KiB
Raw Blame History

Architettura di Encelado

Aggiornato: 2026-09-23 (Fase 1 del piano 5.0: registro ordini, stati Pending, classificazione delle posizioni).

1. Che cosa c'era prima della modifica (ricognizione)

1.1 Albero dei progetti

Encelado.slnx
├── src/Encelado.Core        libreria portabile (net10.0), zero NuGet, AOT/trim-compatibile
│   ├── Backtest/            replay su coppie cointegrate (era Binance), CsvBarSource, CrossSectional
│   ├── Indicators/          SMA, EMA, RSI, MACD, ATR, RollingStdDev, Bollinger, Donchian, RollingWindow<T>
│   ├── Journal/             IJournalSink e record dei journal (DecisionRow, TradeRow, ProbaDecisionRow…)
│   ├── Market/              Bar, Quote, Tick, Side, TimeFrame
│   ├── Ml/                  GBDT nativo, meta-labeling, triple barrier, PurgedCv/CPCV, Pbo (CSCV), Classification (AUC, Brier, log-loss, calibrazione), DriftMonitor (PSI/KS)
│   ├── Research/            pipeline ProbaBot: AssetFrame, eventi CUSUM, bracci di feature B/C, EventBacktest, Trials, Gates
│   ├── Risk/                RiskEngine e RiskLimits (kill-switch giornaliero, esposizione, spread)
│   ├── Rl/                  Mlp a due strati (Adam), DqnAgent, PairEnvironment
│   ├── Statistics/          Ols, DickeyFuller, Cointegration (+HalfLife), Johansen, Kalman, Pca, Performance (Sharpe, PSR, DSR, Kelly), Normal
│   └── Strategies/          StatArbStrategy (coppie), StrategyParameters
├── src/Encelado.Storage     SQLite (Microsoft.Data.Sqlite) — l'unica dipendenza NuGet a runtime; journal in doppia scrittura, dataset, modelli, campioni
├── src/Encelado.CTrader     adattatore cTrader Open API (NuGet cTrader.OpenAPI.Net). NON referenziato dal Bot: non è mai stato collegato
├── src/Encelado.Bot         WPF (net10.0-windows), WinExe "Encelado.exe", zero NuGet
│   ├── 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
│   ├── Engine/              BotSupervisor (ciclo di vita, snapshot), ProbaEngine (motore cTrader, incompleto), AccountState, BotSnapshot, TradeJournal, DecisionLog
│   ├── Diagnostics/         CsvTable (tabelle ';' con header e spostamento in .old), Metrics
│   ├── Logging/             Log statico non bloccante su Channel<T>, file ';' con rotazione, Sink per la UI
│   ├── Ui/                  MainViewModel (INotifyPropertyChanged a mano), Theme.xaml (tema scuro proprio), pagine Status/Log/Settings, LoginWindow (OAuth cTrader), SettingsCatalogue, SettingField, Converters
│   └── MainWindow.xaml(.cs) shell con navigazione laterale, timer 1 s che applica lo snapshot
├── tests/Encelado.Tests     xunit 2.9 (framework GIÀ presente: si usa quello, niente mini-runner)
├── tools/Encelado.Backtest  strumento console di ricerca ("backtest <comando>"), unico progetto con TA-Lib
└── build/                   Release.proj (verifica, pacchetto, rilascio su Gitea), Encelado.iss (Inno Setup)
  • 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.
  • 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.
  • 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: 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.
  • 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

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)
src/Encelado.Core/Notifications/ INotifier, NullNotifier (dalla Fase 5 TelegramNotifier)
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),
                                .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),
                                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)

flowchart LR
  E[eToro Public API] -->|rates ogni 3 s| Q[Quote poller]
  Q --> B[Bar builder M15]
  E -->|candles| B
  B --> S[Strategy loop<br/>un solo thread]
  C[Calendario + RSS] --> F[Feature contesto]
  F --> S
  M[Meta-modello in ombra<br/>vol forecast] --> S
  S -->|decisione| X[Executor<br/>leg-risk protocol]
  X --> E
  S --> L[(Ledger jsonl/csv)]
  X --> L
  S --> U[Snapshot → UI / headless]
  L --> K[Ciclo settimanale: L0-L3]
  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.

2.3 Macchina a stati del basket

Idle ──(segnale + cancelli)──► Entering ──(A e B eseguite)──► Open ──(add)──► Adding ──► Open
  ▲        ▲                      │ (B rifiutata → chiudi A, leg_risk_unwind, basket disattivato 1 h)
  │        │                      │ (A senza esito oltre legTimeoutSec) ──► PendingA ──(A eseguita, segnale valido)──► Entering
  │        │                      │                                          │ (A rifiutata → Idle; A eseguita e segnale decaduto → chiudi A → Idle)
  │        │                      │ (A eseguita, B senza esito) ──► PendingB ──(B eseguita)──► Open
  │        └───────────────────────────────────────────────────────────────── (B rifiutata → chiudi A → Idle)
  │                               ▼
  └────────── Closed ◄──── Exiting ◄──(TP | z_out | stop | time-stop | manuale | forzata)── Open
                                  │ (una gamba non chiude dopo 3 tentativi)
                                  ▼
                                Error (blocco nuove entrate finché non risolto)

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

  • 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).
  • 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, Snapshot().
  • IUiActions (Bot): ciò che le pagine possono chiedere alla finestra (chiudi basket, kill-switch, preset, reset, chiavi, file).

2.5 Vincoli e limiti scoperti in Fase 0

  • 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.
  • 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.
  • 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.
  • 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).