MQL5 中的电子表格:用 CTable 类在二维数组里玩转异构数据(基础篇)
(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 里对照头文件核对签名:
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 位精度,两张表此后精度互不影响。外汇与贵金属波动剧烈,这类表只作本地数据分析,实盘信号须自行验证风险。
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