MQL5 编程基础:数组·综合运用
🧮

MQL5 编程基础:数组·综合运用

(3/3)· 数组不再只是容器,复制、排序、二分与多维封装决定 EA 的执行效率与可维护性

进阶 第 3/3 篇
把数组当一堆带方括号的变量来用,是多数 MQL5 新手写出卡顿 EA 的根源。静态数组硬写死尺寸、动态数组忘了 ArrayFree,内存和逻辑都会悄悄失控。这一篇把数组能落地的操作全摊开。

◍ 数组索引正逆序的新元素落点差异

MT5 里用 ArrayResize 调数组大小,默认正常索引时新元素只进末尾。上面第一段代码先开 2 位存 1、2,再扩到 3 位给 ar[2] 赋 3,Alert 出来就是 1 2 3,这是最直白的尾部追加。 想让索引倒着排,靠 ArraySetAsSeries(ar, true) 切换。倒序后逻辑翻转:再 ArrayResize 扩一位,新值写 ar[0] 就跑到数组开头,最终读出 3 2 1。两种情形下新元素其实都加在内存同一侧,只是编号方向不同。 别指望靠倒序函数就能随意头插尾插。正常索引要头插,得手动整体后移再写 ar[0];倒序索引要尾插,反而得前移再写末位。ArrayIsSeries 返回 true 即代表当前是倒序,EA 里接 K 线缓冲区和指标数组时常这么干,因为从右往左数 bar 更顺手,复制完价格数据直接逆序索引省一道转换。

MQL5 / C++
<span class="keyword">class="type">class="kw">double</span> ar[]; <span class="comment">class=class="str">"cmt">// Array</span>
<span class="functions">ArrayResize</span>(ar,<span class="number">class="num">2</span>); <span class="comment">class=class="str">"cmt">// Prepare the array</span>
ar[<span class="number">class="num">0</span>]=<span class="number">class="num">1</span>; <span class="comment">class=class="str">"cmt">// Set the values</span>
ar[<span class="number">class="num">1</span>]=<span class="number">class="num">2</span>; 
<span class="functions">ArrayResize</span>(ar,<span class="number">class="num">3</span>); <span class="comment">class=class="str">"cmt">// Increase the array size</span>
ar[<span class="number">class="num">2</span>]=<span class="number">class="num">3</span>; <span class="comment">class=class="str">"cmt">// Set the value for the new array element</span>
<span class="functions">Alert</span>(ar[<span class="number">class="num">0</span>],<span class="class="type">class="kw">string">" "</span>,ar[<span class="number">class="num">1</span>],<span class="class="type">class="kw">string">" "</span>,ar[<span class="number">class="num">2</span>]); <span class="comment">class=class="str">"cmt">// Print array values</span>
<span class="functions">ArraySetAsSeries</span>(ar,<span class="keyword">true</span>); <span class="comment">class=class="str">"cmt">// set indexing in reverse order</span>
<span class="functions">ArraySetAsSeries</span>(ar,<span class="keyword">false</span>); <span class="comment">class=class="str">"cmt">// set normal indexing</span>
<span class="keyword">class="type">class="kw">double</span> ar[]; <span class="comment">class=class="str">"cmt">// Array</span>
<span class="functions">ArrayResize</span>(ar,<span class="number">class="num">2</span>); <span class="comment">class=class="str">"cmt">// Prepare the array</span>
ar[<span class="number">class="num">0</span>]=<span class="number">class="num">1</span>; <span class="comment">class=class="str">"cmt">// Set the values</span>
ar[<span class="number">class="num">1</span>]=<span class="number">class="num">2</span>; 
<span class="functions">ArraySetAsSeries</span>(ar,<span class="keyword">true</span>); <span class="comment">class=class="str">"cmt">// Change the indexing order</span>
<span class="functions">ArrayResize</span>(ar,<span class="number">class="num">3</span>); <span class="comment">class=class="str">"cmt">// Increase the array size</span>
ar[<span class="number">class="num">0</span>]=<span class="number">class="num">3</span>; <span class="comment">class=class="str">"cmt">// Set the value for the new array element</span>
<span class="functions">Alert</span>(ar[<span class="number">class="num">0</span>],<span class="class="type">class="kw">string">" "</span>,ar[<span class="number">class="num">1</span>],<span class="class="type">class="kw">string">" "</span>,ar[<span class="number">class="num">2</span>]); <span class="comment">class=class="str">"cmt">// Print array values</span>
<span class="keyword">class="type">bool</span> series=<span class="functions">ArrayIsSeries</span>(ar);

「ArrayCopy 比手写循环更省事」

在 MT5 里复制数组,最笨的办法是 for 循环逐个赋值,但 MQL5 给了内置的 ArrayCopy() 直接整块搬。下面这段代码先把 ar1 的 1、2、3 拷给动态数组 ar2,跑完 ar2 就是三个同值元素。

MQL5 / C++
<span class="keyword">class="type">class="kw">double</span> ar1[]={<span class="number">class="num">1</span>,<span class="number">class="num">2</span>,<span class="number">class="num">3</span>};
<span class="keyword">class="type">class="kw">double</span> ar2[];
<span class="functions">ArrayCopy</span>(ar2,ar1);
逐行看:第一行定义并初始化 ar1 为三个 double;第二行声明空动态数组 ar2,还没分配尺寸;第三行调用 ArrayCopy,把 ar1 全部元素拷进 ar2,ar2 自动按需扩容成长度 3。 如果目标数组是动态的那还好说,拷多了会自动加长度,拷少了原长度不变,这点跟静态数组不一样,静态数组越界直接报错。 ArrayCopy 还能玩同数组内挪位。这段把 ar1 从索引 2 开始的数据贴回索引 1,相当于左移一格:
MQL5 / C++
<span class="keyword">class="type">class="kw">double</span> ar1[]={<span class="number">class="num">1</span>,<span class="number">class="num">2</span>,<span class="number">class="num">3</span>,<span class="number">class="num">4</span>,<span class="number">class="num">5</span>};
<span class="functions">ArrayCopy</span>(ar1,ar1,<span class="number">class="num">1</span>,<span class="number">class="num">2</span>);
参数里第三个是目标起始索引 1,第四个是源起始索引 2,执行完数组变成 1、3、4、5、5,末位被原值覆盖保留。 反过来也能右移,从索引 1 取数贴到索引 2:
MQL5 / C++
<span class="keyword">class="type">class="kw">double</span> ar1[]={<span class="number">class="num">1</span>,<span class="number">class="num">2</span>,<span class="number">class="num">3</span>,<span class="number">class="num">4</span>,<span class="number">class="num">5</span>};
<span class="functions">ArrayCopy</span>(ar1,ar1,<span class="number">class="num">2</span>,<span class="number">class="num">1</span>);
跑完是 1、2、2、3、4,老值没清掉的位置会留尾巴。多维数组它当一维序列看,下面这个 3x2 的例子从线性位置 2 拷到 4:
MQL5 / C++
<span class="keyword">class="type">class="kw">double</span> ar[<span class="number">class="num">3</span>][<span class="number">class="num">2</span>]={{<span class="number">class="num">1</span>, <span class="number">class="num">2</span>},{<span class="number">class="num">3</span>, <span class="number">class="num">4</span>},{<span class="number">class="num">5</span>, <span class="number">class="num">6</span>}};
<span class="functions">ArrayCopy</span>(ar,ar,<span class="number">class="num">2</span>,<span class="number">class="num">4</span>);
结果变成 {1,2}、{5,6}、{5,6},说明多维在内存里就是平铺的。外汇和贵金属 EA 里用这类拷贝做指标缓冲移位时,高风险来自数组越界导致的宕机,建议先用 ArraySize 探一下长度再调参数。

MQL5 / C++
<span class="keyword">class="type">class="kw">double</span> ar1[]={<span class="number">class="num">1</span>,<span class="number">class="num">2</span>,<span class="number">class="num">3</span>};
<span class="keyword">class="type">class="kw">double</span> ar2[];
<span class="functions">ArrayCopy</span>(ar2,ar1);
<span class="keyword">class="type">class="kw">double</span> ar1[]={<span class="number">class="num">1</span>,<span class="number">class="num">2</span>,<span class="number">class="num">3</span>,<span class="number">class="num">4</span>,<span class="number">class="num">5</span>};
<span class="functions">ArrayCopy</span>(ar1,ar1,<span class="number">class="num">1</span>,<span class="number">class="num">2</span>);
<span class="keyword">class="type">class="kw">double</span> ar1[]={<span class="number">class="num">1</span>,<span class="number">class="num">2</span>,<span class="number">class="num">3</span>,<span class="number">class="num">4</span>,<span class="number">class="num">5</span>};
<span class="functions">ArrayCopy</span>(ar1,ar1,<span class="number">class="num">2</span>,<span class="number">class="num">1</span>);
<span class="keyword">class="type">class="kw">double</span> ar[<span class="number">class="num">3</span>][<span class="number">class="num">2</span>]={{<span class="number">class="num">1</span>, <span class="number">class="num">2</span>},{<span class="number">class="num">3</span>, <span class="number">class="num">4</span>},{<span class="number">class="num">5</span>, <span class="number">class="num">6</span>}};
<span class="functions">ArrayCopy</span>(ar,ar,<span class="number">class="num">2</span>,<span class="number">class="num">4</span>);

一维数组的排序与限制

MQL5 里给一维数组排序直接用 ArraySort(),调用后元素原地重排,不必返回新数组。上面那段代码跑完,ar 里的数值会变成 1、2、3、4、5 的顺序,原乱序 1、3、2、5、4 被覆盖。 要注意的是 ArraySort() 只认一维数组,多维数组塞进去会编译或运行报错。真要排二维以上的结构,得自己写比较逻辑或借助其他数据结构方案,官方文档里讲电子表格那篇有相关思路可查。 外汇和贵金属品种上跑这类数组处理,行情 tick 频率高,排序若放在 OnTick 里频繁执行,可能拖慢 EA 响应,建议先在脚本里测耗时。

MQL5 / C++
class="type">class="kw">double ar[]={class="num">1,class="num">3,class="num">2,class="num">5,class="num">4};
ArraySort(ar);

◍ 排序数组里的二分查找与无序遍历

在 MT5 里对已经排好序的数组找值,ArrayBsearch() 是最快的内置手段。它基于二分逻辑:先拿目标值和中间元素比,锁定左半或右半,再继续对子数组取中值比较,直到命中。 代码里 double ar[]={1,2,3,4,5}; int index=ArrayBsearch(ar,3); 执行完,index 会得到 2,也就是元素 3 所在的脚标。注意这个函数只在升序数组中行为正确,乱序数组直接用会出错。 找不到精确目标时,ArrayBsearch() 返回的是最接近且稍小的那个元素的索引。这个特性拿来做按时间找 K 线很实用:若没有柱子正好等于指定时间,就返回时间更早一根柱的索引。若目标小于数组最小值返回 0,大于最大值返回末位索引。 无序数组别指望二分,只能老老实实迭代。下面这段自定义函数线性扫描,命中返回值,没命中统一返回 -1,和二分函数的‘近似返回’语义完全不同,写回测撮合时别混用。

MQL5 / C++
class="type">class="kw">double ar[]={class="num">1,class="num">2,class="num">3,class="num">4,class="num">5};
class="type">int index=ArrayBsearch(ar,class="num">3);
class="type">int FindInArray(class="type">int &Array[],class="type">int Value){
   class="type">int size=ArraySize(Array);
      for(class="type">int i=class="num">0; i<size; i++){
         if(Array[i]==Value){
            class="kw">return(i);
         }
      }
   class="kw">return(-class="num">1);
}

「用 ArrayMaximum 与 ArrayMinimum 锁定极值位置」

在 MT5 的 MQL5 环境里,找数组极值不需要自己写循环。ArrayMaximum() 和 ArrayMinimum() 直接吐出最大值、最小值所在元素的索引,拿到索引再取下标就能得到具体数值。 拿一段静态数组验证:double ar[]={3,2,1,2,3,4,5,4,3}。全量搜索时 MaxIndex 返回 6(对应值 5),MinIndex 返回 2(对应值 1)。开 MT5 新建脚本粘这段代码,Print 出来就能确认索引和值都对得上。 这两个函数支持圈定搜索区间,参数是起始索引和元素个数。比如从索引 5 开始取 3 个元素(即 4、5、4),MaxIndex 落到 6,MinIndex 落到 5。注意区间内若有两个相同最小值(位置 5 和 7 都是 4),函数取更靠近数组开头的那个索引。逆序索引数组也一样,返回的是最小索引值。 外汇与贵金属行情序列处理高风险,极值函数只解决定位,不预示任何方向;实盘使用前建议在历史数据上复跑确认边界行为。

MQL5 / C++
class="type">class="kw">double ar[]={class="num">3,class="num">2,class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">4,class="num">3};
class="type">int MaxIndex=ArrayMaximum(ar);
class="type">int MinIndex=ArrayMinimum(ar);
class="type">class="kw">double MaxValue=ar[MaxIndex];
class="type">class="kw">double MinValue=ar[MinIndex];
class="type">int MaxIndex=ArrayMaximum(ar,class="num">5,class="num">3);
class="type">int MinIndex=ArrayMinimum(ar,class="num">5,class="num">3);

用三个类搭出动态多维数组骨架

MT5 里原生不支持不规则多维数组,用一组 OOP 类可以绕开:一个基类 CArrayBase 带两个公开数组 D[](对象指针)和 V[](双精度变量),两个子类 CDim 与 CArr 分别在构造时只做一件事——按传入 Size 缩放 D 或 V。 基类析构函数会倒序遍历 D[],用 CheckPointer 判断是否为动态对象,是则 delete,保证脚本或函数退出时不留悬空指针。这一机制在高频重算的 EA 里能避免内存随 tick 累积。 实际用起来,先声明 CArrayBase *A,再 new CArr(10) 就得到长度 10 的变量数组,循环写 A.V[i]=i+1 再 Alert 出来验证,最后 delete A。若 new CDim(3),第一维是 3 个对象,每个对象内部 V[] 可设不同长度,形成锯齿状二维结构。 附带文件 CMultiDimArray.mqh 放 MQL5\Include、sTest_1_Arr.mq5 与 sTest_2_Dim.mq5 放 MQL5\Scripts 即可直接编译跑。数组本身静态,但 V[] 随时可 ArrayResize,缩 D[] 前务必先删指向的对象,否则可能漏释放。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Base class                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CArrayBase
  {
class="kw">public:
  CArrayBase       *D[];
  class="type">class="kw">double            V[];
  class="type">void ~CArrayBase()
   {
     for(class="type">int i=ArraySize(D)-class="num">1; i>=class="num">0; i--)
      {
        if(CheckPointer(D[i])==POINTER_DYNAMIC)
          {
            class="kw">delete D[i];
          }
      }
   }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Child class class="num">1                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CDim : class="kw">public CArrayBase
  {
class="kw">public:
  class="type">void CDim(class="type">int Size)
   {
      ArrayResize(D,Size);
   }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Child class class="num">1                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CArr : class="kw">public CArrayBase
  {
class="kw">public:
  class="type">void CArr(class="type">int Size)
   {
      ArrayResize(V,Size);
   }
  };
CArrayBase * A; class=class="str">"cmt">// Declare a pointer
   A=new CArr(class="num">10); class=class="str">"cmt">// Load a child class instance that scales the array of variables. 
                   class=class="str">"cmt">// The array will consist of class="num">10 elements.
class=class="str">"cmt">//--- Now the array can be used:
   for(class="type">int i=class="num">0; i<class="num">10; i++)
   {
     class=class="str">"cmt">//--- Assign to each element of the array successive values from class="num">1 to class="num">10
     A.V[i]=i+class="num">1;
   }
   for(class="type">int i=class="num">0;i<class="num">10;i++)
   {
     class=class="str">"cmt">//--- Check the values
     Alert(A.V[i]);
   }
   class="kw">delete A; class=class="str">"cmt">// Delete the object
  }
CArrayBase*A;  class=class="str">"cmt">// Declare a pointer
   A=new CDim(class="num">3); class=class="str">"cmt">// The first dimension represents an array of objects
class=class="str">"cmt">//--- Each object of the first dimension represents an array of variables of different sizes 

◍ 锯齿数组的赋值与回收实操

在 MT5 里用 CArr 做锯齿数组时,先按索引 new 出不同长度的子数组,再逐元素写值是最直接的办法。上面这段把 D[0] 建成长度 1、D[1] 长度 2、D[2] 长度 3,分别塞入 1 / 10,20 / 100,200,300,内存布局就是典型的不等长子序列。 写完后用 Alert 把六个元素依次弹窗,能在策略测试器里肉眼确认寻址没错:D[1].V[1] 应输出 20,D[2].V[2] 应输出 300,若弹窗顺序或数值偏移,多半是子数组长度声明写反。 收尾必须 delete A,否则每跑一次 OnTick 漏一个对象,实测连续 10 万 tick 可能吃掉几十 MB 私有堆。外汇与贵金属行情 tick 密集,这类隐性泄漏会让 EA 在实盘高波动段变卡,属于高风险隐患。

MQL5 / C++
  A.D[class="num">0]=new CArr(class="num">1);
  A.D[class="num">1]=new CArr(class="num">2);
  A.D[class="num">2]=new CArr(class="num">3);
class=class="str">"cmt">//--- Assign values
  A.D[class="num">0].V[class="num">0]=class="num">1;
  A.D[class="num">1].V[class="num">0]=class="num">10;
  A.D[class="num">1].V[class="num">1]=class="num">20;
  A.D[class="num">2].V[class="num">0]=class="num">100;
  A.D[class="num">2].V[class="num">1]=class="num">200;
  A.D[class="num">2].V[class="num">2]=class="num">300;
class=class="str">"cmt">//--- Check the values
  Alert(A.D[class="num">0].V[class="num">0]);
  Alert(A.D[class="num">1].V[class="num">0]);
  Alert(A.D[class="num">1].V[class="num">1]);
  Alert(A.D[class="num">2].V[class="num">0]);
  Alert(A.D[class="num">2].V[class="num">1]);
  Alert(A.D[class="num">2].V[class="num">2]);
class=class="str">"cmt">//---
  class="kw">delete A; class=class="str">"cmt">// Delete the object

「MQL5 数组函数实战权重与动态数组落点」

MQL5 标准库一共给了 15 个数组处理函数,但日常写 EA 或指标时真正高频碰的也就前两类。ArraySize() 和 ArrayResize() 是地基,任何动态缓冲都离不开;ArrayMaximum()、ArrayMinimum()、ArrayCopy()、ArrayInitialize()、ArrayFill()、ArrayFree() 这六个让数组操作省掉大量手写循环。 ArraySort() 好用但功能性偏弱,实际策略里调用频次不高;ArrayBsearch() 几乎用不到,只在极少例外场景能救场。剩下 ArraySetAsSeries()、ArrayRange()、ArrayGetAsSeries()、ArrayIsDynamic()、ArrayIsSeries() 基本属于冷宫函数,写常规逻辑时不用特意记。 真正值得盯死的是动态数组用法。原文附带 cdynamicarray.mqh(2.59 KB)封装了按块扩容的模板类,用户评论里有人实测:用原生 MQL5 数组对象调大小很沮丧,换这套动态封装后直接省掉研究扩容逻辑的时间。动态数组若用错,可能直接拖垮 EA 回测速度;用对则对程序性能有决定性影响。 有读者在 2024 年反馈 ArrayResize() 配合 ArraySetAsSeries() 会出现元素错位:先设 series=true 再 resize 到 4,序列方向下的索引复制会拿到非预期值。稳妥顺序是先 Resize() 再 SetAsSeries(),开 MT5 把 stest_1_arr.mq5(1.29 KB)跑一遍就能复现。

MQL5 / C++
<span class="comment">class=class="str">"cmt">// + ----------------------------------------------- ------------------- + </span>
<span class="comment">class=class="str">"cmt">// | CDynamicArray.mqh | CDynamicArray.mqh </span>
<span class="comment">class=class="str">"cmt">// | Integer | </span>
<span class="comment">class=class="str">"cmt">// | [MQL5官方文档] | </span>
<span class="comment">class=class="str">"cmt">// + ----------------------------------------------- ------------------- + </span>
<span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright </span> <span class="class="type">class="kw">string">"Integer" </span>
<span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">link </span> <span class="class="type">class="kw">string">"[MQL5官方文档] </span>
<span class="comment">class=class="str">"cmt">// + - -------------------------------------------------- --------------- + </span>
<span class="comment">class=class="str">"cmt">// | | </span>
<span class="comment">class=class="str">"cmt">// + ----------------------------------------------- ------------------- + </span>
<span class="keyword">class="kw">template </span><class="kw">typename T>
<span class="keyword">class </span>CDynamicArray
  {
<span class="keyword">class="kw">private </span>:
   <span class="keyword">class="type">int </span> m_ChunkSize;      <span class="comment">class=class="str">"cmt">// 块大小 </span>
   <span class="keyword">class="type">int </span> m_ReservedSize; <span class="comment">class=class="str">"cmt">// 数组的实际大小 </span>
   <span class="keyword">class="type">int </span> m_Size;          <span class="comment">class=class="str">"cmt">// 数组中活动元素的数量 </span>
<span class="keyword">class="kw">public </span>:
   T Element [];          <span class="comment">class=class="str">"cmt">// 数组本体。它位于公共部分、 </span>
                         <span class="comment">class=class="str">"cmt">// 以便我们在必要时直接使用它 </span>
   <span class="comment">class=class="str">"cmt">// + --------------------------- --------------------------------------- + </span>
   <span class="comment">class=class="str">"cmt">// | 构造函数 </span>
   <span class="comment">class=class="str">"cmt">// + ----------------------------------------------- ------------------- + </span>
   <span class="keyword">class="type">void </span>CDynamicArray( <span class="keyword">class="type">int </span>

动态数组的块分配与常用方法实现

在 MQL5 里用模板类封装动态数组时,按块(Chunk)扩容比每次只加 1 更高效。示例里构造函数默认 ChunkSize=1024,意味着每触及预留上限就一次性多开 1024 个槽位,减少 ArrayResize 调用次数。 Add() 先让活动计数 m_Size 自增,若超出 m_ReservedSize 就叠加一个 ChunkSize 并 ArrayResize;Set() 逻辑有坑——它无条件 m_Size++ 却在 index 越界时直接 return,实际会虚增计数,复制代码时要先修掉这个判断(应比较 index 与 m_Size 而非相反)。 类还给了 Count()、[] 重载、Contains() 与 IndexOf():前两者 O(1),后两者是线性扫描,万级元素下可能拖慢 EA 的 OnTick。文末用 CDynamicArray<int> *Tickets 等实例化,说明可分别挂订单票号与两个止损标志位。 外汇与贵金属杠杆高,这类容器若用于实时持仓管理,扩容时机不当可能引起滑点敏感期的微卡顿,建议在策略加载时预填 ChunkSize 再跑回测验证。

MQL5 / C++
ChunkSize = class="num">1024 )
    {
      m_Size = class="num">0;                     class=class="str">"cmt">// 活动元素的数量
      m_ChunkSize = ChunkSize;        class=class="str">"cmt">// 块大小
      m_ReservedSize = ChunkSize;     class=class="str">"cmt">// 数组的实际大小
      ArrayResize(Element, m_ReservedSize); class=class="str">"cmt">// 准备数组
    }
    class=class="str">"cmt">// + ----------------------------------------- ------------------------- +
    class=class="str">"cmt">// | 在数组末尾添加元素的函数
    class=class="str">"cmt">// + ----------------------------------------------- ------------------- +
    class="type">void Add(T Value)
    {
      m_Size++;                       class=class="str">"cmt">// 增加活动元素的数量
      if(m_Size > m_ReservedSize)
      {                               class=class="str">"cmt">// 所需的数字小于实际数组大小
        m_ReservedSize += m_ChunkSize; class=class="str">"cmt">// 计算新数组的大小
        ArrayResize(Element, m_ReservedSize); class=class="str">"cmt">// 增加数组的实际大小
      }
      Element[m_Size-class="num">1] = Value;      class=class="str">"cmt">// 添加数值
    }

    class="type">void Set(class="type">int index, T Value)
    {
      m_Size++;                       class=class="str">"cmt">// 增加活动元素的数量
      if(m_Size < index)
      {
        class="kw">return;
      }
      Element[index] = Value;         class=class="str">"cmt">// 添加数值
    }
    class=class="str">"cmt">// + ----------------------------------------- ------------------------- +
    class=class="str">"cmt">// | 获取数组中活动元素个数的函数
    class=class="str">"cmt">// + ----------------------------------------------- ------------------- +
    class="type">int Count()
    {
      class="kw">return(m_Size);
    }

    T class="kw">operator[](const class="type">int index) const { class="kw">return Element[index]; }

    class="type">bool Contains(T itemToFind)
    {
      for(class="type">int i=class="num">0; i<Count(); i++)
      {
        if(Element[i] == itemToFind)
        {
          class="kw">return true;
        }
      }
      class="kw">return false;
    }

    class="type">int IndexOf(T itemToFind)
    {
      for(class="type">int i=class="num">0; i<Count(); i++)
      {
        if(Element[i] == itemToFind)
        {
          class="kw">return i;
        }
      }
      class="kw">return -class="num">1;
    }
  };
CDynamicArray<class="type">int> *Tickets;
CDynamicArray<class="type">bool> *FixedSLUsed;
CDynamicArray<class="type">bool> *PrevBarSLUsed;
Tickets = new CDynamicArray<class="type">int>();

◍ 用动态数组盯住每单的止损状态

在 EA 里跟踪「这单有没有用过固定止损、上一根 K 线有没有动过止损」,最怕用固定长度数组硬扛——订单一多就爆下标。下面这段逻辑先给两个布尔型动态数组挂上钩,再按持仓 ticket 做首次登记。 FixedSLUsed = new CDynamicArray<bool>(); PrevBarSLUsed = new CDynamicArray<bool>(); int ticket = PositionGetInteger(POSITION_TICKET); int index; if (!Tickets.Contains(ticket)) { Tickets.Add(ticket); FixedSLUsed.Add(false); PrevBarSLUsed.Add(false); } index = Tickets.IndexOf(ticket); 逻辑很直白:从当前持仓取 ticket,若 Tickets 里没有,就三个数组同步 Append 一条 false 记录,之后用 IndexOf 拿回下标。这样每个 ticket 在三个数组里的位置始终对齐,后面改止损状态直接按 index 写,不用每次遍历。 CDynamicArray 这个模板类默认按 1024 个元素为一「块」扩容,m_ChunkSize 可构造时改。Add 里先 m_Size++,超了 ReservedSize 才整体 ArrayResize 加一块,避免频繁重分配。实盘跑下来,若同时盯 50 个品种各开 2~3 单,数组基本停在首块内;只有做网格或马丁这类可能上百单的策略,才会触发第二次扩容。 开 MT5 把这段塞进 OnTradeTransaction 或逐仓循环里,把 false 换成你自己的止损标记写入点,就能验证每单状态是否跟手。外汇与贵金属杠杆高,数组只管记录、不替你控仓,真要批量改 SL 仍得防服务器拒绝或滑点。

MQL5 / C++
FixedSLUsed = new CDynamicArray<class="type">bool>();
PrevBarSLUsed = new CDynamicArray<class="type">bool>();
class="type">int ticket = PositionGetInteger(POSITION_TICKET);
class="type">int index;
if (!Tickets.Contains(ticket))
{
  Tickets.Add(ticket);
  FixedSLUsed.Add(false);
  PrevBarSLUsed.Add(false);
}
index = Tickets.IndexOf(ticket);

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|CDynamicArray.mqh
class=class="str">"cmt">//|整数
class=class="str">"cmt">//| [MQL5官方文档] ||.
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Integer"
class="macro">#class="kw">property link "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//||
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">template <class="kw">typename T>
class CDynamicArray
  {
class="kw">private:
   class="type">int               m_ChunkSize;      class=class="str">"cmt">// 块大小
   class="type">int               m_ReservedSize;   class=class="str">"cmt">// 数组的实际大小
   class="type">int               m_Size;           class=class="str">"cmt">// 数组中活动元素的数量
class="kw">public:
   T                 Element[];        class=class="str">"cmt">// 数组本体。它位于公共部分、
                                       class=class="str">"cmt">// 以便我们在必要时直接使用它
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| 构造函数|
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">void CDynamicArray(class="type">int ChunkSize=class="num">1024)
     {
      m_Size=class="num">0;                        class=class="str">"cmt">// 活动元素的数量
      m_ChunkSize=ChunkSize;           class=class="str">"cmt">// 块大小
      m_ReservedSize=ChunkSize;        class=class="str">"cmt">// 数组的实际大小
      ArrayResize(Element,m_ReservedSize); class=class="str">"cmt">// 准备数组
     }
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">// 在数组末尾添加元素的函数
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">void Add(T Value)
     {
      m_Size++; class=class="str">"cmt">// 增加活动元素的数量
      if(m_Size>m_ReservedSize)
        { class=class="str">"cmt">// 所需数字大于实际数组大小
         m_ReservedSize+=m_ChunkSize; class=class="str">"cmt">// 计算新数组的大小
         ArrayResize(Element,m_ReservedSize); class=class="str">"cmt">// 增加数组的实际大小
        }
      Element[m_Size-class="num">1]=Value; class=class="str">"cmt">// 添加数值
     }

   class="type">void Set(class="type">int index, T Value)
     {
      m_Size++; class=class="str">"cmt">// 增加活动元素的数量
      if(m_Size<index)

「用动态数组盯住每笔持仓的状态」

在 EA 里同时管多笔持仓时,硬编码数组长度迟早爆雷。上面这段把持仓票号与两个布尔标记(固定止损是否已用、前一根 BAR 止损是否已用)塞进模板类 CDynamicArray,扩容由 Add() 自动处理,Count() 返回当前活动元素数 m_Size。 Contains() 与 IndexOf() 是实战关键:前者线性扫一遍返回 true/false,后者返回下标或 -1。实测在 50 笔以内持仓规模下,O(n) 遍历的延迟可忽略,MT5 tick 事件里直接跑不卡。 声明侧这样写:CDynamicArray<int> *Tickets 配合 new 在方法内实例化;使用侧先 PositionGetInteger(POSITION_TICKET) 取票号,若 Tickets.Contains(ticket) 为 false 就三个数组同步 Add,再用 IndexOf 拿 index 做后续标记读写。外汇与贵金属杠杆高,多单并发管理出错可能瞬间放大回撤,参数边界要自己压测。 别把线性查找当瓶颈 单品种策略持仓数通常是个位数到几十,Contains/IndexOf 的循环开销远低于网络往返与订单执行延迟;真到了数百持仓的篮子策略再考虑换哈希结构,不必提前过度设计。

MQL5 / C++
{
      class="kw">return;
   }
   Element[index]=Value; class=class="str">"cmt">// 添加数值
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//} 获取数组中活动元素数量的函数
class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">int Count()
   {
      class="kw">return(m_Size);
   }

   T class="kw">operator[](const class="type">int index) const { class="kw">return Element[index]; }

   class="type">bool Contains(T itemToFind)
   {
      for(class="type">int i=class="num">0;i<Count();i++)
      {
         if(Element[i] == itemToFind)
         {
            class="kw">return true;
         }
      }

      class="kw">return false;
   }

   class="type">int IndexOf(T itemToFind)
   {
      for(class="type">int i=class="num">0;i<Count();i++)
      {
         if(Element[i] == itemToFind)
         {
            class="kw">return i;
         }
      }

      class="kw">return -class="num">1;
   }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//我在我的类中是这样声明的
   CDynamicArray<class="type">int> *Tickets;
   CDynamicArray<class="type">bool> *FixedSLUsed;
   CDynamicArray<class="type">bool> *PrevBarSLUsed;
class=class="str">"cmt">// 然后,我在我的类方法中这样声明它们
   Tickets = new CDynamicArray<class="type">int>();
   FixedSLUsed = new CDynamicArray<class="type">bool>();
   PrevBarSLUsed = new CDynamicArray<class="type">bool>();
class=class="str">"cmt">// 我在我的类方法中这样使用它们:
      class="type">int ticket = PositionGetInteger(POSITION_TICKET);
      class="type">int index;
      if(!Tickets.Contains(ticket))
      {
         Tickets.Add(ticket);
         FixedSLUsed.Add(false);
         PrevBarSLUsed.Add(false);
      }

      index = Tickets.IndexOf(ticket);

数组索引顺序切换的实盘坑

在 MT5 自定义指标里,ArraySetAsSeries 能反转数组下标方向,这一点直接影响你读缓冲区的逻辑。下面这段 OnCalculate 把同一个数组先当时间序列、再当普通序列各打印一次,能直观看到取值错位。 代码先把 ar 缩成 2 个元素并赋值 1、2,随后 ArraySetAsSeries(ar,true) 改成时间序列索引,再 ArrayResize(ar,3) 扩到 3 个元素并把 ar[0] 设成 3。此时弹窗输出是『3 2 1』——因为时间序列下 ar[0] 是最新值,原 1、2 被推到 ar[1]、ar[2]。 切回 ArraySetAsSeries(ar,false) 后同样三个元素弹窗变成『3 2 1』的普通顺序读法『Normal indexing: 3 2 1』,这里肉眼看数值一样,但下标语义已变:ar[2] 在时间序列里是两根 K 前,普通模式里是末尾。写指标缓冲区时混用两种模式,回测可能在第 3 根 bar 就出错位信号。 开 MT5 新建一个 indicator,把这段 OnCalculate 粘进去编译跑一遍,切 EURUSD 的 M5 观察 Alert 弹出顺序,比看文档来得快。外汇与贵金属杠杆高,指标逻辑错位的实盘代价可能远超回测想象。

MQL5 / C++
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">class="kw">datetime &time[],
                const class="type">class="kw">double &open[],
                const class="type">class="kw">double &high[],
                const class="type">class="kw">double &low[],
                const class="type">class="kw">double &close[],
                const class="type">long &tick_volume[],
                const class="type">long &volume[],
                const class="type">int &spread[])
  {
class=class="str">"cmt">//---
   class="type">class="kw">double ar[]; class=class="str">"cmt">// 数组
   ArrayResize(ar,class="num">2); class=class="str">"cmt">// 准备数组
   ar[class="num">0]=class="num">1; class=class="str">"cmt">// 设置数值
   ar[class="num">1]=class="num">2;
   ArraySetAsSeries(ar,true); class=class="str">"cmt">// 更改索引顺序
   ArrayResize(ar,class="num">3); class=class="str">"cmt">// 增加数组大小
   ar[class="num">0]=class="num">3; class=class="str">"cmt">// 设置新数组元素的值
   Alert(ar[class="num">0]," ",ar[class="num">1]," ",ar[class="num">2]); class=class="str">"cmt">// 打印数组值
   ArraySetAsSeries(ar,false);
   Alert("Normal indexing: ", ar[class="num">0]," ",ar[class="num">1]," ",ar[class="num">2]); class=class="str">"cmt">// 打印数组值
class=class="str">"cmt">//--- 为下一次调用返回 prev_calculated 的值
   class="kw">return(rates_total);
  }

◍ 数组扩容后索引顺序的坑

在 MT5 自定义指标里动态改数组大小,ArrayResize 不会帮你保住你以为的「逻辑顺序」。下面这段把数组先设成 2 个元素、填 9 和 8,再扩到 6,Alert 打出来 ar[2]~ar[5] 是随机脏值,不是自动补零。 如果先 ArraySetAsSeries(ar,true) 把索引倒过来,再 ArrayResize(ar,4),然后立刻 ArraySetAsSeries(ar,false) 复原,Alert 显示的 ar[0]~ar[3] 里新增元素同样是未初始化内容。实测第二次 Alert 的 ar[2]、ar[3] 每次加载可能不同,说明扩容方向和序列方向叠加时会留下内存残值。 把索引再切回 true 并写 ar[0]=8,此时 ar[0] 对应的是原序列最新端,打印出来前四项里只有被显式写过的位置是确定的。外汇与贵金属行情下跑这类脚本属高风险验证行为,建议先在策略测试器用历史数据观察,不要直接挂实盘。 别把正态当圣经:以为数组扩容会自动清干净旧内存,是写指标时最常见的隐性 bug 来源。

MQL5 / C++
const class="type">class="kw">datetime &time[],
const class="type">class="kw">double &open[],
const class="type">class="kw">double &high[],
const class="type">class="kw">double &low[],
const class="type">class="kw">double &close[],
const class="type">long &tick_volume[],
const class="type">long &volume[],
const class="type">int &spread[])
{
class=class="str">"cmt">//---
 class="type">class="kw">double ar[]; class=class="str">"cmt">// 数组
ArrayResize(ar,class="num">2); class=class="str">"cmt">// 准备数组
ar[class="num">0]=class="num">9; class=class="str">"cmt">// 设置数值
ar[class="num">1]=class="num">8;
ArrayResize(ar,class="num">6); class=class="str">"cmt">// 增加数组大小
Alert("Normal resize: ", ar[class="num">0]," ",ar[class="num">1]," ",ar[class="num">2]," ", ar[class="num">3], " ", ar[class="num">4], " ", ar[class="num">5]);
ArraySetAsSeries(ar,true); class=class="str">"cmt">// 更改索引顺序
ArrayResize(ar,class="num">4); class=class="str">"cmt">// 增加数组大小
ArraySetAsSeries(ar, false);
Alert("See the random element added: ", ar[class="num">0]," ",ar[class="num">1]," ",ar[class="num">2]," ", ar[class="num">3]);
ArraySetAsSeries(ar,true); class=class="str">"cmt">// 更改索引顺序 class="num">0
ar[class="num">0]=class="num">8; class=class="str">"cmt">// 设置新数组元素的值
Alert("Modify the first as serlies: ", ar[class="num">0]," ",ar[class="num">1]," ",ar[class="num">2]," ", ar[class="num">3]); class=class="str">"cmt">// 打印数组值
class=class="str">"cmt">//--- 为下一次调用返回 prev_calculated 的值
 class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|indexingarraytest.mq5
class=class="str">"cmt">//|版权所有 class="num">2024 年,MetaQuotes 有限公司。|
class=class="str">"cmt">//|https://www.mql5.com |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
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=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指示器初始化函数
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
 {
class=class="str">"cmt">//--- 指示器缓冲区映射

class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,

「一点提醒」

上面这段 OnCalculate 尾部演示了 MQL5 里数组尺寸与索引方向的实机行为:ArrayResize(ar,2) 后写入 1、2,再 ArrayResize(ar,3) 扩到 3 个元素,未赋值的新元素 ar[2] 默认是 0,Alert 输出为 "1 2 0"。 把 ArraySetAsSeries(ar,true) 打开后,索引倒序,ar[0] 指向逻辑末尾,此时打印仍是 "1 2 0";随后 ar[0]=8 实际改的是原 ar[2],输出变成 "8 2 1"。切回 false 恢复正常索引,ar[0] 回到首个元素,值已是扩维时遗留的 1,打印 "1 2 8"。 外汇与贵金属行情跳空频繁,这类数组方向误用会让指标缓冲区算错前后根 K 线关系,概率上引发信号偏移。开 MT5 把这段代码贴进自定义指标跑一遍,比看文档直观。

MQL5 / C++
const class="type">int prev_calculated,
const class="type">class="kw">datetime &time[],
const class="type">class="kw">double &open[],
const class="type">class="kw">double &high[],
const class="type">class="kw">double &low[],
const class="type">class="kw">double &close[],
const class="type">long &tick_volume[],
const class="type">long &volume[],
const class="type">int &spread[])
{
class=class="str">"cmt">//---
 class="type">class="kw">double ar[]; class=class="str">"cmt">// 数组
ArrayResize(ar,class="num">2); class=class="str">"cmt">// 准备数组
ar[class="num">0]=class="num">1; class=class="str">"cmt">// 设置数值
ar[class="num">1]=class="num">2;
ArrayResize(ar,class="num">3); class=class="str">"cmt">// 增加数组大小
Alert("Redimension to class="num">3 normal: ", ar[class="num">0], " ", ar[class="num">1], " ", ar[class="num">2]);
ArraySetAsSeries(ar,true); class=class="str">"cmt">// 更改索引顺序
Alert("Redimension to class="num">3 series: ", ar[class="num">0], " ", ar[class="num">1], " ", ar[class="num">2]);
ar[class="num">0]=class="num">8; class=class="str">"cmt">// 设置新数组元素的值
Alert("Change the first element: ", ar[class="num">0]," ",ar[class="num">1]," ",ar[class="num">2]); class=class="str">"cmt">// 打印数组值
ArraySetAsSeries(ar, false);
Alert("Normal renew: ", ar[class="num">0]," ",ar[class="num">1]," ",ar[class="num">2]); class=class="str">"cmt">// 打印数组值
class=class="str">"cmt">//--- 为下一次调用返回 prev_calculated 的值
 class="kw">return(rates_total);
}
把数组诊断交给小布盯盘
这些数组相关的内存与尺寸诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到运行时数组占用的异常提示,你只管调策略逻辑。

常见问题

ArrayResize 只改动态数组尺寸且保留已有元素,ArrayFree 直接释放内存把大小置 0,后续再用需重新 Resize。
可以,小布盯盘的品种页会基于 AIGC 标记出常见的数组索引越界与未初始化动态数组的告警,省去手动翻日志。
二分依赖有序区间的中值收敛,未排序数组的中值不具备划分意义,搜索结果会直接错乱。
封装后能隐藏索引换算、统一边界检查,EA 扩展时改维度不用全量改访问代码,维护成本更低。
编译期尺寸固定,运行期无法扩容;若预估不准只能改用动态数组,或在声明时用常数预留余量。