Motore di Backtesting con Python – Parte II (Gli Eventi)

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

In questo sistema ci sono quattro tipi di 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’oggetto Strategy 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’oggetto Portfolio come consigli su come effettuare i trade.
  • OrderEvent: quando un oggetto Portfolio riceve i SignalEvents, 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 un ExecutionHandler.
  • FillEvent: dopo la ricezione di un OrderEvent, l’ExecutionHandler deve eseguire l’ordine. Una volta che un ordine è stato eseguito, genera un FillEvent, 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
        
Nel prossimo articolo della serie vedremo come sviluppare la gerarchia della classe 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

Torna in alto
Scroll to Top