用于轻松快速开发 MetaTrader 程序的函数库(第三部分)。 市价订单和仓位的集合,搜索和排序(基础篇)
📘

用于轻松快速开发 MetaTrader 程序的函数库(第三部分)。 市价订单和仓位的集合,搜索和排序(基础篇)

第 1/3 篇

「把市价单与持仓做成可检索集合」

在 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 取利润最大和最小索引,日志打出两笔对应成交。外汇与贵金属行情高波动,历史提取逻辑仅降低人工翻记录成本,不预示任何未来价格方向。

MQL5 / C++
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 是个空壳选择器:构造和析构函数体都为空,仅作为后续派生或组合的基础占位。

MQL5 / C++
CSelect();
~CSelect();
上面两行声明了无参构造与析构,类实现中 CSelect::CSelect(){} 和 CSelect::~CSelect(){} 均不执行任何逻辑,编译后不占运行时开销。 Defines.mqh 里用宏固化了几个常量和调试辅助:COUNTRY_LANG 写死为 "Russian",DFUN 展开成当前函数名加冒号,END_TIME 设为 D'31.12.3000 23:59:59' 作为账户历史查询的截止边界。 搜索模块还定义了枚举 ENUM_COMPARER_TYPE,首成员 EQUAL 表示等于比较。开 MT5 新建 include 文件粘入这些宏,编译能看到 DFUN 在日志里输出形如 CSelect::CSelect: 的前缀,外汇与贵金属品种回测时这类选择器可能涉及杠杆与滑点,属高风险验证环境。

MQL5 / C++
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,把枚举和类声明原样贴入,确认能编译通过,就拿到可扩展的筛选骨架;外汇与贵金属波动剧烈、杠杆高风险大,这类底层模块先在小周期回测验证再上实盘。

MQL5 / C++
  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) 表示不负责释放原对象,避免误删正在用的订单实例。外汇与贵金属杠杆高,这类筛选若用于实时平仓逻辑,须自行加滑点与时延校验,信号触发不等于成交。

MQL5 / C++
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;

常见问题

用引擎对象把订单和仓位统一收进集合,再调用选择类做过滤,就能像数组一样逐条读取,不必重复向终端请求。
把检索逻辑封装成选择类,配合宏定义描述属性字段,之后筛单只需一行调用,不用每次手搓循环。
可以,小布能按你给的属性规则扫描持仓与市价单集合,把符合条件的单子直接列出来,省去手动翻终端。
比较算子决定两条记录按哪个属性排大小或判等,是底层骨架;写好它筛选只是一次线性扫描,开销很小。
先建选择类并宏定义好属性名,再传比较算子实例化,调用选择方法传入属性值即可返回子集。