扩展 MQL5 标准库和重用代码(基础篇)
📘

扩展 MQL5 标准库和重用代码(基础篇)

第 1/3 篇

把标准库当积木用

MQL5 自带的标准库(Standard Library)不是黑盒,而是一堆可继承、可改写的 C++ 风格类。想把别人写好的 CTrade、CIndicator 再包一层,直接 include 后派生子类即可,不必从零写下单逻辑。

  • 年 5 月官方发布的这组示例,在站内累计被查看约 3485 次、引发 22 条讨论,说明「扩展而非重写」是社区早就在用的务实路线。外汇与贵金属杠杆高,复用代码前务必在策略测试器用历史数据跑一遍,参数错配可能放大回撤。

实际动手时,新建一个 .mqh 把标准库头文件引进来,只重写你需要的虚函数,比复制整段交易模板更利于后期维护。

「为什么先学会用标准库写代码」

MQL5 标准库本质是一套面向对象的现成类集合,目的是让开发者少造轮子:代码复用、可靠性、灵活性、易维护,这几条是它设计时就承诺的红利。但标准库不可能覆盖所有交易者的个性化需求,当你要做更复杂的工具——比如多货币 EA——如果连标准库的写法都没吃透,自己堆出来的项目很容易在后期崩维护。 原文给出的现实推力很直接:作者本人就是因为要开发复杂多货币 EA,才决定从根基上把项目按标准库理念重构。也就是说,框架思维不是学院派要求,而是复杂度逼出来的生存技能。 一个可验证的落点:打开 MT5 的 MetaEditor,在「文件 → 新建 → 指标」里选「使用标准库模板」,你能直接看到 CSymbolInfo、CIndicator 这类基类是怎么被继承的。先照着抄三遍,比直接手搓裸代码更能摸准这套框架的脉络。外汇与贵金属杠杆高、滑点诡异,复杂 EA 任何架构疏忽都可能放大实盘风险,按标准库规范写至少能把低级坑提前堵住。

◍ 把官方峰谷指标拉进 MT5

在 MT5 终端的 code base 里直接调取 MetaQuotes 发布的峰谷(ZigZag)指标,下载完成后会在 Indicators 目录下生成 zigzag.mq5 与编译好的 zigzag.ex5 两个文件,这是后续所有价格行为改写的基础件。 完整源文件有 298 行,这里只截取输入参数、全局变量与初始化段,方便你对着下文调参时不迷路。ExtDepth=12、ExtDeviation=5、ExtBackstep=3 是出厂默认值,分别对应回看深度、最小偏移点数与回溯步长。 三个缓冲区分工很硬:ZigzagBuffer 画主线,HighMapBuffer 和 LowMapBuffer 只做内部极值计算不显示。OnInit 里用 SetIndexBuffer 绑定索引,PlotIndexSetString 把参数拼进副图标签,便于多周期对照。 外汇与贵金属波动剧烈,ZigZag 仅作结构识别辅助,实际信号滞后可能性较高,切勿单独用作进出场依据。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 ZigZag.mq5 |
class=class="str">"cmt">//|                                     Copyright class="num">2009, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "class="num">2009, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">3
class="macro">#class="kw">property indicator_plots   class="num">1
class=class="str">"cmt">//---- plot Zigzag
class="macro">#class="kw">property indicator_label1  "Zigzag"
class="macro">#class="kw">property indicator_type1   DRAW_SECTION
class="macro">#class="kw">property indicator_color1  Red
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- class="kw">input parameters
class="kw">input class="type">int      ExtDepth=class="num">12;
class="kw">input class="type">int      ExtDeviation=class="num">5;
class="kw">input class="type">int      ExtBackstep=class="num">3;
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double      ZigzagBuffer[];      class=class="str">"cmt">// main buffer
class="type">class="kw">double      HighMapBuffer[];     class=class="str">"cmt">// highs
class="type">class="kw">double      LowMapBuffer[];      class=class="str">"cmt">// lows
class="type">int         level=class="num">3;             class=class="str">"cmt">// recounting depth
class="type">class="kw">double      deviation;           class=class="str">"cmt">// deviation in points
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- indicator buffers mapping
   SetIndexBuffer(class="num">0,ZigzagBuffer,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,HighMapBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(class="num">2,LowMapBuffer,INDICATOR_CALCULATIONS);
class=class="str">"cmt">//--- set class="type">short name and digits   
   PlotIndexSetString(class="num">0,PLOT_LABEL,"ZigZag("+(class="type">class="kw">string)ExtDepth+","+(class="type">class="kw">string)ExtDeviation+","+(class="type">class="kw">string)ExtBackstep+")");
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
class=class="str">"cmt">//--- set empty value

把偏差换算成点数再初始化

指标初始化收尾时,先把空值画刷关掉:PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0) 让第 0 号绘制缓冲在数值为 0.0 时不画任何东西,避免零值被当成有效线。 随后把用户输入的 ExtDeviation(以价格单位计)乘上 _Point,得到真实点数偏差 deviation。例如 EURUSD 的 _Point 是 0.00001,若 ExtDeviation=50,deviation 就是 0.00050,也就是 5 个点。这一步必须在循环前完成,否则后续逐根计算的容差会错位。 最后 return(INIT_SUCCEEDED) 告知终端初始化通过。MT5 里若漏掉这句或返回别的值,自定义指标不会加载到图表。

MQL5 / C++
  PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,class="num">0.0);
class=class="str">"cmt">//--- to use in cycle
  deviation=ExtDeviation*_Point;
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }

「从程序式到面向对象:峰谷指标的两种接法」

想把峰谷指标塞进 MQL5 标准库,先用自顶向下思路看全局:系统层面决定交互范式,再下沉到局部实现。中级开发者多半会直接写程序式 EA,用 iCustom 调原生 ZigZag;高级开发者则倾向做成标准库风格的面向对象类,用起来和内置指标无异。 写两个虚拟 EA 最能暴露差异。下面这段程序式版本在 H1 周期、EURUSD 上加载 ZigZag(参数 12/5/3),仅取最近 2 根缓冲做演示,足以让人在 MT5 里跑通验证。 面向对象方案胜在直观:类封装后,与峰谷功能交互比程序式版本轻松得多。OOP 让问题建模变简单,代码复用拉低维护成本、提升可靠性与灵活性;它还能定义 ADT(抽象数据类型),把传统类型概念从具体语言里抽出来。 别把正态当圣经 原文用正二十面体类比「基于立体理念构建应用」来保障设计持久性,这只是隐喻,不是回测结论。外汇与贵金属杠杆高、滑点随机,任何指标封装都只是降低编码熵,不预示胜率。

MQL5 / C++
class=class="str">"cmt">//+----------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 ExpertOriginalZigZag.mq5   |
class=class="str">"cmt">//|    Copyright © class="num">2013, Laplacianlab - Jordi Bassagañas  | 
class=class="str">"cmt">//+----------------------------------------------------------------------+
class=class="str">"cmt">//--- EA properties
class="macro">#class="kw">property copyright     "Copyright © class="num">2013, Laplacianlab - Jordi Bassagañas"
class="macro">#class="kw">property link          "[MQL5官方文档]
class="macro">#class="kw">property version       "class="num">1.00"
class="macro">#class="kw">property description  "This dummy Expert Advisor is just for showing how to use the original MetaQuotes&class="macro">#x27; ZigZag indicator."
class=class="str">"cmt">//--- EA inputs
class="kw">input ENUM_TIMEFRAMES   EAPeriod=PERIOD_H1;
class="kw">input class="type">class="kw">string            CurrencyPair="EURUSD";
class=class="str">"cmt">//--- global variables
class="type">int     zigZagHandle;
class="type">class="kw">double  zigZagBuffer[];
class="type">class="kw">double  zigZagHigh[];
class="type">class="kw">double  zigZagLow[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   zigZagHandle=iCustom(CurrencyPair,EAPeriod,"zigzag",class="num">12,class="num">5,class="num">3);
   ArraySetAsSeries(zigZagBuffer,true);
   ArraySetAsSeries(zigZagHigh,true);
   ArraySetAsSeries(zigZagLow,true);   
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   IndicatorRelease(zigZagHandle);
   ArrayFree(zigZagBuffer);
   ArrayFree(zigZagHigh);
   ArrayFree(zigZagLow);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   class=class="str">"cmt">//--- refresh data  
   if(CopyBuffer(zigZagHandle,class="num">0,class="num">0,class="num">2,zigZagBuffer)<class="num">0)
   {
      Print("Can&class="macro">#x27;t copy ZigZag buffer class="num">0!");
      class="kw">return;
   }

◍ 用面向对象封装把 ZigZag 接进 EA

把经典 ZigZag 指标塞进 EA 时,直接用 iCustom 加 CopyBuffer 容易把缓冲区序号搞混。下面这段先拷缓冲区 1 和 2 分别存高低点,任何一次 CopyBuffer 返回值小于 0 就打印并报错退出,避免后续拿到空数组还硬算。 if(CopyBuffer(zigZagHandle,1,0,2,zigZagHigh)<0) { Print("Can't copy ZigZag buffer 1!"); return; } if(CopyBuffer(zigZagHandle,2,0,2,zigZagLow)<0) { Print("Can't copy ZigZag buffer 2!"); return; } //--- print values if(zigZagBuffer[0]!=0) Print("zigZagBuffer[0]: ", zigZagBuffer[0]); if(zigZagHigh[0]!=0) Print("zigZagHigh[0]: ", zigZagHigh[0]); if(zigZagLow[0]!=0) Print("zigZagLow[0]: ", zigZagLow[0]); } 更干净的做法是走标准库 CiZigZag 封装:OnInit 里 new 一个对象,Create 传品种、周期加三个参数 12、5、3(分别对应回调点数、最小幅度、拐点步长),EA 卸载时 delete 掉即可,不用自己管句柄。 #include <..\Include\Indicators\Custom\Trend.mqh> input ENUM_TIMEFRAMES EAPeriod=PERIOD_H1; input string CurrencyPair="EURUSD"; CiZigZag *ciZigZag; int OnInit() { ciZigZag=new CiZigZag; ciZigZag.Create(CurrencyPair,EAPeriod,12,5,3); return(0); } void OnDeinit(const int reason) { delete(ciZigZag); } 开 MT5 把这段贴进一个新 EA,把 CurrencyPair 改成你常看的 XAUUSD 或 EURUSD,H1 周期下加载,专家日志里就能看到最近两个拐点的高低值;外汇和贵金属杠杆高,ZigZag 只是结构参考,信号失效概率不低,别直接当入场依据。

MQL5 / C++
if(CopyBuffer(zigZagHandle,class="num">1,class="num">0,class="num">2,zigZagHigh)<class="num">0)
{
   Print("Can&class="macro">#x27;t copy ZigZag buffer class="num">1!");
   class="kw">return;
}
if(CopyBuffer(zigZagHandle,class="num">2,class="num">0,class="num">2,zigZagLow)<class="num">0)
{
   Print("Can&class="macro">#x27;t copy ZigZag buffer class="num">2!");
   class="kw">return;
}
class=class="str">"cmt">//--- print values
if(zigZagBuffer[class="num">0]!=class="num">0) Print("zigZagBuffer[class="num">0]: ", zigZagBuffer[class="num">0]);
if(zigZagHigh[class="num">0]!=class="num">0) Print("zigZagHigh[class="num">0]: ", zigZagHigh[class="num">0]);
if(zigZagLow[class="num">0]!=class="num">0) Print("zigZagLow[class="num">0]: ", zigZagLow[class="num">0]);
}

class="macro">#include <..\Include\Indicators\Custom\Trend.mqh>
class="macro">#class="kw">property copyright "Copyright © class="num">2013, Laplacianlab - Jordi Bassagañas"
class="macro">#class="kw">property link "[MQL5官方文档]
class="macro">#class="kw">property version "class="num">1.00"
class="macro">#class="kw">property description "This dummy Expert Advisor is just for showing how to use the object-oriented version of MetaQuotes&class="macro">#x27; ZigZag indicator."
class="kw">input ENUM_TIMEFRAMES EAPeriod=PERIOD_H1;
class="kw">input class="type">class="kw">string CurrencyPair="EURUSD";
CiZigZag *ciZigZag;
class="type">int OnInit()
{
   ciZigZag=new CiZigZag;
   ciZigZag.Create(CurrencyPair,EAPeriod,class="num">12,class="num">5,class="num">3);
   class="kw">return(class="num">0);
}
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
   class="kw">delete(ciZigZag);
}

常见问题

把标准库里的峰谷指标类当积木用,包含对应头文件后实例化对象即可,不用重复造轮子。
偏差参数需要先换算成点数再传给初始化函数,直接用价格小数会导致对象创建失败。
小布可以读取你的代码逻辑并提示标准库接法错误,比如偏差未转点数、头文件缺失等,省去手动排查。
新手先用程序式跑通信号,再用类封装隔离逻辑,后续换品种或调参更不容易改崩。
只要保留原接口约定、只替换业务片段,重用比复制整段示例更可控,也方便回溯官方更新。