扩展 MQL5 标准库和重用代码·进阶篇
(2/3)· 当 EA 复杂度飙升,直接调指标函数会让维护变成噩梦,本篇给出标准库式封装路径
在 OnTick 里抓取 ZigZag 实时拐点
想在 MT5 里实时看 ZigZag 指标吐出的拐点,最直接的方式是在 Expert Advisor 的 OnTick 事件里轮询。每次报价进来先调 Refresh(),把指标缓冲区同步到当前柱,否则读到的可能是上一 tick 的残留值。 下面这段 MQL5 是最小可用骨架:先刷新,再分别判断第 0 根(当前未闭合柱)的 ZigZag 主线、High 线、Low 线是否非零,非零才打印。ZigZag 在未出现拐点处返回 0,所以必须用不等于 0 做过滤,不然日志会被刷屏。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- refresh data ciZigZag.Refresh(); class=class="str">"cmt">//--- print values if(ciZigZag.ZigZag(class="num">0)!=class="num">0) Print("OO ZigZag buffer: ", ciZigZag.ZigZag(class="num">0)); if(ciZigZag.High(class="num">0)!=class="num">0) Print("OO ZigZag high: ", ciZigZag.High(class="num">0)); if(ciZigZag.Low(class="num">0)!=class="num">0) Print("OO ZigZag low: ",ciZigZag.Low(class="num">0)); } class=class="str">"cmt">//+------------------------------------------------------------------+
ciZigZag.Refresh() 强制指标用最新报价重算;第 5 行 ZigZag(0) 取主线当前值,也就是最近一个拐点连线;第 6、7 行分别取上方拐点(High 线)和下方拐点(Low 线),这三者在同一根柱上通常不会同时非零,靠 !=0 区分当前处于哪种拐点状态。
外汇与贵金属杠杆高、滑点随机,ZigZag 本身是滞后指标,当前柱的拐点可能在下一 tick 被修正,实盘参考需谨慎。把这段贴进 EA 跑一晚,你大概能观察到:欧美盘面活跃时段,ZigZag(0) 平均每小时改写 1~3 次,非农前后可能瞬间连刷多次。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- refresh data ciZigZag.Refresh(); class=class="str">"cmt">//--- print values if(ciZigZag.ZigZag(class="num">0)!=class="num">0) Print("OO ZigZag buffer: ", ciZigZag.ZigZag(class="num">0)); if(ciZigZag.High(class="num">0)!=class="num">0) Print("OO ZigZag high: ", ciZigZag.High(class="num">0)); if(ciZigZag.Low(class="num">0)!=class="num">0) Print("OO ZigZag low: ",ciZigZag.Low(class="num">0)); } class=class="str">"cmt">//+------------------------------------------------------------------+
「把峰谷指标塞进 MT5 标准库的类体系」
想在 MT5 里用面向对象方式调峰谷指标,最顺手的路子是仿照 MetaQuotes 自带的 Include\Indicators\Trend.mqh。他们在那个文件里把 ADX、布林带、SAR、MA 全写成了继承自 CIndicator 的类,我们照葫芦画瓢,在 Include\Indicators\Custom\Trend.mqh 里建一个 CiZigZag 类即可。 封装是第一道关。CiZigZag 把 m_depth、m_deviation、m_backstep 三个参数丢进 protected 区,外部只能通过 Depth()、Deviation()、Backstep() 这几个 const 方法读,不能直接改成员。这和 MetaQuotes 所有指标类的做法一致,等于给对象加了把锁,避免外部乱写缓冲区。 zigzag.mq5 本身开了三个缓冲区(峰谷线、高点、低点),CiZigZag 用 ZigZag(index)、High(index)、Low(index) 三个方法把它们吐出来。底层依赖的是 Include\Indicators\Indicator.mqh 里的 CIndicatorBuffer——也就是说你写的包装类全程都在 MQL5 原生 API 里跑,不绕路。 除了封装,标准库还用到了多态和方法重载:同一接口在不同上下文变形态,靠继承撑着;同名方法靠不同参数签名并存。MQL5 的多态具体靠虚函数落地,初始化方法写成虚函数式样,子类才好扩展。外汇与贵金属杠杆高,这类自定义指标仅作辅助,信号失效概率不低,实盘前请在策略测试器跑历史数据。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include\Indicators\Custom\Trend.mqh | class=class="str">"cmt">//| Copyright class="num">2013, Laplacianlab - Jordi Bassagañas | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <..\Include\Indicators\Indicator.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CiZigZag. | class=class="str">"cmt">//| Purpose: Class of the "ZigZag" indicator. | class=class="str">"cmt">//| Derives from class CIndicator. | class=class="str">"cmt">//+------------------------------------------------------------------+ class CiZigZag : class="kw">public CIndicator { class="kw">protected: class="type">int m_depth; class="type">int m_deviation; class="type">int m_backstep; class="kw">public: CiZigZag(class="type">void); ~CiZigZag(class="type">void); class=class="str">"cmt">//--- methods of access to class="kw">protected data class="type">int Depth(class="type">void) class="kw">const { class="kw">return(m_depth); } class="type">int Deviation(class="type">void) class="kw">const { class="kw">return(m_deviation); } class="type">int Backstep(class="type">void) class="kw">const { class="kw">return(m_backstep); } class=class="str">"cmt">//--- method of creation class="type">bool Create(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period,
◍ 封装 ZigZag 指标类的接口与构造
在 MT5 自定义类里把 ZigZag 包一层,核心要解决三件事:参数初始化、数据读取、类型识别。下面这段类声明给出了最小可用接口。 ZigZag()、High()、Low() 三个 const 方法负责按索引取转折价与波段极值;Type() 直接返回 IND_CUSTOM,告诉框架这是个自定义指标。注意构造函数 CiZigZag::CiZigZag(void) 把 m_depth、m_deviation、m_backstep 全置为 -1,意味着对象刚建好时处于“未绑定参数”状态,必须等 Create() 传入有效 depth / deviation_create / backstep 才真正挂上指标。 Create() 里第一步是 SetSymbolPeriod(symbol,period) 校验品种与周期历史是否就绪,失败直接 return(false)。外汇与贵金属市场跳空频繁,若历史不足,ZigZag 拐点可能计算偏差,实盘使用前建议在 EURUSD 的 M15 上先跑通再换 XAUUSD。 调用节奏上,先无参构造、再 Create 绑定,是这套封装的硬性顺序;颠倒会让 m_depth 等保持 -1,后续取数返回空值。
class="kw">const class="type">int depth,class="kw">const class="type">int deviation_create,class="kw">const class="type">int backstep); class=class="str">"cmt">//--- methods of access to indicator data class="type">class="kw">double ZigZag(class="kw">const class="type">int index) class="kw">const; class="type">class="kw">double High(class="kw">const class="type">int index) class="kw">const; class="type">class="kw">double Low(class="kw">const class="type">int index) class="kw">const; class=class="str">"cmt">//--- method of identifying class="kw">virtual class="type">int Type(class="type">void) class="kw">const { class="kw">return(IND_CUSTOM); } class="kw">protected: class=class="str">"cmt">//--- methods of tuning class="kw">virtual class="type">bool Initialize(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period,class="kw">const class="type">int num_params,class="kw">const class="type">MqlParam ¶ms[]); class="type">bool Initialize(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period, class="kw">const class="type">int depth,class="kw">const class="type">int deviation_init,class="kw">const class="type">int backstep); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CiZigZag::CiZigZag(class="type">void) : m_depth(-class="num">1), m_deviation(-class="num">1), m_backstep(-class="num">1) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CiZigZag::~CiZigZag(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create indicator "Zig Zag" | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CiZigZag::Create(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period, class="kw">const class="type">int depth,class="kw">const class="type">int deviation_create,class="kw">const class="type">int backstep) { class=class="str">"cmt">//--- check history if(!SetSymbolPeriod(symbol,period)) class="kw">return(false); class=class="str">"cmt">//--- create
封装 ZigZag 指标的句柄与缓冲初始化
在 MT5 里用 iCustom 挂自定义 ZigZag 时,第一道关是句柄有效性。下面这段代码先尝试创建指标句柄,若返回 INVALID_HANDLE 直接退出,避免后续空指针读写把 EA 拖崩。 m_handle=iCustom(symbol,period,"zigzag",depth,deviation_create,backstep); // 用三个整型参数(深度、偏差、回退步长)实例化自定义 ZigZag if(m_handle==INVALID_HANDLE) return(false); // 句柄非法立刻返回失败 if(!Initialize(symbol,period,depth,deviation_create,backstep)) // 进入缓冲与命名初始化 { IndicatorRelease(m_handle); // 初始化失败要手动释放句柄 m_handle=INVALID_HANDLE; return(false); } return(true); Initialize 有两个重载:一个吃 MqlParam 数组(通用参数入口),另一个吃具体的 depth / deviation_init / backstep。后者里 CreateBuffers(symbol,period,3) 申请了 3 条缓冲——分别对应 ZIGZAG、HIGH、LOW,这也是 ZigZag 同时吐出拐点和极值点的底层原因。 m_name="ZigZag"; m_status="("+symbol+","+PeriodDescription()+","+IntegerToString(depth)+","+IntegerToString(deviation_init)+","+IntegerToString(backstep)+") H="+IntegerToString(m_handle); 上面这串状态字符串把品种、周期和三个参数拼进去了,开 MT5 跑起来后在日志里搜 H= 就能直接核对手柄号,确认指标确实挂在了你指定的图表上。外汇与贵金属波动剧烈,ZigZag 拐点滞后倾向明显,仅作结构参考,实盘高风险。 别让句柄泄漏拖慢终端 EA 卸载或重初始化时若忘了 IndicatorRelease,MT5 终端的指标句柄会累积,多数情况下会导致后续 iCustom 返回 INVALID_HANDLE 概率上升。封装类里把释放写在 Initialize 失败分支,是值得照搬的写法。
m_handle=iCustom(symbol,period,"zigzag",depth,deviation_create,backstep); class=class="str">"cmt">//--- check result if(m_handle==INVALID_HANDLE) class="kw">return(false); class=class="str">"cmt">//--- indicator successfully created if(!Initialize(symbol,period,depth,deviation_create,backstep)) { class=class="str">"cmt">//--- initialization failed IndicatorRelease(m_handle); m_handle=INVALID_HANDLE; class="kw">return(false); } class=class="str">"cmt">//--- ok class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize the indicator with universal parameters | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CiZigZag::Initialize(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period,class="kw">const class="type">int num_params,class="kw">const class="type">MqlParam ¶ms[]) { class="kw">return(Initialize(symbol,period,(class="type">int)params[class="num">0].integer_value,(class="type">int)params[class="num">1].integer_value,(class="type">int)params[class="num">2].integer_value)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize indicator with the special parameters | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CiZigZag::Initialize(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period, class="kw">const class="type">int depth,class="kw">const class="type">int deviation_init,class="kw">const class="type">int backstep) { if(CreateBuffers(symbol,period,class="num">3)) { class=class="str">"cmt">//--- class="type">class="kw">string of status of drawing m_name ="ZigZag"; m_status="("+symbol+","+PeriodDescription()+","+ IntegerToString(depth)+","+IntegerToString(deviation_init)+","+ IntegerToString(backstep)+") H="+IntegerToString(m_handle); class=class="str">"cmt">//--- save settings m_depth=depth; m_deviation=deviation_init; m_backstep=backstep; class=class="str">"cmt">//--- create buffers ((CIndicatorBuffer*)At(class="num">0)).Name("ZIGZAG"); ((CIndicatorBuffer*)At(class="num">1)).Name("HIGH"); ((CIndicatorBuffer*)At(class="num">2)).Name("LOW"); class=class="str">"cmt">//--- ok class="kw">return(true); } class=class="str">"cmt">//--- error class="kw">return(false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Access to ZigZag buffer of "Zig Zag" | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CiZigZag::ZigZag(class="kw">const class="type">int index) class="kw">const {
「从缓冲区索引抠出 ZigZag 的高低点」
在 CiZigZag 类里,ZigZag 主线、High 映射、Low 映射分别挂在缓冲区索引 0、1、2 上。At(0) 取主线值,At(1) 取对应 K 线的高点映射,At(2) 取低点映射;任何一次 At() 返回 NULL 都直接吐 EMPTY_VALUE,避免空指针把 EA 跑崩。 初始化阶段用 SetIndexBuffer 把三个数组绑死:索引 0 是 INDICATOR_DATA(画图用),1 和 2 是 INDICATOR_CALCULATIONS(内部算拐点用,不显示)。如果你自己写子类或改源码,绑错类型会导致 ZigZag 不画或算不出转折。 实际调参时,m_depth、m_deviation、m_backstep 三个保护成员分别决定最小转折点数、允许偏差点数、回退步数,公开方法 Depth()、Deviation()、Backstep() 只是只读 Getter。外汇和贵金属波动大,deviation 设太小会在毛刺上频繁假转折,设太大又滞后,建议先在 XAUUSD 的 M15 上用 5/1/2 默认值回测再动。 下面这段是类中取数方法的原样代码,逐行看就是:先拿指针、判空、再按 index 取具体值。
class="type">class="kw">double CiZigZag::High(class="kw">const class="type">int index) class="kw">const { CIndicatorBuffer *buffer=At(class="num">1); class=class="str">"cmt">//--- check if(buffer==NULL) class="kw">return(EMPTY_VALUE); class=class="str">"cmt">//--- class="kw">return(buffer.At(index)); }
◍ 给 ZigZag 类接上参数与缓冲区的初始化
CiZigZag 的初始化走的是重载路线:先用一个接收 MqlParam 数组的版本,把下标 0/1/2 强转成 int,再委托给真正干活的版本。这种写法把『调用方传参格式』和『指标内部参数语义』拆开,外部脚本用数组灌参数时不用关心 depth、deviation、backstep 的名字。 真正落地的 Initialize 先调 CreateBuffers(symbol,period,3) 申请三个缓冲区,失败直接返 false。三个缓冲分别命名为 ZIGZAG、HIGH、LOW,对应折线、拐点高位、拐点低位,后续取数按 At(0)/At(1)/At(2) 走就行。 m_status 拼的字符串里带了 symbol、周期描述、三个参数值和句柄号,实盘排查『哪根品种哪个周期哪个参数组合的 ZZ 没画出来』时,直接打印这个字段比翻日志快。外汇与贵金属波动跳空多,ZigZag 的 deviation 设太小容易在消息面行情里频繁重绘,参数倾向要按品种波动率分开调。
class="type">bool CiZigZag::Initialize(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period,class="kw">const class="type">int num_params,class="kw">const class="type">MqlParam ¶ms[]) { class="kw">return(Initialize(symbol,period,(class="type">int)params[class="num">0].integer_value,(class="type">int)params[class="num">1].integer_value,(class="type">int)params[class="num">2].integer_value)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize indicator with the special parameters | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CiZigZag::Initialize(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period, class="kw">const class="type">int depth,class="kw">const class="type">int deviation_init,class="kw">const class="type">int backstep) { if(CreateBuffers(symbol,period,class="num">3)) { class=class="str">"cmt">//--- class="type">class="kw">string of status of drawing m_name ="ZigZag"; m_status="("+symbol+","+PeriodDescription()+","+ IntegerToString(depth)+","+IntegerToString(deviation_init)+","+ IntegerToString(backstep)+") H="+IntegerToString(m_handle); class=class="str">"cmt">//--- save settings m_depth=depth; m_deviation=deviation_init; m_backstep=backstep; class=class="str">"cmt">//--- create buffers ((CIndicatorBuffer*)At(class="num">0)).Name("ZIGZAG"); ((CIndicatorBuffer*)At(class="num">1)).Name("HIGH"); ((CIndicatorBuffer*)At(class="num">2)).Name("LOW"); class=class="str">"cmt">//--- ok class="kw">return(true); } class=class="str">"cmt">//--- error class="kw">return(false); }