In questo articolo vediamo come implementare una semplice strategia di trading utilizzando il motore backtesting basato sugli eventi, descritto negli articoli precedenti. In particolare vediamo come creare le curve equity utilizzando gli iimporti nozionali di portafoglio, simulando così i concetti di margine / leva finanziaria, che è un approccio molto più realistico rispetto all’approccio vettorizzato / basato sui rendimenti.

Questa prima strategia può essere eseguita con dati liberamente disponibili, sia da Yahoo Finance, Google Finance o Quandl. E’ una strategia adatta per trader algoritmici a lungo termine che desiderano studiare solo l’aspetto della generazione del segnale di trade della strategia ma anche l’intero sistema end-to-end. Tali strategie spesso possiedono Sharpe Ratio più piccoli, ma sono molto facili da implementare ed eseguire.

La Strategia di Moving Average Crossover

Sono un grande sostenitore di sistemi di trading basati sull’incrocio della media mobile perché è la prima strategia non banale estremamente utile per testare una nuova implementazione di un motore di backtesting. Su un arco temporale giornaliero, su un numero di anni, con lunghi periodi di ricerca, vengono generati pochi segnali su un singolo stock ed è quindi facile verificare manualmente che il sistema si stia comportando come ci si aspetterebbe.

Per generare effettivamente una tale simulazione basata sul codice di backtesting basato sugli eventi dobbiamo creare una sottoclasse dell’oggetto Strategy, come descritto nell’articolo precedente, per creare l’oggetto MovingAverageCrossStrategy, che conterrà la logica di calcolo delle medie mobili semplici e la generazione dei segnali di trading.
Inoltre dobbiamo creare la funzione __main__ che caricherà l’oggetto Backtest e incapsulerà effettivamente l’esecuzione del programma. Il seguente file, mac.py, contiene entrambi questi oggetti.

Il primo compito, come sempre, è importare correttamente i componenti necessari. Stiamo importando quasi tutti gli oggetti che costituiscono il motore di backtesting event-driven:

# mac.py

import datetime
import numpy as np
import pandas as pd
import statsmodels.api as sm
import datetime
import numpy as np
import pandas as pd
import statsmodels.api as sm

from strategy.strategy import Strategy
from event.event import SignalEvent
from backtest.backtest import Backtest
from data.data import HistoricCSVDataHandler
from execution.execution import SimulatedExecutionHandler
from portfolio.portfolio import Portfolio

Passiamo ora alla creazione della classe  MovingAverageCrossStrategy. La strategia richiede le barre generate da DataHandler, gli eventi gestiti da Event Queue e i periodi di ricerca per le medie mobili semplici che verranno impiegate all’interno della strategia. Per questa strategia consideriamo 100 e 400 come periodi di ricerca “brevi” e “lunghi” per questa strategia.

L’attributo finale, bought, viene utilizzato per indicare all’oggetto Strategy quando il backtest è effettivamente “a mercato”. I segnali di ingresso vengono generati solo se è “OUT” e i segnali di uscita vengono generati solo se è “LONG” o “SHORT”:

# mac.py

class MovingAverageCrossStrategy(Strategy):
    """
    Esegue una strategia base di Moving Average Crossover tra due
    medie mobile semplici, una breve e una lunga. Le finestre brevi / lunghe
    sono rispettivamente di 100/400 periodi.
    """
    def __init__(self, bars, events, short_window=100, long_window=400):
        """
        Initializza la strategia di Moving Average Cross.
        
        Parametri:
        bars - L'oggetto DataHandler object che fornisce le barre dei prezzi
        events - L'oggetto Event Queue.
        short_window - Il periodo per la media mobile breve.
        long_window - Il periodo per la media mobile lunga.
        """
        self.bars = bars
        self.symbol_list = self.bars.symbol_list
        self.events = events
        self.short_window = short_window
        self.long_window = long_window

        # Impostato a True se la strategia è a mercato
        self.bought = self._calculate_initial_bought()
Poiché la strategia inizia fuori dal mercato, impostiamo il valore iniziale “bought” su “OUT”, per ogni simbolo:
# mac.py

    def _calculate_initial_bought(self):
        """
        Aggiunge keys per ogni simbolo al dizionario bought e le
        imposta a 'OUT'.
        """
        bought = {}
        for s in self.symbol_list:
            bought[s] = 'OUT'
            return bought

Il fulcro della strategia è il metodo prepare_signals. Reagisce a un oggetto MarketEvent e per ogni simbolo di trading acquisisce gli prezzi di chiusura delle ultime N barre, dove N è uguale al periodo di ricerca più ampio.

Quindi si calcola le medie mobili semplici di breve e lungo periodo. La regola della strategia è entrare a mercato (andare long su un’azione) quando il valore della media mobile breve supera il valore della media mobile lunga. Al contrario, se il valore della media mobile lunga supera il valore della media mobile breve, alla strategia viene detto di uscire dal mercato.

Questa logica viene gestita posizionando un oggetto SignalEvent sulla coda degli eventi degli eventi in ciascuna delle rispettive situazioni e quindi aggiornando l’attributo “bought” (per ogni simbolo) in modo che sia rispettivamente “LONG” o “SHORT”. Poiché questa è una strategia solo long, non prenderemo in considerazione le posizioni “SHORT”:

# mac.py

    def calculate_signals(self, event):
        """
        Genera un nuovo set di segnali basato sull'incrocio della
        SMA di breve periodo con quella a lungo periodo che
        significa un'entrata long e viceversa per un'entrata short.

        Parametri
        event - Un oggetto MarketEvent.
        """
        if event.type == 'MARKET':
            for s in self.symbol_list:
                bars = self.bars.get_latest_bars_values(
                    s, "adj_close", N=self.long_window
                )
                bar_date = self.bars.get_latest_bar_datetime(s)
                if bars is not None and bars != []:
                    short_sma = np.mean(bars[-self.short_window:])
                    long_sma = np.mean(bars[-self.long_window:])
                    symbol = s
                    dt = datetime.datetime.utcnow()
                    sig_dir = ""
                    if short_sma > long_sma and self.bought[s] == "OUT":
                        print("LONG: %s" % bar_date)
                        sig_dir = 'LONG'

                        signal = SignalEvent(1, symbol, dt, sig_dir, 1.0)
                        self.events.put(signal)
                        self.bought[s] = 'LONG'
                    elif short_sma < long_sma and self.bought[s] == "LONG":
                        print("SHORT: %s" % bar_date)
                        sig_dir = 'EXIT'
                        signal = SignalEvent(1, symbol, dt, sig_dir, 1.0)
                        self.events.put(signal)
                        self.bought[s] = 'OUT'
Questo conclude l’implementazione dell’oggetto MovingAverageCrossStrategy. Il compito finale dell’intero sistema di backtest è implementare un metodo __main__ in mac.py per eseguire effettivamente il backtest.

Innanzitutto, bisogna assicursi di modificare il valore di csv_dir con percorso assoluto della directory dove si trovano i file CSV per i dati finanziari. Si dovrà anche scaricare il file CSV del titolo AAPL (da Yahoo Finance), che è fornito dal seguente link (dal 1 ° gennaio 1990 al 1 ° gennaio 2002), dato che questo è il titolo su cui testeremo la strategia:
https://query1.finance.yahoo.com/v7/finance/download/AAPL?period1=631152000&period2=1009843200&interval=1d&events=history&includeAdjustedClose=true

E’ quindi necessario inserire questo file nel percorso indicato dalla funzione principale in csv_dir. La funzione __main__ crea semplicemente un’istanza di un nuovo oggetto di backtest e quindi chiama il metodo simulate_trading per eseguire il backtest:
# mac.py

if __name__ == "__main__":
    csv_dir = '/path/to/your/csv/file' # DA MODIFICARE
    symbol_list = ['AAPL']
    initial_capital = 100000.0
    heartbeat = 0.0
    start_date = datetime.datetime(1990, 1, 1, 0, 0, 0)
    backtest = Backtest(csv_dir, symbol_list, initial_capital, heartbeat, start_date, 
                        HistoricCSVDataHandler, SimulatedExecutionHandler, Portfolio, 
                        MovingAverageCrossStrategy)
    backtest.simulate_trading()

Per eseguire il codice, bisogna avere aver già configurato un ambiente Python (come descritto nei precedenti articoli) e poi navigare nella directory in cui è memorizzato il codice.
Si dovrà semplicemente lanciare il seguente comando:

python mac.py
Lo script restituisce il seguente elenco (troncato a causa della stampa del conteggio delle barre!)
..
..
3029
3030
Creating summary stats...
Creating equity curve...
AAPL cash commission total returns equity_curve drawdown
datetime
2001-12-18 0 99211 13 99211 0 0.99211 0.025383
2001-12-19 0 99211 13 99211 0 0.99211 0.025383
2001-12-20 0 99211 13 99211 0 0.99211 0.025383
2001-12-21 0 99211 13 99211 0 0.99211 0.025383
2001-12-24 0 99211 13 99211 0 0.99211 0.025383
2001-12-26 0 99211 13 99211 0 0.99211 0.025383
2001-12-27 0 99211 13 99211 0 0.99211 0.025383
2001-12-28 0 99211 13 99211 0 0.99211 0.025383
2001-12-31 0 99211 13 99211 0 0.99211 0.025383
2001-12-31 0 99211 13 99211 0 0.99211 0.025383
[(’Total Return’, ’-0.79%’),
(’Sharpe Ratio’, ’-0.09’),
(’Max Drawdown’, ’2.56%’),
(’Drawdown Duration’, ’2312’)]
Signals: 10
Orders: 10
Fills: 10

La performance di questa strategia è visualizzata nella seguente figura:

SMA_performance_trading_algoritmico
E’ evidente che i rendimenti e lo Sharpe Ratio non sono stellari per le azioni AAPL su questo particolare set di indicatori tecnici!
Chiaramente abbiamo molto lavoro da fare nella prossima serie di strategie per trovare un sistema in grado di generare performance positive.

 

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

Recommended Posts