modificare i parametri di una strategia con Backtrader

Modificare i parametri di una strategia con Backtrader

In questo articolo descriviamo come usare Argparse per modificare i parametri di una strategia con Backtrader e migliorare il nostro trading algoritmico. Python contiene molti moduli nativi e di terze parti che possono farci risparmiare molto tempo e fatica durante lo sviluppo di una strategia. Se non hai familiarità con i moduli Python, un modulo è solo un altro script Python che contiene funzioni e classi già state codificate da richiamare all’interno di nuovi script. Il modulo Argparse è un buon esempio. Con questo modulo possiamo specificare argomenti della riga di comando durante l’esecuzione dei nostri script in modo da modificare i parametri o la logica del nostro codice.

Cos’è un argomento della riga di comando?

Poiché questo tutorial è rivolto ai principianti, vale la pena discutere brevemente cosa sono effettivamente gli argomenti della riga di comando. Quando eseguiamo uno script Python, possiamo eseguirlo direttamente da un IDE (ambiente di sviluppo integrato, come IDLE, l’ambiente di sviluppo nativo in bundle con Python) oppure possiamo eseguire lo script dalla riga di comando. Un argomento della riga di comando è un’opzione del programma scritta dopo il nome del comando/programma. Per esempio:

				
					
cd /home/
ls -l
python3 myStrategy.py
				
			

I comandi precedenti sono tutti semplici esempi di come chiamare un programma e fornirgli un singolo argomento.

ComandoArgumento
cd/home/
ls-l
python3myStrategy.py

Con il modulo argparse di Python possiamo fornire  gli argomenti al nostro script nello stesso modo in cui possiamo fornire argomenti agli altri programmi di sistema.

Argparse

Prima di iniziare, Python ha un’ottima documentazione su argparse con semplici casi d’uso. I documenti ufficiali “how to” possono essere trovati qui:

https://docs.python.org/3/howto/argparse.html

Con questo articolo vogliamo provare ad aggiungere un po’ di valore rispetto ai tantissimi tutorial in circolazione descrivendo come collegare l’Argparse al backtesting e alcuni esempi pratici che mostrano l’uso di argparse negli script Backtrader.

Casi d’uso comuni

Argparse diventa utile dopo aver implementato il “nucleo” della logica della strategica e siamo soddisfatti dei risultati iniziali del backtest. Quindi diamo un’occhiata al codice e decidiamo quali parametri potrebbero essere utili da modificare in fase di esecuzione. Lo stesso vale per la logica nel codice. Il motivo per cui scegliamo i parametri a processo avanzato è perché spesso scopriamo che dopo aver implementato una semplice “prova della strategia” / script essenziale, la strategia si dimostra completamente errata! Quindi, non è utile spendere troppo tempo a lucidare il tavolo prima che abbia le gambe.

Alcuni buoni candidati per le opzioni di runtime includono:

  • Ticker/simbolo dello strumento
  • Intervalli di date di backtest
  • Parametri della strategia (ad es. rischio, periodo di ricerca o qualunque  abbiamo definito come variabile)
  • Modalità operativa: ad esempio Backtest o Live Trading

Qualsiasi parametro o decisione logica “potrebbe” essere un candidato per un’opzione di runtime. Tuttavia, non è utile farlo per ogni parametro nello script. Aggiunge molte righe al codice, aumenta la complessità ed può essere soggetto a errori.

Il codice

In questo articolo, adattiamo il codice scritto nel post Backtrader: Primo Script, in modo da usare argparse per modificare i parametri di una strategia con Backtrader. La natura semplice del codice permette di concentrarci sul test del modulo argparse.

				
					
import backtrader as bt
from datetime import datetime
import argparse


def parse_args():
    parser = argparse.ArgumentParser(description='Argparse Example program\
                        for backtesting')

    parser.add_argument('instrument',
                        help='The yahoo ticker of the instrument being tested')

    parser.add_argument('start',
                        help='Starting date in YYYY-MM-DD format')

    parser.add_argument('end',
                        help='Ending date in YYYY-MM-DD format')

    parser.add_argument('-p', '--period',
                        default=21,
                        type=int,
                        help='Select the RSI lookback period')

    parser.add_argument('-r', '--rsi',
                        choices=['SMA', 'EMA'],
                        help='Select wether a simple moving average or exponential\
                         moving average is used in the RSI calcuation')

    return parser.parse_args()


class firstStrategy(bt.Strategy):
    params = (
        ('rsi', 'SMA'),
        ('period', 21),
    )

    def __init__(self):
        if self.p.rsi == 'EMA':
            self.rsi = bt.indicators.RSI_EMA(self.data.close, period=self.p.period)
        else:
            self.rsi = bt.indicators.RSI_SMA(self.data.close, period=self.p.period)

    def next(self):
        if not self.position:
            if self.rsi < 30:
                self.buy(size=100)
        else:
            if self.rsi > 70:
                self.sell(size=100)


# Ottengo gli argomenti
args = parse_args()

# Converto gli argomenti date in oggetti datetime
fd = datetime.strptime(args.start, '%Y-%m-%d')
td = datetime.strptime(args.end, '%Y-%m-%d')

# Capitale iniziale
startcash = 10000

# Creo un istanza di cerebro
cerebro = bt.Cerebro()

# Aggiungo la strategia
cerebro.addstrategy(firstStrategy, rsi=args.rsi, period=args.period)

# Download dei dati Apple da Yahoo Finance.
data = bt.feeds.YahooFinanceData(
    dataname=args.instrument,
    fromdate=fd,
    todate=td,
    buffered=True
)

# Aggiungo i dati di Apple a Cerebro
cerebro.adddata(data)

# imposto il capitale iniziale
cerebro.broker.setcash(startcash)

# Esecuzione del backtest
cerebro.run()

# Valore finale del portafoglio
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

# Stampa dei risultati finali
print('Final Portfolio Value: ${}'.format(portvalue))
print('P/L: ${}'.format(pnl))

# Grafico dei risultati
cerebro.plot(style='candlestick')
				
			

Commento del codice

Uno dei principali vantaggi del modulo argparse è la possibilità di inserire parametri e opzioni del programma. Inoltre genera automaticamente una semplice documentazione  come help in linea per l’utente. Dopo averlo configurato, possiamo eseguire il programma con l’opzione –help e argparse visualizza un log di aiuto basato sugli argomenti che abbiamo aggiunto al codice.

				
					python3 argparse-tutorial.py --help
				
			

L’esecuzione del comando precedente produce il seguente output:

Backtrader-Argparse-help-output

Se hai utilizzato programmi shell in Linux o Mac OS, l’output ti sembrerà molto familiare. Inoltre, se inserisci un’opzione errata o dimentichi un parametro obbligatorio, argparse ti dirà dove si trova il problema.

Vediamo ora il codice in dettaglio. Scegliamo di impostare gli argomenti dello script all’interno della funzione dedicata parse_args(). Oltre ad essere ben compartimentati, ci sono alcune buone ragioni per farlo, ad esempio possiamo utilizzare lo stesso script come modulo senza tentare di analizzare argomenti inesistenti. Tuttavia, questo è fuori dallo scopo di questo articolo.

Il metodo parse_args()

Questa funzione contiene tutto il necessario per configurare e restituire argomenti dalla riga di comando. Abbiamo cercato di fornire diversi tipi di argomenti usati frequentemente. Di conseguenza, è un facile punto di partenza da copiare e adattare ai propri script. Le funzioni parse_args() contengono i seguenti esempi:

  1. Esempi di argomenti posizionali
  2. Esempi di argomenti facoltativi
  3. Esempi che restituiscono stringhe (questo è il tipo di argomento predefinito)
  4. Un esempio che restituisce un numero utilizzando la parola chiave type
  5. Un esempio che offre scelte

Ogni argomento aggiunto in parse_args() ha la parola chiave help. È da questa che viene generato automaticamente il testo di aiuto. Se non prevediamo che qualcun altro utilizzi il nostro script, possiamo decidere di non includerlo. La mia preferenza personale è scrivere sempre una breve stringa di aiuto che descrive il parametro. È utile quando si torna al codice dopo alcuni mesi e ci siamo dimenticati il significato di ogni parametro. Non è necessario analizzare il codice per capire come eseguirlo!

Argomenti posizionali vs argomenti facoltativi

Gli argomenti posizionali sono quelli che dobbiamo scrivere in un ordine fisso dopo il comando e non usare un flag/switch/parola chiave (es. “ -p “, “ –period “). Qualsiasi argomento per il quale forniamo un flag o una parola chiave viene classificato come argomento facoltativo. Questo può creare un po’ di confusione poiché anche se impostiamo il parametro della keyword di un argomento “required=True“, viene comunque classificato come argomento facoltativo nel testo dell’help.

Applicazione degli argomenti nel backtesting

Arriviamo ora alla parte importante. Come possiamo usare utilizziamo queste opzioni per modificare i parametri di una strategia con Backtrader? Dalla semplice lettura del testo di help di ogni argomento possiamo già avere una chiara idea di come usare gli argomenti nel modo  corretto. Un primo esempio è usare gli argomenti per applicare la stessa strategia a mercati diversi. Un altro esempio è la possibilià di specificare l’intervallo di date in cui concentrare il backtesting Alcuni casi d’uso più avanzati potrebbero includere l’attivazione/disattivazione dei log, l’attivazione dello script in modalità di ottimizzazione (Ottimizzare una strategie con Backtrader) o il passaggio tra modalità di trading dal vivo, il paper trading e il backtesting.

Accedere agli argomenti

Dopo aver chiamato parse_args() nello script, possiamo accedere facilmente al valore di  ogni argomento tramite l’oggetto args restituito. La chiamata args.start restituisce l’ argomento posizionale relativo alla data di inizio. Allo stesso modo, la chiamata args.period restituisce il parametro facoltativo period.

Ottenere i tipi di dati corretti

Per impostazione predefinita, argparse restituisce stringhe, a meno che non lo specifichiamo diversamente. Se abbiamo bisogno di qualcosa di diverso da una stringa, dobbiamo specificare cosa vogliamo usando la parola chiave type. Ad esempio, questo ci consente di specificare che un particolare argomento dovrebbe essere un numero intero. Lo facciamo nel codice per l’argomento “period”.

				
					parser.add_argument('-p', '--period', 
                    default=21,
                    type=int,
                    help='Select the RSI lookback period')
				
			

Come bonus, argparse controlla che l’argomento fornito sia del tipo corretto e genera un errore se non è corretto. Questo ci evita di dover codificare un controllo nello script. Ad esempio, se forniamo una stringa all’argomento –period, argparse genera il seguente errore:

				
						
argparse-tutorial.py: error: argument -p/--period: invalid int value: 'abc'
				
			

Successivamente, abbiamo qualcosa di più interessante. Sfortunatamente, non tutti i “tipi” sono supportati. Ciò è particolarmente vero per i tipi che fanno parte di un altro modulo. Un oggetto datetime ne è un buon esempio. Il feed di dati Yahoo in Backtrader richiede oggetti datetime per specificare la data di inizio e di fine di un backtest. Di conseguenza, dobbiamo gestire la conversione all’interno dello script.

				
					fd = datetime.strptime(args.start, '%Y-%m-%d')
td = datetime.strptime(args.end, '%Y-%m-%d')
				
			

Nella funzione strptime() di datetime, la stringa ‘%Y-%m-%d ‘ definisce il formato previsto della stringa. Questa stringa indica che è necessario fornire una stringa di formato AAAA-MM-GG al metodo strptime. Se la stringa non è in questo formato, si genera un ValueError quando si tenta di eseguire il programma.

				
					ValueError: time data '2017-fwa-a1' does not match format '%Y-%m-%d'
				
			

Per ulteriori informazioni sulle varie opzioni di formattazione, vedere:

https://docs.python.org/3.5/library/datetime.html#strftime-and-strptime-behavior

L’ultima riga che vale la pena commentare è:

				
					# Aggiungere la strategia
cerebro.addstrategy(firstStrategy, rsi=args.rsi, period=args.period)
				
			

Potresti chiederti perché abbiamo voluto estendere l’esempio Backtrader: Primo Script  per includere i parametri della strategia e quindi caricare gli argomenti in cerebro quando la strategia viene inizializzata. Del resto avremmo potuto scrivere:

				
					
class firstStrategy(bt.Strategy):
 
    def __init__(self):
        if args.rsi == 'EMA':
            self.rsi = bt.indicators.RSI_EMA(self.data.close, period=args.period)
        else:
            self.rsi = bt.indicators.RSI_SMA(self.data.close, period=args.period)
				
			

Il motivo è la portabilità. Ora possiamo spostare la classe di questa strategia all’interno di qualsiasi script senza dover portare dietro il bagaglio degli argomenti. Potremmo non avere gli stessi argomenti in un altro script.

Nota finale

Durante i test, abbiamo notato che impostiando l’RSI per utilizzare una media mobile semplice con un periodo di 7, viene generato un ZeroDivisionError. Non credo che ci sia un problema con il codice perché periodi più lunghi funzionano correttamente e un periodo di 7 funziona anche con la media mobile esponenziale. Tuttavia, se troveremo un problema con il codice, aggiorneremo questo articolo.

Codice completo

In questo articolo abbiamo descritto come usare Argparse per modificare i parametri di una strategia con Backtrader e migliorare il nostro trading algoritmico. Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/BackTrader

Torna in alto
Scroll to Top