MQL5 中的电子表格:用 CTable 类在二维数组里玩转异构数据(基础篇)
📊

MQL5 中的电子表格:用 CTable 类在二维数组里玩转异构数据(基础篇)

(1/3)· 还在为 MQL5 里多类型数据混存发愁?一个基类让表格操作像脚本一样轻

含代码示例实战向 第 1/3 篇
很多人以为 MQL5 只能跑单一类型的二维数组,遇到异构数据就拆好几个缓冲硬凑。直接用裸数组管行列,复制截断一多,内存和索引就乱成麻。这篇先铺基类,别急着上手写业务。

◍ 在 MT5 里直接调出电子表格视图

MT5 终端内置了电子表格(Sheet)视图,可把当前图表或自定义数据按行列呈现,省去导出 CSV 再开 Excel 的来回。对做价格行为复盘的人来说,它适合临时把若干根 K 线的 OHLC 拉成二维表,肉眼比对着找分型与缺口。 实操路径:在终端数据窗口或自定义指标面板里切到表格模式,就能看到类似 8 一月 2014, 09:39 这样的带毫秒时间戳行记录。社区里一条相关分享帖累计浏览 1 984 次、跟帖 11 条(Mykola Demko 发布),说明老交易员早就在用这种轻量方式核数据。 外汇与贵金属波动受杠杆和流动性影响,表格里的数值只是历史切片,不能直接推演下一根 K 线方向,概率上只帮你排除明显异常值。

「为什么要在 MQL5 里造一个二维表类」

做 EA 或指标时,经常要在内存里临时存一堆异构数据:价格、手数、字符串标签混在一起。MQL5 原生的二维数组只能装同一类型,直接硬写既难维护又容易越界。这里要做的不是一个山寨 Excel,而是一个轻量 CTable 类,把二维数组包装成能塞不同类型数据的容器。 它本质上接近 C++ 里 Variant 类的简化版,当列数压到 1 时就是个变体数组。性能上肯定比纯同类型二维数组慢——毕竟多了类型判别和寻址封装,但换来了写策略时少掉几层繁琐的类型转换。外汇与贵金属杠杆高、滑点突变频繁,这类工具只负责把数据摆清楚,不替你下任何方向判断。 赶时间的可以直接看 CTable 暴露的方法清单,后面再回头抠实现。先把‘能往表里丢什么、怎么按坐标取出来’跑通,比死磕底层算法更实用。

CTable 类的方法接口逐一拆解

CTable 这套二维表封装,对外暴露的方法基本覆盖了建表、改尺寸、复制、排序和检索全链路。先说尺寸控制:FirstResize 吃一个 ENUM_DATATYPE 数组,行数直接取该数组长度,不用单独传行数参数;SecondResize(int j) 则只动第二维,把列数统一拉到 j,相当于给表加行。 FirstSize 和 SecondSize 分别回传第一维(行)和第二维(列)的当前长度,纯查询无副作用。PruningTable(int count) 不改实际内存,只是改写记录行长的变量,真实分配大小由另一个内部变量管着,所以在初始分配范围内虚拟截短是零成本的,复制表时拿它剔掉不要的行很顺手。 CopyTable 有两个重载:无参版按源表 sor 的整体结构初始化接收表,可当构造函数用;带 sec_beg/sec_end 的版本只搬第二维的闭区间,数据从接收表头部接上,接收表大小被强制设为 sec_end-sec_beg+1。注意复制不带走排序变体,只搬大小、列类型和数据。 变体(variant)机制像给表挂了多个处理快照。默认用 0 号,variant(int ind) 切换时若编号比已分配少就不重分配内存;variantcopy(rec,sor) 把 sor 快照拷到 rec,rec 不存在就自动扩编号并切过去。排序靠 SortTwoDimArray(i,beg,end,mode),mode 默认 false 即沿索引增大方向排;排完用 QuickSearch 做等值快搜,SearchFirst/Last 抓首尾匹配,SearchGreat/Less 抓最近的大于或小于值,这些都必须传此前用过的同款排序 mode。 Set/Get 按列类型做赋值保护,类型不对就警告且不写,唯独字符串可转进其他列类型图个方便;sGet 永远以字符串回传,不管列本来啥类型。StringDigits 管双精度小数位和 datetime 是否显秒,精度绑在列上不随复制走,新表复制后精度回默认。 下面这段声明直接贴出来,方便你在 MT5 里对照头文件核对签名:

MQL5 / C++
class="type">void  FirstResize(const ENUM_DATATYPE &TYPE[]);
class="type">void  SecondResize(class="type">int j);
class="type">int   FirstSize();
class="type">int   SecondSize();
class="type">void  PruningTable(class="type">int count);
class="type">void  CopyTable(CTable *sor);
class="type">void  CopyTable(CTable *sor,class="type">int sec_beg,class="type">int sec_end);
ENUM_DATATYPE  TypeTable(class="type">int i)
class="type">bool  Change(class="type">int &sor0,class="type">int &sor1);
class="type">bool  Insert(class="type">int rec,class="type">int sor);
class="type">void  variant(class="type">int ind);
class="type">int   variant(); 
class="type">void  variantcopy(class="type">int rec,class="type">int sor);
class="type">void  SortTwoDimArray(class="type">int i,class="type">int beg,class="type">int end,class="type">bool mode=false);
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);
class="type">int   SearchFirst(class="type">int i,class="type">long element,class="type">int beg,class="type">int end,class="type">bool mode=false);
class="type">int   SearchLast(class="type">int i,class="type">long element,class="type">int beg,class="type">int end,class="type">bool mode=false);
class="type">int   SearchGreat(class="type">int i,class="type">long element,class="type">int beg,class="type">int end,class="type">bool mode=false);
class="type">int   SearchLess(class="type">int i,class="type">long element,class="type">int beg,class="type">int end,class="type">bool mode=false);

◍ 用 CTable 拼一张可裁切的数据表

在 MT5 里做多品种监控或回测记录时,用二维表比数组直观得多。下面这段接口定义了按行列写入和读取的四种重载:整型长量、浮点、时间、字符串,分别对应不同列的数据性质。 读取侧除了 Get 按引用回传,还提供了 sGet 直接返回字符串,方便 Print 或拼接日志。注意 Get 的 recipient 前带 & 符号,是 MQL5 的引用传参,函数内修改会直接落到外部变量。 实际建表时先 #include <Table.mqh>,用 ENUM_DATATYPE 数组声明每列类型。示例中 7 列分别定义为 LONG、LONG、STRING、DATETIME、STRING、STRING、DOUBLE,下标 0~6 对应注释里的列序。 建表后 FirstResize(TYPE) 锁定列类型,SecondResize(5) 把行数扩到 5 行;Set(6,0,"321.012324568") 往第 6 列第 0 行写串,随后 Insert(2,6) 把第 6 列插到位置 2,PruningTable(3) 裁到只剩 3 列。StringDigits(2,5) 设第 2 列小数位 5 位,Print 出来的是 table 321.01232 这种截断效果。 最后 CopyTable(GetPointer(table)) 把整表浅拷贝给 table1,再对 table1 设 8 位精度,两张表此后精度互不影响。外汇与贵金属波动剧烈,这类表只作本地数据分析,实盘信号须自行验证风险。

MQL5 / C++
class="type">void   Set(class="type">int i,class="type">int j,class="type">long     value); class=class="str">"cmt">// setting value of the i-th row and j-th column
class="type">void   Set(class="type">int i,class="type">int j,class="type">class="kw">double   value); class=class="str">"cmt">// setting value of the i-th row and j-th columns
class="type">void   Set(class="type">int i,class="type">int j,class="type">class="kw">datetime value);class=class="str">"cmt">// setting value of the i-th row and j-tj column
class="type">void   Set(class="type">int i,class="type">int j,class="type">class="kw">string   value); class=class="str">"cmt">// setting value of the i-th row and j-th column

  class=class="str">"cmt">//--- getting value
class="type">void   Get(class="type">int i,class="type">int j,class="type">long     &recipient); class=class="str">"cmt">// getting value of the i-th row and j-th column
class="type">void   Get(class="type">int i,class="type">int j,class="type">class="kw">double   &recipient); class=class="str">"cmt">// getting value of the i-th row and j-th column
class="type">void   Get(class="type">int i,class="type">int j,class="type">class="kw">datetime &recipient); class=class="str">"cmt">// getting value of the i-th row and j-th column
class="type">void   Get(class="type">int i,class="type">int j,class="type">class="kw">string   &recipient); class=class="str">"cmt">// getting value of the i-th row and j-th column
class="type">class="kw">string sGet(class="type">int i,class="type">int j); class=class="str">"cmt">// class="kw">return value of the i-th row and j-th column 
class="type">void   StringDigits(class="type">int i,class="type">int digits);
class="type">int    StringDigits(class="type">int i);
class="macro">#include <Table.mqh>  
ENUM_DATATYPE TYPE[class="num">7]=
  {TYPE_LONG,TYPE_LONG,TYPE_STRING,TYPE_DATETIME,TYPE_STRING,TYPE_STRING,TYPE_DOUBLE};
class=class="str">"cmt">//      class="num">0           class="num">1           class="num">2               class="num">3               class="num">4           class="num">5           class="num">6  //class="num">7 
class="type">void OnStart()
  {
   CTable table,table1;
   table.FirstResize(TYPE);                class=class="str">"cmt">// dividing table, determining column types  
   table.SecondResize(class="num">5);                  class=class="str">"cmt">// change the number of rows   
   table.Set(class="num">6,class="num">0,"class="num">321.012324568");         class=class="str">"cmt">// assigning data to the class="num">6-th column, class="num">0 row
   table.Insert(class="num">2,class="num">6);                     class=class="str">"cmt">// insert class="num">6-th column in the class="num">2-nd position
   table.PruningTable(class="num">3);                 class=class="str">"cmt">// cut the table to class="num">3 columns
   table.StringDigits(class="num">2,class="num">5);               class=class="str">"cmt">// set precision of class="num">5 digits after the decimal point
   Print("table ",table.sGet(class="num">2,class="num">0));       class=class="str">"cmt">// print the cell located in the class="num">2-nd column, class="num">0 row  
   table1.CopyTable(GetPointer(table));   class=class="str">"cmt">// copy the entire table &class="macro">#x27;table&class="macro">#x27; to the &class="macro">#x27;table1&class="macro">#x27; table
   table1.StringDigits(class="num">2,class="num">8);              class=class="str">"cmt">// set class="num">8-digit precision
交给小布盯盘看盘口
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到;把重复劳动交给小布,你专注决策。

常见问题

单一类型直接访问的二维数组最快;CTable 为方便异构操作牺牲部分性能,倾向用于逻辑复杂但数据量不大的场景。
FirstResize 依据传入的类型数组定列数与单元格类型,SecondResize 改行数,二者分工明确避免误写越界。
目前小布盯盘内置的是对品种页诊断的 AIGC 视图,自定义 CTable 结构需自行桥接;把重复劳动交给小布,你专注决策。
它只在初始分配内存范围内改写行长度变量,不真正释放底层内存,复制表格时用来裁掉无用段更合适。
不会,只复制大小、列类型与数据,排序内部结构保留在源表,接收表按新示例重建。