stock screener con alpha vantage per backtrader

Stock Screener di AlphaVantage con Backtrader

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:

  1. Usare il servizio API di Alpha Vantage con Backtrader
  2. 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.

stock screener con alpha vantage per backtrader

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 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:

  1. Creare una nuova variabile self.inds di tipo dict().
  2. 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.
  3. Aggiungere l’indicatore ad una  chiave ['value'].
  4. 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:

stock screener di AlphaVantage con Backtrader

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

Scroll to Top