Nifty & BN 2 Candle Theory Back Testing and Alert Notification 
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
//@version=5

//---------------------------//2 Candle Theorey + Golden Crossover\\------------------------------\\

strategy('2 Candle Theorey V2', process_orders_on_close=true, overlay = true)

//---------------------------------//Options\\---------------------------------\\
showsession = input.bool(defval=false, title='', inline='Session', group='Timefilter')
session2 = input.session(defval='1430-1000', title='Session Timings(to avoid trades)', inline='Session', group='Timefilter')

profitMultiplier = input.float(defval=2.0, minval=0.1,step=0.1, maxval=10.0, title='Risk to Reward', group='Main Settings', tooltip='Modify Risk to Reward for Take Profit')
volumecutoff = input.float(defval=50000, minval= 10000 , step= 1000, maxval=1000000, title='Volume Cutoff', tooltip='Modify Volume Cutoff ex: 2 Candles with Volume above 50k for Entry Condition', group='Main Settings')
showVolumeColor = input.bool(defval=true,title='', group='Main Settings', inline='Color')
color volumeColorInput = input.color(color.new(color.yellow,20), " Show High Volume Candles", group='Main Settings', tooltip='Change Canlde Color for Candles above Volume Cutoff', inline='Color')

var g_filter = 'Filter Settings'
emaFilter = input.int(title='EMA Filter', defval=0, group=g_filter, tooltip='EMA length to filter trades - set to zero to disable')
i_startTime = input.time(title='Start Date Filter', defval=timestamp('01 Jan 2000 13:30 +0000'), group=g_filter, tooltip='Date & time to begin trading from')
i_endTime = input.time(title='End Date Filter', defval=timestamp('1 Jan 2099 19:30 +0000'), group=g_filter, tooltip='Date & time to stop trading')

// Backtester Settings
var g_tester = 'Backtester Settings'
startBalance = input.float(title='Starting Balance', defval=10000.0, group=g_tester, tooltip='Your starting balance for the custom inbuilt tester system')
riskPerTrade = input.float(title='Risk Per Trade', defval=1.0, group=g_tester, tooltip='Your desired % risk per trade (as a whole number)')
drawTester = input.bool(title='Draw Backtester', defval=true, group=g_tester, tooltip='Turn on/off inbuilt backtester display')

//-----------------------------// Custom Functions \\---------------------------------\\
    
// Custom function to truncate (cut) excess decimal places
truncate(_number, _decimalPlaces) =>
    _factor = math.pow(10, _decimalPlaces)
    int(_number * _factor) / _factor

//---------------------------------//EMA Filter\\---------------------------------\\

ema = ta.ema(close, emaFilter == 0 ? 1 : emaFilter)

//---------------------------------//RSI Code\\---------------------------------\\

marsi(sourcersi, lengthrsi, typersi) =>
    switch typersi
        "SMA" => ta.sma(sourcersi, lengthrsi)
        "Bollinger Bands" => ta.sma(sourcersi, lengthrsi)
        "EMA" => ta.ema(sourcersi, lengthrsi)
        "SMMA (RMA)" => ta.rma(sourcersi, lengthrsi)
        "WMA" => ta.wma(sourcersi, lengthrsi)
        "VWMA" => ta.vwma(sourcersi, lengthrsi)

rsiLengthInput = input.int(14, minval=1, title="RSI Length", group="RSI Settings")
rsiSourceInput = input.source(close, "Source", group="RSI Settings")
maTypeInput = input.string("SMA", title="MA Type", options=["SMA", "Bollinger Bands", "EMA", "SMMA (RMA)", "WMA", "VWMA"], group="RSI Settings")
maLengthInput = input.int(14, title="MA Length", group="RSI Settings")
bbMultInput = input.float(2.0, minval=0.001, maxval=50, title="BB StdDev", group="RSI Settings")

up0 = ta.rma(math.max(ta.change(rsiSourceInput), 0), rsiLengthInput)
down0 = ta.rma(-math.min(ta.change(rsiSourceInput), 0), rsiLengthInput)
rsi = down0 == 0 ? 100 : up0 == 0 ? 0 : 100 - (100 / (1 + up0 / down0))
rsiMA = marsi(rsi, maLengthInput, maTypeInput)
isBB = maTypeInput == "Bollinger Bands"

//---------------------------------//PSAR Code\\---------------------------------\\

start0 = input(0.02)
increment0 = input(0.02)
maximum0 = input(0.2, "Max Value")
out = ta.sar(start0, increment0, maximum0)
plot(out, "ParabolicSAR", style=plot.style_cross, color=#2962FF)

//---------------------------------//Supertrend Code\\---------------------------------\\

length = input(title='ATR Period', defval=10, group="Supertrend Settings")
mult = input.float(title='ATR Multiplier', step=0.1, defval=2, group="Supertrend Settings")
src = input(title='Source', defval=close, group="Supertrend Settings")
wicks = input(title='Take Wicks into Account ?', defval=true, group="Supertrend Settings")
showLabels = input(title='Show Buy/Sell Labels ?', defval=true, group="Supertrend Settings")
highlightState = input(title='Highlight State ?', defval=true, group="Supertrend Settings")

atr = mult * ta.atr(length)

highPrice = wicks ? high : close
lowPrice = wicks ? low : close
doji4price = open == close and open == low and open == high

longStop = src - atr
longStopPrev = nz(longStop[1], longStop)

if longStop > 0
    if doji4price
        longStop := longStopPrev
        longStop
    else
        longStop := lowPrice[1] > longStopPrev ? math.max(longStop, longStopPrev) : longStop
        longStop
else
    longStop := longStopPrev
    longStop

shortStop = src + atr
shortStopPrev = nz(shortStop[1], shortStop)

if shortStop > 0
    if doji4price
        shortStop := shortStopPrev
        shortStop
    else
        shortStop := highPrice[1] < shortStopPrev ? math.min(shortStop, shortStopPrev) : shortStop
        shortStop
else
    shortStop := shortStopPrev
    shortStop

var int dir = 1
dir := dir == -1 and highPrice > shortStopPrev ? 1 : dir == 1 and lowPrice < longStopPrev ? -1 : dir

var color longColor = color.green
var color shortColor = color.red

longStopPlot = plot(dir == 1 ? longStop : na, title='Long Stop', style=plot.style_linebr, linewidth=2, color=color.new(longColor, 0))
buySignal = dir == 1 and dir[1] == -1
plotshape(buySignal ? longStop : na, title='Long Stop Start', location=location.absolute, style=shape.circle, size=size.tiny, color=color.new(longColor, 0))
//plotshape(buySignal and showLabels ? longStop : na, title='Buy Label', text='Buy', location=location.absolute, style=shape.labelup, size=size.tiny, color=color.new(longColor, 0), textcolor=color.new(color.white, 0))

shortStopPlot = plot(dir == 1 ? na : shortStop, title='Short Stop', style=plot.style_linebr, linewidth=2, color=color.new(shortColor, 0))
sellSignal = dir == -1 and dir[1] == 1
//plotshape(sellSignal ? shortStop : na, title='Short Stop Start', location=location.absolute, style=shape.circle, size=size.tiny, color=color.new(shortColor, 0))
//plotshape(sellSignal and showLabels ? shortStop : na, title='Sell Label', text='Sell', location=location.absolute, style=shape.labeldown, size=size.tiny, color=color.new(shortColor, 0), textcolor=color.new(color.white, 0))

midPricePlot = plot(ohlc4, title='', style=plot.style_circles, linewidth=0, display=display.none, editable=false)

longFillColor = highlightState ? dir == 1 ? longColor : na : na
shortFillColor = highlightState ? dir == -1 ? shortColor : na : na
//fill(midPricePlot, longStopPlot, title='Long State Filling', color=longFillColor, transp=90)
//fill(midPricePlot, shortStopPlot, title='Short State Filling', color=shortFillColor, transp=90)

changeCond = dir != dir[1]

//---------------------------------//VMA Code\\---------------------------------\\

lenvma = input.int(20, "Length", minval=1, group="VMA Settings")
srcvma = input(close, "Source", group="VMA Settings")
mavma = ta.vwma(srcvma, lenvma)
offsetvma = input.int(0, "Offset", minval = -500, maxval = 500, group="VMA Settings")
plot(mavma, title="VWMA", color=#2962FF, offset = offsetvma)

//---------------------------------//VWAP Code\\---------------------------------\\
// Inputs
vwap_show = input(true, title='Show VWAP', group = "VWAP Settings")
vwapOffset = input(title='VWAP offset', defval=0, group = "VWAP Settings")

start = request.security(syminfo.tickerid, 'D', time)

newSession = start > start[1] ? 1 : 0

vwapsum = 0.0
volumesum = 0.0
vwapsum := newSession[1] ? hlc3 * volume : vwapsum[1] + hlc3 * volume
volumesum := newSession[1] ? volume : volumesum[1] + volume
vwap_now = vwapsum / volumesum

plot(vwap_now, color=color.new(color.white, 0), title='VWAP')

//---------------------------------//Logic\\---------------------------------\\

// Check Sessions to avoid trades
session = time('1', session2 + string(':1234567'), str.tostring(syminfo.timezone))//'GMT+5:30')
sessioncond = showsession ? not session : true

// See if this bar's time happened within date filter
dateFilter = time >= i_startTime and time <= i_endTime

// Check EMA Filter
emaFilterLong = emaFilter == 0 or close > ema
emaFilterShort = emaFilter == 0 or close < ema

// Check RSI
rsiFilterLong = rsi > 50 and rsi <= 80
rsiFilterShort = rsi < 40 and rsi >= 20

// Check VWAP
vwapFilterLong = close > vwap_now
vwapFilterShort = close < vwap_now

// Check Supertrend
supertrendFilterLong = close > longStop
supertrendFilterShort = close < shortStop

// Check VMA
vmaFilterLong = close > mavma
vmaFilterShort = close < mavma

// Check PSAR
psarFilterLong = close > out
psarFilterShort = close < out

// Check Volume 
volumeFilter = volume[1] > volumecutoff and volume > volumecutoff //and barColor == barColor[1]
volumeLongCond = close > open and close[1] > open[1]
volumeShortCond = close < open and close[1] < open[1]
volumeLongEntry = volumeFilter and volumeLongCond
volumeShortEntry = volumeFilter and volumeShortCond 

// Determine if we have a valid setup
validLongSetup = rsiFilterLong and vwapFilterLong and supertrendFilterLong and vmaFilterLong and psarFilterLong and volumeFilter and volumeLongCond and emaFilterLong and sessioncond
validShortSetup = rsiFilterShort and vwapFilterShort and supertrendFilterShort and vmaFilterShort and psarFilterShort and volumeFilter and volumeShortCond and emaFilterShort and sessioncond

// Check if we have confirmation for our setup
validLongEntry = validLongSetup and strategy.position_size == 0 and dateFilter and barstate.isconfirmed
validShortEntry = validShortSetup and strategy.position_size == 0 and dateFilter and barstate.isconfirmed

// Highlight candles above Volume Cutoff if true
barcolor(showVolumeColor? volume>volumecutoff ? volumeColorInput : na : na, title='Big Volume Candles')
    
//---------------------------------// Golden Cross Code\\---------------------------\\

// Golden Cross Conditions
supertrendCrossBull = ta.crossover(longStop,vwap_now)
supertrendCrossBear = ta.crossunder(shortStop,vwap_now)
vmaCrossBull = ta.crossover(mavma,vwap_now)
vmaCrossBear = ta.crossunder(mavma,vwap_now)

// Golden Cross 
validGoldenCrossBullSetup = supertrendCrossBull and vmaCrossBull and volumeLongEntry and rsiFilterLong//and validLongEntry 
validGoldenCrossBearSetup = supertrendCrossBear and vmaCrossBear and volumeShortEntry and rsiFilterShort//and validShortEntry

//---------------------------------// Strategy Execution \\-------------------------\\

float sl = na
float tp = na
sl := nz(sl[1], 0.0)
tp := nz(tp[1], 0.0)

if validLongEntry and strategy.position_size <= 0
    strategy.entry('Long Entry', strategy.long, when=validLongEntry)
    sl := low[1]
    sl
    alert(message= "2 Candle Theorey Long Entry", freq=alert.freq_once_per_bar_close)
    
if validShortEntry and strategy.position_size >= 0
    strategy.entry('Short Entry', strategy.short, when=validShortEntry)
    sl := high[1]
    sl
    alert(message= "2 Candle Theorey Short Entry", freq=alert.freq_once_per_bar_close)

if validGoldenCrossBullSetup and strategy.position_size <= 0
    strategy.entry('Golden Cross Long Entry', strategy.long, when=validGoldenCrossBullSetup)
    sl := low[1]
    sl
    alert(message= "Golden Cross Long Entry", freq=alert.freq_once_per_bar_close)
    
if validGoldenCrossBearSetup and strategy.position_size >= 0
    strategy.entry('Golden Cross Short Entry', strategy.short, when=validGoldenCrossBearSetup)
    sl := high[1]
    sl
    alert(message= "Golden Cross Short Entry", freq=alert.freq_once_per_bar_close)

if sl != 0.0
    if strategy.position_size > 0
        tp := strategy.position_avg_price + (strategy.position_avg_price - sl) * profitMultiplier
        strategy.exit(id='Exit', limit=tp, stop=sl)

    if strategy.position_size < 0
        tp := strategy.position_avg_price - (sl - strategy.position_avg_price) * profitMultiplier
        strategy.exit(id='Exit', limit=tp, stop=sl)

//---------------------------------//Plots\\---------------------------------\\
// Draw trade data
plot(validLongEntry or validShortEntry ? sl : na, title='Trade Stop Price', color=color.new(color.red, 0), style=plot.style_linebr)
plot(validLongEntry or validShortEntry ? tp : na, title='Trade Target Price', color=color.new(color.green, 0), style=plot.style_linebr)

// Draw EMA if it's enabled
plot(emaFilter == 0 ? na : ema, color=emaFilterLong ? color.green : color.red, linewidth=2, title='EMA')

// Draw Golden Cross Setups
plotchar(validGoldenCrossBullSetup,"Bull Golden Cross",'🚀',location.abovebar,offset=0,size=size.small)
plotchar(validGoldenCrossBearSetup,"Bear Golden Cross",'🚀',location.belowbar,offset=0,size=size.small)

//---------------------------------//Dashbaord\\---------------------------------\\

// --- BEGIN TESTER CODE --- //
// Declare performance tracking variables
var balance = startBalance
var drawdown = 0.0
var maxDrawdown = 0.0
var maxBalance = 0.0
var totalPips = 0.0
var totalWins = 0
var totalLoss = 0

// Detect winning trades
if strategy.wintrades != strategy.wintrades[1]
    balance += riskPerTrade / 100 * balance * profitMultiplier
    //totalPips += math.abs(t_entry - t_target)
    totalWins += 1
    if balance > maxBalance
        maxBalance := balance
        maxBalance

// Detect losing trades
if strategy.losstrades != strategy.losstrades[1]
    balance -= riskPerTrade / 100 * balance
    //totalPips -= math.abs(t_entry - t_stop)
    totalLoss += 1
    // Update drawdown
    drawdown := balance / maxBalance - 1
    if drawdown < maxDrawdown
        maxDrawdown := drawdown
        maxDrawdown
        
// ProfitFactor returns the strategy's current profit factor,
ProfitFactor = strategy.grossprofit / strategy.grossloss

maxloss = math.round(maxDrawdown*(-100))

winrate = (strategy.wintrades / strategy.closedtrades) * 100

initbalance = startBalance


// Prepare stats table
var table testTable = table.new(position.bottom_right, 5, 3, border_width=1)
f_fillCell(_table, _column, _row, _title, _value, _bgcolor, _txtcolor) =>
    _cellText = _title + '\n' + _value
    table.cell(_table, _column, _row, _cellText, bgcolor=_bgcolor, text_color=_txtcolor)

// Draw stats table
var bgcolor = color.new(color.black, 0)
if drawTester
    if barstate.islastconfirmedhistory
        // Update table
        dollarReturn = balance - startBalance
        f_fillCell(testTable, 0, 0, 'Backtester by EtherMatt', str.tostring(syminfo.tickerid) , color.blue, color.white)
        f_fillCell(testTable, 0, 1, '2 Candle Theory V2', str.tostring(timeframe.period) + 'M' , color.blue, color.white)
        f_fillCell(testTable, 1, 0, 'Total Trades:', str.tostring(strategy.closedtrades), bgcolor, color.white)
        f_fillCell(testTable, 1, 1, 'Win Rate:', str.tostring(truncate(winrate, 2)) + '%', winrate > 40 ? color.green : color.red, color.white)// bgcolor, color.white)
        f_fillCell(testTable, 2, 0, 'Starting:', '$' + str.tostring(startBalance), bgcolor, color.white)
        f_fillCell(testTable, 2, 1, 'Ending:', '$' + str.tostring(truncate(balance, 2)), balance > initbalance ? color.green : color.red, color.white)
        f_fillCell(testTable, 3, 0, 'Profit Factor:', str.tostring(truncate(ProfitFactor, 2)), ProfitFactor > 0 ? color.green : color.red, color.white)
        f_fillCell(testTable, 3, 1, 'Consec. Loss:', str.tostring(maxloss), maxloss <= 5 ? color.green : color.red, color.white)//color.new(color.red,50), bgcolor, color.white)
        f_fillCell(testTable, 4, 0, 'Return:', (dollarReturn > 0 ? '+' : '') + str.tostring(truncate(dollarReturn / startBalance * 100, 2)) + '%', dollarReturn > 0 ? color.green : color.red, color.white)
        f_fillCell(testTable, 4, 1, 'Max DD:', str.tostring(truncate(maxDrawdown * 100, 2)) + '%', color.red, color.white)

