构建自动运行的 EA(第 14 部分):自动化(VI)(基础篇)
📘

构建自动运行的 EA(第 14 部分):自动化(VI)(基础篇)

第 1/3 篇

◍ 用定时器把EA从图表事件里解放出来

MT5 的 EA 默认靠 OnTick 驱动,但高频 tick 下做重计算会拖慢终端。想让策略按固定节奏跑,而不是被每一笔报价牵着走,得用 EventSetTimer 把控制权交给系统定时器。 定时器精度受终端心跳限制,实测在普通 VPS 上最小稳定间隔约 100 毫秒,设更小的值也不会真触发那么密。对多数日内策略,1 秒~1 分钟的脉冲足够覆盖信号判定,还能把 CPU 占用压下去。 别把正态当圣经:定时器不会暂停 EA 逻辑,若你在 OnTimer 里写了下单动作,断网重连期间也可能补发订单,贵金属和外汇杠杆品种务必加连接状态判断。 下面这段是开启每秒定时器的骨架,注意 OnDeinit 里要清掉定时器,否则切换周期会残留。

MQL5 / C++
class="type">int OnInit()
{
   EventSetTimer(class="num">1); class=class="str">"cmt">// 设置1秒定时器
   class="kw">return(INIT_SUCCEEDED);
}

class="type">void OnTimer()
{
   class=class="str">"cmt">// 每秒执行一次的策略逻辑
}

class="type">void OnDeinit(class="kw">const class="type">int reason)
{
   EventKillTimer(); class=class="str">"cmt">// 销毁定时器
}

从手动 EA 走向自动化的路径起点

本系列前一篇《构建自动运行的 EA(第 13 部分):自动化(V)》已经讲清:零编程基础的交易者也能把交易逻辑落成自动 EA,那套思路对所有 EA 都通用。本文挑其中一条可行路线,专门解决「手动智能 EA 怎么转自动化」这个问题。 如果你没读过上一篇,直接看本篇会很吃力——上一篇给的是底层骨架,本篇是在骨架上接神经。建议先补完前篇再往下走,否则参数映射和信号触发逻辑会看不懂。 外汇和贵金属市场高杠杆、滑点跳空频繁,自动化只是把人为延迟去掉,不消除风险。转自动化后回测和实盘偏差可能扩大,上线前必须用 MT5 策略测试器跑至少 3 个月 tick 数据。

「用 C_Automaton 接管订单簿的自动路径」

把手动交易模型改成自动模型,核心不是重写 EA,而是在 EA 与底层管理类之间插一个 C_Automaton 类。它继承自 C_Manager,替交易者去订单簿里开平仓或挂单,原系统逻辑不动,风险隔离反而更清晰。 改造时只动了极少代码:删掉 OnChartEvent 处理程序,给 OnTime 定时器事件挂一个新函数。其余部分和手动模式完全一致。如果你要改系统本身,正确顺序是先移除 C_Automaton、还原 C_Mouse 做全量测试,确认稳了再加回自动类——永远保留人工监督的入口,人为干预留作最后手段。 下面这段是自动 EA 的头部声明与输入参数,注意 user01~user08 是交易相关的可调项,user90~user95 是一周各品种交易时段字符串。复制进 MT5 后,改这些 input 就能适配不同品种节奏,但 C_Automaton 构造函数参数可能因系统而异,别当成万能模板。 监督不能省 自动系统跑起来后,交易者至少得在终端盯着定时器与日志。外汇和贵金属杠杆高、滑点跳空频繁,无人值守的 EA 可能在极端行情把账户拖垮,监督本应持续而非摆设。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#class="kw">property description "This one is an automatic Expert Advisor"
class="macro">#class="kw">property description "for demonstration. To understand how to"
class="macro">#class="kw">property description "develop yours in order to use a particular"
class="macro">#class="kw">property description "operational, see the articles where there"
class="macro">#class="kw">property description "is an explanation of how to proceed."
class="macro">#class="kw">property version   "class="num">1.14"
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Generic Auto Trader\C_Automaton.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
C_Automaton *automaton;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">int      user01   = class="num">1;          class=class="str">"cmt">//Leverage Factor
class="kw">input class="type">class="kw">double   user02   = class="num">100;        class=class="str">"cmt">//Take Profit( FINANCE )
class="kw">input class="type">class="kw">double   user03   = class="num">75;         class=class="str">"cmt">//Stop Loss( FINANCE )
class="kw">input class="type">bool     user04   = true;       class=class="str">"cmt">//Day Trade ?
class="kw">input class="type">class="kw">double   user08   = class="num">35;         class=class="str">"cmt">//BreakEven( FINANCE )
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">class="kw">string   user90  = "class="num">00:class="num">00 - class="num">00:class="num">00";     class=class="str">"cmt">//Sunday
class="kw">input class="type">class="kw">string   user91  = "class="num">09:class="num">05 - class="num">17:class="num">35";     class=class="str">"cmt">//Monday
class="kw">input class="type">class="kw">string   user92  = "class="num">10:class="num">05 - class="num">16:class="num">50";     class=class="str">"cmt">//Tuesday
class="kw">input class="type">class="kw">string   user93  = "class="num">09:class="num">45 - class="num">13:class="num">38";     class=class="str">"cmt">//Wednesday
class="kw">input class="type">class="kw">string   user94  = "class="num">11:class="num">07 - class="num">15:class="num">00";     class=class="str">"cmt">//Thursday
class="kw">input class="type">class="kw">string   user95  = "class="num">12:class="num">55 - class="num">16:class="num">25";     class=class="str">"cmt">//Friday

◍ 用定时器驱动跨周期状态机

这段初始化代码把一周七天的交易时段字符串塞进一个 C_Automaton 实例,魔法数写死为 987654321,M5 周期下跑逻辑。注意周六参数 user96 默认是 "00:00 - 00:00",等于直接禁掉周末交易。 OnInit 里 EventSetMillisecondTimer(100) 是关键:每 100 毫秒触发一次 OnTimer,再由 (*automaton).Triggers() 轮询状态,而不是靠 OnTick 推。外汇和贵金属市场跳空频繁,这种脱离报价事件的定时器架构,在流动性断裂时仍可能误触,属于高风险设计,上 MT5 前先测休市时段。 OnDeinit 里 delete automaton 和 EventKillTimer 成对出现,避免 EA 重载时残留定时器句柄。OnTick 留空说明全部决策权交给了毫秒定时器与状态机内部。

MQL5 / C++
class="kw">input class="type">class="kw">string    user96   = "class="num">00:class="num">00 - class="num">00:class="num">00";        class=class="str">"cmt">//Saturday
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_MAGIC_NUMBER class="num">987654321
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
      class="type">class="kw">string szInfo;
      
      automaton = new C_Automaton(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, PERIOD_M5);
      for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)
      {
            class="kw">switch (c0)
            {
                  case SUNDAY      : szInfo = user90; class="kw">break;
                  case MONDAY      : szInfo = user91; class="kw">break;
                  case TUESDAY     : szInfo = user92; class="kw">break;
                  case WEDNESDAY   : szInfo = user93; class="kw">break;
                  case THURSDAY    : szInfo = user94; class="kw">break;
                  case FRIDAY      : szInfo = user95; class="kw">break;
                  case SATURDAY    : szInfo = user96; class="kw">break;
            }
            (*automaton).SetInfoCtrl(c0, szInfo);
      }
      (*automaton).CheckToleranceLevel();
      EventSetMillisecondTimer(class="num">100);
      class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
      class="kw">delete automaton;
      EventKillTimer();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick() { }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
{
      (*automaton).Triggers();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTradeTransaction(class="kw">const MqlTradeTransaction &trans, class="kw">const class="type">MqlTradeRequest &request, class="kw">const class="type">MqlTradeResult &result)
{
      class="kw">switch (trans.type)
      {
            case TRADE_TRANSACTION_POSITION:

把成交回执喂给状态机

这段 switch 是 EA 接收交易终端回执的末端逻辑,挂在 OnTradeTransaction 里跑。它把五种交易事务类型分别路由到 automaton 指针所指的状态机方法,让持仓、挂单、删单和改 SLTP 在内存里实时同步。 遇到 ORDER_DELETE 且订单号等于持仓号时,直接调 PendingToPosition 把挂单转成持仓视图;若不等,则更新持仓并擦掉对应挂单 ticket。REQUEST 分支额外卡了三道条件:品种必须是当前 _Symbol、retcode 为 TRADE_RETCODE_DONE、magic 等于 def_MAGIC_NUMBER,避免别的逻辑或手动单污染状态。 开 MT5 把这段贴进你的事务回调,把 def_MAGIC_NUMBER 改成实盘用的幻数,就能在日志里观察到每笔成交后 automaton 内部持仓数组的更新延迟——通常在一帧 tick 内完成,黄金和外汇品种都适用,但实盘滑点可能让 ORDER_UPDATE 晚于 DEAL 到达,属于高风险环境下的正常抖动。

MQL5 / C++
(*automaton).UpdatePosition(trans.position);
class="kw">break;
case TRADE_TRANSACTION_ORDER_DELETE:
   if (trans.order == trans.position) (*automaton).PendingToPosition();
   else
   {
      (*automaton).UpdatePosition(trans.position);
      (*automaton).EraseTicketPending(trans.order);
   }
   class="kw">break;
case TRADE_TRANSACTION_ORDER_UPDATE:
   (*automaton).UpdatePending(trans.order);
   class="kw">break;
case TRADE_TRANSACTION_REQUEST: if ((request.symbol == _Symbol) && (result.retcode == TRADE_RETCODE_DONE) && (request.magic == def_MAGIC_NUMBER)) class="kw">switch (request.action)
   {
      case TRADE_ACTION_DEAL:
         (*automaton).UpdatePosition(request.order);
         class="kw">break;
      case TRADE_ACTION_SLTP:
         (*automaton).UpdatePosition(trans.position);
         class="kw">break;
      case TRADE_ACTION_REMOVE:
         (*automaton).EraseTicketPending(request.order);
         class="kw">break;
   }
   class="kw">break;
}

常见问题

可以用定时器让 EA 脱离图表点击事件,按固定间隔自动执行检查与下单逻辑,不必手动刷新。
先抽离订单执行与状态判断,用一个基础自动化类接管路径,再把触发源从按钮改成定时器。
小布可以盯盘口与状态机回执,发现成交异常或长时间无进展时直接提示你介入。
用定时器驱动跨周期状态机,每个周期只更新自身状态,由统一机判断条件满足再发单。
把成交回执作为事件写入状态机输入队列,由机内确认环节消费,避免回调丢失导致挂起。