From f97f1e1fc3f67cac355c9fde8e540ce39f3f7e08 Mon Sep 17 00:00:00 2001 From: Alberto Balbo Date: Wed, 23 Sep 2026 10:24:54 +0200 Subject: [PATCH] =?UTF-8?q?Fase=200=20della=205.0:=20piano,=20post-mortem?= =?UTF-8?q?=20degli=20ordini=20pendenti,=20domande=20D-26=E2=80=A6D-37?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verifica della diagnosi sul codice e sul conto demo via API: il lookup per referenceId fallisce perché il server registra un riferimento nullo per gli ordini v2, e dal terzo ordine eToro ha ridotto ogni esecuzione a 2 000 USD di margine. Il piano fissa l'ordine delle fasi e le decisioni vincolanti (orderId come chiave, registro persistente degli ordini, margine come vincolo di primo livello, picco al netto dei movimenti di cassa). Co-Authored-By: Claude Fable 5.1 --- Encelado/docs/PIANO_5.0.md | 74 +++++++++++++++++++ Encelado/docs/POSTMORTEM_ordini_pendenti.md | 82 +++++++++++++++++++++ Encelado/docs/QUESTIONS.md | 19 +++++ 3 files changed, 175 insertions(+) create mode 100644 Encelado/docs/PIANO_5.0.md create mode 100644 Encelado/docs/POSTMORTEM_ordini_pendenti.md diff --git a/Encelado/docs/PIANO_5.0.md b/Encelado/docs/PIANO_5.0.md new file mode 100644 index 0000000..8a4121f --- /dev/null +++ b/Encelado/docs/PIANO_5.0.md @@ -0,0 +1,74 @@ +# 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. + +## 0. Verifica della diagnosi preliminare + +Verificata il 2026-09-23 sul codice, sul conto demo via API (sola lettura) e sui file di questa macchina. + +| Affermazione del prompt | Esito | +|---|---| +| `EtoroBroker.OpenAsync` legge `orderId` e non lo usa più; il polling passa solo da `orders:lookup?referenceId=` | **Confermato** (`src/Encelado.Etoro/EtoroBroker.cs`, metodo `OpenAsync` e `LookupOrderAsync`). | +| Il lookup per `referenceId` risponde 404 | **Confermato e spiegato**: `GET api/v1/trading/info/demo/orders/381739181` (uno degli ordini del bot) risponde `referenceID: 00000000-0000-0000-0000-000000000000`. Per gli ordini v2 il server **non ha registrato** l'`x-request-id` come riferimento, quindi la ricerca per riferimento non può trovarli. La ricerca per `orderId` funziona (verificata su due ordini, v1 e `orders:lookup?orderId=`). | +| `BasketExecutor.SendAsync` ripete il lookup e dichiara la gamba «non eseguita»; il basket è rifiutato e l'ordine resta sul server | **Confermato** nel codice. Dallo storico del conto demo: **21 posizioni forex del bot** fra il 16/9 19:00 e il 21/9 10:15 UTC (EURUSD 1, USDCAD 1, AUDUSD 4, NZDUSD 3, EURAUD 12), tutte gambe singole, tutte chiuse il **21/9 alle 13:26 UTC** in blocco (chiusura manuale, non del bot: il kill-switch non avrebbe trovato slot da chiudere). P&L netto complessivo di quelle 21 gambe: **+289,05 USD**, per fortuna e non per merito. | +| `ReconcileAsync` segnala le posizioni sconosciute una volta e non le tocca; il contatore conta gli slot | **Confermato**. | +| `KillAsync → CloseAllAsync` chiude solo gli slot con `Position` | **Confermato**. | +| Il ledger 16-22/9 ha 65 `segnale_ingresso` e 65 `rifiuto` | **Non verificabile qui**: su questa macchina `Documenti\Encelado\data\ledger\decisions.jsonl` ha 112 righe del solo 16/9 (mattina e pomeriggio, 110 `skip` e 3 `correzione`). Le sessioni dal 16/9 sera in poi hanno girato altrove (installazione 4.0.0 su un'altra macchina o un altro profilo). Chiesto in D-36. | +| «Depositi» = movimenti di cassa del conto virtuale, non operazioni del bot | **Confermato per esclusione**: nessuna rotta di deposito o trasferimento è chiamata dal codice; le rotte di trasferimento dell'API (`api/v1/money/transfers`) non compaiono nel client. La cifra tonda (+30 000) è coerente con un accredito demo. | +| Margine: un basket Aggressive ha impegnato tutto il margine | **Confermato e precisato**: i primi due ordini eseguiti (16/9 19:00:48 EURUSD short 547 214,68 unità, 62 792 USD di margine; 19:00:58 USDCAD long 464 358,5 unità, 46 436 USD) sommano 109 228 USD di margine = l'intera equity, cioè 1 092 281 USD di nozionale a leva 10. Sono due gambe A di due basket diversi (EURUSD/USDCHF e USDCAD/EURUSD), ciascuna sizata al tetto `maxEffectiveLeverage = 10` sul nozionale del basket. **Fatto nuovo**: dal terzo ordine in poi eToro ha eseguito ogni ordine a **esattamente 2 000 USD di margine** (`requestedAmount 2000`, `frozenAmount 2000`, unità con sei decimali ricalcolate dal server, mentre il bot manda unità a due decimali): il server ha ridotto gli ordini a un importo fisso perché il margine era esaurito. Conseguenze: (1) le unità eseguite possono differire molto da quelle richieste, quindi la riconciliazione per «unità ± 1 %» è solo un indizio, non la chiave: la chiave è l'`orderId`; (2) i limiti di margine di §10 sono indispensabili. | + +## 1. Valutazione punto per punto + +| § | Contenuto | Valutazione | Note | +|---|---|---|---| +| 1 | Scheda Storico (ordini, posizioni, profitti per periodo, export CSV) | **fattibile** | Modulo puro `Core/Baskets/History` + endpoint web. La vista dipende da §11 (web UI). Fonte del realizzato: `api/v1/trading/info/trade/{demo/}history` (già usato), da riconciliare con `baskets.csv` e `orders.jsonl`. | +| 2 | Navigation rail M3 a sinistra | **fattibile** in web (§11); in WPF sarebbe lavoro perso | Da fare dopo D-28. | +| 3 | Versione fuori dalla dashboard, Informazioni e Diagnostica | **fattibile** | Banale in web; in WPF vale la pena solo se D-28 è «no». | +| 4 | Valuta di visualizzazione | **fattibile** | Tassi dalle quote già in polling: EURUSD (1), USDCHF (6), AUDUSD (7), USDCAD (4), NZDUSD (3) ci sono; **GBP e JPY no**: servono GBPUSD e USDJPY nel polling (2 strumenti in più, stessa richiesta). Conversione solo in presentazione. | +| 5 | OrderTracker, lookup per orderId, stati Pending, orfane, contatori, picco netto dei movimenti di cassa, bonifica | **fattibile, prioritario** | Endpoint verificati (D-26, D-27). Il matching per posizioni usa strumento + verso + finestra temporale; le unità solo come conferma (vedi §0). Movimenti di cassa: il conto demo non compare in `api/v1/balances` (solo i conti reali), quindi si riconoscono dal salto del saldo non spiegato dalle chiusure (`CashFlowDetector`), in Demo e in Live allo stesso modo. | +| 6 | Recupero dopo inattività, heartbeat | **fattibile** | Riusa riconciliazione, riscaldamento e decisore già esistenti. | +| 7 | Telegram | **fattibile** | Solo `HttpClient`; token e chatId da ambiente (D-31). Un solo task di long polling. | +| 8 | Apprendimento: disattivare MLP, bandit, ciclo settimanale; spostarli nello strumento | **fattibile**, decisione D-30 | Il bandit oggi **applica** il preset in Demo (`ObserveOutcome`): va spento comunque, è un cambio di parametro automatico che contraddice la regola «nessun parametro cambiato dal bot». | +| 9 | Kill-switch reale e ripristino guidato | **fattibile, prioritario** | Endpoint di cancellazione esiste: `DELETE api/v2/trading/execution/{demo/}orders/{orderId}` (200 = richiesta accettata, esito da confermare con lookup: 7 o 9 annullato, 6 in corso). | +| 10 | Margine: soglie, buffer, sizing = min(rischio, margine), ordine per \|z\|, margin guard | **fattibile, prioritario** | In Demo `available` viene da `pnl` (`credit + bonusCredit`), in Live idem; `api/v1/balances` aggiunge `totalUsedMargin`/`currentPNL` solo per il reale. | +| 11 | UI web M3 servita dal bot, ritiro di WPF | **da chiarire (D-28)** | Tecnicamente fattibile senza NuGet (`Microsoft.AspNetCore.App` è un framework reference). È la scelta più impattante del prompt: cambia progetti, installer, test. Si fa dopo la conferma. | +| 12 | Engine/Server, Docker, IKeyStore | **da chiarire (D-28, D-29)** | Dipende da §11. Il `Dockerfile` multi-stage e l'`entrypoint` con PUID/PGID sono standard. | +| 13 | Template Unraid | **da chiarire (D-29)** | Serve il registry e l'URL dell'icona. | +| 14 | Documentazione, ADR, skill di progetto | **fattibile** | Le skill in `.claude/skills/` vanno bene; `encelado-diagnose` legge i file nuovi. | +| 15 | Domande | poste in `docs/QUESTIONS.md` (D-26…D-37) | D-26 e D-27 già risposte via API. | + +### Punti da rifiutare o ridimensionare (con motivo) + +- **§5.2 «polling per orderId e in parallelo per referenceId»**: il riferimento non è registrato dal server per gli ordini v2 (vedi §0). Si interroga **per `orderId`**, e per riferimento solo come ripiego quando la risposta al `POST` è andata persa (timeout di rete) — che è l'unico caso in cui l'`orderId` non c'è. Non «in parallelo»: la quota dei lookup è 60/min condivisa con l'esito delle chiusure, e due lookup ogni 400 ms per gamba la esaurirebbero in un minuto con tre basket. +- **§5.4 «unità ± 1 %» come criterio di adozione**: eToro può ridurre l'ordine (vedi §0), quindi le unità non sono affidabili. Criterio applicato: `orderId`/`positionId` nel registro (certo) → strumento + verso + orario ± 90 s coerenti con un ordine del registro o con una riga `segnale_ingresso`/`rifiuto`/`pending` del ledger (probabile; le unità ± 1 % alzano la confidenza ma non sono richieste). Le posizioni «probabili» sono comunque orfane-bot: sul conto demo non c'è altro che operi sul forex a leva 10 con quegli orari di chiusura di barra. +- **§8 bandit in Demo**: già oggi cambia il preset da solo (`ObserveOutcome` → `SetPreset`). Non è «da tenere in ombra»: è da spegnere del tutto a runtime, perché è un cambio automatico di parametro. +- **§11 test (aa) «UiRenderTests sostituiti»**: i test di rendering WPF restano finché esiste WPF; vengono rimossi con il progetto (Fase 6-7), non prima. + +## 2. Ordine di esecuzione e criteri + +Invariato rispetto a §16 del prompt, con una precisazione: le Fasi 1-3 non dipendono da nessuna domanda aperta (D-26/27 sono verificate, D-32/34/35 hanno default che sono parametri) e vengono eseguite subito; le Fasi 4-5 nemmeno; le Fasi 6-9 dipendono da D-28/D-29 e aspettano la risposta. + +| Fase | Contenuto | Stima | Dipendenze | +|---|---|---|---| +| 0 | questo piano, post-mortem, domande | 0,5 | — | +| 1 | §5: `OrderTracker` + `pending_orders.json` + `orders.jsonl`; lookup per `orderId` con matching sulle posizioni; stati `PendingA/PendingB`; classificazione `basket / orfana-bot / esterna` e chiusura delle orfane; contatori e P&L del conto nello snapshot; picco al netto dei movimenti di cassa; comando `bonifica`; spezzare `BasketEngine.cs` in file parziali (riconciliazione, stato, comandi) | 2 | — | +| 2 | §10: sezione `risk` in `strategy.json`, sizing a margine, ricontrollo di `available` prima di B, ordine per \|z\|, margin guard, `sizing_bound` nel ledger | 1 | Fase 1 | +| 3 | §9: kill-switch con cancellazione, chiusura di basket + orfane, esterne opzionali, verifica di piattezza, `Halted-Residuo`; procedura di ripristino in cinque passi nel motore (la finestra WPF la usa come oggi, la web UI la mostrerà passo per passo) | 1 | Fasi 1-2 | +| 4 | §6: heartbeat, `recovery` in `strategy.json`, procedura di recupero, lock di istanza, rapporto `recupero_.csv` | 1 | Fasi 1-3 | +| 5 | §7: `INotifier`, `TelegramNotifier`, stato orario, eventi, riepilogo, comandi | 1 | Fase 4 | +| 6 | §12: `Encelado.Engine`, `Encelado.Server`, Kestrel, SSE, API JSON, `IKeyStore` | 2 | **D-28** | +| 7 | §11 + §1-4 + §8: UI web M3, Storico, valuta, navigazione, Ricerca | 3 | Fase 6 | +| 8 | §12-13: Dockerfile, compose, template Unraid, docs | 1 | Fase 6, **D-29** | +| 9 | §14: documenti, ADR, skill, CHANGELOG, 5.0.0 | 0,5 | tutto | + +Totale: circa 13 sessioni. Ogni fase termina con `dotnet build`, `dotnet test`, documenti aggiornati e un commit. + +## 3. Decisioni di progetto prese in Fase 0 (vincolanti per le fasi seguenti) + +1. **Un ordine non si abbandona mai.** Ogni invio viene registrato in `data/state/pending_orders.json` prima della chiamata HTTP; all'avvio i pendenti si risolvono prima di qualsiasi decisione; una posizione che porta la firma del bot è del bot. +2. **La chiave è l'`orderId`.** Il riferimento cliente resta nell'intestazione (idempotenza) e nel registro, ma non è il mezzo con cui si chiede l'esito. +3. **Nessun esito sintetico.** `OrderOutcome.Status` riporta ciò che il server ha detto; quando non ha detto niente lo stato è `Unknown` e l'ordine resta pendente nel registro. +4. **Le unità eseguite le dice il server.** Il sizing propone, l'esecuzione registra ciò che l'API riporta (`positionExecutions[].openingData.units`), e la gamba B si dimensiona sulle unità **eseguite** di A, non su quelle richieste. +5. **Il margine è un vincolo di primo livello**, non una conseguenza: nessun ordine parte se `available` non copre il margine con il buffer. +6. **Il picco di equity è al netto dei movimenti di cassa**: depositi e prelievi spostano il riferimento, non il drawdown. +7. **Il codice nuovo di logica pura va nel Core** (`OrderTracker`, `PositionClassifier`, `CashFlowDetector`, `History`, sizing a margine) così la migrazione a `Encelado.Engine` della Fase 6 sposta file, non riscrive niente. diff --git a/Encelado/docs/POSTMORTEM_ordini_pendenti.md b/Encelado/docs/POSTMORTEM_ordini_pendenti.md new file mode 100644 index 0000000..55e76d7 --- /dev/null +++ b/Encelado/docs/POSTMORTEM_ordini_pendenti.md @@ -0,0 +1,82 @@ +# Post-mortem: ordini dall'esito ignoto e gambe orfane (16-21 settembre 2026) + +Scritto: 2026-09-23. Riguarda il forward test in Demo della versione 4.0.0. + +## Che cosa è successo + +Dal 16 settembre (sera) al 21 settembre il bot ha inviato ordini di apertura della gamba A di vari basket, ha dichiarato ogni gamba «non eseguita» perché non riusciva a leggerne l'esito, ha rifiutato il basket e non ha inviato la gamba B. Gli ordini però erano stati eseguiti dal server: sul conto demo si sono accumulate **posizioni singole senza copertura**, con lo stop nativo ma senza take-profit né stop di basket, che il bot vedeva come «posizioni sconosciute» e non toccava. Nessun basket è mai passato in `Open`; il contatore in dashboard diceva `0/5` mentre il conto aveva fino a venti posizioni aperte. + +## Evidenze + +Dallo storico del conto demo (`api/v1/trading/info/trade/demo/history`, letto il 2026-09-23): + +| Apertura (UTC) | Strumento | Verso | Unità | Margine USD | P&L netto | +|---|---|---|---|---|---| +| 16/9 19:00:48 | EURUSD | short | 547 214,68 | 62 792,33 | −355,69 | +| 16/9 19:00:58 | USDCAD | long | 464 358,50 | 46 435,84 | +414,56 | +| 16/9 20:00:14 | AUDUSD | long | 28 207,949 | 2 000,00 | +122,14 | +| 16/9 22:15:01 | AUDUSD | long | 28 226,660 | 1 999,99 | +135,49 | +| 16/9 23:01:22 | AUDUSD | long | 28 221,482 | 2 000,00 | +131,79 | +| 17/9 00:45:04 | EURAUD | long | 17 444,888 | 1 999,99 | −84,13 | +| 17/9 01:15:03 | EURAUD | long | 17 448,661 | 1 999,99 | −76,67 | +| 17/9 02:00:23 | EURAUD | long | 17 452,394 | 1 999,99 | −72,96 | +| 17/9 03:15:01 | EURAUD | long | 17 450,257 | 1 999,99 | −51,91 | +| 17/9 12:30:02 | AUDUSD | short | 28 073,722 | 2 000,00 | −26,39 | +| 18/9 04:00:03 | NZDUSD | short | 34 883,315 | 1 999,99 | +17,79 | +| 18/9 04:15:04 | NZDUSD | short | 34 843,813 | 2 000,00 | +40,42 | +| 18/9 07:00:32 | EURAUD | short | 17 418,146 | 1 999,99 | +18,89 | +| 18/9 08:15:04 | NZDUSD | short | 34 950,981 | 1 999,99 | −20,97 | +| 18/9 09:15:03 | EURAUD | short | 17 419,919 | 1 999,99 | +26,97 | +| 18/9 10:15:01 | EURAUD | short | 17 415,984 | 2 000,00 | +27,83 | +| 21/9 06:45:02 | EURAUD | short | 17 427,466 | 1 999,99 | +12,68 | +| 21/9 07:15:03 | EURAUD | short | 17 432,862 | 2 000,00 | +9,57 | +| 21/9 08:15:02 | EURAUD | short | 17 425,358 | 1 999,99 | −0,62 | +| 21/9 09:00:44 | EURAUD | short | 17 421,555 | 1 999,99 | +7,95 | +| 21/9 10:15:03 | EURAUD | short | 17 420,945 | 1 999,99 | +12,31 | + +Tutte chiuse il 21/9 alle 13:26 UTC in blocco (chiusura manuale). Totale: 21 gambe, **+289,05 USD** netti, 0 commissioni. Il segno positivo è casuale: gambe singole senza copertura, nessuna regola di uscita applicata per cinque giorni. + +Osservazioni: + +1. Gli orari sono chiusure di barra M15 (`:00`, `:15`, `:30`, `:45` più pochi secondi): sono ordini del bot. Ogni volta che il segnale restava oltre `zIn` alla barra successiva, il basket — tornato `Idle` dopo il «rifiuto» — inviava **un'altra gamba A**. Le dodici EURAUD sono lo stesso segnale ripetuto per dodici barre. +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=` 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. +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. + +## Causa + +Tre difetti concatenati, nessuno dei quali da solo avrebbe prodotto il danno: + +1. **Esito cercato con la chiave sbagliata** (`EtoroBroker.OpenAsync`): l'`orderId` restituito dal `POST` veniva letto e ignorato; l'esito era chiesto solo per `referenceId`, che il server non registra per gli ordini v2. Dopo `fillTimeoutSeconds` il metodo restituiva un esito **costruito a mano** (`"Received"`, «esito non ancora noto») invece di dire «non so». +2. **Ordine abbandonato** (`BasketExecutor.SendAsync`, `BasketEngine.ExecuteEntryAsync`): allo scadere di `legTimeoutSec` la gamba era «non eseguita», il basket rifiutato e riportato a `Idle`, e nessuno seguiva più l'ordine. Alla barra dopo si ripartiva da zero. +3. **Posizione sconosciuta = posizione intoccabile** (`BasketEngine.ReconcileAsync`): la regola di sicurezza «non toccare ciò che non è tuo» era corretta per le posizioni esterne, ma il bot non aveva alcun modo per riconoscere le proprie. Il contatore contava gli slot, non le posizioni; il kill-switch chiudeva gli slot, non le posizioni. + +Aggravante: il **sizing non guardava il margine**. Con `orderLeverage = maxEffectiveLeverage = 10` un solo basket al tetto impegna il 100 % dell'equity. + +## Correzione (Fase 1-3 del piano 5.0) + +| Difetto | Correzione | Dove | +|---|---|---| +| esito per `referenceId` | esito per **`orderId`** (`orders:lookup?orderId=`, ripiego `api/v1/trading/info/{demo/}orders/{id}`); il riferimento solo se la risposta al `POST` è andata persa; matching sulle posizioni (strumento + verso + orario ± 90 s) come ultima risorsa | `EtoroBroker.OpenAsync`, `LookupOrderByIdAsync` | +| esito sintetico | `OrderOutcome` con stato `Unknown` e `Pending = true`; mai un nome di stato che il server non ha detto | `EtoroBroker`, `OrderOutcome` | +| ordine abbandonato | **`OrderTracker`** con registro persistente `data/state/pending_orders.json` scritto **prima** dell'HTTP; stati `PendingA` / `PendingB`; risoluzione a ogni ciclo e all'avvio; `orders.jsonl` append-only | `Core/Baskets/OrderTracker.cs`, `BasketExecutor`, `BasketEngine` | +| posizioni proprie non riconosciute | **`PositionClassifier`**: `basket` / `orfana-bot` / `esterna`; le orfane-bot vengono adottate e chiuse; contatori separati in dashboard; banner se equity − saldo non torna con le posizioni | `Core/Baskets/PositionClassifier.cs`, `BasketEngine.Reconcile.cs` | +| kill-switch che chiude gli slot | kill-switch che chiude **le posizioni** (basket + orfane), cancella i pendenti, verifica la piattezza, stato `Halted-Residuo` | `BasketEngine.Kill.cs` (Fase 3) | +| sizing senza margine | `risk.maxMarginUsePct`, `maxMarginPerBasketPct`, `marginBufferPct`, sizing = min(rischio, margine), ricontrollo di `available` prima di B, margin guard | `VolParitySizing`, `BasketDecider`, `BasketExecutor` (Fase 2) | +| accredito che gonfia il picco | `CashFlowDetector`: salto di saldo non spiegato dalle chiusure = movimento di cassa; picco e equity di inizio giornata al netto | `Core/Baskets/CashFlowDetector.cs`, `BasketEngine.State.cs` | + +## Come si verifica che non si ripeta + +- Test (m): lookup 404 persistente + posizione presente → `Filled` per matching (`EtoroBrokerTests`). +- Test (n): A eseguita, B rifiutata → A richiusa entro 5 s (`LegRiskTests`). +- Test (o): A pendente oltre il timeout poi eseguita → `PendingA → Open` con B, oppure unwind se il segnale è decaduto (`OrderTrackerTests`). +- Test (p): posizione con la firma del bot → `orfana-bot` e chiusa (`PositionClassifierTests`). +- Test (q): un deposito non muove il picco né il drawdown (`CashFlowTests`). +- In Demo: 24 ore con ordini eseguiti riconosciuti e **zero** orfane (contatore «Gambe orfane» a 0 per tutta la sessione; `orders.jsonl` senza stati `Unknown` non risolti). +- Skill `encelado-diagnose`: rapporto di riconciliazione fra `orders.jsonl`, `pending_orders.json` e le posizioni del conto. + +## 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). +- 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`). diff --git a/Encelado/docs/QUESTIONS.md b/Encelado/docs/QUESTIONS.md index 1fca0f1..9e075fb 100644 --- a/Encelado/docs/QUESTIONS.md +++ b/Encelado/docs/QUESTIONS.md @@ -41,3 +41,22 @@ Ogni domanda è numerata per fase. Quando l'utente non ha risposto, è stato app | D-23 | Fuso orario della finestra: quello del computer o selezionabile? | computer, selezionabile | **Applicato**: `ui.timeZone` = `computer` di fabbrica; elenco dei fusi di Windows in Impostazioni; `ENCELADO_TIME_ZONE` da ambiente. Solo la finestra cambia: il log porta l'offset, il ledger è UTC. | | D-24 | L'endpoint dei costi restituiva markup e overnight a zero: era davvero zero? | verificare | **Verificato via API il 2026-09-16 12:45 UTC**: il campo si chiama `value`, non `amount`. EURUSD 10 000 unità leva 10: markup 0,0, spread di mercato 0,1 USD (0,1 pip), overnight 0,91 USD/giorno (≈ 0,9 pip/gamba/giorno). Parser corretto; aggiunto lo scenario di costi `api` al backtest. | | D-25 | Google News vieta `/rss/search` nel robots.txt: forzare, sostituire o omettere? | omettere | **Default applicato: omettere** (il bot rispetta il robots.txt). SNB e RBNZ restano senza fonte; documentato in `KNOWN_ISSUES.md`. | + +## 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. + +| # | 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=` (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-28 | Confermi il ritiro del progetto WPF a favore della web UI servita dal bot? | sì (ADR-0007) | **In attesa.** Le Fasi 6-9 non partono senza risposta. | +| D-29 | Registry per l'immagine e URL dell'icona del template Unraid? | Gitea (`build/gitea.example.json`), icona nel repo | **In attesa.** | +| D-30 | Confermi la disattivazione a runtime di MLP, bandit e ciclo settimanale? | sì (ADR-0006) | **Default applicato in Fase 1 per il solo bandit**: oggi il bandit cambia il preset da solo in Demo (`ObserveOutcome`), cioè un parametro cambiato dal bot; viene spento subito. MLP e ciclo settimanale seguono in Fase 7 con ADR-0006. | +| D-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-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-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-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). |