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
3,083 lines of Pine Script v6 from pine/TheStratSuite_v2.2.6-split.pine, published under MPL-2.0. Keep the author's credit and licence lines when you reuse it.
TheStratSuite_v2.2.6-split.pine
//@version=6
// TheStrat Suite - v2.2.6-split
//
// Multi-timeframe price action indicator implementing TheStrat methodology.
// Detects key patterns including inside bars, hammers, shooters, and Failed 2s
// (Range Reclaims) with intelligent signal consolidation and extensive customization.
//
// Features:
// - 6 configurable timeframes with independent settings
// - Pattern detection: Inside bars, Hammers, Shooters, Failed 2s, Outside bars
// - Magnitude and Exhaustion level targets
// - Full Timeframe Continuity (FTFC) filtering
// - Domino detection (consecutive inside bars across timeframes)
// - Preview mode for planning ahead before market open
// - Consolidated alerts with emoji indicators
// - Data table showing current state across all timeframes
//
// For setup guides and documentation, visit: thestratsuite.com
indicator("TheStrat Suite - v2.2.6-split", shorttitle = "TheStrat Suite", overlay = true, max_labels_count = 200, max_lines_count = 200, max_boxes_count = 200, max_bars_back = 200)
// ============================================================================
// SECTION 1: HELPER FUNCTIONS
// ============================================================================
getTablePos(pos) =>
result = switch pos
"Bottom Center" => position.bottom_center
"Bottom Left" => position.bottom_left
"Bottom Right" => position.bottom_right
"Middle Center" => position.middle_center
"Middle Left" => position.middle_left
"Middle Right" => position.middle_right
"Top Center" => position.top_center
"Top Left" => position.top_left
"Top Right" => position.top_right
=> position.bottom_right
result
getSize(sizeStr) =>
result = switch sizeStr
"Tiny" => size.tiny
"Small" => size.small
"Normal" => size.normal
"Large" => size.large
=> size.small
result
getTimeframeLabel(tf) =>
result = switch tf
"1" => "1m"
"3" => "3m"
"5" => "5m"
"15" => "15m"
"30" => "30m"
"60" => "1H"
"120" => "2H"
"240" => "4H"
"480" => "8H"
"720" => "12H"
"D" => "D"
"1d" => "D"
"W" => "W"
"1W" => "W"
"M" => "M"
"1M" => "M"
"3M" => "3M"
"6M" => "6M"
"12M" => "12M"
=> tf
result
getLineStyle(style) =>
style == "Solid" ? line.style_solid : style == "Dashed" ? line.style_dashed : line.style_dotted
// Update an existing line in-place or create a new one. Returns the line reference.
updateOrCreateLine(line ref, int x1, float y1, int x2, float y2, color c, int w, string style) =>
if na(ref)
line.new(x1, y1, x2, y2, color = c, width = w, style = style, xloc = xloc.bar_time)
else
line.set_xy1(ref, x1, y1)
line.set_xy2(ref, x2, y2)
line.set_color(ref, c)
line.set_width(ref, w)
line.set_style(ref, style)
ref
// Update an existing box in-place or create a new one. Returns the box reference.
updateOrCreateBox(box ref, int x1, float y1, int x2, float y2, color bgc, color borderc) =>
if na(ref)
box.new(x1, y1, x2, y2, bgcolor = bgc, border_color = borderc, xloc = xloc.bar_time)
else
box.set_lefttop(ref, x1, y1)
box.set_rightbottom(ref, x2, y2)
box.set_bgcolor(ref, bgc)
box.set_border_color(ref, borderc)
ref
// Delete a line if it exists, return na
deleteLine(line ref) =>
if not na(ref)
line.delete(ref)
line(na)
// Delete a box if it exists, return na
deleteBox(box ref) =>
if not na(ref)
box.delete(ref)
box(na)
var chartTfSeconds = timeframe.in_seconds(timeframe.period)
// ============================================================================
// SECTION 2: INPUTS
// ============================================================================
// -------------------- PRESET --------------------
grpPreset = "Timeframe Preset"
tfPreset = input.string("TheStrat Classic", "Preset", options = ["Custom", "TheStrat Classic", "Scalp", "Day Trade", "Futures/Crypto", "Swing Trade", "Investing"], group = grpPreset, tooltip = "Select a timeframe preset to auto-configure timeframes and line widths. When set to anything other than Custom, individual Timeframe 1-6 settings are ignored. You must select Custom to use your own timeframe configuration.")
labelStyleMode = input.string("TheStrat", "Label Style", options = ["TheStrat", "Universal"], group = grpPreset, tooltip = "TheStrat: standard Strat combo notation (e.g. 2d-1-2u HAM). Universal: plain language labels like REVERSAL, CONTINUATION, INSIDE, and OUTSIDE with diamond for hammer/shooter confidence. Designed for traders who are new to TheStrat or prefer a simplified view.")
simpleLabelMode = labelStyleMode == "Universal"
customTFs = tfPreset == "Custom" // FIX MOD-3: true only when no preset is active; gates the Timeframe rows' `active`
// -------------------- TIMEFRAMES --------------------
// FIX MOD-3: every Timeframe row is greyed out (active = customTFs) while a preset is selected,
// since presets override these. Direct UX signal that the manual config is inert under a preset.
grpTFs = "Timeframes"
tf1Enabled = input.bool(false, "Timeframe 1", inline = "tf1a", group = grpTFs, active = customTFs)
tf1 = input.timeframe("15", "", inline = "tf1a", group = grpTFs, active = customTFs)
tf1LineWidth = input.int(1, "Width", minval = 1, maxval = 5, inline = "tf1b", group = grpTFs, active = customTFs)
tf1ShowOpen = input.bool(false, "Open", inline = "tf1b", group = grpTFs, active = customTFs)
tf2Enabled = input.bool(false, "Timeframe 2", inline = "tf2a", group = grpTFs, active = customTFs)
tf2 = input.timeframe("30", "", inline = "tf2a", group = grpTFs, active = customTFs)
tf2LineWidth = input.int(1, "Width", minval = 1, maxval = 5, inline = "tf2b", group = grpTFs, active = customTFs)
tf2ShowOpen = input.bool(false, "Open", inline = "tf2b", group = grpTFs, active = customTFs)
tf3Enabled = input.bool(false, "Timeframe 3", inline = "tf3a", group = grpTFs, active = customTFs)
tf3 = input.timeframe("60", "", inline = "tf3a", group = grpTFs, active = customTFs)
tf3LineWidth = input.int(1, "Width", minval = 1, maxval = 5, inline = "tf3b", group = grpTFs, active = customTFs)
tf3ShowOpen = input.bool(false, "Open", inline = "tf3b", group = grpTFs, active = customTFs)
tf4Enabled = input.bool(false, "Timeframe 4", inline = "tf4a", group = grpTFs, active = customTFs)
tf4 = input.timeframe("D", "", inline = "tf4a", group = grpTFs, active = customTFs)
tf4LineWidth = input.int(2, "Width", minval = 1, maxval = 5, inline = "tf4b", group = grpTFs, active = customTFs)
tf4ShowOpen = input.bool(false, "Open", inline = "tf4b", group = grpTFs, active = customTFs)
tf5Enabled = input.bool(false, "Timeframe 5", inline = "tf5a", group = grpTFs, active = customTFs)
tf5 = input.timeframe("W", "", inline = "tf5a", group = grpTFs, active = customTFs)
tf5LineWidth = input.int(3, "Width", minval = 1, maxval = 5, inline = "tf5b", group = grpTFs, active = customTFs)
tf5ShowOpen = input.bool(false, "Open", inline = "tf5b", group = grpTFs, active = customTFs)
tf6Enabled = input.bool(false, "Timeframe 6", inline = "tf6a", group = grpTFs, active = customTFs)
tf6 = input.timeframe("M", "", inline = "tf6a", group = grpTFs, active = customTFs)
tf6LineWidth = input.int(4, "Width", minval = 1, maxval = 5, inline = "tf6b", group = grpTFs, active = customTFs)
tf6ShowOpen = input.bool(false, "Open", inline = "tf6b", group = grpTFs, active = customTFs)
// Resolve preset overrides
// When preset != Custom, these override the individual TF inputs
usePreset = tfPreset != "Custom"
rTF1 = usePreset ? (tfPreset == "TheStrat Classic" ? "30" : tfPreset == "Scalp" ? "5" : tfPreset == "Day Trade" ? "15" : tfPreset == "Futures/Crypto" ? "60" : tfPreset == "Swing Trade" ? "240" : tfPreset == "Investing" ? "W" : tf1) : tf1
rTF2 = usePreset ? (tfPreset == "TheStrat Classic" ? "60" : tfPreset == "Scalp" ? "15" : tfPreset == "Day Trade" ? "30" : tfPreset == "Futures/Crypto" ? "240" : tfPreset == "Swing Trade" ? "D" : tfPreset == "Investing" ? "M" : tf2) : tf2
rTF3 = usePreset ? (tfPreset == "TheStrat Classic" ? "D" : tfPreset == "Scalp" ? "30" : tfPreset == "Day Trade" ? "60" : tfPreset == "Futures/Crypto" ? "720" : tfPreset == "Swing Trade" ? "W" : tfPreset == "Investing" ? "3M" : tf3) : tf3
rTF4 = usePreset ? (tfPreset == "TheStrat Classic" ? "W" : tfPreset == "Scalp" ? "60" : tfPreset == "Day Trade" ? "240" : tfPreset == "Futures/Crypto" ? "D" : tfPreset == "Swing Trade" ? "M" : tfPreset == "Investing" ? "12M" : tf4) : tf4
rTF5 = usePreset ? (tfPreset == "TheStrat Classic" ? "M" : tfPreset == "Scalp" ? "60" : tfPreset == "Day Trade" ? "D" : tfPreset == "Futures/Crypto" ? "W" : tfPreset == "Swing Trade" ? "M" : tfPreset == "Investing" ? "12M" : tf5) : tf5
rTF6 = usePreset ? (tfPreset == "TheStrat Classic" ? "M" : tfPreset == "Scalp" ? "60" : tfPreset == "Day Trade" ? "D" : tfPreset == "Futures/Crypto" ? "W" : tfPreset == "Swing Trade" ? "M" : tfPreset == "Investing" ? "12M" : tf6) : tf6
rEN1 = usePreset ? true : tf1Enabled
rEN2 = usePreset ? true : tf2Enabled
rEN3 = usePreset ? true : tf3Enabled
rEN4 = usePreset ? true : tf4Enabled
rEN5 = usePreset ? (tfPreset == "TheStrat Classic" or tfPreset == "Futures/Crypto" or tfPreset == "Day Trade") : tf5Enabled
rEN6 = usePreset ? false : tf6Enabled
rLW1 = usePreset ? 1 : tf1LineWidth
rLW2 = usePreset ? (tfPreset == "TheStrat Classic" or tfPreset == "Day Trade" ? 1 : 2) : tf2LineWidth
rLW3 = usePreset ? (tfPreset == "TheStrat Classic" ? 2 : tfPreset == "Futures/Crypto" ? 2 : tfPreset == "Day Trade" ? 2 : 3) : tf3LineWidth
rLW4 = usePreset ? (tfPreset == "TheStrat Classic" ? 3 : tfPreset == "Futures/Crypto" ? 3 : tfPreset == "Day Trade" ? 3 : 4) : tf4LineWidth
rLW5 = usePreset ? 4 : tf5LineWidth
rLW6 = usePreset ? 4 : tf6LineWidth
rSO1 = usePreset ? false : tf1ShowOpen
rSO2 = usePreset ? false : tf2ShowOpen
rSO3 = usePreset ? false : tf3ShowOpen
rSO4 = usePreset ? false : tf4ShowOpen
rSO5 = usePreset ? false : tf5ShowOpen
rSO6 = usePreset ? false : tf6ShowOpen
// -------------------- SIGNALS --------------------
grpSigRev = "Signals - Reversals"
showInsideReversals = input.bool(true, "Inside Reversals |", inline = "s_ir", group = grpSigRev, tooltip = "C1 is inside, CC breaks opposite C2's direction (e.g. 2d-1-2u). The bread-and-butter Strat pattern: price consolidated then reversed.")
insideRevOnlyMomo = input.bool(false, "HAM/SHO", inline = "s_ir", group = grpSigRev, tooltip = "HAM/SHO filter: Require C1 to be a hammer or shooter. These candles tested one side and got rejected, adding conviction that the breakout direction has structural backing.")
insideRevRequireFTFC = input.bool(false, "FTFC", inline = "s_ir", group = grpSigRev, tooltip = "FTFC filter: Require all monitored timeframes to agree on direction. Removes setups that fight the broader structural alignment, keeping only those swimming with the current.")
showAllReversals = input.bool(true, "2-2 Reversals |", inline = "s_2r", group = grpSigRev, tooltip = "Direct reversal with no inside bar pause (e.g. 2d-2u). CC immediately breaks opposite C1. Faster signal but less coiled energy than inside reversals.")
reversalsRequireActionable = input.bool(false, "HAM/SHO", inline = "s_2r", group = grpSigRev, tooltip = "HAM/SHO filter: Require C1 to be a hammer or shooter. These candles tested one side and got rejected, adding conviction that the breakout direction has structural backing.")
reversalsRequireC1F2 = input.bool(false, "F2", inline = "s_2r", group = grpSigRev, tooltip = "F2 filter: Require C1 to be a Failed 2 itself (e.g. F2d-2u). Adds structural confluence: the prior bar already reversed a failed breakout, so trapped traders from that failure fuel the current move.")
reversalsRequireFTFC = input.bool(false, "FTFC", inline = "s_2r", group = grpSigRev, tooltip = "FTFC filter: Require all monitored timeframes to agree on direction. Removes setups that fight the broader structural alignment, keeping only those swimming with the current.")
grpSigCont = "Signals - Continuations"
showInsideContinuations = input.bool(true, "Inside Continuations |", inline = "s_ic", group = grpSigCont, tooltip = "C1 is inside, CC breaks same direction as C2 (e.g. 2u-1-2u). Price paused to consolidate then continued the prior move. Trend continuation with a pullback.")
insideContOnlyMomo = input.bool(false, "HAM/SHO", inline = "s_ic", group = grpSigCont, tooltip = "HAM/SHO filter: Require C1 to be a hammer or shooter. These candles tested one side and got rejected, adding conviction that the breakout direction has structural backing.")
insideContRequireFTFC = input.bool(false, "FTFC", inline = "s_ic", group = grpSigCont, tooltip = "FTFC filter: Require all monitored timeframes to agree on direction. Removes setups that fight the broader structural alignment, keeping only those swimming with the current.")
showContinuations = input.bool(false, "2-2 Continuations |", inline = "s_2c", group = grpSigCont, tooltip = "Sustained momentum with no pause (e.g. 2u-2u). CC keeps pushing in C1's direction. Can be noisy without filters since any two trending bars qualify.")
twoTwoContOnlyMomo = input.bool(false, "HAM/SHO", inline = "s_2c", group = grpSigCont, tooltip = "HAM/SHO filter: Require C1 to be a hammer or shooter. These candles tested one side and got rejected, adding conviction that the breakout direction has structural backing.")
continuationsRequireFTFC = input.bool(false, "FTFC", inline = "s_2c", group = grpSigCont, tooltip = "FTFC filter: Require all monitored timeframes to agree on direction. Removes setups that fight the broader structural alignment, keeping only those swimming with the current.")
grpSigF2 = "Signals - Reclaims"
show_p3_toggle = input.bool(false, "Failing 2s (Range Reclaims) |", inline = "s_f2", group = grpSigF2, tooltip = "Show Range Reclaim signals where price reverses after breaking out. Also known as Failing 2s, Potential 3, or 1-bar Rev Strat.")
p3RequireFTFC = input.bool(false, "FTFC", inline = "s_f2", group = grpSigF2, tooltip = "FTFC filter: Require all monitored timeframes to agree on direction. Removes setups that fight the broader structural alignment, keeping only those swimming with the current.")
grpSigExp = "Signals - Expansions"
showRangeExpansions = input.bool(false, "3-2 Expansions |", inline = "s_3e", group = grpSigExp, tooltip = "C1 was a 3-bar (outside bar), CC commits to one direction (e.g. 3u-2u). After C1 took both sides, CC resolves the indecision. Often powerful when the 3-bar shook out weak hands.")
rangeExpOnlyMomo = input.bool(false, "HAM/SHO", inline = "s_3e", group = grpSigExp, tooltip = "HAM/SHO filter: Require C1 to be a hammer or shooter. A 3-bar that also formed a hammer/shooter tested and rejected one side despite the range expansion, adding directional conviction.")
rangeExpansionsRequireFTFC = input.bool(false, "FTFC", inline = "s_3e", group = grpSigExp, tooltip = "FTFC filter: Require all monitored timeframes to agree on direction. Removes setups that fight the broader structural alignment, keeping only those swimming with the current.")
show3Expansions = input.bool(false, "Outside Bars (3 Exp) |", inline = "s_ob", group = grpSigExp, tooltip = "CC itself is a 3-bar, engulfing C1's entire range. Represents range expansion and volatility. Direction determined by close vs open (3u = bullish, 3d = bearish).")
threeExpRequireFTFC = input.bool(false, "FTFC", inline = "s_ob", group = grpSigExp, tooltip = "FTFC filter: Require all monitored timeframes to agree on direction. Removes setups that fight the broader structural alignment, keeping only those swimming with the current.")
// -------------------- FILTERS --------------------
grpHammerShooter = "Filters - Hammer/Shooter Detection"
logicType = input.string("Broad (Loose)", "Definition", options = ["Broad (Loose)", "Classic", "Pin Bar (Strict)"], group = grpHammerShooter, tooltip = "Broad: relaxed detection based on body position. Classic: small body, long wick (3x body), body in upper/lower third. Pin Bar: stricter wick requirements.")
onlyColor = input.bool(false, "Match Candle Color", group = grpHammerShooter, tooltip = "When enabled, hammers must be green (bullish) and shooters must be red (bearish) to qualify")
grpDomino = "Filters - Domino Setups"
minDominoTFs = input.int(2, "Minimum Timeframes", minval = 2, maxval = 6, group = grpDomino, tooltip = "Minimum number of consecutive inside bar timeframes (with visible signals) to trigger a Domino. Respects your signal filter settings.")
showDominoInTable = input.bool(true, "Show in Data Table", group = grpDomino, tooltip = "Show a Domino row in the Data Table when consecutive inside bars are detected")
grpDirFilter = "Filters - Lead Signal"
enableDirectionalFilter = input.bool(false, "Only Show Signals Following the Lead", group = grpDirFilter, tooltip = "When enabled, only shows signals on lower timeframes that align with the direction of the highest timeframe signal in-force (the Lead). If no higher TF has a signal in-force, all signals are shown.")
// -------------------- MAGNITUDE & EXHAUSTION --------------------
grpMagExh = "Targets - Magnitude & Exhaustion"
showMagnitude = input.bool(true, "Show Magnitude Levels", group = grpMagExh, tooltip = "Show magnitude (C2 high/low) as take-profit targets for actionable signals")
magOnlyWhenInForce = input.bool(false, "Only When In-Force", group = grpMagExh, tooltip = "Only show magnitude levels when the signal has triggered (breakout occurred). Hides targets on potential setups so you only see them when the trade is live.")
showExhaustion = input.bool(true, "Show Exhaustion Levels", group = grpMagExh, tooltip = "Show exhaustion levels (prior swing high/low beyond C2) as extended targets")
exhOnlyWhenInForce = input.bool(false, "Only When In-Force", group = grpMagExh, tooltip = "Only show exhaustion levels when the signal has triggered (breakout occurred). Hides extended targets on potential setups so you only see them when the trade is live.")
exhRequiresMagHit = input.bool(false, "Only After Magnitude Hit", group = grpMagExh, tooltip = "When enabled, exhaustion levels only appear after price has reached the magnitude level")
// Gate for exhaustion search: only scan for pivot-based exhaustion when targets are enabled
showAnyTargets = showMagnitude or showExhaustion
// -------------------- TAKE ACTION WINDOWS --------------------
grpTAW = "Targets - Take Action Windows"
showTakeActionWindows = input.bool(true, "Show Take Action Windows", group = grpTAW, tooltip = "Highlight the zone between trigger and target levels for actionable signals")
tawOnlyWhenInForce = input.bool(true, "Only When In-Force", group = grpTAW, tooltip = "Only show take action windows when the signal has triggered (breakout occurred)")
tawExtendToExhaustion = input.bool(true, "Extend to Exhaustion", group = grpTAW, tooltip = "Extend the take action window to the exhaustion level instead of magnitude")
tawExhOnlyWhenInForce = input.bool(true, "Only When In-Force", group = grpTAW, tooltip = "Only extend to exhaustion when the signal has triggered")
tawFillOpacity = input.int(8, "Fill Opacity", minval = 0, maxval = 100, group = grpTAW, tooltip = "Opacity of the take action window fill (0 = transparent, 100 = solid)")
tawBorderOpacity = input.int(30, "Border Opacity", minval = 0, maxval = 100, group = grpTAW, tooltip = "Opacity of the take action window border")
// -------------------- STOP LEVELS --------------------
grpStops = "Stops - Stop Levels"
showStopLevels = input.bool(false, "Enable", group = grpStops, tooltip = "Show stop loss levels on the opposite side of the trigger. Once a signal goes in force, the stop is locked and persists for the remainder of that period even if the signal is no longer in force.")
stopReference = input.string("CC", "Reference", options = ["CC", "C1"], group = grpStops, tooltip = "CC: stop tracks the breakout bar's opposite extreme in real time (updates as the candle forms, locks when it closes). Tighter risk, invalidated when the breakout candle itself is reversed. C1: stop at the prior bar's opposite side, locked when signal goes in force. Wider risk, invalidated when the full setup range is taken out. Failing 2 signals always use CC regardless of this setting.")
stopBEatMag = input.bool(false, "Break Even at Magnitude", group = grpStops, tooltip = "Move stop to the entry trigger (break even) when price hits the magnitude level. If no magnitude level exists, uses exhaustion as the trigger instead.")
stopBEatExh = input.bool(false, "Break Even at Exhaustion", group = grpStops, tooltip = "Move stop to the entry trigger (break even) when price hits the exhaustion level. If no exhaustion level exists, uses magnitude as the trigger instead.")
stopColorMode = input.string("Opposite Signal", "Color Mode", options = ["Opposite Signal", "Match Signal", "Custom"], group = grpStops, tooltip = "Opposite Signal: bullish stops use the bearish signal color, bearish stops use the bullish signal color. Match Signal: stops match their signal's color. Custom: use the single color below for all stops.")
colorStop = input.color(#FFFFFF, "Custom Color", group = grpStops, tooltip = "Color for stop level lines and labels when Color Mode is set to Custom", active = showStopLevels and stopColorMode == "Custom") // FIX MOD-3: only relevant when stops on AND Custom color mode
// -------------------- DISPLAY - PREVIEW MODE --------------------
grpPreview = "Display - Preview Mode"
previewMode = input.string("Auto", "Preview Mode", options = ["Off", "On", "Auto"], group = grpPreview, tooltip = "Off: never preview. Must be set to Off for Bar Replay mode. On: always preview next period levels. Auto: activates whenever the market is not actively trading, automatically disabled during bar replay. Stocks/ETFs/Options: before 9:30 AM, after 4 PM ET, and weekends. Futures/Forex/Commodities: weekends and the daily 5-6 PM ET maintenance window. Crypto: only activates during extended outages. Also activates during holidays for all asset types.")
// -------------------- DISPLAY - DATA TABLE --------------------
grpTable = "Display - Data Table"
showBarTypeTable = input.bool(true, "Show Data Table", group = grpTable, tooltip = "Show the multi-timeframe data table")
tableMode = input.string("Full (TheStrat)", "Mode", options = ["Full (TheStrat)", "Compact (Universal)"], group = grpTable, tooltip = "Full (TheStrat): Complete table with C2, C1, AS, CC columns using Strat notation. Compact (Universal): Horizontal row of TF labels only. Yellow = inside & above open, Orange = inside & below open, Green/Red = signal in force.")
tablePosition = input.string("Top Right", "Position", options = ["Bottom Center", "Bottom Left", "Bottom Right", "Middle Center", "Middle Left", "Middle Right", "Top Center", "Top Left", "Top Right"], group = grpTable, tooltip = "Position of the data table on the chart")
tableTextSize = input.string("Small", "Text Size", options = ["Tiny", "Small", "Normal", "Large"], group = grpTable, tooltip = "Size of the table text")
useDashSeparator = input.bool(true, "Use Dash Separator", group = grpTable, tooltip = "Separate candle values with a dash (e.g., 2u-2d instead of 2u2d)")
// -------------------- DISPLAY - LABELS --------------------
grpLabels = "Display - Labels"
showTimelineLabels = input.bool(true, "Show Timeline Labels", group = grpLabels, tooltip = "Show labels at the end of each level line, anchored to the line's end time")
labelOffset = input.int(0, "Offset (bars)", minval = 0, maxval = 20, group = grpLabels, tooltip = "Number of bars to offset timeline labels from the line end", active = showTimelineLabels) // FIX MOD-3
showFloatingLabels = input.bool(false, "Show Floating Labels", group = grpLabels, tooltip = "Show labels at a fixed offset from the current bar, keeping them visible on screen as price moves")
floatingLabelOffset = input.int(1, "Offset (bars)", minval = -20, maxval = 50, group = grpLabels, tooltip = "Number of bars to offset floating labels from the current bar (negative = left, positive = right)", active = showFloatingLabels) // FIX MOD-3
showPriceInLabel = input.bool(false, "Show Price in Label", group = grpLabels, tooltip = "Include the price level in each label")
showHighLowInLabel = input.bool(false, "Show H/L in Labels", group = grpLabels, tooltip = "Show 'H' for high levels and 'L' for low levels in labels and alerts")
labelTextSize = input.string("Small", "Text Size", options = ["Tiny", "Small", "Normal", "Large"], group = grpLabels, tooltip = "Size of the label text")
labelBackgroundColor = input.color(#2e2e2e, "Background Color", group = grpLabels, tooltip = "Background color for labels")
labelBackgroundTransparency = input.int(20, "Transparency", minval = 0, maxval = 100, group = grpLabels, tooltip = "Transparency of the label background (0 = solid, 100 = invisible)")
// Auto-preview detection: activate whenever the market is not actively trading
// Equity-like (stocks, ETFs, options, indices, ADRs): outside 9:30 AM - 4:00 PM ET and weekends
// Futures-like (futures, forex, commodities): weekend closure (Fri 5PM - Sun 6PM ET) and daily maintenance (5-6PM ET)
// Crypto: 24/7, only triggers via holiday fallback
// All others: holiday fallback catches unusual closures
chartPeriodMs = timeframe.in_seconds(timeframe.period) * 1000
etHour = hour(timenow, "America/New_York")
etMinute = minute(timenow, "America/New_York")
etDOW = dayofweek(timenow, "America/New_York")
isWeekendDay = etDOW == dayofweek.saturday or etDOW == dayofweek.sunday
// Stocks/ETFs/Options: closed before 9:30 AM, after 4:00 PM, and weekends
isEquityLike = syminfo.type == "stock" or syminfo.type == "fund" or syminfo.type == "dr" or syminfo.type == "index" or syminfo.type == "option"
isStockClosed = isEquityLike and (isWeekendDay or etHour >= 16 or etHour < 9 or (etHour == 9 and etMinute < 30))
// Futures/Forex/Commodities: weekend closure (Fri 5PM through Sun 6PM) and daily maintenance window (5-6PM ET Mon-Thu)
isFuturesLike = syminfo.type == "futures" or syminfo.type == "forex" or syminfo.type == "commodity"
isFuturesWeekendClosed = etDOW == dayofweek.saturday or (etDOW == dayofweek.friday and etHour >= 17) or (etDOW == dayofweek.sunday and etHour < 18)
isFuturesMaintenance = not isWeekendDay and etDOW != dayofweek.friday and etHour >= 17 and etHour < 18
isFuturesClosed = isFuturesLike and (isFuturesWeekendClosed or isFuturesMaintenance)
// Holiday fallback: now is 3x chart period or 4 hours (whichever is larger) past the bar's
// SCHEDULED CLOSE.
// FIX GLUE-1 (2026-07-06): measure from time_close, not time (the open). CME glues holiday
// half-sessions into the next trade date (Fri Jul 3 2026 lives inside Mon Jul 6's bar), so
// the LIVE Monday bar opened with a Thursday-evening stamp and (timenow - time) read ~87h:
// Auto preview engaged during a live session and demoted the forming week/day into C1
// ("weekly levels on the current day", D/W = "?", phantom PREVIEW MODE banner). A live bar's
// scheduled close is always in the future, so this can never fire while a session trades;
// genuinely halted markets still trip it once now exceeds the last scheduled close.
isHolidayClosed = not na(time_close) and (timenow - time_close) > math.max(chartPeriodMs * 3, 4 * 60 * 60 * 1000)
isBarReplay = bar_index < last_bar_index
marketLikelyClosed = barstate.islast and not isBarReplay and (isStockClosed or isFuturesClosed or isHolidayClosed)
previewNextPeriod = previewMode == "On" or (previewMode == "Auto" and marketLikelyClosed)
// FIX HTF-STRADDLE-1 (T1): a chart bar can straddle a higher-TF boundary (e.g. a weekly bar spanning
// a mid-week month/quarter roll). request.security maps each chart bar to the HTF bar in effect at the
// chart bar's OPEN, so the served M/3M/6M/12M data stays one period stale for the rest of that chart
// bar even while the market trades. The preview shift already fixes exactly this problem in its
// market-closed form; this flag arms the same shift for calendar TFs on the live last bar too.
// previewMode = Off still disables everything (preserves the bar-replay workaround).
// FIX HTF-STRADDLE-1: live-straddle window (market open, last bar, not replay; previewMode Off disables).
// RE-ENABLED 2026-07-02 with the corrected session-offset-aware detection (see shouldStraddle above).
bool straddleWindowOK = previewMode != "Off" and barstate.islast and not isBarReplay
// -------------------- STYLE --------------------
grpStyleBullish = "Style - Bullish Levels"
colorBullishActive = input.color(color.green, "Signal", group = grpStyleBullish, inline = "bull1")
colorBullishHit = input.color(#9c9c9c, "Crossed", group = grpStyleBullish, inline = "bull1")
colorBullishPotential = input.color(#ffeb3b, "1 (Inside Bar)", group = grpStyleBullish, inline = "bull2")
colorBullish3Bar = input.color(#089981, "3 (Outside Bar)", group = grpStyleBullish, inline = "bull2")
colorBullishP3 = input.color(#81c784, "Range Reclaim (F2d)", group = grpStyleBullish, tooltip = "Color for bullish Range Reclaim signals (failed bearish breakout)")
grpStyleBearish = "Style - Bearish Levels"
colorBearishActive = input.color(color.red, "Signal", group = grpStyleBearish, inline = "bear1")
colorBearishHit = input.color(#4a4a4a, "Crossed", group = grpStyleBearish, inline = "bear1")
colorBearishPotential = input.color(#ff9800, "1 (Inside Bar)", group = grpStyleBearish, inline = "bear2")
colorBearish3Bar = input.color(#e91e63, "3 (Outside Bar)", group = grpStyleBearish, inline = "bear2")
colorBearishP3 = input.color(#f77c80, "Range Reclaim (F2u)", group = grpStyleBearish, tooltip = "Color for bearish Range Reclaim signals (failed bullish breakout)")
var color COLOR_FAILED_2U = colorBearishP3
var color COLOR_FAILED_2D = colorBullishP3
var color COLOR_3_HIGH = colorBullish3Bar
var color COLOR_3_LOW = colorBearish3Bar
var color COLOR_INSIDE_HIGH = colorBullishPotential
var color COLOR_INSIDE_LOW = colorBearishPotential
var color COLOR_HIT_HIGH = colorBullishHit
var color COLOR_HIT_LOW = colorBearishHit
var color COLOR_BG_BLACK = color.new(color.black, 0)
var color COLOR_BG_GRAY50 = color.new(color.gray, 50)
colorStopHigh = stopColorMode == "Opposite Signal" ? colorBearishActive : stopColorMode == "Match Signal" ? colorBullishActive : colorStop
colorStopLow = stopColorMode == "Opposite Signal" ? colorBullishActive : stopColorMode == "Match Signal" ? colorBearishActive : colorStop
tawFillTransp = 100 - tawFillOpacity
tawBorderTransp = 100 - tawBorderOpacity
tawBullishFill = color.new(colorBullishActive, tawFillTransp)
tawBullishBorder = color.new(colorBullishActive, tawBorderTransp)
tawBearishFill = color.new(colorBearishActive, tawFillTransp)
tawBearishBorder = color.new(colorBearishActive, tawBorderTransp)
// -------------------- ALERTS --------------------
grpAlerts = "Alerts - Detailed"
alertTF1 = input.bool(true, "TF1", inline = "alert_tfs", group = grpAlerts, tooltip = "Select which timeframes to include in the consolidated 'Signal In-Force (Any)' alert. Uncheck a TF to exclude it from alert messages. Per-TF alertconditions (e.g. 'Signal In-Force TF1') are not affected by these checkboxes.")
alertTF2 = input.bool(true, "TF2", inline = "alert_tfs", group = grpAlerts)
alertTF3 = input.bool(true, "TF3", inline = "alert_tfs", group = grpAlerts)
alertTF4 = input.bool(true, "TF4", inline = "alert_tfs", group = grpAlerts)
alertTF5 = input.bool(true, "TF5", inline = "alert_tfs", group = grpAlerts)
alertTF6 = input.bool(true, "TF6", inline = "alert_tfs", group = grpAlerts)
alertShowFTFC = input.bool(false, "Include FTFC", inline = "alert_content", group = grpAlerts, tooltip = "Enrich the consolidated alert message with additional context. Each toggle appends data to the alert when available. Example output: 1H 2d-1-2u HAM @ 542.10 MAG 545.30 EXH 548.20 STOP 538.10 | FTFC Up. Fields that don't exist for a signal (e.g. no magnitude) are omitted automatically.")
alertShowTrigger = input.bool(false, "Trigger", inline = "alert_content", group = grpAlerts)
alertShowMagnitude = input.bool(false, "MAG", inline = "alert_content", group = grpAlerts)
alertShowExhaustion = input.bool(false, "EXH", inline = "alert_content", group = grpAlerts)
alertShowStop = input.bool(false, "STOP", inline = "alert_content", group = grpAlerts)
// -------------------- ADVANCED --------------------
grpAdvExh = "Advanced - Exhaustion Behavior"
exhDisablesInForce = input.bool(false, "Exhaustion Disables 'In Force'", group = grpAdvExh, tooltip = "OFF (default): A signal stays 'in force' even after its targets are hit. The data table continues showing the signal as active, and the Lead Signal filter continues using it for directional bias. ON: Once a signal hits exhaustion (or magnitude if no exhaustion exists), it is no longer considered 'in force.' This frees up the Lead Signal filter to allow reversal setups on lower timeframes. Example: if the Daily has a bullish signal that already hit exhaustion, turning this on lets bearish setups appear on the 15m/1H instead of staying suppressed by a move that may be done.")
exhDisablesFTFC = input.bool(false, "Exhaustion Excludes from FTFC", group = grpAdvExh, tooltip = "OFF (default): All timeframes count toward FTFC regardless of whether their targets have been hit. ON: A timeframe that has hit exhaustion (or magnitude if no exhaustion exists) is removed from the FTFC calculation. This means a timeframe whose move is structurally complete no longer blocks FTFC from flipping. Example: if 5 of 6 timeframes are bearish but the 6th is bullish and already hit its exhaustion, turning this on excludes that exhausted bullish timeframe so FTFC reads as bearish instead of Conflict.")
grpAdvOB = "Advanced - Outside Bars"
show3ExpMagnitude = input.bool(true, "Show Magnitude for Outside Bars", group = grpAdvOB, tooltip = "Show the C2 high/low as a magnitude target for outside bar (3-bar) expansions")
show3ExpExhaustion = input.bool(true, "Show Exhaustion for Outside Bars", group = grpAdvOB, tooltip = "Show exhaustion levels for outside bar (3-bar) expansions")
grpAdvF2 = "Advanced - Failing 2 Detection"
enableFailed2Detection = input.bool(true, "Enable Failing 2 Detection", group = grpAdvF2, tooltip = "When enabled, detects Failing 2s (f2u/f2d) where price reclaims the prior range or crosses back against the breakout direction. When disabled, only shows standard 2u/2d signals.")
failed_2_method = input.string("Reclaim", "Detection Method", options = ["Reclaim", "Open", "Reclaim + Open", "Reclaim OR Open"], group = grpAdvF2, tooltip = "Reclaim: closes back inside C1 range. Open: closes against breakout direction. Reclaim + Open: requires both conditions. Reclaim OR Open: requires one or the other.")
showF2OpenLine = input.bool(false, "Show Failing 2 Open Level", group = grpAdvF2, tooltip = "Show the CC open price as a dotted reference line when a Failing 2 is detected. The open acts as the trigger level for the reclaim.")
grpAdvTable = "Advanced - Data Table"
colorTFWhenInForce = input.bool(true, "Color TF When In-Force", group = grpAdvTable, tooltip = "Highlight the TF column with the CC color when a signal you have enabled is in-force (Full mode only)")
grpAdvTAW = "Advanced - Take Action Windows"
showP3TakeActionWindows = input.bool(true, "Include Failing 2s", group = grpAdvTAW, tooltip = "Highlight the C1 range as a take action zone for Failing 2 (Range Reclaim) setups")
show3ExpTakeActionWindows = input.bool(true, "Include Outside Bars", group = grpAdvTAW, tooltip = "Highlight the zone between trigger and magnitude for outside bar (3-bar) expansions")
grpAdvDomino = "Advanced - Domino Setups"
enableDominoAlerts = input.bool(false, "Include in Consolidated Alerts", group = grpAdvDomino, tooltip = "Include Domino alerts in the main alert() function. You can also set up a dedicated Domino alert using the 'Domino Setup' alertcondition.")
// -------------------- DEBUG --------------------
grpDebug = "Debug"
showDebugPanel = input.bool(false, "Show Debug Panel", group = grpDebug, tooltip = "Show detailed debug information for a selected timeframe to troubleshoot signal logic")
debugTimeframe = input.string("TF1", "Timeframe to Debug", options = ["TF1", "TF2", "TF3", "TF4", "TF5", "TF6"], group = grpDebug, tooltip = "Select which timeframe slot to show debug info for (TF1 = first timeframe in settings, etc.)", active = showDebugPanel) // FIX MOD-3
// ============================================================================
// SECTION 2A: HELPER FUNCTIONS (POST-INPUTS)
// ============================================================================
// Format candle combo string with optional dash separator
// e.g., "2u2d" becomes "2u-2d" when useDashSeparator is true
formatCombo(string combo) =>
if not useDashSeparator or str.length(combo) < 2
combo
else
string result = ""
int i = 0
int len = str.length(combo)
while i < len
char = str.substring(combo, i, i + 1)
// Check if current char is start of a bar type (digit, F, or *)
isBarStart = char == "1" or char == "2" or char == "3" or char == "F" or char == "f" or char == "*"
// Add dash before bar types (except first)
if isBarStart and i > 0
prevChar = str.substring(combo, i - 1, i)
// Dash after completed bar types: "u"/"d" suffixed bars, or standalone "1"/"3"
if prevChar == "u" or prevChar == "d" or prevChar == "1" or prevChar == "2" or prevChar == "3"
result := result + "-"
result := result + char
i := i + 1
result
// ============================================================================
// SECTION 3: USER-DEFINED TYPES
// ============================================================================
type CandleMetrics
float body
float bodyPct
float upperWick
float lowerWick
float upperWickPct
float lowerWickPct
float closeFromHigh
float closeFromLow
float openFromHigh
float openFromLow
float bodyCenterFromHigh
float bodyCenterFromLow
bool isBullish
bool isFlat
type TimeframeData
line highLine
line lowLine
line openLine
line magHighLine
line magLowLine
line exhHighLine
line exhLowLine
box actionWindowBullish
box actionWindowBearish
float prevHigh
float prevLow
float currOpen
float prevMagHigh
float prevMagLow
float exhHighPrice
float exhLowPrice
int prevTime
int prevMagTime
int exhHighTime
int exhLowTime
bool magHighCrossed
bool magLowCrossed
bool exhHighCrossed
bool exhLowCrossed
bool c1IsInside
bool c1IsHammer
bool c1IsShooter
bool c1IsHighContinuation
bool c1IsLowContinuation
line stopHighLine // stop for bullish signal (drawn at C1 low)
line stopLowLine // stop for bearish signal (drawn at C1 high)
bool stopHighTriggered // sticky: bullish signal was in force this period
bool stopLowTriggered // sticky: bearish signal was in force this period
float stopHighPrice // locked stop price for bullish signal
float stopLowPrice // locked stop price for bearish signal
line f2OpenLine // pre-F2: CC open shown as potential trigger when reclaim met but open not crossed
int lastPeriodStart // FIX P0-1: per-slot HTF period-open time (replaces the shared ta.change(time(tf)) buffer)
bool wasPreview = false // FIX P1-d: prior-bar preview state, used to re-latch when preview exits mid-period (default false avoids na() on series bool)
type SimpleLabelData
array<float> prices
array<string> texts
array<color> colors
array<int> times
type LabelPoolT // FIX P1-i: reusable label pool - update-in-place instead of delete+recreate every tick
array<label> items
int used
type LineInfo
float price
int tfSeconds
line lineRef
TimeframeData owner // FIX P1-h: owning slot, so a suppressed line's field can be nulled after delete
int kind // FIX P1-h: which TimeframeData line field this ref came from (see nullSuppressedLine)
type TFRawData
int c1Time
float c1H
float c1L
float c1O
float c1C
int ccTime
float ccO
float ccH
float ccL
float ccC
int c2Time
float c2H
float c2L
float c2O
float c2C
float c3H
float c3L
float c4H
float c4L
float exhHP
int exhHT
float exhLP
int exhLT
bool isPreview
int realPeriodTime // FIX P0-1: unshifted current HTF period-open time, for per-slot new-period detection
type ProcessingResult
float high
float low
float open
float magHigh
float magLow
float exhHigh
float exhLow
color highColor
color lowColor
color openColor
color magHighColor
color magLowColor
color exhHighColor
color exhLowColor
bool drawHigh
bool drawLow
bool drawOpen
bool drawMagHigh
bool drawMagLow
bool drawExhHigh
bool drawExhLow
int time
string tfLabel
bool isF2u
bool isF2d
bool c1Is3
string c3Num
string c2Num
string c1Num
string c1Type
string ccType
bool signalInForceHigh
bool signalInForceLow
float stopHigh // bullish stop price (C1 low)
float stopLow // bearish stop price (C1 high)
bool drawStopHigh
bool drawStopLow
bool preF2u // pre-F2u: CC broke high, reclaimed, close still above open
bool preF2d // pre-F2d: CC broke low, reclaimed, close still below open
float f2OpenPrice // CC open price for pre-F2 line
int ccStartTime // CC candle start time for Domino dedup
bool c1WasF2 // C1 was itself a Failed 2
bool c2WasF2 // C2 was itself a Failed 2
string highStyle // computed style for high trigger line
string lowStyle // computed style for low trigger line
type DebugInfo
string tfLabel
bool c1_was_2u
bool c1_was_2d
bool c1_is_3
bool isHammer
bool isShooter
bool isInside
bool cc_is_2u
bool cc_is_2d
bool cc_is_3
bool cc_broke_high
bool cc_broke_low
bool is_f2u
bool is_f2d
bool pre_f2u
bool pre_f2d
bool ftfc_up
bool ftfc_down
bool is_hammer_reversal
bool is_hammer_momo
bool hammer_shows
bool is_shooter_reversal
bool is_shooter_momo
bool shooter_shows
bool drawHigh
bool drawLow
bool drawMagHigh
bool drawMagLow
bool drawExhHigh
bool drawExhLow
bool force_mag_high
bool force_mag_low
bool mag_high_passes_ftfc
bool mag_low_passes_ftfc
type TFConfig
bool enabled
string tf
int lineWidth
bool showOpen
string debugLabel
var DebugInfo debugData = DebugInfo.new("", false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false)
// ============================================================================
// SECTION 3B: CLEANUP HELPER METHODS
// ============================================================================
// FIX P2-1: removed five unused cleanup methods (clearHighExtras / clearLowExtras /
// clearHighAll / clearLowAll / clearCommon). They had zero reachable call sites - the
// only callers were each other (clearHighAll -> clearHighExtras, etc.), so the whole
// cluster was transitively dead. The live suppress* methods below are unaffected.
// ProcessingResult flag suppression methods
method suppressHighFlags(ProcessingResult this) =>
this.drawHigh := false
this.drawMagHigh := false
this.drawExhHigh := false
this.drawStopHigh := false
method suppressLowFlags(ProcessingResult this) =>
this.drawLow := false
this.drawMagLow := false
this.drawExhLow := false
this.drawStopLow := false
method suppressAllFlags(ProcessingResult this) =>
this.suppressHighFlags()
this.suppressLowFlags()
// ============================================================================
// SECTION 4: PATTERN DETECTION FUNCTIONS
// ============================================================================
calcMetrics(o, h, l, c) =>
float r = h - l
if r == 0
CandleMetrics.new(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, true, true)
else
float bodySize = math.abs(c - o)
float bodyCenter = (o + c) / 2
float upperWick = h - math.max(o, c)
float lowerWick = math.min(o, c) - l
CandleMetrics.new(bodySize, bodySize / r, upperWick, lowerWick, upperWick / r, lowerWick / r, (h - c) / r, (c - l) / r, (h - o) / r, (o - l) / r, (h - bodyCenter) / r, (bodyCenter - l) / r, c > o, false)
isClassicHammer(CandleMetrics m) =>
if m.isFlat
false
else
float wickRatio = m.bodyPct > 0.001 ? (m.lowerWick / m.body) : 0
m.bodyPct <= 0.30 and wickRatio >= 3.0 and m.upperWickPct <= 0.35 and m.bodyCenterFromHigh <= 0.33 and m.closeFromHigh <= 0.25
isClassicShooter(CandleMetrics m) =>
if m.isFlat
false
else
float wickRatio = m.bodyPct > 0.001 ? (m.upperWick / m.body) : 0
m.bodyPct <= 0.30 and wickRatio >= 3.0 and m.lowerWickPct <= 0.35 and m.bodyCenterFromLow <= 0.33 and m.closeFromLow <= 0.25
isPinBarHammer(CandleMetrics m) =>
m.isFlat ? false : m.closeFromHigh <= 0.25 and m.openFromHigh <= 0.25
isPinBarShooter(CandleMetrics m) =>
m.isFlat ? false : m.closeFromLow <= 0.25 and m.openFromLow <= 0.25
isBroadHammer(CandleMetrics m) =>
// FIX BTC-2: strict > 0.50 so a body centered exactly at the range midpoint is NOT a hammer.
// Paired with the strict < in isBroadShooter, a centered doji classifies as neither (correct -
// no directional rejection), instead of satisfying BOTH and drawing a spurious wrong-side line.
m.isFlat ? false : m.openFromLow > 0.50 and m.closeFromLow > 0.50
isBroadShooter(CandleMetrics m) =>
m.isFlat ? false : m.openFromLow < 0.50 and m.closeFromLow < 0.50 // FIX BTC-2: strict < (see isBroadHammer)
isInsideBar(currH, currL, prevH, prevL) =>
currH <= prevH and currL >= prevL
detectPatterns(c1_open, c1_high, c1_low, c1_close) =>
m = calcMetrics(c1_open, c1_high, c1_low, c1_close)
bool rawHammer = false
bool rawShooter = false
if logicType == "Classic"
rawHammer := isClassicHammer(m)
rawShooter := isClassicShooter(m)
else if logicType == "Pin Bar (Strict)"
rawHammer := isPinBarHammer(m)
rawShooter := isPinBarShooter(m)
else
rawHammer := isBroadHammer(m)
rawShooter := isBroadShooter(m)
isHammer = rawHammer and (not onlyColor or m.isBullish)
isShooter = rawShooter and (not onlyColor or not m.isBullish)
[isHammer, isShooter]
findExhaustionLevels() =>
float exhHighPrice = na
int exhHighTime = na
float exhLowPrice = na
int exhLowTime = na
// Only calculate on recent bars - historical exhaustion is never displayed
// Loop goes to 48, but accesses i+1, so max offset is 49
// Require bar_index > 50 to ensure enough historical data
if showAnyTargets and bar_index > last_bar_index - 100 and bar_index > 50
// FIX P0-2: seed running max/min from the completed bar [1], not the forming bar [0],
// and start the scan at i=2 so the i-1 neighbor test never reads the forming bar.
// Under lookahead_on, high[0] in the HTF context returns the developing value live
// but the final period value on reload, so the [0] seed made the latched exhaustion
// level repaint. Max offset is now i+1 = 49 (loop still ends at 48).
// DELIBERATELY NOT math.max(high[1], high[0]) for the seed: pulling the forming bar
// back into the intact-pivot gate would re-introduce the live-vs-reload repaint this
// fix removes. A period-open exhaustion snapshot that sits slightly behind live price
// on a rare mid-period preview re-latch is acceptable and, crucially, reload-stable.
float maxHighSoFar = high[1]
float minLowSoFar = low[1]
for i = 2 to 48
if na(exhHighPrice)
isPivotHigh = high[i] > high[i-1] and high[i] > high[i+1]
if isPivotHigh and maxHighSoFar < high[i]
exhHighPrice := high[i]
exhHighTime := time[i]
if na(exhLowPrice)
isPivotLow = low[i] < low[i-1] and low[i] < low[i+1]
if isPivotLow and minLowSoFar > low[i]
exhLowPrice := low[i]
exhLowTime := time[i]
maxHighSoFar := math.max(maxHighSoFar, high[i])
minLowSoFar := math.min(minLowSoFar, low[i])
if not na(exhHighPrice) and not na(exhLowPrice)
break
[exhHighPrice, exhHighTime, exhLowPrice, exhLowTime]
calcBarType(prevH, prevL, currH, currL, currO, currC) =>
if na(prevH) or na(prevL) or na(currH) or na(currL) or na(currO) or na(currC)
""
else
aboveOpen = currC > currO
highCrossed = currH > prevH
lowCrossed = currL < prevL
if highCrossed and lowCrossed
aboveOpen ? "3u" : "3d"
else if highCrossed and not lowCrossed
"2u"
else if lowCrossed and not highCrossed
"2d"
else
"1"
calcBarNum(prevH, prevL, currH, currL) =>
if na(prevH) or na(prevL) or na(currH) or na(currL)
""
else
highCrossed = currH > prevH
lowCrossed = currL < prevL
if highCrossed and lowCrossed
"3"
else if highCrossed or lowCrossed
"2"
else
"1"
getDataWithExhaustion() =>
[exhHP, exhHT, exhLP, exhLT] = findExhaustionLevels()
[time, open, high, low, close, time[1], high[1], low[1], open[1], close[1], time[2], high[2], low[2], open[2], close[2], high[3], low[3], high[4], low[4], exhHP, exhHT, exhLP, exhLT]
calculateFTFC(tf1_en, tf1_o, tf1_c, tf2_en, tf2_o, tf2_c, tf3_en, tf3_o, tf3_c, tf4_en, tf4_o, tf4_c, tf5_en, tf5_o, tf5_c, tf6_en, tf6_o, tf6_c) =>
ftfc_up = true
ftfc_down = true
if tf1_en and not na(tf1_c) and not na(tf1_o)
if tf1_c > tf1_o
ftfc_down := false
else
ftfc_up := false
if tf2_en and not na(tf2_c) and not na(tf2_o)
if tf2_c > tf2_o
ftfc_down := false
else
ftfc_up := false
if tf3_en and not na(tf3_c) and not na(tf3_o)
if tf3_c > tf3_o
ftfc_down := false
else
ftfc_up := false
if tf4_en and not na(tf4_c) and not na(tf4_o)
if tf4_c > tf4_o
ftfc_down := false
else
ftfc_up := false
if tf5_en and not na(tf5_c) and not na(tf5_o)
if tf5_c > tf5_o
ftfc_down := false
else
ftfc_up := false
if tf6_en and not na(tf6_c) and not na(tf6_o)
if tf6_c > tf6_o
ftfc_down := false
else
ftfc_up := false
[ftfc_up, ftfc_down]
// ============================================================================
// SECTION 5: DECISION LOGIC FUNCTIONS
// ============================================================================
detectFailed2(c1_high, c1_low, cc_open, cc_close, cc_high, cc_low, method) =>
cc_is_3 = cc_high > c1_high and cc_low < c1_low
cc_is_2u = cc_high > c1_high and cc_low >= c1_low and not cc_is_3
cc_is_2d = cc_low < c1_low and cc_high <= c1_high and not cc_is_3
is_f2u = false
is_f2d = false
pre_f2u = false
pre_f2d = false
if enableFailed2Detection and (cc_is_2u or cc_is_2d) and not cc_is_3
cc_inside_c1 = cc_close <= c1_high and cc_close >= c1_low
cc_above_open = cc_close > cc_open
if method == "Open"
is_f2u := cc_is_2u and not cc_above_open
is_f2d := cc_is_2d and cc_above_open
else if method == "Reclaim"
is_f2u := cc_is_2u and cc_inside_c1
is_f2d := cc_is_2d and cc_inside_c1
else if method == "Reclaim + Open"
is_f2u := cc_is_2u and not cc_above_open and cc_inside_c1
is_f2d := cc_is_2d and cc_above_open and cc_inside_c1
else
is_f2u := cc_is_2u and (not cc_above_open or cc_inside_c1)
is_f2d := cc_is_2d and (cc_above_open or cc_inside_c1)
// Pre-F2: CC broke range, reclaimed back inside, but close hasn't crossed open yet
// Only relevant when method uses open condition (Open or Reclaim + Open)
if (method == "Open" or method == "Reclaim + Open") and not is_f2u and not is_f2d
if cc_is_2u and cc_inside_c1 and cc_above_open
pre_f2u := true
if cc_is_2d and cc_inside_c1 and not cc_above_open
pre_f2d := true
[cc_is_2u, cc_is_2d, cc_is_3, is_f2u, is_f2d, pre_f2u, pre_f2d]
shouldDrawC1Level(bool isBullish, c1_is_inside, c1_is_hammer, c1_is_shooter, cc_is_2u, cc_is_2d, cc_is_3, is_f2u, is_f2d, ftfc_up, ftfc_down, c1_was_2u, c1_was_2d, c1_is_3, c2_closed_up, c2_closed_down, c2_was_3) =>
result = false
is_continuation = false
cc_is_inside = not cc_is_2u and not cc_is_2d and not cc_is_3
ftfc_blocks = isBullish ? ftfc_down : ftfc_up
c2_same_dir = isBullish ? c2_closed_up : c2_closed_down
c2_opp_dir = isBullish ? c2_closed_down : c2_closed_up
momo_pattern = isBullish ? c1_is_hammer : c1_is_shooter
cc_in_force = isBullish ? cc_is_2u : cc_is_2d
cc_opposite = isBullish ? cc_is_2d : cc_is_2u
c1_was_same = isBullish ? c1_was_2u : c1_was_2d
c1_was_opp = isBullish ? c1_was_2d : c1_was_2u
failed_same = isBullish ? is_f2u : is_f2d
failed_opp = isBullish ? is_f2d : is_f2u
inside_rev_ftfc_blocks = insideRevRequireFTFC and ftfc_blocks
reversals_ftfc_blocks = reversalsRequireFTFC and ftfc_blocks
continuations_ftfc_blocks = continuationsRequireFTFC and ftfc_blocks
inside_cont_ftfc_blocks = insideContRequireFTFC and ftfc_blocks
expansions_ftfc_blocks = rangeExpansionsRequireFTFC and ftfc_blocks
threeExp_ftfc_blocks = threeExpRequireFTFC and ftfc_blocks
p3_ftfc_blocks = p3RequireFTFC and ftfc_blocks
inside_is_continuation = c1_is_inside and c2_same_dir
inside_is_reversal = c1_is_inside and (c2_opp_dir or (not c2_closed_up and not c2_closed_down))
if cc_is_3
if show3Expansions and not threeExp_ftfc_blocks
result := true
else if show_p3_toggle and failed_same
result := not p3_ftfc_blocks
else if show_p3_toggle and failed_opp
result := not p3_ftfc_blocks
else if c1_is_inside
if inside_is_reversal and showInsideReversals
if insideRevOnlyMomo and not momo_pattern
result := false
else
result := not inside_rev_ftfc_blocks
else if inside_is_reversal and not showInsideReversals
result := false
else if inside_is_continuation and showInsideContinuations
if insideContOnlyMomo and not momo_pattern
result := false
else
result := not inside_cont_ftfc_blocks
is_continuation := result
else if inside_is_continuation and not showInsideContinuations
result := false
else
if showInsideReversals
if insideRevOnlyMomo and not momo_pattern
result := false
else
result := not inside_rev_ftfc_blocks
else if c1_is_3 and cc_in_force and showRangeExpansions
if rangeExpOnlyMomo and not momo_pattern
result := false
else
result := not expansions_ftfc_blocks
is_continuation := result
else if c1_is_3 and cc_is_inside and showRangeExpansions
if rangeExpOnlyMomo and not momo_pattern
result := false
else
result := not expansions_ftfc_blocks
is_continuation := result
else if momo_pattern and not (c1_is_3 and not showRangeExpansions)
is_momo_reversal = c1_was_opp or (c2_opp_dir and not c1_was_same) or (not c1_was_2u and not c1_was_2d and not c2_same_dir)
is_momo_cont = c1_was_same or (c2_same_dir and not c1_was_opp)
cc_is_opp_continuation = cc_opposite and c1_was_opp
if cc_is_opp_continuation
result := false
else if is_momo_reversal and not cc_opposite
result := not reversals_ftfc_blocks // FIX BTC-1: momo (hammer/shooter) reversal now obeys the Reversals-FTFC toggle (was wrongly using inside-reversal's)
else if is_momo_cont and showContinuations
result := not continuations_ftfc_blocks // FIX BTC-1: momo continuation now obeys the Continuations-FTFC toggle (was wrongly using inside-reversal's)
is_continuation := result
else if c1_was_opp and cc_in_force and showAllReversals
if momo_pattern
result := not reversals_ftfc_blocks
else if reversalsRequireActionable
result := false
else
result := not reversals_ftfc_blocks
else if c1_was_opp and showAllReversals and not cc_opposite
if momo_pattern
result := not reversals_ftfc_blocks
else if reversalsRequireActionable
result := false
else
result := not reversals_ftfc_blocks
else if c1_was_same and cc_in_force and showContinuations and not c2_was_3
if twoTwoContOnlyMomo and not momo_pattern
result := false
else
result := not continuations_ftfc_blocks
is_continuation := result
else if c1_was_same and cc_is_inside and showContinuations and not c2_was_3
if twoTwoContOnlyMomo and not momo_pattern
result := false
else
result := not continuations_ftfc_blocks
is_continuation := result
[result, is_continuation]
shouldDrawC1High(c1_is_inside, c1_is_hammer, c1_is_shooter, cc_is_2u, cc_is_2d, cc_is_3, is_f2u, is_f2d, ftfc_up, ftfc_down, c1_was_2u, c1_was_2d, c1_is_3, c2_closed_up, c2_closed_down, c2_was_3) =>
shouldDrawC1Level(true, c1_is_inside, c1_is_hammer, c1_is_shooter, cc_is_2u, cc_is_2d, cc_is_3, is_f2u, is_f2d, ftfc_up, ftfc_down, c1_was_2u, c1_was_2d, c1_is_3, c2_closed_up, c2_closed_down, c2_was_3)
shouldDrawC1Low(c1_is_inside, c1_is_shooter, c1_is_hammer, cc_is_2u, cc_is_2d, cc_is_3, is_f2u, is_f2d, ftfc_up, ftfc_down, c1_was_2u, c1_was_2d, c1_is_3, c2_closed_up, c2_closed_down, c2_was_3) =>
shouldDrawC1Level(false, c1_is_inside, c1_is_hammer, c1_is_shooter, cc_is_2u, cc_is_2d, cc_is_3, is_f2u, is_f2d, ftfc_up, ftfc_down, c1_was_2u, c1_was_2d, c1_is_3, c2_closed_up, c2_closed_down, c2_was_3)
shouldDrawMagnitude(c1_broke_c2, is_signal_in_force) =>
not c1_broke_c2 and is_signal_in_force
// ============================================================================
// SECTION 6: DATA STRUCTURES (Array-based for loop processing)
// ============================================================================
var array<TimeframeData> tfDataArr = array.new<TimeframeData>(6)
if barstate.isfirst
for i = 0 to 5
array.set(tfDataArr, i, TimeframeData.new(na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, na, false, false, false, false, false, false, false, false, false, na, na, false, false, na, na))
var array<TFConfig> tfConfigs = array.new<TFConfig>(6)
if barstate.isfirst
array.set(tfConfigs, 0, TFConfig.new(rEN1, rTF1, rLW1, rSO1, "TF1"))
array.set(tfConfigs, 1, TFConfig.new(rEN2, rTF2, rLW2, rSO2, "TF2"))
array.set(tfConfigs, 2, TFConfig.new(rEN3, rTF3, rLW3, rSO3, "TF3"))
array.set(tfConfigs, 3, TFConfig.new(rEN4, rTF4, rLW4, rSO4, "TF4"))
array.set(tfConfigs, 4, TFConfig.new(rEN5, rTF5, rLW5, rSO5, "TF5"))
array.set(tfConfigs, 5, TFConfig.new(rEN6, rTF6, rLW6, rSO6, "TF6"))
// Tiered calc_bars_count: Monthly+=100, Weekly=100, Daily=150, 4H+=200, intraday=250
// Minimum 100 required for exhaustion scan (bar_index > 50 gate inside security context)
// Inlined as simple int: Pine functions return series, which request.security rejects.
simple int cb1 = rTF1 == "M" or rTF1 == "1M" or rTF1 == "3M" or rTF1 == "6M" or rTF1 == "12M" ? 100 : rTF1 == "W" or rTF1 == "1W" ? 100 : rTF1 == "D" or rTF1 == "1D" ? 150 : rTF1 == "240" or rTF1 == "480" or rTF1 == "720" ? 200 : 250
simple int cb2 = rTF2 == "M" or rTF2 == "1M" or rTF2 == "3M" or rTF2 == "6M" or rTF2 == "12M" ? 100 : rTF2 == "W" or rTF2 == "1W" ? 100 : rTF2 == "D" or rTF2 == "1D" ? 150 : rTF2 == "240" or rTF2 == "480" or rTF2 == "720" ? 200 : 250
simple int cb3 = rTF3 == "M" or rTF3 == "1M" or rTF3 == "3M" or rTF3 == "6M" or rTF3 == "12M" ? 100 : rTF3 == "W" or rTF3 == "1W" ? 100 : rTF3 == "D" or rTF3 == "1D" ? 150 : rTF3 == "240" or rTF3 == "480" or rTF3 == "720" ? 200 : 250
simple int cb4 = rTF4 == "M" or rTF4 == "1M" or rTF4 == "3M" or rTF4 == "6M" or rTF4 == "12M" ? 100 : rTF4 == "W" or rTF4 == "1W" ? 100 : rTF4 == "D" or rTF4 == "1D" ? 150 : rTF4 == "240" or rTF4 == "480" or rTF4 == "720" ? 200 : 250
simple int cb5 = rTF5 == "M" or rTF5 == "1M" or rTF5 == "3M" or rTF5 == "6M" or rTF5 == "12M" ? 100 : rTF5 == "W" or rTF5 == "1W" ? 100 : rTF5 == "D" or rTF5 == "1D" ? 150 : rTF5 == "240" or rTF5 == "480" or rTF5 == "720" ? 200 : 250
simple int cb6 = rTF6 == "M" or rTF6 == "1M" or rTF6 == "3M" or rTF6 == "6M" or rTF6 == "12M" ? 100 : rTF6 == "W" or rTF6 == "1W" ? 100 : rTF6 == "D" or rTF6 == "1D" ? 150 : rTF6 == "240" or rTF6 == "480" or rTF6 == "720" ? 200 : 250
[tf1CurrTime, tf1O, tf1H_curr, tf1L_curr, tf1C, tf1Time, tf1H, tf1L, tf1O_C1, tf1C_C1, tf1MagTime, tf1MagH, tf1MagL, tf1O_C2, tf1C_C2, tf1H_C3, tf1L_C3, tf1H_C4, tf1L_C4, tf1ExhHP, tf1ExhHT, tf1ExhLP, tf1ExhLT] = request.security(syminfo.tickerid, rTF1, getDataWithExhaustion(), lookahead = barmerge.lookahead_on, calc_bars_count = cb1)
[tf2CurrTime, tf2O, tf2H_curr, tf2L_curr, tf2C, tf2Time, tf2H, tf2L, tf2O_C1, tf2C_C1, tf2MagTime, tf2MagH, tf2MagL, tf2O_C2, tf2C_C2, tf2H_C3, tf2L_C3, tf2H_C4, tf2L_C4, tf2ExhHP, tf2ExhHT, tf2ExhLP, tf2ExhLT] = request.security(syminfo.tickerid, rTF2, getDataWithExhaustion(), lookahead = barmerge.lookahead_on, calc_bars_count = cb2)
[tf3CurrTime, tf3O, tf3H_curr, tf3L_curr, tf3C, tf3Time, tf3H, tf3L, tf3O_C1, tf3C_C1, tf3MagTime, tf3MagH, tf3MagL, tf3O_C2, tf3C_C2, tf3H_C3, tf3L_C3, tf3H_C4, tf3L_C4, tf3ExhHP, tf3ExhHT, tf3ExhLP, tf3ExhLT] = request.security(syminfo.tickerid, rTF3, getDataWithExhaustion(), lookahead = barmerge.lookahead_on, calc_bars_count = cb3)
[tf4CurrTime, tf4O, tf4H_curr, tf4L_curr, tf4C, tf4Time, tf4H, tf4L, tf4O_C1, tf4C_C1, tf4MagTime, tf4MagH, tf4MagL, tf4O_C2, tf4C_C2, tf4H_C3, tf4L_C3, tf4H_C4, tf4L_C4, tf4ExhHP, tf4ExhHT, tf4ExhLP, tf4ExhLT] = request.security(syminfo.tickerid, rTF4, getDataWithExhaustion(), lookahead = barmerge.lookahead_on, calc_bars_count = cb4)
[tf5CurrTime, tf5O, tf5H_curr, tf5L_curr, tf5C, tf5Time, tf5H, tf5L, tf5O_C1, tf5C_C1, tf5MagTime, tf5MagH, tf5MagL, tf5O_C2, tf5C_C2, tf5H_C3, tf5L_C3, tf5H_C4, tf5L_C4, tf5ExhHP, tf5ExhHT, tf5ExhLP, tf5ExhLT] = request.security(syminfo.tickerid, rTF5, getDataWithExhaustion(), lookahead = barmerge.lookahead_on, calc_bars_count = cb5)
[tf6CurrTime, tf6O, tf6H_curr, tf6L_curr, tf6C, tf6Time, tf6H, tf6L, tf6O_C1, tf6C_C1, tf6MagTime, tf6MagH, tf6MagL, tf6O_C2, tf6C_C2, tf6H_C3, tf6L_C3, tf6H_C4, tf6L_C4, tf6ExhHP, tf6ExhHT, tf6ExhLP, tf6ExhLT] = request.security(syminfo.tickerid, rTF6, getDataWithExhaustion(), lookahead = barmerge.lookahead_on, calc_bars_count = cb6)
// FIX HTF-STRADDLE-1 (T3, probe-validated): completed DAILY sub-bars of the current chart bar, used
// to rebuild the true forming HTF candle for straddled slots. Constant gate: the request either always
// executes (chart above D) or never does - both legal per v6 dynamic requests; charts at/below D never
// need it (probe confirmed clean load on a 1D chart with the gate off).
bool canRecon = timeframe.in_seconds() > timeframe.in_seconds("D")
array<int> reconDT = na
array<float> reconDO = na
array<float> reconDH = na
array<float> reconDL = na
if canRecon
[rdT, rdO, rdH, rdL] = request.security_lower_tf(syminfo.tickerid, "D", [time, open, high, low])
reconDT := rdT
reconDO := rdO
reconDH := rdH
reconDL := rdL
[ftfc_up, ftfc_down] = calculateFTFC(rEN1, tf1O, tf1C, rEN2, tf2O, tf2C, rEN3, tf3O, tf3C, rEN4, tf4O, tf4C, rEN5, tf5O, tf5C, rEN6, tf6O, tf6C)
// FIX HTF-STRADDLE-1: calendar timeframes are the only ones that can straddle a chart bar - their
// period boundaries do not have to align with chart bar opens (months/quarters roll mid-week on a
// weekly chart). Intraday/daily/weekly requests from lower charts always align on session opens.
isCalendarTF(string tf) =>
tf == "W" or tf == "1W" or tf == "M" or tf == "1M" or tf == "3M" or tf == "6M" or tf == "12M"
// FIX HTF-STRADDLE-1 (corrected 2026-07-02, probe-validated): live straddle detection, session aware.
// A futures HTF bar's open is stamped at the prior-evening session (~18:00 ET), so naive month()/
// quarter() on it reads ONE calendar period early (the probe confirmed: the current Q3 quarterly bar
// is stamped 2026-06-30 18:00 -> parses as Q2). Adding +12h before parsing normalizes any period-open
// into its true calendar period, robust across futures (18:00 prior day -> next morning), equities
// (9:30 -> same day) and crypto (00:00 -> same day) without ever overshooting the day.
// Straddle fires ONLY for a calendar slot STRICTLY coarser than the chart (so the chart's own TF can
// never straddle itself) whose served bar's scheduled close has already passed (see shouldStraddle).
OFF_MS = 43200000 // 12h in ms (used by pkeyCal's session-stamp normalization for daily recon)
isStraddleTF(string tf) =>
isCalendarTF(tf) and timeframe.in_seconds(tf) > chartTfSeconds
// FIX GLUE-2b (2026-07-06): staleness test is now purely time-based: the served HTF bar is
// stale exactly when "now" is past its SCHEDULED CLOSE. The previous calendar-key approach
// (open stamp + 12h) broke on holiday-glued bars: Monday Jul 6's WEEKLY bar opens Thursday
// Jul 2 18:00 ET (Friday's half-session belongs to Monday's trade date), ~84h before its
// true period, so it still parsed as last week and kept a phantom W straddle firing through
// the live Monday session, pinning weekly C1 to the forming week. time_close(tf) is computed
// from the same trade-date calendar that stamps the bars, so it is immune to early opens,
// needs no week-numbering, and is direction-aware (a trade-date bar running AHEAD of the
// calendar, like the glued Friday session inside next week's bar, can never fire it). The
// mid-bar calendar-roll case this feature exists for still works: once the served Q2 bar's
// Jun 30 17:00 close passes, the shift arms for the rest of the straddling chart bar. This
// also obsoletes the 07-02 double-reading hardening: a period-end-afternoon phantom cannot
// occur, because the bar's close has not passed while its session still trades.
shouldStraddle(string tf) =>
int tc = time_close(tf)
not na(tc) and timenow > tc
// FIX HTF-STRADDLE-1 (T3): comparable period key for a calendar TF from a timestamp. EVERY timestamp
// passed here must be +12h-normalized FIRST (see OFF_MS), so a session-stamped open - e.g. a futures
// period that opens ~18:00 ET on the prior calendar day - lands in its true calendar period. This
// applies to BOTH the served daily-bar opens and "now" (see FIX RECON-KEY-1 at the call site).
pkeyCal(int t, string tf) =>
m = month(t, "America/New_York")
y = year(t, "America/New_York")
w = weekofyear(t, "America/New_York")
q = m <= 3 ? 1 : m <= 6 ? 2 : m <= 9 ? 3 : 4
tf == "12M" ? y : tf == "6M" ? y * 10 + (m <= 6 ? 1 : 2) : tf == "3M" ? y * 10 + q : tf == "W" or tf == "1W" ? y * 1000 + w : y * 100 + m
// Classic preview - UNCHANGED from the pre-straddle version (market-closed / previewMode=On projection).
shouldApplyPreview(string tf, bool previewEnabled) =>
if not previewEnabled
false
else
// Compare last bar's period vs current calendar period
// This handles: Friday EOD -> Weekend (same period), but also Jan 31 -> Feb 1 weekend (new period)
currentMonth = month(timenow, "America/New_York")
currentYear = year(timenow, "America/New_York")
currentWeek = weekofyear(timenow, "America/New_York")
currentQuarter = math.ceil(currentMonth / 3)
currentSemiAnnual = currentMonth <= 6 ? 1 : 2
barMonth = month(time, "America/New_York")
barYear = year(time, "America/New_York")
barWeek = weekofyear(time, "America/New_York")
barQuarter = math.ceil(barMonth / 3)
barSemiAnnual = barMonth <= 6 ? 1 : 2
isWeekly = tf == "W" or tf == "1W"
isMonthly = tf == "M" or tf == "1M"
isQuarterly = tf == "3M"
isSemiAnnual = tf == "6M"
isYearly = tf == "12M"
if isWeekly
differentWeek = currentWeek != barWeek or currentYear != barYear
dow = dayofweek(timenow, "America/New_York")
isWeekend = dow == dayofweek.friday or dow == dayofweek.saturday or dow == dayofweek.sunday
differentWeek or isWeekend
else if isYearly
currentYear != barYear
else if isSemiAnnual
currentSemiAnnual != barSemiAnnual or currentYear != barYear
else if isQuarterly
currentQuarter != barQuarter or currentYear != barYear
else if isMonthly
currentMonth != barMonth or currentYear != barYear
else
true
applyPreviewShift(int c1Time, float c1H, float c1L, float c1O, float c1C, int ccTime, float ccO, float ccH, float ccL, float ccC, int c2Time, float c2H, float c2L, float c2O, float c2C, float c3H, float c3L, float c4H, float c4L, float exhHP, int exhHT, float exhLP, int exhLT, string tf, bool previewEnabled, bool straddleEnabled) =>
// FIX HTF-STRADDLE-1: classic preview OR a session-aware live straddle (calendar slot coarser than chart).
// FIX GLUE-2b (2026-07-06): straddle = now is past the served HTF bar's scheduled close.
doPreview = shouldApplyPreview(tf, previewEnabled) or (straddleEnabled and isStraddleTF(tf) and shouldStraddle(tf))
if doPreview
newC1Time = ccTime
newC1H = ccH
newC1L = ccL
newC1O = ccO
newC1C = ccC
newCCTime = time
newCCO = ccC
newCCH = ccH
newCCL = ccL
newCCC = (ccH + ccL) / 2
newC2Time = c1Time
newC2H = c1H
newC2L = c1L
newC2O = c1O
newC2C = c1C
newC3H = c2H
newC3L = c2L
newC4H = c3H
newC4L = c3L
// FIX P0-1: realPeriodTime = the unshifted real current period-open (the `ccTime` param),
// even in preview where ccTime is reused as the synthetic C1. New-period detection uses this.
TFRawData.new(newC1Time, newC1H, newC1L, newC1O, newC1C, newCCTime, newCCO, newCCH, newCCL, newCCC, newC2Time, newC2H, newC2L, newC2O, newC2C, newC3H, newC3L, newC4H, newC4L, exhHP, exhHT, exhLP, exhLT, true, ccTime)
else
TFRawData.new(c1Time, c1H, c1L, c1O, c1C, ccTime, ccO, ccH, ccL, ccC, c2Time, c2H, c2L, c2O, c2C, c3H, c3L, c4H, c4L, exhHP, exhHT, exhLP, exhLT, false, ccTime)
var array<TFRawData> tfRawArr = array.new<TFRawData>(6)
array.set(tfRawArr, 0, applyPreviewShift(tf1Time, tf1H, tf1L, tf1O_C1, tf1C_C1, tf1CurrTime, tf1O, tf1H_curr, tf1L_curr, tf1C, tf1MagTime, tf1MagH, tf1MagL, tf1O_C2, tf1C_C2, tf1H_C3, tf1L_C3, tf1H_C4, tf1L_C4, tf1ExhHP, tf1ExhHT, tf1ExhLP, tf1ExhLT, rTF1, previewNextPeriod, straddleWindowOK))
array.set(tfRawArr, 1, applyPreviewShift(tf2Time, tf2H, tf2L, tf2O_C1, tf2C_C1, tf2CurrTime, tf2O, tf2H_curr, tf2L_curr, tf2C, tf2MagTime, tf2MagH, tf2MagL, tf2O_C2, tf2C_C2, tf2H_C3, tf2L_C3, tf2H_C4, tf2L_C4, tf2ExhHP, tf2ExhHT, tf2ExhLP, tf2ExhLT, rTF2, previewNextPeriod, straddleWindowOK))
array.set(tfRawArr, 2, applyPreviewShift(tf3Time, tf3H, tf3L, tf3O_C1, tf3C_C1, tf3CurrTime, tf3O, tf3H_curr, tf3L_curr, tf3C, tf3MagTime, tf3MagH, tf3MagL, tf3O_C2, tf3C_C2, tf3H_C3, tf3L_C3, tf3H_C4, tf3L_C4, tf3ExhHP, tf3ExhHT, tf3ExhLP, tf3ExhLT, rTF3, previewNextPeriod, straddleWindowOK))
array.set(tfRawArr, 3, applyPreviewShift(tf4Time, tf4H, tf4L, tf4O_C1, tf4C_C1, tf4CurrTime, tf4O, tf4H_curr, tf4L_curr, tf4C, tf4MagTime, tf4MagH, tf4MagL, tf4O_C2, tf4C_C2, tf4H_C3, tf4L_C3, tf4H_C4, tf4L_C4, tf4ExhHP, tf4ExhHT, tf4ExhLP, tf4ExhLT, rTF4, previewNextPeriod, straddleWindowOK))
array.set(tfRawArr, 4, applyPreviewShift(tf5Time, tf5H, tf5L, tf5O_C1, tf5C_C1, tf5CurrTime, tf5O, tf5H_curr, tf5L_curr, tf5C, tf5MagTime, tf5MagH, tf5MagL, tf5O_C2, tf5C_C2, tf5H_C3, tf5L_C3, tf5H_C4, tf5L_C4, tf5ExhHP, tf5ExhHT, tf5ExhLP, tf5ExhLT, rTF5, previewNextPeriod, straddleWindowOK))
array.set(tfRawArr, 5, applyPreviewShift(tf6Time, tf6H, tf6L, tf6O_C1, tf6C_C1, tf6CurrTime, tf6O, tf6H_curr, tf6L_curr, tf6C, tf6MagTime, tf6MagH, tf6MagL, tf6O_C2, tf6C_C2, tf6H_C3, tf6L_C3, tf6H_C4, tf6L_C4, tf6ExhHP, tf6ExhHT, tf6ExhLP, tf6ExhLT, rTF6, previewNextPeriod, straddleWindowOK))
// FIX HTF-STRADDLE-1 (T3, probe-validated 2026-07-02): for a straddle-shifted slot, rebuild the TRUE
// forming HTF candle from the chart bar's daily sub-bars plus the live close, replacing the synthetic
// preview candle. The probe reproduced the finer charts' CC exactly (BTC 1W: f2d including the
// sub-C1-low wick the earlier estimate missed). Gated on shouldStraddle, so a classic next-period
// preview (weekend W/M projection, whose period has no dailies yet) is never overridden; n == 0
// leaves the "?" candle. isPreview stays TRUE: the every-tick preview re-latch is what keeps the
// shifted C1/C2 alive across realtime rollback, and alerts stay conservatively suppressed.
// FIX GLUE-2b (2026-07-06): staleness re-check is time-based (see shouldStraddle).
var array<bool> reconOKArr = array.new<bool>(6, false)
if barstate.islast
for iS = 0 to 5
array.set(reconOKArr, iS, false)
cfgS = array.get(tfConfigs, iS)
rawS = array.get(tfRawArr, iS)
if cfgS.enabled and not na(rawS) and rawS.isPreview and canRecon and not na(reconDT) and array.size(reconDT) > 0 and isStraddleTF(cfgS.tf) and shouldStraddle(cfgS.tf)
// FIX RECON-KEY-1 (2026-07-07, Codex review): normalize "now" by +12h too, matching the
// daily-bar keys below. It was raw - valid only under the old +12h/raw double-reading
// straddle test that GLUE-2b removed. Raw "now" keyed one period behind the +12h daily keys
// for the ~7h between a period's session close and calendar midnight, so recon matched the
// PRIOR period's dailies at each roll and painted a wrong CC as current. Both keys normalized now.
int pNow = pkeyCal(timenow + OFF_MS, cfgS.tf)
float rO = na
float rH = na
float rL = na
int rT = na
int n = 0
for jS = 0 to array.size(reconDT) - 1
if pkeyCal(array.get(reconDT, jS) + OFF_MS, cfgS.tf) == pNow
if n == 0
rO := array.get(reconDO, jS)
rT := array.get(reconDT, jS)
rH := na(rH) ? array.get(reconDH, jS) : math.max(rH, array.get(reconDH, jS))
rL := na(rL) ? array.get(reconDL, jS) : math.min(rL, array.get(reconDL, jS))
n += 1
if n > 0
rawS.ccTime := rT
rawS.ccO := rO
rawS.ccC := close
rawS.ccH := math.max(rH, close)
rawS.ccL := math.min(rL, close)
array.set(reconOKArr, iS, true)
var array<ProcessingResult> tfResults = array.new<ProcessingResult>(6)
// ============================================================================
// SECTION 7: MAIN PROCESSING FUNCTION
// ============================================================================
// Detect bar type and Failed 2 status (used by processTimeframe and table rendering)
detectBarTypeAndFailed(prevH, prevL, currH, currL, currO, currC) =>
if na(prevH) or na(prevL) or na(currH) or na(currL) or na(currO) or na(currC) or currH < currL or prevH < prevL
["", false]
else
aboveOpen = currC > currO
gapUp = currO > prevH
gapDown = currO < prevL
is2up = false
is2down = false
isInside = false
isOutside = false
if gapUp
if currL < prevL
isOutside := true
else
is2up := true
else if gapDown
if currH > prevH
isOutside := true
else
is2down := true
else
highCrossed = currH > prevH
lowCrossed = currL < prevL
if highCrossed and lowCrossed
isOutside := true
else if highCrossed and not lowCrossed
is2up := true
else if lowCrossed and not highCrossed
is2down := true
else
isInside := true
isFailed = false
insidePrevRange = currC <= prevH and currC >= prevL
// FIX P1-f: honor enableFailed2Detection here too. The main engine (detectFailed2)
// is gated on this input, but the table/label path (detectBarTypeAndFailed) was not,
// so with detection OFF the table showed f2u/f2d while the engine emitted plain 2u/2d.
if enableFailed2Detection and (is2up or is2down)
if failed_2_method == "Open"
if is2up
isFailed := not aboveOpen
else if is2down
isFailed := aboveOpen
else if failed_2_method == "Reclaim"
isFailed := insidePrevRange
else if failed_2_method == "Reclaim + Open"
if is2up
isFailed := not aboveOpen and insidePrevRange
else if is2down
isFailed := aboveOpen and insidePrevRange
else
if is2up
isFailed := not aboveOpen or insidePrevRange
else if is2down
isFailed := aboveOpen or insidePrevRange
barType = ""
if isInside
barType := aboveOpen ? "1u" : "1d"
else if is2up
barType := isFailed ? "F2u" : "2u"
else if is2down
barType := isFailed ? "F2d" : "2d"
else if isOutside
barType := aboveOpen ? "3u" : "3d"
[barType, isFailed]
computeSignalState(data, tf, raw, showOpen, populateDebug, debugTfStr) =>
tfTime = raw.c1Time
tfH = raw.c1H
tfL = raw.c1L
tfO_C1 = raw.c1O
tfC_C1 = raw.c1C
tfCurrTime = raw.ccTime
tfO = raw.ccO
tfH_curr = raw.ccH
tfL_curr = raw.ccL
tfC = raw.ccC
tfMagTime = raw.c2Time
tfMagH = raw.c2H
tfMagL = raw.c2L
tfO_C2 = raw.c2O
tfC_C2 = raw.c2C
tfH_C3 = raw.c3H
tfL_C3 = raw.c3L
tfH_C4 = raw.c4H
tfL_C4 = raw.c4L
tfExhHP = raw.exhHP
tfExhHT = raw.exhHT
tfExhLP = raw.exhLP
tfExhLT = raw.exhLT
isInPreview = raw.isPreview
// FIX P0-1: detect a new HTF period PER SLOT by comparing the security-returned real
// period-open time against a per-slot stored value. The old `ta.change(time(tf)) != 0`
// shared ONE history buffer across all 6 loop iterations (computeSignalState has a single
// written call site inside `for i = 5 to 0`), so its [1] compared this bar's time(tfA)
// against the previous bar's last-iteration time(tfB) and fired on nearly every bar -
// constantly wiping the crossed-flag "came back" memory and the sticky stops. No ta.* state.
// FIX P1-d: also re-latch when preview has just EXITED mid-period (no real rollover);
// otherwise the preview-shifted prevHigh/prevMag persist until the next genuine period.
bool prevWasPreview = data.wasPreview
newPeriod = na(data.lastPeriodStart) or raw.realPeriodTime != data.lastPeriodStart
if newPeriod or (isInPreview and barstate.islast) or (prevWasPreview and not isInPreview)
data.magHighCrossed := false
data.magLowCrossed := false
data.exhHighCrossed := false
data.exhLowCrossed := false
data.stopHighTriggered := false
data.stopLowTriggered := false
data.stopHighPrice := na
data.stopLowPrice := na
data.prevHigh := tfH
data.prevLow := tfL
data.prevTime := tfTime
data.prevMagHigh := tfMagH
data.prevMagLow := tfMagL
data.prevMagTime := tfMagTime
data.exhHighPrice := tfExhHP
data.exhHighTime := tfExhHT
data.exhLowPrice := tfExhLP
data.exhLowTime := tfExhLT
data.lastPeriodStart := raw.realPeriodTime
data.wasPreview := isInPreview
data.currOpen := tfO
validTimeframe = timeframe.in_seconds() <= timeframe.in_seconds(tf)
float retHigh = na, float retLow = na, float retOpen = na, float retMagHigh = na, float retMagLow = na, float retExhHigh = na, float retExhLow = na
color retHighColor = na, color retLowColor = na, color retOpenColor = na, color retMagHighColor = na, color retMagLowColor = na, color retExhHighColor = na, color retExhLowColor = na
bool retDrawHigh = false, bool retDrawLow = false, bool retDrawOpen = false, bool retDrawMagHigh = false, bool retDrawMagLow = false, bool retDrawExhHigh = false, bool retDrawExhLow = false
int retTime = na, bool retIsF2u = false, bool retIsF2d = false, bool retC1Is3 = false
string retC3Num = "", string retC2Num = "", string retC1Num = "", string retC1Type = "", string retCCType = ""
bool retC1WasF2 = false, bool retC2WasF2 = false
bool retSignalInForceHigh = false, bool retSignalInForceLow = false
float retStopHigh = na, float retStopLow = na, bool retDrawStopHigh = false, bool retDrawStopLow = false
bool retPreF2u = false, bool retPreF2d = false, float retF2OpenPrice = na
string retHighStyle = "Dashed", string retLowStyle = "Dashed"
if validTimeframe and not na(data.prevHigh)
if not na(data.prevMagHigh) and not data.magHighCrossed and tfH_curr >= data.prevMagHigh
data.magHighCrossed := true
if not na(data.prevMagLow) and not data.magLowCrossed and tfL_curr <= data.prevMagLow
data.magLowCrossed := true
if not na(data.exhHighPrice) and not data.exhHighCrossed and tfH_curr >= data.exhHighPrice
data.exhHighCrossed := true
if not na(data.exhLowPrice) and not data.exhLowCrossed and tfL_curr <= data.exhLowPrice
data.exhLowCrossed := true
// FIX P1-a: run the detection + sticky-stop latch on every bar when stops are enabled,
// not only on barstate.islast. The stop "locks for the period" semantic requires the
// latch to have fired on the bars where the signal went in force; with the old islast
// gate, a chart reload (those bars now historical) never re-armed the latch and the
// locked stop vanished. Gated on showStopLevels so the common case (stops off, default)
// keeps the original islast-only cost - no performance change unless stops are on.
if barstate.islast or showStopLevels
[isHammer, isShooter] = detectPatterns(tfO_C1, tfH, tfL, tfC_C1)
isInside = isInsideBar(tfH, tfL, tfMagH, tfMagL)
data.c1IsInside := isInside
data.c1IsHammer := isHammer
data.c1IsShooter := isShooter
[cc_is_2u, cc_is_2d, cc_is_3, is_f2u, is_f2d, pre_f2u, pre_f2d] = detectFailed2(data.prevHigh, data.prevLow, tfO, tfC, tfH_curr, tfL_curr, failed_2_method)
retIsF2u := is_f2u
retIsF2d := is_f2d
retC3Num := calcBarNum(tfH_C4, tfL_C4, tfH_C3, tfL_C3)
retC2Num := calcBarNum(tfH_C3, tfL_C3, tfMagH, tfMagL)
retC1Num := calcBarNum(tfMagH, tfMagL, tfH, tfL)
string c1Type = calcBarType(tfMagH, tfMagL, tfH, tfL, tfO_C1, tfC_C1)
if is_f2u
retCCType := "F2u"
else if is_f2d
retCCType := "F2d"
else if cc_is_3
retCCType := tfC > tfO ? "3u" : "3d"
else if cc_is_2u
retCCType := "2u"
else if cc_is_2d
retCCType := "2d"
else
retCCType := "1"
retC1Type := c1Type
// Detect if C1/C2 were themselves Failed 2s (for label accuracy)
[_c1BarType, c1f2flag] = detectBarTypeAndFailed(tfMagH, tfMagL, tfH, tfL, tfO_C1, tfC_C1)
[_c2BarType, c2f2flag] = detectBarTypeAndFailed(tfH_C3, tfL_C3, tfMagH, tfMagL, tfO_C2, tfC_C2)
retC1WasF2 := c1f2flag
retC2WasF2 := c2f2flag
cc_broke_high = tfH_curr > data.prevHigh
cc_broke_low = tfL_curr < data.prevLow
c1_broke_mag_high = tfH > tfMagH
c1_broke_mag_low = tfL < tfMagL
c1_was_2u = c1_broke_mag_high and not c1_broke_mag_low
c1_was_2d = c1_broke_mag_low and not c1_broke_mag_high
c2_broke_c3_high = tfMagH > tfH_C3
c2_broke_c3_low = tfMagL < tfL_C3
c2_was_2u = c2_broke_c3_high and not c2_broke_c3_low
c2_was_2d = c2_broke_c3_low and not c2_broke_c3_high
c2_was_3 = c2_broke_c3_high and c2_broke_c3_low
c2_closed_up = tfC_C2 > tfO_C2
c2_closed_down = tfC_C2 < tfO_C2
// C1 F2d (bullish): broke C2 low but closed back inside range
// C1 F2u (bearish): broke C2 high but closed back inside range
c1_was_f2d = c1_was_2d and tfC_C1 > tfMagL // broke low, closed above it
c1_was_f2u = c1_was_2u and tfC_C1 < tfMagH // broke high, closed below it
c1_was_neutral = not c1_was_2u and not c1_was_2d
inside_bullish = isInside and c2_closed_up
inside_bearish = isInside and c2_closed_down
is_hammer_reversal = c1_was_2d or inside_bearish or (c1_was_neutral and not isInside)
is_shooter_reversal = c1_was_2u or inside_bullish or (c1_was_neutral and not isInside)
is_hammer_momo = c1_was_2u or inside_bullish
is_shooter_momo = c1_was_2d or inside_bearish
hammer_shows = isHammer and (is_hammer_reversal or (is_hammer_momo and showContinuations))
shooter_shows = isShooter and (is_shooter_reversal or (is_shooter_momo and showContinuations))
inside_cont_high_passes_momo = not insideContOnlyMomo or isHammer
inside_cont_low_passes_momo = not insideContOnlyMomo or isShooter
twotwo_cont_high_passes_momo = (not twoTwoContOnlyMomo or isHammer) and not c2_was_3
twotwo_cont_low_passes_momo = (not twoTwoContOnlyMomo or isShooter) and not c2_was_3
c1_is_3 = tfH > tfMagH and tfL < tfMagL
retC1Is3 := c1_is_3
is_22_cont_high = c1_was_2u and c2_was_2u and showContinuations and twotwo_cont_high_passes_momo
is_22_cont_low = c1_was_2d and c2_was_2d and showContinuations and twotwo_cont_low_passes_momo
is_32_cont_high = c1_is_3 and cc_is_2u and showRangeExpansions
is_32_cont_low = c1_is_3 and cc_is_2d and showRangeExpansions
twotwo_rev_high_passes = (not reversalsRequireActionable or isHammer) and (not reversalsRequireC1F2 or c1_was_f2d)
twotwo_rev_low_passes = (not reversalsRequireActionable or isShooter) and (not reversalsRequireC1F2 or c1_was_f2u)
p3_high_passes_ftfc = not p3RequireFTFC or ftfc_up
p3_low_passes_ftfc = not p3RequireFTFC or ftfc_down
// FIX CSS-1: a flat (doji) C2 counts as an inside reversal in-force, matching the draw path
// (shouldDrawC1Level treats flat C2 as a reversal). The old strict c2_closed_down/c2_closed_up
// left flat-C2 inside reversals drawn + alerting but never marked in-force, so the table, the
// sticky stop latch, and the "only when in force" target/TAW gates all disagreed with the line.
signal_in_force_high = (isInside and not c2_closed_up and cc_is_2u) or (inside_bullish and showInsideContinuations and inside_cont_high_passes_momo and cc_is_2u) or (hammer_shows and cc_broke_high) or (showAllReversals and c1_was_2d and cc_is_2u and twotwo_rev_high_passes) or (showContinuations and c1_was_2u and cc_is_2u and twotwo_cont_high_passes_momo) or (showRangeExpansions and c1_is_3 and cc_is_2u) or (show_p3_toggle and is_f2d and p3_high_passes_ftfc)
signal_in_force_low = (isInside and not c2_closed_down and cc_is_2d) or (inside_bearish and showInsideContinuations and inside_cont_low_passes_momo and cc_is_2d) or (shooter_shows and cc_broke_low) or (showAllReversals and c1_was_2u and cc_is_2d and twotwo_rev_low_passes) or (showContinuations and c1_was_2d and cc_is_2d and twotwo_cont_low_passes_momo) or (showRangeExpansions and c1_is_3 and cc_is_2d) or (show_p3_toggle and is_f2u and p3_low_passes_ftfc)
// For directional filter, signal is only "currently in force" if price is still beyond the trigger
currently_above_trigger = tfC > data.prevHigh
currently_below_trigger = tfC < data.prevLow
three_2u_suppress_low = c1_is_3 and cc_is_2u and not is_f2u
three_2d_suppress_high = c1_is_3 and cc_is_2d and not is_f2d
cc_3u_suppress_low = cc_is_3 and tfC > tfO
cc_3d_suppress_high = cc_is_3 and tfC <= tfO
inside_breakout_suppress_low = isInside and cc_broke_high and not is_f2u and not cc_is_3
inside_breakout_suppress_high = isInside and cc_broke_low and not is_f2d and not cc_is_3
// F2 filter for 2-2 reversals: suppress draw when C1 wasn't itself a Failed 2
// Skip suppression when CC has an active F2 (range reclaim signal draws both sides)
twotwo_rev_f2_suppress_high = reversalsRequireC1F2 and c1_was_2d and not c1_was_f2d and not c1_is_3 and not isInside and not is_f2u and not is_f2d
twotwo_rev_f2_suppress_low = reversalsRequireC1F2 and c1_was_2u and not c1_was_f2u and not c1_is_3 and not isInside and not is_f2u and not is_f2d
[drawHighResult, isHighContinuation] = shouldDrawC1High(isInside, isHammer, isShooter, cc_is_2u, cc_is_2d, cc_is_3, is_f2u, is_f2d, ftfc_up, ftfc_down, c1_was_2u, c1_was_2d, c1_is_3, c2_closed_up, c2_closed_down, c2_was_3)
[drawLowResult, isLowContinuation] = shouldDrawC1Low(isInside, isShooter, isHammer, cc_is_2u, cc_is_2d, cc_is_3, is_f2u, is_f2d, ftfc_up, ftfc_down, c1_was_2u, c1_was_2d, c1_is_3, c2_closed_up, c2_closed_down, c2_was_3)
drawHigh = drawHighResult and not three_2d_suppress_high and not inside_breakout_suppress_high and not twotwo_rev_f2_suppress_high
drawLow = drawLowResult and not three_2u_suppress_low and not inside_breakout_suppress_low and not twotwo_rev_f2_suppress_low
// signalInForce must also require drawHigh/drawLow to align with user settings
// If exhDisablesInForce is on, exhausted signals are not considered in force
// If no exhaustion level exists, magnitude serves the same function
// F2 signals are "in force" when the F2 detection conditions are met (per user's method),
// without requiring price beyond the opposite trigger. For non-F2 signals, still require
// price beyond trigger (currently_above/below_trigger).
bool magFallbackHigh = na(data.exhHighPrice) and not na(data.prevMagHigh) and data.magHighCrossed
bool magFallbackLow = na(data.exhLowPrice) and not na(data.prevMagLow) and data.magLowCrossed
exhDisablesHigh = exhDisablesInForce and (data.exhHighCrossed or magFallbackHigh)
exhDisablesLow = exhDisablesInForce and (data.exhLowCrossed or magFallbackLow)
// Non-F2 in-force requires price beyond trigger; F2 in-force is confirmed by detection itself
non_f2_in_force_high = signal_in_force_high and not is_f2d and currently_above_trigger
f2_in_force_high = show_p3_toggle and is_f2d and drawHigh and p3_high_passes_ftfc
non_f2_in_force_low = signal_in_force_low and not is_f2u and currently_below_trigger
f2_in_force_low = show_p3_toggle and is_f2u and drawLow and p3_low_passes_ftfc
// Raw in-force (before exhaustion gate) - used for stop triggers
// Exhaustion disabling affects signal display/filtering, not protective stops
bool rawInForceHigh = (non_f2_in_force_high or f2_in_force_high) and drawHigh
bool rawInForceLow = (non_f2_in_force_low or f2_in_force_low) and drawLow
retSignalInForceHigh := rawInForceHigh and not exhDisablesHigh
retSignalInForceLow := rawInForceLow and not exhDisablesLow
// Sticky stop triggers - once in force this period, stop persists
// F2 signals always use CC (the failed candle defines the trade)
// Non-F2: CC mode = breakout bar's opposite side, C1 mode = prior bar's opposite side
// CC mode: update each bar to track running CC extreme (consistent across chart TFs)
// C1 mode: lock on first trigger (stable, uses completed prior candle)
useCC = stopReference == "CC"
if rawInForceHigh and not data.stopHighTriggered
data.stopHighTriggered := true
data.stopHighPrice := (is_f2d or useCC) ? tfL_curr : data.prevLow
else if data.stopHighTriggered and (is_f2d or useCC)
data.stopHighPrice := tfL_curr
if rawInForceLow and not data.stopLowTriggered
data.stopLowTriggered := true
data.stopLowPrice := (is_f2u or useCC) ? tfH_curr : data.prevHigh
else if data.stopLowTriggered and (is_f2u or useCC)
data.stopLowPrice := tfH_curr
// Break even: move stop to entry trigger when magnitude/exhaustion is hit
// Bullish stop (stopHigh) protects long → BE = prevHigh (entry level, moves stop up)
// Bearish stop (stopLow) protects short → BE = prevLow (entry level, moves stop down)
if data.stopHighTriggered
bool magHitHigh = data.magHighCrossed and not na(data.prevMagHigh)
bool exhHitHigh = data.exhHighCrossed and not na(data.exhHighPrice)
bool noMagHigh = na(data.prevMagHigh)
bool noExhHigh = na(data.exhHighPrice)
bool beMagTriggerHigh = stopBEatMag and (magHitHigh or (noMagHigh and exhHitHigh))
bool beExhTriggerHigh = stopBEatExh and (exhHitHigh or (noExhHigh and magHitHigh))
if beMagTriggerHigh or beExhTriggerHigh
data.stopHighPrice := data.prevHigh
if data.stopLowTriggered
bool magHitLow = data.magLowCrossed and not na(data.prevMagLow)
bool exhHitLow = data.exhLowCrossed and not na(data.exhLowPrice)
bool noMagLow = na(data.prevMagLow)
bool noExhLow = na(data.exhLowPrice)
bool beMagTriggerLow = stopBEatMag and (magHitLow or (noMagLow and exhHitLow))
bool beExhTriggerLow = stopBEatExh and (exhHitLow or (noExhLow and magHitLow))
if beMagTriggerLow or beExhTriggerLow
data.stopLowPrice := data.prevLow
data.c1IsHighContinuation := isHighContinuation
data.c1IsLowContinuation := isLowContinuation
cc_above_open_for_mag = tfC > tfO
threeExp_show_mag_high = cc_is_3 and show3Expansions and show3ExpMagnitude and (not threeExpRequireFTFC or ftfc_up) and cc_above_open_for_mag
threeExp_show_mag_low = cc_is_3 and show3Expansions and show3ExpMagnitude and (not threeExpRequireFTFC or ftfc_down) and not cc_above_open_for_mag
// FIX CSS-2: require global showExhaustion, mirroring how outside-bar magnitude requires
// global showMagnitude (via force_mag). Was asymmetric: OB exhaustion drew with Show Exhaustion off.
threeExp_show_exh_high = cc_is_3 and show3Expansions and showExhaustion and show3ExpExhaustion and (not threeExpRequireFTFC or ftfc_up) and cc_above_open_for_mag
threeExp_show_exh_low = cc_is_3 and show3Expansions and showExhaustion and show3ExpExhaustion and (not threeExpRequireFTFC or ftfc_down) and not cc_above_open_for_mag
// Gate inside→3 mag behind show3Expansions to prevent orphan magnitude when 3-exp display is off
inside_3u_setup = isInside and cc_is_3 and cc_above_open_for_mag and show3Expansions and (not threeExpRequireFTFC or ftfc_up)
inside_3d_setup = isInside and cc_is_3 and not cc_above_open_for_mag and show3Expansions and (not threeExpRequireFTFC or ftfc_down)
// Directional F2 mag/exh suppression
// F2u (failed bullish breakout → bearish): suppress bullish mag/exh (upside failed)
// F2d (failed bearish breakout → bullish): suppress bearish mag/exh (downside failed)
p3_suppress_mag_high = is_f2u
p3_suppress_mag_low = is_f2d
cont_suppress_mag_high = is_22_cont_high or is_32_cont_high
cont_suppress_mag_low = is_22_cont_low or is_32_cont_low
high_suppress_base = three_2d_suppress_high or inside_breakout_suppress_high or cc_3d_suppress_high
low_suppress_base = three_2u_suppress_low or inside_breakout_suppress_low or cc_3u_suppress_low
exh_high_valid = not na(data.exhHighPrice) and data.exhHighPrice != data.prevHigh
exh_low_valid = not na(data.exhLowPrice) and data.exhLowPrice != data.prevLow
cont_show_exh_high = showExhaustion and drawHigh and cont_suppress_mag_high
cont_show_exh_low = showExhaustion and drawLow and cont_suppress_mag_low
force_mag_high = showMagnitude and (drawHigh or inside_3u_setup or threeExp_show_mag_high) and not p3_suppress_mag_high and not cont_suppress_mag_high
force_mag_low = showMagnitude and (drawLow or inside_3d_setup or threeExp_show_mag_low) and not p3_suppress_mag_low and not cont_suppress_mag_low
mag_high_passes_ftfc = true
mag_low_passes_ftfc = true
price_hit_mag_high = tfH_curr >= data.prevMagHigh
price_hit_mag_low = tfL_curr <= data.prevMagLow
exh_high_mag_filter = not exhRequiresMagHit or price_hit_mag_high
exh_low_mag_filter = not exhRequiresMagHit or price_hit_mag_low
exh_high_base_condition = showExhaustion and mag_high_passes_ftfc and exh_high_mag_filter and exh_high_valid and data.exhHighPrice != data.prevLow and data.exhHighPrice != data.prevMagLow and not high_suppress_base and not p3_suppress_mag_high
exh_low_base_condition = showExhaustion and mag_low_passes_ftfc and exh_low_mag_filter and exh_low_valid and data.exhLowPrice != data.prevHigh and data.exhLowPrice != data.prevMagHigh and not low_suppress_base and not p3_suppress_mag_low
drawExhHigh = ((exh_high_base_condition and drawHigh) or (exh_high_base_condition and data.exhHighCrossed) or (cont_show_exh_high and exh_high_valid) or (threeExp_show_exh_high and exh_high_valid)) and (not exhOnlyWhenInForce or rawInForceHigh)
drawExhLow = ((exh_low_base_condition and drawLow) or (exh_low_base_condition and data.exhLowCrossed) or (cont_show_exh_low and exh_low_valid) or (threeExp_show_exh_low and exh_low_valid)) and (not exhOnlyWhenInForce or rawInForceLow)
exh_high_same_as_mag = drawExhHigh and data.exhHighPrice == data.prevMagHigh
exh_low_same_as_mag = drawExhLow and data.exhLowPrice == data.prevMagLow
mag_high_same_as_trigger = data.prevMagHigh == data.prevHigh
mag_low_same_as_trigger = data.prevMagLow == data.prevLow
// shouldDrawMagnitude fallback requires drawHigh/drawLow to prevent orphan mag lines
// when signal toggles are off but signal_in_force fires
drawMagHigh = ((force_mag_high and mag_high_passes_ftfc and not c1_broke_mag_high) or (drawHigh and shouldDrawMagnitude(c1_broke_mag_high, signal_in_force_high))) and not high_suppress_base and not exh_high_same_as_mag and not p3_suppress_mag_high and not cont_suppress_mag_high and not mag_high_same_as_trigger and (not magOnlyWhenInForce or rawInForceHigh)
drawMagLow = ((force_mag_low and mag_low_passes_ftfc and not c1_broke_mag_low) or (drawLow and shouldDrawMagnitude(c1_broke_mag_low, signal_in_force_low))) and not low_suppress_base and not exh_low_same_as_mag and not p3_suppress_mag_low and not cont_suppress_mag_low and not mag_low_same_as_trigger and (not magOnlyWhenInForce or rawInForceLow)
if populateDebug
debugData.tfLabel := getTimeframeLabel(debugTfStr)
debugData.c1_was_2u := c1_was_2u
debugData.c1_was_2d := c1_was_2d
debugData.c1_is_3 := c1_is_3
debugData.isHammer := isHammer
debugData.isShooter := isShooter
debugData.isInside := isInside
debugData.cc_is_2u := cc_is_2u
debugData.cc_is_2d := cc_is_2d
debugData.cc_is_3 := cc_is_3
debugData.cc_broke_high := cc_broke_high
debugData.cc_broke_low := cc_broke_low
debugData.is_f2u := is_f2u
debugData.is_f2d := is_f2d
debugData.pre_f2u := pre_f2u
debugData.pre_f2d := pre_f2d
debugData.ftfc_up := ftfc_up
debugData.ftfc_down := ftfc_down
debugData.is_hammer_reversal := is_hammer_reversal
debugData.is_hammer_momo := is_hammer_momo
debugData.hammer_shows := hammer_shows
debugData.is_shooter_reversal := is_shooter_reversal
debugData.is_shooter_momo := is_shooter_momo
debugData.shooter_shows := shooter_shows
debugData.drawHigh := drawHigh
debugData.drawLow := drawLow
debugData.drawMagHigh := drawMagHigh
debugData.drawMagLow := drawMagLow
debugData.drawExhHigh := drawExhHigh
debugData.drawExhLow := drawExhLow
debugData.force_mag_high := force_mag_high
debugData.force_mag_low := force_mag_low
debugData.mag_high_passes_ftfc := mag_high_passes_ftfc
debugData.mag_low_passes_ftfc := mag_low_passes_ftfc
highColor = color.gray
lowColor = color.gray
highStyle = "Dashed"
lowStyle = "Dashed"
cc_is_inside = not cc_broke_high and not cc_broke_low and not cc_is_3
if drawHigh
if cc_is_3
cc_close_below = tfC < data.prevLow
cc_closing_up = tfC > tfO
highColor := COLOR_3_HIGH
if show3Expansions and cc_closing_up and not cc_close_below
highStyle := "Dashed"
else
highStyle := "Solid"
else if is_f2d
highColor := COLOR_FAILED_2D
highStyle := "Solid"
else if c1_is_3 and cc_is_2u
highColor := colorBullishActive
highStyle := "Dashed"
else if c1_is_3 and cc_is_inside
highColor := COLOR_INSIDE_HIGH
highStyle := "Dashed"
else if c1_is_3
highColor := COLOR_3_HIGH
highStyle := "Solid"
else if cc_broke_high
highColor := colorBullishActive
highStyle := "Dashed"
else if cc_is_inside
highColor := COLOR_INSIDE_HIGH
highStyle := "Dashed"
else
highColor := colorBullishActive
highStyle := "Dashed"
if drawLow
if cc_is_3
cc_close_above = tfC > data.prevHigh
cc_closing_down = tfC < tfO
lowColor := COLOR_3_LOW
if show3Expansions and cc_closing_down and not cc_close_above
lowStyle := "Dashed"
else
lowStyle := "Solid"
else if is_f2u
lowColor := COLOR_FAILED_2U
lowStyle := "Solid"
else if c1_is_3 and cc_is_2d
lowColor := colorBearishActive
lowStyle := "Dashed"
else if c1_is_3 and cc_is_inside
lowColor := COLOR_INSIDE_LOW
lowStyle := "Dashed"
else if c1_is_3
lowColor := COLOR_3_LOW
lowStyle := "Solid"
else if cc_broke_low
lowColor := colorBearishActive
lowStyle := "Dashed"
else if cc_is_inside
lowColor := COLOR_INSIDE_LOW
lowStyle := "Dashed"
else
lowColor := colorBearishActive
lowStyle := "Dashed"
// F2 override: side being failed goes dashed (color preserved from pattern logic)
if is_f2u or pre_f2u
highStyle := "Dashed"
if is_f2d or pre_f2d
lowStyle := "Dashed"
retHighStyle := highStyle
retLowStyle := lowStyle
// Compute phase: populate return values from draw conditions
periodDuration = timeframe.in_seconds(tf) * 1000
lineEndTime = tfCurrTime + periodDuration
if drawHigh
retHigh := data.prevHigh
retHighColor := highColor
retDrawHigh := true
retTime := lineEndTime
if drawLow
retLow := data.prevLow
retLowColor := lowColor
retDrawLow := true
retTime := lineEndTime
bool f2SuppressesOpen = (is_f2u or is_f2d or pre_f2u or pre_f2d) and showF2OpenLine
if showOpen and (drawHigh or drawLow) and not f2SuppressesOpen
retOpen := data.currOpen
retOpenColor := color.gray
retDrawOpen := true
retTime := lineEndTime
if drawMagHigh
retMagHigh := data.prevMagHigh
retMagHighColor := data.magHighCrossed ? COLOR_HIT_HIGH : colorBullishActive
retDrawMagHigh := true
retTime := lineEndTime
if drawMagLow
retMagLow := data.prevMagLow
retMagLowColor := data.magLowCrossed ? COLOR_HIT_LOW : colorBearishActive
retDrawMagLow := true
retTime := lineEndTime
if drawExhHigh
retExhHigh := data.exhHighPrice
retExhHighColor := data.exhHighCrossed ? COLOR_HIT_HIGH : colorBullishActive
retDrawExhHigh := true
retTime := lineEndTime
if drawExhLow
retExhLow := data.exhLowPrice
retExhLowColor := data.exhLowCrossed ? COLOR_HIT_LOW : colorBearishActive
retDrawExhLow := true
retTime := lineEndTime
drawStopHigh = showStopLevels and data.stopHighTriggered
drawStopLow = showStopLevels and data.stopLowTriggered
if drawStopHigh and not na(data.stopHighPrice)
retStopHigh := data.stopHighPrice
retDrawStopHigh := true
if drawStopLow and not na(data.stopLowPrice)
retStopLow := data.stopLowPrice
retDrawStopLow := true
// F2 open price for pre-F2 and confirmed F2
if pre_f2d and not na(tfO)
retPreF2d := true
retF2OpenPrice := tfO
else if is_f2d and not na(tfO)
retF2OpenPrice := tfO
if pre_f2u and not na(tfO)
retPreF2u := true
retF2OpenPrice := tfO
else if is_f2u and not na(tfO)
retF2OpenPrice := tfO
tfLabelStr = getTimeframeLabel(tf)
ProcessingResult.new(retHigh, retLow, retOpen, retMagHigh, retMagLow, retExhHigh, retExhLow, retHighColor, retLowColor, retOpenColor, retMagHighColor, retMagLowColor, retExhHighColor, retExhLowColor, retDrawHigh, retDrawLow, retDrawOpen, retDrawMagHigh, retDrawMagLow, retDrawExhHigh, retDrawExhLow, retTime, tfLabelStr, retIsF2u, retIsF2d, retC1Is3, retC3Num, retC2Num, retC1Num, retC1Type, retCCType, retSignalInForceHigh, retSignalInForceLow, retStopHigh, retStopLow, retDrawStopHigh, retDrawStopLow, retPreF2u, retPreF2d, retF2OpenPrice, tfCurrTime, retC1WasF2, retC2WasF2, retHighStyle, retLowStyle)
// Render signal levels based on fully-resolved ProcessingResult flags
// Called AFTER directional filter has modified flags, so no draw-then-delete needed
renderSignalLevels(TimeframeData data, ProcessingResult r, int lineWidth, int lineEndTime) =>
if barstate.islast and not na(r)
// High trigger line
if r.drawHigh
data.highLine := updateOrCreateLine(data.highLine, data.prevTime, r.high, lineEndTime, r.high, r.highColor, lineWidth, getLineStyle(r.highStyle))
else
data.highLine := deleteLine(data.highLine)
// Low trigger line
if r.drawLow
data.lowLine := updateOrCreateLine(data.lowLine, data.prevTime, r.low, lineEndTime, r.low, r.lowColor, lineWidth, getLineStyle(r.lowStyle))
else
data.lowLine := deleteLine(data.lowLine)
// Open reference line
if r.drawOpen
data.openLine := updateOrCreateLine(data.openLine, r.ccStartTime, r.open, lineEndTime, r.open, color.gray, lineWidth, line.style_dotted)
else
data.openLine := deleteLine(data.openLine)
// Magnitude high
if r.drawMagHigh
data.magHighLine := updateOrCreateLine(data.magHighLine, data.prevMagTime, r.magHigh, lineEndTime, r.magHigh, r.magHighColor, lineWidth, line.style_solid)
else
data.magHighLine := deleteLine(data.magHighLine)
// Magnitude low
if r.drawMagLow
data.magLowLine := updateOrCreateLine(data.magLowLine, data.prevMagTime, r.magLow, lineEndTime, r.magLow, r.magLowColor, lineWidth, line.style_solid)
else
data.magLowLine := deleteLine(data.magLowLine)
// Exhaustion high
if r.drawExhHigh
data.exhHighLine := updateOrCreateLine(data.exhHighLine, data.exhHighTime, r.exhHigh, lineEndTime, r.exhHigh, r.exhHighColor, lineWidth, line.style_solid)
else
data.exhHighLine := deleteLine(data.exhHighLine)
// Exhaustion low
if r.drawExhLow
data.exhLowLine := updateOrCreateLine(data.exhLowLine, data.exhLowTime, r.exhLow, lineEndTime, r.exhLow, r.exhLowColor, lineWidth, line.style_solid)
else
data.exhLowLine := deleteLine(data.exhLowLine)
// Stop high (bullish signal stop at C1 low)
bool stopHighOverlaps = r.drawStopHigh and not na(r.stopHigh) and not na(r.high) and math.round(r.stopHigh / syminfo.mintick) == math.round(r.high / syminfo.mintick)
if r.drawStopHigh and not na(r.stopHigh) and not stopHighOverlaps
data.stopHighLine := updateOrCreateLine(data.stopHighLine, r.ccStartTime, r.stopHigh, lineEndTime, r.stopHigh, colorStopHigh, lineWidth, line.style_solid)
else
data.stopHighLine := deleteLine(data.stopHighLine)
// Stop low (bearish signal stop at C1 high)
bool stopLowOverlaps = r.drawStopLow and not na(r.stopLow) and not na(r.low) and math.round(r.stopLow / syminfo.mintick) == math.round(r.low / syminfo.mintick)
if r.drawStopLow and not na(r.stopLow) and not stopLowOverlaps
data.stopLowLine := updateOrCreateLine(data.stopLowLine, r.ccStartTime, r.stopLow, lineEndTime, r.stopLow, colorStopLow, lineWidth, line.style_solid)
else
data.stopLowLine := deleteLine(data.stopLowLine)
// F2 open reference line
bool f2OpenOverlapsLow = not na(r.f2OpenPrice) and not na(r.low) and math.round(r.f2OpenPrice / syminfo.mintick) == math.round(r.low / syminfo.mintick)
bool f2OpenOverlapsHigh = not na(r.f2OpenPrice) and not na(r.high) and math.round(r.f2OpenPrice / syminfo.mintick) == math.round(r.high / syminfo.mintick)
bool drawF2Open = showF2OpenLine and not na(r.f2OpenPrice) and (r.preF2d or r.preF2u or r.isF2d or r.isF2u)
if drawF2Open
color f2Color = r.preF2d ? colorBullishActive : r.isF2d ? COLOR_FAILED_2D : r.preF2u ? colorBearishActive : COLOR_FAILED_2U
bool f2Overlaps = (r.preF2d or r.isF2d) ? f2OpenOverlapsLow : f2OpenOverlapsHigh
if not f2Overlaps
data.f2OpenLine := updateOrCreateLine(data.f2OpenLine, r.ccStartTime, r.f2OpenPrice, lineEndTime, r.f2OpenPrice, f2Color, lineWidth, line.style_dotted)
else
data.f2OpenLine := deleteLine(data.f2OpenLine)
else
data.f2OpenLine := deleteLine(data.f2OpenLine)
// Action window boxes
data.actionWindowBullish := deleteBox(data.actionWindowBullish)
data.actionWindowBearish := deleteBox(data.actionWindowBearish)
// FIX TAW-1: each direction must check only its OWN trigger. The old guards required
// BOTH r.high and r.low to be non-na, but the opposite trigger is suppressed exactly when
// a signal goes in force (inside_breakout_suppress_low/high nulls the other side, and
// one-sided directional signals never draw it). That made the main magnitude/exhaustion
// Take Action Window unable to render for in-force signals - the default config. Each box's
// geometry only uses its own side (bullish: prevMagHigh..r.high; bearish: r.low..prevMagLow),
// so requiring the opposite trigger was both unnecessary and the cause of the window vanishing.
bool currentlyAboveTrigger = not na(r.high) and close > r.high
bool currentlyBelowTrigger = not na(r.low) and close < r.low
bool isF2uSignal = r.isF2u
bool isF2dSignal = r.isF2d
bool ccIs3 = str.startswith(r.ccType, "3")
if showTakeActionWindows or tawExtendToExhaustion
bool p3SuppressHigh = isF2uSignal
bool p3SuppressLow = isF2dSignal
mag_bullish_qualifies = showTakeActionWindows and r.drawHigh and not p3SuppressHigh and not ccIs3 and (not tawOnlyWhenInForce or r.signalInForceHigh) and currentlyAboveTrigger
mag_bearish_qualifies = showTakeActionWindows and r.drawLow and not p3SuppressLow and not ccIs3 and (not tawOnlyWhenInForce or r.signalInForceLow) and currentlyBelowTrigger
exh_bullish_qualifies = tawExtendToExhaustion and r.drawExhHigh and not p3SuppressHigh and not ccIs3 and (not tawExhOnlyWhenInForce or r.signalInForceHigh) and currentlyAboveTrigger
exh_bearish_qualifies = tawExtendToExhaustion and r.drawExhLow and not p3SuppressLow and not ccIs3 and (not tawExhOnlyWhenInForce or r.signalInForceLow) and currentlyBelowTrigger
if exh_bullish_qualifies and not na(data.exhHighPrice) and not data.exhHighCrossed
data.actionWindowBullish := updateOrCreateBox(data.actionWindowBullish, data.prevTime, data.exhHighPrice, lineEndTime, r.high, tawBullishFill, tawBullishBorder)
else if mag_bullish_qualifies and not na(data.prevMagHigh) and not data.magHighCrossed
data.actionWindowBullish := updateOrCreateBox(data.actionWindowBullish, data.prevTime, data.prevMagHigh, lineEndTime, r.high, tawBullishFill, tawBullishBorder)
if exh_bearish_qualifies and not na(data.exhLowPrice) and not data.exhLowCrossed
data.actionWindowBearish := updateOrCreateBox(data.actionWindowBearish, data.prevTime, r.low, lineEndTime, data.exhLowPrice, tawBearishFill, tawBearishBorder)
else if mag_bearish_qualifies and not na(data.prevMagLow) and not data.magLowCrossed
data.actionWindowBearish := updateOrCreateBox(data.actionWindowBearish, data.prevTime, r.low, lineEndTime, data.prevMagLow, tawBearishFill, tawBearishBorder)
if showP3TakeActionWindows and show_p3_toggle
if isF2dSignal and r.drawHigh and not currentlyAboveTrigger and (not tawOnlyWhenInForce or r.signalInForceHigh)
data.actionWindowBullish := updateOrCreateBox(data.actionWindowBullish, data.prevTime, r.high, lineEndTime, r.low, tawBullishFill, tawBullishBorder)
if isF2uSignal and r.drawLow and not currentlyBelowTrigger and (not tawOnlyWhenInForce or r.signalInForceLow)
data.actionWindowBearish := updateOrCreateBox(data.actionWindowBearish, data.prevTime, r.high, lineEndTime, r.low, tawBearishFill, tawBearishBorder)
if show3ExpTakeActionWindows and show3Expansions and ccIs3
bool ccExpandingUp = str.endswith(r.ccType, "u")
if ccExpandingUp and r.drawHigh and not na(data.prevMagHigh) and not data.magHighCrossed and (not tawOnlyWhenInForce or r.signalInForceHigh)
data.actionWindowBullish := updateOrCreateBox(data.actionWindowBullish, data.prevTime, data.prevMagHigh, lineEndTime, r.high, tawBullishFill, tawBullishBorder)
if not ccExpandingUp and r.drawLow and not na(data.prevMagLow) and not data.magLowCrossed and (not tawOnlyWhenInForce or r.signalInForceLow)
data.actionWindowBearish := updateOrCreateBox(data.actionWindowBearish, data.prevTime, r.low, lineEndTime, data.prevMagLow, tawBearishFill, tawBearishBorder)
true
// ============================================================================
// SECTION 8: LABEL COLLECTION & CONSOLIDATION
// ============================================================================
var SimpleLabelData highLabels = SimpleLabelData.new(array.new<float>(), array.new<string>(), array.new<color>(), array.new<int>())
var SimpleLabelData lowLabels = SimpleLabelData.new(array.new<float>(), array.new<string>(), array.new<color>(), array.new<int>())
var SimpleLabelData openLabels = SimpleLabelData.new(array.new<float>(), array.new<string>(), array.new<color>(), array.new<int>())
// FIX P1-i: label pool replaces the delete-all-and-recreate-every-tick pattern. Labels are
// updated in place via setters; surplus pooled labels are trimmed after each render. acquireLabel
// takes the pool object by reference so it can advance the cursor (functions cannot mutate globals).
var LabelPoolT lblPool = LabelPoolT.new(array.new<label>(), 0)
acquireLabel(pool, int x, float y, string txt, bool useBarIndex, color bg, color txtCol, sz) =>
xl = useBarIndex ? xloc.bar_index : xloc.bar_time
label lbl = na
if pool.used < array.size(pool.items)
lbl := array.get(pool.items, pool.used)
label.set_xloc(lbl, x, xl)
label.set_y(lbl, y)
label.set_text(lbl, txt)
label.set_color(lbl, bg)
label.set_textcolor(lbl, txtCol)
label.set_size(lbl, sz)
label.set_style(lbl, label.style_label_left)
else
lbl := label.new(x, y, txt, xloc = xl, style = label.style_label_left, color = bg, textcolor = txtCol, size = sz)
array.push(pool.items, lbl)
pool.used += 1
lbl
collectTimeframeLabels(TimeframeData data, ProcessingResult r) =>
ccIsInside = r.ccType == "1"
ccIs3 = str.startswith(r.ccType, "3")
// Detect if stop is at break even (same price as trigger level)
bool stopHighAtBE = r.drawStopHigh and not na(r.stopHigh) and not na(r.high) and math.round(r.stopHigh / syminfo.mintick) == math.round(r.high / syminfo.mintick)
bool stopLowAtBE = r.drawStopLow and not na(r.stopLow) and not na(r.low) and math.round(r.stopLow / syminfo.mintick) == math.round(r.low / syminfo.mintick)
if r.drawHigh or r.drawLow
patternSuffixHigh = data.c1IsHammer ? " HAM" : ""
patternSuffixLow = data.c1IsShooter ? " SHO" : ""
string comboStrHigh = ""
string comboStrLow = ""
bool c1IsInside = r.c1Num == "1"
bool is22Pattern = r.c1Num == "2"
bool is32Pattern = r.c1Num == "3"
bool isDoubleInside = r.c1Num == "1" and r.c2Num == "1"
// F2-aware display strings for label accuracy
string c1TypeF2 = (r.c1WasF2 ? "F" : "") + r.c1Type
string c1NumF2 = (r.c1WasF2 ? "F" : "") + r.c1Num
string c2NumF2 = (r.c2WasF2 ? "F" : "") + r.c2Num
if simpleLabelMode
// Simple mode labels: ↑/↓ direction + CONT, REV, INSIDE, EXP, FAILING, OUTSIDE
string simpleHigh = ""
string simpleLow = ""
bool ccIs2u = str.startswith(r.ccType, "2u")
bool ccIs2d = str.startswith(r.ccType, "2d")
// Step 1: Base labels from pattern type
if ccIs3
simpleHigh := "↑EXP"
simpleLow := "↓EXP"
else if is32Pattern and ccIs2u
simpleHigh := "↑EXP"
simpleLow := "*OUTSIDE"
else if is32Pattern and ccIs2d
simpleHigh := "*OUTSIDE"
simpleLow := "↓EXP"
else if is32Pattern
simpleHigh := "↑EXP"
simpleLow := "↓EXP"
else if c1IsInside
if ccIsInside
simpleHigh := "*↑INSIDE"
simpleLow := "*↓INSIDE"
else
simpleHigh := "↑INSIDE"
simpleLow := "↓INSIDE"
else if ccIsInside
simpleHigh := data.c1IsHighContinuation ? "*↑CONTINUATION" : "*↑REVERSAL"
simpleLow := data.c1IsLowContinuation ? "*↓CONTINUATION" : "*↓REVERSAL"
else
simpleHigh := data.c1IsHighContinuation ? "↑CONTINUATION" : "↑REVERSAL"
simpleLow := data.c1IsLowContinuation ? "↓CONTINUATION" : "↓REVERSAL"
// Step 2: Apply ◆ confidence indicator to base label (before FAILING overlay)
if data.c1IsHammer
simpleHigh := simpleHigh + "◆"
if data.c1IsShooter
simpleLow := simpleLow + "◆"
// Step 3: F2 overlay - failing side keeps base label + FAILING, opposite side is potential outside
if r.isF2u or r.preF2u
// Bullish breakout failing: high label appends FAILING, low is potential outside
simpleHigh := simpleHigh + " FAILING"
simpleLow := "*OUTSIDE"
else if r.isF2d or r.preF2d
// Bearish breakout failing: low label appends FAILING, high is potential outside
simpleLow := simpleLow + " FAILING"
simpleHigh := "*OUTSIDE"
comboStrHigh := simpleHigh
comboStrLow := simpleLow
// Diamond already applied above, clear suffix to avoid doubling
patternSuffixHigh := ""
patternSuffixLow := ""
else
// Full combo mode
if ccIsInside
if isDoubleInside
comboStrHigh := "*" + r.c3Num + c2NumF2 + c1NumF2 + "2u"
comboStrLow := "*" + r.c3Num + c2NumF2 + c1NumF2 + "2d"
else if c1IsInside
comboStrHigh := "*" + c2NumF2 + c1NumF2 + "2u"
comboStrLow := "*" + c2NumF2 + c1NumF2 + "2d"
else if is32Pattern
comboStrHigh := "*32u"
comboStrLow := "*32d"
else
if r.c2Num == "3"
comboStrHigh := "*3" + c1TypeF2 + "2u"
comboStrLow := "*3" + c1TypeF2 + "2d"
else
comboStrHigh := "*" + c1TypeF2 + "2u"
comboStrLow := "*" + c1TypeF2 + "2d"
else
// Strip F2 prefix from ccType for base combo (F2 overlay handles it separately)
string baseCCType = r.ccType
if r.isF2u
baseCCType := "2u"
else if r.isF2d
baseCCType := "2d"
string defaultCombo = ""
string c2Prefix = (r.c2Num == "3" and is22Pattern) ? "3" : ""
if is22Pattern
defaultCombo := c2Prefix + c1TypeF2 + baseCCType
else if is32Pattern
defaultCombo := "3" + baseCCType
else if isDoubleInside
defaultCombo := r.c3Num + c2NumF2 + c1NumF2 + baseCCType
else
defaultCombo := c2NumF2 + c1NumF2 + baseCCType
// F2 labels: failing side = c1Type + f2 tag, opposite side = potential outside
if r.isF2u
comboStrHigh := c1TypeF2 + "f2u"
comboStrLow := "*" + c1TypeF2 + "3d"
else if r.isF2d
comboStrLow := c1TypeF2 + "f2d"
comboStrHigh := "*" + c1TypeF2 + "3u"
else
comboStrHigh := defaultCombo
comboStrLow := defaultCombo
if r.drawHigh
string highText = r.tfLabel + " " + formatCombo(comboStrHigh) + patternSuffixHigh + (showHighLowInLabel ? " H" : "") + (stopHighAtBE ? " + STOP" : "")
array.push(highLabels.prices, r.high)
array.push(highLabels.texts, highText)
array.push(highLabels.colors, r.highColor)
array.push(highLabels.times, r.time)
if r.drawLow
string lowText = r.tfLabel + " " + formatCombo(comboStrLow) + patternSuffixLow + (showHighLowInLabel ? " L" : "") + (stopLowAtBE ? " + STOP" : "")
array.push(lowLabels.prices, r.low)
array.push(lowLabels.texts, lowText)
array.push(lowLabels.colors, r.lowColor)
array.push(lowLabels.times, r.time)
if r.drawOpen
array.push(openLabels.prices, r.open)
array.push(openLabels.texts, r.tfLabel + " OPEN")
array.push(openLabels.colors, r.openColor)
array.push(openLabels.times, r.time)
if r.drawMagHigh
array.push(highLabels.prices, r.magHigh)
array.push(highLabels.texts, r.tfLabel + (simpleLabelMode ? " TARGET" : " MAG") + (showHighLowInLabel ? " H" : ""))
array.push(highLabels.colors, r.magHighColor)
array.push(highLabels.times, r.time)
if r.drawMagLow
array.push(lowLabels.prices, r.magLow)
array.push(lowLabels.texts, r.tfLabel + (simpleLabelMode ? " TARGET" : " MAG") + (showHighLowInLabel ? " L" : ""))
array.push(lowLabels.colors, r.magLowColor)
array.push(lowLabels.times, r.time)
if r.drawExhHigh
array.push(highLabels.prices, r.exhHigh)
array.push(highLabels.texts, r.tfLabel + (simpleLabelMode ? " FINAL" : " EXH") + (showHighLowInLabel ? " H" : ""))
array.push(highLabels.colors, r.exhHighColor)
array.push(highLabels.times, r.time)
if r.drawExhLow
array.push(lowLabels.prices, r.exhLow)
array.push(lowLabels.texts, r.tfLabel + (simpleLabelMode ? " FINAL" : " EXH") + (showHighLowInLabel ? " L" : ""))
array.push(lowLabels.colors, r.exhLowColor)
array.push(lowLabels.times, r.time)
// Stop labels - bullish stop at C1 low (lowLabels), bearish stop at C1 high (highLabels)
// Skip when at break even (consolidated into trigger label above)
if r.drawStopHigh and not stopHighAtBE
array.push(lowLabels.prices, r.stopHigh)
array.push(lowLabels.texts, r.tfLabel + " STOP")
array.push(lowLabels.colors, colorStopHigh)
array.push(lowLabels.times, r.time)
if r.drawStopLow and not stopLowAtBE
array.push(highLabels.prices, r.stopLow)
array.push(highLabels.texts, r.tfLabel + " STOP")
array.push(highLabels.colors, colorStopLow)
array.push(highLabels.times, r.time)
// Pre-F2: CC open shown as potential trigger level
// Skip if open price matches C1 level (FAILING label already marks that price)
if showF2OpenLine and r.preF2d and not na(r.f2OpenPrice)
bool preF2dOpenOverlaps = math.round(r.f2OpenPrice / syminfo.mintick) == math.round(r.low / syminfo.mintick)
if not preF2dOpenOverlaps
array.push(highLabels.prices, r.f2OpenPrice)
array.push(highLabels.texts, r.tfLabel + (simpleLabelMode ? " OPEN" : " f2d"))
array.push(highLabels.colors, colorBullishActive)
array.push(highLabels.times, r.time)
if showF2OpenLine and r.preF2u and not na(r.f2OpenPrice)
bool preF2uOpenOverlaps = math.round(r.f2OpenPrice / syminfo.mintick) == math.round(r.high / syminfo.mintick)
if not preF2uOpenOverlaps
array.push(lowLabels.prices, r.f2OpenPrice)
array.push(lowLabels.texts, r.tfLabel + (simpleLabelMode ? " OPEN" : " f2u"))
array.push(lowLabels.colors, colorBearishActive)
array.push(lowLabels.times, r.time)
// Confirmed F2: CC open as trigger reference (dotted line needs label)
// Gate: show if at least one side is drawing (trend-aligned F2 keeps one side,
// counter-trend suppresses both via directional filter)
if showF2OpenLine and r.isF2d and not r.preF2d and not na(r.f2OpenPrice) and (r.drawHigh or r.drawLow)
bool f2dOpenOverlaps = math.round(r.f2OpenPrice / syminfo.mintick) == math.round(r.low / syminfo.mintick)
if not f2dOpenOverlaps
array.push(highLabels.prices, r.f2OpenPrice)
array.push(highLabels.texts, r.tfLabel + (simpleLabelMode ? " OPEN" : " f2d"))
array.push(highLabels.colors, COLOR_FAILED_2D)
array.push(highLabels.times, r.time)
if showF2OpenLine and r.isF2u and not r.preF2u and not na(r.f2OpenPrice) and (r.drawHigh or r.drawLow)
bool f2uOpenOverlaps = math.round(r.f2OpenPrice / syminfo.mintick) == math.round(r.high / syminfo.mintick)
if not f2uOpenOverlaps
array.push(lowLabels.prices, r.f2OpenPrice)
array.push(lowLabels.texts, r.tfLabel + (simpleLabelMode ? " OPEN" : " f2u"))
array.push(lowLabels.colors, COLOR_FAILED_2U)
array.push(lowLabels.times, r.time)
consolidateAndCreate(labelData, pool) =>
numLabels = array.size(labelData.prices)
if numLabels > 0
consolidatedPrices = array.new<float>()
consolidatedTexts = array.new<string>()
consolidatedColors = array.new<color>()
consolidatedTimes = array.new<int>()
// v2.2.0: Map-based dedup replaces O(n^2) inner loop
var priceToIndex = map.new<float, int>()
map.clear(priceToIndex)
for i = 0 to numLabels - 1
currentPrice = array.get(labelData.prices, i)
currentText = array.get(labelData.texts, i)
currentColor = array.get(labelData.colors, i)
currentTime = array.get(labelData.times, i)
roundedCurrentPrice = math.round(currentPrice / syminfo.mintick) * syminfo.mintick
if map.contains(priceToIndex, roundedCurrentPrice)
idx = map.get(priceToIndex, roundedCurrentPrice)
existingText = array.get(consolidatedTexts, idx)
array.set(consolidatedTexts, idx, existingText + " + " + currentText)
else
map.put(priceToIndex, roundedCurrentPrice, array.size(consolidatedPrices))
array.push(consolidatedPrices, currentPrice)
array.push(consolidatedTexts, currentText)
array.push(consolidatedColors, currentColor)
array.push(consolidatedTimes, currentTime)
labelBgColor = color.new(labelBackgroundColor, labelBackgroundTransparency)
finalSize = array.size(consolidatedPrices)
tfSeconds = timeframe.in_seconds() * 1000
for i = 0 to finalSize - 1
price = array.get(consolidatedPrices, i)
labelText = array.get(consolidatedTexts, i)
labelColor = array.get(consolidatedColors, i)
labelTime = array.get(consolidatedTimes, i)
displayText = showPriceInLabel ? labelText + " " + str.tostring(price, format.mintick) : labelText
lblSize = getSize(labelTextSize)
if showTimelineLabels
acquireLabel(pool, labelTime + (labelOffset * tfSeconds), price, displayText, false, labelBgColor, labelColor, lblSize)
if showFloatingLabels
acquireLabel(pool, bar_index + floatingLabelOffset, price, displayText, true, labelBgColor, labelColor, lblSize)
if barstate.islast
array.clear(highLabels.prices)
array.clear(highLabels.texts)
array.clear(highLabels.colors)
array.clear(highLabels.times)
array.clear(lowLabels.prices)
array.clear(lowLabels.texts)
array.clear(lowLabels.colors)
array.clear(lowLabels.times)
array.clear(openLabels.prices)
array.clear(openLabels.texts)
array.clear(openLabels.colors)
array.clear(openLabels.times)
lblPool.used := 0 // FIX P1-i: reset pool cursor; labels are reused in place, surplus trimmed after render
showAnyLabels = showTimelineLabels or showFloatingLabels
// ============================================================================
// SECTION 9: MAIN PROCESSING LOOP (with Lead Signal Filter)
// ============================================================================
getSignalDirection(ProcessingResult r) =>
int direction = 0
if not na(r)
// Use signalInForce which checks both that signal qualifies AND price is currently beyond trigger
if r.signalInForceHigh and r.signalInForceLow
// Both directions in force (rare). FIX LEAD-F2-TIEBREAK-2: F2u is bearish and F2d is bullish,
// so resolve those explicitly first. The generic close-suffix test below maps "F2u" -> +1 (bull),
// which is inverted and would flip the Lead anchor (and all counter-trend suppression).
if r.isF2u
direction := -1
else if r.isF2d
direction := 1
else if str.endswith(r.ccType, "u")
direction := 1
else
direction := -1
else if r.signalInForceHigh
direction := 1
else if r.signalInForceLow
direction := -1
direction
for i = 5 to 0
cfg = array.get(tfConfigs, i)
if cfg.enabled
data = array.get(tfDataArr, i)
raw = array.get(tfRawArr, i)
isDebugTF = showDebugPanel and debugTimeframe == cfg.debugLabel
result = computeSignalState(data, cfg.tf, raw, cfg.showOpen and not previewNextPeriod and not raw.isPreview, isDebugTF, cfg.tf) // FIX HTF-STRADDLE-1: no open line for straddled slots (proxy open)
array.set(tfResults, i, result)
var int anchorIndex = -1
var int anchorDirection = 0
if barstate.islast and enableDirectionalFilter
anchorIndex := -1
anchorDirection := 0
for i = 5 to 0
cfg = array.get(tfConfigs, i)
if cfg.enabled and anchorIndex == -1
r = array.get(tfResults, i)
dir = getSignalDirection(r)
if dir != 0
anchorIndex := i
anchorDirection := dir
if barstate.islast and enableDirectionalFilter and anchorIndex > 0 and anchorDirection != 0
for i = 0 to anchorIndex - 1
cfg = array.get(tfConfigs, i)
if cfg.enabled
data = array.get(tfDataArr, i)
r = array.get(tfResults, i)
if not na(r)
// Unified directional filter: modify flags only (render phase handles drawing)
bool suppressHigh = anchorDirection == -1
bool isCounterF2 = (suppressHigh ? r.isF2d : r.isF2u) and not r.c1Is3
bool isTrendF2 = (suppressHigh ? r.isF2u : r.isF2d) and not r.c1Is3
if isCounterF2
// Counter-trend confirmed F2: suppress ALL flags
r.suppressAllFlags()
r.drawOpen := false
// FIX P1-g: also clear in-force so the data table / Lead row do not keep
// highlighting a suppressed counter-trend TF as in-force (lines/labels/alerts
// were already suppressed via the draw flags; signalInForce was not).
r.signalInForceHigh := false
r.signalInForceLow := false
else if isTrendF2
// Trend-aligned confirmed F2: suppress counter-side extras only
if suppressHigh
r.drawMagHigh := false
r.drawExhHigh := false
r.drawStopHigh := false
else
r.drawMagLow := false
r.drawExhLow := false
r.drawStopLow := false
else
// Regular signal or pre-F2: suppress counter-trend side
if suppressHigh
r.suppressHighFlags()
r.signalInForceHigh := false // FIX P1-g: keep table/Lead consistent with hidden lines
else
r.suppressLowFlags()
r.signalInForceLow := false // FIX P1-g
// Pre-F2: suppress open line (not a confirmed reversal yet)
if r.preF2d or r.preF2u
r.preF2u := false
r.preF2d := false
// If active side not drawing, open is orphaned
bool activeSideDraw = suppressHigh ? r.drawLow : r.drawHigh
if not activeSideDraw
r.drawOpen := false
// Blanket catch: if both sides suppressed by any path, clean up open
if not r.drawHigh and not r.drawLow
r.drawOpen := false
if not na(data.openLine)
line.delete(data.openLine)
data.openLine := na
// Render phase: draw/update/delete all lines and boxes based on final flag state
if barstate.islast
for i = 5 to 0
cfg = array.get(tfConfigs, i)
if cfg.enabled
data = array.get(tfDataArr, i)
r = array.get(tfResults, i)
if not na(r)
periodDuration = timeframe.in_seconds(cfg.tf) * 1000
raw = array.get(tfRawArr, i)
lineEndTime = raw.ccTime + periodDuration
renderSignalLevels(data, r, cfg.lineWidth, lineEndTime)
if barstate.islast and showAnyLabels
for i = 5 to 0
cfg = array.get(tfConfigs, i)
if cfg.enabled
data = array.get(tfDataArr, i)
r = array.get(tfResults, i)
if not na(r)
// Label suppression matching line deletion logic (unified direction)
bool skipHighLabels = false
bool skipLowLabels = false
if enableDirectionalFilter and i < anchorIndex
bool suppressHigh = anchorDirection == -1
bool isCounterF2 = (suppressHigh ? r.isF2d : r.isF2u) and not r.c1Is3
bool isTrendF2 = (suppressHigh ? r.isF2u : r.isF2d) and not r.c1Is3
if isCounterF2
skipHighLabels := true
skipLowLabels := true
else if not isTrendF2
// Regular: suppress counter-trend side labels
if suppressHigh
skipHighLabels := true
else
skipLowLabels := true
if not skipHighLabels and not skipLowLabels
collectTimeframeLabels(data, r)
else
modifiedResult = ProcessingResult.new(skipHighLabels ? na : r.high, skipLowLabels ? na : r.low, r.open, skipHighLabels ? na : r.magHigh, skipLowLabels ? na : r.magLow, skipHighLabels ? na : r.exhHigh, skipLowLabels ? na : r.exhLow, r.highColor, r.lowColor, r.openColor, r.magHighColor, r.magLowColor, r.exhHighColor, r.exhLowColor, skipHighLabels ? false : r.drawHigh, skipLowLabels ? false : r.drawLow, r.drawOpen and ((not skipHighLabels and r.drawHigh) or (not skipLowLabels and r.drawLow)), skipHighLabels ? false : r.drawMagHigh, skipLowLabels ? false : r.drawMagLow, skipHighLabels ? false : r.drawExhHigh, skipLowLabels ? false : r.drawExhLow, r.time, r.tfLabel, r.isF2u, r.isF2d, r.c1Is3, r.c3Num, r.c2Num, r.c1Num, r.c1Type, r.ccType, r.signalInForceHigh, r.signalInForceLow, skipHighLabels ? na : r.stopHigh, skipLowLabels ? na : r.stopLow, skipHighLabels ? false : r.drawStopHigh, skipLowLabels ? false : r.drawStopLow, skipHighLabels ? false : r.preF2u, skipLowLabels ? false : r.preF2d, r.f2OpenPrice, r.ccStartTime, r.c1WasF2, r.c2WasF2)
collectTimeframeLabels(data, modifiedResult)
// ============================================================================
// SECTION 10: LINE SUPPRESSION
// ============================================================================
collectLinesFromTF(TimeframeData data, string tf, array<LineInfo> highLines, array<LineInfo> lowLines, array<LineInfo> openLines) =>
tfSec = timeframe.in_seconds(tf)
// FIX P1-h: each LineInfo now carries its owner slot + a kind code so suppression can
// null the originating field after deleting the line. Kinds: 0=high 1=low 2=magHigh
// 3=magLow 4=exhHigh 5=exhLow 6=open 7=f2Open.
if not na(data.highLine) and not na(data.prevHigh)
array.push(highLines, LineInfo.new(data.prevHigh, tfSec, data.highLine, data, 0))
if not na(data.lowLine) and not na(data.prevLow)
array.push(lowLines, LineInfo.new(data.prevLow, tfSec, data.lowLine, data, 1))
if not na(data.magHighLine) and not na(data.prevMagHigh)
array.push(highLines, LineInfo.new(data.prevMagHigh, tfSec, data.magHighLine, data, 2))
if not na(data.magLowLine) and not na(data.prevMagLow)
array.push(lowLines, LineInfo.new(data.prevMagLow, tfSec, data.magLowLine, data, 3))
if not na(data.exhHighLine) and not na(data.exhHighPrice)
array.push(highLines, LineInfo.new(data.exhHighPrice, tfSec, data.exhHighLine, data, 4))
if not na(data.exhLowLine) and not na(data.exhLowPrice)
array.push(lowLines, LineInfo.new(data.exhLowPrice, tfSec, data.exhLowLine, data, 5))
if not na(data.openLine) and not na(data.currOpen)
array.push(openLines, LineInfo.new(data.currOpen, tfSec, data.openLine, data, 6))
if not na(data.f2OpenLine) and not na(data.currOpen)
array.push(openLines, LineInfo.new(data.currOpen, tfSec, data.f2OpenLine, data, 7))
// Stop lines are intentionally excluded from line suppression.
// Stops must always remain visible for risk management regardless of
// overlapping trigger, magnitude, or exhaustion levels.
// FIX P1-h: null the owning TimeframeData line field after a suppressed line is deleted,
// so the render phase recreates it via line.new() next time (instead of calling line.set_*
// on a deleted id - undefined behavior), and a suppressed line can reappear once the price
// collision clears. Kinds match collectLinesFromTF.
nullSuppressedLine(TimeframeData d, int kind) =>
if kind == 0
d.highLine := na
else if kind == 1
d.lowLine := na
else if kind == 2
d.magHighLine := na
else if kind == 3
d.magLowLine := na
else if kind == 4
d.exhHighLine := na
else if kind == 5
d.exhLowLine := na
else if kind == 6
d.openLine := na
else if kind == 7
d.f2OpenLine := na
suppressLowerTFLines(array<LineInfo> lines) =>
numLines = array.size(lines)
if numLines > 1
var priceToMaxTF = map.new<float, int>()
var priceToInfo = map.new<float, LineInfo>()
map.clear(priceToMaxTF)
map.clear(priceToInfo)
for i = 0 to numLines - 1
lineI = array.get(lines, i)
roundedPrice = math.round(lineI.price / syminfo.mintick) * syminfo.mintick
if map.contains(priceToMaxTF, roundedPrice)
existingTF = map.get(priceToMaxTF, roundedPrice)
existingInfo = map.get(priceToInfo, roundedPrice)
if lineI.tfSeconds > existingTF
// current is higher-TF: delete the previously-kept lower-TF line and null its field
line.delete(existingInfo.lineRef)
nullSuppressedLine(existingInfo.owner, existingInfo.kind)
map.put(priceToMaxTF, roundedPrice, lineI.tfSeconds)
map.put(priceToInfo, roundedPrice, lineI)
else
// current loses: delete it and null its field
line.delete(lineI.lineRef)
nullSuppressedLine(lineI.owner, lineI.kind)
else
map.put(priceToMaxTF, roundedPrice, lineI.tfSeconds)
map.put(priceToInfo, roundedPrice, lineI)
true
if barstate.islast
var highLines = array.new<LineInfo>()
var lowLines = array.new<LineInfo>()
var openLines = array.new<LineInfo>()
array.clear(highLines)
array.clear(lowLines)
array.clear(openLines)
for i = 5 to 0
cfg = array.get(tfConfigs, i)
if cfg.enabled
data = array.get(tfDataArr, i)
collectLinesFromTF(data, cfg.tf, highLines, lowLines, openLines)
suppressLowerTFLines(highLines)
suppressLowerTFLines(lowLines)
suppressLowerTFLines(openLines)
if barstate.islast and showAnyLabels
consolidateAndCreate(highLabels, lblPool)
consolidateAndCreate(lowLabels, lblPool)
consolidateAndCreate(openLabels, lblPool)
// FIX P1-i: trim the pool to the number of labels actually used this bar. When labels are
// disabled, lblPool.used stays 0 (consolidate did not run), so all pooled labels are deleted.
// Runs every islast regardless of showAnyLabels so toggling labels off clears them.
if barstate.islast
while array.size(lblPool.items) > lblPool.used
label.delete(array.pop(lblPool.items))
// ============================================================================
// SECTION 11: DATA TABLE
// ============================================================================
getBarTypeColor(barType) =>
switch barType
"1u" => color.new(#ffeb3b, 0)
"1d" => color.new(#ff9800, 0)
"2u" => color.new(#4caf50, 0)
"2d" => color.new(#f23645, 0)
"F2u" => color.new(COLOR_FAILED_2U, 0)
"F2d" => color.new(COLOR_FAILED_2D, 0)
"3u" => color.new(colorBullish3Bar, 0)
"3d" => color.new(colorBearish3Bar, 0)
=> COLOR_BG_GRAY50
getBarTypeTextColor(barType) =>
switch barType
"1u" => #ffeb3b
"1d" => #ff9800
"2u" => #4caf50
"2d" => #f23645
"F2u" => COLOR_FAILED_2U
"F2d" => COLOR_FAILED_2D
"3u" => colorBullish3Bar
"3d" => colorBearish3Bar
=> color.gray
var table barTypeTable = na
var int enabledTFCount = 0
isCompactMode = tableMode == "Compact (Universal)"
if barstate.isfirst
enabledTFCount := (rEN1 ? 1 : 0) + (rEN2 ? 1 : 0) + (rEN3 ? 1 : 0) + (rEN4 ? 1 : 0) + (rEN5 ? 1 : 0) + (rEN6 ? 1 : 0)
if showBarTypeTable and enabledTFCount > 0
if isCompactMode
// Compact (Universal) table: horizontal row of TFs + rows for FTFC, Filter, Domino, Preview + padding
barTypeTable := table.new(getTablePos(tablePosition), enabledTFCount, 5, bgcolor = COLOR_BG_BLACK, frame_color = color.gray, frame_width = 1, border_color = #333333, border_width = 1)
else
// Full (TheStrat) table: vertical with columns
barTypeTable := table.new(getTablePos(tablePosition), 5, enabledTFCount + 5, bgcolor = COLOR_BG_BLACK, frame_color = color.gray, frame_width = 1, border_color = #333333, border_width = 1)
populateBarTypeRow(table tbl, int row, string tf, TimeframeData data, TFRawData raw, ProcessingResult pResult, string textSize, bool reconOK) =>
tfSeconds = timeframe.in_seconds(tf)
isLowerTF = tfSeconds < chartTfSeconds
tfLabelStr = getTimeframeLabel(tf)
sz = getSize(textSize)
if isLowerTF
table.cell(tbl, 0, row, tfLabelStr, text_color = #555555, bgcolor = #111111, text_size = sz)
table.cell(tbl, 1, row, "", text_color = color.gray, bgcolor = COLOR_BG_BLACK, text_size = sz)
table.cell(tbl, 2, row, "", text_color = color.gray, bgcolor = COLOR_BG_BLACK, text_size = sz)
table.cell(tbl, 3, row, "", text_color = color.gray, bgcolor = COLOR_BG_BLACK, text_size = sz)
table.cell(tbl, 4, row, "", text_color = color.gray, bgcolor = COLOR_BG_BLACK, text_size = sz)
else
[barType_CC, _isFailed_CC] = detectBarTypeAndFailed(raw.c1H, raw.c1L, raw.ccH, raw.ccL, raw.ccO, raw.ccC)
[barType_C1, _isFailed_C1] = detectBarTypeAndFailed(raw.c2H, raw.c2L, raw.c1H, raw.c1L, raw.c1O, raw.c1C)
[barType_C2, _isFailed_C2] = detectBarTypeAndFailed(raw.c3H, raw.c3L, raw.c2H, raw.c2L, raw.c2O, raw.c2C)
// Use centralized signalInForce from processing result (matches compact table behavior)
bool isBullInForce = not na(pResult) and pResult.signalInForceHigh
bool isBearInForce = not na(pResult) and pResult.signalInForceLow
isInForce = colorTFWhenInForce and (isBullInForce or isBearInForce)
// FIX TFCOLOR-1 (2026-07-07): an inside bar (1u/1d) is by definition NOT in force, so it must
// not highlight the TF column under "Color TF When In-Force". The old ccIsInsideBar term lit the
// TF on every inside bar (the common case) - "coloring everything". In force = a live 2/F2 signal
// (isInForce, from signalInForceHigh/Low) OR an enabled outside-bar (3) expansion, which IS in
// force by definition (broke both C1 sides). Inside bars remain colored in the CC cell only.
bool ccIs3Exp = colorTFWhenInForce and show3Expansions and str.startswith(barType_CC, "3")
bool highlightTF = isInForce or ccIs3Exp
tfBgColor = highlightTF ? getBarTypeColor(barType_CC) : #111111
tfTextColor = highlightTF ? color.black : color.white
table.cell(tbl, 0, row, tfLabelStr, text_color = tfTextColor, bgcolor = tfBgColor, text_size = sz)
table.cell(tbl, 1, row, barType_C2, text_color = getBarTypeTextColor(barType_C2), bgcolor = COLOR_BG_BLACK, text_size = sz)
table.cell(tbl, 2, row, barType_C1, text_color = getBarTypeTextColor(barType_C1), bgcolor = COLOR_BG_BLACK, text_size = sz)
asText = data.c1IsHammer ? "HAM" : data.c1IsShooter ? "SHO" : data.c1IsInside ? "INS" : ""
table.cell(tbl, 3, row, asText, text_color = color.white, bgcolor = COLOR_BG_BLACK, text_size = sz)
ccRawText = barType_CC == "F2u" ? "f2u" : barType_CC == "F2d" ? "f2d" : barType_CC
// FIX HTF-STRADDLE-1 (T3): a straddled slot whose forming candle was rebuilt from real daily
// sub-bars (reconOK) renders normally - it is true data matching finer charts. Only a preview
// slot WITHOUT reconstruction (classic preview, or no new-period dailies yet) shows "?".
bool ccUnknown = raw.isPreview and not reconOK
ccDisplayText = ccUnknown ? "?" : ccRawText
ccBgColor = ccUnknown ? COLOR_BG_BLACK : getBarTypeColor(barType_CC)
ccTextColor = ccUnknown ? color.yellow : barType_CC != "" ? color.black : color.white
table.cell(tbl, 4, row, ccDisplayText, text_color = ccTextColor, bgcolor = ccBgColor, text_size = sz)
// Domino calculation - CC is inside across consecutive TFs with same direction
// Runs every bar (not just islast) so alertconditions work. Table reads on islast.
int dominoBestRun = 0
string dominoBestTFs = ""
int currentRunBull = 0
int currentRunBear = 0
int bestRunBull = 0
int bestRunBear = 0
string currentTFsBull = ""
string currentTFsBear = ""
string bestTFsBull = ""
string bestTFsBear = ""
int prevCCTimeBull = na
int prevCCTimeBear = na
for i = 5 to 0
cfg = array.get(tfConfigs, i)
if cfg.enabled
result = array.get(tfResults, i)
tfLabel = getTimeframeLabel(cfg.tf)
ccIsInside = not na(result) and str.startswith(result.ccType, "1")
hasBullSetup = not na(result) and result.drawHigh
hasBearSetup = not na(result) and result.drawLow
int thisCCTime = not na(result) ? result.ccStartTime : na
// Bullish Domino: skip if same candle as previous TF in run
bool bullSameCandle = not na(thisCCTime) and not na(prevCCTimeBull) and thisCCTime == prevCCTimeBull
if ccIsInside and hasBullSetup and not bullSameCandle
currentRunBull += 1
currentTFsBull := currentTFsBull == "" ? tfLabel : tfLabel + "+" + currentTFsBull
prevCCTimeBull := thisCCTime
if currentRunBull > bestRunBull
bestRunBull := currentRunBull
bestTFsBull := currentTFsBull
else if ccIsInside and hasBullSetup and bullSameCandle
// Same candle: don't increment count, just update prevTime to maintain chain
prevCCTimeBull := thisCCTime
else
currentRunBull := 0
currentTFsBull := ""
prevCCTimeBull := na
// Bearish Domino: skip if same candle as previous TF in run
bool bearSameCandle = not na(thisCCTime) and not na(prevCCTimeBear) and thisCCTime == prevCCTimeBear
if ccIsInside and hasBearSetup and not bearSameCandle
currentRunBear += 1
currentTFsBear := currentTFsBear == "" ? tfLabel : tfLabel + "+" + currentTFsBear
prevCCTimeBear := thisCCTime
if currentRunBear > bestRunBear
bestRunBear := currentRunBear
bestTFsBear := currentTFsBear
else if ccIsInside and hasBearSetup and bearSameCandle
prevCCTimeBear := thisCCTime
else
currentRunBear := 0
currentTFsBear := ""
prevCCTimeBear := na
if bestRunBull >= bestRunBear
dominoBestRun := bestRunBull
dominoBestTFs := bestTFsBull
else
dominoBestRun := bestRunBear
dominoBestTFs := bestTFsBear
// Calculate shared values for table (FTFC)
var string tableFtfcText = ""
var color tableFtfcTextColor = color.gray
var color tableFtfcBgColor = color.gray
if barstate.islast
// Modified FTFC calculation - exclude exhausted TFs if setting is enabled
// ftfc_up = true means all TFs closed bullish; ftfc_down = true means all TFs closed bearish
bool modFtfcUp = ftfc_up
bool modFtfcDown = ftfc_down
if exhDisablesFTFC
modFtfcUp := true
modFtfcDown := true
for i = 5 to 0
cfg = array.get(tfConfigs, i)
if cfg.enabled
tfSeconds = timeframe.in_seconds(cfg.tf)
isLowerTF = tfSeconds < chartTfSeconds
if not isLowerTF
raw = array.get(tfRawArr, i)
data = array.get(tfDataArr, i)
if not na(raw) and not na(data)
ccClosedUp = raw.ccC > raw.ccO
// If bullish TF hit high exhaustion, don't let it block ftfc_up (bullish setups)
// If bearish TF hit low exhaustion, don't let it block ftfc_down (bearish setups)
// If no exhaustion exists, magnitude crossed serves same function
bool bullMagFallback = na(data.exhHighPrice) and not na(data.prevMagHigh) and data.magHighCrossed
bool bearMagFallback = na(data.exhLowPrice) and not na(data.prevMagLow) and data.magLowCrossed
bullExhausted = ccClosedUp and (data.exhHighCrossed or bullMagFallback)
bearExhausted = not ccClosedUp and (data.exhLowCrossed or bearMagFallback)
if ccClosedUp and not bullExhausted
// FIX P1-e (doc): this affects the DATA TABLE FTFC cell ONLY. It does
// NOT gate signal drawing or alerts - those use the raw ftfc_up/ftfc_down
// from calculateFTFC(). A non-exhausted bullish TF keeps the table cell
// from reading "FTFC Down". (Wiring exhDisablesFTFC into signal gating is
// a deliberate, separately-validated change - see review notes.)
modFtfcDown := false
if not ccClosedUp and not bearExhausted
// FIX P1-e (doc): table-display only (see above); not signal gating.
modFtfcUp := false
tableFtfcText := modFtfcUp ? (simpleLabelMode ? "Trend Up" : "FTFC Up") : modFtfcDown ? (simpleLabelMode ? "Trend Down" : "FTFC Down") : (simpleLabelMode ? "No Trend" : "Conflict")
tableFtfcTextColor := modFtfcUp ? color.green : modFtfcDown ? color.red : color.gray
tableFtfcBgColor := modFtfcUp ? color.new(color.green, 80) : modFtfcDown ? color.new(color.red, 80) : color.new(color.gray, 80)
// Shared Lead row values (used by both compact and standard table modes)
var string leadRowText = "No Lead"
var color leadRowTextColor = color.gray
var color leadRowBgColor = color.new(color.gray, 85)
if barstate.islast and enableDirectionalFilter
string leadTfLabel = ""
if anchorIndex >= 0 and anchorIndex <= 5
leadCfg = array.get(tfConfigs, anchorIndex)
leadTfLabel := getTimeframeLabel(leadCfg.tf)
string leadDirText = anchorDirection == 1 ? "BULL" : anchorDirection == -1 ? "BEAR" : ""
leadRowText := leadTfLabel != "" and leadDirText != "" ? "Lead: " + leadTfLabel + " " + leadDirText : "No Lead"
leadRowTextColor := anchorDirection == 1 ? color.green : anchorDirection == -1 ? color.red : color.gray
leadRowBgColor := anchorDirection == 1 ? color.new(color.green, 85) : anchorDirection == -1 ? color.new(color.red, 85) : color.new(color.gray, 85)
if barstate.islast and showBarTypeTable and not na(barTypeTable)
textSize = getSize(tableTextSize)
if isCompactMode
// COMPACT MODE
table.clear(barTypeTable, 0, 0, enabledTFCount - 1, 4)
// TF labels row
col = 0
for i = 5 to 0
cfg = array.get(tfConfigs, i)
if cfg.enabled
tfSeconds = timeframe.in_seconds(cfg.tf)
isLowerTF = tfSeconds < chartTfSeconds
tfLabelStr = getTimeframeLabel(cfg.tf)
raw = array.get(tfRawArr, i)
result = array.get(tfResults, i)
// Determine cell color: signal-aware for quick visual scan
color cellBg = #111111
color cellText = #555555
if not isLowerTF and not na(raw) and not na(result)
if raw.isPreview and not array.get(reconOKArr, i) // FIX HTF-STRADDLE-1 (T3): reconstructed slots color normally
cellText := color.yellow
else if result.signalInForceHigh
// Bullish signal in force - check type for color
if result.isF2d or result.isF2u
cellBg := COLOR_FAILED_2D
else if str.startswith(result.ccType, "3")
cellBg := COLOR_3_HIGH
else
cellBg := #4caf50
cellText := color.black
else if result.signalInForceLow
// Bearish signal in force - check type for color
if result.isF2u or result.isF2d
cellBg := COLOR_FAILED_2U
else if str.startswith(result.ccType, "3")
cellBg := COLOR_3_LOW
else
cellBg := #f23645
cellText := color.black
else
// Not in force - check if 3-bar or inside
ccIs3Exp = show3Expansions and str.startswith(result.ccType, "3")
ccIsInside = raw.ccH <= raw.c1H and raw.ccL >= raw.c1L
ccAboveOpen = raw.ccC > raw.ccO
if ccIs3Exp
cellBg := ccAboveOpen ? COLOR_3_HIGH : COLOR_3_LOW
cellText := color.black
else if ccIsInside
cellBg := ccAboveOpen ? #ffeb3b : #ff9800
cellText := color.black
else
cellBg := #111111
cellText := color.white
table.cell(barTypeTable, col, 0, tfLabelStr, text_color = cellText, bgcolor = cellBg, text_size = textSize)
col += 1
// Subsequent rows: Domino, FTFC, Filter, Preview
compactRow = 1
if showDominoInTable and dominoBestRun >= minDominoTFs
table.merge_cells(barTypeTable, 0, compactRow, enabledTFCount - 1, compactRow)
table.cell(barTypeTable, 0, compactRow, "Domino: " + dominoBestTFs, text_color = color.black, bgcolor = color.white, text_size = textSize)
compactRow += 1
// Row 2: FTFC
table.merge_cells(barTypeTable, 0, compactRow, enabledTFCount - 1, compactRow)
table.cell(barTypeTable, 0, compactRow, tableFtfcText, text_color = tableFtfcTextColor, bgcolor = tableFtfcBgColor, text_size = textSize)
compactRow += 1
// Row 3: Filter (if enabled)
if enableDirectionalFilter
table.merge_cells(barTypeTable, 0, compactRow, enabledTFCount - 1, compactRow)
table.cell(barTypeTable, 0, compactRow, leadRowText, text_color = leadRowTextColor, bgcolor = leadRowBgColor, text_size = textSize)
compactRow += 1
// Row 4: Preview mode (if enabled)
if previewNextPeriod
table.merge_cells(barTypeTable, 0, compactRow, enabledTFCount - 1, compactRow)
table.cell(barTypeTable, 0, compactRow, "PREVIEW MODE", text_color = color.yellow, bgcolor = color.new(color.yellow, 85), text_size = textSize)
compactRow += 1
else
// STANDARD MODE
table.clear(barTypeTable, 0, 0, 4, enabledTFCount + 4)
table.cell(barTypeTable, 0, 0, "TF", text_color = #888888, bgcolor = #1a1a1a, text_size = textSize)
table.cell(barTypeTable, 1, 0, "C2", text_color = #888888, bgcolor = #1a1a1a, text_size = textSize)
table.cell(barTypeTable, 2, 0, "C1", text_color = #888888, bgcolor = #1a1a1a, text_size = textSize)
table.cell(barTypeTable, 3, 0, "AS", text_color = #888888, bgcolor = #1a1a1a, text_size = textSize)
table.cell(barTypeTable, 4, 0, "CC", text_color = #888888, bgcolor = #1a1a1a, text_size = textSize)
row = 1
for i = 5 to 0
cfg = array.get(tfConfigs, i)
if cfg.enabled
data = array.get(tfDataArr, i)
raw = array.get(tfRawArr, i)
result = array.get(tfResults, i)
populateBarTypeRow(barTypeTable, row, cfg.tf, data, raw, result, tableTextSize, array.get(reconOKArr, i))
row += 1
if showDominoInTable and dominoBestRun >= minDominoTFs
string dominoText = "Domino: " + dominoBestTFs
table.merge_cells(barTypeTable, 0, row, 4, row)
table.cell(barTypeTable, 0, row, dominoText, text_color = color.black, bgcolor = color.white, text_size = textSize)
row += 1
table.merge_cells(barTypeTable, 0, row, 4, row)
table.cell(barTypeTable, 0, row, tableFtfcText, text_color = tableFtfcTextColor, bgcolor = tableFtfcBgColor, text_size = textSize)
row += 1
if enableDirectionalFilter
table.merge_cells(barTypeTable, 0, row, 4, row)
table.cell(barTypeTable, 0, row, leadRowText, text_color = leadRowTextColor, bgcolor = leadRowBgColor, text_size = textSize)
row += 1
if previewNextPeriod
table.merge_cells(barTypeTable, 0, row, 4, row)
table.cell(barTypeTable, 0, row, "PREVIEW MODE", text_color = color.yellow, bgcolor = color.new(color.yellow, 85), text_size = textSize)
row += 1
// ============================================================================
// SECTION 12: DEBUG PANEL
// ============================================================================
debugBoolStr(b) => b ? "YES" : "NO"
debugBoolBg(b) => b ? color.new(#00aa00, 20) : color.new(#aa0000, 20)
debugBoolText(b) => b ? color.white : color.new(color.gray, 35) // FIX P2-3: false branch was also white (dead ternary); now dimmed for contrast
debugHeaderBg = color.new(#333333, 0)
debugRowBg = color.new(#1a1a1a, 0)
debugLabelColor = #aaaaaa
var table debugTable = na
if barstate.isfirst and showDebugPanel
debugTable := table.new(position.middle_left, 2, 30, bgcolor = color.new(#0d0d0d, 0), frame_color = color.orange, frame_width = 2, border_color = color.new(#333333, 0), border_width = 1)
if barstate.islast and showDebugPanel and not na(debugTable)
sz = size.small
r = 0
table.cell(debugTable, 0, r, "DEBUG: " + debugData.tfLabel, text_color = color.orange, bgcolor = debugHeaderBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, "", bgcolor = debugHeaderBg, text_size = sz)
r += 1
table.cell(debugTable, 0, r, "C1 Bar Type", text_color = color.white, bgcolor = debugHeaderBg, text_size = sz, text_halign = text.align_center)
table.cell(debugTable, 1, r, "", bgcolor = debugHeaderBg, text_size = sz)
r += 1
table.cell(debugTable, 0, r, "c1_was_2u", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.c1_was_2u), text_color = debugBoolText(debugData.c1_was_2u), bgcolor = debugBoolBg(debugData.c1_was_2u), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "c1_was_2d", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.c1_was_2d), text_color = debugBoolText(debugData.c1_was_2d), bgcolor = debugBoolBg(debugData.c1_was_2d), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "c1_is_3", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.c1_is_3), text_color = debugBoolText(debugData.c1_is_3), bgcolor = debugBoolBg(debugData.c1_is_3), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "Patterns", text_color = color.white, bgcolor = debugHeaderBg, text_size = sz, text_halign = text.align_center)
table.cell(debugTable, 1, r, "", bgcolor = debugHeaderBg, text_size = sz)
r += 1
table.cell(debugTable, 0, r, "isHammer", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.isHammer), text_color = debugBoolText(debugData.isHammer), bgcolor = debugBoolBg(debugData.isHammer), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "isShooter", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.isShooter), text_color = debugBoolText(debugData.isShooter), bgcolor = debugBoolBg(debugData.isShooter), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "isInside", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.isInside), text_color = debugBoolText(debugData.isInside), bgcolor = debugBoolBg(debugData.isInside), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "CC Failed", text_color = color.white, bgcolor = debugHeaderBg, text_size = sz, text_halign = text.align_center)
table.cell(debugTable, 1, r, "", bgcolor = debugHeaderBg, text_size = sz)
r += 1
table.cell(debugTable, 0, r, "cc_is_2u", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.cc_is_2u), text_color = debugBoolText(debugData.cc_is_2u), bgcolor = debugBoolBg(debugData.cc_is_2u), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "cc_is_2d", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.cc_is_2d), text_color = debugBoolText(debugData.cc_is_2d), bgcolor = debugBoolBg(debugData.cc_is_2d), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "cc_is_3", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.cc_is_3), text_color = debugBoolText(debugData.cc_is_3), bgcolor = debugBoolBg(debugData.cc_is_3), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "is_f2u", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.is_f2u), text_color = debugBoolText(debugData.is_f2u), bgcolor = debugBoolBg(debugData.is_f2u), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "is_f2d", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.is_f2d), text_color = debugBoolText(debugData.is_f2d), bgcolor = debugBoolBg(debugData.is_f2d), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "pre_f2u", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.pre_f2u), text_color = debugBoolText(debugData.pre_f2u), bgcolor = debugBoolBg(debugData.pre_f2u), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "pre_f2d", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.pre_f2d), text_color = debugBoolText(debugData.pre_f2d), bgcolor = debugBoolBg(debugData.pre_f2d), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "Draw Decisions", text_color = color.white, bgcolor = debugHeaderBg, text_size = sz, text_halign = text.align_center)
table.cell(debugTable, 1, r, "", bgcolor = debugHeaderBg, text_size = sz)
r += 1
table.cell(debugTable, 0, r, "drawHigh", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.drawHigh), text_color = debugBoolText(debugData.drawHigh), bgcolor = debugBoolBg(debugData.drawHigh), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "drawLow", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.drawLow), text_color = debugBoolText(debugData.drawLow), bgcolor = debugBoolBg(debugData.drawLow), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "drawMagHigh", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.drawMagHigh), text_color = debugBoolText(debugData.drawMagHigh), bgcolor = debugBoolBg(debugData.drawMagHigh), text_size = sz, text_halign = text.align_center)
r += 1
table.cell(debugTable, 0, r, "drawMagLow", text_color = debugLabelColor, bgcolor = debugRowBg, text_size = sz, text_halign = text.align_left)
table.cell(debugTable, 1, r, debugBoolStr(debugData.drawMagLow), text_color = debugBoolText(debugData.drawMagLow), bgcolor = debugBoolBg(debugData.drawMagLow), text_size = sz, text_halign = text.align_center)
// ============================================================================
// SECTION 13: ALERTS
// ============================================================================
isSetupBull(ProcessingResult r) =>
bool result = false
if not na(r) and r.drawHigh and not r.isF2d
is3Bar = str.startswith(r.ccType, "3")
if is3Bar
result := r.ccType == "3u"
else
result := r.ccType == "2u"
result
isSetupBear(ProcessingResult r) =>
bool result = false
if not na(r) and r.drawLow and not r.isF2u
is3Bar = str.startswith(r.ccType, "3")
if is3Bar
result := r.ccType == "3d"
else
result := r.ccType == "2d"
result
isF2Bull(ProcessingResult r) =>
// FIX P0-3: gate on show_p3_toggle so F2 (Range Reclaim) alerts do not fire when
// Failing 2s are toggled OFF. The in-force state (f2_in_force_high) already gates on
// show_p3_toggle, so without this the alert fired while the table said "not in force".
show_p3_toggle and not na(r) and r.drawHigh and r.isF2d
isF2Bear(ProcessingResult r) =>
show_p3_toggle and not na(r) and r.drawLow and r.isF2u
buildAlertLabel(ProcessingResult r, bool isBullish, TimeframeData data, ProcessingResult r_full) =>
string result = ""
if not na(r)
string patternSuffix = ""
if simpleLabelMode
patternSuffix := (isBullish and data.c1IsHammer) ? "◆" : (not isBullish and data.c1IsShooter) ? "◆" : ""
else
patternSuffix := (isBullish and data.c1IsHammer) ? " HAM" : (not isBullish and data.c1IsShooter) ? " SHO" : ""
string combo = ""
c1IsInside = r.c1Num == "1"
is32Pattern = r.c1Is3
ccIs3 = str.startswith(r.ccType, "3")
ccIsInsideAlert = r.ccType == "1"
string c1TypeF2a = (r.c1WasF2 ? "F" : "") + r.c1Type
string c1NumF2a = (r.c1WasF2 ? "F" : "") + r.c1Num
string c2NumF2a = (r.c2WasF2 ? "F" : "") + r.c2Num
string dirArrow = ""
if simpleLabelMode
dirArrow := isBullish ? "↑" : "↓"
// Simple mode for alerts
if ccIs3 or is32Pattern
combo := dirArrow + "EXP"
else if (r.isF2d and isBullish) or (r.isF2u and not isBullish)
combo := dirArrow + "FAILING"
else if c1IsInside
combo := dirArrow + "INSIDE"
else if ccIsInsideAlert
// CC inside: potential setup (alerts rarely fire here)
isCont = (isBullish and data.c1IsHighContinuation) or (not isBullish and data.c1IsLowContinuation)
combo := isCont ? dirArrow + "CONTINUATION" : dirArrow + "REVERSAL"
else if isBullish and data.c1IsHighContinuation
combo := dirArrow + "CONTINUATION"
else if not isBullish and data.c1IsLowContinuation
combo := dirArrow + "CONTINUATION"
else
combo := dirArrow + "REVERSAL"
else
// F2 labels: only show C1 prior (CC is live = lowercase f)
if r.isF2d and isBullish
combo := c1TypeF2a + "f2d"
else if r.isF2u and not isBullish
combo := c1TypeF2a + "f2u"
else if is32Pattern
combo := "3" + r.ccType
else if c1IsInside
combo := c2NumF2a + c1NumF2a + r.ccType
else
combo := c1TypeF2a + r.ccType
string side = showHighLowInLabel ? (isBullish ? " H" : " L") : ""
string emoji = isBullish ? " 🟢" : " 🔴"
result := r.tfLabel + " " + formatCombo(combo) + patternSuffix + side + emoji
// FIX P1-j: build detail fields into a separate string and only append the " | "
// separator when at least one field is actually present. The old code appended the
// pipe unconditionally whenever any toggle was on, producing a dangling "... | " (and
// double-pipes after TF consolidation) when every field was na - contradicting the
// tooltip's "omitted automatically" promise. Single-space joins, no trailing space.
string details = ""
if alertShowTrigger
float trigPrice = isBullish ? r_full.high : r_full.low
if not na(trigPrice)
details := details + (details == "" ? "" : " ") + "@ " + str.tostring(trigPrice, format.mintick)
if alertShowMagnitude
float magPrice = isBullish ? r_full.magHigh : r_full.magLow
if not na(magPrice)
details := details + (details == "" ? "" : " ") + "MAG " + str.tostring(magPrice, format.mintick)
if alertShowExhaustion
float exhPrice = isBullish ? r_full.exhHigh : r_full.exhLow
if not na(exhPrice)
details := details + (details == "" ? "" : " ") + "EXH " + str.tostring(exhPrice, format.mintick)
if alertShowStop
float stopPrice = isBullish ? r_full.stopHigh : r_full.stopLow
if not na(stopPrice)
details := details + (details == "" ? "" : " ") + "STOP " + str.tostring(stopPrice, format.mintick)
if details != ""
result := result + " | " + details
result
isPotentialBull(ProcessingResult r) =>
bool result = false
if not na(r) and r.drawHigh
result := r.ccType == "1" // FIX P2-5: ccType only ever emits 1/2u/2d/3u/3d/F2u/F2d; "1u"/"1d" were dead disjuncts
result
isPotentialBear(ProcessingResult r) =>
bool result = false
if not na(r) and r.drawLow
result := r.ccType == "1" // FIX P2-5: removed unreachable "1u"/"1d" comparisons
result
var array<bool> wasSetupBull = array.from(false, false, false, false, false, false)
var array<bool> wasSetupBear = array.from(false, false, false, false, false, false)
var array<bool> wasF2Bull = array.from(false, false, false, false, false, false)
var array<bool> wasF2Bear = array.from(false, false, false, false, false, false)
var array<bool> wasPotentialBull = array.from(false, false, false, false, false, false)
var array<bool> wasPotentialBear = array.from(false, false, false, false, false, false)
var array<int> alertLastPeriodStart = array.new<int>(6, na) // FIX P1-c: last-seen HTF period-start per slot, to reset was-state on a new period
var bool any_newly_in_force = false
var bool any_newly_potential = false
var string alertMsg = ""
any_newly_in_force := false
any_newly_potential := false
alertMsg := ""
// FIX P1-c: reset per-slot was-state at the start of a new HTF period, so a brand-new
// signal that opens already in force (e.g. a gap-through new period that ended the prior
// period in force) still produces a false->true edge and alerts, instead of being masked
// by the prior period's true->true (no edge). Runs BEFORE the snapshot below so both the
// consolidated alert and the per-TF alertconditions observe the reset.
// Keyed off raw.realPeriodTime (the unshifted real period-open, same value P0-1 uses), NOT
// ccStartTime: ccStartTime is rewritten to the chart-bar time in preview and would force a
// reset every bar. The store sits OUTSIDE the reset guard so the slot latches on first sight
// (na seed) and the boundary test works thereafter; first-sight reset is a no-op (was-state
// is already false at init).
for pi = 0 to 5
rawp = array.get(tfRawArr, pi)
if not na(rawp)
int sp = rawp.realPeriodTime
if not na(sp)
prevSp = array.get(alertLastPeriodStart, pi)
if na(prevSp) or prevSp != sp
array.set(wasSetupBull, pi, false)
array.set(wasSetupBear, pi, false)
array.set(wasF2Bull, pi, false)
array.set(wasF2Bear, pi, false)
array.set(wasPotentialBull, pi, false)
array.set(wasPotentialBear, pi, false)
array.set(alertLastPeriodStart, pi, sp)
// Snapshot was-state before loop updates it (alertconditions read after loop)
bool wasSetBull0 = array.get(wasSetupBull, 0), bool wasSetBear0 = array.get(wasSetupBear, 0)
bool wasSetBull1 = array.get(wasSetupBull, 1), bool wasSetBear1 = array.get(wasSetupBear, 1)
bool wasSetBull2 = array.get(wasSetupBull, 2), bool wasSetBear2 = array.get(wasSetupBear, 2)
bool wasSetBull3 = array.get(wasSetupBull, 3), bool wasSetBear3 = array.get(wasSetupBear, 3)
bool wasSetBull4 = array.get(wasSetupBull, 4), bool wasSetBear4 = array.get(wasSetupBear, 4)
bool wasSetBull5 = array.get(wasSetupBull, 5), bool wasSetBear5 = array.get(wasSetupBear, 5)
bool wasF2B0 = array.get(wasF2Bull, 0), bool wasF2Br0 = array.get(wasF2Bear, 0)
bool wasF2B1 = array.get(wasF2Bull, 1), bool wasF2Br1 = array.get(wasF2Bear, 1)
bool wasF2B2 = array.get(wasF2Bull, 2), bool wasF2Br2 = array.get(wasF2Bear, 2)
bool wasF2B3 = array.get(wasF2Bull, 3), bool wasF2Br3 = array.get(wasF2Bear, 3)
bool wasF2B4 = array.get(wasF2Bull, 4), bool wasF2Br4 = array.get(wasF2Bear, 4)
bool wasF2B5 = array.get(wasF2Bull, 5), bool wasF2Br5 = array.get(wasF2Bear, 5)
for i = 0 to 5
cfg = array.get(tfConfigs, i)
if cfg.enabled
bool alertEnabled = switch i
0 => alertTF1
1 => alertTF2
2 => alertTF3
3 => alertTF4
4 => alertTF5
5 => alertTF6
=> true
r = array.get(tfResults, i)
d = array.get(tfDataArr, i)
// FIX HTF-STRADDLE-1: never emit alerts from a preview/straddled slot - its CC is synthetic
// or estimate-grade. Was-state still updates (U2 pattern), and the P1-c period reset re-arms
// cleanly when the slot exits preview, so the first real bar of the new period alerts normally.
rawAl = array.get(tfRawArr, i)
bool slotPreview = straddleWindowOK and not na(rawAl) and rawAl.isPreview
bool suppressBull = enableDirectionalFilter and anchorDirection == -1 and i < anchorIndex
bool suppressBear = enableDirectionalFilter and anchorDirection == 1 and i < anchorIndex
// Check setups (non-F2)
isNowSetupBull = suppressBull ? false : isSetupBull(r)
isNowSetupBear = suppressBear ? false : isSetupBear(r)
wasSetBull = array.get(wasSetupBull, i)
wasSetBear = array.get(wasSetupBear, i)
// Check F2s separately
isNowF2Bull = suppressBull ? false : isF2Bull(r)
isNowF2Bear = suppressBear ? false : isF2Bear(r)
wasF2B = array.get(wasF2Bull, i)
wasF2Br = array.get(wasF2Bear, i)
// FIX U2: only the consolidated alert() MESSAGE is gated by this TF's alert checkbox.
// The was-state arrays below are updated UNCONDITIONALLY (was: an early `continue`
// skipped them), so the per-TF alertconditions - which read the pre-loop snapshots -
// detect true edges instead of firing every bar when a TF is enabled but its alert
// checkbox is off.
if alertEnabled and not slotPreview
// Alert on new setup (independent of F2 state)
if isNowSetupBull and not wasSetBull
any_newly_in_force := true
newLabel = buildAlertLabel(r, true, d, r)
alertMsg := alertMsg == "" ? newLabel : alertMsg + " | " + newLabel
if isNowSetupBear and not wasSetBear
any_newly_in_force := true
newLabel = buildAlertLabel(r, false, d, r)
alertMsg := alertMsg == "" ? newLabel : alertMsg + " | " + newLabel
// Alert on new F2 (independent of setup state)
if isNowF2Bull and not wasF2B
any_newly_in_force := true
newLabel = buildAlertLabel(r, true, d, r)
alertMsg := alertMsg == "" ? newLabel : alertMsg + " | " + newLabel
if isNowF2Bear and not wasF2Br
any_newly_in_force := true
newLabel = buildAlertLabel(r, false, d, r)
alertMsg := alertMsg == "" ? newLabel : alertMsg + " | " + newLabel
// Update state (unconditional - keeps per-TF alertcondition snapshots accurate)
array.set(wasSetupBull, i, isNowSetupBull)
array.set(wasSetupBear, i, isNowSetupBear)
array.set(wasF2Bull, i, isNowF2Bull)
array.set(wasF2Bear, i, isNowF2Bear)
// Potential signals
isPotBull = isPotentialBull(r)
isPotBear = isPotentialBear(r)
wasPotBull = array.get(wasPotentialBull, i)
wasPotBear = array.get(wasPotentialBear, i)
if alertEnabled and not slotPreview and ((isPotBull and not wasPotBull) or (isPotBear and not wasPotBear))
any_newly_potential := true
array.set(wasPotentialBull, i, isPotBull)
array.set(wasPotentialBear, i, isPotBear)
if any_newly_in_force and str.length(alertMsg) > 0
if alertShowFTFC
string ftfcState = ftfc_up ? "FTFC Up" : ftfc_down ? "FTFC Down" : "Conflict"
alertMsg := alertMsg + " | " + ftfcState
alert(alertMsg, alert.freq_once_per_bar)
var string lastDominoCombo = ""
var bool dominoAlertTriggered = false
dominoAlertTriggered := false
// Domino alert (uses shared dominoBestRun/dominoBestTFs computed before table section)
if dominoBestRun >= minDominoTFs and dominoBestTFs != lastDominoCombo
dominoAlertTriggered := true
if enableDominoAlerts
alert("Domino: " + dominoBestTFs, alert.freq_once_per_bar)
lastDominoCombo := dominoBestTFs
else if dominoBestRun < minDominoTFs
lastDominoCombo := ""
r1 = array.get(tfResults, 0)
r2 = array.get(tfResults, 1)
r3 = array.get(tfResults, 2)
r4 = array.get(tfResults, 3)
r5 = array.get(tfResults, 4)
r6 = array.get(tfResults, 5)
// FIX HTF-STRADDLE-1: per-TF alertconditions also skip preview/straddled slots
bool okA1 = not (straddleWindowOK and array.get(tfRawArr, 0).isPreview)
bool okA2 = not (straddleWindowOK and array.get(tfRawArr, 1).isPreview)
bool okA3 = not (straddleWindowOK and array.get(tfRawArr, 2).isPreview)
bool okA4 = not (straddleWindowOK and array.get(tfRawArr, 3).isPreview)
bool okA5 = not (straddleWindowOK and array.get(tfRawArr, 4).isPreview)
bool okA6 = not (straddleWindowOK and array.get(tfRawArr, 5).isPreview)
// Per-TF alertconditions use pre-loop snapshots to detect transitions correctly
tf1_newly_bull = rEN1 and okA1 and ((isSetupBull(r1) and not wasSetBull0) or (isF2Bull(r1) and not wasF2B0))
tf1_newly_bear = rEN1 and okA1 and ((isSetupBear(r1) and not wasSetBear0) or (isF2Bear(r1) and not wasF2Br0))
tf2_newly_bull = rEN2 and okA2 and ((isSetupBull(r2) and not wasSetBull1) or (isF2Bull(r2) and not wasF2B1))
tf2_newly_bear = rEN2 and okA2 and ((isSetupBear(r2) and not wasSetBear1) or (isF2Bear(r2) and not wasF2Br1))
tf3_newly_bull = rEN3 and okA3 and ((isSetupBull(r3) and not wasSetBull2) or (isF2Bull(r3) and not wasF2B2))
tf3_newly_bear = rEN3 and okA3 and ((isSetupBear(r3) and not wasSetBear2) or (isF2Bear(r3) and not wasF2Br2))
tf4_newly_bull = rEN4 and okA4 and ((isSetupBull(r4) and not wasSetBull3) or (isF2Bull(r4) and not wasF2B3))
tf4_newly_bear = rEN4 and okA4 and ((isSetupBear(r4) and not wasSetBear3) or (isF2Bear(r4) and not wasF2Br3))
tf5_newly_bull = rEN5 and okA5 and ((isSetupBull(r5) and not wasSetBull4) or (isF2Bull(r5) and not wasF2B4))
tf5_newly_bear = rEN5 and okA5 and ((isSetupBear(r5) and not wasSetBear4) or (isF2Bear(r5) and not wasF2Br4))
tf6_newly_bull = rEN6 and okA6 and ((isSetupBull(r6) and not wasSetBull5) or (isF2Bull(r6) and not wasF2B5))
tf6_newly_bear = rEN6 and okA6 and ((isSetupBear(r6) and not wasSetBear5) or (isF2Bear(r6) and not wasF2Br5))
var bool was_ftfc_up = false
var bool was_ftfc_down = false
ftfc_shifted_up = ftfc_up and not was_ftfc_up
ftfc_shifted_down = ftfc_down and not was_ftfc_down
ftfc_shifted_conflict = not ftfc_up and not ftfc_down and (was_ftfc_up or was_ftfc_down)
ftfc_shifted = ftfc_shifted_up or ftfc_shifted_down or ftfc_shifted_conflict
was_ftfc_up := ftfc_up
was_ftfc_down := ftfc_down
alertcondition(any_newly_in_force, "Signal In-Force (Any)", "Signal In-Force")
alertcondition(tf1_newly_bull or tf1_newly_bear, "Signal In-Force (TF1)", "TF1 In-Force")
alertcondition(tf2_newly_bull or tf2_newly_bear, "Signal In-Force (TF2)", "TF2 In-Force")
alertcondition(tf3_newly_bull or tf3_newly_bear, "Signal In-Force (TF3)", "TF3 In-Force")
alertcondition(tf4_newly_bull or tf4_newly_bear, "Signal In-Force (TF4)", "TF4 In-Force")
alertcondition(tf5_newly_bull or tf5_newly_bear, "Signal In-Force (TF5)", "TF5 In-Force")
alertcondition(tf6_newly_bull or tf6_newly_bear, "Signal In-Force (TF6)", "TF6 In-Force")
alertcondition(tf1_newly_bull or tf2_newly_bull or tf3_newly_bull or tf4_newly_bull or tf5_newly_bull or tf6_newly_bull, "Signal In-Force (Bullish)", "Bullish In-Force")
alertcondition(tf1_newly_bear or tf2_newly_bear or tf3_newly_bear or tf4_newly_bear or tf5_newly_bear or tf6_newly_bear, "Signal In-Force (Bearish)", "Bearish In-Force")
alertcondition(any_newly_potential, "New Potential (Any)", "New Potential Signal")
alertcondition(dominoAlertTriggered, "Domino Setup", "Domino Setup")
alertcondition(ftfc_shifted, "FTFC Shifted", "FTFC Shifted")
alertcondition(ftfc_shifted_up, "FTFC Up", "FTFC Up")
alertcondition(ftfc_shifted_down, "FTFC Down", "FTFC Down")
alertcondition(ftfc_shifted_conflict, "FTFC Conflict", "FTFC Conflict")
Overview
TheStrat Suite plots inside bars, hammers, shooters, failed 2s and outside bars on the price chart, with magnitude and exhaustion targets, stop levels and full timeframe continuity filters. Users choose presets or custom slots, line widths and optional reversal rules. A live table summarizes each timeframe, and alerts can be consolidated. Higher-timeframe reads use lookahead, so historical signals can use future data. It is not intended for Heiken-Ashi charts.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Preset
string
TheStrat Classic
tfPreset
Label Style
string
TheStrat
labelStyleMode
Timeframe 1
bool
false
tf1Enabled
tf1
timeframe
15
tf1
Width
int
1
tf1LineWidth
Open
bool
false
tf1ShowOpen
Timeframe 2
bool
false
tf2Enabled
tf2
timeframe
30
tf2
Width
int
1
tf2LineWidth
Open
bool
false
tf2ShowOpen
Timeframe 3
bool
false
tf3Enabled
tf3
timeframe
60
tf3
Show all 40 settings
Setting
Type
Default
Variable
Width
int
1
tf3LineWidth
Open
bool
false
tf3ShowOpen
Timeframe 4
bool
false
tf4Enabled
tf4
timeframe
D
tf4
Width
int
2
tf4LineWidth
Open
bool
false
tf4ShowOpen
Timeframe 5
bool
false
tf5Enabled
tf5
timeframe
W
tf5
Width
int
3
tf5LineWidth
Open
bool
false
tf5ShowOpen
Timeframe 6
bool
false
tf6Enabled
tf6
timeframe
M
tf6
Width
int
4
tf6LineWidth
Open
bool
false
tf6ShowOpen
Inside Reversals |
bool
true
showInsideReversals
HAM/SHO
bool
false
insideRevOnlyMomo
FTFC
bool
false
insideRevRequireFTFC
2-2 Reversals |
bool
true
showAllReversals
HAM/SHO
bool
false
reversalsRequireActionable
F2
bool
false
reversalsRequireC1F2
FTFC
bool
false
reversalsRequireFTFC
Inside Continuations |
bool
true
showInsideContinuations
HAM/SHO
bool
false
insideContOnlyMomo
FTFC
bool
false
insideContRequireFTFC
2-2 Continuations |
bool
false
showContinuations
HAM/SHO
bool
false
twoTwoContOnlyMomo
FTFC
bool
false
continuationsRequireFTFC
Failing 2s (Range Reclaims) |
bool
false
show_p3_toggle
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.
This overlay indicator draws a set of higher-timeframe candles on the price chart. The user sets the timeframe or lets it select automatically, plus candle count, size, offset, margin, and whether the candles are standard or Heikin Ashi. A table can show the a
The indicator tracks four-bar fractal sweep and reclaim setups on chart timeframes of 15 minutes and above. It calculates Reclaim Strength based on where C2 closes relative to C1's range and plots level lines for the swept C1 extreme and C2 extreme. An on-scre
This overlay indicator marks bullish and bearish chart patterns on the price chart and can hide either side. Optional filters require agreement from fast and slow EMAs, Supertrend, and ADX, plus volume, RSI, nearby pivot support or resistance, and a user-defin
This indicator segments recent price bars into defined groups and visual boxes to serve as a visual chart ruler. It includes automated trend detection with configurable angle thresholds to plot uptrend, downtrend, and range segments directly on the price chart
Forex Majors 4H Rejection Scanner tracks seven customizable currency pairs on a four-hour timeframe and displays their bias in a table overlay on the main price chart. It checks the last two closed four-hour candles for high or low price rejections, labeling a
The indicator plots ICT displacement candles on the price chart and can switch to retracement scanning. Displacement requires a move larger than a set share of the 14-bar average high-low range, lasting at least a chosen number of bars, with optional close, pr