MQL5 向导:如何创建交易信号模块·综合运用
(3/3)· 把前两步搭好的骨架真正跑起来,15 节拆解信号类编写与 EA 装配的最后一公里
很多人卡在向导能生成 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,就能直接跑均线与收盘价的穿越逻辑;外汇和贵金属波动大,参数未经实盘验证前切勿重仓,亏损概率不低。
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 分钟图上快速验证信号触发频率。外汇与贵金属杠杆高,回测通过不等于实盘稳,参数需随品种波动率重调。
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 括号后左大括号,后续赋值逻辑和长头镜像,只是方向反过来。
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 策略测试器用历史数据跑一遍验证触发频率。
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 可快速验证不同均线组合的表现。外汇与贵金属杠杆高,回测盈利不代表实盘概率,需自行承压测试。
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」被列出来,省掉手填参数的麻烦。
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 根就可能让回踩触发概率明显不同。
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)); };
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。
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 等成员需在类内先声明赋值。
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 用历史数据跑一遍命中率再上实盘。
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 三个外部参数就能看到不同距离下的触发频率差异。
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) 那个入参。
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 字段和参数解析,比看文档更快摸清结构。外汇和贵金属杠杆高,模块回测顺不等于实盘稳,先开模拟盘验证再谈真金白银。
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">//+------------------------------------------------------------------+