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

//@version=5
strategy("BBPullback", overlay=true, margin_long=100, margin_short=100,process_orders_on_close=true)

TAKE_PROFIT_HILO = 0
TAKE_PROFIT_CLOSE = 1
TAKE_PROFIT_BB = 2

OPTION0 = "high crosses MA"
OPTION1 = "closes beyond MA"
OPTION2 = "high crosses opposite band"
tpoption=input.string(OPTION0,title="Take Proft trigger",options=[OPTION0,OPTION1,OPTION2])

i_from = input.time(defval = timestamp("23 Feb 2023 00:00 +0000"), title = "From")
i_thru = input.time(defval = timestamp("09 Apr 2023 00:00 +0000"), title = "Thru")

var take_profit_option=TAKE_PROFIT_HILO
if tpoption == OPTION0
    take_profit_option := TAKE_PROFIT_HILO
else if tpoption == OPTION1
    take_profit_option := TAKE_PROFIT_CLOSE
else
    take_profit_option := TAKE_PROFIT_BB
//
// COMPUTE BOLLINGER BANDS INTERNALLY    
//
source = close
length = 20
mult = 2.0
basis = ta.sma(source, length)
dev = mult * ta.stdev(source, length)
upperBBValue = basis + dev
lowerBBValue = basis - dev

NO_SETUP = 0
BB_BREAK_DOWN = 1
RETRACE_BAR_UP = 2
PENDING_ENTRY_LONG = 3
ENTRY_LONG = 4

BB_BREAK_UP = 5
RETRACE_BAR_DOWN = 6
PENDING_ENTRY_SHORT = 7
ENTRY_SHORT = 8

ID_STATE = 0

ID_BUY_ENTRY = 0
ID_SELL_ENTRY = 1
ID_BUY_STOPLOSS = 2
ID_SELL_STOPLOSS = 3
ID_SWING_HIGH = 4
ID_SWING_LOW = 5

LARGE_NUMBER = 99999999.9

var state = NO_SETUP
var buy_entry_price = 0.0
var sell_entry_price = 0.0
var buy_stoploss_price = 0.0
var sell_stoploss_price = 0.0
var swing_low = LARGE_NUMBER
var swing_high = 0.0

var themax = 0.0
var themin = 99999999.0

//global_float = array.new_bool(1)
//array.set(global_bool,ID_TRENDMODEON,trendMode)

global_int = array.new_int(1)
array.set(global_int,ID_STATE,state)

global_float = array.new_float(6)
array.set(global_float,ID_BUY_ENTRY,buy_entry_price)
array.set(global_float,ID_SELL_ENTRY,sell_entry_price)
array.set(global_float,ID_BUY_STOPLOSS,buy_stoploss_price)
array.set(global_float,ID_SELL_STOPLOSS,sell_stoploss_price)
array.set(global_float,ID_SWING_HIGH,swing_high)
array.set(global_float,ID_SWING_LOW,swing_low)
//
// Main code starts here, executes every bar
//
dateok() => 
    time >= i_from and time <= i_thru 

minval(x, y) =>
    (x < y ? x : y)

maxval(x, y) =>
    (x > y ? x : y)

IsFlat() =>
    strategy.position_size == 0

HasPosition() =>
    strategy.position_size != 0

PullbackCandleLong() =>
    close >= open or low > low[1]

PullbackCandleShort() =>
    close <= open or high < high[1]

isUpCandle() =>
    close >= open

isDownCandle() =>
    open > close

StateInTrade() =>
    array.get(global_int,ID_STATE) == ENTRY_LONG or array.get(global_int,ID_STATE) == ENTRY_SHORT

StatePendingLong() =>
    array.get(global_int,ID_STATE) == PENDING_ENTRY_LONG

StatePendingShort() =>
    array.get(global_int,ID_STATE) == PENDING_ENTRY_SHORT

StateInLongTrade() =>
    array.get(global_int,ID_STATE) == ENTRY_LONG

StateInShortTrade() =>
    array.get(global_int,ID_STATE) == ENTRY_SHORT

LongTakeProfitHigh() =>
    high > basis

ShortTakeProfitLow() =>
    low < basis

LongTakeProfitClose() =>
    close > basis

ShortTakeProfitClose() =>
    close < basis

LongTakeProfitBB() =>
    high > upperBBValue

ShortTakeProfitBB() =>
    low < lowerBBValue


LongTakeProfitHit() =>
    if take_profit_option == TAKE_PROFIT_HILO
        LongTakeProfitHigh()
    else if take_profit_option == TAKE_PROFIT_CLOSE
        LongTakeProfitClose()
    else
        LongTakeProfitBB()

ShortTakeProfitHit() =>
    if take_profit_option == TAKE_PROFIT_HILO
        ShortTakeProfitLow()
    else if take_profit_option == TAKE_PROFIT_CLOSE
        ShortTakeProfitClose()
    else
        ShortTakeProfitBB()

LongTriggerCondition() =>
    if (dateok() and isUpCandle() and minval(open,close) < lowerBBValue and high < basis and high > lowerBBValue) 
        true
    else
        false

ShortTriggerCondition() =>
    if (dateok() and isDownCandle() and maxval(open,close) > upperBBValue and low > basis and low < upperBBValue) 
        true
    else
        false

if (HasPosition() and StatePendingLong())
    array.set(global_int,ID_STATE,ENTRY_LONG)
else if (HasPosition() and StatePendingShort())
    array.set(global_int,ID_STATE,ENTRY_SHORT)
else if (IsFlat() and StateInTrade()) 
    array.set(global_int,ID_STATE,NO_SETUP)

//
// If an order didn't trigger this bar then cancel it
//
var cancel_long=false
var cancel_short=false
if IsFlat()
    if (array.get(global_int,ID_STATE) == PENDING_ENTRY_LONG)
        cancel_long := true
        strategy.cancel("Enter Long")
        array.set(global_int,ID_STATE,NO_SETUP)
    else if (array.get(global_int,ID_STATE) == PENDING_ENTRY_SHORT)
        cancel_short := true
        strategy.cancel("Enter Short")
        array.set(global_int,ID_STATE,NO_SETUP)

//
// Trading logic long
//
if IsFlat()
    if (array.get(global_int,ID_STATE) == NO_SETUP) 
        if (LongTriggerCondition()) 
            //array.set(global_int,ID_STATE,BB_BREAK_DOWN)
            array.set(global_float,ID_BUY_ENTRY,high)
            if (cancel_long) 
                array.set(global_float,ID_SWING_LOW,minval(low,array.get(global_float,ID_SWING_LOW)))
            else
                array.set(global_float,ID_SWING_LOW,low)
            
            array.set(global_float,ID_SWING_HIGH,0.0)
            //array.set(global_float,ID_BUY_STOPLOSS,array.get(global_float,ID_SWING_LOW))
            array.set(global_float,ID_BUY_STOPLOSS,low)
            strategy.entry("Enter Long", strategy.long,stop=array.get(global_float,ID_BUY_ENTRY),limit=array.get(global_float,ID_BUY_ENTRY))
            array.set(global_int,ID_STATE,PENDING_ENTRY_LONG)
            strategy.exit("Stop Out",from_entry="Enter Long",stop=array.get(global_float,ID_BUY_STOPLOSS))
    else if (array.get(global_int,ID_STATE) == BB_BREAK_DOWN) 
        if (PullbackCandleLong())
            array.set(global_float,ID_BUY_ENTRY,high)
            array.set(global_float,ID_BUY_STOPLOSS,low)
            strategy.entry("Enter Long", strategy.long,stop=array.get(global_float,ID_BUY_ENTRY),limit=array.get(global_float,ID_BUY_ENTRY))
            array.set(global_int,ID_STATE,PENDING_ENTRY_LONG)
    else if (array.get(global_int,ID_STATE) == PENDING_ENTRY_LONG) 
        strategy.cancel("Enter Long")
        array.set(global_int,ID_STATE,NO_SETUP)

//if (array.get(global_int,ID_STATE) == PENDING_ENTRY_LONG) 
//    strategy.exit("Stop Out",from_entry="Enter Long",stop=array.get(global_float,ID_BUY_STOPLOSS))

//
// Trading logic short
//
if IsFlat()
    if (array.get(global_int,ID_STATE) == NO_SETUP) 
        if (ShortTriggerCondition())
            //array.set(global_int,ID_STATE,BB_BREAK_UP)
            array.set(global_float,ID_SELL_ENTRY,low)
            if (cancel_short) 
                array.set(global_float,ID_SWING_HIGH,maxval(high,array.get(global_float,ID_SWING_HIGH)))
            else
                array.set(global_float,ID_SWING_HIGH,high)
            array.set(global_float,ID_SWING_LOW,LARGE_NUMBER)
            //array.set(global_float,ID_SELL_STOPLOSS,array.get(global_float,ID_SWING_HIGH))
            array.set(global_float,ID_SELL_STOPLOSS,high)
            strategy.entry("Enter Short", strategy.short,stop=array.get(global_float,ID_SELL_ENTRY))
            array.set(global_int,ID_STATE,PENDING_ENTRY_SHORT)
            strategy.exit("Stop Out",from_entry="Enter Short",stop=array.get(global_float,ID_SELL_STOPLOSS))
    else if (array.get(global_int,ID_STATE) == BB_BREAK_UP)
        if (PullbackCandleShort())
            //array.set(global_int,ID_STATE,RETRACE_BAR_DOWN)
            array.set(global_float,ID_SELL_ENTRY,low)
            array.set(global_float,ID_SELL_STOPLOSS,high)
            strategy.entry("Enter Short", strategy.short,stop=array.get(global_float,ID_SELL_ENTRY))
            array.set(global_int,ID_STATE,PENDING_ENTRY_SHORT)
    else if (array.get(global_int,ID_STATE) == PENDING_ENTRY_SHORT) 
        strategy.cancel("Enter Short")
        array.set(global_int,ID_STATE,NO_SETUP)

//if (array.get(global_int,ID_STATE) == PENDING_ENTRY_SHORT) 
//    strategy.exit("Stop Out",from_entry="Enter Short",stop=array.get(global_float,ID_SELL_STOPLOSS))

if StateInLongTrade()
    if (LongTakeProfitHit()) 
        strategy.close("Enter Long",comment="Take Profit")
        array.set(global_int,ID_STATE,NO_SETUP)
else if StateInShortTrade()
    if (ShortTakeProfitHit())
        strategy.close("Enter Short",comment="Take Profit")
        array.set(global_int,ID_STATE,NO_SETUP)

state := array.get(global_int,ID_STATE)
buy_entry_price := array.get(global_float,ID_BUY_ENTRY)
sell_entry_price := array.get(global_float,ID_SELL_ENTRY)
buy_stoploss_price := array.get(global_float,ID_BUY_STOPLOSS)
sell_stoploss_price := array.get(global_float,ID_SELL_STOPLOSS)

themin := minval(open,close)
themax := maxval(open,close)

plotchar(state, "State", "", location = location.top)
plotchar(themin, "TheMin", "", location = location.top)
plotchar(themax, "TheMax", "", location = location.top)
plotchar(upperBBValue, "UpperBB", "", location = location.top)
plotchar(lowerBBValue, "LowerBB", "", location = location.top)
plotchar(buy_stoploss_price, "buy stoploss", "", location = location.top)
plotchar(sell_stoploss_price, "sell stoploss", "", location = location.top)


swing_low := array.get(global_float,ID_SWING_LOW)
plotchar(swing_low, "SwingLow", "", location = location.top)



