Captain Backtest Model [TFO]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © tradeforopp

//@version=5
strategy("Captain Backtest Model [TFO]", "Captain Backtest Model [TFO]", true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500)

var g_SET = "Time"
prev_range = input.session("0600-1000", "Price Range", tooltip = "The range from which the high and low will be tracked for bias", group = g_SET)
take_by = input.session("1000-1115", "Window to Determine Bias", tooltip = "Price must take the high or low of the price range within this time window. Otherwise, will not determine a bias and will not trade for that session", group = g_SET)
trade_window = input.session("1000-1600", "Trade Window", tooltip = "Time window to take trades", group = g_SET)

var g_STRAT = "Strategy"
retrace_1 = input.bool(true, "Wait for Retracement - Opposite Close Candles", tooltip = "Wait for down close candles after long bias has been determined, or up close candles after short bias has been determined", group = g_STRAT)
retrace_2 = input.bool(true, "Wait for Retracement - Took Previous High/Low", tooltip = "Wait for price to take a previous candle low after long bias has been determined, or for price to take a previous candle high after short bias has been determined", group = g_STRAT)
stop_orders = input.bool(false, "Use Stop Orders", tooltip = "When enabled, will only enter long setups once price has taken the previous candle's high (and vice versa), as opposed to entering immediately once all criteria are met", group = g_STRAT)
fixed_rr = input.bool(true, "Use Fixed R:R", tooltip = "When enabled, will use the risk and reward inputs as stop loss and take profit orders. Otherwise, will hold until the trade window closes (EOD)", group = g_STRAT)
risk = input.float(25, "Risk (Points)", tooltip = "Stop loss, specified in points", group = g_STRAT)
reward = input.float(75, "Reward (Points)", tooltip = "Profit target, specified in points", group = g_STRAT)

var g_VIS = "Visuals"
range_color = input.color(color.new(color.blue, 70), "Price Range Color", group = g_VIS)
show_bias = input.bool(true, "Show Bias", inline = "BIAS", tooltip = "Display bias for the day", group = g_VIS)
long_color = input.color(color.teal, "", inline = "BIAS", group = g_VIS)
short_color = input.color(color.red, "", inline = "BIAS", group = g_VIS)
draw_rr = input.bool(true, "Draw Fixed R:R", tooltip = "Draw risk to reward boxes relative to entry", group = g_VIS)
draw_cutoff = input.bool(true, "Draw Time Cutoff", tooltip = "Draw the cutoff time for when price needs to take the range high or low", group = g_VIS)

t_pre = not na(time("", prev_range, "America/New_York"))
t_take = not na(time("", take_by, "America/New_York"))
t_trade = not na(time("", trade_window, "America/New_York"))

var float pre_h = na
var float pre_l = na

var box pre_box = na
var line pre_h_line = na
var line pre_l_line = na

var bool bias = na

var bool long = false
var bool short = false
var bool opp_close = false
var bool took_hl = false

var float buy_stop = na
var float sell_stop = na

var float entry = na

var line entry_line = na

var box[] risk_box = array.new_box()
var box[] reward_box = array.new_box()

var float[] risk_price = array.new_float()
var float[] reward_price = array.new_float()

rr_boxes(entry, long) =>
    if long
        reward_box.unshift(box.new(time[1], entry + reward, time, entry, xloc = xloc.bar_time, bgcolor = color.new(long_color, 50), border_color = color.new(long_color, 50)))
        risk_box.unshift(box.new(time[1], entry, time, entry - risk, xloc = xloc.bar_time, bgcolor = color.new(short_color, 50), border_color = color.new(short_color, 50)))
    else
        reward_box.unshift(box.new(time[1], entry, time, entry - reward, xloc = xloc.bar_time, bgcolor = color.new(long_color, 50), border_color = color.new(long_color, 50)))
        risk_box.unshift(box.new(time[1], entry + risk, time, entry, xloc = xloc.bar_time, bgcolor = color.new(short_color, 50), border_color = color.new(short_color, 50)))

if t_pre
    pre_h := na(pre_h) ? high : math.max(pre_h, high)
    pre_l := na(pre_l) ? low :  math.min(pre_l, low)

    if not t_pre[1]
        pre_box := box.new(time, high, time, low, xloc = xloc.bar_time, bgcolor = range_color, border_color = na, text = str.tostring(prev_range), text_color = range_color)
        pre_h_line := line.new(time, high, time, high, xloc = xloc.bar_time, color = color.new(range_color, 0))
        pre_l_line := line.new(time, low,  time, low,  xloc = xloc.bar_time, color = color.new(range_color, 0))
    else
        pre_box.set_top(pre_h)
        pre_box.set_rightbottom(time, pre_l)

        pre_h_line.set_y1(pre_h)
        pre_h_line.set_xy2(time, pre_h)
        
        pre_l_line.set_y1(pre_l)
        pre_l_line.set_xy2(time, pre_l)

if not t_trade and t_trade[1]
    pre_h := na
    pre_l := na
    bias := na
    entry := na
    buy_stop := na
    sell_stop := na
    entry_line := na
    long := false
    short := false
    opp_close := false
    took_hl := false
    reward_box.clear()
    risk_box.clear()

if t_take
    if high > pre_h and na(bias)
        bias := true
        pre_h_line.set_x2(time)
        if show_bias
            label.new(bar_index, high, "Bias Long", textcolor = color.white, color = long_color)
    else if low < pre_l and na(bias)
        bias := false
        pre_l_line.set_x2(time)
        if show_bias
            label.new(bar_index, low, "Bias Short", textcolor = color.white, color = short_color, style = label.style_label_up)

if not t_take and t_take[1]
    if draw_cutoff
        line.new(bar_index, high, bar_index, low, extend = extend.both, color = chart.fg_color, style = line.style_dotted)
    if na(bias)
        label.new(bar_index, high, "Range Not Breached\nby 11:15", textcolor = chart.bg_color, color = chart.fg_color)

plotshape(show_bias, color = (bias == true) ? long_color : (bias == false) ? short_color : na, location = location.bottom, size = size.small, style = shape.square)
plotshape(long[1] and not long[2], color = long_color, location = location.belowbar, size = size.small, style = shape.triangleup)
plotshape(short[1] and not short[2], color = short_color, location = location.abovebar, size = size.small, style = shape.triangledown)

if t_trade
    if not na(entry_line) and na(entry)
        entry_line.set_x2(time)

    if not retrace_1
        opp_close := true
    else
        if bias == true and close < open
            opp_close := true
        if bias == false and close > open
            opp_close := true

    if not retrace_2
        took_hl := true
    else            
        if bias == true and low < low[1]
            took_hl := true
        if bias == false and high > high[1]
            took_hl := true

    if bias[1] == true and close > high[1] and opp_close and took_hl and not long
        long := true
        buy_stop := high
        if stop_orders
            entry_line := line.new(time, buy_stop, time + timeframe.in_seconds() * 1000, buy_stop, xloc = xloc.bar_time, color = long_color)
    if bias[1] == false and close < low[1] and opp_close and took_hl and not short
        short := true
        sell_stop := low
        if stop_orders
            entry_line := line.new(time, sell_stop, time + timeframe.in_seconds() * 1000, sell_stop, xloc = xloc.bar_time, color = short_color)

    if long == true and na(entry)
        strategy.entry("Long", strategy.long, stop = stop_orders ? buy_stop : na)
    else if short == true and na(entry)
        strategy.entry("Short", strategy.short, stop = stop_orders ? sell_stop : na)
    
    if fixed_rr
        if strategy.position_size[1] == 0 and strategy.position_size != 0
            if strategy.position_size > 0
                entry := stop_orders ? buy_stop : open
                risk_price.unshift(entry - risk)
                reward_price.unshift(entry + reward)
            if strategy.position_size < 0
                entry := stop_orders ? sell_stop : open
                risk_price.unshift(entry + risk)
                reward_price.unshift(entry - reward)

        if strategy.position_size > 0
            strategy.exit("Long", comment = "Exit Long", limit = reward_price.get(0), stop = risk_price.get(0))

            if draw_rr 
                if fixed_rr and na(entry[2]) and risk_box.size() == 0
                    rr_boxes(entry, true)
                if risk_box.size() > 0
                    risk_box.get(0).set_right(time)
                    reward_box.get(0).set_right(time)

        if strategy.position_size < 0
            strategy.exit("Short", comment = "Exit Short", limit = reward_price.get(0), stop = risk_price.get(0))

            if draw_rr
                if fixed_rr and na(entry[2]) and risk_box.size() == 0
                    rr_boxes(entry, false)
                if risk_box.size() > 0
                    risk_box.get(0).set_right(time)
                    reward_box.get(0).set_right(time)

if not t_trade and t_trade[1]
    strategy.close_all("EOD")