Page 2 of 2
Re: XAUUSD M1 vs M5: when I switch primary mid-session
Posted: Mon Sep 21, 2026 9:04 am
by PTScalper
API Implementation Details
Bars.Count - 2 Target: In cTrader, Bars.Count - 1 is the current, active, forming tick data. By locking the evaluation to Bars.Count - 2, you guarantee the mathematical integrity of the Close price against the MidPoint.
State Isolation: The for loops explicitly carve out the historical subsets. Pine Script handles historical indexing arrays implicitly behind the scenes, but doing it explicitly in C# guarantees memory safety and prevents IndexOutOfRangeException errors during rapid tick execution.
Execution Efficiency: Bypassing Bars.Maximum() or LINQ aggregations in favor of raw for loops against the primitive arrays (Bars.HighPrices) ensures this cBot operates entirely in the L1/L2 cache footprint, introducing zero latency overhead on the main thread if you eventually wire it to execute automated orders.
Re: XAUUSD M1 vs M5: when I switch primary mid-session
Posted: Mon Sep 21, 2026 9:06 am
by PTScalper
Here is the exact microstructure and liquidity sweep logic implemented as Expert Advisors for both MetaTrader platforms.
By executing this inside OnTick() and restricting evaluation to the most recently closed bar (when a new candle opens), we sidestep the overhead of indicator buffers and ensure execution remains extremely fast.
MQL4 Implementation (MicrostructureFilter.mq4)
MQL4 natively handles historical arrays (Open, Close, High, Low) in reverse chronological order, making the loop logic nearly identical to the C# implementation.
Code: Select all
//+------------------------------------------------------------------+
//| MicrostructureFilter.mq4 |
//+------------------------------------------------------------------+
#property strict
input int OverlapBars = 4; // Chop: Overlap Bars
input int SweepLookback = 10; // Sweep: Pivot Lookback
datetime lastBarTime = 0;
void OnTick()
{
// Ensure we only evaluate once per newly opened bar
if (Time[0] == lastBarTime) return;
// Ensure enough historical data exists
int requiredBars = MathMax(OverlapBars, SweepLookback) + 2;
if (Bars < requiredBars) return;
lastBarTime = Time[0];
// Index 1 is the most recently closed candle
int evalIndex = 1;
// --- 1. Microstructure Consolidation (Chop) Detection ---
double maxBody = 0.0;
double minBody = 9999999.0;
double sumWickSize = 0;
for (int i = 0; i < OverlapBars; i++)
{
int curr = evalIndex + i;
double bodyTop = MathMax(Open[curr], Close[curr]);
double bodyBottom = MathMin(Open[curr], Close[curr]);
if (bodyTop > maxBody) maxBody = bodyTop;
if (bodyBottom < minBody) minBody = bodyBottom;
double bodySize = bodyTop - bodyBottom;
double totalSize = High[curr] - Low[curr];
sumWickSize += (totalSize - bodySize);
}
double bodyRange = maxBody - minBody;
double avgWickSize = sumWickSize / OverlapBars;
bool isChop = bodyRange < avgWickSize;
// --- 2. Liquidity Sweep Detection ---
double localHigh = 0.0;
double localLow = 9999999.0;
// Calculate local extremes EXCLUDING the evaluation bar
for (int i = 1; i <= SweepLookback; i++)
{
int curr = evalIndex + i;
if (High[curr] > localHigh) localHigh = High[curr];
if (Low[curr] < localLow) localLow = Low[curr];
}
double barHigh = High[evalIndex];
double barLow = Low[evalIndex];
double barClose = Close[evalIndex];
double midPoint = barLow + (barHigh - barLow) * 0.5;
bool bearishSweep = (barHigh > localHigh) && (barClose < localHigh) && (barClose < midPoint);
bool bullishSweep = (barLow < localLow) && (barClose > localLow) && (barClose > midPoint);
// --- 3. Logging and Visual Outputs ---
if (bullishSweep && !isChop)
{
PrintFormat("Bullish Sweep at %f. Swept liquidity below %f.", barLow, localLow);
string objName = "SweepBull_" + TimeToString(Time[evalIndex]);
ObjectCreate(0, objName, OBJ_ARROW_BUY, 0, Time[evalIndex], barLow - 5 * Point);
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrMediumSeaGreen);
}
else if (bearishSweep && !isChop)
{
PrintFormat("Bearish Sweep at %f. Swept liquidity above %f.", barHigh, localHigh);
string objName = "SweepBear_" + TimeToString(Time[evalIndex]);
ObjectCreate(0, objName, OBJ_ARROW_SELL, 0, Time[evalIndex], barHigh + 5 * Point);
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrCrimson);
}
}
Re: XAUUSD M1 vs M5: when I switch primary mid-session
Posted: Mon Sep 21, 2026 9:07 am
by PTScalper
MQL5 Implementation (MicrostructureFilter.mq5)
Because MQL5 arrays index left-to-right by default, this version utilizes CopyRates coupled with ArraySetAsSeries(true). This dynamically forces the MQL5 struct array to mirror MQL4's right-to-left index mapping (where rates[1] is safely the most recently closed bar), completely avoiding messy compatibility wrappers.
Code: Select all
//+------------------------------------------------------------------+
//| MicrostructureFilter.mq5 |
//+------------------------------------------------------------------+
#property strict
input int OverlapBars = 4; // Chop: Overlap Bars
input int SweepLookback = 10; // Sweep: Pivot Lookback
datetime lastBarTime = 0;
void OnTick()
{
datetime timeArray[];
if(CopyTime(_Symbol, _Period, 0, 1, timeArray) <= 0) return;
if(timeArray[0] == lastBarTime) return;
int requiredBars = MathMax(OverlapBars, SweepLookback) + 2;
if(Bars(_Symbol, _Period) < requiredBars) return;
lastBarTime = timeArray[0];
// Copy struct data and map index chronologically backwards
MqlRates rates[];
ArraySetAsSeries(rates, true);
if(CopyRates(_Symbol, _Period, 0, requiredBars, rates) < requiredBars) return;
int evalIndex = 1;
// --- 1. Microstructure Consolidation (Chop) Detection ---
double maxBody = 0.0;
double minBody = 9999999.0;
double sumWickSize = 0;
for(int i = 0; i < OverlapBars; i++)
{
int curr = evalIndex + i;
double bodyTop = MathMax(rates[curr].open, rates[curr].close);
double bodyBottom = MathMin(rates[curr].open, rates[curr].close);
if(bodyTop > maxBody) maxBody = bodyTop;
if(bodyBottom < minBody) minBody = bodyBottom;
double bodySize = bodyTop - bodyBottom;
double totalSize = rates[curr].high - rates[curr].low;
sumWickSize += (totalSize - bodySize);
}
double bodyRange = maxBody - minBody;
double avgWickSize = sumWickSize / OverlapBars;
bool isChop = bodyRange < avgWickSize;
// --- 2. Liquidity Sweep Detection ---
double localHigh = 0.0;
double localLow = 9999999.0;
for(int i = 1; i <= SweepLookback; i++)
{
int curr = evalIndex + i;
if(rates[curr].high > localHigh) localHigh = rates[curr].high;
if(rates[curr].low < localLow) localLow = rates[curr].low;
}
double barHigh = rates[evalIndex].high;
double barLow = rates[evalIndex].low;
double barClose = rates[evalIndex].close;
double midPoint = barLow + (barHigh - barLow) * 0.5;
bool bearishSweep = (barHigh > localHigh) && (barClose < localHigh) && (barClose < midPoint);
bool bullishSweep = (barLow < localLow) && (barClose > localLow) && (barClose > midPoint);
// --- 3. Logging and Visual Outputs ---
if(bullishSweep && !isChop)
{
PrintFormat("Bullish Sweep at %f. Swept liquidity below %f.", barLow, localLow);
string objName = "SweepBull_" + TimeToString(rates[evalIndex].time);
ObjectCreate(0, objName, OBJ_ARROW_BUY, 0, rates[evalIndex].time, barLow - 5 * _Point);
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrMediumSeaGreen);
}
else if(bearishSweep && !isChop)
{
PrintFormat("Bearish Sweep at %f. Swept liquidity above %f.", barHigh, localHigh);
string objName = "SweepBear_" + TimeToString(rates[evalIndex].time);
ObjectCreate(0, objName, OBJ_ARROW_SELL, 0, rates[evalIndex].time, barHigh + 5 * _Point);
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrCrimson);
}
}
Re: XAUUSD M1 vs M5: when I switch primary mid-session
Posted: Mon Sep 21, 2026 9:18 pm
by LondonScalper
PTScalper wrote:If M1 starts looking like a barcode — overlapping dojis, wicks in both directions without displacement — I immediately step back to M5. If M1 is not cleanly respecting M5 POIs, the micro-structure is broken.
That matches my mid-session switch on gold. Timeframe is a tool, not an identity. Two scratches on M1 still mean I zoom out; your POI-respect filter is a clean second veto. Volume and cost matter as much as noise: London/NY overlap can make M1 usable; as the afternoon thins and spreads stick, paying the spread on a tiny M1 move is mathematically worse than taking the same idea on M5.
I keep the rule dull and written:
M5 for bias, M1 for entry only while displacement and spread both clear. When the tape turns into overlapping noise, M1 is retired for the rest of that session — no "one more micro attempt." Chart overlays that align the two timeframes are optional; the fill log and the spread gate do the real work. I would rather miss a barcode scalp than invent structure that is not there.
On your overlap days, do you hard-switch back to M5 at a fixed wall-clock, or only when sticky spreads and barcode prints show up together?