MQL5 中的电子表格·综合运用
📊

MQL5 中的电子表格·综合运用

(3/3)· 从数组排列到程序结构,把前篇的表格基类真正跑进可用脚本

新手友好 第 3/3 篇
很多人写了 CTable 类却只在构造函数里转圈,一碰到动态加行、跨表拷贝就重写整套逻辑。二维变体数组不是玩具,误用 FirstResize 和 PruningTable 会让你在回测里悄悄丢数据。

「用类包装实现动态二维数组排列」

在 MQL5 里直接声明动态二维数组并不方便做灵活排列,更实用的做法是把一维数组先包成一个类,再用指针式对象数组去调用这些一维数组,本质上就是「数组的数组」。 上面这段代码给出了最小可编译骨架:CarrayInt 内部只放一个 int 动态数组 array[],CTwoarrayInt 则放一个 CarrayInt 动态数组 array[]。两层都靠 MQL5 的对象数组机制在堆上动态扩列。 在 OnStart 里声明 CTwoarrayInt two 后,two.array[0].array[0] 就能定位到「第 0 行第 0 列」的元素。实际跑之前记得先 Resize 两层 array,否则下标访问会抛数组越界。外汇与贵金属 EA 里用这种结构缓存多周期信号矩阵时,要注意动态扩容带来的内存抖动风险。

MQL5 / C++
class CarrayInt
  {
class="kw">public:
                    ~CarrayInt(){};
  class="type">int                array[];
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTwoarrayInt
  {
class="kw">public:
                    ~CTwoarrayInt(){};
  CarrayInt         array[];
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
  CTwoarrayInt two;
  two.array[class="num">0].array[class="num">0];
  }

◍ 用模板拼出二维表的内核

CTable 这套二维表不是从头造轮子,而是把标准库的数组类用伪模板改写后快速堆出来的。基类 CBASEArray 只在 CTable 私有区段里以动态指针数组形式存在,真正的对象构建和实例选择都压在 FirstResize() 里完成,CopyTable() 因为内部调用了它,也能顺带把这件事干了。 派生类 CLONGArray、CDOUBLEArray、CDATETIMEArray、CSTRINGArray 各自在私有段塞了一个同类型数组,公共方法名和基类保持一致,但逻辑全靠覆盖。CTable 本身不搬数据,只改索引对象里的值;列的位置关系交给私有动态数组 aic[] 管,第一个维度索引靠它重排,比如 Change() 操作只交换两个存列索引的内存单元,看起来像挪列其实没动真数据。 为了避免重复代码过长,频繁出现的覆盖段用宏定义替掉。下面这段预处理宏和快速排序片段就是典型:_CHECK_ 在类型不匹配时打印函数名和参数并返回 -1;_FIRST_ 取 first_data[aic[i]] 做间接访问;QuickSearch 先 check_type 拦掉非 long 列,再转发给底层。 快速排序从标准类搬过来时改了两点:不稳定排序前先备份数据,同时按数据改动模式同步索引对象。原算法没有 Cint2D 实例,这里把 index.Ind(i) 当成交互换位的载体,i、j 双指针扫描时连数据带索引一起换。想验证就开 MT5 把这段宏贴进 EA,故意传错列类型看 _CHECK_ 是否报函数名加参数。外汇与贵金属行情高波动,这类结构仅用于回测与逻辑验证,实盘须自担风险。

MQL5 / C++
class="macro">#define _CHECK0_ Print(__FUNCTION__+("(")+((class="type">class="kw">string)i+(",")+((class="type">class="kw">string)j+(")"))));class="kw">return;
class="macro">#define _CHECK_ Print(__FUNCTION__+("(")+((class="type">class="kw">string)i+(")")));class="kw">return(-class="num">1);
class="macro">#define _FIRST_ first_data[aic[i]]
class="macro">#define _PARAM0_ array_index.Ind(j),value
class="macro">#define _PARAM1_ array_index.Ind(j),recipient
class="macro">#define _PARAM2_ element,beg,end,array_index,mode
class="type">int QuickSearch(class="type">int i,class="type">long element,class="type">int beg,class="type">int end,class="type">bool mode=false){if(!check_type(i,TYPE_LONG)){_CHECK_}class="kw">return(_FIRST_.QuickSearch(_PARAM2_));};
class="type">int QuickSearch(class="type">int i,class="type">long element,class="type">int beg,class="type">int end,class="type">bool mode=false){if(!check_type(i,TYPE_LONG)){Print(__FUNCTION__+("(")+((class="type">class="kw">string)i+(")")));class="kw">return(-class="num">1);} class="kw">return(first_data[aic[i]].QuickSearch(element,beg,end,array_index,mode));};
class="type">void CLONGArray::QuickSort(class="type">long &m_data[],Cint2D &index,class="type">int beg,class="type">int end,class="type">bool mode=class="num">0)
...
        if(i<=j)
          {
           t=m_data[i];          it=index.Ind(i);
           m_data[i++]=m_data[j];  index.Ind(i-class="num">1,index.Ind(j));
           m_data[j]=t;            index.Ind(j,it);
           if(j==class="num">0) break;
           else     j--;
          }
...

一点提醒

CTable 类在 MQL5 里用变体类型实现了通用表格数据容器,本身就能当复杂应用的基类,没必要为了灵活去改语言标准或削弱类型安全。 从附件看,table.mqh 体积 61.33 KB,说明这套封装不是玩具级代码,开 MT5 把 ZIP 里的头文件拖进 Include 目录就能直接继承复用。 外汇和贵金属行情波动剧烈、杠杆风险高,用这类通用类搭 EA 前,先在策略测试器跑历史回测确认逻辑,再上模拟盘验证。 面向对象把多币种多周期的处理拆干净了,但架构清晰不代表胜率有保证,参数和品种相关性仍要你自己调。

把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到表格型指标的排列与拷贝是否越界,你专注决策就好。

常见问题

SecondResize 真正为第二维度所有数组分配新长度,增加物理行数;PruningTable 只在初始分配内存范围内改写第一维度长度变量,不触内存,常用于裁掉复制后的多余部分。
MQL5 对象默认按值语义处理,表格内部持有动态数组,用 GetPointer 传引用才能避免拷贝截断,让接收表完整拿到列类型与数据。
可以,小布盯盘的 AIGC 模块支持把 CTable 派生的二维数据以表格视图呈现,省去你自己写面板排列代码。
混合类型走 Variant 式封装,失去直接内存寻址,单次读写倾向慢于同构数组;在高频 OnTick 里大量调用可能成为瓶颈,建议预划分好列。