MQL5 简介(第 8 部分):初学者构建 EA 交易系统指南(二)·综合运用
🤖

MQL5 简介(第 8 部分):初学者构建 EA 交易系统指南(二)·综合运用

(3/3)·从伪代码到完整 EA,解决论坛里最高频的 7 个下单与限仓疑问

含代码示例实战向 第 3/3 篇

不少自学 MQL5 的人能看懂单条代码片段,却卡在「怎么把买卖、限仓、交易时段拼成一套不报错的 EA」。本篇用基于项目的方式,把前一天日线方向判断与当日首根小时线确认直接落成可运行逻辑。

日线定方向后的 H1 破位触发

这套逻辑先把日线收线作为方向过滤器:当日线收阴(close[0] < open[0])只找做空机会,收阳则只找做多。外汇与贵金属波动受消息驱动,日线定调只是概率倾向,不等于趋势延续。 具体触发看 H1 节奏。以空单为例,要求倒数第 3 根 H1 收盘 ≥ 首根参考收盘、倒数第 2 根收盘跌破该参考、且当前时间过了交易起点,三个条件同时满足才 trade.Sell。多单镜像处理。 新 K 线识别用 CopyRates 拉最近 3 根 H1 的 MqlRates,比 bar[0].time 与 lastBarTime:更大就置 newBar=true 并更新时间戳,否则 false。仅在 newBar 为真时跑方向判断,避免同根 K 线内重复发单。 实盘前把 1.0 手数换成你的仓位,sl/tp 变量也需在别处算好。MT5 策略测试器用 2023 年 EURUSD H1 跑一遍,能直接看出该过滤在震荡日容易连续不触发。

MQL5 / C++
if(daily_close[class="num">0] < daily_open[class="num">0])
  {
    class=class="str">"cmt">// Check specific conditions for a sell trade
    if(H1_price_close[class="num">2] >= first_h1_price_close[class="num">0] && H1_price_close[class="num">1] < first_h1_price_close[class="num">0] && current_time >= first_tradetime)
      {
       class=class="str">"cmt">// Execute the sell trade
       trade.Sell(class="num">1.0, _Symbol, Bid, sl_sell, tp_sell); class=class="str">"cmt">// Replace with your lot size
       Comment("It&class="macro">#x27;s a sell");
      }
  }
class=class="str">"cmt">// If the last daily bar is bullish
if(daily_close[class="num">0] > daily_open[class="num">0])
  {
    class=class="str">"cmt">// Check specific conditions for a buy trade
    if(H1_price_close[class="num">2] <= first_h1_price_close[class="num">0] && H1_price_close[class="num">1] > first_h1_price_close[class="num">0] && current_time >= first_tradetime)
      {
       class=class="str">"cmt">// Execute the buy trade
       trade.Buy(class="num">1.0, _Symbol, Ask, sl_buy, tp_buy); class=class="str">"cmt">// Replace with your lot size
       Comment("It&class="macro">#x27;s a buy");
      }
  }
}
class=class="str">"cmt">// Flag to indicate a new bar has formed
class="type">bool newBar;
class=class="str">"cmt">// Variable to store the time of the last bar
class="type">class="kw">datetime lastBarTime;
class=class="str">"cmt">// Array to store bar data(OHLC)
class="type">MqlRates bar[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">// Check for a new bar
   CopyRates(_Symbol, PERIOD_H1, class="num">0, class="num">3, bar); class=class="str">"cmt">// Copy the latest class="num">3 H1 bars
   if(bar[class="num">0].time > lastBarTime)  class=class="str">"cmt">// Check if the latest bar time is greater than the last recorded bar time
     {
      newBar = true; class=class="str">"cmt">// Set the newBar flag to true
      lastBarTime = bar[class="num">0].time; class=class="str">"cmt">// Update the last bar time
     }
   else
     {
      newBar = class="kw">false; class=class="str">"cmt">// Set the newBar flag to class="kw">false
     }
class=class="str">"cmt">// If a new bar has formed
   if(newBar == true)
     {
      class=class="str">"cmt">// If the last daily bar is bearish
      if(daily_close[class="num">0] < daily_open[class="num">0])
        {
         class=class="str">"cmt">// Check specific conditions for a sell trade
         if(H1_price_close[class="num">2] >= first_h1_price_close[class="num">0] && H1_price_close[class="num">1] < first_h1_price_close[class="num">0] && current_time >= first_tradetime)
           {
            class=class="str">"cmt">// Execute the sell trade
            trade.Sell(class="num">1.0, _Symbol, Bid, sl_sell, tp_sell); class=class="str">"cmt">// Replace with your lot size
            Comment("It&class="macro">#x27;s a sell");

◍ 持仓计数与当日成交回看

日线收阳之后,策略并不急着直接市价买入,而是先盘点当前账户的持仓状态与当日已成交记录,避免重复开仓或超出计划笔数。 下面这段先统计匹配 MagicNumber 的未平仓位:遍历 PositionsTotal(),用 PositionGetTicket(i) 取ticket,再比对 POSITION_MAGIC。每命中一次 totalPositions 自增 1,实盘里你能凭这个数判断「今天是否已经占好坑」。 随后用 HistorySelect(start_time, end_time) 拉取指定时段历史,循环 HistoryDealsTotal() 并筛 DEAL_MAGIC == MagicNumber 且 DEAL_ENTRY == DEAL_ENTRY_IN 的成交,totalDeal 记录当日进场次数。若回测发现 totalDeal 经常大于 2,说明过滤条件过松,外汇与贵金属波动剧烈,需收紧 first_tradetime 或 H1 穿越判定。 利润统计部分另起循环,依旧基于 HistoryDealsTotal(),先取 ticket 再判 DEAL_ENTRY_IN,准备按成交魔术码归集 profit。注意这里 dealsMagic 与 profit 已声明但未在片段内累加完,复制到 MT5 时须补完赋值,否则 totalProfit 恒为 0。

MQL5 / C++
class="type">int totalPositions = class="num">0;
for(class="type">int i = class="num">0; i < PositionsTotal(); i++)
  {
  class="type">class="kw">ulong ticket = PositionGetTicket(i);
  if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)
    {
      totalPositions++;
    }
  }
class="type">bool success = HistorySelect(start_time, end_time);
class="type">int totalDeal = class="num">0;
if(success)
  {
   for(class="type">int i = class="num">0; i < HistoryDealsTotal(); i++)
    {
      class="type">class="kw">ulong ticket = HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber)
        {
          if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
            {
              totalDeal++;
            }
        }
    }
  }
class="type">class="kw">double totalProfit = class="num">0;
class="type">long dealsMagic = class="num">0;
class="type">class="kw">double profit = class="num">0;
if(success)
  {
   for(class="type">int i = class="num">0; i < HistoryDealsTotal(); i++)
    {
      class="type">class="kw">ulong ticket = HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
        {
          class=class="str">"cmt">// Get the magic number of the deal

「按魔术码归集平仓盈亏并卡掉特定交易日」

这段逻辑在做两件事:从历史成交里把指定 EA 的平仓利润加总,以及在到达 end_time 时把同魔术码持仓全平。外汇与贵金属杠杆高,历史统计和强制平仓都只是概率性风控,不保证规避回撤。 历史遍历里先用 HistoryDealGetInteger 取 DEAL_MAGIC,再判断 DEAL_ENTRY 是否为 DEAL_ENTRY_OUT。若是平仓且魔术码吻合,就把 DEAL_PROFIT 通过 HistoryDealGetDouble 读出并累加到 totalProfit。这样你能只统计自己 EA 的出场成绩,不会被手动单干扰。 收盘段用 PositionsTotal 循环,PositionGetInteger(POSITION_MAGIC) 等于 MagicNumber 且 current_time == end_time 时调用 trade.PositionClose(ticket)。注意这是等号硬比对,若服务器时间跳秒就可能漏平,实盘建议改成 current_time >= end_time 更稳。 时间过滤部分用 MqlDateTime 取 day_of_week 和 mon。代码里写了 week_day==5 提示周五不交易、week_day==4 提示周四不交易,但只是 Comment 输出,并没有真正阻断下单。要落地禁交易得在开仓前加 return 或信号屏蔽。

MQL5 / C++
   dealsMagic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
   }
   class=class="str">"cmt">// Check if the deal was an exit
   if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT)
     {
      class=class="str">"cmt">// Get the profit of the deal
      profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
      class=class="str">"cmt">// Check if the magic number matches
      if(MagicNumber == dealsMagic)
        {
         class=class="str">"cmt">// Add the profit to the total profit
         totalProfit += profit;
        }
     }
   }
}
class=class="str">"cmt">// Close trades at the specified end time
for(class="type">int i = class="num">0; i < PositionsTotal(); i++)
  {
class=class="str">"cmt">// Get the ticket number for the position
   class="type">class="kw">ulong ticket = PositionGetTicket(i);
class=class="str">"cmt">// Check if the position&class="macro">#x27;s magic number matches and if it&class="macro">#x27;s the end time
   if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && current_time == end_time)
     {
      class=class="str">"cmt">// Close the position
      trade.PositionClose(ticket);
     }
  }
class=class="str">"cmt">//getting the day of week and month
class="type">MqlDateTime day; class=class="str">"cmt">//Declare an class="type">MqlDateTime structure to hold the current time and date
TimeCurrent(day); class=class="str">"cmt">// Get the current time and fill the class="type">MqlDateTime structure
class="type">int week_day = day.day_of_week; class=class="str">"cmt">//Extract the day of the week(class="num">0 = Sunday, class="num">1 = Monday, ..., class="num">6 = Saturday)
class=class="str">"cmt">//getting the current month
class="type">MqlDateTime month; class=class="str">"cmt">//Declare a structure to hold current month information
TimeCurrent(month); class=class="str">"cmt">//Get the current date and time
class="type">int year_month = month.mon; class=class="str">"cmt">//Extract the month component(class="num">1 for January, class="num">2 for February, ..., class="num">12 for December)
if(week_day == class="num">5)
  {
   Comment("No trades on fridays", "\nday of week: ",week_day);
  }
else
  if(week_day == class="num">4)
    {
      Comment("No trades on Thursdays", "\nday of week: ",week_day);
    }
   else
    {
      Comment(week_day);
    }

把这条线请下神坛

走到这一节,你已经能在 MT5 里让 EA 只开一单、圈定交易时段、给盈亏封顶,也知道用项目式练习替代死记语法。前面几篇铺的买卖、取 K 线开收价、防每次 tick 乱触发,合起来就是一套能跑的最小闭环。 真正卡住人的从来不是语言本身。MQL5 只是把你的价格行为逻辑翻译成 broker 能执行的指令,外汇和贵金属杠杆高、滑点跳空随时来,回测顺不代表实盘稳,概率上仍可能连续止损。 下次打开 MQL5Project2.mq5(约 10 KB),先改一处:把单笔交易限制从 1 调到 2,看风控逻辑哪行开始报警。代码是死的,盘面是活的,这条线没必要供着。

把重复诊断交给小布盯盘
这些 EA 里的方向判定与交易密度检查,小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时烛形结构与时段提示,你只需专注策略微调。

常见问题

通常通过 iOpen 与 iClose 配合 PERIOD_D1 周期与偏移量 1 来读取,具体封装方式见本篇代码节。
目前小布提供品种级形态与时段诊断,不直接导出 mq5 文件,但可作为你写 EA 前的信号验证参照。
用时间点或柱体闭合事件做触发守卫,并配合持仓计数与每日交易上限变量拦截多余订单。
用 TimeDayOfWeek 结合自定义起止小时做范围判断,并在 OnTick 开头提前 return 过滤非交易窗口。
遍历持仓按幻数筛选,若存在同幻数仓位则跳过新信号,这是论坛里最常见的限仓写法之一。