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>
18 KiB
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 testnet10.0-windows),NullableeTreatWarningsAsErrorsattivi per tutti i progetti viaDirectory.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;Lockper lo stato condiviso; snapshot immutabili verso la UI; ogni tabella è CSV;con colonna finalemotivazione; log strutturatotimestamp;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
ProbaEngineusa i tipi cTrader direttamente. - Storage: SQLite in
%ProgramData%\Encelado\encelado.db(barre, dataset, modelli, journal) più CSV nella cartella dei log. Configurazione inDocumenti\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, pulsantiPrimary/Danger,PowerButton,ModeBadge), font tabulareCascadia Mono. Nessuna libreria MVVM:MainViewModelimplementaINotifyPropertyChangeda mano. - Test: xunit con test di binding WPF (
UiBindingTestsascolta 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 completadotnet msbuild build/Release.proj -t:Verifica; l'app leggeDocumenti\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 perorderId, 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;EmptyContextProvidernei test).IModel:Predict(features),Update(features, label), JSON, implementato daOnlineLogisticeSmallMlp.IEngine(Bot):RunAsync,CloseAllAsync,ExecuteAsync(EngineCommand)conClose,KillSwitch(argomentoesterneper 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 ≤ 1000senza 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 strumentobacktest 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
ratessono senza markup; il costo effettivo (markup + spread di mercato + overnight) arriva daPOST /trading/info/{demo/}costs(20/min dedicate). Il cost gate somma i due. - Ordini:
POST /api/v2/trading/execution/{demo/}orders(asincrono: esito conorders:lookup?orderId=; il server non registra l'x-request-idcomereferenceId, verificato il 2026-09-23);sellShorte leva > 1 richiedonostopLossRate. 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).