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
918 lines of Pine Script v5 from clusterbreak.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.
clusterbreak.pine
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
//
// Code is based on Multi Timeframe Break of Structure(BOS) & Market Structure Shift(MSS) from Lenny_Kiruthu
// Original Source: https://www.tradingview.com/script/1LrVbh1I-MTF-Break-of-Structure-BOS-Market-Structure-Shift-MSS/
//
// BRACKET RULES
// SHORT: bearish MSS that directly follows a bullish zone (BOS or MSS), and the two are TOGETHER: the swing low
// it breaks was created INSIDE that bullish zone, or within 'Touch tolerance' bars after it (the zones touch)
// Entry = market order when the break bar closes (broken swing low = "1")
// Stop = broken level + Stop x zone height (1.0 = "0", the highest high inside the zone) or a fixed number of points
// TP = broken level - TP x zone height (1.0 = "2", one zone height below "1") or a fixed number of points
// LONG : bullish MSS that directly follows a bearish zone (BOS or MSS) - mirror image
// Stop = broken level - Stop x zone height (1.0 = lowest low inside the zone) or fixed points
// TP = broken level + TP x zone height or fixed points
// Stop mode '% of TP distance': stop distance = TP distance x Stop % (100 = a 1:1 stop, 50 = half of it, 70 = 70% of it).
// Zone height = distance from the broken level to the far end of the zone (the "0" to "1" distance).
// Set both multiples to 1.0 for the original 1:1 bracket.
// EXIT: with 'Exit when an opposite structure break forms' on, a bearish break while long (or a bullish break while
// short) closes the trade at market (closeposition). If a new opposite signal fires on that same bar, the new
// entry goes out with flatten_first=true instead of a separate close.
//
// HTF STRUCTURE / LTF ENTRY (optional)
// With 'Detect structure on a higher timeframe' on, BOS/MSS structure is detected on the timeframe set in 'Structure
// timeframe' (e.g. 1m) via request.security, while the chart itself can be run on a lower timeframe (e.g. 30s) for a
// tighter entry. Immediate mode enters at the close of the LTF bar the HTF break is confirmed on (roughly the same
// price as running the script on the HTF alone, just up to one LTF bar sooner). Retrace mode waits for LTF price to
// pull back a chosen % into the HTF zone before entering (a limit-style fill), and cancels if that retrace doesn't
// happen within the bar timeout or if price hits the stop side first. Stop/TP/exit settings above are unchanged and
// apply to whichever price this module fills at. Requires the chart's own timeframe to be lower than 'Structure
// timeframe' - if it isn't, this has no effect.
//
// TRADE TIME WINDOWS
// With 'Only trade inside time windows' on, new entries (and their webhook alerts) are only allowed while the bar is
// inside an enabled window. Signals outside the window are still drawn but labelled 'skip: outside trade window'.
// Exits are never blocked: stop, TP and the opposite-break exit keep working outside the windows.
// DAILY P&L TABLE
// Tracks this script's own signals as virtual trades (entry = signal bar close, exit at stop/TP) and shows
// trades, W-L, win % and P&L per trading day. The day rolls at 18:00 New York by default.
//
// WEBHOOK ALERTS
// Stop/TP ride the entry order as a bracket (take_profit / stop_loss). Nothing else is ever sent for exits.
// Create ONE TradingView alert on this indicator: condition = "Any alert() function call",
// webhook URL = your webhook URL (Notifications tab). Recreate the alert after every script edit.
// VERSION HISTORY
// v2.3 2026-09-30 Renamed to 'clusterbreak' (title/shorttitle only) to match the GitHub repo. No logic changes.
// v2.2 2026-09-28 New defaults: swing length 8, quantity 3, day ends 16:00, Light chart theme, all 3 trade windows on
// (05:00-13:30, 13:30-15:30, 03:00-05:00 New York). No logic changes.
// v2.1 2026-09-28 Fix: request.security can't sit inside an if/else in Pine v5. Now called unconditionally every bar
// (as Pine requires) and only used when 'Detect structure on a higher timeframe' is on.
// v2.0 2026-09-28 Optional HTF structure / LTF entry: detect BOS/MSS on a higher timeframe (e.g. 1m) while running the
// script on a lower timeframe (e.g. 30s), and either enter immediately on the HTF break's close or wait
// for a retrace into the zone on the LTF candles for a better price. See 'HTF STRUCTURE / LTF ENTRY' below.
// v1.4 2026-09-28 Saved as a separate script: 'BOS MSS Bracket Webhook'. Renamed; generic webhook wording in the name, settings and labels.
// The webhook message format itself is unchanged.
// v1.4 2026-09-28 Price removed from the Stop/Entry/TP labels (optional 'Show price on Stop / Entry / TP labels' toggle, off by default).
// v1.3 2026-09-28 New stop mode '% of TP distance' (50 = stop is half of a 1:1 stop, so 2:1 reward-to-risk; 70 = 70% of a 1:1 stop).
// v1.2 2026-09-28 Adjustable stop and take profit (zone multiple or fixed points each), so it is no longer always 1:1.
// v1.1 2026-09-28 Trade time windows: up to 3 windows, entries only inside them, optional close at window end, window shading.
// v1.0 2026-09-28 First version-stamped build: BOS/MSS touching-zone bracket signals, opposite-break exit, daily P&L table,
// adjustable look, plus the new Settings table (for sharing) and White/Black chart background toggle.
//@version=5
indicator(title = "clusterbreak v2.3", shorttitle = "clusterbreak v2.3", overlay = true, max_labels_count = 500, max_lines_count = 500, max_polylines_count = 100, max_bars_back = 500)
// =====================================================================
// >>>>>>>>>> PASTE YOUR WEBHOOK KEY BETWEEN THE QUOTES <<<<<<<<<<
// (hardcoded on purpose; a Settings-panel key gets wiped by the mobile UI.
// A full-file re-paste of this script resets it, so paste the key back in.)
// =====================================================================
var string WEBHOOK_KEY = "PASTE_KEY_HERE"
// =====================================================================
// CONSTANTS
// =====================================================================
KEY_PLACEHOLDER = "PASTE_KEY_HERE"
SCRIPT_VERSION = "v2.3" // keep in sync with the title and the version history above
Use_BOS_Plot = true
Use_MSS_Plot = true
MAX_LOOKBACK = 500 // keep equal to max_bars_back in indicator() above
// =====================================================================
// INPUTS
// =====================================================================
General_Settings_group = "-------General Settings-------"
Fill_Zones_group = "-------Fill Zones-------"
Swing_Settings_group = "-------Swing Settings-------"
Trade_group = "-------Bracket Trade Settings-------"
Webhook_group = "-------Webhook-------"
Table_group = "-------Daily P&L Table-------"
Cfg_group = "-------Settings Table (for sharing)-------"
Window_group = "-------Trade Time Windows-------"
HTF_group = "-------HTF Structure / LTF Entry-------"
Swing_Length = input.int(defval = 8, title = "Swing Point Length", minval = 1, maxval = 50, group = Swing_Settings_group,
tooltip = "Number of bars to the left and right used to identify a swing point. Lower values are more sensitive, higher values are more stable.")
Send_Webhook = input.bool(defval = false, title = "Send Webhook Alerts", group = Webhook_group,
tooltip = "OFF by default. Nothing is sent until this is turned on.")
Account = input.string(defval = "", title = "Account", group = Webhook_group,
tooltip = "Must exactly match the account name set up for your webhook receiver (case-sensitive).")
Auto_Instrument = input.bool(defval = true, title = "Auto-Detect Instrument From Chart", group = Webhook_group,
tooltip = "Converts a dated contract chart (e.g. MNQU2026) to NT8 short form (MNQU6). Does NOT work on a continuous symbol like MNQ1! - use the manual override there.")
Manual_Instrument = input.string(defval = "", title = "Manual Instrument Override", group = Webhook_group,
tooltip = "If filled in, this is used instead of auto-detect. Enter the exact NT8 instrument name.")
Trade_Qty = input.int(defval = 3, title = "Quantity", minval = 1, group = Webhook_group)
Reverse_On_Opposite = input.bool(defval = false, title = "Flatten & reverse on opposite signal", group = Webhook_group,
tooltip = "OFF (default): a new signal is ignored while the previous trade is still open (in_trade guard). ON: an opposite signal sends flatten_first=true and reverses.")
Show_Status = input.bool(defval = true, title = "Show status box", group = Webhook_group)
Show_PnL_Table = input.bool(defval = true, title = "Show daily P&L table", group = Table_group)
Table_Days = input.int(defval = 5, title = "Days to show", minval = 1, maxval = 20, group = Table_group)
Day_End_Hour = input.int(defval = 16, title = "Trading day ends at (hour, 0-23)", minval = 0, maxval = 23, group = Table_group,
tooltip = "18 = the day rolls over at 18:00 in the timezone below (CME futures day).")
Day_Timezone = input.string(defval = "America/New_York", title = "Day timezone", group = Table_group,
tooltip = "Timezone used for the day rollover, for example America/New_York or America/Chicago.")
Table_Pos_Input = input.string(defval = "Bottom Right", title = "Table position", options = ["Top Left", "Top Right", "Middle Left", "Middle Right", "Bottom Left", "Bottom Right"], group = Table_group)
Show_Cfg_Table = input.bool(defval = true, title = "Show settings table", group = Cfg_group,
tooltip = "A compact table of the settings that shape the signals, so a screenshot of the chart shows exactly how it was set up. It never shows your account name or webhook key.")
Cfg_Pos_Input = input.string(defval = "Top Left", title = "Settings table position", options = ["Top Left", "Top Right", "Middle Left", "Middle Right", "Bottom Left", "Bottom Right"], group = Cfg_group)
Use_Windows = input.bool(defval = true, title = "Only trade inside time windows", group = Window_group,
tooltip = "ON: new entries (and webhook alerts) are only allowed inside an enabled window below. OFF: trade all day. If this is ON and no window is enabled, nothing will trade. Exits are never blocked by the windows.")
Window_TZ = input.string(defval = "America/New_York", title = "Window timezone", group = Window_group,
tooltip = "Timezone the window times are read in, for example America/New_York or America/Chicago.")
W1_On = input.bool(defval = true, title = "Window 1", group = Window_group, inline = "w1")
W1_Sess = input.session(defval = "0500-1330", title = "", group = Window_group, inline = "w1")
W2_On = input.bool(defval = true, title = "Window 2", group = Window_group, inline = "w2")
W2_Sess = input.session(defval = "1330-1530", title = "", group = Window_group, inline = "w2")
W3_On = input.bool(defval = true, title = "Window 3", group = Window_group, inline = "w3")
W3_Sess = input.session(defval = "0300-0500", title = "", group = Window_group, inline = "w3")
Close_At_End = input.bool(defval = false, title = "Close open trade when the window ends", group = Window_group,
tooltip = "ON: a trade still open when the last enabled window closes is exited at market (closeposition, which also cancels its stop/TP). OFF: the trade stays on its stop/TP bracket.")
Shade_Windows = input.bool(defval = true, title = "Shade the windows on the chart", group = Window_group, inline = "shade")
Window_Shade = input.color(defval = color.new(color.teal, 92), title = "", group = Window_group, inline = "shade")
Use_HTF_Entry = input.bool(defval = false, title = "Detect structure on a higher timeframe", group = HTF_group,
tooltip = "OFF (default): structure is detected on the chart's own timeframe, as before. ON: structure is detected on 'Structure timeframe' below, and this module controls the entry. Run the chart itself on a LOWER timeframe than that (e.g. chart on 30s, structure timeframe 1m).")
HTF_Period = input.timeframe(defval = "1", title = "Structure timeframe", group = HTF_group,
tooltip = "The higher timeframe BOS/MSS structure is detected on, e.g. 1 (1 minute), 5, 15.")
Entry_Trigger = input.string(defval = "Retrace into zone (better price)", title = "Entry trigger", options = ["Immediate (HTF break close)", "Retrace into zone (better price)"], group = HTF_group,
tooltip = "Immediate: enter at the close of the LTF bar the HTF break is confirmed on. Retrace: wait for LTF price to pull back into the HTF zone before entering.")
Retrace_Pct = input.float(defval = 50.0, title = "Retrace % into the zone", minval = 1.0, maxval = 99.0, step = 5.0, group = HTF_group,
tooltip = "How far back into the HTF zone price must pull back before entering, as a % of the zone height. 50 = the middle of the zone (same math as the 30/50/70 lines above). Only used in Retrace mode.")
Retrace_Timeout = input.int(defval = 40, title = "Retrace timeout (LTF bars)", minval = 1, group = HTF_group,
tooltip = "If the retrace target isn't reached within this many bars of the chart's own timeframe, the setup is cancelled. Only used in Retrace mode.")
Show_Retrace_Line = input.bool(defval = true, title = "Show retrace target while waiting", group = HTF_group)
Trade_Shorts = input.bool(defval = true, title = "Trade shorts (bearish MSS after bullish MSS)", group = Trade_group)
Trade_Longs = input.bool(defval = true, title = "Trade longs (bullish MSS after bearish MSS)", group = Trade_group)
Show_Bracket = input.bool(defval = true, title = "Draw Stop / Entry / TP levels", group = Trade_group)
Show_Bracket_Price = input.bool(defval = false, title = "Show price on Stop / Entry / TP labels", group = Trade_group,
tooltip = "OFF (default): the labels just say Stop, Entry and TP. ON: the price is added after each name.")
Bracket_Bars = input.int(defval = 15, title = "Bracket line length (bars)", minval = 1, maxval = 100, group = Trade_group)
Touch_Tolerance = input.int(defval = 10, title = "Touch tolerance (bars)", minval = 0, maxval = 50, group = Trade_group,
tooltip = "The swing level a reversal breaks must have formed inside the previous zone, or at most this many bars AFTER that zone broke (for example a pullback low right after a spike). 0 = strictly inside the previous zone. Bigger = more signals.")
Stop_Mode = input.string(defval = "Zone multiple", title = "Stop loss mode", options = ["Zone multiple", "Fixed points", "% of TP distance"], group = Trade_group,
tooltip = "Zone multiple: distance from the broken level = multiple x zone height. 1.0 puts the stop exactly at the far end of the zone (your '0' line). Fixed points: a set number of points from the broken level. % of TP distance: the stop is a percentage of however far the take profit is, so 100 = a 1:1 stop, 50 = half of a 1:1 stop (2:1 reward-to-risk), 70 = 70% of a 1:1 stop.")
Stop_Mult = input.float(defval = 1.0, title = "Stop multiple (x zone height, used in Zone multiple mode)", minval = 0.1, step = 0.05, group = Trade_group)
Stop_Pct = input.float(defval = 50.0, title = "Stop % of TP distance (used in % of TP distance mode)", minval = 5.0, maxval = 500.0, step = 5.0, group = Trade_group)
Stop_Points = input.float(defval = 20.0, title = "Stop fixed points", minval = 0.25, step = 0.25, group = Trade_group)
TP_Mode = input.string(defval = "Zone multiple", title = "Take profit mode", options = ["Zone multiple", "Fixed points"], group = Trade_group,
tooltip = "Zone multiple: distance from the broken level = multiple x zone height. 1.0 = your '2' line (1:1 with a 1.0 stop). 2.0 = twice the zone height. Fixed points: a set number of points from the broken level.")
TP_Mult = input.float(defval = 1.0, title = "Take profit multiple (x zone height)", minval = 0.1, step = 0.05, group = Trade_group)
TP_Points = input.float(defval = 40.0, title = "Take profit fixed points", minval = 0.25, step = 0.25, group = Trade_group)
Exit_On_Break = input.bool(defval = true, title = "Exit when an opposite structure break forms", group = Trade_group,
tooltip = "While long, a bearish break (BOS or MSS) closes the trade at market; while short, a bullish break does. The stop/TP bracket still applies until then. Turn off to hold every trade to its stop or TP.")
Display_Pivots = input.bool(defval = false, title = "Display Swing Points", group = General_Settings_group, inline = "Swing Point")
Swing_Color = input.color(defval = color.rgb(255, 255, 255, 50), title = "", group = General_Settings_group, inline = "Swing Point")
Plot_Entry_Levels = input.bool(defval = true, title = "Plot 30-50-70 Entry levels", group = General_Settings_group)
Entry_Text = input.string(defval = "None", title = "Entry level text", options = ["None", "Percentage", "Price", "Both"], group = General_Settings_group)
Plot_30 = input.bool(defval = false, title = "Plot 30%", tooltip = "Show the 30% entry level line. Its text is controlled by 'Entry level text'.", group = General_Settings_group)
Plot_50 = input.bool(defval = true, title = "Plot 50%", tooltip = "Show the 50% entry level line. Its text is controlled by 'Entry level text'.", group = General_Settings_group)
Plot_70 = input.bool(defval = false, title = "Plot 70%", tooltip = "Show the 70% entry level line. Its text is controlled by 'Entry level text'.", group = General_Settings_group)
Label_Size_Input = input.string(defval = "default", title = "Entry level text size", options = ["auto", "very small", "small", "default", "large", "very large"], group = General_Settings_group)
Fill_Bull_Zone1 = input.bool(defval = true, title = "Fill Only Bullish Zone 1", group = Fill_Zones_group)
Fill_Bear_Zone1 = input.bool(defval = true, title = "Fill Only Bearish Zone 1", group = Fill_Zones_group)
Fill_Bull_Zones = input.bool(defval = false, title = "Fill All Bullish Zones", group = Fill_Zones_group)
Fill_Bear_Zones = input.bool(defval = false, title = "Fill All Bearish Zones", group = Fill_Zones_group)
Color_Bull_Zone = input.color(defval = color.rgb(75, 130, 30, 50), title = "Bullish Zone 1 color", group = Fill_Zones_group, inline = "TF 1 Color")
Color_Bear_Zone = input.color(defval = color.rgb(130, 30, 30, 50), title = "Bearish Zone 1 color", group = Fill_Zones_group, inline = "TF 1 Color")
// ---------- LOOK: sizes and colors for chart text, signals and tables ----------
Look_group = "-------Look: Theme-------"
Text_group = "-------Look: Chart Text-------"
Signal_group = "-------Look: Signals-------"
Theme_Input = input.string(defval = "Light chart", title = "Theme preset", options = ["Dark chart", "Light chart", "Custom"], group = Look_group,
tooltip = "Dark chart / Light chart apply readable text, line, swing-point and table colors for that chart background. Choose Custom to use the individual colors marked (Custom), including the swing point color in General Settings. Sizes, widths, bullish/bearish colors and label background always apply.")
BG_Mode = input.string(defval = "Off", title = "Chart background", options = ["Off", "White", "Black"], group = Look_group,
tooltip = "Paints the chart background white or black behind the candles. White/Black also switches the text, line and table colors to match (unless Theme preset is Custom). Off leaves your chart background alone and uses the Theme preset.")
Bracket_Size_Input = input.string(defval = "small", title = "Stop/Entry/TP label size", options = ["tiny", "small", "normal", "large", "huge"], group = Text_group)
Swing_Size_Input = input.string(defval = "small", title = "Swing label size (HH/HL/LH/LL)", options = ["tiny", "small", "normal", "large", "huge"], group = Text_group)
Show_Skips = input.bool(defval = true, title = "Show why a signal was skipped", group = Text_group,
tooltip = "Puts a small label on every MSS that did not become a trade, with the reason (no touch + gap in bars, previous trade still open, and so on).")
Skip_Size_Input = input.string(defval = "tiny", title = "Skipped-signal label size", options = ["tiny", "small", "normal", "large", "huge"], group = Text_group)
Sig_Text_In = input.color(defval = color.new(#26a69a, 0), title = "Stop/Entry/TP text color (Custom)", group = Text_group)
Lbl_BG = input.color(defval = color.new(color.white, 100), title = "Label background color (all chart labels)", group = Text_group,
tooltip = "Background box behind Stop/Entry/TP, 30-50-70 and swing labels. Fully transparent by default; make it opaque for a solid box behind the text.")
Bull_Color = input.color(defval = color.green, title = "Bullish line / level color", group = Signal_group)
Bear_Color = input.color(defval = color.red, title = "Bearish line / level color", group = Signal_group)
Line_Width = input.int(defval = 1, title = "BOS / MSS line width", minval = 1, maxval = 5, group = Signal_group)
Sig_Line_In = input.color(defval = color.new(color.gray, 0), title = "Stop/Entry/TP line color (Custom)", group = Signal_group)
Sig_Width = input.int(defval = 1, title = "Stop/Entry/TP line width", minval = 1, maxval = 5, group = Signal_group)
Tbl_Size_Input = input.string(defval = "small", title = "Table text size (P&L table + status box)", options = ["tiny", "small", "normal", "large", "huge"], group = Table_group)
Tbl_BG_In = input.color(defval = color.new(color.black, 25), title = "Table background color (Custom)", group = Table_group)
Tbl_Head_In = input.color(defval = color.new(color.white, 0), title = "Header / total text color (Custom)", group = Table_group)
Tbl_Text_In = input.color(defval = color.new(color.white, 0), title = "Body text color (Custom)", group = Table_group)
Tbl_Frame_In = input.color(defval = color.gray, title = "Frame / border color (Custom)", group = Table_group)
Tbl_Profit_In = input.color(defval = color.lime, title = "Profit color (Custom)", group = Table_group)
Tbl_Loss_In = input.color(defval = color.red, title = "Loss color (Custom)", group = Table_group)
Entry_Label_Size = switch Label_Size_Input
"very small" => size.tiny
"small" => size.small
"default" => size.normal
"large" => size.large
"very large" => size.huge
"auto" => size.auto
// =====================================================================
// RESOLVED LOOK (theme preset or Custom colors)
// =====================================================================
bool isDarkTheme = Theme_Input != "Custom" and (BG_Mode == "Black" or (BG_Mode == "Off" and Theme_Input == "Dark chart"))
bool isLightTheme = Theme_Input != "Custom" and (BG_Mode == "White" or (BG_Mode == "Off" and Theme_Input == "Light chart"))
Tbl_BG = isDarkTheme ? color.new(#000000, 25) : (isLightTheme ? color.new(#FFFFFF, 8) : Tbl_BG_In)
Tbl_Head = isDarkTheme ? color.new(#FFFFFF, 0) : (isLightTheme ? color.new(#131722, 0) : Tbl_Head_In)
Tbl_Text = isDarkTheme ? color.new(#FFFFFF, 0) : (isLightTheme ? color.new(#131722, 0) : Tbl_Text_In)
Tbl_Frame = isDarkTheme ? color.new(#787B86, 0) : (isLightTheme ? color.new(#9598A1, 0) : Tbl_Frame_In)
Tbl_Profit = isDarkTheme ? color.new(#00E676, 0) : (isLightTheme ? color.new(#008A5B, 0) : Tbl_Profit_In)
Tbl_Loss = isDarkTheme ? color.new(#FF5252, 0) : (isLightTheme ? color.new(#C62828, 0) : Tbl_Loss_In)
Sig_Line = isDarkTheme ? color.new(#787B86, 0) : (isLightTheme ? color.new(#5D606B, 0) : Sig_Line_In)
Sig_Text = isDarkTheme ? color.new(#26A69A, 0) : (isLightTheme ? color.new(#00695C, 0) : Sig_Text_In)
Swing_Txt = isDarkTheme ? color.new(#FFFFFF, 50) : (isLightTheme ? color.new(#131722, 30) : Swing_Color)
sizeFrom(string _s) =>
switch _s
"tiny" => size.tiny
"small" => size.small
"normal" => size.normal
"large" => size.large
"huge" => size.huge
=> size.small
Bracket_Size = sizeFrom(Bracket_Size_Input)
Swing_Size = sizeFrom(Swing_Size_Input)
Tbl_Size = sizeFrom(Tbl_Size_Input)
Skip_Size = sizeFrom(Skip_Size_Input)
bgcolor(BG_Mode == "White" ? color.white : (BG_Mode == "Black" ? color.black : na), title = "Chart background")
// =====================================================================
// INSTRUMENT RESOLUTION (dated contract -> NT8 short form, e.g. MNQU2026 -> MNQU6)
// =====================================================================
string _root = syminfo.root
string _tk = syminfo.ticker
string _suffix = str.length(_tk) > str.length(_root) ? str.substring(_tk, str.length(_root)) : ""
string _mCode = str.length(_suffix) >= 3 ? str.substring(_suffix, 0, 1) : ""
string _yrStr = str.length(_suffix) >= 3 ? str.substring(_suffix, 1) : ""
bool _monthOk = _mCode != "" and str.contains("FGHJKMNQUVXZ", _mCode)
bool _yearOk = _yrStr != "" and not na(str.tonumber(_yrStr)) and (str.length(_yrStr) == 4 or str.length(_yrStr) == 2)
string autoInstrument = (_monthOk and _yearOk) ? _root + _mCode + str.substring(_yrStr, str.length(_yrStr) - 1) : ""
// Manual override wins. Never falls back to a bare continuous ticker.
string instrument = Manual_Instrument != "" ? Manual_Instrument : (Auto_Instrument ? autoInstrument : "")
bool canSend = Send_Webhook and instrument != "" and Account != "" and WEBHOOK_KEY != KEY_PLACEHOLDER
// =====================================================================
// HTF ENGINE (pure - no drawing - so it can run inside request.security)
// Detects the same touching-zone BOS/MSS signal as the main script, on whatever timeframe it is called with.
// Returns: [sigLong, sigShort, lonLevel, lonZoneLow, shoLevel, shoZoneHigh, sigTime, hBrk, lBrk, breakTime]
// sigLong/sigShort: a qualifying (touching-zone) signal fired on this call's bar.
// lonLevel/lonZoneLow, shoLevel/shoZoneHigh: the broken level and the opposite extreme of that zone (for stop/TP/retrace math).
// sigTime: time of a qualifying signal (na if none). hBrk/lBrk: any break, BOS or MSS. breakTime: time of any break (na if none).
// =====================================================================
f_htfEngine(int swingLen, int touchTol) =>
var float pHigh = na
var float pLow = na
var int pHighIdx = na
var int pLowIdx = na
var bool hAct = false
var bool lAct = false
var int lastBrk = 0
var int zStart = na
var int zBreak = na
float sh = ta.pivothigh(high, swingLen, swingLen)
float sl = ta.pivotlow(low, swingLen, swingLen)
if not na(sh)
pHigh := sh
pHighIdx := bar_index - swingLen
hAct := true
if not na(sl)
pLow := sl
pLowIdx := bar_index - swingLen
lAct := true
bool hBrk = hAct and close > pHigh
bool lBrk = lAct and close < pLow
bool sigL = false
bool sigS = false
float lonLvl = na
float lonLo = na
float shoLvl = na
float shoHi = na
int sTime = na
int bTime = na
if hBrk
hAct := false
bTime := time
bool isMSS = lastBrk == -1
if isMSS and not na(zStart) and pHighIdx >= zStart and pHighIdx <= zBreak + touchTol and (bar_index - pHighIdx) < MAX_LOOKBACK
float lowest = na
for i = 0 to (bar_index - pHighIdx)
lowest := na(lowest) ? low[i] : math.min(lowest, low[i])
sigL := true
lonLvl := pHigh
lonLo := lowest
sTime := time
zStart := pHighIdx
zBreak := bar_index
lastBrk := 1
if lBrk
lAct := false
bTime := time
bool isMSSb = lastBrk == 1
if isMSSb and not na(zStart) and pLowIdx >= zStart and pLowIdx <= zBreak + touchTol and (bar_index - pLowIdx) < MAX_LOOKBACK
float highest = na
for i = 0 to (bar_index - pLowIdx)
highest := na(highest) ? high[i] : math.max(highest, high[i])
sigS := true
shoLvl := pLow
shoHi := highest
sTime := time
zStart := pLowIdx
zBreak := bar_index
lastBrk := -1
[sigL, sigS, lonLvl, lonLo, shoLvl, shoHi, sTime, hBrk, lBrk, bTime]
// =====================================================================
// HELPERS
// =====================================================================
// Draws one entry level (short line + optional text) starting at the current (break) bar.
drawEntryLevel(float _price, string _pct, bool _show, color _col) =>
if _show
line.new(bar_index, _price, bar_index + 5, _price, color = _col)
string _txt = switch Entry_Text
"Percentage" => _pct
"Price" => str.tostring(_price, format.mintick)
"Both" => _pct + " (" + str.tostring(_price, format.mintick) + ")"
=> ""
if _txt != ""
label.new(bar_index + 6, _price, text = _txt, color = Lbl_BG, style = label.style_label_left, textcolor = _col, textalign = text.align_left, size = Entry_Label_Size)
// Draws one bracket level (line + text) starting at the current (break) bar. Local visual only.
drawBracketLevel(float _price, string _name) =>
line.new(bar_index, _price, bar_index + Bracket_Bars, _price, color = Sig_Line, width = Sig_Width)
label.new(bar_index + Bracket_Bars + 1, _price, text = Show_Bracket_Price ? _name + " " + str.tostring(_price, format.mintick) : _name, color = Lbl_BG, style = label.style_label_left, textcolor = Sig_Text, textalign = text.align_left, size = Bracket_Size)
// =====================================================================
// STATE
// =====================================================================
var float prevHigh = na // most recent swing high level
var float prevLow = na // most recent swing low level
var int prevHighIdx = na // bar_index of that swing high
var int prevLowIdx = na // bar_index of that swing low
var bool highActive = false // swing high not yet broken
var bool lowActive = false // swing low not yet broken
var int lastBreak = 0 // 1 = last break was bullish, -1 = bearish, 0 = none yet
var int lastZoneStartIdx = na // left edge of the previous break's zone, BOS or MSS (bar_index of the swing it broke)
var int lastZoneBreakIdx = na // right edge of the previous break's zone (bar_index of that break)
// Virtual trade (local in_trade guard: assumes the bracket fills at the levels; never sends anything)
var bool inTrade = false
var int tradeDir = 0
var float tradeEntry = na
var float tradeStop = na
var float tradeTP = na
var int lastFlattenDay = 0 // day-key of the last entry that carried flatten_first
// Trade signals for the current bar (filled in by the break blocks below)
bool goShort = false
bool goLong = false
float shortStop = na
float shortTP = na
float shortLevel = na
float longStop = na
float longTP = na
float longLevel = na
string skipWhy = "" // reason an MSS did not become a trade (drawn as a small label)
float fillPrice = close // actual entry price used for tradeEntry; overridden by the HTF/retrace path when it fills
// HTF structure / LTF entry pending state
var bool pending = false
var int pendDir = 0
var float pendStop = na
var float pendTP = na
var float pendLevel = na
var float pendTarget = na
var int pendBars = 0
var int lastSigTime = na // dedupes qualifying HTF signals
var int lastBreakTime = na // dedupes any HTF break, for highBroken/lowBroken edges and the opposite-break exit
// =====================================================================
// DAILY STATS (one row per trading day; the day rolls at Day_End_Hour in Day_Timezone)
// =====================================================================
var array<int> dKey = array.new<int>()
var array<int> dTrades = array.new<int>()
var array<int> dWins = array.new<int>()
var array<int> dLosses = array.new<int>()
var array<float> dPts = array.new<float>()
// Adds one closed virtual trade (result in points) to the current trading day.
recordTrade(float _pts) =>
int _n = array.size(dKey) - 1
array.set(dTrades, _n, array.get(dTrades, _n) + 1)
if _pts > 0
array.set(dWins, _n, array.get(dWins, _n) + 1)
else if _pts < 0
array.set(dLosses, _n, array.get(dLosses, _n) + 1)
array.set(dPts, _n, array.get(dPts, _n) + _pts)
// Shift time so the day boundary lands on midnight, then read the date in the chosen timezone.
int shiftedTime = time + (24 - Day_End_Hour) * 3600000
int tradingDay = year(shiftedTime, Day_Timezone) * 10000 + month(shiftedTime, Day_Timezone) * 100 + dayofmonth(shiftedTime, Day_Timezone)
bool isNewDay = array.size(dKey) == 0 ? true : array.get(dKey, array.size(dKey) - 1) != tradingDay
if isNewDay
array.push(dKey, tradingDay)
array.push(dTrades, 0)
array.push(dWins, 0)
array.push(dLosses, 0)
array.push(dPts, 0.0)
// =====================================================================
// SWING DETECTION
// =====================================================================
// request.security cannot sit inside an if/else in Pine v5, so this runs every bar regardless of
// Use_HTF_Entry; the result is only used below when that setting is on.
[htfSigL, htfSigS, htfLonLvl, htfLonLo, htfShoLvl, htfShoHi, htfSigTime, htfHBrk, htfLBrk, htfBreakTime] =
request.security(syminfo.tickerid, HTF_Period, f_htfEngine(Swing_Length, Touch_Tolerance), gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off)
bool highBroken = false
bool lowBroken = false
if not Use_HTF_Entry
swingHigh = ta.pivothigh(high, Swing_Length, Swing_Length)
swingLow = ta.pivotlow(low, Swing_Length, Swing_Length)
bool HH = false
bool LH = false
bool HL = false
bool LL = false
if not na(swingHigh)
if not na(prevHigh) // first swing has nothing to compare against
if swingHigh >= prevHigh
HH := true
else
LH := true
prevHigh := swingHigh
prevHighIdx := bar_index - Swing_Length
highActive := true
if not na(swingLow)
if not na(prevLow)
if swingLow >= prevLow
HL := true
else
LL := true
prevLow := swingLow
prevLowIdx := bar_index - Swing_Length
lowActive := true
if Display_Pivots
if HH
label.new(prevHighIdx, prevHigh, "HH", color = Lbl_BG, style = label.style_label_down, textcolor = Swing_Txt, size = Swing_Size)
if LH
label.new(prevHighIdx, prevHigh, "LH", color = Lbl_BG, style = label.style_label_down, textcolor = Swing_Txt, size = Swing_Size)
if HL
label.new(prevLowIdx, prevLow, "HL", color = Lbl_BG, style = label.style_label_up, textcolor = Swing_Txt, size = Swing_Size)
if LL
label.new(prevLowIdx, prevLow, "LL", color = Lbl_BG, style = label.style_label_up, textcolor = Swing_Txt, size = Swing_Size)
// =====================================================================
// BREAKS (close beyond the swing level)
// =====================================================================
highBroken := highActive and close > prevHigh
lowBroken := lowActive and close < prevLow
// ---------- Bullish break: BOS if last break was bullish, MSS if it was bearish ----------
if highBroken
highActive := false
bool isMSS = lastBreak == -1
bool isBOS = lastBreak == 1
if (isBOS and Use_BOS_Plot) or (isMSS and Use_MSS_Plot)
line.new(prevHighIdx, prevHigh, bar_index, prevHigh, color = Bull_Color, style = line.style_dashed, width = Line_Width)
int cnt = bar_index - prevHighIdx
if cnt < MAX_LOOKBACK // zone needs history access; skip the zone if the swing is older than the buffer
float lowest = na
array<chart.point> pts = array.new<chart.point>()
for i = 0 to cnt
if i > 0
pts.push(chart.point.from_index(bar_index - i, math.max(open[i], close[i])))
lowest := na(lowest) ? low[i] : math.min(lowest, low[i])
pts.push(chart.point.from_index(prevHighIdx, prevHigh))
pts.push(chart.point.from_index(bar_index, prevHigh))
if Fill_Bull_Zones or (isMSS and Fill_Bull_Zone1)
polyline.new(pts, curved = false, closed = true, line_color = Color_Bull_Zone, fill_color = Color_Bull_Zone, xloc = xloc.bar_index)
if isMSS and Plot_Entry_Levels
float rng = prevHigh - lowest
drawEntryLevel(prevHigh - rng * 0.30, "30%", Plot_30, Bull_Color)
drawEntryLevel(prevHigh - rng * 0.50, "50%", Plot_50, Bull_Color)
drawEntryLevel(prevHigh - rng * 0.70, "70%", Plot_70, Bull_Color)
// LONG signal: bullish MSS, and the swing high it breaks was created inside the previous (bearish) zone, BOS or MSS,
// or at most Touch_Tolerance bars after that zone broke
if isMSS
if na(lastZoneStartIdx)
skipWhy := "skip: no prior zone"
else if prevHighIdx < lastZoneStartIdx
skipWhy := "skip: swing older than prior zone"
else if prevHighIdx > lastZoneBreakIdx + Touch_Tolerance
skipWhy := "skip: no touch (gap " + str.tostring(prevHighIdx - lastZoneBreakIdx) + " bars)"
else
float zoneH = prevHigh - lowest
float tpDistL = TP_Mode == "Fixed points" ? TP_Points : zoneH * TP_Mult
float stopDistL = Stop_Mode == "Fixed points" ? Stop_Points : (Stop_Mode == "% of TP distance" ? tpDistL * Stop_Pct / 100.0 : zoneH * Stop_Mult)
float stopL = math.round_to_mintick(prevHigh - stopDistL)
float tpL = math.round_to_mintick(prevHigh + tpDistL)
if zoneH > syminfo.mintick and close < tpL and close > stopL
goLong := true
longStop := stopL
longTP := tpL
longLevel := prevHigh
else
skipWhy := close >= tpL ? "skip: price already past TP" : (close <= stopL ? "skip: price already past stop" : "skip: zone too small")
else if isMSS
skipWhy := "skip: swing too old"
lastZoneStartIdx := prevHighIdx
lastZoneBreakIdx := bar_index
lastBreak := 1
// ---------- Bearish break: BOS if last break was bearish, MSS if it was bullish ----------
if lowBroken
lowActive := false
bool isMSSb = lastBreak == 1
bool isBOSb = lastBreak == -1
if (isBOSb and Use_BOS_Plot) or (isMSSb and Use_MSS_Plot)
line.new(prevLowIdx, prevLow, bar_index, prevLow, color = Bear_Color, style = line.style_dashed, width = Line_Width)
int cntb = bar_index - prevLowIdx
if cntb < MAX_LOOKBACK
float highest = na
array<chart.point> ptsb = array.new<chart.point>()
for i = 0 to cntb
if i > 0
ptsb.push(chart.point.from_index(bar_index - i, math.min(open[i], close[i])))
highest := na(highest) ? high[i] : math.max(highest, high[i])
ptsb.push(chart.point.from_index(prevLowIdx, prevLow))
ptsb.push(chart.point.from_index(bar_index, prevLow))
if Fill_Bear_Zones or (isMSSb and Fill_Bear_Zone1)
polyline.new(ptsb, curved = false, closed = true, line_color = Color_Bear_Zone, fill_color = Color_Bear_Zone, xloc = xloc.bar_index)
if isMSSb and Plot_Entry_Levels
float rngb = highest - prevLow
drawEntryLevel(prevLow + rngb * 0.30, "30%", Plot_30, Bear_Color)
drawEntryLevel(prevLow + rngb * 0.50, "50%", Plot_50, Bear_Color)
drawEntryLevel(prevLow + rngb * 0.70, "70%", Plot_70, Bear_Color)
// SHORT signal: bearish MSS, and the swing low it breaks was created inside the previous (bullish) zone, BOS or MSS,
// or at most Touch_Tolerance bars after that zone broke
if isMSSb
if na(lastZoneStartIdx)
skipWhy := "skip: no prior zone"
else if prevLowIdx < lastZoneStartIdx
skipWhy := "skip: swing older than prior zone"
else if prevLowIdx > lastZoneBreakIdx + Touch_Tolerance
skipWhy := "skip: no touch (gap " + str.tostring(prevLowIdx - lastZoneBreakIdx) + " bars)"
else
float zoneHb = highest - prevLow
float tpDistS = TP_Mode == "Fixed points" ? TP_Points : zoneHb * TP_Mult
float stopDistS = Stop_Mode == "Fixed points" ? Stop_Points : (Stop_Mode == "% of TP distance" ? tpDistS * Stop_Pct / 100.0 : zoneHb * Stop_Mult)
float stopS = math.round_to_mintick(prevLow + stopDistS)
float tpS = math.round_to_mintick(prevLow - tpDistS)
if zoneHb > syminfo.mintick and close > tpS and close < stopS
goShort := true
shortStop := stopS
shortTP := tpS
shortLevel := prevLow
else
skipWhy := close <= tpS ? "skip: price already past TP" : (close >= stopS ? "skip: price already past stop" : "skip: zone too small")
else if isMSSb
skipWhy := "skip: swing too old"
lastZoneStartIdx := prevLowIdx
lastZoneBreakIdx := bar_index
lastBreak := -1
else
// highBroken/lowBroken fire once per NEW higher-timeframe break (not held true for every LTF bar inside it)
bool htfNewBreak = not na(htfBreakTime) and htfBreakTime != lastBreakTime
if htfNewBreak
lastBreakTime := htfBreakTime
highBroken := htfNewBreak and htfHBrk
lowBroken := htfNewBreak and htfLBrk
// Arm a new pending setup (or fill immediately) on a fresh qualifying HTF signal
if not na(htfSigTime) and htfSigTime != lastSigTime and (htfSigL or htfSigS)
lastSigTime := htfSigTime
int dir = htfSigL ? 1 : -1
float lvl = htfSigL ? htfLonLvl : htfShoLvl
float zoneH = htfSigL ? htfLonLvl - htfLonLo : htfShoHi - htfShoLvl
if zoneH > syminfo.mintick
float tpDist = TP_Mode == "Fixed points" ? TP_Points : zoneH * TP_Mult
float stopDist = Stop_Mode == "Fixed points" ? Stop_Points : (Stop_Mode == "% of TP distance" ? tpDist * Stop_Pct / 100.0 : zoneH * Stop_Mult)
float stopP = math.round_to_mintick(dir == 1 ? lvl - stopDist : lvl + stopDist)
float tpP = math.round_to_mintick(dir == 1 ? lvl + tpDist : lvl - tpDist)
if Entry_Trigger == "Immediate (HTF break close)"
fillPrice := close
if dir == 1
goLong := true
longStop := stopP
longTP := tpP
longLevel := lvl
else
goShort := true
shortStop := stopP
shortTP := tpP
shortLevel := lvl
else
pending := true
pendDir := dir
pendLevel := lvl
pendStop := stopP
pendTP := tpP
pendTarget := math.round_to_mintick(dir == 1 ? lvl - zoneH * Retrace_Pct / 100.0 : lvl + zoneH * Retrace_Pct / 100.0)
pendBars := 0
else
skipWhy := "skip: HTF zone too small"
// Manage a pending retrace: fill, invalidate (stop side hit first), or time out
if pending
bool reached = pendDir == 1 ? (low <= pendTarget) : (high >= pendTarget)
bool blownBy = pendDir == 1 ? (low <= pendStop) : (high >= pendStop)
if blownBy
pending := false
skipWhy := "skip: invalidated before entry"
else if reached
pending := false
fillPrice := pendTarget
if pendDir == 1
goLong := true
longStop := pendStop
longTP := pendTP
longLevel := pendLevel
else
goShort := true
shortStop := pendStop
shortTP := pendTP
shortLevel := pendLevel
else
pendBars += 1
if pendBars > Retrace_Timeout
pending := false
skipWhy := "skip: retrace timeout"
if Show_Retrace_Line and pending
label.new(bar_index, pendTarget, "Retrace target", color = Lbl_BG, style = pendDir == 1 ? label.style_label_up : label.style_label_down, textcolor = Sig_Text, size = Skip_Size)
// =====================================================================
// VIRTUAL TRADE TRACKING (closes the local in_trade flag when stop or TP is touched)
// =====================================================================
bool w1Now = W1_On and not na(time(timeframe.period, W1_Sess, Window_TZ))
bool w2Now = W2_On and not na(time(timeframe.period, W2_Sess, Window_TZ))
bool w3Now = W3_On and not na(time(timeframe.period, W3_Sess, Window_TZ))
bool inWindow = (not Use_Windows) or w1Now or w2Now or w3Now
bool windowEnded = Use_Windows and inWindow[1] and not inWindow
bgcolor(Shade_Windows and Use_Windows and inWindow ? Window_Shade : na, title = "Trade windows")
bool exitedOnBreak = false // true on the bar where an opposite structure break closed the trade
bool exitedAtWindowEnd = false // true on the bar where the trade window closing exited the trade
if inTrade
bool stopHit = tradeDir == -1 ? high >= tradeStop : low <= tradeStop
bool tpHit = tradeDir == -1 ? low <= tradeTP : high >= tradeTP
if stopHit or tpHit
float exitPx = stopHit ? tradeStop : tradeTP // if both are touched in one bar, assume the stop came first
recordTrade((exitPx - tradeEntry) * tradeDir)
inTrade := false
else if Exit_On_Break and ((tradeDir == 1 and lowBroken) or (tradeDir == -1 and highBroken))
float brkPts = (close - tradeEntry) * tradeDir // exit at the close of the break bar
recordTrade(brkPts)
exitedOnBreak := true
inTrade := false
if Show_Bracket
string exitTxt = "Exit (opposite break) " + (brkPts > 0 ? "+" : "") + str.tostring(brkPts, "0.00")
if tradeDir == 1
label.new(bar_index, low, exitTxt, color = Lbl_BG, style = label.style_label_up, textcolor = Sig_Text, size = Bracket_Size)
else
label.new(bar_index, high, exitTxt, color = Lbl_BG, style = label.style_label_down, textcolor = Sig_Text, size = Bracket_Size)
else if Close_At_End and windowEnded
float endPts = (close - tradeEntry) * tradeDir // exit at the close of the first bar outside the window
recordTrade(endPts)
exitedAtWindowEnd := true
inTrade := false
if Show_Bracket
string endTxt = "Exit (window end) " + (endPts > 0 ? "+" : "") + str.tostring(endPts, "0.00")
if tradeDir == 1
label.new(bar_index, low, endTxt, color = Lbl_BG, style = label.style_label_up, textcolor = Sig_Text, size = Bracket_Size)
else
label.new(bar_index, high, endTxt, color = Lbl_BG, style = label.style_label_down, textcolor = Sig_Text, size = Bracket_Size)
// =====================================================================
// ENTRY + WEBHOOK (bracket rides the entry order only)
// =====================================================================
int sigDir = (goShort and Trade_Shorts) ? -1 : ((goLong and Trade_Longs) ? 1 : 0)
bool isReversal = inTrade and sigDir != 0 and sigDir != tradeDir
bool takeIt = sigDir != 0 and inWindow and ((not inTrade) or (isReversal and Reverse_On_Opposite))
if goShort and not Trade_Shorts
skipWhy := "skip: shorts are off"
if goLong and not Trade_Longs
skipWhy := "skip: longs are off"
if sigDir != 0 and not takeIt
skipWhy := inWindow ? "skip: previous trade still open" : "skip: outside trade window"
if takeIt
float sStop = sigDir == -1 ? shortStop : longStop
float sTP = sigDir == -1 ? shortTP : longTP
float sLevel = sigDir == -1 ? shortLevel : longLevel
if Show_Bracket
drawBracketLevel(sStop, "Stop")
drawBracketLevel(sLevel, "Entry")
drawBracketLevel(sTP, "TP")
if canSend
int dayKey = year * 10000 + month * 100 + dayofmonth
bool firstOfDay = dayKey != lastFlattenDay
bool flatten = firstOfDay or isReversal or exitedOnBreak
string payload = "key=" + WEBHOOK_KEY + ";command=place;account=" + Account + ";instrument=" + instrument + ";action=" + (sigDir == -1 ? "SELL" : "BUY") + ";qty=" + str.tostring(Trade_Qty) + ";order_type=market;tif=day;take_profit=" + str.tostring(sTP, format.mintick) + ";stop_loss=" + str.tostring(sStop, format.mintick) + ";" + (flatten ? "flatten_first=true;" : "")
alert(payload, alert.freq_once_per_bar_close)
if firstOfDay
lastFlattenDay := dayKey
if isReversal
recordTrade((close - tradeEntry) * tradeDir) // old trade is flattened at market
inTrade := true
tradeDir := sigDir
tradeEntry := fillPrice
tradeStop := sStop
tradeTP := sTP
// =====================================================================
// EXIT ALERT: opposite-break exit or window-end exit (only when no new trade is entered on the same bar)
// CLOSEPOSITION flattens the position and cancels the resting stop/TP orders.
// =====================================================================
if (exitedOnBreak or exitedAtWindowEnd) and not takeIt and canSend
alert("key=" + WEBHOOK_KEY + ";command=closeposition;account=" + Account + ";instrument=" + instrument + ";", alert.freq_once_per_bar_close)
// =====================================================================
// SKIPPED-SIGNAL REASONS (answers "why was this not marked?")
// =====================================================================
if Show_Skips and skipWhy != ""
if highBroken
label.new(bar_index, low, skipWhy, color = Lbl_BG, style = label.style_label_up, textcolor = Sig_Text, size = Skip_Size)
else
label.new(bar_index, high, skipWhy, color = Lbl_BG, style = label.style_label_down, textcolor = Sig_Text, size = Skip_Size)
// =====================================================================
// STATUS BOX (spot-check against NT8 before turning the webhook on)
// =====================================================================
var table statusTbl = table.new(position.top_right, 1, 3, bgcolor = Tbl_BG, frame_color = Tbl_Frame, frame_width = 1)
if Show_Status and barstate.islast
table.cell(statusTbl, 0, 0, "Webhook: " + (Send_Webhook ? (canSend ? "ON" : "ON (incomplete setup)") : "OFF"), text_color = Send_Webhook ? (canSend ? Tbl_Profit : color.orange) : color.gray, text_size = Tbl_Size)
table.cell(statusTbl, 0, 1, "Acct: " + (Account == "" ? "(not set)" : Account), text_color = Tbl_Text, text_size = Tbl_Size)
table.cell(statusTbl, 0, 2, "Instr: " + (instrument == "" ? "(not resolved)" : instrument), text_color = Tbl_Text, text_size = Tbl_Size)
// =====================================================================
// SETTINGS TABLE (screenshot-friendly summary; never shows the account name or webhook key)
// =====================================================================
Cfg_Pos = switch Cfg_Pos_Input
"Top Left" => position.top_left
"Top Right" => position.top_right
"Middle Left" => position.middle_left
"Middle Right" => position.middle_right
"Bottom Left" => position.bottom_left
"Bottom Right" => position.bottom_right
var table cfgTbl = table.new(Cfg_Pos, 2, 15, bgcolor = Tbl_BG, frame_color = Tbl_Frame, frame_width = 1, border_color = color.new(Tbl_Frame, 60), border_width = 1)
onOff(bool _b) =>
_b ? "On" : "Off"
cfgRow(int _r, string _k, string _v) =>
table.cell(cfgTbl, 0, _r, _k, text_color = Tbl_Text, text_size = Tbl_Size, text_halign = text.align_left)
table.cell(cfgTbl, 1, _r, _v, text_color = Tbl_Text, text_size = Tbl_Size, text_halign = text.align_left)
if Show_Cfg_Table and barstate.islast
table.cell(cfgTbl, 0, 0, "BOS MSS Bracket", text_color = Tbl_Head, text_size = Tbl_Size, text_halign = text.align_left)
table.cell(cfgTbl, 1, 0, SCRIPT_VERSION, text_color = Tbl_Head, text_size = Tbl_Size, text_halign = text.align_left)
cfgRow(1, "Symbol / TF", syminfo.ticker + " / " + timeframe.period)
cfgRow(2, "Swing length", str.tostring(Swing_Length))
cfgRow(3, "Touch tolerance", str.tostring(Touch_Tolerance) + " bars")
cfgRow(4, "Exit on opp. break", onOff(Exit_On_Break))
cfgRow(5, "Shorts / Longs", onOff(Trade_Shorts) + " / " + onOff(Trade_Longs))
cfgRow(6, "Quantity", str.tostring(Trade_Qty))
cfgRow(7, "Reverse on opposite", onOff(Reverse_On_Opposite))
string stopTxt = Stop_Mode == "Fixed points" ? str.tostring(Stop_Points, "0.0#") + " pts" : (Stop_Mode == "% of TP distance" ? str.tostring(Stop_Pct, "0.#") + "% of TP" : str.tostring(Stop_Mult, "0.0#") + "x zone")
string tpTxt = TP_Mode == "Fixed points" ? str.tostring(TP_Points, "0.0#") + " pts" : str.tostring(TP_Mult, "0.0#") + "x zone"
string rrTxt = Stop_Mode == "% of TP distance" ? " (" + str.tostring(100.0 / Stop_Pct, "0.0#") + "R)" : (Stop_Mode == TP_Mode ? " (" + str.tostring(Stop_Mode == "Fixed points" ? TP_Points / Stop_Points : TP_Mult / Stop_Mult, "0.0#") + "R)" : "")
cfgRow(8, "Stop / TP", stopTxt + " / " + tpTxt + rrTxt)
cfgRow(9, "Day ends", str.tostring(Day_End_Hour, "00") + ":00 " + Day_Timezone)
cfgRow(10, "Webhook", onOff(Send_Webhook))
cfgRow(11, "Background", BG_Mode == "Off" ? Theme_Input : BG_Mode)
cfgRow(12, "P&L basis", "Virtual trades (not fills)")
string winTxt = not Use_Windows ? "Off (all day)" : ((W1_On or W2_On or W3_On) ? ((W1_On ? W1_Sess + " " : "") + (W2_On ? W2_Sess + " " : "") + (W3_On ? W3_Sess + " " : "") + Window_TZ + (Close_At_End ? ", close at end" : "")) : "none enabled (no trades)")
cfgRow(13, "Trade windows", winTxt)
cfgRow(14, "Structure / Entry", Use_HTF_Entry ? (HTF_Period + " structure, " + (Entry_Trigger == "Immediate (HTF break close)" ? "immediate entry" : str.tostring(Retrace_Pct, "0") + "% retrace entry")) : "Chart timeframe only")
// =====================================================================
// DAILY P&L TABLE
// Built from this script's own virtual trades, NOT from broker fills: entry = close of the signal bar,
// exits at exactly the stop/TP price, no commissions or slippage, stop wins a same-bar tie.
// A trade counts on the day it CLOSES. $ = points x point value of the symbol x quantity.
// =====================================================================
Table_Pos = switch Table_Pos_Input
"Top Left" => position.top_left
"Top Right" => position.top_right
"Middle Left" => position.middle_left
"Middle Right" => position.middle_right
"Bottom Left" => position.bottom_left
"Bottom Right" => position.bottom_right
var table pnlTbl = table.new(Table_Pos, 6, Table_Days + 2, bgcolor = Tbl_BG, frame_color = Tbl_Frame, frame_width = 1, border_color = color.new(Tbl_Frame, 60), border_width = 1)
dayLabel(int _key) =>
int _m = int((_key % 10000) / 100)
int _d = _key % 100
str.tostring(_m, "00") + "/" + str.tostring(_d, "00")
fmtPts(float _x) =>
(_x > 0 ? "+" : "") + str.tostring(_x, "0.00")
fmtUsd(float _x) =>
(_x > 0 ? "+$" : (_x < 0 ? "-$" : "$")) + str.tostring(math.abs(_x), "0.00")
fmtWinPct(int _w, int _l) =>
(_w + _l) > 0 ? str.tostring(100.0 * _w / (_w + _l), "0") + "%" : "-"
pnlColor(float _x) =>
_x > 0 ? Tbl_Profit : (_x < 0 ? Tbl_Loss : Tbl_Text)
if Show_PnL_Table and barstate.islast
color hdrCol = Tbl_Head
table.cell(pnlTbl, 0, 0, "Day", text_color = hdrCol, text_size = Tbl_Size, text_halign = text.align_left)
table.cell(pnlTbl, 1, 0, "Trades", text_color = hdrCol, text_size = Tbl_Size)
table.cell(pnlTbl, 2, 0, "W-L", text_color = hdrCol, text_size = Tbl_Size)
table.cell(pnlTbl, 3, 0, "Win %", text_color = hdrCol, text_size = Tbl_Size)
table.cell(pnlTbl, 4, 0, "P&L pts", text_color = hdrCol, text_size = Tbl_Size)
table.cell(pnlTbl, 5, 0, "P&L $", text_color = hdrCol, text_size = Tbl_Size)
int nDays = array.size(dKey)
int shown = math.min(nDays, Table_Days)
int tTr = 0
int tW = 0
int tL = 0
float tP = 0.0
if shown > 0
for k = 0 to shown - 1
int idx = nDays - 1 - k
int trd = array.get(dTrades, idx)
int win = array.get(dWins, idx)
int los = array.get(dLosses, idx)
float pts = array.get(dPts, idx)
float usd = pts * syminfo.pointvalue * Trade_Qty
color pc = pnlColor(pts)
table.cell(pnlTbl, 0, k + 1, dayLabel(array.get(dKey, idx)), text_color = Tbl_Text, text_size = Tbl_Size, text_halign = text.align_left)
table.cell(pnlTbl, 1, k + 1, str.tostring(trd), text_color = Tbl_Text, text_size = Tbl_Size)
table.cell(pnlTbl, 2, k + 1, str.tostring(win) + "-" + str.tostring(los), text_color = Tbl_Text, text_size = Tbl_Size)
table.cell(pnlTbl, 3, k + 1, fmtWinPct(win, los), text_color = Tbl_Text, text_size = Tbl_Size)
table.cell(pnlTbl, 4, k + 1, fmtPts(pts), text_color = pc, text_size = Tbl_Size)
table.cell(pnlTbl, 5, k + 1, fmtUsd(usd), text_color = pc, text_size = Tbl_Size)
tTr += trd
tW += win
tL += los
tP += pts
float tUsd = tP * syminfo.pointvalue * Trade_Qty
color tc = pnlColor(tP)
int tRow = Table_Days + 1
table.cell(pnlTbl, 0, tRow, "Total", text_color = hdrCol, text_size = Tbl_Size, text_halign = text.align_left)
table.cell(pnlTbl, 1, tRow, str.tostring(tTr), text_color = hdrCol, text_size = Tbl_Size)
table.cell(pnlTbl, 2, tRow, str.tostring(tW) + "-" + str.tostring(tL), text_color = hdrCol, text_size = Tbl_Size)
table.cell(pnlTbl, 3, tRow, fmtWinPct(tW, tL), text_color = hdrCol, text_size = Tbl_Size)
table.cell(pnlTbl, 4, tRow, fmtPts(tP), text_color = tc, text_size = Tbl_Size)
table.cell(pnlTbl, 5, tRow, fmtUsd(tUsd), text_color = tc, text_size = Tbl_Size)
Overview
Clusterbreak triggers signals when a reversal breaks a swing point formed inside an opposing zone, projecting stop loss and take profit targets based on zone height, fixed points, or percentages. Users can enable higher-timeframe structure tracking with lower-timeframe retrace entries, filter entries using configurable trading sessions, and output webhook alert payloads. A simulated daily P&L table and settings dashboard display performance metrics and current configurations directly on screen.
BOS and MSS structure detection
Touching zones reversal entry filter
Higher-timeframe structure with lower-timeframe entries
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Swing Point Length
int
8
Swing_Length
Send Webhook Alerts
bool
false
Send_Webhook
Account
string
Account
Auto-Detect Instrument From Chart
bool
true
Auto_Instrument
Manual Instrument Override
string
Manual_Instrument
Quantity
int
3
Trade_Qty
Flatten & reverse on opposite signal
bool
false
Reverse_On_Opposite
Show status box
bool
true
Show_Status
Show daily P&L table
bool
true
Show_PnL_Table
Days to show
int
5
Table_Days
Trading day ends at (hour, 0-23)
int
16
Day_End_Hour
Day timezone
string
America/New_York
Day_Timezone
Show all 40 settings
Setting
Type
Default
Variable
Table position
string
Bottom Right
Table_Pos_Input
Show settings table
bool
true
Show_Cfg_Table
Settings table position
string
Top Left
Cfg_Pos_Input
Only trade inside time windows
bool
true
Use_Windows
Window timezone
string
America/New_York
Window_TZ
Window 1
bool
true
W1_On
W1_Sess
session
0500-1330
W1_Sess
Window 2
bool
true
W2_On
W2_Sess
session
1330-1530
W2_Sess
Window 3
bool
true
W3_On
W3_Sess
session
0300-0500
W3_Sess
Close open trade when the window ends
bool
false
Close_At_End
Shade the windows on the chart
bool
true
Shade_Windows
Window_Shade
color
color.new(color.teal, 92)
Window_Shade
Detect structure on a higher timeframe
bool
false
Use_HTF_Entry
Structure timeframe
timeframe
1
HTF_Period
Entry trigger
string
Retrace into zone (better price)
Entry_Trigger
Retrace % into the zone
float
50.0
Retrace_Pct
Retrace timeout (LTF bars)
int
40
Retrace_Timeout
Show retrace target while waiting
bool
true
Show_Retrace_Line
Trade shorts (bearish MSS after bullish MSS)
bool
true
Trade_Shorts
Trade longs (bullish MSS after bearish MSS)
bool
true
Trade_Longs
Draw Stop / Entry / TP levels
bool
true
Show_Bracket
Show price on Stop / Entry / TP labels
bool
false
Show_Bracket_Price
Bracket line length (bars)
int
15
Bracket_Bars
Touch tolerance (bars)
int
10
Touch_Tolerance
Stop loss mode
string
Zone multiple
Stop_Mode
Stop multiple (x zone height, used in Zone multiple mode)
float
1.0
Stop_Mult
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.
The Golden Bullet Strategy is an overlay signal indicator based on Smart Money Concepts and time-based liquidity models. It identifies liquidity sweeps of a designated reference session range during a specified hunt window. It then confirms Market Structure Sh
Indicatorv6SMC / ICT / price actionSessions & time
This indicator detects and draws the first Fair Value Gap (FVG) that forms in each hourly session based on a selected lower timeframe, with specialized handling for Regular Trading Hours (RTH) confluence windows. It draws FVG range boxes, midpoint lines, hourl
This indicator determines daily and weekly bias using higher-timeframe structural levels and liquidity targets based on TTrades concepts. It plots period separators, tracks previous highs and lows, and changes level colors when liquidity is raided. An on-scree
Apex Protocol Assistant is a Smart Money Concepts indicator designed for session-based trading. It determines market direction using a higher timeframe Exponential Moving Average and highlights Asian range boundaries alongside London and New York killzones. Th
Indicatorv5SMC / ICT / price actionSessions & time
This overlay indicator scans a pasted list of symbols for ICT liquidity sweep patterns using prior swing sweeps. The screening method looks back one, two, or three swings and can list long and short signals in a results table with adjustable rows, position, an
This indicator overlays an intraday SPX and ES chart with a midnight session range, regular-trading-hours opening levels, and a VWAP projection box. It draws the midnight high, mid, and low, flags edge rejections within an absolute tolerance, and plots optiona