Motore di Backtesting con Python – Parte IV (Gestione della Strategia)

In questa serie di articoli relativa all’implementazione di un ambiente di backtesting basato sugli eventi abbiamo già descritto la struttura degli event-loop, la gerarchia della classe Event e la componente per la gestione dei dati. In questo articolo si introduce la gerarchia della classe Strategy. Gli oggetti “strategia” prendono i dati di mercato come input e producono eventi di tipo Signal Trading come output.

Un oggetto Strategy include tutti i calcoli sui dati di mercato che generano segnali advisory per l’oggetto Portfolio. In questa fase di sviluppo dell’ambiente di backtesting event-driven non introduciamo i concetto di indicatore o filtro, come quelli che sono usati nell’analisi tecnica classica. Questi sono tuttavia buoni candidati per la creazione di una gerarchia di classi, ma vanno oltre lo scopo di questo articolo.

Gestione delle Strategia

La gerarchia della classe Strategy è relativamente semplice poiché consiste in una classe base astratta con un singolo metodo puro virtuale per generare oggetti SignalEvent. Per creare la gerarchia della strategia è necessario importare NumPy, Pandas, l’oggetto Queue, i strumenti della classe base astratta e SignalEvent:

            # strategy.py

import datetime
import numpy as np
import pandas as pd
import queue

from abc import ABCMeta, abstractmethod

from event import SignalEvent
        

 

La classe base astratta Strategy definisce semplicemente il metodo virtuale calculate_signals. Questo metodo sarà usato nelle classi derivate per gestire la creazione di oggetti SignalEvent a seconda degli aggiornamenti dei dati di mercato:

            # strategy.py


class Strategy(object):
    """
    Strategy è una classe base astratta che fornisce un'interfaccia per
    tutti i successivi oggetti (ereditati) di gestione della strategia.

    L'obiettivo di un oggetto (derivato da) Strategy è generare un oggetto
    Signal per specifici simboli basati sugli input di Bars
    (OLHCVI) generati da un oggetto DataHandler.

    Questo è progettato per funzionare sia con dati storici che in tempo reale
    quindi l'oggetto Strategy è agnostico rispetto all'origine dati,
    poiché ricava le tuple di barre da un oggetto Queue (coda).
    """

    __metaclass__ = ABCMeta

    @abstractmethod
    def calculate_signals(self):
        """
        Fornisce il meccanismo per calcolare la lista di segnali.
        """
        raise NotImplementedError("Should implement calculate_signals()")
        

 

Come mostrato nel codice precedente, la definizione della classe astratta Strategy è semplice.
Un primo esempio di sottoclasse dell’oggetto Strategy è la creazione della classe BuyAndHoldStrategy, che implementa la classica strategia buy and hold. Questa strategia compra un asset ad una certo istante e lo conserva all’interno del portafoglio. Quindi viene generato un solo segnale per ogni asset.

Il costruttore (__init__) prevede, come input, il gestore dei dati di mercato e l’oggetto della coda degli eventi Events:

            # strategy.py

class BuyAndHoldStrategy(Strategy):
    """
    Questa è una strategia estremamente semplice che va LONG su tutti
    i simboli non appena viene ricevuta una barra. Non uscirà mai da una posizione.

    Viene utilizzato principalmente come meccanismo di test per la classe Strategy
    nonché un benchmark con cui confrontare le altre strategie.
    """

    def __init__(self, bars, events):
        """
        Inizializza la strategia "buy and hold".

        Parametri:
        bars - L'oggetto DataHandler che fornisce le informazioni sui prezzi
        events - L'oggetto Event Queue (coda di eventi).
        """
        self.bars = bars
        self.symbol_list = self.bars.symbol_list
        self.events = events

        # Quando il segnale "buy & hold" viene inviato, questi sono impostati a True
        self.bought = self._calculate_initial_bought()
        

Nell’inizializzazione di BuyAndHoldStrategy, l’attributo bought viene instanziato con un dictionary (una struttura dati nativa di Python) di chiavi per ogni simbolo, tutte impostate con False. Una volta che un asset è andato “long”, la relativa chiave viene impostata su True. In sostanza ciò consente alla Strategia di sapere su quali asset è “sul mercato” o meno:

            # strategy.py

     def _calculate_initial_bought(self):
        """
        Aggiunge le chiavi di tutti i simboli al dizionario "bought"
        e li imposta su False.
        """
        bought = {}
        for s in self.symbol_list:
            bought[s] = False
        return bought
        

Il metodo virtuale calculate_signals viene concretamente implementato in questa classe. Il metodo scorre su tutti i simboli nell’elenco dei simboli e recupera la barra OLHCV più recente dal gestore dei dati di mercato. Quindi controlla se quel simbolo è stato “comprato” (cioè se abbiamo una posizione aperta a mercato per questo simbolo o no) e, in caso negativo, crea un singolo oggetto SignalEvent. Quest’ultimo viene poi inserito nella coda degli eventi e il dizionario bought viene correttamente aggiornato con True per questo specifico simbolo:

            # strategy.py

    def calculate_signals(self, event):
        """
        For "Buy and Hold" generiamo un singolo segnale per simbolo
        e quindi nessun segnale aggiuntivo. Ciò significa che siamo
        costantemente LONG sul mercato a partire dalla data di 
        inizializzazione della strategia.

        Parametri
        event - Un oggetto MarketEvent.
        """
        if event.type == 'MARKET':
            for s in self.symbol_list:
                bars = self.bars.get_latest_bars(s, N=1)
                if bars is not None and bars != []:
                    if self.bought[s] == False:
                        # (Symbol, Datetime, Type = LONG, SHORT or EXIT)
                        signal = SignalEvent(bars[0][0], bars[0][1], 'LONG')
                        self.events.put(signal)
                        self.bought[s] = True
        

Questa semplice strategia è sufficiente per dimostrare la natura di una gerarchia basata su eventi. Negli articoli successivi considereremo strategie più sofisticate come il pairs trading.

Infine nel prossimo articolo considereremo come creare la gerarchia della classe Portfolio che tenga traccia delle nostre posizioni con un profitto e una perdita (“PnL”)

 

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

Scroll to Top