Indicator ยท Pine Script v6 ยท On the chart

Trend Continuation Zones

by rosijay17-cmd

An overlay indicator that signals trend-continuation breakouts only in the direction of a mandatory trend filter built from a long EMA and a confirmed higher-timeframe EMA stack.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code1,292
Settings37inputs
LicenceGPL-3.0
Last update7 days ago22 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

1,292 lines of Pine Script v6 from Trend_Continuation_Zones.pine, published under GPL-3.0. Keep the author's credit and licence lines when you reuse it.

Trend_Continuation_Zones.pine
//@version=6
indicator("Trend Continuation Zones [No Counter-Trend]", shorttitle="Trend Continuation Zones [No Counter-Trend]", overlay=true, max_labels_count=500, max_lines_count=500, max_boxes_count=500)

// ============================================================================
// OVERVIEW
// This script is a trend-continuation breakout framework -- the
// trend-following sibling of the liquidity-sweep reversal framework it
// shares its architecture and code style with.
// Core workflow:
// 1) Determine the mandatory trend direction from a long EMA and a
//    confirmed higher-timeframe EMA stack -- no input anywhere in this
//    script can disable or override this gate
// 2) Store recent swing highs and lows as breakout reference levels
// 3) Detect breakouts through those levels, only in the direction the
//    trend filter already allows
// 4) Optionally require a shallow pullback / retest of the broken
//    level before confirming entry
// 5) Optionally simulate ATR-based SL and RR-based TP projections
// 6) Summarize live market state in a clean status panel
//
// Notes:
// - Signals are confirmed on bar close.
// - Stop Loss is calculated from ATR.
// - Take Profit is calculated from selected RR.
// - If both TP and SL appear on the same bar after entry, SL is prioritized.
// - The simulation is a chart review tool, not an execution engine.
// - Counter-trend signals are structurally impossible: a bullish breakout
//   is only ever detected while the trend filter is bullish, a bearish
//   breakout only while it is bearish, and confirmation re-checks the
//   same filter again before a signal is allowed to fire.
// ============================================================================

// ============================================================================
// COLORS
// ============================================================================
color CLR_BG          = color.rgb(7, 10, 16)
color CLR_PANEL       = color.rgb(13, 18, 27)
color CLR_PANEL_ALT   = color.rgb(18, 25, 36)
color CLR_BORDER      = color.rgb(44, 57, 76)
color CLR_TEXT        = color.rgb(245, 247, 250)
color CLR_MUTED       = color.rgb(148, 163, 184)

color CLR_CYAN        = color.rgb(34, 211, 238)
color CLR_CYAN_SOFT   = color.rgb(8, 145, 178)
color CLR_GREEN       = color.rgb(16, 185, 129)
color CLR_GREEN_SOFT  = color.rgb(5, 150, 105)
color CLR_RED         = color.rgb(239, 68, 68)
color CLR_RED_SOFT    = color.rgb(220, 38, 38)
color CLR_SILVER      = color.rgb(203, 213, 225)
color CLR_GOLD        = color.rgb(250, 204, 21)
color CLR_TRACE_BULL  = color.rgb(74, 222, 128)
color CLR_TRACE_BEAR  = color.rgb(248, 113, 113)

// ============================================================================
// INTERNAL SETTINGS
// ============================================================================
int BOX_TRANSPARENCY = 86

// ============================================================================
// INPUT GROUPS
// ============================================================================
groupTrend    = "๐Ÿ“ˆ Trend Filter"
groupChop     = "๐ŸŒช๏ธ Chop Filter"
groupBreak    = "๐Ÿš€ Breakout Detection"
groupQuality  = "๐Ÿงช Candle Quality Filters"
groupConfirm  = "โœ… Entry Confirmation"
groupSim      = "โš–๏ธ ATR Risk Projection"
groupVisual   = "๐ŸŽจ Visual Settings"

// ============================================================================
// INPUTS
// ============================================================================
trendEmaLength = input.int(
     50,
     "๐Ÿ“ Trend EMA Length",
     minval=1,
     group=groupTrend,
     tooltip="Defines the long EMA used to determine the mandatory trend direction. Every breakout must agree with this direction -- there is no input to override or disable this filter.",
     display=display.none
)

htfTimeframe = input.timeframe(
     "60",
     "โฑ๏ธ Higher Timeframe",
     group=groupTrend,
     tooltip="Higher timeframe whose EMA stack must also agree with the trend direction. This confirmation is always required and cannot be disabled.",
     display=display.none
)

htfFastLength = input.int(
     20,
     "๐Ÿงญ HTF Fast EMA Length",
     minval=1,
     group=groupTrend,
     tooltip="Fast EMA on the higher timeframe used to confirm trend direction.",
     display=display.none
)

htfSlowLength = input.int(
     50,
     "๐Ÿงญ HTF Slow EMA Length",
     minval=2,
     group=groupTrend,
     tooltip="Slow EMA on the higher timeframe used to confirm trend direction.",
     display=display.none
)

useChopFilter = input.bool(
     true,
     "๐ŸŒช๏ธ Enable Chop Filter",
     group=groupChop,
     tooltip="When enabled, breakouts are blocked while the market lacks trend strength (ADX) or is showing high choppiness (Choppiness Index) -- the two most common causes of failed breakouts. Unlike the Trend Filter, this can be turned off, since doing so only removes an extra quality gate -- it can never enable a counter-trend signal.",
     display=display.none
)

adxLen = input.int(
     14,
     "๐Ÿ“ ADX Length",
     minval=1,
     group=groupChop,
     tooltip="Period used to calculate ADX, the trend-strength component of the chop filter.",
     display=display.none
)

adxMinimum = input.float(
     20.0,
     "๐Ÿ“ˆ Minimum ADX",
     minval=1.0,
     step=0.5,
     group=groupChop,
     tooltip="Breakouts are blocked while ADX is below this level -- a low ADX means the market is not trending strongly enough for a breakout to be reliable.",
     display=display.none
)

chopLen = input.int(
     14,
     "๐Ÿ“ Choppiness Index Length",
     minval=2,
     group=groupChop,
     tooltip="Period used to calculate the Choppiness Index, the range/consolidation component of the chop filter.",
     display=display.none
)

chopMaximum = input.float(
     61.8,
     "๐Ÿ“‰ Maximum Choppiness Index",
     minval=0.0,
     maxval=100.0,
     step=0.1,
     group=groupChop,
     tooltip="Breakouts are blocked while the Choppiness Index is above this level -- high readings indicate range-bound, non-directional price action. 61.8 is the traditional threshold.",
     display=display.none
)

pivotLength = input.int(
     5,
     "๐Ÿงฑ Swing Pivot Length",
     minval=1,
     group=groupBreak,
     tooltip="Controls how swing highs and swing lows are detected as breakout reference levels.",
     display=display.none
)

storedLevels = input.int(
     20,
     "๐Ÿ“š Stored Reference Levels",
     minval=5,
     maxval=100,
     group=groupBreak,
     tooltip="Maximum number of recent breakout reference levels stored from swing highs and swing lows.",
     display=display.none
)

minBreakoutDistanceAtr = input.float(
     0.10,
     "๐Ÿ“ Minimum Breakout Distance / ATR",
     minval=0.01,
     step=0.01,
     group=groupBreak,
     tooltip="Minimum breakout distance measured as an ATR multiplier. Example: 0.10 means the close must clear the level by at least 10% of ATR.",
     display=display.none
)

breakoutRule = input.string(
     "Close Beyond Level",
     "๐Ÿ” Breakout Rule",
     options=["Close Beyond Level", "Strong Close"],
     group=groupBreak,
     tooltip="Close Beyond Level requires price to close beyond the broken reference level. Strong Close also requires a stronger close beyond the candle midpoint in the breakout direction.",
     display=display.none
)

maxWickPercent = input.float(
     0.35,
     "๐Ÿ•ฏ๏ธ Maximum Opposing Wick %",
     minval=0.0,
     maxval=1.0,
     step=0.01,
     group=groupQuality,
     tooltip="Maximum opposing wick percentage allowed on the breakout candle. Lower values require cleaner, more decisive breakout closes.",
     display=display.none
)

minBodyPercent = input.float(
     0.50,
     "๐Ÿ“ฆ Minimum Body %",
     minval=0.05,
     maxval=1.0,
     step=0.01,
     group=groupQuality,
     tooltip="Minimum candle body percentage required. Higher values favor strong-bodied continuation candles over indecisive ones.",
     display=display.none
)

minCandleRangeAtr = input.float(
     0.20,
     "๐Ÿ“ Minimum Candle Range / ATR",
     minval=0.01,
     step=0.01,
     group=groupQuality,
     tooltip="Minimum candle range measured as an ATR multiplier. Helps filter out very small breakout candles.",
     display=display.none
)

requireBullishBody = input.bool(
     true,
     "๐ŸŸข Long Confirmation Must Be Bullish",
     group=groupQuality,
     tooltip="When enabled, long confirmations require the confirmation candle to close above its open.",
     display=display.none
)

requireBearishBody = input.bool(
     true,
     "๐Ÿ”ด Short Confirmation Must Be Bearish",
     group=groupQuality,
     tooltip="When enabled, short confirmations require the confirmation candle to close below its open.",
     display=display.none
)

confirmationWindow = input.int(
     2,
     "โฑ๏ธ Max Bars After Breakout For Confirmation",
     minval=0,
     maxval=10,
     group=groupConfirm,
     tooltip="Maximum number of bars allowed after a valid breakout for confirmation to appear.",
     display=display.none
)

requirePullbackRetest = input.bool(
     true,
     "โ†ฉ๏ธ Require Pullback / Retest Before Confirmation",
     group=groupConfirm,
     tooltip="When enabled, price must first pull back within Retest Tolerance of the broken level before a continuation candle can confirm the signal. This is the trend-following equivalent of a confirmation window.",
     display=display.none
)

retestToleranceAtr = input.float(
     0.35,
     "๐Ÿ“ Retest Tolerance / ATR",
     minval=0.01,
     step=0.01,
     group=groupConfirm,
     tooltip="How close price must come back to the broken level, measured as an ATR multiplier, for a pullback to count as a valid retest.",
     display=display.none
)

signalCooldownBars = input.int(
     10,
     "๐ŸงŠ Cooldown Bars Between Signals",
     minval=0,
     group=groupConfirm,
     tooltip="Minimum number of bars required between two confirmed signals.",
     display=display.none
)

postLossCooldownBars = input.int(
     20,
     "๐ŸงŠ Extra Cooldown After Stop-Out",
     minval=0,
     group=groupConfirm,
     tooltip="Additional bars of cooldown layered on top of the standard signal cooldown, applied only after a stopped-out simulated trade. Helps prevent repeated whipsaw entries during choppy stretches.",
     display=display.none
)

enableSimulation = input.bool(
     true,
     "๐Ÿงช Enable Internal Trade Simulation",
     group=groupSim,
     tooltip="When enabled, the script projects ATR-based stop loss and RR-based take profit levels after each signal.",
     display=display.none
)

atrLength = input.int(
     14,
     "๐Ÿ“ ATR Length",
     minval=1,
     group=groupSim,
     tooltip="ATR period used to calculate the stop loss distance.",
     display=display.none
)

stopLossAtrMultiplier = input.float(
     1.50,
     "๐Ÿ›‘ Stop Loss ATR Multiplier",
     minval=0.10,
     step=0.10,
     group=groupSim,
     tooltip="Stop Loss distance is calculated as ATR multiplied by this value.",
     display=display.none
)

takeProfitRRInput = input.string(
     "3 RR",
     "๐ŸŽฏ Take Profit RR",
     options=["1.5 RR", "2 RR", "3 RR", "4 RR", "5 RR", "6 RR"],
     group=groupSim,
     tooltip="Take Profit is calculated from the ATR-based stop distance using the selected RR multiple.",
     display=display.none
)

blockSignalsInTrade = input.bool(
     true,
     "๐Ÿšง Block New Signals While Trade Is Active",
     group=groupSim,
     tooltip="When enabled, new signals are blocked while an internal projected trade is still active.",
     display=display.none
)

showStoppedTrades = input.bool(
     false,
     "๐Ÿ‘๏ธ Show Stopped Trades",
     group=groupSim,
     tooltip="When disabled, stopped-out trade projection objects are removed from the chart for a cleaner view.",
     display=display.none
)

showSignalLabels = input.bool(
     true,
     "๐Ÿท๏ธ Show Signal Labels",
     group=groupVisual,
     tooltip="Shows BUY and SELL labels on confirmed signals.",
     display=display.none
)

showEntryLine = input.bool(
     true,
     "โšช Show Entry Line",
     group=groupVisual,
     tooltip="Shows the projected entry line after a confirmed signal.",
     display=display.none
)

showStatusPanel = input.bool(
     true,
     "๐Ÿ“Š Show Status Panel",
     group=groupVisual,
     tooltip="Shows the live status dashboard. Performance rows such as trades, win rate, net result, and drawdown are not displayed.",
     display=display.none
)

showSignalBarColors = input.bool(
     true,
     "๐ŸŽจ Color Signal Bars",
     group=groupVisual,
     tooltip="Colors confirmed signal bars green for long and red for short.",
     display=display.none
)

showBreakoutGuides = input.bool(
     true,
     "๐Ÿงฒ Show Breakout Guides",
     group=groupVisual,
     tooltip="Shows dotted breakout trace guides when valid breakouts are detected.",
     display=display.none
)

guideExtendBars = input.int(
     20,
     "โžก๏ธ Guide Extension Bars",
     minval=5,
     maxval=100,
     group=groupVisual,
     tooltip="Controls how far breakout guide lines extend into the future.",
     display=display.none
)

signalLabelSize = input.string(
     "Normal",
     "๐Ÿ”  Signal Label Size",
     options=["Tiny", "Small", "Normal", "Large"],
     group=groupVisual,
     tooltip="Controls the size of BUY, SELL, TP HIT, and SL HIT labels.",
     display=display.none
)

panelPosition = input.string(
     "Bottom Right",
     "๐Ÿ“ Panel Position",
     options=["Top Right", "Top Left", "Bottom Right", "Bottom Left"],
     group=groupVisual,
     tooltip="Controls where the status panel appears on the chart.",
     display=display.none
)

// ============================================================================
// HELPERS
// ============================================================================
f_table_position(_pos) =>
    _pos == "Top Left" ? position.top_left :
     _pos == "Bottom Right" ? position.bottom_right :
     _pos == "Bottom Left" ? position.bottom_left :
     position.top_right

f_fmt(_value) =>
    str.tostring(_value, "#.##")

f_label_size(_size) =>
    _size == "Tiny" ? size.tiny :
     _size == "Small" ? size.small :
     _size == "Large" ? size.large :
     size.normal

f_tp_rr(_value) =>
    _value == "1.5 RR" ? 1.5 :
     _value == "2 RR" ? 2.0 :
     _value == "3 RR" ? 3.0 :
     _value == "4 RR" ? 4.0 :
     _value == "5 RR" ? 5.0 :
     6.0

f_clamp(_value, _min, _max) =>
    math.max(_min, math.min(_max, _value))

f_score_color(_score) =>
    _score >= 75 ? CLR_GREEN : _score >= 55 ? CLR_CYAN : CLR_RED

f_push_line_prune(_arr, _line, _max) =>
    array.push(_arr, _line)
    if array.size(_arr) > _max
        line _old = array.shift(_arr)
        line.delete(_old)

f_push_label_prune(_arr, _label, _max) =>
    array.push(_arr, _label)
    if array.size(_arr) > _max
        label _old = array.shift(_arr)
        label.delete(_old)

f_push_box_prune(_arr, _box, _max) =>
    array.push(_arr, _box)
    if array.size(_arr) > _max
        box _old = array.shift(_arr)
        box.delete(_old)

f_panel_clear(_t) =>
    for col = 0 to 1
        for row = 0 to 10
            table.cell(_t, col, row, "", bgcolor=CLR_BG, text_color=CLR_BG)

f_quality_score(_bodyPct, _wickPct, _rangeAtr, _minBody, _maxWick, _minRangeAtr, _emaDistanceAtr, _htfOk) =>
    float bodyScore  = _minBody > 0 ? f_clamp((_bodyPct / _minBody) * 32.0, 0.0, 32.0) : 32.0
    float wickScore  = _maxWick > 0 ? f_clamp(((_maxWick - _wickPct) / _maxWick) * 24.0, 0.0, 24.0) : 24.0
    float rangeScore = _minRangeAtr > 0 ? f_clamp((_rangeAtr / _minRangeAtr) * 18.0, 0.0, 18.0) : 18.0
    float emaScore   = f_clamp(_emaDistanceAtr * 10.0, 0.0, 10.0)
    float htfScore   = _htfOk ? 10.0 : 0.0
    f_clamp(bodyScore + wickScore + rangeScore + emaScore + htfScore, 0.0, 94.0)

f_retest_score(_bestDistanceAtr, _tolerance, _required) =>
    not _required ? 6.0 :
     na(_bestDistanceAtr) ? 0.0 :
     _tolerance > 0 ? f_clamp((1.0 - (_bestDistanceAtr / _tolerance)) * 6.0, 0.0, 6.0) : 6.0

f_draw_signal_brackets(_bar, _high, _low, _buffer, _color, _arr) =>
    int leftX   = _bar - 1
    int midX    = _bar
    int rightX  = _bar + 1
    float topY  = _high + _buffer
    float botY  = _low - _buffer
    float topIn = _high + (_buffer * 0.35)
    float botIn = _low - (_buffer * 0.35)

    line l1 = line.new(x1=leftX, y1=topY, x2=midX, y2=topY, xloc=xloc.bar_index, color=_color, width=1)
    line l2 = line.new(x1=leftX, y1=topY, x2=leftX, y2=topIn, xloc=xloc.bar_index, color=_color, width=1)
    line l3 = line.new(x1=leftX, y1=botY, x2=midX, y2=botY, xloc=xloc.bar_index, color=_color, width=1)
    line l4 = line.new(x1=leftX, y1=botY, x2=leftX, y2=botIn, xloc=xloc.bar_index, color=_color, width=1)

    line l5 = line.new(x1=midX, y1=topY, x2=rightX, y2=topY, xloc=xloc.bar_index, color=_color, width=1)
    line l6 = line.new(x1=rightX, y1=topY, x2=rightX, y2=topIn, xloc=xloc.bar_index, color=_color, width=1)
    line l7 = line.new(x1=midX, y1=botY, x2=rightX, y2=botY, xloc=xloc.bar_index, color=_color, width=1)
    line l8 = line.new(x1=rightX, y1=botY, x2=rightX, y2=botIn, xloc=xloc.bar_index, color=_color, width=1)

    f_push_line_prune(_arr, l1, 160)
    f_push_line_prune(_arr, l2, 160)
    f_push_line_prune(_arr, l3, 160)
    f_push_line_prune(_arr, l4, 160)
    f_push_line_prune(_arr, l5, 160)
    f_push_line_prune(_arr, l6, 160)
    f_push_line_prune(_arr, l7, 160)
    f_push_line_prune(_arr, l8, 160)

f_add_breakout_trace(_price, _bull, _extendBars, _lineArr, _ghostArr, _labelArr) =>
    color traceColor = _bull ? color.new(CLR_TRACE_BULL, 0) : color.new(CLR_TRACE_BEAR, 0)
    color ghostColor = _bull ? color.new(CLR_TRACE_BULL, 82) : color.new(CLR_TRACE_BEAR, 82)

    line traceLine = line.new(x1=bar_index, y1=_price, x2=bar_index + _extendBars, y2=_price, xloc=xloc.bar_index, extend=extend.none, color=traceColor, width=1, style=line.style_dotted)
    line ghostLine = line.new(x1=bar_index, y1=_price, x2=bar_index + (_extendBars * 2), y2=_price, xloc=xloc.bar_index, extend=extend.none, color=ghostColor, width=1, style=line.style_solid)
    label traceLab = label.new(bar_index + _extendBars, _price, "BREAKOUT LEVEL", xloc=xloc.bar_index, style=_bull ? label.style_label_up : label.style_label_down, color=_bull ? CLR_GREEN_SOFT : CLR_RED_SOFT, textcolor=CLR_TEXT, size=size.tiny)

    f_push_line_prune(_lineArr, traceLine, 80)
    f_push_line_prune(_ghostArr, ghostLine, 20)
    f_push_label_prune(_labelArr, traceLab, 40)

f_session_name(_hour) =>
    _hour >= 0 and _hour < 8 ? "ASIA" :
     _hour >= 8 and _hour < 16 ? "LONDON" :
     _hour >= 16 and _hour < 24 ? "NEW YORK" :
     "UNKNOWN"

// ============================================================================
// CORE SERIES
// ============================================================================
localTrendEma = ta.ema(close, trendEmaLength)
atrValue = ta.atr(atrLength)
takeProfitRR = f_tp_rr(takeProfitRRInput)

candleRange = high - low
candleBody  = math.abs(close - open)
upperWick   = high - math.max(open, close)
lowerWick   = math.min(open, close) - low

bodyPercent      = candleRange > 0 ? candleBody / candleRange : 0.0
upperWickPercent = candleRange > 0 ? upperWick / candleRange : 0.0
lowerWickPercent = candleRange > 0 ? lowerWick / candleRange : 0.0

candleRangeAtr      = not na(atrValue) and atrValue > 0 ? candleRange / atrValue : 0.0
minBreakoutDistancePx = not na(atrValue) ? minBreakoutDistanceAtr * atrValue : na
minRangeDistancePx    = not na(atrValue) ? minCandleRangeAtr * atrValue : na
retestTolerancePx     = not na(atrValue) ? retestToleranceAtr * atrValue : na

stopLossDistance   = not na(atrValue) ? atrValue * stopLossAtrMultiplier : na
takeProfitDistance = not na(stopLossDistance) ? stopLossDistance * takeProfitRR : na
riskModelReady     = not na(stopLossDistance) and stopLossDistance > 0 and not na(takeProfitDistance) and takeProfitDistance > 0

float visualBuffer    = math.max(syminfo.mintick * 8.0, candleRange * 0.08)
float entryZoneHalf   = math.max(syminfo.mintick * 6.0, nz(atrValue, candleRange) * 0.04)
float entryLineOffset = math.max(syminfo.mintick * 2.0, nz(atrValue, candleRange) * 0.02)

// ============================================================================
// VOLATILITY / SESSION STATE
// ============================================================================
float atrPct = not na(atrValue) and close != 0 ? (atrValue / close) * 100.0 : 0.0
string volatilityState = atrPct >= 0.80 ? "HIGH" : atrPct >= 0.35 ? "NORMAL" : "LOW"
color volatilityColor = atrPct >= 0.80 ? CLR_RED : atrPct >= 0.35 ? CLR_CYAN : CLR_GREEN

int gmt3Hour = hour(time, "GMT+3")
string sessionState = f_session_name(gmt3Hour)
color sessionColor = sessionState == "LONDON" ? CLR_CYAN : sessionState == "NEW YORK" ? CLR_GOLD : CLR_SILVER

// ============================================================================
// TREND FILTER (MANDATORY -- NO OVERRIDE)
// ============================================================================
// lookahead=barmerge.lookahead_off keeps this reading only data from the
// last confirmed higher-timeframe bar -- no repainting of unclosed HTF
// bars. Both the long EMA and the confirmed HTF EMA stack must agree
// before either direction becomes eligible. There is no input in this
// script that can bypass or disable this gate.
float htfFastEma = request.security(syminfo.tickerid, htfTimeframe, ta.ema(close, htfFastLength), lookahead=barmerge.lookahead_off)
float htfSlowEma = request.security(syminfo.tickerid, htfTimeframe, ta.ema(close, htfSlowLength), lookahead=barmerge.lookahead_off)

bool htfValid   = not na(htfFastEma) and not na(htfSlowEma)
bool htfBullish = htfValid and htfFastEma > htfSlowEma
bool htfBearish = htfValid and htfFastEma < htfSlowEma

bool localBullish = close > localTrendEma
bool localBearish = close < localTrendEma

bool longTrendOk  = localBullish and htfBullish
bool shortTrendOk = localBearish and htfBearish

// ============================================================================
// CHOP FILTER
// ============================================================================
// Two independent chop lenses: ADX measures trend strength, the
// Choppiness Index measures how range-bound the last chopLen bars have
// been. Either one failing is enough to block a breakout -- chop kills
// breakout systems through false breaks, so this is deliberately strict.
[plusDI, minusDI, adxValue] = ta.dmi(adxLen, adxLen)

float chopTrueRange = ta.tr(true)
float chopRangeSum  = math.sum(chopTrueRange, chopLen)
float chopHighest    = ta.highest(high, chopLen)
float chopLowest     = ta.lowest(low, chopLen)
float chopRange       = chopHighest - chopLowest
float chopValue = chopRange > 0 and chopLen > 1 ? 100.0 * math.log10(chopRangeSum / chopRange) / math.log10(chopLen) : na

bool chopFilterOk =
     not useChopFilter or
     (
         not na(adxValue) and adxValue >= adxMinimum and
         (na(chopValue) or chopValue <= chopMaximum)
     )

// ============================================================================
// PIVOTS AND BREAKOUT LEVEL STORAGE
// ============================================================================
pivotHigh = ta.pivothigh(high, pivotLength, pivotLength)
pivotLow  = ta.pivotlow(low, pivotLength, pivotLength)

var float[] storedHighLevels = array.new_float()
var int[]   storedHighBars   = array.new_int()
var float[] storedLowLevels  = array.new_float()
var int[]   storedLowBars    = array.new_int()

if not na(pivotHigh)
    array.push(storedHighLevels, pivotHigh)
    array.push(storedHighBars, bar_index - pivotLength)
    if array.size(storedHighLevels) > storedLevels
        array.shift(storedHighLevels)
        array.shift(storedHighBars)

if not na(pivotLow)
    array.push(storedLowLevels, pivotLow)
    array.push(storedLowBars, bar_index - pivotLength)
    if array.size(storedLowLevels) > storedLevels
        array.shift(storedLowLevels)
        array.shift(storedLowBars)

// ============================================================================
// FIND MOST RECENTLY BROKEN LEVEL
// ============================================================================
float brokenHighLevel = na
int   brokenHighBar   = na
float brokenLowLevel  = na
int   brokenLowBar    = na

int highLevelCount = array.size(storedHighLevels)
if highLevelCount > 0
    for i = highLevelCount - 1 to 0
        float highLevel = array.get(storedHighLevels, i)
        int   highBar   = array.get(storedHighBars, i)
        if highBar < bar_index and close > highLevel and not na(minBreakoutDistancePx) and (close - highLevel) >= minBreakoutDistancePx
            brokenHighLevel := highLevel
            brokenHighBar   := highBar
            break

int lowLevelCount = array.size(storedLowLevels)
if lowLevelCount > 0
    for i = lowLevelCount - 1 to 0
        float lowLevel = array.get(storedLowLevels, i)
        int   lowBar   = array.get(storedLowBars, i)
        if lowBar < bar_index and close < lowLevel and not na(minBreakoutDistancePx) and (lowLevel - close) >= minBreakoutDistancePx
            brokenLowLevel := lowLevel
            brokenLowBar   := lowBar
            break

// ============================================================================
// BREAKOUT VALIDATION
// ============================================================================
bullBreakoutCloseOk =
     not na(brokenHighLevel) and
     (
         breakoutRule == "Close Beyond Level" ?
         close > brokenHighLevel :
         close > brokenHighLevel and close > (high + low) / 2.0
     )

bearBreakoutCloseOk =
     not na(brokenLowLevel) and
     (
         breakoutRule == "Close Beyond Level" ?
         close < brokenLowLevel :
         close < brokenLowLevel and close < (high + low) / 2.0
     )

validBuyBreakout =
     not na(brokenHighLevel) and
     bullBreakoutCloseOk and
     upperWickPercent <= maxWickPercent and
     bodyPercent >= minBodyPercent and
     not na(minRangeDistancePx) and
     candleRange >= minRangeDistancePx and
     longTrendOk and
     chopFilterOk

validSellBreakout =
     not na(brokenLowLevel) and
     bearBreakoutCloseOk and
     lowerWickPercent <= maxWickPercent and
     bodyPercent >= minBodyPercent and
     not na(minRangeDistancePx) and
     candleRange >= minRangeDistancePx and
     shortTrendOk and
     chopFilterOk

// ============================================================================
// BREAKOUT TRACE / REFERENCE MEMORY
// ============================================================================
var line[]  breakoutTraceLines  = array.new_line()
var line[]  referenceLines      = array.new_line()
var label[] breakoutTraceLabels = array.new_label()

if showBreakoutGuides and validBuyBreakout and not na(brokenHighLevel)
    f_add_breakout_trace(brokenHighLevel, true, guideExtendBars, breakoutTraceLines, referenceLines, breakoutTraceLabels)

if showBreakoutGuides and validSellBreakout and not na(brokenLowLevel)
    f_add_breakout_trace(brokenLowLevel, false, guideExtendBars, breakoutTraceLines, referenceLines, breakoutTraceLabels)

// ============================================================================
// STORE PENDING BREAKOUT EVENTS
// ============================================================================
var int   pendingBullBreakoutBar   = na
var float pendingBullBreakoutLevel = na
var float pendingBullBaseScore     = na
var float pendingBullRetestBestAtr = na
var bool  pendingBullRetestDone    = false

var int   pendingBearBreakoutBar   = na
var float pendingBearBreakoutLevel = na
var float pendingBearBaseScore     = na
var float pendingBearRetestBestAtr = na
var bool  pendingBearRetestDone    = false

emaDistanceAtr = not na(atrValue) and atrValue > 0 ? math.abs(close - localTrendEma) / atrValue : 0.0

bullBaseScoreNow = f_quality_score(bodyPercent, upperWickPercent, candleRangeAtr, minBodyPercent, maxWickPercent, minCandleRangeAtr, emaDistanceAtr, htfBullish)
bearBaseScoreNow = f_quality_score(bodyPercent, lowerWickPercent, candleRangeAtr, minBodyPercent, maxWickPercent, minCandleRangeAtr, emaDistanceAtr, htfBearish)

if validBuyBreakout
    pendingBullBreakoutBar   := bar_index
    pendingBullBreakoutLevel := brokenHighLevel
    pendingBullBaseScore     := bullBaseScoreNow
    pendingBullRetestBestAtr := na
    pendingBullRetestDone    := false

if validSellBreakout
    pendingBearBreakoutBar   := bar_index
    pendingBearBreakoutLevel := brokenLowLevel
    pendingBearBaseScore     := bearBaseScoreNow
    pendingBearRetestBestAtr := na
    pendingBearRetestDone    := false

bullWindowOpen = not na(pendingBullBreakoutBar) and (bar_index - pendingBullBreakoutBar <= confirmationWindow)
bearWindowOpen = not na(pendingBearBreakoutBar) and (bar_index - pendingBearBreakoutBar <= confirmationWindow)

// Track how tightly price retests the broken level while a breakout is
// pending confirmation -- feeds both the retest gate and the quality score.
if bullWindowOpen and not na(pendingBullBreakoutLevel) and not na(atrValue) and atrValue > 0
    float bullRetestDistanceAtr = math.abs(low - pendingBullBreakoutLevel) / atrValue
    pendingBullRetestBestAtr := na(pendingBullRetestBestAtr) ? bullRetestDistanceAtr : math.min(pendingBullRetestBestAtr, bullRetestDistanceAtr)
    if not na(retestTolerancePx) and low <= pendingBullBreakoutLevel + retestTolerancePx
        pendingBullRetestDone := true

if bearWindowOpen and not na(pendingBearBreakoutLevel) and not na(atrValue) and atrValue > 0
    float bearRetestDistanceAtr = math.abs(high - pendingBearBreakoutLevel) / atrValue
    pendingBearRetestBestAtr := na(pendingBearRetestBestAtr) ? bearRetestDistanceAtr : math.min(pendingBearRetestBestAtr, bearRetestDistanceAtr)
    if not na(retestTolerancePx) and high >= pendingBearBreakoutLevel - retestTolerancePx
        pendingBearRetestDone := true

// A close back beyond the broken level invalidates the breakout
// immediately -- the continuation thesis has failed and confirmation
// must not be allowed to complete on stale pending state.
if bullWindowOpen and close < pendingBullBreakoutLevel
    pendingBullBreakoutBar   := na
    pendingBullBreakoutLevel := na
    pendingBullBaseScore     := na
    pendingBullRetestBestAtr := na
    pendingBullRetestDone    := false

if bearWindowOpen and close > pendingBearBreakoutLevel
    pendingBearBreakoutBar   := na
    pendingBearBreakoutLevel := na
    pendingBearBaseScore     := na
    pendingBearRetestBestAtr := na
    pendingBearRetestDone    := false

// Re-evaluate the window after a possible invalidation above.
bullWindowOpen := not na(pendingBullBreakoutBar) and (bar_index - pendingBullBreakoutBar <= confirmationWindow)
bearWindowOpen := not na(pendingBearBreakoutBar) and (bar_index - pendingBearBreakoutBar <= confirmationWindow)

// ============================================================================
// CONFIRMATION LOGIC
// ============================================================================
bullBodyOk   = not requireBullishBody or close > open
bearBodyOk   = not requireBearishBody or close < open
bullRetestOk = not requirePullbackRetest or pendingBullRetestDone
bearRetestOk = not requirePullbackRetest or pendingBearRetestDone

buyConfirmed =
     bullWindowOpen and
     bullBodyOk and
     bullRetestOk and
     longTrendOk and
     not na(pendingBullBreakoutLevel) and
     close > pendingBullBreakoutLevel

sellConfirmed =
     bearWindowOpen and
     bearBodyOk and
     bearRetestOk and
     shortTrendOk and
     not na(pendingBearBreakoutLevel) and
     close < pendingBearBreakoutLevel

// ============================================================================
// SIGNAL COOLDOWN
// ============================================================================
var int lastSignalBar = na
var int lastLossBar   = na

bool standardCooldownPassed = na(lastSignalBar) or (bar_index - lastSignalBar > signalCooldownBars)
bool lossCooldownPassed     = na(lastLossBar) or (bar_index - lastLossBar > postLossCooldownBars)

cooldownPassed = standardCooldownPassed and lossCooldownPassed

rawBuySignal  = buyConfirmed
rawSellSignal = sellConfirmed

buySignal  = rawBuySignal and cooldownPassed
sellSignal = rawSellSignal and cooldownPassed and not buySignal

var float lastSignalScore = na
var int   lastSignalDir   = 0

if buySignal
    float bullRetestScoreFinal = f_retest_score(pendingBullRetestBestAtr, retestToleranceAtr, requirePullbackRetest)
    lastSignalScore := f_clamp(nz(pendingBullBaseScore, bullBaseScoreNow) + bullRetestScoreFinal, 0.0, 100.0)
    lastSignalDir   := 1

if sellSignal
    float bearRetestScoreFinal = f_retest_score(pendingBearRetestBestAtr, retestToleranceAtr, requirePullbackRetest)
    lastSignalScore := f_clamp(nz(pendingBearBaseScore, bearBaseScoreNow) + bearRetestScoreFinal, 0.0, 100.0)
    lastSignalDir   := -1

if buySignal or sellSignal
    lastSignalBar := bar_index

if buySignal or (not na(pendingBullBreakoutBar) and bar_index - pendingBullBreakoutBar > confirmationWindow)
    pendingBullBreakoutBar   := na
    pendingBullBreakoutLevel := na
    pendingBullBaseScore     := na
    pendingBullRetestBestAtr := na
    pendingBullRetestDone    := false

if sellSignal or (not na(pendingBearBreakoutBar) and bar_index - pendingBearBreakoutBar > confirmationWindow)
    pendingBearBreakoutBar   := na
    pendingBearBreakoutLevel := na
    pendingBearBaseScore     := na
    pendingBearRetestBestAtr := na
    pendingBearRetestDone    := false

// ============================================================================
// INTERNAL TRADE SIMULATION
// ============================================================================
var int   positionDirection    = 0
var float simulatedEntry       = na
var float simulatedTP          = na
var float simulatedSL          = na
var float simulatedEntryOffset = na
var int   simulatedEntryBar    = na

tradeIsActive = positionDirection != 0

allowLongSignal  = enableSimulation ? (riskModelReady and (blockSignalsInTrade ? not tradeIsActive : true)) : true
allowShortSignal = enableSimulation ? (riskModelReady and (blockSignalsInTrade ? not tradeIsActive : true)) : true

plotLongSignal  = buySignal and allowLongSignal
plotShortSignal = sellSignal and allowShortSignal

// ============================================================================
// VISUAL MEMORY ARRAYS
// ============================================================================
var line[]  bracketLines = array.new_line()
var label[] scoreLabels  = array.new_label()
var box[]   signalBoxes  = array.new_box()
var line[]  pathLines    = array.new_line()

// ============================================================================
// TRADE OBJECTS
// ============================================================================
var box   activeTpBox            = na
var box   activeSlBox            = na
var box   activeEntryZone        = na
var line  activeEntryLine1       = na
var line  activeEntryLine2       = na
var line  activeInvalidationLine = na
var line  activeProjectionLine   = na
var label activeEntryTag         = na
var label activeTpTag            = na
var label activeSlTag            = na
var label activeInvalidationTag  = na

// ============================================================================
// SIGNAL VISUAL BUILD
// ============================================================================
if plotLongSignal
    f_draw_signal_brackets(bar_index, high, low, visualBuffer, CLR_CYAN, bracketLines)

    float sigTop = math.max(open, close)
    float sigBot = math.min(open, close)
    box signalBodyBox = box.new(left=bar_index, top=sigTop, right=bar_index + 1, bottom=sigBot, xloc=xloc.bar_index, border_color=color.new(CLR_CYAN, 0), bgcolor=color.new(CLR_GREEN, 82), border_width=1)
    f_push_box_prune(signalBoxes, signalBodyBox, 50)

    label scoreTag = label.new(bar_index, low - (visualBuffer * 1.2), "Q:" + str.tostring(math.round(nz(lastSignalScore, 0))), xloc=xloc.bar_index, style=label.style_label_up, color=CLR_CYAN_SOFT, textcolor=CLR_TEXT, size=size.tiny)
    f_push_label_prune(scoreLabels, scoreTag, 50)

if plotShortSignal
    f_draw_signal_brackets(bar_index, high, low, visualBuffer, CLR_CYAN, bracketLines)

    float sigTop = math.max(open, close)
    float sigBot = math.min(open, close)
    box signalBodyBox = box.new(left=bar_index, top=sigTop, right=bar_index + 1, bottom=sigBot, xloc=xloc.bar_index, border_color=color.new(CLR_CYAN, 0), bgcolor=color.new(CLR_RED, 82), border_width=1)
    f_push_box_prune(signalBoxes, signalBodyBox, 50)

    label scoreTag = label.new(bar_index, high + (visualBuffer * 1.2), "Q:" + str.tostring(math.round(nz(lastSignalScore, 0))), xloc=xloc.bar_index, style=label.style_label_down, color=CLR_CYAN_SOFT, textcolor=CLR_TEXT, size=size.tiny)
    f_push_label_prune(scoreLabels, scoreTag, 50)

// ============================================================================
// R:R VISUAL GATE (diagnostic -- extracted, not a new filter)
// ============================================================================
// This is the exact condition the TRADE CREATION block below runs on,
// pulled out so it can be inspected directly instead of inferred from
// whether a box happened to render. It is identical in root cause to the
// BUY/SELL label's own condition (showSignalLabels and plotLongSignal /
// plotShortSignal) except for swapping showSignalLabels for
// enableSimulation -- there is no Quality Score, Trend State, ADX/chop, or
// session condition anywhere in this gate. Both toggles default true, so in
// practice this is true on every bar plotLongSignal/plotShortSignal is true,
// same bar as the BUY/SELL label. If a BUY label shows with no R:R diagram,
// this gate was still true on that bar -- the box/line/label objects were
// drawn and then silently recycled later by TradingView's global
// max_boxes_count/max_lines_count/max_labels_count caps, because (unlike
// every other object type in this script) activeTpBox/activeSlBox/
// activeEntryZone/activeEntryLine1/activeEntryLine2/activeInvalidationLine/
// activeEntryTag/activeTpTag/activeSlTag/activeInvalidationTag are never
// pushed into a size-capped prune array and are never explicitly deleted on
// a winning trade (only a losing trade with Show Stopped Trades off cleans
// up immediately) -- they just get overwritten to na while the drawn
// objects themselves stay on the chart until the platform recycles them.
bool rrBoxConditionLong  = enableSimulation and plotLongSignal
bool rrBoxConditionShort = enableSimulation and plotShortSignal

// ============================================================================
// TRADE CREATION
// ============================================================================
if enableSimulation and plotLongSignal
    positionDirection    := 1
    simulatedEntry       := close
    simulatedSL          := simulatedEntry - stopLossDistance
    simulatedTP          := simulatedEntry + takeProfitDistance
    simulatedEntryOffset := entryLineOffset
    simulatedEntryBar    := bar_index

    activeTpBox := box.new(left=bar_index, top=simulatedTP, right=bar_index, bottom=simulatedEntry, xloc=xloc.bar_index, border_color=color.new(CLR_CYAN, 0), bgcolor=color.new(CLR_CYAN, BOX_TRANSPARENCY), border_width=1)
    activeSlBox := box.new(left=bar_index, top=simulatedEntry, right=bar_index, bottom=simulatedSL, xloc=xloc.bar_index, border_color=color.new(CLR_RED, 0), bgcolor=color.new(CLR_RED, BOX_TRANSPARENCY), border_width=1)
    activeEntryZone := box.new(left=bar_index, top=simulatedEntry + entryZoneHalf, right=bar_index, bottom=simulatedEntry - entryZoneHalf, xloc=xloc.bar_index, border_color=color.new(CLR_CYAN, 40), bgcolor=color.new(CLR_CYAN, 92), border_width=1)

    if showEntryLine
        activeEntryLine1 := line.new(x1=bar_index, y1=simulatedEntry, x2=bar_index, y2=simulatedEntry, xloc=xloc.bar_index, extend=extend.none, color=color.new(CLR_CYAN, 0), width=1, style=line.style_solid)
        activeEntryLine2 := line.new(x1=bar_index, y1=simulatedEntry + simulatedEntryOffset, x2=bar_index, y2=simulatedEntry + simulatedEntryOffset, xloc=xloc.bar_index, extend=extend.none, color=color.new(CLR_CYAN, 70), width=1, style=line.style_dotted)

    activeInvalidationLine := line.new(x1=bar_index, y1=simulatedSL, x2=bar_index, y2=simulatedSL, xloc=xloc.bar_index, extend=extend.none, color=color.new(CLR_RED, 0), width=1, style=line.style_dashed)
    activeProjectionLine   := line.new(x1=bar_index, y1=simulatedEntry, x2=bar_index + 6, y2=simulatedEntry + ((simulatedTP - simulatedEntry) * 0.22), xloc=xloc.bar_index, extend=extend.none, color=color.new(CLR_GREEN, 15), width=1, style=line.style_dotted)
    f_push_line_prune(pathLines, activeProjectionLine, 60)

    activeEntryTag        := label.new(bar_index, simulatedEntry, "ENTRY", xloc=xloc.bar_index, style=label.style_label_left, color=CLR_CYAN_SOFT, textcolor=CLR_TEXT, size=size.tiny)
    activeTpTag           := label.new(bar_index, simulatedTP, "TARGET " + f_fmt(takeProfitRR) + "R", xloc=xloc.bar_index, style=label.style_label_left, color=CLR_GREEN_SOFT, textcolor=CLR_TEXT, size=size.tiny)
    activeSlTag           := label.new(bar_index, simulatedSL, "ATR STOP", xloc=xloc.bar_index, style=label.style_label_left, color=CLR_RED_SOFT, textcolor=CLR_TEXT, size=size.tiny)
    activeInvalidationTag := label.new(bar_index, simulatedSL, "INVALIDATION", xloc=xloc.bar_index, style=label.style_label_left, color=CLR_RED_SOFT, textcolor=CLR_TEXT, size=size.tiny)

if enableSimulation and plotShortSignal
    positionDirection    := -1
    simulatedEntry       := close
    simulatedSL          := simulatedEntry + stopLossDistance
    simulatedTP          := simulatedEntry - takeProfitDistance
    simulatedEntryOffset := entryLineOffset
    simulatedEntryBar    := bar_index

    activeTpBox := box.new(left=bar_index, top=simulatedEntry, right=bar_index, bottom=simulatedTP, xloc=xloc.bar_index, border_color=color.new(CLR_CYAN, 0), bgcolor=color.new(CLR_CYAN, BOX_TRANSPARENCY), border_width=1)
    activeSlBox := box.new(left=bar_index, top=simulatedSL, right=bar_index, bottom=simulatedEntry, xloc=xloc.bar_index, border_color=color.new(CLR_RED, 0), bgcolor=color.new(CLR_RED, BOX_TRANSPARENCY), border_width=1)
    activeEntryZone := box.new(left=bar_index, top=simulatedEntry + entryZoneHalf, right=bar_index, bottom=simulatedEntry - entryZoneHalf, xloc=xloc.bar_index, border_color=color.new(CLR_CYAN, 40), bgcolor=color.new(CLR_CYAN, 92), border_width=1)

    if showEntryLine
        activeEntryLine1 := line.new(x1=bar_index, y1=simulatedEntry, x2=bar_index, y2=simulatedEntry, xloc=xloc.bar_index, extend=extend.none, color=color.new(CLR_CYAN, 0), width=1, style=line.style_solid)
        activeEntryLine2 := line.new(x1=bar_index, y1=simulatedEntry - simulatedEntryOffset, x2=bar_index, y2=simulatedEntry - simulatedEntryOffset, xloc=xloc.bar_index, extend=extend.none, color=color.new(CLR_CYAN, 70), width=1, style=line.style_dotted)

    activeInvalidationLine := line.new(x1=bar_index, y1=simulatedSL, x2=bar_index, y2=simulatedSL, xloc=xloc.bar_index, extend=extend.none, color=color.new(CLR_RED, 0), width=1, style=line.style_dashed)
    activeProjectionLine   := line.new(x1=bar_index, y1=simulatedEntry, x2=bar_index + 6, y2=simulatedEntry - ((simulatedEntry - simulatedTP) * 0.22), xloc=xloc.bar_index, extend=extend.none, color=color.new(CLR_RED, 15), width=1, style=line.style_dotted)
    f_push_line_prune(pathLines, activeProjectionLine, 60)

    activeEntryTag        := label.new(bar_index, simulatedEntry, "ENTRY", xloc=xloc.bar_index, style=label.style_label_left, color=CLR_CYAN_SOFT, textcolor=CLR_TEXT, size=size.tiny)
    activeTpTag           := label.new(bar_index, simulatedTP, "TARGET " + f_fmt(takeProfitRR) + "R", xloc=xloc.bar_index, style=label.style_label_left, color=CLR_GREEN_SOFT, textcolor=CLR_TEXT, size=size.tiny)
    activeSlTag           := label.new(bar_index, simulatedSL, "ATR STOP", xloc=xloc.bar_index, style=label.style_label_left, color=CLR_RED_SOFT, textcolor=CLR_TEXT, size=size.tiny)
    activeInvalidationTag := label.new(bar_index, simulatedSL, "INVALIDATION", xloc=xloc.bar_index, style=label.style_label_left, color=CLR_RED_SOFT, textcolor=CLR_TEXT, size=size.tiny)

// ============================================================================
// ACTIVE TRADE VISUAL UPDATE
// ============================================================================
if enableSimulation and tradeIsActive
    if not na(activeTpBox)
        box.set_right(activeTpBox, bar_index)
    if not na(activeSlBox)
        box.set_right(activeSlBox, bar_index)
    if not na(activeEntryZone)
        box.set_right(activeEntryZone, bar_index)

    if not na(activeEntryLine1)
        line.set_x2(activeEntryLine1, bar_index)
        line.set_y2(activeEntryLine1, simulatedEntry)

    if not na(activeEntryLine2)
        float activeOffset = nz(simulatedEntryOffset, entryLineOffset)
        line.set_x2(activeEntryLine2, bar_index)
        line.set_y2(activeEntryLine2, positionDirection == 1 ? simulatedEntry + activeOffset : simulatedEntry - activeOffset)

    if not na(activeInvalidationLine)
        line.set_x2(activeInvalidationLine, bar_index)
        line.set_y2(activeInvalidationLine, simulatedSL)

    if not na(activeEntryTag)
        label.set_x(activeEntryTag, bar_index)
        label.set_y(activeEntryTag, simulatedEntry)

    if not na(activeTpTag)
        label.set_x(activeTpTag, bar_index)
        label.set_y(activeTpTag, simulatedTP)

    if not na(activeSlTag)
        label.set_x(activeSlTag, bar_index)
        label.set_y(activeSlTag, simulatedSL)

    if not na(activeInvalidationTag)
        label.set_x(activeInvalidationTag, bar_index)
        label.set_y(activeInvalidationTag, simulatedSL)

// ============================================================================
// EXIT LOGIC
// ============================================================================
bool exitWin = false
bool exitLoss = false
int  exitDirection = positionDirection

if enableSimulation and tradeIsActive and bar_index > simulatedEntryBar
    if positionDirection == 1
        bool longSlHit = low <= simulatedSL
        bool longTpHit = high >= simulatedTP

        if longSlHit and longTpHit
            exitLoss := true
        else if longSlHit
            exitLoss := true
        else if longTpHit
            exitWin := true

    if positionDirection == -1
        bool shortSlHit = high >= simulatedSL
        bool shortTpHit = low <= simulatedTP

        if shortSlHit and shortTpHit
            exitLoss := true
        else if shortSlHit
            exitLoss := true
        else if shortTpHit
            exitWin := true

if exitWin
    label.new(bar_index, exitDirection == 1 ? high : low, "TP " + f_fmt(takeProfitRR) + "R HIT", xloc=xloc.bar_index, style=exitDirection == 1 ? label.style_label_down : label.style_label_up, color=CLR_GREEN, textcolor=CLR_TEXT, size=f_label_size(signalLabelSize))

    positionDirection      := 0
    simulatedEntry         := na
    simulatedTP            := na
    simulatedSL            := na
    simulatedEntryOffset   := na
    simulatedEntryBar      := na
    activeTpBox            := na
    activeSlBox            := na
    activeEntryZone        := na
    activeEntryLine1       := na
    activeEntryLine2       := na
    activeInvalidationLine := na
    activeProjectionLine   := na
    activeEntryTag         := na
    activeTpTag            := na
    activeSlTag            := na
    activeInvalidationTag  := na

if exitLoss
    lastLossBar := bar_index

    if showStoppedTrades
        label.new(bar_index, exitDirection == 1 ? low : high, "ATR SL HIT", xloc=xloc.bar_index, style=exitDirection == 1 ? label.style_label_up : label.style_label_down, color=CLR_RED, textcolor=CLR_TEXT, size=f_label_size(signalLabelSize))
    else
        if not na(activeTpBox)
            box.delete(activeTpBox)
        if not na(activeSlBox)
            box.delete(activeSlBox)
        if not na(activeEntryZone)
            box.delete(activeEntryZone)
        if not na(activeEntryLine1)
            line.delete(activeEntryLine1)
        if not na(activeEntryLine2)
            line.delete(activeEntryLine2)
        if not na(activeInvalidationLine)
            line.delete(activeInvalidationLine)
        if not na(activeProjectionLine)
            line.delete(activeProjectionLine)
        if not na(activeEntryTag)
            label.delete(activeEntryTag)
        if not na(activeTpTag)
            label.delete(activeTpTag)
        if not na(activeSlTag)
            label.delete(activeSlTag)
        if not na(activeInvalidationTag)
            label.delete(activeInvalidationTag)

    positionDirection      := 0
    simulatedEntry         := na
    simulatedTP            := na
    simulatedSL            := na
    simulatedEntryOffset   := na
    simulatedEntryBar      := na
    activeTpBox            := na
    activeSlBox            := na
    activeEntryZone        := na
    activeEntryLine1       := na
    activeEntryLine2       := na
    activeInvalidationLine := na
    activeProjectionLine   := na
    activeEntryTag         := na
    activeTpTag            := na
    activeSlTag            := na
    activeInvalidationTag  := na

// ============================================================================
// PANEL STATES
// ============================================================================
string simulationState = enableSimulation ? "ON" : "OFF"
string riskModelText = "SL " + f_fmt(stopLossAtrMultiplier) + " ATR / TP " + f_fmt(takeProfitRR) + "R"

string biasText =
     longTrendOk ? "BULLISH" :
     shortTrendOk ? "BEARISH" :
     "NEUTRAL"

color biasColor =
     longTrendOk ? CLR_GREEN :
     shortTrendOk ? CLR_RED :
     CLR_SILVER

string trendStateText =
     validBuyBreakout ? "BULL BREAKOUT DETECTED" :
     validSellBreakout ? "BEAR BREAKOUT DETECTED" :
     bullWindowOpen and pendingBullRetestDone ? "BULL RETEST CONFIRMED" :
     bearWindowOpen and pendingBearRetestDone ? "BEAR RETEST CONFIRMED" :
     bullWindowOpen ? "BULL BREAKOUT PENDING" :
     bearWindowOpen ? "BEAR BREAKOUT PENDING" :
     "IDLE"

color trendStateColor =
     validBuyBreakout ? CLR_GREEN :
     validSellBreakout ? CLR_RED :
     bullWindowOpen or bearWindowOpen ? CLR_CYAN :
     CLR_MUTED

string signalStateText =
     plotLongSignal ? "BUY CONFIRMED" :
     plotShortSignal ? "SELL CONFIRMED" :
     tradeIsActive ? "TRADE ACTIVE" :
     "WAITING"

color signalStateColor =
     plotLongSignal ? CLR_GREEN :
     plotShortSignal ? CLR_RED :
     tradeIsActive ? CLR_CYAN :
     CLR_MUTED

float panelScore =
     bullWindowOpen ? nz(pendingBullBaseScore, 0.0) :
     bearWindowOpen ? nz(pendingBearBaseScore, 0.0) :
     nz(lastSignalScore, 0.0)

color panelScoreColor = f_score_color(panelScore)

string riskStateText =
     not riskModelReady ? "WARMING UP" :
     atrPct >= 0.80 ? "DEFENSIVE" :
     atrPct >= 0.35 ? "CONTROLLED" :
     "LOW VOL"

color riskStateColor =
     not riskModelReady ? CLR_MUTED :
     atrPct >= 0.80 ? CLR_RED :
     atrPct >= 0.35 ? CLR_CYAN :
     CLR_GREEN

string chopStateText =
     not useChopFilter ? "OFF" :
     chopFilterOk ? "TRENDING " + f_fmt(adxValue) : "CHOPPY " + f_fmt(adxValue)

color chopStateColor =
     not useChopFilter ? CLR_MUTED :
     chopFilterOk ? CLR_GREEN :
     CLR_RED

// ============================================================================
// SIGNAL VISUALS
// ============================================================================
plotshape(validBuyBreakout,  title="Bull Breakout", style=shape.circle, location=location.belowbar, color=color.new(CLR_CYAN, 0), size=size.tiny)
plotshape(validSellBreakout, title="Bear Breakout", style=shape.circle, location=location.abovebar, color=color.new(CLR_CYAN, 0), size=size.tiny)

plotshape(plotLongSignal,  title="Buy Signal Ring", style=shape.circle, location=location.belowbar, color=color.new(CLR_CYAN, 0), size=size.small)
plotshape(plotShortSignal, title="Sell Signal Ring", style=shape.circle, location=location.abovebar, color=color.new(CLR_CYAN, 0), size=size.small)

plotshape(plotLongSignal,  title="Buy Signal Marker", style=shape.triangleup, location=location.belowbar, color=color.new(CLR_GREEN, 0), size=size.tiny)
plotshape(plotShortSignal, title="Sell Signal Marker", style=shape.triangledown, location=location.abovebar, color=color.new(CLR_RED, 0), size=size.tiny)

if showSignalLabels and plotLongSignal
    label.new(bar_index, low, "BUY", xloc=xloc.bar_index, style=label.style_label_up, color=CLR_GREEN_SOFT, textcolor=CLR_TEXT, size=f_label_size(signalLabelSize))

if showSignalLabels and plotShortSignal
    label.new(bar_index, high, "SELL", xloc=xloc.bar_index, style=label.style_label_down, color=CLR_RED_SOFT, textcolor=CLR_TEXT, size=f_label_size(signalLabelSize))

barcolor(
     showSignalBarColors ?
     plotLongSignal ? color.new(CLR_GREEN, 0) :
     plotShortSignal ? color.new(CLR_RED, 0) :
     na
     : na
)

// ============================================================================
// STATUS PANEL
// ============================================================================
var table panel = table.new(f_table_position(panelPosition), 2, 11, border_width=1, border_color=CLR_BORDER)

if barstate.islast
    if showStatusPanel
        table.cell(panel, 0, 0, "TREND CONTINUATION", bgcolor=CLR_PANEL_ALT, text_color=CLR_TEXT)
        table.cell(panel, 1, 0, "STATUS PANEL", bgcolor=CLR_PANEL_ALT, text_color=CLR_GOLD)

        table.cell(panel, 0, 1, "Simulation", bgcolor=CLR_PANEL, text_color=CLR_MUTED)
        table.cell(panel, 1, 1, simulationState, bgcolor=CLR_PANEL, text_color=CLR_CYAN)

        table.cell(panel, 0, 2, "Risk Model", bgcolor=CLR_BG, text_color=CLR_TEXT)
        table.cell(panel, 1, 2, riskModelText, bgcolor=CLR_BG, text_color=CLR_GOLD)

        table.cell(panel, 0, 3, "Bias", bgcolor=CLR_BG, text_color=CLR_TEXT)
        table.cell(panel, 1, 3, biasText, bgcolor=CLR_BG, text_color=biasColor)

        table.cell(panel, 0, 4, "Trend State", bgcolor=CLR_BG, text_color=CLR_TEXT)
        table.cell(panel, 1, 4, trendStateText, bgcolor=CLR_BG, text_color=trendStateColor)

        table.cell(panel, 0, 5, "Signal State", bgcolor=CLR_BG, text_color=CLR_TEXT)
        table.cell(panel, 1, 5, signalStateText, bgcolor=CLR_BG, text_color=signalStateColor)

        table.cell(panel, 0, 6, "Quality Score", bgcolor=CLR_BG, text_color=CLR_TEXT)
        table.cell(panel, 1, 6, f_fmt(panelScore), bgcolor=CLR_BG, text_color=panelScoreColor)

        table.cell(panel, 0, 7, "Volatility", bgcolor=CLR_BG, text_color=CLR_TEXT)
        table.cell(panel, 1, 7, volatilityState, bgcolor=CLR_BG, text_color=volatilityColor)

        table.cell(panel, 0, 8, "Session", bgcolor=CLR_BG, text_color=CLR_TEXT)
        table.cell(panel, 1, 8, sessionState, bgcolor=CLR_BG, text_color=sessionColor)

        table.cell(panel, 0, 9, "Risk State", bgcolor=CLR_BG, text_color=CLR_TEXT)
        table.cell(panel, 1, 9, riskStateText, bgcolor=CLR_BG, text_color=riskStateColor)

        table.cell(panel, 0, 10, "Chop Filter", bgcolor=CLR_BG, text_color=CLR_TEXT)
        table.cell(panel, 1, 10, chopStateText, bgcolor=CLR_BG, text_color=chopStateColor)
    else
        f_panel_clear(panel)

// ============================================================================
// ALERTS
// ============================================================================
alertcondition(plotLongSignal,  title="Bullish Trend Continuation", message="Bullish trend continuation breakout confirmed")
alertcondition(plotShortSignal, title="Bearish Trend Continuation", message="Bearish trend continuation breakout confirmed")

// ============================================================================
// DATA WINDOW
// ============================================================================
plot(adxValue, "ADX", display=display.data_window)
plot(chopValue, "Choppiness Index", display=display.data_window)

// Overlay these two pairs on any bar to confirm the R:R box gate and the
// BUY/SELL label gate always move together -- proves the label-without-box
// bars you see on the chart are an object-pruning artifact, not a logic gate.
plot(rrBoxConditionLong ? 1.0 : 0.0, "RR Box Condition Long", display=display.data_window)
plot(plotLongSignal ? 1.0 : 0.0, "BUY Label Condition (plotLongSignal)", display=display.data_window)
plot(rrBoxConditionShort ? 1.0 : 0.0, "RR Box Condition Short", display=display.data_window)
plot(plotShortSignal ? 1.0 : 0.0, "SELL Label Condition (plotShortSignal)", display=display.data_window)

Overview

Recent swing highs and lows are stored as reference levels, and a breakout needs a qualifying candle in terms of body, opposing wick and range versus ATR. An optional retest of the broken level is required before confirmation, with cooldown bars between signals. A chop filter uses ADX and the choppiness index. An internal simulation projects an ATR stop and a reward-to-risk target as a chart review tool.

Details

Published by

rosijay17-cmd on GitHub

Draws

On the price chart

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
๐Ÿ“ Trend EMA Lengthint50trendEmaLength
โฑ๏ธ Higher Timeframetimeframe60htfTimeframe
๐Ÿงญ HTF Fast EMA Lengthint20htfFastLength
๐Ÿงญ HTF Slow EMA Lengthint50htfSlowLength
๐ŸŒช๏ธ Enable Chop FilterbooltrueuseChopFilter
๐Ÿ“ ADX Lengthint14adxLen
๐Ÿ“ˆ Minimum ADXfloat20.0adxMinimum
๐Ÿ“ Choppiness Index Lengthint14chopLen
๐Ÿ“‰ Maximum Choppiness Indexfloat61.8chopMaximum
๐Ÿงฑ Swing Pivot Lengthint5pivotLength
๐Ÿ“š Stored Reference Levelsint20storedLevels
๐Ÿ“ Minimum Breakout Distance / ATRfloat0.10minBreakoutDistanceAtr
Show all 37 settings
SettingTypeDefaultVariable
๐Ÿ” Breakout RulestringClose Beyond LevelbreakoutRule
๐Ÿ•ฏ๏ธ Maximum Opposing Wick %float0.35maxWickPercent
๐Ÿ“ฆ Minimum Body %float0.50minBodyPercent
๐Ÿ“ Minimum Candle Range / ATRfloat0.20minCandleRangeAtr
๐ŸŸข Long Confirmation Must Be BullishbooltruerequireBullishBody
๐Ÿ”ด Short Confirmation Must Be BearishbooltruerequireBearishBody
โฑ๏ธ Max Bars After Breakout For Confirmationint2confirmationWindow
โ†ฉ๏ธ Require Pullback / Retest Before ConfirmationbooltruerequirePullbackRetest
๐Ÿ“ Retest Tolerance / ATRfloat0.35retestToleranceAtr
๐ŸงŠ Cooldown Bars Between Signalsint10signalCooldownBars
๐ŸงŠ Extra Cooldown After Stop-Outint20postLossCooldownBars
๐Ÿงช Enable Internal Trade SimulationbooltrueenableSimulation
๐Ÿ“ ATR Lengthint14atrLength
๐Ÿ›‘ Stop Loss ATR Multiplierfloat1.50stopLossAtrMultiplier
๐ŸŽฏ Take Profit RRstring3 RRtakeProfitRRInput
๐Ÿšง Block New Signals While Trade Is ActivebooltrueblockSignalsInTrade
๐Ÿ‘๏ธ Show Stopped TradesboolfalseshowStoppedTrades
๐Ÿท๏ธ Show Signal LabelsbooltrueshowSignalLabels
โšช Show Entry LinebooltrueshowEntryLine
๐Ÿ“Š Show Status PanelbooltrueshowStatusPanel
๐ŸŽจ Color Signal BarsbooltrueshowSignalBarColors
๐Ÿงฒ Show Breakout GuidesbooltrueshowBreakoutGuides
โžก๏ธ Guide Extension Barsint20guideExtendBars
๐Ÿ”  Signal Label SizestringNormalsignalLabelSize
๐Ÿ“ Panel PositionstringBottom RightpanelPosition
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

Sideway Zones Live

by dearvn
257 stars

Sideway Zones Live is an overlay indicator that estimates inner and outer sideways bands from recent highs and lows with a rational quadratic kernel. It tracks swing pivots over a loopback period and counts bars that remain between the outer high and low. Afte

Indicator v5 TrendSupport & resistance
Updated 6 weeks ago ยท None stated GitHub repository of Sideway Zones Live
56 stars

This indicator calculates dynamic upper and lower trendlines from pivot highs and pivot lows across a specified bar length. Trendline slopes are derived using ATR, standard deviation, or linear regression methods. It plots trendlines and breakout signal labels

Indicator v5 TrendSupport & resistance Repaints
Updated 2 months ago ยท None stated GitHub repository of Trendlines With Breaks [LUX]

Reversal Breakout System

by TamaraKGreen
46 stars

This indicator detects inside candles inside larger parent candles on the price chart. It plots colored boxes around inside and parent candles, fills them with user-adjustable transparency, draws Fibonacci retracement lines and mother candle range lines, and d

Indicator v6 Candlestick patternsBreakout
Updated 7 weeks ago ยท None stated GitHub repository of Reversal Breakout System
24 stars

This indicator maps Smart Money Concepts (SMC) market structure directly onto the price chart. It detects internal and swing structure breaks (BOS and CHoCH), tracks bullish and bearish order blocks, identifies fair value gaps (FVG), highlights equal highs and

Indicator v5 SMC / ICT / price actionSupport & resistance Repaints
Updated 11 days ago ยท Custom licence GitHub repository of Smart Money Concepts [LuxAlgo] Test B

The Market Tradability Engine calculates multi-factor tradability, balance, compression, and direction scores to evaluate market regime quality. It classifies market conditions into eight distinct states, including Balance, Compression, Ready, Breakout, Trend,

Indicator v6 TrendVolatility & bands
Updated 2 days ago ยท MIT GitHub repository of Market Tradability Engine [WavesUnchained]

This indicator tracks swing highs and swing lows to generate trend-following trailing stop levels on the price chart. When the closing price breaks above the previous swing high, the system establishes a bullish state using the recent swing low as a trailing s

Indicator v4 TrendBreakout
Updated 4 years ago ยท None stated GitHub repository of Accurate Swing Trading System

More Trend scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers