English Русский Español Deutsch 日本語 Português
preview
在 MQL5 中创建每日回撤限制器 EA

在 MQL5 中创建每日回撤限制器 EA

MetaTrader 5交易 | 29 一月 2025, 12:01
325 0
Kikkih25
Kikkih25

概述 

在本文中,我们将使用 MetaQuotes Language (MQL5) 为 MetaTrader 5 创建一个每日回撤限制器的外汇交易 EA 交易系统。该 EA 的目标是为交易账户设定每日回撤上限。该 EA 会分析总柱形数、起始余额、每日时间和每日余额等因素,并检查是否根据特定条件进行交易。它还从 MetaTrader 平台收集余额、资本和资金等信息。该 EA 专为在 MetaTrader 交易平台上运行而设计,需要交易账户才能正常运行。

本次旅程将涵盖以下主题:

  1. 回撤限制器说明
  2. 在 MQL5 中创建 EA
  3. 结论


回撤限制器说明

回撤限制器是交易和投资中使用的一种工具,通过限制回撤期间的潜在损失来管理风险。当市场波动或经济状况导致资产或投资组合价值下降时,就会出现回撤期。在此期间,如果价值低于设定水平,回撤限制器会自动出售全部或部分投资,从而帮助投资者避免重大损失。这一工具旨在减少可能的损失,保障投资者的资金安全。管理期货账户和其他投资工具通常使用回撤限制器来控制风险,并防范市场大幅下跌。 

交易者在交易资金账户时难以控制自己的回撤,每日回撤限制器就是为他们设计的。Prop 公司通常会设定一个名为"交易员每日回撤"的规则,如果不遵守该规则,交易员就会被取消资格。回撤限制器可帮助交易者:

  1. 跟踪账户回撤
  2. 当交易者进行高风险交易时发出警报
  3. 跟踪交易员每日回撤情况
  4. 通过限制敞口头寸防止交易者过度交易 


在 MQL5 中创建 EA 

EA 交易的基本功能是监控账户上的所有活动,无论是手动交易还是由其他 EA 自动进行的交易。交易者需要添加一个图表,它就将获得控制权。交易者会在屏幕上看到 "交通信号灯"。其 "交通信号灯" 功能将以简单的图形方式告知交易者上述四个关键因素。EA 注释可以隐藏,并可自定义,以符合他们喜欢的颜色和字体。只需点击一下,就可以隐藏详细信息页面,以在图表上获得一些空间。交通信号灯的位置和样式可根据图表风格进行超级定制。

我们需要开立交易仓位。开仓的最简单方法是加入一个交易实例,通常是通过加入另一个专门用于开仓的文件来实现。我们使用 include 指令来包含交易库,其中包含用于交易操作的函数。首先,我们用角括弧表示我们要包含的文件包含在 include 文件夹中,以提供一个交易文件夹,然后是普通斜线或反斜线,接着是目标文件名,在本例中为 "Trade.mqh"。CTrade 是一个用于处理交易操作的类,obj-trade 是该类的一个实例,通常是从 CTrade 类创建的指针对象,用于访问该类的成员变量。

#include <Trade/Trade.mqh>
CTrade obj-Trade;

之后,我们需要一些控制逻辑来生成开仓信号。在我们的例子中,函数 OnTick() 正在检查变量 isTradeAllowed 是否为 true。如果是这样,就会调用 checkDailyProfit() 函数,这表明 OnTick() 函数的目的是检查每日利润,并可能根据检查结果允许或不允许交易。bars 变量会记录图表上柱形的总数,确保每个新柱形的交易逻辑只执行一次,防止在一个柱形内多次执行。这些变量共同作用,使 EA 交易能够根据数值生成交易信号,同时保持适当的执行时机。由于该函数不带参数,让我们来看看它执行的如下操作:

  • 它定义了变量 total_day_Profit,并将其初始化为 0。  
  • 此外,它还获取当前时间,并使用存储在日期变量中的 TimeToString 函数将其转换为字符串。
  • 同样,它通过在一天的开始时间上加 1 来计算一天的初始小时数,并将其保存在一个变量中。
  • 检查当前时间(dayTime)是否小于该时间。如果是,则设置 dayTime 值,并使用 Acc_B 函数计算当前余额,将其存储在 dayBalance 变量中。
  • 使用历史记录选择函数选择当天的历史数据,并将开始和结束时间设置为当天的开始和结束时间。
  • 使用 HistoryDealsTotal 函数计算当天的交易总数,并将其存储在 TotalDeals 变量中。
  • 不仅如此,它还会查看历史记录中的每笔交易,检查交易条目类型是否为 DEAL_ENTRY_OUT,这意味着这是一笔平仓交易。如果是,它会通过将 DEAL_PROFIT、DEAL_COMMISSION DEAL_SWAP 值相加来计算交易利润,并将其添加到 total_day_profit 变量中。
  • 它通过使用带有 ACCOUNT_BALANCE 参数的 AccountInfoDouble 函数,从当前账户余额中减去 total_day_Profit 来计算当日的初始余额。

该函数将计算出的期初余额返回为双精度型数值。

int totalBars = 0;
double initialBalance = 0;
datetime dayTime = 0;
double dayBalance = 0;
bool isTradeAllowed = true;

接下来,我们将讨论函数的定义。这些函数似乎与账户信息有关,并根据函数名称返回不同的值。函数 Acc_B()、Acc_E() 和 Acc_S() 分别用于获取有关账户余额、净值和货币的信息。这些函数用于监控账户的财务状况。

double Acc_B(){return AccountInfoDouble(ACCOUNT_BALANCE);}
double Acc_E(){return AccountInfoDouble(ACCOUNT_EQUITY);}
string Acc_S(){return AccountInfoString(ACCOUNT_CURRENCY);}

开立仓位的完整代码如下:

#include <Trade/Trade.mqh>
CTrade obj-Trade;

int totalBars = 0;
double initialBalance = 0;
datetime dayTime = 0;
double dayBalance = 0;
bool isTradeAllowed=true;

double Acc_B(){return AccountInfoDouble(ACCOUNT_BALANCE);}
double Acc_E(){return AccountInfoDouble(ACCOUNT_EQUITY);}
string Acc_S(){return AccountInfoString(ACCOUNT_CURRENCY);}

每当 EA 交易系统初始化时,都会调用 Onlnit 事件处理函数。我们需要在该实例中初始化指标并创建文本,以显示账户的初始余额数据,供进一步分析。为了初始化指标,我们使用内置函数,通过提供正确的参数来调用 createText。文本对象被定位在特定坐标上,并使用特定字体大小和颜色。以下是我们通过此函数实现的目标的细分:

  1. 它使用 Acc_B() 函数查询账户的初始余额,并将其存储在变量 "initialBalance" 中。
  2. 这将在屏幕上(30,30)的位置创建一个文本框,其中包含 "* 进度控制面板 *"文本,字体大小为 13,颜色为淡蓝色(clrAqual)。
  3. 这将创建多个文本框,向用户显示不同的消息和信息。这些文本框位于屏幕上的不同位置,具有不同的字体大小和颜色。

这里的主要目的是创建一个用户界面,显示与账户管理和交易相关的各种信息。文本框用于向用户显示帐户信息、消息和其他相关数据。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {

   initialBalance = Acc_B();
   
   createText("0","***DAILY DRAWDOWN LIMITER ***",30,30,clrAqua,13);
   createText("00","______________________________________",30,30,clrAqua,13);
   createText("1","DrawDown Limiter is Active.",70,50,clrWhite,11);
   createText("2","Counters will be Reset on Next Day Start.",70,65,clrWhite,10);
   createText("3","From: ",70,80,clrWhite,10);
   createText("4",'Time Here',120,80,clrGray,10);
   createText("5","To: ",70,95,clrWhite,10);
   createText("6",'Time Here',120,95,clrGray,10);
   createText("7",'Current: ',70,110,clrWhite,10);
   createText("8",'Time Here',120,110,clrGray,10);

   createText("9",'ACCOUNT DRAWDOWN ============',70,130,clrPeru,11);
   createText("10",'Account Initial Balance: ',70,145,clrWhite,10);
   createText("11",DoubleToString(initialBalance,2)+" "+Acc_S(),250,145,clrWhite,10);
   createText("12",'Torelated DrawDown: ',70,160,clrWhite,10);
   createText("13","12.00 %",250,160,clrAqua,10);
   createText("14",'Current Account Equity: ',70,175,clrWhite,10);
   createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrWhite,10);
   createText("16",'Current Balance Variation: ',70,190,clrWhite,10);
   createText("17",DoubleToString((Acc_E()-Acc_B())/Acc_B()*100,2)+" %",250,190,clrGray,10);

   createText("18",'DAILY DRAWDOWN ================',70,210,clrPeru,11);
   createText("19",'Starting Balance: ',70,225,clrWhite,10);
   createText("20",DoubleToString(Acc_B(),2)+" "+Acc_S(),270,225,clrWhite,10);
   createText("21",'DrawDowm Maximum Threshold: ',70,240,clrWhite,10);
   createText("22",'5.00 %"+" "+Acc_S(),270,240,clrAqua,10);
   createText("23",'DrawDown Maximum Amount: ',70,255,clrWhite,10);
   createText("24",'-"+DoubleToString(Acc_B()*5/100,2)+' "+Acc_S(),270,255,clrYellow,10);
   createText("25",'Current Closed Daily Profit: ',70,270,clrWhite,10);
   createText("26",'0.00"+" "+Acc_S(),270,270,clrGray,10);
   createText("27",'Current DrawDown Percent: ',70,285,clrWhite,10);
   createText("28",'0.00"+" %",270,285,clrGray,10);

   createText("29",'>>> Initializing The Program, Get Ready To Trade.",70,300,clrYellow,10);
   
   return(INIT_SUCCEEDED);
}

在这里,每当 EA 所连接的交易品种出现新的分时报价时,就会调用 OnTick 函数。对于 checkDailyProfit 函数,需要确保其正确实现。isTradeAllowed 是一个布尔型变量,用于控制是否允许交易。如果 isTradeAllowed 为 false,则立即返回,不会在 OnTick 函数中执行其他代码。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){   
   
   checkDailyProfit();
   
   if (!isTradeAllowed) return;
    

我们只需定义分解波动的实例。这需要在每一个分时上进行,因此我们不加限制。我们首先声明卖出价和买入价,一旦满足相应条件,我们将使用这两个价格开仓。请注意,这也需要在每次分时进行,以便我们获得最新报价。在此,我们声明了用于存储近期价格的 double 数据类型变量,并通过对浮点数进行四舍五入,将其归一化为交易品种货币的位数,以保持准确性。

double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); 
double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); 

在定义了突破波动的实例后,我们现在开始定义一个名为 "iBars" 的函数,在我们的例子中,该函数需要两个参数:"_Symbol"(交易品种)和 "_Period"(周期)。"iBars" 返回一个称为 "bars" 的整数值。然后,我们检查变量 "totalBars" 是否等于 "iBars" 函数返回的值。如果两者相等,函数返回,不做任何其他操作。如果它们不相等,"totalBars" 的值将设置为 "iBars" 函数返回的 "bars" 值。

   int bars = iBars(_Symbol,_Period);
   if (totalBars == bars) return;
   totalBars = bars;

现在,我们继续定义 "iBars" 函数;在这里,我们要检查调用 "positionTotal()" 函数的结果是否大于 1。如果是,则函数返回,不做任何其他操作。如果不是,代码将转到下一行。"int number = MathRand()%" 一行似乎不完整,因为没有结尾括号或分号。假定目标是生成一个随机整数,则该行应填写如下:" int number = MathRand()% totalBars;" 该行生成一个介于 0 和 "totalBars" 值(包含)之间的随机数,并将其赋值给变量 "number"。

 if (PositionsTotal() > 1) return;
   int number = MathRand()%

定义函数的完整代码如下:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){   
   
   checkDailyProfit();
   
   if (!isTradeAllowed) return;
   
   double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);

   int bars = iBars(_Symbol,_Period);
   if (totalBars == bars) return;
   totalBars = bars;
   
   if (PositionsTotal() > 1) return;
   int number = MathRand()%

以下是文章函数关键部分的详细介绍,例如下单交易、在图表上创建文本标签以及查看每日利润: 

1.交易执行 

在该组件中,我们将检查名为 "number" 的变量的值,并根据其值采取不同的操作。

如果 "number" 为 0,代码就会在一个名为 "obj-Trade" 的对象上触发一个名为 "Buy" 的方法。该方法需要五个参数:第一个是 0.1,第二个是变量 "_Symbol",第三个是变量 "ask",第四个是 "ask "值减去变量 "_Point" 的 70 倍,第五个是 "ask" 值加上相同变量 "_Point" 的 70 倍。这表明,考虑到买卖价差,代码正试图以略低于当前卖价的价格购买资产。

如果 "number" 为 1,代码就会在同一个对象 "obj-Trade" 上执行名为 "Sell" 的方法。该方法也需要五个参数:第一个是 0.1,第二个是变量 "_Symbol",第三个是变量 "bid",第四个是 "bid" 值加上变量"_Point" 的 70 倍,第五个是 "bid" 值减去同一变量 "_Point" 的 70 倍。这意味着代码正试图以略高于当前买入价的价格出售资产,同时也考虑了买入价与卖出价之间的价差。它会评估一个名为 "number" 的变量的值,并根据其值执行不同的操作。

if (number == 0){
      obj_Trade.Buy(0.1,_Symbol,ask,ask-70*_Point,ask+70*_Point);
   }
   else if (number == 1){
      obj_Trade.Sell(0.1,_Symbol,bid,bid+70*_Point,bid-70*_Point);
   }

2.文本创建

在我们之前的代码中,有一个名为 createText 的函数,负责在图表上创建标签对象。执行此函数需要某些参数,如对象名称(objName)、文本内容(text)、标签位置的 x 和 y 坐标(x 和 y)、文本颜色(clrTxt)和字体大小(font size)。利用这些输入参数,该函数将在图表上创建一个标签对象,自定义其功能,并更新图表。此外,函数还会返回一个布尔型值,表示标签创建成功或失败。


bool createText(string objName,string text,int x, int y,color clrTxt,int fontSize){
   ResetLastError();
   if (!ObjectCreate(0,objName,OBJ_LABEL,0,0,0)){
      Print(__FUNCTION__,": failed to create the Label! Error Code = ",GetLastError());
      return (false);
   }
   ObjectSetInteger(0,objName,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,objName,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,objName,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetString(0,objName,OBJPROP_TEXT,text);
   ObjectSetInteger(0,objName,OBJPROP_COLOR,clrTxt);
   ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,fontSize);
   
   ChartRedraw(0);
   return (true);
}

3.每日利润检查

在 MQL 中,名为 checkDailyProfit 的函数计算给定一天的总利润。该函数不带参数,执行以下步骤:

  • 定义变量 total_day_profit,并将其初始化为 0。
  • 获取当前时间,并使用 TimeToString 函数将其转换为字符串,存储在日期变量中。
  • 将一天的开始时间加 1 并保存在变量中,从而计算出一天的初始小时数。
  • 检查每日时间是否小于当前时间。如果是,则设置 dayTime 并使用 Acc_B 函数计算当前余额,该值存储在 dayBalance 变量中。
  • 使用 HistorySelect 函数选择当天的历史数据,并将开始和结束时间设置为当天的开始和结束时间。
  • 使用 HistoryDealsTotal 函数计算当天的交易总数,并将其存储在 TotalDeals 变量中。
  • 它会查看历史记录中的所有交易,检查交易条目类型是否为 DEAL_ENTRY_OUT,这意味着这是一笔平仓交易。如果是,它将计算 DEAL_PROFIT、DEAL_COMMISSION 和 DEAL_SWAP 值相加后的交易利润,并添加到 total_day_profit 变量中。
  • 使用带有 ACCOUNT_BALANCE 参数的 AccountInfoDouble 函数,从当前账户余额中减去 total_day_profit 计算当日初始余额。

该函数将计算出的期初余额返回为双精度型数值。

void checkDailyProfit(){

   double total_day_Profit = 0;
   datetime end = TimeCurrent();
   string sdate = TimeToString(TimeCurrent(),TIME_DATE);
   datetime start = StringToTime(sdate);
   datetime to = start + (1*24*60*60);
   
   if (dayTime < to){
      dayTime = to;
      dayBalance = Acc_B();
   }
   
   HistorySelect(start,end);
   int TotalDeals = HistoryDealsTotal();
   for (int i=0; i<TotalDeals; i++){
      ulong Ticket = HistoryDealGetTicket(i);
      if (HistoryDealGetInteger(Ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT){
         double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT)
                               +HistoryDealGetDouble(Ticket,DEAL_COMMISSION)
                               +HistoryDealGetDouble(Ticket,DEAL_SWAP));
         total_day_Profit += Latest_Day_Profit;
      }
   }
   double startingBalance = 0;
   startingBalance = AccountInfoDouble(ACCOUNT_BALANCE) - total_day_Profit;
   double daily_profit_or_drawdown = NormalizeDouble((total_day_Profit*100/startingBalance),2);
   string daily_profit_in_Text_Format = "";
   daily_profit_in_Text_Format = DoubleToString(daily_profit_or_drawdown,2)+" %";
   
   //Print(total_day_Profit, " >>> ",daily_profit_in_Text_Format);
   
   createText("4",TimeToString(start),120,80,clrYellow,10);
   createText("6",TimeToString(to),120,95,clrYellow,10);
   createText("8",TimeToString(end),120,110,clrWhite,10);

   if (Acc_E() > initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrLime,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrLime,10);
   }
   else if (Acc_E() < initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrRed,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrRed,10);
   }
   if (Acc_E() == initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrWhite,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrWhite,10);
   }
   
   createText("20",DoubleToString(dayBalance,2)+" "+Acc_S(),270,225,clrWhite,10);
   createText("24","-"+DoubleToString(dayBalance*5/100,2)+" "+Acc_S(),270,255,clrYellow,10);

   if (Acc_B() > dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrLime,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrLime,10);
   }
   else if (Acc_B() < dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrRed,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrRed,10);
   }
   else if (Acc_B() == dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrWhite,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrWhite,10);
   }
   
   if (daily_profit_or_drawdown <= -5.00 || ((Acc_E()-initialBalance)/initialBalance*100) < -12.00){
      createText("29",">>> Maximum Threshold Hit, Can't Trade.",70,300,clrRed,10);
      isTradeAllowed = false;
   }
   else {
      createText("29",">>> Maximum Threshold Not Hit, Can Trade.",70,300,clrL…

文章中函数关键部分的完整代码如下:

2;
   if (number == 0){
      obj_Trade.Buy(0.1,_Symbol,ask,ask-70*_Point,ask+70*_Point);
   }
   else if (number == 1){
      obj_Trade.Sell(0.1,_Symbol,bid,bid+70*_Point,bid-70*_Point);
   }
}
//+------------------------------------------------------------------+
bool createText(string objName,string text,int x, int y,color clrTxt,int fontSize){
   ResetLastError();
   if (!ObjectCreate(0,objName,OBJ-LABEL,0,0,0)){
      Print(__FUNCTION__,": failed to create the Label! Error Code = ",GetLastError());
      return (false);
   }
   ObjectSetInteger(0,objName,OBJPROP-XDISTANCE,x);
   ObjectSetInteger(0,objName,OBJPROP-YDISTANCE,y);
   ObjectSetInteger(0,objName,OBJPROP-CORNER,CORNER_LEFT_UPPER);
   ObjectSetString(0,objName,OBJPROP-TEXT,text);
   ObjectSetInteger(0,objName,OBJPROP-COLOR,clrTxt);
   ObjectSetInteger(0,objName,OBJPROP-FONTSIZE,fontSize);
   
   ChartRedraw(0);
   return (true);
}

void checkDailyProfit(){

   double total_day_Profit = 0;
   datetime end = TimeCurrent();
   string sdate = TimeToString(TimeCurrent(),TIME_DATE);
   datetime start = StringToTime(sdate);
   datetime to = start + (1*24*60*60);
   
   if (dayTime < to){
      dayTime = to;
      dayBalance = Acc_B();
   }
   
   HistorySelect(start,end);
   int TotalDeals = HistoryDealsTotal();
   for (int i=0; i<TotalDeals; i++){
      ulong Ticket = HistoryDealGetTicket(i);
      if (HistoryDealGetInteger(Ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT){
         double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT)
                               +HistoryDealGetDouble(Ticket,DEAL_COMMISSION)
                               +HistoryDealGetDouble(Ticket,DEAL_SWAP));
         total_day_Profit += Latest_Day_Profit;
      }
   }
   double startingBalance = 0;
   startingBalance = AccountInfoDouble(ACCOUNT-BALANCE) - total_day_Profit;
   double daily_profit_or_drawdown = NormalizeDouble((total_day_Profit*100/startingBalance),2);
   string daily_profit_in_Text_Format = "";
   daily_profit_in_Text_Format = DoubleToString(daily_profit_or_drawdown,2)+" %";
   
   //Print(total_day_Profit, " >>> ",daily_profit_in_Text_Format);
   
   createText("4",TimeToString(start),120,80,clrYellow,10);
   createText("6",TimeToString(to),120,95,clrYellow,10);
   createText("8",TimeToString(end),120,110,clrWhite,10);

   if (Acc_E() > initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrLime,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrLime,10);
   }
   else if (Acc_E() < initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrRed,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrRed,10);
   }
   if (Acc_E() == initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrWhite,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrWhite,10);
   }
   
   createText("20",DoubleToString(dayBalance,2)+" "+Acc_S(),270,225,clrWhite,10);
   createText("24","-"+DoubleToString(dayBalance*5/100,2)+" "+Acc_S(),270,255,clrYellow,10);

   if (Acc_B() > dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrLime,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrLime,10);
   }
   else if (Acc_B() < dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrRed,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrRed,10);
   }
   else if (Acc_B() == dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrWhite,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrWhite,10);
   }
   
   if (daily_profit_or_drawdown <= -5.00 || ((Acc_E()-initialBalance)/initialBalance*100) < -12.00){
      createText("29",">>> Maximum Threshold Hit, Can't Trade.",70,300,clrRed,10);
      isTradeAllowed = false;
   }
   else {
      createText("29",">>> Maximum Threshold Not Hit, Can Trade.",70,300,clrRed);

下面是我们得到的结果。

远离阈值的逻辑示例。

远离阈值

接近阈值的逻辑示例。

接近阈值

阈值命中的逻辑示例:

阈值命中

创建回撤限制器的完整代码如下:

//+------------------------------------------------------------------+
//|                                       Daily Drawdown Limiter.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade obj_Trade;

int totalBars = 0;
double initialBalance = 0;
double dayBalance = 0;
datetime dayTime = 0;
bool isTradeAllowed = true;

// Functions to get account balance, equity, and currency
double Acc_B() {return AccountInfoDouble(ACCOUNT_BALANCE);}
double Acc_E() {return AccountInfoDouble(ACCOUNT_EQUITY);}
string Acc_S() {return AccountInfoString(ACCOUNT_CURRENCY);}

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Initialize initial balance
   initialBalance = Acc_B();
   
   // Create dashboard texts
   createText("0","*** Daily Drawdown Limiter ***",30,30,clrBlack,13);
   createText("00","______________________________________",30,30,clrBlack,13);
   createText("1","DrawDown Limiter is Active.",70,50,clrBlack,11);
   createText("2","Counters will be reset on Next Day Start.",70,65,clrBlack,10);
   createText("3","From: ",70,80,clrBlack,10);
   createText("4","Time Here",120,80,clrGray,10);
   createText("5","To: ",70,95,clrBlack,10);
   createText("6","Time Here",120,95,clrGray,10);
   createText("7","Current: ",70,110,clrBlack,10);
   createText("8","Time Here",120,110,clrGray,10);

   createText("9","ACCOUNT DRAWDOWN ============",70,130,clrPeru,11);
   createText("10","Account Initial Balance: ",70,145,clrBlack,10);
   createText("11",DoubleToString(initialBalance,2)+" "+Acc_S(),250,145,clrBlack,10);
   createText("12","Tolerated DrawDown: ",70,160,clrBlack,10);
   createText("13","12.00 %",250,160,clrBlack,10);
   createText("14","Current Account Equity: ",70,175,clrBlack,10);
   createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrBlack,10);
   createText("16","Current Balance Variation: ",70,190,clrBlack,10);
   createText("17",DoubleToString((Acc_E()-Acc_B())/Acc_B()*100,2)+" %",250,190,clrGray,10);

   createText("18","DAILY DRAWDOWN ================",70,210,clrPeru,11);
   createText("19","Starting Balance: ",70,225,clrBlack,10);
   createText("20",DoubleToString(Acc_B(),2)+" "+Acc_S(),270,225,clrBlack,10);
   createText("21","DrawDown Maximum Threshold: ",70,240,clrBlack,10);
   createText("22","5.00 %",270,240,clrBlack,10);
   createText("23","DrawDown Maximum Amount: ",70,255,clrBlack,10);
   createText("24","-"+DoubleToString((Acc_B()*5/100),2)+" "+Acc_S(),270,255,clrBlue,10);
   createText("25","Current Closed Daily Profit: ",70,270,clrBlack,10);
   createText("26","0.00"+" "+Acc_S(),270,270,clrGray,10);
   createText("27","Current DrawDown Percent: ",70,285,clrBlack,10);
   createText("28","0.00 %",270,285,clrGray,10);
   createText("29",">>> Initializing The Program, Get Ready To Trade.",70,300,clrBlue,10);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Deinitialization code here (if needed)
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // Check daily profit and drawdown
   checkDailyProfit();
   
   // If trading is not allowed, exit function
   if (!isTradeAllowed) return;
   
   // Get current ask and bid prices
   double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   // Check for new bar
   int bars = iBars(_Symbol,_Period);
   if (totalBars == bars) return;
   totalBars = bars;
   
   // If more than one position, exit function
   if (PositionsTotal() > 1) return;
   
   // Random trade decision
   int number = MathRand()%2;
   Print(number);
   
   if (number == 0){
      obj_Trade.Buy(1,_Symbol,ask,ask-70*_Point,ask+70*_Point);
   }
   else if (number == 1){
      obj_Trade.Sell(1,_Symbol,bid,bid+70*_Point,bid-70*_Point);
   }
  }
//+------------------------------------------------------------------+
//| Check daily profit and drawdown                                  |
//+------------------------------------------------------------------+
void checkDailyProfit() {
   
   double total_day_Profit = 0.0;
   datetime end = TimeCurrent();
   string sdate = TimeToString (TimeCurrent(), TIME_DATE);
   datetime start = StringToTime(sdate);
   datetime to = start + (1*24*60*60);
   
   // Reset daily balance and time at start of new day
   if (dayTime < to){
      dayTime = to;
      dayBalance = Acc_B();
   }

   // Calculate total daily profit
   HistorySelect(start,end);
   int TotalDeals = HistoryDealsTotal();

   for(int i = 0; i < TotalDeals; i++){
      ulong Ticket = HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(Ticket,DEAL_ENTRY) == DEAL_ENTRY_OUT){
         double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT)
                                    + HistoryDealGetDouble(Ticket,DEAL_COMMISSION)
                                    + HistoryDealGetDouble(Ticket,DEAL_SWAP));
         total_day_Profit += Latest_Day_Profit;
      }
   }
   
   double startingBalance = 0.0;
   startingBalance = AccountInfoDouble(ACCOUNT_BALANCE) - total_day_Profit;
   string day_profit_in_TextFormat = "";
   double daily_Profit_or_Drawdown = NormalizeDouble(((total_day_Profit) * 100/startingBalance),2);
   day_profit_in_TextFormat = DoubleToString(daily_Profit_or_Drawdown,2) + " %";
      
   // Update dashboard texts with new data
   createText("4",TimeToString(start),120,80,clrBlue,10);
   createText("6",TimeToString(to),120,95,clrBlue,10);
   createText("8",TimeToString(end),120,110,clrBlack,10);

   createText("11",DoubleToString(initialBalance,2)+" "+Acc_S(),250,145,clrBlack,10);
   if (Acc_E() > initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrMediumBlue,10);
      createText("17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*100,2)+" %",250,190,clrMediumBlue,10);
   }
   else if (Acc_E() < initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrRed,10);
      createText("17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*100,2)+" %",250,190,clrRed,10);
   }
   else if (Acc_E() == initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrBlack,10);
      createText("17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*100,2)+" %",250,190,clrBlack,10);
   }

   createText("20",DoubleToString(dayBalance,2)+" "+Acc_S(),270,225,clrBlack,10);
   createText("24","-"+DoubleToString((dayBalance*5/100),2)+" "+Acc_S(),270,255,clrBlue,10);
   if (Acc_B() > dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrMediumBlue,10);
      createText("28",day_profit_in_TextFormat,270,285,clrMediumBlue,10);
   }
   else if (Acc_B() < dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrRed,10);
      createText("28",day_profit_in_TextFormat,270,285,clrRed,10);
   }
   else if (Acc_B() == dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrBlack,10);
      createText("28",day_profit_in_TextFormat,270,285,clrBlack,10);
   }
   
   // Check if drawdown limits are hit and update trading permission
   if (daily_Profit_or_Drawdown <= -5.00 ||((Acc_E()-initialBalance)/initialBalance)*100 < -12.00){
      createText("29",">>> Max ThreshHold Hit, Can't Trade.",70,300,clrRed,10);
      isTradeAllowed = false;
   }
   else {
      createText("29",">>> Max ThresHold Not Hit, Can Trade.",70,300,clrMediumBlue,10);
      isTradeAllowed = true;
   }
}

//+------------------------------------------------------------------+
//| Create text label on the chart                                   |
//+------------------------------------------------------------------+
bool createText(string objName, string text, int x, int y, color clrTxt,int fontSize) {
 ResetLastError();
     if (!ObjectCreate(0,objName,OBJ_LABEL,0,0,0)){
        Print(__FUNCTION__,": failed to create the Label! Error code = ", GetLastError());
        return(false);
     }

   ObjectSetInteger(0,objName,OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0,objName,OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0,objName,OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString(0,objName,OBJPROP_TEXT, text);
   ObjectSetInteger(0,objName,OBJPROP_FONTSIZE, fontSize);
   //ObjectSetString(0,objName,OBJPROP_FONT, "Calibri");
   ObjectSetInteger(0,objName,OBJPROP_COLOR, clrTxt);
   
   ChartRedraw(0);
   
   return(true); 
}

为我们干杯吧!现在,我们在为交易账户设定每日回撤上限的基础上,为外汇交易 EA 交易系统创建了每日回撤限额。


结论

这篇文章监控交易活动,并用相关文本标签更新图表,以显示交易状态、利润和基于特定阈值的交易权限。使用函数在图表上设置和更新文本标签有助于组织文章和简化维护。我们已经了解了在 MQL5 中实现著名的每日平仓回撤限制外汇交易策略自动化所需的基本步骤。我们提供了策略的基本定义和说明,并展示了如何在 MQL5 中创建策略。交易者可以利用所展示的知识来开发一个更复杂的每日回撤限制系统,之后可以对其进行优化,最终产生更好的结果。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15199

附加的文件 |
神经网络变得简单(第 89 部分):频率增强分解变换器(FEDformer) 神经网络变得简单(第 89 部分):频率增强分解变换器(FEDformer)
到目前为止,我们研究过的所有模型在分析环境状态时都将其当作时间序列。不过,时间序列也能以频率特征的形式表示。在本文中,我将向您介绍一种算法,即利用时间序列的频率分量来预测未来状态。
构建K线图趋势约束模型(第六部分):一体化集成 构建K线图趋势约束模型(第六部分):一体化集成
我们的一个主要挑战是:如何管理运行相同程序但具有不同功能的同一货币对的多个图表窗口。让我们讨论一下如何将多个窗口集成整合到一个主程序中。此外,我们还将分享如何配置程序以将信息打印到日志中,以及在图表界面上对成功发出的信号进行注释的见解。随着本系列文章的推进,您将在本文中找到更多的相关信息。
开发回放系统(第 56 部分):调整模块 开发回放系统(第 56 部分):调整模块
虽然模块之间已经可以正常交互,但在回放服务中尝试使用鼠标指标时会出现错误。在进入下一步之前,我们需要解决这个问题。此外,我们还将修复鼠标指标代码中的一个问题。所以这个版本经过适当的打磨,最终会稳定下来。
在您的 MQL 项目中使用 JSON 数据 API 在您的 MQL 项目中使用 JSON 数据 API
想象一下,您可以使用 MetaTrader 中没有的数据,您只能通过价格分析和技术分析从指标中获得数据。现在想象一下,您可以访问数据,这将使你的交易能力更高。如果您通过 API(应用程序编程接口)数据混合其他软件、宏观分析方法和超高级工具的输出,您就可以倍增 MetaTrader 软件的力量。在本文中,我们将教您如何使用 API,并介绍有用和有价值的 API 数据服务。