diff --git a/TimedOrder.cs b/TimedOrder.cs index 5b90f36..2a72b33 100644 --- a/TimedOrder.cs +++ b/TimedOrder.cs @@ -2,10 +2,8 @@ // Opens a trade (market or pending order) at the specified time. // One-time or daily. // -// As of 2023-11-20, Stop Limit orders in cTrader don't make much sense, so they aren't implemented in this EA. -// -// Version 1.01. -// Copyright 2023, EarnForex.com +// Version 1.02. +// Copyright 2025, EarnForex.com // https://www.earnforex.com/metatrader-expert-advisors/TimedOrder/ // ------------------------------------------------------------------------------- @@ -45,6 +43,12 @@ public enum ENUM_TIME_TYPE Server // Server time } + public enum ENUM_INEQUALITY + { + LessThan, // <= + GreaterThan // >= + }; + [Parameter("=== Trading", DefaultValue = "=================")] public string MainSettings { get; set; } @@ -140,6 +144,22 @@ public enum ENUM_TIME_TYPE public int ATR_Period { get; set; } + [Parameter("=== Price Check", DefaultValue = "=================")] + public string PriceCheckInputs { get; set; } + + [Parameter("Use Price Check", DefaultValue = false)] + public bool UsePriceCheck { get; set; } + + [Parameter("Price Symbol (Empty = Current)", DefaultValue = "")] + public string PriceSymbol { get; set; } + + [Parameter("Above or Below", DefaultValue = ENUM_INEQUALITY.LessThan)] + public ENUM_INEQUALITY AboveOrBelow { get; set; } + + [Parameter("Check Price", DefaultValue = 0, MinValue = 0)] + public double Price { get; set; } + + [Parameter("=== Daily mode", DefaultValue = "=================")] public string DailyModeInputs { get; set; } @@ -354,6 +374,42 @@ private void DoTrading() return; } + if (UsePriceCheck) + { + string s = Symbol.Name; + if (PriceSymbol != "") s = PriceSymbol; + + string explanation = ""; + Symbol symb = Symbols.GetSymbol(s); + if (symb == null) + { + explanation = "Symbol not found: " + s + "!"; + } + else if (AboveOrBelow == ENUM_INEQUALITY.LessThan) + { + if (symb.Bid > Price) // Fail + { + explanation = s + " price " + symb.Bid.ToString() + " > " + Price.ToString() + ". Not opening the trade."; + } + } + else if (symb.Ask < Price) // Fail + { + explanation = s + " price " + symb.Ask.ToString() + " < " + Price.ToString() + ". Not opening the trade."; + } + + if (explanation != "") + { + Print(explanation); + if (AlertsOnFailure) + { + string Text = Symbol.Name + " @ " + TimeFrame.Name + " - " + OrderType.ToString() + ". " + explanation; + Notifications.SendEmail(AlertEmailFrom, AlertEmailTo, "Timed Order Alert - " + Symbol.Name + " @ " + TimeFrame.Name, Text); + } + failure = true; + return; + } + } + TradeResult tr; ENUM_BETTER_ORDER_TYPE order_type = OrderType; // Might get updated in pending mode. Should be reflected in the alerts. if ((order_type == ENUM_BETTER_ORDER_TYPE.Buy) || (order_type == ENUM_BETTER_ORDER_TYPE.Sell)) // Market @@ -595,7 +651,7 @@ double GetPositionSize double LotStep = Symbol.VolumeInUnitsStep; double steps = PositionSize / LotStep; - if (Math.Floor(steps) < steps) + if (Math.Abs(Math.Round(steps) - steps) > 0.00000001) { Print("Calculated position size (" + PositionSize + ") uses uneven step size. Allowed step size = " + LotStep + ". Setting position size to " + (Math.Floor(steps) * LotStep) + "."); PositionSize = Math.Floor(steps) * LotStep; @@ -657,7 +713,7 @@ string CheckInputParameters() if (FixedPositionSize < min_lot) return "Position size " + FixedPositionSize.ToString() + " < minimum volume " + min_lot.ToString(); if (FixedPositionSize > max_lot) return "Position size " + FixedPositionSize.ToString() + " > maximum volume " + max_lot.ToString(); double steps = FixedPositionSize / lot_step; - if (Math.Floor(steps) < steps) return "Position size " + FixedPositionSize.ToString() + " is not a multiple of lot step " + lot_step.ToString(); + if (Math.Abs(Math.Round(steps) - steps) > 0.00000001) return "Position size " + FixedPositionSize.ToString() + " is not a multiple of lot step " + lot_step.ToString(); } else { @@ -717,8 +773,20 @@ void ShowStatus() else if (FixedBalance > 0) s += "Risk = " + Risk.ToString() + "% of " + FixedBalance.ToString() + " " + Account.Asset.Name; else s += "Risk = " + Risk.ToString() + "% of Balance (" + Account.Balance.ToString() + " " + Account.Asset.Name + ")"; } - } - + + if (UsePriceCheck) + { + s += "\n"; + + string symbol = Symbol.Name; + if (PriceSymbol != "") symbol = PriceSymbol; + s += symbol; + if (AboveOrBelow == ENUM_INEQUALITY.LessThan) s += " <= "; + else s += " >= "; + s += Price.ToString(); + } + } + s += "\n"; DateTime order_time = trade_time; diff --git a/TimedOrder.mq4 b/TimedOrder.mq4 index f58fb3c..3da53c3 100644 --- a/TimedOrder.mq4 +++ b/TimedOrder.mq4 @@ -1,11 +1,11 @@ //+------------------------------------------------------------------+ //| Timed Order | -//| Copyright © 2023, EarnForex.com | +//| Copyright © 2025, EarnForex.com | //| https://www.earnforex.com/ | //+------------------------------------------------------------------+ -#property copyright "Copyright © 2023, EarnForex" +#property copyright "Copyright © 2025, EarnForex" #property link "https://www.earnforex.com/metatrader-expert-advisors/TimedOrder/" -#property version "1.01" +#property version "1.02" #property strict #include @@ -38,6 +38,12 @@ enum ENUM_BETTER_ORDER_TYPE BETTER_ORDER_TYPE_SELLSTOP // Sell Stop }; +enum ENUM_INEQUALITY +{ + LESSTHAN, // <= + GREATERTHAN // >= +}; + input group "Trading" input datetime OrderTime = __DATETIME__; // Date/time (server) to open order input ENUM_BETTER_ORDER_TYPE OrderType = BETTER_ORDER_TYPE_BUY; // Order type @@ -57,6 +63,11 @@ input bool RetryUntilMaxSpread = false; // Retry until spread falls below MaxSpr input int Slippage = 30; // Maximum slippage in points input ENUM_TIMEFRAMES ATR_Timeframe = PERIOD_CURRENT; // ATR Timeframe input int ATR_Period = 14; // ATR Period +input group "Price check" +input bool UsePriceCheck = false; +input string PriceSymbol = ""; // PriceSymbol: Empty = current +input ENUM_INEQUALITY AboveOrBelow = LESSTHAN; +input double Price = 0; input group "Daily mode" input bool DailyMode = false; // Daily mode: if true, will trade every given day. input string DailyTime = "18:34:00"; // Time for daily trades in HH:MM:SS format. @@ -412,7 +423,7 @@ double GetPositionSize(double entry, double stoploss) if (PositionSize < MarketInfo(Symbol(), MODE_MINLOT)) PositionSize = MarketInfo(Symbol(), MODE_MINLOT); else if (PositionSize > MarketInfo(Symbol(), MODE_MAXLOT)) PositionSize = MarketInfo(Symbol(), MODE_MAXLOT); double steps = PositionSize / SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); - if (MathFloor(steps) < steps) PositionSize = MathFloor(steps) * MarketInfo(Symbol(), MODE_LOTSTEP); + if (MathAbs(MathRound(steps) - steps) > 0.00000001) PositionSize = MathFloor(steps) * MarketInfo(Symbol(), MODE_LOTSTEP); return PositionSize; } @@ -515,6 +526,40 @@ void DoTrading() return; } + if (UsePriceCheck) + { + string s = Symbol(); + if (PriceSymbol != "") s = PriceSymbol; + + string explanation = ""; + if (AboveOrBelow == LESSTHAN) + { + if (SymbolInfoDouble(s, SYMBOL_BID) > Price) // Fail + { + explanation = s + " price " + DoubleToString(SymbolInfoDouble(s, SYMBOL_BID), (int)SymbolInfoInteger(s, SYMBOL_DIGITS)) + " > " + DoubleToString(Price, (int)SymbolInfoInteger(s, SYMBOL_DIGITS)) + ". Not opening the trade."; + } + } + else if (SymbolInfoDouble(s, SYMBOL_ASK) < Price) // Fail + { + explanation = s + " price " + DoubleToString(SymbolInfoDouble(s, SYMBOL_ASK), (int)SymbolInfoInteger(s, SYMBOL_DIGITS)) + " < " + DoubleToString(Price, (int)SymbolInfoInteger(s, SYMBOL_DIGITS)) + ". Not opening the trade."; + } + + if (explanation != "") + { + Output(explanation); + if (AlertsOnFailure) + { + string Text; + Text = Symbol() + " @ " + StringSubstr(EnumToString((ENUM_TIMEFRAMES)Period()), 7) + " - " + OrderToString(TradeType) + ". " + explanation; + if (EnableNativeAlerts) Alert(Text); + if (EnableEmailAlerts) SendMail("Timed Order Alert - " + Symbol() + " @ " + StringSubstr(EnumToString((ENUM_TIMEFRAMES)Period()), 7), Text); + if (EnablePushAlerts) SendNotification(Text); + } + global_ticket = -1; + return; + } + } + int ticket = 0; ENUM_ORDER_TYPE order_type = TradeType; // Might get updated in pending mode. Should be reflected in the alerts. if ((TradeType == OP_BUY) || (TradeType == OP_SELL)) // Market @@ -747,7 +792,7 @@ string CheckInputParameters() if (FixedPositionSize < SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN)) return "Position size " + DoubleToString(FixedPositionSize, CountDecimalPlaces(FixedPositionSize)) + " < minimum volume " + DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN), CountDecimalPlaces(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN))); if (FixedPositionSize > SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX)) return "Position size " + DoubleToString(FixedPositionSize, CountDecimalPlaces(FixedPositionSize)) + " > maximum volume " + DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX), CountDecimalPlaces(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX))); double steps = FixedPositionSize / SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP); - if (MathFloor(steps) < steps) return "Position size " + DoubleToString(FixedPositionSize, CountDecimalPlaces(FixedPositionSize)) + " is not a multiple of lot step " + DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP), CountDecimalPlaces(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP))); + if (MathAbs(MathRound(steps) - steps) > 0.00000001) return "Position size " + DoubleToString(FixedPositionSize, CountDecimalPlaces(FixedPositionSize)) + " is not a multiple of lot step " + DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP), CountDecimalPlaces(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP))); } else { @@ -830,6 +875,18 @@ void ShowStatus() else if (FixedBalance > 0) s += "Risk = " + DoubleToString(Risk, CountDecimalPlaces(Risk)) + "% of " + DoubleToString(FixedBalance, 2) + " " + AccountInfoString(ACCOUNT_CURRENCY); else s += "Risk = " + DoubleToString(Risk, CountDecimalPlaces(Risk)) + "% of Balance (" + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + ")"; } + + if (UsePriceCheck) + { + s += "\n"; + + string symbol = Symbol(); + if (PriceSymbol != "") symbol = PriceSymbol; + s += symbol; + if (AboveOrBelow == LESSTHAN) s += " <= "; + else s += " >= "; + s += DoubleToString(Price, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); + } } s += "\n"; diff --git a/TimedOrder.mq5 b/TimedOrder.mq5 index fbb153d..3bd79c4 100644 --- a/TimedOrder.mq5 +++ b/TimedOrder.mq5 @@ -1,11 +1,11 @@ //+------------------------------------------------------------------+ //| Timed Order | -//| Copyright © 2023, EarnForex.com | +//| Copyright © 2025, EarnForex.com | //| https://www.earnforex.com/ | //+------------------------------------------------------------------+ -#property copyright "Copyright © 2023, EarnForex" +#property copyright "Copyright © 2025, EarnForex" #property link "https://www.earnforex.com/metatrader-expert-advisors/TimedOrder/" -#property version "1.01" +#property version "1.02" #include @@ -39,6 +39,12 @@ enum ENUM_BETTER_ORDER_TYPE BETTER_ORDER_TYPE_SELL_STOP_LIMIT // Sell Stop Limit }; +enum ENUM_INEQUALITY +{ + LESSTHAN, // <= + GREATERTHAN // >= +}; + input group "Trading" input datetime OrderTime = __DATETIME__; // Date/time (server) to open order input ENUM_BETTER_ORDER_TYPE OrderType = BETTER_ORDER_TYPE_BUY; // Order type @@ -59,6 +65,11 @@ input bool RetryUntilMaxSpread = false; // Retry until spread falls below MaxSpr input int Slippage = 30; // Maximum slippage in points input ENUM_TIMEFRAMES ATR_Timeframe = PERIOD_CURRENT; // ATR Timeframe input int ATR_Period = 14; // ATR Period +input group "Price check" +input bool UsePriceCheck = false; +input string PriceSymbol = ""; // PriceSymbol: Empty = current +input ENUM_INEQUALITY AboveOrBelow = LESSTHAN; +input double Price = 0; input group "Daily mode" input bool DailyMode = false; // Daily mode: if true, will trade every given day. input string DailyTime = "18:34:00"; // Time for daily trades in HH:MM:SS format. @@ -354,7 +365,7 @@ double GetPositionSize(double entry, double stoploss, ENUM_ORDER_TYPE dir) if (PositionSize < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) PositionSize = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); else if (PositionSize > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)) PositionSize = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double steps = PositionSize / SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); - if (MathFloor(steps) < steps) PositionSize = MathFloor(steps) * SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + if (MathAbs(MathRound(steps) - steps) > 0.00000001) PositionSize = MathFloor(steps) * SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); return PositionSize; } @@ -469,6 +480,40 @@ void DoTrading() return; } + if (UsePriceCheck) + { + string s = Symbol(); + if (PriceSymbol != "") s = PriceSymbol; + + string explanation = ""; + if (AboveOrBelow == LESSTHAN) + { + if (SymbolInfoDouble(s, SYMBOL_BID) > Price) // Fail + { + explanation = s + " price " + DoubleToString(SymbolInfoDouble(s, SYMBOL_BID), (int)SymbolInfoInteger(s, SYMBOL_DIGITS)) + " > " + DoubleToString(Price, (int)SymbolInfoInteger(s, SYMBOL_DIGITS)) + ". Not opening the trade."; + } + } + else if (SymbolInfoDouble(s, SYMBOL_ASK) < Price) // Fail + { + explanation = s + " price " + DoubleToString(SymbolInfoDouble(s, SYMBOL_ASK), (int)SymbolInfoInteger(s, SYMBOL_DIGITS)) + " < " + DoubleToString(Price, (int)SymbolInfoInteger(s, SYMBOL_DIGITS)) + ". Not opening the trade."; + } + + if (explanation != "") + { + Output(explanation); + if (AlertsOnFailure) + { + string NativeText = OrderToString(TradeType) + ". " + explanation; + string Text = Symbol() + " @ " + StringSubstr(EnumToString((ENUM_TIMEFRAMES)Period()), 7) + " - " + NativeText; + if (EnableNativeAlerts) Alert(NativeText); + if (EnableEmailAlerts) SendMail("Timed Order Alert - " + Symbol() + " @ " + StringSubstr(EnumToString((ENUM_TIMEFRAMES)Period()), 7), Text); + if (EnablePushAlerts) SendNotification(Text); + } + global_ticket = -1; + return; + } + } + long ticket = 0; ENUM_ORDER_TYPE order_type = TradeType; // Might get updated in pending mode. Should be reflected in the alerts. if ((TradeType == ORDER_TYPE_BUY) || (TradeType == ORDER_TYPE_SELL)) // Market @@ -792,7 +837,8 @@ string CheckInputParameters() if (FixedPositionSize < SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN)) return "Position size " + DoubleToString(FixedPositionSize, CountDecimalPlaces(FixedPositionSize)) + " < minimum volume " + DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN), CountDecimalPlaces(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN))); if (FixedPositionSize > SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX)) return "Position size " + DoubleToString(FixedPositionSize, CountDecimalPlaces(FixedPositionSize)) + " > maximum volume " + DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX), CountDecimalPlaces(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX))); double steps = FixedPositionSize / SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP); - if (MathFloor(steps) < steps) return "Position size " + DoubleToString(FixedPositionSize, CountDecimalPlaces(FixedPositionSize)) + " is not a multiple of lot step " + DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP), CountDecimalPlaces(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP))); + // Using double-safe comparison. + if (MathAbs(MathRound(steps) - steps) > 0.00000001) return "Position size " + DoubleToString(FixedPositionSize, CountDecimalPlaces(FixedPositionSize)) + " is not a multiple of lot step " + DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP), CountDecimalPlaces(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP))); } else { @@ -879,6 +925,18 @@ void ShowStatus() else if (FixedBalance > 0) s += "Risk = " + DoubleToString(Risk, CountDecimalPlaces(Risk)) + "% of " + DoubleToString(FixedBalance, CountDecimalPlaces(AccountInfoDouble(ACCOUNT_BALANCE))) + " " + AccountInfoString(ACCOUNT_CURRENCY); else s += "Risk = " + DoubleToString(Risk, CountDecimalPlaces(Risk)) + "% of Balance (" + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), CountDecimalPlaces(AccountInfoDouble(ACCOUNT_BALANCE))) + " " + AccountInfoString(ACCOUNT_CURRENCY) + ")"; } + + if (UsePriceCheck) + { + s += "\n"; + + string symbol = Symbol(); + if (PriceSymbol != "") symbol = PriceSymbol; + s += symbol; + if (AboveOrBelow == LESSTHAN) s += " <= "; + else s += " >= "; + s += DoubleToString(Price, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); + } } s += "\n";