Pine Script – Lesson 6: How To Detect Engulfing Candles
Table of Contents
How To Identify Candle Patterns Using Pine Script
In this lesson I’ll show you how to detect basic candlestick patterns using Pine Script.
We’ll focus solely on Engulfing Candles for now, but the process involved in identifying them is similar for all other candle patterns such as pinbars, shooting stars and hammers, dojis, higher-high higher-close and lower-low lower-close candles.
Today we’ll be expanding upon the script that we made in Lesson 4: Generating RSI Signals. This way the lesson will produce a practical and useful outcome – an RSI oscillator that you can modify to detect whatever kinds of candle patterns you want based on the RSI conditions.
Video Lesson
If you prefer to learn in a visual/audio manner, then here’s a video version of this lesson:
Working With the RSI Indicator
This script will essentially be a basic remake of my RSI Swing Signals indicator. I hope to demonstrate how you can create your own custom indicators similar to this:
If you’re inexperienced with Pine Script and you haven’t gone through the Basics section of my Pine Script lessons then I highly recommend that you do that first. Otherwise… let’s get started.
Here’s the source code from the final lesson of the Basics section which we will be working with again today. I recommend starting a new script and pasting this code into the Pine Script Editor before continuing:
RSI Signal Source Code
//@version=4 study(title="Lesson 5", shorttitle="RSI Signal", overlay=true) // Get user input rsiSource = input(title="RSI Source", type=input.source, defval=close) rsiLength = input(title="RSI Length", type=input.integer, defval=14) rsiOverbought = input(title="RSI Overbought Level", type=input.integer, defval=70) rsiOversold = input(title="RSI Oversold Level", type=input.integer, defval=30) // Get RSI value rsiValue = rsi(rsiSource, rsiLength) rsiOB = rsiValue >= rsiOverbought rsiOS = rsiValue <= rsiOversold // Plot signals to chart plotshape(rsiOB, title="Overbought", location=location.abovebar, color=color.red, transp=0, style=shape.triangledown, text="OB") plotshape(rsiOS, title="Oversold", location=location.belowbar, color=color.green, transp=0, style=shape.triangleup, text="OS") // Send out an alert if this candle meets our conditions alertcondition(rsiOB or rsiOS, title="RSI Alert!", message="RSI signal for XXX")
The Four Basic Elements Of A Strategy
All rules-based strategies are comprised of at least four basic elements:
- Entry Conditions (eg. trending market, test of support/resistance zone)
- Indicator Conditions (eg. RSI overbought/oversold, price is above/below a moving average)
- Entry Reasons (eg. a bullish or bearish engulfing candle)
- Trade Management (eg. stops and targets, position size).
I won’t go into detail about all of these elements in this lesson because that’s outside the scope of what we’re doing. Instead today’s lesson will be focusing on the second and third elements – indicator conditions and entry reasons.
The Anatomy Of Candles
There are four built-in Pine Script variables we have to work with in order to detect candle patterns: the open price, the close price, the high and the low.
Using these four variables we can determine if a candle meets the criteria to be called a certain pattern – such as an “engulfing candle”.

The example above is called an engulfing candle. This is because the close of the green candle closes higher than the open of the red candle.
This is a sign of bullish strength – but if this pattern occurs in the opposite direction as a bearish engulfing candle, then it’s a sign of potential bearish strength.
This simple pattern when used in conjunction with market and indicator conditions and filters can make for a high-accuracy entry reason for almost any strategy.
Comparing Candle Prices With Pine Script
So we know which variables we need to work with – open, close, high, low.
In the case of a bullish engulfing candle, the completion candle must close at a higher price than the previous candle’s open price, just like in the picture above.
In Pine Script we could detect this candle condition with the following line of code:
higherClose = close >= open[1]
This variable will turn true only if the current candle’s closing price is greater than or equal to the previous candle’s opening price.
In order to be considered an engulfing candle, the previous candle must have also closed in the opposite direction – for example, in the illustration above the candle preceding the engulfing candle is red.
In order to determine whether the previous candle was red we can add this line of code:
bullishEngulfing = close >= open[1] and close[1] < open[1]
It’s that simple!
Of course here are a few more steps you can add that will dramatically improve the accuracy of this engulfing candle detection (for example – ensuring that it’s a swing low, ignoring setups with large rejection wicks, etc). But this will do the trick for detecting basic engulfing candles. I’ll cover those more advanced techniques in future lessons.
I haven’t covered arrays yet in any of my lessons, but they are very simple to understand. Square brackets [ ] are used to reference an “array”, which can be thought of as a list of values. In Pine Script this is referred to as the Historical Referencing Operator which will perhaps make more sense if you’re new to coding.
In the first statement we’re asking for the opening price of the candle with the array index (position) of “1”. In programming, arrays and lists typically always start at 0 (zero) instead of 1. This means that close[0] will give us the current candle closing price, and close[100] will give us the candle closing price from 99 candles in the past.
So in the line above, we are essentially saying “close[0] >= open[1]”.
This can be a bit confusing if you’re new to programming but don’t worry – it’ll make sense in time.
Just know that when you are referencing candles in Pine Script you must count up from 0 as you count backwards – so the closing price of the candle 3 bars ago from the current bar will be referenced as “close[2]”.
There are multiple variations of engulfing candles – such as a higher-high higher-close engulfing candle and a fractal swing-low engulfing candle.
In today’s lesson we won’t go into that much detail, but by comparing these candle values with each other it’s quite easy to detect any variation of these patterns that you desire.
For example, if you wanted to detect a higher-high higher-close engulfing candle (ie. one that closes above the high of the wick and not just the opening price), then you can do so with this line of code:
higherHighHigherClose = close >= high[1]
Detecting Engulfing Candles
Now that we’ve covered the basics of a candle’s anatomy and how to get and compare these variables in Pine Script, let’s implement this knowledge into an actual script.
The first thing we’re going to do is add three new variables above the section of Lesson 5’s script that says “// Plot signals to chart”:
bullishEC = close > open[1] and close[1] < open[1] bearishEC = close < open[1] and close[1] > open[1] tradeSignal = ((rsiOS or rsiOS[1]) and bullishEC) or ((rsiOB or rsiOB[1]) and bearishEC)
I’ll walk you through what each of these lines does.
The first variable “bullishEC” will turn true if the current candle’s closing price is higher than the previous candle’s opening price and the previous candle was bearish.
The second variable “bearishEC” will turn true if the current candle’s closing price is lower than the previous candle’s opening price and the previous candle was bullish.
The third variable “tradeSignal” will turn true if a bullish or bearish engulfing candle is detected while the RSI conditions are met.
So if the RSI is currently oversold or it was oversold on the previous bar and “bullishEC” is true, then “tradeSignal” will turn true.
Or alternatively, if the RSI is currently overbought or it was overbought on the previous bar and “bearishEC” is true, “tradeSignal” will turn true.
If it is false and no signal is detected then we ignore the current candle.
With these three variables we can now detect basic engulfing candles whenever the RSI goes overbought or oversold! Simply change your “plot” code to look like this:
// Plot signals to chart plotshape(tradeSignal and bullishEC, title="Long", location=location.belowbar, color=color.green, transp=0, style=shape.triangleup, text="Long") plotshape(tradeSignal and bearishEC, title="Short", location=location.abovebar, color=color.red, transp=0, style=shape.triangledown, text="Short")
And voila!
You have an indicator that will detect counter-trend setups for you while you’re backtesting or even while you’re away from your computer.

If you want to turn this into an oscillator indicator similar to my RSI Swing Signals oscillator then all you need to do is change the parameter “overlay=true” to “overlay=false” in the study() constructor, and then add the line “plot(rsi)” to the end of your script.
You may also need to play around with bgcolor() or with plotshape() in order to see the signals better – but that’s all there is to it!
If you have any questions or suggestions about what you’d like me to cover next, feel free to leave them below. See you next time!
Advanced Course
If you want to take your Pine Script coding to the next level, then I think you’ll be interested in my Pine Script Mastery Course.
If you liked this free content then I promise that you’ll love my premium content where I am able to go into much greater detail and help answer students’ questions!
Source Code
//@version=4 study(title="Lesson 6", shorttitle="RSI Swing Signals", overlay=true) // Get user input rsiSource = input(title="RSI Source", type=input.source, defval=close) rsiLength = input(title="RSI Length", type=input.integer, defval=14) rsiOverbought = input(title="RSI Overbought Level", type=input.integer, defval=70) rsiOversold = input(title="RSI Oversold Level", type=input.integer, defval=30) // Get RSI value rsiValue = rsi(rsiSource, rsiLength) rsiOB = rsiValue >= rsiOverbought rsiOS = rsiValue <= rsiOversold // Identify engulfing candles bullishEC = close > open[1] and close[1] < open[1] bearishEC = close < open[1] and close[1] > open[1] tradeSignal = ((rsiOS or rsiOS[1]) and bullishEC) or ((rsiOB or rsiOB[1]) and bearishEC) // Plot signals to chart plotshape(tradeSignal and bullishEC, title="Long", location=location.belowbar, color=color.green, transp=0, style=shape.triangleup, text="Long") plotshape(tradeSignal and bearishEC, title="Short", location=location.abovebar, color=color.red, transp=0, style=shape.triangledown, text="Short") // Send out an alert if this candle meets our conditions alertcondition(tradeSignal, title="RSI Trade Alert!", message="RSI Swing Signal for XXX")
Hi, could you help me with writing my own script for kind of zig zag indicator? I want to write script that will draw trend line based on candles max and min. If next candle ends higher then previous one then it will be up trend, but when next candle ends on the same level or lower then script should check minimum of candle, and if the min of next candle is lower than min of prev candle than trend should change to downtrend. I’ve searched internet but I can’t find similiar script
Great content!
Hi,
Excellent content! I am just starting to study pine and your lessons help a lot.
One thing I noticed in this lesson: it seems your definition of engulfing candles is incomplete. I believe it should be more like this
// Identify engulfing candles
bullishEC = close >= open[1] and close[1] <= open[1] and open <= close[1]
bearishEC = close <= open[1] and close[1] >= open[1] and open >= close[1]
This makes it complete ! Perfect addition.
That’s funny I actually came here to post the exact same comment and code modification. It’s weird that sometimes the engulfing’s entry price gaps. But what’s even weirder is that the original code gets more wins on my 50 (so far) back test on binance futures than real engulfing setups, lol!
Haha I can relate to that! There’s been several scripts I’ve written with a small mistake or oversight that turned out to be more profitable than doing it properly.
But yes thanks guys, you’re right about this code modification. I work mostly with forex and the broker I use doesn’t have gaps in price action, so I often forget about this issue when working with two-candle patterns!
Awesome to see you guys have worked it out though, well done.
Great article and love your video/course – thank you! just wondered if you had used arrays or something similar to only display labels if they interact with a plotted horizontal line (through pine script). Image attached but no idea if it’s possible and figure if anyone knows if it is…it’d be you :D cheers!
Hi Mbuk2k! You can achieve this without arrays, you can just check if the current bar touches the hline price and use that to determine whether or not to display a label. Here’s an example script (change the hlPrice to whatever you need it to be to display on your chart): https://pastebin.com/GezqB1bJ
Bro you’re a bloody legend! thank you!! keep up the great work matey
Hi!
I’m trying to create an array which can hold boolean values for the last 5 instances of Bullish engulfing (1) & Bearish engulfing (0) patterns. The idea is to generate a buy signal when there are 2 consecutive bullish engulfing patterns.. Can you help with the code please?
Thanks..
Hi Matthew, it’s really helpful. Can you please write a code to detect a DOUBLE TOP AND DOUBLE BOTTOM instead of just engulfing candle ON THIS? Please..
Hi Mat, I’ve combined some of your basic lessons with ema-crossover. For some reason it does’nt work. what have I done wrong? Would you help me out? Best regards, Robert here’s the code: //@version=4 study(title=”RSI EMA-Crossings Swing”, overlay=true) // Get user input RSI rsiSource = input(title=”RSI Source”, type=input.source, defval=close) rsiLength = input(title=”RSI Length”, type=input.integer, defval=14) rsiOverbought = input(title=”RSI Overbought Level”, type=input.integer, defval=70) rsiOversold = input(title=”RSI Oversold Level”, type=input.integer, defval=30) // Get user input Ema short = ema(close, 9) long = ema(close, 21) initialcrossover = crossover(short,long) initialcrossunder = crossunder(short,long) // Get RSI value rsiValue = rsi(rsiSource, rsiLength) rsiOB = rsiValue >=… Read more »
//@version=4 study(title = “RSI EMA-Crossings Swing”, overlay=true) // Get user input RSI rsiSource = input(title=“RSI Source”, type=input.source, defval=close) rsiLength = input(title=“RSI Length”, type=input.integer, defval=14) rsiOverbought = input(title=“RSI Overbought Level”, type=input.integer, defval=75) rsiOversold = input(title=“RSI Oversold Level”, type=input.integer, defval=30) // Get user input Ema short = ema(close, 9) long = ema(close, 21) initialcrossover = crossover(short,long) initialcrossunder = crossunder(short,long) // Get RSI value rsiValue = rsi(rsiSource, rsiLength) rsiOB = rsiValue >= rsiOverbought rsiOS = rsiValue <= rsiOversold // Identify engulfing candles bullishEC = close >= open[1] and close[1] <= open[1] bearishEC = close < open[1] and close[1] > open[1] tradeSignallong =(initialcrossover and… Read more »