In questo articolo descriviamo come implementare uno stock screener di AlphaVantage con Backtrader per il trading algoritmico. Il framework è una scelta perfetta per uno stock screener perchè è molto facile creare indicatori personalizzati. Grazie ad una impressionante libreria di funzionalità integrate in Backtrader, è facile controllare i valori e l’andamento dei nostri indicatori preferiti.
Pre-letture
Prima di iniziare, vale la pena sottolineare che questo articolo si basa su alcuni argomenti basi. Più specificamente, usiamo Alpha Vantage per fornire dati allo screener. Per approfondire come scaricare i dati e, come minimo, registrarti per una chiave API da Alpha Vantage, si può leggere i seguenti articoli:
- Usare il servizio API di Alpha Vantage con Backtrader
- Integrare i datafeed con Alpha Vantage con Backtrader
Obiettivo
L’obiettivo del codice è implementare uno stock screener di AlphaVantage con Backtrader. Iniziamo scaricando i dati giornalieri per un massimo di 500 strumenti. I dati verranno quindi inseriti in Backtrader che analizza i dati e calcola degli valori degli indicatori. Alla fine del backtest, creiamo un report basato sui valori dell’ultima barra di dati per mostrare se gli indicatori sono rialzisti o ribassisti. L’esempio può essere migliorato per aggiungere o sostituire gli indicatori che ci interessano.
Prima di eseguire il backtest
Dobbiamo assicurarci di trovare la seguente riga nel codice e inseriamo la chiave API.
Apikey = 'INSERT YOUR API KEY HERE'
Per far funzionare correttamente il codice. In caso contrario riceviamo un errore.
Stock screener di AlphaVantage con Backtrader
from alpha_vantage.timeseries import TimeSeries
import pandas as pd
import numpy as np
import backtrader as bt
from datetime import datetime
import time
# IMPORTANTE!
# ----------
# Registrare per un API su:
# https://www.alphavantage.co/support/#api-key
# e inserirla qui
Apikey = 'INSERT YOUR API KEY HERE'
def alpha_vantage_eod(symbol_list, compact=False, debug=False, *args, **kwargs):
'''
funzione helper per scaricare i dati di Alpha Vantage.
Restituisce una lista nidificata dove ogni elemento contiene:
[0] dataframe di pandas
[1] il nome del datafeed
'''
data_list = list()
size = 'compact' if compact else 'full'
count = 0
total = len(symbol_list)
for symbol in symbol_list:
count += 1
print('\nDownloading: {}'.format(symbol))
print('Symbol: {} of {}'.format(count, total, symbol))
print('-'*80)
# Inviamo l'API e creiamo la sessione
alpha_ts = TimeSeries(key=Apikey, output_format='pandas')
data, meta_data = alpha_ts.get_daily(symbol=symbol, outputsize=size)
# Converire l'indice in datetime.
data.index = pd.to_datetime(data.index)
data.columns = ['Open', 'High', 'Low', 'Close','Volume']
if debug:
print(data)
data_list.append((data, symbol))
# Pausa per evitare di raggiungere i limiti dell'API
print('Sleeping |', end='', flush=True)
for x in range(12):
print('=', end='', flush=True)
time.sleep(1)
print('| Done!')
return data_list
class TestStrategy(bt.Strategy):
def __init__(self):
self.inds = dict()
self.inds['RSI'] = dict()
self.inds['SMA'] = dict()
for i, d in enumerate(self.datas):
# Per ogni indicatore monitoriamo il suo valore e se è rialzista o
# ribassista. Possiamo farlo creando una nuova riga che restituisca vero o falso.
# RSI
self.inds['RSI'][d._name] = dict()
self.inds['RSI'][d._name]['value'] = bt.indicators.RSI(d, period=14)
self.inds['RSI'][d._name]['bullish'] = self.inds['RSI'][d._name]['value'] > 50
self.inds['RSI'][d._name]['bearish'] = self.inds['RSI'][d._name]['value'] < 50
# SMA
self.inds['SMA'][d._name] = dict()
self.inds['SMA'][d._name]['value'] = bt.indicators.SMA(d, period=20)
self.inds['SMA'][d._name]['bullish'] = d.close > self.inds['SMA'][d._name]['value']
self.inds['SMA'][d._name]['bearish'] = d.close < self.inds['SMA'][d._name]['value']
def stop(self):
'''
Chiamato quando backtrader ha terminato il backtest.
Ricaviamo i valori finali alla fine del test per ciascun indicatore.
'''
# Supponendo che tutti i simboli avranno gli stessi dati negli stessi giorni.
# Se questo non è il caso e si mescolano asset di diverse classi, regioni
# o exchanges, allora prendere in considerazione l'aggiunta di una colonna in
# più nei risultati finali.
print('{}: Results'.format(self.datas[0].datetime.date()))
print('-'*80)
results = dict()
for key, value in self.inds.items():
results[key] = list()
for nested_key, nested_value in value.items():
if nested_value['bullish'] == True or nested_value['bearish'] == True:
results[key].append([nested_key, nested_value['bullish'][0],
nested_value['bearish'][0], nested_value['value'][0]])
# Crea e stamp l'intestazione
headers = ['Indicator','Symbol','Bullish','Bearish','Value']
print('|{:^10s}|{:^10s}|{:^10s}|{:^10s}|{:^10s}|'.format(*headers))
print('|'+'-'*10+'|'+'-'*10+'|'+'-'*10+'|'+'-'*10+'|'+'-'*10+'|')
# Ordina e stampa le rige
for key, value in results.items():
#print(value)
value.sort(key= lambda x: x[0])
for result in value:
print('|{:^10s}|{:^10s}|{:^10}|{:^10}|{:^10.2f}|'.format(key, *result))
# Crea un'istanza di cerebro
cerebro = bt.Cerebro()
# Aggiunge la strategia
cerebro.addstrategy(TestStrategy)
# Scarica di dati da Alpha Vantage.
symbol_list = ['LGEN.L','LLOY.L','NG.L', 'BDEV.L']
data_list = alpha_vantage_eod(
symbol_list,
compact=True,
debug=False)
for i in range(len(data_list)):
data = bt.feeds.PandasData(
dataname=data_list[i][0], # Pandas DataFrame
name=data_list[i][1], # Symbol
timeframe=bt.TimeFrame.Days,
compression=1
)
# Aggiunge i dati a Cerebro
cerebro.adddata(data)
print('\nStarting Analysis')
print('-'*80)
# Esegue il backtest
cerebro.run()
Commento
Nel codice che implementa uno stock screener di AlphaVantage con Backtrader iniziamo scaricando i dati per ogni ticker presente in insieme di ticker. Per questa versione, usiamo una semplice lista di stringhe. Se si ha un elenco con moltissimi ticker o memorizzato da qualche altra parte, si può implementare facilmente un importazione di un elenco di ticker da un file. Per scaricare i dati, preleviamo ogni elemento dell’elenco ed effettuiamo una chiamata all’API. Dato che l’API di Alpha Vantage ha limiti rigorosi, dobbiamo prevedere una pausa tra ogni richiesta di download di dati. In particolare dobbiamo prevedere una pausa di 12 secondi tra le chiamate di download perché siamo limitati a 5 chiamate API al minuto.
Per questo motivo, lo screener non è realmente destinato per essere usato su timeframe intraday. Sarebbe troppo lento a meno che la lista di ticker non sia molto piccola. Detto questo, possiamo modificare l’acquisizione dei dati con un altro servizio e il resto del codice continua comunque funzionare. Un’altra limitazione dell’API gratuita di Alpha Vantage è il limite di 500 chiamate al giorno, quindi per lo screening infragiornaliero, dovremmo evitare troppe chiamate.
Dopo aver scaricato tutti i dati, possiamo aggiungere i nostri indicatori preferiti in un dizionario. Il dizionario è strutturato in modo da poter facilmente produrre un report finale. Durante il metodo __init__()
creiamo il dizionario, aggiungiamo gli indicatori e creiamo alcuni “feed di dati”. Questi feed segnalano se l’indicatore è rialzista o ribassista. Creare un feed è semplice come creare una condizione che restituisca True
o False
, che è una dei principali vantaggi di Backtrader. L’esempio con RSI mostra questo sulla riga:
self.inds['RSI'][d._name]['bullish'] = self.inds['RSI'][d._name]['value'] > 50
Vediamo che stiamo solamente controllando se l’RSI è superiore a 50. In caso affermativo, il feed di dati sarà True
, in caso contrario sarà False
. Per maggiori informazioni leggere la documentazione: https://www.backtrader.com/docu/concepts/#almost-everything-is-a-data-feed
Aggiungere indicatori extra
Se vogliamo aggiungere ulteriori indicatori, dobbiamo semplicemente seguire lo stesso formato degli esempi precedenti
self.inds = dict()
self.inds['RSI'] = dict()
self.inds['SMA'] = dict()
for i, d in enumerate(self.datas):
# Per ogni indicatore monitoriamo il suo valore e se è rialzista o
# ribassista. Possiamo farlo creando una nuova riga che restituisca vero o falso.
# RSI
self.inds['RSI'][d._name] = dict()
self.inds['RSI'][d._name]['value'] = bt.indicators.RSI(d, period=14)
self.inds['RSI'][d._name]['bullish'] = self.inds['RSI'][d._name]['value'] > 50
self.inds['RSI'][d._name]['bearish'] = self.inds['RSI'][d._name]['value'] < 50
Il codice prevede i seguenti passi:
- Creare una nuova variabile
self.inds
di tipodict()
. - Durante il ciclo for dei datafeed, usare il nome del feed di dati (
d._name
) per creare un nuovo dizionario all’interno del dizionario appena creata. - Aggiungere l’indicatore ad una chiave
['value']
. - Creare un controllo per verificare se l’indicatore è rialzista o ribassista tramite un valore booleano.
Seguendo il formato corretto per aggiungere i nuovi indicatori in __init__()
, allora il metodo stop()
non richiederà alcun aggiornamento.
Risultati
Dopo aver eseguito lo screener di Backtrader, otteniamo un output una tabella simile alla segunete:
Se vogliamo controllare molti strumenti, possiamo prendere in considerazione l’idea di eseguire lo screener come un lavoro notturno dopo la chiusura dei mercati.
Codice completo
In questo articolo abbiamo descritto come implementare uno stock screener di AlphaVantage con Backtrader per il trading algoritmico. . Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/BackTrader