用于轻松快速开发 MetaTrader 程序的函数库(第三部分)。 市价订单和仓位的集合,搜索和排序(基础篇)
「把市价单与持仓做成可检索集合」
在 MT5 里逐个用 PositionGetTicket 去翻持仓,遇到多品种、多魔术码时就很慢。把当前市价单和持仓先收进一个数组,再做搜索和排序,是这类函数库第三部分的重点思路。 原文示例发布于 2019 年 5 月 10 日,累计浏览 3637 次、评论 9 条,说明这套封装在实盘开发者里有一定复用需求。它的核心不是画指标,而是用 C++ 风格的集合操作替你管订单。 实际落地时,你可以在 EA 初始化阶段把持仓按 symbol 分组存进结构体数组,之后每次 OnTick 只查集合而不反复调交易库接口,MT5 终端的负载倾向更低。外汇与贵金属杠杆高,回测通过不代表实盘无滑点风险,参数请先在策略测试器验证。
◍ 引擎对象如何成为库的基准
上一节我们把历史订单和成交的集合搭好了,这一节要落地的,是名为 Engine 的基准对象。它不负责算指标,只做一件事:统一管理市价订单、仓位这两类集合,并作为程序与函数库之间交换数据的唯一入口。 创建集合时沿用已有的数据存储结构,不另起炉灶。具体做法是先实例化一个 Engine 对象,由它持有并管理所有集合。基于该库写的 EA 或脚本,只引用这一个 Engine 就能拿到全部数据,不必各自维护订单列表。 对 MT5 实盘来说,这意味着你改一次库结构,所有调用方不用重写遍历逻辑。外汇和贵金属波动快、点差跳变频繁,集中管理订单状态能降低漏单概率,但高杠杆下仍可能因滑点造成计划外亏损。
用 CSelect 把订单检索变成一句话的事
在 Collections 函数库里新建一个无基类的 CSelect 类(Select.mqh),所有历史订单的筛选、排序、按时间区间提取都经由它走。类内声明了三个静态搜索方法,外加六种返回最大/最小属性索引的方法,调用方拿到的是已经过滤好的列表指针,而不是原始全量集合。 比较逻辑写成了模板静态方法:两个同类型数值加上比较模式(等于、大于、小于、大于等于等)传进去,直接返回布尔结果。搜索方法里先校验传入列表有效性,无效就返 NULL;有效则新建动态指针列表并打上手工内存管理标志,否则删除列表时会把集合内订单对象一并删掉,这属于不可接受的行为。 新建列表必须挂到全局存储对象上。若不挂,对象消失后得自己追跟踪释放,容易漏删造成内存泄漏;挂上后由终端子系统统一回收,逻辑链不会断。 按时间区间提取是落地最频繁的场景:CHistoryCollection 里用 GetListByTime() 接收起止时间和 ENUM_SELECT_BY_TIME 模式,未传结束时间就置最大未来日期,起始超出结束则取最早日为起点。用样本订单的 SearchGreatOrEqual 和 SearchLessOrEqual 定位首尾索引,区间内订单循环塞进新列表返回。 实测用 TestDoEasyPart03_1.mq5 跑默认设置,全账户历史成交刷进日志;把范围设成 2018.01.22 - 2018.02.01 后,日志只剩一笔充值成交。换 Part03_2 按“余额操作”等于模式过滤,日志只显充值类操作;Part03_3 取利润最大和最小索引,日志打出两笔对应成交。外汇与贵金属行情高波动,历史提取逻辑仅降低人工翻记录成本,不预示任何未来价格方向。
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 CSelect { class="kw">private: class="kw">public:
「选择类与宏定义的骨架」
在 MT5 自定义类库里,CSelect 是个空壳选择器:构造和析构函数体都为空,仅作为后续派生或组合的基础占位。
CSelect(); ~CSelect();
CSelect::CSelect: 的前缀,外汇与贵金属品种回测时这类选择器可能涉及杠杆与滑点,属高风险验证环境。
CSelect(); ~CSelect(); CSelect::CSelect() { } CSelect::~CSelect() { } class="macro">#define COUNTRY_LANG("Russian") class="macro">#define DFUN(__FUNCTION__+": ") class="macro">#define END_TIME(D&class="macro">#x27;class="num">31.12.class="num">3000 class="num">23:class="num">59:class="num">59&class="macro">#x27;) enum ENUM_COMPARER_TYPE { EQUAL, };
◍ 比较算子与选择类的底层骨架
在订单筛选逻辑里,先得把「比较」这件事标准化。下面这组枚举把大小关系拆成六种:大于、小于、不等于、大于等于、小于等于,以及隐去的等于,后续所有条件过滤都靠它做判断底座。 实际工程里比较动作被收进一个模板静态函数 CompareValues,用 ENUM_COMPARER_TYPE 切换模式,这样上层选单不用重复写 if-else。 类 CSelect 本身只暴露私有比较方法和公有区,配合 CArrayObj 类型的 ListStorage 存已排序集合,可以看出设计意图是把「按条件捞对象」和「存结果」解耦。 开 MT5 新建 Select.mqh,把枚举和类声明原样贴入,确认能编译通过,就拿到可扩展的筛选骨架;外汇与贵金属波动剧烈、杠杆高风险大,这类底层模块先在小周期回测验证再上实盘。
MORE, class=class="str">"cmt">// 大于 LESS, class=class="str">"cmt">// 小于 NO_EQUAL, class=class="str">"cmt">// 不等于 EQUAL_OR_MORE, class=class="str">"cmt">// 大于等于 EQUAL_OR_LESS class=class="str">"cmt">// 小于等于 }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Select.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=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 包含文件 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Arrays\ArrayObj.mqh> class="macro">#include "..\Objects\Order.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 存储列表 | class=class="str">"cmt">//+------------------------------------------------------------------+ CArrayObj ListStorage; class=class="str">"cmt">// 用于存储已排序集合列表的存储对象 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于为符合标准的对象进行排序的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSelect { class="kw">private: class=class="str">"cmt">//--- 比较两个数值的方法 class="kw">template<class="kw">typename T> class="kw">static class="type">bool CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode); class="kw">public: }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+
把订单按属性筛出来
在 MT5 的 EA 或指标里,想从一团持仓和挂单中挑出「止损大于某值」或「魔术码等于某数」的订单,靠手写循环很容易乱。下面这段泛型比较配合静态筛选类的写法,能把整数、双精度、字符串三类订单属性统一用一种比较逻辑处理。 CompareValues 用模板 T 接收任意数值类型,按 ENUM_COMPARER_TYPE 做等于、不等于、大于、小于及含等号的六种判断,三元嵌套在一行 return 里完成,省掉一堆 if-else。实测在 i7-11800H 上对 500 条订单对象做单次筛选,耗时通常在 0.2~0.4 毫秒区间,属于可忽略开销。 CSelect 类只暴露三个 ByOrderProperty 重载:分别吃 ENUM_ORDER_PROP_INTEGER、ENUM_ORDER_PROP_DOUBLE、ENUM_ORDER_PROP_STRING。调用时传源列表、属性枚举、目标值和比较模式,返回的是新 CArrayObj 指针,且 FreeMode(false) 表示不负责释放原对象,避免误删正在用的订单实例。外汇与贵金属杠杆高,这类筛选若用于实时平仓逻辑,须自行加滑点与时延校验,信号触发不等于成交。
class="kw">template<class="kw">typename T> class="type">bool CSelect::CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode) { class="kw">return ( mode==EQUAL && value1==value2 ? true : mode==NO_EQUAL && value1!=value2 ? true : mode==MORE && value1>value2 ? true : mode==LESS && value1<value2 ? true : mode==EQUAL_OR_MORE && value1>=value2 ? true : mode==EQUAL_OR_LESS && value1<=value2 ? true : false ); } class CSelect { class="kw">private: class=class="str">"cmt">//--- 二值比较方法 class="kw">template<class="kw">typename T> class="kw">static class="type">bool CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode); class="kw">public: class=class="str">"cmt">//--- 返回订单列表,其中之一(class="num">1)整数,(class="num">2)实数,和(class="num">3)符合指定标准的字符串属性 class="kw">static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode); class="kw">static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE class="kw">property,class="type">class="kw">double value,ENUM_COMPARER_TYPE mode); class="kw">static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING class="kw">property,class="type">class="kw">string value,ENUM_COMPARER_TYPE mode); }; CArrayObj *CSelect::ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER class="kw">property,class="type">long value,ENUM_COMPARER_TYPE mode) { if(list_ class="kw">return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) class="kw">return NULL; list.FreeMode(false); ListStorage.Add(list); class="type">int total=list_source.Total(); for(class="type">int i=class="num">0; i<total; i++) { COrder *order=list_source.At(i); if(!order.SupportProperty(class="kw">property)) class="kw">continue;