Skip to content

Fix portfolio, account and order reads on the trading port - #2

Merged
NiccoloSalvini merged 3 commits into
NiccoloSalvini:mainfrom
simoneb:fix/trading-port-reads
Aug 12, 2026
Merged

Fix portfolio, account and order reads on the trading port#2
NiccoloSalvini merged 3 commits into
NiccoloSalvini:mainfrom
simoneb:fix/trading-port-reads

Conversation

@simoneb

@simoneb simoneb commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Ciao! Ho usato questa libreria per costruire un server MCP verso Darwin e, testandola contro una piattaforma reale, ho trovato tre problemi sul percorso di lettura della porta di trading (10002) che la rendevano inutilizzabile per portafoglio, saldo e ordini. Tutto verificato su Darwin 2.5.1 (build 04/02/2025).

Il più insidioso non dà errore: perde dati in silenzio.

1. Due comandi che Darwin rifiuta

get_portfolio() invia GETPORTFOLIO e get_account_info() invia GETACCTINFO. Darwin risponde ERR;<comando>;1004 a entrambi, quindi nessuna delle due chiamate può restituire dati:

> GETPORTFOLIO      ERR;GETPORTFOLIO;1004
> INFOSTOCKS        STOCK;<ticker>;<ora>;...      (una riga per posizione)
> GETACCTINFO       ERR;GETACCTINFO;1004
> INFOACCOUNT       INFOACCOUNT;<ora>;<conto>;...

Curiosamente i parser erano già scritti per i comandi giusti: i docstring di parse_portfolio_response e parse_account_info_response nominano proprio INFOSTOCKS e INFOACCOUNT. Era solo l'invio a essere sbagliato.

Ho verificato anche che non fosse un problema di sinonimi: GETACCOUNT, GETAVAILABILITY, INFOSTOCK (singolare), PORTFOLIO, INFOPOSITION, GETPENDINGORDERS e TABLEDESCRIPTION rispondono tutti 1004. Funzionano INFOACCOUNT, INFOAVAILABILITY, INFOSTOCKS, GETPOSITION <sym>, ORDERLIST, ORDERLISTPENDING, DARWINSTATUS.

2. Le risposte multi-riga venivano troncate alla prima riga

TradingConnection.send_command restituiva solo la prima riga corrispondente al prefisso atteso, scartando il resto. Misurato dal vivo su un conto con 15 posizioni e 4 record di ordine:

sul socket restituito da send_command
ORDERLIST 4 ordini 1
INFOSTOCKS 15 posizioni 1

Nessun errore, nessun warning: il chiamante riceve un portafoglio da una posizione e non ha modo di sapere che ne mancano 14. Su una libreria di trading mi è sembrato il problema più grave dei tre.

Due cause secondarie contribuivano allo stesso guasto:

  • Il ciclo di lettura si fermava al primo chunk contenente un newline, quindi una lista distribuita su più recv() veniva tagliata dove cadeva il confine del segmento TCP.
  • Darwin pusha traffico non richiesto su questa porta: portafoglio e lista ordini completi a ogni connessione, poi aggiornamenti di posizioni e ordini quando avvengono. Quelle righe venivano lette come se fossero la risposta al comando in volo — e una riga STOCK pushata è indistinguibile da una riga di risposta a INFOSTOCKS.

3. FLOWPOINT, per sapere dove finisce una lista

Senza marker, una lista è solo una sequenza di righe e l'unico modo per sapere di averle tutte è aspettare il silenzio e sperare. FLOWPOINT TRUE risolve: Darwin avvolge le liste in BEGIN/END.

> FLOWPOINT TRUE
FLOWPOINT;TRUE
> INFOSTOCKS
BEGIN STOCKLIST
STOCK;<ticker>;...
... una riga per posizione ...
END STOCKLIST

connect() ora lo abilita, così la lettura termina su un marker invece che su un timeout, e le righe pushate a metà risposta cadono fuori dai marker. Se Darwin rifiuta, si torna al fallback a intervallo di inattività con un warning nel log — nessuna regressione per chi ha una build che non lo supporta.

Cosa cambia nel codice

  • La risposta è letta riga per riga, con i byte residui bufferizzati tra le chiamate, così una risposta arrivata nello stesso segmento TCP del push successivo non viene persa.
  • Le righe sono selezionate per prefisso; un comando di lista restituisce tutte le righe corrispondenti.
  • Il push iniziale finisce in TradingConnection.pushed_lines anziché essere letto come risposta. È uno snapshot legittimo del conto, quindi lo conservo invece di scartarlo.
  • I codici ERR 1024-1028 segnalano eventi del collegamento trading/datafeed e arrivano non richiesti: non fanno più fallire il comando in volo.

send_command restituisce ancora una stringa, e per i comandi a riga singola restituisce ancora una riga singola — parse_account_info_response, che fa split(';') sull'intera risposta, continua a funzionare invariato.

Test

Ho aggiunto tests/test_trading_connection.py: 12 test contro un finto Darwin su socket, quindi girano in CI senza la piattaforma. Coprono le risposte complete, le righe spezzate tra letture TCP (chunk_size=4), il push iniziale non confuso con una risposta, le notifiche di link che non fanno fallire un comando, i due nomi di comando, e il fallback senza FLOWPOINT. Le forme delle risposte seguono quelle reali; i valori sono sintetici.

I 20 test (12 nuovi + 8 preesistenti di simulazione) passano.

Verificato anche end-to-end contro Darwin reale con la libreria patchata: get_account_info e get_portfolio tornano a restituire dati (prima: ERR 1004 entrambi), e get_orders restituisce tutti i record invece di uno solo.

Note

  • I commit sono separati per problema, quindi puoi prenderli indipendentemente se preferisci valutarli uno per volta.
  • Non ho toccato la porta storica (10003): il suo send_command legge fino a END CANDLES/END TBT e non ha lo stesso difetto. Non ho comunque potuto vedere dati storici reali perché il conto usato non ha le quotazioni abilitate — ogni comando su 10003 risponde 1032, coerentemente con il flag datafeed FALSE in DARWIN_STATUS.
  • Non ho toccato il percorso ordini: il conto usato è reale, quindi non ho inviato nessun ordine. Vale la pena verificare se send_command gestisca bene anche le risposte TRADOK/TRADERR/TRADCONFIRM, che con il mio fix passano dal ramo "prefisso non noto".
  • Se preferisci un taglio diverso — per esempio tenere il fix del troncamento e lasciar fuori FLOWPOINT — dimmelo e riorganizzo volentieri. Ho incluso FLOWPOINT perché senza i marker le righe pushate restano indistinguibili da quelle di risposta, quindi il fix del troncamento da solo mi sembrava a metà.

Grazie per aver pubblicato la libreria — avere il protocollo già mappato in Python mi ha fatto risparmiare un bel po' di lavoro.

simoneb and others added 3 commits August 12, 2026 12:08
get_portfolio() sent GETPORTFOLIO and get_account_info() sent GETACCTINFO.
Darwin rejects both with ERR;<command>;1004, so neither call could ever
return data. Verified against Darwin 2.5.1:

    > GETPORTFOLIO     ERR;GETPORTFOLIO;1004
    > INFOSTOCKS       STOCK;<ticker>;<ora>;...        (one line per position)
    > GETACCTINFO      ERR;GETACCTINFO;1004
    > INFOACCOUNT      INFOACCOUNT;<ora>;<conto>;...

The parsers were already written for the correct commands — the docstrings
of parse_portfolio_response and parse_account_info_response name INFOSTOCKS
and INFOACCOUNT respectively — so only the commands sent were wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
TradingConnection.send_command returned only the first line matching the
expected prefix, discarding the rest of a multi-line response. Against a
live account this meant ORDERLIST returned 4 orders on the socket and
get_orders() returned 1, and a 15-position portfolio arrived as a single
position. Nothing reported that data had been dropped.

Two further problems fed into the same failure:

- The read loop stopped at the first chunk containing a newline, so a list
  spanning several TCP reads was cut wherever the segment boundary fell.
- Darwin pushes unsolicited traffic on this port: a full portfolio and
  order snapshot on connect, then position and order updates as they
  happen. Those lines were read as if they answered the command in flight,
  and pushed STOCK lines are indistinguishable from INFOSTOCKS rows.

The response is now read line by line, with the leftover bytes buffered
across calls, until the response is actually complete. Completion is
determined by an end marker rather than by silence: connect() enables
FLOWPOINT, so Darwin wraps list responses in BEGIN/END STOCKLIST and
BEGIN/END ORDERLIST. If Darwin declines, the reader falls back to an idle
gap and logs a warning. Lines are then selected by prefix, and a list
command returns every matching row.

The connect-time push is drained into TradingConnection.pushed_lines
instead of being read as a response — it is a genuine snapshot, so it is
kept rather than dropped. ERR codes 1024-1028 announce trading/datafeed
link events and arrive unprompted; they no longer fail whichever command
happens to be in flight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers the reads against a stand-in for Darwin's socket, so they can run
without the platform: complete multi-line responses, lines split across
TCP reads, the connect-time push not being mistaken for a response, link
notifications not failing a command, and the two command names Darwin
accepts. Response shapes follow a live Darwin 2.5.1; values are synthetic.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@NiccoloSalvini
NiccoloSalvini merged commit 59ed80b into NiccoloSalvini:main Aug 12, 2026
7 checks passed
@NiccoloSalvini

Copy link
Copy Markdown
Owner

Grazie Simone, mergiata.

Segnalazione fatta come si deve: il protocollo verificato contro Darwin reale, i tre problemi separati in tre commit, e la tabella "sul socket / restituito da send_command" che rende il troncamento impossibile da fraintendere. Hai ragione che era il più grave dei tre — gli altri due almeno davano ERR 1004 e si notavano, quello restituiva un portafoglio valido con 1 posizione su 15 e nessun modo di accorgersene.

Prima di mergiare ho girato i 12 test nuovi contro il codice vecchio: falliscono tutti e dodici, quindi fissano davvero il comportamento rotto e non solo quello nuovo. Suite completa verde (20 test) e CI ok su 3.9/3.10/3.11.

Ho apprezzato anche il fatto che i parser fossero già scritti per INFOSTOCKS/INFOACCOUNT e che tu l'abbia notato invece di riscriverli: era effettivamente solo il layer di trasporto a essere sbagliato.

Sul percorso ordini hai ragione a lasciarlo fuori — conto reale, giusto non inviare nulla. Confermo la tua osservazione: TRADOK/TRADERR/TRADCONFIRM non hanno un prefisso mappato e finiscono nel ramo "prefisso ignoto", quindi si fermano alla prima riga. Non è una regressione introdotta da te, ma è il pezzo che resta scoperto e va provato su demo.

Se il server MCP verso Darwin che stai costruendo diventa pubblico, linkalo pure — mi fa piacere sapere che la libreria è servita a qualcosa.

@simoneb
simoneb deleted the fix/trading-port-reads branch August 12, 2026 14:36
@simoneb

simoneb commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants