Skip to content

Commit 4a0aa41

Browse files
author
EarnForex
authored
1.01
Added the daily mode option for continuous operation. Added an option to set pending order's entry price as the distance from the price at the time of its creation. Added an option to let the EA wait until the current spread tightens below the threshold value. Added parameters for seconds in the cTrader version. Fixed some minor typos in the source code.
1 parent 85bca16 commit 4a0aa41

3 files changed

Lines changed: 473 additions & 122 deletions

File tree

TimedOrder.cs

Lines changed: 160 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
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 += "\nExecution 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

Comments
 (0)