在 MQL5 中创建每日回撤限制器 EA·综合运用
📘

在 MQL5 中创建每日回撤限制器 EA·综合运用

第 3/3 篇

◍ 面板初始化与逐笔触发骨架

EA 启动时先在图表左上区域铺一块日回撤监控面板:从 y=210 到 y=300 按 15 像素行距写死标签,起始余额、5.00% 最大回撤阈值、按余额×5% 算出的蓝色金额线、当日平仓利润与当前回撤百分比都先置 0,最后用蓝字提示「初始化完成,准备交易」并 return INIT_SUCCEEDED。 OnTick 里第一件事是调 checkDailyProfit() 刷新面板数值,随后用 isTradeAllowed 开关拦掉不允许交易的时段;若开关没开直接 return,不取价也不开单。 取价用 NormalizeDouble 包 SymbolInfoDouble 拿 SYMBOL_ASK / SYMBOL_BID 并对齐 _Digits,避免浮点位数乱掉。新K线判定靠 iBars 计数:totalBars 等于当前 bars 就退出,不等才把 totalBars 更新并往下走——这套写法在 M1 上每次新柱最多触发一次逻辑,回测里能明显压低重复计算开销。 外汇与贵金属杠杆品种跳空频繁,5% 回撤线只是阈值参考,实际穿阈速度可能远快于面板刷新节奏,上 MT5 跑之前建议先把 5.00% 改成变量方便调参。

MQL5 / C++
  createText("class="num">18","DAILY DRAWDOWN ================",class="num">70,class="num">210,clrPeru,class="num">11);
  createText("class="num">19","Starting Balance: ",class="num">70,class="num">225,clrBlack,class="num">10);
  createText("class="num">20",DoubleToString(Acc_B(),class="num">2)+" "+Acc_S(),class="num">270,class="num">225,clrBlack,class="num">10);
  createText("class="num">21","DrawDown Maximum Threshold: ",class="num">70,class="num">240,clrBlack,class="num">10);
  createText("class="num">22","class="num">5.00 %",class="num">270,class="num">240,clrBlack,class="num">10);
  createText("class="num">23","DrawDown Maximum Amount: ",class="num">70,class="num">255,clrBlack,class="num">10);
  createText("class="num">24","-"+DoubleToString((Acc_B()*class="num">5/class="num">100),class="num">2)+" "+Acc_S(),class="num">270,class="num">255,clrBlue,class="num">10);
  createText("class="num">25","Current Closed Daily Profit: ",class="num">70,class="num">270,clrBlack,class="num">10);
  createText("class="num">26","class="num">0.00"+" "+Acc_S(),class="num">270,class="num">270,clrGray,class="num">10);
  createText("class="num">27","Current DrawDown Percent: ",class="num">70,class="num">285,clrBlack,class="num">10);
  createText("class="num">28","class="num">0.00 %",class="num">270,class="num">285,clrGray,class="num">10);
  createText("class="num">29",">>> Initializing The Program, Get Ready To Trade.",class="num">70,class="num">300,clrBlue,class="num">10);
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
  class=class="str">"cmt">// Deinitialization code here(if needed)
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
  class=class="str">"cmt">// Check daily profit and drawdown
  checkDailyProfit();

  class=class="str">"cmt">// If trading is not allowed, exit function
  if (!isTradeAllowed) class="kw">return;

  class=class="str">"cmt">// Get current ask and bid prices
  class="type">class="kw">double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
  class="type">class="kw">double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);

  class=class="str">"cmt">// Check for new bar
  class="type">int bars = iBars(_Symbol,_Period);
  if (totalBars == bars) class="kw">return;
  totalBars = bars;

用随机信号测每日盈亏与回撤边界

这段逻辑先卡死持仓数量:当账户里已有超过 1 个持仓时直接 return,保证同一时刻最多只跑一笔单。随后用 MathRand()%2 掷出 0 或 1,等概率随机做多或做空,挂单止损止盈都放在离市价 70 个 _Point 的位置——这本质是用纯随机入场来压力测试风控模块,而不是真拿来交易。 checkDailyProfit() 负责按自然日切割战绩。它取 TimeCurrent() 转成日期字符串,再拼出当天 0 点至次日 0 点的区间,用 HistorySelect 拉出区间内所有平仓记录,把每笔的 DEAL_PROFIT、DEAL_COMMISSION、DEAL_SWAP 加总得到 total_day_Profit。 日收益率的计算口径是:(当日总盈亏 / 期初余额) * 100,其中期初余额 = 当前余额 - 当日总盈亏,结果用 NormalizeDouble 保留两位小数。外汇与贵金属杠杆高,这类随机单若实盘跑,爆仓概率极大,仅适合在策略测试器里验证逻辑。 面板文字用 createText 刷新:标签 "4" 写起始时间、"6" 写结束时间,坐标分别落在 (120,80) 与 (120,95),字号 10、蓝色。你打开 MT5 把这段塞进 EA,改掉 70*_Point 这个间距,就能直观看到不同止损宽度下当日权益曲线的抖动。

MQL5 / C++
  class=class="str">"cmt">// If more than one position, exit function
  if (PositionsTotal() > class="num">1) class="kw">return;
  
  class=class="str">"cmt">// Random trade decision
  class="type">int number = MathRand()%class="num">2;
  Print(number);
  
  if (number == class="num">0){
      obj_Trade.Buy(class="num">1,_Symbol,ask,ask-class="num">70*_Point,ask+class="num">70*_Point);
  }
  else if (number == class="num">1){
      obj_Trade.Sell(class="num">1,_Symbol,bid,bid+class="num">70*_Point,bid-class="num">70*_Point);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check daily profit and drawdown                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void checkDailyProfit() {
  
  class="type">class="kw">double total_day_Profit = class="num">0.0;
  class="type">class="kw">datetime end = TimeCurrent();
  class="type">class="kw">string sdate = TimeToString(TimeCurrent(), TIME_DATE);
  class="type">class="kw">datetime start = StringToTime(sdate);
  class="type">class="kw">datetime to = start + (class="num">1*class="num">24*class="num">60*class="num">60);
  
  class=class="str">"cmt">// Reset daily balance and time at start of new day
  if (dayTime < to){
      dayTime = to;
      dayBalance = Acc_B();
  }
  class=class="str">"cmt">// Calculate total daily profit
  HistorySelect(start,end);
  class="type">int TotalDeals = HistoryDealsTotal();
  for(class="type">int i = class="num">0; i < TotalDeals; i++){
      class="type">class="kw">ulong Ticket = HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(Ticket,DEAL_ENTRY) == DEAL_ENTRY_OUT){
          class="type">class="kw">double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT)
                                      + HistoryDealGetDouble(Ticket,DEAL_COMMISSION)
                                      + HistoryDealGetDouble(Ticket,DEAL_SWAP));
          total_day_Profit += Latest_Day_Profit;
      }
  }
  
  class="type">class="kw">double startingBalance = class="num">0.0;
  startingBalance = AccountInfoDouble(ACCOUNT_BALANCE) - total_day_Profit;
  class="type">class="kw">string day_profit_in_TextFormat = "";
  class="type">class="kw">double daily_Profit_or_Drawdown = NormalizeDouble(((total_day_Profit) * class="num">100/startingBalance),class="num">2);
  day_profit_in_TextFormat = DoubleToString(daily_Profit_or_Drawdown,class="num">2) + " %";
    
  class=class="str">"cmt">// Update dashboard texts with new data
  createText("class="num">4",TimeToString(start),class="num">120,class="num">80,clrBlue,class="num">10);
  createText("class="num">6",TimeToString(to),class="num">120,class="num">95,clrBlue,class="num">10);

「在图表上画出账户与日内盈亏面板」

这段逻辑负责把账户状态直接渲染到 MT5 主图,省去切终端窗口看净值的麻烦。它分别处理了总权益相对初始资金的盈亏着色,以及当日余额与当日保本线(dayBalance)的比较,用颜色区分风险状态。 初始余额显示在坐标 (250,145),字号 10;若当前权益 Acc_E() 高于 initialBalance,权益值与百分比用 clrMediumBlue,低于则用 clrRed,等于用 clrBlack。注意百分比算法是 (Acc_E()-initialBalance)/initialBalance*100,保留两位小数。 当日面板另算一条 5% 回撤警戒线:代码里用 dayBalance*5/100 作为「-X.XX」文本画在 (270,255)。当 Acc_B() 大于 dayBalance 时,当日利润 total_day_Profit 与格式化文本 day_profit_in_TextFormat 着蓝色,否则红色或黑色。外汇与贵金属杠杆高,这类浮亏着色只帮你快速识别状态,不预示后续走势。 直接把下面代码贴进你的 EA 的绘图函数里,改一下坐标就能适配自己的 HUD 布局;开 MT5 用策略测试器跑一遍,看颜色切换是否符合你的风险阈值。

MQL5 / C++
  createText("class="num">8",TimeToString(end),class="num">120,class="num">110,clrBlack,class="num">10);
  createText("class="num">11",DoubleToString(initialBalance,class="num">2)+" "+Acc_S(),class="num">250,class="num">145,clrBlack,class="num">10);
  if (Acc_E() > initialBalance){
      createText("class="num">15",DoubleToString(Acc_E(),class="num">2)+" "+Acc_S(),class="num">250,class="num">175,clrMediumBlue,class="num">10);
      createText("class="num">17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*class="num">100,class="num">2)+" %",class="num">250,class="num">190,clrMediumBlue,class="num">10);
  }
  else if (Acc_E() < initialBalance){
      createText("class="num">15",DoubleToString(Acc_E(),class="num">2)+" "+Acc_S(),class="num">250,class="num">175,clrRed,class="num">10);
      createText("class="num">17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*class="num">100,class="num">2)+" %",class="num">250,class="num">190,clrRed,class="num">10);
  }
  else if (Acc_E() == initialBalance){
      createText("class="num">15",DoubleToString(Acc_E(),class="num">2)+" "+Acc_S(),class="num">250,class="num">175,clrBlack,class="num">10);
      createText("class="num">17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*class="num">100,class="num">2)+" %",class="num">250,class="num">190,clrBlack,class="num">10);
  }
  createText("class="num">20",DoubleToString(dayBalance,class="num">2)+" "+Acc_S(),class="num">270,class="num">225,clrBlack,class="num">10);
  createText("class="num">24","-"+DoubleToString((dayBalance*class="num">5/class="num">100),class="num">2)+" "+Acc_S(),class="num">270,class="num">255,clrBlue,class="num">10);
  if (Acc_B() > dayBalance){
      createText("class="num">26",DoubleToString(total_day_Profit,class="num">2)+" "+Acc_S(),class="num">270,class="num">270,clrMediumBlue,class="num">10);
      createText("class="num">28",day_profit_in_TextFormat,class="num">270,class="num">285,clrMediumBlue,class="num">10);
  }
  else if (Acc_B() < dayBalance){
      createText("class="num">26",DoubleToString(total_day_Profit,class="num">2)+" "+Acc_S(),class="num">270,class="num">270,clrRed,class="num">10);
      createText("class="num">28",day_profit_in_TextFormat,class="num">270,class="num">285,clrRed,class="num">10);
  }
  else if (Acc_B() == dayBalance){
      createText("class="num">26",DoubleToString(total_day_Profit,class="num">2)+" "+Acc_S(),class="num">270,class="num">270,clrBlack,class="num">10);
      createText("class="num">28",day_profit_in_TextFormat,class="num">270,class="num">285,clrBlack,class="num">10);
  }

◍ 用浮亏阈值卡死当日交易开关

这段逻辑把「当日回撤」和「账户总回撤」两条红线直接焊死在 EA 里:只要单日浮亏达到 -5.00%,或者账户权益相对初始余额跌穿 -12.00%,图表左上角就会弹出红色字样「Max ThreshHold Hit, Can't Trade.」,同时 isTradeAllowed 被置为 false,后续下单分支全部失效。 反过来若两条线都没碰,标签变蓝字提示可交易,开关重新打开。外汇与贵金属杠杆品种波动剧烈,这类硬截断能避免情绪加仓把账户拖进不可恢复的深坑,但是否触发仍取决于行情概率,不保证避损。 下面这个 createText 是配套的图表标签函数,用 OBJ_LABEL 在左上角画字。逐行看:ObjectCreate 建标签失败就 Print 错误码并返回 false;随后用 ObjectSetInteger 设 X/Y 像素偏移、锚定 CORNER_LEFT_UPPER、字号;ObjectSetString 写文本与颜色;最后 ChartRedraw 刷新。复制进 MT5 把 x=70、y=300 改成你习惯的坐标就能直接用。

MQL5 / C++
  class=class="str">"cmt">// Check if drawdown limits are hit and update trading permission
  if (daily_Profit_or_Drawdown <= -class="num">5.00 ||((Acc_E()-initialBalance)/initialBalance)*class="num">100 < -class="num">12.00){
        createText("class="num">29",">>> Max ThreshHold Hit, Can&class="macro">#x27;t Trade.",class="num">70,class="num">300,clrRed,class="num">10);
        isTradeAllowed = false;
  }
  else {
        createText("class="num">29",">>> Max ThresHold Not Hit, Can Trade.",class="num">70,class="num">300,clrMediumBlue,class="num">10);
        isTradeAllowed = true;
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create text label on the chart                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool createText(class="type">class="kw">string objName, class="type">class="kw">string text, class="type">int x, class="type">int y, class="type">class="kw">color clrTxt,class="type">int fontSize) {
ResetLastError();
     if (!ObjectCreate(class="num">0,objName,OBJ_LABEL,class="num">0,class="num">0,class="num">0)){
          Print(__FUNCTION__,": failed to create the Label! Error code = ", GetLastError());
          class="kw">return(false);
     }
   ObjectSetInteger(class="num">0,objName,OBJPROP_XDISTANCE, x);
   ObjectSetInteger(class="num">0,objName,OBJPROP_YDISTANCE, y);
   ObjectSetInteger(class="num">0,objName,OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString(class="num">0,objName,OBJPROP_TEXT, text);
   ObjectSetInteger(class="num">0,objName,OBJPROP_FONTSIZE, fontSize);
   class=class="str">"cmt">//ObjectSetString(class="num">0,objName,OBJPROP_FONT, "Calibri");
   ObjectSetInteger(class="num">0,objName,OBJPROP_COLOR, clrTxt);

   ChartRedraw(class="num">0);

   class="kw">return(true);
}

一点提醒

把每日回撤上限做成图表文本标签,本质是用 ObjectSetString 把状态、利润和交易权限阈值直接画在窗口上,维护起来比埋在日志里省事。给出的 Daily_Drawdown_Limiter.mq5 示例(9.31 KB)已经把基础框架搭好,读者在 MT5 里加载后改一个阈值参数就能跑。 外汇和贵金属杠杆高、跳空频繁,这类限额策略只能压住单日亏损扩散的概率,拦不住极端行情下的滑点穿透。拿去扩成多品种级联监控前,建议先用历史数据回测不同阈值下的触发频率。 代码是开源底子,真要上实盘先把作者归属和重算逻辑核对清楚,别直接当原创交差。

常见问题

在EA初始化时创建文本标签或图形对象,用来显示账户总盈亏、当日盈亏和回撤百分比,并在OnTick里更新数值,避免每笔单都重画。
用随机买卖信号跑历史或模拟tick,观察当日浮亏触线后是否真的禁止新开仓,重点看回撤边界有没有被突破。
可以,小布能按你设的浮亏阈值监控账户,当日回撤碰线时直接提醒你关掉自动交易,不用自己写EA也能控风险。
多半是每次tick都重建对象导致重绘抖动,改成只更新已有标签的文本内容,别在OnTick里反复创建删除图形。
在代码里记录每日日期,跨天后把当日盈亏和开关状态清零,新交易日首次判断通过即恢复允许交易。