探索标准库的交易策略类 - 自定义策略(基础篇)
📘

探索标准库的交易策略类 - 自定义策略(基础篇)

第 1/3 篇

用向导拼出可交易的策略类

MetaEditor 里的 MQL5 向导能直接生成 .mq5 和 .ex5,新手不必从零写 EA。它依赖标准库里的交易策略类,这些类把指标逻辑封装成“交易模式”,由向导装配进 EA 骨架。 标准库随 MT5 自带一套指标,每个指标至少配了一个策略类,部分源自成熟的行情与盈利分析研究。也就是说,你选个指标就能拿到现成的信号类,不用自己实现算法。 向导的机制是指标以 #include 方式被生成的 EA 调用,策略类输出买卖决策,再汇入 EA 核心执行。想验证的话,打开 MetaEditor 跑一次向导,勾一个内置指标,看生成的代码里是不是自动带进了策略类。外汇与贵金属杠杆高,生成的 EA 仅为技术原型,实盘前务必在策略测试器跑历史数据。

◍ 用向导起一个双信号 EA 骨架

在 MetaEditor 里按 File → New,选 Expert Advisor (generate) 就能调出 MQL5 向导。第一步先把生成出来的 EA 命名为 MyExpert,这是后面所有信号模块挂接的容器。 往向导里加信号时,我们挑了 RSI 和 MA 两个常见指标,顺序先 RSI 后 MA。指标参数可以直接用默认值,不影响后续逻辑验证。 继续点 OK 后那一屏先别开追踪止损;再下一屏把交易百分比设成 5.0、手数填 0.1,这两个值只关乎仓位,不碰本文核心机制。 这套骨架的价值在于:你十分钟内就能在 MT5 里编译出可跑的 EA,立刻用默认参数回测 RSI+MA 双过滤的信号触发频率。外汇与贵金属品种波动剧烈,实盘前务必用策略测试器跑至少三年数据。

「分析生成的代码」

完成后您将得到 "MyExpert.mq5" 文件。我们来分析一下生成代码的要点。 首先要注意的是,#include 文件通过向导添加至生成的代码。我们可以看到: Expert.mqh SignalRSI.mq SignalMA.mqh 然后是代码的下述部分: 正如标题所示,“过滤器”将被应用至附加到图表或在策略测试程序中测试的生成 EA 的市场情形。而 filter0 是索引为 0 的第一个过滤器,对于此过滤器,我们在示例中选择 RSI。 CSignalRSI 表示类信号 RSI。此类用于调用 RSI 指标,并向其应用一些条件以通过使用向导的模式逻辑创建买入或卖出信号。RSI 是我们的第一个过滤器(过滤器编号 0)。 在代码接下来的部分中,有一些过滤器参数,然后是追踪止损部分(我们已选择无追踪),再往后是有关资金管理的代码部分。 接下来是: 此部分属于 Expert.mqh 包含文件。这是关于“EA 交易”操作所需指标的初始化。 生成的 EA 代码的最后一部分是关于取消初始化和其他常见“EA 交易”事件: 实际上,此 EA 使用了两个指标(RSI 和 MA),通过利用“过滤器”和“权重”逻辑的交易类的标准库用于交易决策。您可以在“MQL5 参考”的 交易信号模块 部分找到更多相关信息。但我们的目的是将我们自己的交易策略用作新的过滤器。 因此,针对第一步(使用我们自己的交易策略),我们要对 MyExpert.mq5 稍事修改。首先,我们添加另一个过滤器。它将是 filter2 ,并且我们要将它放置在紧接着 filter1 代码部分的后面。 让我们回到作为过滤器和市场决策核心的 #include 文件。第一个是 #include <Expert\Expert.mqh> 文件。此包含文件反过来包含其他文件: #include "ExpertBase.mqh" #include "ExpertTrade.mqh" #include "ExpertSignal.mqh" #include "ExpertMoney.mqh" #include "ExpertTrailing.mqh" 这些包含文件分别是 EA、交易结构、信号、资金和追踪止损处理的主要结构。我们不打算深入分析这些文件或是对它们进行改动。 我们的目的主要是通过使用来自 MetaTrader 5 标准指标组的现有指标添加我们自己的策略以及添加它们的包含文件。 在 MyExpert.mq5 代码中,我们有在本例中用作交易的市场决策的信号/过滤器的 RSI 和 MA 指标的 #include 文件。此时,让我们添加我们自己的自定义包含文件。为此,我们将使用属于 CCI 指标的修改(“改善”)版信号。 SignalCCIxx.mqh 文件应位于 \MQL5\Include\Expert\Signal\ 文件夹中,并且与向导生成 EA 的可集成性相一致,就像标准库的其他 #include 交易类一样 - 信号文件已经在此文件夹中(SignalRSI.mqh 和 SignalMA.mqh)。 对于此示例,我们将复制原始 CCI 文件,创建另一个名为 CCIxx 的文件并对代码稍事修改,然后将其用作 #include 文件。现在,为简单起见,我们只是使用标准库的 CCI 指标的复制版。 我们需要做的就是将 "\MQL5\Include\Expert\Signal\SignalCCI.mqh" 文件复制到 "\MQL5\Include\Expert\Signal\SignalCCI xx .mqh" 文件。这

EA 事件分流与自定义 CCI 信号挂载

MT5 专家顾问把底层事件统一转交给一个封装对象处理:退出时调 Deinit、每笔报价调 OnTick、成交回执调 OnTrade、定时器触发调 OnTimer。你只要看 ExtExpert 这个实例,就知道整套逻辑不在主文件里散着写,而是被收口到同一处。 下面这段是往信号堆里挂一个自写过滤器 CSignalCCIxx 的实际写法。先 new 一个实例,若返回 NULL 说明内存没分到,直接打印错误、调 Deinit 清理并返回 -4,避免半初始化状态跑单。 挂上之后用三行设参:PeriodCCIxx 定 CCI 周期,Applied 定价格类型,Weight 定该信号在组合里的权重。外汇与贵金属杠杆高,这类多信号加权只是概率倾向,不等于方向锁定。 文件尾部用 #include 把 Expert.mqh 及 RSI、MA、CCIxx 三个信号模块拉进来,其中 SignalCCIxx.mqh 是自行扩展的自定义信号,用来做标准库覆盖不到的入场管理。

MQL5 / C++
class=class="str">"cmt">//| Deinitialization function of the expert                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   ExtExpert.Deinit();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Tick" event handler function                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   ExtExpert.OnTick();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Trade" event handler function                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTrade()
  {
   ExtExpert.OnTrade();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Timer" event handler function                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
   ExtExpert.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Creating filter CSignalCCIxx
   CSignalCCIxx *filter2=new CSignalCCIxx;
   if(filter2==NULL)
     {
      class=class="str">"cmt">//--- failed
      printf(__FUNCTION__+": error creating filter2");
      ExtExpert.Deinit();
      class="kw">return(-class="num">4);
     }
   signal.AddFilter(filter2);
class=class="str">"cmt">//--- Set filter parameters
filter2.PeriodCCIxx(Signal_CCIxx_PeriodCCI);
filter2.Applied(Signal_CCIxx_Applied);
filter2.Weight(Signal_CCIxx_Weight);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include                                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Expert\Expert.mqh>
class=class="str">"cmt">//--- available signals
class="macro">#include <Expert\Signal\SignalRSI.mqh>
class="macro">#include <Expert\Signal\SignalMA.mqh>
class="macro">#include <Expert\Signal\SignalCCIxx.mqh>   class=class="str">"cmt">// This is our own &class="macro">#x27;custom&class="macro">#x27; indicator for custom Signal management of the EA

◍ 给向导EA挂上自改的CCIxx信号

想在 MT5 向导产出的 EA 里塞进自己写的 CCI 逻辑,不必从零造指标。把标准库里的 SignalCCI.mqh 复制成 SignalCCIxx.mqh,就是一条低门槛路子:不会写完整自定义信号的用户,也能靠改现成包含文件拿到专属过滤器。 具体动作很直白。先在 MyExpert.mq5 的输入段把 Signal_RSI_Weight 和 Signal_MA_Weight 从 1.0 改成 0.7 与 0.6,再贴入从 SignalCCI.mqh 抄来的三行 CCIxx 输入变量(仅在原参数名后加 xx 后缀)。类声明 protected 区里原本有 m_pattern 用 0~3 编号四种模式,你加两行 int m_pattern_4、m_pattern_5 就扩出两个自定义模式位。 记得在 CSignalCCIxx.mqh 里按 CTRL+H 把 "CCI" 全替换成 "CCIxx",原文案例里这一替就是 41 处;但文件顶部的 CiCCI 类名不能改,否则包含链报错。编译应零警告零错误,不然就是替换漏了。 模式权重自己定。案例给 m_pattern_4、m_pattern_5 都填 90,但这只是作者随手值,真实策略里必须按你的模型重设,它们直接牵动整 EA 的开平仓倾向。下面这段输入代码就是向导 EA 头部参数骨架,PeriodRSI=8 那行显示默认 RSI 周期,你能在同结构里照抄加 CCIxx 周期参数。 外汇与贵金属自动化改造波动剧烈、风险偏高,文中新增的两个市场模型仅作学习示例,未经验证不应直接上实盘。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Inputs                                                                              |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//--- inputs for expert</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">string</span>                Expert_Title           =<span class="class="type">class="kw">string">"MyExpert"</span>;   <span class="comment">class=class="str">"cmt">// Document name</span>
<span class="keyword">class="type">class="kw">ulong</span>                    Expert_MagicNumber   =<span class="number">class="num">26287</span>;         <span class="comment">class=class="str">"cmt">// </span>
<span class="keyword">class="type">bool</span>                     Expert_EveryTick     =<span class="keyword">class="kw">false</span>;         <span class="comment">class=class="str">"cmt">// </span>
<span class="comment">class=class="str">"cmt">//--- inputs for main signal</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span>                   Signal_ThresholdOpen =<span class="number">class="num">40</span>;            <span class="comment">class=class="str">"cmt">// Signal threshold value to open [class="num">0...class="num">100]</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span>                   Signal_ThresholdClose=<span class="number">class="num">60</span>;            <span class="comment">class=class="str">"cmt">// Signal threshold value to close [class="num">0...class="num">100]</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">double</span>                Signal_PriceLevel     =<span class="number">class="num">0.0</span>;           <span class="comment">class=class="str">"cmt">// Price level to execute a deal</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">double</span>                Signal_StopLevel      =<span class="number">class="num">50.0</span>;          <span class="comment">class=class="str">"cmt">// Stop Loss level(in points)</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">double</span>                Signal_TakeLevel      =<span class="number">class="num">50.0</span>;          <span class="comment">class=class="str">"cmt">// Take Profit level(in points)</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span>                   Signal_Expiration     =<span class="number">class="num">4</span>;             <span class="comment">class=class="str">"cmt">// Expiration of pending orders(in bars)</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span>                   Signal_RSI_PeriodRSI =<span class="number">class="num">8</span>;             <span class="comment">class=class="str">"cmt">// Relative Strength Index(class="num">8,...) Period of calculation</span>

「把 CCI 接进多指标信号类」

在 MT5 里做组合信号,常把 RSI、MA 与 CCI 的权重分开配置。上面这段输入参数里,CCI 的计算周期设为 8、收盘价序列、权重 0.8,比 RSI 的 0.7 和 MA 的 0.6 都高,说明在这一套逻辑里 CCI 短周期反转倾向被赋予了更大发言权。 被高亮的那三行 input 是 CCI 通道专属:Signal_CCIxx_PeriodCCI=8 决定采样窗口,Signal_CCIxx_Applied=PRICE_CLOSE 锁定数据源,Signal_CCIxx_Weight=0.8 控制它在总信号里的占比。改这几个数,等于直接调 CCI 在策略里的脾气。 CSignalCCI 类继承自 CExpertSignal,内部挂了 CiCCI 实例,并预留了四个市场模型权重:方向确认、超买超卖反转、普通背离、双重背离。m_extr_osc[10] 与 m_extr_pr[10] 两个数组分别存振荡器与价格的极值,用来跑背离判定。外汇与贵金属波动大,这类多模型信号只代表概率倾向,实盘前务必在策略测试器里用历史数据验证。

MQL5 / C++
class="kw">input ENUM_APPLIED_PRICE Signal_RSI_Applied   =PRICE_CLOSE; class=class="str">"cmt">// Relative Strength Index(class="num">8,...) Prices series
class="kw">input class="type">class="kw">double                Signal_RSI_Weight     =class="num">0.7;          class=class="str">"cmt">// Relative Strength Index(class="num">8,...) Weight [class="num">0...class="num">1.0]
class="kw">input class="type">int                   Signal_MA_PeriodMA   =class="num">90;            class=class="str">"cmt">// Moving Average(class="num">12,class="num">0,...) Period of averaging
class="kw">input class="type">int                   Signal_MA_Shift      =class="num">0;             class=class="str">"cmt">// Moving Average(class="num">12,class="num">0,...) Time shift
class="kw">input ENUM_MA_METHOD        Signal_MA_Method     =MODE_SMA;      class=class="str">"cmt">// Moving Average(class="num">12,class="num">0,...) Method of averaging
class="kw">input ENUM_APPLIED_PRICE    Signal_MA_Applied    =PRICE_CLOSE;   class=class="str">"cmt">// Moving Average(class="num">12,class="num">0,...) Prices series
class="kw">input class="type">class="kw">double                Signal_MA_Weight     =class="num">0.6;           class=class="str">"cmt">// Moving Average(class="num">12,class="num">0,...) Weight [class="num">0...class="num">1.0]
class="kw">input class="type">int                   Signal_CCIxx_PeriodCCI =class="num">8;            class=class="str">"cmt">// Commodity Channel Index(class="num">8,...) Period of calculation
class="kw">input ENUM_APPLIED_PRICE    Signal_CCIxx_Applied   =PRICE_CLOSE;  class=class="str">"cmt">// Commodity Channel Index(class="num">8,...) Prices series
class="kw">input class="type">class="kw">double                Signal_CCIxx_Weight    =class="num">0.8;          class=class="str">"cmt">// Commodity Channel Index(class="num">8,...) Weight [class="num">0...class="num">1.0]
class CSignalCCI : class="kw">public CExpertSignal
  {
class="kw">protected:
   CiCCI                m_cci;              class=class="str">"cmt">// object-oscillator
   class=class="str">"cmt">//--- adjusted parameters
   class="type">int                  m_periodCCI;        class=class="str">"cmt">// the "period of calculation" parameter of the oscillator
   ENUM_APPLIED_PRICE   m_applied;          class=class="str">"cmt">// the "prices series" parameter of the oscillator
   class=class="str">"cmt">//--- "weights" of market models(class="num">0-class="num">100)
   class="type">int                  m_pattern_0;        class=class="str">"cmt">// model class="num">0 "the oscillator has required direction"
   class="type">int                  m_pattern_1;        class=class="str">"cmt">// model class="num">1 "reverse behind the level of overbuying/overselling"
   class="type">int                  m_pattern_2;        class=class="str">"cmt">// model class="num">2 "divergence of the oscillator and price"
   class="type">int                  m_pattern_3;        class=class="str">"cmt">// model class="num">3 "class="type">class="kw">double divergence of the oscillator and price"
   class=class="str">"cmt">//--- variables
   class="type">class="kw">double               m_extr_osc[class="num">10];     class=class="str">"cmt">// array of values of extremums of the oscillator
   class="type">class="kw">double               m_extr_pr[class="num">10];      class=class="str">"cmt">// array of values of the corresponding extremums of price

常见问题

在向导的多指标信号类里分别添加信号实例并指定周期参数,生成后会在CExpertSignal派生类里自动初始化两个信号指针。
生成的代码用一个CExpert类统一接收事件,再内部调用信号、仓位管理模块的对应方法,你只需改信号类不必动主循环。
可以,小布能读取你品种页加载的EA结构并提示信号类绑定状态,打开对应品种页即可看到诊断结果。
若只改了信号类源码,直接重新编译EA即可;若要在向导里换挂载逻辑才需重新跑向导。
在信号类里用SetWeight分配权重,建议先各设0.5回测,再按品种波动调比例,外汇贵金属高风险需严控仓位。