//@version=6
strategy("66 Reversion Strategy v1.0", overlay=true, initial_capital=10000, currency=currency.USD, pyramiding=0, commission_type=strategy.commission.cash_per_contract, commission_value=0.20, slippage=10, margin_long=10, margin_short=10, calc_on_every_tick=false, calc_on_order_fills=false, process_orders_on_close=false, backtest_fill_limits_assumption=1)
// Research variant; separate from the installed SIGNAL indicator.
// Cost defaults are ILLUSTRATIVE: $0.20 per unit per side, slippage 10 ticks.
// Change Properties to approximate your broker. Never read these as broker quotes.
// Orders fill via TradingView broker emulator. No webhook contract is emitted.
float stopATR = input.float(1.5, "Stop distance / ATR", minval=0.5, step=0.1)
int maxBars = input.int(32, "Maximum holding bars", minval=1, maxval=500)
int cooldown = input.int(4, "Cooldown bars after resolution", minval=0)
float riskPct = input.float(0.5, "Target initial stop risk / equity %", minval=0.1, maxval=2, step=0.1)
float exposureCap = input.float(2.0, "Maximum notional / equity", minval=0.1, maxval=10, step=0.1)
int fromTime = input.time(timestamp("01 Jan 2025 00:00 +0000"), "Test start UTC")
int untilTime = input.time(timestamp("01 Jan 2030 00:00 +0000"), "Test end UTC")
if barstate.isfirst and (not chart.is_standard or not timeframe.isminutes or timeframe.multiplier != 15 or syminfo.tickerid != "OANDA:XAUUSD")
    runtime.error("Use OANDA:XAUUSD, 15 minutes, standard candles.")
if barstate.isfirst and fromTime >= untilTime
    runtime.error("Test start must precede test end.")
float atr = ta.atr(14)
[plusDI, minusDI, adx] = ta.dmi(14, 14)
float basis = ta.sma(close, 20)
float deviation = ta.stdev(close, 20) * 2.0
float upper = basis + deviation
float lower = basis - deviation
float rsi = ta.rsi(close, 14)
// Re-enter the band after an extreme close, only in low-ADX conditions.
bool longCondition = close[1] < lower[1] and close > lower and close < basis and rsi[1] < 35 and rsi > rsi[1] and adx < 20 and math.abs(basis - basis[5]) < atr
bool shortCondition = close[1] > upper[1] and close < upper and close > basis and rsi[1] > 65 and rsi < rsi[1] and adx < 20 and math.abs(basis - basis[5]) < atr
plot(basis, "Band basis", color=color.yellow)
plot(upper, "Upper band", color=color.gray)
plot(lower, "Lower band", color=color.gray)

var bool managing = false
var int signalBar = na
var int lastResolved = na
var int seenClosed = 0
var string orderId = ""
var float plannedRisk = na
var float plannedTicks = na
bool openedLong = false
bool openedShort = false
if barstate.isconfirmed
    // Handles trades which enter and exit entirely inside one historical bar too.
    if managing and strategy.position_size == 0 and strategy.closedtrades > seenClosed
        managing := false
        lastResolved := bar_index
        orderId := ""
        plannedRisk := na
        plannedTicks := na
    // If the emulator could not open the order (for example a margin rejection),
    // release the pending slot after the next bar instead of freezing the system.
    if managing and strategy.position_size == 0 and bar_index > signalBar and strategy.closedtrades == seenClosed
        strategy.cancel_all()
        managing := false
        lastResolved := bar_index
        orderId := ""
        plannedRisk := na
        plannedTicks := na
    seenClosed := strategy.closedtrades
    if managing and strategy.position_size != 0
        int filledAt = strategy.opentrades.entry_bar_index(0)
        bool timedOut = bar_index - filledAt + 1 >= maxBars
        if timedOut or time_close >= untilTime
            strategy.cancel_all()
            strategy.close_all(comment=timedOut ? "HOLDING_WINDOW" : "TEST_END")
    // 200 bars warmup for all three systems. One position, no reversals.
    bool ready = not managing and strategy.position_size == 0 and bar_index >= 200 and time >= fromTime and time_close < untilTime and (na(lastResolved) or bar_index - lastResolved > cooldown)
    if ready and atr > 0 and (longCondition or shortCondition) and not (longCondition and shortCondition) and strategy.equity > 0
        float ticks = math.max(1, math.round(atr * stopATR / syminfo.mintick))
        float unitRisk = ticks * syminfo.mintick * syminfo.pointvalue
        float rawQty = math.min(strategy.equity * riskPct / 100 / unitRisk, strategy.equity * exposureCap / (close * syminfo.pointvalue))
        // Four minimum-contract units permit exact 50/25/25 partitions.
        float step = syminfo.mincontract * 4
        float qty = math.floor(rawQty / step) * step
        if qty >= step
            plannedRisk := qty * unitRisk
            plannedTicks := ticks
            orderId := "66R1|" + str.tostring(time_close) + "|R=" + str.tostring(plannedRisk, "#.########") + "|Q=" + str.tostring(qty, "#.########")
            strategy.entry(orderId, longCondition ? strategy.long : strategy.short, qty=qty)
            // Relative ticks anchor brackets to simulated fill price, including entry slippage.
            // Brackets are submitted with the entry; calc_on_order_fills is intentionally off.
            strategy.exit("TP1", from_entry=orderId, qty_percent=50, profit=ticks, loss=ticks, comment_profit="TP1", comment_loss="SL")
            strategy.exit("TP2", from_entry=orderId, qty_percent=25, profit=ticks * 2, loss=ticks, comment_profit="TP2", comment_loss="SL")
            strategy.exit("TP3", from_entry=orderId, qty_percent=25, profit=ticks * 3, loss=ticks, comment_profit="TP3", comment_loss="SL")
            signalBar := bar_index
            managing := true
            openedLong := longCondition
            openedShort := shortCondition

float fill = strategy.position_size != 0 ? strategy.position_avg_price : na
float dir = strategy.position_size > 0 ? 1 : -1
float dist = plannedTicks * syminfo.mintick
plot(fill, "Simulated fill", color=color.yellow, style=plot.style_linebr)
plot(fill - dir * dist, "Fixed stop", color=color.red, style=plot.style_linebr)
plot(fill + dir * dist, "TP1 / 50%", color=color.teal, style=plot.style_linebr)
plot(fill + dir * dist * 2, "TP2 / 25%", color=color.new(color.teal, 25), style=plot.style_linebr)
plot(fill + dir * dist * 3, "TP3 / 25%", color=color.new(color.teal, 50), style=plot.style_linebr)
plotshape(openedLong, title="Long order submitted", text="BUY ORDER", style=shape.labelup, location=location.belowbar, color=color.teal, textcolor=color.white, size=size.tiny)
plotshape(openedShort, title="Short order submitted", text="SELL ORDER", style=shape.labeldown, location=location.abovebar, color=color.red, textcolor=color.white, size=size.tiny)
var table desk = table.new(position.top_right, 1, 3, bgcolor=color.new(color.black, 15))
if barstate.islast
    table.cell(desk, 0, 0, "66 Reversion / Strategy v1.0", text_color=color.yellow, text_size=size.small)
    table.cell(desk, 0, 1, "50 / 25 / 25 · fixed SL · next-bar fill", text_color=color.white, text_size=size.small)
    table.cell(desk, 0, 2, "Review costs in Properties · USD · simulated", text_color=color.silver, text_size=size.small)
