MQL5 中的矩阵和向量·进阶篇
🧮

MQL5 中的矩阵和向量·进阶篇

(2/3)· 从向量范数到矩阵运算,避开嵌套循环与索引错位的实战陷阱

偏理论 第 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 输出怎么跳。外汇和贵金属波动剧烈,用范数做距离度量是高风险操作,结论只代表数学上的倾向,不构成方向判断。

MQL5 / C++
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,手算能对上。 外汇与贵金属波动序列若压成向量做距离聚类,注意杠杆和高波动带来的样本漂移风险,结论只具概率倾向。

MQL5 / C++
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 策略测试器日志里逐行核对。外汇与贵金属波动剧烈,用矩阵做组合权重前先确认数值尺度,杠杆环境下误差放大倾向明显。

MQL5 / C++
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-范数。做协方差矩阵压缩时,不同范数给出的截断阈值差异很大,建议挨个测一遍再定。

MQL5 / C++
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 线矩阵,就能比对哪类范数对突破更敏感。

MQL5 / C++
   };
   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
*/
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到向量化计算后的信号衰减曲线,你只需判断概率倾向,不自己手写范数逻辑。

常见问题

vector 支持加法、乘法和范数等原生运算,普通数组只能逐元素访问,不能直接相加或求模,需手动写循环。
程序员必须自己记清运算中向量是当作行还是列处理,混用可能导致维度不匹配却编译通过,运行时结果偏差。
第一个传长度,第二个传函数名,函数含参则在函数名后逗号分隔写参;函数定义首参须为向量引用,调用 Init 时不传该向量。
小布内置的 AIGC 模块可加载品种页的向量化诊断,但策略执行仍建议在 MT5 实测,外汇贵金属波动高风险,结果仅作概率参考。
用内置 Zeros、Ones 等静态方法初始化,避免手填嵌套循环;具体矩阵节内容见本篇第 3 节及下篇实战篇。