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
+ }
0 commit comments