C++ nel trading ad alta frequenza: dai dati di mercato alla latenza deterministica

C++ nel trading ad alta frequenza: dai dati di mercato alla latenza deterministica

C++ nel trading ad alta frequenza: dai dati di mercato alla latenza deterministica

Introduzione

Il trading ad alta frequenza riesce a semplificare gli argomenti tecnici. In molte aree del software, un sistema può rimanere rispettabile nascondendo l’inefficienza dietro dimensioni, budget hardware o generose aspettative in termini di tempi di risposta. Nell’HFT, la lentezza non è semplicemente inelegante. È costoso. L’instabilità non è solo fastidiosa. Danneggia la qualità della strategia, oscura la diagnosi e indebolisce la fiducia nell’intero stack. Il dominio non elimina la teoria, ma costringe la teoria a rispondere al tempo.

Ecco perché il C++ continua ad avere così tanta importanza nei sistemi di trading. La lingua sopravvive lì non perché l’industria sia incapace di cambiare e non perché gli ingegneri si trovino a dover affrontare inutili difficoltà. Sopravvive perché HFT richiede ripetutamente una combinazione di proprietà che C++ fornisce ancora insolitamente bene: controllo sul layout della memoria, lavoro prestazionale preciso, strumenti nativi maturi, profonda integrazione del sistema operativo e della rete e un enorme corpo di conoscenza pratica accumulata attraverso decenni di sistemi reali costruiti sotto pressione.

Si è tentati di ridurlo a uno slogan, ad esempio C++ è veloce. Ma quello slogan è troppo piccolo. L’HFT non premia la velocità pura in astratto. Premia il comportamento deterministico lungo l’intero percorso, dai dati di mercato alla decisione fino alla trasmissione dell’ordine. La latenza media aiuta, ma la latenza prevedibile aiuta di più. Un sistema che occasionalmente è brillante e regolarmente nervoso è spesso peggiore di uno leggermente più lento e costantemente comprensibile. La storia più profonda, quindi, non è che il C++ sia semplicemente veloce. Il fatto è che il C++ rimane uno dei linguaggi più forti per la creazione di sistemi a bassa latenza il cui comportamento può essere modellato, misurato e corretto nei minimi dettagli.

Perché HFT continua a tornare al C++

Uno stack di trading che compete in tempo si preoccupa dei dettagli che la maggior parte degli altri domini può permettersi di offuscare. Quante allocazioni si verificano sul percorso caldo? Quali dati convivono nella cache? Quale thread va dove? Quanti salti di coda separano l'arrivo dei pacchetti dalla logica della strategia? Il parser tocca più memoria del necessario? Il gateway migra tra i core? Una fase di registrazione o normalizzazione apparentemente innocua allarga la coda della distribuzione della latenza? Queste non sono domande decorative. Sono il lavoro.

Il C++ rimane una sede naturale per questo lavoro perché consente agli ingegneri di confrontare direttamente questi dettagli. Il linguaggio non impone un modello di allocazione, una storia di coda, una storia di proprietà o uno scheduler di runtime sull'intero sistema. Quella libertà è pericolosa nelle mani di team imprudenti, ma l’HFT è uno dei luoghi in cui l’uso disciplinato di quella libertà crea un vero vantaggio. Le organizzazioni commerciali mature non vogliono chiedere gentilmente alla macchina. Vogliono sapere esattamente cosa viene detto alla macchina e dove si nascondono i costi.

C’è anche un argomento relativo all’ecosistema che conta più di quanto la gente ammetta. L’HFT non è solo un problema linguistico. È un problema di strumenti ed esperienza. C++ viene fornito con compilatori, profiler, grafici di fiamma maturi, flussi di lavoro di contatore hardware, supporto di sanitizzazione, modelli di integrazione a livello di sistema operativo e una lunga eredità da settori adiacenti critici per le prestazioni. Gli assistenti AI beneficiano sempre più di quella stessa eredità pubblica. Quando un ingegnere chiede aiuto per migliorare un parser, restringere una coda o interpretare l'output della profilazione in un percorso caldo nativo, la densità storica attorno al C++ rimane un serio vantaggio.

Ciò che un evento basato sui dati di mercato sperimenta realmente

È utile immaginare un evento relativo ai dati di mercato non come un’informazione in astratto ma come un peso fisico che si muove attraverso una macchina. Il pacchetto arriva. Deve essere ricevuto dallo stack di rete o dal gestore del feed, analizzato, mappato in una rappresentazione interna, applicato a una o più strutture di libri, osservato dalla logica della strategia, filtrato attraverso controlli di rischio e forse convertito in un ordine in uscita o in una cancellazione. Se tutto va bene, questa catena sembra istantanea. Se l'architettura è trascurata, il pacchetto acquista peso ad ogni passaggio.

Un’allocazione extra qui, una coda condivisa là, un passaggio di normalizzazione che copia più del dovuto, una struttura del libro elegante nel senso di un libro di testo ma fredda nella memoria, un percorso di registrazione pensato solo per sicurezza, un filo che migra nel momento sbagliato: nessuno di questi costi suona mitico preso da solo. Il loro pericolo risiede nell’accumulo e nella ripetizione. Gli ingegneri dell’HFT imparano a pensare in questo modo cumulativo perché il sistema punisce l’ottimismo. Un’inefficienza minima per evento diventa grande se moltiplicata per l’attività di mercato, la frequenza della strategia e l’importanza per il business di tempi di reazione prevedibili.

Questo è anche il motivo per cui il percorso caldo nel trading raramente è solo una funzione. È un'ecologia. I dati di mercato, la gestione dello stato, la pianificazione, la serializzazione, il rischio e la trasmissione interagiscono tra loro. Gli ingegneri che ottimizzano solo il circuito più affascinante, lasciando trascurati il ​​coordinamento e il layout, spesso producono sistemi che funzionano bene in frammenti e deludono nell'unico posto che conta: il percorso completo.

La latenza deterministica è una disciplina architettonica

L'espressione bassa latenza viene spesso utilizzata come se descrivesse una proprietà di una funzione. Nell’HFT serio, la bassa latenza è una proprietà dell’architettura. Emerge da come è strutturato l’intero sistema. I dati caldi dovrebbero rimanere caldi. La proprietà della memoria dovrebbe essere ovvia. I fili devono essere posizionati deliberatamente anziché lasciati andare alla deriva. Lo stato mutevole condiviso dovrebbe essere trattato con sospetto. Le code dovrebbero esistere perché sono necessarie, non perché fanno sembrare i diagrammi modulari. L’osservabilità dovrebbe essere sufficientemente economica da consentire al sistema di rimanere ispezionabile senza affogare nella propria diagnostica.

La disposizione dei dati è importante perché la macchina si muove ancora attraverso la memoria, non attraverso le intenzioni. Layout contigui, rappresentazioni di libri compatte e strutture che riflettono modelli di accesso piuttosto che il sentimento del programmatore valgono più di astrazioni intelligenti che sembrano riutilizzabili ma disperdono lo stato caldo ovunque. La disciplina dell'allocazione è importante perché la memoria dinamica sul percorso caldo non è semplicemente lenta in un certo senso medio; può anche creare jitter, contese e interazioni sorprendenti con il resto del runtime. Nell’HFT, il jitter è spesso il problema più umiliante.

Il threading merita la stessa serietà. Più thread non significano automaticamente maggiori prestazioni. A volte significano più coordinamento, più movimento della cache, più errori di affinità e più posti in cui il sistema operativo può diventare un coautore involontario. I sistemi di trading maturi fissano i thread deliberatamente, rispettano i confini NUMA ove rilevante e mantengono il numero di decisioni condivise basso quanto consentito dall’architettura. Ciò non rende il codice alla moda. Rende il comportamento più stabile, il che di solito è molto più prezioso.

Rete, analisi e manutenzione dei libri

Il percorso di networking nel trading merita il suo stesso rispetto perché è dove l’astrazione è più tentata di nascondersi. Un feed binario non è solo input. È un flusso di cambiamento di stato che deve essere interpretato fedelmente e rapidamente. Più veloce è il parser, meno spazio c'è per la confusione a valle. Meno allocazioni e ramificazioni esegue, più facile diventa capire per cosa sta pagando la macchina. Il codice di gestione dei mangimi spesso sembra austero proprio per questo motivo. Ha imparato, con dolore, quali forme di eleganza il mercato non premia.

Il mantenimento del portafoglio ordini ha un carattere simile. Un libro non ha valore perché teoricamente è bello. È prezioso perché può essere aggiornato, interrogato, riprodotto e ragionato sotto carico. La rigiocabilità conta qui più di quanto gli estranei a volte si aspettano. I team HFT imparano moltissimo riproducendo il traffico reale, confrontando il comportamento della strategia tra le revisioni e diagnosticando dove un sistema è diventato più lento o meno stabile. Una rappresentazione di un libro difficile da riprodurre o ispezionare può comunque apparire veloce in un test ristretto e tuttavia essere operativamente debole. Nel trading, veloce e diagnosticabile batte veloce e misterioso.

È qui che il C++ si adatta particolarmente bene. Consente alla stessa base di codice di parlare fluentemente per alimentare parser, strutture dati sensibili alla memoria, strumenti di profilazione e comportamento di basso livello del sistema operativo. Altri linguaggi possono partecipare ai sistemi di scambio, e molti lo fanno, ma quando il sottosistema in questione è il percorso caldo stesso, il C++ fornisce comunque una delle migliori combinazioni di controllo e supporto dell'ecosistema.

Rischio, replay e maturità operativa

È un errore immaginare l’HFT come pura velocità privata della governance. Il percorso più veloce al mondo è inutile se può inviare l’ordine sbagliato, non riuscire a ripristinare lo stato o diventare inspiegabile dopo un evento di mercato volatile. I buoni sistemi di trading mantengono quindi espliciti i controlli dei rischi, la gestione dei guasti provata e la riproduzione dell'infrastruttura vicina alla vita ingegneristica quotidiana. Questi non sono accessori burocratici. Fanno parte della competitività.

Una base di codice HFT sana di solito riflette questa maturità. Contiene osservabilità a buon mercato piuttosto che nessuna osservabilità. Contiene strumenti di riproduzione perché i team sanno che ciò che non può essere riprodotto non può essere migliorato con sicurezza. Contiene benchmark e profiler che esaminano l'intero percorso, non solo i microkernel selezionati con cura. Tratta la coerenza della distribuzione, le impostazioni del compilatore, la strategia di affinità e la configurazione della macchina come preoccupazioni ingegneristiche di prima classe. In altre parole, i migliori sistemi di trading non sono semplici pezzi di codice veloci. Sono ambienti tecnici disciplinati.

Questo è uno dei motivi per cui la stabilità spesso batte la pura intelligenza. Un piccolo miglioramento in un benchmark di laboratorio vale meno di un sistema ripetibile di cui si comprendono le code, la cui gestione del mangime è spiegabile e il cui comportamento strategico può essere ricostruito a posteriori. Gli ingegneri che entrano nell’HFT a volte si aspettano atti eroici. Ciò che invece spesso praticano le squadre mature è una sorta di calmo rigore. Tolgono le sorprese. Il mercato ne fornisce già abbastanza.

I miti comuni meritano di essere ritirati

Molti miti sopravvivono perché lusingano gli ingegneri. Si dice che le prestazioni HFT riguardino principalmente l'assemblaggio scritto a mano o le micro-ottimizzazioni esoteriche. In realtà, i successi più significativi derivano dall’architettura, dalla misurazione e dalla rimozione ripetuta dei rifiuti ordinari. Un altro sostiene che le strutture prive di serrature sono automaticamente superiori. A volte hanno esattamente ragione. A volte importano la complessità e i costi di ordinamento della memoria in luoghi in cui un progetto più semplice avrebbe funzionato meglio. Un terzo dice che più thread aiutano sempre. Nei sistemi a bassa latenza, la concorrenza aggiuntiva può ridurre la prevedibilità più velocemente di quanto non migliori il throughput.

Esiste anche un mito moderno secondo cui l'uso continuato del C++ nell'HFT deve essere principalmente un'inerzia storica. La storia certamente conta, ma l’inerzia da sola non sopravvive in un campo in cui i sistemi sono continuamente misurati rispetto al denaro e al tempo. Il C++ rimane perché i team continuano a scoprire che il linguaggio, i suoi strumenti e la cultura ingegneristica circostante si allineano ancora bene con le realtà della progettazione deterministica a bassa latenza. Se un altro linguaggio creasse costantemente risultati migliori sui percorsi commerciali più caldi, le aziende HFT se ne accorgerebbero. Hanno incentivi abbastanza forti per prestare attenzione.

Perché vale ancora la pena studiare questo dominio

Anche per gli ingegneri che non hanno mai lavorato in un’azienda commerciale, l’HFT rimane un valido insegnante perché rende difficile evitare la verità del sistema. Forza una stretta relazione tra codice e conseguenze. Insegna che la disposizione dei dati non è una decorazione, che le code non sono libere, che la latenza media può mentire, che la riproduzione è una forma di comprensione e che l’architettura è spesso l’ottimizzazione più importante. Queste lezioni vanno ben oltre il trading.

Il C++ continua a essere al centro di quella lezione perché consente all'ingegnere di mantenere un difficile equilibrio. È abbastanza espressivo da costruire sistemi sostanziali, sufficientemente di basso livello da esporre i costi in modo onesto e abbastanza vecchio da portare con sé una vasta eredità di strumenti e pratiche vissute. Questa combinazione è ancora importante in uno dei settori prestazionali più esigenti che abbiamo.

Se c’è qualcosa di motivante nell’HFT, non è la mitologia della velocità fine a se stessa. Ci ricorda che il software può essere reso preciso, misurabile e dignitoso sotto pressione. Il C++ rimane uno dei linguaggi in cui quella disciplina è ancora parlata più fluentemente.

Laboratorio pratico: crea un piccolo replay feed-to-book

Concludiamo costruendo un giocattolo in miniatura in stile HFT. Non farà soldi. Questo è eccellente. La maggior parte degli esempi di codice che promettono di fare soldi sono educativi nel peggiore dei modi.

Ciò che farà è più utile: riprodurre una sequenza di aggiornamenti di mercato in una piccola rappresentazione di un libro in memoria e segnalare la migliore domanda e offerta.

__CODICE_0__

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <limits>
#include <string>
#include <vector>

enum class Side { Bid, Ask };

struct Update {
    Side side;
    int price;
    int qty;
};

struct Book {
    std::vector<Update> bids;
    std::vector<Update> asks;

    void apply(const Update& u) {
        auto& side = (u.side == Side::Bid) ? bids : asks;
        auto it = std::find_if(side.begin(), side.end(), [&](const Update& x) {
            return x.price == u.price;
        });

        if (u.qty == 0) {
            if (it != side.end()) side.erase(it);
            return;
        }

        if (it == side.end()) {
            side.push_back(u);
        } else {
            it->qty = u.qty;
        }
    }

    int best_bid() const {
        int best = 0;
        for (const auto& b : bids) best = std::max(best, b.price);
        return best;
    }

    int best_ask() const {
        int best = std::numeric_limits<int>::max();
        for (const auto& a : asks) best = std::min(best, a.price);
        return best;
    }
};

int main() {
    std::vector<Update> replay{
        {Side::Bid, 10010, 5},
        {Side::Bid, 10020, 3},
        {Side::Ask, 10040, 4},
        {Side::Ask, 10035, 8},
        {Side::Bid, 10020, 0},
        {Side::Ask, 10035, 6},
        {Side::Bid, 10025, 7}
    };

    Book book;
    const auto t0 = std::chrono::steady_clock::now();

    for (const auto& u : replay) {
        book.apply(u);
    }

    const auto t1 = std::chrono::steady_clock::now();
    const auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count();

    std::cout << "best_bid=" << book.best_bid() << "\n";
    std::cout << "best_ask=" << book.best_ask() << "\n";
    std::cout << "replay_ns=" << ns << "\n";
}

Costruire

Su Linux o macOS:

g++ -O2 -std=c++20 -o tiny_book main.cpp
./tiny_book

Su Windows:

cl /O2 /std:c++20 main.cpp
.\main.exe

Cosa ti insegna questo

Anche questo minuscolo programma di riproduzione solleva rapidamente vere e proprie domande sull'HFT:

  • i livelli dei prezzi dovrebbero vivere in vettori, mappe, array o scale personalizzate?
  • cosa succede quando il replay passa da 7 aggiornamenti a 7 milioni?
  • quanto tempo occorre per gli aggiornamenti di stato rispetto al reporting?
  • dove compaiono le allocazioni se la struttura si espande dinamicamente?

L’esempio è piccolo, ma le domande non sono affatto piccole.

Attività di prova per appassionati

  1. Sostituisci la ricerca lineare in apply con una struttura che si adatta meglio e confronta i tempi di riproduzione.
  2. Genera un milione di aggiornamenti sintetici e misura il modo in cui la struttura ingenua si degrada.
  3. Aggiungi un thread produttore e un thread consumatore con una coda SPSC tra la riproduzione del feed e l'aggiornamento del libro, quindi confronta stabilità e complessità.
  4. Aggiungi il thread di riproduzione a un core su Linux e confronta la varianza da esecuzione a esecuzione.
  5. Aggiungi un percorso di registrazione deliberatamente rumoroso e osserva quanto velocemente una decisione di debug "innocua" contamina le misurazioni della latenza.

Questi esercizi sono umili, ed è proprio per questo che sono buoni. La vera ingegneria a bassa latenza è costruita a partire da molte strutture umili che vengono scelte con attenzione o rimpianti in seguito.

Riepilogo

Il C++ rimane centrale nel trading ad alta frequenza perché l’HFT non riguarda semplicemente la scrittura di funzioni veloci. Si tratta di costruire sistemi deterministici a bassa latenza lungo l’intero percorso, dai dati di mercato alla trasmissione degli ordini, e quindi di mantenere tali sistemi sufficientemente comprensibili per effettuare diagnosi sotto pressione. Questo lavoro dipende da una disposizione disciplinata dei dati, da un'allocazione contenuta, da un'attenta gestione dei thread, da una profilazione onesta, da una convalida riproducibile e da una cultura che valorizza la stabilità tanto quanto la velocità.

Questo è il motivo per cui il C++ continua a mantenere la sua posizione. Offre agli ingegneri il livello di controllo, profondità degli strumenti e pratica storica che questo dominio ancora premia. Altri linguaggi possono e contribuiscono allo scambio di stack, ma quando il problema è il percorso caldo stesso, il C++ rimane uno dei modi più efficaci che conosciamo per trasformare le prestazioni da uno slogan in una proprietà ingegneristica ripetibile.

Riferimenti

  1. Specifiche NASDAQ TotalView-ITCH: https://nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/NQTVITCHSpecification.pdf
  2. Documentazione DPDK: https://doc.dpdk.org/guides/
  3. Pagina man dell'API socket Linux: https://man7.org/linux/man-pages/man7/socket.7.html
  4. Documentazione sulla marcatura temporale di Linux: https://docs.kernel.org/networking/timestamping.html
  5. Infrastruttura orologio hardware Linux PTP: https://docs.kernel.org/driver-api/ptp.html
  6. Pagina man perf di Linux: https://man7.org/linux/man-pages/man1/perf.1.html
  7. Grafici delle fiamme di Brendan Gregg: https://www.brendangregg.com/flamegraphs.html
  8. Documentazione di Intel VTune Profiler: https://www.intel.com/content/www/us/en/docs/vtune-profiler/overview.html
Philip P.

Philip P. – Direttore tecnico

Torna al blog

Contatto

Inizia la conversazione

Bastano poche righe chiare. Descrivi il sistema, la pressione e la decisione che è bloccata. Oppure scrivi direttamente a midgard@stofu.io.

01 What the system does
02 What hurts now
03 What decision is blocked
04 Optional: logs, specs, traces, diffs
0 / 10000