Create a new Indicator in cTrader Automate, name it NYOpenSweepDetector, and paste the following C# code:
Code: Select all
using System;
using System.Collections.Generic;
using cAlgo.API;
using cAlgo.API.Internals;
namespace cAlgo.Indicators
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class NYOpenSweepDetector : Indicator
{
#region Parameters
[Parameter("Pre-Market Start Hour (UTC)", Group = "1. Session Times (UTC)", DefaultValue = 7, MinValue = 0, MaxValue = 23)]
public int PmStartHour { get; set; }
[Parameter("Pre-Market Start Min (UTC)", Group = "1. Session Times (UTC)", DefaultValue = 0, MinValue = 0, MaxValue = 59)]
public int PmStartMin { get; set; }
[Parameter("NY Open Hour (UTC)", Group = "1. Session Times (UTC)", DefaultValue = 13, MinValue = 0, MaxValue = 23)]
public int NyOpenHour { get; set; }
[Parameter("NY Open Min (UTC)", Group = "1. Session Times (UTC)", DefaultValue = 30, MinValue = 0, MaxValue = 59)]
public int NyOpenMin { get; set; }
[Parameter("NY Watch End Hour (UTC)", Group = "1. Session Times (UTC)", DefaultValue = 16, MinValue = 0, MaxValue = 23)]
public int NyEndHour { get; set; }
[Parameter("NY Watch End Min (UTC)", Group = "1. Session Times (UTC)", DefaultValue = 0, MinValue = 0, MaxValue = 59)]
public int NyEndMin { get; set; }
[Parameter("Min Sweep Penetration (Index Pts)", Group = "2. Sweep Filters", DefaultValue = 1.5, MinValue = 0.0)]
public double MinSweepPoints { get; set; }
[Parameter("Min Rejection Wick (% of Candle)", Group = "2. Sweep Filters", DefaultValue = 30.0, MinValue = 5.0, MaxValue = 90.0)]
public double MinWickPercent { get; set; }
[Parameter("Stop-Loss Buffer (Index Pts)", Group = "3. Risk & Targets", DefaultValue = 2.5, MinValue = 0.5)]
public double SlBufferPoints { get; set; }
[Parameter("Enable Popup & Sound Alert", Group = "4. Notifications", DefaultValue = true)]
public bool EnableAlerts { get; set; }
[Parameter("Send Email Alert", Group = "4. Notifications", DefaultValue = false)]
public bool EnableEmail { get; set; }
[Parameter("Email Address", Group = "4. Notifications", DefaultValue = "")]
public string EmailAddress { get; set; }
#endregion
private class DailySessionData
{
public double PmHigh = double.MinValue;
public double PmLow = double.MaxValue;
public DateTime PmStartTime;
public DateTime NyEndTime;
public bool IsValid = false;
public bool BearishSweepTriggered = false;
public bool BullishSweepTriggered = false;
}
private readonly Dictionary<DateTime, DailySessionData> _sessions = new Dictionary<DateTime, DailySessionData>();
private int _lastProcessedBarIndex = -1;
protected override void Initialize()
{
if (TimeFrame != TimeFrame.Minute15)
{
Chart.DrawStaticText("TF_Warning",
"WARNING: NY Open Sweep Detector is optimized for the M15 TimeFrame.",
VerticalAlignment.Top, HorizontalAlignment.Right, Color.OrangeRed);
}
}
public override void Calculate(int index)
{
// Process historical bars and only trigger live logic once per newly closed bar
if (index < 2) return;
DateTime currentBarTime = Bars.OpenTimes[index];
DateTime tradingDay = currentBarTime.Date;
if (!_sessions.TryGetValue(tradingDay, out DailySessionData session))
{
session = new DailySessionData
{
PmStartTime = tradingDay.AddHours(PmStartHour).AddMinutes(PmStartMin),
NyEndTime = tradingDay.AddHours(NyEndHour).AddMinutes(NyEndMin)
};
_sessions[tradingDay] = session;
}
DateTime nyOpenTime = tradingDay.AddHours(NyOpenHour).AddMinutes(NyOpenMin);
// 1. Accumulate Pre-Market / London High & Low
if (currentBarTime >= session.PmStartTime && currentBarTime < nyOpenTime)
{
if (Bars.HighPrices[index] > session.PmHigh) session.PmHigh = Bars.HighPrices[index];
if (Bars.LowPrices[index] < session.PmLow) session.PmLow = Bars.LowPrices[index];
session.IsValid = true;
DrawSessionLevels(tradingDay, session, nyOpenTime);
}
// 2. Evaluate Closed Bar (index - 1) during NY Open Killzone
int closedIdx = index - 1;
if (closedIdx <= _lastProcessedBarIndex) return;
DateTime closedBarTime = Bars.OpenTimes[closedIdx];
if (session.IsValid && closedBarTime >= nyOpenTime && closedBarTime < session.NyEndTime)
{
EvaluateSweepOnClosedBar(closedIdx, session, tradingDay);
_lastProcessedBarIndex = closedIdx;
}
}
private void EvaluateSweepOnClosedBar(int idx, DailySessionData session, DateTime tradingDay)
{
double high = Bars.HighPrices[idx];
double low = Bars.LowPrices[idx];
double open = Bars.OpenPrices[idx];
double close = Bars.ClosePrices[idx];
double candleRange = high - low;
if (candleRange <= double.Epsilon) return;
// --- BEARISH SWEEP CHECK (Sweeps PM High, Closes Inside) ---
if (!session.BearishSweepTriggered)
{
double sweepDistance = high - session.PmHigh;
double upperWick = high - Math.Max(open, close);
double wickPct = (upperWick / candleRange) * 100.0;
if (sweepDistance >= MinSweepPoints && close < session.PmHigh && wickPct >= MinWickPercent)
{
session.BearishSweepTriggered = true;
double sl = high + SlBufferPoints;
double risk = sl - close;
double tp1 = close - (risk * 1.5);
double tp2 = close - (risk * 3.0);
string iconName = $"ShortSweep_{tradingDay:yyyyMMdd}_{idx}";
Chart.DrawIcon(iconName, ChartIconType.DownArrow, Bars.OpenTimes[idx], high + (candleRange * 0.25), Color.Crimson);
TriggerSignal("SHORT (PM High Sweep)", close, sl, tp1, tp2, idx);
}
}
// --- BULLISH SWEEP CHECK (Sweeps PM Low, Closes Inside) ---
if (!session.BullishSweepTriggered)
{
double sweepDistance = session.PmLow - low;
double lowerWick = Math.Min(open, close) - low;
double wickPct = (lowerWick / candleRange) * 100.0;
if (sweepDistance >= MinSweepPoints && close > session.PmLow && wickPct >= MinWickPercent)
{
session.BullishSweepTriggered = true;
double sl = low - SlBufferPoints;
double risk = close - sl;
double tp1 = close + (risk * 1.5);
double tp2 = close + (risk * 3.0);
string iconName = $"LongSweep_{tradingDay:yyyyMMdd}_{idx}";
Chart.DrawIcon(iconName, ChartIconType.UpArrow, Bars.OpenTimes[idx], low - (candleRange * 0.25), Color.LimeGreen);
TriggerSignal("LONG (PM Low Sweep)", close, sl, tp1, tp2, idx);
}
}
}
private void DrawSessionLevels(DateTime day, DailySessionData session, DateTime nyOpenTime)
{
string highLine = $"PM_High_{day:yyyyMMdd}";
string lowLine = $"PM_Low_{day:yyyyMMdd}";
string boxName = $"PM_Box_{day:yyyyMMdd}";
// Shaded Pre-Market Range Box
var box = Chart.DrawRectangle(boxName, session.PmStartTime, session.PmHigh, nyOpenTime, session.PmLow, Color.FromArgb(30, 100, 149, 237));
box.IsFilled = true;
// Projected Liquidity Lines into NY Killzone
Chart.DrawTrendLine(highLine, nyOpenTime, session.PmHigh, session.NyEndTime, session.PmHigh, Color.DodgerBlue, 2, LineStyle.DotsRare);
Chart.DrawTrendLine(lowLine, nyOpenTime, session.PmLow, session.NyEndTime, session.PmLow, Color.DodgerBlue, 2, LineStyle.DotsRare);
}
private void TriggerSignal(string direction, double entry, double sl, double tp1, double tp2, int barIdx)
{
// Only fire real-time notifications if this is the most recent closed bar
if (barIdx < Bars.Count - 2) return;
string msg = $"{Symbol.Name} M15 {direction} | Entry: {entry:F2} | SL: {sl:F2} | TP1 (1.5R): {tp1:F2} | TP2 (3R): {tp2:F2}";
Print(msg);
if (EnableAlerts)
{
Notifications.PlaySound(SoundType.Announcement);
MessageBox.Show(msg, $"NY Open Liquidity Sweep: {Symbol.Name}", MessageBoxButton.OK, MessageBoxImage.Information);
}
if (EnableEmail && !string.IsNullOrWhiteSpace(EmailAddress))
{
Notifications.SendEmail(EmailAddress, EmailAddress, $"[cTrader Sweep] {Symbol.Name} {direction}", msg);
}
}
}
}