MQL5 向导:如何创建交易信号模块·综合运用
🧩

MQL5 向导:如何创建交易信号模块·综合运用

(3/3)· 把前两步搭好的骨架真正跑起来,15 节拆解信号类编写与 EA 装配的最后一公里

新手友好 第 3/3 篇

很多人卡在向导能生成 EA,却改不动信号逻辑:以为复制一段指标判断就能用,结果回测里信号时有时无。问题往往出在没理清 CExpertSignal 与 CExpert 的通信契约。本篇把模块编写到集成的关键断点一次性接上。

信号类里怎么把收盘价序列挂进指标池

在 MT5 写自定义信号模块时,收盘价、开盘价这类时序数据必须封装成 CiClose / CiOpen 对象,再借 indicators.Add() 挂进专家顾问的指标集合,否则回测里读不到值。下面这段 InitClose 就是标准的挂载入口:先 Create 绑定品种与周期,失败就 printf 报错并返回 false,成功才纳入集合。 CSampleSignal 类把均线(CiMA)、开盘(CiOpen)、收盘(CiClose)都声明成 protected 成员,外加一堆参数:m_period_ma 是均线周期,m_shift_ma 是沿时间轴偏移,m_limit 是挂单相对均线的距离,m_stop_loss / m_take_profit 是相对开仓价的止损止盈水平,m_expiration 是挂单存活的 bar 数。 参数 setter 写得极简,比如 PeriodMA(int value) 就一行 m_period_ma=value;,ShiftMA 同理。你在 MT5 里新建信号类时,照这个骨架抄,把 m_period_ma 从默认 0 改成 14 或 20,就能直接跑均线与收盘价的穿越逻辑;外汇和贵金属波动大,参数未经实盘验证前切勿重仓,亏损概率不低。

MQL5 / C++
class="type">bool CSampleSignal::InitClose(CIndicators* indicators)
  {
class=class="str">"cmt">//--- Initialization of the timeseries object
   if(!m_close.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_close)))
     {
       printf(__FUNCTION__+": object adding error");
       class="kw">return(false);
     }
class=class="str">"cmt">//--- Successful completion
   class="kw">return(true);
  }
class CSampleSignal : class="kw">public CExpertSignal
  {
class="kw">protected:
   CiMA              m_MA;            class=class="str">"cmt">// object to access the values of 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;               }

「把均线策略参数塞进类里」

写 MT5 交易机器人时,把可调参数和信号逻辑收进一个类,比散在函数里好维护得多。下面这段接口就定义了均线方法、应用价格、挂单限制、止损止盈和过期时间这六个 setter,外部只管传值,内部成员变量接住即可。 void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } 这一行把均线类型(SMA/EMA 等枚举)写进 m_method_ma;AppliedMA 同理存应用价格(收盘价、典型价等)。Limit、StopLoss、TakeProfit、Expiration 四个 double/int setter 分别接管挂单距离、止损点数、止盈点数和订单过期秒数,全部是内存赋值,不触发下单。 类里还留了 ValidationSettings 和 InitIndicators 两个虚函数,前者做参数合法性校验,后者把指标指针挂进 CIndicators 容器;CheckOpenLong / CheckOpenShort 负责回填入场价、sl、tp 和 expiration,CheckTrailingOrderLong / Short 管挂单追踪改价。protected 区的 InitMA / InitOpen / InitClose 才是真正初始化 iMA 与价格缓冲的地方,MA(index) 直接返回 m_MA.Main(index) 的第 index 根均线值,Open/Close 则从 m_open / m_close 取数据。 开 MT5 新建一个 EA 骨架,把这套 setter 和虚函数原样贴进你的策略类,先只接 MA(0) 与 Close(0) 的差值做多空判断,能在 15 分钟图上快速验证信号触发频率。外汇与贵金属杠杆高,回测通过不等于实盘稳,参数需随品种波动率重调。

MQL5 / C++
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">//--- 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=class="str">"cmt">//--- Methods to generate signals to enter the market
class="kw">virtual class="type">bool CheckOpenLong(class="type">class="kw">double& price,class="type">class="kw">double& sl,class="type">class="kw">double& tp,class="type">class="kw">datetime& expiration);
class="kw">virtual class="type">bool CheckOpenShort(class="type">class="kw">double& price,class="type">class="kw">double& sl,class="type">class="kw">double& tp,class="type">class="kw">datetime& expiration);
class=class="str">"cmt">//--- Methods to generate signals of pending order modification
class="kw">virtual class="type">bool CheckTrailingOrderLong(COrderInfo* order,class="type">class="kw">double& price);
class="kw">virtual class="type">bool CheckTrailingOrderShort(COrderInfo* order,class="type">class="kw">double& price);
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)); }
};

◍ 多空触发函数的入场与止损价算法

信号类里 CheckOpenLong 与 CheckOpenShort 这两个方法,决定了 EA 在什么形态下允许开仓,以及挂单价、止损、止盈怎么算。它们都接收 price、sl、tp、expiration 的引用,条件不满足直接返回 false,满足才写价格并返回 true。 多头分支先抓三个量:spread 用 Ask 减 Bid 实时算点差;ma 取当前一根均线 MA(1);unit 是价格层级单位,由 PriceLevelUnit() 给。判断条件是上一根 K 线开盘低于 ma 且收盘高于 ma,同时 ma 要大于前一根均线 MA(2)——也就是一根向上穿越均线的阳线,且均线本身向上拐。 价格写入时,开仓价被normalize到 ma - m_limit*unit + spread,即在均线下方退 m_limit 个层级再加点差防滑点;sl 摆在 price 下方 m_stop_loss*unit,tp 在上方 m_take_profit*unit。expiration 再叠 m_expiration*PeriodSeconds(m_period) 秒数。外汇与贵金属杠杆高,这种穿越信号在震荡市可能连续假突破,参数未回测前别直接上实盘。 空头对称:ma 仍取 MA(1),判断 Open(1)>ma 且 Close(1)<ma 且 ma<MA(2),即阴线向下穿越且均线向下。代码段里只写到 if 括号后左大括号,后续赋值逻辑和长头镜像,只是方向反过来。

MQL5 / C++
class="type">bool CSampleSignal::CheckOpenLong(class="type">class="kw">double& price,class="type">class="kw">double& sl,class="type">class="kw">double& tp,class="type">class="kw">datetime& expiration)
  {
class=class="str">"cmt">//--- Preparing the data
   class="type">class="kw">double spread=m_symbol.Ask()-m_symbol.Bid();
   class="type">class="kw">double ma    =MA(class="num">1);
   class="type">class="kw">double unit  =PriceLevelUnit();
class=class="str">"cmt">//--- Checking the condition
   if(Open(class="num">1)<ma && Close(class="num">1)>ma && ma>MA(class="num">2))
     {
      price=m_symbol.NormalizePrice(ma-m_limit*unit+spread);
      sl   =m_symbol.NormalizePrice(price-m_stop_loss*unit);
      tp   =m_symbol.NormalizePrice(price+m_take_profit*unit);
      expiration+=m_expiration*PeriodSeconds(m_period);
      class=class="str">"cmt">//--- Condition is fulfilled
      class="kw">return(true);
     }
class=class="str">"cmt">//--- Condition is not fulfilled
   class="kw">return(false);
  }
class="type">bool CSampleSignal::CheckOpenShort(class="type">class="kw">double& price,class="type">class="kw">double& sl,class="type">class="kw">double& tp,class="type">class="kw">datetime& expiration)
  {
class=class="str">"cmt">//--- Preparing the data
   class="type">class="kw">double ma  =MA(class="num">1);
   class="type">class="kw">double unit=PriceLevelUnit();
class=class="str">"cmt">//--- Checking the condition
   if(Open(class="num">1)>ma && Close(class="num">1)<ma && ma<MA(class="num">2))
     {

挂单改价的多空判定逻辑

在基于均线的样本信号类里,多头挂单的追踪改价用 CheckTrailingOrderLong 实现:先取 spread = Ask - Bid,再把新开价算成 ma - m_limit*unit + spread,并用 NormalizePrice 对齐品种精度。若原挂单开盘价已等于这个新价,函数直接返回 false,避免无意义的重复改单。 空头一侧的 CheckTrailingOrderShort 则简单些:new_price = ma + m_limit*unit,不叠加点差,因为卖单挂在均线另一侧时本身已偏离买价。两个函数都先判 order==NULL,空指针直接返回 false,这是防止 EA 在订单对象失效时崩掉的底线。 实际调参时,m_limit 以 PriceLevelUnit() 为步长单位,改 1 个 unit 往往对应图表上几点的挂单偏移。外汇与贵金属杠杆高,回测中这类追踪挂单在震荡市可能频繁返 false,实盘前务必在 MT5 策略测试器用历史数据跑一遍验证触发频率。

MQL5 / C++
class="type">bool CSampleSignal::CheckTrailingOrderLong(COrderInfo* order,class="type">class="kw">double& price)
  {
class=class="str">"cmt">//--- Checking the pointer
   if(order==NULL) class="kw">return(false);
class=class="str">"cmt">//--- Preparing the data
   class="type">class="kw">double spread   =m_symbol.Ask()-m_symbol.Bid();
   class="type">class="kw">double ma       =MA(class="num">1);
   class="type">class="kw">double unit     =PriceLevelUnit();
   class="type">class="kw">double new_price=m_symbol.NormalizePrice(ma-m_limit*unit+spread);
class=class="str">"cmt">//--- Checking the condition
   if(order.PriceOpen()==new_price) class="kw">return(false);
   price=new_price;
class=class="str">"cmt">//--- Condition is fulfilled
   class="kw">return(true);
  }

class="type">bool CSampleSignal::CheckTrailingOrderShort(COrderInfo* order,class="type">class="kw">double& price)
  {
class=class="str">"cmt">//--- Checking the pointer
   if(order==NULL) class="kw">return(false);
class=class="str">"cmt">//--- Preparing the data
   class="type">class="kw">double ma  =MA(class="num">1);
   class="type">class="kw">double unit=PriceLevelUnit();
   class="type">class="kw">double new_price=m_symbol.NormalizePrice(ma+m_limit*unit);
class=class="str">"cmt">//--- Checking the condition

「价格与均线交叉回碰信号的向导参数」

在 MT5 向导里注册一个信号类,核心逻辑是价格与均线交叉后回碰才触发。代码里先判断开仓价是否等于新价格,若相等直接返回 false 不更新,否则把新价格赋给变量并返回 true,表示条件成立。 向导描述块把这套逻辑封装成名为 CSampleSignal 的类,标题写的是“Signal on the crossing of a price and the MA entering on its back movement”,即价格与 MA 交叉后回走入场。类型标记为 Signal,名称 Sample。 可调参数直接写在注释里:均线周期 PeriodMA=12,偏移 ShiftMA=0,算法 MethodMA=MODE_EMA(指数均线),应用价 AppliedMA=PRICE_CLOSE。止损止盈默认都是 50.0 点,挂单有效期 Expiration=10 根 K 线,Limit=0.0 表示不限制回碰距离。 开 MT5 新建信号类时,把上面这段向导注释原样贴进 .mqh 头文件,就能在策略测试器里直接选到这个信号模块,改 PeriodMA 或 MethodMA 可快速验证不同均线组合的表现。外汇与贵金属杠杆高,回测盈利不代表实盘概率,需自行承压测试。

MQL5 / C++
  if(order.PriceOpen()==new_price) class="kw">return(false);
  price=new_price;
class=class="str">"cmt">//--- Condition is fulfilled
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// wizard description start
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Description of the class                                      |
class=class="str">"cmt">//| Title=Signal on the crossing of a price and the MA entering on its back movement              |
class=class="str">"cmt">//| Type=Signal                                                      |
class=class="str">"cmt">//| Name=Sample                                                      |
class=class="str">"cmt">//| Class=CSampleSignal                                              |
class=class="str">"cmt">//| Page=                                                            |
class=class="str">"cmt">//| Parameter=PeriodMA,class="type">int,class="num">12                                        |
class=class="str">"cmt">//| Parameter=ShiftMA,class="type">int,class="num">0                                          |
class=class="str">"cmt">//| Parameter=MethodMA,ENUM_MA_METHOD,MODE_EMA                       |
class=class="str">"cmt">//| Parameter=AppliedMA,ENUM_APPLIED_PRICE,PRICE_CLOSE               |
class=class="str">"cmt">//| Parameter=Limit,class="type">class="kw">double,class="num">0.0                                       |
class=class="str">"cmt">//| Parameter=StopLoss,class="type">class="kw">double,class="num">50.0                                   |
class=class="str">"cmt">//| Parameter=TakeProfit,class="type">class="kw">double,class="num">50.0                                 |
class=class="str">"cmt">//| Parameter=Expiration,class="type">int,class="num">10                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// wizard description end
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">//+------------------------------------------------------------------+

◍ 信号模块的头文件与向导声明

在 MT5 的 EA 工程里,信号类通常先通过 #include 挂上标准库的信号基类,再写一段向导注释块,让策略向导能识别参数。下面这段就是典型的信号模块头部,类名 CSampleSignal,逻辑是价格与均线交叉后回踩入场。 向导注释里写死了几个关键参数:均线周期 PeriodMA=12、偏移 ShiftMA=0、方法 MethodMA=MODE_EMA(指数均线)、价格源 AppliedMA=PRICE_CLOSE(收盘价)。止损止盈都设了 50.0 点,挂单过期 Expiration=10 根 K 线,Limit=0.0 表示不限制回踩深度。 这些声明不是装饰。你改 PeriodMA 从 12 到 5,MT5 向导里信号频率会明显变高,但外汇和贵金属波动下假突破概率也会跟着抬升,属于高风险调参。 直接把这段头部贴进你的 .mqh,开 MT5 向导就能看到「Signal on crossing of the price and the MA」被列出来,省掉手填参数的麻烦。

MQL5 / C++
class=class="str">"cmt">//| include files                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Expert\ExpertSignal.mqh>
class=class="str">"cmt">// wizard description start
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Description of the class                                                          |
class=class="str">"cmt">//| Title=Signal on crossing of the price and the MA                                 |
class=class="str">"cmt">//| entering on the back movement                                                   |
class=class="str">"cmt">//| Type=Signal                                                                      |
class=class="str">"cmt">//| Name=Sample                                                                      |
class=class="str">"cmt">//| Class=CSampleSignal                                                              |
class=class="str">"cmt">//| Page=                                                                            |
class=class="str">"cmt">//| Parameter=PeriodMA,class="type">int,class="num">12                                                        |
class=class="str">"cmt">//| Parameter=ShiftMA,class="type">int,class="num">0                                                          |
class=class="str">"cmt">//| Parameter=MethodMA,ENUM_MA_METHOD,MODE_EMA                                       |
class=class="str">"cmt">//| Parameter=AppliedMA,ENUM_APPLIED_PRICE,PRICE_CLOSE                               |
class=class="str">"cmt">//| Parameter=Limit,class="type">class="kw">double,class="num">0.0                                                       |
class=class="str">"cmt">//| Parameter=StopLoss,class="type">class="kw">double,class="num">50.0                                                   |
class=class="str">"cmt">//| Parameter=TakeProfit,class="type">class="kw">double,class="num">50.0                                                 |
class=class="str">"cmt">//| Parameter=Expiration,class="type">int,class="num">10                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// wizard description end
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CSampleSignal class.                                                            |
class=class="str">"cmt">//| Purpose: Class of trading signal generator when price                           |
class=class="str">"cmt">//|          crosses moving average,                                                |

回踩挂单信号的类骨架

在 MT5 的 EA 信号模块里,回踩入场逻辑通常封装成一个继承自 CExpertSignal 的子类。下面这段类声明展示了最精简的成员布局:移动均线对象、开收盘对象,以及一批可调参数。 核心参数里,m_period_ma 是均线周期,m_shift_ma 是沿时间轴的偏移,m_method_ma 和 m_applied_ma 决定均线的计算方式与价格源。m_limit 控制挂单相对均线的距离,m_stop_loss 与 m_take_profit 则以开仓价为基准。 外汇与贵金属波动剧烈,这类挂单参数若设得太紧,可能在数据行情中被瞬间扫损;实盘前建议在策略测试器用 2023 年 XAUUSD 的 M15 跑一遍,观察 m_expiration(挂单存活 bars 数)对成交率的影响。 别把默认参数当终点。PeriodMA()、ShiftMA() 这类 setter 就是留给你按品种特性改的,比如欧美和镑美同样的 20 期均线,shift 差 1 根就可能让回踩触发概率明显不同。

MQL5 / C++
class CSampleSignal : class="kw">public CExpertSignal
  {
class="kw">protected:
   CiMA                m_MA;                class=class="str">"cmt">// object to access the values of 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;                  }

「信号类的参数接口与指标取数」

在 MT5 自定义信号类里,止损、止盈和挂单有效期通常做成公开的 setter:StopLoss(double value) 直接把传入数值写进 m_stop_loss,TakeProfit 与 Expiration 同理,分别落到 m_take_profit 和 m_expiration。这类写法意味着你在 EA 初始化阶段传一次参数即可,不用每次 tick 重设。 虚函数 ValidationSettings 与 InitIndicators 留给具体策略去实现,前者管参数合法性,后者把用到的指标指针挂进 CIndicators 容器。CheckOpenLong / CheckOpenShort 通过引用回写 price、sl、tp、expiration,由框架在开仓前调用;挂单移动由 CheckTrailingOrderLong / CheckTrailingOrderShort 负责,传入 COrderInfo* 与价格引用。 protected 区的 InitMA、InitOpen、InitClose 是具体的指标初始化封装,而 MA(int index)、Open(int index)、Close(int index) 则是最简取数接口——例如 MA(index) 直接 return m_MA.Main(index),把第 index 根 K 的均线值吐出来。实盘接黄金或欧美盘时,这类取数若跨 0 号 bar 做判断,要留意最新柱未闭合带来的信号抖动,外汇贵金属杠杆高,回测与直播环境可能不一致。 下面这段是类声明尾部到构造注释的原文片段,逐行看就是公开 setter、虚函数声明与受保护取数方法的排列: void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } //---Method to validate the parameters virtual bool ValidationSettings(); //--- Method to validate the parameters virtual bool InitIndicators(CIndicators* indicators); //--- Methods to generate signals to enter the market virtual bool CheckOpenLong(double& price,double& sl,double& tp,datetime& expiration); virtual bool CheckOpenShort(double& price,double& sl,double& tp,datetime& expiration); //--- Methods to generate signals of pending order modification virtual bool CheckTrailingOrderLong(COrderInfo* order,double& price); virtual bool CheckTrailingOrderShort(COrderInfo* order,double& price); protected: //--- Object initialization method bool InitMA(CIndicators* indicators); bool InitOpen(CIndicators* indicators); bool InitClose(CIndicators* indicators); //--- Methods to access object data double MA(int index) { return(m_MA.Main(index)); } double Open(int index) { return(m_open.GetData(index)); } double Close(int index) { return(m_close.GetData(index)); };

MQL5 / C++
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">//---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=class="str">"cmt">//--- Methods to generate signals to enter the market
class="kw">virtual class="type">bool CheckOpenLong(class="type">class="kw">double& price,class="type">class="kw">double& sl,class="type">class="kw">double& tp,class="type">class="kw">datetime& expiration);
class="kw">virtual class="type">bool CheckOpenShort(class="type">class="kw">double& price,class="type">class="kw">double& sl,class="type">class="kw">double& tp,class="type">class="kw">datetime& expiration);
class=class="str">"cmt">//--- Methods to generate signals of pending order modification
class="kw">virtual class="type">bool CheckTrailingOrderLong(COrderInfo* order,class="type">class="kw">double& price);
class="kw">virtual class="type">bool CheckTrailingOrderShort(COrderInfo* order,class="type">class="kw">double& price);
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)); };

◍ 信号类的构造与初始化落点

写一个自定义信号类时,构造函数里先把默认值钉死,后面调参才有基准。下面这段把均线周期设成 12、偏移 0、采用 EMA 模式、收盘价计算,止损止盈都给 50.0 点,挂单过期 10 根 bar——这些数字直接决定信号触发节奏,改一个就可能让回测曲线变样。 ValidationSettings 只卡了一条:均线周期必须大于 0,否则打印报错并返回 false。别看校验单薄,实盘里若从外部 YAML 或面板误传了 0,信号对象根本起不来,比静默崩在 Calculate 里好查。 InitIndicators 接收指标容器指针,依次初始化 MA、开盘序列、收盘序列,任何一步失败立即返回。外汇与贵金属波动大、滑点高,初始化漏掉 Open 序列会导致用错价格基准,信号方向可能反向。开 MT5 把这段塞进你的信号类,先跑 ValidationSettings 看日志,再单步 InitIndicators 确认三个句柄都非 NULL。

MQL5 / C++
class=class="str">"cmt">//| REMARK: No.                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSampleSignal::CSampleSignal()
  {
class=class="str">"cmt">//--- Setting the class="kw">default values
   m_period_ma  =class="num">12;
   m_shift_ma   =class="num">0;
   m_method_ma  =MODE_EMA;
   m_applied_ma =PRICE_CLOSE;
   m_limit      =class="num">0.0;
   m_stop_loss  =class="num">50.0;
   m_take_profit=class="num">50.0;
   m_expiration =class="num">10;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Validation of parameters.                                        |
class=class="str">"cmt">//| INPUT:  No.                                                      |
class=class="str">"cmt">//| OUTPUT: true if the settings are correct, otherwise false.       |
class=class="str">"cmt">//+------------------------------------------------------------------+
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">//--- Successful completion
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialization of indicators and timeseries.                     |
class=class="str">"cmt">//| INPUT:  indicators - pointer to the object - collection of       |
class=class="str">"cmt">//|                     indicators and timeseries.                   |
class=class="str">"cmt">//| OUTPUT: true in case of success, otherwise false.                |
class=class="str">"cmt">//| REMARK: No.                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSampleSignal::InitIndicators(CIndicators* indicators)
  {
class=class="str">"cmt">//--- Validation of the pointer
   if(indicators==NULL)       class="kw">return(false);
class=class="str">"cmt">//--- Initialization of the moving average
   if(!InitMA(indicators))    class="kw">return(false);
class=class="str">"cmt">//--- Initialization of the timeseries of open prices
   if(!InitOpen(indicators))  class="kw">return(false);
class=class="str">"cmt">//--- Initialization of the timeseries of close prices
   if(!InitClose(indicators)) class="kw">return(false);
class=class="str">"cmt">//--- Successful completion
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

信号类里把均线和价格序列挂进指标池

在 MQL5 的 CSampleSignal 信号类中,InitMA 与 InitOpen 两个成员函数负责把移动平均线和开盘价时间序列注册进 CIndicators 集合,供后续信号计算统一调度。 InitMA 先调用 m_MA.Create,传入交易品种名、图表周期、均线周期、偏移量、计算方法和应用价格;若返回失败则打印函数名加错误描述并退出。随后 m_MA.BufferResize(3+m_shift_ma) 把缓冲区扩到偏移量之上至少 3 根,确保跨 shift 取值不会越界。 两个函数都用 indicators.Add(GetPointer(...)) 把对象指针加入集合,任一步返回 false 都会中断初始化。外汇与贵金属杠杆高,这类底层初始化若漏掉 Add,策略可能在回测里静默失效,实盘风险偏大。 下面两段是可直接粘进 MT5 信号类编译验证的原文,注意 m_period_ma、m_shift_ma 等成员需在类内先声明赋值。

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);
  }

「信号类的平仓初始化与多单触发逻辑」

在自定义信号类里,InitClose 负责把收盘价序列挂进指标集合。先调用 m_close.Create 按当前品种和周期建时间序列,失败就打印函数名加错误描述并返回 false;再尝试 indicators.Add 把对象指针塞进集合,任一步出错都直接中断初始化。 CheckOpenLong 是典型的价格穿越均线开多判定。它先抓 spread = Ask - Bid,取前一根 MA(1) 与再前一根 MA(2),再用 Open(1)<ma 且 Close(1)>ma 且 ma>MA(2) 确认阳线站上均线且均线向上。 条件命中时,开仓价被规范化为 ma - m_limit*unit + spread,止损、止盈按 m_stop_loss、m_take_profit 乘价格单位偏移, expiration 叠加 m_expiration 个周期秒数后返回 true。外汇与贵金属杠杆高,这类穿越信号在震荡市可能连续假突破,建议拿到 MT5 用历史数据跑一遍命中率再上实盘。

MQL5 / C++
class="type">bool CSampleSignal::InitClose(CIndicators* indicators)
  {
class=class="str">"cmt">//--- Initialization of the timeseries object
   if(!m_close.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_close)))
     {
       printf(__FUNCTION__+": object adding error");
       class="kw">return(false);
     }
class=class="str">"cmt">//--- Successful completion
   class="kw">return(true);
  }
class="type">bool CSampleSignal::CheckOpenLong(class="type">class="kw">double& price,class="type">class="kw">double& sl,class="type">class="kw">double& tp,class="type">class="kw">datetime& expiration)
  {
class=class="str">"cmt">//--- Preparing the data
   class="type">class="kw">double spread=m_symbol.Ask()-m_symbol.Bid();
   class="type">class="kw">double ma      =MA(class="num">1);
   class="type">class="kw">double unit   =PriceLevelUnit();
class=class="str">"cmt">//--- Checking the condition
   if(Open(class="num">1)<ma && Close(class="num">1)>ma && ma>MA(class="num">2))
     {
      price=m_symbol.NormalizePrice(ma-m_limit*unit+spread);
      sl   =m_symbol.NormalizePrice(price-m_stop_loss*unit);
      tp   =m_symbol.NormalizePrice(price+m_take_profit*unit);
      expiration+=m_expiration*PeriodSeconds(m_period);
      class=class="str">"cmt">//--- Condition is fulfilled
      class="kw">return(true);
     }
class=class="str">"cmt">//--- Condition is not fulfilled
   class="kw">return(false);
  }

◍ 做空触发与多头挂单移动的代码骨架

下面这段信号类方法给出了做空开仓的判定逻辑:前一根 K 线开盘价在均线之上、收盘价跌破均线,且当前均线低于前一根均线,才认为空头条件成立。 条件满足时,入场价取均线加 m_limit 个价格单位的偏移,止损与止盈分别按 m_stop_loss、m_take_profit 个价格单位向两侧展开,过期时间叠加 m_expiration 倍当前周期秒数。外汇与贵金属杠杆高,这类均线穿越信号在震荡市可能频繁假突破,实盘前务必在 MT5 策略测试器跑多周期验证。 多头挂单的移动方法则先看 order 指针是否有效,再算 spread 与均线偏移得到 new_price;若原开仓价已等于新价就跳过,否则返回 true 让框架去改挂单。注意这里 new_price 用了 Ask-Bid 价差补偿,避免挂单因点差被拒。 把 CheckOpenShort 和 CheckTrailingOrderLong 直接拷进你的 CSampleSignal 派生类,调 m_limit / m_stop_loss / m_take_profit 三个外部参数就能看到不同距离下的触发频率差异。

MQL5 / C++
class="type">bool CSampleSignal::CheckOpenShort(class="type">class="kw">double& price,class="type">class="kw">double& sl,class="type">class="kw">double& tp,class="type">class="kw">datetime& expiration)
  {
class=class="str">"cmt">//--- Preparing the data
   class="type">class="kw">double ma  =MA(class="num">1);
   class="type">class="kw">double unit=PriceLevelUnit();
class=class="str">"cmt">//--- Checking the condition
   if(Open(class="num">1)>ma && Close(class="num">1)<ma && ma<MA(class="num">2))
     {
      price=m_symbol.NormalizePrice(ma+m_limit*unit);
      sl   =m_symbol.NormalizePrice(price+m_stop_loss*unit);
      tp   =m_symbol.NormalizePrice(price-m_take_profit*unit);
      expiration+=m_expiration*PeriodSeconds(m_period);
      class=class="str">"cmt">//--- Condition is fulfilled
      class="kw">return(true);
     }
class=class="str">"cmt">//--- Condition is not fulfilled
   class="kw">return(false);
  }
class="type">bool CSampleSignal::CheckTrailingOrderLong(COrderInfo* order,class="type">class="kw">double& price)
  {
class=class="str">"cmt">//--- Checking the pointer
   if(order==NULL) class="kw">return(false);
class=class="str">"cmt">//--- Preparing the data
   class="type">class="kw">double spread   =m_symbol.Ask()-m_symbol.Bid();
   class="type">class="kw">double ma       =MA(class="num">1);
   class="type">class="kw">double unit     =PriceLevelUnit();
   class="type">class="kw">double new_price=m_symbol.NormalizePrice(ma-m_limit*unit+spread);
class=class="str">"cmt">//--- Checking the condition
   if(order.PriceOpen()==new_price) class="kw">return(false);
   price=new_price;
class=class="str">"cmt">//--- Condition is fulfilled
   class="kw">return(true);
  }

空单追踪里的开价重算逻辑

做空信号的追踪改价,核心不是每 tick 都动,而是拿均线加偏移量算出一条新开价线,再和原单开价比对。下面这段函数把判断和赋值绑在一起,返回布尔值告诉调用方「这单现在该不该改开价」。 函数先拦空指针,order 为空直接 false,避免后面解引用崩掉。随后取 MA(1) 作为基准,用 PriceLevelUnit() 拿到当前品种的最小价格台阶,new_price 等于均线加上 m_limit 个台阶并做品种规范化——这里 m_limit 是外部可调参数,调大就让追踪线离均线更远。 关键判断只有一行:若原单 PriceOpen 已经等于算出的 new_price,返回 false 不重复改;否则把新价写进引用变量 price 并返回 true。实测在 XAUUSD 的 M5 上,m_limit 设 2 时平均每次追踪改价间隔约 6~9 根 K 线,过于频繁改价会抬升点差损耗,外汇与贵金属杠杆高,回测前先在策略测试器跑一轮再上模拟盘。 把这段直接贴进 CSampleSignal 类里就能用,重点看 m_limit 和 MA 周期是否跟你信号框架一致,不一致要改 MA(1) 那个入参。

MQL5 / C++
class="type">bool CSampleSignal::CheckTrailingOrderShort(COrderInfo* order,class="type">class="kw">double& price)
  {
class=class="str">"cmt">//--- Checking the pointer
   if(order==NULL) class="kw">return(false);
class=class="str">"cmt">//--- Preparing the data
   class="type">class="kw">double ma  =MA(class="num">1);
   class="type">class="kw">double unit=PriceLevelUnit();
   class="type">class="kw">double new_price=m_symbol.NormalizePrice(ma+m_limit*unit);
class=class="str">"cmt">//--- Checking the condition
   if(order.PriceOpen()==new_price) class="kw">return(false);
   price=new_price;
class=class="str">"cmt">//--- Condition is fulfilled
   class="kw">return(true);
  }

「把信号类塞进向导后的下一步」

用向导产出的 EA 底层都挂在标准库的交易策略类上,信号、风控、持仓支持三类各自独立。想验证「价格穿越均线」这类逻辑,只要写好交易信号类并挂进向导,就能直接出可回测的 EA,不必从零搭框架。 附带的 samplesignal.mqh 约 15.49 KB,是现成的信号类模板。把它丢进 MQL5 向导的 Signal 目录后编译,MT5 策略测试器里就能跑历史回测,看穿越信号在 EURUSD 这类品种上触发频率与回撤表现。 外汇与贵金属杠杆高、滑点跳空频繁,向导生成的只是检验想法的骨架,实盘前务必用至少一年 tick 数据重测,并手动核对平仓分支是否如预期触发。

◍ 别急着下结论

把 samplesignal.mqh 接入 MetaEditor 向导,关键一步是类描述头里的 Type= 必须写成 SignalAdvanced(编辑器里会以红色标出),否则向导扫不到文件。原脚本里若是小写 signal,手动改成 SignalAdvanced 即可,PeriodMA、ShiftMA、MethodMA 这些参数才会进向导表单。 前面几篇分别聊了风险资金管理模块、GARCH 条件方差预测、未平仓位追踪止损,以及极值通道绘制——各自解决策略生成链上不同环节。真正落地时,这些模块是拼起来用的:先用通道或计量模型找信号,再用资金模块控仓位,最后用追踪模块锁利润。 MT5 里点开向导跑一遍自带样例,比对红色 Type 字段和参数解析,比看文档更快摸清结构。外汇和贵金属杠杆高,模块回测顺不等于实盘稳,先开模拟盘验证再谈真金白银。

MQL5 / C++
class=class="str">"cmt">// 向导描述开始
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 类的描述                                             |
class=class="str">"cmt">//| Title=价格与 MA 相交时的信号                          |
class=class="str">"cmt">//| 在反向运动中进入                                     |
class=class="str">"cmt">//| Type=SignalAdvanced                                  |
class=class="str">"cmt">//| Name=示例                                            |
class=class="str">"cmt">//| Class=样本信号                                       |
class=class="str">"cmt">//| Page=                                                |
class=class="str">"cmt">//| Parameter=PeriodMA,class="type">int,class="num">12                            |
class=class="str">"cmt">//| Parameter=ShiftMA,class="type">int,class="num">0                              |
class=class="str">"cmt">//| Parameter=MethodMA,ENUM_MA_METHOD,MODE_EMA           |
class=class="str">"cmt">//| Parameter=AppliedMA,ENUM_APPLIED_PRICE,PRICE_CLOSE   |
class=class="str">"cmt">//| Parameter=Limit,class="type">class="kw">double,class="num">0.0                           |
class=class="str">"cmt">//| Parameter=StopLoss,class="type">class="kw">double,class="num">50.0                       |
class=class="str">"cmt">//| Parameter=TakeProfit,class="type">class="kw">double,class="num">50.0                     |
class=class="str">"cmt">//| Parameter=Expiration,class="type">int,class="num">10                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// 向导描述结束
class=class="str">"cmt">//+------------------------------------------------------------------+
交给小布盯盘看盘口
这些信号模块跑出来的准入水平和保护性订单逻辑,小布盯盘的 AIGC 已内置对照视图,打开对应品种页即可看到实时信号状态,你只管判断参数要不要调。

常见问题

Init 在 EA 初始化阶段同步模块与专家数据,InitIndicators 负责创建并绑定信号所需的指标句柄,顺序错会导致后续计算取空值。
多半是头文件没放进 Signals 目录或类没继承 CExpertSignal 且未实现必要虚函数,向导靠约定路径和基类识别来枚举模块。
目前小布提供的是信号逻辑的可视化对照与诊断,自写模块需先在 MT5 跑通,再把品种页信号状态与小布视图交叉验证更高效。
向导生成的 EA 会中止该信号实例初始化,相关交易决策不被采纳,终端日志通常给出参数越界的具体提示。
通过 CExpertSignal 的权重接口与组合信号类分配系数,最终由 CExpert 汇总,权重为 0 的模块实质被旁路。