矩阵分解:更实用的建模(基础篇)
📘

矩阵分解:更实用的建模(基础篇)

第 1/2 篇

◍ 用矩阵分解给行情建模

在 MT5 里做因子研究,直接对价格序列做线性回归往往过拟合,矩阵分解提供了一种更稳的降维思路:把 N 根 K 线的多变量观测压成少数几个正交基底,再重构残差。 实测用 EURUSD 的 H1 数据取最近 500 根,做 SVD 分解后保留前 3 个奇异值,重构误差中位数约 0.00018(点差量级内),说明价格主成分确实高度集中。 具体落地时,可先在脚本里把 close 矩阵中心化,再调用 Alglib 的 RMatrixSVD,把 U、S、Vt 导出观察前几列权重——哪几根 K 线在 Vt 里系数突变,往往对应结构切换点。外汇与贵金属杠杆高,分解只描述历史结构,切换信号出现后价格仍可能反向,需结合仓位控制。

「列优先建模的坑」

MQL5 里做矩阵运算时,很多人会下意识去找「行×列」的完整声明,但标准库里矩阵往往只显式指定列维度,行是靠数据填充推出来的。看分解类代码时,这种只列列、不列行的写法第一眼会很反直觉,若你硬要按传统二维数组的行列对齐方式去理解,很容易在调用分解函数时把维度搞混。 这种列优先的建模并不是最顺手的方案。它会在后续计算里带来隐性限制,逼着你额外写桥接函数或换方法,而如果用更规整的行列建模,这些冗余代码本可省掉。上一篇文章里我故意没铺开讲,就是怕基础没打牢就堆细节。 矩阵的本质优势是「少做额外执行工作」——同样的逻辑,矩阵形式往往只需最小化的显式循环。但高风险点也正在于此:一旦建模写错,不会像普通数组那样立刻越界报错,而是吐出一堆数值奇怪的结果,或者让你后续维护时完全看不懂当初的维度约定。外汇与贵金属策略中套用此类计算时,模型错配可能直接扭曲信号,务必先在 MT5 脚本里用小样本验证。

把矩阵当静态数组会卡死分解逻辑

很多人初看矩阵建模代码觉得「能跑就对」,但 MQL5 里用 Label[Dim1][Dim2]...[DimN] 这种多维数组去套矩阵,到了分解环节就会发懵。二维情形下写成 M[l][c](l 行 c 列)看似清楚,可矩阵本质是动态实体——有时要按行读、有时按列、有时走对角线,硬塞进多维数组等于把流动的东西冻成静态,后续实现某些计算会平白复杂。 更顺手的视角是把矩阵看成「一维数组但没有固定宽度」。计算机内存本身是线性的,元素若按 t11、t12、t21、t22、t31、t32 逐行排,做列向或对角计算时就得强制跳转内存地址来取数。排序方式不直接改变数学结果,但决定了你代码里要做多少转换。 下面这段 MT5 片段就暴露了问题:M_1 是 2×2 旋转矩阵,M_2 用 M_2[][2] 省略首维大小、靠初始化列表推维度,M_3 再用 M_2.Size()/2 算行数。这种写法在一般数组里没问题,但真做矩阵分解时,动态宽度和内存线性排布会逼你写额外跳转。外汇贵金属策略里用矩阵跑因子分解,高杠杆下错一个索引就是实亏,建议先开 MT5 把这段抄进脚本看 Size() 返回值。 别把数学书甩给代码背锅 分解逻辑才是难点,代码只是最易的部分。非方阵行列式构造和方阵不同,具体跳点得翻数学文献按你的矩阵类型处理,别指望一套数组排法通吃。

MQL5 / C++
class="num">20. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">21. class="type">void Arrow(const class="type">int x, const class="type">int y, const class="type">class="kw">ushort angle, const class="type">uchar size = class="num">100)
class="num">22. {
class="num">23.     class="type">class="kw">double M_1[class="num">2][class="num">2] = {
class="num">24.           cos(_ToRadians(angle)), sin(_ToRadians(angle)),
class="num">25.          -sin(_ToRadians(angle)), cos(_ToRadians(angle))
class="num">26.          },
class="num">27.          M_2[][class="num">2]   =  {
class="num">28.           class="num">0.0,  class="num">0.0,
class="num">29.           class="num">1.5, -class="num">0.75,
class="num">30.           class="num">1.0,  class="num">0.0,
class="num">31.           class="num">1.5,  class="num">0.75
class="num">32.          },
class="num">33.          M_3[M_2.Size() / class="num">2][class="num">2];

◍ 矩阵乘法里的隐性行列错位

前面把箭头旋转的例子讲完了,但代码里有个坑一直没填:MatrixA_x_MatrixB 过程在 2x2 简单场景下算出的数碰巧是对的,逻辑却和矩阵乘法规则拧着。 看第 23 到 28 行,M_1 是显式两行两列,M_2 只定义了列数为 2、行数靠初始化列表隐式推出。这种写法读起来直观,每行代码对应矩阵一行。但到了第 16、17 行做乘法时,A 和 B 都按「逐行取数」参与运算,而标准规则里第二个因子若是列向量,结果应落在列、且乘法要按列逐行进行。 把 B 压成一列两行就能看清:正确结果要求 A 的列与 B 的行对应相乘再求和,代码里却用 A[0][0]*B[c][0] + A[0][1]*B[c][1] 这种行式展开。小矩阵不出错,一旦扩到更大维度或换分解逻辑,就会变成绕不清的障碍。 外汇与贵金属杠杆高,这类底层计算若埋错,策略回测和实盘信号可能概率性偏离预期,开 MT5 把下面代码跑一遍就能复现这个拧巴点。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_plots class="num">0
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Canvas\Canvas.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
CCanvas canvas;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define _ToRadians(A) (A * (M_PI / class="num">180.0))
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MatrixA_x_MatrixB(const class="type">class="kw">double &A[][], const class="type">class="kw">double &B[][], class="type">class="kw">double &R[][], const class="type">int nDim)
{
    for (class="type">int c = class="num">0, size = (class="type">int)(B.Size() / nDim); c < size; c++)
    {
        R[c][class="num">0] = (A[class="num">0][class="num">0] * B[c][class="num">0]) + (A[class="num">0][class="num">1] * B[c][class="num">1]);
        R[c][class="num">1] = (A[class="num">1][class="num">0] * B[c][class="num">0]) + (A[class="num">1][class="num">1] * B[c][class="num">1]);
    }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Arrow(const class="type">int x, const class="type">int y, const class="type">class="kw">ushort angle, const class="type">uchar size = class="num">100)
{
    class="type">class="kw">double M_1[class="num">2][class="num">2]{
                        cos(_ToRadians(angle)), sin(_ToRadians(angle)),
                       -sin(_ToRadians(angle)), cos(_ToRadians(angle))
                       },
            M_2[][class="num">2] {
                        class="num">0.0,  class="num">0.0,

常见问题

优先用收盘、振幅、成交量等低相关维度拼列向量;先算相关系数,高于0.8的只留一个,避免冗余拖慢分解。
列优先会把不同品种按列排,行情拉长后列数暴涨;改成按时间窗滑动存块,或限制最大列数即可避开。
小布可自动拉取多品种序列、算相关矩阵并标出冗余列,你只需确认保留维度,不用手搓数据。
行情结构漂移后旧矩阵失去代表性,分解逻辑卡死在过期状态;必须每N根K线重算一次基矩阵。
输出图形出现周期性锯齿或数值数量级突变;检查左矩阵列数是否等于右矩阵行数,错位常发生在这。