通用EA交易:与MetaTrader的标准信号模块集成 (第7部分)(基础篇)
让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 会让信号在实盘里静默失效,概率不低。
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 策略测试器里跑一遍。
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, 就搜索主线的反转 if(IS_PATTERN_USAGE(class="num">1) && DiffMain(idx+class="num">1)<class="num">0.0) result=m_pattern_1; class=class="str">"cmt">// 信号编号 class="num">1 class=class="str">"cmt">//--- 如果使用了模式2,就搜索主线与信号线的交叉 if(IS_PATTERN_USAGE(class="num">2) && State(idx)>class="num">0.0 && State(idx+class="num">1)<class="num">0.0) result=m_pattern_2; class=class="str">"cmt">// 信号编号 class="num">2 class=class="str">"cmt">//--- 如果使用了模式3,就搜索主线与零点线的交叉 if(IS_PATTERN_USAGE(class="num">3) && Main(idx)>class="num">0.0 && Main(idx+class="num">1)<class="num">0.0) result=m_pattern_3; class=class="str">"cmt">// 信号编号3 class=class="str">"cmt">//--- 如果使用了模式4和5,并且主线低于零点线水平,并且在上升,就搜索背离 if((IS_PATTERN_USAGE(class="num">4) || IS_PATTERN_USAGE(class="num">5)) && Main(idx)<class="num">0.0) { class=class="str">"cmt">//--- 进一步分析震荡指标状态 ExtState(idx); class=class="str">"cmt">//--- 如果使用了模式4,就等待背离信号 if(IS_PATTERN_USAGE(class="num">4) && CompareMaps(class="num">1,class="num">1)) class=class="str">"cmt">// class="num">0000 0001b result=m_pattern_4; class=class="str">"cmt">// 信号编号 class="num">4 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 这个阈值敏感跳变,再回退到实盘掩码。
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)