轻松快捷开发 MetaTrader 程序的函数库(第一部分)。 概念,数据管理和首期成果(基础篇)
「从零搭一个 MT5 函数库的底子」
想把反复写的指标、EA 逻辑收进一个函数库,第一步不是写算法,而是先把数据结构定清楚。原文在 2019-04-16 发布的这套思路,核心就是把价格、时间、自定义缓冲都装进统一的结构体,后面调用才不容易乱。 首期成果里,作者先实现了一个最基础的管理类,能存 K 线数据和指标句柄。这种做法让后续扩展模块时,不用每次都重写数据读取。 在 MT5 里验证很简单:新建一个 Include 文件夹下的 mqh,把结构体和首期函数丢进去,随便建个 EA 引用它,编译看是否零报错。外汇和贵金属波动大、滑点高,这类底层库只解决工程效率,不预示任何收益。
看似五花八门,底层全是同一套动作
在 MT5/MT4 里接了大量指标、脚本、EA 的开发单之后,一个现象很直接:不同策略表面差很多,但拆开看,大多由相同的基础函数堆出来。订单流、K线取值、条件判断,在不同程序里反复以相似顺序出现。 MQL4/MQL5 论坛里高频重复的同质提问也能佐证这点——很多人卡在的算法问题,本质是一样的。比如「怎么取前 N 根满足条件的 K 线」「怎么按时间筛订单」,每年都被换皮问一遍。 基于这个判断,可以建一个通用函数库,把取数、筛选、排序封装进去。用户调用时不用重写底层,只用「要什么数据、按什么规则」去问库,就能拿到组合好的结果集。外汇和贵金属波动大、滑点随机,这类封装只解决开发效率,不预示任何收益。
◍ 把行情拆成同构对象列表
在 MT5 里,任何数据类型本质都是「一组具有相同属性的对象」。时间序列就是一条有序长链,链上每节都装着同一套字段——时间、开高低收、量、点差。这类对象的容器就是 MqlRates 结构,它把 K 线周期内的价格与交易成本固化下来。 即时报价走的是另一条同构链:每个 MqlTick 对象固定携带买价、卖价、最后成交价与时间戳,专为快速抓取「此刻价格」设计。订单、成交、挂单也都照此思路排成列表,每类对象字段恒定。 理解了这点,就能为每类对象写最小类:管存储、排序、显示。下面两段结构是后续所有封装的地基,建议直接开 MT5 头文件核对字段偏移。
class="kw">struct class="type">MqlRates { class="type">class="kw">datetime time; class=class="str">"cmt">// 周期起始时间 class="type">class="kw">double open; class=class="str">"cmt">// 开盘价 class="type">class="kw">double high; class=class="str">"cmt">// 每个周期的最高价 class="type">class="kw">double low; class=class="str">"cmt">// 每个周期的最低价 class="type">class="kw">double close; class=class="str">"cmt">// 收盘价 class="type">long tick_volume; class=class="str">"cmt">// 即时报价的交易量 class="type">int spread; class=class="str">"cmt">// 点差 class="type">long real_volume; class=class="str">"cmt">// 兑换交易量 }; class="kw">struct class="type">MqlTick { class="type">class="kw">datetime time; class=class="str">"cmt">// 最后价格更新时间 class="type">class="kw">double bid; class=class="str">"cmt">// 当前出价 class="type">class="kw">double ask; class=class="str">"cmt">// 当前报价 class="type">class="kw">double last; class=class="str">"cmt">// 最后一笔成交的当前价格(最后) class="type">class="kw">ulong volume; class=class="str">"cmt">// 当前最后价格的成交量 class="type">long time_msc; class=class="str">"cmt">// 最后一次价格更新的时间(以毫秒为单位) class="type">uint flags; class=class="str">"cmt">// 即时报价标志 class="type">class="kw">double volume_real; class=class="str">"cmt">// 当前最后价格的交易量提升了准确度 };
「用动态指针数组搭起函数库骨架」
函数库的核心是一组对象列表,它能在不需要人工干预的情况下,自行收集、存储并处理数据,交易者只管调用查询返回的结果。最小结构就是不同对象加上存放它们的列表,对象描述数据必要属性,列表负责承载。 我们将直接借用标准库里那个存放 CObject 及其派生类实例动态指针的数组类来排列表。因为标准库的基类对象本就该塞进这种列表,所以每个自定义对象类只要从 CObject 继承就行,省掉重写容器逻辑的麻烦。 这种“实时”开发方式意味着每篇文章都会往现有程序里加新功能,读者跟着改几行继承代码,就能在 MT5 里看到对象被纳管进列表。外汇与贵金属行情高波动,用这套结构跑历史品种属性扫描时,要先确认终端内存上限,避免大周期对象堆积拖慢回测。
搭好抽象订单类的骨架
做跨平台交易函数库,第一刀落在账户历史。很多策略会读前一笔成交的平仓方式、止损止盈价位,甚至把昨天净值当作今天的开仓起点;历史集合如果访问慢,逻辑再好也跑不顺。这一节先把 MT4 与 MT5 的对象差异抹平:MT4 的市价单和挂单是独立存在,MT5 里订单只是成交流前的请求,成交后才派生仓位。我们统一抽象成「订单类」,把订单、成交、仓位都当同一种数据容器按类型排进列表。 在终端数据目录的 MQL5\Include 下建 DoEasy\Objects 与 DoEasy\Collections 两个子目录(按 Ctrl+Shift+D 可直开数据文件夹)。Objects 里新建 COrder 类,基类挂标准库 CObject,这样对象能直接塞进 CArrayObj 列表。初次编译会报 5 个错,原因是没把 CObject 头文件引进来,补上后编译干净。 COrder 私有区放单据号,以及三个数组存整数、实数、字符串型属性。枚举从 Defines.mqh 集中管理:整型属性从 0 编号,实型和字符型枚举接续前一个总数起编,于是任意属性的索引 = 请求属性序号 − 该枚举起始号。用相对路径 "Defines.mqh" 而不是尖括号包含,函数库整体挪目录也不会断链。 受保护构造函数只接单据号和状态,属性数组暂空;另写跨平台取属性方法,例如魔幻数字:MQL4 走 OrderMagicNumber(),MQL5 按状态分流返回仓位/订单/成交的魔术码。比较方法 Compare() 传对象指针与属性枚举值,大于返 1、小于返 -1、相等返 0,前提是列表已按该属性预排序。 服务函数收进 DELib.mqh:TimeToMs() 把毫秒时间除以 1000 得秒、取余得毫秒并格式化;LangMsg() 比对 COUNTRY_LANG 宏决定显示母语还是英语;还补了品种小数位读取。COrder 再用 DFUN 宏简化日志方法名,实现订单原因、方向、成交类型的本地化描述。到这步,历史订单/成交/仓位都能实例化成带独立属性的对象,后续列表排序与筛选才有地基。外汇与贵金属杠杆高,历史回测结论只代表过去概率,实盘须自担风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Order.mqh | class=class="str">"cmt">//| 版权所有 class="num">2018, MetaQuotes 软件公司 | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"
◍ 订单类的空壳怎么搭
想把每一笔外汇或贵金属订单抽象成对象,最薄的一层就是先声明一个继承自 CObject 的空类,构造函数和析构函数暂时留空,后续再往里塞持仓属性与方法。 下面这段是最小可编译骨架:类 COrder 公开继承 CObject,仅留默认构造与析构声明,函数体为空。MQL5 里继承 CObject 才能用进 CTrade 或自建容器做对象化管理,外汇贵金属的高杠杆特性决定了订单对象迟早要承载止损、魔术码等字段,先占位不亏。 注意首部 #include <Object.mqh> 不能省,否则 CObject 基类未定义会直接编译报错;版本与链接属性只是元信息,不影响逻辑但建议保留以便回溯。
class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class COrder : class="kw">public CObject { class="kw">private: class="kw">public: COrder(); ~COrder(); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ COrder::COrder() { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ COrder::~COrder() { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Order.mqh | class=class="str">"cmt">//| 版权所有 class="num">2018, MetaQuotes 软件公司 | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <Object.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class COrder : class="kw">public CObject { class="kw">private: class="kw">public: COrder(); ~COrder(); }; class=class="str">"cmt">//+------------------------------------------------------------------+