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.
1Copy the scriptPress Copy script above, or the copy button on the code below.
2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
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.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
๐ Trend EMA Length
int
50
trendEmaLength
โฑ๏ธ Higher Timeframe
timeframe
60
htfTimeframe
๐งญ HTF Fast EMA Length
int
20
htfFastLength
๐งญ HTF Slow EMA Length
int
50
htfSlowLength
๐ช๏ธ Enable Chop Filter
bool
true
useChopFilter
๐ ADX Length
int
14
adxLen
๐ Minimum ADX
float
20.0
adxMinimum
๐ Choppiness Index Length
int
14
chopLen
๐ Maximum Choppiness Index
float
61.8
chopMaximum
๐งฑ Swing Pivot Length
int
5
pivotLength
๐ Stored Reference Levels
int
20
storedLevels
๐ Minimum Breakout Distance / ATR
float
0.10
minBreakoutDistanceAtr
Show all 37 settings
Setting
Type
Default
Variable
๐ Breakout Rule
string
Close Beyond Level
breakoutRule
๐ฏ๏ธ Maximum Opposing Wick %
float
0.35
maxWickPercent
๐ฆ Minimum Body %
float
0.50
minBodyPercent
๐ Minimum Candle Range / ATR
float
0.20
minCandleRangeAtr
๐ข Long Confirmation Must Be Bullish
bool
true
requireBullishBody
๐ด Short Confirmation Must Be Bearish
bool
true
requireBearishBody
โฑ๏ธ Max Bars After Breakout For Confirmation
int
2
confirmationWindow
โฉ๏ธ Require Pullback / Retest Before Confirmation
bool
true
requirePullbackRetest
๐ Retest Tolerance / ATR
float
0.35
retestToleranceAtr
๐ง Cooldown Bars Between Signals
int
10
signalCooldownBars
๐ง Extra Cooldown After Stop-Out
int
20
postLossCooldownBars
๐งช Enable Internal Trade Simulation
bool
true
enableSimulation
๐ ATR Length
int
14
atrLength
๐ Stop Loss ATR Multiplier
float
1.50
stopLossAtrMultiplier
๐ฏ Take Profit RR
string
3 RR
takeProfitRRInput
๐ง Block New Signals While Trade Is Active
bool
true
blockSignalsInTrade
๐๏ธ Show Stopped Trades
bool
false
showStoppedTrades
๐ท๏ธ Show Signal Labels
bool
true
showSignalLabels
โช Show Entry Line
bool
true
showEntryLine
๐ Show Status Panel
bool
true
showStatusPanel
๐จ Color Signal Bars
bool
true
showSignalBarColors
๐งฒ Show Breakout Guides
bool
true
showBreakoutGuides
โก๏ธ Guide Extension Bars
int
20
guideExtendBars
๐ Signal Label Size
string
Normal
signalLabelSize
๐ Panel Position
string
Bottom Right
panelPosition
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.
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
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
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
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
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,
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