← Torna agli articoli
July 2, 2026
5 min di lettura

La tassa del framework: quando la tua libreria di backtest è più lenta di un ingenuo ciclo pandas

La tassa del framework: quando la tua libreria di backtest è più lenta di un ingenuo ciclo pandas
#algotrading
#backtest
#prestazioni
#ricerca di parametri
#vettorizzazione
#benchmark
Part 2 of 10 · Collection
High-Performance Backtest Engines

Parte della serie "Backtest senza illusioni".

C'è un'assunzione comoda alla base della maggior parte dei progetti di algotrading: che una libreria di backtest matura e molto stellata sia veloce. Ha anni di contributi, un vero event loop, un modello di broker, uno schema di commissioni. Di sicuro batte quel ciclo pandas raffazzonato che avresti scritto tu stesso. Così la scegli, ci colleghi la tua strategia e avvii una ricerca di parametri — qualche migliaio di configurazioni, un lavoro notturno. Torni la mattina ed è ancora in esecuzione.

Abbiamo messo a confronto otto motori di backtest su un'unica identica ricerca di parametri e abbiamo scoperto qualcosa che dovrebbe cambiare il modo in cui scegli uno strumento per la ricerca. Due dei framework event-driven open-source più popolari — backtrader e bt — hanno eseguito la ricerca più lentamente di un ingenuo ciclo pandas che abbiamo scritto come riferimento usa e getta. Non un po' più lenti. backtrader ha impiegato all'incirca 2.5× il tempo del riferimento pandas; bt, circa 4.7×. Nel frattempo un motore vettorizzato/compilato sullo stesso lavoro è stato circa 13,000× più veloce di bt.

Questa è la tassa del framework. I backtester popolari sono costruiti per una esecuzione onesta — una strategia, un dataset, riempimenti accurati, un broker che si comporta come un broker. È esattamente ciò che vuoi per una validazione finale o un controllo di parità con il live. Ed è esattamente lo strumento sbagliato per la cosa su cui la ricerca in algotrading spende in realtà il suo tempo: eseguire la stessa strategia diecimila volte con manopole leggermente diverse. Questo articolo misura la tassa, ne spiega il meccanismo e fornisce una regola decisionale su quando un framework di backtest "vero" è la scelta sbagliata.

Ogni numero qui proviene da un unico banco di prova riproducibile (benchmarks/bench_oss_engines.py, commit 250dbb5) su un carico di lavoro identico e bloccato in parità. Dove non abbiamo eseguito noi stessi un motore, lo diciamo e lo mettiamo in una sezione dedicata all'onestà anziché inventare una cifra.

Una esecuzione contro diecimila

Lo stesso calcolo di strategia che scorre lungo tre percorsi divergenti: un binario event-driven in rosso-arancio costretto a fermarsi a ogni stazione-barra, un albero di pesi/ribilanciamento di nodi algo in ambra, e un unico fascio vettorizzato smeraldo-ciano che risolve l'intera serie in una volta sola — il paradigma è destino per la velocità della ricerca

Il fatto che definisce la ricerca di parametri è che il motore gira migliaia di volte ma l'analisi avviene una volta sola. Qualunque costo fisso paghi per impostare un backtest — costruire l'event loop, istanziare un broker, allocare un oggetto per barra — lo paghi su ogni singola combinazione. Un costo invisibile in un'esecuzione ("chi se ne importa di 6 secondi?") diventa l'intero conto di una ricerca ("6 secondi × 10,000 = 16.6 ore").

I motori di backtest si dividono in tre paradigmi, e il paradigma è destino per le prestazioni di ricerca:

  1. Event-driven — il motore procede barra per barra, emettendo eventi, chiamando la tua callback next()/onBars(), instradando gli ordini attraverso un oggetto broker. Questa è l'architettura di backtrader, backtesting.py, PyAlgoTrade, zipline e nautilus_trader. Rispecchia il modo in cui il trading dal vivo funziona davvero, il che è precisamente il motivo per cui è considerato affidabile per il realismo — e precisamente il motivo per cui è lento: l'overhead Python per barra viene pagato 150,000 volte per combinazione.
  2. Pesi/ribilanciamentobt si colloca qui. Gli passi una matrice di pesi target e lui ribilancia nelle date che specifichi. Nessuna callback per barra, ma comunque un grafo di oggetti per evento (un albero di algo, un registro delle transazioni) valutato in Python.
  3. Vettorizzato / compilato — l'intera strategia è espressa come operazioni su array (vectorbt), un kernel compilato JIT (numba) o codice nativo (Rust, un kernel GPU MLX). Non c'è alcun Python per barra. Il ciclo, se c'è, gira a velocità macchina.

Il resto di questo articolo è la conseguenza empirica di quella tassonomia. Abbiamo costruito un unico carico di lavoro, fatto in modo che ogni motore facesse in modo dimostrabile lo stesso lavoro, e ne abbiamo cronometrato il tempo.

Il carico di lavoro: una strategia, ottanta manopole, identico per tutti

Un benchmark è onesto solo se ogni motore sta facendo lo stesso lavoro. Il nostro è deliberatamente semplice — un carico di lavoro in cui l'unica cosa che differisce tra i motori è il motore stesso.

  • Dati. Un'unica serie sintetica di chiusure con moto browniano geometrico: 150,000 barre, seed=42, volatilità per barra sigma=0.0008, x0=30000. Deterministica, così chiunque può riprodurla bit per bit. Solo chiusura per costruzione — l'OHLC è impostato alla chiusura su ogni gamba, perché la strategia è un incrocio di chiusure.
  • Strategia. Un incrocio di Hull Moving Average: una HMA di lunghezza L contro una variante HMA-di-un-terzo più veloce (HMA contro HMA3). Sempre sul mercato, si inverte long/short a ogni incrocio. Questo è un indicatore reale e non banale — due medie mobili ponderate annidate più uno smoother a finestra radice quadrata — non una SMA giocattolo, quindi il lavoro per barra è rappresentativo.
  • La ricerca. 80 lunghezze HMA che spaziano da 6..200. È il "diecimila esecuzioni" reso abbastanza piccolo da misurarlo direttamente: 80 combinazioni indipendenti, ciascuna un backtest completo su 150k barre.
  • Costi. Una commissione andata e ritorno dello 0.09%, divisa per lato per i motori che modellano le commissioni per lato. Riempimento nella stessa barra a close[i] — il segnale sulla barra i viene eseguito alla chiusura di quella barra, la convenzione che usa il nostro motore di produzione.

Il timer per combinazione avvolge esattamente due cose: il precalcolo HMA in numpy e l'esecuzione del motore. La configurazione che è genuinamente una tantum (caricamento dati, costruzione degli oggetti barra) resta fuori dal timer. C'è un'esecuzione di riscaldamento, poi ripetizioni best-of-N e — poiché i motori event-driven sono abbastanza lenti da far sì che 80 combinazioni complete richiederebbero da molti minuti a oltre un'ora — cronometriamo un campione uniforme della griglia ed estrapoliamo linearmente. Le combinazioni sono indipendenti, quindi l'estrapolazione lineare è esatta in valore atteso; la stessa convenzione è applicata al riferimento pandas, così nessun motore ne è avvantaggiato.

Parità: dimostrare che tutti fanno lo stesso lavoro

Ecco la trappola in cui cade un ingenuo confronto tra motori: un motore "veloce" potrebbe semplicemente fare di meno. Se backtrader registra 2,700 operazioni e il tuo motore vettorizzato ne registra 40, il motore vettorizzato non è più veloce — è sbagliato, e il confronto è privo di significato.

Quindi blocchiamo il confronto con un controllo di parità del conteggio operazioni. A L=104, il riferimento numpy produce esattamente 2,707 operazioni chiuse. Ogni motore deve riprodurre quel valore entro una tolleranza di ±1, altrimenti l'esecuzione si interrompe con un'asserzione work-parity FAILED. La tolleranza esiste solo perché i motori sono in disaccordo sulle convenzioni contabili — se la posizione aperta finale viene chiusa forzatamente e conteggiata, se l'ingresso iniziale è un'"operazione" — non sulle operazioni stesse:

Motore Operazioni riportate @ L=104 Convenzione
riferimento numpy 2707 andate e ritorni chiusi
backtesting.py 2708 +1: posizione finale chiusa forzatamente alla fine
backtrader 2707 posizione aperta finale non conteggiata
bt 2708 +1: ingresso iniziale conteggiato come transazione
PyAlgoTrade 2708 +1: ingresso iniziale conteggiato come riempimento

Ogni motore atterra su 2707 ± 1. Qualunque risultino essere le differenze di velocità, non sono un artefatto di un motore che salta silenziosamente del lavoro. Questa è la disciplina che ci permette di mettere un framework event-driven e un kernel GPU nella stessa tabella facendo sul serio.

I risultati

Ecco l'intera tabella, ordinata dal più veloce al più lento. combos/s è il throughput; l'ultima colonna è quanto tempo richiede l'intera ricerca di 80 combinazioni. La riga di riferimento è M0 — l'ingenuo motore pandas, un ciclo for sulle barre con contabilità scalare, la cosa che scriveresti in un pomeriggio e getteresti via. Tutto ciò che è più lento di quel riferimento è in grassetto.

Motore combos/s Paradigma Ricerca completa 80 combinazioni
kernel GPU MLX 779 vettorizzato (GPU Apple) 0.10 s
Rust nativo ~350 compilato 0.23 s
mp + numba 246 JIT compilato + multiprocesso 0.33 s
vectorbt 56.9 vettorizzato (numpy/numba) 1.4 s
numba (single core) 39.7 JIT compilato 2.0 s
backtesting.py 1.42 event-driven 56 s
PyAlgoTrade 0.51 event-driven 2.6 min
M0 — pandas ingenuo + ciclo 0.28 riferimento scalare 4.8 min
backtrader 0.11 event-driven 12.7 min
bt 0.06 pesi / ribilanciamento 22.5 min

Leggi la tabella dall'alto verso il basso e il paradigma si ordina da solo: i primi cinque sono tutti vettorizzati o compilati, gli ultimi cinque sono tutti event-driven o basati su grafo di oggetti — con l'ingenuo ciclo pandas che si colloca sopra due framework maturi e popolari. Lo scarto dall'alto verso il basso è di quattro ordini di grandezza. Sull'identico carico di lavoro da 2,707 operazioni, il kernel MLX finisce la ricerca in un decimo di secondo; bt ha bisogno di ventidue minuti e mezzo. È un fattore di circa 13,000×.

Lo scandalo al centro della tabella

Il paradosso al centro della tabella: un semplice esecutore di ciclo-for ingenuo grigio-blu che taglia il traguardo davanti a due elaborate e pesanti macchine-framework event-driven cariche di funzionalità, incandescenti di rosso-arancio, che arrancano ancora dietro, appesantite da modelli di broker e stack di analizzatori — l'umile riferimento che batte i framework maturi sull'identico carico di lavoro da 2,707 operazioni

I numeri che catturano l'occhio sono agli estremi, ma il risultato istruttivo è al centro: backtrader (0.11 combos/s) e bt (0.06 combos/s) sono entrambi più lenti del riferimento pandas ingenuo (0.28 combos/s).

Merita di essere assorbito. M0 non è un motore ingegnoso. È un ciclo for Python che indicizza in un DataFrame, tiene traccia di una posizione e della liquidità in semplici scalari, e aggiunge le operazioni a una lista — il "controllo" deliberatamente non ottimizzato che abbiamo incluso per avere qualcosa di palesemente scadente da battere. L'accesso per riga di pandas è notoriamente lento, e ci abbiamo puntato. Eppure due delle librerie di backtest più raccomandate dell'ecosistema gli soccombono: backtrader di 2.5×, bt di 4.7×.

La sfumatura che mantiene tutto onesto: non ogni motore event-driven è più lento di pandas. backtesting.py (1.42 combos/s) batte il riferimento di 5×, perché è un event loop snello, basato su numpy, che mantiene al minimo la creazione di oggetti per barra. Anche PyAlgoTrade (0.51) supera di poco il riferimento. Quindi "event-driven" non è automaticamente una condanna a morte — ma più pesante è il macchinario per barra, peggio va, e backtrader e bt portano il macchinario più pesante qui. Il paradigma fissa il soffitto; l'implementazione decide dove sotto quel soffitto atterri.

Il punto non è che queste siano cattive librerie. Il modello di broker di backtrader e il design ad albero-di-algo di bt esistono per comprarti correttezza ed espressività — gestione realistica degli ordini, ribilanciamento del portafoglio, analizzatori. Quelle funzionalità hanno un costo a runtime, e quel costo è invisibile quando esegui una volta sola. Su una ricerca è l'intera storia.

Perché i motori event-driven pagano la tassa

Dove viene riscossa la tassa del framework, per barra: una singola iterazione-barra esplosa nelle sue componenti costose — un oggetto barra materializzato, un frame di callback, un broker che instrada un ordine, un registro che viene aggiunto — ognuno un casello incandescente rosso-arancio che screma una moneta, ripetuto lungo un vasto corridoio che si allontana di dodici milioni di iterazioni-barra identiche

Il meccanismo non è misterioso. Un backtest event-driven, per barra, fa qualcosa del genere:

  1. Avanza l'orologio, ritaglia la barra successiva dal feed di dati e la materializza come oggetto (una Bar, una Line, un dict).
  2. Attiva una callback nel codice utente (next(), onBars()), che è una chiamata di funzione Python con il proprio frame.
  3. All'interno della callback, interroga lo stato del broker/posizione, di nuovo tramite chiamate di metodo e ricerche di attributi.
  4. Se viene creato un ordine, instradalo attraverso il broker: convalidalo, verifica margine/liquidità, programma un riempimento, muta un oggetto portafoglio, aggiungi a un registro delle transazioni.
  5. Aggiorna analizzatori, osservatori e qualunque contabilità il framework mantenga.

Ora moltiplica per 150,000 barre, poi per 80 combinazioni: dodici milioni di iterazioni-barra per ricerca, ognuna una manciata di allocazioni di oggetti a livello Python e dispatch dinamici. L'overhead per operazione di Python — da decine a centinaia di nanosecondi per una ricerca di attributo o una piccola allocazione — è banale una volta e rovinoso dodici milioni di volte. Il caso bt è una variante della stessa malattia: anche se ribilancia solo nelle date di operazione anziché a ogni barra, ogni ribilanciamento valuta un albero di oggetti algo e tocca un registro di portafoglio basato su pandas, e ce ne sono 2,707 per combinazione, per 80.

L'ingenuo ciclo pandas batte backtrader e bt per una ragione brutale: fa di meno per barra. Salta il broker, gli oggetti evento, lo stack di analizzatori, la macchina a stati di instradamento degli ordini. Paga la brutta tassa per riga di pandas, ma quell'unica brutta tassa è comunque più economica dell'ordinata, completa di funzionalità, tassa a oggetto-per-evento del framework. Quando riduci un backtest a "posizione × rendimento-successivo − commissioni", la maggior parte di ciò che un framework fa per barra è overhead che non stai usando durante una ricerca.

Ed ecco la trappola: l'overhead è il motivo per cui hai scelto il framework. Volevi il broker realistico. Volevi gli analizzatori. Durante la validazione finale, li vuoi tutti. Durante una ricerca di 10,000 combinazioni in cui leggi un solo obiettivo scalare all'altro capo, stai pagando una limousine per fare giri di pista.

L'altro capo della tabella: vettorizzato e compilato

La parte alta della tabella è ciò che accade quando elimini del tutto il Python per barra.

  • vectorbt (56.9 combos/s) esprime l'intera strategia come operazioni su array numpy/numba. Non c'è alcun ciclo di barre in Python — il segnale, la posizione, il PnL sono tutti a livello di array. Esegue la ricerca in 1.4 secondi contro i 22.5 minuti di bt: circa 950× più veloce sullo stesso lavoro. (Trattiamo il design di vectorbt più in profondità nella panoramica di vectorbt e nel più ampio confronto pandas-vs-polars.)
  • numba (39.7) è il ciclo pandas, invariato nella forma, compilato JIT in codice nativo. Stesso algoritmo di M0, ma @njit trasforma 0.28 combos/s in 39.7 — un acceleramento di ~140× da un decoratore, perché l'overhead dell'interprete che dominava il ciclo scalare semplicemente evapora.
  • mp + numba (246) esegue il kernel compilato su tutti i core della CPU. Le combinazioni sono imbarazzantemente parallele — ognuna è indipendente — quindi il multiprocessing scala quasi linearmente sopra il JIT.
  • Rust nativo (~350) rimuove l'ultimo residuo di collante Python: l'intera ricerca è nativa.
  • GPU MLX (779) mappa la ricerca su un kernel GPU su silicio Apple. 80 combinazioni diventano 80 corsie parallele di aritmetica; la ricerca finisce prima che tu rilasci il tasto invio.

Due cose vale la pena nominare con precisione. Primo, numba dimostra che il paradigma conta più del linguaggio. M0 e numba eseguono lo stesso algoritmo — la differenza è puramente che uno è Python per barra interpretato e l'altro è compilato. Questa è l'intera tassa del framework in un A/B controllato: ~140× per aver eliminato l'interprete dal ciclo interno. Secondo, il salto da numba (39.7) a mp+numba (246) a MLX (779) non riguarda più affatto il motore — riguarda l'orchestrazione e l'hardware. Una volta sparita la tassa per barra, la velocità diventa una questione di quante combinazioni esegui in parallelo e su quale silicio. Percorriamo l'intera progressione nella scala di velocità dei motori di backtest, e il motivo per cui l'ultimo miglio è dominato dal costo di processo/serializzazione nell'articolo sulla tassa IPC.

Cosa non abbiamo misurato (e perché te lo diciamo)

La credibilità di un benchmark vive in ciò che si rifiuta di falsificare. Abbiamo eseguito otto motori dall'inizio alla fine in parità. Su diversi framework noti non abbiamo messo un numero, e preferiamo elencarli onestamente anziché estrapolare una cifra che non abbiamo misurato:

  • zipline / zipline-reloaded — event-driven, lignaggio Quantopian. Setup pesante (un intero calendario di trading e un bundle di dati), che rende delicato un timing per combinazione mela-con-mela. Architetturalmente si colloca con backtrader nel campo event-driven; ci aspetteremmo che stia vicino a quel capo della tabella, ma non lo abbiamo dimostrato.
  • nautilus_trader — event-driven con un core Rust/Cython, progettato esplicitamente per la parità con il live. Il suo core è compilato, quindi è il motore event-driven più probabile a non pagare l'intera tassa Python — una misurazione genuinamente interessante che non abbiamo ancora eseguito.
  • QuantConnect Lean — basato su C#, un runtime del tutto diverso; non direttamente comparabile in un banco di prova Python.
  • Jesse — event-driven, orientato alle cripto; ne abbiamo esaminato il design in una nota separata ma non l'abbiamo messo a benchmark qui.
  • QSTrader — event-driven, orientato al portafoglio; stesse riserve di paradigma.
  • fastquant — ci abbiamo provato; l'installazione/API era rotta nel nostro ambiente, quindi non c'è un numero. Non ne indovineremo uno.

Due riserve oneste sui numeri che riportiamo davvero. Le cifre di vectorbt, numba, mp+numba, Rust-nativo e MLX provengono dalla nostra scala di motori sull'identico carico di lavoro, non dal banco OSS che ha prodotto le quattro righe event-driven — è lo stesso carico di lavoro ma un diverso apparato di misurazione, e la cifra Rust-nativo è un approssimativo ~350, non una misurazione precisa. E il combos/s assoluto è specifico dell'hardware; ciò che viaggia è l'ordinamento e i rapporti, che sono abbastanza grandi (13,000× dall'alto verso il basso, 2.5–4.7× per l'inversione pandas-vs-framework) da far sì che nessuna ragionevole differenza di hardware li ribalti.

In difesa dei motori event-driven

Il motore event-driven nel suo ruolo appropriato: una singola meticolosa esecuzione di validazione ad alta fedeltà di un'unica strategia sopravvissuta, resa come uno strumento preciso smeraldo-e-ciano che modella un riempimento realistico, rispettando il margine e il ciclo di vita dell'ordine — lo stesso macchinario per barra che era puro spreco durante la ricerca mostrato qui come artigianato prezioso, una singola attenta prova generale prima del deployment

Sarebbe facile leggere tutto questo come "i backtester event-driven sono cattivi". È la lezione sbagliata, e ingiusta.

I motori event-driven sono costruiti per un lavoro diverso, e in quello sono bravi. Il broker per barra, il ciclo di vita dell'ordine, la logica di riempimento, gli analizzatori — quel macchinario esiste per far sì che un backtest assomigli il più possibile al trading dal vivo. Quando il tuo obiettivo è una singola, affidabile prova generale di una strategia che stai per mettere in produzione, vuoi che il motore sudi ogni riempimento, modelli esecuzioni parziali, rispetti il margine e si rifiuti di lasciarti operare a prezzi che non avresti potuto ottenere. Quella fedeltà è il prodotto. Il suo costo a runtime è il prezzo del realismo, e per una singola esecuzione il prezzo è trascurabile.

Il fallimento non è il motore; è usarlo per la fase sbagliata. La ricerca ha due fasi distinte con requisiti opposti:

  • La ricerca vuole throughput. Stai esplorando un paesaggio, la maggior parte del quale è spazzatura, e devi valutare migliaia di punti per trovare i pochi che meritano un secondo sguardo. La fedeltà per punto conta a malapena — stai classificando, non mettendo in produzione. Qui la tassa del framework è puro spreco.
  • La validazione vuole fedeltà. Hai una manciata di candidati e devi sapere, il più precisamente possibile, se sopravvivono a esecuzione realistica, commissioni, slippage e le trappole di look-ahead che gonfiano i rendimenti sulla carta. Qui il motore event-driven guadagna il suo costo.

L'errore che la tassa del framework punisce è eseguire la tua ricerca sul tuo motore di validazione — pagando tariffe da limousine per esplorare un paesaggio del quale butterai via il 99%.

La regola decisionale

La conclusione pratica si comprime in un'unica decisione:

Cerca su un motore vettorizzato/compilato. Valida i sopravvissuti su uno event-driven.

Concretamente:

  1. Costruisci o prendi in prestito un kernel veloce per la ricerca. vectorbt se lo vuoi pronto all'uso; un ciclo compilato con numba se la tua strategia non si vettorizza in modo pulito (il solo decoratore @njit qui ha comprato ~140×). Fai passare l'intero spazio dei parametri attraverso di esso.
  2. Non eseguire mai una grande ricerca su backtrader, bt, zipline o qualsiasi pesante framework event-driven. Se una ricerca su uno di questi è il tuo collo di bottiglia, la soluzione non è una macchina più grande — è il motore sbagliato. Persino l'ingenuo ciclo pandas ne batterebbe due.
  3. Promuovi una lista ristretta al motore event-driven per un controllo di fedeltà. Prendi la manciata di sopravvissuti e rieseguili sul motore realistico, dove il modello di broker e la logica di riempimento possono esporre problemi che il kernel veloce aveva astratto via.
  4. Imponi la parità tra i due. Il motore veloce e il motore di fedeltà devono concordare su conteggi delle operazioni e PnL su una configurazione fissa (il nostro controllo ±1 operazione a L=104), altrimenti la ricerca e la validazione stanno misurando strategie diverse e l'intera pipeline è una menzogna.

Questa è la stessa architettura a due velocità che compare ogni volta che ricerca e validazione hanno profili di costo opposti, ed è il motivo per cui il nostro stack mantiene un percorso veloce vettorizzato/compilato per la ricerca di parametri e riserva il macchinario pesante alla valutazione dell'obiettivo finale e ai controlli di plateau.

Punti chiave

  1. Popolare ≠ veloce per le ricerche. Su un unico identico carico di lavoro bloccato in parità (150k barre, 80 combinazioni di incrocio HMA, 2,707 operazioni), backtrader (0.11 combos/s) e bt (0.06) hanno entrambi girato più lentamente di un ingenuo ciclo pandas (0.28). Un framework maturo e molto stellato non è automaticamente la scelta veloce.
  2. La tassa del framework è per barra, e una ricerca la moltiplica. Dodici milioni di iterazioni-barra per ricerca, ognuna che porta un oggetto evento, una callback e un andata-e-ritorno del broker. Un costo invisibile in un'esecuzione è l'intero conto attraverso diecimila.
  3. Il paradigma fissa il soffitto. I motori vettorizzati/compilati (vectorbt 56.9, numba 39.7, mp+numba 246, Rust nativo ~350, MLX 779) battono i motori event-driven di due-quattro ordini di grandezza — fino a ~13,000× dall'alto verso il basso. Lo stesso algoritmo, semplicemente compilato JIT, è andato 140× più veloce.
  4. Non tutti i motori event-driven sono uguali. backtesting.py (1.42) e PyAlgoTrade (0.51) battono comunque il riferimento ingenuo; la tassa scala con quanto è pesante il macchinario per barra. L'implementazione decide dove sotto il soffitto atterri.
  5. Due motori, due fasi. Cerca su un kernel vettorizzato/compilato; valida i sopravvissuti sul realistico motore event-driven. Imponi la parità di conteggio operazioni/PnL tra loro così che entrambi misurino la stessa strategia.
  6. Sii onesto su ciò che hai misurato. Abbiamo messo a benchmark otto motori in parità ed elencato quelli che non abbiamo (zipline, nautilus_trader, Lean, Jesse, QSTrader e l'installazione di fastquant che non voleva partire) anziché inventare numeri per loro.

Il riassunto scomodo: se una ricerca di parametri è il tuo collo di bottiglia, il problema probabilmente non è la tua macchina né la tua strategia. È che stai eseguendo una ricerca su un motore che è stato costruito per una singola esecuzione onesta — e saresti più veloce con il ciclo pandas che eri troppo imbarazzato a tenere.

Il banco di prova completo, le asserzioni di parità e i risultati JSON grezzi per motore vivono in benchmarks/bench_oss_engines.py e benchmarks/results_oss/ al commit 250dbb5. Per il capo compilato/GPU della scala, vedi la scala di velocità dei motori di backtest e l'analisi della tassa IPC.

Disclaimer: le informazioni fornite in questo articolo hanno solo scopo didattico e informativo e non costituiscono consulenza finanziaria, di investimento o di trading. Il trading di criptovalute comporta un rischio significativo di perdita.

Autori

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Resta un Passo Avanti al Mercato

Iscriviti alla nostra newsletter per approfondimenti esclusivi sul trading con IA, analisi di mercato e aggiornamenti sulla piattaforma.

Rispettiamo la tua privacy. Annulla l'iscrizione in qualsiasi momento.