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);
}
}
}
}