6 步创建您自己的交易机器人!(基础篇)
📘

6 步创建您自己的交易机器人!(基础篇)

第 1/3 篇

用六步在 MT5 搭出自己的 EA

想在 MT5 里写个自动交易机器人,不必从零啃文档。官方把流程拆成 6 步:新建 EA 文件、写入场逻辑、加风控、挂订单函数、回测调参、上模拟盘。

  • 年 11 月 6 日发布的这套示例,至今在终端里仍能跑通,说明 MQL5 的基础框架很稳。外汇和贵金属波动大、杠杆高,实盘前务必用策略测试器跑够样本。

打开 MT5 按 F4 进 MetaEditor,选『EA 交易』模板,你就能在十分钟里看到第一个空骨架。后面每步都是填肉,不是重新造轮子。

◍ 向导能跑但改不动的尴尬

MetaEditor 5 自带的 MQL5 向导确实省事:点 5 次鼠标就能生成一个 EA,丢进策略测试仪做参数优化,再挂到账户上自动跑,中间几乎不用手写逻辑。对只想快速验证一个现成系统的交易员来说,这套流程的性价比很高。 但一旦你想写自己的交易信号模块——那种没在任何文章里出现过的独特逻辑——向导就不够用了。打开 MQL5 文档翻到“标准库”那一栏,很多人会卡在接口命名和继承结构上,发现向导生成的骨架并不能直接塞进自定义信号类。 外汇和贵金属杠杆交易高风险,用向导出的机器人直接上实盘前,至少要在策略测试仪里跑够不同年份的 tick 数据,别被“5 次点击”的轻松感带偏。

「向导背后的五个基类」

MQL5 向导能把 EA 的搭建流程自动化,但前提是组件必须挂在五个基类之下。这五个类是 CExpertBase、CExpert、CExpertSignal、CExpertTrailing 和 CExpertMoney。 CExpertBase 是其余四个类的父类;CExpert 负责把交易机器人跑起来,属于交易类;CExpertSignal 管信号模块,本篇只拆这一类;CExpertTrailing 做跟踪止损;CExpertMoney 管仓位与资金。 表面看这是一套让人头大的面向对象编程(OOP)阵容,但实际没必要全懂。现在几乎人人手机功能一大堆却说不清原理,写信号模块也一样——只抠 CExpertSignal 的可用接口就够在 MT5 里验证。 后续我们会从零走一遍信号模块的创建阶段,你会发现不碰类与继承也能产出可用模块;想深钻再补 OOP 不迟。

从零搭一个信号类骨架

做自定义信号模块时,别去动系统自带的交易信号文件,改了容易把自己绕晕。干净做法是新建独立类,只做派生,不碰基类逻辑。 在 MT5 的 Navigator 里,先到 MQL5/Include/Expert/ 目录下右键新建文件,用来放信号代码。再右键该文件选 New File,向导里 Class Name 填 MA_Cross,基类(Base Class)填 CExpertSignal——这个类准备做双均线交叉信号,所以名字直白点好认。 点 Finish 后向导只生成空壳,必须手动补一句 #include 把基类头文件指过去,否则编译器不认 CExpertSignal。补完按 F7 编译,零错误才能往下写逻辑。外汇和贵金属波动大、杠杆高,这类自写模块只解决信号结构,不预示任何盈亏方向。 下面就是刚建好、已通过编译检查的骨架代码,注意包含路径用的是相对写法 "..\ExpertSignal.mqh",少一个斜杠都可能报找不到基类。

MQL5 / C++
class="macro">#include "..\ExpertSignal.mqh"   class=class="str">"cmt">// CExpertSignal 是在包含文件 ExpertSignal 中
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                                  MA_Cross.mqh |
class=class="str">"cmt">//|                                             Copyright class="num">2012, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------
class="macro">#class="kw">property copyright "Copyright class="num">2012, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include "..\ExpertSignal.mqh"   class=class="str">"cmt">// CExpertSignal 是在包含文件 ExpertSignal 中
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------
class MA_Cross : class="kw">public CExpertSignal
  {
class="kw">private:
class="kw">public:
                     MA_Cross();
                    ~MA_Cross();
  };
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------
MA_Cross::MA_Cross()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------
MA_Cross::~MA_Cross()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------

◍ 让空类被向导识别的模块头

新建的 signal 类编译通过却不在 MT5 向导的模块列表里,不是 bug,而是缺了一段给向导看的声明头。标准库每个模块源文件开头都有一段按固定规则写的注释块,向导靠它解析“这是个能用的信号”。 头里第一行是向导下拉框显示的标题;类型必须写 SignalAdvanced,这是信号模块版本的硬规定;名称与短名分别进 EA 内部注释和外部参数前缀(Signal_<ShortName>_<参数名> 形式);类字段填你写的类名。其后的参数行用 参数名,类型,默认值,描述 逗号分隔,枚举类型也直接写。 下面这段就是双均线交叉模块的处理函数。FastPeriod 默认 13、SlowPeriod 默认 21,两条都默认简单平滑 MODE_SMA,向导里就能调这 4 项。保存编译零错误后重开向导,模块出现且参数全暴露。 别把向导当黑箱 头注释写错一个字段类型,向导可能静默忽略你的模块,编译还不报错,排查起来纯靠肉眼对字段。

MQL5 / C++
class=class="str">"cmt">// 向导描述开始
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 类描述                                                            |
class=class="str">"cmt">//| 标题=两条移动平均线交叉信号                                       |
class=class="str">"cmt">//| 类型=SignalAdvanced                                               |
class=class="str">"cmt">//| 名称=My_MA_Cross                                                  |
class=class="str">"cmt">//| 短名=MaCross                                                      |
class=class="str">"cmt">//| 类=MA_Cross                                                       |
class=class="str">"cmt">//| 页=不需要                                                         |
class=class="str">"cmt">//| 参数=FastPeriod,class="type">int,class="num">13,fast MA周期                                |
class=class="str">"cmt">//| 参数=FastMethod,ENUM_MA_METHOD,MODE_SMA,快速 MA 方法              |
class=class="str">"cmt">//| 参数=SlowPeriod,class="type">int,class="num">21,slow MA 周期                               |
class=class="str">"cmt">//| 参数=SlowMethod,ENUM_MA_METHOD,MODE_SMA,慢速 MA 方法              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// 向导描述结束

「把外部参数喂进信号类」

交易模块用 MA_Cross 类封装时,处理函数里声明的 4 个外部参数不能只停在输入区,得在类里落私有成员存值。类中 private 段加 4 行:m_period_fast、m_period_slow、m_method_fast、m_method_slow,分别对应快/慢均线周期与平滑类型。 要让 EA 初始化时自动把外部参数写进这些成员,办法是在 public 段声明同名设置方法。比如 FastPeriod(int value) 直接把入参赋给 m_period_fast,用 MQL5 向导生成 EA 挂图表后,这 4 个方法会在初始化阶段被逐一调用。 模块参数有一条硬规则:处理函数里每声明一个参数,类内就要配一个私有成员存值、一个公共方法设值,且方法名必须和参数名一致。 默认值别留空。变量和成员都必须初始化,否则容易出那种查半天才发现的幽灵 bug。最稳的做法是在构造函数用初始化列表给成员赋模块处理函数里的默认参数,对象一创建就先跑这道。 参数和处理函数数量对等 模块处理函数中写了几个参数,类里实现的方法和成员就得有对应数量,不复杂但构造函数上的默认参数值绝不能漏。下面这段就是参数成员与设置方法的落地形态。

MQL5 / C++
class MA_Cross : class="kw">public CExpertSignal
  {
class="kw">private:
  class=class="str">"cmt">//--- 配置模块参数
   class="type">int                m_period_fast;     class=class="str">"cmt">// 快速 MA 周期
   class="type">int                m_period_slow;     class=class="str">"cmt">// 慢速 MA 周期
   ENUM_MA_METHOD     m_method_fast;     class=class="str">"cmt">// 快速 MA 平滑类型
   ENUM_MA_METHOD     m_method_slow;     class=class="str">"cmt">// 慢速 MA 平滑类型
class="kw">public:
  class=class="str">"cmt">//--- 类构造函数
                     MA_Cross();
  class=class="str">"cmt">//--- 类析构函数
                    ~MA_Cross();
  class=class="str">"cmt">//--- 设置方法
  class="type">void               FastPeriod(class="type">int value)                { m_period_fast=value;      }
  class="type">void               FastMethod(ENUM_MA_METHOD value)     { m_method_fast=value;      }
  class="type">void               SlowPeriod(class="type">int value)                { m_period_slow=value;      }
  class="type">void               SlowMethod(ENUM_MA_METHOD value)     { m_method_slow=value;      }
   };

常见问题

先别动主流程,单独抽出一个信号类改判断条件,向导其余骨架保留,改动最小也最稳。
缺了模块头声明,必须在文件开头用特定宏把类注册成向导可识别模块,否则向导读不到。
可以,小布能读取你的类结构和参数声明,指出模块头缺失或外部参数未接入等常见坑,省去反复编译试错。
在信号类里声明带外部修饰的输入变量,向导生成时会自动把主面板参数映射进去,不用手写传递函数。
不必全懂,先弄清信号类和控制类职责即可跑通基础版,其余三类等要加风控和仓位管理再补。