[DisDev] D-I-Y Gridbot
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © xxattaxx-DisDev
//@version=5
Indicator = '[DisDev] D-I-Y Gridbot'
indicator(Indicator,       overlay=true)

//Create variables for tooltips for easier code readability
ttTrigger   = 'Candle location to trigger the signal. "Wick" will use either high or low, depending on the signal direction. "Close" will use the close price. "MA" will use the MA/moving average.' 
ttConfirm   = 'Market direction to confirm thr candle trigger. "Reverse" will confirm the signal when the price crosses back over the trigger. "Breakout" will confirm when the price breaks out of the trigger.' 
ttMAtype    = 'Exponential Moving Average (EMA), Hull Moving Average (HMA), Simple Moving Average (SMA), Triple Exponential Moving Average (TEMA), Volume Weighted Moving Average (VWMA), Volume Weighted Average Price (VWAP)'
ttFill      = 'Fill the area between the upper and lower gridlines. "MA" will fill between the MA and the gridlines. "Signal" will fill between the signal and the gridlines. "Source" will fill between the buy/sell source and the gridlines.'
ttZones     = 'Number of Support/Resistance zones. 1: Only Top Grid is Support/Only Bottom Grid is Resistance. 2: Top two grids are Resistance/Bottom two grids are Support. 3:Top three grids are Resistance/Bottom three grids are Support'    
ttMA        = 'Filter prices based on MA. No Buys above the MA. No Sells below the MA.'
ttRepeat    = 'If checked, signals will repeat on the same gridline as previous signals if next gridline is crossed before the signal is confirmed. If unchecked, signals will not repeat on the same gridline.'

//Inputs
Trigger     = input.string   ('Wick',      'Trigger          ',                 group='Signals',   inline='trigger',  
             options         = ['Wick',    'Close',     'MA'],                                                      tooltip=ttTrigger)
Confirm     = input.string   ('Breakout',  'Confirmation',                      group='Signals',   inline='confirm',
             options         = ['Reverse', 'Breakout'],                                                             tooltip=ttConfirm)
zones       = input.int      (3,           'S/R Zones     ', minval=1, maxval=3,group='Signals',   inline='zones',  tooltip=ttZones)
smLen       = input.int      (50,          '',         minval=1,                group='MA',        inline='MA',     tooltip='MA Length')
smType      = input.string   ('ema', '', 
             options         = ['ema', 'hull', 'sma', 'tema', 'vwma', 'vwap'],  group='MA',        inline='MA',     tooltip=ttMAtype)
TF          = input.timeframe('',           '',                                 group='MA',        inline='MA1',    tooltip='MA Timeframe')
smUse       = input.bool     (true, 'MA Filter',                                group='MA',        inline='MA2',    tooltip=ttMA)
repeat      = input.bool     (false, 'Allow Repeat Signals',                    group='MA',        inline='MA2',    tooltip=ttRepeat)
colMA       = input.color    (color.new(color.orange, 70), 'MA      ',        group='Colors',    inline='grids')
colResist   = input.color    (color.new(color.red,    10), '   Grids ',       group='Colors',    inline='grids')
colMid      = input.color    (color.new(color.yellow, 30), '',                group='Colors',    inline='grids')
colSupp     = input.color    (color.new(color.green,  10), '',                group='Colors',    inline='grids')
colUpperTop = input.color    (color.new(#FF0000,      75), 'Fill      ',      group='Colors',    inline='fill')
colLowerTop = input.color    (color.new(#228B22,      75), '',                group='Colors',    inline='fill')
colUpperBot = color.new      (colUpperTop,              98)
colLowerBot = color.new      (colLowerTop,              98)
filltype    = input.string   ('MA', '',                                         group='Colors',    inline='fill', 
             options         =['none', 'MA', 'Signal', 'Source'],                                                   tooltip=ttFill)
Price0_     = input.price    (0.0, '(1 of 6)',                                  group='Girdlines', inline='upper',  confirm=true)
Price1_     = input.price    (0.0, '(2 of 6)',                                  group='Girdlines', inline='upper',  confirm=true)
Price2_     = input.price    (0.0, '(3 of 6)',                                  group='Girdlines', inline='middle', confirm=true)
Price3_     = input.price    (0.0, '(4 of 6)',                                  group='Girdlines', inline='middle', confirm=true)
Price4_     = input.price    (0.0, '(5 of 6)',                                  group='Girdlines', inline='lower',  confirm=true)
Price5_     = input.price    (0.0, '(6 of 6)',                                  group='Girdlines', inline='lower',  confirm=true)

//Initialize Variables
var LastSignal=0,   var LastSignal_Index=0, smSrc = close

// Moving Average / VWAP (note: smLen will have no effect on VWAP)
xMA         = switch smType
    'ema'   => ta.ema(smSrc, smLen)
    'hull'  => ta.hma(smSrc, smLen)
    'sma'   => ta.sma(smSrc, smLen)
    'tema'  => ta.ema(ta.ema(ta.ema(smSrc, smLen), smLen), smLen)
    'vwma'  => ta.vwma(smSrc, smLen)
    'vwap'  => ta.vwap(smSrc)
    =>      ta.sma(smSrc, smLen)
MA          = request.security(syminfo.tickerid, TF, barstate.isrealtime? xMA: xMA[1])

// BuySource and SellSource are dependent on candle trigger and confirmation. Wick: Use highs or lows for sell signals. Close: Use close for sell signals. MA: Use MA for sell signals.
BuySource   = Trigger=='Wick'?  low:  Trigger == 'MA'? MA : close
SellSource  = Trigger=='Wick'?  high: Trigger == 'MA'? MA : close

//Create an array from all the input levels so they can be sorted
GridArray   = array.from(Price0_,Price1_,Price2_,Price3_,Price4_,Price5_)
array.sort(GridArray)
LowerLimit  = array.get(GridArray,0), UpperLimit = array.get(GridArray,5)

//Draw Gridlines from first bar onward. No need to redraw every bar.
LineArray  = array.new_line(5)
if barstate.isfirst
    for i = 0 to 5
        lColor = i >= 6 - zones ? colResist : i < zones? colSupp : colMid
        Y = array.get(GridArray,i)
        lWidth = i == 0 or i == 5? 2 : 1
        array.push(LineArray, line.new(time, Y, time+1, Y, xloc=xloc.bar_time, width=lWidth, color=lColor,  extend=extend.both))

// Get the nearest index to buy and sell. If none are found, both indexes will be -1.
Get_BuySell_Index() =>
    BuyIndex = -1, SellIndex = -1
    for [index,array_value] in GridArray
        if Confirm == 'Breakout' 
            BuyIndex        :=BuySource     <= array_value   and   BuySource[1]  > array_value?   index :  BuyIndex
            SellIndex       :=SellSource    >= array_value   and   SellSource[1] < array_value?   index :  SellIndex
        else //if Confirm == 'Reverse'
            BuyIndex        :=BuySource     >= array_value   and   BuySource[1]  < array_value?   index :  BuyIndex 
            SellIndex       :=SellSource    <= array_value   and   SellSource[1] > array_value?   index :  SellIndex 
    [BuyIndex,SellIndex]  
[BuyLine_Index,SellLine_Index]=Get_BuySell_Index()

// Set Buy or Sell if any crosses were found. If none were found, both indexes would be -1 (the initial value set in the function)
Buy         = BuyLine_Index  >= 0 
Sell        = SellLine_Index >= 0 

// No repeat signals at same grid level
Buy         := BuyLine_Index   >= LastSignal_Index? false : Buy
Sell        := SellLine_Index  <= LastSignal_Index? false : Sell

// MA Filters. No buys if price is above MA, no sells if price is below MA
Buy         := smUse and BuySource  > MA? false : Buy
Sell        := smUse and SellSource < MA? false : Sell

// Zones. Calculate the max buy and min sell for each zone. If price is above max buy, no buys. If price is below min sell, no sells.
MaxBuy      = array.get(GridArray,5-zones),     MinSell = array.get(GridArray,zones) 
Buy         := BuySource    > MaxBuy?  false :  Buy
Sell        := SellSource   < MinSell? false :  Sell

// Track the last signal. We can use this to prevent repeat signals at the same grid level, or will reset if next grid level is reached (repeat)
LastSignal          := Buy?1:Sell?-1:LastSignal
LastSignal_Index    := Buy?                             BuyLine_Index :  Sell?                           SellLine_Index : LastSignal_Index
LastSignal_Index    := repeat and BuyLine_Index >= 0?   BuyLine_Index :  repeat and SellLine_Index >= 0? SellLine_Index : LastSignal_Index

// Plots. The lines must be plotted to be able to use them in the gradient fill. 
sigPrice    = array.get(GridArray,LastSignal_Index)
sigLine     = plot  (sigPrice,      "Signal Price",     color=na,           display= display.none)
topLine     = plot  (UpperLimit,    "Upper Limit",      color=na,           display= display.none)
botLine     = plot  (LowerLimit,    "Lower Limit",      color=na,           display= display.none)
bsLine      = plot  (BuySource,     "Buy Source",       color=na,           display= display.none)
ssLine      = plot  (SellSource,    "Sell Source",      color=na,           display= display.none)
maLine      = plot  (MA,            "MA",               color = colMA,      linewidth = 3)

// Gradient fill types. MA: MA line, Source: Buy/Sell Source, Signal: Last Signal Price
fill        (maLine,    botLine,    MA,         LowerLimit, filltype=='MA'?     colLowerBot:na, filltype=='MA'?     colLowerTop: na)
fill        (maLine,    topLine,    MA,         UpperLimit, filltype=='MA'?     colUpperBot:na, filltype=='MA'?     colUpperTop: na)
fill        (bsLine,    botLine,    BuySource,  LowerLimit, filltype=='Source'? colLowerBot:na, filltype=='Source'? colLowerTop: na)
fill        (ssLine,    topLine,    SellSource, UpperLimit, filltype=='Source'? colUpperBot:na, filltype=='Source'? colUpperTop: na)
fill        (sigLine,   botLine,    sigPrice,   LowerLimit, filltype=='Signal'? colLowerBot:na, filltype=='Signal'? colLowerTop: na)
fill        (sigLine,   topLine,    sigPrice,   UpperLimit, filltype=='Signal'? colUpperBot:na, filltype=='Signal'? colUpperTop: na)

// Plot Signals
plotchar    (Buy,  'Buy',  color=color.new(#32CD32, 0), size=size.tiny, location=location.belowbar, char='▲')
plotchar    (Sell, 'Sell', color=color.new(#EE4B2B, 0), size=size.tiny, location=location.abovebar, char='▼')

// Alerts
alertcondition  (condition=Buy,  title='buy',  message='buy')
alertcondition  (condition=Sell, title='sell', message='sell')

// Version Info
version = input.string('v1.00',         options=['v1.00'],       inline='1', group=Indicator)
date    = input.string('06/19/2023',    options=['06/19/2023'],  inline='1', group=Indicator)