通用EA交易:与MetaTrader的标准信号模块集成 (第7部分)(基础篇)
📘

通用EA交易:与MetaTrader的标准信号模块集成 (第7部分)(基础篇)

第 1/3 篇

让EA直接吃进MT5信号模块

MT5 客户端自带标准信号模块(Signals),普通用户是在终端里订阅跟单,而通用 EA 可以把这套模块当接口直接调用,免去自己写订单路由。2016-07-28 那篇示例文在发布后拿到 7074 次浏览、27 条评论,说明集成需求一直有真实受众。 对交易者来说,最实用的落点是:写 EA 时不必重造轮子去处理信号订阅、复制比、滑点容差,直接用标准库的 CSignal 系列类。这样你只需关心自己的进出场逻辑,跟单通道交给终端内核。 外汇与贵金属杠杆高、点差跳空频繁,信号延迟或复制失败可能造成额外滑点亏损,接模块前先在策略测试器用历史数据跑一遍复制逻辑。

◍ 先看清这套策略生成器的骨架

上手 MQL5 策略生成器之前,先把它的目录结构印在脑子里:从简介、类概览,到信号模块与「模式」概念,再到第一次调用信号、基于 CSignalMACD 写第一个策略,以及信号适配器和多信号组合。这条路径不是文档装饰,而是你后面少走弯路的地图。 整本书 6 节里,第 2 节给出的是导航而非细节。真正动手时,你会反复回到「信号模块概览」和「组合多个信号」这两块——前者决定你懂不懂模式切换,后者决定策略是不是单腿走路。 外汇与贵金属市场高杠杆、高波动,用这套框架跑出来的任何信号都只是概率倾向,开 MT5 前先确认自己能承受回撤。

「CStrategy 为什么缺了信号生成这块」

前面几篇里搭出来的 CStrategy 引擎,在半年多的迭代里陆续塞进了技术性下单、执行保护等模块,交易动作这块已经比较稳。但它一直有个硬缺口:只管‘怎么下单’,不管‘什么时候该下单’。 底层看,CStrategy 是标准面向对象结构,交易执行走的是 MT5 自带的 CTrade 类,这部分没毛病。问题出在信号端——它没有任何负责生成交易信号的类或模块,信号数据库完全是空白。 后果很直接:哪怕你想要的信号在 MetaTrader 5 标准包里现成躺着,旧版也逼着你把 EA 逻辑从头写一遍。新版本要补的,就是让 CStrategy 能直接操作 MT5 信号数据库,把标准信号模块接进来。 接下来这篇就拆清楚集成方式,你照着能把现成算法改成自己的策略,开 MT5 就能跑。

MT5 策略生成器背后的类架构与初始化链

MT5 自带的标准交易类都躺在 MQL5\Include 的子目录 mqh 里,向导生成 EA 时直接调用。它们大致分六层:数据基类(CObject、CArrayDouble 等)打底,指标缓冲区类(CDoubleBuffer、CIndicatorBuffer)管指标数据,CSeries 派生的时间序列类承接报价,CBaseExpert 和 CExpert 是 EA 主体,CExpertSignal 派生出各类信号模块,CExpertTrade 基于 CTrade 执行下单。 图里用绿色点线标出的 CExpertSignal 一族最值得盯——信号模块靠指标类出买卖信号,而横向还『包含』指标缓冲区和时间序列,不是单纯继承。 想自己手写这些类的人会撞上初始化墙:建一个 CSignalMacd 对象,得先建交易品种对象、初始化指标、再把开高低收四个时间序列塞进去。下面这段构造函数就是最小可用链条,复制进 MT5 能直接编译看行为。 别以为向导帮你省了事就真无成本 向导用户确实点几下就出 EA,但当你要改信号逻辑或挂自定义资金模块,就必须手动跑完这条 init 链。外汇和贵金属杠杆高,没跑顺的 init 会让信号在实盘里静默失效,概率不低。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CSignalMacd 信号模块的初始化                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CStrategyMACD::CStrategyMACD(class="type">void)
{
   CSymbolInfo* info = new CSymbolInfo();                class=class="str">"cmt">// 创建一个代表此策略交易品种的对象
   info.Name(Symbol());                                  class=class="str">"cmt">// 初始化代表策略中交易品种的对象
   m_signal_ma.Init(info, Period(), class="num">10);                 class=class="str">"cmt">// 根据交易品种和时段初始化信号模块
   m_signal_ma.InitIndicators(GetPointer(m_indicators)); class=class="str">"cmt">// 在空白的指标列表 m_indicators 基础上创建信号模块中所需的指标
   m_signal_ma.EveryTick(true);                          class=class="str">"cmt">// 测试模式
   m_signal_ma.Magic(ExpertMagic());                     class=class="str">"cmt">// 幻数
   m_signal_ma.PatternsUsage(class="num">8);                         class=class="str">"cmt">// 模式的掩码
   m_open.Create(Symbol(), Period());                    class=class="str">"cmt">// 初始化开盘价的时间序列
   m_high.Create(Symbol(), Period());                    class=class="str">"cmt">// 初始化最高价的时间序列
   m_low.Create(Symbol(), Period());                     class=class="str">"cmt">// 初始化最低价的时间序列
   m_close.Create(Symbol(), Period());                   class=class="str">"cmt">// 初始化收盘价的时间序列
   m_signal_ma.SetPriceSeries(GetPointer(m_open),        class=class="str">"cmt">// 初始化信号模块的时间序列对象
                              GetPointer(m_high),
                              GetPointer(m_low),
                              GetPointer(m_close));
}

◍ 信号类里的模式与强度权重

MT5 里每个标准信号模块都继承自 CExpertSignal,本质是一个类,内部去搜一个或多个「模式」——也就是判定该不该进场的逻辑条件。比如快均线从下上穿慢均线,就是一个买入模式;MACD 里主线与信号线交叉、与零轴交叉、背离、双重背离,各自都是独立模式。买入和卖出模式通常互为反向条件,不会混用。 以 MACD 模块为例,它内置了 5 个买入模式和 5 个卖出模式:反转、主线信号线交叉、零轴交叉、背离、双重背离。一般信号模块平均带 3 组买 + 3 组卖模式;单个方向最多塞 32 个模式,因为背后用的是一个整型变量的位掩码来记录启用了哪些模式。 信号不光认模式,还会回一个整数当「建议强度」——数越大倾向越强。用 Pattern_x(int value) 就能给第 x 个模式单独设权重。下面这段初始化把所有模式权重清零,只给 Pattern_3(零轴交叉)设了 100,意味着这个 CSignalMacd 实例只在触发零轴交叉时才返回非零值,其余模式直接跳过。 推荐的买卖是分开走的:LongCondition 回买入建议,ShortCondition 回卖出建议。LongCondition 里靠 IS_PATTERN_USAGE 宏去查位掩码,命中且条件满足,就返回你之前用 Pattern_x 设的权重;多个模式同亮时不会中断排查,最终强度等于顺次确定的那个权重。想用哪几个模式,就把对应位改成 1:只用编号 3(从 0 计,即第 4 位)就传 8,编号 3 加编号 2 同开就传 12。外汇和贵金属波动剧烈,这种信号强度只是概率参考,实盘前务必在 MT5 策略测试器里跑一遍。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CSignalMacd 信号模块的初始化                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CStrategyMACD::CStrategyMACD(class="type">void)
{
   m_signal_ma.Pattern_0(class="num">0);
   m_signal_ma.Pattern_1(class="num">0);
   m_signal_ma.Pattern_2(class="num">0);
   m_signal_ma.Pattern_3(class="num">100);
   m_signal_ma.Pattern_4(class="num">0);
   m_signal_ma.Pattern_5(class="num">0);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 价格将要增长的指示                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalMACD::LongCondition(class="type">void)
  {
   class="type">int result=class="num">0;
   class="type">int idx   =StartIndex();
class=class="str">"cmt">//--- 检查主线的方向
   class="type">class="kw">double diff = DiffMain(idx);
   if(diff>class="num">0.0)
     {
      class=class="str">"cmt">//--- 主线方向向上,这是价格可能上升的确认
      if(IS_PATTERN_USAGE(class="num">0))
         result=m_pattern_0;      class=class="str">"cmt">// 信号确认编号 class="num">0
      class=class="str">"cmt">//--- 如果使用了模式 class="num">1, 就搜索主线的反转
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(IS_PATTERN_USAGE(class="num">1) &amp;&amp; DiffMain(idx+class="num">1)<class="num">0.0)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result=m_pattern_1;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class=class="str">"cmt">// 信号编号 class="num">1
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class=class="str">"cmt">//--- 如果使用了模式2,就搜索主线与信号线的交叉
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(IS_PATTERN_USAGE(class="num">2) &amp;&amp; State(idx)>class="num">0.0 &amp;&amp; State(idx+class="num">1)<class="num">0.0)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result=m_pattern_2;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class=class="str">"cmt">// 信号编号 class="num">2
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class=class="str">"cmt">//--- 如果使用了模式3,就搜索主线与零点线的交叉
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(IS_PATTERN_USAGE(class="num">3) &amp;&amp; Main(idx)>class="num">0.0 &amp;&amp; Main(idx+class="num">1)<class="num">0.0)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result=m_pattern_3;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class=class="str">"cmt">// 信号编号3
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class=class="str">"cmt">//--- 如果使用了模式4和5,并且主线低于零点线水平,并且在上升,就搜索背离
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if((IS_PATTERN_USAGE(class="num">4) || IS_PATTERN_USAGE(class="num">5)) &amp;&amp; Main(idx)<class="num">0.0)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class=class="str">"cmt">//--- 进一步分析震荡指标状态 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ExtState(idx);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class=class="str">"cmt">//--- 如果使用了模式4,就等待背离信号 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(IS_PATTERN_USAGE(class="num">4) &amp;&amp; CompareMaps(class="num">1,class="num">1)) class=class="str">"cmt">// class="num">0000 0001b
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result=m_pattern_4;&nbsp;&nbsp; class=class="str">"cmt">// 信号编号 class="num">4
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class=class="str">"cmt">//--- 如果使用了模式5,就等待双重背离信号 

「把模式位掩码拆开看信号命中」

这段收尾逻辑直接决定前面算出的形态到底能不能往外吐信号。它用位运算做开关,比一堆 bool 变量干净,但也更容易在调参时改错一位就全盘静默。 核心就一句:当第 5 号模式被启用,且 CompareMaps 拿 0x11 和 2 比出真值,函数立刻返回 m_pattern_5,也就是信号编号 5。0x11 即二进制 0001 0001,说明它同时盯了两个特征位,漏掉任意一个都不会进这个分支。 末尾的宏 IS_PATTERN_USAGE(p) 把 m_patterns_usage 右移 p 位再与 1 判非零,等于问“第 p 个模式位是不是 1”。在 MT5 里把 m_patterns_usage 打印成十六进制,你能直接看到当前开了哪些模式;外汇与贵金属波动受事件驱动,这种模式开关误用可能导致信号在非预期时段触发,属高风险操作,验证前先开模拟盘。 想自己抓虫,就把 m_patterns_usage 临时写成 0xFF 跑一天,看信号 5 出现的频率是否随 CompareMaps 的 2 这个阈值敏感跳变,再回退到实盘掩码。

MQL5 / C++
if(IS_PATTERN_USAGE(class="num">5) && CompareMaps(0x11,class="num">2)) class=class="str">"cmt">// class="num">0001 0001b
      class="kw">return(m_pattern_5);  class=class="str">"cmt">//信号编号5
   }
  }
class=class="str">"cmt">//--- 返回结果
  class="kw">return(result);
 }
class=class="str">"cmt">//--- 检查是否使用了市场模式
class="macro">#define IS_PATTERN_USAGE(p)                ((m_patterns_usage&(((class="type">int)class="num">1)<<p))!=class="num">0)

常见问题

在EA初始化时调用策略生成器骨架,把标准信号模块作为子组件挂进CStrategy,EA就能直接读取模块输出的信号,不用自己写生成逻辑。
CStrategy本身只管策略调度,信号生成被拆到了独立的信号类里,需要顺着类架构初始化链去找对应的信号子类。
可以,小布能按你当前品种把信号类的模式位掩码拆开,标出哪些位命中、强度权重怎么算,省去你手动翻类架构。
把掩码按位拆开,每一位对应一种预设模式,位为1表示该模式命中,再乘强度权重得到综合信号值。
会,初始化链里信号类须在策略调度前完成注册,否则EA可能读到空信号,建议先跟一遍初始化顺序再改参数。