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()
# 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'
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
..
..
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:
Chiaramente abbiamo molto lavoro da fare nella prossima serie di strategie per trovare un sistema in grado di generare performance positive.
Codice Completo
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