11// -------------------------------------------------------------------------------
2- // Opens a trade (market or pending order) at specified time.
3- //
4- // As of 2022-09-23, Stop Limit orders in cTrader don't make much sense, so they aren't implemented in this EA.
2+ // Opens a trade (market or pending order) at the specified time.
3+ // One-time or daily.
54//
6- // Version 1.00.
7- // Copyright 2022, EarnForex.com
5+ // As of 2023-11-20, Stop Limit orders in cTrader don't make much sense, so they aren't implemented in this EA.
6+ //
7+ // Version 1.01.
8+ // Copyright 2023, EarnForex.com
89// https://www.earnforex.com/metatrader-expert-advisors/TimedOrder/
910// -------------------------------------------------------------------------------
1011
@@ -68,12 +69,18 @@ public enum ENUM_TIME_TYPE
6869 [ Parameter ( DefaultValue = 0 , MinValue = 0 , MaxValue = 59 ) ]
6970 public int Minute { get ; set ; }
7071
72+ [ Parameter ( DefaultValue = 0 , MinValue = 0 , MaxValue = 59 ) ]
73+ public int Second { get ; set ; }
74+
7175 [ Parameter ( "Order type" , DefaultValue = ENUM_BETTER_ORDER_TYPE . Buy ) ]
7276 public ENUM_BETTER_ORDER_TYPE OrderType { get ; set ; }
7377
74- [ Parameter ( "Entry price (optional unless pending )" , DefaultValue = 0 , MinValue = 0 ) ]
78+ [ Parameter ( "Entry price (optional)" , DefaultValue = 0 , MinValue = 0 ) ]
7579 public double Entry { get ; set ; }
7680
81+ [ Parameter ( "Entry distance in points (for pending)" , DefaultValue = 0 , MinValue = 0 ) ]
82+ public int EntryDistancePoints { get ; set ; }
83+
7784 [ Parameter ( "Stop-loss type" , DefaultValue = ENUM_SLTP_TYPE . Price_Level ) ]
7885 public ENUM_SLTP_TYPE SLType { get ; set ; }
7986
@@ -108,6 +115,9 @@ public enum ENUM_TIME_TYPE
108115 [ Parameter ( DefaultValue = 0 , MinValue = 0 , MaxValue = 59 ) ]
109116 public int MinuteExp { get ; set ; }
110117
118+ [ Parameter ( DefaultValue = 0 , MinValue = 0 , MaxValue = 59 ) ]
119+ public int SecondExp { get ; set ; }
120+
111121 [ Parameter ( "How many times to try sending order before failure?" , DefaultValue = 10 , MinValue = 1 ) ]
112122 public int Retries { get ; set ; }
113123
@@ -117,6 +127,9 @@ public enum ENUM_TIME_TYPE
117127 [ Parameter ( "Maximum spread in points" , DefaultValue = 30 , MinValue = 0 ) ]
118128 public int MaxSpread { get ; set ; }
119129
130+ [ Parameter ( "Retry until spread falls below MaxSpread?" , DefaultValue = false ) ]
131+ public bool RetryUntilMaxSpread { get ; set ; }
132+
120133 [ Parameter ( DefaultValue = 1 , MinValue = 0 ) ]
121134 public int Slippage { get ; set ; }
122135
@@ -127,9 +140,45 @@ public enum ENUM_TIME_TYPE
127140 public int ATR_Period { get ; set ; }
128141
129142
143+ [ Parameter ( "=== Daily mode" , DefaultValue = "=================" ) ]
144+ public string DailyModeInputs { get ; set ; }
145+
146+ [ Parameter ( "Daily mode: if true, will trade every given day." , DefaultValue = false ) ]
147+ public bool DailyMode { get ; set ; }
148+
149+ [ Parameter ( DefaultValue = 0 , MinValue = 0 , MaxValue = 23 ) ]
150+ public int DailyHour { get ; set ; }
151+
152+ [ Parameter ( DefaultValue = 0 , MinValue = 0 , MaxValue = 59 ) ]
153+ public int DailyMinute { get ; set ; }
154+
155+ [ Parameter ( DefaultValue = 0 , MinValue = 0 , MaxValue = 59 ) ]
156+ public int DailySecond { get ; set ; }
157+
158+ [ Parameter ( DefaultValue = true ) ]
159+ public bool Monday { get ; set ; }
160+
161+ [ Parameter ( DefaultValue = true ) ]
162+ public bool Tuesday { get ; set ; }
163+
164+ [ Parameter ( DefaultValue = true ) ]
165+ public bool Wednesday { get ; set ; }
166+
167+ [ Parameter ( DefaultValue = true ) ]
168+ public bool Thursday { get ; set ; }
169+
170+ [ Parameter ( DefaultValue = true ) ]
171+ public bool Friday { get ; set ; }
172+
173+ [ Parameter ( DefaultValue = false ) ]
174+ public bool Saturday { get ; set ; }
175+
176+ [ Parameter ( DefaultValue = false ) ]
177+ public bool Sunday { get ; set ; }
178+
130179 [ Parameter ( "=== Position sizing" , DefaultValue = "=================" ) ]
131180 public string PositionSizing { get ; set ; }
132-
181+
133182 [ Parameter ( "CalculatePositionSize: Use money management module?" , DefaultValue = false ) ]
134183 public bool CalculatePositionSize { get ; set ; }
135184
@@ -194,18 +243,19 @@ public enum ENUM_TIME_TYPE
194243 private bool CanWork = false ;
195244 private bool WillNoLongerTryOpeningTrade = false ;
196245 private string PostOrderText = "" ;
246+ private bool [ ] EnabledDays ; // For Daily Mode.
197247
198248 protected override void OnStart ( )
199249 {
200- trade_time = new DateTime ( Year , Month , Day , Hour , Minute , 0 ) ;
201- expires_time = new DateTime ( YearExp , MonthExp , DayExp , HourExp , MinuteExp , 0 ) ;
250+ trade_time = new DateTime ( Year , Month , Day , Hour , Minute , Second ) ;
251+ expires_time = new DateTime ( YearExp , MonthExp , DayExp , HourExp , MinuteExp , SecondExp ) ;
202252 unix_epoch = new DateTime ( 1970 , 1 , 1 , 0 , 0 , 0 ) ;
203253
204254 string Error = CheckInputParameters ( ) ;
205255 if ( Error != "" )
206256 {
207257 if ( ! Silent ) Chart . DrawStaticText ( "TimedOrder" , "Wrong input parameters!\n " + Error , CornerVertical , CornerHorizontal , Color . Red ) ;
208- Print ( "Wrong input parambers ! " + Error ) ;
258+ Print ( "Wrong input parameters ! " + Error ) ;
209259 CanWork = false ;
210260 return ;
211261 }
@@ -220,23 +270,23 @@ protected override void OnStart()
220270 tf_data = MarketData . GetBars ( ATR_Timeframe ) ;
221271 ATR = Indicators . AverageTrueRange ( tf_data , ATR_Period , MovingAverageType . Simple ) ;
222272 }
223-
273+
274+ EnabledDays = new bool [ 7 ] ;
275+ EnabledDays [ 0 ] = Sunday ;
276+ EnabledDays [ 1 ] = Monday ;
277+ EnabledDays [ 2 ] = Tuesday ;
278+ EnabledDays [ 3 ] = Wednesday ;
279+ EnabledDays [ 4 ] = Thursday ;
280+ EnabledDays [ 5 ] = Friday ;
281+ EnabledDays [ 6 ] = Saturday ;
282+
224283 // For smooth updates.
225284 Timer . Start ( TimeSpan . FromMilliseconds ( 100 ) ) ;
226285
227286 CanWork = true ;
228287 WillNoLongerTryOpeningTrade = false ;
229288 }
230289
231- //+------------------------------------------------------------------+
232- //| Stops timer if needed. |
233- //+------------------------------------------------------------------+
234- /*protected override void OnStop()
235- {
236- Timer.Stop();
237- }
238- }*/
239-
240290//+------------------------------------------------------------------+
241291//| Updates text about time left to news or passed after news. |
242292//+------------------------------------------------------------------+
@@ -259,11 +309,15 @@ protected override void OnTick()
259309//+------------------------------------------------------------------+
260310 private void DoTrading ( )
261311 {
312+ DateTime order_time = trade_time ;
313+ if ( DailyMode ) order_time = GetOrderTimeForDailyMode ( ) ;
314+
262315 // Do nothing if it is too early.
263316 TimeSpan difference ;
264- if ( TimeType == ENUM_TIME_TYPE . Server ) difference = Time . Subtract ( trade_time ) ;
265- else difference = DateTime . Now . Subtract ( trade_time ) ;
317+ if ( TimeType == ENUM_TIME_TYPE . Server ) difference = Time . Subtract ( order_time ) ;
318+ else difference = DateTime . Now . Subtract ( order_time ) ;
266319 if ( difference <= TimeSpan . FromMilliseconds ( 0 ) ) return ;
320+
267321
268322 double SL , TP ;
269323 if ( SLType == ENUM_SLTP_TYPE . ATR ) SL = ATR . Result . LastValue * StopLoss ;
@@ -276,15 +330,21 @@ private void DoTrading()
276330 else if ( TPType == ENUM_SLTP_TYPE . Distance ) TP = TakeProfit * Symbol . TickSize ;
277331 else TP = TakeProfit ; // Level.
278332
279- // Time is due. At this point, if somethign fails, the EA won't try again until it is reloaded.
333+ // Time is due. At this point, if something fails, the EA won't try again until it is reloaded.
280334 WillNoLongerTryOpeningTrade = true ;
281335
282336 // Prevent position opening when the spread is too wide (greater than MaxSpread input).
283337 double spread = Symbol . Spread ;
284338 if ( ( MaxSpread > 0 ) && ( spread / Symbol . TickSize > MaxSpread ) )
285339 {
286340 string explanation = "Current spread " + ( spread / Symbol . TickSize ) . ToString ( ) + " > maximum spread " + MaxSpread . ToString ( ) + ". Not opening the trade." ;
341+ if ( RetryUntilMaxSpread ) explanation += " Waiting for spread to go below the MaxSpread setting." ;
287342 Print ( explanation ) ;
343+ if ( RetryUntilMaxSpread )
344+ {
345+ WillNoLongerTryOpeningTrade = false ; // Let it try again.
346+ return ;
347+ }
288348 if ( AlertsOnFailure )
289349 {
290350 string Text = Symbol . Name + " @ " + TimeFrame . Name + " - " + OrderType . ToString ( ) + ". " + explanation ;
@@ -324,16 +384,26 @@ private void DoTrading()
324384 {
325385 // Modify trade type based on the current price.
326386 double price = Symbol . Ask ;
387+ double entry = Entry ;
327388 if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Stop ) || ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Limit ) ) price = Symbol . Bid ;
328- if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Buy_Stop ) && ( Entry < price ) ) order_type = ENUM_BETTER_ORDER_TYPE . Buy_Limit ;
329- else if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Buy_Limit ) && ( Entry > price ) ) order_type = ENUM_BETTER_ORDER_TYPE . Buy_Stop ;
330- else if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Stop ) && ( Entry > price ) ) order_type = ENUM_BETTER_ORDER_TYPE . Sell_Limit ;
331- else if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Limit ) && ( Entry < price ) ) order_type = ENUM_BETTER_ORDER_TYPE . Sell_Stop ;
332-
333- if ( ( SLType == ENUM_SLTP_TYPE . Price_Level ) && ( SL != 0 ) ) SL = Math . Abs ( Entry - SL ) ;
334- if ( ( TPType == ENUM_SLTP_TYPE . Price_Level ) && ( TP != 0 ) ) TP = Math . Abs ( Entry - TP ) ;
335389
336- tr = CreatePendingOrder ( order_type , GetPositionSize ( SL / Symbol . PipSize ) , Entry , SL / Symbol . PipSize , TP / Symbol . PipSize ) ;
390+ if ( EntryDistancePoints > 0 )
391+ {
392+ // order_type remains being equal TradeType.
393+ if ( ( order_type == ENUM_BETTER_ORDER_TYPE . Buy_Stop ) || ( order_type == ENUM_BETTER_ORDER_TYPE . Sell_Limit ) ) entry = price + EntryDistancePoints * Symbol . TickSize ;
394+ else if ( ( order_type == ENUM_BETTER_ORDER_TYPE . Sell_Stop ) || ( order_type == ENUM_BETTER_ORDER_TYPE . Buy_Limit ) ) entry = price - EntryDistancePoints * Symbol . TickSize ;
395+ }
396+ else
397+ {
398+ if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Buy_Stop ) && ( Entry < price ) ) order_type = ENUM_BETTER_ORDER_TYPE . Buy_Limit ;
399+ else if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Buy_Limit ) && ( Entry > price ) ) order_type = ENUM_BETTER_ORDER_TYPE . Buy_Stop ;
400+ else if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Stop ) && ( Entry > price ) ) order_type = ENUM_BETTER_ORDER_TYPE . Sell_Limit ;
401+ else if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Limit ) && ( Entry < price ) ) order_type = ENUM_BETTER_ORDER_TYPE . Sell_Stop ;
402+ }
403+ if ( ( SLType == ENUM_SLTP_TYPE . Price_Level ) && ( SL != 0 ) ) SL = Math . Abs ( entry - SL ) ;
404+ if ( ( TPType == ENUM_SLTP_TYPE . Price_Level ) && ( TP != 0 ) ) TP = Math . Abs ( entry - TP ) ;
405+
406+ tr = CreatePendingOrder ( order_type , GetPositionSize ( SL / Symbol . PipSize ) , entry , SL / Symbol . PipSize , TP / Symbol . PipSize ) ;
337407
338408 PostOrderText = Symbol . VolumeInUnitsToQuantity ( tr . PendingOrder . VolumeInUnits ) . ToString ( ) + " lots; Open = " + tr . PendingOrder . TargetPrice . ToString ( ) ;
339409 if ( SL != 0 ) PostOrderText += " SL = " + tr . PendingOrder . StopLoss . ToString ( ) ;
@@ -478,7 +548,8 @@ string TimeDistance(TimeSpan t)
478548//+------------------------------------------------------------------+
479549//| Calculate position size depending on money management parameters.|
480550//+------------------------------------------------------------------+
481- double GetPositionSize ( double SL )
551+ double GetPositionSize
552+ ( double SL )
482553 {
483554 if ( ! CalculatePositionSize )
484555 return ( Symbol . QuantityToVolumeInUnits ( FixedPositionSize ) ) ;
@@ -536,14 +607,20 @@ double GetPositionSize(double SL)
536607// Checks whether input parameters make sense and returns error if they don't.
537608 string CheckInputParameters ( )
538609 {
539- // Order time has already passed.
540- if ( ( ( TimeType == ENUM_TIME_TYPE . Server ) && ( trade_time <= Time ) ) || ( ( TimeType == ENUM_TIME_TYPE . Local ) && ( trade_time <= DateTime . Now ) ) ) return "Order time has already passed." ;
541-
610+ if ( ! DailyMode ) // Normal mode (one-time fixed-date trade).
611+ {
612+ // Order time has already passed.
613+ if ( ( ( TimeType == ENUM_TIME_TYPE . Server ) && ( trade_time <= Time ) ) || ( ( TimeType == ENUM_TIME_TYPE . Local ) && ( trade_time <= DateTime . Now ) ) ) return "Order time has already passed." ;
614+ }
615+ else // Daily mode (trades every set day at a given time).
616+ {
617+ if ( ( ! Monday ) && ( ! Tuesday ) && ( ! Wednesday ) && ( ! Thursday ) && ( ! Friday ) && ( ! Saturday ) && ( ! Sunday ) ) return "At least one day of the week should be selected." ;
618+ }
542619 // Pending order with zero entry.
543- if ( ( OrderType != ENUM_BETTER_ORDER_TYPE . Buy ) && ( OrderType != ENUM_BETTER_ORDER_TYPE . Sell ) && ( Entry == 0 ) ) return "Entry price cannot be zero for pending orders." ;
620+ if ( ( OrderType != ENUM_BETTER_ORDER_TYPE . Buy ) && ( OrderType != ENUM_BETTER_ORDER_TYPE . Sell ) && ( Entry == 0 ) && ( EntryDistancePoints <= 0 ) ) return "Entry price and distance cannot be both zero for pending orders." ;
544621
545622 // SL on the wrong side.
546- if ( ( StopLoss != 0 ) && ( SLType == ENUM_SLTP_TYPE . Price_Level ) )
623+ if ( ( StopLoss != 0 ) && ( SLType == ENUM_SLTP_TYPE . Price_Level ) && ( EntryDistancePoints <= 0 ) )
547624 {
548625 if ( StopLoss >= Entry )
549626 {
@@ -556,7 +633,7 @@ string CheckInputParameters()
556633 if ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Limit ) return "Stop-loss cannot be below entry for a Sell Limit." ;
557634 }
558635 }
559- if ( ( TakeProfit != 0 ) && ( TPType == ENUM_SLTP_TYPE . Price_Level ) )
636+ if ( ( TakeProfit != 0 ) && ( TPType == ENUM_SLTP_TYPE . Price_Level ) && ( EntryDistancePoints <= 0 ) )
560637 {
561638 if ( TakeProfit <= Entry )
562639 {
@@ -599,10 +676,14 @@ void ShowStatus()
599676
600677 s += OrderToString ( OrderType ) ;
601678
602- if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Buy_Stop ) || ( OrderType == ENUM_BETTER_ORDER_TYPE . Buy_Limit ) || ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Stop ) || ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Limit ) ) s += " @ " + Entry . ToString ( ) ;
679+ if ( ( OrderType == ENUM_BETTER_ORDER_TYPE . Buy_Stop ) || ( OrderType == ENUM_BETTER_ORDER_TYPE . Buy_Limit ) || ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Stop ) || ( OrderType == ENUM_BETTER_ORDER_TYPE . Sell_Limit ) )
680+ {
681+ if ( EntryDistancePoints <= 0 ) s += " @ " + Entry . ToString ( ) ;
682+ else s += " @ " + EntryDistancePoints . ToString ( ) + " pts." ;
683+ }
603684
604685 if ( failure ) s += "\n Execution failed!" ;
605- if ( trade_done ) // Order already executed or tried to execute.
686+ if ( ( ! DailyMode ) && ( trade_done ) ) // Order already executed or tried to execute.
606687 {
607688 if ( ! failure )
608689 {
@@ -640,14 +721,25 @@ void ShowStatus()
640721
641722 s += "\n " ;
642723
724+ DateTime order_time = trade_time ;
725+ if ( DailyMode ) order_time = GetOrderTimeForDailyMode ( ) ;
726+
643727 TimeSpan difference ;
644- if ( TimeType == ENUM_TIME_TYPE . Server ) difference = Time . Subtract ( trade_time ) ;
645- else difference = DateTime . Now . Subtract ( trade_time ) ;
728+ if ( TimeType == ENUM_TIME_TYPE . Server ) difference = Time . Subtract ( order_time ) ;
729+ else difference = DateTime . Now . Subtract ( order_time ) ;
646730
647731 if ( difference <= TimeSpan . FromMilliseconds ( 0 ) )
648- s += "Time to order:" + TimeDistance ( difference . Negate ( ) ) + "." ;
732+ s += "Time to order:" + TimeDistance ( difference . Negate ( ) ) ;
649733 else
650- s += "Time after order:" + TimeDistance ( difference ) + "." ;
734+ s += "Time after order:" + TimeDistance ( difference ) ;
735+
736+ if ( DailyMode )
737+ {
738+ if ( difference < - TimeSpan . FromSeconds ( 10 ) ) WillNoLongerTryOpeningTrade = false ; // Reset for further order taking.
739+ s += " (daily mode)" ;
740+ }
741+ s += "." ;
742+
651743 Chart . DrawStaticText ( "TimedOrder" , s , CornerVertical , CornerHorizontal , Color . Red ) ;
652744 }
653745
@@ -661,5 +753,29 @@ string OrderToString(ENUM_BETTER_ORDER_TYPE ot)
661753 if ( ot == ENUM_BETTER_ORDER_TYPE . Sell_Stop ) return "Sell Stop" ;
662754 return "" ;
663755 }
756+
757+ DateTime GetOrderTimeForDailyMode ( )
758+ {
759+ bool skip_current_day = false ;
760+
761+ DateTime current_time = DateTime . Now ;
762+ if ( TimeType == ENUM_TIME_TYPE . Server ) current_time = Time ;
763+
764+ DateTime target_time = new DateTime ( current_time . Year , current_time . Month , current_time . Day , DailyHour , DailyMinute , DailySecond ) ; // It's important to get the target time of the appropriate day (local/server).
765+
766+ TimeSpan difference = current_time . Subtract ( target_time ) ;
767+ if ( difference > TimeSpan . FromSeconds ( 10 ) ) skip_current_day = true ; // Skip the current day. Give a 10 seconds buffer.
768+ // Find the next enabled day:
769+ for ( int i = 0 ; i <= 7 ; i ++ , target_time = target_time . AddDays ( 1 ) ) // <= because the next day could be the same day of the week.
770+ {
771+ if ( ( i == 0 ) && ( skip_current_day ) )
772+ {
773+ continue ;
774+ }
775+ if ( EnabledDays [ ( int ) target_time . DayOfWeek ] ) break ;
776+ }
777+
778+ return target_time ;
779+ }
664780 }
665781}
0 commit comments