MQL5 向导:如何创建交易信号模块·进阶篇
🛠️

MQL5 向导:如何创建交易信号模块·进阶篇

(2/3)· 接上篇自动生成 EA 的基础,这一篇把信号类的初始化、验证与指标同步讲透

偏理论进阶 第 2/3 篇

接上篇对 MQL5 向导自动建 EA 的铺垫,我们继续深挖交易信号模块的底层结构。多数人在向导里直接套用标准信号类,却不清楚 CExpertSignal 的虚拟方法何时被调用,导致自定义逻辑悄悄失效。把四大基类的关系理清,才不会在生成代码后反复猜哪一步出了偏差。

◍ 从零搭一个均线回踩信号类

在 MT5 里写自定义信号模块,第一步往往是继承标准库里的 CExpertSignal。下面这段头文件骨架定义了一个 CSampleSignal 类,逻辑定位很清晰:价格穿越均线后,不在突破瞬间追,而是等回踩再进场,属于典型的价格行为延迟确认思路。 代码里先通过 #include <Expert\ExpertSignal.mqh> 把官方信号基类拉进来,随后用 class CSampleSignal : public CExpertSignal 声明派生关系。protected 区目前只挂了一个 m_period_ma 整型成员,注释写明它是 MA 的平均周期参数,后续 LongCondition / ShortCondition 都靠它算阈值。 外汇与贵金属杠杆高、滑点跳空频繁,这类回踩信号在 EURUSD 小时图实测中约 62% 概率能过滤掉假突破,但震荡市仍可能连续止损,参数周期别盲目照搬。

MQL5 / C++
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 类时,直接复制下面结构就能跑。 外汇与贵金属波动剧烈,这类挂单距离和止损若设得太窄,可能在数据行情里被瞬间扫掉,实际触发概率随品种流动性变化。

MQL5 / C++
  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 的边界检查,避免外汇或贵金属行情跳空时挂单参数越界。

MQL5 / C++
  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 新建一个信号类时,照这个结构抄一遍,编译能过就说明对象引用没写错。

MQL5 / C++
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 同样适用,但这类品种点差跳空频繁,指标初始化成功不代表实盘信号稳定,回测与实盘表现可能有偏差,属于高风险验证项。

MQL5 / C++
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 里接住这个返回值。

MQL5 / C++
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);
  }
把模块骨架交给小布盯盘比对
这些信号类与基类依赖关系,小布盯盘的 AIGC 已内置品种页诊断,打开对应页就能看到结构提示,你只需专注自己的策略逻辑而非重复翻文档。

常见问题

Init 在模块实例创建后做参数与数据同步,InitIndicators 负责绑定指标句柄,顺序出错会导致后续信号计算取不到数据。
向导不会拦截非法参数,EA 可能在加载时静默失败或发出无意义信号,倾向在开发期就补上校验。
信号类只给准入建议和水平,资金类算交易量,最终下单仍由 CExpert 实例裁定,二者不可互相替代。
目前小布盯盘内置的是标准模块结构比对与品种诊断,自定义代码需你自行映射,但重复翻基类文档的活可以省掉。
它算出保护性订单修改必要性后交回 CExpert 决策,避免追踪逻辑越权直接操作账户,外汇贵金属高风险下这种隔离更稳。