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>
Il bot deve girare solo nel container (D-28, ADR-0007): la finestra WPF e le chiavi
DPAPI se ne vanno. Il motore diventa la libreria Encelado.Engine; l'eseguibile
Encelado.Server (nessun NuGet) serve un'API JSON scritta a mano, lo stream SSE con uno
snapshot al secondo e l'interfaccia Material 3 incorporata: dashboard con margine e
contatori, storico ordini (ordini, posizioni classificate, profitti per periodo, CSV),
log, impostazioni con chiavi cifrate (AES-GCM + passphrase), ripristino in cinque passi,
ricerca, diagnostica, valuta di visualizzazione, token locale in cookie.
Lo strumento acquista il comando learn; VS Code avvia il server con F5 e la modalità
campione; ~45 membri mai usati e i test WPF sono rimossi. Test dell'HTML incorporato,
del token e dello stream, dello storico: 210 verdi.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Con il backtest negativo e zero basket chiusi nel forward test, un meta-modello
attivo e un bandit che cambia il preset da solo sono rumore: learning.enabled
= false di fabbrica lascia il ledger, la calibrazione, la previsione di
volatilità e la logistica in ombra, e spegne ciclo settimanale e challenger
finché non valgono 300 basket chiusi e un P&L forward non negativo. Le skill
in .claude/skills dicono come si comincia, si verifica, si diagnostica, si
chiude e si rilascia una sessione. CLAUDE.md, glossario e problemi noti
aggiornati alla 5.0; catena Verifica verde con 219 test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Il bot riferisce al telefono: stato ogni ora, riepilogo giornaliero ed eventi
(avvio e arresto, basket aperti e chiusi, gambe in attesa, ordini risolti,
orfane, kill-switch, equity stop, perdita giornaliera, margin guard, recupero,
reset, preset, API in errore, scarto orologio) e accetta comandi dalla sola
chat autorizzata (/stato, /posizioni, /storico, /pausa, /riprendi, /chiudi,
/kill CONFERMO, /reset). Il canale è solo HttpClient: coda non bloccante, un
messaggio al secondo, retry con backoff e retry_after, long polling da un solo
task; il token vive nell'ambiente. Ogni comando eseguito, da qualunque origine,
è una riga «comando» nel ledger. Test (s)-(u).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Un riavvio, una sospensione del PC o un container fermo lasciavano i basket
aperti senza che nessuno applicasse le regole di uscita per il tempo perso.
Ora un heartbeat ogni 30 s misura l'inattività; oltre la soglia il bot blocca
le entrate, risolve gli ordini senza esito, riconcilia, riscalda le serie con le
barre perse e rivaluta ogni basket aperto come a una chiusura di barra
ordinaria (chiudi o tieni), chiude le orfane, riporta le esterne, scrive
reports/recupero_<run_id>.csv e riapre le entrate dopo il riscaldamento; a
mercato chiuso aspetta. Un lock tenuto in esclusiva impedisce due istanze
sulla stessa cartella dati. Sezione recovery in strategy.json. Test (r).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
Un solo basket sizato a rischio aveva impegnato il 100 % del margine del conto
(16/9). Ora strategy.json ha la sezione risk (12 % dell'equity per basket, 40 %
in totale, disponibile con buffer del 25 %), la size è il minimo fra rischio e
margine e il ledger scrive quale vincolo ha deciso; il conto viene riletto
prima della gamba B e, se non copre, A viene richiusa; i segnali della stessa
barra vanno per |z| decrescente con rilettura del conto; sotto equity/margine
1,5 niente entrate, sotto 1,2 si chiude il basket peggiore. Il backtest applica
gli stessi limiti. Test (y), (z).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Un ordine dall'esito ignoto non viene più abbandonato: entra in
data/state/pending_orders.json prima della chiamata HTTP, l'esito si legge per
orderId (il server non registra il referenceId degli ordini v2) e in mancanza
si ricostruisce dalla posizione comparsa sul conto. Una gamba senza esito porta
il basket in PendingA/PendingB invece di rifiutarlo; alla risoluzione parte la
gamba B, ridimensionata sulle unità eseguite, oppure la gamba A viene richiusa.
Ogni posizione del conto è classificata basket / orfana-bot / esterna: le orfane
del bot vengono adottate e chiuse, le esterne contate e mai toccate. Il picco di
equity ignora i movimenti di cassa. Bonifica da headless, orders.jsonl, contatori
in dashboard, bandit che propone e non applica. ADR-0009, test (m)-(q).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Perché: lo stato del lavoro deve dire cosa ha fatto il bot davvero. In quattro
ore di Demo autonomo 31 segnali sono stati rifiutati dal solo cancello di
correlazione (ρ_W mai sotto −0,6): è il primo dato da leggere nel ledger prima
di proporre un cambiamento di soglia.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Perché: l'utente ha chiesto un bot che operi cinque basket di coppie forex
correlate su eToro, autonomo, con ledger, feed gratuiti e apprendimento
costruito da zero, e ha deciso di eliminare tutto ciò che restava delle
gestioni precedenti (Binance, cTrader/proba, ricerca con SQLite, GBDT, RL,
TA-Lib) e di non avere approvazioni manuali sui singoli ordini.
Cosa cambia:
- nuovo Core dei basket (cross sintetici, decisore, cost gate, sizing,
esecutore leg-risk, backtest con PSR/DSR/PBO, livelli 0-3 di apprendimento),
adattatore eToro Public API, motore autonomo con equity stop, kill-switch,
riconciliazione, ledger append-only, calendario e notizie con sentiment;
- modalità Paper / Demo / Live (Live con flag e frase CONFERMO LIVE);
- interfaccia rifatta: barra in alto con tre schede, dashboard con i soli
numeri principali, fuso orario selezionabile, test di rendering in PNG;
- corretto il parser dei costi eToro (campo "value"): markup e overnight
non venivano letti;
- strumento di ricerca ridotto a ticks / baskets / falsify con due scenari di
costo; risultati in results/ e reports/: nessuna configurazione è
profittevole al netto dei costi (docs/STRATEGY.md lo dice con i numeri);
- documentazione completa (STRATEGY, ML_AND_LEARNING, RUNBOOK, GLOSSARY,
KNOWN_ISSUES, ADR-0004, ADR-0005) e catena di rilascio aggiornata.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Il bot ora ha un secondo parere prima di ogni ingresso: un classificatore GBDT
(scritto in C#, senza dipendenze native) addestrato sugli esiti dei segnali passati
con triple-barrier e meta-labeling, validato con CPCV, PBO e Sharpe deflazionato
contando tutte le configurazioni provate. Il modello non propone mai operazioni:
può solo rifiutarne una sotto la probabilità minima o ridurne la size, e si
sospende da solo quando le feature dal vivo derivano da quelle di addestramento.
Senza un campione promosso il bot opera come prima.
Perché tutto questo serve, e nell'ordine in cui è stato fatto:
- I log del giro reale sul testnet mostravano zero barre chiuse in tre giorni: il
decodificatore saltava l'oggetto annidato dei kline. Corretto con test di
regressione. Lo stesso giro restava a 1499/1500 barre di riscaldamento perché
Binance ne serve al massimo 1500 per richiesta: il client ora pagina e il motore
chiede quante ne servono davvero.
- Il log è diventato una tabella `;` con data, livello, sorgente, evento ed
eccezione (grep `;ERR;` trova ogni errore), con rotazione a dimensione impostabile
dalla finestra. Anche decisions.csv/executions.csv/trades.csv hanno intestazione
stabile, id monotoni e colonna `motivazione`, e vengono scritti anche in SQLite.
- La configurazione vive in Documenti\Encelado (con migrazione dal file accanto
all'eseguibile), le credenziali restano in LocalAppData, il database in
%ProgramData%\Encelado: tre cartelle per tre ruoli diversi.
- In modalità demo gli ordini partono davvero sul testnet (dryRun spento di
fabbrica): è l'unico modo di provare il percorso di esecuzione come in produzione.
- Lo strumento di backtest copre le fasi 0-4 della guida: qualità dei dati,
baseline buy&hold/SMA con PSR e DSR, Engle-Granger + Johansen + Kalman con costo
di break-even, dataset e addestramento del meta-modello, DQN su molti seed.
Ogni tabella è CSV `;` con motivazione, e la promozione a campione avviene solo
se il modello supera i criteri della Fase 3.
Sui dati disponibili nessuna coppia supera quei criteri, quindi nessun campione è
stato promosso: il bot resta sulla sola regola statistica, che a sua volta non
regge fuori campione. Il risultato è documentato, non nascosto.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Il bot smette di operare direzionalmente su un singolo asset e passa a coppie
delta-neutral: long una gamba, short l'altra nel rapporto che il test di
cointegrazione produce, scommettendo solo sul fatto che la distanza fra le due
si richiuda. È questo che gli permette di girare da una connessione domestica,
perché su barre da 15 minuti la latenza smette di contare.
Cosa cambia
- Encelado.Alpaca sostituito da Encelado.Binance: REST firmato in HMAC-SHA256
con correzione dello scarto d'orologio, uno stream combinato per kline, book
e mark price, e lo stream ordini autenticato con listen key rinnovata.
- Nuovo livello statistico in Core: OLS, test di Dickey-Fuller aumentato con
scelta del ritardo per AIC, ed Engle-Granger con i valori critici di MacKinnon
per la cointegrazione.
- Il rischio ragiona per coppia: divide il controvalore fra le gambe secondo β,
così le due si annullano invece di lasciare un residuo direzionale, e corregge
la dimensione con il funding netto atteso.
- Interfaccia da sette pagine a quattro. I grafici a candele sono spariti: su una
coppia coperta la candela di una gamba non dice niente, lo z-score sì.
- Ripristino dei valori predefiniti da Impostazioni, con copia datata del file
precedente. Ripristina il documento, commenti compresi, non solo i numeri.
L'ordine che non partiva
Il segnale diceva di entrare e non succedeva niente perché il router registrava
quasi tutti i rifiuti a livello debug: alla verbosità predefinita il bot
annunciava l'ingresso, rinunciava per un motivo che nessuno poteva vedere, e
sembrava aver ignorato la propria decisione. Adesso ogni intento produce una
riga a info o warn con il nome della coppia e il motivo esatto, la frase che
l'operatore legge e la decisione che il motore prende vengono dallo stesso
stato, e una barra che lo stream non consegna viene recuperata via REST.
Che cosa dice il backtest
Il banco di prova rigioca le coppie attraverso la STESSA classe che gira in
produzione, con la calibrazione che cammina in avanti. Su 6,6 anni di ETHUSDT,
BTCUSDT, SOLUSDT e AVAXUSDT: a 5 minuti nessuna combinazione di soglie supera
i filtri di taratura; a 15 minuti e a un'ora la griglia trova combinazioni che
rendono in taratura e in verifica, ma nessuna delle prime dieci resta positiva
sulla terza fetta. La finestra dello z-score va molte volte oltre l'emivita del
rientro — le 100 barre della guida sono le peggiori misurate — e il filtro di
cointegrazione è ciò che tiene in piedi tutto: senza, ogni combinazione passa
da leggermente positiva a −73%/−87%.
Per questo dryRun parte attivo. I valori consegnati sono i meglio supportati
fra quelli provati, non una strategia dimostrata, e il file lo dice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Chiusura. Il gestore annullava la chiusura, aspettava lo spegnimento del motore
e la richiedeva alla fine. Un secondo clic sulla X durante l'attesa usciva pero'
SENZA annullare: la finestra entrava nella propria sequenza di chiusura e la
Close() del primo tentativo ci finiva dentro, sollevando "non e' possibile
chiamare Close durante la chiusura di un oggetto Window". Ora ogni tentativo
successivo viene annullato e la chiusura vera si rimanda a un frame nuovo del
dispatcher, cosi' non puo' mai eseguire dentro il gestore.
La correzione ovvia — annullare tutto quando lo spegnimento e' in corso —
sarebbe stata peggiore del difetto: la chiusura finale ripassa dallo stesso
gestore, veniva annullata anche lei e la finestra non si chiudeva piu'. Passa
solo quella, riconosciuta da un flag alzato prima di chiamarla.
Strategia. Un aggiornamento che rimuove una strategia lascia il suo nome nella
configurazione dell'utente, perche' l'installazione la conserva — ed e' giusto,
le tarature sono sue. Il campo pero' era di sola lettura: l'unica via d'uscita
era modificare il JSON a mano. Ora e' un elenco a discesa che propone solo cio'
che il programma sa costruire, quindi non ci si puo' scrivere un nome
inesistente, e un valore obsoleto si segnala da se' all'apertura della pagina
invece di aspettare che qualcuno prema AVVIA. All'avvio l'applicazione lo dice e
porta in Impostazioni.
Stessa cosa per gli altri campi a insieme chiuso — barre, tipo di ordine,
livello del registro e i booleani — che ora si scelgono e non si scrivono.
Trovato provando il giro completo a video: applicare due volte lo stesso lotto
di modifiche falliva con "The node already has a parent", perche' la pagina lo
applica prima a una copia temporanea per validarlo e poi al file vero, e un
JsonNode appartiene a un albero solo. ConfigWriter ora clona.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entrambi i pacchetti devono esistere anche costruendo senza pubblicare su
Gitea, quindi lo zip si crea in Pacchetto e Rilascia si limita ad allegarlo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La generazione del setup, il versionamento e il caricamento su Gitea passano
ora da un solo file MSBuild in build/Release.proj, com'e' gia' per AutoBidder,
al posto dello script PowerShell make-installer.ps1.
Il cambiamento di sostanza e' da dove viene la versione: dal tag git, non piu'
da Directory.Build.props. Il numero arriva a dotnet publish come proprieta' da
riga di comando, quindi tag, eseguibile, installatore e release lo portano
uguale per costruzione invece che per disciplina. Il tag si crea in fondo, a
installatore esistente, e non si crea affatto se l'albero e' sporco: un giro
andato male non lascia dietro un tag per una versione mai costruita.
Tre adattamenti rispetto ad AutoBidder, segnati sul posto: la versione sta in
Directory.Build.props e non nel csproj; la pubblicazione produce una cartella
e non un eseguibile unico, perche' l'app legge encelado.json accanto a se',
quindi la copia portabile allegata alla release e' uno zip; il target Backtest
rigioca serie storiche di prezzi invece dei dossier delle aste.
Il setup si chiama Encelado_<versione>.exe e la pubblicazione ora fallisce se
nella cartella finiscono sorgenti o simboli di debug.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>