Backtrader: Candlestick Patterns – Hammer

The article this week comes to us courtesy of a recent suggestion from Rushi. (S)He requested an article for Backtrader that demonstrates how to detect popular candle-stick patterns. Whilst, we won’t be able to cover all candles stick patterns in one article, we can at least make a start! So without further ado, let’s dive in and take a look at the hammer candlestick pattern.

The Hammer

Before we get into the code, let’s spend a bit of time looking at what a hammer or hanging man candle actually is and why some traders believe that these patterns can signal the start of a reversal in price action. As with most candlestick patterns, the hammer candle derives its name from its appearance. You see, it looks like a hammer!
An image showing the price action of the hammer candle.
A classic hammer candle
This type of candle forms when there was significant selling pressure pushing the price lower. However, that pressure does not last as lots of buyers start to come in and push the price back up to finish much higher than the low of the candle. The result of this is a large lower wick which (not coincidentally) looks like a hammers handle. Note: In the image above, we show a candle that closed above the open. However, that is not a requirement of a hammer candle. We can still close down, have a significant rebound and still class the candle as a hammer candle.

Why is this a bullish signal?

The general theory is that this pattern signals that selling has been exhausted. There is not enough selling to continue to push the price lower. At the same time, it shows that buyers have started to come back into the picture. Of course, they were always in the picture but the balance has now shifted towards having greater demand than supply. So in short, this can indicate that:
  1. This level is a level in which buyers gain an appetite and thus, price MIGHT not drop below that level again.
  2. We MIGHT have the start of a significant bounce or trend reversal.
Readers should pay particular attention to the use of might!

Code Scope

As mentioned in the intro. We will focus this article on detecting hammer candles as they happen. We will do this by building a custom indicator that will place a simple mark on the chart when they happen. The example will focus on the candle pattern itself rather than trying to catch the perfect reversal. In other words, we won’t be checking to see if the price is generally rising or falling before the hammer appears. Similarly, we won’t be making checks to weed out small insignificant hammer candles. I.e The ones with a tiny range but which still meet the overall detection criteria. These are both things that can help improve the quality of your hammer candle and would certainly be worth to consider. However, adding these would ultimately shift us away from the topic in focus. Candle Patterns!

Before we begin

So that the code below can be a complete example, a few parts will be needed to gather data and ingest it into our strategy. The commentary below will not cover these parts. For more information on what is happening read these articles:
  1. For downloading data from Alpha Vantage and ingesting it into Backtrader, read this article.
  2. For creating a strategy with multiple data feeds, read this article.
Of course, the other option is just to extract the indicator portion and insert it into your own strategy. If you do decide to use the complete example, make sure you find the line the following line and insert your API key.
Apikey = 'INSERT YOUR API KEY HERE'
The script will not work if you don’t.

Example Code

'''
Author: www.backtest-rookies.com

MIT License

Copyright (c) 2019 backtest-rookies.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''

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

# IMPORTANT!
# ----------
# Register for an API at:
# https://www.alphavantage.co/support/#api-key
# Then insert it here.
Apikey = 'INSERT YOUR API KEY HERE'


class HammerCandles(bt.Indicator):
    '''
    Thor is a pin candle reversal indicator that tries to catch swings and
    ride the retracement down.
    '''
    lines = ('signal','bull_hammer', 'bear_hammer')
    params = (
        ('rev_wick_ratio', 0.6), #ratio of the long wick
    )

    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):

        #then check the ranges
        range = round(self.data.high - self.data.low,5)

        # Calcualte ratios for green candles or open/close are the same value
        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

        # Repeat for a red candle
        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):
    '''
    Helper function to download Alpha Vantage Data.

    This will return a nested list with each entry containing:
        [0] pandas dataframe
        [1] the name of the feed.
    '''
    data_list = list()

    size = 'compact' if compact else 'full'

    for symbol in symbol_list:

        if debug:
            print('Downloading: {}, Size: {}'.format(symbol, size))

        # Submit our API and create a session
        alpha_ts = TimeSeries(key=Apikey, output_format='pandas')

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

        #Convert the index to 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))


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

# Add our strategy
cerebro.addstrategy(TestStrategy)

# Download our data from Alpha Vantage.
symbol_list = ['LGEN.L','LLOY.L']
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], # This is the Pandas DataFrame
                name=data_list[i][1], # This is the symbol
                timeframe=bt.TimeFrame.Days,
                compression=1,
                fromdate=datetime(2018,1,1),
                todate=datetime(2019,1,1)
                )

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

print('Starting to run')
# Run the strategy
cerebro.run()

cerebro.plot(style='candlestick')

Code Commentary

First, let’s spend a few moments looking at the indicator from a higher level. It has the following lines:
  • Bull_Hammer: This is a line which places markers on the chart. We can configure a line to plot markers using a plot_linesdictionary. The bull hammer line will place a marker anytime we find a hammer candle with a long lower wick.
  • Bear_Hammer: Again, another line which plots markers on the chart. Our bear hammer markers indicate a long upper wick.
  • Signal: This line is not plotted but is available and can be checked in the strategy. This will allow you to monitor it for long and short signals. It will be +1 when a bullish hammer candle is found, -1 when a bearish hammer candle is found and 0 the rest of the time.
Moving on, we get to the magic formula. In fact, the formula used to detect the hammer candle is quite simple in principle (despite the many lines of code!). All we do is take a look at the overall rangeof the candle (highlow). Then we just work out if the wick is greater than a given proportion of the range. The value used is to set the target proportion is rev_wick_ratio and the default value of this parameter is 0.6. This means that the upper or lower wick must be 60% or more of the overall range to be considered a hammer candle.

Why all the lines of code?

The reason we need a few lines to make this calculation is that we need to know how long the wick is. Depending on whether the candle closed up or down means our measurement will change. I.e for the lower wick, if we closed down (red candle) we would measure from the close to the low. Conversely, if it closed up, we would measure from the open to the low. So much of the code within next()is making these checks and then extracting the upper and lower wick ratios accordingly. Apart from that, we try to catch ZeroDivisionErrorsas this can happen easily if your data is not perfect or you have a candle with no wick. Finally, we just make the check by dividing the wick ratio by the range of the candle and compare this to our rev_wick_ratio. If we equal or exceed it, we mark update our lines.

Results

When running the strategy you should see an output like this. showing the output of the example code. This image is zoomed out Zoom in a little and we can see the candles a bit more clearly A closer look at the hammer candle detection As you can see the orange marker indicate a bearish signal and the blue markers indicate a bullish signal.

Conclusion

The idea of using ratios to detect a hammer candle can be applied to many other candlestick types. An engulfing candle is simply a candle with a large body ratio and is larger than the previous candle. With a little thought, you should be able to apply this technique elsewhere with only a few changes.

Find This Post Useful?

If this post saved you time and effort, please consider support the site! There are many ways to support us and some won’t even cost you a penny. 

Backtest Rookies is a registered with Brave publisher!

Brave users can drop us a tip.

Alternatively, support us by switching to Brave using this referral link and we will receive some BAT! 


https://brave.com/bac691

Referral Link

Enjoying the content and thinking of subscribing to Tradingview? Support this site by clicking the referral link before you sign up! 

https://tradingview.go2cloud.org/SHpB


Donate with PayPal using any payment method you are comfortable with! 


3HxNVyh5729ieTfPnTybrtK7J7QxJD9gjD

0x9a2f88198224d59e5749bacfc23d79507da3d431

M8iUBnb8KamHR18gpgi2sSG7jopddFJi8S