Hammer Candlestick Pattern con Backtrader

L’Hammer Candlestick Pattern con Backtrader

In questo articolo descriviamo come rilevare i popolari pattern di candele ad effettuare i backtest di strategie di trading algoritmico basate su questi pattern. Non potendo approfondire tutti i pattern delle candlestick, ci concentriamo sull’implementazione di una strategia basata sull’Hammer Candlestick Pattern con Backtrader e Python.

L’Hammer

Prima di vedere il codice,  descriviamo i concetti alla base di una candela Hammer o Hanging man perché l’analisi tecnica dice che questi pattern possono segnalare l’inizio di un’inversione nell’azione dei prezzi. Come con la maggior parte dei candlestick pattern, la candela Hammer prende il nome dal suo aspetto, infatti sembra un martello!

Hammer Candlestick Pattern con Backtrade

Questo tipo di candela si forma quando c’è stata una significativa pressione di vendita che ha spinto il prezzo verso il basso. Successivamente, la pressione in vendita si annulla quando molti acquirenti iniziano ad entrare e spingono il prezzo verso l’alto fino ad arrivare vicino ai prezzi di apertura  e di massimo. Il risultato è  una candela con una grande  ombra o spike inferiore che (non a caso) sembra il manico di un martello. Nota: l’immagine mostra una candela che si è chiusa sopra l’apertura ma non è un requisito per una candela  hammer. Si può anche chiudere sotto il prezzo di apertura ma avere un significativo rimbalzo dai minimi per classificare la candela come una hammer.

Perché è un segnale rialzista

La teoria generale dice che questo pattern segnala l’esaurimento delle vendite. Non ci sono abbastanza vendite per continuare a spingere il prezzo verso il basso. Allo stesso tempo mostra che gli acquirenti hanno iniziato a tornare sul mercato, quindi l’equilibrio si è spostato verso una domanda maggiore rispetto all’offerta. In sintesi una candela hammer può indicare:

  1. un livello dove gli acquirenti sono  entrati nel mercato, quindi, il prezzo POTREBBE non scendere al di sotto di quel livello.
  2. un livello dove POTREBBE iniziare un rimbalzo significativo o un’inversione di trend.

Sottolineiamo l’uso del condizionale “potrebbe”.

La strategia

Come detto nell’introduzione, questo articolo si concentra su come rilevare un Hammer Candlestick Pattern con Backtrader. Per raggiungere questo obiettivo implementiamo un indicatore personalizzato che visualizza un semplice segno sul grafico quando si verificano candele hammer. L’esempio si focalizza sul pattern di candela piuttosto che tentare di cogliere l’inversione perfetta. In altre parole, non controlliamo se il prezzo è in trend rialzia o ribassista prima della candella hammer. Inoltre non prevediamo controlli per eliminare le candele hammer insignificanti, cioè quelle candele con una range (volatilità) ridotta ma che soddisfano comunque i criteri generali. Questi aspetti possono aiutare a migliorare la  significatività della candela a martello e devono sicuramente essere presi in considerazione. Tuttavia, non sono fondamentali per l’obietti di questo articolo.

Prima di iniziare

Affinché il codice seguente possa essere un esempio completo, sono necessarie alcune funzioni per scaricare i dati storici e usarli nella strategia. In questo articolo non  approfondiamo l’implementazione di queste funzioni che è descritta in dettaglio nei seguenti articoli:

  1. Scaricare i dati da Alpha Vantage e inserirli in Backtrader.
  2. Creare una strategia con più datafeed in Backtrader.

Naturalmente, è sempre possibile estrarre il codice dell’indicatore ed inserirlo in altre strategie.

Per eseguire il codice è necessario modificare la seguente riga inserendo una  chiave API che si ottiene registrandosi ad Alpha Vantage.

				
					Apikey = 'INSERT YOUR API KEY HERE'
				
			

Altrimenti lo script non funzionerà.

Hammer Candlestick Pattern con Backtrader

				
					

from alpha_vantage.timeseries import TimeSeries
import pandas as pd
import numpy as np
import backtrader as bt
from datetime import datetime

# IMPORTANTE!
# ----------
# Registrare per un API su:
# https://www.alphavantage.co/support/#api-key
# e inserirla qui
Apikey = 'INSERT YOUR API KEY HERE'


class HammerCandles(bt.Indicator):
    '''
    Indicatore della candela d'inversione che prova a cattuare gli swing 
    e cavalcare il ritracciamento.
    '''
    lines = ('signal','bull_hammer', 'bear_hammer')
    params = (
        ('rev_wick_ratio', 0.6), # rapporto dello spike
    )

    plotinfo = dict(
        subplot=False,
        plotlinelabels=True
    )
    plotlines = dict(
        bull_hammer=dict(marker='^', markersize=8.0, color='blue', fillstyle='full', ls='',
        ),
        bear_hammer=dict(marker='v', markersize=8.0, color='orange', fillstyle='full', ls='',
        ),
        signal=dict(_plotskip=True)
    )

    def __init__(self):
        pass

    def next(self):

        # Controllo il range
        range = round(self.data.high - self.data.low,5)

        # Calcola il rapporto per le candele verdi o se open = close
        if self.data.open <= self.data.close:

            upper_wick = round(self.data.high - self.data.close,5)
            lower_wick = round(self.data.open - self.data.low,5)

            try:
                upper_ratio = round(upper_wick/range,5)
            except ZeroDivisionError:
                upper_ratio = 0

            try:
                lower_ratio = round(lower_wick/range,5)
            except ZeroDivisionError:
                lower_ratio = 0

        # Calcolo rapporto per le candele rosse
        elif self.data.open > self.data.close:

            upper_wick = round(self.data.high - self.data.open,5)
            lower_wick = round(self.data.close - self.data.low,5)

            try:
                upper_ratio = round(upper_wick/range,5)
            except ZeroDivisionError:
                upper_ratio = 0

            try:
                lower_ratio = round(lower_wick/range,5)
            except ZeroDivisionError:
                lower_ratio = 0


        if upper_ratio >= self.p.rev_wick_ratio:

            self.lines.bear_hammer[0] = self.data.high[0]
            self.lines.signal[0] = -1

        elif lower_ratio >= self.p.rev_wick_ratio:

            self.lines.bull_hammer[0] = self.data.low[0]
            self.lines.signal[0] = 1

        else:
            self.lines.signal[0] = 0


def alpha_vantage_eod(symbol_list, compact=False, debug=False, *args, **kwargs):
    '''
        funzione helper per scaricare i dati di Alpha Vantage.

        Restituisce una lista nidificata dove ogni elemento contiene:
            [0] dataframe di pandas
            [1] il nome del datafeed
        '''
    data_list = list()

    size = 'compact' if compact else 'full'

    count = 0
    total = len(symbol_list)

    for symbol in symbol_list:
        count += 1

        print('\nDownloading: {}'.format(symbol))
        print('Symbol: {} of {}'.format(count, total, symbol))
        print('-' * 80)

        # Inviamo l'API e creiamo la sessione
        alpha_ts = TimeSeries(key=Apikey, output_format='pandas')

        data, meta_data = alpha_ts.get_daily(symbol=symbol, outputsize=size)

        # Converire l'indice in datetime.
        data.index = pd.to_datetime(data.index)
        data.columns = ['Open', 'High', 'Low', 'Close', 'Volume']

        if debug:
            print(data)

        data_list.append((data, symbol))

    return data_list

class TestStrategy(bt.Strategy):

    def __init__(self):

        self.inds = dict()
        for i, d in enumerate(self.datas):
            self.inds[d] = dict()
            self.inds[d] = HammerCandles(d)


    def next(self):

        for i, d in enumerate(self.datas):

            bar = len(d)
            dt = d.datetime.datetime()
            dn = d._name
            o = d.open[0]
            h = d.high[0]
            l = d.low[0]
            c = d.close[0]
            v = d.volume[0]


            print('{} Bar: {} | {} | O: {} H: {} L: {} C: {} V:{}'.format(dt, bar,dn,o,h,l,c,v))


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

# Aggiunge la strategia
cerebro.addstrategy(TestStrategy)

# Scarica di dati da Alpha Vantage.
symbol_list = ['AAPL','MSFT',]
data_list = alpha_vantage_eod(
                symbol_list,
                compact=False,
                debug=False)

for i in range(len(data_list)):

    data = bt.feeds.PandasData(
                dataname=data_list[i][0], # Pandas DataFrame
                name=data_list[i][1], # Symbol
                timeframe=bt.TimeFrame.Days,
                compression=1,
                fromdate=datetime(2018,1,1),
                todate=datetime(2019,1,1)
                )
 
    # Aggiunge i dati a Cerebro
    cerebro.adddata(data)


print('Starting to run')
# Esecuzione del backtest
cerebro.run()

cerebro.plot(style='candlestick')
				
			

Commento al codice

Per prima cosa, guardiamo l’indicatore ad alto livello. Presenta le seguenti righe:

  • Bull_Hammer: una lines che posiziona i marcatori sul grafico. Possiamo configurare una linea per tracciare i marcatori usando un dizionario plot_lines. La linea Bull_Hammer posizione un marcatore ogni volta che abbiamo una candela hammer con una lunga ombra inferiore.
  • Bear_Hammer: una lines che traccia i marcatori sul grafico. I marcatori Bear_Hammer indicano una lunga ombra o spike superiore.
  • Signal: questa  lines non è visualizzata ma è disponibile e aiuta a verificare la strategia. Possiamo monitare i segnali long e  short. E’ pari a +1 quando  abbiamo una hammer rialzista, -1 quando abbiamo una hammer e 0 in tutti gli altri casi.

Vediamo ora la formula  usata per rilevare la candela hammer che in linea di principio abbastanza semplice (nonostante le molte righe di codice!). La funzione calcola il range totale della candela (high – low), quindi verifica se l’ombra della candela è maggiore di una determinata percentuale del range. Il variabile usata per specificare la percentuale target è rev_wick_ratio che prevede un valore di default  pari a 0.6, cioè l’ombra (spike) della candela deve maggiore del 60% del range per essere considerata una candela hammer.

La logica di verifica

L’esempio contiene molte righe di codice per determinare se la candela è un hammer significativo perchè dobbiamo prevede logiche diverse a seconda che la candela sia verde o rossa. In particolare se abbiamo una candela rossa, dobbiamo calcolare l’ombra dal valore close al valore low, mentre se abbiamo una candela verde calcoliamo ombra dall’open al low. Gran parte del codice all’interno metodo next() implementa questi controlli e calcola i rapporti tra range superiore e range inferiore. Inoltre prevediamo di intercettare e gestire gli errori del tipo  ZeroDivisionErrors che possono accadere se i dati sono sporchi o abbiamo una candela senza spike. Infine, verifichiamo se abbiamo una candela hammer dividendo il range dell’ombra per il range della candela e confrontando il risultato con il valore rev_wick_ratio. In caso sia superiore abbiamo un segnale ed aggiorniamo le lines.

Risultati

Quando eseguiamo la strategia otteniamo un output simile al seguente:

Backtrader-Hammer-Candle-Grafico

Ingrandiamo il grafico e possiamo vedere le candele un po’ più chiaramente

Backtrader-Hammer-Candle-Grafico-Zoom

Possiamo vedere che l’indicatore arancione indica un segnale ribassista mentre l’indicatore blu indica un segnale rialzista.

Conclusione

L’idea di utilizzare i rapporti di range per rilevare una candela hammer può essere applicata a molti altri tipi di candele. Una candela engulfing è semplicemente una candela con un ampio range del corpo ed è più grande della candela precedente. Con un po’ di  esercizio, possiamo applicare questa tecnica  in altri scenari con solo poche modifiche.

Codice completo

In questo articolo abbiamo descritto come rilevare un Hammer Candlestick Pattern con Backtrader e Python. Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/BackTrader

Torna in alto
Scroll to Top