nuovi Analyzer con backtrader

Come usare gli Analyzers di Backtrader

In questo articolo descriviamo come usare gli analyzers di backtrader. Dopo aver implementato una strategia di base, il passo successivo consiste nel verificare e quantificare la  bontà di questa strategia. Backtrader ha una ricca libreria di strumenti di analisi in grado di fornire molte metriche, dal semplice rapporto vincite/perdite ai più complessi Sharpe Ratio e analisi del drawdown.

Cosa sono gli Analyzer di Backtrader

Gli analyzer sono oggetti che possono essere caricati cerebro (il motore di Backtrader) e sono in grado di monitorare la tua strategia mentre questa viene eseguita. Quando cerebro ha terminato l’esecuzione del backtesting, è possibile accedere agli analizzatori tramite oggetti di strategia che sono restituiti da cerebro a termine dell’esecuzione. Gli oggetti dell’analizzatore hanno un metodo (funzione) speciale che restituisce un dizionario di tutte le statistiche che l’analizzatore sta monitorando. A volte si tratta di molte informazioni e altre volte solo una o due statistiche. 

Non ti preoccupare se in questo momento ti sembra complesso, diventerà tutto più chiaro quando vedrai il codice. Esiste un’intera libreria dei diversi analizzatori che possono essere utilizzati in Backtrader (e se ne possono creare di nuovi in ogni momento). Dopo aver lavorato con questo script, dai un’occhiata alla documentazione per vedere quali analizzatori ti interessano e modifica il codice per includerli nel tuo backtesting.

Obiettivo

Questo articolo ha lo scopo di mostrare come inizializzare, analizzare e restituire i risultati di un backtesting. E’ il continuo del precedente articolo “Primo Script con Backtrader” e fa parte della serie introduttiva di Backtrader. Per vedere le regole della strategia e la spiegazione del codice, dai un’occhiata al precedente articolo.

Background

Esistono un paio di metriche di backtest / trading che rientrano in questo post. Ecco un glossario:

  • Strike Rate: questa è una percentuale che rappresenta il numero di volte di trade vincenti rispetto al numero totale di scambi che sono stati effettuati (tasso di vittoria / trade totali). Può aiutare a identificare se esiste un vantaggio nel mercato. Alcuni trader mirano a ottenere il strike rate più alto possibile, che corrisponde a tante piccole vincite. Altri sono felici con strike rate più bassi, ma puntano a grandi vincite e piccole perdite.
  • SQN: System Quality Number (numero di qualità del sistema), questo è stato definito dal dott. Van Tharp dell’istituto Van Tharp. In pratica dà un punteggio alla tua strategia. Una spiegazione più accademica del SQN, presente nel sito web di Van Tharp, è la seguente:

l’SQN misura la relazione tra la media (stimata) e la deviazione standard della distribuzione “R-multipla” generata da un sistema di trading. Apporta inoltre un adeguamento rispetto al numero di operazioni coinvolte.

Nota: la documentazione di Backtrader fornisce un utile sistema di classificazione per SQN:

  • 1.6 – 1.9 Sotto la media
  • 2,0 – 2,4 Media
  • 2,5 – 2,9 Buono
  • 3,0 – 5,0 Eccellente
  • 5,1 – 6,9 Superbo
  • 7.0 – Santo Graal?

Il Codice

				
					import backtrader as bt
from datetime import datetime
from collections import OrderedDict

class RsiStrategy(bt.Strategy):

    def __init__(self):
        self.rsi = bt.indicators.RSI_SMA(self.data.close, period=21)

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


def tradeAnalysis(analyzer):
    '''
    Funzione per stampare i risultati dell'Analisi Tecnica in un bel formato.
    '''
    # Ottenere i risultati che ci interessano
    total_open = analyzer.total.open
    total_closed = analyzer.total.closed
    total_won = analyzer.won.total
    total_lost = analyzer.lost.total
    win_streak = analyzer.streak.won.longest
    lose_streak = analyzer.streak.lost.longest
    pnl_net = round(analyzer.pnl.net.total,2)
    strike_rate = (total_won / total_closed) * 100
    # Definire le righe
    h1 = ['Total Open', 'Total Closed', 'Total Won', 'Total Lost']
    h2 = ['Strike Rate','Win Streak', 'Losing Streak', 'PnL Net']
    r1 = [total_open, total_closed,total_won,total_lost]
    r2 = [strike_rate, win_streak, lose_streak, pnl_net]
    # Controllore che il set degli headers è il più lungo
    if len(h1) > len(h2):
        header_length = len(h1)
    else:
        header_length = len(h2)
    # Stampa le righe
    print_list = [h1,r1,h2,r2]
    row_format ="{:<15}" * (header_length + 1)
    print("Trade Analysis Results:")
    for row in print_list:
        print(row_format.format('',*row))

def SQN(analyzer):
    sqn = round(analyzer.sqn,2)
    print('SQN: {}'.format(sqn))

# Variabile per il capitale iniziale
startcash = 100000

# Creare un'istanza di cerebro
cerebro = bt.Cerebro()

# Aggiungere la strategia
cerebro.addstrategy(RsiStrategy)

# Download dei dati di Apple da Yahoo Finance.
data = bt.feeds.YahooFinanceData(
    dataname='AAPL',
    fromdate = datetime(2009,1,1),
    todate = datetime(2017,1,1),
    buffered= True
    )

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

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

# Aggiungere gli analyzer
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="ta")
cerebro.addanalyzer(bt.analyzers.SQN, _name="sqn")

# Esecuzione
strategies = cerebro.run()
rsiStrat = strategies[0]

# Stampa degli analyzer
tradeAnalysis(rsiStrat.analyzers.ta.get_analysis())
SQN(rsiStrat.analyzers.sqn.get_analysis())

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

# Stampa del risultato finale
print('Final Portfolio Value: ${}'.format(portvalue))

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

Come usare gli analyzers di backtrader

Vediamo innanzitutto come aggiungere un Analyzer alla strategia consiste semplicemente nel richiamare la funzione addanaylzer(). In particolare, si sta aggiungendo il TradeAnalyzer e assegnandogli un nome. Il nome rende molto più semplice accedere questo oggetto in qualsiasi momento

				
					
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="ta")
				
			

Al termine dell’esecuzione di cerebro, viene restituito un elenco di oggetti strategia. In questo esempio abbiamo caricato in cerebro una sola strategia. Anche in questo caso, l’unica strategia viene comunque restituita all’interno di un array. Pertanto è necessario estrarre la nostra strategia  utilizzando un indice della posizione all’interno dell’array ([0]). Una volta che abbiamo quello, abbiamo la nostra strategia.

				
					
# Esecuzione
strategies = cerebro.run()
rsiStrat = strategies[0]

				
			

A questo punto dobbiamo accedere ai nostri dati. È possibile accedere agli Analyzers dall’interno dell’oggetto strategia ed utilizzare un metodo (funzione) nativo per restituire un dizionario (dict) contenente tutti i risultati. Di seguito si evidenzia come lo script accede all’analyzer “ta” (che è il nome che gli abbiamo dato durante il caricamento dell’analyzer in cerebro) dall’oggetto strategia rsiStrat e si richiama il metodo get_analysis().

				
					bt.analyzers.TradeAnalyzer, _name="ta"
				
			

Estrazione e stampa dei dati

Dopo aver ottenuto il dizionario dei dati, è necessario estrarre i dati che ci interessano ed elaborarli a seconda dei nostri scopi. In questo caso, si considerano solo alcune metriche e vengono stampate sul terminale. Tuttavia, è possibile espandere questa soluzione ed esportare i risultati in un CSV. In questo esempio si esaminano:

  • Totale operazioni ancora aperte
  • Totale operazioni chiuse
  • Totale operazioni vincenti
  • Totale operazioni perdenti
  • Strike rate (che si calcola sulla base dei dati forniti)
  • Migliore serie vincente
  • Peggior serie perdente
  • Profitti o perdite.
  • SQN della strategia

Per fare ciò ho creato due funzioni di stampa speciali. E’ quindi possibile riutilizzare questo codice (copia e incolla) per gli script futuri.

La funzione di stampa che merita di essere commentata più avanti è:

				
					tradeAnalysis(analyzer):
				
			
Temo che questa funzione possa essere eccessivamente complessa per questo articolo introduttivo e destinato ai principianti a causa della linea mostrata di seguito. Mi piace solo l’output pulito che produce nel terminale. Contiene alcune tecniche avanzate di Python per la formattazione del testo. Sto parlando della linea che assomiglia a questa:
				
					row_format ="{:<15}" * (header_length + 1)
				
			

Questa riga mi consente di stampare l’output distribuito uniformemente senza dover installare un altro modulo di Python, come TextTable. Per ulteriori informazioni sulla formattazione dei dati in Python, consultare i documenti qui: https://docs.python.org/3/library/string.html

Un’alternativa (e un’opzione molto più semplice) consiste nell’utilizzare il metodo print() integrato nativamente nella classe dell’analyzer. Questo metodo stampa ogni valore all’interno dell’analizzatore su una riga separata. Se vuoi farlo o no è solo questione di gusti personali. Ad esempio, se si desidera disporre di una formattazione alternativa, selezionare determinate statistiche di interesse o elaborare ulteriormente i dati. Di seguito è riportato un esempio di come gestire questa alternativa:

				
					
# Esecuzione
strategies = cerebro.run()
firstStrat = strategies[0]

for x in firstStrat.analyzers:
    x.print()
				
			

I Risultati

Eseguendo lo script si dovrebbe otterene qualcosa del genere al seguente output:
Come usare gli Analyzers di Backtrader

Non è un ottimo risultato di trading! Sembra che la nostra strategia super semplice abbia bisogno di qualche raffinamento.

Chi l’avrebbe mai detto?!?

Codice completo

In questo articolo abbiamo descritto come usare gli Analyzers di Backtrader. 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