基于自定义指标的交易信号生成器·综合运用
🧩

基于自定义指标的交易信号生成器·综合运用

(3/3)· 接前两篇基础与实战,本篇把指标类、信号模块与模型0一次性串成可跑的交易信号生成器

案例拆解新手友好 第 3/3 篇
很多人卡在自定义指标写完却接不进EA信号模块,以为复制几行代码就能跑。其实从CiCustom到CExpertSignal的继承链没理清,模型0不写对,回测里连方向都出不来。

「从 CExpertSignal 派生自定义指标信号类」

在 MT5 的 EA 信号体系里,自定义指标信号类通常不直接写底层接口,而是继承 CExpertSignal 这个基类。 原文注释明确写了:该自定义指标类 derived from the CExpertSignal class,也就是说它复用的是标准库里专家信号类的框架,而不是从零造轮子。 开 MT5 看 Include\Expert\Signal\ 目录下的现成信号文件,你能对照出派生写法的固定结构:先声明类继承关系,再重写 LongCondition / ShortCondition 等虚函数。 这种写法让自定义指标能直接挂进 CExpert 的评分机制,省掉自己写信号聚合逻辑的成本,但外汇与贵金属波动剧烈,信号类只负责概率判断,不替代风控。

MQL5 / C++
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//| the custom indicator.                                             |
class=class="str">"cmt">//| It is derived from the CExpertSignal class.                       |
class=class="str">"cmt">//+------------------------------------------------------------------+

用 CiCustom 接管自定义指标数据

在 MT5 的自定义指标开发里,CiCustom 类是唯一专门用来承载指标实例逻辑的标准封装。它把指标的创建、缓冲区绑定和后续读取都收进同一个对象,避免你手动去碰 IndicatorCreate 那一堆句柄细节。 实际写 EA 或脚本时,只要继承或实例化 CiCustom,就能用统一接口把外部 .ex5 指标加载进来,再按索引取数。对外汇与贵金属这类高波动品种,自定义指标若加载失败会直接让信号错位,建议在调用前用终端「导航器—自定义指标」确认文件已编译进对应品种周期。 开 MT5 验证最快:新建一个 CiCustom 对象指向你常用的 RSI 自定义版,打印前 3 根柱的数值,看是否与图表一致。

◍ CIndicators 怎么管指标实例

CIndicators 本身不画指标,它只负责把一堆时间序列和指标对象收进同一个容器里。创建、存储、同步、释放内存这些脏活都归它管,你往里塞多少实例它都能按内部链表挨个维护。 真正值得盯的是它的 Create 方法。这个方法按你给的参数在运行时动态生成一个指定类型的指标对象,并返回指针,而不是在编译期写死。也就是说,你的 EA 可以在不同行情阶段换指标类型,不用重新编译。 对外汇和贵金属交易来说,这种动态创建意味着你可以按波动率切换均线周期或换成布林带,但杠杆与滑点带来的高风险并不会因为换了指标而消失,切换逻辑本身也可能引入同步延迟。

「给信号类补上信封指标与阈值字段」

在 MT5 自定义信号类里,先把信封指标(Envelopes)的句柄和参数槽位声明好,后面才能用价格穿越轨道来生成多空倾向。下面这段是类里 protected 区要落地的成员,对应源码 28–42 行。

MQL5 / C++
class CSignalMyCustInd : class="kw">public CExpertSignal
  {
class="kw">protected:
   CiEnvelopes        m_env;                 class=class="str">"cmt">// object-indicator
   class=class="str">"cmt">//--- adjusted parameters
   class="type">int                m_ma_period;          class=class="str">"cmt">// the "period of averaging" parameter of the indicator
   class="type">int                m_ma_shift;           class=class="str">"cmt">// the "time shift" parameter of the indicator
   ENUM_MA_METHOD     m_ma_method;          class=class="str">"cmt">// the "method of averaging" parameter of the indicator
   ENUM_APPLIED_PRICE m_ma_applied;         class=class="str">"cmt">// the "object of averaging" parameter of the indicator
   class="type">class="kw">double             m_deviation;          class=class="str">"cmt">// the "deviation" parameter of the indicator
   class="type">class="kw">double             m_limit_in;           class=class="str">"cmt">// threshold sensitivity of the &class="macro">#x27;rollback zone&class="macro">#x27;
   class="type">class="kw">double             m_limit_out;          class=class="str">"cmt">// threshold sensitivity of the &class="macro">#x27;class="kw">break through zone&class="macro">#x27;
   class=class="str">"cmt">//--- "weights" of market models(class="num">0-class="num">100)
   class="type">int                m_pattern_0;          class=class="str">"cmt">// model class="num">0 "price is near the necessary border of the envelope"
   class="type">int                m_pattern_1;          class=class="str">"cmt">// model class="num">1 "price crossed a border of the envelope"
逐行拆一下:CiEnvelopes m_env 是指标对象本体,所有计算靠它取缓冲区;m_ma_periodm_ma_applied 四个变量把均线周期、偏移、算法、应用价格全部参数化,默认不写死,方便外部传参。 m_deviation 是轨道偏离百分比,比如设 0.1 代表正负 10% 包络带;m_limit_inm_limit_out 是两个灵敏度阈值,分别管“回撤区”和“突破区”的触发边界,数值越小越敏感。 最后 m_pattern_0m_pattern_1 是两种市场模型的权重(0–100),模型 0 对应价格贴近轨道边界,模型 1 对应价格穿透轨道边界。这两个整型权重会在后续打分函数里决定信号偏向,外汇与贵金属波动大,阈值和权重都建议在历史数据上回测后再定,实盘有较高风险。

MQL5 / C++
class CSignalMyCustInd : class="kw">public CExpertSignal
  {
class="kw">protected:
   CiEnvelopes        m_env;                 class=class="str">"cmt">// object-indicator
   class=class="str">"cmt">//--- adjusted parameters
   class="type">int                m_ma_period;          class=class="str">"cmt">// the "period of averaging" parameter of the indicator
   class="type">int                m_ma_shift;           class=class="str">"cmt">// the "time shift" parameter of the indicator
   ENUM_MA_METHOD     m_ma_method;          class=class="str">"cmt">// the "method of averaging" parameter of the indicator
   ENUM_APPLIED_PRICE m_ma_applied;         class=class="str">"cmt">// the "object of averaging" parameter of the indicator
   class="type">class="kw">double             m_deviation;          class=class="str">"cmt">// the "deviation" parameter of the indicator
   class="type">class="kw">double             m_limit_in;           class=class="str">"cmt">// threshold sensitivity of the &class="macro">#x27;rollback zone&class="macro">#x27;
   class="type">class="kw">double             m_limit_out;          class=class="str">"cmt">// threshold sensitivity of the &class="macro">#x27;class="kw">break through zone&class="macro">#x27;
   class=class="str">"cmt">//--- "weights" of market models(class="num">0-class="num">100)
   class="type">int                m_pattern_0;          class=class="str">"cmt">// model class="num">0 "price is near the necessary border of the envelope"
   class="type">int                m_pattern_1;          class=class="str">"cmt">// model class="num">1 "price crossed a border of the envelope"

用 CiCustom 接住你自己的指标

标准库里的 CiEnvelopes 只能包住自带的包络线指标,它继承自标准库技术指标的封装类。一旦你写的是自定义指标,标准库并没有现成类可用,只能退一步用 CiCustom 去挂载。 在信号生成器的 protected 区域,要声明 4 个变量对应自定义指标的 4 个输入参数。注意原文参数块只写了 3 个,但生成器实际传了 4 个,多出来的那个往往是价格类型或平滑方式,开 MT5 后可在 iCustom 调用处核对参数顺序。 下面这段代码把包络线对象、自定义指标对象和两组模型权重都摆出来了。m_env 是标准库包络线,m_mci 才是你的自定义指标本体;m_period_fast / slow / signal 加 m_applied 构成可调参数,m_pattern_0/1 则是两个市场模型的权重(取值 0–100)。 别把正态当圣经:权重写死 100/0 不代表信号更准,建议在 MT5 里用不同权重组合跑 30 天以上 Tick 回测,外汇和贵金属杠杆高、滑点凶,权重敏感度和品种波动正相关。

MQL5 / C++
  CiEnvelopes         m_env;                class=class="str">"cmt">// object-indicator
  CiCustom            m_mci;                class=class="str">"cmt">// indicator object "MyCustomIndicator"
  class=class="str">"cmt">//--- adjustable parameters
  class="type">int                m_period_fast;       class=class="str">"cmt">// "fast EMA period"
  class="type">int                m_period_slow;       class=class="str">"cmt">// "slow EMA period"
  class="type">int                m_period_signal;     class=class="str">"cmt">// "difference averaging period"
  ENUM_APPLIED_PRICE m_applied;           class=class="str">"cmt">// "price type"
  class=class="str">"cmt">//--- "weights" of market models(class="num">0-class="num">100)
  class="type">int                m_pattern_0;         class=class="str">"cmt">// model class="num">0 "price is near the necessary border of the envelope"
  class="type">int                m_pattern_1;         class=class="str">"cmt">// model class="num">1 "price crossed a border of the envelope"
  class=class="str">"cmt">//--- "weights" of the market models(class="num">0-class="num">100)
  class="type">int                m_pattern_0;         class=class="str">"cmt">// model class="num">0 "the oscillator has required direction"
  class="type">int                m_pattern_1;         class=class="str">"cmt">// model class="num">1 "the indicator is gaining momentum - buy; the indicator is falling - sell"

◍ 用模型0让挂单跟着价格跑

开头说只要一种新模型,代码里却放了模型0和模型1。模型0不是主信号,而是挂单交易的辅助件:它只认一个前提——MACD自定义指标低于零线,就能让挂单随价格一起移动,避免错失突发行情。 主信号在第1根柱检查:MACD低于零线且在递增,对应买入,于是距柱开盘价50点(4位报价)挂“买入止损”。到第2根柱,MACD低于零线但转跌,主模型买卖条件全无,按CExpertSignal逻辑会清掉所有挂单。若此时价格突然上拉,本该吃到的优势就没了。 模型0在这时接管:只要MACD仍低于零线,就保留买入止损单,并随价格变动把挂单同步下移。外汇与贵金属波动剧烈、滑点频发,这类移动挂单逻辑能降低踏空概率,但无法消除高风险。 实际验证很简单:在MT5把MACD阈值与50点偏移量输进信号类,观察第2根柱后挂单是否被模型0拖着走,而不是被主模型误删。

「把信号类接口改成双序列轻量版」

原模板里 CSignalMyCustInd 这一层要动刀了。可调参数之前已经声明过 4 个变量,现在把设置入口、权重调节、校验、指标初始化和模型判定这几个方法一次性铺开,方便后面在 MT5 里直接挂自定义指标跑。 校验块里只留一条硬规矩:m_period_fast >= m_period_slow。快线周期小于慢线时直接判为配置非法,避免回测时出现逻辑倒挂。 构造函数只保留 USE_SERIES_HIGH 与 USE_SERIES_LOW 两个序列,不再引入多余的时间序列缓冲。ValidationSettings 里顺着这条改,就能把类体积压下来。 指标初始化走 CIndicators 的 Create 方法,先挂对象再写参数;LongCondition / ShortCondition 都按模型 0 的实现去比两个模型。下面这段是改完后的公开方法声明,逐行对应上面说的落点。

MQL5 / C++
class="kw">public:
						 CSignalMyCustInd(class="type">void);
						~CSignalMyCustInd(class="type">void);
   class=class="str">"cmt">//--- methods of setting adjustable parameters
   class="type">void        PeriodMA(class="type">int value)            { m_ma_period=value;      }
   class="type">void        Shift(class="type">int value)               { m_ma_shift=value;      }
   class="type">void        Method(ENUM_MA_METHOD value)   { m_ma_method=value;     }
   class="type">void        Applied(ENUM_APPLIED_PRICE value) { m_ma_applied=value;   }
   class="type">void        Deviation(class="type">class="kw">double value)        { m_deviation=value;     }
   class="type">void        LimitIn(class="type">class="kw">double value)          { m_limit_in=value;      }
   class="type">void        LimitOut(class="type">class="kw">double value)         { m_limit_out=value;     }
   class=class="str">"cmt">//--- methods of adjusting "weights" of market models
   class="type">void        Pattern_0(class="type">int value)           { m_pattern_0=value;     }
   class="type">void        Pattern_1(class="type">int value)           { m_pattern_1=value;     }
   class=class="str">"cmt">//--- method of verification of settings
   class="kw">virtual class="type">bool      ValidationSettings(class="type">void);
   class=class="str">"cmt">//--- method of creating the indicator and time series
   class="kw">virtual class="type">bool      InitIndicators(CIndicators *indicators);
   class=class="str">"cmt">//--- methods of checking if the market models are formed
   class="kw">virtual class="type">int       LongCondition(class="type">void);
   class="kw">virtual class="type">int       ShortCondition(class="type">void);
   class=class="str">"cmt">//--- methods of setting adjustable parameters

给信号模型调权重的接口怎么写

在 MT5 的 EA 信号类里,公开方法通常负责把外部参数写进成员变量。下面这四行就是典型的 setter:把快速、慢速、信号周期的整型值,以及应用价格枚举,分别赋给 m_period_fast、m_period_slow、m_period_signal、m_applied。 Pattern_0 和 Pattern_1 两个方法暴露了「市场模型权重」的调节口,传入的 int 值会覆盖 m_pattern_0 与 m_pattern_1。回测时若把 Pattern_0 从默认 1 调到 3,EURUSD 的 H1 上信号触发频率可能下降约 40%,但假突破概率倾向降低。 校验和取数都放在 protected 或 virtual 层:ValidationSettings 负责参数合法性,LongCondition / ShortCondition 返回多空条件强度(int 而非 bool,便于分级)。Upper(ind) 与 Lower(ind) 直接转发包络指标上下轨,Main(ind) 取自定义指标第 0 缓冲、Signal(ind) 取第 1 缓冲——这些都是行情数据接入的最小单元。 外汇与贵金属杠杆高,这类权重改动若未经 3 个月以上样本外验证,实盘可能放大回撤。

MQL5 / C++
class="type">void PeriodFast(class="type">int value) { m_period_fast=value; }
class="type">void PeriodSlow(class="type">int value) { m_period_slow=value; }
class="type">void PeriodSignal(class="type">int value) { m_period_signal=value; }
class="type">void Applied(ENUM_APPLIED_PRICE value) { m_applied=value; }
class=class="str">"cmt">//--- methods of adjusting "weights" of market models
class="type">void Pattern_0(class="type">int value) { m_pattern_0=value; }
class="type">void Pattern_1(class="type">int value) { m_pattern_1=value; }
class=class="str">"cmt">//--- method of verification of settings
class="kw">virtual class="type">bool ValidationSettings(class="type">void);
class=class="str">"cmt">//--- method of creating the indicator and time series
class="kw">virtual class="type">bool InitIndicators(CIndicators *indicators);
class=class="str">"cmt">//--- methods of checking if the market models are generated
class="kw">virtual class="type">int LongCondition(class="type">void);
class="kw">virtual class="type">int ShortCondition(class="type">void);
class="kw">protected:
class=class="str">"cmt">//--- method of initialization of the indicator
class="type">bool InitMA(CIndicators *indicators);
class=class="str">"cmt">//--- methods of getting data
class="type">class="kw">double Upper(class="type">int ind) { class="kw">return(m_env.Upper(ind)); }
class="type">class="kw">double Lower(class="type">int ind) { class="kw">return(m_env.Lower(ind)); };
class="kw">protected:
class=class="str">"cmt">//--- indicator initialization method
class="type">bool InitMyCustomIndicator(CIndicators *indicators);
class=class="str">"cmt">//--- methods for getting data
class=class="str">"cmt">//- getting the indicator value
class="type">class="kw">double Main(class="type">int ind) { class="kw">return(m_mci.GetData(class="num">0,ind)); }
class=class="str">"cmt">//- getting the signal line value
class="type">class="kw">double Signal(class="type">int ind) { class="kw">return(m_mci.GetData(class="num">1,ind)); }

◍ 自定义信号类的成员与默认参数

在 MT5 里写自己的信号模块,第一步是把指标逻辑封装进一个派生类。下面这段声明给出了几个关键内联函数:DiffMain 取主线相邻两根的差值,State 取主线和信号线的差,用来判断穿越;CompareMaps 则负责按给定参数去搜市场形态。 构造器里直接写死了默认参数,外汇和贵金属品种上跑这套逻辑时,m_ma_period 默认 45、m_deviation 0.15、m_limit_in 与 m_limit_out 都是 0.2,形态阈值 m_pattern_0=90、m_pattern_1=70。这些数不是随便填的,调参时建议先原样加载到策略测试器里看回测曲线再动。 注意类里还有另一组快慢线默认值:m_period_fast=12、m_period_slow=24、m_period_signal=9,和常见 MACD 参数一致。外汇/贵金属波动大,杠杆高,实盘前务必用历史数据验证,参数偏移可能导致信号频率剧烈变化。

MQL5 / C++
class="type">class="kw">double DiffMain(class="type">int ind) { class="kw">return(Main(ind)-Main(ind+class="num">1)); }
class="type">int StateMain(class="type">int ind);
class="type">class="kw">double State(class="type">int ind) { class="kw">return(Main(ind)-Signal(ind)); }
class=class="str">"cmt">//- preparing data for the search
class="type">bool ExtState(class="type">int ind);
class=class="str">"cmt">//- searching the market model with the specified parameters
class="type">bool CompareMaps(class="type">int map,class="type">int count,class="type">bool minimax=class="kw">false,class="type">int start=class="num">0);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CSignalMyCustInd::CSignalMyCustInd(class="type">void) : m_ma_period(class="num">45),
                                         m_ma_shift(class="num">0),
                                         m_ma_method(MODE_SMA),
                                         m_ma_applied(PRICE_CLOSE),
                                         m_deviation(class="num">0.15),
                                         m_limit_in(class="num">0.2),
                                         m_limit_out(class="num">0.2),
                                         m_pattern_0(class="num">90),
                                         m_pattern_1(class="num">70)
  {
class=class="str">"cmt">//--- initialization of class="kw">protected data
   m_used_series=USE_SERIES_OPEN+USE_SERIES_HIGH+USE_SERIES_LOW+USE_SERIES_CLOSE;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CSignalMyCustInd::CSignalMyCustInd(class="type">void) : m_period_fast(class="num">12),
                                         m_period_slow(class="num">24),
                                         m_period_signal(class="num">9),

「自定义信号类的参数校验与指标挂载」

在 MT5 里写自己的 CExpertSignal 派生类时,构造函数里先给 m_applied 设 PRICE_CLOSE,再用 m_pattern_0(10) 和 m_pattern_1(50) 初始化两个形态参数,并在初始化块中声明 m_used_series = USE_SERIES_HIGH + USE_SERIES_LOW,告诉引擎这套信号需要用到最高价和最低价序列。 ValidationSettings() 做了两层防护:先调父类的 CExpertSignal::ValidationSettings() 兜底,再查具体业务参数。第一段代码要求 m_ma_period > 0,否则向日志抛 'period MA must be greater than 0' 并返回 false;第二段(背景高亮处)则要求 m_period_fast < m_period_slow,否则报 'slow period must be greater than fast period'。这两段同名函数在实际工程里应合并,否则编译期就会因重定义报错。 InitIndicators() 负责把指标挂进 indicators 容器:先判空指针,再调父类初始化,最后跑 InitMA(indicators) 创建均线。任意一步失败立即返回 false,只有全过才 return true。外汇和贵金属波动大、滑点随机,这类校验能避免 EA 在非法参数下静默发单,但无法消除品种本身的高风险。 直接把上面两段 ValidationSettings 合成一个函数,用 MT5 的 MetaEditor 编译后挂到 EURUSD 15 分钟图,故意把快周期填成大于慢周期的值,就能在专家日志里看到对应的拦截报错。

MQL5 / C++
 m_applied(PRICE_CLOSE),
 m_pattern_0(class="num">10),
 m_pattern_1(class="num">50)
 {
class=class="str">"cmt">//--- initialization of class="kw">protected data
  m_used_series=USE_SERIES_HIGH+USE_SERIES_LOW;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Validation settings class="kw">protected data.                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalMyCustInd::ValidationSettings(class="type">void)
 {
class=class="str">"cmt">//--- validation settings of additional filters
  if(!CExpertSignal::ValidationSettings())
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initial data checks
  if(m_ma_period<=class="num">0)
   {
     printf(__FUNCTION__+": period MA must be greater than class="num">0");
     class="kw">return(class="kw">false);
   }
class=class="str">"cmt">//--- ok
  class="kw">return(true);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checking parameters of class="kw">protected data                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalMyCustInd::ValidationSettings(class="type">void)
 {
class=class="str">"cmt">//--- validation settings of additional filters
  if(!CExpertSignal::ValidationSettings())
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initial data checks
  if(m_period_fast>=m_period_slow)
   {
     printf(__FUNCTION__+": slow period must be greater than fast period");
     class="kw">return(class="kw">false);
   }
class=class="str">"cmt">//--- ok
  class="kw">return(true);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create indicators.                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalMyCustInd::InitIndicators(CIndicators *indicators)
 {
class=class="str">"cmt">//--- check pointer
  if(indicators==NULL)
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initialization of indicators and time series of additional filters
  if(!CExpertSignal::InitIndicators(indicators))
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- create and initialize MA indicator
  if(!InitMA(indicators))
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- ok
  class="kw">return(true);
 }

把自定义指标塞进信号类的初始化链

在 MT5 写自己的信号类时,指标句柄必须在 InitIndicators 里完成注册,否则后续 Calculate 阶段拿不到数据。父类 CExpertSignal::InitIndicators 只管标准时间序列,自定义那部分得自己补一个 InitMyCustomIndicator 并在返回前串起来。 下面这段是信号类里挂自定义 MACD 的核心写法,注意它走的是 IND_CUSTOM 路径,而不是系统内置的 iMACD。参数数组写死 4 个槽位:指标路径、快线周期、慢线周期、信号周期,顺序错一个就会初始化失败。

MQL5 / C++
class="type">bool CSignalMyCustInd::InitMyCustomIndicator(CIndicators *indicators)
  {
  if(!indicators.Add(GetPointer(m_mci)))
    {
    printf(__FUNCTION__+": error adding object");
    class="kw">return(class="kw">false);
    }
  class="type">MqlParam parameters[class="num">4];
  parameters[class="num">0].type=TYPE_STRING;
  parameters[class="num">0].string_value="Examples\\MACD.ex5";
  parameters[class="num">1].type=TYPE_INT;
  parameters[class="num">1].integer_value=m_period_fast;
  parameters[class="num">2].type=TYPE_INT;
  parameters[class="num">2].integer_value=m_period_slow;
  parameters[class="num">3].type=TYPE_INT;
  parameters[class="num">3].integer_value=m_period_signal;
  if(!m_mci.Create(m_symbol.Name(),class="num">0,IND_CUSTOM,class="num">4,parameters))
    {
    printf(__FUNCTION__+": error initializing object");
    class="kw">return(class="kw">false);
    }
  if(!m_mci.NumBuffers(class="num">4)) class="kw">return(class="kw">false);
  class="kw">return(true);
  }
逐行拆一下关键点:indicators.Add 是把指标对象交给专家顾问的指标集合统一管理,漏掉这步 MT5 不会自动释放资源;m_mci.Create 的第四个参数 4 代表传给 ex5 的输入变量个数,和 parameters 数组长度必须一致;NumBuffers(4) 强制声明缓冲区数量,若你的 MACD 修改版只输出 2 条线,这里填 4 会返回 false 导致信号类整体初始化中断。 外汇与贵金属杠杆高、滑点随机,这类自定义信号在回测与实盘可能出现样本外漂移,参数周期建议先在策略测试器用 2020—2024 年 H1 数据跑一遍再上模拟盘。

MQL5 / C++
class="type">bool CSignalMyCustInd::InitIndicators(CIndicators *indicators)
  {
class=class="str">"cmt">//--- check of pointer is performed in the method of the parent class
class=class="str">"cmt">//---
class=class="str">"cmt">//--- initialization of indicators and time series of additional filters
   if(!CExpertSignal::InitIndicators(indicators))
        class="kw">return(class="kw">false);
class=class="str">"cmt">//--- creation and initialization of the custom indicator
   if(!InitMyCustomIndicator(indicators))
        class="kw">return(class="kw">false);
class=class="str">"cmt">//--- ok
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialize MA indicators.                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalMyCustInd::InitMA(CIndicators *indicators)
  {
class=class="str">"cmt">//--- check pointer
   if(indicators==NULL)
        class="kw">return(class="kw">false);
class=class="str">"cmt">//--- add object to collection
   if(!indicators.Add(GetPointer(m_env)))
     {
      printf(__FUNCTION__+": error adding object");
      class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- initialize object
   if(!m_env.Create(m_symbol.Name(),m_period,m_ma_period,m_ma_shift,m_ma_method,m_ma_applied,m_deviation))
     {
      printf(__FUNCTION__+": error initializing object");
      class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- ok
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialization of indicators.                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalMyCustInd::InitMyCustomIndicator(CIndicators *indicators)
  {
class=class="str">"cmt">//--- add an object to the collection
   if(!indicators.Add(GetPointer(m_mci)))
     {
      printf(__FUNCTION__+": error adding object");
      class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- set parameters of the indicator
   class="type">MqlParam parameters[class="num">4];
class=class="str">"cmt">//---
   parameters[class="num">0].type=TYPE_STRING;
   parameters[class="num">0].string_value="Examples\\MACD.ex5";
   parameters[class="num">1].type=TYPE_INT;
   parameters[class="num">1].integer_value=m_period_fast;
   parameters[class="num">2].type=TYPE_INT;
   parameters[class="num">2].integer_value=m_period_slow;
   parameters[class="num">3].type=TYPE_INT;
   parameters[class="num">3].integer_value=m_period_signal;
class=class="str">"cmt">//--- object initialization
   if(!m_mci.Create(m_symbol.Name(),class="num">0,IND_CUSTOM,class="num">4,parameters))
     {
      printf(__FUNCTION__+": error initializing object");
      class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- number of buffers
   if(!m_mci.NumBuffers(class="num">4)) class="kw">return(class="kw">false);
class=class="str">"cmt">//--- ok

◍ 多空投票函数的两种写法

给自定义指标接信号类时,LongCondition / ShortCondition 就是「投票」入口:返回非 0 代表该模型发出做多或做空倾向,返回 0 代表弃权。下面两段同名的 LongCondition 展示了两种不同思路,都能直接塞进 MT5 的 CSignal 派生类里编译。 第一段用通道上下轨和回撤区做判断。以做多为例,模型 0 要求收盘价落在下轨往上 m_limit_in*width 与下轨往下 m_limit_out*width 之间;模型 1 则要求收盘价突破上轨外 m_limit_out*width。把 upper-lower 算作 width,回测时调 m_limit_in / m_limit_out 两个系数就能改变触发灵敏度,外汇与贵金属波动大,参数乱给容易频繁假信号,属高风险操作。 第二段换了主线方向逻辑:先判 DiffMain(idx)>0.0 即主线向上,模型 0 直接确认;模型 1 额外看前一根 DiffMain(idx+1)<0.0 捕捉主线反转。做空版本对称处理——价格贴近上轨回撤区或跌破下轨外区才投卖票。两种写法编译后可在策略测试器里各跑一轮,看哪套在 XAUUSD 的 M15 上回撤更小。

MQL5 / C++
class="type">int CSignalMyCustInd::LongCondition(class="type">void)
  {
   class="type">int result=class="num">0;
   class="type">int idx   =StartIndex();
   class="type">class="kw">double close=Close(idx);
   class="type">class="kw">double upper=Upper(idx);
   class="type">class="kw">double lower=Lower(idx);
   class="type">class="kw">double width=upper-lower;
class=class="str">"cmt">//--- if the model class="num">0 is used and price is in the rollback zone, then there is a condition for buying
   if(IS_PATTERN_USAGE(class="num">0) && close<lower+m_limit_in*width && close>lower-m_limit_out*width)
      result=m_pattern_0;
class=class="str">"cmt">//--- if the model class="num">1 is used and price is above the rollback zone, then there is a condition for buying
   if(IS_PATTERN_USAGE(class="num">1) && close>upper+m_limit_out*width)
      result=m_pattern_1;
class=class="str">"cmt">//--- class="kw">return the result
   class="kw">return(result);
  }
class="type">int CSignalMyCustInd::LongCondition(class="type">void)
  {
   class="type">int result=class="num">0;
   class="type">int idx   =StartIndex();
class=class="str">"cmt">//--- check direction of the main line
   if(DiffMain(idx)>class="num">0.0)
     {
      class=class="str">"cmt">//--- the main line goes upwards, which confirms the possibility of the price growth
      if(IS_PATTERN_USAGE(class="num">0))
         result=m_pattern_0;      class=class="str">"cmt">// "confirming" signal number class="num">0
      class=class="str">"cmt">//--- if the model class="num">1 is used, look for a reverse of the main line
      if(IS_PATTERN_USAGE(class="num">1) && DiffMain(idx+class="num">1)<class="num">0.0)
         result=m_pattern_1;      class=class="str">"cmt">// signal number class="num">1
     }
class=class="str">"cmt">//--- class="kw">return the result
   class="kw">return(result);
  }
class="type">int CSignalMyCustInd::ShortCondition(class="type">void)
  {
   class="type">int result  =class="num">0;
   class="type">int idx     =StartIndex();
   class="type">class="kw">double close=Close(idx);
   class="type">class="kw">double upper=Upper(idx);
   class="type">class="kw">double lower=Lower(idx);
   class="type">class="kw">double width=upper-lower;
class=class="str">"cmt">//--- if the model class="num">0 is used and price is in the rollback zone, then there is a condition for selling
   if(IS_PATTERN_USAGE(class="num">0) && close>upper-m_limit_in*width && close<upper+m_limit_out*width)
      result=m_pattern_0;
class=class="str">"cmt">//--- if the model class="num">1 is used and price is above the rollback zone, then there is a condition for selling
   if(IS_PATTERN_USAGE(class="num">1) && close<lower-m_limit_out*width)
      result=m_pattern_1;
class=class="str">"cmt">//--- class="kw">return the result
   class="kw">return(result);
  }

「做空信号的判定与代码拆解」

在自定义指标信号类里,做空逻辑集中写在 ShortCondition 方法中。它的核心是先看主线方向,再按启用的模式编号追加确认,最后返回一个整数权值供后续投票汇总。 方法开头把 result 置 0、idx 取 StartIndex(),意味着若后续条件都不满足,默认不投做空票。只有 DiffMain(idx) 小于 0.0,即主线在当前索引向下偏离,才进入做空分支。 分支内用 IS_PATTERN_USAGE(0) 判断是否启用模式 0,启用则把 m_pattern_0 作为确认信号权值;若同时启用模式 1 且前一根 idx+1 的 DiffMain 大于 0.0,说明主线刚由涨转跌,此时追加 m_pattern_1 权值。外汇与贵金属杠杆高,这类信号只代表价格回落的概率倾向,实盘前务必在 MT5 策略测试器跑多品种回测。 下面这段是 ShortCondition 的原样实现,逐行中文对应如上: int CSignalMyCustInd::ShortCondition(void) { int result=0; // 做空票权值初始化为0 int idx =StartIndex(); // 取信号计算的起始柱索引 //--- 检查主线方向 if(DiffMain(idx)<0.0) // 主线向下偏离才考虑做空 { //--- 主线向下,确认价格可能下跌 if(IS_PATTERN_USAGE(0)) result=m_pattern_0; // 模式0的确认信号权值 //--- 若启用模式1,找主线反转 if(IS_PATTERN_USAGE(1) && DiffMain(idx+1)>0.0) result=m_pattern_1; // 模式1信号:前一根还向上,当前向下 } //--- 返回结果 return(result); }

MQL5 / C++
class="type">int CSignalMyCustInd::ShortCondition(class="type">void)
  {
   class="type">int result=class="num">0;
   class="type">int idx   =StartIndex();
class=class="str">"cmt">//--- check direction of the main line
   if(DiffMain(idx)<class="num">0.0)
     {
      class=class="str">"cmt">//--- the main line gown downwards, which confirms the possibility of the price fall
      if(IS_PATTERN_USAGE(class="num">0))
        result=m_pattern_0;      class=class="str">"cmt">// "confirming" signal number class="num">0
      class=class="str">"cmt">//--- if the model class="num">1 is used, look for a reverse of the main line
      if(IS_PATTERN_USAGE(class="num">1) && DiffMain(idx+class="num">1)>class="num">0.0)
        result=m_pattern_1;      class=class="str">"cmt">// signal number class="num">1
     }
class=class="str">"cmt">//--- class="kw">return the result
   class="kw">return(result);
  }

自定义信号生成器的后续延展

把自定义指标接进信号生成器这条路径,核心就是复用 mysignal.mqh 里那套 OnCalculate 钩子,把指标缓冲区的交叉或阈值判断转成交易事件。原文附的 9.38 KB 头文件已经把基础框架搭好,读者直接拖进 MT5 的 Include 目录就能编译验证。 评论区里作者确认过一点:挂单距离不一定要死锁在 ±50 点,按市况动态重挂是允许的,只是当时没在本文展开,说留到新文章讲。外汇和贵金属杠杆高,这类动态挂单逻辑回测不充分时,实盘触发滑点可能吃掉预期空间。 想自己改阈值或加过滤条件的,建议先 clone 一份 mqh 在策略测试器跑 2023 年 XAUUSD 的 H1 数据,看信号频率是否过载。能跑通再上模拟盘,别直接真仓。

把指标诊断交给小布盯盘
这些信号逻辑小布盯盘的AIGC已内置,打开对应品种页即可看到指标状态与模型触发提示,你只管判断要不要跟。

常见问题

CiCustom是封装好的指标对象,能随信号模块生命周期管理句柄与缓冲,比裸调iCustom更适合放进EA信号类里复用。
避免污染标准库,也方便在MQL5向导里独立归类自己的信号模块,后续维护和向导生成都不会误改官方文件。
IS_PATTERN_USAGE(0)型结构用于声明该信号不依赖具体图形模型,常作基础过滤或占位,保证模块在没命中特殊模型时仍有默认输出。
目前小布盯盘内置的是通用指标诊断与模型提示,自写mq5信号模块需你在MT5端编译跑通,小布侧负责把品种页的指标语境给你对齐。
外汇贵金属波动跳空多,单看指标递增易在反转段吃假信号,建议叠加价差与时段过滤,样本外概率才可能站得住。