Since you are a .NET engineer, you'll appreciate how much cleaner this is in C# compared to MQL. Instead of passing symbol names to global functions like iHigh(), we retrieve the daily timeframe natively using MarketData.GetBars(TimeFrame.Daily) in the Initialize() method and reference it synchronously without blocking.
I used TimeSpan for fast intraday time-bound checks, preventing the need to parse strings on every tick.
Save this in cTrader's Automate tab as a new Indicator:
Code: Select all
using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class LondonNYOverlapADR : Indicator
{
[Parameter("London Start (HH:MM)", Group = "Session Settings", DefaultValue = "08:00")]
public string LondonStartStr { get; set; }
[Parameter("London End (HH:MM)", Group = "Session Settings", DefaultValue = "13:00")]
public string LondonEndStr { get; set; }
[Parameter("Overlap End (HH:MM)", Group = "Session Settings", DefaultValue = "17:00")]
public string OverlapEndStr { get; set; }
[Parameter("ADR Lookback", Group = "ADR Exhaustion", DefaultValue = 14, MinValue = 1)]
public int AdrLookback { get; set; }
[Parameter("Exhaustion Threshold (%)", Group = "ADR Exhaustion", DefaultValue = 80.0)]
public double AdrThreshold { get; set; }
[Parameter("Log Alerts to Journal", Group = "Alerts", DefaultValue = true)]
public bool EnableLog { get; set; }
[Parameter("Play Sound", Group = "Alerts", DefaultValue = false)]
public bool EnableSound { get; set; }
[Output("London High", LineColor = "SeaGreen", Thickness = 2, PlotType = PlotType.Line)]
public IndicatorDataSeries OutHigh { get; set; }
[Output("London Low", LineColor = "Crimson", Thickness = 2, PlotType = PlotType.Line)]
public IndicatorDataSeries OutLow { get; set; }
[Output("London Mid", LineColor = "SlateGray", Thickness = 1, PlotType = PlotType.Points)]
public IndicatorDataSeries OutMid { get; set; }
private TimeSpan _lonStart, _lonEnd, _overlapEnd;
private Bars _dailyBars;
private int _lastHighSweepDay = -1;
private int _lastLowSweepDay = -1;
protected override void Initialize()
{
TimeSpan.TryParse(LondonStartStr, out _lonStart);
TimeSpan.TryParse(LondonEndStr, out _lonEnd);
TimeSpan.TryParse(OverlapEndStr, out _overlapEnd);
// Fetch higher timeframe data natively for the ADR check
_dailyBars = MarketData.GetBars(TimeFrame.Daily);
}
public override void Calculate(int index)
{
var currentTime = Bars.OpenTimes[index];
var timeOfDay = currentTime.TimeOfDay;
// Process only inside the active window
if (timeOfDay >= _lonStart && timeOfDay < _overlapEnd)
{
double hi = double.MinValue;
double lo = double.MaxValue;
// Scan backwards to find the extremes of today's London session
for (int i = index; i >= 0; i--)
{
var barTime = Bars.OpenTimes[i];
if (barTime.Date != currentTime.Date) break;
if (barTime.TimeOfDay >= _lonStart && barTime.TimeOfDay < _lonEnd)
{
hi = Math.Max(hi, Bars.HighPrices[i]);
lo = Math.Min(lo, Bars.LowPrices[i]);
}
}
// If valid extremes were found, plot them
if (hi > double.MinValue && lo < double.MaxValue)
{
OutHigh[index] = hi;
OutLow[index] = lo;
OutMid[index] = (hi + lo) / 2.0;
if (IsLastBar)
{
HandleLiveEdgeLogic(index, hi, lo, currentTime);
}
}
else
{
ClearOutputs(index);
}
}
else
{
ClearOutputs(index);
if (IsLastBar) Chart.RemoveObject("ADR_HUD");
}
}
private void HandleLiveEdgeLogic(int index, double hi, double lo, DateTime currentTime)
{
double adr = GetHistoricalADR();
double currentRange = hi - lo;
double pctConsumed = (currentRange / adr) * 100;
bool isExhausted = pctConsumed >= AdrThreshold;
// Draw non-blocking HUD (cTrader handles this natively in the chart overlay)
string status = isExhausted ? "NO-GO (EXHAUSTED)" : "GO (ROOM TO MOVE)";
Color statusColor = isExhausted ? Color.Red : Color.LimeGreen;
string hudText = $"London ADR Consumed: {pctConsumed:F1}% | {status}";
Chart.DrawStaticText("ADR_HUD", hudText, VerticalAlignment.Top, HorizontalAlignment.Right, statusColor);
// Process Alerts specifically inside the NY Overlap timeframe
var timeOfDay = currentTime.TimeOfDay;
if (timeOfDay >= _lonEnd && timeOfDay < _overlapEnd && !isExhausted)
{
if (Bars.HighPrices[index] > hi && _lastHighSweepDay != currentTime.DayOfYear)
{
string msg = $"NY Overlap Sweep (GO): {SymbolName} swept London High at {hi}. (ADR: {pctConsumed:F1}%)";
TriggerAlert(msg);
_lastHighSweepDay = currentTime.DayOfYear;
}
if (Bars.LowPrices[index] < lo && _lastLowSweepDay != currentTime.DayOfYear)
{
string msg = $"NY Overlap Sweep (GO): {SymbolName} swept London Low at {lo}. (ADR: {pctConsumed:F1}%)";
TriggerAlert(msg);
_lastLowSweepDay = currentTime.DayOfYear;
}
}
}
private double GetHistoricalADR()
{
double sum = 0;
int count = 0;
// _dailyBars.Count - 2 ensures we only look at fully closed daily candles, avoiding repainting
int lastClosedDailyIndex = _dailyBars.Count - 2;
for (int i = 0; i < AdrLookback; i++)
{
int idx = lastClosedDailyIndex - i;
if (idx < 0) break;
sum += (_dailyBars.HighPrices[idx] - _dailyBars.LowPrices[idx]);
count++;
}
return count > 0 ? sum / count : 0.0001; // Avoid divide-by-zero
}
private void ClearOutputs(int index)
{
OutHigh[index] = double.NaN;
OutLow[index] = double.NaN;
OutMid[index] = double.NaN;
}
private void TriggerAlert(string msg)
{
if (EnableLog) Print(msg);
if (EnableSound) Notifications.PlaySound(SoundType.Doorbell);
}
}
}