Advertisement IC Markets

Stop-run reclaim on XAUUSD M1: how I define a valid reclaim candle

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Stop-run reclaim on XAUUSD M1: how I define a valid reclaim candle

Post by PTScalper »

Porting this to cTrader (cAlgo) is structurally different because C# evaluates the Calculate(int index) method on every incoming tick for the live bar.

To prevent the indicator from repainting or flashing permanent signals on unfinished bars, this implementation anchors the sweep candle to index - 1 (the last closed bar) and evaluates the confirmation logic dynamically on index (the live forming bar).

It exposes BullBuffer and BearBuffer as standard IndicatorDataSeries so you can hook a cBot directly into it, while using precise Chart.DrawIcon and Chart.DrawTrendLine objects to keep the visual chart clean.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Stop-run reclaim on XAUUSD M1: how I define a valid reclaim candle

Post by PTScalper »

Version for Ctrader:

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo.Indicators
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class M1LiquidityReclaim : Indicator
    {
        [Parameter("Swing Left (Bars)", DefaultValue = 15, Group = "Market Structure")]
        public int LeftBars { get; set; }

        [Parameter("Swing Right (Bars)", DefaultValue = 5, Group = "Market Structure")]
        public int RightBars { get; set; }

        [Parameter("Min Reclaim % of Candle Range", DefaultValue = 0.35, Step = 0.05, Group = "Reclaim Mechanics")]
        public double ReclaimPct { get; set; }

        [Parameter("Require Next Candle Confirmation", DefaultValue = true, Group = "Reclaim Mechanics")]
        public bool RequireConf { get; set; }

        [Parameter("Filter by Session", DefaultValue = true, Group = "Session")]
        public bool UseSession { get; set; }

        [Parameter("Session Start (HH:mm)", DefaultValue = "08:00", Group = "Session")]
        public string SessionStart { get; set; }

        [Parameter("Session End (HH:mm)", DefaultValue = "16:00", Group = "Session")]
        public string SessionEnd { get; set; }

        // Outputs exposed for cBot algorithmic integration
        [Output("Bullish Reclaim", LineColor = "DodgerBlue", PlotType = PlotType.Points, Thickness = 3)]
        public IndicatorDataSeries BullBuffer { get; set; }

        [Output("Bearish Reclaim", LineColor = "Red", PlotType = PlotType.Points, Thickness = 3)]
        public IndicatorDataSeries BearBuffer { get; set; }

        private TimeSpan _start;
        private TimeSpan _end;

        protected override void Initialize()
        {
            TimeSpan.TryParse(SessionStart, out _start);
            TimeSpan.TryParse(SessionEnd, out _end);
        }

        public override void Calculate(int index)
        {
            // Initialize empty buffers to prevent false signals in cBots
            BullBuffer[index] = double.NaN;
            BearBuffer[index] = double.NaN;

            string lineNameL = "L_Purge_Line_" + index;
            string iconNameL = "L_Purge_Icon_" + index;
            string lineNameH = "H_Purge_Line_" + index;
            string iconNameH = "H_Purge_Icon_" + index;

            // Wait for enough history to form the left/right pivots + the 2-candle setup
            if (index < LeftBars + RightBars + 2)
                return;

            if (!InSession(Bars.OpenTimes[index]))
            {
                RemoveObjectIfExists(lineNameL); RemoveObjectIfExists(iconNameL);
                RemoveObjectIfExists(lineNameH); RemoveObjectIfExists(iconNameH);
                return;
            }

            int sweepIdx = index - 1;
            double c1_high = Bars.HighPrices[sweepIdx];
            double c1_low = Bars.LowPrices[sweepIdx];
            double c1_close = Bars.ClosePrices[sweepIdx];
            
            double c1_range = c1_high - c1_low;
            if (c1_range == 0) c1_range = Symbol.PipSize;

            double lastPL = GetLastPivotLow(sweepIdx);
            double lastPH = GetLastPivotHigh(sweepIdx);

            bool isBullSetup = false;
            bool isBearSetup = false;

            // 1. Evaluate Bullish Purge
            if (!double.IsNaN(lastPL) && c1_low < lastPL && c1_close > lastPL)
            {
                double bullCloseDist = c1_close - lastPL;
                bool validBullClose = bullCloseDist >= (c1_range * ReclaimPct);
                
                // Confirming candle (live forming) must hold level and close favorably
                bool bullConf = Bars.ClosePrices[index] > lastPL && Bars.ClosePrices[index] >= c1_close;

                if (validBullClose && (!RequireConf || bullConf))
                {
                    isBullSetup = true;
                    BullBuffer[index] = Bars.LowPrices[index] - (Symbol.PipSize * 5);
                    
                    var line = Chart.DrawTrendLine(lineNameL, Bars.OpenTimes[sweepIdx], lastPL, Bars.OpenTimes[index], lastPL, Color.DodgerBlue);
                    line.LineStyle = LineStyle.Lines;
                    
                    Chart.DrawIcon(iconNameL, ChartIconType.UpArrow, Bars.OpenTimes[index], Bars.LowPrices[index] - (Symbol.PipSize * 15), Color.DodgerBlue);
                }
            }

            // 2. Evaluate Bearish Purge
            if (!double.IsNaN(lastPH) && c1_high > lastPH && c1_close < lastPH)
            {
                double bearCloseDist = lastPH - c1_close;
                bool validBearClose = bearCloseDist >= (c1_range * ReclaimPct);
                bool bearConf = Bars.ClosePrices[index] < lastPH && Bars.ClosePrices[index] <= c1_close;

                if (validBearClose && (!RequireConf || bearConf))
                {
                    isBearSetup = true;
                    BearBuffer[index] = Bars.HighPrices[index] + (Symbol.PipSize * 5);

                    var line = Chart.DrawTrendLine(lineNameH, Bars.OpenTimes[sweepIdx], lastPH, Bars.OpenTimes[index], lastPH, Color.Red);
                    line.LineStyle = LineStyle.Lines;

                    Chart.DrawIcon(iconNameH, ChartIconType.DownArrow, Bars.OpenTimes[index], Bars.HighPrices[index] + (Symbol.PipSize * 15), Color.Red);
                }
            }

            // Intra-bar cleanup: if price fluctuates and invalidates the confirmation, remove the visual objects
            if (!isBullSetup) 
            {
                RemoveObjectIfExists(lineNameL);
                RemoveObjectIfExists(iconNameL);
            }
            if (!isBearSetup) 
            {
                RemoveObjectIfExists(lineNameH);
                RemoveObjectIfExists(iconNameH);
            }
        }

        private void RemoveObjectIfExists(string name)
        {
            var obj = Chart.FindObject(name);
            if (obj != null)
                Chart.RemoveObject(name);
        }

        private bool InSession(DateTime time)
        {
            if (!UseSession) return true;
            
            TimeSpan current = time.TimeOfDay;
            if (_start < _end)
                return current >= _start && current <= _end;
            else // Handles crossover periods (e.g., 22:00 to 02:00)
                return current >= _start || current <= _end;
        }

        // Dynamically search backward for the most recent valid Pivot Low
        private double GetLastPivotLow(int sweepIdx)
        {
            for (int p = sweepIdx - RightBars - 1; p >= LeftBars; p--)
            {
                bool isPivot = true;
                
                for (int k = 1; k <= LeftBars; k++)
                    if (Bars.LowPrices[p - k] <= Bars.LowPrices[p]) { isPivot = false; break; }
                
                if (!isPivot) continue;

                for (int k = 1; k <= RightBars; k++)
                    if (Bars.LowPrices[p + k] <= Bars.LowPrices[p]) { isPivot = false; break; }
                
                if (isPivot) return Bars.LowPrices[p];
            }
            return double.NaN;
        }

        // Dynamically search backward for the most recent valid Pivot High
        private double GetLastPivotHigh(int sweepIdx)
        {
            for (int p = sweepIdx - RightBars - 1; p >= LeftBars; p--)
            {
                bool isPivot = true;
                
                for (int k = 1; k <= LeftBars; k++)
                    if (Bars.HighPrices[p - k] >= Bars.HighPrices[p]) { isPivot = false; break; }
                
                if (!isPivot) continue;

                for (int k = 1; k <= RightBars; k++)
                    if (Bars.HighPrices[p + k] >= Bars.HighPrices[p]) { isPivot = false; break; }
                
                if (isPivot) return Bars.HighPrices[p];
            }
            return double.NaN;
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
LondonScalper
Posts: 701
Joined: Sat Sep 05, 2026 7:54 am

Re: Stop-run reclaim on XAUUSD M1: how I define a valid reclaim candle

Post by LondonScalper »

PTScalper wrote:For a reclaim to be valid, the candle must close back through the level by at least 25% to 30% of its own total range. On M1, the immediate following candle is the ultimate filter against imaginary reclaims.
That is the standard I wanted written down. A one-tick courtesy close on gold is unfinished business — wick-only "reclaims" are how early liquidity gets trapped. Demanding roughly a quarter of the candle's range back through the swept level proves displacement, not a pause in a continuing run. Requiring the next M1 bar to hold the level (ideally trading past the sweep candle's extreme) filters the imaginary ones before size is on.

Session context and news still sit above the candle rules. Tier-1 resets the book; raw microstructure means little while spreads are dishonest. From this London desk I also refuse reclaim tickets in the first minutes of cash open when the book is theatrical, even if the candle math looks perfect.

Desk rule: close depth + next-bar hold + spread inside cap — miss any one and the reclaim is invalid.

Do you measure the 25–30% from the full wick-to-wick range, or from body only when the sweep wick is extreme?
LondonNewsTrader
Posts: 79
Joined: Mon Sep 21, 2026 9:30 am

Re: Stop-run reclaim on XAUUSD M1: how I define a valid reclaim candle

Post by LondonNewsTrader »

PTScalper wrote:Your session constraint is the most critical filter. A perfect technical setup during the London open initial balance is often just engineered liquidity for the actual move at 09:30 or 10:00. Here is a refactored, production-ready Pine Script.
A couple of things I'd look at before trusting the signals.

The pivots use 15 bars left and 5 right, so a swing low only becomes last_pl five M1 bars after it printed. On gold that's usually fine, but a fast stop run that comes back within those five minutes won't be seen, because the level didn't exist yet when it was swept.

More important: last_pl isn't cleared once it's been swept. After the first valid reclaim, the next candle that dips under the same level and closes above it can fire again, and on a choppy morning you can get three or four signals off one pool. Setting last_pl to na after a confirmed reclaim, or storing a consumed flag, keeps it to one trade per level, which is closer to what LondonScalper describes.

The session string '0800-1100,1300-1600' is read in the chart's exchange timezone unless you pass one explicitly. Depending on the gold feed that might be UTC or New York, which shifts the windows by hours. Passing "Europe/London" as the third argument to time() makes it do what the label says.

0.35 is a sensible default for the reclaim fraction. I'd test 0.25 to 0.5 across a CPI week and a quiet week separately, because the distribution of candle ranges is completely different.
Last edited by LondonNewsTrader on Sat Sep 26, 2026 12:13 pm, edited 1 time in total.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Stop-run reclaim on XAUUSD M1: how I define a valid reclaim candle

Post by PTScalper »

LondonNewsTrader wrote: Thu Sep 24, 2026 8:12 am
PTScalper wrote:Your session constraint is the most critical filter. A perfect technical setup during the London open initial balance is often just engineered liquidity for the actual move at 09:30 or 10:00. Here is a refactored, production-ready Pine Script.
Thread take for Stop-run reclaim on XAUUSD M1: how I define a valid reclaim cand: keep the catalyst list next to the chart or the idea is incomplete.

Execution detail beats a prettier curve. Broker clock drift has cancelled more stacks for me than bad signal logic. Session gates live in the same place as entries.

Alerts can carry SL/TP text; they still sit behind a human confirm when a red folder is live.

Research curves that need fantasy spreads stay in research.

PTScalper’s practical framing fits a news desk when the calendar is treated as market structure, not a footnote.

What ticket cap do you use on London-only days versus full overlap for ideas like this?
Hi LondonNewsTrader,

For M1 XAUUSD stop-run reclaims, the ticket cap must strictly reflect the volume profile of the session to prevent algorithmic churning when the market regime shifts.

London-Only: 2 to 3 Tickets

Gold often lacks the sustained volume during the pure London session to validate multiple sequential M1 sweeps. If the first two stop-run reclaims fail, the microstructure isn't conducive to the strategy—it’s likely settling into low-liquidity chop or a slow, grinding directional trend where mean-reversion gets continually run over. Once you hit three, the session is dead for that specific logic.

Full Overlap (London + NY): 3 to 5 Tickets

The intersection of NY liquidity, the US open, and major macroeconomic data drops creates the extreme volatility needed for highly profitable stop-runs. This window naturally produces more genuine liquidity sweeps (e.g., the initial red-folder reaction sweep, followed by a secondary structural sweep). You need slightly more capacity to execute, but a 5-ticket ceiling acts as a necessary circuit breaker against a purely directional, one-sided trend day.

Execution Constraints to Support the Cap:

Spread-Aware Gating: Since you are already filtering out "fantasy spreads," the script’s session gates must freeze the ticket count if spreads cross your maximum threshold during the overlap. This prevents widened spreads around red folders from burning through your daily cap on alerts you'd never manually confirm anyway.

Time-Syncing the Reset: To combat broker clock drift affecting your session gates, ensure the script's daily ticket reset and overlap activation rely on a strict server time offset (or an external NTP sync in C#/MQL), rather than relying entirely on the broker's tick data timestamps which can lag during high-frequency volatility spikes.

Do you tie your overlap ticket cap to a hard daily drawdown limit, or do you strictly limit it by the raw trade count?
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply