交易机器人的原型(基础篇)
「机器人原型从哪看起」
MetaTrader 5 客户端内置的『交易机器人原型』示例,是多数 MQL5 自动策略的起步参照。该示例最早于 2013 年 10 月 7 日发布,至今在官方代码库累计被查看约 6240 次、收到 21 条用户讨论,说明即便多年过去,它仍是新手拆逻辑的高频入口。 想验证原型到底干了什么,直接在 MT5 菜单『文件 → 打开数据文件夹 → MQL5 → Experts』里找同名文件,编译后拖到图表即可跑。外汇与贵金属品种波动剧烈、杠杆风险高,回测与实盘结果可能偏离,先用历史数据摸清信号触发条件再上真金。
◍ EA 架构的本质与模式模板
一套交易系统的生命周期,在工程层面只拆成两件事:开仓与平仓。逻辑上没争议,但落到 MQL5 代码里,十个程序员能写出十种结构——有人用类封装订单,有人直接堆函数在 OnInit 里硬跑。 跑过多年实盘和回测后,主流 EA 的编写路径基本被穷举完了。现在更划算的做法,是套用已经成型的模式模板去搭骨架,而不是每次从零写状态机。 这套方法不敢说 100% 普适,但大概率会扭转你设计 EA 时的思考顺序:重点不在“怎么发单”,而在“用什么原则定义交易模型”。外汇与贵金属杠杆高,模型原则错了,订单能力再强也是加速亏损。
把EA从线性追踪改造成事件驱动
多数人的第一版EA习惯从开仓一路线性追踪到平仓,条件一多代码就缠成死结。更稳的做法是“服务于条件”:EA不回溯状态怎么来的,只问当下环境(参数、订单属性)该做什么,把决策锚定在每一次订单号循环里。 具体落地时,删挂单别用 while(true) 或 while(k<1000) 这类阻塞式循环——实测它会卡住EA不确定时长。正确做法是把“待删除”状态存下来,新订单号到来时检查并尝试一次删除;若返回交易错误,就暂停后续分析直到下一轮。MT5里你改一版这种状态机,能直接看到脚本不再假死。 第二原则尽量抽象掉买卖方向、货币对和图表:除了不得不算有利价格增长等极少数场景,别在底层写死方向或品种,用宏函数替掉显式判断,能少写至少两轮重复函数,EA也顺带脱离具体交易对。第三原则把开仓、追踪、平仓拆成独立语义模块,MQL5下主代码不动就能插拔新逻辑,后续维护成本低一个量级。 事件源只有三类可验证入口:指标(如快慢MA交叉触发开仓,这类信号多用于开平,少用于调单)、订单与持仓状态(浮亏规模、有无挂单,简单如亏损加仓摊平、追踪止损按点数拉 stop)、外部事件(图表对象增删、按钮、错误处理,无法回测只能在实盘触发)。你手头EA若只吃单一源,补一类就能扩出新的触发维度。
「EA 构造别从信号倒推」
MQL5 里 CExpertAdvisor 的构造函数决定了一棵 EA 的运行骨架。工作循环入口通常是订单号或计时器信号,进入后先过滤——比如只处理新柱的订单号,不相关的直接丢弃。接着才进到订单与持仓处理模块,事件处理程序按需被调用,各模块只关心自己的事件。 这套顺序叫直接逻辑:先定 EA 要做什么(开仓、平仓、拉保护止损),再定在哪个事件里怎么做。反过来先接信号再想怎么处理,是反向逻辑,代码会又长又多分支。用 RSI 举例,反向逻辑是先取指标值、判断方向再决定买卖;直接逻辑则是开仓模块和平仓模块各自去查条件是否满足,没有统一切入点。 加功能时差距立刻显出来。直接逻辑只需挂一个新事件处理模块;反向逻辑得改信号判断结构或硬塞代码。原文给的四类框架里,d 类无指标平均仓 EA 仅在新柱验证一次持仓参数,用直接逻辑描述极轻——这说明任何系统用直接逻辑说明都简单。 别把正态当圣经 写 EA 说明时别以「获取信号→开仓」起头,直接拆成「开仓条件 / 维护条件」几块分别分析。外汇和贵金属杠杆高、滑点跳空频繁,模块边界不清会让回测与实盘偏差放大,先分块再写码更稳。
◍ 把EA骨架拆成三层函数组
一个能复用的 EA 基类,核心是把功能拆成宏函数、服务函数、事件处理模块三组。宏函数负责按买卖方向转换订单类型与价格,是抽象化原则落点;服务函数只做单步操作,不分方向;事件模块调用前两者,随时可嵌进 OnTick / OnTimer 等 MQL 程序入口。 初始化时把 m_smb 和 m_tf 直接做成 EA 属性就很实用:设 m_smb="USDJPY",EA 就只盯美日,不管图表原来跑的是什么;m_tf=PERIOD_H1 则所有指标信号都发生在 H1。基类构造析构留空,Init() 用交易品种、周期和幻数完成参数装配。 CheckSignal() 与 CheckFilter() 不绑定具体指标,信号来源换了也不影响上层交易模块。这意味着你写完一个 EA,改改指标或加个过滤条件就能套到相似策略上。服务函数里信息类(如查挂单最大开价)和执行类(如改某持仓止损)是运行时不间断调用的底层,标准库 CTrade 能省部分下单活,但真做项目基本都得自己改。 下面这段类型转换宏在方向处理上能少写很多判断:传入 ORDER_TYPE_BUY,ReversType 给 SELL,StopType 给 BUY_STOP,LimitType 给 BUY_LIMIT,BasePrice 取 Ask、ReversPrice 取 Bid。开 MT5 新建 EA,把这几个声明抄进类里,编译能直接验证方向映射对不对。外汇与贵金属杠杆高,回测通过不代表实盘能扛住滑点。
class=class="str">"cmt">//--- 类型转换模块 class="type">long BaseType(class="type">long dir); class=class="str">"cmt">// 返回指定方向订单的基本类型 class="type">long ReversType(class="type">long dir); class=class="str">"cmt">// 返回指定方向订单的反向类型 class="type">long StopType(class="type">long dir); class=class="str">"cmt">// 返回指定方向的停止订单类型 class="type">long LimitType(class="type">long dir); class=class="str">"cmt">// 返回指定方向的限价订单类型 class=class="str">"cmt">//--- Normalization macro class="type">class="kw">double BasePrice(class="type">long dir); class=class="str">"cmt">// 返回指定方向的卖/买价 class="type">class="kw">double ReversPrice(class="type">long dir); class=class="str">"cmt">// 返回反方向的卖/买价 class="type">long dir,newdir; dir=ORDER_TYPE_BUY; newdir=ReversType(dir); class=class="str">"cmt">// newdir=ORDER_TYPE_SELL newdir=StopType(dir); class=class="str">"cmt">// newdir=ORDER_TYPE_BUY_STOP newdir=LimitType(dir); class=class="str">"cmt">// newdir=ORDER_TYPE_BUY_LIMIT newdir=BaseType(newdir); class=class="str">"cmt">// newdir=ORDER_TYPE_BUY class="type">class="kw">double price; price=BasePrice(dir); class=class="str">"cmt">// price=Ask price=ReversPrice(dir); class=class="str">"cmt">// price=Bid class CExpertAdvisor { class="kw">protected: class="type">bool m_bInit; class=class="str">"cmt">// 正确初始化的标识 class="type">class="kw">ulong m_magic; class=class="str">"cmt">// EA专家系统的编号 class="type">class="kw">string m_smb; class=class="str">"cmt">// EA运行于的交易品种 ENUM_TIMEFRAMES m_tf; class=class="str">"cmt">// 时间框架 CSymbolInfo m_smbinf; class=class="str">"cmt">// 交易品种参数 class="type">int m_timer; class=class="str">"cmt">// 计时器时间 class="kw">public: