In questa lezione del corso Trading System Event Driven con Python, iniziamo a progettare l’architettura event-driven di un trading system, uno dei modelli più realistici ed efficienti per il backtesting di strategie algoritmiche. A differenza degli approcci vettorializzati tradizionali con Python e Pandas, il paradigma event-driven replica in modo più preciso le dinamiche operative di un sistema di trading reale.
La natura vettoriale di Pandas consente di elaborare rapidamente grandi set di dati. Tuttavia, gli approcci di backtesting vettorializzato presentano alcune criticità quando simulano l’esecuzione dei trade. In questa lezione analizziamo la struttura base del sistema, i principali componenti coinvolti e il funzionamento del ciclo degli eventi. In questo modo poniamo le basi per costruire un motore di backtest scalabile e modulare in Python.
Software basati sugli Eventi
Prima di sviluppare un’architettura event-driven per un trading system, introduciamo i concetti fondamentali dei sistemi basati sugli eventi. I videogiochi rappresentano un classico esempio di questo tipo di software e offrono un contesto semplice per comprendere il funzionamento. Un videogioco contiene diversi componenti che interagiscono in tempo reale all’interno di un ambiente ad alto frame-rate. Il sistema gestisce queste interazioni eseguendo una serie di calcoli all’interno di un ciclo continuo, chiamato event-loop o game-loop.
A ogni tick del game-loop, il sistema richiama una funzione che acquisisce l’ultimo evento generato da un’azione precedente nel loop. A seconda della natura dell’evento — ad esempio la pressione di un tasto o il clic del mouse — il sistema esegue specifiche azioni che interrompono il ciclo oppure generano nuovi eventi.
Di seguito riportiamo un esempio di pseudo-codice dell’event-loop:
while True: # Esecuzione infinita del loop f
new_event = get_new_event() # ottengo l'ultimo evento
# A seconda del tipo di evento si esegue una azione
if new_event.type == "LEFT_MOUSE_CLICK":
open_menu()
elif new_event.type == "ESCAPE_KEY_PRESS":
quit_game()
elif new_event.type == "UP_KEY_PRESS":
move_player_north()
# ... e molti altri eventi
redraw_screen() # Update dell'output per fornire un'animazione
tick(50) # Pausa di 50 millisecondi
Il codice controlla continuamente la presenza di nuovi eventi ed esegue le azioni corrispondenti in base al tipo rilevato. In particolare, simula un sistema a risposta in tempo reale perché eseguiamo il codice in un ciclo infinito, verificando costantemente la comparsa di eventi. Questo approccio rappresenta esattamente ciò di cui abbiamo bisogno per simulare strategie di trading ad alta frequenza.
Perché abbiamo bisogno di un’Architettura Event-Driven
L’architettura event-driven offre numerosi vantaggi rispetto a un approccio vettorializzato:
- Riutilizzo del codice – Progettiamo un sistema basato sugli eventi in modo che possa funzionare sia per il backtesting storico sia per il live trading, modificando solo pochi componenti. I sistemi vettorializzati, invece, richiedono la disponibilità simultanea di tutti i dati per eseguire analisi statistiche.
- Bias di Look-Ahead – I sistemi event-driven eliminano il rischio di bias previsionali, poiché trattiamo l’acquisizione dei dati finanziari come un evento che richiede azioni specifiche. In questo modo, alimentiamo il sistema “istante dopo istante” con i dati di mercato, replicando fedelmente il comportamento di un sistema di gestione ordini e portafoglio.
- Realismo – I sistemi basati sugli eventi ci permettono di personalizzare l’esecuzione degli ordini e i relativi costi di transazione. Gestiamo facilmente market-order, limit-order, market-on-open (MOO) e market-on-close (MOC), costruendo un exchange handler su misura.
Svantaggi
Nonostante i numerosi vantaggi, questi sistemi presentano due principali svantaggi rispetto a quelli vettorializzati. Innanzitutto, risultano più complessi da progettare e testare. La presenza di più “parti mobili” aumenta la probabilità di introdurre bug. Per ridurre questo rischio, possiamo applicare metodologie di testing del software come il test-driven development.
In secondo luogo, il codice richiede più tempo per l’esecuzione rispetto ai sistemi vettorializzati, poiché non possiamo utilizzare operazioni vettoriali ottimizzate durante i calcoli. Affronteremo queste limitazioni nelle prossime lezioni.
Architettura Event-Driven di un Trading System
Per applicare un approccio event-driven, dobbiamo definire le componenti di base (o oggetti), ciascuna responsabile di un compito specifico:
- Event: la classe
Event
rappresenta la base del sistema event-driven. Include un attributo “tipo” (ad esempio “MARKET”, “SIGNAL”, “ORDER” o “FILL”) che stabilisce come trattiamo ogni evento nel ciclo di esecuzione. - Event Queue: utilizziamo una Queue Python per raccogliere tutti gli oggetti Event generati dagli altri moduli.
- DataHandler: la classe
DataHandler
, definita come classe astratta (ABC), fornisce un’interfaccia per gestire dati storici o real-time. Offre grande flessibilità , permettendo ai moduli della strategia e del portfolio di funzionare con entrambi i tipi di dati. Inoltre, genera nuovi MarketEvent nel loop del sistema. - Strategy: anche la
Strategy
è una classe ABC. Riceve i dati di mercato e produce i SignalEvent, che poi utilizziamo nel modulo Portfolio. Ogni SignalEvent contiene un ticker, una direzione (LONG o SHORT) e un timestamp. - Portfolio: questa classe gestisce ordini e posizioni della strategia, includendo anche la gestione del rischio (esposizione settoriale, dimensionamento delle posizioni). In versioni avanzate, possiamo delegare la gestione del rischio a una classe separata. La classe
Portfolio
prende i SignalEvent dalla coda e genera uno o più OrderEvent da reinserire nella stessa coda. - ExecutionHandler: la classe
ExecutionHandler
simula una connessione a un broker. Preleva gli OrderEvent dalla coda e li esegue, in simulazione o connessi a un broker reale. Dopo l’esecuzione, crea i FillEvent, che specificano cosa è stato scambiato, incluse commissioni, spread e slippage. - Loop – Il ciclo principale coordina tutti i componenti, indirizzando ciascun evento al modulo responsabile.
Implementazione
Questo schema rappresenta la struttura base di un motore di trading. Possiamo estendere il sistema in molti modi, soprattutto nella gestione del portafoglio. Inoltre, possiamo modellare diversi costi di transazione attraverso una gerarchia di classi personalizzate. Tuttavia, per ora evitiamo questa complessità . Vedremo più avanti come aggiungere ulteriori elementi di realismo.
Di seguito potete trovare il codice Python che mostra come il backtester funziona in pratica. Ci sono due loop nidificati all’interno del codice. Il loop esterno è usato per dare al backtester un impulso, o ritmo. Nel live trading questa è la frequenza con cui vengono acquisiti i nuovi dati di mercato. Per le strategie di backtesting questo non è strettamente necessario poiché il backtester utilizza i dati di mercato forniti in forma di drip-feed (vedi la riga bars.update_bars ())
.
Il ciclo interno gestisce effettivamente gli eventi dall’oggetto Queue. Gli Eventi specifici sono delegati al rispettivo componente e successivamente vengono aggiunti nuovi eventi alla coda. Quando la coda degli eventi è vuota, si riprende il ciclo esterno:
# Dichiarazione dei componenti e rispettive classi
bars = DataHandler(..)
strategy = Strategy(..)
port = Portfolio(..)
broker = ExecutionHandler(..)
while True:
# Update delle barre dei prezzi (codice specifico per il backtesting, opposto al live trading)
if bars.continue_backtest == True:
bars.update_bars()
else:
break
# Gestione degli eventi
while True:
try:
event = events.get(False)
except Queue.Empty:
break
else:
if event is not None:
if event.type == 'MARKET':
strategy.calculate_signals(event)
port.update_timeindex(event)
elif event.type == 'SIGNAL':
port.update_signal(event)
elif event.type == 'ORDER':
broker.execute_order(event)
elif event.type == 'FILL':
port.update_fill(event)
# pausa di 10 minuti
time.sleep(10 * 60)
Questo è lo schema di base di un’architettura event-driven di un trading system. Nella prossima lezione descriviamo la gerarchia della classe Events.
Il codice completo presentato in questa lezione, basato sul sistema di trading event-driven TQTradingSystem, è disponibile nel seguente repository GitHub: https://github.com/tradingquant-it/TQTradingSystem.”