矩阵实用工具,扩展矩阵和向量的标准库功能(基础篇)
◍ 从标准库扩出矩阵实战工具
MT5 自带的矩阵和向量标准库只覆盖最基础的线性代数运算,真要做统计滤波或特征构造,往往要自己补一层工具函数。2023 年 5 月社区里就有作者基于标准库做了扩展封装,这类思路对做多品种协整或 PCA 降维的交易者直接有用。 外汇与贵金属杠杆高、跳空频繁,拿矩阵算相关系数时务必先剔除停牌与异常 tick,否则样本矩阵可能病态不可逆。 这类扩展通常不动底层,只是在 CMatrix 和 CVector 上包一层方法,比如按列标准化、滚动协方差。你可以在 MT5 里新建 include 把标准库 include 进来,照着自己策略补几个方法,比每次手写循环省事且不易错。
给标准库矩阵工具打补丁
MQL5 自带的矩阵标准库已经能覆盖初始化、转换、操纵等基础动作,但在实盘特征工程里经常不够用。比如要把 CSV 里的历史 tick 序列直接读成矩阵、把向量拆成 X/Y 训练集、或者做独热编码,标准库并不会替你写好,得在 Utils 类里扩展静态方法,免得每次都重新造轮子。 下面这些扩展点没有优先级之分,都是直接从工程里抠出来的:从 CSV 读矩阵、读编码值、写回 CSV;矩阵与向量互转、数组与向量互转;删单列/多列/单行、删向量单值;按训练测试比例拆矩阵、拆 X 和 Y;建设计矩阵、独热编码、从向量取类标签;生成随机向量、向量间追加与复制。 外汇和贵金属行情的高波动特性意味着,任何矩阵预处理错误都会直接放大到信号层,扩展这些方法时建议先在 MT5 策略测试器里用小样本 CSV 跑通再接实盘数据。
「把 CSV 塞进矩阵:自己写读取函数」
做信号回测或加载历史成交时,经常要把 CSV 里的数值直接灌进 MT5 的 matrix 对象。标准库里并没有现成的 Matrix.FromFile() 之类接口,作者因此自己写了 ReadCsv 方法,跳过首行字符串表头,只把数值部分落进矩阵。 函数假定 CSV 第一行全是列名(字符串),读取时先塞进 csv_header 数组方便后续对照字段;从第二行开始,每一格字符串转成 double 写入 mat_ 对应坐标。遇到行结束符就给矩阵扩一行,最后统一 Resize 去掉表头占的那一行。 实测中若文件被别的程序占用或路径不对,handle 会返回 INVALID_HANDLE,代码里用 GetLastError 区分「文件在使用中」还是其他错误。外汇与贵金属数据加载同样适用此思路,但行情跳空可能导致某行列数不齐,矩阵维度会报错,需自行校验。 下面这段是核心循环与函数壳,注意 FileReadString 按分隔符切字段,首行 rows==0 只存表头不写矩阵:
matrix CMatrixutils::ReadCsv(class="type">class="kw">string file_name,class="type">class="kw">string delimiter=",") { matrix mat_ = {}; class="type">int rows_total=class="num">0; class="type">int handle = FileOpen(file_name,FILE_READ|FILE_CSV|FILE_ANSI,delimiter); ResetLastError(); if(handle == INVALID_HANDLE) { printf("Invalid %s handle Error %d ",file_name,GetLastError()); Print(GetLastError()==class="num">0?" TIP | File Might be in use Somewhere else or in another Directory":""); } else { class="type">int column = class="num">0, rows=class="num">0; class="kw">while(!FileIsEnding(handle)) { class="type">class="kw">string data = FileReadString(handle); class=class="str">"cmt">//--- if(rows ==class="num">0) { ArrayResize(csv_header,column+class="num">1); csv_header[column] = data; } if(rows>class="num">0) class=class="str">"cmt">//Avoid the first column which contains the column&class="macro">#x27;s header mat_[rows-class="num">1,column] = (class="type">class="kw">double(data)); column++; class=class="str">"cmt">//--- if(FileIsLineEnding(handle)) { rows++; mat_.Resize(rows,column); column = class="num">0; } } rows_total = rows; FileClose(handle); } mat_.Resize(rows_total-class="num">1,mat_.Cols()); class="kw">return(mat_); }
◍ 把多资产快照塞进矩阵再打印出来
做跨市场关联分析时,最怕一列列手工对齐。把 S&P500、50SMA、13RSI、NASDAQ 这类异构数据先写进一个 matrix,再用 ArrayPrint 把表头打出来,肉眼就能确认列序没乱。 下面这段在 EURUSD H1 图表上跑出的日志,第一行表头是 "S&P500" "50SMA" "13RSI" "NASDAQ",紧接着是 9 行矩阵数据。例如首行 S&P500=4173.8、50SMA=13386.6、13RSI=34.8、NASDAQ=13067.5;到第 9 行 50SMA 已爬到 13457.8,但 13RSI 始终在 31~37 区间晃,说明均线发散而动量没跟上。 这种打印方式的价值在于:你改了任意一列的计算逻辑,重跑一次就能从终端日志直接比对数值漂移,不用切图表。外汇和贵金属自带高杠杆高风险,这类跨市场矩阵只作共振参考,信号失效概率始终存在。
ArrayPrint(matrix_utils.csv_header); Print(Matrix); CS class="num">0 class="num">06:class="num">48:class="num">21.228 matrix test(EURUSD,H1) "S&P500" "50SMA" "13RSI" "NASDAQ" CS class="num">0 class="num">06:class="num">48:class="num">21.228 matrix test(EURUSD,H1) [[class="num">4173.8,class="num">13386.6,class="num">34.8,class="num">13067.5] CS class="num">0 class="num">06:class="num">48:class="num">21.228 matrix test(EURUSD,H1) [class="num">4179.2,class="num">13396.7,class="num">36.6,class="num">13094.8] CS class="num">0 class="num">06:class="num">48:class="num">21.228 matrix test(EURUSD,H1) [class="num">4182.7,class="num">13406.6,class="num">37.5,class="num">13108] CS class="num">0 class="num">06:class="num">48:class="num">21.228 matrix test(EURUSD,H1) [class="num">4185.8,class="num">13416.8,class="num">37.1,class="num">13104.3] CS class="num">0 class="num">06:class="num">48:class="num">21.228 matrix test(EURUSD,H1) [class="num">4180.8,class="num">13425.2,class="num">34.9,class="num">13082.2] CS class="num">0 class="num">06:class="num">48:class="num">21.228 matrix test(EURUSD,H1) [class="num">4174.6,class="num">13432.2,class="num">31.8,class="num">13052] CS class="num">0 class="num">06:class="num">48:class="num">21.228 matrix test(EURUSD,H1) [class="num">4174.9,class="num">13440.4,class="num">33.2,class="num">13082.2] CS class="num">0 class="num">06:class="num">48:class="num">21.228 matrix test(EURUSD,H1) [class="num">4170.8,class="num">13447.6,class="num">32.2,class="num">13070.6] CS class="num">0 class="num">06:class="num">48:class="num">21.228 matrix test(EURUSD,H1) [class="num">4182.2,class="num">13457.8,class="num">32.5,class="num">13078.8]
把带字符串的 CSV 拆成可编码数组
做分类类策略时,常碰到含字符串的 CSV(比如天气类公开数据集)。MT5 的 matrix 只能装数值,装不了文本,所以得先把整份文件读进字符串数组,再逐列编码后灌回矩阵。 ReadCsvEncode() 的思路是先探明列数:打开文件一路 FileReadString 直到行尾,统计 csv_columns,顺手把表头存进 csv_header[]。实测一份 14 列的数据集,单列读完后 toArr[] 里索引 0–13 是第一列、14–27 是第二列,顺序和原文件一致,之后按段切分就能拿到每列原始值。 LabelEncoder() 按「相同特征贴同标签」的原则工作,核心在 Classes():它扫一遍数组提取去重后的类,写入引用的 classes_arr[],还有个向量版变体可用。下面这段是列读取的骨架代码,注意它每次迭代重开一次 CSV 只取第 i+1 列,且 rows>=1 跳过了表头行。 外汇与贵金属市场波动剧烈、杠杆风险高,这类编码仅用于本地特征工程,不代表任何方向判断。
class=class="str">"cmt">//--- Obtaining the columns matrix Matrix={}; class=class="str">"cmt">//--- Loading the entire Matrix to an Array class="type">int csv_columns=class="num">0, rows_total=class="num">0; class="type">int handle = CSVOpen(file_name,delimiter); if (handle != INVALID_HANDLE) { class="kw">while (!FileIsEnding(handle)) { class="type">class="kw">string data = FileReadString(handle); csv_columns++; class=class="str">"cmt">//--- if (FileIsLineEnding(handle)) class="kw">break; } } FileClose(handle); ArrayResize(csv_header,csv_columns); class=class="str">"cmt">//--- class="type">class="kw">string toArr[]; class="type">int counter=class="num">0; for (class="type">int i=class="num">0; i<csv_columns; i++) { if ((handle = CSVOpen(file_name,delimiter)) != INVALID_HANDLE) { class="type">int column = class="num">0, rows=class="num">0; class="kw">while (!FileIsEnding(handle)) { class="type">class="kw">string data = FileReadString(handle); column++; class=class="str">"cmt">//--- if (column==i+class="num">1) { if (rows>=class="num">1) class=class="str">"cmt">//Avoid the first column which contains the column&class="macro">#x27;s header { counter++; ArrayResize(toArr,counter); class=class="str">"cmt">//array size for all the columns toArr[counter-class="num">1]=data; } else csv_header[column-class="num">1] = data; } class=class="str">"cmt">//--- if (FileIsLineEnding(handle))
「把 CSV 读进矩阵后打印出来看一眼」
上面这段是文件解析收尾的逻辑:内层循环每填满一行就把 rows 加一、column 归零;跳出后把有效行数(rows-1)累加到 rows_total,最后用 FileClose(handle) 关掉句柄,避免 MT5 里文件描述符泄漏。 紧跟着的日志是 matrix test 在 US500 的 D1 周期上跑出来的真实输出。从 [0] 到 [48] 共 5 行打印,每行 12 个单元格,拼起来是一个 5×12 的分类矩阵:天气(Sunny/Overcast/Rain)、温度(Hot/Mild/Cool)、湿度(High/Normal)、风力(Weak/Strong)和是否play(Yes/No)都被原样读进了矩阵。 你在 MT5 终端里用专家日志看这几行,能直接核对 CSV 解析有没有错位——比如 [12] 那行开头是 Overcast、Rain、Hot,若你本地文件里对应位置不是这三个值,就说明分隔符或换行处理有 bug。外汇和贵金属行情用类似方法灌进矩阵时,高风险在于历史数据缺口会让行数统计偏少,回测信号可能因此失真。
{
rows++;
column = class="num">0;
}
}
rows_total += rows-class="num">1;
}
FileClose(handle);
}
class=class="str">"cmt">//---