MQL5 编程基础:数组·综合运用
(3/3)· 数组不再只是容器,复制、排序、二分与多维封装决定 EA 的执行效率与可维护性
◍ 数组索引正逆序的新元素落点差异
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 更顺手,复制完价格数据直接逆序索引省一道转换。
<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 就是三个同值元素。
<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>);
<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 响应,建议先在脚本里测耗时。
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,和二分函数的‘近似返回’语义完全不同,写回测撮合时别混用。
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),函数取更靠近数组开头的那个索引。逆序索引数组也一样,返回的是最小索引值。 外汇与贵金属行情序列处理高风险,极值函数只解决定位,不预示任何方向;实盘使用前建议在历史数据上复跑确认边界行为。
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[] 前务必先删指向的对象,否则可能漏释放。
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 在实盘高波动段变卡,属于高风险隐患。
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)跑一遍就能复现。
<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 再跑回测验证。
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 仍得防服务器拒绝或滑点。
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 的循环开销远低于网络往返与订单执行延迟;真到了数百持仓的篮子策略再考虑换哈希结构,不必提前过度设计。
{
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 弹出顺序,比看文档来得快。外汇与贵金属杠杆高,指标逻辑错位的实盘代价可能远超回测想象。
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 来源。
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 把这段代码贴进自定义指标跑一遍,比看文档直观。
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); }