Rekt Edge Reversion Band
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// last edited: 2023-03-03 11:30AM v. 28.0
// © RektEdge


//@version=5
import maartenheremans/statistics/1 as stats
import ZenAndTheArtOfTrading/ZenLibrary/5 as zen

indicator(title = "RektEdge Reversion Band V2", shorttitle = "RektEdge Reversion Band V2", overlay = true)

// --- Inputs ---
var confidence_level = input.float(
      2.0, 
      minval=-1.0, 
      title="Confidence Level", 
      group="Confidence",
      tooltip="If the combined weight of the checks is equal to or over this value, a signal will plot"
      )
// Weighted Checks
var band_trade_weight = input.float(1.0, title="Band Trade Weight", group="Check Weight")
var ma_cross_weight = input.float(1.0, title="MA Cross Weight", group="Check Weight")
var engulfing_weight = input.float(1.0, title="Engulfing Weight", group="Check Weight")
var efficiency_weight = input.float(1.0, title="Efficiency Weight", group="Check Weight")
var pullback_weight = input.float(1.0, title="Pullback Weight", group="Check Weight")
var steep_trend_weight = input.float(
     1.0, title="Countertrend Deduction", 
     tooltip="Deducted from total weight if buying/selling against steep trend",
     group="Check Weight"
     )
var total_weight = 0.0

// Same Type Avoidance Check
var same_type_avoidance = input.bool(
     true, 
     "Avoid Repeating Same Type Signals", 
     tooltip="If set to true, signals of the same type as the last signal will be ignored. For example, if the last signal was a buy signal, the next buy signal will be ignored.",
     group="Same Type Avoidance Check")

// Trendline

var ma_select = input.string(
     defval = "combinedMA", 
     options = ["combinedMA", "SMA", "EMA"], 
     title = "Moving Average Type", 
     tooltip = "Selects the type of moving average trendline",
     group="Trendline")
var ma_period = input.int(defval = 36,
      title = "Moving Average Period",
      minval = 1, 
      tooltip = "Selects the number of periods for the moving average", 
      step = 7,
     group="Trendline")
    
// Reversion Band

var trendline_avg_period = input.int(
      7,
      "Trendline Average Period",
      tooltip="Sets period of trendline MA to establish baseline for green/red trend coloring of band",
     group="Reversion Band"
      )
var band_transparency = input.int(
     80,
     "Band Transparency",
     group="Reversion Band"
     )
var delay = input.int(
     2,
     "Band Delay",
     tooltip="Delay the band by this many bars",     
     minval=0,
     group="Reversion Band"
     )
var unit_select = input.string(
     title = "Unit of Separation", 
     defval = "ATR", 
     options = ["ATR","STDEV"], 
     tooltip = "Selects the unit of separation for the channels",
     group="Reversion Band"
     )
var atr_period = input(
     14, 
     title="ATR Period",
     tooltip="Selects the number of periods for the Average True Range unit and filter pass",
     group="Reversion Band"
     )
var stdev_period = input.int(
     defval = 7, 
     title = "Standard Deviation Period", 
     step = 1, 
     tooltip = "Selects the number of periods for the Standard Deviation separation unit (if selected)",
     group="Reversion Band"
     )
var unit_mult = input.float(
     defval = 2.0, 
     title = "Multiples of Unit", 
     step = 0.1, 
     tooltip = "Select the number of separation units for the channels and the ATR filter",
     group="Reversion Band")

// Steep Countertrend Filter
var trend_lookback = input.int(
     20,
     "Trend Lookback",
     group="Countertrend Filter"
     )
var steep_factor = input.float(
     1.0,
     "Steepness Factor",
     tooltip="A counter-trend with slope above this threshold will trigger the filter and reduce the total weight. Suggested values: Day(2-3), Swing(1.5-2), Position(1-1.5)",
     group="Countertrend Filter"
     )     

// Is Last Candle Doji
var wickSize = input(
     2, 
     title="Wick Size (in % of candle)",
     group="Is Last Candle Doji",
     tooltip="Wick size to qualify as Doji")

var bodySize = input(
     0.05, 
     title="Body Size (in % of candle)",
     tooltip="Body size to qualify as Doji",
     group="Is Last Candle Doji")

// Is Last Candle Hammer or Shooting Star
var fib = input(
     0.382, 
     title="Fib Level",
     tooltip="Fib level to qualify as Hammer or Star",
     group="Is Last Candle Hammer or Shooting Star",
     inline="HammerStar")

var colorMatch = input(
     false, 
     title="Color Match",
     tooltip="Require Hammer or Star to also be bullish or bearish respectively",
     group="Is Last Candle Hammer or Shooting Star",
     inline="HammerStar")

// Efficiency Check
var efficiency_z_threshold = input.float(
     0.674,
     "Efficiency Z-Score Threshold",
     tooltip="A value of 2.0 represents 95% confidence of significance",
     group="Efficiency Check"
     )
var fractal_z_threshold = input.float(
     0.674,
     "Fractal Efficiency Z-Score Threshold",
     tooltip="A value of 2.0 represents 95% confidence of significance, the default is 75%",
     group="Efficiency Check"
     )

// Pullback Check
var lookback_pullback = input(
     14, 
     title="Pullback Lookback Period",
     group="Pullback Check")
var direction = input(
     1, 
     title="Direction (1=Green, -1=Red)", 
     tooltip="Pullback bars count direction",
     group="Pullback Check")
var pullback_z_threshold = input.float(
     0.674,
     "Pullback Z-Score Threshold",
     tooltip="A value of 2.0 represents 95% confidence of significance, the default is 75%",
     group="Pullback Check"
     )

// Engulfing Candle Check
var allowance = input(
     0, 
     title="Allowance (in POINTS)",
     tooltip="Allows candles off by this many points to still be considered engulfing",
     group="Engulfing Candle Check")

var rejectionWickSize = input(
     0, 
     title="Rejection Wick Size (in % of body)",
     tooltip="Candles with wicks larger than this size will not be considered engulfing",
     group="Engulfing Candle Check")

var engulfWick = input.bool(
     false, 
     "Wick Engulfed Also",
     tooltip="Require wick to be engulfed as well",
     group="Engulfing Candle Check")


// Bars Above/Below MA Check

var lookback_ma_above = input(
     14, 
     title="MA Above Lookback Period",
     group="Bars Above/Below MA Check")

var lookback_ma_below = input(
     14, 
     title="MA Below Lookback Period",
     group="Bars Above/Below MA Check")

var lookback_ma_crossed = input(
     14, 
     title="MA Crossed Lookback Period",
     group="Bars Above/Below MA Check")



// Indicator Variables
var bool last_signal_is_buy = na
var bool last_signal_is_sell = na
bool is_buy = na
bool is_sell = na

// Calculations
sma = ta.sma(close, ma_period)
ema = ta.ema(close, ma_period)
alpha = 2 / (ma_period + 1)
combined_ma = (alpha * close) + ((1 - alpha) * sma)
trendline = ma_select == "SMA" ? sma : ma_select == "EMA" ? ema : combined_ma
separation = math.abs(unit_mult * (unit_select == "ATR" ? ta.atr(ma_period) : ta.stdev(close,stdev_period)))
long_ema = ta.ema(close, 100)

upper_band = trendline[delay] + separation[delay]
lower_band = trendline[delay] - separation[delay]

[macdLine, signalLine, histLine] = ta.macd(close, 12, 26, 9)
macd_trend_up = macdLine > signalLine and macdLine > 0
macd_trend_down = macdLine < signalLine and macdLine < 0

trendline_diff = trendline - trendline[trend_lookback]
avg_trendline_diff = ta.sma(trendline_diff, trend_lookback)

steep_down = trendline_diff < -avg_trendline_diff * steep_factor
steep_up = trendline_diff > avg_trendline_diff * steep_factor

swingHigh = high[1] == ta.highest(high, ma_period)
swingLow = low[1] == ta.lowest(low, ma_period)

ema_separation = (close - long_ema) / ta.atr(100)
avg_separation = ta.sma(ema_separation, 7)

isAboveLEMA = avg_separation[1] > 1.0
isBelowLEMA = avg_separation[1] < 0 and math.abs(avg_separation[1]) > 1.0

trendline_roc = (trendline - trendline[1]) / (time - time[1])
trendline_avg = ta.sma(trendline, trendline_avg_period)
trending_up = trendline > trendline_avg
trending_down = trendline < trendline_avg
isPivotTop = ta.pivothigh(trendline, 40)
isPivotBottom = ta.pivotlow(trendline,40)
isBelowBand = low[1]<lower_band
isAboveBand = high[1]>upper_band

is_buy := false
is_sell := false
total_weight := 0.0

bars_above_ma_count = ta.barssince(close > sma)
bars_below_ma_count = ta.barssince(close < sma)

pullback_bars_count = 0
direction := 0
efficiency_ratio = stats.efficiencyRatio(close, 14)
fractal_efficiency = stats.fractalEfficiency(close, 30)
bars_crossed_ma_count = zen.barsCrossedMA(lookback_ma_crossed, ma_period)
is_last_candle_doji = zen.isDoji(wickSize, bodySize)
is_last_candle_hammer = zen.isHammer(fib, colorMatch)
is_last_candle_shooting_star = zen.isStar(fib, colorMatch)
is_last_candle_bullish_engulfing = zen.isBullishEC(allowance, rejectionWickSize, engulfWick)
is_last_candle_bearish_engulfing = zen.isBearishEC(allowance, rejectionWickSize, engulfWick)
is_atr_filter_pass = zen.atrFilter(atr_period, unit_mult)

// Check 1: Price below lower band and candle is bullish and swingLow
if is_atr_filter_pass and isBelowBand and (is_last_candle_doji or is_last_candle_bullish_engulfing or is_last_candle_hammer)
    is_buy := true
    total_weight += band_trade_weight

// Check 2: Price above upper band and candle is bearish and swingHigh
if is_atr_filter_pass and isAboveBand and (is_last_candle_doji or is_last_candle_bearish_engulfing or is_last_candle_shooting_star)
    is_sell := true
    total_weight += band_trade_weight

// Check 3: Efficiency Ratio and Fractal Efficiency with Statistical Significance
efficiency_z_score = (efficiency_ratio - ta.sma(efficiency_ratio, 100)) / ta.stdev(efficiency_ratio, 100)
fractal_z_score = (fractal_efficiency - ta.sma(fractal_efficiency, 100)) / ta.stdev(fractal_efficiency, 100)

if efficiency_z_score > efficiency_z_threshold and fractal_z_score > fractal_z_threshold and is_last_candle_hammer
    total_weight += efficiency_weight


// Check 4: MA Cross and Trend
if bars_crossed_ma_count > 0 and is_last_candle_shooting_star and is_atr_filter_pass and trending_up
    is_sell := true
    total_weight += ma_cross_weight
else if bars_crossed_ma_count < 0 and is_last_candle_hammer and is_atr_filter_pass and not trending_up
    is_buy := true
    total_weight += ma_cross_weight

// Check 5: Engulfing
if is_last_candle_bullish_engulfing and is_atr_filter_pass
    is_buy := true
    total_weight += engulfing_weight
else if is_last_candle_bearish_engulfing and is_atr_filter_pass
    is_sell := true
    total_weight += engulfing_weight

// Check 6: Pullback with Statistical Significance
pullback_size = high[pullback_bars_count > 0 ? pullback_bars_count - 1 : 0] - low[pullback_bars_count > 0 ? pullback_bars_count - 1 : 0]
pullback_z_score = (pullback_size - ta.sma(pullback_size, 100)) / ta.stdev(pullback_size, 100)

if pullback_bars_count > 1 and is_atr_filter_pass and direction > 0 and pullback_z_score < pullback_z_threshold and is_last_candle_hammer
    is_buy := true
    total_weight += pullback_weight
else if pullback_bars_count > 1 and is_atr_filter_pass and direction < 0 and pullback_z_score > -pullback_z_threshold and is_last_candle_shooting_star
    is_sell := true
    total_weight += pullback_weight

// Check 7: Avoid Trading Against A Strong Countertrend
if steep_down and is_buy and not macd_trend_down
    total_weight -= steep_trend_weight
else if steep_up and is_sell and not macd_trend_up
    total_weight -= steep_trend_weight



// Weighted Sum Check
if total_weight < confidence_level
    is_buy := false
    is_sell := false

// Check for Same Type Buy/Sell Avoidance
if same_type_avoidance and ((is_buy and last_signal_is_buy) or (is_sell and last_signal_is_sell))
    is_buy := false
    is_sell := false

// Update last signal
if is_buy
    last_signal_is_buy := true
    last_signal_is_sell := false
if is_sell
    last_signal_is_buy := false
    last_signal_is_sell := true

// --- Plots ---
ma_plot = plot(
      trendline, 
      linewidth = 1, 
      color = color.white, 
      title = "Moving Average")
upper_plot = plot(upper_band, linewidth = 1, color = color.gray, title = "Upper Band")
lower_plot = plot(lower_band, linewidth = 1, color = color.gray, title = "Lower Band")
//long_plot = plot(long_ema, "Long EMA", color.white)
fill(
      plot1 = upper_plot, plot2 = lower_plot, 
      color = trending_up ? color.new(color.green, band_transparency) : 
          trending_down ? color.new(color.red, band_transparency) : 
          color.new(color.gray, band_transparency), 
      title = "Channel"
      )
plotshape(
      series = is_buy, 
      style = shape.triangleup, 
      color = color.green, 
      title = "Buy Signal", 
      display = display.pane, 
      size=size.tiny,
      location=location.belowbar
      )
plotshape(
      series = is_sell, 
      style = shape.triangledown, 
      color = color.red, 
      title = "Sell Signal", 
      display = display.pane, 
      size=size.tiny)