Strategy Lab · optional
gold-sweep-desk-strategy.pine
The same plans as orders for TradingView's Strategy Tester, with a commission that matches the desk's cost estimate.
It tracks gold's pools, refuses most raids by name, and draws a plan only when a sweep flips the nearest gap a full ATR from its extreme.
Day 1 · the first after the warm-up to reach 1R
Simulated fifteen-minute gold candles (the Pine project's own seeded market), not a real market, run through Gold Sweep Desk 1.1.0's own rules, ported line for line and held to its reference engine; it draws what the script draws at each close. R is after the script's 0.40 cost estimate. Day 1 is the first simulated day after the warm-up whose plan reaches its 1R line, so every step of a plan shows; Another day plays the next simulated days with a plan, in order, whatever they did.
| Time | Event | Prices |
|---|---|---|
| 18:00 | The trading day opens | PDH 3619.62 · PDL 3533.50 |
| 00:00 | The Asia range is set: its high and low are pools | 3563.48–3546.16 |
| 02:45 | Raid · Asia high | Bar 3566.89 high · 3559.40 low |
| 03:00 | Swept · Asia high: a close back inside | Close 3561.96 |
| 04:00 | Flip: a close through the nearest opposing gap | Close 3554.77 |
| 04:00 | ▼ SHORT 3554.77 (flip close) | Stop 3573.67 · 1R 3535.87 · target 3516.97 |
| 05:00 | The London range is set: its high and low are pools | 3573.09–3544.30 |
| 07:30 | 1R reached | 1R 3535.87 |
| 16:30 | ✗ FLAT AT THE TIME EXIT −0.6R | Exit 3565.09 · −0.5R before costs |
No signup · Pine v6 · MIT. New York time on any chart, every decision on closed candles.
The close back inside is only step two: the desk waits for a close through the gap, and a sweep that fails first ends without a plan.
09:45: price reaches 3769.95, and the short at the sweep is stopped. The desk never traded it.
The same simulated market and rules: the first simulated day after the warm-up where a sweep failed. The red box is an entry at the sweep's close, with the desk's own stop: a tenth of an ATR past the extreme; it is not the desk's rule.
Candles that traded past a fresh pool inside a window.
Raids that closed back inside within three candles.
Sweeps that closed through the nearest gap, a full ATR from the extreme.
Flips whose stop and clock passed the checks, each then drawn to its ending.
The desk's own record on a real chart: OANDA:XAUUSD 15m, the 63 trading days from 30 June 2026, read on 26 September 2026.
Shape the candles after a raid of Asia's high, and the desk's own engine, run on them, names what it does.
A bullish gap under it
The close after the sweep
Drawn candles, not the simulation. The verdict is the desk's own engine, run on them at its defaults.
A raid that stays beyond the level for three more closes is held, not swept.
The nearest opposing fair value gap born in the 30 candles before the sweep.
A close through that gap, at least one ATR from the extreme, within 12 candles.
A stop wider than six ATR, or an entry at 16:45 or later, is refused too.
A plan ends at its stop, at twice its risk, or flat at 16:45 New York; Pro takes half off at the 1R line on the way.
Pro's half at 1R leaves less on the day that ran.
Three plans from the simulated run: the first simulated plan after the warm-up to end at its target, the first to end at its stop, and the first to reach 1R and end at the 16:45 time exit. R is after the 0.40 cost estimate; Pro's is the same plan with half closed at 1R.
Beyond the sweep's extreme, fixed when the plan is made, and checked first on every candle.
The 1R line on the way is drawn; the free desk leaves it to you.
Before gold's daily break, a plan still open closes at that candle's close.
Your spread and slippage, set in input 04; every R here is after it.
Two files, the complete scripts, ready for TradingView's Pine Editor.
On a phone? Send this page to your desktop.
//@version=6// Gold Sweep Desk 1.1.0 · MIT · © 2026 ProEA Lab · built for r/pinescript// Gold liquidity as a desk: the prior-day, Asia and London highs and lows, their sweeps, the flip and the plan, in words.// Every clock is New York time; bars while spot gold is closed are skipped, so spot and 24/7 feeds agree.// Decisions happen on closed bars. The record is this chart's history after an estimated cost, not a forecast.// Rules: docs/engine-contract.md in the release · https://proea.app/en/free/gold-sweep-desk// License: MIT. Share it freely; keep this header.indicator("Gold Sweep Desk", "GoldSweep", overlay = true, behind_chart = false, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500, max_bars_back = 2000)const string GS_VERSION = "1.1.0"const bool GS_IS_STRATEGY = false // ═══ ENGINE · core.pinefrag ═════════════════════════════════════════════════════════════════════════════════════// The engine decides; the renderer (visuals.pinefrag) only reads. Every engine name starts with "gs" and a// capital letter; tests/display.test.mjs refuses any renderer line that assigns one. The rules are written in// docs/engine-contract.md; tests/reference/engine.mjs is the independent reference.//// ① SWEEP a bar in an entry window trades through a fresh pool (prior day, Asia or London high/low) and a close// comes back inside it within a few bars.// ② FLIP a close goes through the nearest opposing fair value gap: the gap inverts.// ③ PLAN entry at that close (or at a later close back inside the gap); stop beyond the sweep's extreme; target// in R; flat at the time exit. Stop first when a bar reaches both.// Every clock is New York time by the bar's open. Bars while spot gold is closed (Friday 17:00 → Sunday 18:00 and// the daily 17:00–18:00 break) are skipped entirely, so a 24/7 feed and a spot feed give the same bar stream.// Decisions happen on closed bars only. A plan's geometry is frozen at entry. string gsG1 = "01 · Pools and windows"bool gsPoolPd = input.bool(true, "Prior day high / low", group = gsG1, tooltip = "The previous trading day's high and low. Gold's day runs 18:00 → 17:00 New York.")bool gsPoolAsia = input.bool(true, "Asia high / low · 20:00–24:00", group = gsG1, tooltip = "The range of the bars opening 20:00–24:00 New York; a pool from the first bar after midnight.")bool gsPoolLon = input.bool(true, "London high / low · 02:00–05:00", group = gsG1, tooltip = "The range of the bars opening 02:00–05:00 New York; a pool from the first bar after 05:00.")string gsWindows = input.string("London + New York", "Entry windows", options = ["London + New York", "Continuous 01:00–11:00"], group = gsG1, tooltip = "A setup can only start on a bar that opens inside a window (New York time). The pools are used up by any bar that trades through them, window or not.")bool gsWinLon = input.bool(true, " London 02:00–05:00", group = gsG1)bool gsWinNy = input.bool(true, " New York 07:00–11:00", group = gsG1) string gsG2 = "02 · Sweep and flip"float gsDepth = input.float(0.05, "Sweep depth · ATR beyond the level", minval = 0, maxval = 5, step = 0.05, group = gsG2, tooltip = "The raid must reach at least this far beyond the outermost pool it takes, in ATR(14).")int gsReclaim = input.int(3, "Close back inside within · bars", minval = 0, maxval = 20, group = gsG2, tooltip = "The close back inside the swept level may come on the raid bar or on this many bars after it. Otherwise price was accepted beyond the level (HELD).")string gsFailOn = input.string("A close back beyond the level", "A sweep fails on", options = ["A close back beyond the level", "Any new extreme"], group = gsG2, tooltip = "While the flip is awaited: a close back beyond the swept level ends the setup, and a new wick only moves the extreme (and the stop) further; or any new extreme ends it.")string gsConfirm = input.string("FVG flip", "Confirmation", options = ["FVG flip", "Close back only"], group = gsG2, tooltip = "FVG flip: a close through the nearest opposing fair value gap. Close back only: the sweep's own close is the trigger.")int gsLookback = input.int(30, "Gap born within · bars before the sweep", minval = 3, maxval = 200, group = gsG2)int gsFlipBars = input.int(12, "Flip within · bars after the sweep", minval = 0, maxval = 100, group = gsG2)float gsMinLeg = input.float(1.0, "Leg from the extreme · at least ATR", minval = 0, maxval = 10, step = 0.1, group = gsG2, tooltip = "The flip close must be at least this far from the sweep's extreme.") string gsG3 = "03 · Plan"string gsEntryMode = input.string("Flip close", "Entry", options = ["Flip close", "Retest close"], group = gsG3, tooltip = "Flip close: the flip's close is the entry. Retest close: after the flip, the first close back inside the gap within the retest bars.")int gsRetestBars = input.int(12, " Retest within · bars", minval = 1, maxval = 100, group = gsG3)float gsBuffer = input.float(0.1, "Stop buffer · ATR beyond the extreme", minval = 0, maxval = 3, step = 0.05, group = gsG3)float gsMaxStop = input.float(6.0, "Skip if the stop is wider than · ATR", minval = 0.5, maxval = 50, step = 0.5, group = gsG3)float gsTargetR = input.float(2.0, "Target · R", minval = 0.5, maxval = 20, step = 0.5, group = gsG3, tooltip = "A design choice, not a tested setting. 1R is the distance from the entry to the stop; the 1R milestone is recorded on the way.")string gsExitAt = input.string("16:45", "Flat at · HH:MM New York", group = gsG3, tooltip = "An open plan closes at the close of the bar that ends at or after this time; no entry on or after it. Gold's daily break starts at 17:00.")string gsSide = input.string("Both", "Direction", options = ["Both", "Long only", "Short only"], group = gsG3) string gsG4 = "04 · Costs and record"float gsCost = input.float(0.40, "Estimated round-trip cost · price", minval = 0, step = 0.05, group = gsG4, tooltip = "Spread plus slippage for entry and exit together, in the chart's price (0.40 = 40 cents an ounce on spot gold). Every R on the desk is after this estimate. Set your own broker's.")int gsPctMin = input.int(20, "Show percentages from N closed plans", minval = 5, maxval = 500, group = gsG4, tooltip = "Below this many closed plans the desk shows counts only: a percentage of a few plans says little.") // ── clock: New York, by the bar's openstring gsTz = "America/New_York"float gsTick = syminfo.mintickbool gsClosed = barstate.isconfirmedf_gsClock(string hm) =>
No coding needed.
gold-sweep-desk-strategy.pine
The same plans as orders for TradingView's Strategy Tester, with a commission that matches the desk's cost estimate.
The full source under MIT; nothing is locked.
The indicator reads and plans; the Strategy Lab sends the same plans to the Strategy Tester.
Whatever your chart's time zone; the closed hours are skipped, so every gold feed agrees.
Raids, sweeps, flips, plans and endings, in Text or JSON; alerts never place orders.
Pro draws both sides of gold's day before the window opens, so you see the level a sweep must reach and the close that would flip it.
No subscription / Full source / 7-day refund before download
Pro is a set of tools for reading and planning gold's day. It does not trade for you and it does not promise results.
Yes. Both scripts are free under MIT: the full source, no signup, nothing locked.
No tested edge. We tested sixteen rule variants, each registered before its result: none passed, and the one selected failed its check. The desk is a way to read gold's day, not a promise of results. What we measured has every row.
No. The indicator draws plans and says why; it places no orders. The optional Strategy Lab sends the same plans to TradingView's Strategy Tester.
Every clock on the desk is New York's, by the candle's open, whatever your chart shows. Gold's day runs 18:00 to 17:00, and the closed hours are skipped, so a spot chart and a 24-hour feed show the same setups.
Gold on 5 or 15 minutes with standard candles. On 30 minutes or more, or under 5, a TIMEFRAME line says why the desk was not built for them.
The game plan before each window, seven context checks, SMT with silver, half off at 1R, a day guard, the eight-market Radar and full-plan alerts. The free desk stays complete, free and MIT.
We, ProEA Lab, build Gold Sweep Desk. The films run its rules on simulated candles, not market data. Not financial advice.
Gold Sweep Desk Pro · $49 · one payment
See Gold Sweep Desk Pro →Pro is a set of tools for reading and planning gold's day. It does not trade for you and it does not promise results.
Read your chart, install it, and every rule one question at a time.
Beyond yesterday's high and low and the Asia and London ranges sit other traders' stops. A candle that trades through one of those pools in the London or New York window is a raid. It is a sweep only when a close comes back inside within three candles: the stops were taken and price did not stay there. Try it.
● SWEPT · Asia high · waiting for the flip The stops above were taken and price did not stay there.
After the sweep the desk takes the nearest opposing fair value gap born in the thirty candles before it. The flip is a close through that gap, at least one ATR from the sweep's extreme, within twelve candles. The entry is that close; the stop sits a tenth of an ATR beyond the extreme; the target is twice the risk, with the 1R line on the way.
The gap sits more than one ATR from the extreme, so a close through it is the flip, and the entry is that close.


Download the indicator (.pine)Download the Strategy Lab (.pine)Download the complete 1.1.0 package (.zip)MIT licence
//@version=6
// Gold Sweep Desk 1.1.0 · MIT · © 2026 ProEA Lab · built for r/pinescript
// Gold liquidity as a desk: the prior-day, Asia and London highs and lows, their sweeps, the flip and the plan, in words.
// Every clock is New York time; bars while spot gold is closed are skipped, so spot and 24/7 feeds agree.
// Decisions happen on closed bars. The record is this chart's history after an estimated cost, not a forecast.
// Rules: docs/engine-contract.md in the release · https://proea.app/en/free/gold-sweep-desk
// License: MIT. Share it freely; keep this header.
indicator("Gold Sweep Desk", "GoldSweep", overlay = true, behind_chart = false, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500, max_bars_back = 2000)
const string GS_VERSION = "1.1.0"
const bool GS_IS_STRATEGY = false
// ═══ ENGINE · core.pinefrag ═════════════════════════════════════════════════════════════════════════════════════
// The engine decides; the renderer (visuals.pinefrag) only reads. Every engine name starts with "gs" and a
// capital letter; tests/display.test.mjs refuses any renderer line that assigns one. The rules are written in
// docs/engine-contract.md; tests/reference/engine.mjs is the independent reference.
//
// ① SWEEP a bar in an entry window trades through a fresh pool (prior day, Asia or London high/low) and a close
// comes back inside it within a few bars.
// ② FLIP a close goes through the nearest opposing fair value gap: the gap inverts.
// ③ PLAN entry at that close (or at a later close back inside the gap); stop beyond the sweep's extreme; target
// in R; flat at the time exit. Stop first when a bar reaches both.
// Every clock is New York time by the bar's open. Bars while spot gold is closed (Friday 17:00 → Sunday 18:00 and
// the daily 17:00–18:00 break) are skipped entirely, so a 24/7 feed and a spot feed give the same bar stream.
// Decisions happen on closed bars only. A plan's geometry is frozen at entry.
string gsG1 = "01 · Pools and windows"
bool gsPoolPd = input.bool(true, "Prior day high / low", group = gsG1, tooltip = "The previous trading day's high and low. Gold's day runs 18:00 → 17:00 New York.")
bool gsPoolAsia = input.bool(true, "Asia high / low · 20:00–24:00", group = gsG1, tooltip = "The range of the bars opening 20:00–24:00 New York; a pool from the first bar after midnight.")
bool gsPoolLon = input.bool(true, "London high / low · 02:00–05:00", group = gsG1, tooltip = "The range of the bars opening 02:00–05:00 New York; a pool from the first bar after 05:00.")
string gsWindows = input.string("London + New York", "Entry windows", options = ["London + New York", "Continuous 01:00–11:00"], group = gsG1, tooltip = "A setup can only start on a bar that opens inside a window (New York time). The pools are used up by any bar that trades through them, window or not.")
bool gsWinLon = input.bool(true, " London 02:00–05:00", group = gsG1)
bool gsWinNy = input.bool(true, " New York 07:00–11:00", group = gsG1)
string gsG2 = "02 · Sweep and flip"
float gsDepth = input.float(0.05, "Sweep depth · ATR beyond the level", minval = 0, maxval = 5, step = 0.05, group = gsG2, tooltip = "The raid must reach at least this far beyond the outermost pool it takes, in ATR(14).")
int gsReclaim = input.int(3, "Close back inside within · bars", minval = 0, maxval = 20, group = gsG2, tooltip = "The close back inside the swept level may come on the raid bar or on this many bars after it. Otherwise price was accepted beyond the level (HELD).")
string gsFailOn = input.string("A close back beyond the level", "A sweep fails on", options = ["A close back beyond the level", "Any new extreme"], group = gsG2, tooltip = "While the flip is awaited: a close back beyond the swept level ends the setup, and a new wick only moves the extreme (and the stop) further; or any new extreme ends it.")
string gsConfirm = input.string("FVG flip", "Confirmation", options = ["FVG flip", "Close back only"], group = gsG2, tooltip = "FVG flip: a close through the nearest opposing fair value gap. Close back only: the sweep's own close is the trigger.")
int gsLookback = input.int(30, "Gap born within · bars before the sweep", minval = 3, maxval = 200, group = gsG2)
int gsFlipBars = input.int(12, "Flip within · bars after the sweep", minval = 0, maxval = 100, group = gsG2)
float gsMinLeg = input.float(1.0, "Leg from the extreme · at least ATR", minval = 0, maxval = 10, step = 0.1, group = gsG2, tooltip = "The flip close must be at least this far from the sweep's extreme.")
string gsG3 = "03 · Plan"
string gsEntryMode = input.string("Flip close", "Entry", options = ["Flip close", "Retest close"], group = gsG3, tooltip = "Flip close: the flip's close is the entry. Retest close: after the flip, the first close back inside the gap within the retest bars.")
int gsRetestBars = input.int(12, " Retest within · bars", minval = 1, maxval = 100, group = gsG3)
float gsBuffer = input.float(0.1, "Stop buffer · ATR beyond the extreme", minval = 0, maxval = 3, step = 0.05, group = gsG3)
float gsMaxStop = input.float(6.0, "Skip if the stop is wider than · ATR", minval = 0.5, maxval = 50, step = 0.5, group = gsG3)
float gsTargetR = input.float(2.0, "Target · R", minval = 0.5, maxval = 20, step = 0.5, group = gsG3, tooltip = "A design choice, not a tested setting. 1R is the distance from the entry to the stop; the 1R milestone is recorded on the way.")
string gsExitAt = input.string("16:45", "Flat at · HH:MM New York", group = gsG3, tooltip = "An open plan closes at the close of the bar that ends at or after this time; no entry on or after it. Gold's daily break starts at 17:00.")
string gsSide = input.string("Both", "Direction", options = ["Both", "Long only", "Short only"], group = gsG3)
string gsG4 = "04 · Costs and record"
float gsCost = input.float(0.40, "Estimated round-trip cost · price", minval = 0, step = 0.05, group = gsG4, tooltip = "Spread plus slippage for entry and exit together, in the chart's price (0.40 = 40 cents an ounce on spot gold). Every R on the desk is after this estimate. Set your own broker's.")
int gsPctMin = input.int(20, "Show percentages from N closed plans", minval = 5, maxval = 500, group = gsG4, tooltip = "Below this many closed plans the desk shows counts only: a percentage of a few plans says little.")
// ── clock: New York, by the bar's open
string gsTz = "America/New_York"
float gsTick = syminfo.mintick
bool gsClosed = barstate.isconfirmed
f_gsClock(string hm) =>
array<string> parts = str.split(hm, ":")
int out = -1
if array.size(parts) == 2
float h = str.tonumber(array.get(parts, 0))
float m = str.tonumber(array.get(parts, 1))
if not na(h) and not na(m)
out := int(h) * 60 + int(m)
out
// A trading day starts at 18:00: its id is the New York date six hours later.
f_gsDayId(int t) =>
int t6 = t + 6 * 3600000
year(t6, "America/New_York") * 10000 + month(t6, "America/New_York") * 100 + dayofmonth(t6, "America/New_York")
int gsExitMin = f_gsClock(gsExitAt) < 0 ? 16 * 60 + 45 : f_gsClock(gsExitAt)
int gsDow = dayofweek(time, gsTz)
int gsHm = hour(time, gsTz) * 60 + minute(time, gsTz)
int gsDayNow = f_gsDayId(time)
int gsCloseHm = hour(time_close, gsTz) * 60 + minute(time_close, gsTz)
int gsCloseDay = f_gsDayId(time_close)
// dayofweek: 1 = Sunday … 7 = Saturday.
bool gsGoldShut = gsDow == dayofweek.saturday or (gsDow == dayofweek.sunday and gsHm < 1080) or (gsDow == dayofweek.friday and gsHm >= 1020) or (gsHm >= 1020 and gsHm < 1080)
bool gsInAsia = gsHm >= 1200
bool gsInLon = gsHm >= 120 and gsHm < 300
string gsWinNow = gsWindows == "Continuous 01:00–11:00" ? (gsHm >= 60 and gsHm < 660 ? (gsHm < 420 ? "London" : "New York") : "") : (gsWinLon and gsHm >= 120 and gsHm < 300) ? "London" : (gsWinNy and gsHm >= 420 and gsHm < 660) ? "New York" : ""
bool gsAvailable = chart.is_standard and timeframe.isintraday and timeframe.in_seconds() <= 3600
string gsUnavailable = gsAvailable ? "" : not chart.is_standard ? "standard candles needed" : "an intraday chart of 1 hour or less is needed"
// ── state
type GSPool
string name
int side
float price
bool fresh = true
int bornBar = na
int takenBar = na
int day = 0
type GSGap
int dir
int born
int bornBar
float bottom
float top
bool killed = false
// The setup in progress: 0 idle · 1 raid · 2 swept · 3 flipped (waiting for the retest).
type GSSetup
int state = 0
int dir = 0
string window = ""
float ref = na
string pool = ""
string pools = ""
int poolsN = 0
float extreme = na
float depthAtr = na
int raidN = na
int raidBar = na
int sweepN = na
int sweepBar = na
float gapB = na
float gapT = na
int gapBar = na
float gapAtr = na
int flipN = na
int flipBar = na
float legAtr = na
float atrFlip = na
float stop = na
// The plan (dir 0 = none). Its geometry is frozen at entry; a copy goes to gsDone when it ends.
type GSPlan
int dir = 0
int day = 0
string window = ""
string pool = ""
int poolsN = 0
float depthAtr = na
float legAtr = na
float gapAtr = na
float riskAtr = na
int raidBar = na
int sweepBar = na
int flipBar = na
int gapBar = na
int entryN = na
int entryBar = na
int entryTime = na
string how = ""
float extreme = na
float gapB = na
float gapT = na
float entry = na
float stop = na
float risk = na
float tp1 = na
float target = na
bool tp1Hit = false
int tp1Bar = na
int exitBar = na
int exitTime = na
float exitPrice = na
string ending = ""
float grossR = na
float netR = na
type GSStat
int plans = 0
int wins = 0
float sumR = 0.0
int nTarget = 0
int nStop = 0
int nTime = 0
int nTp1 = 0
int raids = 0
int sweeps = 0
int flips = 0
int held = 0
int noGap = 0
int failed = 0
int noFlip = 0
int noEntry = 0
// What happened on this bar.
type GSEv
int raid = 0
int sweep = 0
int flip = 0
int entry = 0
bool exit = false
string why = ""
float r = na
int exitDir = 0
bool tp1 = false
string stopped = ""
string pool = ""
var GSSetup gsS = GSSetup.new()
var GSPlan gsP = GSPlan.new()
var GSStat gsSt = GSStat.new()
var array<GSPool> gsPools = array.new<GSPool>()
var array<GSPool> gsPoolLog = array.new<GSPool>()
var array<GSGap> gsGaps = array.new<GSGap>()
var array<GSPlan> gsDone = array.new<GSPlan>()
var array<int> gsEvBar = array.new<int>()
var array<string> gsEvWhat = array.new<string>()
var array<float> gsEvPx = array.new<float>()
GSEv gsEv = GSEv.new()
var float gsAtr = na
var float gsTrSum = 0.0
var int gsTrN = 0
var float gsPrevC = na
var int gsN = -1
var int gsDay = 0
var float gsDayH = na
var float gsDayL = na
var float gsAsiaH = na
var float gsAsiaL = na
var bool gsAsiaDone = false
var float gsLonH = na
var float gsLonL = na
var bool gsLonDone = false
var int gsRestN = -1
var float gsH1 = na
var float gsL1 = na
var int gsD1 = 0
var float gsH2 = na
var float gsL2 = na
var int gsD2 = 0
// The day's story for the desk, in words (a field, so the helpers below can extend it).
type GSText
string log = ""
var GSText gsTx = GSText.new()
f_gsRText(float v) =>
(v < 0 ? "−" : "+") + str.tostring(math.abs(v), "0.0") + "R"
f_gsHhmm() =>
str.tostring(hour(time, "America/New_York"), "00") + ":" + str.tostring(minute(time, "America/New_York"), "00")
f_gsLogAdd(string what) =>
gsTx.log := gsTx.log + (gsTx.log == "" ? "" : " · ") + f_gsHhmm() + what
f_gsNote(string what) =>
array.push(gsEvBar, bar_index)
array.push(gsEvWhat, what)
array.push(gsEvPx, close)
if array.size(gsEvBar) > 200
array.shift(gsEvBar)
array.shift(gsEvWhat)
array.shift(gsEvPx)
f_gsPoolNew(string name, int side, float price, int day) =>
GSPool p = GSPool.new(name, side, price, true, bar_index, na, day)
array.push(gsPools, p)
array.push(gsPoolLog, p)
if array.size(gsPoolLog) > 120
array.shift(gsPoolLog)
f_gsReset(string why) =>
// A setup ends without a plan: remembered for the desk and the alerts.
gsEv.stopped := why
gsS.state := 0
gsS.dir := 0
if why == "HELD"
gsSt.held += 1
else if why == "NO GAP"
gsSt.noGap += 1
else if why == "SWEEP FAILED"
gsSt.failed += 1
else if why == "NO FLIP" or why == "NO RETEST"
gsSt.noFlip += 1
else
gsSt.noEntry += 1
f_gsNote(why)
f_gsLogAdd(" " + str.lower(why))
// Entry at this close, or a refusal. Returns true when a plan starts.
f_gsEnter(float px, string how) =>
int d = gsS.dir
float risk = (px - gsS.stop) * d
bool ok = false
if not (risk > 0)
f_gsReset("NO ENTRY · stop not beyond entry")
else if risk > gsMaxStop * gsS.atrFlip
f_gsReset("NO ENTRY · stop too wide")
else if not (gsCloseDay == gsDayNow and gsCloseHm < gsExitMin)
f_gsReset("NO ENTRY · too late in the day")
else
ok := true
gsP.dir := d
gsP.day := gsDayNow
gsP.window := gsS.window
gsP.pool := gsS.pool
gsP.poolsN := gsS.poolsN
gsP.depthAtr := gsS.depthAtr
gsP.legAtr := gsS.legAtr
gsP.gapAtr := gsS.gapAtr
gsP.riskAtr := risk / gsS.atrFlip
gsP.raidBar := gsS.raidBar
gsP.sweepBar := gsS.sweepBar
gsP.flipBar := gsS.flipBar
gsP.gapBar := gsS.gapBar
gsP.entryN := gsN
gsP.entryBar := bar_index
gsP.entryTime := time
gsP.how := how
gsP.extreme := gsS.extreme
gsP.gapB := gsS.gapB
gsP.gapT := gsS.gapT
gsP.entry := px
gsP.stop := gsS.stop
gsP.risk := risk
gsP.tp1 := px + d * risk
gsP.target := px + d * gsTargetR * risk
gsP.tp1Hit := false
gsP.tp1Bar := na
gsP.exitBar := na
gsP.exitTime := na
gsP.exitPrice := na
gsP.ending := ""
gsP.grossR := na
gsP.netR := na
gsS.state := 4
gsEv.entry := d
f_gsNote(d == 1 ? "ENTRY LONG" : "ENTRY SHORT")
f_gsLogAdd((d == 1 ? " ▲ long " : " ▼ short ") + str.tostring(px, format.mintick))
ok
// The flip on this bar, if the close goes through the gap far enough from the extreme.
f_gsFlip() =>
int d = gsS.dir
bool through = d == -1 ? close < gsS.gapB : close > gsS.gapT
float leg = (gsS.extreme - close) * -d
bool done = false
if through and leg >= gsMinLeg * gsAtr
done := true
gsS.flipN := gsN
gsS.flipBar := bar_index
gsS.legAtr := leg / gsAtr
gsS.atrFlip := gsAtr
gsS.stop := d == -1 ? math.ceil((gsS.extreme + gsBuffer * gsAtr) / gsTick - 1e-9) * gsTick : math.floor((gsS.extreme - gsBuffer * gsAtr) / gsTick + 1e-9) * gsTick
gsEv.flip := d
gsSt.flips += 1
f_gsNote("FLIP")
f_gsLogAdd(" flip")
if gsEntryMode == "Flip close"
f_gsEnter(close, "flip close")
else
gsS.state := 3
done
// The sweep on this bar: pick the gap to invert (or trigger at once with "Close back only").
f_gsSweep() =>
gsS.sweepN := gsN
gsS.sweepBar := bar_index
gsEv.sweep := gsS.dir
gsEv.pool := gsS.pool
gsSt.sweeps += 1
f_gsNote("SWEEP " + gsS.pool)
f_gsLogAdd(" swept " + gsS.pool)
if gsConfirm == "Close back only"
gsS.legAtr := (gsS.extreme - close) * -gsS.dir / gsAtr
gsS.atrFlip := gsAtr
gsS.flipN := gsN
gsS.flipBar := bar_index
gsS.stop := gsS.dir == -1 ? math.ceil((gsS.extreme + gsBuffer * gsAtr) / gsTick - 1e-9) * gsTick : math.floor((gsS.extreme - gsBuffer * gsAtr) / gsTick + 1e-9) * gsTick
f_gsEnter(close, "reclaim close")
else
int want = -gsS.dir
GSGap best = na
for g in gsGaps
if g.dir == want and not g.killed and g.born >= gsN - gsLookback and g.born < gsN
if na(best)
best := g
else if want == 1 ? g.bottom > best.bottom : g.top < best.top
best := g
if na(best)
f_gsReset("NO GAP")
else
gsS.gapB := best.bottom
gsS.gapT := best.top
gsS.gapBar := best.bornBar
gsS.gapAtr := (best.top - best.bottom) / gsAtr
gsS.state := 2
f_gsFlip()
// The branches above return different types; the function returns the setup state.
gsS.state
f_gsEnd(float px, string why) =>
float gross = (px - gsP.entry) * gsP.dir / gsP.risk
float net = gross - gsCost / gsP.risk
gsP.exitBar := bar_index
gsP.exitTime := time
gsP.exitPrice := px
gsP.ending := why
gsP.grossR := gross
gsP.netR := net
if gsClosed and gsAvailable and not gsGoldShut
gsN += 1
// (A) ATR over session bars, this bar included.
float tr = na(gsPrevC) ? high - low : math.max(high - low, math.abs(high - gsPrevC), math.abs(low - gsPrevC))
if gsTrN < 14
gsTrSum += tr
gsTrN += 1
if gsTrN == 14
gsAtr := gsTrSum / 14
else
gsAtr := (tr + 13 * gsAtr) / 14
gsPrevC := close
// (B) A new trading day: yesterday's range becomes the prior-day pools; the session pools start again.
if gsDayNow != gsDay
for p in gsPools
if p.fresh
p.takenBar := bar_index - 1
array.clear(gsPools)
if gsDay != 0 and gsPoolPd and not na(gsDayH)
f_gsPoolNew("PDH", 1, gsDayH, gsDayNow)
f_gsPoolNew("PDL", -1, gsDayL, gsDayNow)
gsDay := gsDayNow
gsDayH := na
gsDayL := na
gsAsiaH := na
gsAsiaL := na
gsAsiaDone := false
gsLonH := na
gsLonL := na
gsLonDone := false
gsTx.log := ""
if not gsAsiaDone and not na(gsAsiaH) and not gsInAsia
gsAsiaDone := true
if gsPoolAsia
f_gsPoolNew("Asia high", 1, gsAsiaH, gsDayNow)
f_gsPoolNew("Asia low", -1, gsAsiaL, gsDayNow)
if not gsLonDone and not na(gsLonH) and not gsInLon
gsLonDone := true
if gsPoolLon
f_gsPoolNew("London high", 1, gsLonH, gsDayNow)
f_gsPoolNew("London low", -1, gsLonL, gsDayNow)
// (C) The open plan: a later day, stop first, target, then the time exit.
if gsP.dir != 0 and gsN > gsP.entryN
int d = gsP.dir
bool stopHit = d == 1 ? low <= gsP.stop : high >= gsP.stop
bool targetHit = d == 1 ? high >= gsP.target : low <= gsP.target
bool lateDay = gsDayNow != gsP.day
if lateDay
f_gsEnd(open, "TIME")
else if stopHit
f_gsEnd((d == 1 ? open <= gsP.stop : open >= gsP.stop) ? open : gsP.stop, "STOP")
else if targetHit
f_gsEnd(gsP.target, "TARGET")
else if gsCloseDay == gsP.day and gsCloseHm >= gsExitMin
f_gsEnd(close, "TIME")
if not gsP.tp1Hit and not stopHit and not lateDay and (d == 1 ? high >= gsP.tp1 : low <= gsP.tp1)
gsP.tp1Hit := true
gsP.tp1Bar := bar_index
gsEv.tp1 := true
if gsP.ending != ""
gsEv.exit := true
gsEv.why := gsP.ending
gsEv.r := gsP.netR
gsEv.exitDir := gsP.dir
gsSt.plans += 1
gsSt.wins += gsP.netR > 0 ? 1 : 0
gsSt.sumR += gsP.netR
gsSt.nTarget += gsP.ending == "TARGET" ? 1 : 0
gsSt.nStop += gsP.ending == "STOP" ? 1 : 0
gsSt.nTime += gsP.ending == "TIME" ? 1 : 0
gsSt.nTp1 += gsP.tp1Hit ? 1 : 0
f_gsNote(gsP.ending)
f_gsLogAdd(" " + str.lower(gsP.ending) + " " + f_gsRText(gsP.netR))
array.push(gsDone, gsP.copy())
if array.size(gsDone) > 300
array.shift(gsDone)
gsP.dir := 0
gsRestN := gsN
gsS.state := 0
gsS.dir := 0
// (D) Pools this bar trades through are used up, whatever else happens.
int penHN = 0
int penLN = 0
float penHMax = na
float penLMin = na
string penHName = ""
string penLName = ""
string penHAll = ""
string penLAll = ""
for p in gsPools
if p.fresh and p.side == 1 and high > p.price
p.fresh := false
p.takenBar := bar_index
penHN += 1
penHAll += (penHAll == "" ? "" : " + ") + p.name
if na(penHMax) or p.price > penHMax
penHMax := p.price
penHName := p.name
else if p.fresh and p.side == -1 and low < p.price
p.fresh := false
p.takenBar := bar_index
penLN += 1
penLAll += (penLAll == "" ? "" : " + ") + p.name
if na(penLMin) or p.price < penLMin
penLMin := p.price
penLName := p.name
// (E) The setup: RAID → SWEPT → (FLIPPED →) plan.
if gsP.dir == 0
if gsS.state == 1
int d = gsS.dir
gsS.extreme := d == -1 ? math.max(gsS.extreme, high) : math.min(gsS.extreme, low)
int moreN = d == -1 ? penHN : penLN
if moreN > 0
gsS.poolsN += moreN
gsS.pools += " + " + (d == -1 ? penHAll : penLAll)
float outer = d == -1 ? penHMax : penLMin
if d == -1 ? outer > gsS.ref : outer < gsS.ref
gsS.ref := outer
gsS.pool := d == -1 ? penHName : penLName
if d == -1 ? close < gsS.ref : close > gsS.ref
f_gsSweep()
else if gsN - gsS.raidN >= gsReclaim
f_gsReset("HELD")
else if gsS.state == 2
int d = gsS.dir
bool beyond = d == -1 ? high > gsS.extreme : low < gsS.extreme
bool onClose = gsFailOn == "A close back beyond the level"
if onClose and beyond
gsS.extreme := d == -1 ? high : low
if onClose ? (d == -1 ? close > gsS.ref : close < gsS.ref) : beyond
f_gsReset("SWEEP FAILED")
else if not f_gsFlip() and gsN - gsS.sweepN >= gsFlipBars
f_gsReset("NO FLIP")
else if gsS.state == 3
int d = gsS.dir
if d == -1 ? high >= gsS.stop : low <= gsS.stop
f_gsReset("NO ENTRY · stop traded before the retest")
else if close >= gsS.gapB and close <= gsS.gapT
f_gsEnter(close, "retest close")
else if gsN - gsS.flipN >= gsRetestBars
f_gsReset("NO RETEST")
else if gsS.state == 0 and gsN != gsRestN and not na(gsAtr) and gsTrN >= 14
bool up = penHN > 0 and gsSide != "Long only"
bool dn = penLN > 0 and gsSide != "Short only"
if gsWinNow != "" and up != dn
int d = up ? -1 : 1
float outer = up ? penHMax : penLMin
float depth = d == -1 ? high - outer : outer - low
if depth >= gsDepth * gsAtr
gsS := GSSetup.new(1, d, gsWinNow, outer, up ? penHName : penLName, up ? penHAll : penLAll, up ? penHN : penLN, d == -1 ? high : low, depth / gsAtr, gsN, bar_index)
gsEv.raid := d
gsEv.pool := gsS.pool
gsSt.raids += 1
f_gsNote("RAID " + gsS.pool)
f_gsLogAdd(" raid " + gsS.pool)
if d == -1 ? close < gsS.ref : close > gsS.ref
f_gsSweep()
// (F) Gaps: a close through a gap kills it; a gap completes on this bar (three bars of the same trading day).
for g in gsGaps
if not g.killed and g.born < gsN and (g.dir == 1 ? close < g.bottom : close > g.top)
g.killed := true
if gsN >= 2 and gsD2 == gsDayNow
if low > gsH2
array.push(gsGaps, GSGap.new(1, gsN, bar_index, gsH2, low, false))
if high < gsL2
array.push(gsGaps, GSGap.new(-1, gsN, bar_index, high, gsL2, false))
while array.size(gsGaps) > 0 and array.first(gsGaps).born < gsN - 2 * gsLookback
array.shift(gsGaps)
gsH2 := gsH1
gsL2 := gsL1
gsD2 := gsD1
gsH1 := high
gsL1 := low
gsD1 := gsDayNow
// (G) Today's range and the session ranges include this bar.
gsDayH := na(gsDayH) ? high : math.max(gsDayH, high)
gsDayL := na(gsDayL) ? low : math.min(gsDayL, low)
if gsInAsia and not gsAsiaDone
gsAsiaH := na(gsAsiaH) ? high : math.max(gsAsiaH, high)
gsAsiaL := na(gsAsiaL) ? low : math.min(gsAsiaL, low)
if gsInLon and not gsLonDone
gsLonH := na(gsLonH) ? high : math.max(gsLonH, high)
gsLonL := na(gsLonL) ? low : math.min(gsLonL, low)
// ═══ ALERTS · alerts.pinefrag (indicator only) ══════════════════════════════════════════════════════════════════
// Engine events are decided on closed bars, so every message goes out once per bar close. For the messages, create
// one TradingView alert on this indicator with "Any alert() function call"; the named conditions below also work
// one by one. JSON keys: src, version, symbol, tf, event, side, pool, entry, sl, tp1, target, reason, r, time; a
// missing number is null.
string gsG5 = "05 · Alerts"
string gsAlertFmt = input.string("Text", "Alert format", options = ["Text", "JSON"], group = gsG5, tooltip = "Create the alert with 'Any alert() function call'. Text sends one readable line; JSON sends one object per event for webhooks and bots.")
bool gsAlertSetup = input.bool(true, "Also alert the sweep and the flip (before an entry)", group = gsG5)
f_gsNum(float v, string fmt) =>
na(v) ? "null" : str.tostring(v, fmt)
f_gsPx(float v) =>
na(v) ? "—" : str.tostring(v, format.mintick)
f_gsMsg(string ev, int side, string pool, float entry, float sl, float tp1, float target, string reason, float r) =>
string sideTxt = side == 1 ? "LONG" : side == -1 ? "SHORT" : "NONE"
string js = "{\"src\":\"GoldSweepDesk\",\"version\":\"" + GS_VERSION + "\",\"symbol\":\"" + syminfo.ticker + "\",\"tf\":\"" + timeframe.period + "\",\"event\":\"" + ev + "\",\"side\":\"" + sideTxt + "\",\"pool\":\"" + pool + "\",\"entry\":" + f_gsNum(entry, format.mintick) + ",\"sl\":" + f_gsNum(sl, format.mintick) + ",\"tp1\":" + f_gsNum(tp1, format.mintick) + ",\"target\":" + f_gsNum(target, format.mintick) + ",\"reason\":\"" + reason + "\",\"r\":" + f_gsNum(r, "0.00") + ",\"time\":" + str.tostring(time_close) + "}"
string tx = "Gold Sweep Desk · " + syminfo.ticker + " " + timeframe.period + " · " + ev + (side != 0 and ev != sideTxt ? " " + sideTxt : "") + (pool != "" ? " · " + pool : "") + (na(entry) ? "" : " · entry " + f_gsPx(entry) + " · stop " + f_gsPx(sl) + " · 1R " + f_gsPx(tp1) + " · target " + f_gsPx(target)) + (reason != "" ? " · " + reason : "") + (na(r) ? "" : " · " + (r < 0 ? "−" : "+") + str.tostring(math.abs(r), "0.00") + "R after the estimated cost")
gsAlertFmt == "JSON" ? js : tx
// In the order they happen on a bar: the 1R milestone, an exit, then a new setup, a sweep, a flip and an entry.
if gsClosed
if gsEv.tp1 and gsP.dir != 0
alert(f_gsMsg("ONE_R", gsP.dir, gsP.pool, gsP.entry, gsP.stop, gsP.tp1, gsP.target, "1R reached", na), alert.freq_once_per_bar_close)
if gsEv.exit and array.size(gsDone) > 0
GSPlan x = array.last(gsDone)
alert(f_gsMsg("EXIT", x.dir, x.pool, x.entry, x.stop, x.tp1, x.target, x.ending, x.netR), alert.freq_once_per_bar_close)
if gsAlertSetup and gsEv.sweep != 0
alert(f_gsMsg("SWEEP", gsEv.sweep, gsEv.pool, na, na, na, na, "swept, waiting for the flip", na), alert.freq_once_per_bar_close)
if gsAlertSetup and gsEv.flip != 0 and gsEv.entry == 0
alert(f_gsMsg("FLIP", gsEv.flip, gsS.pool, na, gsS.stop, na, na, gsEntryMode == "Retest close" ? "waiting for the retest close" : gsEv.stopped, na), alert.freq_once_per_bar_close)
if gsEv.entry != 0
alert(f_gsMsg(gsEv.entry == 1 ? "LONG" : "SHORT", gsP.dir, gsP.pool, gsP.entry, gsP.stop, gsP.tp1, gsP.target, gsP.how, na), alert.freq_once_per_bar_close)
alertcondition(gsEv.sweep != 0, "Sweep", "Gold Sweep Desk: a pool was swept; waiting for the flip")
alertcondition(gsEv.entry == 1, "Long", "Gold Sweep Desk: long, a swept low flipped")
alertcondition(gsEv.entry == -1, "Short", "Gold Sweep Desk: short, a swept high flipped")
alertcondition(gsEv.tp1, "1R reached", "Gold Sweep Desk: the plan reached 1R")
alertcondition(gsEv.exit and gsEv.why == "TARGET", "Target", "Gold Sweep Desk: the target was reached")
alertcondition(gsEv.exit and gsEv.why == "STOP", "Stopped", "Gold Sweep Desk: the stop ended the plan")
alertcondition(gsEv.exit and gsEv.why == "TIME", "Flat at the time exit", "Gold Sweep Desk: the time exit closed the plan")
// ═══ DISPLAY · visuals.pinefrag ═════════════════════════════════════════════════════════════════════════════════
// Reads engine state and draws it. It never assigns an engine variable, requests data, trades or alerts
// (tests/display.test.mjs enforces this), so a display change cannot change a plan or a result.
// Desk standard: state line · reason · ribbon · steps (✓ / ✗ / —, ● required) · plan in price and R · today ·
// record (counts first, % only from the threshold) · footer. Co-Pilot: NOW · WHY · PLAN · LIMITS.
// Layers: the desk and the live drawings follow every tick; the history is redrawn once per closed bar.
string gsvG = "06 · View"
string gsvTheme = input.string("Standard", "Theme", options = ["Standard", "Colorblind-safe"], group = gsvG)
string gsvDeskPos = input.string("Top right", "Desk position", options = ["Top right", "Top left", "Bottom right", "Bottom left", "Off"], group = gsvG)
string gsvRows = input.string("Compact", "Desk rows", options = ["Compact", "Full"], group = gsvG, tooltip = "Full adds every pool of the day, the last plan and the average result.")
string gsvPilot = input.string("Brief", "Co-Pilot", options = ["Standard", "Brief", "Off"], group = gsvG, tooltip = "The desk in plain words. Brief shows NOW and PLAN; Standard adds WHY and LIMITS.")
string gsvPilotPos = input.string("Bottom left", "Co-Pilot position", options = ["Bottom left", "Bottom right", "Top left", "Top right"], group = gsvG)
string gsvTextSize = input.string("Normal", "Text size", options = ["Small", "Normal", "Large"], group = gsvG)
int gsvDays = input.int(2, "Days drawn", minval = 1, maxval = 30, group = gsvG, tooltip = "Pools, sessions and plans of this many trading days (today included), within the last 1,500 bars.")
bool gsvPools = input.bool(true, "Liquidity pools", group = gsvG)
bool gsvSessions = input.bool(true, "Asia and London boxes", group = gsvG)
bool gsvZones = input.bool(true, "Risk / reward zones", group = gsvG)
bool gsvMisses = input.bool(true, "Mark setups that ended without a plan (◌)", group = gsvG)
bool gsvTint = input.bool(false, "Tint candles while a plan is open", group = gsvG)
// Colour carries meaning: teal = long / target, coral = short / stop, gold = liquidity and a setup in progress.
bool gsvCb = gsvTheme == "Colorblind-safe"
color gsvUp = gsvCb ? #56B4E9 : #26A69A
color gsvDown = gsvCb ? #E69F00 : #EF5350
color gsvGold = gsvCb ? #F0E442 : #F2B33D
color gsvAsia = #7E8CE0
color gsvInk = #E6E9F0
color gsvMuted = #8A93A6
color gsvGrey = #6B7280
color gsvPanel = color.new(#10151F, 6)
string gsvSz = gsvTextSize == "Small" ? size.tiny : gsvTextSize == "Large" ? size.normal : size.small
string gsvSzState = gsvTextSize == "Small" ? size.small : gsvTextSize == "Large" ? size.large : size.normal
f_gsvCorner(string p) =>
p == "Top left" ? position.top_left : p == "Bottom right" ? position.bottom_right : p == "Bottom left" ? position.bottom_left : position.top_right
f_gsvPx(float v) =>
na(v) ? "—" : str.tostring(v, format.mintick)
f_gsvR(float v) =>
na(v) ? "—" : math.abs(v) < 0.05 ? "0.0R" : (v < 0 ? "−" : "+") + str.tostring(math.abs(v), "0.0") + "R"
f_gsvHm(int mins) =>
str.tostring(int(mins / 60) % 24, "00") + ":" + str.tostring(mins % 60, "00")
f_gsvAt(int t) =>
str.tostring(hour(t, "America/New_York"), "00") + ":" + str.tostring(minute(t, "America/New_York"), "00")
f_gsvLeft(int mins) =>
mins <= 0 ? "closing" : (mins >= 60 ? str.tostring(int(mins / 60)) + " h " : "") + str.tostring(mins % 60) + " m left"
f_gsvMark(int s) =>
s == 1 ? "✓ met" : s == 0 ? "✗ not met" : s == -1 ? "— no data" : s == -3 ? "— waiting" : "— off"
f_gsvTone(int s) =>
s == 1 ? gsvUp : s == 0 ? gsvDown : gsvMuted
f_gsvEnding(string why) =>
why == "TARGET" ? "TARGET" : why == "STOP" ? "STOP" : why == "TIME" ? "FLAT AT THE TIME EXIT" : why
f_gsvEndMark(string why, float r) =>
why == "TARGET" ? "✓" : why == "STOP" ? "✗" : r > 0.05 ? "✓" : r < -0.05 ? "✗" : "◌"
f_gsvWrap(string txt, int columns) =>
array<string> words = str.split(txt, " ")
string output = ""
int width = 0
for word in words
if width > 0 and width + str.length(word) + 1 > columns
output += "\n"
width := 0
output += (width > 0 ? " " : "") + word
width += str.length(word) + 1
output
f_gsvSide(int d) =>
d == 1 ? "long" : "short"
// ── windows, in New York minutes
bool gsvCont = gsWindows == "Continuous 01:00–11:00"
int gsvWinEnd = gsvCont ? 660 : gsWinNow == "London" ? 300 : 660
string gsvWinTxt = gsvCont ? "01:00–11:00" : gsWinNow == "London" ? "London 02:00–05:00" : "New York 07:00–11:00"
// The next window start after this minute of the day: later today, tomorrow, or Monday (no windows at the weekend).
f_gsvNextWin(int hm) =>
int dow = dayofweek(time, "America/New_York")
bool later = gsvCont ? hm >= 60 : not ((gsWinLon and hm < 120) or (gsWinNy and hm < 420))
string day = dow == dayofweek.saturday or dow == dayofweek.sunday or (dow == dayofweek.friday and later) ? "Monday " : later ? "tomorrow " : ""
string win = gsvCont ? "01:00" : (not later and gsWinLon and hm < 120) or (later and gsWinLon) ? "London 02:00" : gsWinNy ? "New York 07:00" : ""
win == "" ? "none (both windows off)" : day + win
// ── what the desk shows (display variables; the engine's objects are only read)
string gsvState = ""
string gsvReason = ""
string gsvNow = ""
string gsvPlan = ""
color gsvTone = gsvMuted
string gsvRib = ""
int gsvDir = 0
string gsvNearUp = ""
string gsvNearDn = ""
int gsvFresh = 0
int gsvPoolsToday = 0
if barstate.islastconfirmedhistory or barstate.isrealtime
// Nearest fresh pools above and below the close.
float upPx = na
float dnPx = na
for p in gsPools
gsvPoolsToday += 1
if p.fresh
gsvFresh += 1
if p.price >= close and (na(upPx) or p.price < upPx)
upPx := p.price
gsvNearUp := p.name + " " + f_gsvPx(p.price) + " (+" + str.tostring(p.price - close, format.mintick) + ")"
if p.price < close and (na(dnPx) or p.price > dnPx)
dnPx := p.price
gsvNearDn := p.name + " " + f_gsvPx(p.price) + " (−" + str.tostring(close - p.price, format.mintick) + ")"
gsvDir := gsP.dir != 0 ? gsP.dir : gsS.state != 0 ? gsS.dir : 0
string r1 = gsvFresh > 0 ? "✓" : "·"
string r2 = "·"
string r3 = "·"
string r4 = "·"
string r5 = "·"
if not gsAvailable
gsvState := "— UNAVAILABLE · " + timeframe.period + " chart"
gsvReason := gsUnavailable == "standard candles needed" ? "Heikin Ashi, Renko and other synthetic candles move the closes. Use standard candles." : "Use an intraday chart of 1 hour or less (5 or 15 minutes on gold)."
gsvNow := "This chart cannot run the rule. " + gsvReason
else if gsTrN < 14
gsvState := "○ WARMING UP"
gsvReason := "The ATR needs 14 session bars."
gsvNow := gsvReason
else if gsP.dir != 0
float openR = (close - gsP.entry) * gsP.dir / gsP.risk
gsvState := "● IN TRADE · " + (gsP.dir == 1 ? "LONG" : "SHORT") + " · " + f_gsvR(openR) + (gsP.tp1Hit ? " · 1R reached" : "")
gsvTone := gsP.dir == 1 ? gsvUp : gsvDown
gsvReason := "Stop " + f_gsvPx(gsP.stop) + " · 1R " + f_gsvPx(gsP.tp1) + (gsP.tp1Hit ? " ✓" : "") + " · target " + f_gsvPx(gsP.target) + " · flat at " + gsExitAt
gsvNow := "In a " + f_gsvSide(gsP.dir) + " from " + f_gsvPx(gsP.entry) + " after the " + gsP.pool + " sweep (" + gsP.how + "), " + str.tostring(bar_index - gsP.entryBar) + " bars ago, " + f_gsvR(openR) + " at this close before costs."
gsvPlan := "Stop first on every closed bar: the stop " + f_gsvPx(gsP.stop) + " is beyond the sweep's extreme " + f_gsvPx(gsP.extreme) + ", where the idea is wrong. The target " + f_gsvPx(gsP.target) + " ends it; otherwise it is flat at " + gsExitAt + " New York."
r2 := "✓"
r3 := "✓"
r4 := "✓"
r5 := "▶"
else if gsGoldShut
gsvState := "◌ GOLD IS CLOSED"
gsvReason := "Spot gold trades Sunday 18:00 → Friday 17:00 New York, with a break 17:00–18:00. These bars are skipped."
gsvNow := gsvReason
else if gsS.state == 1
int left = math.max(0, gsReclaim - (gsN - gsS.raidN))
gsvState := "● RAID · " + gsS.pool + " · " + (gsS.dir == -1 ? "above" : "below")
gsvTone := gsvGold
gsvReason := "A close back " + (gsS.dir == -1 ? "below " : "above ") + f_gsvPx(gsS.ref) + " makes it a sweep (" + str.tostring(left) + (left == 1 ? " bar" : " bars") + " left); otherwise price was accepted beyond the level."
gsvNow := "Price is raiding the " + gsS.pool + " (" + f_gsvPx(gsS.ref) + ") in the " + gsS.window + " window, extreme " + f_gsvPx(gsS.extreme) + ". " + gsvReason
r2 := "▶"
else if gsS.state == 2
int left = math.max(0, gsFlipBars - (gsN - gsS.sweepN))
float lvl = gsS.dir == -1 ? math.min(gsS.gapB, gsS.extreme - gsMinLeg * gsAtr) : math.max(gsS.gapT, gsS.extreme + gsMinLeg * gsAtr)
gsvState := "● SWEPT · " + gsS.pool + " · waiting for the flip"
gsvTone := gsvGold
gsvReason := "A close " + (gsS.dir == -1 ? "below " : "above ") + f_gsvPx(lvl) + " flips the gap " + f_gsvPx(gsS.gapB) + "–" + f_gsvPx(gsS.gapT) + " (" + str.tostring(left) + (left == 1 ? " bar" : " bars") + " left). " + (gsFailOn == "Any new extreme" ? "A new extreme ends it." : "A close back " + (gsS.dir == -1 ? "above " : "below ") + f_gsvPx(gsS.ref) + " ends it.")
gsvNow := "The " + gsS.pool + " was swept (extreme " + f_gsvPx(gsS.extreme) + "). The desk now waits for a close through the nearest opposing gap, the flip that confirms the reversal."
r2 := "✓"
r3 := "▶"
else if gsS.state == 3
int left = math.max(0, gsRetestBars - (gsN - gsS.flipN))
gsvState := "● FLIPPED · waiting for the retest"
gsvTone := gsvGold
gsvReason := "A close back inside " + f_gsvPx(gsS.gapB) + "–" + f_gsvPx(gsS.gapT) + " enters " + f_gsvSide(gsS.dir) + " (" + str.tostring(left) + (left == 1 ? " bar" : " bars") + " left); a touch of the stop " + f_gsvPx(gsS.stop) + " cancels."
gsvNow := "The gap flipped after the " + gsS.pool + " sweep. Retest mode waits for a close back inside the flipped gap."
r2 := "✓"
r3 := "✓"
r4 := "▶"
else if gsWinNow != ""
int left = gsvWinEnd - gsHm
gsvState := "○ WATCHING · " + gsWinNow + " window · " + str.tostring(gsvFresh) + (gsvFresh == 1 ? " fresh pool" : " fresh pools")
gsvTone := gsvGold
gsvReason := gsvFresh == 0 ? "Every pool of today is used up. The next ones: Asia and London ranges, then tomorrow's prior-day levels." : "A raid of a fresh pool, then a close back inside it, starts a setup (" + f_gsvLeft(left) + ")."
gsvNow := "Watching the " + gsWinNow + " window. " + (gsvNearUp != "" ? "Above: " + gsvNearUp + ". " : "") + (gsvNearDn != "" ? "Below: " + gsvNearDn + ". " : "") + (gsvFresh == 0 ? "No fresh pool is left today." : "")
else
gsvState := "◌ OUTSIDE THE WINDOWS · next " + f_gsvNextWin(gsHm)
gsvReason := "Setups start only in " + (gsvCont ? "the 01:00–11:00 window" : (gsWinLon ? "London 02:00–05:00" : "") + (gsWinLon and gsWinNy ? " and " : "") + (gsWinNy ? "New York 07:00–11:00" : "")) + " (New York time). Pools still get used up now."
gsvNow := "Outside the entry windows. " + (gsvNearUp != "" ? "Above: " + gsvNearUp + ". " : "") + (gsvNearDn != "" ? "Below: " + gsvNearDn + "." : "")
gsvRib := "01 POOLS " + r1 + " 02 SWEEP " + r2 + " 03 FLIP " + r3 + " 04 PLAN " + r4 + " 05 RESULT " + r5
if gsvPlan == ""
gsvPlan := "Rule: sweep of the prior day, Asia or London high/low in " + (gsvCont ? "01:00–11:00" : "the London or New York window") + " → a close through the nearest opposing gap" + (gsEntryMode == "Retest close" ? " → a close back inside it" : "") + " → entry at that close, stop beyond the sweep's extreme, target " + str.tostring(gsTargetR, "0.0#") + "R, flat at " + gsExitAt + "."
// ── desk and Co-Pilot
var table gsvDesk = table.new(f_gsvCorner(gsvDeskPos), 2, 40, frame_width = 0, border_width = 0)
string gsvPilotCorner = gsvPilotPos != gsvDeskPos ? gsvPilotPos : gsvDeskPos == "Bottom left" ? "Bottom right" : "Bottom left"
var table gsvPilotT = table.new(f_gsvCorner(gsvPilotCorner), 1, 7, frame_width = 0, border_width = 0)
int gsvPOff = gsvPilotCorner == "Top left" ? 1 : 0
int gsvOff = gsvDeskPos == "Top left" ? 1 : 0
f_gsvRow(int r, string a, string b, color ca, color cb) =>
table.cell(gsvDesk, 0, r + gsvOff, a, text_color = ca, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
table.cell(gsvDesk, 1, r + gsvOff, b, text_color = cb, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
f_gsvWide(int r, string txt, color c, bool bold, string tip) =>
table.cell(gsvDesk, 0, r + gsvOff, txt, text_color = c, bgcolor = gsvPanel, text_size = bold ? gsvSzState : gsvSz, text_halign = text.align_left, text_formatting = bold ? text.format_bold : text.format_none, tooltip = tip)
table.merge_cells(gsvDesk, 0, r + gsvOff, 1, r + gsvOff)
// The rule is built for 5–15 minute candles: from 30 minutes a candle is a large part of a window and the 2R target has
// little room before 16:45; TradingView loads only a few days of 1–4 minute candles, too few for this chart's record.
int gsvTfSec = timeframe.in_seconds()
string gsvTfMins = str.tostring(gsvTfSec / 60, "#")
string gsvTfNote = gsvTfSec >= 1800 ? "TIMEFRAME · built for 5–15 minutes: on " + gsvTfMins + "-minute candles a plan has little room for its target before 16:45" : gsvTfSec < 300 ? "TIMEFRAME · built for 5–15 minutes: TradingView loads only a few days of " + gsvTfMins + "-minute candles, too few for a record" : ""
// One step row: the mark, a detail, and "● required" (every step of the sequence is required).
f_gsvStep(int r, string name, int s, string detail) =>
f_gsvRow(r, name, f_gsvMark(s) + (detail == "" ? "" : " · " + detail) + " ● required", gsvInk, f_gsvTone(s))
f_gsvDrawDesk() =>
table.clear(gsvDesk, 0, 0, 1, 39)
if gsvDeskPos != "Off"
if gsvOff == 1
table.cell(gsvDesk, 0, 0, "", height = 14, bgcolor = na)
table.cell(gsvDesk, 1, 0, "", height = 14, bgcolor = na)
bool full = gsvRows == "Full"
bool inPlan = gsP.dir != 0
int st = gsS.state
// Step states: 1 met · 0 not met · -1 no data · -3 waiting.
int sWin = inPlan or st > 0 ? 1 : gsWinNow != "" ? 1 : 0
int sPool = inPlan or st > 0 ? 1 : gsvFresh > 0 ? 1 : 0
int sSweep = inPlan or st >= 2 ? 1 : st == 1 ? -3 : -3
int sGap = inPlan or st >= 2 ? 1 : -3
int sFlip = inPlan or st == 3 ? 1 : -3
int met = (sWin == 1 ? 1 : 0) + (sPool == 1 ? 1 : 0) + (sSweep == 1 ? 1 : 0) + (sGap == 1 ? 1 : 0) + (sFlip == 1 ? 1 : 0)
f_gsvWide(0, "GOLD SWEEP DESK · " + syminfo.ticker + " · " + timeframe.period + (GS_IS_STRATEGY ? " · STRATEGY LAB" : ""), gsvMuted, false, "Gold Sweep Desk " + GS_VERSION + " · a paper reference decided on closed bars, New York time." + (GS_IS_STRATEGY ? " The Strategy Tester shows TradingView's fills of the same plans." : "") + " Fills at your broker can differ.")
f_gsvWide(1, gsvState, gsvTone, true, "Pools → raid → sweep → flip → plan → result.")
f_gsvWide(2, f_gsvWrap(gsvReason, 58), gsvInk, false, "")
f_gsvWide(3, gsvRib, gsvMuted, false, "01 fresh pools · 02 a raid and a close back inside · 03 the gap flips · 04 the plan · 05 its result")
f_gsvWide(4, "STEPS" + (gsvDir != 0 ? " · for a " + f_gsvSide(gsvDir) : "") + " · " + str.tostring(met) + " of 5 required met", gsvMuted, false, "")
f_gsvStep(5, "Window", sWin, inPlan or st > 0 ? (inPlan ? gsP.window : gsS.window) : gsWinNow != "" ? gsvWinTxt + " · " + f_gsvLeft(gsvWinEnd - gsHm) : "closed · next " + f_gsvNextWin(gsHm))
f_gsvStep(6, "Fresh pool", sPool, inPlan ? gsP.pool : st > 0 ? gsS.pool : str.tostring(gsvFresh) + " of " + str.tostring(gsvPoolsToday) + " today")
f_gsvStep(7, "Sweep · close back inside", sSweep, inPlan ? gsP.pool + " · extreme " + f_gsvPx(gsP.extreme) : st >= 2 ? gsS.pool + " · extreme " + f_gsvPx(gsS.extreme) : st == 1 ? "raid under way" : "")
f_gsvStep(8, "Opposing gap", sGap, inPlan ? f_gsvPx(gsP.gapB) + "–" + f_gsvPx(gsP.gapT) : st >= 2 ? f_gsvPx(gsS.gapB) + "–" + f_gsvPx(gsS.gapT) : "")
f_gsvStep(9, "Flip close ≥ " + str.tostring(gsMinLeg, "0.0#") + " ATR from the extreme", sFlip, inPlan ? f_gsvAt(gsP.entryTime) : st == 3 ? "done" : "")
int r = 10
if gsvNearUp != "" or gsvNearDn != ""
f_gsvRow(r, "Nearest fresh", (gsvNearUp != "" ? "▲ " + gsvNearUp : "") + (gsvNearUp != "" and gsvNearDn != "" ? "\n" : "") + (gsvNearDn != "" ? "▼ " + gsvNearDn : ""), gsvMuted, gsvGold)
r += 1
if full
for p in gsPools
if r < 24
f_gsvRow(r, " " + p.name, f_gsvPx(p.price) + (p.fresh ? " · fresh" : " · taken"), gsvMuted, p.fresh ? gsvGold : gsvGrey)
r += 1
if inPlan
f_gsvWide(r, "PLAN · " + f_gsvSide(gsP.dir) + " · " + gsP.how + " · frozen at entry", gsvMuted, false, "Frozen at entry.")
f_gsvRow(r + 1, "Entry", f_gsvPx(gsP.entry) + " · " + str.tostring(bar_index - gsP.entryBar) + " bars ago", gsvMuted, gsvGold)
f_gsvRow(r + 2, "Stop · 1R", f_gsvPx(gsP.stop) + " · " + str.tostring(math.abs(close - gsP.stop), format.mintick) + " away", gsvMuted, gsvDown)
f_gsvRow(r + 3, "1R", f_gsvPx(gsP.tp1) + (gsP.tp1Hit ? " ✓" : " · " + str.tostring(math.abs(gsP.tp1 - close), format.mintick) + " away"), gsvMuted, gsvUp)
f_gsvRow(r + 4, "Target · " + str.tostring(gsTargetR, "0.0#") + "R", f_gsvPx(gsP.target) + " · " + str.tostring(math.abs(gsP.target - close), format.mintick) + " away", gsvMuted, gsvUp)
r += 5
else
f_gsvWide(r, f_gsvWrap("PLAN · " + gsvPlan, 58), gsvMuted, false, "")
r += 1
if gsTx.log != ""
f_gsvWide(r, f_gsvWrap("TODAY " + gsTx.log, 58), gsvInk, false, "New York times.")
r += 1
string counts = "THIS CHART " + str.tostring(gsSt.raids) + " raids · " + str.tostring(gsSt.sweeps) + " sweeps · " + str.tostring(gsSt.flips) + " flips · " + (gsSt.plans == 0 ? "no closed plan yet" : str.tostring(gsSt.plans) + " plans: target " + str.tostring(gsSt.nTarget) + " · stop " + str.tostring(gsSt.nStop) + " · time " + str.tostring(gsSt.nTime) + " · reached 1R " + str.tostring(gsSt.nTp1))
f_gsvWide(r, f_gsvWrap(counts, 58), gsvInk, false, "Counts of this chart's history. History on this chart, not a forecast.")
r += 1
if full
string avgTxt = gsSt.plans == 0 ? "Average: needs a closed plan" : "Average " + f_gsvR(gsSt.sumR / gsSt.plans) + " over " + str.tostring(gsSt.plans) + " plans after an estimated " + str.tostring(gsCost, "0.00") + " round trip" + (gsSt.plans >= gsPctMin ? " · " + str.tostring(math.round(100.0 * gsSt.wins / gsSt.plans)) + "% closed above 0R" : "")
f_gsvWide(r, f_gsvWrap(avgTxt, 58), gsvMuted, false, "Net R after the estimated cost in 04. A percentage appears only from " + str.tostring(gsPctMin) + " closed plans.")
string ended = "Ended before a plan: held " + str.tostring(gsSt.held) + " · no gap " + str.tostring(gsSt.noGap) + " · sweep failed " + str.tostring(gsSt.failed) + " · no flip " + str.tostring(gsSt.noFlip) + " · refused " + str.tostring(gsSt.noEntry)
f_gsvWide(r + 1, f_gsvWrap(ended, 58), gsvMuted, false, "")
r += 2
if gsvTfNote != ""
f_gsvWide(r, f_gsvWrap(gsvTfNote, 58), gsvGold, false, "The rule's windows and its 16:45 exit are set in minutes of the New York day.")
r += 1
f_gsvWide(r, "Rules, not odds · history, not a forecast · ProEA Lab", gsvMuted, false, "")
f_gsvDrawPilot() =>
table.clear(gsvPilotT, 0, 0, 0, 6)
if gsvPilot != "Off"
string why = "Above an old high and below an old low sit other traders' stops. A raid through such a level that closes back inside took that liquidity without accepting the new price. The flip, a close through the nearest opposing gap, is the sign that the move has turned; the sweep's extreme is where the idea is wrong."
string limits = "A rule, not a forecast. The pools are price levels, not an order book. Results are this chart's history after an estimated " + str.tostring(gsCost, "0.00") + " round trip; fills at your broker can differ."
int o = gsvPOff
if o == 1
table.cell(gsvPilotT, 0, 0, "", height = 14, bgcolor = na)
table.cell(gsvPilotT, 0, 0 + o, "CO-PILOT", text_color = gsvMuted, bgcolor = gsvPanel, text_size = size.tiny, text_halign = text.align_left)
table.cell(gsvPilotT, 0, 1 + o, f_gsvWrap("NOW · " + gsvNow, 60), text_color = gsvInk, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
if gsvPilot == "Standard"
table.cell(gsvPilotT, 0, 2 + o, f_gsvWrap("WHY · " + why, 60), text_color = gsvInk, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
table.cell(gsvPilotT, 0, 3 + o, f_gsvWrap("PLAN · " + gsvPlan, 60), text_color = gsvInk, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
if gsvPilot == "Standard"
table.cell(gsvPilotT, 0, 4 + o, f_gsvWrap("LIMITS · " + limits, 60), text_color = gsvMuted, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
if gsvPilotCorner == "Bottom left" or gsvPilotCorner == "Bottom right"
table.cell(gsvPilotT, 0, 6, "", height = 5, bgcolor = na)
// ── chart layers: bounded handles, never further back than the history buffer (max_bars_back = 2000)
var array<box> gsvHB = array.new<box>()
var array<line> gsvHL = array.new<line>()
var array<label> gsvHT = array.new<label>()
var array<box> gsvLB = array.new<box>()
var array<line> gsvLL = array.new<line>()
var array<label> gsvLT = array.new<label>()
f_gsvClear(array<box> bs, array<line> ls, array<label> ts) =>
for b in bs
box.delete(b)
for l in ls
line.delete(l)
for x in ts
label.delete(x)
array.clear(bs)
array.clear(ls)
array.clear(ts)
int gsvTfMin = math.max(1, int(timeframe.in_seconds() / 60))
// Short names on the chart; the desk and the tooltips use the full ones.
f_gsvShort(string name) =>
name == "Asia high" ? "ASIA H" : name == "Asia low" ? "ASIA L" : name == "London high" ? "LDN H" : name == "London low" ? "LDN L" : name
// A pool's tag sits where its line ends: the candle that took it, or just right of the last candle while fresh.
f_gsvTagX(GSPool p) =>
na(p.takenBar) ? bar_index + 3 : math.max(p.takenBar, math.max(p.bornBar, bar_index - 1500))
// A tag is about eight candles wide: tags within 12 candles and 1 ATR of each other would print over each other,
// so the upper one moves above its line and the lower one below it (a middle one of three stays on its line).
f_gsvPlace(int pi, int oldestDay, array<int> tagDays) =>
GSPool p = array.get(gsPoolLog, pi)
int x = f_gsvTagX(p)
float near = nz(gsAtr, 0)
bool above = false
bool below = false
for [qi, q] in gsPoolLog
if qi != pi and q.day >= oldestDay and q.bornBar >= bar_index - 1500 and array.includes(tagDays, q.day) and math.abs(f_gsvTagX(q) - x) <= 12 and math.abs(q.price - p.price) <= near
if q.price > p.price or (q.price == p.price and qi > pi)
above := true
else
below := true
below and not above ? label.style_label_lower_left : above and not below ? label.style_label_upper_left : label.style_label_left
f_gsvDrawPool(GSPool p, bool today, string place) =>
int x1 = math.max(p.bornBar, bar_index - 1500)
int x2 = f_gsvTagX(p)
bool live = today and p.fresh
color c = live ? gsvGold : gsvGrey
array.push(gsvHL, line.new(x1, p.price, x2, p.price, color = color.new(c, live ? 10 : today ? 35 : 60), style = live ? line.style_solid : line.style_dotted, width = live ? 2 : 1))
if today
array.push(gsvHT, label.new(x2, p.price, f_gsvShort(p.name) + (p.fresh ? " " + f_gsvPx(p.price) : " ✕"), style = place, color = color.new(#10151F, 20), textcolor = color.new(c, p.fresh ? 0 : 20), size = size.small, tooltip = p.name + " " + f_gsvPx(p.price) + (p.fresh ? " · fresh: no bar has traded through it yet" : " · taken")))
f_gsvDrawSession(GSPool hi, GSPool lo, int mins, color c, string name) =>
int x2 = hi.bornBar - 1
int x1 = math.max(x2 - int(mins / gsvTfMin) + 1, bar_index - 1500)
if x2 > x1
array.push(gsvHB, box.new(x1, hi.price, x2, lo.price, border_color = color.new(c, 70), bgcolor = color.new(c, 92), text = name, text_color = color.new(c, 30), text_size = size.small, text_halign = text.align_left, text_valign = text.align_top))
f_gsvDrawPlan(GSPlan pl, bool isOpen) =>
int xa = math.max(pl.entryBar, bar_index - 1500)
int xb = isOpen ? bar_index + 2 : math.max(pl.exitBar, xa)
int xg = math.max(pl.gapBar - 2, bar_index - 1500)
if not na(pl.gapB) and pl.gapBar >= bar_index - 1500
array.push(gsvHB, box.new(xg, pl.gapT, math.max(pl.flipBar, xg), pl.gapB, border_color = color.new(gsvGold, 55), bgcolor = color.new(gsvGold, 85), text = "FLIP", text_color = color.new(gsvGold, 20), text_size = size.tiny, text_halign = text.align_right, text_valign = text.align_center))
if pl.sweepBar >= bar_index - 1500
array.push(gsvHT, label.new(pl.sweepBar, pl.extreme, "SWEPT " + pl.pool, style = pl.dir == -1 ? label.style_label_down : label.style_label_up, color = color.new(gsvGold, 20), textcolor = #0C131D, size = size.small, tooltip = "Raid of the " + pl.pool + " and a close back inside · extreme " + f_gsvPx(pl.extreme)))
if gsvZones
array.push(gsvHB, box.new(xa, math.max(pl.entry, pl.stop), xb, math.min(pl.entry, pl.stop), border_color = color.new(gsvDown, 100), bgcolor = color.new(gsvDown, isOpen ? 84 : 93)))
array.push(gsvHB, box.new(xa, math.max(pl.entry, pl.target), xb, math.min(pl.entry, pl.target), border_color = color.new(gsvUp, 100), bgcolor = color.new(gsvUp, isOpen ? 84 : 93)))
array.push(gsvHL, line.new(xa, pl.tp1, xb, pl.tp1, color = color.new(gsvUp, isOpen ? 35 : 65), style = line.style_dashed))
array.push(gsvHL, line.new(xa, pl.entry, xb, pl.entry, color = color.new(gsvGold, isOpen ? 10 : 50)))
if pl.entryBar >= bar_index - 1500
array.push(gsvHT, label.new(pl.entryBar, pl.entry, (pl.dir == 1 ? "▲ LONG " : "▼ SHORT ") + f_gsvPx(pl.entry), style = pl.dir == 1 ? label.style_label_up : label.style_label_down, color = color.new(pl.dir == 1 ? gsvUp : gsvDown, 15), textcolor = #0C131D, size = size.small, tooltip = pl.how + " after the " + pl.pool + " sweep · stop " + f_gsvPx(pl.stop) + " · 1R " + f_gsvPx(pl.tp1) + " · target " + f_gsvPx(pl.target)))
if not isOpen and pl.exitBar >= bar_index - 1500
bool above = pl.exitPrice >= pl.entry
color c = pl.netR > 0.05 ? gsvUp : pl.netR < -0.05 ? gsvDown : gsvGrey
array.push(gsvHT, label.new(pl.exitBar, pl.exitPrice, f_gsvEndMark(pl.ending, pl.netR) + " " + f_gsvEnding(pl.ending) + " " + f_gsvR(pl.netR), style = above ? label.style_label_down : label.style_label_up, color = color.new(c, 15), textcolor = #0C131D, size = size.small, tooltip = "Exit " + f_gsvPx(pl.exitPrice) + " · " + f_gsvR(pl.grossR) + " before costs, " + f_gsvR(pl.netR) + " after the estimated cost"))
f_gsvDrawHistory() =>
f_gsvClear(gsvHB, gsvHL, gsvHT)
// The trading days to draw: the latest gsvDays distinct day ids in the pool log.
int oldestDay = gsDay
int seen = 0
int lastDay = -1
int i = array.size(gsPoolLog) - 1
while i >= 0 and seen < gsvDays
GSPool p = array.get(gsPoolLog, i)
if p.day != lastDay
seen += 1
lastDay := p.day
oldestDay := p.day
i -= 1
// Pool names are written for today and for every drawn day that had a plan.
array<int> tagDays = array.from(gsDay)
for pl in gsDone
if pl.day >= oldestDay
array.push(tagDays, pl.day)
GSPool asiaHi = na
GSPool lonHi = na
for [pi, p] in gsPoolLog
if p.day >= oldestDay and p.bornBar >= bar_index - 1500
if gsvPools
bool tagged = array.includes(tagDays, p.day)
f_gsvDrawPool(p, tagged, tagged ? f_gsvPlace(pi, oldestDay, tagDays) : label.style_label_left)
if gsvSessions and p.name == "Asia high"
asiaHi := p
if gsvSessions and p.name == "Asia low" and not na(asiaHi)
f_gsvDrawSession(asiaHi, p, 240, gsvAsia, "ASIA")
if gsvSessions and p.name == "London high"
lonHi := p
if gsvSessions and p.name == "London low" and not na(lonHi)
f_gsvDrawSession(lonHi, p, 180, gsvGold, "LONDON")
int j = array.size(gsDone) - 1
while j >= 0
GSPlan pl = array.get(gsDone, j)
if pl.day < oldestDay or bar_index - pl.entryBar >= 1500
break
f_gsvDrawPlan(pl, false)
j -= 1
if gsP.dir != 0 and bar_index - gsP.entryBar < 1500
f_gsvDrawPlan(gsP, true)
if gsvMisses
int k = array.size(gsEvBar) - 1
int marks = 0
while k >= 0 and marks < 40
int eb = array.get(gsEvBar, k)
if bar_index - eb >= 1500
break
string w = array.get(gsEvWhat, k)
if w == "HELD" or w == "NO GAP" or w == "SWEEP FAILED" or w == "NO FLIP" or w == "NO RETEST" or str.startswith(w, "NO ENTRY")
array.push(gsvHT, label.new(eb, array.get(gsEvPx, k), "◌", style = label.style_label_center, color = color.new(#000000, 100), textcolor = color.new(gsvGrey, 10), size = size.tiny, tooltip = str.lower(w) + " · the setup ended without a plan"))
marks += 1
k -= 1
f_gsvDrawLive() =>
f_gsvClear(gsvLB, gsvLL, gsvLT)
if gsP.dir == 0 and gsS.state != 0
int x1 = math.max(gsS.raidBar, bar_index - 1500)
color c = gsS.dir == -1 ? gsvDown : gsvUp
array.push(gsvLL, line.new(x1, gsS.extreme, bar_index + 3, gsS.extreme, color = color.new(c, 20), width = 2))
array.push(gsvLT, label.new(bar_index + 3, gsS.extreme, (gsS.state == 1 ? "RAID " : "SWEPT ") + gsS.pool + " · extreme " + f_gsvPx(gsS.extreme), style = label.style_label_left, color = color.new(#10151F, 10), textcolor = c, size = size.small))
if gsS.state >= 2 and not na(gsS.gapB)
int xg = math.max(gsS.gapBar - 2, bar_index - 1500)
array.push(gsvLB, box.new(xg, gsS.gapT, bar_index + 3, gsS.gapB, border_color = color.new(gsvGold, 20), border_style = line.style_dashed, bgcolor = color.new(gsvGold, 85), text = gsS.state == 2 ? "GAP TO FLIP" : "FLIPPED · RETEST", text_color = color.new(gsvGold, 10), text_size = size.tiny, text_halign = text.align_right, text_valign = text.align_center))
if gsS.state == 3
array.push(gsvLL, line.new(x1, gsS.stop, bar_index + 3, gsS.stop, color = color.new(gsvDown, 30), style = line.style_dashed))
// Draw on the last historical bar and on every realtime execution. A strategy never runs on the forming bar, so
// barstate.islast alone would leave the Strategy Lab blank while the market is open.
if barstate.islastconfirmedhistory or barstate.isrealtime
if barstate.islastconfirmedhistory or barstate.isconfirmed
f_gsvDrawHistory()
f_gsvDrawLive()
f_gsvDrawDesk()
f_gsvDrawPilot()
barcolor(gsvTint and gsP.dir != 0 ? color.new(gsP.dir == 1 ? gsvUp : gsvDown, 15) : na, title = "Plan tint")
//@version=6
// Gold Sweep Desk 1.1.0 · MIT · © 2026 ProEA Lab · built for r/pinescript
// Gold liquidity as a desk: the prior-day, Asia and London highs and lows, their sweeps, the flip and the plan, in words.
// Every clock is New York time; bars while spot gold is closed are skipped, so spot and 24/7 feeds agree.
// Decisions happen on closed bars. The record is this chart's history after an estimated cost, not a forecast.
// Rules: docs/engine-contract.md in the release · https://proea.app/en/free/gold-sweep-desk
// License: MIT. Share it freely; keep this header.
strategy("Gold Sweep Desk · Strategy Lab", "GS Lab", overlay = true, behind_chart = false, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500, max_bars_back = 2000, initial_capital = 100000, currency = currency.USD, pyramiding = 0, margin_long = 100, margin_short = 100, commission_type = strategy.commission.cash_per_contract, commission_value = 0.20, slippage = 0, process_orders_on_close = true, calc_on_every_tick = false)
const string GS_VERSION = "1.1.0"
const bool GS_IS_STRATEGY = true
// ═══ ENGINE · core.pinefrag ═════════════════════════════════════════════════════════════════════════════════════
// The engine decides; the renderer (visuals.pinefrag) only reads. Every engine name starts with "gs" and a
// capital letter; tests/display.test.mjs refuses any renderer line that assigns one. The rules are written in
// docs/engine-contract.md; tests/reference/engine.mjs is the independent reference.
//
// ① SWEEP a bar in an entry window trades through a fresh pool (prior day, Asia or London high/low) and a close
// comes back inside it within a few bars.
// ② FLIP a close goes through the nearest opposing fair value gap: the gap inverts.
// ③ PLAN entry at that close (or at a later close back inside the gap); stop beyond the sweep's extreme; target
// in R; flat at the time exit. Stop first when a bar reaches both.
// Every clock is New York time by the bar's open. Bars while spot gold is closed (Friday 17:00 → Sunday 18:00 and
// the daily 17:00–18:00 break) are skipped entirely, so a 24/7 feed and a spot feed give the same bar stream.
// Decisions happen on closed bars only. A plan's geometry is frozen at entry.
string gsG1 = "01 · Pools and windows"
bool gsPoolPd = input.bool(true, "Prior day high / low", group = gsG1, tooltip = "The previous trading day's high and low. Gold's day runs 18:00 → 17:00 New York.")
bool gsPoolAsia = input.bool(true, "Asia high / low · 20:00–24:00", group = gsG1, tooltip = "The range of the bars opening 20:00–24:00 New York; a pool from the first bar after midnight.")
bool gsPoolLon = input.bool(true, "London high / low · 02:00–05:00", group = gsG1, tooltip = "The range of the bars opening 02:00–05:00 New York; a pool from the first bar after 05:00.")
string gsWindows = input.string("London + New York", "Entry windows", options = ["London + New York", "Continuous 01:00–11:00"], group = gsG1, tooltip = "A setup can only start on a bar that opens inside a window (New York time). The pools are used up by any bar that trades through them, window or not.")
bool gsWinLon = input.bool(true, " London 02:00–05:00", group = gsG1)
bool gsWinNy = input.bool(true, " New York 07:00–11:00", group = gsG1)
string gsG2 = "02 · Sweep and flip"
float gsDepth = input.float(0.05, "Sweep depth · ATR beyond the level", minval = 0, maxval = 5, step = 0.05, group = gsG2, tooltip = "The raid must reach at least this far beyond the outermost pool it takes, in ATR(14).")
int gsReclaim = input.int(3, "Close back inside within · bars", minval = 0, maxval = 20, group = gsG2, tooltip = "The close back inside the swept level may come on the raid bar or on this many bars after it. Otherwise price was accepted beyond the level (HELD).")
string gsFailOn = input.string("A close back beyond the level", "A sweep fails on", options = ["A close back beyond the level", "Any new extreme"], group = gsG2, tooltip = "While the flip is awaited: a close back beyond the swept level ends the setup, and a new wick only moves the extreme (and the stop) further; or any new extreme ends it.")
string gsConfirm = input.string("FVG flip", "Confirmation", options = ["FVG flip", "Close back only"], group = gsG2, tooltip = "FVG flip: a close through the nearest opposing fair value gap. Close back only: the sweep's own close is the trigger.")
int gsLookback = input.int(30, "Gap born within · bars before the sweep", minval = 3, maxval = 200, group = gsG2)
int gsFlipBars = input.int(12, "Flip within · bars after the sweep", minval = 0, maxval = 100, group = gsG2)
float gsMinLeg = input.float(1.0, "Leg from the extreme · at least ATR", minval = 0, maxval = 10, step = 0.1, group = gsG2, tooltip = "The flip close must be at least this far from the sweep's extreme.")
string gsG3 = "03 · Plan"
string gsEntryMode = input.string("Flip close", "Entry", options = ["Flip close", "Retest close"], group = gsG3, tooltip = "Flip close: the flip's close is the entry. Retest close: after the flip, the first close back inside the gap within the retest bars.")
int gsRetestBars = input.int(12, " Retest within · bars", minval = 1, maxval = 100, group = gsG3)
float gsBuffer = input.float(0.1, "Stop buffer · ATR beyond the extreme", minval = 0, maxval = 3, step = 0.05, group = gsG3)
float gsMaxStop = input.float(6.0, "Skip if the stop is wider than · ATR", minval = 0.5, maxval = 50, step = 0.5, group = gsG3)
float gsTargetR = input.float(2.0, "Target · R", minval = 0.5, maxval = 20, step = 0.5, group = gsG3, tooltip = "A design choice, not a tested setting. 1R is the distance from the entry to the stop; the 1R milestone is recorded on the way.")
string gsExitAt = input.string("16:45", "Flat at · HH:MM New York", group = gsG3, tooltip = "An open plan closes at the close of the bar that ends at or after this time; no entry on or after it. Gold's daily break starts at 17:00.")
string gsSide = input.string("Both", "Direction", options = ["Both", "Long only", "Short only"], group = gsG3)
string gsG4 = "04 · Costs and record"
float gsCost = input.float(0.40, "Estimated round-trip cost · price", minval = 0, step = 0.05, group = gsG4, tooltip = "Spread plus slippage for entry and exit together, in the chart's price (0.40 = 40 cents an ounce on spot gold). Every R on the desk is after this estimate. Set your own broker's.")
int gsPctMin = input.int(20, "Show percentages from N closed plans", minval = 5, maxval = 500, group = gsG4, tooltip = "Below this many closed plans the desk shows counts only: a percentage of a few plans says little.")
// ── clock: New York, by the bar's open
string gsTz = "America/New_York"
float gsTick = syminfo.mintick
bool gsClosed = barstate.isconfirmed
f_gsClock(string hm) =>
array<string> parts = str.split(hm, ":")
int out = -1
if array.size(parts) == 2
float h = str.tonumber(array.get(parts, 0))
float m = str.tonumber(array.get(parts, 1))
if not na(h) and not na(m)
out := int(h) * 60 + int(m)
out
// A trading day starts at 18:00: its id is the New York date six hours later.
f_gsDayId(int t) =>
int t6 = t + 6 * 3600000
year(t6, "America/New_York") * 10000 + month(t6, "America/New_York") * 100 + dayofmonth(t6, "America/New_York")
int gsExitMin = f_gsClock(gsExitAt) < 0 ? 16 * 60 + 45 : f_gsClock(gsExitAt)
int gsDow = dayofweek(time, gsTz)
int gsHm = hour(time, gsTz) * 60 + minute(time, gsTz)
int gsDayNow = f_gsDayId(time)
int gsCloseHm = hour(time_close, gsTz) * 60 + minute(time_close, gsTz)
int gsCloseDay = f_gsDayId(time_close)
// dayofweek: 1 = Sunday … 7 = Saturday.
bool gsGoldShut = gsDow == dayofweek.saturday or (gsDow == dayofweek.sunday and gsHm < 1080) or (gsDow == dayofweek.friday and gsHm >= 1020) or (gsHm >= 1020 and gsHm < 1080)
bool gsInAsia = gsHm >= 1200
bool gsInLon = gsHm >= 120 and gsHm < 300
string gsWinNow = gsWindows == "Continuous 01:00–11:00" ? (gsHm >= 60 and gsHm < 660 ? (gsHm < 420 ? "London" : "New York") : "") : (gsWinLon and gsHm >= 120 and gsHm < 300) ? "London" : (gsWinNy and gsHm >= 420 and gsHm < 660) ? "New York" : ""
bool gsAvailable = chart.is_standard and timeframe.isintraday and timeframe.in_seconds() <= 3600
string gsUnavailable = gsAvailable ? "" : not chart.is_standard ? "standard candles needed" : "an intraday chart of 1 hour or less is needed"
// ── state
type GSPool
string name
int side
float price
bool fresh = true
int bornBar = na
int takenBar = na
int day = 0
type GSGap
int dir
int born
int bornBar
float bottom
float top
bool killed = false
// The setup in progress: 0 idle · 1 raid · 2 swept · 3 flipped (waiting for the retest).
type GSSetup
int state = 0
int dir = 0
string window = ""
float ref = na
string pool = ""
string pools = ""
int poolsN = 0
float extreme = na
float depthAtr = na
int raidN = na
int raidBar = na
int sweepN = na
int sweepBar = na
float gapB = na
float gapT = na
int gapBar = na
float gapAtr = na
int flipN = na
int flipBar = na
float legAtr = na
float atrFlip = na
float stop = na
// The plan (dir 0 = none). Its geometry is frozen at entry; a copy goes to gsDone when it ends.
type GSPlan
int dir = 0
int day = 0
string window = ""
string pool = ""
int poolsN = 0
float depthAtr = na
float legAtr = na
float gapAtr = na
float riskAtr = na
int raidBar = na
int sweepBar = na
int flipBar = na
int gapBar = na
int entryN = na
int entryBar = na
int entryTime = na
string how = ""
float extreme = na
float gapB = na
float gapT = na
float entry = na
float stop = na
float risk = na
float tp1 = na
float target = na
bool tp1Hit = false
int tp1Bar = na
int exitBar = na
int exitTime = na
float exitPrice = na
string ending = ""
float grossR = na
float netR = na
type GSStat
int plans = 0
int wins = 0
float sumR = 0.0
int nTarget = 0
int nStop = 0
int nTime = 0
int nTp1 = 0
int raids = 0
int sweeps = 0
int flips = 0
int held = 0
int noGap = 0
int failed = 0
int noFlip = 0
int noEntry = 0
// What happened on this bar.
type GSEv
int raid = 0
int sweep = 0
int flip = 0
int entry = 0
bool exit = false
string why = ""
float r = na
int exitDir = 0
bool tp1 = false
string stopped = ""
string pool = ""
var GSSetup gsS = GSSetup.new()
var GSPlan gsP = GSPlan.new()
var GSStat gsSt = GSStat.new()
var array<GSPool> gsPools = array.new<GSPool>()
var array<GSPool> gsPoolLog = array.new<GSPool>()
var array<GSGap> gsGaps = array.new<GSGap>()
var array<GSPlan> gsDone = array.new<GSPlan>()
var array<int> gsEvBar = array.new<int>()
var array<string> gsEvWhat = array.new<string>()
var array<float> gsEvPx = array.new<float>()
GSEv gsEv = GSEv.new()
var float gsAtr = na
var float gsTrSum = 0.0
var int gsTrN = 0
var float gsPrevC = na
var int gsN = -1
var int gsDay = 0
var float gsDayH = na
var float gsDayL = na
var float gsAsiaH = na
var float gsAsiaL = na
var bool gsAsiaDone = false
var float gsLonH = na
var float gsLonL = na
var bool gsLonDone = false
var int gsRestN = -1
var float gsH1 = na
var float gsL1 = na
var int gsD1 = 0
var float gsH2 = na
var float gsL2 = na
var int gsD2 = 0
// The day's story for the desk, in words (a field, so the helpers below can extend it).
type GSText
string log = ""
var GSText gsTx = GSText.new()
f_gsRText(float v) =>
(v < 0 ? "−" : "+") + str.tostring(math.abs(v), "0.0") + "R"
f_gsHhmm() =>
str.tostring(hour(time, "America/New_York"), "00") + ":" + str.tostring(minute(time, "America/New_York"), "00")
f_gsLogAdd(string what) =>
gsTx.log := gsTx.log + (gsTx.log == "" ? "" : " · ") + f_gsHhmm() + what
f_gsNote(string what) =>
array.push(gsEvBar, bar_index)
array.push(gsEvWhat, what)
array.push(gsEvPx, close)
if array.size(gsEvBar) > 200
array.shift(gsEvBar)
array.shift(gsEvWhat)
array.shift(gsEvPx)
f_gsPoolNew(string name, int side, float price, int day) =>
GSPool p = GSPool.new(name, side, price, true, bar_index, na, day)
array.push(gsPools, p)
array.push(gsPoolLog, p)
if array.size(gsPoolLog) > 120
array.shift(gsPoolLog)
f_gsReset(string why) =>
// A setup ends without a plan: remembered for the desk and the alerts.
gsEv.stopped := why
gsS.state := 0
gsS.dir := 0
if why == "HELD"
gsSt.held += 1
else if why == "NO GAP"
gsSt.noGap += 1
else if why == "SWEEP FAILED"
gsSt.failed += 1
else if why == "NO FLIP" or why == "NO RETEST"
gsSt.noFlip += 1
else
gsSt.noEntry += 1
f_gsNote(why)
f_gsLogAdd(" " + str.lower(why))
// Entry at this close, or a refusal. Returns true when a plan starts.
f_gsEnter(float px, string how) =>
int d = gsS.dir
float risk = (px - gsS.stop) * d
bool ok = false
if not (risk > 0)
f_gsReset("NO ENTRY · stop not beyond entry")
else if risk > gsMaxStop * gsS.atrFlip
f_gsReset("NO ENTRY · stop too wide")
else if not (gsCloseDay == gsDayNow and gsCloseHm < gsExitMin)
f_gsReset("NO ENTRY · too late in the day")
else
ok := true
gsP.dir := d
gsP.day := gsDayNow
gsP.window := gsS.window
gsP.pool := gsS.pool
gsP.poolsN := gsS.poolsN
gsP.depthAtr := gsS.depthAtr
gsP.legAtr := gsS.legAtr
gsP.gapAtr := gsS.gapAtr
gsP.riskAtr := risk / gsS.atrFlip
gsP.raidBar := gsS.raidBar
gsP.sweepBar := gsS.sweepBar
gsP.flipBar := gsS.flipBar
gsP.gapBar := gsS.gapBar
gsP.entryN := gsN
gsP.entryBar := bar_index
gsP.entryTime := time
gsP.how := how
gsP.extreme := gsS.extreme
gsP.gapB := gsS.gapB
gsP.gapT := gsS.gapT
gsP.entry := px
gsP.stop := gsS.stop
gsP.risk := risk
gsP.tp1 := px + d * risk
gsP.target := px + d * gsTargetR * risk
gsP.tp1Hit := false
gsP.tp1Bar := na
gsP.exitBar := na
gsP.exitTime := na
gsP.exitPrice := na
gsP.ending := ""
gsP.grossR := na
gsP.netR := na
gsS.state := 4
gsEv.entry := d
f_gsNote(d == 1 ? "ENTRY LONG" : "ENTRY SHORT")
f_gsLogAdd((d == 1 ? " ▲ long " : " ▼ short ") + str.tostring(px, format.mintick))
ok
// The flip on this bar, if the close goes through the gap far enough from the extreme.
f_gsFlip() =>
int d = gsS.dir
bool through = d == -1 ? close < gsS.gapB : close > gsS.gapT
float leg = (gsS.extreme - close) * -d
bool done = false
if through and leg >= gsMinLeg * gsAtr
done := true
gsS.flipN := gsN
gsS.flipBar := bar_index
gsS.legAtr := leg / gsAtr
gsS.atrFlip := gsAtr
gsS.stop := d == -1 ? math.ceil((gsS.extreme + gsBuffer * gsAtr) / gsTick - 1e-9) * gsTick : math.floor((gsS.extreme - gsBuffer * gsAtr) / gsTick + 1e-9) * gsTick
gsEv.flip := d
gsSt.flips += 1
f_gsNote("FLIP")
f_gsLogAdd(" flip")
if gsEntryMode == "Flip close"
f_gsEnter(close, "flip close")
else
gsS.state := 3
done
// The sweep on this bar: pick the gap to invert (or trigger at once with "Close back only").
f_gsSweep() =>
gsS.sweepN := gsN
gsS.sweepBar := bar_index
gsEv.sweep := gsS.dir
gsEv.pool := gsS.pool
gsSt.sweeps += 1
f_gsNote("SWEEP " + gsS.pool)
f_gsLogAdd(" swept " + gsS.pool)
if gsConfirm == "Close back only"
gsS.legAtr := (gsS.extreme - close) * -gsS.dir / gsAtr
gsS.atrFlip := gsAtr
gsS.flipN := gsN
gsS.flipBar := bar_index
gsS.stop := gsS.dir == -1 ? math.ceil((gsS.extreme + gsBuffer * gsAtr) / gsTick - 1e-9) * gsTick : math.floor((gsS.extreme - gsBuffer * gsAtr) / gsTick + 1e-9) * gsTick
f_gsEnter(close, "reclaim close")
else
int want = -gsS.dir
GSGap best = na
for g in gsGaps
if g.dir == want and not g.killed and g.born >= gsN - gsLookback and g.born < gsN
if na(best)
best := g
else if want == 1 ? g.bottom > best.bottom : g.top < best.top
best := g
if na(best)
f_gsReset("NO GAP")
else
gsS.gapB := best.bottom
gsS.gapT := best.top
gsS.gapBar := best.bornBar
gsS.gapAtr := (best.top - best.bottom) / gsAtr
gsS.state := 2
f_gsFlip()
// The branches above return different types; the function returns the setup state.
gsS.state
f_gsEnd(float px, string why) =>
float gross = (px - gsP.entry) * gsP.dir / gsP.risk
float net = gross - gsCost / gsP.risk
gsP.exitBar := bar_index
gsP.exitTime := time
gsP.exitPrice := px
gsP.ending := why
gsP.grossR := gross
gsP.netR := net
if gsClosed and gsAvailable and not gsGoldShut
gsN += 1
// (A) ATR over session bars, this bar included.
float tr = na(gsPrevC) ? high - low : math.max(high - low, math.abs(high - gsPrevC), math.abs(low - gsPrevC))
if gsTrN < 14
gsTrSum += tr
gsTrN += 1
if gsTrN == 14
gsAtr := gsTrSum / 14
else
gsAtr := (tr + 13 * gsAtr) / 14
gsPrevC := close
// (B) A new trading day: yesterday's range becomes the prior-day pools; the session pools start again.
if gsDayNow != gsDay
for p in gsPools
if p.fresh
p.takenBar := bar_index - 1
array.clear(gsPools)
if gsDay != 0 and gsPoolPd and not na(gsDayH)
f_gsPoolNew("PDH", 1, gsDayH, gsDayNow)
f_gsPoolNew("PDL", -1, gsDayL, gsDayNow)
gsDay := gsDayNow
gsDayH := na
gsDayL := na
gsAsiaH := na
gsAsiaL := na
gsAsiaDone := false
gsLonH := na
gsLonL := na
gsLonDone := false
gsTx.log := ""
if not gsAsiaDone and not na(gsAsiaH) and not gsInAsia
gsAsiaDone := true
if gsPoolAsia
f_gsPoolNew("Asia high", 1, gsAsiaH, gsDayNow)
f_gsPoolNew("Asia low", -1, gsAsiaL, gsDayNow)
if not gsLonDone and not na(gsLonH) and not gsInLon
gsLonDone := true
if gsPoolLon
f_gsPoolNew("London high", 1, gsLonH, gsDayNow)
f_gsPoolNew("London low", -1, gsLonL, gsDayNow)
// (C) The open plan: a later day, stop first, target, then the time exit.
if gsP.dir != 0 and gsN > gsP.entryN
int d = gsP.dir
bool stopHit = d == 1 ? low <= gsP.stop : high >= gsP.stop
bool targetHit = d == 1 ? high >= gsP.target : low <= gsP.target
bool lateDay = gsDayNow != gsP.day
if lateDay
f_gsEnd(open, "TIME")
else if stopHit
f_gsEnd((d == 1 ? open <= gsP.stop : open >= gsP.stop) ? open : gsP.stop, "STOP")
else if targetHit
f_gsEnd(gsP.target, "TARGET")
else if gsCloseDay == gsP.day and gsCloseHm >= gsExitMin
f_gsEnd(close, "TIME")
if not gsP.tp1Hit and not stopHit and not lateDay and (d == 1 ? high >= gsP.tp1 : low <= gsP.tp1)
gsP.tp1Hit := true
gsP.tp1Bar := bar_index
gsEv.tp1 := true
if gsP.ending != ""
gsEv.exit := true
gsEv.why := gsP.ending
gsEv.r := gsP.netR
gsEv.exitDir := gsP.dir
gsSt.plans += 1
gsSt.wins += gsP.netR > 0 ? 1 : 0
gsSt.sumR += gsP.netR
gsSt.nTarget += gsP.ending == "TARGET" ? 1 : 0
gsSt.nStop += gsP.ending == "STOP" ? 1 : 0
gsSt.nTime += gsP.ending == "TIME" ? 1 : 0
gsSt.nTp1 += gsP.tp1Hit ? 1 : 0
f_gsNote(gsP.ending)
f_gsLogAdd(" " + str.lower(gsP.ending) + " " + f_gsRText(gsP.netR))
array.push(gsDone, gsP.copy())
if array.size(gsDone) > 300
array.shift(gsDone)
gsP.dir := 0
gsRestN := gsN
gsS.state := 0
gsS.dir := 0
// (D) Pools this bar trades through are used up, whatever else happens.
int penHN = 0
int penLN = 0
float penHMax = na
float penLMin = na
string penHName = ""
string penLName = ""
string penHAll = ""
string penLAll = ""
for p in gsPools
if p.fresh and p.side == 1 and high > p.price
p.fresh := false
p.takenBar := bar_index
penHN += 1
penHAll += (penHAll == "" ? "" : " + ") + p.name
if na(penHMax) or p.price > penHMax
penHMax := p.price
penHName := p.name
else if p.fresh and p.side == -1 and low < p.price
p.fresh := false
p.takenBar := bar_index
penLN += 1
penLAll += (penLAll == "" ? "" : " + ") + p.name
if na(penLMin) or p.price < penLMin
penLMin := p.price
penLName := p.name
// (E) The setup: RAID → SWEPT → (FLIPPED →) plan.
if gsP.dir == 0
if gsS.state == 1
int d = gsS.dir
gsS.extreme := d == -1 ? math.max(gsS.extreme, high) : math.min(gsS.extreme, low)
int moreN = d == -1 ? penHN : penLN
if moreN > 0
gsS.poolsN += moreN
gsS.pools += " + " + (d == -1 ? penHAll : penLAll)
float outer = d == -1 ? penHMax : penLMin
if d == -1 ? outer > gsS.ref : outer < gsS.ref
gsS.ref := outer
gsS.pool := d == -1 ? penHName : penLName
if d == -1 ? close < gsS.ref : close > gsS.ref
f_gsSweep()
else if gsN - gsS.raidN >= gsReclaim
f_gsReset("HELD")
else if gsS.state == 2
int d = gsS.dir
bool beyond = d == -1 ? high > gsS.extreme : low < gsS.extreme
bool onClose = gsFailOn == "A close back beyond the level"
if onClose and beyond
gsS.extreme := d == -1 ? high : low
if onClose ? (d == -1 ? close > gsS.ref : close < gsS.ref) : beyond
f_gsReset("SWEEP FAILED")
else if not f_gsFlip() and gsN - gsS.sweepN >= gsFlipBars
f_gsReset("NO FLIP")
else if gsS.state == 3
int d = gsS.dir
if d == -1 ? high >= gsS.stop : low <= gsS.stop
f_gsReset("NO ENTRY · stop traded before the retest")
else if close >= gsS.gapB and close <= gsS.gapT
f_gsEnter(close, "retest close")
else if gsN - gsS.flipN >= gsRetestBars
f_gsReset("NO RETEST")
else if gsS.state == 0 and gsN != gsRestN and not na(gsAtr) and gsTrN >= 14
bool up = penHN > 0 and gsSide != "Long only"
bool dn = penLN > 0 and gsSide != "Short only"
if gsWinNow != "" and up != dn
int d = up ? -1 : 1
float outer = up ? penHMax : penLMin
float depth = d == -1 ? high - outer : outer - low
if depth >= gsDepth * gsAtr
gsS := GSSetup.new(1, d, gsWinNow, outer, up ? penHName : penLName, up ? penHAll : penLAll, up ? penHN : penLN, d == -1 ? high : low, depth / gsAtr, gsN, bar_index)
gsEv.raid := d
gsEv.pool := gsS.pool
gsSt.raids += 1
f_gsNote("RAID " + gsS.pool)
f_gsLogAdd(" raid " + gsS.pool)
if d == -1 ? close < gsS.ref : close > gsS.ref
f_gsSweep()
// (F) Gaps: a close through a gap kills it; a gap completes on this bar (three bars of the same trading day).
for g in gsGaps
if not g.killed and g.born < gsN and (g.dir == 1 ? close < g.bottom : close > g.top)
g.killed := true
if gsN >= 2 and gsD2 == gsDayNow
if low > gsH2
array.push(gsGaps, GSGap.new(1, gsN, bar_index, gsH2, low, false))
if high < gsL2
array.push(gsGaps, GSGap.new(-1, gsN, bar_index, high, gsL2, false))
while array.size(gsGaps) > 0 and array.first(gsGaps).born < gsN - 2 * gsLookback
array.shift(gsGaps)
gsH2 := gsH1
gsL2 := gsL1
gsD2 := gsD1
gsH1 := high
gsL1 := low
gsD1 := gsDayNow
// (G) Today's range and the session ranges include this bar.
gsDayH := na(gsDayH) ? high : math.max(gsDayH, high)
gsDayL := na(gsDayL) ? low : math.min(gsDayL, low)
if gsInAsia and not gsAsiaDone
gsAsiaH := na(gsAsiaH) ? high : math.max(gsAsiaH, high)
gsAsiaL := na(gsAsiaL) ? low : math.min(gsAsiaL, low)
if gsInLon and not gsLonDone
gsLonH := na(gsLonH) ? high : math.max(gsLonH, high)
gsLonL := na(gsLonL) ? low : math.min(gsLonL, low)
// ═══ STRATEGY LAB · strategy.pinefrag (strategy only) ═════════════════════════════════════════════════════════
// TradingView's broker emulator runs the desk's plans. Orders process on the bar close, like the desk's decisions:
// entry a market order at the entry close and, in the same calculation, one exit order with the frozen stop and
// target, so the position is protected from the first bar after entry;
// end every plan end cancels pending orders first (cancel_all after close_all would cancel the close), then
// closes anything still held at that close (the time exit, or an emulator fill that differed).
// The desk stays the reference. The emulator orders a bar's high and low its own way, so its fills can differ; that
// difference is what the Strategy Tester shows. A strategy does not run on the forming candle. Commission is set in
// Properties: cash per contract 0.20 a side = the desk's 0.40 round-trip estimate on spot gold (1 contract = 1 oz).
string gsG5 = "05 · Strategy Lab"
float gsRiskPct = input.float(1.0, "Risk per plan · % of equity", minval = 0.1, maxval = 10, step = 0.1, group = gsG5, tooltip = "The loss at the stop, as a share of equity, before costs.")
float gsExposurePct = input.float(90, "Largest position · % of equity", minval = 1, maxval = 1000, step = 1, group = gsG5, tooltip = "Caps a position's value. The script declares 100 % margin; 90 % leaves room for costs. Raise it only for leveraged trading, together with the margin settings.")
// Quantity: the smaller of the risk budget and the exposure cap, in the account currency (a symbol quoted in
// another currency is converted), rounded down to the symbol's contract step. Zero means no affordable size.
f_gsQty(float risk, float price) =>
float conv = strategy.convert_to_account(1.0)
float unitRisk = risk * syminfo.pointvalue * conv
float unitNotional = math.abs(price) * syminfo.pointvalue * conv
float step = syminfo.mincontract > 0 ? syminfo.mincontract : 1.0
float equity = math.max(0, strategy.equity)
float units = unitRisk > 0 and unitNotional > 0 ? math.min(equity * gsRiskPct / 100 / unitRisk, equity * gsExposurePct / 100 / unitNotional) : 0.0
math.floor(units / step + 1e-9) * step
var int gsLabSkipped = 0
// Every plan end: cancel pending orders, then close what the emulator still holds.
if gsClosed and gsEv.exit
strategy.cancel_all()
if strategy.position_size != 0
strategy.close_all(comment = "DESK " + gsEv.why)
// Entry and its protection in the same calculation. A position the emulator still holds is not doubled.
if gsClosed and gsEv.entry != 0
float qty = f_gsQty(gsP.risk, gsP.entry)
if strategy.position_size != 0
gsLabSkipped += 1
else if qty > 0
string id = gsEv.entry == 1 ? "GS L" : "GS S"
strategy.entry(id, gsEv.entry == 1 ? strategy.long : strategy.short, qty = qty, comment = (gsEv.entry == 1 ? "▲ " : "▼ ") + gsP.pool)
strategy.exit(id + " exit", id, stop = gsP.stop, limit = gsP.target, comment_loss = "STOP", comment_profit = "TARGET")
// ═══ DISPLAY · visuals.pinefrag ═════════════════════════════════════════════════════════════════════════════════
// Reads engine state and draws it. It never assigns an engine variable, requests data, trades or alerts
// (tests/display.test.mjs enforces this), so a display change cannot change a plan or a result.
// Desk standard: state line · reason · ribbon · steps (✓ / ✗ / —, ● required) · plan in price and R · today ·
// record (counts first, % only from the threshold) · footer. Co-Pilot: NOW · WHY · PLAN · LIMITS.
// Layers: the desk and the live drawings follow every tick; the history is redrawn once per closed bar.
string gsvG = "06 · View"
string gsvTheme = input.string("Standard", "Theme", options = ["Standard", "Colorblind-safe"], group = gsvG)
string gsvDeskPos = input.string("Top right", "Desk position", options = ["Top right", "Top left", "Bottom right", "Bottom left", "Off"], group = gsvG)
string gsvRows = input.string("Compact", "Desk rows", options = ["Compact", "Full"], group = gsvG, tooltip = "Full adds every pool of the day, the last plan and the average result.")
string gsvPilot = input.string("Brief", "Co-Pilot", options = ["Standard", "Brief", "Off"], group = gsvG, tooltip = "The desk in plain words. Brief shows NOW and PLAN; Standard adds WHY and LIMITS.")
string gsvPilotPos = input.string("Bottom left", "Co-Pilot position", options = ["Bottom left", "Bottom right", "Top left", "Top right"], group = gsvG)
string gsvTextSize = input.string("Normal", "Text size", options = ["Small", "Normal", "Large"], group = gsvG)
int gsvDays = input.int(2, "Days drawn", minval = 1, maxval = 30, group = gsvG, tooltip = "Pools, sessions and plans of this many trading days (today included), within the last 1,500 bars.")
bool gsvPools = input.bool(true, "Liquidity pools", group = gsvG)
bool gsvSessions = input.bool(true, "Asia and London boxes", group = gsvG)
bool gsvZones = input.bool(true, "Risk / reward zones", group = gsvG)
bool gsvMisses = input.bool(true, "Mark setups that ended without a plan (◌)", group = gsvG)
bool gsvTint = input.bool(false, "Tint candles while a plan is open", group = gsvG)
// Colour carries meaning: teal = long / target, coral = short / stop, gold = liquidity and a setup in progress.
bool gsvCb = gsvTheme == "Colorblind-safe"
color gsvUp = gsvCb ? #56B4E9 : #26A69A
color gsvDown = gsvCb ? #E69F00 : #EF5350
color gsvGold = gsvCb ? #F0E442 : #F2B33D
color gsvAsia = #7E8CE0
color gsvInk = #E6E9F0
color gsvMuted = #8A93A6
color gsvGrey = #6B7280
color gsvPanel = color.new(#10151F, 6)
string gsvSz = gsvTextSize == "Small" ? size.tiny : gsvTextSize == "Large" ? size.normal : size.small
string gsvSzState = gsvTextSize == "Small" ? size.small : gsvTextSize == "Large" ? size.large : size.normal
f_gsvCorner(string p) =>
p == "Top left" ? position.top_left : p == "Bottom right" ? position.bottom_right : p == "Bottom left" ? position.bottom_left : position.top_right
f_gsvPx(float v) =>
na(v) ? "—" : str.tostring(v, format.mintick)
f_gsvR(float v) =>
na(v) ? "—" : math.abs(v) < 0.05 ? "0.0R" : (v < 0 ? "−" : "+") + str.tostring(math.abs(v), "0.0") + "R"
f_gsvHm(int mins) =>
str.tostring(int(mins / 60) % 24, "00") + ":" + str.tostring(mins % 60, "00")
f_gsvAt(int t) =>
str.tostring(hour(t, "America/New_York"), "00") + ":" + str.tostring(minute(t, "America/New_York"), "00")
f_gsvLeft(int mins) =>
mins <= 0 ? "closing" : (mins >= 60 ? str.tostring(int(mins / 60)) + " h " : "") + str.tostring(mins % 60) + " m left"
f_gsvMark(int s) =>
s == 1 ? "✓ met" : s == 0 ? "✗ not met" : s == -1 ? "— no data" : s == -3 ? "— waiting" : "— off"
f_gsvTone(int s) =>
s == 1 ? gsvUp : s == 0 ? gsvDown : gsvMuted
f_gsvEnding(string why) =>
why == "TARGET" ? "TARGET" : why == "STOP" ? "STOP" : why == "TIME" ? "FLAT AT THE TIME EXIT" : why
f_gsvEndMark(string why, float r) =>
why == "TARGET" ? "✓" : why == "STOP" ? "✗" : r > 0.05 ? "✓" : r < -0.05 ? "✗" : "◌"
f_gsvWrap(string txt, int columns) =>
array<string> words = str.split(txt, " ")
string output = ""
int width = 0
for word in words
if width > 0 and width + str.length(word) + 1 > columns
output += "\n"
width := 0
output += (width > 0 ? " " : "") + word
width += str.length(word) + 1
output
f_gsvSide(int d) =>
d == 1 ? "long" : "short"
// ── windows, in New York minutes
bool gsvCont = gsWindows == "Continuous 01:00–11:00"
int gsvWinEnd = gsvCont ? 660 : gsWinNow == "London" ? 300 : 660
string gsvWinTxt = gsvCont ? "01:00–11:00" : gsWinNow == "London" ? "London 02:00–05:00" : "New York 07:00–11:00"
// The next window start after this minute of the day: later today, tomorrow, or Monday (no windows at the weekend).
f_gsvNextWin(int hm) =>
int dow = dayofweek(time, "America/New_York")
bool later = gsvCont ? hm >= 60 : not ((gsWinLon and hm < 120) or (gsWinNy and hm < 420))
string day = dow == dayofweek.saturday or dow == dayofweek.sunday or (dow == dayofweek.friday and later) ? "Monday " : later ? "tomorrow " : ""
string win = gsvCont ? "01:00" : (not later and gsWinLon and hm < 120) or (later and gsWinLon) ? "London 02:00" : gsWinNy ? "New York 07:00" : ""
win == "" ? "none (both windows off)" : day + win
// ── what the desk shows (display variables; the engine's objects are only read)
string gsvState = ""
string gsvReason = ""
string gsvNow = ""
string gsvPlan = ""
color gsvTone = gsvMuted
string gsvRib = ""
int gsvDir = 0
string gsvNearUp = ""
string gsvNearDn = ""
int gsvFresh = 0
int gsvPoolsToday = 0
if barstate.islastconfirmedhistory or barstate.isrealtime
// Nearest fresh pools above and below the close.
float upPx = na
float dnPx = na
for p in gsPools
gsvPoolsToday += 1
if p.fresh
gsvFresh += 1
if p.price >= close and (na(upPx) or p.price < upPx)
upPx := p.price
gsvNearUp := p.name + " " + f_gsvPx(p.price) + " (+" + str.tostring(p.price - close, format.mintick) + ")"
if p.price < close and (na(dnPx) or p.price > dnPx)
dnPx := p.price
gsvNearDn := p.name + " " + f_gsvPx(p.price) + " (−" + str.tostring(close - p.price, format.mintick) + ")"
gsvDir := gsP.dir != 0 ? gsP.dir : gsS.state != 0 ? gsS.dir : 0
string r1 = gsvFresh > 0 ? "✓" : "·"
string r2 = "·"
string r3 = "·"
string r4 = "·"
string r5 = "·"
if not gsAvailable
gsvState := "— UNAVAILABLE · " + timeframe.period + " chart"
gsvReason := gsUnavailable == "standard candles needed" ? "Heikin Ashi, Renko and other synthetic candles move the closes. Use standard candles." : "Use an intraday chart of 1 hour or less (5 or 15 minutes on gold)."
gsvNow := "This chart cannot run the rule. " + gsvReason
else if gsTrN < 14
gsvState := "○ WARMING UP"
gsvReason := "The ATR needs 14 session bars."
gsvNow := gsvReason
else if gsP.dir != 0
float openR = (close - gsP.entry) * gsP.dir / gsP.risk
gsvState := "● IN TRADE · " + (gsP.dir == 1 ? "LONG" : "SHORT") + " · " + f_gsvR(openR) + (gsP.tp1Hit ? " · 1R reached" : "")
gsvTone := gsP.dir == 1 ? gsvUp : gsvDown
gsvReason := "Stop " + f_gsvPx(gsP.stop) + " · 1R " + f_gsvPx(gsP.tp1) + (gsP.tp1Hit ? " ✓" : "") + " · target " + f_gsvPx(gsP.target) + " · flat at " + gsExitAt
gsvNow := "In a " + f_gsvSide(gsP.dir) + " from " + f_gsvPx(gsP.entry) + " after the " + gsP.pool + " sweep (" + gsP.how + "), " + str.tostring(bar_index - gsP.entryBar) + " bars ago, " + f_gsvR(openR) + " at this close before costs."
gsvPlan := "Stop first on every closed bar: the stop " + f_gsvPx(gsP.stop) + " is beyond the sweep's extreme " + f_gsvPx(gsP.extreme) + ", where the idea is wrong. The target " + f_gsvPx(gsP.target) + " ends it; otherwise it is flat at " + gsExitAt + " New York."
r2 := "✓"
r3 := "✓"
r4 := "✓"
r5 := "▶"
else if gsGoldShut
gsvState := "◌ GOLD IS CLOSED"
gsvReason := "Spot gold trades Sunday 18:00 → Friday 17:00 New York, with a break 17:00–18:00. These bars are skipped."
gsvNow := gsvReason
else if gsS.state == 1
int left = math.max(0, gsReclaim - (gsN - gsS.raidN))
gsvState := "● RAID · " + gsS.pool + " · " + (gsS.dir == -1 ? "above" : "below")
gsvTone := gsvGold
gsvReason := "A close back " + (gsS.dir == -1 ? "below " : "above ") + f_gsvPx(gsS.ref) + " makes it a sweep (" + str.tostring(left) + (left == 1 ? " bar" : " bars") + " left); otherwise price was accepted beyond the level."
gsvNow := "Price is raiding the " + gsS.pool + " (" + f_gsvPx(gsS.ref) + ") in the " + gsS.window + " window, extreme " + f_gsvPx(gsS.extreme) + ". " + gsvReason
r2 := "▶"
else if gsS.state == 2
int left = math.max(0, gsFlipBars - (gsN - gsS.sweepN))
float lvl = gsS.dir == -1 ? math.min(gsS.gapB, gsS.extreme - gsMinLeg * gsAtr) : math.max(gsS.gapT, gsS.extreme + gsMinLeg * gsAtr)
gsvState := "● SWEPT · " + gsS.pool + " · waiting for the flip"
gsvTone := gsvGold
gsvReason := "A close " + (gsS.dir == -1 ? "below " : "above ") + f_gsvPx(lvl) + " flips the gap " + f_gsvPx(gsS.gapB) + "–" + f_gsvPx(gsS.gapT) + " (" + str.tostring(left) + (left == 1 ? " bar" : " bars") + " left). " + (gsFailOn == "Any new extreme" ? "A new extreme ends it." : "A close back " + (gsS.dir == -1 ? "above " : "below ") + f_gsvPx(gsS.ref) + " ends it.")
gsvNow := "The " + gsS.pool + " was swept (extreme " + f_gsvPx(gsS.extreme) + "). The desk now waits for a close through the nearest opposing gap, the flip that confirms the reversal."
r2 := "✓"
r3 := "▶"
else if gsS.state == 3
int left = math.max(0, gsRetestBars - (gsN - gsS.flipN))
gsvState := "● FLIPPED · waiting for the retest"
gsvTone := gsvGold
gsvReason := "A close back inside " + f_gsvPx(gsS.gapB) + "–" + f_gsvPx(gsS.gapT) + " enters " + f_gsvSide(gsS.dir) + " (" + str.tostring(left) + (left == 1 ? " bar" : " bars") + " left); a touch of the stop " + f_gsvPx(gsS.stop) + " cancels."
gsvNow := "The gap flipped after the " + gsS.pool + " sweep. Retest mode waits for a close back inside the flipped gap."
r2 := "✓"
r3 := "✓"
r4 := "▶"
else if gsWinNow != ""
int left = gsvWinEnd - gsHm
gsvState := "○ WATCHING · " + gsWinNow + " window · " + str.tostring(gsvFresh) + (gsvFresh == 1 ? " fresh pool" : " fresh pools")
gsvTone := gsvGold
gsvReason := gsvFresh == 0 ? "Every pool of today is used up. The next ones: Asia and London ranges, then tomorrow's prior-day levels." : "A raid of a fresh pool, then a close back inside it, starts a setup (" + f_gsvLeft(left) + ")."
gsvNow := "Watching the " + gsWinNow + " window. " + (gsvNearUp != "" ? "Above: " + gsvNearUp + ". " : "") + (gsvNearDn != "" ? "Below: " + gsvNearDn + ". " : "") + (gsvFresh == 0 ? "No fresh pool is left today." : "")
else
gsvState := "◌ OUTSIDE THE WINDOWS · next " + f_gsvNextWin(gsHm)
gsvReason := "Setups start only in " + (gsvCont ? "the 01:00–11:00 window" : (gsWinLon ? "London 02:00–05:00" : "") + (gsWinLon and gsWinNy ? " and " : "") + (gsWinNy ? "New York 07:00–11:00" : "")) + " (New York time). Pools still get used up now."
gsvNow := "Outside the entry windows. " + (gsvNearUp != "" ? "Above: " + gsvNearUp + ". " : "") + (gsvNearDn != "" ? "Below: " + gsvNearDn + "." : "")
gsvRib := "01 POOLS " + r1 + " 02 SWEEP " + r2 + " 03 FLIP " + r3 + " 04 PLAN " + r4 + " 05 RESULT " + r5
if gsvPlan == ""
gsvPlan := "Rule: sweep of the prior day, Asia or London high/low in " + (gsvCont ? "01:00–11:00" : "the London or New York window") + " → a close through the nearest opposing gap" + (gsEntryMode == "Retest close" ? " → a close back inside it" : "") + " → entry at that close, stop beyond the sweep's extreme, target " + str.tostring(gsTargetR, "0.0#") + "R, flat at " + gsExitAt + "."
// ── desk and Co-Pilot
var table gsvDesk = table.new(f_gsvCorner(gsvDeskPos), 2, 40, frame_width = 0, border_width = 0)
string gsvPilotCorner = gsvPilotPos != gsvDeskPos ? gsvPilotPos : gsvDeskPos == "Bottom left" ? "Bottom right" : "Bottom left"
var table gsvPilotT = table.new(f_gsvCorner(gsvPilotCorner), 1, 7, frame_width = 0, border_width = 0)
int gsvPOff = gsvPilotCorner == "Top left" ? 1 : 0
int gsvOff = gsvDeskPos == "Top left" ? 1 : 0
f_gsvRow(int r, string a, string b, color ca, color cb) =>
table.cell(gsvDesk, 0, r + gsvOff, a, text_color = ca, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
table.cell(gsvDesk, 1, r + gsvOff, b, text_color = cb, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
f_gsvWide(int r, string txt, color c, bool bold, string tip) =>
table.cell(gsvDesk, 0, r + gsvOff, txt, text_color = c, bgcolor = gsvPanel, text_size = bold ? gsvSzState : gsvSz, text_halign = text.align_left, text_formatting = bold ? text.format_bold : text.format_none, tooltip = tip)
table.merge_cells(gsvDesk, 0, r + gsvOff, 1, r + gsvOff)
// The rule is built for 5–15 minute candles: from 30 minutes a candle is a large part of a window and the 2R target has
// little room before 16:45; TradingView loads only a few days of 1–4 minute candles, too few for this chart's record.
int gsvTfSec = timeframe.in_seconds()
string gsvTfMins = str.tostring(gsvTfSec / 60, "#")
string gsvTfNote = gsvTfSec >= 1800 ? "TIMEFRAME · built for 5–15 minutes: on " + gsvTfMins + "-minute candles a plan has little room for its target before 16:45" : gsvTfSec < 300 ? "TIMEFRAME · built for 5–15 minutes: TradingView loads only a few days of " + gsvTfMins + "-minute candles, too few for a record" : ""
// One step row: the mark, a detail, and "● required" (every step of the sequence is required).
f_gsvStep(int r, string name, int s, string detail) =>
f_gsvRow(r, name, f_gsvMark(s) + (detail == "" ? "" : " · " + detail) + " ● required", gsvInk, f_gsvTone(s))
f_gsvDrawDesk() =>
table.clear(gsvDesk, 0, 0, 1, 39)
if gsvDeskPos != "Off"
if gsvOff == 1
table.cell(gsvDesk, 0, 0, "", height = 14, bgcolor = na)
table.cell(gsvDesk, 1, 0, "", height = 14, bgcolor = na)
bool full = gsvRows == "Full"
bool inPlan = gsP.dir != 0
int st = gsS.state
// Step states: 1 met · 0 not met · -1 no data · -3 waiting.
int sWin = inPlan or st > 0 ? 1 : gsWinNow != "" ? 1 : 0
int sPool = inPlan or st > 0 ? 1 : gsvFresh > 0 ? 1 : 0
int sSweep = inPlan or st >= 2 ? 1 : st == 1 ? -3 : -3
int sGap = inPlan or st >= 2 ? 1 : -3
int sFlip = inPlan or st == 3 ? 1 : -3
int met = (sWin == 1 ? 1 : 0) + (sPool == 1 ? 1 : 0) + (sSweep == 1 ? 1 : 0) + (sGap == 1 ? 1 : 0) + (sFlip == 1 ? 1 : 0)
f_gsvWide(0, "GOLD SWEEP DESK · " + syminfo.ticker + " · " + timeframe.period + (GS_IS_STRATEGY ? " · STRATEGY LAB" : ""), gsvMuted, false, "Gold Sweep Desk " + GS_VERSION + " · a paper reference decided on closed bars, New York time." + (GS_IS_STRATEGY ? " The Strategy Tester shows TradingView's fills of the same plans." : "") + " Fills at your broker can differ.")
f_gsvWide(1, gsvState, gsvTone, true, "Pools → raid → sweep → flip → plan → result.")
f_gsvWide(2, f_gsvWrap(gsvReason, 58), gsvInk, false, "")
f_gsvWide(3, gsvRib, gsvMuted, false, "01 fresh pools · 02 a raid and a close back inside · 03 the gap flips · 04 the plan · 05 its result")
f_gsvWide(4, "STEPS" + (gsvDir != 0 ? " · for a " + f_gsvSide(gsvDir) : "") + " · " + str.tostring(met) + " of 5 required met", gsvMuted, false, "")
f_gsvStep(5, "Window", sWin, inPlan or st > 0 ? (inPlan ? gsP.window : gsS.window) : gsWinNow != "" ? gsvWinTxt + " · " + f_gsvLeft(gsvWinEnd - gsHm) : "closed · next " + f_gsvNextWin(gsHm))
f_gsvStep(6, "Fresh pool", sPool, inPlan ? gsP.pool : st > 0 ? gsS.pool : str.tostring(gsvFresh) + " of " + str.tostring(gsvPoolsToday) + " today")
f_gsvStep(7, "Sweep · close back inside", sSweep, inPlan ? gsP.pool + " · extreme " + f_gsvPx(gsP.extreme) : st >= 2 ? gsS.pool + " · extreme " + f_gsvPx(gsS.extreme) : st == 1 ? "raid under way" : "")
f_gsvStep(8, "Opposing gap", sGap, inPlan ? f_gsvPx(gsP.gapB) + "–" + f_gsvPx(gsP.gapT) : st >= 2 ? f_gsvPx(gsS.gapB) + "–" + f_gsvPx(gsS.gapT) : "")
f_gsvStep(9, "Flip close ≥ " + str.tostring(gsMinLeg, "0.0#") + " ATR from the extreme", sFlip, inPlan ? f_gsvAt(gsP.entryTime) : st == 3 ? "done" : "")
int r = 10
if gsvNearUp != "" or gsvNearDn != ""
f_gsvRow(r, "Nearest fresh", (gsvNearUp != "" ? "▲ " + gsvNearUp : "") + (gsvNearUp != "" and gsvNearDn != "" ? "\n" : "") + (gsvNearDn != "" ? "▼ " + gsvNearDn : ""), gsvMuted, gsvGold)
r += 1
if full
for p in gsPools
if r < 24
f_gsvRow(r, " " + p.name, f_gsvPx(p.price) + (p.fresh ? " · fresh" : " · taken"), gsvMuted, p.fresh ? gsvGold : gsvGrey)
r += 1
if inPlan
f_gsvWide(r, "PLAN · " + f_gsvSide(gsP.dir) + " · " + gsP.how + " · frozen at entry", gsvMuted, false, "Frozen at entry.")
f_gsvRow(r + 1, "Entry", f_gsvPx(gsP.entry) + " · " + str.tostring(bar_index - gsP.entryBar) + " bars ago", gsvMuted, gsvGold)
f_gsvRow(r + 2, "Stop · 1R", f_gsvPx(gsP.stop) + " · " + str.tostring(math.abs(close - gsP.stop), format.mintick) + " away", gsvMuted, gsvDown)
f_gsvRow(r + 3, "1R", f_gsvPx(gsP.tp1) + (gsP.tp1Hit ? " ✓" : " · " + str.tostring(math.abs(gsP.tp1 - close), format.mintick) + " away"), gsvMuted, gsvUp)
f_gsvRow(r + 4, "Target · " + str.tostring(gsTargetR, "0.0#") + "R", f_gsvPx(gsP.target) + " · " + str.tostring(math.abs(gsP.target - close), format.mintick) + " away", gsvMuted, gsvUp)
r += 5
else
f_gsvWide(r, f_gsvWrap("PLAN · " + gsvPlan, 58), gsvMuted, false, "")
r += 1
if gsTx.log != ""
f_gsvWide(r, f_gsvWrap("TODAY " + gsTx.log, 58), gsvInk, false, "New York times.")
r += 1
string counts = "THIS CHART " + str.tostring(gsSt.raids) + " raids · " + str.tostring(gsSt.sweeps) + " sweeps · " + str.tostring(gsSt.flips) + " flips · " + (gsSt.plans == 0 ? "no closed plan yet" : str.tostring(gsSt.plans) + " plans: target " + str.tostring(gsSt.nTarget) + " · stop " + str.tostring(gsSt.nStop) + " · time " + str.tostring(gsSt.nTime) + " · reached 1R " + str.tostring(gsSt.nTp1))
f_gsvWide(r, f_gsvWrap(counts, 58), gsvInk, false, "Counts of this chart's history. History on this chart, not a forecast.")
r += 1
if full
string avgTxt = gsSt.plans == 0 ? "Average: needs a closed plan" : "Average " + f_gsvR(gsSt.sumR / gsSt.plans) + " over " + str.tostring(gsSt.plans) + " plans after an estimated " + str.tostring(gsCost, "0.00") + " round trip" + (gsSt.plans >= gsPctMin ? " · " + str.tostring(math.round(100.0 * gsSt.wins / gsSt.plans)) + "% closed above 0R" : "")
f_gsvWide(r, f_gsvWrap(avgTxt, 58), gsvMuted, false, "Net R after the estimated cost in 04. A percentage appears only from " + str.tostring(gsPctMin) + " closed plans.")
string ended = "Ended before a plan: held " + str.tostring(gsSt.held) + " · no gap " + str.tostring(gsSt.noGap) + " · sweep failed " + str.tostring(gsSt.failed) + " · no flip " + str.tostring(gsSt.noFlip) + " · refused " + str.tostring(gsSt.noEntry)
f_gsvWide(r + 1, f_gsvWrap(ended, 58), gsvMuted, false, "")
r += 2
if gsvTfNote != ""
f_gsvWide(r, f_gsvWrap(gsvTfNote, 58), gsvGold, false, "The rule's windows and its 16:45 exit are set in minutes of the New York day.")
r += 1
f_gsvWide(r, "Rules, not odds · history, not a forecast · ProEA Lab", gsvMuted, false, "")
f_gsvDrawPilot() =>
table.clear(gsvPilotT, 0, 0, 0, 6)
if gsvPilot != "Off"
string why = "Above an old high and below an old low sit other traders' stops. A raid through such a level that closes back inside took that liquidity without accepting the new price. The flip, a close through the nearest opposing gap, is the sign that the move has turned; the sweep's extreme is where the idea is wrong."
string limits = "A rule, not a forecast. The pools are price levels, not an order book. Results are this chart's history after an estimated " + str.tostring(gsCost, "0.00") + " round trip; fills at your broker can differ."
int o = gsvPOff
if o == 1
table.cell(gsvPilotT, 0, 0, "", height = 14, bgcolor = na)
table.cell(gsvPilotT, 0, 0 + o, "CO-PILOT", text_color = gsvMuted, bgcolor = gsvPanel, text_size = size.tiny, text_halign = text.align_left)
table.cell(gsvPilotT, 0, 1 + o, f_gsvWrap("NOW · " + gsvNow, 60), text_color = gsvInk, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
if gsvPilot == "Standard"
table.cell(gsvPilotT, 0, 2 + o, f_gsvWrap("WHY · " + why, 60), text_color = gsvInk, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
table.cell(gsvPilotT, 0, 3 + o, f_gsvWrap("PLAN · " + gsvPlan, 60), text_color = gsvInk, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
if gsvPilot == "Standard"
table.cell(gsvPilotT, 0, 4 + o, f_gsvWrap("LIMITS · " + limits, 60), text_color = gsvMuted, bgcolor = gsvPanel, text_size = gsvSz, text_halign = text.align_left)
if gsvPilotCorner == "Bottom left" or gsvPilotCorner == "Bottom right"
table.cell(gsvPilotT, 0, 6, "", height = 5, bgcolor = na)
// ── chart layers: bounded handles, never further back than the history buffer (max_bars_back = 2000)
var array<box> gsvHB = array.new<box>()
var array<line> gsvHL = array.new<line>()
var array<label> gsvHT = array.new<label>()
var array<box> gsvLB = array.new<box>()
var array<line> gsvLL = array.new<line>()
var array<label> gsvLT = array.new<label>()
f_gsvClear(array<box> bs, array<line> ls, array<label> ts) =>
for b in bs
box.delete(b)
for l in ls
line.delete(l)
for x in ts
label.delete(x)
array.clear(bs)
array.clear(ls)
array.clear(ts)
int gsvTfMin = math.max(1, int(timeframe.in_seconds() / 60))
// Short names on the chart; the desk and the tooltips use the full ones.
f_gsvShort(string name) =>
name == "Asia high" ? "ASIA H" : name == "Asia low" ? "ASIA L" : name == "London high" ? "LDN H" : name == "London low" ? "LDN L" : name
// A pool's tag sits where its line ends: the candle that took it, or just right of the last candle while fresh.
f_gsvTagX(GSPool p) =>
na(p.takenBar) ? bar_index + 3 : math.max(p.takenBar, math.max(p.bornBar, bar_index - 1500))
// A tag is about eight candles wide: tags within 12 candles and 1 ATR of each other would print over each other,
// so the upper one moves above its line and the lower one below it (a middle one of three stays on its line).
f_gsvPlace(int pi, int oldestDay, array<int> tagDays) =>
GSPool p = array.get(gsPoolLog, pi)
int x = f_gsvTagX(p)
float near = nz(gsAtr, 0)
bool above = false
bool below = false
for [qi, q] in gsPoolLog
if qi != pi and q.day >= oldestDay and q.bornBar >= bar_index - 1500 and array.includes(tagDays, q.day) and math.abs(f_gsvTagX(q) - x) <= 12 and math.abs(q.price - p.price) <= near
if q.price > p.price or (q.price == p.price and qi > pi)
above := true
else
below := true
below and not above ? label.style_label_lower_left : above and not below ? label.style_label_upper_left : label.style_label_left
f_gsvDrawPool(GSPool p, bool today, string place) =>
int x1 = math.max(p.bornBar, bar_index - 1500)
int x2 = f_gsvTagX(p)
bool live = today and p.fresh
color c = live ? gsvGold : gsvGrey
array.push(gsvHL, line.new(x1, p.price, x2, p.price, color = color.new(c, live ? 10 : today ? 35 : 60), style = live ? line.style_solid : line.style_dotted, width = live ? 2 : 1))
if today
array.push(gsvHT, label.new(x2, p.price, f_gsvShort(p.name) + (p.fresh ? " " + f_gsvPx(p.price) : " ✕"), style = place, color = color.new(#10151F, 20), textcolor = color.new(c, p.fresh ? 0 : 20), size = size.small, tooltip = p.name + " " + f_gsvPx(p.price) + (p.fresh ? " · fresh: no bar has traded through it yet" : " · taken")))
f_gsvDrawSession(GSPool hi, GSPool lo, int mins, color c, string name) =>
int x2 = hi.bornBar - 1
int x1 = math.max(x2 - int(mins / gsvTfMin) + 1, bar_index - 1500)
if x2 > x1
array.push(gsvHB, box.new(x1, hi.price, x2, lo.price, border_color = color.new(c, 70), bgcolor = color.new(c, 92), text = name, text_color = color.new(c, 30), text_size = size.small, text_halign = text.align_left, text_valign = text.align_top))
f_gsvDrawPlan(GSPlan pl, bool isOpen) =>
int xa = math.max(pl.entryBar, bar_index - 1500)
int xb = isOpen ? bar_index + 2 : math.max(pl.exitBar, xa)
int xg = math.max(pl.gapBar - 2, bar_index - 1500)
if not na(pl.gapB) and pl.gapBar >= bar_index - 1500
array.push(gsvHB, box.new(xg, pl.gapT, math.max(pl.flipBar, xg), pl.gapB, border_color = color.new(gsvGold, 55), bgcolor = color.new(gsvGold, 85), text = "FLIP", text_color = color.new(gsvGold, 20), text_size = size.tiny, text_halign = text.align_right, text_valign = text.align_center))
if pl.sweepBar >= bar_index - 1500
array.push(gsvHT, label.new(pl.sweepBar, pl.extreme, "SWEPT " + pl.pool, style = pl.dir == -1 ? label.style_label_down : label.style_label_up, color = color.new(gsvGold, 20), textcolor = #0C131D, size = size.small, tooltip = "Raid of the " + pl.pool + " and a close back inside · extreme " + f_gsvPx(pl.extreme)))
if gsvZones
array.push(gsvHB, box.new(xa, math.max(pl.entry, pl.stop), xb, math.min(pl.entry, pl.stop), border_color = color.new(gsvDown, 100), bgcolor = color.new(gsvDown, isOpen ? 84 : 93)))
array.push(gsvHB, box.new(xa, math.max(pl.entry, pl.target), xb, math.min(pl.entry, pl.target), border_color = color.new(gsvUp, 100), bgcolor = color.new(gsvUp, isOpen ? 84 : 93)))
array.push(gsvHL, line.new(xa, pl.tp1, xb, pl.tp1, color = color.new(gsvUp, isOpen ? 35 : 65), style = line.style_dashed))
array.push(gsvHL, line.new(xa, pl.entry, xb, pl.entry, color = color.new(gsvGold, isOpen ? 10 : 50)))
if pl.entryBar >= bar_index - 1500
array.push(gsvHT, label.new(pl.entryBar, pl.entry, (pl.dir == 1 ? "▲ LONG " : "▼ SHORT ") + f_gsvPx(pl.entry), style = pl.dir == 1 ? label.style_label_up : label.style_label_down, color = color.new(pl.dir == 1 ? gsvUp : gsvDown, 15), textcolor = #0C131D, size = size.small, tooltip = pl.how + " after the " + pl.pool + " sweep · stop " + f_gsvPx(pl.stop) + " · 1R " + f_gsvPx(pl.tp1) + " · target " + f_gsvPx(pl.target)))
if not isOpen and pl.exitBar >= bar_index - 1500
bool above = pl.exitPrice >= pl.entry
color c = pl.netR > 0.05 ? gsvUp : pl.netR < -0.05 ? gsvDown : gsvGrey
array.push(gsvHT, label.new(pl.exitBar, pl.exitPrice, f_gsvEndMark(pl.ending, pl.netR) + " " + f_gsvEnding(pl.ending) + " " + f_gsvR(pl.netR), style = above ? label.style_label_down : label.style_label_up, color = color.new(c, 15), textcolor = #0C131D, size = size.small, tooltip = "Exit " + f_gsvPx(pl.exitPrice) + " · " + f_gsvR(pl.grossR) + " before costs, " + f_gsvR(pl.netR) + " after the estimated cost"))
f_gsvDrawHistory() =>
f_gsvClear(gsvHB, gsvHL, gsvHT)
// The trading days to draw: the latest gsvDays distinct day ids in the pool log.
int oldestDay = gsDay
int seen = 0
int lastDay = -1
int i = array.size(gsPoolLog) - 1
while i >= 0 and seen < gsvDays
GSPool p = array.get(gsPoolLog, i)
if p.day != lastDay
seen += 1
lastDay := p.day
oldestDay := p.day
i -= 1
// Pool names are written for today and for every drawn day that had a plan.
array<int> tagDays = array.from(gsDay)
for pl in gsDone
if pl.day >= oldestDay
array.push(tagDays, pl.day)
GSPool asiaHi = na
GSPool lonHi = na
for [pi, p] in gsPoolLog
if p.day >= oldestDay and p.bornBar >= bar_index - 1500
if gsvPools
bool tagged = array.includes(tagDays, p.day)
f_gsvDrawPool(p, tagged, tagged ? f_gsvPlace(pi, oldestDay, tagDays) : label.style_label_left)
if gsvSessions and p.name == "Asia high"
asiaHi := p
if gsvSessions and p.name == "Asia low" and not na(asiaHi)
f_gsvDrawSession(asiaHi, p, 240, gsvAsia, "ASIA")
if gsvSessions and p.name == "London high"
lonHi := p
if gsvSessions and p.name == "London low" and not na(lonHi)
f_gsvDrawSession(lonHi, p, 180, gsvGold, "LONDON")
int j = array.size(gsDone) - 1
while j >= 0
GSPlan pl = array.get(gsDone, j)
if pl.day < oldestDay or bar_index - pl.entryBar >= 1500
break
f_gsvDrawPlan(pl, false)
j -= 1
if gsP.dir != 0 and bar_index - gsP.entryBar < 1500
f_gsvDrawPlan(gsP, true)
if gsvMisses
int k = array.size(gsEvBar) - 1
int marks = 0
while k >= 0 and marks < 40
int eb = array.get(gsEvBar, k)
if bar_index - eb >= 1500
break
string w = array.get(gsEvWhat, k)
if w == "HELD" or w == "NO GAP" or w == "SWEEP FAILED" or w == "NO FLIP" or w == "NO RETEST" or str.startswith(w, "NO ENTRY")
array.push(gsvHT, label.new(eb, array.get(gsEvPx, k), "◌", style = label.style_label_center, color = color.new(#000000, 100), textcolor = color.new(gsvGrey, 10), size = size.tiny, tooltip = str.lower(w) + " · the setup ended without a plan"))
marks += 1
k -= 1
f_gsvDrawLive() =>
f_gsvClear(gsvLB, gsvLL, gsvLT)
if gsP.dir == 0 and gsS.state != 0
int x1 = math.max(gsS.raidBar, bar_index - 1500)
color c = gsS.dir == -1 ? gsvDown : gsvUp
array.push(gsvLL, line.new(x1, gsS.extreme, bar_index + 3, gsS.extreme, color = color.new(c, 20), width = 2))
array.push(gsvLT, label.new(bar_index + 3, gsS.extreme, (gsS.state == 1 ? "RAID " : "SWEPT ") + gsS.pool + " · extreme " + f_gsvPx(gsS.extreme), style = label.style_label_left, color = color.new(#10151F, 10), textcolor = c, size = size.small))
if gsS.state >= 2 and not na(gsS.gapB)
int xg = math.max(gsS.gapBar - 2, bar_index - 1500)
array.push(gsvLB, box.new(xg, gsS.gapT, bar_index + 3, gsS.gapB, border_color = color.new(gsvGold, 20), border_style = line.style_dashed, bgcolor = color.new(gsvGold, 85), text = gsS.state == 2 ? "GAP TO FLIP" : "FLIPPED · RETEST", text_color = color.new(gsvGold, 10), text_size = size.tiny, text_halign = text.align_right, text_valign = text.align_center))
if gsS.state == 3
array.push(gsvLL, line.new(x1, gsS.stop, bar_index + 3, gsS.stop, color = color.new(gsvDown, 30), style = line.style_dashed))
// Draw on the last historical bar and on every realtime execution. A strategy never runs on the forming bar, so
// barstate.islast alone would leave the Strategy Lab blank while the market is open.
if barstate.islastconfirmedhistory or barstate.isrealtime
if barstate.islastconfirmedhistory or barstate.isconfirmed
f_gsvDrawHistory()
f_gsvDrawLive()
f_gsvDrawDesk()
f_gsvDrawPilot()
barcolor(gsvTint and gsP.dir != 0 ? color.new(gsP.dir == 1 ? gsvUp : gsvDown, 15) : na, title = "Plan tint")
Pools: yesterday's high and low (PDH, PDL), the Asia range (20:00–24:00 New York) and the London range (02:00–05:00). Each is fresh until a candle trades through it, window or not.
Sweep: in a window, a candle reaches at least 0.05 ATR beyond a fresh pool and a close comes back inside on that candle or within the three after it.
Flip: a close through the nearest opposing fair value gap born in the thirty candles before the sweep, at least one ATR from the sweep's extreme, within twelve candles. A new wick beyond the extreme moves the extreme; a close back beyond the swept level ends the setup.
Plan: the entry is the flip close; the stop sits 0.1 ATR beyond the sweep's extreme; the target is twice the risk. The plan is skipped if the stop is wider than six ATR, or if the entry comes at 16:45 or later. The levels are fixed when the plan is made.
Result: on every closed candle the stop is checked first, then the target; otherwise the plan closes at 16:45 New York, before gold's daily break.
Every time on the desk is New York time by the candle's open, whatever your chart shows. Gold's day runs 18:00 → 17:00. Candles while spot gold is closed (Friday 17:00 to Sunday 18:00, and the daily 17:00–18:00 break) are skipped entirely, so a spot chart and a 24-hour gold feed show the same setups.
A setup that ended before a plan: held beyond the level (no close back inside in time), no opposing gap to flip, a failed sweep (a close back beyond the level before the flip), or no flip within twelve candles. The mark's tooltip names which.
The Strategy Lab is a separate script for TradingView's Strategy Tester. It runs the same plans, with the stop and target placed as orders at the entry and a commission that matches the desk's cost estimate.
Yes: the indicator's alerts carry each event as it happens, in Text or JSON. Alerts never place orders.
Two studies on 2026 gold data, with every candidate and period written down before its result. Sixteen rule variants in all: none passed, and the one selected failed its check. So the defaults are design choices, not tested winners. The desk's own record on your chart counts that chart's history; it is not a forecast. Read what we measured, every candidate and period included.
Every decision reads closed candles. Changing the symbol, timeframe, feed or inputs recalculates the history. The desk sees chart candles, not news or your broker's fills, and fills at your broker can differ. It is research tooling, not financial advice.
What shipped and what changed in Gold Sweep Desk, newest first. Ask for the next change below; what gets built lands here with a date, and on the lab queue on the front page.
Gold Sweep Desk v1.1.0 Shipped
Gold's daily liquidity, the sweep, the flip and the plan, in words.
No setting earned a tested default Lab note Gold Sweep Desk
Two studies on 2026 gold data, registered in advance: no setting earned a place as a tested default, so the defaults are design choices.
A small friction or a big idea. Tell us what would make this tool work better for you.
Requests for a new tool go on the same queue: see what is asked for and what is being built.