Nel precedente articolo abbiamo introdotto la struttura base di un ambiente di backtesting event-driven. Il resto di questa serie di articoli si concentrerà su ciascuna delle gerarchie di classi che costituiscono il sistema generale. In questo articolo i descrivono gli Eventi e come questi possono essere usati per scambiare informazioni tra gli oggetti.
Come discusso nel precedente articolo, il sistema di trading utilizza due loop: uno esterno e uno interno. Il loop interno gestisce l’acquisizione degli eventi da una coda in memoria, e il loro smistamento verso gli specifici componenti che gestiscono la conseguente azione.
Gli Eventi
- MarketEvent: viene attivato quando il loop esterno inizia un nuovo “impulso”. Si verifica quando l’oggetto
DataHandler
riceve un nuovo aggiornamento dei dati di mercato per tutti i simboli che sono attualmente monitorati. Viene utilizzato per attivare l’oggettoStrategy
che genera nuovi segnali di trading. L’oggetto Event contiene semplicemente un’identificazione che si tratta di un evento di mercato, senza altre strutture. - SignalEvent: l’oggetto
Strategy
utilizza i dati di mercato per creare nuovi SignalEvent.SignalEvents
contiene un simbolo ticker, il timestamp di quando è stato generato e una direzione (long o short). I SignalEvents sono utilizzati dall’oggettoPortfolio
come consigli su come effettuare i trade. - OrderEvent: quando un oggetto
Portfolio
riceve iSignalEvents
, questi sono valutati nel contesto generale del portfolio, in termini di rischio e dimensionamento della posizione. Questo processo genera un OrderEvents che verrà inviato a unExecutionHandler
. - FillEvent: dopo la ricezione di un
OrderEvent
, l’ExecutionHandler
deve eseguire l’ordine. Una volta che un ordine è stato eseguito, genera unFillEvent
, che descrive il costo di acquisto o vendita, nonché i costi di transazione, come le commissioni o lo slippage.
La classe Event
La classe genitore è chiamata
Event
. È una classe base e non fornisce alcuna funzionalità o interfaccia specifica. Nelle implementazioni successive gli oggetti Event svilupperanno una maggiore complessità e quindi stiamo definendo la progettazione di tali sistemi creando una gerarchia di classi.
# event.py
class Event(object):
"""
Event è la classe base che fornisce un'interfaccia per tutti
i tipi di sottoeventi (ereditati), che attiverà ulteriori
eventi nell'infrastruttura di trading.
"""
pass
La classe MarketEventi
La classe
MarketEvent
eredita da Event
e prevede semplicemente di autoidentificare l’evento come di tipo “MARKET”.
# event.py
class MarketEvent(Event):
"""
Gestisce l'evento di ricezione di un nuovo aggiornamento dei
dati di mercato con le corrispondenti barre.
"""
def __init__(self):
"""
Inizializzazione del MarketEvent.
"""
self.type = 'MARKET'
La classe SignalEvent
La classe
SignalEvent
richiede un simbolo ticker, un timestamp della generazione e una direzione per “avvisare” un oggetto Portfolio
.
# event.py
class SignalEvent(Event):
"""
Gestisce l'evento di invio di un Segnale da un oggetto Strategia.
Questo viene ricevuto da un oggetto Portfolio e si agisce su di esso.
"""
def __init__(self, symbol, datetime, signal_type):
"""
Inizializzazione del SignalEvent.
Parametri:
symbol - Il simbolo del ticker, es. 'GOOG'.
datetime - Il timestamp al quale il segnale è stato generato.
signal_type - 'LONG' o 'SHORT'.
"""
self.type = 'SIGNAL'
self.symbol = symbol
self.datetime = datetime
self.signal_type = signal_type
La classe OrderEvent
La classe
OrderEvent
è leggermente più complessa rispetto alla SignalEvent poiché contiene un campo quantità, oltre alle già citate proprietà di SignalEvent
. La quantità è determinata dai vincoli del portafoglio. Inoltre, OrderEvent ha un metodo print_order()
, utilizzato per inviare le informazioni alla console, se necessario.
# event.py
class OrderEvent(Event):
"""
Gestisce l'evento di invio di un ordine al sistema di esecuzione.
L'ordine contiene un simbolo (ad esempio GOOG), un tipo di ordine
(a mercato o limite), una quantità e una direzione.
"""
def __init__(self, symbol, order_type, quantity, direction):
"""
Inizializza il tipo di ordine, impostando se è un ordine a mercato
('MKT') o un ordine limite ('LMT'), la quantità (integral)
e la sua direzione ('BUY' or 'SELL').
Parametri:
symbol - Lo strumento da tradare.
order_type - 'MKT' o 'LMT' per ordine Market or Limit.
quantity - Intero non negativo per la quantità.
direction - 'BUY' o 'SELL' per long o short.
"""
self.type = 'ORDER'
self.symbol = symbol
self.order_type = order_type
self.quantity = quantity
self.direction = direction
def print_order(self):
"""
Stampa dei valori che compongono l'ordine.
"""
print
"Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s" % \
(self.symbol, self.order_type, self.quantity, self.direction)
La classe FillEvent
La classe
FillEvent
è l’evento con la maggiore complessità. Contiene un timestamp di quando è stato effettuato un ordine, il simbolo dell’ordine e l’exchange su cui è stato eseguito, la quantità di azioni negoziate, il prezzo effettivo dell’acquisto e la commissione sostenuta.
La commissione viene calcolata utilizzando le commissioni di Interactive Brokers. Per l’azionario statunitenze questa commissione è pari ad un minimo di 1 USD per ordine, con una tariffa fissa di 0,005 USD per azione.
# event.py
class FillEvent(Event):
"""
Incorpora il concetto di un ordine eseguito, come restituito
da un broker. Memorizza l'effettiva quantità scambiata di
uno strumento e a quale prezzo. Inoltre, memorizza
la commissione del trade applicata dal broker.
"""
def __init__(self, timeindex, symbol, exchange, quantity,
direction, fill_cost, commission=None):
"""
Inizializza l'oggetto FillEvent. Imposta il simbolo, il broker,
la quantità, la direzione, il costo di esecuzione e una
commissione opzionale.
Se la commissione non viene fornita, l'oggetto Fill la calcola
in base alla dimensione del trade e alle commissioni di
Interactive Brokers.
Parametri:
timeindex - La risoluzione delle barre quando l'ordine è stato eseguito.
symbol - Lo strumento che è stato eseguito.
exchange - Il broker/exchange dove l'ordine è stato eseguito.
quantity - La quantità effettivamente scambiata.
direction - La direzione dell'esecuzione ('BUY' o 'SELL')
fill_cost - Il valore nominale in dollari.
commission - La commissione opzionale inviata da IB.
"""
self.type = 'FILL'
self.timeindex = timeindex
self.symbol = symbol
self.exchange = exchange
self.quantity = quantity
self.direction = direction
self.fill_cost = fill_cost
# Calcolo della commissione
if commission is None:
self.commission = self.calculate_ib_commission()
else:
self.commission = commission
def calculate_ib_commission(self):
"""
Calcolo delle commisioni di trading basate sulla struttura
delle fee per la API di Interactive Brokers, in USD.
Non sono incluse le fee di exchange o ECN.
Basata sulla "US API Directed Orders":
https://www.interactivebrokers.com/en/index.php?f=commission&p=stocks2
"""
full_cost = 1.3
if self.quantity <= 300:
full_cost = max(0.35, 0.0035 * self.quantity)
elif self.quantity <= 3000:
full_cost = max(0.35, 0.002 * self.quantity)
elif self.quantity <= 20000:
full_cost = max(0.35, 0.0015 * self.quantity)
elif self.quantity <= 100000:
full_cost = max(0.35, 0.001 * self.quantity)
else: # Maggiore di 100 mila azioni
full_cost = max(0.35, 0.0005 * self.quantity)
# full_cost = min(full_cost, 1.0 / 100.0 * self.quantity * self.fill_cost)
return full_cost
DataHandler
che permetta sia backtesting storico che il live trading, tramite la stessa classe di interfaccia.
Per il codice completo riportato in questo articolo, utilizzando il modulo di backtesting event-driven DataBacktest si può consultare il seguente repository di github:
https://github.com/datatrading-info/DataBacktest