CandleEvaluation
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © SimpleCryptoLife

//@version=5

// @description Functions to evaluate bullish and bearish, engulfing, and outsized candles. They are different from the built-in indicators from TradingView in that
// these functions don't evaluate classical patterns composed of multiple candles, and they reflect my own understanding of what is "bullish" and bearish", "engulfing", and "outsized".

library("CandleEvaluation", overlay=true)

var string errorTextLibraryName = "SimpleCryptoLife/CandleEvaluation"


// ------------------------------------------//
//          BULLISH & BEARISH CANDLES        //
// ------------------------------------------//

// @function isBullishBearishCandle() determines if the current candle is bullish or bearish according to the length of the wicks and the open and close.
// @param int _barsBack How many bars back is the candle you want to evaluate. By default this is 0, i.e., the current bar.
// @returns Two values, true or false, for whether it's a bullish or bearish candle respectively.

export isBullishBearishCandle(int _barsBack = 0) =>
    var string _errorTextFunctionName = "isBullishBearishCandle()"
    if _barsBack < 0
        runtime.error("Parameter _barsBack is less than zero. [Error001" + " from function " + _errorTextFunctionName + " in library " + errorTextLibraryName + "]")
    int _i = _barsBack  // It's just shorter, for readability
    float _higherOfOpenClose = open[_i] > close[_i] ? open[_i] : open[_i] <= close[_i] ? close[_i] : na, float _lowerOfOpenClose = open[_i] <= close[_i] ? open[_i] : open[_i] > close[_i] ? close[_i] : na
    float _upperWick = high[_i] - _higherOfOpenClose, float _lowerWick = _lowerOfOpenClose - low[_i]
    float _body = math.abs(open[_i]-close[_i])
    bool _isUpperWickBigger = _upperWick > (_body + _lowerWick) // If the upper wick is larger than the body + lower wick, it’s a bearish candle.
    bool _isLowerWickBigger = _lowerWick > (_body + _upperWick) // If the lower wick is larger than the body + upper wick, it’s a bullish candle.
    bool _isGreenCandle =  close[_i] >= open[_i]
    bool _isRedCandle =  close[_i] < open[_i]
    bool _noWickBigger = not _isUpperWickBigger and not _isLowerWickBigger
    bool _isBullishCandle = _isLowerWickBigger or (_noWickBigger and _isGreenCandle) // If neither wick is larger, go by colour.
    bool _isBearishCandle = _isUpperWickBigger or (_noWickBigger and _isRedCandle)
    [_isBullishCandle,_isBearishCandle]

// DEMO

[isBullishCandle,isBearishCandle] = isBullishBearishCandle()

bool inputColourBullishAndBearishCandles = input.bool(defval=true, title="Colour Bullish and Bearish Candles", group="isBullishBearishCandle()")
color demoBarColor = not inputColourBullishAndBearishCandles ? color.gray : isBullishCandle ? color.green : isBearishCandle ? color.red : color.yellow
barcolor(demoBarColor)
alertcondition(isBullishCandle,".isBullishCandle", "A bullish candle has printed. Library: SimpleCryptoLife/CandleEvaluation")
alertcondition(isBearishCandle,".isBearishCandle", "A bearish candle has printed. Library: SimpleCryptoLife/CandleEvaluation")


// ------------------------------------------//
//          TRIPLE BULL/BEAR CANDLES         //
// ------------------------------------------//

// @function isTripleBull() Tells you whether a candle is a "Triple Bull" - that is, one which is bullish in three ways:
// It closes higher than it opens; it closes higher than the body of the previous candle; and the High is above the High of the previous candle.
// @param int _barsBack How many bars back is the candle you want to evaluate. By default this is 0, i.e., the current bar.
// @returns True or false.

export isTripleBull(int _barsBack = 0) =>
    var string _errorTextFunctionName = "isTripleBull()"
    if _barsBack < 0
        runtime.error("Parameter _barsBack is less than zero. [Error001" + " from function " + _errorTextFunctionName + " in library " + errorTextLibraryName + "]")
    int _i0 = _barsBack, int _i1 = _barsBack+1  // Get the equivalents of [0] and [1]
    bool _1 = close[_i0] > open[_i0] // This is an up candle
    bool _2 = (close[_i0] > open[_i1]) and (close[_i0] > close[_i1]) // We closed higher than the body of the previous candle
    bool _3 = high[_i0] > high[_i1] // The High is higher than the previous High
    bool _result = _1 and _2 and _3

// @function isTripleBear() Tells you whether a candle is a "Triple Bear" - that is, one which is bearish in three ways:
// It closes lower than it opens; it closes lower than the body of the previous candle; and the Low is below the Low of the previous candle.
// @param int _barsBack How many bars back is the candle you want to evaluate. By default this is 0, i.e., the current bar.
// @returns True or false.

export isTripleBear(int _barsBack = 0) =>
    var string _errorTextFunctionName = "isTripleBear()"
    if _barsBack < 0
        runtime.error("Parameter _barsBack is less than zero. [Error001" + " from function " + _errorTextFunctionName + " in library " + errorTextLibraryName + "]")
    int _i0 = _barsBack, int _i1 = _barsBack+1  // Get the equivalents of [0] and [1]
    bool _1 = close[_i0] < open[_i0] // This is a down candle
    bool _2 = (close[_i0] < open[_i1]) and (close[_i0] < close[_i1]) // We closed lower than the body of the previous candle
    bool _3 = low[_i0] < low[_i1] // The Low is lower than the previous Low
    bool _result = _1 and _2 and _3

// DEMO

isTripleBullCandle = isTripleBull()
isTripleBearCandle = isTripleBear()

bool inputShowTripleBull = input.bool(defval=true, title="Show Triple Bull Candles", group="isTripleBull()")
tripleBullPlot = isTripleBullCandle and inputShowTripleBull ? low : na
plotshape(tripleBullPlot, title="Triple Bullish candle", style=shape.labelup, text="T+", textcolor=color.white,
  location=location.absolute, color=color.blue, size=size.small)

bool inputShowTripleBear = input.bool(defval=true, title="Show Triple Bear Candles", group="isTripleBear()")
tripleBearPlot = isTripleBearCandle and inputShowTripleBear ? high : na
plotshape(tripleBearPlot, title="Triple Bearish candle", style=shape.labeldown, text="T-", textcolor=color.white,
  location=location.absolute, color=color.orange, size=size.small) 

alertcondition(isTripleBullCandle,".isTripleBullCandle", "A Triple Bullish candle has printed. Library: SimpleCryptoLife/CandleEvaluation")
alertcondition(isTripleBearCandle,".isTripleBearCandle", "A Triple Bearish candle has printed. Library: SimpleCryptoLife/CandleEvaluation")


// ------------------------------------------//
//              BIG BODY CANDLE              //
// ------------------------------------------//

// @function isBigBodyCandle() Tells you if the current candle has a larger than average body size.
// @param int _length - The length of the sma to calculate the average
// @param float _percent - The percentage of the average that the candle body has to be to count as "big". E.g. 100 means it has to be just larger than the average, 200 means it has to be twice as large.
// @returns True or false

export isBigBody(int _length = 20, float _percent = 100) =>
    var string _errorTextFunctionName = "isBigBody()"
    if _length < 2 or _length > 200
        runtime.error("Parameter _length is too small or too large. Default is 20. Min: 2. Max: 200. [Error001" + " from function " + _errorTextFunctionName + " in library " + errorTextLibraryName + "]")    
    if _percent < 10 or _percent > 1000
        runtime.error("Parameter _percent is too small or too large. Default is 100. Min: 10. Max: 1000. [Error002" + " from function " + _errorTextFunctionName + " in library " + errorTextLibraryName + "]")
    int _numBars = bar_index < _length ? bar_index + 1 : _length  // If we haven't had that many bars yet just use what we have
    float _bodySize = math.abs(open-close)  // Get the size of the candle body
    float _bodyPercent = (_bodySize/open)/100
    // TODO: Turn it into a percentage of its absolute price, otherwise for crypto assets which move in price fast, it ceases to make sense.
    _simpleMovingAverage = ta.sma(_bodyPercent,_numBars)
    bool isBig = _bodyPercent > (_simpleMovingAverage * (_percent/100))
    isBig

// DEMO
float inputBigBodyPercent = input.float(defval=100, minval=10, maxval=1000, step=10, title="% of Average That Counts as Big", group="isBigBodyCandle()")
isBigBodyCandle = isBigBody(_percent = inputBigBodyPercent)

bool inputShowBigCandles = input.bool(defval=true, title="Colour Big Candles")
color bigBarColour = isBigBodyCandle and inputShowBigCandles ? color.new(color.yellow,80) : na
bgcolor(bigBarColour)

alertcondition(isBigBodyCandle,".isBigBodyCandle", "A big bodied candle has printed. Library: SimpleCryptoLife/CandleEvaluation")


// ====================================
//          ENGULFING CANDLES
// ====================================

// @function isBullishEngulfing() Tells you if the current candle is a bullish engulfing candle. 
// @param int _barsBack How many bars back is the candle you want to evaluate. By default this is 0, i.e., the current bar.
// @param int _atrFraction The denominator for the ATR fraction, which is the small amount by which the open can be different from the previous close.
// @returns True or false

// Clean logic doesn't work because the open is sometimes not EXACTLY the same as the close. So we need to add a tiny ATR % filter.
export isBullishEngulfing(int _barsBack = 0, int _atrFraction = 20) => 
    var string _errorTextFunctionName = "isBullishEngulfing()"
    if _barsBack < 0
        runtime.error("Parameter _barsBack is less than zero. [Error001" + " from function " + _errorTextFunctionName + " in library " + errorTextLibraryName + "]")
    if _atrFraction < 5 or _atrFraction > 100
        runtime.error("Parameter _atrFraction is too big or too small. Default value is 20. Min: 5. Max: 100. [Error002" + " from function " + _errorTextFunctionName + " in library " + errorTextLibraryName + "]")   
    int _index0 = _barsBack, int _index1 = _barsBack+1  // Get the equivalents of [0] and [1]
    bool _1 = close[_index1] < open[_index1] // Previous candle was a down candle
    bool _2 = (close[_index0] > open[_index1]) and (close[_index0] > close[_index1]) // We closed above the previous candle body
    bool _3 = open[_index0] < (close[_index1] + (ta.atr(14)/_atrFraction)) // We opened not more than a tiny amount above the previous close
    bool _result = _1 and _2 and _3

// @function isBearishEngulfing() Tells you if the current candle is a bearish engulfing candle.
// @param int _barsBack How many bars back is the candle you want to evaluate. By default this is 0, i.e., the current bar.
// @param int _atrFraction The denominator for the ATR fraction, which is the small amount by which the open can be different from the previous close.
// @returns True or false

export isBearishEngulfing(int _barsBack = 0, int _atrFraction = 20) => 
    var string _errorTextFunctionName = "isBearishEngulfing()"
    if _barsBack < 0
        runtime.error("Parameter _barsBack is less than zero. [Error001" + " from function " + _errorTextFunctionName + " in library " + errorTextLibraryName + "]")
    if _atrFraction < 5 or _atrFraction > 100
        runtime.error("Parameter _atrFraction is too big or too small. Default value is 20. Min: 5. Max: 100. [Error002" + " from function " + _errorTextFunctionName + " in library " + errorTextLibraryName + "]")   
    int _index0 = _barsBack, int _index1 = _barsBack+1  // Get the equivalents of [0] and [1]
    bool _1 = close[_index1] > open[_index1] // Previous candle was an up candle
    bool _2 = (close[_index0] < open[_index1]) and (close[_index0] < close[_index1]) // We closed below the previous candle body
    bool _3 = open[_index0] > (close[_index1] - (ta.atr(14)/_atrFraction)) // We opened not more than a tiny amount below the previous close
    bool _result = _1 and _2 and _3

// DEMO

int barsBack = 0 
bool isBullishEngulfingCandle = isBullishEngulfing(barsBack)
bool isBearishEngulfingCandle = isBearishEngulfing(barsBack)

float atr14 = ta.atr(14)

inputShowBullishEngulfing = input.bool(defval=true, title="Show Bullish Engulfing Candles", group="isBullishEngulfing\(\)")
inputShowBearishEngulfing = input.bool(defval=true, title="Show Bearish Engulfing Candles", group="isBearishEngulfing\(\)")

var int offset = 0
// offset := 0 - barsBack // This would display the labels in the right places regardless of how many bars back you go.
isBullishEngulfingPlot = isBullishEngulfingCandle and inputShowBullishEngulfing ? low - atr14/2 : na
plotshape(isBullishEngulfingPlot, title="Bullish engulfing candle", style=shape.labelup, text="BE", textcolor=color.white,
  location=location.absolute, color=color.green, size=size.small, offset=offset)

isBearishEngulfingPlot = isBearishEngulfingCandle and inputShowBearishEngulfing ? high + atr14/2 : na
plotshape(isBearishEngulfingPlot, title="Bearish engulfing candle", style=shape.labeldown, text="BE", textcolor=color.white,
  location=location.absolute, color=color.red, size=size.small, offset=offset)

alertcondition(isBullishEngulfingCandle,".isBullishEngulfingCandle", "A Bullish Engulfing candle has printed. Library: SimpleCryptoLife/CandleEvaluation")
alertcondition(isBearishEngulfingCandle,".isBearishEngulfingCandle", "A Bearish Engulfing candle has printed. Library: SimpleCryptoLife/CandleEvaluation")


// ====================================
//          AVERAGE WICK SIZE
// ====================================

// @function f_wickSize() - A function to get the average size of upper and lower wicks over time. This can be more useful than the standard ATR in evaluating the spikiness of an asset or in calculating stops.
// @param float _open - The Open of the candle. Defaults to the Open on the chart timeframe. Included for HTF compatibility.
// @param float _high - The High of the candle. Defaults to the High on the chart timeframe. Included for HTF compatibility.
// @param float _low - The Low of the candle. Defaults to the Low on the chart timeframe. Included for HTF compatibility.
// @param float _close - The Close of the candle. Defaults to the Close on the chart timeframe. Included for HTF compatibility.
// @param float _ohlc4 - The OHLC4 of the candle. Defaults to the OHLC4 on the chart timeframe. Included for HTF compatibility.
// @param int _length - The length for the RMA of wicks.
// @param int _length2 - The length for the EMA of OHLC4.
// @returns Two floats for the absolute values of the average upper and lower candle wicks.


f_rma(_source, _length) =>
    // SMA function from the Pine docs. We can't use the built-in functions because we want to change the length dynamically.
    _sum_sma = 0.0
    int _lengthAdjusted = _length < 1 ? 1 : _length
    for i = 0 to _lengthAdjusted - 1
        _sum_sma := _sum_sma + _source[i] / _lengthAdjusted
    // RMA function from the Pine docs
	_alpha = 1/_lengthAdjusted
	_sum = 0.0
	_sum := na(_sum[1]) ? _sum_sma : _alpha * _source + (1 - _alpha) * nz(_sum[1])

f_ema(_source,_length) => _alpha = (2 / (_length + 1)), var float _ema = 0.0, _ema := na(_ema[1]) ? _source : _alpha * _source + (1 - _alpha) * nz(_ema[1])  // Function taken from the Pine docs for ema().


export f_wickSize(float _open=open, float _high=high, float _low=low, float _close=close, float _ohlc4=ohlc4, int _length=8, int _length2=8) =>
    // Get the current upper and lower wick size
    float _higherOC = math.max(_open,_close)
    float _lowerOC = math.min(_open,_close)
    float _upperWick = _high - _higherOC
    float _lowerWick = _lowerOC - _low
    // Get the wick size as a fraction of OHLC4. 
    //      We use fractions because if an asset moves a large absolute amount, e.g., crypt on a weekly chart, the absolute values are not really relevant any more.
    //      Fractions also aren't a perfect solution, but they're much better. The averages ensure the values catch up reasonably quickly.
    float _upperWickFraction = (_upperWick/_ohlc4)
    float _lowerWickFraction = (_lowerWick/_ohlc4)
    // Get the average of the fraction
    int _lengthAdjusted = bar_index < 1 ? 1 : bar_index <_length ? bar_index : _length
    float _averageUpperWickFraction = f_rma(_upperWickFraction, _lengthAdjusted)
    float _averageLowerWickFraction = f_rma(_lowerWickFraction, _lengthAdjusted)
    // Get the average of OHLC4
    int _lengthAdjusted2 = bar_index < 1 ? 1 : bar_index <_length2 ? bar_index : _length2
    float _averageOHLC4 = f_ema(_ohlc4, _lengthAdjusted2)
    // Convert the fraction back to an absolute value
    float _averageUpperWick = _averageUpperWickFraction * _averageOHLC4
    float _averageLowerWick = _averageLowerWickFraction * _averageOHLC4

    [_averageUpperWick, _averageLowerWick]


// DEMO

in_wickRMALength = input.int(title="Wick RMA Length", group="f_wickSize()", defval=21, minval=5, step=5, maxval=200)
in_OHLC4Length = input.int(title="OHLC4 RMA Length", defval=8, minval=2, maxval=200)
in_showWickSize = input.bool(title="Show Average Wick Size", defval=true)

[averageUpperWick, averageLowerWick] = f_wickSize(_open=open, _high=high, _low=low, _close=close, _ohlc4=ohlc4, _length=in_wickRMALength, _length2=in_OHLC4Length)

plot(in_showWickSize ? high+averageUpperWick : na, title="averageUpperWick", linewidth=1, color=color.green)
plot(in_showWickSize ? low-averageLowerWick : na, title="averageLowerWick", linewidth=1, color=color.red)

// END