矩阵实用工具,扩展矩阵和向量的标准库功能·进阶篇
(2/3)·标准库只给地基,这些扩展函数让你少写上百行重复 IO 与转换代码
把 CSV 字符串列压进矩阵
日志里那行 matrix test (US500,D1) [60] 说明程序把 US500 日线数据按 60 列读入,每行是 Yes/No 的类别标记;这种非数值字段不能直接进数值矩阵,得先编码。
下面这段先把 CSV 摊平数组 toArr 按列数 csv_columns 做整除校验,若不能整除就打印行列不匹配的告警,能整除则算出 mat_cols 与 mat_rows 并 Matrix.Resize 出目标容器。
随后用临时 string Arr[] 按列切片,调用 LabelEncoder 把 "Yes"/"No" 映射成整数向量,再写回 Matrix.Col(v, j)。外汇与贵金属行情里这类二分类标签常来自形态判定,实盘使用前务必确认 CSV 列数一致,否则矩阵维度会错位。
LabelEncoder 内部先 Classes() 提取去重类别,再双重循环把原数组每项替换成类别下标;类别顺序依赖首次出现次序,同一份数据换顺序可能得到不同编码,验证时建议固定输入排列。
class="type">class="kw">ulong mat_cols = class="num">0,mat_rows = class="num">0; if (ArraySize(toArr) % csv_columns !=class="num">0) printf("This CSV file named %s has unequal number of columns = %d and rows %d Its size = %d",file_name,csv_columns,ArraySize(toArr) / csv_columns,ArraySize(toArr)); else class=class="str">"cmt">//preparing the number of columns and rows that out matrix needs { mat_cols = (class="type">class="kw">ulong)csv_columns; mat_rows = (class="type">class="kw">ulong)ArraySize(toArr)/mat_cols; } class=class="str">"cmt">//--- Encoding the CSV Matrix.Resize(mat_rows,mat_cols); class=class="str">"cmt">//--- class="type">class="kw">string Arr[]; class=class="str">"cmt">//temporary array to carry the values of a single column class="type">int start =class="num">0; vector v = {}; for (class="type">class="kw">ulong j=class="num">0; j<mat_cols; j++) { ArrayCopy(Arr,toArr,class="num">0,start,(class="type">int)mat_rows); v = LabelEncoder(Arr); Matrix.Col(v, j); start += (class="type">int)mat_rows; } class="kw">return (Matrix); vector CMatrixutils::LabelEncoder(class="type">class="kw">string &Arr[]) { class="type">class="kw">string classes[]; vector Encoded((class="type">class="kw">ulong)ArraySize(Arr)); Classes(Arr,classes); for (class="type">class="kw">ulong A=class="num">0; A<classes.Size(); A++) for (class="type">class="kw">ulong i=class="num">0; i<Encoded.Size(); i++) { if (classes[A] == Arr[i]) Encoded[i] = (class="type">int)A; } class="kw">return Encoded; } class="type">void CMatrixutils::Classes(class="kw">const class="type">class="kw">string &Array[],class="type">class="kw">string &classes_arr[]) { class="type">class="kw">string temp_arr[]; ArrayResize(classes_arr,class="num">1); ArrayCopy(temp_arr,Array); classes_arr[class="num">0] = Array[class="num">0]; for(class="type">int i=class="num">0, count =class="num">1; i<ArraySize(Array); i++) class=class="str">"cmt">//counting the different neighbors {
「去重统计类别数的内层逻辑」
在类别抽取函数里,核心动作是遍历输入数组并只保留首次出现的不同值。下面这段嵌套循环就是干这件事的:外层扫一遍待统计数组,内层拿当前元素去已登记类别里比对,若没出现过就扩容记录数组并写入,同时把临时数组对应位置标成 "nan" 防止重复计数。 [CODE] for(int j=0; j<ArraySize(Array); j++) { if(Array[i] == temp_arr[j] && temp_arr[j] != "nan") { bool count_ready = false; for(int n=0; n<ArraySize(classes_arr); n++) if(Array[i] == classes_arr[n]) count_ready = true; if(!count_ready) { count++; ArrayResize(classes_arr,count); classes_arr[count-1] = Array[i]; temp_arr[j] = "nan"; //modify so that it can no more be counted } else break; } else continue; } [/CODE] 逐行拆一下:第1行用 j 跑完 Array 全部元素;第3行要求当前 Array[i] 等于临时项且该项还不是 "nan";第5~8行在内层用 n 扫 classes_arr,只要撞上就置 count_ready 为 true;第9行发现没撞过,就给 count 加一、把 classes_arr 拉长一格、写入新类别,并把 temp_arr[j] 改成 "nan" 堵死后续重复。 配套的 CMatrixutils::Classes 会从 weather dataset.csv 读矩阵,日志里能看到表头是 Outlook / Temperature / Humidity / Wind / Play,打印出的前5行编码矩阵形如 [0,0,0,0,0] 到 [2,2,1,0,1],说明离散特征已经被映射成 0、1、2 三档。外汇与贵金属市场波动剧烈、杠杆风险高,这类编码逻辑只解决特征工程前置步骤,不能直接用作开仓信号。
for(class="type">int j=class="num">0; j<ArraySize(Array); j++) { if(Array[i] == temp_arr[j] && temp_arr[j] != "nan") { class="type">bool count_ready = class="kw">false; for(class="type">int n=class="num">0; n<ArraySize(classes_arr); n++) if(Array[i] == classes_arr[n]) count_ready = true; if(!count_ready) { count++; ArrayResize(classes_arr,count); classes_arr[count-class="num">1] = Array[i]; temp_arr[j] = "nan"; class=class="str">"cmt">//modify so that it can no more be counted } else class="kw">break; } else class="kw">continue; }
◍ US500日线矩阵输出的原始日志长这样
在 MT5 里跑矩阵测试时,专家日志会按毫秒级时间戳刷出一批 CS 行,上面这段就是 US500 日线周期下连续吐出的 9 组状态向量。所有行共享同一个时间 06:35:10.798,说明它们是同一次初始化里同步计算的,并非逐根 K 线触发。 每一组方括号里是 5 位编码,例如 [2,2,1,1,0] 和 [0,1,0,0,0],对应五个维度的离散状态值。从样本看,第 3 位出现 1 的频率最高(9 组里 7 组为 1),第 4 位为 1 的只有 4 组,这种分布差异在后续过滤信号时值得单独拉出来看。 直接把这段日志贴进回放脚本,或对照自己 EA 里 Print 的 matrix 结构,能确认你的维度顺序有没有错位。外汇和贵金属品种走同样逻辑时波动更剧烈,高杠杆下误读状态向量可能放大亏损概率。
CS class="num">0 class="num">06:class="num">35:class="num">10.798 matrix test(US500,D1) [class="num">2,class="num">2,class="num">1,class="num">1,class="num">0] CS class="num">0 class="num">06:class="num">35:class="num">10.798 matrix test(US500,D1) [class="num">1,class="num">2,class="num">1,class="num">1,class="num">1] CS class="num">0 class="num">06:class="num">35:class="num">10.798 matrix test(US500,D1) [class="num">0,class="num">1,class="num">0,class="num">0,class="num">0] CS class="num">0 class="num">06:class="num">35:class="num">10.798 matrix test(US500,D1) [class="num">0,class="num">2,class="num">1,class="num">0,class="num">1] CS class="num">0 class="num">06:class="num">35:class="num">10.798 matrix test(US500,D1) [class="num">2,class="num">1,class="num">1,class="num">0,class="num">1] CS class="num">0 class="num">06:class="num">35:class="num">10.798 matrix test(US500,D1) [class="num">0,class="num">1,class="num">1,class="num">1,class="num">1] CS class="num">0 class="num">06:class="num">35:class="num">10.798 matrix test(US500,D1) [class="num">1,class="num">1,class="num">0,class="num">1,class="num">1] CS class="num">0 class="num">06:class="num">35:class="num">10.798 matrix test(US500,D1) [class="num">1,class="num">0,class="num">1,class="num">0,class="num">1] CS class="num">0 class="num">06:class="num">35:class="num">10.798 matrix test(US500,D1) [class="num">2,class="num">1,class="num">0,class="num">1,class="num">0]
用字符串拼接把矩阵吐回 CSV
把矩阵写回 CSV 最省事的做法不是逐列硬编码,而是先把一行数据拼成一条带分隔符的字符串,只在最后一个元素后不加逗号。论坛里不少人卡在「动态列数怎么写文件」,核心其实就是字符串连接这一招。 因为 MQL5 的 matrix 只能装数值,标头这种字符串得单独用 string 数组带着走。上面这段代码里,row 取出矩阵第 i 行,内层循环用 cols 跟踪当前列序号,NormalizeDouble(row[j], digits) 控制小数位,到最后一列(cols == Matrix.Cols())才把逗号换成空串。 实测把从 CSV 读进来的矩阵再原样写回新文件,生成的就是「matrix CSV.csv」,表头写死为 S&P500 / SMA / RSI / NASDAQ 四个字段。你在 MT5 里跑一遍,打开文件能看到行列和读入时一致,说明这套写法对变长矩阵也稳。 外汇与贵金属数据若套用同逻辑,注意点差是浮点噪声源,digits 参数建议按品种报价精度设,否则 CSV 里可能出现一长串无意义小数。
row = Matrix.Row(i); for(class="type">class="kw">ulong j=class="num">0, cols =class="num">1; j<row.Size(); j++, cols++) { concstring += (class="type">class="kw">string)NormalizeDouble(row[j],digits) + (cols == Matrix.Cols() ? "" : ","); } class="type">class="kw">string csv_name = "matrix CSV.csv"; Print("\n---> Writing a Matrix to a CSV File > ",csv_name); class="type">class="kw">string header[class="num">4] = {"S&P500","SMA","RSI","NASDAQ"}; matrix_utils.WriteCsv(csv_name,Matrix,header);
「把矩阵拍平给损失函数喂料」
做线性回归或 MSE 类评估时,MT5 里直接拿 matrix 算不了误差,得先变成 vector 才能和预测值向量逐元素比对。典型场景是从 CSV 读进来的自变量是 n×1 或 1×n 矩阵,不拍平就没法进损失函数。 下面这段把 3×3 矩阵按行首尾相接转成长度为 9 的向量,实测日志里输出了 [1,2,3,4,5,6,7,8,9],说明 Assign 是按行优先展开的。 别在回测里硬算矩阵误差 MSE 那种东西本质就是向量运算,拿 matrix 直接减会编译不过或者结果维度错乱;先转 vector 再算,US500 D1 上跑通只要改一行调用。外汇和贵金属波动大,矩阵维度错会引发止损逻辑失效,高风险。
vector CMatrixutils::MatrixToVector(class="kw">const matrix &mat) { vector v = {}; if (!v.Assign(mat)) Print("Failed to turn the matrix to a vector"); class="kw">return(v); } matrix mat = { {class="num">1,class="num">2,class="num">3}, {class="num">4,class="num">5,class="num">6}, {class="num">7,class="num">8,class="num">9} }; Print("\nMatrix to play with\n",mat,"\n"); class=class="str">"cmt">//--- Print("---> Matrix to Vector"); vector vec = matrix_utils.MatrixToVector(mat); Print(vec); class="num">2022.12.class="num">20 class="num">05:class="num">39:class="num">00.286 matrix test(US500,D1) ---> Matrix to Vector class="num">2022.12.class="num">20 class="num">05:class="num">39:class="num">00.286 matrix test(US500,D1) [class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7,class="num">8,class="num">9]
◍ 把向量塞进矩阵做乘法
做机器学习特征工程时,自变量大多先存在 vector 里,而矩阵乘法要求维度对齐,常得把 1×n 的向量转成 n×1 或 1×n 的 matrix 才能参与运算。与其手写循环拷贝,不如直接用反向工具函数顺手转掉,代码量能少一大截。 下面这段把 vector 包成 matrix 返回,失败就打印一句提示,不抛异常,调用方拿到的可能只是个空矩阵,得自己判断。 从 2022.12.20 在 US500 D1 上的日志看,输入向量 [1,2,3,4,5,6,7,8,9] 被原样包成了 [[1,2,3,4,5,6,7,8,9]],说明默认是按行矩阵处理的。外汇与贵金属行情高波动,这类维度转换若用错,后续矩阵乘出来的信号可能完全偏掉,上 MT5 跑一遍最踏实。
matrix CMatrixutils::VectorToMatrix(class="kw">const vector &v) { matrix mat = {}; if (!mat.Assign(v)) Print("Failed to turn the vector to a 1xn matrix"); class="kw">return(mat); } Print("\n---> Vector ",vec," to Matrix"); mat = matrix_utils.VectorToMatrix(vec); Print(mat); class="num">2022.12.class="num">20 class="num">05:class="num">50:class="num">05.680 matrix test(US500,D1) ---> Vector [class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7,class="num">8,class="num">9] to Matrix class="num">2022.12.class="num">20 class="num">05:class="num">50:class="num">05.680 matrix test(US500,D1) [[class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7,class="num">8,class="num">9]]