6 步创建您自己的交易机器人!·进阶篇
📘

6 步创建您自己的交易机器人!·进阶篇

第 2/3 篇

双均线类的默认参数写在构造里

把快速和慢速均线的周期、平滑方式直接塞进构造函数初始化列表,是 MQL5 里封装指标信号类最常见的起步做法。下面这段初始化把快线默认设成 13 周期 SMA,慢线默认设成 21 周期 SMA,类一实例化就带好基础参数,不用每次在 OnInit 里手动赋值。 注意原注释里有一处笔误:快线周期写的是 13,但注释说「默认快速 MA 周期是 3」,实际代码以 m_period_fast(13) 为准,3 只是错写。开 MT5 新建 EA 把这段贴进类定义,编译后从左侧导航看成员变量初始值就能验证。 别把注释当真值 代码注释和初始化列表不一致时,以分号前的赋值表达式为唯一依据;注释只作人读参考,回测前先打印 m_period_fast 确认。

MQL5 / C++
MA_Cross::MA_Cross(class="type">void) : m_period_fast(class="num">13),
                     m_method_fast(MODE_SMA),
                     m_period_slow(class="num">21),
                     m_method_slow(MODE_SMA)
  {
  }

◍ 给均线交叉信号类加一道参数闸门

写 EA 信号模块时,参数填错不会让编译器立刻报错,但会让策略在实盘里静默失效。MA_Cross 继承自 CExpertSignal,父类强制要求重写 ValidationSettings(),无参且必须返回 bool——这是 MT5 框架初始化交易信号模块的硬性入口。 很多人漏掉的第一行是调用基类校验:if(!CExpertSignal::ValidationSettings()) return(false);。若省掉这行,EA 加载时可能直接初始化失败,信号模块根本不会挂载。外汇与贵金属杠杆高,参数错误引发的错单风险会被放大,上线前必须本地先跑通。 下面这段是类内声明与函数体的最小可用骨架,逐行拆给你看:

class MA_Cross : public CExpertSignal 定义派生关系;bool ValidationSettings(); 是公共区的方法声明。函数体开头先调基类,再用 m_period_fast<1m_period_slow<1 拦掉周期小于 1 的非法值;m_period_fast>m_period_slow 保证快线周期小于慢线;最后用四个 MODE_* 枚举比对,卡死平滑类型只能是 SMA/EMA/SMMA/LWMA 之一。任一不满足就 PrintFormat 报错并返回 false。

验证动作很直接:把 fast 周期故意设成 50、slow 设成 10,编译挂 EA 到 EURUSD 图表,专家日志会打印 SlowPeriod=10 必须大于 FastPeriod=50! 并拒绝启动。改回 fast=10、slow=50 才过校验。

MQL5 / C++
class MA_Cross : class="kw">public CExpertSignal
  {
...
  class=class="str">"cmt">//--- 类构造函数
                   MA_Cross(class="type">void);
  class=class="str">"cmt">//--- 类析构函数
                  ~MA_Cross(class="type">void);
  class=class="str">"cmt">//--- 检查输入数据正确与否
  class="type">bool              ValidationSettings();
...
  };
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 检查输入参数和返回true是否一切OK     |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">bool MA_Cross:: ValidationSettings()
  {
  class=class="str">"cmt">//--- 调用基类函数
  if(!CExpertSignal::ValidationSettings())   class="kw">return(false);
  class=class="str">"cmt">//--- 检查周期,柱线数量计算MA >=class="num">1
  if(m_period_fast<class="num">1 || m_period_slow<class="num">1)
     {
       PrintFormat("不正确的周期值!FastPeriod=%d, SlowPeriod=%d",
                   m_period_fast,m_period_slow);
       class="kw">return false;
     }
class=class="str">"cmt">//--- 慢速 MA 周期必须大于快速MA周期
  if(m_period_fast>m_period_slow)
     {
       PrintFormat("SlowPeriod=%d 必须大于 FastPeriod=%d!",
                   m_period_slow,m_period_fast);
       class="kw">return false;
     }
class=class="str">"cmt">//--- 快速 MA 平滑类型必须是四个枚举类型值之一
  if(m_method_fast!=MODE_SMA && m_method_fast!=MODE_EMA && m_method_fast!=MODE_SMMA && m_method_fast!=MODE_LWMA)
     {
       PrintFormat("无效平滑类型: 快速 MA!");
       class="kw">return false;
     }
class=class="str">"cmt">//--- 慢速 MA 平滑类型必须是四个枚举类型值之一
  if(m_method_slow!=MODE_SMA && m_method_slow!=MODE_EMA && m_method_slow!=MODE_SMMA && m_method_slow!=MODE_LWMA) 
     {
       PrintFormat("无效平滑类型: 慢速 MA!");
       class="kw">return false;
     }
class=class="str">"cmt">//--- 所有检查完成, 全部通过
  class="kw">return true;
  }
class="type">bool MA_Cross:: ValidationSettings()
class=class="str">"cmt">//--- 调用基类函数
  if(!CExpertSignal::ValidationSettings())   class="kw">return(false);
class=class="str">"cmt">//--- 我们的代码检查参数

「把自定义均线接进信号模块」

信号模块跑起来时,InitIndicators() 会被 EA 自动调用,这里就是挂指标的地方。原文把快速、慢速两条均线拆成 CreateFastMA() 和 CreateSlowMA() 两个独立函数,成功返回 true,失败直接断链,逻辑干净也好排错。 MQL5 里 CIndicators 是专门收留技术指标实例的容器,只认 CIndicator 及其子类。想塞自定义指标,就得用 CiCustom——它本身就是 CIndicator 的派生类,能直接进这个集中被统一管理(同步、计算、释放内存全交给框架)。 以快速 MA 为例,先判一下 indicators 指针是否为空,再把 m_fast_ma 的指针加进集里;随后填 MqlParam 结构。标准库自带的 Custom Moving Average.mq5 实际在 data_folder/MQL5/Indicators/Examples/ 下,所以路径必须带 Examples/,否则测试仪找不到。参数对依次是:指标名字符串、时段 int、位移 int、平均法 int,最后用 Create() 绑定品种、周期、ENUM_INDICATOR 类型和缓冲区数。 慢速 MA 同理。关键一步:文件头加 #property tester_indicator 指向编译后的 .ex5,不然回测时自定义指标加载不了——用几个不同指标就写几行,缺一行测出来就是空值。 取数不用自己写缓冲读取,直接调父类 SIndicator 的 GetData() 按索引拿值就行。下面这段是 InitIndicators 的骨架,注意 MA_Cross:: 作用域和指针判空是必写项。

MQL5 / C++
class="kw">public:
  class=class="str">"cmt">//--- 类构造函数
  MA_Cross(class="type">void);
  class=class="str">"cmt">//--- 类析构函数
  ~MA_Cross(class="type">void);
  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; }
  class=class="str">"cmt">//--- 检查输入数据正确与否
  class="type">bool ValidationSettings();
  class=class="str">"cmt">//--- 创建信号模型的指标和时间帧
  class="type">bool InitIndicators(CIndicators *indicators);
 };
...
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 创建指标                                          |
class=class="str">"cmt">//| 输入: 一组指标指针                                |
class=class="str">"cmt">//| 输出:若成功true,否则false                        |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">bool MA_Cross::InitIndicators(CIndicators* indicators)
  {
class=class="str">"cmt">//--- 标准检查一组 NULL指标
   if(indicators==NULL) class="kw">return(false);
class=class="str">"cmt">//--- 初始化指标和时间帧及附加过滤器
   if(!CExpertSignal::InitIndicators(indicators)) class="kw">return(false);
class=class="str">"cmt">//--- 创建我们的 MA 指标
   ... 一些代码
class=class="str">"cmt">//--- 抵达此处, 函数成功, 返回 true
   class="kw">return(true);

双均线信号的指标初始化链路

在 MT5 的 EA 信号类里,均线的生命周期从 InitIndicators 开始。基类 CExpertSignal 先接管 indicators 指针做空值校验,再交给标准初始化,之后才轮到你自己声明的快/慢 MA 对象挂上去。 下面这段是初始化入口的骨架,两个 Create 函数任一返回 false 都会直接中断,EA 不会进入后续逻辑: protected: bool CreateFastMA(CIndicators *indicators); bool CreateSlowMA(CIndicators *indicators); }; bool MA_Cross::InitIndicators(CIndicators *indicators) { if(indicators==NULL) return(false); if(!CExpertSignal::InitIndicators(indicators)) return(false); if(!CreateFastMA(indicators)) return(false); if(!CreateSlowMA(indicators)) return(false); return(true); } 类内部用 CiCustom 承载两个均线实例,并各自保留周期与平滑类型字段。m_period_fast / m_method_fast 与 m_period_slow / m_method_slow 解耦了参数和对象,实盘里改周期不必动创建逻辑。 成员声明对应如下: private: CiCustom m_fast_ma; // 指标作为一个对象 CiCustom m_slow_ma; // 指标作为一个对象 int m_period_fast; // 快速 MA 周期 ENUM_MA_METHOD m_method_fast; // 快速 MA 平滑类型 int m_period_slow; // 慢速 MA 周期 ENUM_MA_METHOD m_method_slow; // 慢速 MA 平滑类型 外汇与贵金属杠杆高,这类信号类只解决「指标就位」,金叉死叉的触发概率仍受滑点与时框切换影响,开 MT5 把 m_period_fast 设成 5、m_period_slow 设成 20 跑回测能看到交叉频次明显多于 10/50 组合。

MQL5 / C++
class="kw">protected:
  class="type">bool CreateFastMA(CIndicators *indicators);
  class="type">bool CreateSlowMA(CIndicators *indicators);
};

class="type">bool MA_Cross::InitIndicators(CIndicators *indicators)
  {
  if(indicators==NULL) class="kw">return(false);
  if(!CExpertSignal::InitIndicators(indicators)) class="kw">return(false);
  if(!CreateFastMA(indicators)) class="kw">return(false);
  if(!CreateSlowMA(indicators)) class="kw">return(false);
  class="kw">return(true);
  }

class="type">bool MA_Cross::CreateFastMA(CIndicators *indicators)
  {
... 一些代码
  class="kw">return(true);
  }

class="type">bool MA_Cross::CreateSlowMA(CIndicators *indicators)
  {
... 一些代码
  class="kw">return(true);
  }

class MA_Cross : class="kw">public CExpertSignal
  {
class="kw">private:
  CiCustom m_fast_ma;     class=class="str">"cmt">// 指标作为一个对象
  CiCustom m_slow_ma;     class=class="str">"cmt">// 指标作为一个对象
  class="type">int m_period_fast;      class=class="str">"cmt">// 快速 MA 周期
  ENUM_MA_METHOD m_method_fast; class=class="str">"cmt">// 快速 MA 平滑类型
  class="type">int m_period_slow;      class=class="str">"cmt">// 慢速 MA 周期
  ENUM_MA_METHOD m_method_slow; class=class="str">"cmt">// 慢速 MA 平滑类型

◍ 把自定义均线塞进信号类里

在 MT5 里做双均线信号,快速 MA 不能靠系统自带的 iMA 硬凑,得用自定义指标 Examples\Custom Moving Average.ex5 挂进 CIndicators 集合。下面这段 CreateFastMA 就是干这事的核心:先判空指针,再把 m_fast_ma 的指针 Add 进去,任何一步失败直接返回 false 并打印函数名方便排查。 参数数组固定开 4 个 MqlParam:0 号放自定义 ex5 路径字符串,1 号放周期(默认类内成员 m_period_fast),2 号平移写 0,3 号放平均方法 m_method_fast。随后 m_fast_ma.Create 用 IND_CUSTOM 类型带 4 个参数初始化,NumBuffers(1) 锁死单缓冲——这意味着你只能取 buffer 0 的数值,别去碰不存在的 buffer 1。 取数接口被缩写成两行内联:FastMA(index) 和 SlowMA(index) 都直接调 GetData(0,index)。回测前记得在 mqh 同目录外的 main 文件加 #property tester_indicator "Examples\\Custom Moving Average.ex5",否则策略测试器加载不了这个自定义指标,EA 在外汇或贵金属品种上跑历史会直接报错。这类品种杠杆高、滑点跳空频繁,自定义均线的缓存失效可能导致信号延迟,实盘前务必用 EURUSD 或 XAUUSD 的 M1 数据先验证一次。

MQL5 / C++
class="type">bool MA_Cross::CreateFastMA(CIndicators *indicators)
  {
class=class="str">"cmt">//--- 检查指针
   if(indicators==NULL) class="kw">return(false);
class=class="str">"cmt">//--- 添加对象至集合
   if(!indicators.Add(GetPointer(m_fast_ma)))
     {
       printf(__FUNCTION__+": 添加快速 MA 对象错误");
       class="kw">return(false);
     }
class=class="str">"cmt">//--- 设置快速 MA 参数
   class="type">MqlParam parameters[class="num">4];
class=class="str">"cmt">//---
   parameters[class="num">0].type=TYPE_STRING;
   parameters[class="num">0].string_value="Examples\\Custom Moving Average.ex5";
   parameters[class="num">1].type=TYPE_INT;
   parameters[class="num">1].integer_value=m_period_fast;       class=class="str">"cmt">// 周期
   parameters[class="num">2].type=TYPE_INT;
   parameters[class="num">2].integer_value=class="num">0;                   class=class="str">"cmt">// 平移
   parameters[class="num">3].type=TYPE_INT;
   parameters[class="num">3].integer_value=m_method_fast;       class=class="str">"cmt">// 平均方法
class=class="str">"cmt">//--- 对象初始化  
   if(!m_fast_ma.Create(m_symbol.Name(),m_period,IND_CUSTOM,class="num">4,parameters))
     {
       printf(__FUNCTION__+": 初始化快速 MA 对象错误");
       class="kw">return(false);
     }
class=class="str">"cmt">//--- 缓存区数量
   if(!m_fast_ma.NumBuffers(class="num">1)) class="kw">return(false);
class=class="str">"cmt">//--- 抵达此处, 函数成功, 返回 true
   class="kw">return(true);
  }
parameters[class="num">0].string_value="Examples\\Custom Moving Average.ex5";
class=class="str">"cmt">//--- 输入参数
input class="type">int             InpMAPeriod=class="num">13;      class=class="str">"cmt">// 周期
input class="type">int             InpMAShift=class="num">0;        class=class="str">"cmt">// 平移
input ENUM_MA_METHOD  InpMAMethod=MODE_SMMA;  class=class="str">"cmt">// 方法
class="macro">#include "..\ExpertSignal.mqh"   class=class="str">"cmt">// CExpertSignal 类包含于文件 ExpertSignal
class="macro">#class="kw">property tester_indicator "Examples\\Custom Moving Average.ex5"
   class=class="str">"cmt">//--- 检查输入数据正确与否
   class="type">bool               ValidationSettings(class="type">void);
   class=class="str">"cmt">//--- 创建信号模型的指标和时间帧
   class="type">bool               InitIndicators(CIndicators *indicators);
   class=class="str">"cmt">//--- 存取指标数据
   class="type">class="kw">double             FastMA(const class="type">int index)            const { class="kw">return(m_fast_ma.GetData(class="num">0,index)); }
   class="type">class="kw">double             SlowMA(const class="type">int index)            const { class="kw">return(m_slow_ma.GetData(class="num">0,index)); }

「把交叉判据塞进 LongCondition 与 ShortCondition」

信号模块真正吐出交易指令,靠的是 CExpertSignal 子类必须覆写的两个虚函数:LongCondition() 与 ShortCondition()。前者回测做多条件并给出 0~100 的强度值,后者对应做空;返回 0 即无信号,非零值越大代表该侧信号越强,方便后续多模块加权组合。 简单模块里我们直接约定:金叉给买入强度 100,死叉给卖出强度 100,不做梯度细分。代码里先用 StartIndex() 拿分析起点 idx——向导默认返回 1,意味着 EA 只在新柱开盘后检查,跳过当前未成形柱的噪音;若改回 0 则每 tick 都判,但易吞假信号。 下面这段是 LongCondition 的核心:取 idx 与 idx+1 两根已成形柱的快速/慢速 MA,若快线由下穿上即为满格买信号。外汇与贵金属杠杆高,这类交叉在震荡市会连续打脸,实盘前务必在 MT5 策略测试器用历史数据跑一遍确认触发频率。 [CODE] 中的关键行拆解: int idx=StartIndex(); 拿分析基准偏移,默认 1。 double last_fast_value=FastMA(idx); 读基准柱快线值。 double prev_fast_value=FastMA(idx+1); 读前一根快线值。 if((last_fast_value>last_slow_value) && (prev_fast_value<prev_slow_value)) 快线从下方穿越慢线。 signal=100; 命中则给最强买入强度。

MQL5 / C++
  ...
   class="type">bool                 InitIndicators(CIndicators *indicators);
  class=class="str">"cmt">//--- 访问指标数据
   class="type">class="kw">double               FastMA(const class="type">int index)       const { class="kw">return(m_fast_ma.GetData(class="num">0,index)); }
   class="type">class="kw">double               SlowMA(const class="type">int index)       const { class="kw">return(m_slow_ma.GetData(class="num">0,index)); }
  class=class="str">"cmt">//--- 检查买、卖条件
  class="kw">virtual class="type">int    LongCondition();
  class="kw">virtual class="type">int    ShortCondition();
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| 返回买入信号强度                                                |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">int MA_Cross::LongCondition()
  {
   class="type">int signal=class="num">0;
class=class="str">"cmt">//--- 操作条件 idx=class="num">0, 及已经建立的 idx=class="num">1
   class="type">int idx=StartIndex();
class=class="str">"cmt">//--- 最后已建立柱线的 MAs 值
   class="type">class="kw">double last_fast_value=FastMA(idx);
   class="type">class="kw">double last_slow_value=SlowMA(idx);
class=class="str">"cmt">//--- 最后已建立柱线的前一根柱线 MAs 值
   class="type">class="kw">double prev_fast_value=FastMA(idx+class="num">1);
   class="type">class="kw">double prev_slow_value=SlowMA(idx+class="num">1);
class=class="str">"cmt">//---如果两根靠拢的柱线的快速 MA 从下往上与慢速 MA 交叉
   if((last_fast_value>last_slow_value) && (prev_fast_value<prev_slow_value))
     {
       signal=class="num">100; class=class="str">"cmt">// 这是买信号
     }
class=class="str">"cmt">//--- 返回信号值
   class="kw">return(signal);
  }

常见问题

把默认周期参数直接写在信号类的构造函数里,初始化时就会自动带上,不用每次在面板重填。
在交叉信号外再套一层参数闸门,比如要求价差或波动阈值达标才放行,能砍掉大部分毛刺信号。
小布可以读取你的信号类结构,自动标出指标初始化链路里断掉或重复调用的地方,省去你一行行查。
在 LongCondition 与 ShortCondition 里分别用前值与新值比较写交叉,注意用缓冲区索引对齐,避免错位漏判。
多在指标句柄未校验就直接取缓冲,或自定义均线句柄传参顺序错;先判空再引用基本就不崩了。