Gestire le candele Heikin Ashi con Backtrader

Gestire le candele Heikin Ashi con Backtrader

In questo articolo descriviamo come gestire le candele Heikin Ashi con BackTrader per implementare strategie di  trading algoritmicoSe hai mai visto un grafico Heikin Ashi su una piattaforma di trading, probabilmente sei rimasto sbalordito dalla bellezza delle candele. Sembrano catturare gli alti e bassi del movimento dei prezzi con lunghe fasce di verde o rosso. Meglio ancora, le barre Heikin Ashi sembrano davvero catturare punti di inversione di una tendenza. 

Questi punti di inversione sono spesso preceduti da un lungo movimento nella direzione opposta. Di conseguenza, la maggior parte delle persone che hanno visto le candele Heikin Ashi hanno pensato ad una semplice strategia di acquisto sul verde e vendita sul la prima candela rossa. Sfortunatamente, la vita non è mai così semplice e in questo articolo descriviamo come lavorare con le candele Heikin Ashi con Backtrader, e dimostriamo che una buona entrata e uscita su un grafico Heikin-Ashi non sempre si traduce in trade profittevoli nel mondo reale.

Cosa sono le candele Heikin-Ashi

Come si può intuire dal nome, il concept delle candele Heikin Ashi è nato in Giappone. Anche le normali candele OHLC ha avuto origine dalla terra del sol levante. Quindi, non è un caso che i nostri amici in oriente facciano le cose in modo diverso, le candele Heikin Ashi sono in realtà derivati della candele OHLC. Si parte dai dati OHLC e si applica una formula per eliminare parte del rumore dal movimento dei prezzi. Diamo un’occhiata a come viene calcolata una candela Heikin Ashi. (Fonte completa: Wikipedia)

  • Chiusura = (apertura + massimo + minimo + chiusura) / 4
  • Massimo = massimo  maggiore, apertura o chiusura (qualunque sia il maggiore)
  • Minimo = minimo minore, apertura o chiusura (qualunque sia il minore)
  • Open = (apertura della barra precedente + chiusura della barra precedente) / 2

Da notare che le candele Heikin Ashi non ricalcolano semplicemente i dati di una barra OHLC, hanno bisogno di due barre. In altre parole non possiamo calcolare con precisione una candela Heikin Ashi finché non abbiamo almeno 1 barra di dati storici. Un’altra importante differenza tra una candela standard e una Heikin Ashi è che la open close delle candele non è la stessa (così come la high e la low). Potrebbe sembrare un’affermazione ovvia dopo aver esaminato la formula, ma ha alcune conseguenze sull’accuratezza del backtest e dei risultati.

Backtrader: Lavorare con le candele Heikin Ashi - confronto

Gestire le candele Heikin Ashi con Backtrader

Ora vediamo come lavorare con le candele Heikin Ashi in Backtrader per il backtesting di strategie di trading. Sfortunatamente non  è così semplice come usare il comando  cerebro.plot(style='candlestick') descritto negli articoli precedenti.

Non possiamo usare il  comando cerebro.plot(style='heikinashi') perchè, anche se funzionasse , visualizzerebbe i dati in candele Heikin Ashi solo al termine del backtest (poiché il  grafico è l’ultima cosa che facciamo). Affinché le candele Heikin Ashi influenzino la logica della strategia, dobbiamo disporre della versione Heikin-Ashi dei  dati openhighlowclose disponibili durante il test. Per fare ciò, dobbiamo utilizzare  il Data Filter di Backtrader per creare dati Heikin Ashi dalla sorgente dei dati in input. (La documentazione ufficiale sui filtri è disponibile su  https://www.backtrader.com/docu/filters) Un filtro di dati elabora i dati prima che vengano inseriti in cerebro. Un caso d’uso comune consiste nel filtrare eventuali barre presenti nel feed di dati che sono fuori dall’orario/ sessione che vogliamo considerare nel backtest. Le candele Heikin Ashi sono dati elaborati/modificati dal filtro. L’unica differenza è che  si rimuove nulla.

Codice di Esempio

Il seguente codice mostra un esempio per aggiungere un filtro dati Heikin Ashi a una strategia.

				
					
import backtrader as bt
from datetime import datetime

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

# Aggiungere una strategia
cerebro.addstrategy(bt.Strategy)

data = bt.feeds.Quandl(
    dataname='TSLA',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True,
    )

# Filtro sui dati
data.addfilter(bt.filters.HeikinAshi(data))

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

# Esecuzione del backtest
cerebro.run()

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

Nota importante: i dati in questo tutorial utilizzano l’API di Quandl. Con questa API abbiamo un numero limitato di chiamate che possiamo effettuare ogni giorno. Per avere accesso illimitato ai loro dati gratuiti, dobbiamo registrati per ottenere una chiave API e quindi aggiungere l’attributo apikey alla chiamata dei dati Quandl in questo modo:

				
					
data = bt.feeds.Quandl(
    dataname='F',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True,
    apikey="INSERT YOUR API KEY"
    )
				
			

Fonte: https://www.quandl.com/?modal=register

Risultati

L’esecuzione del codice precedente produce un grafico simle al seguente:

backtrader - risultati

I dati Heikin Ashi non sono prezzi reali

Ora possiamo gestire le candele Heikin Ashi sul grafico, ma l’esecuzione di una strategia su questi dati produce aspettative irrealistiche. Come abbiamo  introdotto nei paragrafi precedenti, i prezzi delle candele Heikin Ashi non sono prezzi reali. Quindi i prezzi di entrata e di uscita sono diversi da quelli con le candele reali. Per complicare ulteriormente le cose, anche i massimi e i minimi sono diversi, il che può portare a risultati completamente diversi quando si utilizzano i trailing stop. Puoi finire per essere in uno scambio per un periodo di tempo completamente diverso. Per vederlo di persona, eseguire il codice di esempio riportato di seguito.

Codice 

				
					
import backtrader as bt
from datetime import datetime
import math
import argparse

def parse_args():
    parser = argparse.ArgumentParser(description='Ngyuen: Price/Volume Anomaly Detection Strategy')

    parser.add_argument('--heikin',
                            action ='store_true',
                            help=('Add Heikin Data Filter'))

    parser.add_argument('--debug',
                            action ='store_true',
                            help=('Print Debugs'))

    return parser.parse_args()


class HeikinStrategy(bt.Strategy):

    params = (('debug', False),)

    def next(self):
        bar = len(self)
        if bar == 10:
            self.buy(size=100)

        if bar == 50:
            self.close()

        if self.p.debug:
            print('---------------------------- NEXT ----------------------------------')
            print("1: Data Name:                            {}".format(self.data._name))
            print("2: Bar Num:                              {}".format(len(self.data)))
            print("3: Current date:                         {}".format(self.data.datetime.datetime()))
            print('4: Open:                                 {}'.format(self.data.open[0]))
            print('5: High:                                 {}'.format(self.data.high[0]))
            print('6: Low:                                  {}'.format(self.data.low[0]))
            print('7: Close:                                {}'.format(self.data.close[0]))
            print('8: Volume:                               {}'.format(self.data.volume[0]))
            print('--------------------------------------------------------------------')

    def notify_trade(self,trade):
        if trade.isclosed:
            dt = self.data.datetime.date()
            print('---------------------------- TRADE ---------------------------------')
            print("1: Data Name:                            {}".format(trade.data._name))
            print("2: Bar Num:                              {}".format(len(trade.data)))
            print("3: Current date:                         {}".format(dt))
            print('4: Status:                               Trade Complete')
            print('5: Ref:                                  {}'.format(trade.ref))
            print('6: PnL:                                  {}'.format(round(trade.pnl,2)))
            print('--------------------------------------------------------------------')

args = parse_args()

# Capitale iniziale
startcash = 10000

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

# Aggiungere una strategia
cerebro.addstrategy(HeikinStrategy, debug=args.debug)

data = bt.feeds.Quandl(
    dataname='F',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True
    )

# Filtro sui dati
if args.heikin:
    data.addfilter(bt.filters.HeikinAshi(data))

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

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

# Esecuzione del backtest
cerebro.run()

# Ottenre il valore finale del portafoglio
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

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

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

Il codice precedente può essere eseguito in due modalità. La prima modalità usa  i prezzi reali e la seconda i prezzi Heikin Ashi (selezionando il parametro --heikin durante l’esecuzione dello script). La strategia stessa è progettata per aprire e chiudere la posizione a delle barre arbitrarie. In questo modo possiamo verificare le aspettative irrealistiche. Immagina che due trader e una scimmia stanno operando. Un trader sta guardando un grafico Heikin Ashi e l’altro sta guardando un grafico normale e la scimmia sta guardando una banana. Se tutti aprono e chiudono un’operazione esattamente nello stesso momento,  ci aspettiamo che tutti abbiano lo stesso PnL. Sarebbe un’aspettativa realistica del risultato. Tuttavia,  come dimostrato in questo script,  nonostante le operazioni sono aperte e chiuse  negli stessi momenti otteniamo risultati diversi. Questo accade perché Backtrader non usa i dati reali per il calcolo del PnL. Non ha idea di cosa sia “reale”, calcola solo il PnL da qualsiasi feed di dati sta usando.

Risultati

Il primo risultato usa i dati reali:

				
					---------------------------- TRADE ---------------------------------
1: Data Name:                            F
2: Bar Num:                              51
3: Current date:                         2016-03-16
4: Status:                               Trade Complete
5: Ref:                                  1
6: PnL:                                  133.31
--------------------------------------------------------------------
Final Portfolio Value: $10133.31
P/L: $133.31
				
			

Il secondo risultato usa le candele Heikin Ashi:

				
					---------------------------- TRADE ---------------------------------
1: Data Name:                            F
2: Bar Num:                              51
3: Current date:                         2016-03-16
4: Status:                               Trade Complete
5: Ref:                                  1
6: PnL:                                  136.17
--------------------------------------------------------------------
Final Portfolio Value: $10136.17
P/L: $136.17
				
			

Notiamo che abbiamo ottenuto un profitto extra di $ 3 dollari con le candele Heikin Ashi. Il problema è che i 3 dollari non sono mai esistiti! Abbiamo aperto e chiuso  i trade esattamente nello stesso momento.

Backtest di una strategia

Per vedere come gestire le candele Heikin Ashi con backtrader facciamo un ulteriore passo avanti nell’esempio e implementiamo la strategia menzionata all’inizio del l’articolo. Ricapitolando, vogliamo comprare sulle candele verdi e vendere sulle  candele rosse. Con questo esempio vogliamo descrivere quanto possono essere imprecisi i risultati se non configuriamo correttamente Backtrader.

Codice

				
					

import backtrader as bt
from datetime import datetime
import math
import argparse

def parse_args():
    parser = argparse.ArgumentParser(description='Ngyuen: Price/Volume Anomaly Detection Strategy')

    parser.add_argument('--heikin',
                            action ='store_true',
                            help=('Add Heikin Data Filter'))

    parser.add_argument('--debug',
                            action ='store_true',
                            help=('Print Debugs'))

    return parser.parse_args()


class HeikinStrategy(bt.Strategy):

    params = (('debug', False),)

    def next(self):

        if not self.position:
            if self.data.close[0] > self.data.open[0]: # Heikin-Ashi Verde
                self.buy(size=100)
            elif self.data.close[0] < self.data.open[0]: # Heikin-Ashi Rossa
                self.sell(size=100)
            else:
                pass # Chiusura neutrale

        else:
            if self.position.size > 0 and self.data.close[0] < self.data.open[0]: # Heikin-Ashi Rossa
                self.sell(size=200) # Posizione Reverse
            elif self.position.size < 0 and self.data.close[0] > self.data.open[0]: # Heikin-Ashi Verde
                self.buy(size=200) # Posizione Reverse
            else:
                pass

        if self.p.debug:
            print('---------------------------- NEXT ----------------------------------')
            print("1: Data Name:                            {}".format(self.data._name))
            print("2: Bar Num:                              {}".format(len(self.data)))
            print("3: Current date:                         {}".format(self.data.datetime.datetime()))
            print('4: Open:                                 {}'.format(self.data.open[0]))
            print('5: High:                                 {}'.format(self.data.high[0]))
            print('6: Low:                                  {}'.format(self.data.low[0]))
            print('7: Close:                                {}'.format(self.data.close[0]))
            print('8: Volume:                               {}'.format(self.data.volume[0]))
            print('--------------------------------------------------------------------')

    def notify_trade(self,trade):
        if trade.isclosed:
            dt = self.data.datetime.date()
            print('---------------------------- TRADE ---------------------------------')
            print("1: Data Name:                            {}".format(trade.data._name))
            print("2: Bar Num:                              {}".format(len(trade.data)))
            print("3: Current date:                         {}".format(dt))
            print('4: Status:                               Trade Complete')
            print('5: Ref:                                  {}'.format(trade.ref))
            print('6: PnL:                                  {}'.format(round(trade.pnl,2)))
            print('--------------------------------------------------------------------')


args = parse_args()

# Capitale iniziale
startcash = 10000

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

# Aggiungere una strategia
cerebro.addstrategy(HeikinStrategy, debug=args.debug)

data = bt.feeds.Quandl(
    dataname='F',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True
    )

# Filtro sui dati
if args.heikin:
    data.addfilter(bt.filters.HeikinAshi(data))

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

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

# Esecuzione del backtest
cerebro.run()

# Ottenre il valore finale del portafoglio
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

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

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

Il codice precedente è progettato per essere eseguito solamente con il  flag --heikin in modo da poter  verificare il PnL se impostato in modo errato. Senza il flag --heikin, la strategia funziona operando nei cambiamenti di colore dei normali dati OHLC.

Risultati

L’esecuzione del codice con il flag --Heikin produce alcuni profitti davvero interessanti. Oltre a guadagnare $1279, guardando il grafico è facile entusiasmarsi: vediamo che si acquista i minimi e si vende i massimi quasi costantemente.

Heikin Ashi con backtrader - Esempio

A prima vista sembra una buona strategia. Ora dobbiamo verificare quali profitti otteniamo se acquistiamo e vendiamo esattamente negli stessi orari ma usando i prezzi reali. Per risolvere questo problema dobbiamo sviluppare un metodo per analizzare le candele Heikin Ashi ma per fare trading sui dati reali.

Heikin Ashi e i dati reali

Per affrontare questo problema dovremmo utilizzare solo i dati di Hiekin-Ashi per decidere quando entrare e uscire da una posizione. Quindi utilizziamo un secondo feed di dati contenente prezzi reali per entrare/uscire dalla posizione. Il codice seguente presenta solo una soluzione al problema. È abbastanza buono per portare a termine il lavoro, ma può sicuramente essere migliorato!

Codice

				
					

import backtrader as bt
from datetime import datetime
import math
import argparse

def parse_args():
    parser = argparse.ArgumentParser(description='Ngyuen: Price/Volume Anomaly Detection Strategy')

    parser.add_argument('--debug',
                            action ='store_true',
                            help=('Print Debugs'))

    return parser.parse_args()


class HeikinStrategy(bt.Strategy):

    params = (('debug', False),)

    def __init__(self):
        # Imposto alcuni puntatori/riferimenti
        for i, d in enumerate(self.datas):
            if d._name == 'Real':
                self.real = d
            elif d._name == 'Heikin':
                self.hk = d


    def next(self):

        pos = self.getposition(self.real).size

        if not pos:
            if self.hk.close[0] > self.hk.open[0]: # Heikin-Ashi Verde
                self.buy(self.real, size=100)
            elif self.hk.close[0] < self.hk.open[0]: # Heikin-Ashi Rossa
                self.sell(self.real, size=100)
            else:
                pass # Chiusura neutra

        else:
            if pos > 0 and self.hk.close[0] < self.hk.open[0]: # Heikin-Ashi Rossa
                self.sell(self.real, size=200) # Posizione Reverse
            elif pos < 0 and self.hk.close[0] > self.hk.open[0]: # Heikin-Ashi Verde
                self.buy(self.real, size=200) # Posizione Reverse
            else:
                pass

        if self.p.debug:
            print('---------------------------- NEXT ----------------------------------')
            print("1: Data Name:                            {}".format(data._name))
            print("2: Bar Num:                              {}".format(len(data)))
            print("3: Current date:                         {}".format(data.datetime.datetime()))
            print('4: Open:                                 {}'.format(data.open[0]))
            print('5: High:                                 {}'.format(data.high[0]))
            print('6: Low:                                  {}'.format(data.low[0]))
            print('7: Close:                                {}'.format(data.close[0]))
            print('8: Volume:                               {}'.format(data.volume[0]))
            print('--------------------------------------------------------------------')

    def notify_trade(self,trade):
        if trade.isclosed:
            dt = self.data.datetime.date()
            print('---------------------------- TRADE ---------------------------------')
            print("1: Data Name:                            {}".format(trade.data._name))
            print("2: Bar Num:                              {}".format(len(trade.data)))
            print("3: Current date:                         {}".format(dt))
            print('4: Status:                               Trade Complete')
            print('5: Ref:                                  {}'.format(trade.ref))
            print('6: PnL:                                  {}'.format(round(trade.pnl,2)))
            print('--------------------------------------------------------------------')


args = parse_args()

# Capitale iniziale
startcash = 10000

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

# Aggiungere una strategia
cerebro.addstrategy(HeikinStrategy, debug=args.debug)

data = bt.feeds.Quandl(
    dataname='F',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True
    )


dataHK = bt.feeds.Quandl(
    dataname='F',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True
    )

# Filtro sui dati
dataHK.addfilter(bt.filters.HeikinAshi(dataHK))

# Aggiungere i dati a Cerebro
cerebro.adddata(data, name="Real")
cerebro.adddata(dataHK, name="Heikin")

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

# Esecuzione del backtest
cerebro.run()

# Ottenre il valore finale del portafoglio
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

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

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

Commento al codice

Iniziamo ottenendo due versioni dello stesso feed di dati. Una volta che abbiamo due copie del feed di dati, ne filtriamo una, trasformandola in dati Heikin Ashi e quindi aggiungiamo entrambi i feed con nomi diversi. Durante l’esecuzione della strategia assegniamo un alias ai feed di dati durante la chiamata del metodo __init_(), in modo da poter facilmente usare il datafeed corretto nelle successive chiamate di next().

Durante l’esecuzione del metodo next() controlliamo per vedere se la candela Heikin-Ashi ha chiuso sopra l’apertura. In tal caso, abbiamo una candela verde e andiamo long. Se ha chiuso al ribasso, allora abbiamo una candela rossa e andiamo short. Da notare che quando entriamo in una posizione usiamo l’alias self.real. Questo per garantire che l’operazione sia inserita nel feed di dati corretto e quindi  siano usati i prezzi dei dati corretti/reali.

Risultati

Heikin Ashi con backtrader - Esempio3

E la performance nel mondo reale… $51,65! Abbastanza lontano dai $ 1279 che abbiamo ottenuto in precedenza. Speriamo che questo dimostri l’importanza di verifiche la correttezza del setup quando si gioca con le candele Heikin-Ashi altrimenti si avrà molti problemi quando si usa il codice nel trading live.

Non stiamo suggerendo che le candele Heikin-Ashi sono inutili o fuorvianti. Dopotutto, questa strategia non è stata progettata per  avere prestazioni stellari e ha comunque restituito un profitto reale!

Codice completo

In questo articolo abbiamo descritto come gestire le candele Heikin Ashi con BackTrader per implementare strategie di  trading algoritmico. Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/BackTrader

Scroll to Top