MQL5 中的矩阵和向量操作(基础篇)
◍ MQL5 里的矩阵与向量能干什么
MQL5 在语言层面加入了矩阵(matrix)和向量(vector)这两类特殊数据类型,专门用来处理一大类数值计算问题。它们不是靠外部库硬凑,而是语言原生支持,并带一组内置方法。
写代码时可以直接用接近数学记号的写法,比如矩阵相乘、转置、求逆,可读性比早年用数组手搓要高出一个层级。对于做多资产协方差、因子加权、批量回测参数矩阵的交易者,这套类型能明显压缩代码量。
官方帮助里的「矩阵和矢量方法」一节列出了全部内置方法签名与约束(如维度匹配规则)。想在 MT5 里验证,新建一个 EA 或脚本,直接声明 matrix m; 看编译器是否认,是最快确认你客户端版本支持与否的办法。外汇与贵金属杠杆高、滑点随机,任何数值模型都只是概率辅助,别当成确定性信号。
「从数组到矩阵:MQL5 为何另起炉灶」
传统 MQL5 数组按索引存取,一维和二维结构能覆盖多数行情数据存储需求。但一旦碰到线性代数类任务,嵌套循环会迅速拖垮代码可读性,最简单的矩阵乘法也得手写多层遍历。 现代机器学习、神经网络与 3D 图形都建立在向量和矩阵运算之上。MQL5 因此引入了 matrix 和 vector 专用类型,把常规数学操作封装掉,减少样板代码。 下面这段声明展示了老式数组的三种常见形态:50 个整数的向量、7×50 的二维矩阵、以及 100 个自定义结构的集合。 对于外汇与贵金属策略研发,这类底层类型差异直接影响回测脚本的维护成本,高杠杆品种下模型误差放大,风险显著。
class="type">int a[class="num">50]; class=class="str">"cmt">// One-dimensional array of class="num">50 integers class="type">class="kw">double m[class="num">7][class="num">50]; class=class="str">"cmt">// Two-dimensional array of class="num">7 subarrays, each consisting of class="num">50 integers MyTime t[class="num">100]; class=class="str">"cmt">// Array containing elements of MyTime type
MQL5 里的向量与矩阵到底是什么
在 MQL5 中,向量就是一维的 double 数组,矩阵则是二维的 double 数组。两者行、列索引均从 0 起算,和常规数组一致;矩阵可视为若干水平向量按行堆叠,第一维是行号、第二维是列号,水平或垂直向量在语法层面不作区分。 除基础 matrix、vector(双精度)外,语言还提供 matrixf、matrixc、vectorf、vectorc 四种变体,分别承载浮点与复数数据。截至本文撰写时,matrixc 与 vectorc 的内部实现尚未完工,尚不能在内置方法里直接调用。 模板写法 matrix<double>、vector<float> 可等价替代具名类型,方便泛型封装。下面这段演示了 vectorf 的初始化与模板向量互赋值,输出为 [0,1,2,3],可直接贴进 MT5 脚本验证。 外汇与贵金属行情波动剧烈,用矩阵批量处理多品种信号时须自行做好越界与精度校验,相关计算结论仅具概率意义。
vectorf v_f1= {class="num">0, class="num">1, class="num">2, class="num">3,}; class=class="str">"cmt">// 声明浮点向量并用字面量初始化,含尾随逗号合法
vector<class="type">class="kw">float> v_f2=v_f1; class=class="str">"cmt">// 用模板写法声明同类型向量,直接拷贝 v_f1
Print("v_f2 = ", v_f2); class=class="str">"cmt">// 打印向量内容到日志
class=class="str">"cmt">/*
v_f2 = [class="num">0,class="num">1,class="num">2,class="num">3]
*/◍ MT5里矩阵与向量的几种声明姿势
在 MT5 的 MQL5 环境里,矩阵(matrix)和向量(vector)按用途分了九大类,但创建方式主要就几类。最轻量的做法是不指定大小直接声明,只写类型和变量名,此时不分配内存,后续再改尺寸并填数,也能直接丢进内置矩阵方法里参与计算。 也可以按指定大小声明,括号里写行数和列数(向量写长度),这时内存分配了但元素不初始化,值是脏的。第三种是声明同时用大括号初始化,尺寸由花括号内的数字序列自动推断,比如双精度矩阵 {{0.1,0.2,0.3},{0.4,0.5,0.6}} 就是 2×3。 除了直接声明,还有静态方法能造出特定填充规律的矩阵:Eye(4,5,1) 生成 4 行 5 列、主对角为 1 的矩阵,Full(3,4,M_PI) 把 3×4 全填成 π,Identity 和 Ones、Zeros 也类似。非静态的 Init 和 Fill 则能在对象已存在后按给定值重填,Init 还能传初始化函数按规则生成元素,例如重设尺寸后用随机值铺满。 下面这段代码把上述几种写法都列了出来,注意 float 和 double 类型不能混用赋值,且向量长度参数可以是变量或表达式(如 InpSize+16)。开 MT5 新建脚本把这些贴进去编译,就能直观看到每种对象占用的内存和默认排布。
matrix matrix_a; class=class="str">"cmt">// class="type">class="kw">double type matrix matrix<class="type">class="kw">double> matrix_a1; class=class="str">"cmt">// another way to declare a class="type">class="kw">double matrix, suitable for use in templates matrix<class="type">class="kw">float> matrix_a3; class=class="str">"cmt">// class="type">class="kw">float type matrix vector vector_a; class=class="str">"cmt">// class="type">class="kw">double type vector vector<class="type">class="kw">double> vector_a1; class=class="str">"cmt">// another notation to create a class="type">class="kw">double vector vector<class="type">class="kw">float> vector_a3; class=class="str">"cmt">// class="type">class="kw">float type vector matrix matrix_a(class="num">128,class="num">128); class=class="str">"cmt">// the parameters can be either constants matrix<class="type">class="kw">double> matrix_a1(InpRows,InpCols); class=class="str">"cmt">// or variables matrix<class="type">class="kw">float> matrix_a3(InpRows,class="num">1); class=class="str">"cmt">// analogue of a vertical vector vector vector_a(class="num">256); vector<class="type">class="kw">double> vector_a1(InpSize); vector<class="type">class="kw">float> vector_a3(InpSize+class="num">16); class=class="str">"cmt">// expression can be used as a parameter matrix matrix_a={{class="num">0.1,class="num">0.2,class="num">0.3},{class="num">0.4,class="num">0.5,class="num">0.6}}; matrix<class="type">class="kw">double> matrix_a1=matrix_a; class=class="str">"cmt">// the matrices must be of the same type matrix<class="type">class="kw">float> matrix_a3={{class="num">1,class="num">2},{class="num">3,class="num">4}}; vector vector_a={-class="num">5,-class="num">4,-class="num">3,-class="num">2,-class="num">1,class="num">0,class="num">1,class="num">2,class="num">3,class="num">4,class="num">5}; vector<class="type">class="kw">double> vector_a1={class="num">1,class="num">5,class="num">2.4,class="num">3.3}; vector<class="type">class="kw">float> vector_a3=vector_a2; class=class="str">"cmt">// the vectors must be of the same type matrix matrix_a =matrix::Eye(class="num">4,class="num">5,class="num">1); matrix<class="type">class="kw">double> matrix_a1=matrix::Full(class="num">3,class="num">4,M_PI); matrixf matrix_a2=matrixf::Identity(class="num">5,class="num">5); matrixf<class="type">class="kw">float> matrix_a3=matrixf::Ones(class="num">5,class="num">5); matrix matrix_a4=matrix::Tri(class="num">4,class="num">5,-class="num">1); vector vector_a =vector::Ones(class="num">256); vectorf vector_a1=vector<class="type">class="kw">float>::Zeros(class="num">16);
「矩阵扩容后旧值残留的坑」
MT5 里 matrix 的 Fill 只覆盖已有维度,一旦用 Init 改尺寸,新扩出的区域不会自动清零。上面那段代码里,2×2 的矩阵 Fill(10) 后全是 10;Init(4,6) 扩成 4 行 6 列,打印出来第 0 行前 4 个还是 10,后面却混进了 0.0078125 和 32.00000762939453 这种脏数据,其余三行全是 0。 开 MT5 把下面代码贴进脚本跑一遍,就能复现这个现象。做向量化信号计算前,若依赖 Init 动态调整矩阵大小,得手动重填,否则后续点积或归一化会带进不可控偏差。 自定义初始化回调能绕开这个问题。例子里的 MatrixSetValues 以初始值 1 按行填 2 的倍数,3×6 矩阵跑出来就是 1 到 131072 的等比数列,旧内存不会被误用。外汇和贵金属行情高频刷新,矩阵维度常随品种数变动,这种写法在实盘里更稳,但杠杆品种波动剧烈,仍属高风险操作。
vector<class="type">class="kw">float> vector_a2=vectorf::Full(class="num">128,float_value); matrix m(class="num">2, class="num">2); m.Fill(class="num">10); Print("matrix m \n", m); class=class="str">"cmt">/* matrix m [[class="num">10,class="num">10] [class="num">10,class="num">10]] */ m.Init(class="num">4, class="num">6); Print("matrix m \n", m); class=class="str">"cmt">/* matrix m [[class="num">10,class="num">10,class="num">10,class="num">10,class="num">0.0078125,class="num">32.00000762939453] [class="num">0,class="num">0,class="num">0,class="num">0,class="num">0,class="num">0] [class="num">0,class="num">0,class="num">0,class="num">0,class="num">0,class="num">0] [class="num">0,class="num">0,class="num">0,class="num">0,class="num">0,class="num">0]] */ class="type">void OnStart() { class=class="str">"cmt">//--- matrix init(class="num">3, class="num">6, MatrixSetValues); Print("init = \n", init); class=class="str">"cmt">/* Execution result init = [[class="num">1,class="num">2,class="num">4,class="num">8,class="num">16,class="num">32] [class="num">64,class="num">128,class="num">256,class="num">512,class="num">1024,class="num">2048] [class="num">4096,class="num">8192,class="num">16384,class="num">32768,class="num">65536,class="num">131072]] */ } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Fills the matrix with powers of a number | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void MatrixSetValues(matrix& m, class="type">class="kw">double initial=class="num">1) { class="type">class="kw">double value=initial; for(class="type">class="kw">ulong r=class="num">0; r<m.Rows(); r++) { for(class="type">class="kw">ulong c=class="num">0; c<m.Cols(); c++) { m[r][c]=value; value*=class="num">2; } } }
数组灌进矩阵时尺寸与类型怎么变
在 MT5 里做向量化计算,经常要把一个普通数组直接塞进矩阵。除了 Copy 方法,更顺手的是赋值号 '=' 和 Assign 调用;区别是 Assign 在数组和矩阵两边都能用,不挑类型。 从 int 数组转到 double 矩阵,Assign 会顺手做两件事:按源数组的行列自动改目标矩阵大小,并把整型升成双精度。下面这段实测里,原矩阵是 2 行 10 列的 3.14 填充阵,Assign 一个 5×5 的 int 数组后,直接变成了 5 行 5 列、未赋值位补 0 的矩阵。 注意那个 5×5 的 int_arr 只写了前 6 个有效数,其余 19 个格子默认是 0,灌进去后双精度矩阵对应位置也是 0.0。外汇和贵金属策略里拿历史 tick 数组去初始化特征矩阵时,这种自动补零可能掩盖样本边界,建议先 Print 出来核对维度。 开 MT5 把代码贴进脚本跑一遍,重点看 double_matrix 前后维度从 2×10 变成 5×5——这一行就能确认 Assign 的隐式 reshape 行为,比看文档直观。
class=class="str">"cmt">//--- copying matrices matrix a= {{class="num">2, class="num">2}, {class="num">3, class="num">3}, {class="num">4, class="num">4}}; matrix b=a+class="num">2; matrix c; Print("matrix a \n", a); Print("matrix b \n", b); c.Assign(b); Print("matrix c \n", c); class=class="str">"cmt">/* matrix a [[class="num">2,class="num">2] [class="num">3,class="num">3] [class="num">4,class="num">4]] matrix b [[class="num">4,class="num">4] [class="num">5,class="num">5] [class="num">6,class="num">6]] matrix c [[class="num">4,class="num">4] [class="num">5,class="num">5] [class="num">6,class="num">6]] */ class=class="str">"cmt">//--- copying an array to a matrix matrix double_matrix=matrix::Full(class="num">2,class="num">10,class="num">3.14); Print("double_matrix before Assign() \n", double_matrix); class="type">int int_arr[class="num">5][class="num">5]= {{class="num">1, class="num">2}, {class="num">3, class="num">4}, {class="num">5, class="num">6}}; Print("int_arr: "); ArrayPrint(int_arr); double_matrix.Assign(int_arr); Print("double_matrix after Assign(int_arr) \n", double_matrix); class=class="str">"cmt">/* double_matrix before Assign() [[class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14] [class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14,class="num">3.14]] int_arr: [,class="num">0][,class="num">1][,class="num">2][,class="num">3][,class="num">4] [class="num">0,] class="num">1 class="num">2 class="num">0 class="num">0 class="num">0 [class="num">1,] class="num">3 class="num">4 class="num">0 class="num">0 class="num">0 [class="num">2,] class="num">5 class="num">6 class="num">0 class="num">0 class="num">0 [class="num">3,] class="num">0 class="num">0 class="num">0 class="num">0 class="num">0 [class="num">4,] class="num">0 class="num">0 class="num">0 class="num">0 class="num">0 double_matrix after Assign(int_arr) [[class="num">1,class="num">2,class="num">0,class="num">0,class="num">0] [class="num">3,class="num">4,class="num">0,class="num">0,class="num">0] [class="num">5,class="num">6,class="num">0,class="num">0,class="num">0] [class="num">0,class="num">0,class="num">0,class="num">0,class="num">0] [class="num">0,class="num">0,class="num">0,class="num">0,class="num">0]] */