Skip to content

Commit 4604421

Browse files
authored
1.02
1. Added a multi-timeframe (MTF) mode. 2. Added a cTrader version of the Recent High/Low Alert indicator. 3. Added a sound-only alert option. 4. Added an input parameter (Shift) to use select the starting point for recent High/Low calculation. 5. Clarified the TriggerCandle parameter's description.
1 parent f18dfa5 commit 4604421

File tree

3 files changed

+228
-0
lines changed

3 files changed

+228
-0
lines changed

Recent High Low Alert.cs

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// -------------------------------------------------------------------------------
2+
// Draws lines on the high/low of the recent N bars from selected timeframe.
3+
// Alerts when the Bid price of the current bar crosses previous high/low.
4+
// Shift parameter allows displaying High/Low from previous periods.
5+
//
6+
// Version 1.02
7+
// Copyright 2025, EarnForex.com
8+
// https://www.earnforex.com/indicators/Recent-High-Low-Alert/
9+
// -------------------------------------------------------------------------------
10+
11+
using System;
12+
using cAlgo.API;
13+
using cAlgo.API.Internals;
14+
15+
namespace cAlgo.Indicators
16+
{
17+
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
18+
public class MTFRecentHighLowAlert : Indicator
19+
{
20+
[Parameter("Timeframe", DefaultValue = "Current")]
21+
public TimeFrame SelectedTimeFrame { get; set; }
22+
23+
[Parameter("N", DefaultValue = 20, MinValue = 1, Group = "Main")]
24+
public int N { get; set; }
25+
26+
[Parameter("Shift", DefaultValue = 0, MinValue = 0, Group = "Main")]
27+
public int Shift { get; set; }
28+
29+
[Parameter("Trigger Candle", DefaultValue = TriggerCandle.Previous, Group = "Main")]
30+
public TriggerCandle TriggerCandleOption { get; set; }
31+
32+
// Not implemented?
33+
//[Parameter("Native Alerts", DefaultValue = false, Group = "Alerts")]
34+
//public bool EnableNativeAlerts { get; set; }
35+
36+
[Parameter("Sound Alerts", DefaultValue = false, Group = "Alerts")]
37+
public bool EnableSoundAlerts { get; set; }
38+
39+
[Parameter("Sound Type", DefaultValue = SoundType.Announcement, Group = "Alerts")]
40+
public SoundType SoundType { get; set; }
41+
42+
[Parameter("Email Alerts", DefaultValue = false, Group = "Alerts")]
43+
public bool EnableEmailAlerts { get; set; }
44+
45+
[Parameter("Email Address", DefaultValue = "", Group = "Alerts")]
46+
public string EmailAddress { get; set; }
47+
48+
[Output("High", LineColor = "DodgerBlue", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)]
49+
public IndicatorDataSeries High { get; set; }
50+
51+
[Output("Low", LineColor = "Yellow", PlotType = PlotType.Line, LineStyle = LineStyle.Solid, Thickness = 1)]
52+
public IndicatorDataSeries Low { get; set; }
53+
54+
private DateTime LastHighAlert = new DateTime(1970, 1, 1);
55+
private DateTime LastLowAlert = new DateTime(1970, 1, 1);
56+
private bool PriceAboveHigh = false;
57+
private bool PriceBelowLow = false;
58+
private Bars _timeframeBars;
59+
private TimeFrame _usedTimeFrame;
60+
61+
public enum TriggerCandle
62+
{
63+
Current = 0,
64+
Previous = 1
65+
}
66+
67+
private enum Direction
68+
{
69+
HIGH,
70+
LOW
71+
}
72+
73+
protected override void Initialize()
74+
{
75+
// Determine the timeframe to use
76+
if (SelectedTimeFrame.ToString() == "Current")
77+
_usedTimeFrame = TimeFrame;
78+
else
79+
_usedTimeFrame = SelectedTimeFrame;
80+
81+
// Check if selected timeframe is lower than current
82+
if (_usedTimeFrame < TimeFrame)
83+
{
84+
_usedTimeFrame = TimeFrame;
85+
Print("Selected timeframe is lower than current chart timeframe. Using current timeframe instead.");
86+
}
87+
88+
// Get bars for the selected timeframe
89+
_timeframeBars = MarketData.GetBars(_usedTimeFrame);
90+
}
91+
92+
public override void Calculate(int index)
93+
{
94+
// Get the corresponding index on the selected timeframe
95+
var currentTime = Bars.OpenTimes[index];
96+
var tfIndex = _timeframeBars.OpenTimes.GetIndexByTime(currentTime);
97+
98+
if (tfIndex < 0)
99+
{
100+
// No corresponding bar found, use previous values
101+
if (index > 0)
102+
{
103+
High[index] = High[index - 1];
104+
Low[index] = Low[index - 1];
105+
}
106+
return;
107+
}
108+
109+
// Apply shift
110+
tfIndex -= Shift;
111+
112+
// Check if we have enough bars
113+
if (tfIndex - N + 1 < 0)
114+
{
115+
// Not enough data, use previous values
116+
if (index > 0)
117+
{
118+
High[index] = High[index - 1];
119+
Low[index] = Low[index - 1];
120+
}
121+
return;
122+
}
123+
124+
// Find highest high and lowest low in the last N bars of selected timeframe
125+
double highest = 0;
126+
double lowest = double.MaxValue;
127+
128+
for (int i = 0; i < N; i++)
129+
{
130+
if (tfIndex - i < 0) break;
131+
132+
double highValue = _timeframeBars.HighPrices[tfIndex - i];
133+
double lowValue = _timeframeBars.LowPrices[tfIndex - i];
134+
135+
if (highValue > highest) highest = highValue;
136+
if (lowValue < lowest) lowest = lowValue;
137+
}
138+
139+
High[index] = highest;
140+
Low[index] = lowest;
141+
142+
// Alert checking (only in real-time)
143+
if (IsLastBar && index == Bars.Count - 1)
144+
{
145+
int triggerIndex = (int)TriggerCandleOption;
146+
double currentHigh = High[index - triggerIndex];
147+
double currentLow = Low[index - triggerIndex];
148+
149+
// Check if price crossed above high
150+
if (Symbol.Bid > currentHigh)
151+
{
152+
// Only alert if we weren't already above high
153+
if (!PriceAboveHigh && LastHighAlert != Bars.OpenTimes[index])
154+
{
155+
SendAlert(Direction.HIGH, currentHigh, index);
156+
PriceAboveHigh = true;
157+
}
158+
}
159+
else
160+
{
161+
// Price is not above high, reset flag
162+
PriceAboveHigh = false;
163+
}
164+
165+
// Check if price crossed below low
166+
if (Symbol.Bid < currentLow)
167+
{
168+
// Only alert if we weren't already below low
169+
if (!PriceBelowLow && LastLowAlert != Bars.OpenTimes[index])
170+
{
171+
SendAlert(Direction.LOW, currentLow, index);
172+
PriceBelowLow = true;
173+
}
174+
}
175+
else
176+
{
177+
// Price is not below low, reset flag
178+
PriceBelowLow = false;
179+
}
180+
}
181+
}
182+
183+
// Issues alerts and remembers last sent alert time.
184+
private void SendAlert(Direction direction, double price, int index)
185+
{
186+
string alert = "Local ";
187+
string subject;
188+
string tfStr = _usedTimeFrame.ToString();
189+
190+
if (direction == Direction.HIGH)
191+
{
192+
alert += $"high ({tfStr})";
193+
subject = $"High broken @ {Symbol.Name} - {tfStr}";
194+
LastHighAlert = Bars.OpenTimes[index];
195+
}
196+
else if (direction == Direction.LOW)
197+
{
198+
alert += $"low ({tfStr})";
199+
subject = $"Low broken @ {Symbol.Name} - {tfStr}";
200+
LastLowAlert = Bars.OpenTimes[index];
201+
}
202+
else
203+
{
204+
// Default (some enum error)
205+
subject = "Error";
206+
}
207+
208+
alert += $" broken at {price.ToString("F" + Symbol.Digits.ToString())}.";
209+
210+
//if (EnableNativeAlerts)
211+
//{
212+
// Not implemented?
213+
//Notifications.ShowPopup("Recent High/Low Alert", alert, PopupNotificationState.Information);
214+
//}
215+
216+
if (EnableSoundAlerts)
217+
{
218+
Notifications.PlaySound(SoundType);
219+
}
220+
221+
if (EnableEmailAlerts && !string.IsNullOrEmpty(EmailAddress))
222+
{
223+
Notifications.SendEmail(EmailAddress, EmailAddress, subject,
224+
$"{Server.Time.ToString("yyyy-MM-dd HH:mm:ss")} {alert}");
225+
}
226+
}
227+
}
228+
}

RecentHighLowAlert.mq4

6.95 KB
Binary file not shown.

RecentHighLowAlert.mq5

8.78 KB
Binary file not shown.

0 commit comments

Comments
 (0)