MQL5 中的矩阵和向量·进阶篇
(2/3)· 从向量范数到矩阵运算,避开嵌套循环与索引错位的实战陷阱
接上篇对 MQL5 数据结构的铺垫,我们继续深挖原生矩阵与向量类型。多数交易者写指标时仍用裸数组硬套循环,一旦维度变化就陷入调试泥潭。把数学符号直接映射成代码,才是这类问题更稳的解法。
◍ 用范数给向量量尺寸
在 MT5 的 vector 类里,范数就是给一串数值量“长度”的工具。它能直接反映向量量级,也能拿去算两个向量之间的间距,是后面做价差和波动度量的底子。 MQL5 把常用算法收进 ENUM_VECTOR_NORM:无穷范数取绝对值最大分量,负无穷范数取绝对值最小分量,而 VECTOR_NORM_P 靠一个指数参数 p 切换不同定义。下面这段脚本把 7 个元素的向量 v={1,2,3,4,5,6,7} 跑了全套。 跑出来的硬数据值得记一下:VECTOR_NORM_INF 得 7.0,MINUS_INF 得 1.0;p=1 时各分量直接求和得 28,p=2 是欧氏长度 11.8322,p 往负走(如 -1)则压缩到 0.385675。同一向量在不同范数下量级差出几十倍,选错标准可能把弱信号当强信号。 开 MT5 把代码丢进脚本跑一遍,改 v 里的数字或 p 值,看 Print 输出怎么跳。外汇和贵金属波动剧烈,用范数做距离度量是高风险操作,结论只代表数学上的倾向,不构成方向判断。
class="type">void OnStart() { class=class="str">"cmt">//--- class="kw">struct str_vector_norm { ENUM_VECTOR_NORM norm; class="type">int value; }; str_vector_norm vector_norm[]= { {VECTOR_NORM_INF, class="num">0}, {VECTOR_NORM_MINUS_INF, class="num">0}, {VECTOR_NORM_P, class="num">0}, {VECTOR_NORM_P, class="num">1}, {VECTOR_NORM_P, class="num">2}, {VECTOR_NORM_P, class="num">3}, {VECTOR_NORM_P, class="num">4}, {VECTOR_NORM_P, class="num">5}, {VECTOR_NORM_P, class="num">6}, {VECTOR_NORM_P, class="num">7}, {VECTOR_NORM_P, -class="num">1}, {VECTOR_NORM_P, -class="num">2}, {VECTOR_NORM_P, -class="num">3}, {VECTOR_NORM_P, -class="num">4}, {VECTOR_NORM_P, -class="num">5}, {VECTOR_NORM_P, -class="num">6}, {VECTOR_NORM_P, -class="num">7} }; vector v{class="num">1, class="num">2, class="num">3, class="num">4, class="num">5, class="num">6, class="num">7}; class="type">class="kw">double norm; Print("v = ", v); class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<ArraySize(vector_norm); i++) { class="kw">switch(vector_norm[i].norm) { case VECTOR_NORM_INF : norm=v.Norm(VECTOR_NORM_INF); Print("v.Norm(VECTOR_NORM_INF) = ", norm); class="kw">break; case VECTOR_NORM_MINUS_INF : norm=v.Norm(VECTOR_NORM_MINUS_INF); Print("v.Norm(VECTOR_NORM_MINUS_INF) = ", norm); class="kw">break; case VECTOR_NORM_P : norm=v.Norm(VECTOR_NORM_P, vector_norm[i].value); PrintFormat("v.Norm(VECTOR_NORM_P,%d) = %G", vector_norm[i].value, norm); } } } /* v = [class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7] v.Norm(VECTOR_NORM_INF) = class="num">7.0 v.Norm(VECTOR_NORM_MINUS_INF) = class="num">1.0 v.Norm(VECTOR_NORM_P,class="num">0) = class="num">7 v.Norm(VECTOR_NORM_P,class="num">1) = class="num">28 v.Norm(VECTOR_NORM_P,class="num">2) = class="num">11.8322 v.Norm(VECTOR_NORM_P,class="num">3) = class="num">9.22087 v.Norm(VECTOR_NORM_P,class="num">4) = class="num">8.2693 v.Norm(VECTOR_NORM_P,class="num">5) = class="num">7.80735 v.Norm(VECTOR_NORM_P,class="num">6) = class="num">7.5473 v.Norm(VECTOR_NORM_P,class="num">7) = class="num">7.38704 v.Norm(VECTOR_NORM_P,-class="num">1) = class="num">0.385675 v.Norm(VECTOR_NORM_P,-class="num">2) = class="num">0.813305 v.Norm(VECTOR_NORM_P,-class="num">3) = class="num">0.942818
「用 P 范数量两向量间的距离」
在 MT5 的 vector 类型里,Norm(VECTOR_NORM_P, p) 走的是带幂次 p 的 P 范数路线,和默认二范数不是一回事。上面那段注释里的输出很说明问题:当 p 取 -4、-5、-6、-7 时,范数值从 0.980594 一路爬到 0.998813,越靠近 -∞ 越收敛,这种反向幂次在常规量化书里很少提,但向量机里拿来压异常维度有用。 下面这段可直接丢进 MT5 脚本跑。先建两个三维向量 a 和 b,取差后再用二范数(p=2)求距离,结果 1.7320508075688772 就是 √3,手算能对上。 外汇与贵金属波动序列若压成向量做距离聚类,注意杠杆和高波动带来的样本漂移风险,结论只具概率倾向。
class="type">void OnStart() { class=class="str">"cmt">//--- vector a{class="num">1,class="num">2,class="num">3}; vector b{class="num">2,class="num">3,class="num">4}; class="type">class="kw">double distance=(b-a).Norm(VECTOR_NORM_P,class="num">2); Print("a = ",a); Print("b = ",b); Print("|a-b| = ",distance); }
MT5 里的矩阵类型和构造方法
向量不过是矩阵的一种退化形态,本质就是 double 类型的二维数组。矩阵可看成一排等长向量叠起来:行数即向量个数,列数即向量长度。传统语言用普通二维数组硬凑矩阵,但数组之间不能直接相加相乘,也没有范数概念,MT5 的 matrix 类型补上了这块短板。 加法、乘法、范数运算都直接作用在 matrix 对象上。系统内置了 Eye、Identity、Ones、Zeros、Tri、Diag、Full、Fill 等静态或实例方法,对应 NumPy 里的 eye、identity、ones、zeros、tri、diag、full 等接口,初始化一个 4×4 单位矩阵只需一行 matrix::Eye(4,4)。 矩阵范数方面,MQL5 在 ENUM_MATRIX_NORM 枚举里给了 9 种计算方式,覆盖了常用的谱范数、Frobenius 范数等。做多资产协方差矩阵或权重矩阵时,直接取范数判断数值稳定性比手撸循环快得多。 下面这段实测代码把主要构造方式都跑了一遍,从 3×3 手填到 5×5 全填 100,Resize 后未覆盖区补 0,结果可在 MT5 策略测试器日志里逐行核对。外汇与贵金属波动剧烈,用矩阵做组合权重前先确认数值尺度,杠杆环境下误差放大倾向明显。
class="type">void OnStart() { class=class="str">"cmt">//--- matrix m{{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("m = \n", m); matrix ones=matrix::Ones(class="num">4, class="num">4); Print("ones = \n", ones); matrix zeros=matrix::Zeros(class="num">4, class="num">4); Print("zeros = \n", zeros); matrix eye=matrix::Eye(class="num">4, class="num">4); Print("eye = \n", eye); matrix identity(class="num">4, class="num">5); Print("matrix_identity\n", identity); identity.Identity(); Print("matrix_identity\n", identity); matrix tri=matrix::Tri(class="num">3, class="num">4); Print("tri = \n", tri); Print("tri.Transpose() = \n", tri.Transpose()); class=class="str">"cmt">// transpose the matrix matrix diag(class="num">5, class="num">5); Print("diag = \n", diag); vector d{class="num">1, class="num">2, class="num">3, class="num">4, class="num">5}; diag.Diag(d); Print("diag = \n", diag); class=class="str">"cmt">// insert values from the vector into the matrix diagonal matrix fill(class="num">5, class="num">5); fill.Fill(class="num">10); Print("fill = \n", fill); matrix full =matrix::Full(class="num">5, class="num">5, class="num">100); Print("full = \n", full); matrix init(class="num">5, class="num">7); Print("init = \n", init); m.Init(class="num">4, class="num">6); Print("init = \n", init); matrix resize=matrix::Full(class="num">2, class="num">2, class="num">5); resize.Resize(class="num">5,class="num">5); Print("resize = \n", resize); }
◍ 矩阵填充与范数枚举的实测写法
在 MT5 里用 matrix 类做批量数值实验,第一步是把矩阵填进内容再打印。下面这段可直接贴进 EA 的 OnStart 里跑,生成 4×5 随机矩阵与 3×6 等比矩阵。 随机矩阵每个元素由 MathRand()/32767. 得到,值域落在 [0,1]。实测打印首行是 [0.4200262459181494,0.5014496292001098,0.7520371105075229,0.652058473464156,0.08783227027191992],每次重启数值会变。 等比矩阵从 1 开始逐元素乘 2,第三行末位是 131072。若你拿它做仓位梯度测试,这种 2 的幂次扩张比线性步长更容易暴露边际风险,外汇与贵金属杠杆场景下回撤可能非线性放大。 创建空矩阵后可用 Resize(3,3) 等效于声明时给尺寸,两种写法 MT5 都认。 范数类型用 ENUM_MATRIX_NORM 枚举列出来,从 MATRIX_NORM_FROBENIUS 到 MATRIX_NORM_MINUS_P2 共 10 种,覆盖弗罗贝尼乌斯、谱、核及各类 p-范数。做协方差矩阵压缩时,不同范数给出的截断阈值差异很大,建议挨个测一遍再定。
matrix random(class="num">4, class="num">5, MatrixRandom); Print("random = \n",random); matrix init(class="num">3, class="num">6, MatrixSetValues); Print("init = \n", init); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Fills the matrix with random values | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void MatrixRandom(matrix& m) { 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]=class="type">class="kw">double(MathRand())/class="num">32767.; } } } 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; } } } class=class="str">"cmt">/* Execution result random = [[class="num">0.4200262459181494,class="num">0.5014496292001098,class="num">0.7520371105075229,class="num">0.652058473464156,class="num">0.08783227027191992] [class="num">0.5991088595233008,class="num">0.4311960203863643,class="num">0.8718832972197638,class="num">0.1350138859218116,class="num">0.901882992034669] [class="num">0.4964445936460463,class="num">0.8354747154148991,class="num">0.5258339182714317,class="num">0.6055482650227363,class="num">0.5952940458388012] [class="num">0.3959166234321116,class="num">0.8146916104617451,class="num">0.2053590502639851,class="num">0.2657551805169835,class="num">0.3672292245246742]] 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">//--- create a matrix of a given &class="macro">#x27;rows x cols&class="macro">#x27; size matrix m(class="num">3, class="num">3); class=class="str">"cmt">// ------ equivalent matrix m; m.Resize(class="num">3, class="num">3); class="type">void OnStart() { class=class="str">"cmt">//--- ENUM_MATRIX_NORM matrix_norm[]= {MATRIX_NORM_FROBENIUS, MATRIX_NORM_SPECTRAL, MATRIX_NORM_NUCLEAR, MATRIX_NORM_INF, MATRIX_NORM_MINUS_INF, MATRIX_NORM_P1, MATRIX_NORM_MINUS_P1, MATRIX_NORM_P2, MATRIX_NORM_MINUS_P2}
「用九种范数把矩阵拆开看」
在 MT5 里给 matrix 对象调 Norm 方法,可以一次性拿到九种不同的矩阵范数。上面这段实测用的是 3×3 整数矩阵 m = [[1,2,3],[4,5,6],[7,8,9]],遍历 matrix_norm 枚举后逐个打印。 跑出来的结果里,Frobenius 范数约 16.881943,谱范数约 14.790157,核范数约 17.916473;而按行求和的无穷范数是 24.000000,按行最小和是 6.000000。P1 与 P-1 分别是 18 和 12,P2 约 16.848103,P-2 直接是 0。 外汇与贵金属行情序列转成矩阵后,不同范数度量的是波动能量或极端偏移的不同侧面,高风险品种尤其要警惕单看一种范数会漏掉尾部。开 MT5 把这段直接贴进脚本,换你自己的 K 线矩阵,就能比对哪类范数对突破更敏感。
};
matrix m{{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("matrix m:\n",m);
class=class="str">"cmt">//--- compute the norm class="kw">using all ways
class="type">class="kw">double norm;
for(class="type">int i=class="num">0; i<ArraySize(matrix_norm); i++)
{
norm=m.Norm(matrix_norm[i]);
PrintFormat("%d. Norm(%s) = %.6f",i+class="num">1, EnumToString(matrix_norm[i]),norm);
}
class=class="str">"cmt">//---
class="kw">return;
}
class=class="str">"cmt">/*
Execution result
matrix m:
[[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]]
class="num">1. Norm(MATRIX_NORM_FROBENIUS) = class="num">16.881943
class="num">2. Norm(MATRIX_NORM_SPECTRAL) = class="num">14.790157
class="num">3. Norm(MATRIX_NORM_NUCLEAR) = class="num">17.916473
class="num">4. Norm(MATRIX_NORM_INF) = class="num">24.000000
class="num">5. Norm(MATRIX_NORM_MINUS_INF) = class="num">6.000000
class="num">6. Norm(MATRIX_NORM_P1) = class="num">18.000000
class="num">7. Norm(MATRIX_NORM_MINUS_P1) = class="num">12.000000
class="num">8. Norm(MATRIX_NORM_P2) = class="num">16.848103
class="num">9. Norm(MATRIX_NORM_MINUS_P2) = class="num">0.000000
*/