Skip to content

Commit

Permalink
1.02
Browse files Browse the repository at this point in the history
1. Added an option to check the price against a threshold before initiating a trade.
2. Fixed a bug in position size verification.
  • Loading branch information
EarnForex authored Jan 13, 2025
1 parent 4a0aa41 commit b24efe7
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 18 deletions.
84 changes: 76 additions & 8 deletions TimedOrder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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/
// -------------------------------------------------------------------------------

Expand Down Expand Up @@ -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; }

Expand Down Expand Up @@ -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; }

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
Expand Down
67 changes: 62 additions & 5 deletions TimedOrder.mq4
Original file line number Diff line number Diff line change
@@ -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 <stdlib.mqh>
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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";
Expand Down
68 changes: 63 additions & 5 deletions TimedOrder.mq5
Original file line number Diff line number Diff line change
@@ -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 <Trade/Trade.mqh>

Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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";
Expand Down

0 comments on commit b24efe7

Please sign in to comment.