Backtesting Candlestick Patterns in Python
One may be interested in using candlestick patterns in their trading strategies. Thanks to libraries like TA-lib, we can automatically detect all of the most common patterns and hence automate our backtesting so we can test out more strategies without the legwork of manually labelling the candles.
Preparing our price data
If you have your own data source you can skip straight to the next section. Just make sure you have full OHLC data, otherwise TA-lib won't be able to construct the candles or recognise any patterns.
I'll be using yfinance here to quickly scrape some crypto price data
1import pandas as pd
2import yfinance as yf
3from datetime import datetime, timedelta
4
5end_time = datetime.now()
6start_time = end_time - timedelta(days = 5)
7
8# Yfinance doesn't have great minute data
9data = yf.download(
10 "BTC-USD",
11 start=start_time,
12 end=end_time,
13 interval="1h")
14
15print(data)
That should leave you with a pandas dataframe full of OHLCV data. You can change the value in timedelta
to get a different amount of data. The smaller timeframe that you choose, the less price history you can request. For hourly data you can request about a month's data.
If you need to scrape a significant amount of historical data, you can check out my article on scraping from Bitstamp.
Detecting Candlestick Patterns
Once we've got our OHLC data, detecting common patterns is a one-liner in TA-lib
1import talib
2hammer = talib.CDLHAMMER(data.Open, data.High, data.Low, data.Close)
In this case we're looking for a hammer pattern. This (supposedly) indicates a bullish reversal, as you can see in the bottom-most green candle on the thumbnail of this article. You can find a big list of all the candlestick patterns and their respective TA-lib functions on the TA-lib github page.
TA-lib very much can be a pain to install. Before installing with pip, you'll want to make sure that the underlying C libraries are installed. You can find instructions on the TA-lib page under dependencies
If you print out hammer
you'll get a pandas series that indicates whether the pattern has been detected
12022-07-09 19:00:00+00:00 0
22022-07-09 20:00:00+00:00 0
32022-07-09 21:00:00+00:00 0
42022-07-09 22:00:00+00:00 0
52022-07-09 23:00:00+00:00 0
6 ..
72022-07-14 16:00:00+00:00 0
82022-07-14 17:00:00+00:00 0
92022-07-14 18:00:00+00:00 0
102022-07-14 19:00:00+00:00 0
112022-07-14 19:06:00+00:00 0
You'll notice here all of the entries are 0
. To make sure it's working we can look for entries where the value is equal to 100
.
1print(hammer[hammer == 100])
12022-07-10 11:00:00+00:00 100
22022-07-11 00:00:00+00:00 100
32022-07-11 02:00:00+00:00 100
42022-07-11 23:00:00+00:00 100
52022-07-12 17:00:00+00:00 100
62022-07-13 10:00:00+00:00 100
72022-07-13 16:00:00+00:00 100
82022-07-14 09:00:00+00:00 100
92022-07-14 11:00:00+00:00 100
10dtype: int32
11Series([], dtype: bool)
If you want to find the range of values that can be returned by a TA-lib function, as well as its inputs, you can open up a regular python shell and try the following:
1Python 3.8.10 (default, Jun 22 2022, 20:18:18)
2[GCC 9.4.0] on linux
3Type "help", "copyright", "credits" or "license" for more information.
4>>> import talib
5>>> help(talib.CDLHAMMER)
1Help on function CDLHAMMER in module talib._ta_lib:
2
3CDLHAMMER(...)
4 CDLHAMMER(open, high, low, close)
5
6 Hammer (Pattern Recognition)
7
8 Inputs:
9 prices: ['open', 'high', 'low', 'close']
10 Outputs:
11 integer (values are -100, 0 or 100)
From experimentation, I've found that a value of 100
represents a bullish signal, -100
a bearish signal, and 0
neutral.
Backtesting the strategy
I'm also going to grab all the instances of the hanging man pattern to use as our sell signal. It's very similar to the hammer and is the bearish equivalent.
1hanging_man =talib.CDLHANGINGMAN(data.Open, data.High, data.Low, data.Close)
From here we can construct our buys
and sells
series, which we can use to feed into VectorBt.
1buys = hammer == 100
2sells = hanging_man == -100
3pf = vbt.Portfolio.from_signals(data.Close, buys, sells, fees = 0.005)
The syntax might look a little weird there but we just want to convert our numerical value from our series into True/False
values that VectorBt can accept. Make sure to import vectorbt as vbt
.
At this point you can get some stats from the backtest by printing out pf.stats()
, or you can plot what we've got so far with pf.plot()
.
You can also print out the trade records in an easy to read format
1trade_records = pf.trades.records_readable
2print(trade_records.sort_values("Entry Timestamp").to_string())
This is great for checking that order execution is as you expect. So that's everything you need to know to get started backtesting candlestick pattern based strategies. Everything from here is just composing what we've learned here with some basic pandas knowledge.
Video tutorial
If you'd prefer a video tutorial, check out my youtube channel: