Introduzione a QuantConnect

Il contenuto di questo sito è stato inizialmente focalizzato su due piattaforme. Per essere più specifici, tali piattaforme sono Backtrader e Tradingview. Il motivo è semplicemente che queste si sono rivelate per me le soluzioni migliori quando ho iniziato questo viaggio. Come la maggior parte delle persone, prima di decidere di investire il mio tempo in queste piattaforme, ho dato un’occhiata a diverse altre opzioni. QuantConnect era solo una delle tante altre piattaforme disponibili. Tuttavia, dopo aver trascorso un po’ di tempo a frugare nel sito, in quel momento ho deciso che QuantConnect non faceva per me. Di recente, ho capito che era tempo di dare un’altra occhiata. Dopotutto, la tecnologia si muove a un ritmo rapidissimo ed immaginavo che la piattaforma avrebbe potuto fare molta strada dopo la mia prima visita. Inoltre, dopo aver dedicato molto tempo allo sviluppo nelle altre piattaforme, ora ho una nuova prospettiva e una visione diversa di ciò che è importante per me. Quindi iniziamo un nuovo viaggio come principianti di questa piattaforma. Vediamo se l’erba è davvero più verde da quelli parti.

QuantConnect

Per quelli di voi che non ne hanno mai sentito parlare, QuantConnect è una piattaforma online che consente agli utenti di scrivere, collaborare e persino ottenere finanziamenti per algoritmi di trading. Il codice viene generalmente scritto nel browser e backtestato online utilizzando i dati e la potenza di calcolo di QuantConnects.

Link: https://www.quantconnect.com/

Introduzione

Prima di sviluppare il nostro primo script, fornirò alcune considerazioni iniziali (sia positive che negative) sulle caratteristiche generali della piattaforma. Ciò può aiutare i lettori a valutare rapidamente se continuare o meno questo viaggio. Innanzitutto, parliamo delle cose buone! Ci sono alcuni aspetti di QuantConnect davvero interessanti e verranno evidenziati man mano lungo questa serie di articoli. Ma quello che è veramente unico la grande quantità di dati storici che sono disponibili per gli utenti. È di facile accesso e quindi non c’è bisogno di lunghe, fatiche e costose ricerche per reperire e conservare i dati di cui abbiamo bisogno. Chiunque abbia mai provato, saprà bene che è un dispendio di tempo e di denaro! QuantConnect supporta anche numerosi linguaggi di programmazione (Python, C # e F). Questo è ottimo per una piattaforma online con perimetro semi-murato. Puoi programmare nel linguaggio che conosci, invece di dover imparare un linguaggio di scripting come Pine-Script di Tradingview.

Integrazione con il Broker

Un altro enorme vantaggio è la stretta integrazione con i maggiori broker per i trader retail. Ovviamente, anche Backtrader supporta l’integrazione con i broker, ma QuantConnect ha creato partnership ufficiali. Inoltre, forniscono macchine virtuali per facilitare la distribuzione di un algoritmo e garantire un solido tempo di attività. L’implementazione di un algoritmo live è semplice, basta premere un pulsante nella parte superiore dell’interfaccia utente e selezionare il proprio broker. Non sono necessarie modifiche al codice.
Se sei interessato al live trading, QuantConnect offre diversi tipi di abbonamenti per i server e gli account (il Backtesting è gratuito). Il prezzo per aprire un server di live trading è abbastanza ragionevole, solo $20 al mese. Questo ti dà pieno accesso all’infrastruttura e agli strumenti che impiegherebbero molto tempo per essere sviluppati in altri framework come Backtrader. Inoltre, se sei un cliente di Oanda, in realtà ti sovvenzionano il costo del server, rendendolo GRATUITO!

Una Visione Oggettiva

Se vogliamo dare un giudizio oggettivo, dobbiamo essere critici e onesti riguardo alle carenze della piattaforma (e ce ne sono alcune).

  • Lo sviluppo, il debug e il testing è un processo lungo e lento nel caso si utilizza la piattaforma online. L’esecuzione del codice, ogni volta, può richiedere fino a 30 secondi per compilare, analizzare ed eseguire il codice. L’analisi da sola richiede in media 15 secondi e questo deve essere fatto prima di vedere qualsiasi tipo di errore. Quindi, se stai esplorando o eseguendo il debug e provando cose diverse, preparati a lenti progressi. Ad essere onesti, sembra esserci un’opzione per lo sviluppo locale. Gli utenti possono scaricare il motore “lean” ed eseguirlo localmente. Tuttavia, credo che la maggior parte degli utenti occasionali / al dettaglio non seguirà questa strada.
  • Documentazione, anche se ce n’è molta, personalmente ho trovato difficile trovare quello che mi interessava. I programmatori C# hanno un’eccellente funzione di copilota che fornisce suggerimenti sulla documentazione e fornisce frammenti ci codice delle varie risorse online. Sfortunatamente, il copilota non è disponibile in Python, il mio linguaggio preferito.
  • USA – Centrico: i dati disponibili sono davvero spettacolari ma sono molto focalizzati sui mercati statunitensi. Se si desidera eseguire il backtest delle azioni nel resto del mondo, è comunque necessario procurarsi altri fornitori di dati. Detto questo, i dati del Forex, Crypto e CFD sono disponibili per le persone che non sono interessate alle azioni statunitensi.

Risorse Limitate

Poiché si sta usando le risorse di altre persone, ci sono alcune sensibili limitazioni. Ovviamente, QuantConnect non vuole che un utente blocchi accidentalmente il sistema o prosciughi tutte le risorse. Pertanto, sono state previste le seguenti limitazioni:

  • Limiti di stampa: ogni grafico è limitato a un certo numero di punti. Se si supera il numero massimo di punti, la stampa viene interrotta. Pertanto, se si desidera eseguire un backtest con molti dati, non sarà possibile tracciare completamente tutti i dati. Pertanto, la stampa deve essere eseguita durante periodi di test più brevi per verificare che l’algoritmo funzioni come previsto.
  • Limite di logging giornaliero. Ogni utente può creare solo x Kb nei file di log. Ciò significa che devi essere un po’ attento ai dati che decidi di stampare nei log e chiederti se sono veramente utili.
  • Pacchetti: poiché siamo in esecuzione in un sistema con un perimetro ben limitato, solo una manciata di pacchetti sono ufficialmente disponibili per l’importazione. Fortunatamente, i pacchetti che possiamo importare sono quelli più utilizzati e che probabilmente sono esenziali,  come Panda, Numpy, Sci-kit learn, statsmodels ecc.

Iniziamo

Il prossimo post relativo a QuantConnect riguarderà un’introduzione di base per iniziare a utilizzare Python su QuantConnect. Segue un formato simile (ove possibile) agli articoli introduttivi delle altre piattaforme descritte su DataTrading.info

Creare un Indicatore con Tradingview

Questo articolo fa anche parte della serie introduttiva su Tradingview. In questo tutorial, seguiremo passaggi simili a quelli descritti nell’articolo Tradingview: il primo Script, con la differenza che in questo caso si vuole creare un indicatore invece che una strategia.

Se non sai come aprire l’editor di pine-script, ti suggerisco di leggere l’articolo menzionato sopra. Ti guiderà attraverso l’apertura di un grafico e l’accesso all’editor.

L'indicatore

Prima di iniziare a sporcarci le mani, è opportuno descrivere la logica dell’indicatore che si vuole creare. In questo articolo si descrive come creare un indicatore RSI con un doppio intervallo di tempo. L’indicatore traccia i valori RSI relativi a 2 timeframe. La prima linea tracciata corrisponde al timeframe corrente del grafico, mentre la seconda è relativa ad un timeframe selezionato dall’utente. L’idea base consiste nel ritenere probabile un’inversione del trend (o almeno un ritorno verso la media) di uno strumento quando il prezzo è ipercomprato / ipervenduto contemporaneamente su due diversi timeframe. Questo indicatore è adatto in condizioni di mercato laterale.

Gli argomenti di base trattati in questo tutorial sono:

  • Comprensione della funzione di studio
  • Aggiunta degli ingressi dell’indicatore
  • Importazione dei dati relativi ad un diverso timeframe
  • Chiamata ad una funzione RSI
  • Mostrare graficamente le linee orizzontali e le linee RSI

Ok, vediamo in dettaglio l’implementazione di questo indicatore

Il Codice

Di seguito puoi trovare il codice completo. A seguire si descrive in dettaglio la logica di ogni sezione di questo codice.
                    
//@version=3
study("Dual Strength RSI", "DS-RSI")
 
// Inputs
otf = input(defval="D", title="Second Momentum Timeframe", type=resolution)
otf_period = input(defval=14, title="Look Back Period (2nd Timeframe)", type=integer)
ctf_period = input(defval=14, title="Look Back Period (Chart Timeframe)", type=integer)
ob = input(defval=70, title="Overbought Area", type=integer)
os = input(defval=30, title="Oversold Area", type=integer)
 
//Get the data
otf_rsi = security(tickerid, otf, rsi(close, otf_period))
 
//Calculate RSI Values
ctf_rsi = rsi(close, ctf_period)
 
//Plot
hline(ob, title='Overbought Line', color=black, linestyle=dashed, linewidth=1)
hline(os, title='Oversold Line', color=black, linestyle=dashed, linewidth=1)
plot(otf_rsi, title='OTF RSI', color=blue, style=line, linewidth=3)
plot(ctf_rsi, title='CTF RSI', color=green, style=line, linewidth=3)                    
                

Spiegazione del Codice

Iniziato dall’intestazione del codice con la funzione study().
                    
study("Dual Strength RSI", "DS-RSI")                    
                

La funzione study deve essere inclusa in ogni script. Tradingview classifica questo tipo di funzione come una “funzione di annotazione“. Come suggerisce il nome, queste funzioni annotano le informazioni che appaiono sul grafico. Ad esempio, assegnando un titolo all’indicatore presente nel grafico. Nell’esempio precedente si assegna all’indicatore un titolo completo e una notazione breve.

 

Inputs

                    
otf = input(defval="D", title="Second Momentum Timeframe", type=resolution)
otf_period = input(defval=14, title="Look Back Period (2nd Timeframe)", type=integer)
ctf_period = input(defval=14, title="Look Back Period (Chart Timeframe)", type=integer)
ob = input(defval=80, title="Overbought Area", type=integer)
os = input(defval=20, title="Oversold Area", type=integer)                    
                

A seguire si ha la sezione degli input. Gli input forniscono i parametri dell’indicatore che possono essere modificati dopo aver aggiunto l’indicatore al grafico. Offrono agli altri utenti la possibilità di modificare le impostazioni dell’indicatore nel caso non condividino le impostazioni di default. Inoltre, gli utenti possono salvare la propria configurazione per qualsiasi indicatore. Non c’è bisogno di codificare queste logiche perchè sono integrate nelle funzionalità stardand della piattaforma.

  • defval = valore predefinito per il parametro
  • title = Il testo che appare nella casella di input
  • type = il tipo di valore che deve essere inserito. Si noti che un parametro è di tipo “resolution“. Questa è una variabile speciale che fornisce l’elenco di tutti i timeframe supportati.

Di seguito si mostra un esempio della rappresentazione grafica di questi input.

 

Import di altri dati

                    
//Get the data
otf_rsi = security(tickerid, otf, rsi(close, otf_period))                    
                
Vediamo ora l’importazione dei dati. Questa linea di codice usa una funzione chiamata security() per importare i dati all’interno dello script. I dati importati potrebbero provenire da un diverso timeframe dello stesso strumento, dallo stesso timeframe ma di uno strumento diverso oppure da un timeframe e uno strumento completamente diversi da quelli rappresentati nel grafico principale.
  • tickerid = È un’altra variabile speciale che si riferisce allo strumento nel grafico principale. Questo ci consente di ottenere facilmente un altro timeframe dello stesso strumento. Ancora più importante, dato che non si specifica il nome di uno strumento (ad esempio LON: VOD), questo indicatore può essere applicato a qualsiasi strumento senza bisogno di modificare il codice.
  • otf = È la variabile restituita dalla funzione di input che abbiamo aggiunto sopra.
  • rsi (close, otf_period) = Restituisce una serie RSI per il timeframe selezionato. In questo caso l’RSI è calcolato  a partire dai valori close di ogni barra di quel timeframe.
Plotting
                    
//Plot
hline(ob, title='Overbought Line', color=black, linestyle=dashed, linewidth=1)
hline(os, title='Oversold Line', color=black, linestyle=dashed, linewidth=1)
plot(otf_rsi, title='OTF RSI', color=blue, style=line, linewidth=3)
plot(ctf_rsi, title='CTF RSI', color=green, style=line, linewidth=3)                    
                
Infine si tracciano tutte le linee sul grafico. La funzione hline() traccia le linee orizzontali. La funzione plot() traccia i valori della serie RSI. Per concludere penso che gli argomenti delle keyword siano abbastanza autoesplicativi.

Il Risultato

Per vedere il risultato finale del nostro indicatore è sufficiente:

  1. Aprire Tradingview
  2. Scrivere il codice nell’editor di pine-script.
  3. Premere il pulsante “Aggiungi al grafico”
Questo è tutto quello che c’è da fare per creare un indicatore con pine-script di tradingview. Con questo codice di partenza prova a giocare con il setup dei dati importando diversi strumenti, prova un mix di indicatori, gioca con le funzioni di disegno e vedi se riesci a trovare qualche combinazione interessante.

Tradingview: lookahead dei dati realtime e dei dati storici

Per quanto la documentazione di pine-script sia ben fatta, ci sono ancora parti non spiegate sufficientemente bene, oppure è rivolta ad un target di pubblico con competenze molto avanzate. Ho quindi deciso di scrivere questo articolo dopo aver speso un po’ di tempo a studiare le differenze tra i dati “in tempo reale” e i dati storici e come l’utilizzo di un tipo o dell’altro possa modificare i calcoli prodotti da un indicatore. Questo è particolarmente vero quando si inizia ad utilizzare la keyword lookahead. Ne parleremo più avanti …

Nota: se hai appena iniziato ad utilizzare pine-script, ho scritto un’introduzione nell’articolo “Trading View: il primo Script”

Dati Storici vs Dati Real-time

I dati “real-time” di Tradingview non devono essere confusi con i dati in tempo reale forniti dalle borse e dagli exchange. In pine-script i dati in tempo reale sono tutti i dati acquisiti durante la creazione di una candela (anche se i dati stessi sono in ritardo). Al contrario, i dati storici si riferiscono a qualsiasi candela chiusa prima di aggiungere qualsiasi indicatore sul grafico. Sembra logico vero? E’ necessario però fare attenzione a un paio di cose:

  • I calcoli effettuati su dati in tempo reale quasi sempre hanno risultati diversi rispetto a quelli effettuati sui dati storici. Questo può sembrare ovvio, ma negli esempi seguenti si evidenzia come questo può provocare effetti indesiderati.
  • I dati in tempo reale non diventano dati storici una volta completata la candela. In altre parole, tali dati non sono elaborati nello stesso modo in cui sono elaborati i dati storici quando si aggiunge l’indicatore per la prima volta. Anche in questo caso, i seguenti esempi evidenziano queste criticità.

Andiamo avanti con un esempio pratico e creiamo un indicatore.

L'indicatore

L’indicatore che si vuol utilizzare è un semplice indicatore che sovrappone i valori giornalieri di High e Low su un grafico infragiornaliero. Questo potrebbe essere utile quando si cercano breakout sopra / sotto il massimo / minimo del giorno precedente.

Esempio 1 - Livelli Base

Il seguente codice è l’esempio più semplice di questo articolo. L’indicatore raccoglie semplicemente valori alti e bassi per lo stesso strumento usando un timeframe giornaliero. Quindi traccia questi valori nel timeframe corrente.

                    
//@version=3
study("Barmerge Tests", overlay=true)
 
daily_high = security(tickerid, "D", high)
daily_low = security(tickerid, "D", low)
 
plot(daily_high, style=cross, color=green)
plot(daily_low, style=cross, color=red)                    
                

Come al solito, puoi copiare ed incollare il codice precedente direttamente nell’editor di pine-script di Tradingview ed aggiungire l’indicatore al tuo grafico.

Risultati dell'esempio 1

Nell’immagine precedente si può notare come i dati storici sono calcolati e tracciati come previsto. Si ha una bella linea retta dall’inizio alla fine di ogni giorno. Tuttavia, guarda cosa succede quando arrivano i dati in tempo reale. Le barre si spostano verso l’alto nel bel mezzo della giornata! Dopo aver esaminato più da vicino le linee generate dopo l’aggiunta dell’indicatore (dati in tempo reale), si può notare come le linee hanno iniziato a far riferimento ai valori High / Low del giorno corrente.

Lookahead

Il Lookahead permette di includere i dati futuri nei calcoli di un indicatore. Se si dichiara "lookahead_on", l’indicatore guarderà avanti per trovare alti e bassi per il resto della giornata. Li userà quindi per tracciare ogni barra infragiornaliera quel giorno, indipendentemente dal fatto che siamo prima o dopo il massimo o il minimo di quel giorno. L’esempio seguente mostra questo in modo più dettagliato.

Example 2 – lookahead attivo

Nell’esempio seguente si utilizza attivamente la funzionalità di lookahead. Se non si fornisce un argomento per la kyword “lookaheda”, il valore di default è OFF (barmerge.lookahead_off).

                    
//@version=3
study("Barmerge Tests", overlay=true)
 
daily_high = security(tickerid, "D", high, lookahead=barmerge.lookahead_on)
daily_low = security(tickerid, "D", low, lookahead=barmerge.lookahead_on)
 
plot(daily_high, style=cross, color=green)
plot(daily_low, style=cross, color=red)                    
                


Risultati dell'esempio 2

Nella figura precedente si mostra l’indicatore appena stato aggiunto al grafico. Per i dati storici, il massimo e il minimo della giornata sono tracciati prima che questi avvengano effettivamente durante la giornata!

Non utilizzare mai questo tipo di indicatore per il backtest. Ad esempio, è possibile overfittare i risultati aprendo una posizione short all’inizio della giornata e chiudendola prima della fine della stessa giornata. Si avrebbero ottime prestazioni ma non sarebbero applicabili nel mondo reale.

Per i dati in tempo reale, il calcolo restituisce valori diversi all’interno della stessa giornata, quando il prezzo si sposta oltre il massimo o il minimo corrente. Questo è ovvio perché non possiamo conoscere in anticipo il valore delle candele che si devono ancora formare e che potrebbero avere high più alti e low più bassi.

Di seguito è riportato il grafico dell’indicatore che utilizza i dati in tempo reale dove il prezzo si sposta oltre i dati storici High e Low già noti.

Esempio 3 - Lookahead on e l'index.

In questo caso si può vedere che le linee High e Low rappresentano i valori relativi ai giorni precedenti. Inoltre, i dati in tempo reale forniscono lo stesso valore.
                    
//@version=3
study("Barmerge Tests", overlay=true)
 
daily_high = security(tickerid, "D", high[1], lookahead=barmerge.lookahead_on)
daily_low = security(tickerid, "D", low[1], lookahead=barmerge.lookahead_on)
 
plot(daily_high, style=cross, color=green)
plot(daily_low, style=cross, color=red)                    
                


Risultati dell'esempio 3

Qui possiamo vedere che le linee High e Low rappresentanto i valori relativi ai giorni precedenti. Inoltre, i dati in tempo reale forniscono lo stesso valore.

Indicatore di Trendline per BackTrader

Questo è un frammento di codice per un indicatore di Trendline. Come suggerisce il nome, calcola il valore del prezzo in diversi punti di una trendline e, di conseguenza, genera segnali di acquisto e vendita. In alcuni casi mi piace poter adottare un approccio semi-automatico al trading algoritmico. Si potrebbe individuare una bella trendline su Tradingview ma si vuole eseguire operazioni in un ambiente di forward testing, utilizzando Backtrader, quando il prezzo raggiunge la trendline. In quanto tale, considero questo un indicatore a breve termine che può essere utilizzato fino a quando la trendline non viene rotta. L’indicatore di trendline descritto in questo articolo è destinato a essere utilizzato nel framework di Backtrader, ma i calcoli utilizzati per calcolare la trendline possono essere facilmente trasferiti su altri framework.

Background Matematico

Se come me, non sei un mago della matematica, potrebbe essere necessario un po’ di teoria per capire le equazioni contenute nel codice. E’ necessario quindi introdurre quelle che nel mondo della matematica sono conosciute come “equazioni lineari“, al fine di poter sviluppare questo indicatore.

Un tutorial ben scritto e molto utile, anche se in inglese, è il seguente:
mathplanet.com/education/algebra-1/formulating-linear-equations/writing-linear-equations-using-the-slope-intercept-form

Il concetto principale è che per poter calcolare il prezzo della trendline in qualsiasi momento, è necessario calcolare la velocità con cui cambia la pendenza tra due punti temporali. Questi punti temporali sono i due prezzi inseriti come input per l’indicatore. Come identificare questi due punti dipende da te. Come accennato in precedenza, identifico e traccio in modo discrezionale la trendline su Tradingview e quindi prendo nota del punto iniziale e del punto finale da utilizzare con questo indicatore.

L’equazione a cui dobbiamo arrivare è:

\(
\begin{eqnarray}
y = mx + b
\end{eqnarray}
\)

Dove:

y = prezzo

m = pendenza

x = data / ora

b = intersezione con l’asse y

A questo punto, una cosa che ho trovato difficile da spiegare è come la maggior parte dei tutorial presume che tu possa vedere visivamente l’intersezione con l’asse y, cioè il valore di y quando attraversa l’asse y per x=0. In questo modo:

Come sappiamo, i grafici dei prezzi sono leggermente diversi perchè difficilmente si può tornare indietro nel tempo (dove si potrebbe attraversare l’asse y). Non possiamo vedere dove è l’intersezione dell’asse y ma è possibile calcolarla! Per fare questo è necessario capovolgere un po’ l’equazione.

\( \begin{eqnarray} y = mx + b \end{eqnarray} \)
diventa
\( \begin{eqnarray} b = y – m*x \end{eqnarray} \)

Dopo aver risolto l’equazione, si può utilizzare i valori di x e y del punto iniziale e finale della trendline che hai indiviuato. Vedrai come l’ho implementato nel codice seguente.

Le Regole dell'Indicatore

L’indicatore della trendline genera un segnale di acquisto se il prezzo attraversa la trendline verso l’alto, mentre genera un segnale di vendita se il prezzo la attraversa dal basso. Poiché la trendline funge da supporto o resistenza, sarà sempre inferiore al prezzo quando si cercano segnali di acquisto mentre sarà superiore al prezzo quando si cercano segnali di vendita.

Il Codice

                    
class TrendLine(bt.Indicator):
 
    lines = ('signal','trend')
    params = (
        ('x1', None),
        ('y1', None),
        ('x2', None),
        ('y2', None)
    )
 
    def __init__(self):
        self.p.x1 = datetime.datetime.strptime(self.p.x1, "%Y-%m-%d %H:%M:%S")
        self.p.x2 = datetime.datetime.strptime(self.p.x2, "%Y-%m-%d %H:%M:%S")
        x1_time_stamp = time.mktime(self.p.x1.timetuple())
        x2_time_stamp = time.mktime(self.p.x2.timetuple())
        self.m = self.get_slope(x1_time_stamp,x2_time_stamp,self.p.y1,self.p.y2)
        self.B = self.get_y_intercept(self.m, x1_time_stamp, self.p.y1)
        self.plotlines.trend._plotskip = True
 
    def next(self):
        date = self.data0.datetime.datetime()
        date_timestamp = time.mktime(date.timetuple())
        Y = self.get_y(date_timestamp)
        self.lines.trend[0] = Y
 
        #Check if price has crossed up / down into it.
        if self.data0.high[-1] < Y and self.data0.high[0] > Y:
            self.lines.signal[0] = -1
            return
 
        #Check for cross downs (Into support)
        elif self.data0.low[-1] > Y and self.data0.low[0] < Y:
            self.lines.signal[0] = 1
            return
 
        else:
            self.lines.signal[0] = 0
 
    def get_slope(self, x1,x2,y1,y2):
        m = (y2-y1)/(x2-x1)
        return m
 
    def get_y_intercept(self, m, x1, y1):
        b=y1-m*x1
        return b
 
    def get_y(self,ts):
        Y = self.m * ts + self.B
        return Y                    
                


Spiegazione del codice

Si generano due lines per questo indicatore. Una line è la trendline e l’altra line è il segnale. Da notare che la trendline non viene tracciata nel grafico perché in backtrader non è possibile tracciare una line sul grafico principale e un’altra sul grafico secondario. Si deve scegliere l’uno o l’altro. Tecnicamente si potrebbe tracciare entrambi sul grafico secondario, ma risulterebbe un po’ strano poiché la trendline può essere qualsiasi numero (dato che il valore dipende dal prezzo dello strumento) mentre il segnale oscilla solamente tra -1 e 1. Una soluzione alternativa è presentata nella sezione dei risultati. Un’altra cosa da notare sono le date, che devono essere convertite in un timestamp in modo che i calcoli possano essere fatti su un numero e non su un oggetto data. Infine, ci si potrebbe domandare perché si sta costruendo la trendline e richiamando get_y() nel metodo next() invece che nel metodo __init__()? Questo perché backtrader genera un IndexError se si prova ad ottenere le informazioni sulla data all’interno di __init__(). Se qualcuno sa come risolvere il problema, lasciatemi un commento! Probabilmente ho trascurato qualcosa all’interno della documentazione.

I metodi chiave

Se si desidera implementare questo codice in un altro framewoek, i tre metodi fondamentali da implementare sono i seguenti:
                    
    def get_slope(self, x1,x2,y1,y2):
        m = (y2-y1)/(x2-x1)
        return m
 
    def get_y_intercept(self, m, x1, y1):
        b=y1-m*x1
        return b
 
    def get_y(self,ts):
        Y = self.m * ts + self.B
        return Y                    
                

Come in precedenza, è necessario utilizzare i timestamp per i parametri x e richiamare le funzioni nel seguente ordine:

  1. get_slope()
  2. get_y_intercept(), perché ha bisogno del valore di m, risultato di get_slope()
  3. get_y, per ottenere il prezzo finale per qualsiasi data e ora.

Risultati

Come accennato in precedenza, se si esegue questo codice così com’è, l’indicatore non traccia automaticamente nessuna trendline sul grafico di output. Poiché l’indicatore è progettato per produrre segnali di acquisto e vendita, la priorità è data alla stampa della line del segnale.

Come soluzione alternativa, per creare una certa verifica visiva del corretto funzionamento dell’indicatore, è possibile inizializzare un secondo indicatore all’interno della strategia che traccia una media mobile semplice della trendline, con un periodo pari a 1.

                    
self.sma = bt.indicators.MovingAverageSimple(self.ind1.lines.trend, period = 1, plotmaster = self.data0)                    
                
Da notare che, nel codice precedenete, è necessario inserire la riga all’interno del metodo __init__() della strategia, supponendo di aver già inizializzato l’indicatore con il nome ind1 (self.ind1.lines.trend).
Come si può vedere, si ha una trendline che attraversa il grafico e i segnali vengono generati secondo le regole implementate nel codice, come rappresentato nel grafico secondario.

Gestire il Position Sizing con BackTrader – Parte I

Con questo articolo si introduce un argomento fondamentale nel mondo del trading automatico: il dimensionamento delle posizioni. Inizialmente, pensavo di scrivere un solo articolo, ma mentre mi immergevo sempre più in profondità nell’argomento mi sono reso conto che ci sarebbero stati troppi contenuti per un solo articolo di ragionevole durata. In quanto tale, questo primo articolo si concentra sui concetti fondamenti per lo sviluppo dei sizer e fornisce un paio di semplici esempi. Il secondo articolo approfondisce alcuni aspetti, ed i particolare a comminfo. Dato che comminfo è una classe,si ha numerosi metodi che possono essere utilizzati per far sì che i tuoi algoritmi di dimensionamento tengano conto delle commissioni, degli interessi e delle posizioni aperte.

La classe Sizer

I Sizer sono classi che possono essere caricate in cerebro e utilizzate per decidere quante azioni, quote, contratti, ecc, acquistare o vendere ogni volta che viene chiamato self.buy() o self.sell(). I sizers sono utilizzati per un calcolo della posizione solo quando non viene fornita alcuna dimensione. In altre parole, se il tuo script contiene una chiamata di acquisto come self.buy(size=100), il Sizer non verrà chiamato. Tuttavia, se si chiama solo tramite self.buy(), cerebro chiederà al sizer la dimensione da acquistare.

Perchè abbiammo bisogno dei Sizer?

Alcuni trader possono preferire di dichiarare esplicitamente la dimensione, invece di implementare un qualsiasi tipo di logica di dimensionamento all’interno del codice della loro strategia. Se preferisci questo approccio non c’è niente di male, dopotutto ci sono molti modi per risolvere lo stesso problema! Tuttavia, dal mio punto di vista, usare i Sizer permette alcuni vantaggi. Se ti piace avere un codice ben strutturato e suddiviso, allora i Sizer fanno al tuo caso. Inoltre i Sizer consentono di apportare alcune modifiche, piccole o anche più consistenti, alla logica di una strategia senza dover toccare il codice della classe Strategia. La documentazione di Backtrader ha un buon esempio su come un sizer viene utilizzato per trasformare una strategia “long/short” in una strategia “solo long”, semplicemente usando un diverso sizer. Facendo un ulteriore passo avanti, è facile immaginare di poter implementare una libreria di Sizer che ti consenta di implementare la stessa strategia in mercati diversi con schemi commissionali e condizioni commerciali diverse senza dover modificare il codice di strategia principale.

Documentazione: backtrader.com/docu/sizers/sizers.html#practical-sizer-applicability

La Struttura di un Sizer

Un sizer è una sottoclasse di backtrader.Sizer. La sottoclasse ci consente di costruire un oggetto utilizzando la classe principale come base di partenza. L’oggetto eredita quindi tutte le caratteristiche e le funzionalità della classe principale senza dover copiare e incollare il codice nella nuova classe. E’ quindi possibile modificare solo le parti del codice, riscrivendo un metodo (una funzione di classe), un attributo (una variabile di classe) o aggiungendo qualcosa di nuovo. Tutte le parti rimaste intatte continueranno a funzionare nello stesso modo in cui erano state scritte nella classe genitore. Nel codice qui sotto è presente backtrader.Sizer scritto come bt.Sizer poiché generalmente utilizzo questo istruzione import backtrader as bt
                    
class exampleSizer(bt.Sizer):
    params = (('size',1),)
    def _getsizing(self, comminfo, cash, data, isbuy):
        return self.p.size                    
                
Il codice precedente contiene un esempio di ridimensionamento nella sua forma più semplice. Questo ci permetterà di suddividere ed analizzare le componenti chiave di un sizer.

La tupla "params"

I Sizer, proprio come le strategie e gli indicatori possono contenere una tupla di parametri. Avere un set di parametri può offrire una certa flessibilità durante il caricamento del sizer in cerebro e fornire i dati al sizer, che altrimenti non sarebbero disponibili.

_getsizing()

Successivamente abbiamo il metodo _getsizing (). Questo metodo viene chiamato ogni volta che una strategia effettua una chiamata self.buy() o self.sell() senza indicare la dimensione dell’ordine. Il metodo _getsizing() prevede una serie di parametri, provenienti dal framework Backtrader. Questi sono:
  • comminfo: fornisce l’accesso a vari metodi che permettono di conoscere i dati del iano commissionale previsto dal broker. Ciò consente di valutare tutte le commissioni relative al singolo trade prima di decidere la dimensione. Nella seconda parte di questo tutorial si descrive comminfo in modo più dettagliato.
  • cash: fornisce la quantità di denaro disponibile sul conto.
  • data: fornisce l’accesso al feed dei dati. Ad esempio, tramite questo parametro possiamo accedere all’ultimo prezzo di chiusura.
  • isbuy: è un valore booleano (Vero / Falso) che identifica se l’ordine è un ordine di acquisto. Se è falso, allora l’ordine è un ordine di vendita.

Strategy e Broker

Ci sono inoltre le due classi accessibili, ma non visibili nel codice precedente, self.strategy e self.broker. Con questi due oggetti si ha praticamente accesso a tutto il necessario per creare complessi algoritmi di dimensionamento. Da sottolineare però, nel caso si eseguono calcoli basati su attributi di strategia, è nessario assicurarsi che siano attributi / variabili standard nel framework. In altre parole, attributi disponibili per tutte le strategie (anziché attributi personalizzati, aggiunti al codice per proprio conto). In caso contrario si rinuncia alla portabilità del sizer poiché questo funzionerà solo con la strategia dove codificato quello specifico attributo.

Non dimenticarti di restituire qualcosa

Infine, è necessario ricordarsi di restituire un valore alla fine del calcolo. Se lo si dimentica, la strategia non effettuerà alcun ordine.

Il Codice

Il seguente codice contiene tre esempi di Sizer. Il primo prevede dimensioni fisse, analogo a quanto mostrato nel blocco precedente. Il secondo è un esempio di sizer che stampa tutti i parametri del metodo _getsizing () ad eccezione di comminfo (che vedremo più dettagliatamente in seguito). L’esempio finale fornisce l’implementazione di un pratico ridimensionamento per limitare le dimensioni di un trade a una percentuale della liquidità totale del conto. Questo è un comune algoritmo di position sizing che molte strategie utilizzano per limitare il rischio.

import backtrader as bt
from datetime import datetime
import math


class exampleSizer(bt.Sizer):
    params = (('size',1),)
    def _getsizing(self, comminfo, cash, data, isbuy):
        return self.p.size

class printSizingParams(bt.Sizer):
    '''
    Prints the sizing parameters and values returned from class methods.
    '''
    def _getsizing(self, comminfo, cash, data, isbuy):
        #Strategy Method example
        pos = self.strategy.getposition(data)
        #Broker Methods example
        acc_value = self.broker.getvalue()

        #Print results
        print('----------- SIZING INFO START -----------')
        print('--- Strategy method example')
        print(pos)
        print('--- Broker method example')
        print('Account Value: {}'.format(acc_value))
        print('--- Param Values')
        print('Cash: {}'.format(cash))
        print('isbuy??: {}'.format(isbuy))
        print('data[0]: {}'.format(data[0]))
        print('------------ SIZING INFO END------------')

        return 0

class maxRiskSizer(bt.Sizer):
    '''
    Returns the number of shares rounded down that can be purchased for the
    max rish tolerance
    '''
    params = (('risk', 0.03),)

    def __init__(self):
        if self.p.risk > 1 or self.p.risk < 0:
            raise ValueError('The risk parameter is a percentage which must be'
                'entered as a float. e.g. 0.5')

    def _getsizing(self, comminfo, cash, data, isbuy):
        if isbuy == True:
            size = math.floor((cash * self.p.risk) / data[0])
        else:
            size = math.floor((cash * self.p.risk) / data[0]) * -1
        return size



class firstStrategy(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() else: if self.rsi > 70:
                self.close()

    def notify_trade(self, trade):
        if trade.justopened:
            print('----TRADE OPENED----')
            print('Size: {}'.format(trade.size))
        elif trade.isclosed:
            print('----TRADE CLOSED----')
            print('Profit, Gross {}, Net {}'.format(
                                                round(trade.pnl,2),
                                                round(trade.pnlcomm,2)))
        else:
            return


#Variable for our starting cash
startcash = 10000

#Create an instance of cerebro
cerebro = bt.Cerebro()

#Add our strategy
cerebro.addstrategy(firstStrategy)

#Get Apple data from Yahoo Finance.
data = bt.feeds.YahooFinanceData(
    dataname='AAPL',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True
    )

#Add the data to Cerebro
cerebro.adddata(data)

# Set our desired cash start
cerebro.broker.setcash(startcash)

#add the sizer
cerebro.addsizer(printSizingParams)

# Run over everything
cerebro.run()

#Get final portfolio Value
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

#Print out the final result
print('----SUMMARY----')
print('Final Portfolio Value: ${}'.format(portvalue))
print('P/L: ${}'.format(pnl))

#Finally plot the end results
cerebro.plot(style='candlestick')
                    
import backtrader as bt
from datetime import datetime
import math


class exampleSizer(bt.Sizer):
    params = (('size',1),)
    def _getsizing(self, comminfo, cash, data, isbuy):
        return self.p.size

class printSizingParams(bt.Sizer):
    '''
    Prints the sizing parameters and values returned from class methods.
    '''
    def _getsizing(self, comminfo, cash, data, isbuy):
        #Strategy Method example
        pos = self.strategy.getposition(data)
        #Broker Methods example
        acc_value = self.broker.getvalue()

        #Print results
        print('----------- SIZING INFO START -----------')
        print('--- Strategy method example')
        print(pos)
        print('--- Broker method example')
        print('Account Value: {}'.format(acc_value))
        print('--- Param Values')
        print('Cash: {}'.format(cash))
        print('isbuy??: {}'.format(isbuy))
        print('data[0]: {}'.format(data[0]))
        print('------------ SIZING INFO END------------')

        return 0

class maxRiskSizer(bt.Sizer):
    '''
    Returns the number of shares rounded down that can be purchased for the
    max rish tolerance
    '''
    params = (('risk', 0.03),)

    def __init__(self):
        if self.p.risk > 1 or self.p.risk < 0:
            raise ValueError('The risk parameter is a percentage which must be'
                'entered as a float. e.g. 0.5')

    def _getsizing(self, comminfo, cash, data, isbuy):
        if isbuy == True:
            size = math.floor((cash * self.p.risk) / data[0])
        else:
            size = math.floor((cash * self.p.risk) / data[0]) * -1
        return size



class firstStrategy(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() else: if self.rsi > 70:
                self.close()

    def notify_trade(self, trade):
        if trade.justopened:
            print('----TRADE OPENED----')
            print('Size: {}'.format(trade.size))
        elif trade.isclosed:
            print('----TRADE CLOSED----')
            print('Profit, Gross {}, Net {}'.format(
                                                round(trade.pnl,2),
                                                round(trade.pnlcomm,2)))
        else:
            return


#Variable for our starting cash
startcash = 10000

#Create an instance of cerebro
cerebro = bt.Cerebro()

#Add our strategy
cerebro.addstrategy(firstStrategy)

#Get Apple data from Yahoo Finance.
data = bt.feeds.YahooFinanceData(
    dataname='AAPL',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True
    )

#Add the data to Cerebro
cerebro.adddata(data)

# Set our desired cash start
cerebro.broker.setcash(startcash)

#add the sizer
cerebro.addsizer(printSizingParams)

# Run over everything
cerebro.run()

#Get final portfolio Value
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

#Print out the final result
print('----SUMMARY----')
print('Final Portfolio Value: ${}'.format(portvalue))
print('P/L: ${}'.format(pnl))

#Finally plot the end results
cerebro.plot(style='candlestick')                    
                

Spiegazione del codice

Prima di tutto, dato che abbiamo più esempi, vale la pena notare come passare da uno all’altro. Per cambiare il sizer in uso, è sufficiente modificare questa riga:
                    
#add the sizer
cerebro.addsizer(exampleSizer, size=50)                    
                

In questa:

                    
#add the sizer
cerebro.addsizer(printSizingParams)                    
                

O in questa:

                    
#add the sizer
cerebro.addsizer(maxRiskSizer, risk=0.2)                    
                

Import math

In questo codice ho incluso un modulo Python aggiuntivo. Il modulo math fornisce math.floor() che semplifica l’arrotondamento per difetto al numero più vicino. Con un algoritmo di rischio massimo, non si può mai arrotondare per eccesso perché può portarci potenzialmente oltre al nostro limite di rischio. L’arrotondamento per diffetto non potrà mai farlo. La documentazione ufficiale di Python per il modulo math è disponibile al seguente link: docs.python.org/3/library/math.html

exampleSizer ()
Il sizer di esempio è stato incluso solo per discutere l’anatomia di un sizer. Tuttavia, fornisce anche un esempio di un sizer che utilizza una dimensione fissa. Una versione quasi identica appare nella documentazione ufficiale di Backtrader: www.backtrader.com/docu/sizers/sizers.html#sizer-development

printSizingParams ()
Per verificare il codice ho incluso un semplice sizer che stampa il contenuto dei parametri del calibratore. Di solito vedere esattamente cosa viene restituito mi aiuta a capire esattamente cosa sta facendo il parametro e come posso usarlo. È doppiamente utile quando si ha problemi a leggere la documentazione. L’esecuzione del codice fornisce il seguente output:

Il primo parametro stampato dal codice è un esempio dei dati che possono essere consultati tramite la classe di strategia, usando self.strategy.getposition(). Si può subito notare che “pos” è un oggetto posizione anziché un semplice valore come “cash“. Allo stesso modo la variabile account value fornisce un esempio dei dati a cui è possibile accedere facilmente tramite self.broker(). Nota nel caso live trading, è indispensabile assicurarsi quali metodi live del broker prescelto sono supportari nella documentazione di Backtrader. Ho dovuto implementare alcune soluzioni alternative perchè alcuni metodi non erano disponibili in Backtrader durante il trading live con Oanda. maxRiskSizer maxRiskSizer() calcola semplicemente la posizione della dimensione massima che puoi assumere senza superare una determinata percentuale del capitale disponibile nel tuo account. La percentuale viene impostata tramite il parametro “risk” presente nella tupla dei parametri. La percentuale viene immessa come float tra 0 e 1 e il valore default è pari a 3%, ma può essere impostato su qualsiasi valore quando viene caricato in cerebro. Per quelli come voi che non sono maghi matematici, il simbolo *-1 nella seguente riga di codice modifica il valore della dimensione da positiva a negativa. Si ha bisogno di una dimensione negativa nel caso di un ordine di vendita.
                    
size = math.floor ((cash * self.p.risk) / data [0]) * -1                    
                
​Attenzione a maxRiskSizer buy.sell() Se hai l’abitudine di chiudere una posizione con una dimensione fissa utilizzando buy.sell(), devi essere consapevole che l’uso di maxRiskSizer può far sì che le posizioni non vengano chiuse e causi entrate indesiderate. Questo perché i livelli di liquidità sono dinamiche, cioè si modificano quando i trade sono aperti e chiusi, quindi la precentuale x% di apertura è ora l’x% di un totale diverso. In altre parole, il valore dello strumento sta cambiando in modo tale che x% comporti una diversa quantità di azioni / contratti acquistati. La semplice soluzione è chiudere le posizioni con la fuzione ​self.close(). Questo calcolerà la dimensione corretta necessaria per chiudere completamente una posizione.

Ottimizzare le Strategie con BackTrader

Dopo aver creato una strategia di base ed averla analizzata, il prossimo passo consiste nell’ottimizzare questa strategia. L’ottimizzazione è un processo di verifica che assegna valori diversi per ogni parametro della strategia al fine di individuare quale set di valori (o configurazione) fornisce i migliori risultati, in termini di profitto. Da notare che non tutti i trader algoritmici concordano sul fatto che questo processo può portare a risultati migliori. Infatti è molto facile cadere nella trappola del sovradimensionamento dei dati (meglio noto come overfitting).

Perchè Ottimizzare?

La motivazione è che i mercati sono in continua evoluzione. Abbiamo mercati rialzisti, mercati ribassisti, periodi di inflazione, periodi di deflazione, tempi instabili e momenti di serenità. Se ciò non bastasse, strumenti diversi hanno ritmi diversi e mercati diversi hanno nature e comportamenti diversi. Ciò significa che i parametri per uno strumento in un mercato potrebbero non essere ottimali per un altro strumento in un altro mercato.

...ma attenzione all'OverFitting

Quando si ottimizzano le strategie, è necessario prestare la massima attenzione a non creare parametri che funzionino solo per in un determinato “momento nel tempo”. Può essere allettante ottenere i migliori risultati dall’ottimizzazione e quindi prevedere l’esecuzione live della strategia con tali parametri. Tuttavia, se il set di dati è limitato ad un breve periodo di tempo o copre solamente una determinata condizione di mercato, è possibile che i parametri siano ottimizzati solo quel specifico momento nel passato, quindi del tutto inutilizzabili nel futuro. Nella statistica o nel machine learning, infatti, un modello statistico o un algoritmo viene applicato ai dati di addestramento (training) in modo che possa essere utilizzato per fare previsioni nel futuro. L’overfitting si verifica quando il modello o l’algoritmo è troppo complesso per il set di dati preso in considerazione. In questo contesto, complesso significa che l’algoritmo è ottimizzato a tal punto da adattarsi (fit) solo a quei dati. L’overfitting provoca reazioni eccessive se applicato all’esterno dei dati di training. Nel nostro ambiente di backtesting si possono considerare i nostri dati storici di backtest come i dati di training e la nostra strategia come l’algoritmo.

Requisiti

Il codice in questo articolo fa seguito al codice sviluppato nel precedente articolo Backtrader: Primo Script e fa parte della serie introduttiva a BackTrader. Se è la prima volta che senti parlare di Backtrader e / o Python, ti suggerisco di iniziare dall’articolo Setup di base per Python e BackTrader

Il Codice

Il codice di questo tutorial è costruito su tre esempi. Ogni esempio sarà accompagnato da specifici commenti e output.

Parte 1° - Aggiungere i Parametri

Prima di poter ottimizzare il codice dobbiamo fornire alla strategia qualcosa da ottimizzare, cioè alcuni parametri modificabili. Se si osserva il codice del precedente articolo, si può notare come abbiamo impostato a 21 il parametro relativo al periodo del RSI. Questa è una codifica rigida cioè il parametro è valorizzato all’interno del codice e non può essere successivamente modificato. Per effettuare l’ottimizzazione è necessario rendere questo parametro configurabile quando si carica la strategia all’interno del motore cerebro.
                    
import backtrader as bt
from datetime import datetime

class rsiStrategy(bt.Strategy):
    params = (
        ('period',21),
        )

    def __init__(self):
        self.rsi = bt.indicators.RSI_SMA(self.data.close, period=self.params.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)


#Variable for our starting cash
startcash = 10000

#Create an instance of cerebro
cerebro = bt.Cerebro()

#Add our strategy
cerebro.addstrategy(rsiStrategy, period=14)

#Get Apple data from Yahoo Finance.
data = bt.feeds.YahooFinanceData(
    dataname='AAPL',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True
    )

#Add the data to Cerebro
cerebro.adddata(data)

# Set our desired cash start
cerebro.broker.setcash(startcash)

# Run over everything
cerebro.run()

#Get final portfolio Value
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

#Print out the final result
print('Final Portfolio Value: ${}'.format(portvalue))
print('P/L: ${}'.format(pnl))

#Finally plot the end results
cerebro.plot(style='candlestick')                    
                

Spiegazione del codice

Prima di tutto, facciamo riferimento al codice nel nostro primo script. Questo permette facilmente di notare alcune modifiche. Di seguito sono riportati uno snippet del codice per dichiarare la classe e il metodo __init__() del primo script
                    
class rsiStrategy(bt.Strategy):

    def __init__(self):
        self.rsi = bt.indicators.RSI_SMA(self.data.close, period=21)                    
                
Mentre in questo esempio, il codice è diventato:
                    
class rsiStrategy(bt.Strategy):
    params = (
        ('period',21),
        )

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

In questo caso è stata aggiunta la tupla “params“, essa contiene altre tuple che sono utilizzate per dichiarare i parametri della strategia. Che cos’è una tupla? Una tupla è un elenco di elementi fissi che non possono essere cambiati o modificati. Nei linguaggi di programmazione è abitudine differenziare ciò che è immutabile (non modificabile), come le costanti, e ciò che è mutabile (che può essere modificato), come le variabili. All’interno della tupla dei parametri, si ha un parametro ("period", 21). Il primo elemento è una stringa che identifica il nome / riferimento per il parametro. Il secondo elemento è il valore predefinito per quel determinato parametro. Avere un valore predefinito significa che non è necessario specificare un parametro ogni volta che si esegue la strategia. Se non viene specificato nulla, la strategia verrà eseguita con il valore di default. Puoi inserire tutti i parametri che desideri nella tupla dei parametri. Assicurati solo di aggiungerli come tupla all’interno della tupla principale (nota come tupla nidificata). I parametri della strategia sono accessibili ovunque nella classe. sono infatti gestiti come qualsiasi altro attributo di classe (variabile). Nel metodo __init__() si accede a self.params.period e viene assegnato alla keyword period quando si aggiunge l’indicatore RSI.

Chiamata alla Strategia

                    
cerebro.addstrategy(firstStrategy, period=14)                    
                
Il codice relativo all’aggiunta della strategia all’interno di cerebro è stato modificato in modo da poter specificare la keyword per il parametro. Come accennato in precedenza, questo è facoltativo. Richiamare la strategia in questo modo ci permetterà di ottimizzarla in un secondo momento.

NOTE:

Ci sono un paio di cose a cui prestare attenzione quando si aggiungono parametri. Il primo è che ogni tupla nell’elenco delle tuple necessita di una virgola alla fine . Se sei abituato a scrivere codice in Python, saprai che per gli oggetti list e dict, l’ultimo valore non dovrebbe avere la virgola finale.

Se si digita: (errato)

                    
params = (
        ('period',21)
    )                    
                

Invece di: (corretto):

                    
params = (
        ('period',21),
    )                    
                
Si ottiene un ValueError: ValueError: too many values to unpack (expected 2)   Inoltre, fai attenzione quando aggiungi i tuoi indicatori nel metodo __init__(). Se dimentichi di usare una keyword, puoi ottenere un TypeError. Se si digita: (errato)
                    
def __init__(self):
        self.rsi = bt.indicators.RSI_SMA(self.data.close, self.params.period)                    
                
Invece di: (corretto)
                    
def __init__(self):
        self.rsi = bt.indicators.RSI_SMA(self.data.close, period=self.params.period)                    
                

Si ottiene il seguente errore:

TypeError: __init__() takes 1 positional argument but 2 were given

 
Questo errore può creare molta confusione. Infatti abbiamo aggiunto l’indicatore nel metodo __init__() della strategia, ma in realtà l’errore si riferisce al metodo __init __ () dell’indicatore (classe indicators)! Si potrebbe perdere molto tempo ad eseguire il debug della cosa sbagliata.

Parte 2° - Ottimizzazione

Ora che siamo in grado di inizializzare la strategia con parametri differenti, ottimizzare il codice è piuttosto semplice. Tecnicamente dobbiamo solamente sostituire la linea cerebro.addstrategy() con:

                    
#Add our strategy
cerebro.optstrategy(firstStrategy, period=range(14,21))                    
                
Quindi cerebro eseguirà la strategia per ogni periodo nell’intervallo indicato. Tuttavia, l’output non sarebbe utile. Se vogliamo essere in grado di vedere quale parametro ha le migliori prestazioni si dovrà aggiungere un nuovo metodo alla nostra strategia. Il codice completo è il seguente:
                    
import backtrader as bt
from datetime import datetime

class rsiStrategy(bt.Strategy):
    params = (
        ('period',21),
        )

    def __init__(self):
        self.startcash = self.broker.getvalue()
        self.rsi = bt.indicators.RSI_SMA(self.data.close, period=self.params.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)

    def stop(self):
        pnl = round(self.broker.getvalue() - self.startcash,2)
        print('RSI Period: {} Final PnL: {}'.format(
            self.params.period, pnl))

if __name__ == '__main__':
    #Variable for our starting cash
    startcash = 10000

    #Create an instance of cerebro
    cerebro = bt.Cerebro()

    #Add our strategy
    cerebro.optstrategy(rsiStrategy, period=range(14,21))

    #Get Apple data from Yahoo Finance.
    data = bt.feeds.YahooFinanceData(
        dataname='AAPL',
        fromdate = datetime(2016,1,1),
        todate = datetime(2017,1,1),
        buffered= True
        )

    #Add the data to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(startcash)

    # Run over everything
    strats = cerebro.run()                    
                


Spiegazione del codice

I lettori attenti avranno sicuramente notato che ci state alcune cancellazioni, oltre al nuovo metodo (funzione) aggiunto alla strategia. Innanzitutto diamo un’occhiata al nuovo metodo:

                    
    def stop(self):
        pnl = round(self.broker.getvalue() - self.startcash,2)
        print('RSI Period: {} Final PnL: {}'.format(
            self.params.period, pnl))                    
                

Backtrader eseguirà diversi cicli di backtesting, uno per ogni diverso valore dei parametri, prima di arrivare al termine dello script. Nell’esempio precedente, si ha come output il valore del portafoglio e il PnL (profitti e perdite) alla fine dello script. Questo significa che non si ha visibilità dei risultati dei singoli vedrai i risultati dei singoli backtest se lasciamo l’istruzione print() dopo la fine dell’esecuzione di cerebro. Di conseguenza, un metodo stop() viene aggiunto allo script. Questo metodo fa parte della classe base bt.Strategy e si sta semplicemente sovrascrivendo la logica al suo interno, dato che si eredita la bt.Strategy durante la creazione della classe della nostra strategia. Come suggerisce il nome, si richiama questo metodo quando la strategia si interrompe. Questo è l’ideale per restituire i profitti o le perdite finali al termine del test.

Plotting

Oltre a rimuovere l’istruzioni print() alla fine dello script, è stata rimossa anche la funzione di stampa dei grafici. Quando si effettua l’ottimizzazione, ti consiglio di non graficare l’output perchè, al momento della stesura di questo articolo, il framework prevede la creazione di un grafico alla fine di ogni ciclo della strategia. E’ quindi necessario chiudere manualmente il grafico prima dell’inizio del ciclio successivo. Se si ha molti parametri, questo può richiedere molto tempo e diventare fastidioso.

Risultati della 2° Parte

Quindi sembra che un periodo pari a 17 sia il valore ottimale per questo set di dati. È interessante notare come se il valore fosse diverso di sole 2 unità (un periodo di 19), i risultati sarebbero drasticamente diversi!

Parte 3 - Fare un ulteriore passo avanti

L’esempio precedente è funzionalmente corretto ma secondo me c’è un problema. I risultati sopra riportati non sono ordinati e si potrebbe aver esigenza di qualcosa di più della sola stampa dei risultati. Immagina di avere 3 parametri che possono produrre oltre a 100 combinazioni. Sarebbe piuttosto laborioso e soggetto a errori se si dovesse leggere le righe una per una. In questa parte, vedremo come accedere ai risultati dopo che cerebro avrà terminato la sua elaborazione.
                    
import backtrader as bt
from datetime import datetime

class rsiStrategy(bt.Strategy):
    params = (
        ('period',21),
        )

    def __init__(self):
        self.startcash = self.broker.getvalue()
        self.rsi = bt.indicators.RSI_SMA(self.data.close, period=self.params.period)

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

if __name__ == '__main__':
    #Variable for our starting cash
    startcash = 10000

    #Create an instance of cerebro
    cerebro = bt.Cerebro(optreturn=False)

    #Add our strategy
    cerebro.optstrategy(rsiStrategy, period=range(14,21))

    #Get Apple data from Yahoo Finance.
    data = bt.feeds.YahooFinanceData(
        dataname='AAPL',
        fromdate = datetime(2016,1,1),
        todate = datetime(2017,1,1),
        buffered= True
        )

    #Add the data to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(startcash)

    # Run over everything
    opt_runs = cerebro.run()

    # Generate results list
    final_results_list = []
    for run in opt_runs:
        for strategy in run:
            value = round(strategy.broker.get_value(),2)
            PnL = round(value - startcash,2)
            period = strategy.params.period
            final_results_list.append([period,PnL])

    #Sort Results List
    by_period = sorted(final_results_list, key=lambda x: x[0])
    by_PnL = sorted(final_results_list, key=lambda x: x[1], reverse=True)

    #Print results
    print('Results: Ordered by period:')
    for result in by_period:
        print('Period: {}, PnL: {}'.format(result[0], result[1]))
    print('Results: Ordered by Profit:')
    for result in by_PnL:
        print('Period: {}, PnL: {}'.format(result[0], result[1]))                    
                

Spiegazione del codice

In questo esempio, ci sono alcune modifiche al codice. Innanzitutto abbiamo rimosso il metodo stop() nell’ultimo esempio. Avremo accesso a tutti i valori di cui abbiamo bisogno solo dopo che lo script avrà terminato l’esecuzione. Un altro cambiamento che potrebbe essere poco visibile se si sta semplicemente copiando e incollando il codice è:
                    
cerebro = bt.Cerebro(optreturn=False)                    
                
In questo caso abbiamo aggiunto un nuovo parametro all’inizializzazione di cerebro. Questo parametro modifica ciò che viene restituito da cerebro.run() alla fine dello script. In un normale script cerebro.run() restituisce oggetti completi della classe strategia. Questi oggetti sono creati a partite dal modello della classe rsiStrategy che abbiamo scritto nel codice. Gli oggetti Strategia permettono di accedere a tutti disponibili per cerebro durante il test (indicatori, dati, analizzatori, osservatori, ecc.) anche dopo il termine dell’esecuzione di cerebro. In questo modo si ha accesso a tutti i dati e i risultati. Tuttavia, durante l’ottimizzazione, cerebro.run() restituisce gli oggetti OptReturn come impostazione di default predefinita. Questi sono oggetti limitati dato che contengono solo i parametri e gli analizzatori, al fine di migliorare la velocità di ottimizzazione. Si presume che le metriche importanti necessarie per decidere quali parametri siano i migliori possano essere dedotte solo dagli analizzatori e dai parametri. Tuttavia, poiché gli esempi riportati in questo articolo hanno restituito il profitto finale, è opportuno mantenere questa convenzione anche nell’esempio finale. Per questo motivo, il parametro optreturn deve essere impostato su false poiché le informazioni del broker (per i profitti / perdite) non fanno parte di un analizzatore. Abbiamo bisogno di Cerebro per ricavare oggetti Strategia completi. Il resto del codice di interesse per questo esempio si verifica al termine dell’esecuzione di cerebro.

Ricavare i dati da un oggetto Strategia

                    
# Run over everything
opt_runs = cerebro.run()

# Generate results list
final_results_list = []
for run in opt_runs:
    for strategy in run:
        value = round(strategy.broker.get_value(),2)
        PnL = round(value - startcash,2)
        period = strategy.params.period
        final_results_list.append([period,PnL])                    
                
Cerebro restituisce un elenco di oggetti Strategia per ciascun ciclo tramite la lista dei parametri. In questo esempio, esiste solo una strategia. Tuttavia, poiché viene restituito una lista nidificata (lista di liste), è necessario iterare l’oggetto restituito per due volte per ottenere le informazioni necessarie. Dopo aver ricavato i valori desiderati, questi possono essere aggiunti alla lista final_results_list. Questa lista può essere quindi ordinata come si desidera.
                    
#Sort Results List
by_period = sorted(final_results_list, key=lambda x: x[0])
by_PnL = sorted(final_results_list, key=lambda x: x[1], reverse=True)                    
                
Se non conosci Python, questa parte potrebbe sembrare un po’ complessa. Anche final_results_list è una lista nidificata. Per ordinarla correttamente, dobbiamo fornire una chiave di ordinamento. È quindi necessario passare una funzione all’argomento della keyword key. Un lambda è una piccola funzione formata da una riga che ci consente di utilizzare la chiave di ordinamento. Per ulteriori informazioni, ho aggiunto alcuni link di riferimento per letture di approffondimento alla fine di questo articolo.

Risultati della 3° parte

Eccoci. Questo articolo è diventato molto più lungo di quanto mi aspettassi quando ho iniziato a scriverlo. Se sei riuscito a farcela fino a qui senza saltare, spero che il contenuto abbia fornito qualche consiglio e spunto operativo.

Letture di Approfondimento

Utilizzare gli Analyzers di Backtrader

Una volta compreso come scrivere una strategia di base, il passo successivo consiste nel quantificare la qualità 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

In poche parole, 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 stessi 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 “Backtrader: Primo Script” 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 (Per ulteriori informazioni, consultare il sito www.vantharp.com)

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):
    '''
    Function to print the Technical Analysis results in a nice format.
    '''
    #Get the results we are interested in
    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
    #Designate the rows
    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]
    #Check which set of headers is the longest.
    if len(h1) > len(h2):
        header_length = len(h1)
    else:
        header_length = len(h2)
    #Print the rows
    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))

#Variable for our starting cash
startcash = 100000

#Create an instance of cerebro
cerebro = bt.Cerebro()

#Add our strategy
cerebro.addstrategy(RsiStrategy)

#Get Apple data from Yahoo Finance.
data = bt.feeds.YahooFinanceData(
    dataname='AAPL',
    fromdate = datetime(2009,1,1),
    todate = datetime(2017,1,1),
    buffered= True
    )

#Add the data to Cerebro
cerebro.adddata(data)

# Set our desired cash start
cerebro.broker.setcash(startcash)

# Add the analyzers we are interested in
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="ta")
cerebro.addanalyzer(bt.analyzers.SQN, _name="sqn")

# Run over everything
strategies = cerebro.run()
rsiStrat = strategies[0]

# print the analyzers
tradeAnalysis(rsiStrat.analyzers.ta.get_analysis())
SQN(rsiStrat.analyzers.sqn.get_analysis())

#Get final portfolio Value
portvalue = cerebro.broker.getvalue()

#Print out the final result
print('Final Portfolio Value: ${}'.format(portvalue))

#Finally plot the end results
cerebro.plot(style='candlestick')                    
                


Spiegazione del Codice

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.

                    
# Run over everything
strategies = cerebro.run()
firstStrat = 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"                    
                

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:

                    
# Run over everything
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:

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

Chi l’avrebbe mai detto?!?

BackTrader: Primo Script

Dopo aver creato il nostro ambiente di sviluppo, come descritto in questo articolo, è il tempo di scrivere il nostro primo script.

Obiettivo

Questo articolo ha lo scopo di creare una semplice strategia basata sugli indicatori, mantenendo il codice il più semplice possibile. Cercherò di evitare alcuni concetti più avanzati presenti nella documentazione e in Python in generale. Ad esempio le linee come:
                    
if __name__ == '__main__':                     
                

non saranno inclusi poiché ritengo che per imparare è necessario che i principianti dovrebbero cercare di reperire le informazioni su internet ed inoltre non voglio distrarre il lettore dalle funzionalità strettamente legate alla strategia che si vuole implementare (anche se alcuni programmatori professionisti potrebbero deridere la qualità del codice).

Ambito di backtesting: Per motivi di semplicità, il tutorial non include le commissioni, lo spread o altre considerazioni di backtesting più avanzate come benchmarking, l’ottimizzazione e l’analisi dei trade. Ho anche scelto di escludere la stampa / log per mantenere il codice il più semplicepossibile. (Anche se penso che la stampa / log sia importante per il debug quando aumenta la complessità del codice!)

La Strategia

Al momento della stesura di questo articolo siamo in presenza di un lungo mercato rialzista. Quando si negoziano azioni in un tale mercato, è ragionevolmente pensare di andare long. La ragione di ciò è che si trovano sul lato giusto del momentum long alla base della dinamica ascendente.

Con questo in mente, divertiamoci un po ‘a implementare una strategia “solo long” che andrà long quando un semplice indicatore RSI giornaliero è ipervenduto e si mantiene la posizione fino a quando l’RSI non raggiungerà il livello di ipercomprato.

Entry

  • Quando RSI <30

Exit

  • Quando RSI> 70

Gestione del trade e dimensionamento delle posizioni

  • Non si implementa nessuna gestione del trade. Non è previsto nessun ridimensionamento in / out. Solo semplici acquisti e vendite con un’unica posizione aperta alla volta.
  • Per quanto riguarda le dimensioni della posizione, si semplifica le cose comprando / vendendo 100 azioni alla volta senza fare calcoli per vedere se abbiamo abbastanza liquidità per le dimensioni della posizione (se non abbiamo abbastanza liquidità, backtrader è abbastanza intelligente da rifiutare l’ordine)

Settaggio dell’indicatore

  • Periodo = 21
    Consente di utilizzare una finestra mobile più lunga rispetto ai 14 periodo standard. In teoria ciò dovrebbe tradursi nell’avere meno falsi segnali falsi e il prezzo dovrebbe scendere / aumentare di più prima di essere considerato ipercomprato / ipervenduto.
 

Requisiti

Questo script estrae i dati online da Yahoo. Penso che questo semplifichi le cose per un primo script in quanto non sarà scaricare ed utilizzare i propri dati. Tuttavia, per questo motivo, lo script richiede laa versione backtrader 1.9.49.116 o successiva a causa delle recenti modifiche nell’API di Yahoo.
Per verificare la versione di backtrader che stai utilizzando hai alcune opzioni. Per prima cosa puoi controllare il pip semplicemente digitando:

                    
pip3 list                    
                

(Nota per gli utenti di Windows: invece di “pip3” potrebbe essere “pip” se è installata solo una versione di python)

 

Quindi basta cercare la voce “backtrader” nell’output del precedente comando. Gli utenti Linux e Mac possono rendere questo processo un po ‘più veloce eseguendo il piping dell’output su grep.

                    
pip3 list | grep backtrader                    
                

Ho notato che su una delle mie macchine Linux, pip3 riportava una versione inferiore rispetto a quella effettivamente installata (non ho ancora capito perché). Quindi, se la versione segnalata non sembra corretta, puoi anche controllarla aprendo una shell python, importando backtrader e stampare la versione.

                    
import backtrader as bt
print(bt.__version__)                    
                

Se non si utilizza la versione più recente, avviare un terminale (o il prompt dei comandi) e digitare i seguenti comandi:

                    
pip3 install --upgrade backtrader                    
                


I risultati

Per vedere i risultati del test in un grafico bidimensionale, si può usare la libreria Python (esterna all’installazione standard) chiamata “Matplotlib”. Questo modulo è la libreria grafica defacto per molti scienziati, analisti e ricercatori che usano Python.

Assicurati di averlo installato aprendo un terminale o un prompt dei comandi e digitando:

                    
pip3 install matplotlib                    
                


Il Codice

Lo script è composto da due parti principali. La prima parte si crea una nuova classe dove implementare tutta la logica della strategia. Nella seconda parte si configura l’ambiente di esecuzione dello script (come aggiungere il framework, prevedere una fonte dati per i test, ecc.). Nota importante: in questo esempio si utilizza i dati disponibili con l’API di Quandl. Con questa API sei limitato al numero di chiamate che puoi effettuare al giorno. Per avere accesso illimitato ai loro dati è sufficiente registrarsi gratuitamente nel loro sito e richiedere una API key ed aggiungi la keyword apikey alla chiamata ai dati di quandl in questo modo (www.quandl.com):
                    
​data = bt.feeds.Quandl(
    dataname='F',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True,
    apikey="INSERT YOUR API KEY"
    )                    
                


Imports

                    
​import backtrader as bt
from datetime import datetime                    
                

Il primo import dovrebbe essere abbastanza ovvia. Stiamo importando il framework backtrader. Inoltre, si importa il modulo datetime dalla libreria standard di Python. Questo è usato per impostare le date di inizio e fine del periodo di backtesting. Backtrader prevede di ricevere oggetti datetime durante la creazione di feed di dati. Per ulteriori informazioni sul modulo datetime, consultare la documentazione disponibile su:

docs.python.org/3/library/datetime.html

La strategia

                    
class firstStrategy(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)                    
                
​Quando si crea una strategia in backtrader, si eredita molti metodi e attributi dalla classe base `bt.Strategy`. In parole più semplici, si sta fondamentalmente prendendo il template di una strategia che è stata scritta nel framework di backtest e aggiungendo la nostra logica sopra ad essa. Quando nella programmazione si usa l’ereditarietà, possiamo usare tutto il codice che è stato scritto nella strategia di base e sovrascrivere le parti che vogliamo cambiare. Ciò significa che non è necessario preoccuparsi di tutto il codice, dietro le quinte, che consente di interagire con i dati, gestire gli ordini, le notifiche dei broker e così via. E’ Il framework BackTrader che se ne occupa! La nuova classe della strategia può essere breve, pulita e di facile lettura. Tuttavia, se lo desideri, puoi scavare più a fondo e modificare il contenuto del nucleo della classe base. La nostra classe si chiama firstStrategy (piuttosto originale) e contiene il codice per l’inizializzazione della strategia e il metodo “next”. L’inizializzazione è solo una riga, vogliamo solo inizializzare un indicatore RSI dalla libreria di backtrader. Per fare ciò si aggiunge l’indicatore durante l’inizializzazione della strategia (__init__). Una volta impostato, backtrader si occupa di tenere traccia dei dati, calcolare i risultati e aggiungerli al grafico finale. Se non conosci la programmazione, è importante notare che puoi chiamare il tuo indicatore RSI come preferisci, ma devi prefissarlo con “self”. Ciò ti consentirà di accedervi da altri metodi (chiamati anche “funzioni” quando non stiamo parlando di una classe). In questo caso, abbiamo semplificato la lettura del codice e inizializzato l’indicatore come “self.rsi”. Il metodo next (funzione) viene chiamato ad ogni nuova barra o, in altre parole, ogni volta che viene ricevuta una nuova candela. È in questo metodo che si deve scrivere la logica “if this then that“. In questo esempio si controlla se siamo in posizione, dato che come prerequisito si prevede di effettuare un solo trade alla volta. Se non siamo in una posizione, allora si verifica il livello dell’indicatore RSI. Se è inferiore a 30, si acquista 100 azioni. Se è superiore non si fa nulla (Perché non abbiamo scritto alcun codice per ciò che accade quando l’RSI è sopra i 30 e non siamo in una posizione). Il secondo step della logica prevede le azioni da fare quando SIAMO già in una posizione. In questo caso si cerca un’opportunità di vendita. Se l’RSI supera i 70, si vende tutte le 100 azioni. Altrimenti, di nuovo non si fa nulla. Questo si ripete ogni volta che arriva una nuova candela (ogni giorno) fino a quando tutti i dati sono stati controllati.

Il Setup

                    
#Variable for starting cash
startcash = 10000

#Create instance of cerebro
cerebro = bt.Cerebro()

#Add strategy
cerebro.addstrategy(firstStrategy)

#Get Apple data from Yahoo Finance.
data = bt.feeds.Quandl(
    dataname='AAPL',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True
    )

#Add the data to Cerebro
cerebro.adddata(data)

# Set desired cash start
cerebro.broker.setcash(startcash)                    
                

Dopo aver scritto la strategia, è necessario implementare il setup e all’esecuzione. Questo in sostanza si riduce a:

  • Chiamare il motore backtrader (cerebro)
  • Ottenere i dati storici e caricarli nel motore.
  • Impostazione della quantità di denaro che dobbiamo negoziare (startcash)
  • Caricare la strategia nel motore
  • Esecuzione del test
  • Monitorare i risultati

Alcune cose da sottolineare:

  • Durante la stampa, si utilizza l’argomento con keyword style = candlestick, se non lo si utilizza si ottiene un grafico a linee del prezzo di chiusura.
  • Durante l’inizializzazione del feed dei dati si richiama il modulo datetime, come sottolineato in precedenza. Stiamo passando un oggetto datetime agli argomenti della keyword fromdate e todate. Infine la keyword buffer indica che backtrader eseguirà il buffer di tutti i dati richiesti prima dell’inizio dell’analisi.

Eseguire lo script

Presumo che tu abbia svolto le lezioni di base su Python, come menzionato nel mio precedente post. Quindi copia il codice, aggiungi le parti basi del linguaggio, avvia lo script e controlla i risultati.

I Risultati

2 operazioni in profitto. Tasso di vincita del 100%. Saremo ricchi!

 

Tornando ad un tono più serio, prendi questi risultati con le pinze. Non abbiamo confrontato i risultati con un benchmark o una semplice strategia di “buy & hold”. Non abbiamo aggiunto commissioni e slippage, non disponiamo di dati di test a lungo termine sufficienti per convalidare la strategia. Un test per un titolo azionario in solo anno non dovrebbe riempirci di fiducia, ma almeno abbiamo creato una base per fare ricerche ulteriori.

Tradingview: il primo Script

Tradingview sta rapidamente diventando uno degli strumenti grafici più popolari del settore. Con i suoi strumenti di charting facili da usare, gli indicatori e l’integrazione con i social network, gli operatori hanno un set completo di strumenti per eseguire analisi tecniche e condividere idee. Oltre a ciò, Tradingview ha anche sviluppato un proprio linguaggio di scripting che consente agli operatori di creare indicatori personalizzati e sviluppare strategie.

Prima di entrare nel codice, per prima cosa. Se non lo hai già fatto, ti suggerisco di registrarti per un account sul sito Web Tradingview. Una volta che hai un account sarai in grado di salvare i tuoi script.

Quando ho iniziato a dare un’occhiata a Tradingview per effettuare un backtesting, sono rimasto colpito immediatamente dalla semplicità della piattaforma. Questo lo rende una scelta eccellente per i  principianti del backtesting. Non solo il linguaggio Pine Script è abbastanza semplice da capire, ma Tradingview rende molto facile l’accesso alle informazioni e tutorial.

Per esempio:

  • Passa il mouse sopra le funzioni per aiuto. Questo fornisce informazioni di aiuto in forma abbreviata.
  • Controllo della versione integrata. Ogni volta che premi il pulsante Salva, Tradingview salva lo script come una nuova versione. Questo semplifica la vita se accidentalmente vai troppo lontano dal percorso e devi tornare indietro.
  • CTL + Click per visualizzare la documentazione dettagliata (opzione + click per utenti Mac). Una cosa bella della finestra della guida pop-up è che puoi ancora digitare nel riquadro dello script dietro di essa. Non è necessario aprire e chiudere costantemente la schermata della guida mentre si lavora attraverso i parametri di input della una funzione.

Galleria (clicca su un’immagine per ingrandirla)

Un aspetto negativo che ho notato (al momento della scrittura) è non aver a disposizione (o non sono riuscito a trovarla) un’opzione per stampare le stringhe sulla console di output. Questo può rendere il debug un po ‘complicato. Soprattutto nei casi in cui si desidera verificare il valore di una determinata variabile.

La Piattaforma

Se sei completamente nuovo di tradingview, può essere utile esaminare le funzionalità di questa web-application e di come accedere all’editor del codice di pine-script.

Prima carica un grafico:

  • Apri Tradingview.
  • Fai clic su “Chart” nel menu della home page.
  • Se questa è la prima volta che usi la funzione di creazione di grafici, verrai rimandato ad un grafico. In caso contrario, verrà visualizzato un menu a discesa con un modello di grafico “senza nome”.
  • Il pine editor comprare sul fondo della pagina. Clicca sul tab del pine editor.
Quando si crea una strategia, non iniziare dalla pagina di default di pine script. Questo template è utilizzato per gli indicatori. Si può utilizzare uno specifico template per le strategie. Il template indicatore di default è simile a quanto segue:
Come puoi vedere è un po ‘spartano. Per avere un template base per una strategia devi fare clic su “Nuovo -> Script strategia vuoto”. In questo modo si ottiene un template di base per iniziare.
Si ottiene il template di un codice che assomiglia a quanto segue:

Come puoi notare, questo ci fornisce tutto il necessario per eseguire uno script:

  • La strategia è inizializzata
  • Viene fornito un trigger per andare long
  • Viene fornito un trigger per andare short.

Se vuoi, puoi andare avanti e premere il pulsante “Aggiungi al grafico” e avere il tuo primo script. Tuttavia, se vuoi andare un po’ oltre e apportare alcune modifiche, continua a leggere.

La Strategia

La strategia che implementeremo segue le stesse regole descritte nell’articolo “Backtrader: Primo Script“. L’ho fatto per coerenza e in modo che coloro che devono ancora decidere quale piattaforma utilizzare per il backtest possano avere un confronto coerente. È una semplicissima strategia long che ha l’obiettivo di rimanere dalla parte giusta di un mercato rialzista dei titoli azionari

Entry

  • Entrata Long quando RSI < 30

Exit

  • Entrata Long quando RSI > 70

Gestione del Trade e Dimensionamento della Posizione

  • Non è implementata nessuna gestione del trade e nessun ridimensionamento in / out. Solo semplici acquisti e vendite con un’unica posizione aperta alla volta.
  • Per quanto riguarda la posizione, si prevedono cose semplici e si mantiene il default proposto da Tradingview cioè quello di acquistare e vendere 1 contratto.

Parametri dell’Indicatore

  • Periodo = 21
  • Si utilizza un periodo di ricerca più grande rispetto a quello predefinito a 14. In teoria ciò si traduce in meno falsi segnali e il prezzo dovrebbe dimunuire / aumentare di più prima di essere considerato ipercomprato / ipervenduto.

Il Codice

                    
//@version=3
strategy("Simple RSI", overlay=true, initial_capital=10000, currency='USD')
 
fromYear = year > 2014
toYear = year < 2016
 
longCondition = rsi(close, 21) < 30 
if (longCondition and fromYear and toYear) 
    strategy.entry("Long 1", strategy.long) 

closeCondition = rsi(close, 21) > 70
if (closeCondition)
    strategy.close("Long 1")                    
                

Da notare la semplicità e la brevità di questo codice rispetto a quello descritto nell’articolo “Backtrader: primo Script“, si apprezza sicuramente quanto sia più facile iniziare a lavorare! Quello che si rinuncia in termini di funzionalità e di controllo, torna indietro in termini di semplicità e di tempo.

Se lo si confronta con il template vuoto della strategia vuoto mostrato in precedenza, si può notare le seguenti modifiche:

  • L’entrata short è stata convertita in una funzione di chiusura in modo da non andare short (in conformità con le regole della strategia)
  • Le funzioni del crossover SMA sono state modificate per verificare se l’indicatore RSI è sopra o sotto un certo livello.
  • L’istruzione if per la condizione long è stata estesa.

Per prima cosa diamo un’occhiata all’inizializzazione della strategia e parliamo di un’altra caratteristica davvero interessante. Ci sono pochissimi parametri obbligatori necessari durante l’impostazione della strategia. In realtà, è richiesto solo il titolo e corrisponde al nome con cui la strategia può essere salvata. Tutti i parametri della strategia possono essere modificati visivamente tramite un modulo nella scheda “strategie”. Abbastanza carino vero? Ma c’è di più, i parametri possono essere regolati qui in qualsiasi momento e i risultati presenti nel riepilogo delle prestazioni si aggiornano automaticamente quando si apportano talile modifiche.

Inoltre durante l’inizializzazione della strategia, ho aggiunto alcuni parametri opzionali:

                    
strategy("Simple RSI", overlay=true, initial_capital=10000, currency='USD')                    
                
Questi parametri (initial_capital and currency) impostano i valori predefiniti che si vedono nel pannello del tester della strategia, quando si preme il pulsante di configurazione:
Un’altra modifica che ho apportato è stata quella di creare alcune variabili che vengono utilizzate per valutare se l’anno è superiore o inferiore a un determinato numero. L’ho fatto in quanto preferisco concentrarmi su particolari periodi di tempo durante il backtesting, in modo che un lungo periodo non mascherasse brevi periodi di prestazioni scadenti. È una scelta personale.
                    
fromYear = year > 2014
toYear = year < 2016                    
                

Cosa succede qui?

Ogni volta che viene prodotta una nuova candela, viene eseguito un controllo per vedere se l’anno è maggiore del 2014 o inferiore al 2016. In caso affermativo, il valore booleano “true” viene salvato nelle variabili fromYear e toYear. Questo può quindi essere usato nell’istruzione IF per verificare se possiamo aprire un nuovo trade:

                    
longCondition = rsi(close, 21) < 30
if (longCondition and fromYear and toYear) 
      strategy.entry("Long 1", strategy.long)                     
                
Stiamo verificando se l’RSI è inferiore a 30 e la data sia successiva al 2014 e precedente al 2016. La chiusura della strategia è più semplice. Chiude la posizione quando l’RSI supera il valore 70.

Utilizzare questo codice su Tradingview

Copia il codice e fai clic qui per aprire una finestra del grafico su Tradingview. Incolla il codice nell’editor di Pine Script, ora puoi fare clic sul pulsante “Aggiungi al grafico” per avviare il test e vedere visivamente dove la strategia ha acquistato e venduto.

Non dimenticare di fare clic su Salva, in modo da poter tornare alla strategia in un secondo momento! Puoi applicare questa strategia a qualsiasi strumento. Tutte le strategie caricate vengono visualizzate nella scheda “Strategie” nel riquadro inferiore dell’area grafica. Per caricare le strategie, è possibile selezionare “Apri” dalla scheda del pine editor.

I risultati

Tempo di i grandi risultati … Un utile netto di $10,56! Non così impressionante, ma non è questo il punto. In questo momento stiamo solo configurando il backtesting su Tradingview!

Prova a giocare con questo codice, cambia gli indicatori, cambia i parametri della strategia e abituati alla piattaforma.

Spero che questo post ti aiuti a iniziare!

Setup di base per Python e BackTrader

In questo post, diamo un’occhiata a come scaricare Python, dove recuperare i migliori tutorial introduttivi su Python, installare il framework BackTrader e infine verificare che sei in grado di accedere al framework all’interno di Python. Se sei ancora indeciso quale framework di backtesting o linguaggio di programmazione usare, puoi consultare l’articolo sui linguaggi di programmazione e gli ambienti di backtesting.

Nota: Questo post non va oltre il semplice settaggio di un ambiente di sviluppo per Python. Puoi quindi saltare questo articolo se hai già esperienza nell’uso di Python e pip.

Installare Python

Linux:

Se sei un utente Linux, sei molto fortunato. Molto probabilmente Python è già installato. In effetti, potrei scrivere cose superflue poiché non conosco nessun utente Linux che non sia già a conoscenza e / o abbia già armeggiato con Python in passato.

Mac OS:

Ce l’hai, ma non lo usi… La versione di Python installata con il sistema operativo è destinata esclusivamente al sistema. Alcune persone lo chiamano “system python”. Perché non dovresti usarlo? Da sottolineare che questa versione di Python è la vecchia versione python 2 mentre la maggior parte dei tutorial su questo sito è codificato con python 3. Un altro problema è che l’aggiornamento di Mac OS può potenziale per interrompere alcuni degli aggiornamenti installati sulla versione di sistema di python. Inoltre, alcuni pacchetti sono difficili da aggiornare a causa delle modifiche apportate da Apple nella versione python del sistema. Infine, il sistem python è disponibile solo tramite il terminale. Non c’è alcuna voce nella cartella delle applicazioni e nessun programma di avvio su cui fare clic.

Quindi, come puoi immaginare, ti consiglio di scaricare e installare una nuova versione di Python. Dopo aver scaricato e installato Python, avrai una cartella Python 3.x nella cartella delle applicazioni.
Questa ha un IDE (un ambiente di sviluppo) per scrivere ed eseguire il codice Python. Inoltre viene anche installato un launcher python che essenzialmente consente di avviare (eseguire) script Python facendo doppio clic su di essi.

Quindi iniziamo:

Per prima cosa vai alla pagina dei download sul sito Web python.org facendo clic qui.

Quindi scegli di installare l’ultima versione di Python 3. (secondo link dall’alto)

Dove aver scarico l’installer, è sufficiente seguire i classici steps per l’installazione di un software sul MAC.

Per verificare se Python è stato installato correttamente è sufficiente:

  1. Aprire un Terminale (se non sai dove si trova l’applicazione Terminale, puoi aprire una finistra di ricerca e digitare “terminal”)
  2. Digitare python3 nel termnale e premere Enter.

Si dovrà ottenere un risultato simile al seguente:

Per uscire dalla shell di python è necessario digitare quit() e premere Enter. (Questa è una funzione “built-in” per uscire dalla shell.)

Windows:

Windows non prevede nessuna versione di Python installata per default. Rispetto al Mac, questo rende le cose più semplici perchè non ci si deve preoccupare di un “python di sistema”. Come sopra, si inizia dalla pagina dei download di Python, facendo clic qui.

Una volta caricata la pagina, si seleziona di nuovo il secondo collegamento dall’alto, l’ultima versione di Python 3. Sembra identico allo precedente screenshot per Mac OS tranne che la scritta “Mac OS X” è sostituita con “Windows” (come ci si aspetterebbe!).

Dopo aver scaricato il programma di installazione, seguire i soliti passaggi per l’installazione per i software in ambiente Windows. Nella prima schermata di installazione è necessario assicurasi di selezionare:

  1. Installa per tutti gli utenti
  2. Aggiungi Python 3.x al PATH.
Per verificare che python sia stato installato correttamente, è sufficiente aprire un prompt dei comandi e digitare python. Da notare che non è necessario specificare ‘python3’ poiché su Windows non è possibile installate contemporaneamente diverse versioni di Python. L’output che si dovrebbe ottenere è lo stesso di quello per il Mac OS.
Per uscire dalla shell di python si deve digitare quit() e premere Enter. Questa è la funzione “built-in” per uscire dalla shell…

Primi passi con Python

Una volta installato Python 3, ti consiglio di imparare alcune nozioni di base in modo da comprendere completamente la logica alla base di BackTrader. Sia che tu preferisca imparare con un libro, sul Web o sporcarti le mani nei documenti ufficiali, hai  moltissime opzioni per apprendere tutte le funzionalità di questo linguaggio. Ho verificato la bontà dei contenuti che consiglio di seguito.

Gran parte di ciò che faremo nel trading si riduce al construtto “If this happens then do that“. Almeno all’inizio lLe competenze di base dovrebbero essere sufficienti, quindi non pensare di dover trascorrere mesi a studiare. Credo fortemente che il modo migliore per imparare le cose sia studiarle man mano che si va avanti con la sperimentazione e implementazione. Se passi troppo tempo a fare tutorial su progetti che non ti interessano, è facile annoiarsi e sentirsi frustrati quindi si rimarrà inevitabilmente bloccati.

Alcune concetti basi da imparare sono:

  • Assegnare variabili
  • Operazioni di base
  • Dichiarazioni If / Else
  • I cicli loop
  • Scrivere funzioni
  • Le basi delle Classi

Sul Web – Python Programming

https://pythonprogramming.net/introduction-to-python-programming/

​Questo sito offre una serie di eccellenti tuturial su una vasta gamma di argomenti. Meglio ancora, Harrison (l’autore) fornisce video e commenti ad ogni lezione della serie. Probabilmente il miglior sito sulla rete relativo alla programmazione in Python.

Purtroppo non ho trovato nulla di paragonabile (in termini di quantità e qualità) in italiano.

Ecco una copia del video di Harrison relativo all’introduzione della programmazione in Python:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.

Libri – Automatizzare le cose noiose con Python

Automatizzare le cose noiose con Python. Programmazione pratica per principianti assoluti fornisce progetti di automazione rivolti al principiante. L’aspetto interessante è che tali progetti hanno impatto nella nostra vita quotidiana. Si lavora con la gestione dei file, i fogli Excel, le ricerche sul web ecc. Trovo che non ci sia nulla di più soddisfacente che ottenere un uso pratico e regolare da qualcosa che hai codificato.

Sporcarsi le mani - I docs ufficiali

Python fornisce un’ottima documentazione sul proprio sito ufficiale. All’inizio alcuni potrebbero trovare la documentazione un po ‘intimidatoria (specialmente quelli senza background informatico) ma è necessario attenersi ad essa. Una volta che ci si è abituati, questo diventa una preziosa risorsa.

https://docs.python.org/3/

Il framework BackTrader

Per installare pacchetti e framework di terze parti in Python utilizziamo uno strumento chiamato “pip” (pip3 in python3). Questo è lo strumento di gestione dei pacchetti in python, cioè si occupa del download, l’installazione, l’aggiornamento e la rimozione del codice sorgente richiesto dai pacchetti di terze parti. Apri un terminale o una console e digita il seguente comando: pip3 install backtrader Semplice! Dovresti vedere pip entrare in azione ed iniziare a scaricare / installare i pacchetti. Una volta completato puoi verificare l’installazione aprendo una shell python, digitando python3 nel terminale e premendo invio. Quindi digitare i seguenti comandi, una riga alla volta (premendo invio alla fine di ogni riga):
                    
import backtrader
print(backtrader.__version__)                    
                

Dovresti vedere il numero della versione stampato a video:

Se ricevi degli errori, puoi pubblicali di seguito nella sezione dei commenti e possiamo dare loro un’occhiata (e quindi aggiornare questa pagina le secondo necessità)