Introduction
Candlestick pattern formations commonly consist of 1 to 3 bar structures. You may wish to automatically identify candlestick patterns on your chart where they are occurring and be alerted to them. While some technical analysis software has this feature built in already, you might wonder how you can code candlestick pattern detection using Python for free.
In this post we will look at replicating something along the lines of CQG’s Candlestick Formations feature available in their Integrated Client platform. These particular patterns were referenced from Steve Nison’s book ‘Japanese Candlestick Charting Techniques‘, Second Edition (Prentice Hall, 2001). Steve Nison is the foremost authority on candlestick patterns having introduced the ancient Japanese charting technique to the West back in 1989 in a two page article before the first edition of this book in 1991.
Starting with Python
We will be using Python to identify candlestick patterns in our data, if you’re unfamiliar with Python you can download VSCode for free from Microsoft and follow its step by step instructions for setting up Python on your computer. You then just need to create the complete single python file (it’s easy, you can copy and paste the sections, in order, from this page, then save it as .py file type) and then just click the play icon in the top right of VSCode to run it.
You will need to install any Python modules you use, which you can do in the Terminal window at the base of your VSCode Python setup e.g type pip install yfinance to install the Yahoo Finance module for example and press enter. You can see which other ones you need to install in Step 1. Any error in the Terminal output when running the .py file will indicate if you’re missing any additional module installations compared to those we import.
In the following I will break down what goes into this single .py file and why.
Step 1: Import Necessary Libraries
As mentioned you may need to ‘pip install’ any of these not already on your system from the Terminal window, e.g. ‘pip install mplfinance’. Then at the top of the python file you have created you first need to import the modules we intend to use as the first few lines:
import pandas as pd
import matplotlib.pyplot as plt
import mplfinance as mpf
import yfinance as yf
import matplotlib.patches as patches
import matplotlib.dates as mdates
Step 2: Load Your Data
In this example we will choose daily data for Apple stock for the full year of 2022 but feel free to adjust the dates for 2023 if you prefer. This is pulled down from yfinance (Yahoo finance) imported at the start of our file above, to create the data frame. So place the following code beneath what we have above in Step 1:
# Define the ticker symbol
tickerSymbol = 'AAPL'
# Get data on this ticker
tickerData = yf.Ticker(tickerSymbol)
# Get the historical prices for this ticker
df = tickerData.history(period='1d', start='2022-01-01', end='2022-12-31')
# Ensure that data is sorted by date
df = df.sort_values('Date')
# Define the plot style
style = mpf.make_mpf_style(base_mpf_style='classic')
Step 3: Define the Candlestick Patterns
Next we will get Python to identify candlestick patterns for the same 15 formations which CQG uses in their tool, to keep things simple. Below we cover what candlestick pattern analysis needs to occur, i.e. the rules for it, and then present the corresponding Python code to detect that particular candlestick pattern.
You would add each of these code blocks into your .py file.
Single Bar Candlestick Formations
Hammer
The conditions for the Hammer pattern in our code are:
- The close price is higher than the open price (indicating a bullish candle).
- The low price is lower than both the open and close prices (indicating a long lower shadow).
- The difference between the high price and the close price is less than 10% of the difference between the close and open prices (indicating a short upper shadow).
The Hammer pattern is typically identified by a small real body (the difference between the open and close prices) at the top of the trading range, a long lower shadow (at least twice the length of the real body), and a short or non-existent upper shadow.
The code checks if the real body is at the upper end of the trading range, the lower shadow is at least twice the length of the real body, and the upper shadow is short (less than 10% of the real body size) as follows:
# Define Hammer (HR) pattern
def hammer(df):
real_body = abs(df['Close'] - df['Open']) # Real body size
lower_shadow = df['Open'] - df['Low'] # Lower shadow size
upper_shadow = df['High'] - df['Close'] # Upper shadow size
cond1 = df['Close'] > df['Open'] # Real body is at the upper end of the trading range
cond2 = lower_shadow > 2 * real_body # Long lower shadow (at least twice the length of the real body)
cond3 = upper_shadow < 0.1 * real_body # Short upper shadow
return cond1 & cond2 & cond3
Hanging Man
The conditions for the Hanging Man pattern in our code are:
- The close price is lower than the open price (indicating a bearish candle).
- The low price is lower than both the open and close prices (indicating a long lower shadow).
- The difference between the high price and the close price is less than 10% of the difference between the close and open prices (indicating a short upper shadow).
The Hanging Man pattern is typically identified by a small real body (the difference between the open and close prices) at the top of the trading range, a long lower shadow (at least twice the length of the real body), and a short or non-existent upper shadow.
This code calculates the size of the real body, upper shadow, and lower shadow for each candle. It then checks if the real body is at the upper end of the trading range, the lower shadow is at least twice the length of the real body, and the upper shadow is short (less than 10% of the real body size).
# Define Hanging Man (HM) pattern
def hanging_man(df):
real_body = abs(df['Close'] - df['Open']) # Real body size
upper_shadow = df['High'] - df['Open'] # Upper shadow size
lower_shadow = df['Open'] - df['Low'] # Lower shadow size
cond1 = df['Close'] < df['Open'] # Real body is at the upper end of the trading range
cond2 = lower_shadow > 2 * real_body # Long lower shadow (at least twice the length of the real body)
cond3 = upper_shadow < 0.1 * real_body # Short upper shadow
return cond1 & cond2 & cond3
Inverted Hammer
The conditions for the Inverted Hammer pattern in our code are:
- The close price is higher than the open price (indicating a bullish candle).
- The high price is higher than both the open and close prices (indicating a long upper shadow).
- The difference between the close price and the low price is less than 10% of the difference between the open and close prices (indicating a short lower shadow).
The Inverted Hammer pattern is typically identified by a small real body (the difference between the open and close prices) at the bottom of the trading range, a long upper shadow (at least twice the length of the real body), and a short or non-existent lower shadow.
This code calculates the size of the real body, upper shadow, and lower shadow for each candle. It then checks if the real body is at the lower end of the trading range, the upper shadow is at least twice the length of the real body, and the lower shadow is short (less than 10% of the real body size).
# Define Inverted Hammer (IH) pattern
def inverted_hammer(df):
real_body = abs(df['Close'] - df['Open']) # Real body size
upper_shadow = df['High'] - df['Close'] # Upper shadow size
lower_shadow = df['Close'] - df['Low'] # Lower shadow size
cond1 = df['Close'] > df['Open'] # Real body is at the lower end of the trading range
cond2 = upper_shadow > 2 * real_body # Long upper shadow (at least twice the length of the real body)
cond3 = lower_shadow < 0.1 * real_body # Short lower shadow
return cond1 & cond2 & cond3
Shooting Star
The conditions for the Shooting Star pattern in our code are:
- The close price is lower than the open price (indicating a bearish candle).
- The high price is higher than both the open and close prices (indicating a long upper shadow).
- The difference between the close price and the low price is less than 10% of the difference between the open and close prices (indicating a short lower shadow).
The Shooting Star pattern is typically identified by a small real body (the difference between the open and close prices) at the bottom of the trading range, a long upper shadow (at least twice the length of the real body), and a short or non-existent lower shadow.
This code calculates the size of the real body, upper shadow, and lower shadow for each candle. It then checks if the real body is at the lower end of the trading range, the upper shadow is at least twice the length of the real body, and the lower shadow is short (less than 10% of the real body size).
# Define Shooting Star (SS) pattern
def shooting_star(df):
real_body = abs(df['Close'] - df['Open']) # Real body size
upper_shadow = df['High'] - df['Close'] # Upper shadow size
lower_shadow = df['Close'] - df['Low'] # Lower shadow size
cond1 = df['Close'] < df['Open'] # Real body is at the lower end of the trading range
cond2 = upper_shadow > 2 * real_body # Long upper shadow (at least twice the length of the real body)
cond3 = lower_shadow < 0.1 * real_body # Short lower shadow
return cond1 & cond2 & cond3
2-Bar Candlestick Formations
Engulfing Bearish
The conditions for the Engulfing Bearish pattern in our code are:
- The close price of the previous candle is higher than its open price (indicating a bullish candle).
- The close price of the current candle is lower than its open price (indicating a bearish candle).
- The open price of the current candle is higher than the close price of the previous candle (indicating that the current candle opens above the previous close).
- The close price of the current candle is lower than the open price of the previous candle (indicating that the current candle closes below the previous open).
This pattern is typically identified by a small bullish candle (the previous candle) completely covered or “engulfed” by a larger bearish candle (the current candle). The bearish candle’s open price is higher than the bullish candle’s close price, and the bearish candle’s close price is lower than the bullish candle’s open price.
This code calculates the size of the real body for the previous and current candles. It then checks if the current candle’s real body is larger than the previous candle’s real body, in addition to the other conditions.
# Define Engulfing Bearish (EGb) pattern
def engulfing_bearish(df):
real_body_prev = abs(df['Close'].shift(1) - df['Open'].shift(1)) # Real body size of the previous candle
real_body_curr = abs(df['Close'] - df['Open']) # Real body size of the current candle
cond1 = df['Close'].shift(1) > df['Open'].shift(1) # Previous candle is bullish
cond2 = df['Close'] < df['Open'] # Current candle is bearish
cond3 = df['Open'] > df['Close'].shift(1) # Current candle opens above previous close
cond4 = df['Close'] < df['Open'].shift(1) # Current candle closes below previous open
cond5 = real_body_curr > real_body_prev # Current candle's real body is larger than the previous candle's real body
return cond1 & cond2 & cond3 & cond4 & cond5
Engulfing Bullish
The conditions for the Engulfing Bullish pattern in our code are:
- The close price of the previous candle is lower than its open price (indicating a bearish candle).
- The close price of the current candle is higher than its open price (indicating a bullish candle).
- The open price of the current candle is lower than the close price of the previous candle (indicating that the current candle opens below the previous close).
- The close price of the current candle is higher than the open price of the previous candle (indicating that the current candle closes above the previous open).
This pattern is typically identified by a small bearish candle (the previous candle) completely covered or “engulfed” by a larger bullish candle (the current candle). The bullish candle’s open price is lower than the bearish candle’s close price, and the bullish candle’s close price is higher than the bearish candle’s open price.
This code calculates the size of the real body for the previous and current candles. It then checks if the current candle’s real body is larger than the previous candle’s real body, in addition to the other conditions.
# Define Engulfing Bullish (EGB) pattern
def engulfing_bullish(df):
real_body_prev = abs(df['Close'].shift(1) - df['Open'].shift(1)) # Real body size of the previous candle
real_body_curr = abs(df['Close'] - df['Open']) # Real body size of the current candle
cond1 = df['Close'].shift(1) < df['Open'].shift(1) # Previous candle is bearish
cond2 = df['Close'] > df['Open'] # Current candle is bullish
cond3 = df['Open'] < df['Close'].shift(1) # Current candle opens below previous close
cond4 = df['Close'] > df['Open'].shift(1) # Current candle closes above previous open
cond5 = real_body_curr > real_body_prev # Current candle's real body is larger than the previous candle's real body
return cond1 & cond2 & cond3 & cond4 & cond5
Dark Cloud Cover
The conditions for the Dark Cloud Cover pattern in our code are:
- The close price of the previous candle is higher than its open price (indicating a bullish candle).
- The close price of the current candle is lower than its open price (indicating a bearish candle).
- The open price of the current candle is higher than the high price of the previous candle (indicating that the current candle opens above the previous high).
- The close price of the current candle is lower than the midpoint of the previous candle (indicating that the current candle closes below the midpoint of the previous candle).
This pattern is typically identified by a bullish candle (the previous candle) followed by a bearish candle (the current candle) that opens above the high of the previous candle and closes below the midpoint of the previous candle.
# Define Dark Cloud (DC) pattern
def dark_cloud(df):
cond1 = df['Close'].shift(1) > df['Open'].shift(1) # Previous candle is bullish
cond2 = df['Close'] < df['Open'] # Current candle is bearish
cond3 = df['Open'] > df['Close'].shift(1) # Current candle opens above previous close
cond4 = df['Close'] < (df['Open'].shift(1) + df['Close'].shift(1)) / 2 # Current candle closes below midpoint of previous candle
return cond1 & cond2 & cond3 & cond4
Double Doji
The conditions for the Double Doji pattern in our code are:
- The open price of the previous candle is equal to its close price (indicating a doji).
- The open price of the current candle is equal to its close price (indicating a doji).
This pattern is typically identified by two consecutive doji candles, where the open and close prices are the same or very close to each other.
In real-world scenarios, the open and close prices may not be exactly the same due to the volatility of the market. In this code, 0.001 * df['Close']
is 0.1% of the close price. You can adjust this value based on how much difference you want to allow.
# Define Double Doji (DD) pattern
def double_doji(df):
cond1 = abs(df['Open'].shift(1) - df['Close'].shift(1)) < (0.001 * df['Close'].shift(1)) # Previous candle is a doji (open is close to close) within .1% of close
cond2 = abs(df['Open'] - df['Close']) < (0.001 * df['Close']) # Current candle is a doji (open is close to close) within 1% of close
return cond1 & cond2
Harami Bearish
The conditions for the Harami Bearish pattern in our code are:
- The close price of the previous candle is greater than its open price (indicating a bullish candle).
- The close price of the current candle is less than its open price (indicating a bearish candle).
- The open price of the current candle is less than the close price of the previous candle.
- The close price of the current candle is greater than the open price of the previous candle.
This pattern is typically identified by a large bullish candle followed by a smaller bearish candle where the open and close prices of the bearish candle are within the open and close prices of the previous bullish candle.
In this code, 0.001 * df['Close'].shift(1)
and 0.001 * df['Open'].shift(1)
are 0.1% of the close and open prices of the previous candle. You can adjust these values based on how much difference you want to allow.
# Define Harami Bearish (HIb) pattern
def harami_bearish(df):
cond1 = df['Close'].shift(1) > df['Open'].shift(1) # Previous candle is bullish
cond2 = df['Close'] < df['Open'] # Current candle is bearish
cond3 = df['Open'] <= df['Close'].shift(1) + (0.001 * df['Close'].shift(1)) # Current candle opens below or close to previous close within .1% of close
cond4 = df['Close'] >= df['Open'].shift(1) - (0.001 * df['Open'].shift(1)) # Current candle closes above or close to previous open within .1% of open
return cond1 & cond2 & cond3 & cond4
Harami Bullish
The conditions for the Harami Bullish pattern in our code are:
- The close price of the previous candle is less than its open price (indicating a bearish candle).
- The close price of the current candle is greater than its open price (indicating a bullish candle).
- The open price of the current candle is greater than the close price of the previous candle.
- The close price of the current candle is less than the open price of the previous candle.
This pattern is typically identified by a large bearish candle followed by a smaller bullish candle where the open and close prices of the bullish candle are within the open and close prices of the previous bearish candle.
Similar to above, in this code, 0.001 * df['Close'].shift(1)
and 0.001 * df['Open'].shift(1)
are 0.1% of the close and open prices of the previous candle. You can adjust these values based on how much difference you want to allow.
# Define Harami Bullish (HIB) pattern
def harami_bullish(df):
cond1 = df['Close'].shift(1) < df['Open'].shift(1) # Previous candle is bearish
cond2 = df['Close'] > df['Open'] # Current candle is bullish
cond3 = df['Open'] <= df['Close'].shift(1) + (0.001 * df['Close'].shift(1)) # Current candle opens above or close to previous close within .1% of close
cond4 = df['Close'] >= df['Open'].shift(1) - (0.001 * df['Open'].shift(1)) # Current candle closes below or close to previous open within .1% of open
return cond1 & cond2 & cond3 & cond4
Piercing Line
The conditions for the Piercing Line pattern in our code are:
- The close price of the previous candle is less than its open price (indicating a bearish candle).
- The close price of the current candle is greater than its open price (indicating a bullish candle).
- The open price of the current candle is less than the low price of the previous candle.
- The close price of the current candle is greater than the midpoint of the open and close prices of the previous candle.
This pattern is typically identified by a bearish candle followed by a bullish candle where the bullish candle opens lower than the low of the previous day and closes more than halfway into the previous bearish candle’s body.
In this code, 0.001 * df['Low'].shift(1)
is 0.1% of the low price of the previous candle. You can adjust this value based on how much difference you want to allow.
# Define Piercing Line (PL) pattern
def piercing_line(df):
cond1 = df['Close'].shift(1) < df['Open'].shift(1) # Previous candle is bearish
cond2 = df['Close'] > df['Open'] # Current candle is bullish
cond3 = df['Open'] <= df['Low'].shift(1) + (0.001 * df['Low'].shift(1)) # Current candle opens below or close to previous low within .1%
cond4 = df['Close'] >= (df['Open'].shift(1) + df['Close'].shift(1)) / 2 # Current candle closes above or close to midpoint of previous candle
return cond1 & cond2 & cond3 & cond4
3-Bar Candlestick Formations
Morning Star
The conditions for the Morning Star pattern in our code are:
- The close price of the first candle (two periods ago) is less than its open price (indicating a bearish candle).
- The close price of the second candle (one period ago) is greater than its open price (indicating a bullish candle).
- The close price of the current candle is greater than its open price (indicating a bullish candle).
- The close price of the second candle is less than the low price of the first candle.
- The close price of the current candle is greater than the midpoint of the open and close prices of the first candle.
This pattern is typically identified by a bearish candle followed by a small bullish or bearish candle that gaps below the close of the previous day, followed by a bullish candle that closes well into the first session’s bearish candle body.
In this code, 0.001 * df['Low'].shift(2)
is 0.1% of the low price of the first candle. You can adjust this value based on how much difference you want to allow.
def morning_star(df):
cond1 = df['Close'].shift(2) < df['Open'].shift(2) # First candle is bearish
cond2 = df['Close'].shift(1) > df['Open'].shift(1) # Second candle is bullish
cond3 = df['Close'] > df['Open'] # Third candle is bullish
cond4 = df['Close'].shift(1) <= df['Low'].shift(2) + (0.001 * df['Low'].shift(2)) # Second candle closes below or close to first candle's low within .1%
cond5 = df['Close'] >= (df['Open'].shift(2) + df['Close'].shift(2)) / 2 # Third candle closes above or close to midpoint of first candle
return cond1 & cond2 & cond3 & cond4 & cond5
Morning Doji Star
The conditions for the Morning Doji Star pattern in our code are:
- The close price of the first candle (two periods ago) is less than its open price (indicating a bearish candle).
- The open price of the second candle (one period ago) equals its close price (indicating a doji).
- The close price of the current candle is greater than its open price (indicating a bullish candle).
- The open price of the second candle is less than the low price of the first candle.
- The close price of the current candle is greater than the midpoint of the open and close prices of the first candle.
This pattern is typically identified by a bearish candle followed by a doji that gaps below the close of the previous day, followed by a bullish candle that closes well into the first session’s bearish candle body.
In this code, 0.001 * df['Close'].shift(1)
and 0.001 * df['Low'].shift(2)
are 0.1% of the close price of the second candle and the low price of the first candle, respectively. You can adjust these values based on how much difference you want to allow.
# Define Morning Doji Star (MDS) pattern
def morning_doji_star(df):
cond1 = df['Close'].shift(2) < df['Open'].shift(2) # First candle is bearish
cond2 = abs(df['Open'].shift(1) - df['Close'].shift(1)) <= (0.001 * df['Close'].shift(1)) # Second candle is a doji (open equals close or close to it) within .1%
cond3 = df['Close'] > df['Open'] # Third candle is bullish
cond4 = df['Open'].shift(1) <= df['Low'].shift(2) + (0.001 * df['Low'].shift(2)) # Second candle opens below or close to first candle's low within .1%
cond5 = df['Close'] >= (df['Open'].shift(2) + df['Close'].shift(2)) / 2 # Third candle closes above or close to midpoint of first candle
return cond1 & cond2 & cond3 & cond4 & cond5
Evening Star
The conditions for the Evening Star pattern in our code are:
- The close price of the first candle (two periods ago) is greater than its open price (indicating a bullish candle).
- The close price of the second candle (one period ago) is less than its open price (indicating a bearish candle).
- The close price of the current candle is less than its open price (indicating a bearish candle).
- The close price of the second candle is greater than the high price of the first candle.
- The close price of the current candle is less than the midpoint of the open and close prices of the first candle.
This pattern is typically identified by a bullish candle followed by a bearish candle that gaps above the close of the previous day, followed by a bearish candle that closes well into the first session’s bullish candle body.
In this code, 0.001 * df['High'].shift(2)
is 0.1% of the high price of the first candle. You can adjust this value based on how much difference you want to allow.
# Define Evening Star (ES) pattern
def evening_star(df):
cond1 = df['Close'].shift(2) > df['Open'].shift(2) # First candle is bullish
cond2 = df['Close'].shift(1) < df['Open'].shift(1) # Second candle is bearish
cond3 = df['Close'] < df['Open'] # Third candle is bearish
cond4 = df['Close'].shift(1) >= df['High'].shift(2) - (0.001 * df['High'].shift(2)) # Second candle closes above or close to first candle's high within .1%
cond5 = df['Close'] <= (df['Open'].shift(2) + df['Close'].shift(2)) / 2 # Third candle closes below or close to midpoint of first candle
return cond1 & cond2 & cond3 & cond4 & cond5
Evening Doji Star
The conditions for the Evening Doji Star pattern in our code are:
- The close price of the first candle (two periods ago) is greater than its open price (indicating a bullish candle).
- The open price of the second candle (one period ago) equals its close price (indicating a doji).
- The close price of the current candle is less than its open price (indicating a bearish candle).
- The open price of the second candle is greater than the high price of the first candle.
- The close price of the current candle is less than the midpoint of the open and close prices of the first candle.
This pattern is generally identified by a bullish candle followed by a doji that gaps above the close of the previous day, followed by a bearish candle that closes well into the first session’s bullish candle body.
In this code, 0.001 * df['Close'].shift(1)
and 0.001 * df['High'].shift(2)
are 0.1% of the close price of the second candle and the high price of the first candle, respectively. Again, adjust these values based on how much difference you want to allow.
# Define Evening Doji Star (EDS) pattern
def evening_doji_star(df):
cond1 = df['Close'].shift(2) > df['Open'].shift(2) # First candle is bullish
cond2 = abs(df['Open'].shift(1) - df['Close'].shift(1)) <= (0.001 * df['Close'].shift(1)) # Second candle is a doji (open equals close) within .1%
cond3 = df['Close'] < df['Open'] # Third candle is bearish
cond4 = df['Open'].shift(1) >= df['High'].shift(2) - (0.001 * df['High'].shift(2)) # Second candle opens above or close to first candle's high within .1%
cond5 = df['Close'] <= (df['Open'].shift(2) + df['Close'].shift(2)) / 2 # Third candle closes below or close to midpoint of first candle
return cond1 & cond2 & cond3 & cond4 & cond5
Step 4: Apply the Patterns to the Data
Next we add the pattern columns into the data frame as follows:
# Add the pattern columns to the DataFrame
df['Hammer'] = hammer(df)
df['Hanging Man'] = hanging_man(df)
df['Inverted Hammer'] = inverted_hammer(df)
df['Shooting Star'] = shooting_star(df)
df['Engulfing Bearish'] = engulfing_bearish(df)
df['Engulfing Bullish'] = engulfing_bullish(df)
df['Dark Cloud'] = dark_cloud(df)
df['Double Doji'] = double_doji(df)
df['Harami Bearish'] = harami_bearish(df)
df['Harami Bullish'] = harami_bullish(df)
df['Piercing Line'] = piercing_line(df)
df['Morning Star'] = morning_star(df)
df['Morning Doji Star'] = morning_doji_star(df)
df['Evening Star'] = evening_star(df)
df['Evening Doji Star'] = evening_doji_star(df)
Step 5: Arrange Colouring and Labelling
In this part of the file we make abbreviations for each candlestick pattern so that we can label them concisely on the chart to save space and also choose some colours for the rectangles that will be highlighting where the candlesticks generating these patterns are occurring.
# Define abbreviations for each pattern
abbreviations = {
'HR': 'Hammer',
'HM': 'Hanging Man',
'IH': 'Inverted Hammer',
'SS': 'Shooting Star',
'EGb': 'Engulfing Bearish',
'EGB': 'Engulfing Bullish',
'DC': 'Dark Cloud',
'DD': 'Double Doji',
'HIb': 'Harami Bearish',
'HIB': 'Harami Bullish',
'PL': 'Piercing Line',
'MS': 'Morning Star',
'MDS': 'Morning Doji Star',
'ES': 'Evening Star',
'EDS': 'Evening Doji Star'
}
# Define colors for each pattern
colors = {
'HR': 'red',
'HM': 'blue',
'IH': 'green',
'SS': 'purple',
'EGb': 'orange',
'EGB': 'pink',
'DC': 'brown',
'DD': 'gray',
'HIb': 'cyan',
'HIB': 'magenta',
'PL': 'yellow',
'MS': 'lime',
'MDS': 'indigo',
'ES': 'gold',
'EDS': 'silver'
}
Step 6: Identify Candlestick Patterns Visually
To visualise the candlestick pattern detection in Python, we create the code to place highlighted rectangles and labels over the identified formations on a chart, in a similar way to how CQG do it. So for the single bar patterns we only highlight one candle but for the 2 and 3 bar candlestick patterns we highlight the full pattern so that the rectangle covers 2 or 3 candles respectively. I have set the alpha to 0.3 so that we can still see the candle formation behind the highlighting rectangles.
Sometimes I’ve found candlesticks are detected as being part of more than one pattern, so a quick way to stop the labels overlapping each other and making them all illegible, is to place them at a percentage distance of the candle length apart. I’ve set this at 30% (0.3) in the code below, there are likely more elegant ways to do it but it works for now.
# Create a new figure and axes
fig, axes = mpf.plot(df, type='candle', style=style, title='Candlestick Chart - AAPL', ylabel='Price', returnfig=True)
# Get the main Axes object
ax = axes[0]
# Create a dictionary to store labels for each candle
labels = {}
# Iterate over the DataFrame rows
for i in range(len(df)):
# Check each pattern
for abbr, pattern in abbreviations.items():
# If the pattern is found in the current row
if df[pattern].iloc[i]:
# Calculate the rectangle parameters
if pattern in ['Morning Star', 'Morning Doji Star', 'Evening Star', 'Evening Doji Star'] and i > 1:
x = df.index.get_loc(df.index[i-2])
width = 3
elif pattern not in ['Hammer', 'Hanging Man', 'Inverted Hammer', 'Shooting Star'] and i > 0:
x = df.index.get_loc(df.index[i-1])
width = 2
else:
x = df.index.get_loc(df.index[i])
width = 1
y1 = df[['Low']].iloc[i-width+1:i+1].min().values[0]
y2 = df[['High']].iloc[i-width+1:i+1].max().values[0]
# Create the rectangle
ax.fill_between([x-0.5, x+width-0.5], y1, y2, color=colors[abbr], alpha=0.3)
# Add the abbreviation label to the labels dictionary
if x in labels:
labels[x].append(abbr)
else:
labels[x] = [abbr]
# Add the labels to the plot
for x, label_list in labels.items():
for j, label in enumerate(label_list):
ax.text(x, df['Low'][x] - (j * 0.3 * (df['High'][x] - df['Low'][x])), label, color='black', fontsize=9, ha='center', va='top')
# Display the plot
plt.show()
Step 7: Run the file
If all goes to plan your python code will now identify candlestick patterns in your data by producing a chart similar, if not identical to mine below:
If you zoom in you can better see our various patterns coming through, like the Bearish Harami 2-bar pattern (turquoise rectangle) and 5 the Evening Star 3-bar pattern (yellow rectangle) which I’ve focussed in on in the image below:
Or in this next image you can see a bearish engulfing candle pattern is detected but at the same time the Python code has detected a dark cloud cover candle pattern. It is scenarios like this that need the label spacing applied to be able to read them.
Summary
So I hope you found this step by step guide to identifying candlestick patterns with Python helpful and a good starting point to understand how such pattern recognition tools can be coded in Python. You can use this as a building block for further development and utilise it as a step to approach back-testing ideas. For example, did the next 10 days after a bearish candle pattern was identified with the Python code create a profitable trade scenario for this security or not? You could try adding more patterns or work on identifying candlestick patterns in real time with Python over much smaller timeframes. Do these candlestick patterns you identify enforce your other indicators’ buy and sell signals too or conflict with them? I would be very interested to know how you get on.
Pattern Name | Pattern Type | Number of Bars | Pattern Description | Market Implication |
---|---|---|---|---|
Hammer | Bullish | Single | Small real body at the top, long lower shadow, short/no upper shadow. | Potential trend reversal, buying opportunity. |
Hanging Man | Bearish | Single | Similar to Hammer but occurs at the end of an uptrend. | Possible top formation, selling opportunity. |
Inverted Hammer | Bullish | Single | Small real body at the bottom, long upper shadow, short/no lower shadow. | Indicative of a potential trend reversal. |
Shooting Star | Bearish | Single | Inverse of Inverted Hammer, appears in an uptrend. | Signals potential downward reversal. |
Engulfing Bearish | Bearish | 2-Bar | A small bullish candle followed by a large bearish candle that engulfs the former. | Indicates a bearish reversal. |
Engulfing Bullish | Bullish | 2-Bar | A small bearish candle followed by a large bullish candle that engulfs the former. | Suggests a bullish reversal. |
Dark Cloud Cover | Bearish | 2-Bar | A bullish candle followed by a bearish candle that opens above the first’s high and closes below its midpoint. | Bearish reversal pattern in an uptrend. |
Double Doji | Neutral or Bearish | 2-Bar | Two consecutive doji candles, where the open and close prices are almost the same. | Indicates indecision, potential for a reversal. |
Harami Bearish | Bearish | 2-Bar | Large bullish candle followed by a smaller bearish candle within the previous body. | Signals a potential bearish reversal. |
Harami Bullish | Bullish | 2-Bar | Large bearish candle followed by a smaller bullish candle within the previous body. | Suggests a potential bullish reversal. |
Piercing Line | Bullish | 2-Bar | A bearish candle followed by a bullish candle that opens below the previous low and closes more than halfway into its body. | Indicates a bullish reversal in a downtrend. |
Morning Star | Bullish | 3-Bar | A bearish candle followed by a short body candle and then a bullish candle. | Suggests a bullish reversal after a downtrend. |
Morning Doji Star | Bullish | 3-Bar | Similar to Morning Star but with a Doji as the second candle. | Strong indicator of a bullish reversal. |
Evening Star | Bearish | 3-Bar | A bullish candle followed by a short body candle and then a bearish candle. | Indicates a bearish reversal after an uptrend. |
Evening Doji Star | Bearish | 3-Bar | Similar to Evening Star but with a Doji as the second candle. | Strong indicator of a bearish reversal. |
Further Reading
Nison, Steve. Japanese Candlestick Charting Techniques, Second Edition. Prentice Hall, 2001, New York.
Leave a Reply