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

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

第 1/3 篇

在 MT5 里搭一个会自己跑的 EA 骨架

MT5 的 EA 本质上是一段随行情Tick或定时事件反复执行的 MQL5 程序,挂上图表后就能脱离人工点击自动下单。想验证这点,新建一个 EA 把 OnTick 留空只写一行 Print,挂 EURUSD 图表看专家日志,几秒内就会刷出一堆时间戳——说明事件循环已经在跑。 真正自动化的第一步不是写策略,而是先把'不该做'的事拦住:比如没有网络连接、账户禁止自动交易、当前品种点差异常时,EA 应当直接 return。下面这段代码演示了最基础的闸门判断,复制进 MT5 的 EA 模板就能编译通过。 外汇与贵金属保证金交易杠杆高,自动执行会放大滑点和断网风险,任何自动逻辑上线前都应在策略测试器用历史数据跑至少 3 个月。 [CODE] int OnInit() { if(!TerminalInfoInteger(TERMINAL_CONNECTED)) return INIT_FAILED; if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)) return INIT_FAILED; return INIT_SUCCEEDED; } [/CODE] 上面 OnINit 里 TERMINAL_CONNECTED 检查终端是否连着经纪商服务器,断线就初始化失败不加载;ACCOUNT_TRADE_ALLOWED 读账户的交易许可标志,被禁自动交易也直接退出。两个判断都过才返回成功,EA 才会进 OnTick 主循环。

MQL5 / C++
class="type">int OnInit()
  {
   if(!TerminalInfoInteger(TERMINAL_CONNECTED)) class="kw">return INIT_FAILED;
   if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)) class="kw">return INIT_FAILED;
   class="kw">return INIT_SUCCEEDED;
  }

◍ 从半手动到常驻图表的自动化边界

前面那篇里做的盈亏平衡与尾随停止,靠 OCO 持仓止损线或挂单当停止价位两种模式跑,逻辑虽清楚,但本质上还是半手动:移动价位、管理挂单的动作由 EA 按你设的参数执行,人得在场盯着触发。 本篇要加的自动化级别,是让 EA 常驻图表、按你圈定的时间段允许交易,而不是 24 小时无脑发单。外汇与贵金属波动剧烈、杠杆高风险极大,这种时间窗控制的核心就是限频——把每日允许的最大交易量写死在代码里,超了就熄火。 进度控制怎么落到代码上,其实没有唯一解法,主要取决于你打算把控制逻辑塞进 EA 的哪一层。系列文章走最简路线,所以下面这段会写成易删易改的结构,手动 EA 里也能直接复用,但用不用由你定。 必须提醒一句:EA 不管多“自动”,都别让它脱离监督自己跑,这是底线而不是建议。

「MQL5 里绕开多重继承的实务取舍」

MQL5 不像 C++ 那样支持多重继承,但缺这个特性不代表代码表达能力受限。用组合或单向继承链,同样能把时间控制、订单管理这些职责塞进系统,只是路径要重新设计。 原文图例对比了两种建模:图例 01 里 C_Manager 同时继承 C_Orders 和 C_ControlOfTime,方法直接叠进一个类,类体迅速膨胀;图例 02 改成 C_ControlOfTime 继承自 C_Orders,再由 C_Manager 接 C_ControlOfTime。区别在于,后者刻意颠倒了继承顺序——EA 不直接碰 C_Orders,只通过 C_ControlOfTime 暴露需要的接口。 这么排不是为了炫技。C_Orders 管下单撤单,若让 EA 随意调用,容易在时序和权限上写出隐患;把时间控制类放在继承链上层,既保留了订单能力,又收窄了对外暴露面。外汇和贵金属杠杆高、跳空频繁,这类封装上的克制能降低误触风险。 值得记一个数据点:图例 01 的 C_Manager 因合并两个父类实现,类规模增长显著快于图例 02 的单链结构。实现方式没有唯一解,保住代码完整性和运行结果一致才是硬指标,你可以按自己的习惯重排类关系,只要 MT5 编译跑通、逻辑自洽就行。

时隙调度别急着写文件逻辑

规划第二阶段要落的是时隙控制类,核心问题是交易者怎么低成本设定 EA 的工作时间窗。用外部文件存时间范围看似灵活,能支持一天内多段运行,但实测里多数 EA 其实只在单一时段干活,文件方案反而把简单事弄复杂,还容易在表现差的时段压垮你。 MetaTrader 5 自带设置存取机制,可以走中间路线:按上午、下午分别存成 .set 配置,人工或定时加载即可。图例 03 的 EA 设置面板里,<保存> 写盘、<打开> 回载,平台自己维护参数,我们省掉自建调度文件的代码,可靠性反而更高。 还有个边界点:允许时段之外 EA 不能开仓,但已有持仓和挂单必须能管。工作窗外的订单管理逻辑建议保留开关,让你按自己风控政策决定平还是留,这块策略我不写死。

◍ 用 final 锁死时间控制类的继承边界

在 C_ControlOfTime.mqh 里先 include 了 C_Orders.mqh,让新类以 protected 方式继承 C_Orders。这样子类能拿到父类的受保护方法,但又不能直接从外部调用,边界比 public 继承更干净。 私有区定义了一个 st_00 结构,只含 Init 与 End 两个 datetime 字段,用来存每天 EA 允许运行的起止时刻。数组写成 m_InfoCtrl[SATURDAY + 1],而不是硬编码 7,是因为 MQL5 里 ENUM_DAY_OF_WEEK 的 SATURDAY 本身就是枚举值,用单词访问比数字更直白,也顺带避免了把周日当第 0 天、周六当第 6 天时数错。 构造函数里用循环从 SUNDAY 扫到 SATURDAY 填数组。这套写法依赖枚举顺序:周日定义为一周首日、周六末日,循环才成立。若把周一设成首日,MT5 跑这段代码会直接抛运行时错误,所以高级写法看着优雅,配置错一点就崩。 SetInfoCtrl 负责把调用方传来的 "04:15-22:50" 这样的字符串用 StringSplit 按减号拆开,再经 StringToTime 转成时间。StringToTime 会自动补当前日期,通常用因式分解把日期剔掉只留时分;不删也行,但后面 CtrlTimeIsPass 里就要补日期比对。转换完必须查 Start<=End,否则立刻打印并请求关闭 EA。 CtrlTimeIsPass 每次被调都会重新抓本地时间、拆出星期几和时分,按当天范围返回 true/false。哪怕周一开着 EA 跑到周二,它也会自动切到周二时段,不需要人为重启。周模式比绑死日期更省心,但函数和结构体自然就比「只管今天」的实现复杂一截。 两个函数声明都带了 final 关键字。它的意思是:任何继承 C_ControlOfTime 的子类,既不能重写这两个函数,也不能覆盖父类里同名的 final 方法,否则编译直接报错。virtual 放开修改、final 堵死修改,层级深的类体系里提前锁好,能少踩很多覆盖冲突的坑。

MQL5 / C++
class="macro">#include "C_Orders.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class C_ControlOfTime : class="kw">protected C_Orders
{
				class="kw">private :
						class="kw">struct st_00
						{
							class="type">class="kw">datetime Init,
											End;
						}m_InfoCtrl[SATURDAY + class="num">1];
class=class="str">"cmt">//+------------------------------------------------------------------+
				class="kw">public	:
class=class="str">"cmt">//... Class functions ...
};

常见问题

看是否用了 OnTick/OnTimer 等事件函数且没被注释掉;常驻 EA 会在行情跳动时自动执行,半手动通常只在脚本按钮或人工调用时跑。
语言不支持多重继承,实务上用组合替代:把一个类实例作为另一个类的成员来调用,别硬套继承树。
可以,小布能盯盘时自动标注 EA 是否离线、调度是否卡死,把异常汇总推给你,你只管决策要不要干预。
调度频率高时文件锁和 IO 阻塞会拖垮执行节奏,先只跑内存里的状态判断,文件落盘单独低频做。
用 final 禁止派生能避免子类改掉时间边界逻辑导致全局调度错乱,把控制权收口在基类最稳。