基于自定义指标的交易信号生成器·进阶篇
🛠️

基于自定义指标的交易信号生成器·进阶篇

(2/3)· 当标配指标不够用时,怎样把自创市场模型写进 CExpertSignal 子类而不踩虚函数坑

含代码示例 第 2/3 篇
很多人直接把指标值塞进 EA 主循环判断买卖,结果信号抖动、回测和实盘对不上。自定义指标若没有独立的信号类封装,后续加模型或换品种就是重写一遍逻辑。本文接上篇,继续把信号生成器拆清楚。

「信封指标信号类的参数骨架」

在 MT5 自带交易信号框架里,基于 Envelopes 指标写信号类,通常从继承 CExpertSignal 开始。下面这段类声明把可调参数和两种市场模型权重都摊开了,方便后续在 EA 里直接赋值。 类里 protected 区首先挂了一个 CiEnvelopes 实例 m_env,这是指标对象本身;紧接着是一组指标参数映射:m_ma_period 对应均线周期,m_ma_shift 是时间偏移,m_ma_method 为平均方法枚举,m_ma_applied 指定收盘价或别的 applied price,m_deviation 默认 0.15 即通道偏离百分比。 两个阈值 m_limit_in 与 m_limit_out 分别控制“回撤区”和“突破区”的灵敏度,不属于指标原生参数,是信号逻辑自己加的过滤层。m_pattern_0 和 m_pattern_1 是 0–100 的模型权重,前者描述价格贴近通道边界,后者描述价格穿越边界。 public 区暴露了 PeriodMA / Shift / Method 等 inline setter,例如 PeriodMA(int value) 仅一行 m_ma_period=value;。实盘外汇或贵金属用这套,杠杆风险高,参数乱填可能频繁假突破。

MQL5 / C++
class CSignalEnvelopes : class="kw">public CExpertSignal
  {
class="kw">protected:
   CiEnvelopes        m_env;            class=class="str">"cmt">// object-indicator
   class=class="str">"cmt">//--- adjusted parameters
   class="type">int                m_ma_period;     class=class="str">"cmt">// the "period of averaging" parameter of the indicator
   class="type">int                m_ma_shift;      class=class="str">"cmt">// the "time shift" parameter of the indicator
   ENUM_MA_METHOD     m_ma_method;     class=class="str">"cmt">// the "method of averaging" parameter of the indicator
   ENUM_APPLIED_PRICE m_ma_applied;    class=class="str">"cmt">// the "object of averaging" parameter of the indicator
   class="type">class="kw">double             m_deviation;     class=class="str">"cmt">// the "deviation" parameter of the indicator
   class="type">class="kw">double             m_limit_in;      class=class="str">"cmt">// threshold sensitivity of the &class="macro">#x27;rollback zone&class="macro">#x27;
   class="type">class="kw">double             m_limit_out;     class=class="str">"cmt">// threshold sensitivity of the &class="macro">#x27;class="kw">break through zone&class="macro">#x27;
   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 "price is near the necessary border of the envelope"
   class="type">int                m_pattern_1;     class=class="str">"cmt">// model class="num">1 "price crossed a border of the envelope"
class="kw">public:
                     CSignalEnvelopes(class="type">void);
                    ~CSignalEnvelopes(class="type">void);
   class=class="str">"cmt">//--- methods of setting adjustable parameters
   class="type">void               PeriodMA(class="type">int value)                { m_ma_period=value;       }
   class="type">void               Shift(class="type">int value)                   { m_ma_shift=value;        }
   class="type">void               Method(ENUM_MA_METHOD value)       { m_ma_method=value;       }

包络线信号的参数入口与默认构造

这套包络线信号类把可调参数都做成了 setter,外部直接传值就能改模型权重与通道宽度。Applied 接 ENUM_APPLIED_PRICE,决定均线算在哪类价格上;Deviation 控制通道偏离百分比,LimitIn / LimitOut 则是入场与出场的距离阈值。 Pattern_0 和 Pattern_1 两个 int 型 setter 用来调市场模型的“权重”,注释里明说是调整模型占比,不改结构只改侧重。虚函数 LongCondition / ShortCondition 留给具体形态判定,InitMA 在 protected 里包了均线初始化,Upper / Lower 直接返回通道上下轨。 构造函数给出了冷启动默认值:均线周期 45、偏移 0、方法 MODE_SMA、应用价 PRICE_CLOSE。你在 MT5 里 new 一个 CSignalEnvelopes 不传参,就会先跑这套 45 周期 SMA 包络,偏离度由 Deviation 后续设定,外汇与贵金属波动大,默认参数直接上实盘风险偏高,建议先回测调 deviation。

MQL5 / C++
class="type">void Applied(ENUM_APPLIED_PRICE value) { m_ma_applied=value; }
class="type">void Deviation(class="type">class="kw">double value) { m_deviation=value; }
class="type">void LimitIn(class="type">class="kw">double value) { m_limit_in=value; }
class="type">void LimitOut(class="type">class="kw">double value) { m_limit_out=value; }
class=class="str">"cmt">//--- methods of adjusting "weights" of market models
class="type">void Pattern_0(class="type">int value) { m_pattern_0=value; }
class="type">void Pattern_1(class="type">int value) { m_pattern_1=value; }
class=class="str">"cmt">//--- method of verification of settings
class="kw">virtual class="type">bool ValidationSettings(class="type">void);
class=class="str">"cmt">//--- method of creating the indicator and time series
class="kw">virtual class="type">bool InitIndicators(CIndicators *indicators);
class=class="str">"cmt">//--- methods of checking if the market models are formed
class="kw">virtual class="type">int LongCondition(class="type">void);
class="kw">virtual class="type">int ShortCondition(class="type">void);
class="kw">protected:
class=class="str">"cmt">//--- method of initialization of the indicator
class="type">bool InitMA(CIndicators *indicators);
class=class="str">"cmt">//--- methods of getting data
class="type">class="kw">double Upper(class="type">int ind) { class="kw">return(m_env.Upper(ind)); }
class="type">class="kw">double Lower(class="type">int ind) { class="kw">return(m_env.Lower(ind)); }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor |
class=class="str">"cmt">//+------------------------------------------------------------------+
CSignalEnvelopes::CSignalEnvelopes(class="type">void) : m_ma_period(class="num">45),
 m_ma_shift(class="num">0),
 m_ma_method(MODE_SMA),
 m_ma_applied(PRICE_CLOSE),

◍ 包络线信号类的初始化与校验骨架

在自定义信号类 CSignalEnvelopes 的构造函数里,先用初始化列表把几个核心参数钉死:m_deviation 设 0.15、m_limit_in 与 m_limit_out 都是 0.2,形态阈值 m_pattern_0 给 90、m_pattern_1 给 70。这些数值直接决定后续信号触发的宽松度,在 MT5 里改它们就能调敏感程度。 构造函数体内只做一件事——把开高低收四组序列全部标记为受保护数据(m_used_series 按位加总 USE_SERIES_OPEN/HIGH/LOW/CLOSE),确保基类能正确缓存历史。析构函数为空,说明该类没有堆上资源需要手动释放。 ValidationSettings 是实盘前必过的关卡:先调基类校验,再卡 m_ma_period 必须 >0,否则 printf 报错并返回 false。InitIndicators 则负责把指标挂进指示器容器,其中 InitMA 单独抽出来建均线——指针为空或任一环节失败就直接返回 false,避免信号对象带病运行。外汇与贵金属波动剧烈,这类参数若设错可能放大滑点风险。

MQL5 / C++
 m_deviation(class="num">0.15),
 m_limit_in(class="num">0.2),
 m_limit_out(class="num">0.2),
 m_pattern_0(class="num">90),
 m_pattern_1(class="num">70)
 {
class=class="str">"cmt">//--- initialization of class="kw">protected data
 m_used_series=USE_SERIES_OPEN+USE_SERIES_HIGH+USE_SERIES_LOW+USE_SERIES_CLOSE;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
CSignalEnvelopes::~CSignalEnvelopes(class="type">void)
 {
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Validation settings class="kw">protected data.                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalEnvelopes::ValidationSettings(class="type">void)
 {
class=class="str">"cmt">//--- validation settings of additional filters
 if(!CExpertSignal::ValidationSettings())
 class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initial data checks
 if(m_ma_period<=class="num">0)
  {
   printf(__FUNCTION__+": period MA must be greater than class="num">0");
   class="kw">return(class="kw">false);
  }
class=class="str">"cmt">//--- ok
 class="kw">return(true);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create indicators.                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalEnvelopes::InitIndicators(CIndicators *indicators)
 {
class=class="str">"cmt">//--- check pointer
 if(indicators==NULL)
 class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initialization of indicators and time series of additional filters
 if(!CExpertSignal::InitIndicators(indicators))
 class="kw">return(class="kw">false);
class=class="str">"cmt">//--- create and initialize MA indicator
 if(!InitMA(indicators))
 class="kw">return(class="kw">false);
class=class="str">"cmt">//--- ok
 class="kw">return(true);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialize MA indicators.                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalEnvelopes::InitMA(CIndicators *indicators)
 {
class=class="str">"cmt">//--- check pointer
 if(indicators==NULL)

「信封指标的多空投票逻辑」

在 MT5 自带的 CSignalEnvelopes 类里,多空信号不是直接发单,而是用 LongCondition / ShortCondition 两个方法做「投票」:返回 0 表示不投票,返回 m_pattern_x 表示按对应模式投多或投空。 初始化阶段先把指标对象塞进集合并调用 Create,参数依次是品种名、周期、均线周期、偏移、均线算法、应用价格和通道偏离。任一步返回 false 都会打印错误并中止,这在排查 EA 加载失败时很实用。 多头判定分两种模式:模式 0 要求收盘价落在下轨附近的回撤区(lower + m_limit_in*width 到 lower - m_limit_out*width 之间);模式 1 则要求收盘价突破上轨外侧(close > upper + m_limit_out*width)。空头对称处理,模式 0 看上轨回撤区,模式 1 看下轨外侧。 width = upper - lower 是通道宽度,m_limit_in / m_limit_out 是相对宽度的比例系数。把这两个值从默认 0.1 / 0.2 调到 0.05 / 0.35,回测欧美 1H 会明显改变触发频率,外汇与贵金属杠杆品种须警惕假突破带来的高风险。

MQL5 / C++
   class="kw">return(class="kw">false);
class=class="str">"cmt">//--- add object to collection
   if(!indicators.Add(GetPointer(m_env)))
     {
      printf(__FUNCTION__+": error adding object");
      class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- initialize object
   if(!m_env.Create(m_symbol.Name(),m_period,m_ma_period,m_ma_shift,m_ma_method,m_ma_applied,m_deviation))
     {
      printf(__FUNCTION__+": error initializing object");
      class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- ok
   class="kw">return(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Voting" that the price will grow.                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalEnvelopes::LongCondition(class="type">void)
  {
   class="type">int result=class="num">0;
   class="type">int idx   =StartIndex();
   class="type">class="kw">double close=Close(idx);
   class="type">class="kw">double upper=Upper(idx);
   class="type">class="kw">double lower=Lower(idx);
   class="type">class="kw">double width=upper-lower;
class=class="str">"cmt">//--- if the model class="num">0 is used and price is in the rollback zone, then there is a condition for buying
   if(IS_PATTERN_USAGE(class="num">0) && close<lower+m_limit_in*width && close>lower-m_limit_out*width)
      result=m_pattern_0;
class=class="str">"cmt">//--- if the model class="num">1 is used and price is above the rollback zone, then there is a condition for buying
   if(IS_PATTERN_USAGE(class="num">1) && close>upper+m_limit_out*width)
      result=m_pattern_1;
class=class="str">"cmt">//--- class="kw">return the result
   class="kw">return(result);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Voting" that the price will fall.                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalEnvelopes::ShortCondition(class="type">void)
  {
   class="type">int result  =class="num">0;
   class="type">int idx     =StartIndex();
   class="type">class="kw">double close=Close(idx);
   class="type">class="kw">double upper=Upper(idx);
   class="type">class="kw">double lower=Lower(idx);
   class="type">class="kw">double width=upper-lower;
class=class="str">"cmt">//--- if the model class="num">0 is used and price is in the rollback zone, then there is a condition for selling
   if(IS_PATTERN_USAGE(class="num">0) && close>upper-m_limit_in*width && close<upper+m_limit_out*width)
      result=m_pattern_0;
class=class="str">"cmt">//--- if the model class="num">1 is used and price is above the rollback zone, then there is a condition for selling
   if(IS_PATTERN_USAGE(class="num">1) && close<lower-m_limit_out*width)
      result=m_pattern_1;
class=class="str">"cmt">//--- class="kw">return the result
   class="kw">return(result);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Expert\ExpertSignal.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Description of the class                                          |
class=class="str">"cmt">//| Title=Signals of indicator &class="macro">#x27;Envelopes&class="macro">#x27;                            |
class=class="str">"cmt">//| Type=SignalAdvanced                                               |

把包络线信号挂进自定义指标类

在 MT5 信号模块里,Envelopes 指标以 CSignalEnvelopes 类封装,默认参数里周期均线取 45、偏移 0、SMA 平滑、收盘价计算、通道偏离 0.15%。这组配置在 EURUSD 的 H1 上回测,通道外触碰后价格回归中轨的概率倾向较高,但外汇品种受消息面扰动,仍属高风险。 代码段里用注释块声明了信号元数据:Name 与 ShortName 都填 Envelopes,Class 指向 CSignalEnvelopes,Page 映射 signal_envelopes。黄色高亮行展示了一套并行的自定义指标写法,把 Name 换成 MyCustomIndicator、Class 换成 CSignalMyCustInd,即可在同框架内挂载你自己的指标逻辑。 下方 input 参数给出了快慢 EMA 的周期入口:InpFastEMA 默认 12、InpSlowEMA 默认 26。直接改这两个数字就能切换均线组合,比如改 5 和 35 去抓更敏感的通道突破。开 MT5 把这段贴进信号源文件,编译后就能在策略测试器里验证偏离值对胜率的影响。

MQL5 / C++
class=class="str">"cmt">//| Name=Envelopes                                                                  |
class=class="str">"cmt">//| ShortName=Envelopes                                                              |
class=class="str">"cmt">//| Class=CSignalEnvelopes                                                           |
class=class="str">"cmt">//| Page=signal_envelopes                                                            |
class=class="str">"cmt">//| Parameter=PeriodMA,class="type">int,class="num">45,Period of averaging                                    |
class=class="str">"cmt">//| Parameter=Shift,class="type">int,class="num">0,Time shift                                                 |
class=class="str">"cmt">//| Parameter=Method,ENUM_MA_METHOD,MODE_SMA,Method of averaging                     |
class=class="str">"cmt">//| Parameter=Applied,ENUM_APPLIED_PRICE,PRICE_CLOSE,Prices series                   |
class=class="str">"cmt">//| Parameter=Deviation,class="type">class="kw">double,class="num">0.15,Deviation                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Title=Signals of indicator &class="macro">#x27;Envelopes&class="macro">#x27;                                            |
class=class="str">"cmt">//| Title=Signal of the &class="macro">#x27;Custom Indicator&class="macro">#x27; indicator                                  |
class=class="str">"cmt">//| Name=Envelopes                                                                   |
class=class="str">"cmt">//| Name=MyCustomIndicator                                                           |
class=class="str">"cmt">//| ShortName=Envelopes                                                              |
class=class="str">"cmt">//| ShortName=MyCustomIndicator                                                      |
class=class="str">"cmt">//| Class=CSignalEnvelopes                                                           |
class=class="str">"cmt">//| Class=CSignalMyCustInd                                                           |
class=class="str">"cmt">//| Page=signal_envelopes                                                            |
class=class="str">"cmt">//--- input parameters
input class="type">int               InpFastEMA=class="num">12;      class=class="str">"cmt">// Fast EMA period
input class="type">int               InpSlowEMA=class="num">26;      class=class="str">"cmt">// Slow EMA period

◍ 把自定义信号类挂进 EA 的参数写法

想在 MT5 的 EA 框架里接一个自定义指标信号,先得把周期和价格源参数暴露成 input,让策略测试器能直接调。下面这段声明里,信号 SMA 周期默认 9,应用价默认 PRICE_CLOSE,属于最外层可调入口。 原文里还夹了一组被高亮的中间注释,实际是给信号类 CSignalMyCustInd 写的元数据:快线 EMA 周期 12、慢线 EMA 周期 24、差值平均周期 9,应用价同样是收盘价。这四个值若写进信号工厂,就决定信号滤波的快慢节奏。 外汇与贵金属杠杆高,这类多周期 EMA 差分信号在震荡市可能频繁假突破,上实盘前务必在 MTK 用 2020–2023 年 XAUUSD 的 M15 跑一遍样本外验证。

MQL5 / C++
input class="type">int                InpSignalSMA=class="num">9;                class=class="str">"cmt">// Signal SMA period
input ENUM_APPLIED_PRICE  InpAppliedPrice=PRICE_CLOSE; class=class="str">"cmt">// Applied price
class=class="str">"cmt">//| Parameter=PeriodFast,class="type">int,class="num">12,Period of fast EMA                  |
class=class="str">"cmt">//| Parameter=PeriodSlow,class="type">int,class="num">24,Period of slow EMA                 |
class=class="str">"cmt">//| Parameter=PeriodSignal,class="type">int,class="num">9,Period of averaging of difference  |
class=class="str">"cmt">//| Parameter=Applied,ENUM_APPLIED_PRICE,PRICE_CLOSE,Prices series  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Description of the class                                          |
class=class="str">"cmt">//| Title=Signal of the &class="macro">#x27;Custom Indicator&class="macro">#x27; indicator                 |
class=class="str">"cmt">//| Type=SignalAdvanced                                               |
class=class="str">"cmt">//| Name=MyCustomIndicator                                            |
class=class="str">"cmt">//| ShortName=MyCustomIndicator                                       |
class=class="str">"cmt">//| Class=CSignalMyCustInd                                            |
class=class="str">"cmt">//| Page=signal_envelopes                                             |
class=class="str">"cmt">//| Parameter=PeriodFast,class="type">int,class="num">12,Period of fast EMA                    |
class=class="str">"cmt">//| Parameter=PeriodSlow,class="type">int,class="num">24,Period of slow EMA                    |
class=class="str">"cmt">//| Parameter=PeriodSignal,class="type">int,class="num">9,Period of averaging of difference    |
class=class="str">"cmt">//| Parameter=Applied,ENUM_APPLIED_PRICE,PRICE_CLOSE,Prices series    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class CSignalEnvelopes.                                            |
class=class="str">"cmt">//| Purpose: Class of generator of trade signals based on             |
class=class="str">"cmt">//|    the &class="macro">#x27;Envelopes&class="macro">#x27; indicator.                                     |
class=class="str">"cmt">//| It is derived from the CExpertSignal class.                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class CSignalMyCustInd.                                            |
class=class="str">"cmt">//| Purpose: Class of the trading signal generator based on           |
把指标诊断交给小布盯盘
这些 MACD 零线上下递增递减的模型状态,小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时标注,你只管决定是否采信信号。

常见问题

不重写相关虚方法时,子类沿用基类空实现,EA 拿不到方向指示,相当于信号生成器失效,编译不报错但实盘无信号。
标准库目录混放会干扰向导与后续升级,新建 MySignals 子文件夹可隔离自有逻辑,也方便团队复用和版本区分。
目前内置覆盖标准 MACD 零线穿越与增减态,自写模型需手动配置规则,外汇贵金属波动剧烈,任何模型都只是概率参考。
它让信号模块声明自身不依赖标准模式枚举,避免向导强制绑定预设模型,适合挂自定义描述如低于零线且值递增。
不是。指标模型是指标值与价格的特定组合描述,交易模型决定据此建仓方向,本文新模型即把前者映射为买卖持仓倾向。