MQL5 Cookbook: 处理自定义图表事件·综合运用
📘

MQL5 Cookbook: 处理自定义图表事件·综合运用

第 3/3 篇

「平仓分支里的事件复位与重建」

EA 在收到 CHARTEVENT_CUSTOM+4 自定义事件后进入平仓分支,先尝试用 PositionClose(_Symbol) 平掉当前品种持仓。若返回失败且输入参数 InpIsLogging 为真,会向终端打印失败日志,方便在 MT5 Experts 标签里直接抓异常。 平仓成功后并不立即结束流程,而是先 Sleep(InpTradePause) 挂起一段毫秒级停顿,避免同根 K 线上连续发单被经纪商拒绝。随后调用 ResetEvent() 释放旧的事件对象指针,再 new 一个 COrderEvent 实例承接下一步发单事件。 重建事件对象后,代码用 CheckPointer 确认是 POINTER_DYNAMIC 动态分配,然后把魔数塞进 lparam、把 ResultDeal 成交单号转 double 塞进 dparam,向 Generate(6,data) 推送 CHARTEVENT_CUSTOM+6。若生成失败会 PrintFormat 报出具体事件 ID,这种写法让你在回测或实盘里能精确追踪每次平仓后的链路断点。 外汇与贵金属杠杆高,这类自定义事件链任一步返回假值都可能让仓位悬空,建议把 InpTradePause 设为 100–500 毫秒并在策略测试器跑至少 3 个月 tick 数据验证。

MQL5 / C++
         class=class="str">"cmt">//--- class="num">4) check close signal
         case CHARTEVENT_CUSTOM+class="num">4:
         {
            if(!this.m_trade.PositionClose(_Symbol))
            {
               class=class="str">"cmt">//--- to log?
               if(InpIsLogging)
                  Print("Failed to close the position: "+_Symbol);
            }
            else
            {
               class=class="str">"cmt">//--- pause
               Sleep(InpTradePause);
               class=class="str">"cmt">//--- reset the event object
               this.ResetEvent();
               class=class="str">"cmt">//--- create an order event object
               this.m_ptr_event=new COrderEvent();
               if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
               {
                  SEventData data;
                  data.lparam=(class="type">long)this.m_magic;
                  data.dparam=(class="type">class="kw">double)this.m_trade.ResultDeal();
                  class=class="str">"cmt">//--- generate CHARTEVENT_CUSTOM+class="num">6 event
                  class="type">class="kw">ushort curr_id=class="num">6;
                  if(!this.m_ptr_event.Generate(curr_id,data))
                     PrintFormat("Failed to generate an event: %d",curr_id);
               }
            }
            class=class="str">"cmt">//---
            class="kw">break;
         }

图表事件里抓开仓与平仓信号

EA 通过 CHARTEVENT_CUSTOM+5 与 +6 两个自定义图表事件,把『开仓』和『平仓』动作从主逻辑解耦到事件回调里。这样你在 MT5 图表上点一下按钮,或者外部脚本发一个自定义事件,EA 就能立刻响应,不用每帧去轮询持仓。 下面这段只截了开仓分支(case CHARTEVENT_CUSTOM+5)的核心处理:先从事件参数里取出 deal 票据号,再调 HistorySelect 把最近 1 小时(PeriodSeconds(PERIOD_H1))的历史拉进缓存,然后用 HistoryDealSelect 定位这笔成交。 [CODE] //--- 5) position opening case CHARTEVENT_CUSTOM+5: { ulong ticket=(ulong)_data.dparam; ulong deal=(ulong)_data.dparam; //--- datetime now=TimeCurrent(); //--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); //--- if(deal_entry==DEAL_ENTRY_IN) { //--- to log? if(InpIsLogging) { Print("\nNew position for: "+_Symbol); PrintFormat("Volume: %0.2f",deal_vol); } } } //--- break; } [/CODE] 逐行看:ticket 和 deal 都从 _data.dparam 强转成 ulong,实际项目里建议区分开(这里原文复用同一参数);now 用 TimeCurrent 取服务器时间。HistorySelect 的窗口是 now-3600 秒到 now,只查近 1 小时成交,能压低历史遍历开销。 HistoryDealGetDouble(deal,DEAL_VOLUME) 拿到的成交量是 double,DEAL_ENTRY 枚举判断是不是 DEAL_ENTRY_IN(入场成交)。只有入场且 InpIsLogging 开关打开,才向日志打 Symbol 和两位小数音量。外汇和贵金属杠杆高,这种事件驱动开仓若误触发可能瞬间建重仓,建议在 HistorySelect 窗口和 InpIsLogging 之外再加一层手数上限校验。 把 +6 的平仓分支照这个骨架补上,用 DEAL_ENTRY_OUT 判断出场,就能在 MT5 里跑一套完整的自定义事件下单流程。

MQL5 / C++
class=class="str">"cmt">//--- class="num">5) position opening
case CHARTEVENT_CUSTOM+class="num">5:
  {
   class="type">ulong ticket=(class="type">ulong)_data.dparam;
   class="type">ulong deal=(class="type">ulong)_data.dparam;
   class=class="str">"cmt">//---
   class="type">class="kw">datetime now=TimeCurrent();
   class=class="str">"cmt">//--- check the deals & orders history
   if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now))
    if(HistoryDealSelect(deal))
      {
       class="type">class="kw">double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME);
       ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY);
       class=class="str">"cmt">//---
       if(deal_entry==DEAL_ENTRY_IN)
        {
         class=class="str">"cmt">//--- to log?
         if(InpIsLogging)
          {
           Print("\nNew position for: "+_Symbol);
           PrintFormat("Volume: %class="num">0.2f",deal_vol);
          }
        }
      }
   class=class="str">"cmt">//---
   class="kw">break;
  }

◍ 平仓事件与停止交易的回调处理

EA 在图表事件中捕获成交回执时,先通过 _data.dparam 拿到 deal ticket,再用 HistorySelect 限定最近 1 小时(PeriodSeconds(PERIOD_H1))窗口拉取历史,避免全量扫描拖慢主线程。 若 HistoryDealSelect(deal) 命中且 DEAL_ENTRYDEAL_ENTRY_OUT,说明这是平仓成交。此时读取 DEAL_VOLUME 可拿到平仓手数,结合 InpIsLogging 开关决定是否向专家日志打印品种与体积——实盘里建议只在对账阶段开这个开关,常态关掉能少写 30%~50% 的日志 IO。 另一种自定义事件 CHARTEVENT_CUSTOM+7 用于外部脚本下发“停止交易”指令:把传入的 dparam 当作 stop_time,并将成员 m_is_trade 置 false。这样主循环下一轮轮询读到该标志就会跳过下单逻辑,实现不带平仓动作的软熔断。外汇与贵金属波动剧烈,这种熔断只能阻断新单,已有持仓风险依旧在场内。

MQL5 / C++
class="type">ulong deal=(class="type">ulong)_data.dparam;
class=class="str">"cmt">//---
class="type">class="kw">datetime now=TimeCurrent();
class=class="str">"cmt">//--- check the deals & orders history
if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now))
   if(HistoryDealSelect(deal))
     {
      class="type">class="kw">double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME);
      ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY);
      class=class="str">"cmt">//---
      if(deal_entry==DEAL_ENTRY_OUT)
        {
         class=class="str">"cmt">//--- to log?
         if(InpIsLogging)
           {
            Print("\nClosed position for: "+_Symbol);
            PrintFormat("Volume: %class="num">0.2f",deal_vol);
           }
        }
      }
class=class="str">"cmt">//---
class="kw">break;
      class=class="str">"cmt">//--- class="num">7) stop trading
      case CHARTEVENT_CUSTOM+class="num">7:
       {
        class="type">class="kw">datetime stop_time=(class="type">class="kw">datetime)_data.dparam;
        class=class="str">"cmt">//---
        this.m_is_trade=class="kw">false;
                  class=class="str">"cmt">//--- to log?
        if(InpIsLogging)

「用自定义事件掐断与恢复EA交易」

EA在图表事件里监听 CHARTEVENT_CUSTOM+7 与 +8,分别负责停止和恢复自动交易。这类设计适合人工在盘中对冲或规避重大数据行情时手动干预,外汇与贵金属杠杆高,突发波动可能瞬间放大亏损,手动暂停机制不是装饰。 当收到停止指令,代码把 _data.dparam 转型为停止时刻的 datetime,置 m_is_trade=false 阻断后续下单,并在开启日志时打印精确时间戳。恢复分支逻辑对称:把交易开关重新置 true,同样记录恢复时间点到日志。

下面这段是事件处理核心,注意 PrintFormat 里 TimeToString 用了 TIME_DATETIME_MINUTESTIME_SECONDS 三标志位,日志会精确到秒,方便你事后对账哪一根 K 线期间 EA 是被冻住的。
MQL5 / C++
            PrintFormat("Expert trading is stopped at: %s",
                      TimeToString(stop_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS));
            class=class="str">"cmt">//---
            class="kw">break;
         }
      class=class="str">"cmt">//--- class="num">8) resume trading 
      case CHARTEVENT_CUSTOM+class="num">8:
         {
         class="type">class="kw">datetime resume_time=(class="type">class="kw">datetime)_data.dparam;
         this.m_is_trade=true;             class=class="str">"cmt">//--- to log?
         if(InpIsLogging)
            PrintFormat("Expert trading is resumed at: %s",
                      TimeToString(resume_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS));
            class=class="str">"cmt">//---
            class="kw">break;
         }

把自定义事件接进你的项目目录

MQL5 的自定义事件机制已经能跑通一套脱离定时器轮询的事件驱动模型,核心在于用 EventChartCustom 往图表抛 ID、在 OnChartEvent 里按 id 分流处理。想验证这套思路,直接把源码解压丢进 MQL5\Projects\ChartUserEvent 即可编译,原文附带的 ChartUserEvent_EN.zip 体积 85.61 KB,含完整可运行例程。 语言层面仍有缺口:眼下没有类模板,也没有指向函数的指针,所以严格意义上的委托还写不出来。若未来版本补上这两样,才可能用原生语法把事件绑到任意对象方法上,届时自定义事件的写法会瘦一大圈。 外汇与贵金属杠杆高、滑点跳空频繁,事件回调里的下单逻辑务必先在策略测试器跑历史,再上模拟盘。把例程跑通后,你可以试着把其中一个 case 改成自己的信号触发,看图表是不是按预期响应。

常见问题

在平仓逻辑执行完后立即调用EventChartCustom复位标识,并重建下一次监听的事件ID,防止旧事件残留导致误信号。
给开仓与平仓分配不同的自定义事件ID,在OnChartEvent里用switch分支判断lparam值,分别走对应逻辑即可。
小布可读取你的策略日志与图表事件流,在捕获平仓或异常事件时弹出停止交易提醒,并支持一键暂停EA。
发送一个恢复用的自定义事件ID,在回调里把交易开关变量置true,并校验账户状态无误后再放行新订单。
把事件常量统一定义在头文件,处理函放在独立mqh,主EA只做调用,避免事件ID散落多处难维护。