Advertisement IC Markets

XAUUSD range days: my rule for not inventing breakouts on M5

Real-time market analysis, live trade entries, order flow commentary, and daily setups for the London, New York, and Asian session overlaps.
LondonScalper
Posts: 701
Joined: Sat Sep 05, 2026 7:54 am

XAUUSD range days: my rule for not inventing breakouts on M5

Post by LondonScalper »

Gold process for days that refuse to trend — evergreen, not a price target.

On XAUUSD, range days are where I used to donate. M5 looks like it’s “about to go,” I invent a breakout, and the metal mean-reverts through my stop while spreads stay lively enough to punish.

Range-day rule
If the morning has already failed two breakout attempts beyond the established balance (my marked high/low), I switch mode: fade extremes back to mid only, half size, or flat. I do not take the third “this time it runs” breakout without a clear session-structure change (news impulse that actually holds, or a higher-timeframe level break with follow-through beyond a time stop).

What counts as inventing
Entering because a candle closed “strong” inside a well-worn range. Moving the breakout line to justify a late entry. Widening the stop so the fake-out can “breathe.”

I’ll still scalp inside the range if spreads are acceptable and the mid is honest. I just stop paying for escape velocity that isn’t there.

Journal tag: xau_range_mode. Looking back, those days should show smaller size and fewer tickets — if they don’t, I ignored my own rule.

How do you decide a gold day is a range day early enough to matter, without waiting until you’ve already taken the fake-outs?
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: XAUUSD range days: my rule for not inventing breakouts on M5

Post by PTScalper »

LondonScalper wrote: Sat Sep 12, 2026 9:09 pm Gold process for days that refuse to trend — evergreen, not a price target.

On XAUUSD, range days are where I used to donate. M5 looks like it’s “about to go,” I invent a breakout, and the metal mean-reverts through my stop while spreads stay lively enough to punish.

Range-day rule
If the morning has already failed two breakout attempts beyond the established balance (my marked high/low), I switch mode: fade extremes back to mid only, half size, or flat. I do not take the third “this time it runs” breakout without a clear session-structure change (news impulse that actually holds, or a higher-timeframe level break with follow-through beyond a time stop).

What counts as inventing
Entering because a candle closed “strong” inside a well-worn range. Moving the breakout line to justify a late entry. Widening the stop so the fake-out can “breathe.”

I’ll still scalp inside the range if spreads are acceptable and the mid is honest. I just stop paying for escape velocity that isn’t there.

Journal tag: xau_range_mode. Looking back, those days should show smaller size and fewer tickets — if they don’t, I ignored my own rule.

How do you decide a gold day is a range day early enough to matter, without waiting until you’ve already taken the fake-outs?
Hi LondonScalper,

Solid rule set. We’ve all paid the XAUUSD range-day tax, buying that M5 “escape velocity” that instantly turns into a liquidity sweep.

To answer your question on how to spot a range day early without paying for the fake-outs first, you have to look outside the M5 and rely on raw price action and higher-timeframe structure:

The D1/M15 Squeeze: Look at the daily structure before London opens. If D1 is trapped between heavy, established supply and demand zones, or yesterday printed an inside day/tight doji, the probability of chop skyrockets. The M15 will usually show price ping-ponging without creating fresh structural highs or lows.

The London Handoff Failure: The Asian session builds the initial balance. If the London open (which usually injects the volume) fails to decisively displace and hold outside that Asian range within the first 60 to 90 minutes, the market is usually signaling a lack of institutional sponsorship for a trend.

ADR Exhaustion: If Gold moved 150% of its Average Daily Range yesterday, today is statistically likely to be a mean-reverting consolidation day.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: XAUUSD range days: my rule for not inventing breakouts on M5

Post by PTScalper »

To help systematize your exact xau_range_mode rule, I wrote a Pine Script that automates the tracking. It defines a morning balance period, watches the M5 for failed breakouts (both wicks that get rejected, and closes outside that fail to hold), counts them, and visually switches the chart to "Range Mode" once you hit two strikes.

Code: Select all

//@version=5
indicator("Gold Range Mode Detector", overlay=true, max_labels_count=50)

// --- Inputs ---
balanceTime = input.session("0000-0800", "Morning Balance Time (Exchange TZ)")
rangeColor  = input.color(color.new(color.orange, 90), "Range Mode Bg Color")

// --- Core Logic ---
inSession = time(timeframe.period, balanceTime)
newSession = inSession and not inSession[1]
postSession = not inSession and not na(time)

var float balHigh = na
var float balLow = na
var float balMid = na
var int fakeouts = 0
var int pos = 0 // 0 = inside, 1 = above, -1 = below

// Reset at the start of the balance forming session
if newSession
    balHigh := high
    balLow := low
    fakeouts := 0
    pos := 0

// Build the established balance range
if inSession
    balHigh := math.max(balHigh, high)
    balLow := math.min(balLow, low)
    balMid := math.avg(balHigh, balLow)

// Detect failed breakouts after the balance session
isPostSession = not inSession and not na(balHigh)

if isPostSession
    // Check current bar position relative to range based on close
    currentPos = close > balHigh ? 1 : close < balLow ? -1 : 0
    
    // Strike 1: We closed outside previously, but just closed back inside (failed hold)
    if currentPos == 0 and pos != 0
        fakeouts += 1
        
    // Strike 2: We were inside, wicked outside the boundary, but closed inside on this exact bar
    if currentPos == 0 and pos == 0
        if high > balHigh or low < balLow
            fakeouts += 1
            
    pos := currentPos

// --- Visuals ---
rangeModeActive = fakeouts >= 2 and isPostSession

// Plot the balance lines (only visible after session finishes to avoid mid-session clutter)
plot(isPostSession ? balHigh : na, color=color.new(color.red, 40), style=plot.style_linebr, title="Balance High")
plot(isPostSession ? balLow : na, color=color.new(color.green, 40), style=plot.style_linebr, title="Balance Low")
plot(isPostSession ? balMid : na, color=color.new(color.gray, 60), style=plot.style_linebr, title="Balance Mid")

// Alert background
bgcolor(rangeModeActive ? rangeColor : na, title="Range Mode Active")

// Drop a label on the exact bar the rule triggers
if rangeModeActive and not rangeModeActive[1]
    label.new(bar_index, high, "Range Mode\nActive", color=color.orange, textcolor=color.white, style=label.style_label_down)
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: XAUUSD range days: my rule for not inventing breakouts on M5

Post by PTScalper »

Drop that Pine script onto an M5 chart and adjust the balanceTime to fit your broker's timezone. The background will shift orange the moment that second fake-out closes back inside the established balance. It gives you the immediate, objective visual cue to halve your size and just fade the extremes back to the grey mid-line, entirely eliminating the temptation to "invent" that third breakout.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: XAUUSD range days: my rule for not inventing breakouts on M5

Post by PTScalper »

Here is the full implementation for both MT4 (MQL4) and MT5 (MQL5).

Both versions replicate the exact Pine Script logic: they track the morning balance window, monitor for both wick-rejections and closes back inside, count the fake-outs, plot your balance lines (High, Low, Mid), print an orange trigger arrow on strike two, and display live session telemetry in the chart corner.

1. MT4 Version (MQL4)
Save this file as GoldRangeMode.mq4 in your MT4 MQL4/Indicators/ folder.

Code: Select all

//+------------------------------------------------------------------+
//|                                               GoldRangeMode.mq4  |
//|                         XAUUSD Range Mode & Balance Detector     |
//+------------------------------------------------------------------+
#property copyright "Trading Community"
#property link      ""
#property version   "1.00"
#property strict
#property indicator_chart_window

#property indicator_buffers 4
#property indicator_color1  clrCrimson
#property indicator_width1  2
#property indicator_style1  STYLE_SOLID

#property indicator_color2  clrMediumSeaGreen
#property indicator_width2  2
#property indicator_style2  STYLE_SOLID

#property indicator_color3  clrDarkGray
#property indicator_width3  1
#property indicator_style3  STYLE_DOT

#property indicator_color4  clrDarkOrange
#property indicator_width4  3
#property indicator_type4   DRAW_ARROW

// --- Inputs ---
input int      InpStartHour     = 0;       // Balance Start Hour (Server Time)
input int      InpStartMinute   = 0;       // Balance Start Minute
input int      InpEndHour       = 8;       // Balance End Hour (Server Time)
input int      InpEndMinute     = 0;       // Balance End Minute
input int      InpLookbackBars  = 2500;    // History bars to evaluate
input bool     InpEnableAlert   = true;    // Pop-up Alert on Strike 2

// --- Buffers ---
double HighBuffer[];
double LowBuffer[];
double MidBuffer[];
double SignalBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   IndicatorDigits(Digits);

   SetIndexBuffer(0, HighBuffer);
   SetIndexLabel(0, "Balance High");
   SetIndexEmptyValue(0, EMPTY_VALUE);

   SetIndexBuffer(1, LowBuffer);
   SetIndexLabel(1, "Balance Low");
   SetIndexEmptyValue(1, EMPTY_VALUE);

   SetIndexBuffer(2, MidBuffer);
   SetIndexLabel(2, "Balance Mid");
   SetIndexEmptyValue(2, EMPTY_VALUE);

   SetIndexBuffer(3, SignalBuffer);
   SetIndexLabel(3, "Range Mode Trigger");
   SetIndexEmptyValue(3, EMPTY_VALUE);
   SetIndexArrow(3, 234); // Down arrow symbol

   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   Comment("");
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total < 10) return 0;

   int startIdx = MathMin(rates_total - 1, InpLookbackBars);

   // State tracking across chronological bars
   int lastDay = -1;
   double balHigh = 0;
   double balLow = 0;
   double balMid = 0;
   int fakeouts = 0;
   int pos = 0; // 0 = inside, 1 = above, -1 = below
   bool alertFiredToday = false;

   int startMinutes = InpStartHour * 60 + InpStartMinute;
   int endMinutes   = InpEndHour * 60 + InpEndMinute;

   // MT4 indexes 0 as the newest bar, so chronological iteration runs downwards to 0
   for(int i = startIdx; i >= 0; i--)
   {
      HighBuffer[i]   = EMPTY_VALUE;
      LowBuffer[i]    = EMPTY_VALUE;
      MidBuffer[i]    = EMPTY_VALUE;
      SignalBuffer[i] = EMPTY_VALUE;

      MqlDateTime dt;
      TimeToStruct(time[i], dt);
      int barMinutes = dt.hour * 60 + dt.min;

      // New day reset
      if(dt.day_of_year != lastDay)
      {
         lastDay = dt.day_of_year;
         balHigh = 0;
         balLow = 0;
         balMid = 0;
         fakeouts = 0;
         pos = 0;
         alertFiredToday = false;
      }

      bool inSession   = (barMinutes >= startMinutes && barMinutes < endMinutes);
      bool postSession = (barMinutes >= endMinutes);

      // 1. Build Morning Balance
      if(inSession)
      {
         if(balHigh == 0 || high[i] > balHigh) balHigh = high[i];
         if(balLow  == 0 || low[i]  < balLow)  balLow  = low[i];
         balMid = (balHigh + balLow) / 2.0;
      }

      // 2. Post-Session Monitoring
      if(postSession && balHigh > 0 && balLow > 0)
      {
         HighBuffer[i] = balHigh;
         LowBuffer[i]  = balLow;
         MidBuffer[i]  = balMid;

         int currentPos = (close[i] > balHigh) ? 1 : (close[i] < balLow ? -1 : 0);

         // Check failed breakouts
         if(currentPos == 0 && pos != 0)
         {
            fakeouts++; // Closed outside previously, now failed back in
         }
         else if(currentPos == 0 && pos == 0)
         {
            if(high[i] > balHigh || low[i] < balLow)
               fakeouts++; // Wicked outside boundary but closed inside
         }

         pos = currentPos;

         // Trigger on Strike 2
         if(fakeouts >= 2)
         {
            if(fakeouts == 2 && SignalBuffer[i+1] == EMPTY_VALUE)
            {
               SignalBuffer[i] = high[i] + (20 * Point);

               if(i == 0 && !alertFiredToday && InpEnableAlert)
               {
                  Alert(StringFormat("XAUUSD: Range Mode Active! 2 failed breakouts recorded. Balance: %.2f - %.2f", balHigh, balLow));
                  alertFiredToday = true;
               }
            }
         }
      }
   }

   // Telemetry on live bar
   string modeText = (fakeouts >= 2) ? "RANGE MODE ACTIVE (Fade Extremes / Half Size)" : "NORMAL / MONITORING";
   Comment(StringFormat("\n--- XAUUSD Balance Monitor ---\nBalance: High: %.2f | Low: %.2f | Mid: %.2f\nFailed Breakouts: %d / 2\nStatus: %s\n",
           balHigh, balLow, balMid, fakeouts, modeText));

   return(rates_total);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: XAUUSD range days: my rule for not inventing breakouts on M5

Post by PTScalper »

2. MT5 Version (MQL5)

Save this file as GoldRangeMode.mq5 in your MT5 MQL5/Indicators/ folder.

Code: Select all

//+------------------------------------------------------------------+
//|                                               GoldRangeMode.mq5  |
//|                         XAUUSD Range Mode & Balance Detector     |
//+------------------------------------------------------------------+
#property copyright "Trading Community"
#property link      ""
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots   4

#property indicator_label1  "Balance High"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrCrimson
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

#property indicator_label2  "Balance Low"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrMediumSeaGreen
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

#property indicator_label3  "Balance Mid"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrDarkGray
#property indicator_style3  STYLE_DOT
#property indicator_width3  1

#property indicator_label4  "Range Mode Trigger"
#property indicator_type4   DRAW_ARROW
#property indicator_color4  clrDarkOrange
#property indicator_width4  3

// --- Inputs ---
input int      InpStartHour     = 0;       // Balance Start Hour (Server Time)
input int      InpStartMinute   = 0;       // Balance Start Minute
input int      InpEndHour       = 8;       // Balance End Hour (Server Time)
input int      InpEndMinute     = 0;       // Balance End Minute
input int      InpLookbackBars  = 2500;    // History bars to evaluate
input bool     InpEnableAlert   = true;    // Pop-up Alert on Strike 2

// --- Buffers ---
double HighBuffer[];
double LowBuffer[];
double MidBuffer[];
double SignalBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, HighBuffer, INDICATOR_DATA);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   SetIndexBuffer(1, LowBuffer, INDICATOR_DATA);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   SetIndexBuffer(2, MidBuffer, INDICATOR_DATA);
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   SetIndexBuffer(3, SignalBuffer, INDICATOR_DATA);
   PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(3, PLOT_ARROW, 234); // Down arrow symbol

   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   Comment("");
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total < 10) return 0;

   int startIdx = MathMax(0, rates_total - InpLookbackBars);

   int lastDay = -1;
   double balHigh = 0;
   double balLow = 0;
   double balMid = 0;
   int fakeouts = 0;
   int pos = 0;
   bool alertFiredToday = false;

   int startMinutes = InpStartHour * 60 + InpStartMinute;
   int endMinutes   = InpEndHour * 60 + InpEndMinute;

   // MT5 arrays run chronologically from 0 (oldest) to rates_total - 1 (newest)
   for(int i = startIdx; i < rates_total; i++)
   {
      HighBuffer[i]   = EMPTY_VALUE;
      LowBuffer[i]    = EMPTY_VALUE;
      MidBuffer[i]    = EMPTY_VALUE;
      SignalBuffer[i] = EMPTY_VALUE;

      MqlDateTime dt;
      TimeToStruct(time[i], dt);
      int barMinutes = dt.hour * 60 + dt.min;

      // New day reset
      if(dt.day_of_year != lastDay)
      {
         lastDay = dt.day_of_year;
         balHigh = 0;
         balLow = 0;
         balMid = 0;
         fakeouts = 0;
         pos = 0;
         alertFiredToday = false;
      }

      bool inSession   = (barMinutes >= startMinutes && barMinutes < endMinutes);
      bool postSession = (barMinutes >= endMinutes);

      // 1. Build Morning Balance
      if(inSession)
      {
         if(balHigh == 0 || high[i] > balHigh) balHigh = high[i];
         if(balLow  == 0 || low[i]  < balLow)  balLow  = low[i];
         balMid = (balHigh + balLow) / 2.0;
      }

      // 2. Post-Session Monitoring
      if(postSession && balHigh > 0 && balLow > 0)
      {
         HighBuffer[i] = balHigh;
         LowBuffer[i]  = balLow;
         MidBuffer[i]  = balMid;

         int currentPos = (close[i] > balHigh) ? 1 : (close[i] < balLow ? -1 : 0);

         if(currentPos == 0 && pos != 0)
         {
            fakeouts++;
         }
         else if(currentPos == 0 && pos == 0)
         {
            if(high[i] > balHigh || low[i] < balLow)
               fakeouts++;
         }

         pos = currentPos;

         if(fakeouts >= 2)
         {
            if(fakeouts == 2 && i > 0 && SignalBuffer[i-1] == EMPTY_VALUE)
            {
               SignalBuffer[i] = high[i] + (20 * _Point);

               if(i == rates_total - 1 && !alertFiredToday && InpEnableAlert)
               {
                  Alert(StringFormat("XAUUSD: Range Mode Active! 2 failed breakouts. Balance: %.2f - %.2f", balHigh, balLow));
                  alertFiredToday = true;
               }
            }
         }
      }
   }

   string modeText = (fakeouts >= 2) ? "RANGE MODE ACTIVE (Fade Extremes / Half Size)" : "NORMAL / MONITORING";
   Comment(StringFormat("\n--- XAUUSD Balance Monitor ---\nBalance: High: %.2f | Low: %.2f | Mid: %.2f\nFailed Breakouts: %d / 2\nStatus: %s\n",
           balHigh, balLow, balMid, fakeouts, modeText));

   return(rates_total);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: XAUUSD range days: my rule for not inventing breakouts on M5

Post by PTScalper »

In cTrader's cAlgo API, Calculate(int index) fires on every single tick for the live bar. If we just incremented a raw fakeouts counter without isolating it per-index, the live tick wiggles around the balance line would artificially trigger the threshold instantly.

To solve this cleanly for cTrader, I used internal IndicatorDataSeries to track the state (_balHigh, _fakeouts, _pos). This guarantees that the live tick only evaluates against the locked-in state of the previous closed bar, completely eliminating tick-wiggle repainting.

Here is the exact logic translated for cTrader (cAlgo).

3. cTrader Version (C# / cAlgo API)

Save this in cTrader Automate as a new Indicator named GoldRangeMode.

Code: Select all

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

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class GoldRangeMode : Indicator
    {
        // --- Inputs ---
        [Parameter("Balance Start Time (HH:mm)", DefaultValue = "00:00")]
        public string StartTimeStr { get; set; }

        [Parameter("Balance End Time (HH:mm)", DefaultValue = "08:00")]
        public string EndTimeStr { get; set; }

        [Parameter("Enable Alert", DefaultValue = true)]
        public bool EnableAlert { get; set; }

        // --- Outputs ---
        [Output("Balance High", LineColor = "Crimson", Thickness = 2)]
        public IndicatorDataSeries BalHigh { get; set; }

        [Output("Balance Low", LineColor = "MediumSeaGreen", Thickness = 2)]
        public IndicatorDataSeries BalLow { get; set; }

        [Output("Balance Mid", LineColor = "DarkGray", Thickness = 1, LineStyle = LineStyle.Lines)]
        public IndicatorDataSeries BalMid { get; set; }

        // --- Internal State (Isolates tick repainting) ---
        private IndicatorDataSeries _balHighSeries;
        private IndicatorDataSeries _balLowSeries;
        private IndicatorDataSeries _fakeouts;
        private IndicatorDataSeries _pos; // 0 = inside, 1 = above, -1 = below
        
        private TimeSpan _startTime;
        private TimeSpan _endTime;
        private bool _alertFiredToday;

        protected override void Initialize()
        {
            // Parse user input times
            TimeSpan.TryParse(StartTimeStr, out _startTime);
            TimeSpan.TryParse(EndTimeStr, out _endTime);

            // Initialize internal series for tick-proof state tracking
            _balHighSeries = CreateDataSeries();
            _balLowSeries = CreateDataSeries();
            _fakeouts = CreateDataSeries();
            _pos = CreateDataSeries();
        }

        public override void Calculate(int index)
        {
            var barTime = Bars.OpenTimes[index];
            var timeOfDay = barTime.TimeOfDay;
            
            bool isNewDay = index == 0 || Bars.OpenTimes[index].DayOfYear != Bars.OpenTimes[index - 1].DayOfYear;

            // 1. Inherit state from previous index or reset on new day
            if (isNewDay)
            {
                _balHighSeries[index] = double.NaN;
                _balLowSeries[index] = double.NaN;
                _fakeouts[index] = 0;
                _pos[index] = 0;
                
                if (IsLastBar) _alertFiredToday = false;
            }
            else
            {
                _balHighSeries[index] = _balHighSeries[index - 1];
                _balLowSeries[index] = _balLowSeries[index - 1];
                _fakeouts[index] = _fakeouts[index - 1];
                _pos[index] = _pos[index - 1];
            }

            bool inSession = timeOfDay >= _startTime && timeOfDay < _endTime;
            bool postSession = timeOfDay >= _endTime;

            // 2. Build Morning Balance
            if (inSession)
            {
                double h = Bars.HighPrices[index];
                double l = Bars.LowPrices[index];

                if (double.IsNaN(_balHighSeries[index]) || h > _balHighSeries[index]) _balHighSeries[index] = h;
                if (double.IsNaN(_balLowSeries[index]) || l < _balLowSeries[index]) _balLowSeries[index] = l;
            }

            // 3. Post-Session Monitoring
            if (postSession && !double.IsNaN(_balHighSeries[index]) && !double.IsNaN(_balLowSeries[index]))
            {
                BalHigh[index] = _balHighSeries[index];
                BalLow[index] = _balLowSeries[index];
                BalMid[index] = (BalHigh[index] + BalLow[index]) / 2.0;

                double close = Bars.ClosePrices[index];
                double high = Bars.HighPrices[index];
                double low = Bars.LowPrices[index];

                int currentPos = (close > BalHigh[index]) ? 1 : (close < BalLow[index] ? -1 : 0);
                
                // Read from the PREVIOUS bar's locked-in state to evaluate
                int currentFakeouts = (index > 0) ? (int)_fakeouts[index - 1] : 0;
                int prevPos = (index > 0) ? (int)_pos[index - 1] : 0;

                // Strike 1: Closed outside previously, now failed back in
                if (currentPos == 0 && prevPos != 0)
                {
                    currentFakeouts++;
                }
                // Strike 2: Wicked outside boundary but closed inside on this exact bar
                else if (currentPos == 0 && prevPos == 0)
                {
                    if (high > BalHigh[index] || low < BalLow[index])
                        currentFakeouts++;
                }

                // Write to current index state (can safely overwrite itself on every live tick)
                _fakeouts[index] = currentFakeouts;
                _pos[index] = currentPos;

                // 4. Trigger on Strike 2
                if (_fakeouts[index] >= 2)
                {
                    // Only draw icon on the exact bar the threshold is crossed
                    if (_fakeouts[index] == 2 && (index == 0 || _fakeouts[index - 1] < 2))
                    {
                        double arrowOffset = Symbol.TickSize * 150; // Visual padding
                        Chart.DrawIcon("RangeMode_" + index, ChartIconType.DownArrow, index, high + arrowOffset, Color.DarkOrange);

                        if (IsLastBar && EnableAlert && !_alertFiredToday)
                        {
                            Print($"XAUUSD: Range Mode Active! 2 failed breakouts. Balance: {BalHigh[index]} - {BalLow[index]}");
                            Notifications.PlaySound(SoundType.Doorbell);
                            _alertFiredToday = true;
                        }
                    }
                }
            }

            // 5. Live Telemetry
            if (IsLastBar)
            {
                string modeText = (_fakeouts[index] >= 2) ? "RANGE MODE ACTIVE (Fade Extremes / Half Size)" : "NORMAL / MONITORING";
                Color hudColor = (_fakeouts[index] >= 2) ? Color.DarkOrange : Color.WhiteSmoke;
                
                string telemetry = $"--- XAUUSD Balance Monitor ---\n" +
                                   $"Balance: High: {_balHighSeries[index]:F2} | Low: {_balLowSeries[index]:F2} | Mid: {BalMid[index]:F2}\n" +
                                   $"Failed Breakouts: {_fakeouts[index]} / 2\n" +
                                   $"Status: {modeText}";

                Chart.DrawStaticText("TelemetryText", telemetry, VerticalAlignment.Top, HorizontalAlignment.Left, hudColor);
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: XAUUSD range days: my rule for not inventing breakouts on M5

Post by PTScalper »

Key Differences for the cTrader Engine:

Time Parsing: Takes standard HH:mm strings (e.g., 00:00, 08:00) and converts them into native TimeSpan objects using TimeSpan.TryParse during initialization.

IndicatorDataSeries State: The internal _fakeouts counter is an array mapped to chart indexes. This guarantees that if price closes back inside the balance (triggering strike 2), and a new bar opens, the logic references exactly what happened on the previous bar's closure without duplicate firing.

Chart Visuals: Chart.DrawIcon plots the orange arrow directly above the wick that confirmed the rule, and Chart.DrawStaticText pins a clean, dynamic string block to the top left of the chart. The HUD text dynamically shifts from WhiteSmoke to DarkOrange the moment the rule flips.
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: XAUUSD range days: my rule for not inventing breakouts on M5

Post by LondonScalper »

PTScalper wrote:If the London open fails to decisively displace and hold outside that Asian range within the first 60 to 90 minutes, the market is usually signaling a lack of institutional sponsorship for a trend.
That’s a useful early tell — and it beats inventing M5 “escape velocity” on Gold.

I add one desk check before I even open an M5 breakout ticket: at London open I mark the Asian range and a simple D1 compression flag (inside day / tight prior day). If London has not held outside Asia by ~90 minutes, I switch the session tag to range day and only take fades at the edges with half size. No mid-range M5 breakouts.

Your ADR exhaustion point matches what we see: after a 150% ADR day, the next session often mean-reverts. Paying the range-day tax usually means I ignored that handoff failure and treated a liquidity sweep as sponsorship.

Rule: if London fails the Asia handoff, stop hunting breakouts on M5. Do you flip to range mode on the first failed hold, or wait for a second failed attempt before you demote the day?
PropScalpDesk
Posts: 273
Joined: Sat Sep 19, 2026 7:50 pm

Re: XAUUSD range days: my rule for not inventing breakouts on M5

Post by PropScalpDesk »

PTScalper wrote:To help systematize your exact xau_range_mode rule, I wrote a Pine Script that automates the tracking.
Range days on XAU: I forbid breakout stories. Mean-revert or flat. Inventing momentum because I am bored is how the soft stop gets hit.

Session character first, setup second.

How do you label a range day in the first hour so you do not “forget”?

I also log refused tickets so flat time counts as work — otherwise the desk invents activity.

Funded trailing DD is the external referee that keeps the desk honest.

Boring survival beats a clever recovery that spends the week’s DD band.

I would rather log a refused ticket than invent activity for the journal.

I write the walk-away before London so it is not negotiated mid-tape.

Topic note from my sheet for t=12351: keep risk unchanged until the sample says otherwise.
Post Reply