Page 2 of 2
Re: XAGUSD relative strength vs gold as a scalp filter
Posted: Tue Sep 15, 2026 8:23 am
by FTtrader
cTrader (cAlgo) C# Implementation
For a professional algorithmic environment, cTrader's C# API handles cross-asset synchronization (GetIndexByTime) much more elegantly than MT4/MT5, avoiding the repainting issues caused by tick gaps in secondary data feeds.
Code: Select all
using System;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo
{
[Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class MetalsRelativeZScore : Indicator
{
[Parameter("Secondary Symbol (e.g., XAGUSD)", DefaultValue = "XAGUSD")]
public string SecondarySymbolName { get; set; }
[Parameter("Lookback Period", DefaultValue = 20, MinValue = 2)]
public int Period { get; set; }
[Parameter("Z-Score Threshold", DefaultValue = 2.0)]
public double Threshold { get; set; }
[Output("Z-Score", LineColor = "Gray", PlotType = PlotType.Histogram, Thickness = 3)]
public IndicatorDataSeries ZScore { get; set; }
private Bars _secondaryBars;
private IndicatorDataSeries _ratioSeries;
private SimpleMovingAverage _sma;
private StandardDeviation _stdDev;
protected override void Initialize()
{
// Fetch secondary asset data
_secondaryBars = MarketData.GetBars(TimeFrame, SecondarySymbolName);
_ratioSeries = CreateDataSeries();
// Leverage built-in optimized algorithms over the custom ratio series
_sma = Indicators.SimpleMovingAverage(_ratioSeries, Period);
_stdDev = Indicators.StandardDeviation(_ratioSeries, Period, MovingAverageType.Simple);
}
public override void Calculate(int index)
{
// 1. Strictly synchronize time-series across the two assets to prevent repainting
var primaryTime = Bars.OpenTimes[index];
var secondaryIndex = _secondaryBars.OpenTimes.GetIndexByTime(primaryTime);
// Handle data gaps / missing ticks in the secondary symbol
if (secondaryIndex == -1 || _secondaryBars.ClosePrices[secondaryIndex] == 0)
{
ZScore[index] = double.NaN;
return;
}
// 2. Compute raw ratio
_ratioSeries[index] = Bars.ClosePrices[index] / _secondaryBars.ClosePrices[secondaryIndex];
// Wait for sufficient data buffer
if (index < Period) return;
// 3. Compute Z-Score
double std = _stdDev.Result[index];
ZScore[index] = std == 0 ? 0 : (_ratioSeries[index] - _sma.Result[index]) / std;
// 4. Dynamic Color Logic
if (ZScore[index] >= Threshold)
{
ChartObjects.DrawLine("ZScoreLine" + index, index, 0, index, ZScore[index], Colors.Teal, 3);
}
else if (ZScore[index] <= -Threshold)
{
ChartObjects.DrawLine("ZScoreLine" + index, index, 0, index, ZScore[index], Colors.Maroon, 3);
}
}
}
}
Re: XAGUSD relative strength vs gold as a scalp filter
Posted: Tue Sep 15, 2026 8:24 am
by FTtrader
Pro-Level MQL5 Optimization NotesIf you are deploying this to MT5, the previous MQL5 script is functional, but for a production Expert Advisor (EA) or high-frequency execution environment, you should replace the nested for loops in the OnCalculate block.
Instead of recalculating the Mean and Standard Deviation over N periods on every single tick (which is $O(N)$ per tick), you can track a rolling sum and rolling sum of squares. This reduces the time complexity to $O(1)$ per tick, significantly lowering CPU overhead when running backtests over millions of ticks or executing in a latency-sensitive live environment.
Re: XAGUSD relative strength vs gold as a scalp filter
Posted: Tue Sep 15, 2026 8:24 am
by FTtrader
MQL5 Implementation (Production Grade)
This implementation is optimized for the MT5 environment. The critical failure point for cross-asset indicators in MQL5 is asynchronous data loading—if the secondary symbol (XAGUSD) hasn't fully loaded its history for a specific timeframe, standard scripts will calculate on ghost data or throw array out-of-range errors, leading to repainting.
This script explicitly checks
history synchronization and uses exact timestamp matching (CopyClose by time
). If the secondary data is missing a tick, it halts and returns 0 to force the terminal to download the missing data and recalculate, guaranteeing absolute precision during live execution.
Code: Select all
//+------------------------------------------------------------------+
//| Metals_Z_Score_Pro.mq5 |
//| Strict Cross-Asset Filter for Microstructure |
//+------------------------------------------------------------------+
#property copyright "Strict Microstructure Filter"
#property version "1.00"
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots 1
//--- Plot Formatting
#property indicator_label1 "RS Z-Score"
#property indicator_type1 DRAW_COLOR_HISTOGRAM
#property indicator_color1 clrTeal, clrMaroon, clrDimGray
#property indicator_style1 STYLE_SOLID
#property indicator_width1 3
//--- Inputs
sinput string InpSecondarySymbol = "XAGUSD"; // Secondary Symbol (Silver)
input int InpPeriod = 20; // Rolling Period
input double InpThreshold = 2.0; // Z-Score Threshold (Sigma)
//--- Buffers
double ZScoreBuffer[];
double ColorBuffer[];
double RatioBuffer[]; // Internal buffer for raw ratio math
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Bind arrays to indicator buffers
SetIndexBuffer(0, ZScoreBuffer, INDICATOR_DATA);
SetIndexBuffer(1, ColorBuffer, INDICATOR_COLOR_INDEX);
SetIndexBuffer(2, RatioBuffer, INDICATOR_CALCULATIONS);
IndicatorSetInteger(INDICATOR_DIGITS, 2);
// Set Baseline and Sigma Thresholds
IndicatorSetInteger(INDICATOR_LEVELS, 3);
IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 0.0);
IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, InpThreshold);
IndicatorSetDouble(INDICATOR_LEVELVALUE, 2, -InpThreshold);
// Force the terminal to select the secondary symbol in Market Watch
SymbolSelect(InpSecondarySymbol, true);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 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[])
{
// Check if secondary symbol data is synchronized to prevent repainting/errors
if(!SeriesInfoInteger(InpSecondarySymbol, PERIOD_CURRENT, SERIES_SYNCHRONIZED))
return 0; // Force terminal to wait/download data and recalculate
int start = prev_calculated == 0 ? 0 : prev_calculated - 1;
for(int i = start; i < rates_total && !IsStopped(); i++)
{
double secClose[1];
// Strict exact-time matching for cross-asset calculation
if(CopyClose(InpSecondarySymbol, PERIOD_CURRENT, time[i], 1, secClose) <= 0)
{
// If a specific bar is missing in the secondary feed, halt calculation
// Returning 0 ensures the indicator repairs itself on the next tick
return 0;
}
if(secClose[0] == 0) continue;
// Compute core ratio
RatioBuffer[i] = close[i] / secClose[0];
// Wait for sufficient lookback buffer
if(i >= InpPeriod - 1)
{
// 1. Calculate Rolling Mean
double sum = 0;
for(int j = 0; j < InpPeriod; j++)
sum += RatioBuffer[i - j];
double mean = sum / InpPeriod;
// 2. Calculate Rolling Standard Deviation (Variance)
double sqSum = 0;
for(int j = 0; j < InpPeriod; j++)
sqSum += MathPow(RatioBuffer[i - j] - mean, 2);
double stdDev = MathSqrt(sqSum / InpPeriod);
// 3. Compute Z-Score
ZScoreBuffer[i] = stdDev == 0 ? 0 : (RatioBuffer[i] - mean) / stdDev;
// 4. Map Histogram Colors
if(ZScoreBuffer[i] >= InpThreshold)
ColorBuffer[i] = 0; // Teal (XAU Diverging Up)
else if(ZScoreBuffer[i] <= -InpThreshold)
ColorBuffer[i] = 1; // Maroon (XAG Diverging Up)
else
ColorBuffer[i] = 2; // Gray (Neutral/Correlated)
}
else
{
ZScoreBuffer[i] = 0;
ColorBuffer[i] = 2;
}
}
return(rates_total);
}
Re: XAGUSD relative strength vs gold as a scalp filter
Posted: Tue Sep 15, 2026 8:26 am
by FTtrader
To package this logic for an
Expert Advisor, it is best to avoid relying on external indicators via iCustom. Keeping the logic self-contained within a class eliminates external file dependencies, speeds up Strategy Tester optimization, and prevents initialization errors if the secondary symbol's data isn't fully loaded on startup.
Because 1-minute and 5-minute charts can occasionally drop ticks, this class recalculates the lookback window dynamically on every call. For a standard 20-period lookback, looping through the data requires negligible CPU overhead and guarantees that your arrays never fall out of sync.
The Filter Class
You can place this class directly at the top of your EA file or save it as a separate .mqh header.
Code: Select all
//+------------------------------------------------------------------+
//| Class: CZScoreFilter |
//| Purpose: Evaluates cross-asset relative strength for trade veto |
//+------------------------------------------------------------------+
class CZScoreFilter
{
private:
string m_primary;
string m_secondary;
ENUM_TIMEFRAMES m_timeframe;
int m_period;
double m_threshold;
public:
CZScoreFilter(string primary, string secondary, ENUM_TIMEFRAMES tf, int period, double threshold)
: m_primary(primary),
m_secondary(secondary),
m_timeframe(tf),
m_period(period),
m_threshold(threshold)
{
// Ensure the terminal has the secondary symbol available
SymbolSelect(m_secondary, true);
}
// Evaluates the spread and returns a state integer.
// Returns:
// 1 = Primary is heavily outperforming (Veto Primary Shorts)
// -1 = Secondary is heavily outperforming (Veto Primary Longs)
// 0 = Neutral / Correlated (Clear to trade)
int EvaluateFilter(double &out_zscore);
};
int CZScoreFilter::EvaluateFilter(double &out_zscore)
{
out_zscore = 0.0;
datetime times[];
double priClose[];
// Fetch history for the primary symbol
if(CopyTime(m_primary, m_timeframe, 0, m_period, times) != m_period) return 0;
if(CopyClose(m_primary, m_timeframe, 0, m_period, priClose) != m_period) return 0;
double sum = 0;
int count = 0;
double ratio_array[];
ArrayResize(ratio_array, m_period);
// Build the ratio array strictly matching timestamps
for(int i = 0; i < m_period; i++)
{
double secClose[1];
// Attempt to fetch the exact matching bar for the secondary asset
if(CopyClose(m_secondary, m_timeframe, times[i], 1, secClose) > 0 && secClose[0] > 0)
{
ratio_array[i] = priClose[i] / secClose[0];
sum += ratio_array[i];
count++;
}
else
{
ratio_array[i] = 0; // Data gap fallback
}
}
// Require at least two synchronized bars to calculate a valid standard deviation
if(count < 2) return 0;
double mean = sum / count;
double sqSum = 0;
for(int i = 0; i < m_period; i++)
{
if(ratio_array[i] > 0)
sqSum += MathPow(ratio_array[i] - mean, 2);
}
double stdDev = MathSqrt(sqSum / count);
if(stdDev == 0) return 0;
// By default, CopyClose orders oldest [0] to newest [period-1]
double currentRatio = ratio_array[m_period - 1];
if(currentRatio == 0) return 0;
out_zscore = (currentRatio - mean) / stdDev;
// Determine filter state
if(out_zscore >= m_threshold) return 1;
if(out_zscore <= -m_threshold) return -1;
return 0; // Normal correlation
}
Re: XAGUSD relative strength vs gold as a scalp filter
Posted: Tue Sep 15, 2026 8:26 am
by FTtrader
How to Implement it in OnTick()
Initialize the class globally so it only runs SymbolSelect once, then call EvaluateFilter() before passing your trades to the execution module.
Code: Select all
// Global instantiation
CZScoreFilter FilterXAU("XAUUSD", "XAGUSD", PERIOD_M1, 20, 2.0);
void OnTick()
{
// 1. Identify your primary setup (e.g., a Long on Gold)
bool isLongSetup = CheckMyEntryLogic();
if(isLongSetup)
{
double currentZScore;
int filterState = FilterXAU.EvaluateFilter(currentZScore);
// 2. Veto Logic: If Silver is crashing relative to Gold, do not buy Gold.
if(filterState == -1)
{
PrintFormat("Trade Vetoed: XAG premium detected. Z-Score: %.2f", currentZScore);
return; // Abort execution
}
// 3. Clear to execute
ExecuteBuyTrade();
}
}
Re: XAGUSD relative strength vs gold as a scalp filter
Posted: Tue Sep 15, 2026 9:12 am
by LondonScalper
FTtrader wrote:A clean Gold structural-low sweep while Silver fails to make a lower low boosts confidence for a Gold long; when spreads widen or a squeeze hits, raw price action and order-book liquidity take precedence.
Useful framing — divergence as a
confidence boost, never as permission to ignore the book.
I run a similar XAU/XAG check on the 15m before I size a Gold scalp, but I treat it as optional context only. The moment silver’s top-of-book widens or the ratio print starts lagging the tape, the filter is discarded. A rolling Z-score of the ratio is fine for marking divergence; it does not override live depth.
Concrete desk rule: if XAG spread is already past
2× session median at click time, I scratch the relative-strength thesis and trade Gold off its own structure — or stand aside. Offensive silver spreads invalidate the filter faster than any correlation model can adapt.
Rule:
tradable book first; XAG is optional context. Do you hard-kill the filter on spread alone, or do you also require a failed lower-low confirmation on silver before you trust the Gold long?
Re: XAGUSD relative strength vs gold as a scalp filter
Posted: Tue Sep 22, 2026 8:10 pm
by LondonNewsTrader
FTtrader wrote:The biggest pitfall when moving cross-asset logic from Pine Script to MetaTrader is handling time-series synchronization. MT4 and MT5 do not automatically align arrays when one symbol has a missing tick or a data gap.
Cross-asset filters die on alignment long before they die on the z-score formula.
XAG versus XAU as a relative-strength gate is useful on a London metals morning — silver leading or lagging often tells you whether the impulse is broad or single-name noise. But a missing silver tick that desyncs the ratio will invent a fake extreme right as you size up. Explicit time-matching is mandatory if this leaves research and enters live.
I still overlay the calendar: into US inflation prints both metals can gap together and the z-score becomes chaos, not a scalp filter. Use it in calm London structure; mute it inside blackouts.
Are you trading the lagging metal back to gold, or only using the z-score as a veto on silver breakouts?
Re: XAGUSD relative strength vs gold as a scalp filter
Posted: Thu Sep 24, 2026 3:20 am
by PropScalpDesk
FTtrader wrote:On the technical side, relying on MQL for cross-asset synchronization can be clunky. Since you are handling complex logic, I have added a cTrader (cAlgo) C# implementation.
XAG versus gold relative strength is a bias filter for me, not an auto-entry. When silver leads without London depth, I still wait.
Size stays small on squeeze headlines until the book is normal.
Do you trade the relative move, or only use it to veto gold tickets?
I also log refused tickets so flat time counts as work — otherwise the desk invents activity.
If the idea needs a story longer than one line, it waits for another window.
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.
Topic note from my sheet for t=12416: keep risk unchanged until the sample says otherwise.