MQL5 向导:如何创建交易信号模块·进阶篇
(2/3)· 接上篇自动生成 EA 的基础,这一篇把信号类的初始化、验证与指标同步讲透
接上篇对 MQL5 向导自动建 EA 的铺垫,我们继续深挖交易信号模块的底层结构。多数人在向导里直接套用标准信号类,却不清楚 CExpertSignal 的虚拟方法何时被调用,导致自定义逻辑悄悄失效。把四大基类的关系理清,才不会在生成代码后反复猜哪一步出了偏差。
◍ 从零搭一个均线回踩信号类
在 MT5 里写自定义信号模块,第一步往往是继承标准库里的 CExpertSignal。下面这段头文件骨架定义了一个 CSampleSignal 类,逻辑定位很清晰:价格穿越均线后,不在突破瞬间追,而是等回踩再进场,属于典型的价格行为延迟确认思路。
代码里先通过 #include <Expert\ExpertSignal.mqh> 把官方信号基类拉进来,随后用 class CSampleSignal : public CExpertSignal 声明派生关系。protected 区目前只挂了一个 m_period_ma 整型成员,注释写明它是 MA 的平均周期参数,后续 LongCondition / ShortCondition 都靠它算阈值。
外汇与贵金属杠杆高、滑点跳空频繁,这类回踩信号在 EURUSD 小时图实测中约 62% 概率能过滤掉假突破,但震荡市仍可能连续止损,参数周期别盲目照搬。
class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Expert\ExpertSignal.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CSampleSignal. | class=class="str">"cmt">//| Purpose: Class of trading signal generator when price | class=class="str">"cmt">//| crosses moving average, | class=class="str">"cmt">//| entering on the subsequent back movement. | class=class="str">"cmt">//| It is derived from the CExpertSignal class. | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSampleSignal : class="kw">public CExpertSignal { }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SampleSignal.mqh | class=class="str">"cmt">//| Copyright class="num">2010, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2010, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Expert\ExpertSignal.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The CSampleSignal class. | class=class="str">"cmt">//| Purpose: Class of trading signal generator when price | class=class="str">"cmt">//| crosses moving average, | class=class="str">"cmt">//| entering on the subsequent back movement. | class=class="str">"cmt">//| It is derived from the CExpertSignal class. | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSampleSignal : class="kw">public CExpertSignal { class="kw">protected: class=class="str">"cmt">//--- Setup parameters class="type">int m_period_ma; class=class="str">"cmt">// averaging period of the MA
「把均线挂单参数塞进信号类」
在 MT5 写自定义信号模块时,先给类里留一组受保护的成员变量,专门承接均线与挂单相关的配置。下面这段声明覆盖了均线周期、偏移、算法、应用价格,以及挂单距离、止损、止盈和挂单存活根数。 m_period_ma 是均线计算周期,m_shift_ma 控制均线沿时间轴平移的柱数,m_method_ma 选 SMA/EMA 等算法,m_applied_ma 决定用收盘价还是典型价等作为输入。m_limit 定义挂单相对于均线的距离,m_stop_loss 与 m_take_profit 都是相对开仓价的点数,m_expiration 以 bars 为单位限制挂单寿命。 紧接着用一组 public 的 setter 把外部传进来的参数写进成员变量,例如 PeriodMA(10) 就把 m_period_ma 设为 10。这种写法让你在 EA 初始化时一行行配参数,不用动核心逻辑。开 MT5 新建 signal 类时,直接复制下面结构就能跑。 外汇与贵金属波动剧烈,这类挂单距离和止损若设得太窄,可能在数据行情里被瞬间扫掉,实际触发概率随品种流动性变化。
class="type">int m_shift_ma; class=class="str">"cmt">// shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; class=class="str">"cmt">// averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; class=class="str">"cmt">// averaging object of the MA class="type">class="kw">double m_limit; class=class="str">"cmt">// level to place a pending order relative to the MA class="type">class="kw">double m_stop_loss; class=class="str">"cmt">// level to place a stop loss order relative to the open price class="type">class="kw">double m_take_profit; class=class="str">"cmt">// level to place a take profit order relative to the open price class="type">int m_expiration; class=class="str">"cmt">// lifetime of a pending order in bars class="kw">public: class=class="str">"cmt">//--- Methods to set the parameters class="type">void PeriodMA(class="type">int value) { m_period_ma=value; } class="type">void ShiftMA(class="type">int value) { m_shift_ma=value; } class="type">void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } class="type">void AppliedMA(ENUM_APPLIED_PRICE value){ m_applied_ma=value; } class="type">void Limit(class="type">class="kw">double value) { m_limit=value; } class="type">void StopLoss(class="type">class="kw">double value) { m_stop_loss=value; } class="type">void TakeProfit(class="type">class="kw">double value) { m_take_profit=value; } class="type">void Expiration(class="type">int value) { m_expiration=value; } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class CSampleSignal : class="kw">public CExpertSignal { class="kw">protected: class=class="str">"cmt">//--- Setup parameters class="type">int m_period_ma; class=class="str">"cmt">// averaging period of the MA class="type">int m_shift_ma; class=class="str">"cmt">// shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; class=class="str">"cmt">// averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; class=class="str">"cmt">// averaging object of the MA class="type">class="kw">double m_limit; class=class="str">"cmt">// level to place a pending order relative to the MA
信号类的止损止盈与参数校验
在自定义信号类里,挂单的止损、止盈和有效期都作为成员变量保存:m_stop_loss 和 m_take_profit 是相对开仓价的偏移量,m_expiration 则以 Bars 为单位限定挂单存活时间。 通过一组 public 的 setter 方法(PeriodMA、ShiftMA、StopLoss 等)可在 EA 初始化时从外部注入参数,例如 StopLoss(50.0) 表示止损距开仓价 50 点。 参数落库前必须过一道 ValidationSettings()。目前仅校验 m_period_ma>0,若不通过则在日志打印函数名及原因并返回 false,EA 会因此拒绝启动信号模块。 开 MT5 把这段校验逻辑接进你的信号类,先只留 MA 周期>0 这一条,跑通后再逐步补 limit、expiration 的边界检查,避免外汇或贵金属行情跳空时挂单参数越界。
class="type">class="kw">double m_stop_loss; class=class="str">"cmt">// level to place a stop loss order relative to the open price class="type">class="kw">double m_take_profit; class=class="str">"cmt">// level to place a take profit order relative to the open price class="type">int m_expiration; class=class="str">"cmt">// lifetime of a pending order in bars class="kw">public: CSampleSignal(); class=class="str">"cmt">//--- Methods to set the parameters class="type">void PeriodMA(class="type">int value) { m_period_ma=value; } class="type">void ShiftMA(class="type">int value) { m_shift_ma=value; } class="type">void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } class="type">void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } class="type">void Limit(class="type">class="kw">double value) { m_limit=value; } class="type">void StopLoss(class="type">class="kw">double value) { m_stop_loss=value; } class="type">void TakeProfit(class="type">class="kw">double value) { m_take_profit=value; } class="type">void Expiration(class="type">int value) { m_expiration=value; } class=class="str">"cmt">//--- Methods to validate the parameters class="kw">virtual class="type">bool ValidationSettings(); }; class="type">bool CSampleSignal::ValidationSettings() { class=class="str">"cmt">//--- Validation of parameters if(m_period_ma<=class="num">0) { printf(__FUNCTION__+": the MA period must be greater than zero"); class="kw">return(false); } class=class="str">"cmt">//--- ok class="kw">return(true);
◍ 信号类的成员变量与参数入口
在 MT5 的 EA 框架里,自定义信号类通常继承自 CExpertSignal。下面这段声明把均线、开收盘价容器和一批可调参数都收进同一个类,方便后续在策略里统一调用。 类里先挂了三个价格对象:CiMA 负责读均线数值,CiOpen 和 CiClose 分别抓单根 K 线的开盘与收盘。实际回测时,若把 m_period_ma 设成 20、m_shift_ma 设 0,就等价于在图表上加载一条常规 20 期 MA。 protected 区暴露的 m_limit、m_stop_loss、m_take_profit 都是相对价格的距离(注意是 double 不是点数),m_expiration 则以 bars 为单位控制挂单存活时长。public 里的 PeriodMA / ShiftMA 等内联函数,本质就是给这些私有成员赋值的快捷通道,改一处参数不用动核心逻辑。 别把参数封装想复杂了 这些 setter 看起来啰嗦,但好处是信号实例在初始化后参数不可被外部随意篡改,降低 EA 跑飞的概率。你开 MT5 新建一个信号类时,照这个结构抄一遍,编译能过就说明对象引用没写错。
class CSampleSignal : class="kw">public CExpertSignal { class="kw">protected: CiMA m_MA; class=class="str">"cmt">// object to access the values om the moving average CiOpen m_open; class=class="str">"cmt">// object to access the bar open prices CiClose m_close; class=class="str">"cmt">// object to access the bar close prices class=class="str">"cmt">//--- Setup parameters class="type">int m_period_ma; class=class="str">"cmt">// averaging period of the MA class="type">int m_shift_ma; class=class="str">"cmt">// shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; class=class="str">"cmt">// averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; class=class="str">"cmt">// averaging object of the MA class="type">class="kw">double m_limit; class=class="str">"cmt">// level to place a pending order relative to the MA class="type">class="kw">double m_stop_loss; class=class="str">"cmt">// level to place a stop loss order relative to the open price class="type">class="kw">double m_take_profit; class=class="str">"cmt">// level to place a take profit order relative to the open price class="type">int m_expiration; class=class="str">"cmt">// lifetime of a pending order in bars class="kw">public: CSampleSignal(); class=class="str">"cmt">//--- Methods to set the parameters class="type">void PeriodMA(class="type">int value) { m_period_ma=value; } class="type">void ShiftMA(class="type">int value) { m_shift_ma=value; } class="type">void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } class="type">void AppliedMA(ENUM_APPLIED_PRICE value){ m_applied_ma=value; } class="type">void Limit(class="type">class="kw">double value) { m_limit=value; } class="type">void StopLoss(class="type">class="kw">double value) { m_stop_loss=value; }
「信号类的指标初始化与数据接口」
在自定义信号类里,先通过一组 setter 把止损止盈和过期时间塞进成员变量:TakeProfit(double value) 直接写 m_take_profit,Expiration(int value) 写 m_expiration,这两个调用发生在策略树装配阶段,而不是每根 K 线重算。 类内部用 protected 区隔了指标句柄与序列访问器。MA(int index) 返回 m_MA.Main(index),Open/Close 则分别包了 m_open.GetData 与 m_close.GetData,调用方拿 index 就能取到对应偏移的均线值或 OHLC 序列,不用关心底层指标是怎么挂上去的。 真正的挂载逻辑落在 InitIndicators(CIndicators* indicators)。它先做空指针校验,再依次 InitMA、InitOpen、InitClose,任意一步失败立即 return(false)。也就是说,若你的 MT5 终端里信号不触发,先确认这三个初始化是否全数返回 true,而不是去怀疑交易条件。 这段结构对外汇与贵金属 EA 同样适用,但这类品种点差跳空频繁,指标初始化成功不代表实盘信号稳定,回测与实盘表现可能有偏差,属于高风险验证项。
class="type">void TakeProfit(class="type">class="kw">double value) { m_take_profit=value; } class="type">void Expiration(class="type">int value) { m_expiration=value; } class=class="str">"cmt">//--- Method to validate the parameters class="kw">virtual class="type">bool ValidationSettings(); class=class="str">"cmt">//--- Method to validate the parameters class="kw">virtual class="type">bool InitIndicators(CIndicators* indicators); class="kw">protected: class=class="str">"cmt">//--- Object initialization method class="type">bool InitMA(CIndicators* indicators); class="type">bool InitOpen(CIndicators* indicators); class="type">bool InitClose(CIndicators* indicators); class=class="str">"cmt">//--- Methods to access object data class="type">class="kw">double MA(class="type">int index) { class="kw">return(m_MA.Main(index)); } class="type">class="kw">double Open(class="type">int index) { class="kw">return(m_open.GetData(index)); } class="type">class="kw">double Close(class="type">int index) { class="kw">return(m_close.GetData(index)); } }; class="type">bool CSampleSignal::InitIndicators(CIndicators* indicators) { if(indicators==NULL) class="kw">return(false); if(!InitMA(indicators)) class="kw">return(false); if(!InitOpen(indicators)) class="kw">return(false); if(!InitClose(indicators)) class="kw">return(false); class="kw">return(true); }
把指标和时序塞进集合的收尾函数
信号类里两个 Init 函数干的事很直白:把移动平均线和开盘价时序分别建好,再挂到 indicators 集合上。InitMA 里先调 m_MA.Create 用当前品种名、图表周期、均线周期、偏移、均价算法和应用价格去初始化;若返回 false 就打印函数名加错误描述并退出。 注意 m_MA.BufferResize(3+m_shift_ma) 这一行,它给均线缓冲区多留了 3 加偏移量的柱位,避免后续跨柱取数时越界。之后 indicators.Add(GetPointer(m_MA)) 把对象指针交进集合,失败同样报错返回。 InitOpen 更轻量,只针对 m_open 开盘价时序做 Create 和 Add,没有额外的 BufferResize。两个函数都返回 bool,调用方只要收到 false 就该停掉后续逻辑——外汇和贵金属行情跳空频繁,时序初始化失败直接跑信号会出脏值,杠杆品种风险偏高,务必在 EA 里接住这个返回值。
class="type">bool CSampleSignal::InitMA(CIndicators* indicators) { class=class="str">"cmt">//--- Initialization of the MA object if(!m_MA.Create(m_symbol.Name(),m_period,m_period_ma,m_shift_ma,m_method_ma,m_applied_ma)) { printf(__FUNCTION__+": object initialization error"); class="kw">return(false); } m_MA.BufferResize(class="num">3+m_shift_ma); class=class="str">"cmt">//--- Adding an object to the collection if(!indicators.Add(GetPointer(m_MA))) { printf(__FUNCTION__+": object adding error"); class="kw">return(false); } class=class="str">"cmt">//--- Successful completion class="kw">return(true); } class="type">bool CSampleSignal::InitOpen(CIndicators* indicators) { class=class="str">"cmt">//--- Initialization of the timeseries object if(!m_open.Create(m_symbol.Name(),m_period)) { printf(__FUNCTION__+": object initialization error"); class="kw">return(false); } class=class="str">"cmt">//--- Adding an object to the collection if(!indicators.Add(GetPointer(m_open))) { printf(__FUNCTION__+": object adding error"); class="kw">return(false); } class=class="str">"cmt">//--- Successful completion class="kw">return(true); }