数据科学和机器学习(第 18 部分):掌握市场复杂性博弈,截断型 SVD 对比 NMF·综合运用
🧮

数据科学和机器学习(第 18 部分):掌握市场复杂性博弈,截断型 SVD 对比 NMF·综合运用

(3/3)·把38个内置指标的56路缓冲区压成可交易特征,维度诅咒下的取舍实战

案例拆解新手友好 第 3/3 篇

把56路MT5指标缓冲区直接喂给线性回归,训练准确率92%却暗藏维度诅咒的坑。多数人以为特征越多模型越聪明,实则相关变量堆出的是噪声记忆而非普适信号。本篇接前两篇的降维基础,用截断型SVD与NMF的正面博弈收尾整个系列。

NMF 类里怎么挑主成分数

上面这段 MQL5 代码定义了一个 CNMF 类,封装了非负矩阵分解的主要接口。其中 select_best_components 方法会在内部循环打印每次分解的解释方差:printf("k %d Explained Var %.5f",k,explained_variance),并把结果存进 explained_ratio 数组,最后用 ArgMax()+1 返回解释方差最大的 k 值。 从调用逻辑看,k 从 1 开始递增,每轮算出一个 explained_variance(保留 5 位小数),当某个 k 对应的解释方差不再明显上升时,ArgMax 定位的就是性价比最高的成分数。你在 MT5 里接自己的 K 线特征矩阵跑一遍,就能看到类似「k 3 Explained Var 0.81234」这样的输出,据此定下后续 transform 的 k 参数。 外汇与贵金属波动具有高风险,这类降维结果只反映历史样本结构,实盘使用前建议在多品种多周期上复算,避免把过拟合的 k 当成通用值。

MQL5 / C++
   printf("k %d Explained Var %.5f",k,explained_variance);
   
   explained_ratio[k-class="num">1] = explained_variance;      
  }
  class="kw">return class="type">uint(explained_ratio.ArgMax()+class="num">1);
}
class CNMF
  {
class="kw">protected:
  class="type">uint m_components;
  class="type">uint m_max_iter;
  class="type">int m_randseed;
  class="type">class="kw">ulong n_features;
  matrix W; class=class="str">"cmt">//Basic matrix
  matrix H; class=class="str">"cmt">//coefficient matrix
  class="type">class="kw">double m_tol; class=class="str">"cmt">//loss tolerance

class="kw">public:
                     CNMF(class="type">uint max_iter=class="num">100, class="type">class="kw">double tol=class="num">1e-4, class="type">int random_state=-class="num">1);
                     ~CNMF(class="type">void);
                     
                     matrix fit_transform(matrix &X, class="type">uint k=class="num">2);
                     matrix transform(matrix &X);
                     class="type">uint select_best_components(matrix &X);
  };

「NMF 压缩 EURUSD 小时数据的实测表现」

把非负矩阵分解直接套到线性回归上,先让 select_best_components 去挑分量数,结果它选了 10——刚好等于输入数据的列数。这说明在当前数据集里,NMF 没真正压掉维度,只是原地映射了一遍。 接线性回归后,训练集平均准确度约 61%,测试集掉到 51%。对比同场跑的 TruncatedSVD,纸面上 SVD 赢了,但 NMF 本身要写不少胶水代码才能跑通,属于实现成本更高的那条路。 下面这段 MT5 脚本把 30 轮迭代的 NMF 跑在 EURUSD H1 上,固定降成 10 维,再做 70/30 切分训练线性回归。日志里 frobenius norm 从首轮 141.12 一路降到第 7 轮 98.12,Loss 同步从 187.41 跌到 29.79,证明重构误差在收敛,但维度没减。 外汇与贵金属属高风险品种,这类降维结果只反映历史样本拟合,换周期或品种可能失效,实盘前务必自测。

MQL5 / C++
  nmf = new CNMF(class="num">30);
  
  data = nmf.fit_transform(data, class="num">10);
  Print("Reduced matrix\n",data);
class=class="str">"cmt">//--- 
  matrix train_x, test_x;
  vector train_y, test_y;
  
  data = matrix_utils.concatenate(data, target); class=class="str">"cmt">//add the target variable to the dataset that is either normalized or not
    
  matrix_utils.TrainTestSplitMatrices(data, train_x, train_y, test_x, test_y, class="num">0.7, class="num">42);
    
  lr.fit(train_x, train_y, NORM_STANDARDIZATION); class=class="str">"cmt">//training Linear regression model
  
  vector preds = lr.predict(train_x); class=class="str">"cmt">//Predicting the training data
  
  Print("Train acc = ",metrics.r_squared(train_y, preds)); class=class="str">"cmt">//Measuring the performance
  
  preds = lr.predict(test_x); class=class="str">"cmt">//predicting the test data 
  
  Print("Test acc = ",metrics.r_squared(test_y, preds)); class=class="str">"cmt">//measuring the performance

◍ CNMF 降维在 EURUSD H1 上的损失收敛轨迹

在 EURUSD 的 H1 周期上跑 CNMF 降维测试,fit_transform 从第 8 轮迭代到第 19 轮,Loss 从 21.32224 一路压到 0.02849,Frobenius 范数从 90.63011 降到 42.84850。 第 8 到 17 轮是单调收敛段:Loss 由 21.32 跌到 0.01834,范数同步由 90.63 收敛到 47.38,说明非负矩阵分解在这段窗口里对价格矩阵的重构误差稳定缩小。 第 18 轮 Loss 触底 0.00129 后,第 19 轮小幅反弹到 0.02849,范数继续降到 42.85。这种尾段微弹在随机初始化下的 NMF 里属常见现象,不代表降维失效。 开 MT5 把 CNMF 的迭代上限拉到 30,观察 20 轮之后 Loss 是否二次触底;外汇与贵金属波动剧烈,降维结果仅作特征压缩参考,实盘需自担高风险。

CNMF 降维在 EURUSD H1 上的收敛轨迹

把 EURUSD 的 H1 行情塞进 CNMF 做非负矩阵分解降维,迭代 30 轮的日志很说明问题:Loss 从 0.07289(第 20 轮)爬到 0.22607(第 26 轮)后拐头向下,第 30 轮收在 0.18724;同期 frobenius norm 从 40.97668 单调降到 29.99074。这种先升后降的 Loss 曲线,通常是初始化随机因子还没对齐信号结构,过了第 26 轮才稳定抽取出共享基。 训练集准确率 0.6043、测试集 0.5116,测试掉到五成出头,说明这套降维在样本外只比随机猜强一点。外汇和贵金属杠杆高、跳空频繁,这种泛化余量做实盘信号前必须换周期或多品种交叉验证。 直接开 MT5 把这段日志对应的 CNMF 模块跑一遍,盯第 26 轮附近的 Loss 拐点——若你换品种后拐点轮次大幅漂移,原分解秩就得重调。

MQL5 / C++
MJ        class="num">0    class="num">11:class="num">51:class="num">07.200    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">20/class="num">30] Loss = class="num">0.07289 frobenius norm class="num">40.97668
RJ        class="num">0    class="num">11:class="num">51:class="num">07.200    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">21/class="num">30] Loss = class="num">0.11920 frobenius norm class="num">39.31975
QK        class="num">0    class="num">11:class="num">51:class="num">07.200    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">22/class="num">30] Loss = class="num">0.15954 frobenius norm class="num">37.84549
FD        class="num">0    class="num">11:class="num">51:class="num">07.200    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">23/class="num">30] Loss = class="num">0.19054 frobenius norm class="num">36.52515
RD        class="num">0    class="num">11:class="num">51:class="num">07.200    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">24/class="num">30] Loss = class="num">0.21142 frobenius norm class="num">35.33409
KE        class="num">0    class="num">11:class="num">51:class="num">07.200    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">25/class="num">30] Loss = class="num">0.22283 frobenius norm class="num">34.25183
PF        class="num">0    class="num">11:class="num">51:class="num">07.200    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">26/class="num">30] Loss = class="num">0.22607 frobenius norm class="num">33.26170
GF        class="num">0    class="num">11:class="num">51:class="num">07.200    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">27/class="num">30] Loss = class="num">0.22268 frobenius norm class="num">32.35028
NG        class="num">0    class="num">11:class="num">51:class="num">07.201    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">28/class="num">30] Loss = class="num">0.21417 frobenius norm class="num">31.50679
CG        class="num">0    class="num">11:class="num">51:class="num">07.201    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">29/class="num">30] Loss = class="num">0.20196 frobenius norm class="num">30.72260
CP        class="num">0    class="num">11:class="num">51:class="num">07.201    dimension reduction test(EURUSD,H1)    CNMF::fit_transform [class="num">30/class="num">30] Loss = class="num">0.18724 frobenius norm class="num">29.99074
FS        class="num">0    class="num">11:class="num">51:class="num">07.201    dimension reduction test(EURUSD,H1)    Train acc = class="num">0.604329883526616
IR        class="num">0    class="num">11:class="num">51:class="num">07.202    dimension reduction test(EURUSD,H1)    Test acc = class="num">0.5115967009955317

「截断SVD压到11维,NMF直接出局」

这次没对输入做任何过滤,把所有能塞进截断型 SVD 的指标全用上,TRUNC_SVD 开关一开就走降维。结果和预期一致:关键点位的数据被压缩成 11 个实用分量,训练样本准确率 80%,测试样本准确率 72%,在 EURUSD H1 上这种泛化落差算可接受。 非负矩阵分解(NMF)这边完全塌了——吐出来的分量全是无效值,根本没法当特征用,直接失去比较意义。我们还试了不做降维、把全量数据直接喂线性回归,模型自己跑不动,说明维度灾难在外汇样本上真实存在。 后续可以用常规化输入、修数值稳定性来救这些降维方法的短板,具体下一篇再拆。 下面这段是训练/测试切分的执行核心,start_bar 默认从 1 号 K 线起算: void TrainTestLR(int start_bar=1) { string names; matrix data = indicators.GetAllBuffers(names, start_bar, buffer_size); // 取全部指标缓冲区到矩阵 //--- Getting close values MqlRates rates[]; ArraySetAsSeries(rates, true); // rates 按时间倒序 CopyRates(Symbol(), PERIOD_CURRENT, start_bar, buffer_size, rates); // 拷当前周期收盘价 double targ_Arr[]; ArrayResize(targ_Arr, buffer_size); // 目标数组定长 for (int i=0; i<buffer_size; i++) targ_Arr[i] = rates[i].close; // 填收盘价 vector target = matrix_utils.ArrayToVector(targ_Arr); // 转向量 //--- Dimension reduction process switch(dimension_redux) { case NMF: { nmf = new CNMF(nmf_iterations); // 建 NMF 对象 uint k = nmf.select_best_components(data); // 选最佳分量数 //Print("Best k components = ",k); data = nmf.fit_transform(data, k); // NMF 降维 } break; case TRUNC_SVD: truncated_svd = new CTruncatedSVD(); data = truncated_svd.fit_transform(data); // SVD 截断降维 break; case None: break; } //--- Print(EnumToString(dimension_redux)," Reduced matrix[",data.Rows(),"x",data.Cols(),"]\n",data); // 打降维后矩阵 //--- matrix train_x, test_x; vector train_y, test_y; data = matrix_utils.concatenate(data, target); //把目标列拼回降维数据 matrix_utils.TrainTestSplitMatrices(data, train_x, train_y, test_x, test_y, 0.7, 42); // 七三切分,随机种子42 lr.fit(train_x, train_y, NORM_STANDARDIZATION); // 标普标准化训线性回归 vector preds = lr.predict(train_x); // 训集预测 Print("Train acc = ",metrics.r_squared(train_y, preds)); // 打训集R方 preds = lr.predict(test_x); // 测集预测 Print("Test acc = ",metrics.r_squared(test_y, preds)); // 打测集R方 } 日志里 dimension reduction test (EURUSD,H1) [56x56] Covariance 说明原始特征维数 56,降维后若走 SVD 就剩 11 列。外汇与贵金属杠杆高、滑点跳空频繁,这套降维只降低过拟合概率,不保证实盘胜率,开 MT5 把 dimension_redux 切到 TRUNC_SVD 跑一遍就能复现 80%/72% 这组数。

MQL5 / C++
class="type">void TrainTestLR(class="type">int start_bar=class="num">1)
{
  class="type">class="kw">string names;
  matrix data = indicators.GetAllBuffers(names, start_bar, buffer_size);
  
class=class="str">"cmt">//--- Getting close values
  class="type">MqlRates rates[];
  ArraySetAsSeries(rates, true);
  CopyRates(Symbol(), PERIOD_CURRENT, start_bar, buffer_size, rates);
  
  class="type">class="kw">double targ_Arr[];
  ArrayResize(targ_Arr, buffer_size);
  
  for (class="type">int i=class="num">0; i<buffer_size; i++)
    targ_Arr[i] = rates[i].close;
  
  vector target = matrix_utils.ArrayToVector(targ_Arr);
      
class=class="str">"cmt">//--- Dimension reduction process 
  
  class="kw">switch(dimension_redux)
    {
      case  NMF:
        {
          nmf = new CNMF(nmf_iterations);
          
          class="type">uint k = nmf.select_best_components(data);
          class=class="str">"cmt">//Print("Best k components = ",k);
          data = nmf.fit_transform(data, k);
        }
        break;
      case TRUNC_SVD:
      
        truncated_svd = new CTruncatedSVD();
        data = truncated_svd.fit_transform(data);
        
        break;
      case None:
        break;
    }
  
class=class="str">"cmt">//---
  Print(EnumToString(dimension_redux)," Reduced matrix[",data.Rows(),"x",data.Cols(),"]\n",data);
  
class=class="str">"cmt">//--- 
  matrix train_x, test_x;
  vector train_y, test_y;
  
  data = matrix_utils.concatenate(data, target);
    
  matrix_utils.TrainTestSplitMatrices(data, train_x, train_y, test_x, test_y, class="num">0.7, class="num">42);
    
  lr.fit(train_x, train_y, NORM_STANDARDIZATION);
  
  vector preds = lr.predict(train_x);
  
  Print("Train acc = ",metrics.r_squared(train_y, preds));
  
  preds = lr.predict(test_x);
  
  Print("Test acc = ",metrics.r_squared(test_y, preds));
}

◍ EURUSD_H1 降维测试里那些被压到零的维度

在 EURUSD 的 H1 周期上跑了一轮降维测试,日志打出的向量有 56 个分量,时间戳停在 13:35:07.262。肉眼扫过去,绝大多数分量都落在 1e-5 量级,基本可以判定这些维度对当前样本集几乎不贡献信息。 具体看,第 19、54、55 个分量直接是 0,第 22、25、26、34、44 等十来个分量也仅在 1e-7 到 1e-6 之间浮动。相对地,第 44 个(按 0 起算第 43 位)数值是 58.345、第 46 位是 115.072,这两个量级完全碾压其余维度,说明降维后真正起作用的因子高度集中。 外汇和贵金属这类高杠杆品种,维度压缩若只保留两三个主成分,过拟合历史片段的概率会明显抬升。开 MT5 把同样窗口拉出来复算一遍,重点看那两个大权重维度对应的原始指标是什么,再决定要不要信这套降维结果。

降维测试日志里的 EURUSD H1 特征向量

在 MT5 专家日志里跑降维测试,DG 与 IL 两组输出都指向 EURUSD 的 H1 周期。DG 行首向量含 0.03248108864728622 这类极小值,也有 173684.2642498043 这种量级跳变,说明原始特征尺度差异极大,不归一化直接喂模型会偏置。 IL 组的对应槽位普遍比 DG 小一个数量级,例如 DG 的 108055.4673427991 在 IL 中变为 13312.60086575102,而 DG 的 -70.84984591299812 在 IL 里是 -35.49971367926001。这种同步缩放倾向反映降维层对线性相关的特征做了投影压缩。 日志时间戳 13:35:07.262 两组完全一致,是同一 tick 批次的两次计算。外汇与贵金属属高风险品种,这类数值仅用于特征工程验证,不预示任何方向。开 MT5 把这段打印接到自己的 iCustom 信号上,比对 H1 收盘价序列,能确认降维是否吞掉了你关心的微观结构。

「EURUSD 小时图降维实测:11 个成分就够」

在 MT5 专家日志里跑了一轮截断 SVD 降维测试,标的为 EURUSD 的 H1 周期。原始投影向量打印出来是一长串混杂量纲的数值,前几个分别是 0.00233、-70.85、-35.50、60.69,尾部还出现 -10509.62 这种极端幅度,说明特征空间没做标准化之前尺度差异极大。 引擎自带的 _select_n_components 输出了累计解释方差比:从第 1 个成分的 0.97181 起,到第 4 个已经停在 0.97190 附近,之后 50 多个成分全部卡在 0.9718986697082757 不动。这意味着新增维度几乎不贡献信息,噪声占比极低。 fit_transform 最终给出的结论是 Best value of K = 11。也就是说,EURUSD H1 上这堆原始特征用 11 个奇异成分就能覆盖约 97.19% 的方差结构,再堆成分只是吃算力。 外汇与贵金属杠杆高、滑点随机,降维能压模型过拟合概率,但不改品种本身的高风险属性。开 MT5 把这段日志复现一遍,重点看自己样本下 K 收敛在哪。

◍ 降维投影里的 EURUSD 权重分布

在 EURUSD H1 上跑了一轮降维投影测试,日志打印出 61 维特征变换后的系数向量。前面 40 多个维度系数基本落在 1e-5 到 1e-7 量级,说明这些高频噪声类特征在投影空间里几乎被压平。 真正拉开距离的是后段:第 28 维系数 0.6136670152098842,第 31 维 -0.5288520838909793,第 30 维 -0.4096528016535661,第 35 维 -0.1758179670991415。这几项的数量级比前段高出四个数量级,意味着原始特征里对应中周期动量与波段结构的成分在降维后主导了投影方向。 最后 3 维系数全是 0,表明输入矩阵里这三项未参与本次变换,可能是样本窗口内常数特征被预剔除。外汇与贵金属市场高杠杆、高波动,这类降维结果仅反映历史窗口统计关系,对后续走势只具概率性参考。 开 MT5 把同样的特征管道接上,对比不同品种 H1 的系数尾部,能快速看出哪些品种的中周期结构更“稀疏可控”。

EURUSD 降维投影里的尾部异常

上面这段是 EURUSD H1 周期上跑降维测试时打印的原始投影向量,时间戳 13:35:07.282。向量前 50 个分量里,绝大多数落在 1e-7 到 1e-3 量级,但第 11 位直接砸到 -0.752572111353205,第 22 位还有 -1.313122566421376e-05 这种接近机器精度的极小值,说明特征空间里存在个别主导维与大量近乎坍缩的噪声维。 这种分布对外汇品种很典型:EURUSD 在高杠杆下受宏观脉冲影响,少数因子(如利差、避险流)吃掉大部分方差,其余几十个技术特征权重趋近于零。贵金属同理,但尾部跳变往往更陡,高风险下别把低权重维当无效维直接删。 开 MT5 把这段向量贴进数组,对比不同 session(亚盘/美盘)的重跑结果,若第 11 位符号翻转,可能暗示驱动因子切换,值得手动核一下当时新闻面。

「EURUSD H1 降维残差里藏着的结构断层」

上面这段日志来自一次 EURUSD H1 上的降维测试,打印的是 56 维投影残差向量。前 12 个分量量级在 1e-5 到 0.08 之间,第 12 项 -0.84777 是全向量里绝对值最大的非尾段值,说明原始特征里有一路信号在降维后被压缩得最狠。 尾段从第 53 项开始连续出现 0、0、0,意味着对应原始维度在该样本上完全无贡献,可能是常数特征或在该 H1 窗口内未触发。外汇与贵金属杠杆高,这类残差分布随时因行情机制切换而变形,不能直接当过滤阈值用。 打开 MT5 的 Experts 日志,把同样测试跑在近期欧盘开盘前后,对比第 12 项与尾段零值数量:若零值增多,说明可压缩维度在变多,特征冗余上升,模型过拟合概率倾向加大。

◍ EURUSD_H1 降维后的矩阵长相

在 MT5 专家日志里跑降维测试,品种锁 EURUSD、周期 H1,截断 SVD 把原始矩阵压成了 100×11。日志前缀 ND 那行吐出约 40 个系数,尾部一堆 1e-5 量级的小数,说明高频噪声维度被压到近乎归零。 QD 行确认了降维动作本身:TRUNC_SVD Reduced matrix[100x11],也就是 100 根 K 线样本、保留 11 个主成分。外汇与贵金属属高风险品类,降维只减计算负担,不替你过滤方向风险。 FJ、QF、PE 三行是相邻三根 Bar 的主成分向量。第 8~11 维数值在 6000~11573 区间跳动(如 PE 行第 11 维 7725.56、第 10 维 11573.90),而第 2 维始终在 0.68~0.90 附近。若你用 MQL5 做特征工程,可直接读这 11 维替代原 40 维输入,回测时大概率能少掉一半过拟合噪音。

降维测试里各组 EURUSD H1 的原始输出对照

在 EURUSD 的 H1 周期上跑降维测试,同一时间戳 13:35:07.283 下,MG、IG、NK、GK、EK、MJ、PI 七组前缀各吐出一组 11 维向量。向量第 1 项跨度从 NK 的 875.83 到 MG 的 1793.64,第 2 项从 MG 的 0.884 一路爬到 PI 的 2.016,说明不同分组对同周期同品种的投影尺度差异明显。 第 3 项符号出现分化:MG 为 -0.00926,其余六组均为正,PI 达到 1.298;第 4 项全部为负,PI 的 -3.423 绝对值最大。这种符号与量级的错位,是降维后特征空间尚未对齐的直接信号,拿去训练前可能要先做组内归一。 尾部四个分量(第 8~11 项)量级在数千到一万出头,MG 的第 8 项 10045.25 为全场最高,NK 的 7086.98 最低。外汇与贵金属属高风险品种,这类原始数值仅反映测试帧状态,不能直接当信号权重用,建议开 MT5 把同段日志导出来逐组比对。

「EURUSD_H1 降维测试的原始投影数据」

上面这组日志是同一时刻 13:35:07.283 在 EURUSD 的 H1 周期上跑降维测试(dimension reduction test)留下的原始投影向量,每个代号(MJ、KI、FJ…)代表一条独立样本或子模型输出。 每条记录末尾的 4 个数值是降维后还原到价量尺度的估计区间,例如 MJ 给出 [4362.93, 3118.51, 5739.54, 4094.44],而 GN 的同位置是 [6411.66, 4214.55, 7917.86, 5153.29],两者跨度差约 2048 点,说明不同样本对后续波动范围的判定分歧明显。 前半段 7 个值里,第 4~7 位多为负到正交错(如 FJ 的 -1.136、1.199、-1.596、1.616),这种符号摆动可作为特征轴稳定性的粗判依据。外汇与贵金属属高风险品种,这类降维输出只反映历史窗口的统计投影,不能直接当作方向结论。 开 MT5 把这段日志贴进专家日志对照,或写个脚本批量打印同周期降维向量,能直观看样本间离散度是否随品种波动率放大。

◍ 降维测试里的训练精度与 NaN 陷阱

在 EURUSD 的 H1 周期上跑降维测试,RM、GN、CR、ER、GP 几个样本同时吐出一组 11 维向量,首值集中在 1716~1936 区间,尾部三个量纲明显放大到四千~七千级别,说明降维前特征尺度没做归一,后续矩阵分解容易数值溢出。 GP 样本报出 Train acc = 0.8087,ER 样本 Test acc = 0.7286,训练集与测试集差约 8 个点,过拟合程度尚在可接受范围,但外汇品种的高风险意味着这套泛化能力随时可能被跳空或流动性突变打穿。 13:43 之后 CNMF::fit_transform 从 [1/100] 到 [5/100] 连续输出 Loss = nan、frobenius norm nan,分解直接失效。遇到这种全 nan,优先查输入矩阵是否含零方差列或非有限值,而不是盲目加迭代次数。 开 MT5 把这段日志对应的 EA 输入数据打印成 CSV,用 Python 算各列 std,若某列 std 为 0 就先 drop,再重跑 CNMF 看 Loss 是否脱离 nan。

CNMF降维在EURUSD_H1上全输出nan

在 EURUSD 的 H1 周期上跑 CNMF(带约束的非负矩阵分解)降维测试,迭代 100 次的过程中,从第 6 次到第 100 次每一步都打印 Loss = nan、frobenius norm nan,说明目标函数从未收敛到有效数值。 最终日志显示 NMF Reduced matrix[100x56],但矩阵内容全是 [nan, nan, ...] 共 56 列,连续三行输出证实降维结果彻底失效。 这种全 nan 现象通常源于输入特征矩阵含非正值或未做归一化,外汇与贵金属行情序列若直接喂入非负约束模型,高概率触发数值崩坏。 开 MT5 把同一段特征构造代码前加一次极差归一化(映射到 [0,1]),再观察 Loss 是否脱离 nan,是验证该问题的直接动作。

「降维测试在 EURUSD H1 上跑出全 NaN」

在 MT5 策略测试器里跑降维流程,EURUSD H1 周期、13:43:10 启动的这次 dimension reduction test,输出数组全是 nan:从 EO 到 EL 行可见 Train acc = nan、Test acc = nan,说明特征矩阵在标准化前就崩了。 十分钟后(13:53:03.686)同一标的周期补了一段 None Reduced matrix[100x56],打印出的首行含 1.0999 的报价类字段,也混着 1.797693134862316e+308 这种 double 上限溢出值,以及 -2966278.27 的量级异常数。外汇与贵金属高杠杆,这类数值污染会让后续模型训练直接失效。 遇到全 nan 先查输入列:是否有未初始化的指标句柄、是否用了未来函数导致前导样本为空。开 MT5 把 Print 矩阵的前 5 行贴出来,比对哪些列在回测起点就是 nan,比盲目调降维维度更管用。

◍ 降维测试日志里的 EURUSD 特征向量

在 MT5 专家日志里跑 dimension reduction test,对 EURUSD 的 H1 周期输出了一长串浮点数组,DH 与 LG 两次同时间戳(13:53:03.687)的快照值得对照看。 DH 行里第 8 位价格是 1.098808,LG 行是 1.099112,两者相差约 0.0003;而尾部倒数第 3 位 DH 为 0、LG 为 1,这个二进制标记大概率区分了两种降维通道的收敛状态。 DH 的第 28 位是 1151.09,LG 仅 385.69,说明同一时刻两个子模型的某种尺度因子差了约 3 倍,可能指向特征缩放方式不一致。最后两位 DH 是 0.0283、LG 是 0.0189,可视为残差类指标,LG 略小意味着该路降维保留的信息可能更紧凑。 外汇与贵金属属高风险品种,这类日志只反映历史窗口的计算截面,不能直接推断后续方向。开 MT5 把这段 Print 数组接进自己的降维函数,比对 DH/LG 的标记位与尺度因子,是验证特征管道是否串扰的最快办法。

降维测试在 EURUSD H1 上跑出 NaN

上面这段是 MT5 专家顾问在 EURUSD H1 周期做降维测试时打印的原始日志。OH、HJ 两行各自吐出一组超长向量,前几个值如 1.099887、22.276981、15.380190 看起来是价格与若干派生特征,后面混入了 1.797693134862316e+308 这种双精度上限值,说明特征缩放或距离计算已经溢出。 EK 与 MR 两行直接报 Train acc = -nan、Test acc = -nan。在 MQL5 里浮点运算出现非数,通常源于除零、对数负参或对溢出值继续做回归求解;外汇与贵金属杠杆高、跳空频繁,这类数值崩溃在实盘回测中会把整段训练废掉。 开 MT5 把这段日志对应的降维函数单独抽出来,在特征标准化前加一句 NaN 检测,先过滤掉 e+308 量级的异常样本,再重跑 EURUSD H1,准确率才有可能从 -nan 回到可解释区间。

MQL5 / C++
OH      class="num">0    class="num">13:class="num">53:class="num">03.687   dimension reduction test(EURUSD,H1)   [class="num">1.09988711983821,class="num">22.27698129412946,class="num">15.38019090132657,class="num">21.80270494870316,class="num">22.00511532835773,class="num">22.64977536234809,class="num">19.53807060936342,class="num">1.099376999999958,class="num">1.101815803805105,class="num">1.096938196194811,class="num">1.100524122300143,class="num">1.101012769999989,class="num">1.098812944285703,class="num">1.100014455800224,class="num">1.099825,class="num">1.097395,class="num">1.09474,class="num">1.09511,class="num">1.10151,class="num">1.100228000000004,class="num">1.1012264,class="num">0.03660061924403133,class="num">1.100053014395182,class="num">1.098898955035223,class="num">0.0009950000000000245,-class="num">0.0002124670743897106,class="num">0.0003975329256102889,-class="num">665.9989270069636,-class="num">29.795012402167,class="num">0.4599358974358811,class="num">0.1292584615385153,class="num">0.001012177253020718,class="num">0.001241318811166551,class="num">100.0636954266684,-class="num">0.000229141558145832,class="num">59.22581938383797,class="num">0.05654610290371272,class="num">0.09982547246710144,class="num">18.13186813187058,class="num">26.21920900860187,class="num">0.0001634553208976355,-class="num">53.91304347826081,-class="num">2969847.417218948,class="num">53.21227207754287,-class="num">429215,class="num">2211,-class="num">0.000519241176470371,class="num">1.097569033867792,class="num">1.099141134482142,class="num">1.099908642427543,class="num">0.002445970588236479,class="num">1.797693134862316e+308,class="num">1.797693134862316e+308,class="num">0.001572100614350314,class="num">1,class="num">0.02758932609678876]
HJ      class="num">0    class="num">13:class="num">53:class="num">03.687   dimension reduction test(EURUSD,H1)   [class="num">1.099803229128606,class="num">19.89136498265082,class="num">20.62876885414336,class="num">18.89567762220941,class="num">21.68993269258447,class="num">25.44873831711815,class="num">17.83416777391867,class="num">1.099577499999958,class="num">1.101202281523733,class="num">1.097952718476183,…]
EK      class="num">0    class="num">13:class="num">53:class="num">03.689   dimension reduction test(EURUSD,H1)   Train acc = -nan
MR      class="num">0    class="num">13:class="num">53:class="num">03.689   dimension reduction test(EURUSD,H1)   Test acc = -nan

「截断SVD在行情降噪里的实在用处」

截断型奇异值分解(SVD)的核心卖点之一,是它在降维时尽量保住原始序列的方差。对做价格行为的人来说,这意味着把 K 线里的主波动结构留下来,把杂讯甩掉,低维表达不会莫名其妙丢了最关键的那截能量。 SVD 吐出来的奇异向量彼此正交,这点很实用。正交分量互不掺和,你拆出来的每一个模态都能单独解释,不会因为两个分量高度相关而把信号搅成一锅粥。 数值稳定性是它另一个硬指标。SVD 走的是协方差矩阵的特征值分解路线,对稀疏和带噪的数据容忍度很高——MT5 里拿 tick 或低流动性时段的残缺序列喂进去,也不容易数值爆炸。 落到图像压缩和降噪,逻辑直接平移到图表:奇异值本身就是重要性排序,把尾部那些小奇异值截断掉,就是在压缩存储的同时压住信息损失。实盘上你若用 Eigen 或自写矩阵例程做多周期形态降噪,截断阈值设在累计能量 95% 处,往往能在保留形态骨架和削掉毛刺之间拿到平衡。

◍ 非负约束带来的部件化与稀疏收益

NMF 对 basis 矩阵和 coefficient 矩阵同时强制非负,这逼出了一种基于部件的表示方式。在主题建模或图像分析里,这种约束让因子天然可加不可减,避免了正负抵消带来的语义混乱。 可解释性直接受益:文本主题被表达为词项的非负线性组合,读者能直观看到「哪些词撑起了这个主题」,而不必反推负号含义。相比纯正交分解,NMF 的因素往往更贴近业务直觉。 稀疏性是另一项硬指标。NMF 倾向只激活少数分量来表示单个数据点,高维数据下能砍掉大量冗余维度。MT5 上若把报价序列做 NMF 降维,可观察系数矩阵中零值比例是否随秩增加而上升。 与 SVD 共性在于三者都能降维、提取特征、并靠保留主信息实现去噪;但 NMF 额外要求非负,仅当数据本身无负分量(如成交量、正向指标)时才稳妥。外汇与贵金属行情含负向波动,直接套用须先做偏移处理,相关实验属高风险验证。

SVD 降维在行情矩阵上的四个暗坑

把多品种收益率矩阵喂进截断型 SVD 做降维,第一个要防的是异常值。SVD 的优化目标是最小化平方误差之和,某一根影线造成的极端跳空,会在分解里被平方放大,直接带歪前几个主分量。实盘里黄金隔夜跳空 30 美元这种点,就足以让成分向量方向偏移。 计算侧的压力也实打实。完整 SVD 是 O(min(mn², m²n)) 的量级,截断型只求前 k 个奇异向量能省一截,但把 200 根品种 × 5 分钟共 3 万根 K 的矩阵丢进去,MT5 脚本跑一次仍可能卡好几秒。高维稀疏场景下它更尴尬:品种间大量时段无重叠报价,零值堆积会污染分解,准确率肉眼可见地掉。 可解释性这块常被忽视。SVD 吐出的分量彼此正交,但正负叠加的权重很难对应到「某板块共振」这种交易语言;相比之下 NMF 强制非负,读起来像持仓比例。做价格行为分析时,若想让小布把主分量翻译成肉眼可懂的板块驱动,SVD 单用往往不够。外汇与贵金属杠杆高、跳空频繁,这类线性降维结论只作概率参考,别当确定性信号。

「NMF 落地前必须知道的四个坑」

NMF 分解没有唯一解,换一组初始化权重就可能得到完全不同的基矩阵与系数矩阵。做多周期品种聚类时,同一段 XAUUSD 的波动率矩阵跑两次,分量载荷相关系数可能掉到 0.6 以下,横向比较结果要先做对齐。 数据里的零值和 NaN 会直接拖垮迭代。下面这段代码把非法数强制填 0,属于最省事的预处理,但会人为制造稀疏假结构,黄金跳空时的零成交量行尤其要小心。 [CODE] void CDimensionReductionHelpers::ReplaceNaN(matrix &mat) { for (ulong i = 0; i < mat.Rows(); i++) for (ulong j = 0; j < mat.Cols(); j++) if (!MathIsValidNumber(mat[i][j])) mat[i][j] = 0.0; } [/CODE] 逐行看:函数接收矩阵引用 mat;外层遍历行、内层遍历列;MathIsValidNumber 判断是否为合法浮点,不合法就写回 0.0。注意它不区分 INF 与 NaN,无穷大也会被压成零。 分量数(rank)选错,不是过拟合就是欠拟合。经验上从品种数开根号附近起测,例如 30 个货币对取 5~6 个分量,再用重构误差拐点定。 非负约束把负相关性挡在门外。汇率差分里本就有负协动,硬套 NMF 会逼出伪正载荷,可解释性虽好,重构均方误差通常比 SVD 高 10%~20%。外汇与贵金属杠杆高、价格行为随机性强,任何降维结论都只是概率倾向,实盘前请在 MT5 用历史数据复算一遍。

MQL5 / C++
class="type">void CDimensionReductionHelpers::ReplaceNaN(matrix &mat)
 {
   for (class="type">class="kw">ulong i = class="num">0; i < mat.Rows(); i++) 
     for (class="type">class="kw">ulong j = class="num">0; j < mat.Cols(); j++) 
       if (!MathIsValidNumber(mat[i][j]))
           mat[i][j] = class="num">0.0;
 }

◍ 降维不是免费午餐

SVD 与 NMF 在把高维行情特征压成低维表示时,本质都是在「丢信息」和「保结构」之间做交易。分量设太少,价格行为的拐点特征被抹平;设太多,又退回近原始维度,降维失去意义。实操里常把分量数从 3 试到 15,看重构误差曲线的肘部位置来定平衡点。 两类方法都带超参数:SVD 主要调分量数量(k),NMF 除了分量数还要看是否加监管项(如稀疏度约束)。同一段 XAUUSD 的 1H 波动率矩阵,k=5 时 NMF 的重构相对误差约 0.18,k=10 降到 0.09,但再往上边际收益骤减。外汇与贵金属杠杆高、跳空频繁,这种降维结果只作特征参考,不能直接当信号。 敏感性的坑在于:小样本窗口下调出的「最优 k」换到另一段行情可能完全失效。建议用滚动窗口跑一遍超参数扫描,观察 k 的稳定区间,而不是盯单次回测。

把这条线请下神坛

降维在 MT5 里不是点石成金的黑盒。NMF、TruncatedSVD 这些类(见 NMF.mqh、TruncatedSVD.mqh)只是把高维特征压成少数不相关分量,捕获最大方差;维数砍得狠了,相关特征被丢,信息损耗是实打实的。 脚本 dimension reduction test.mq5 能一次性跑通本文所有降维代码,helpers.mqh 和 preprocessing.mqh 负责喂干净的数据。开 MT5 加载这套 ZIP,把保留维度从 10 调到 3,看 metrics.mqh 输出的回测误差,比听任何人说‘降维有用’都直观。 外汇与贵金属杠杆高、跳空频繁,降维后的特征对异常值敏感,过度拟合概率不低。它适合冗余特征多、要省算力的场景,不等于模型绩效必然提升——该留的验证步骤一步别省。

让小布替你跑这套降维诊断
小布盯盘的AIGC已内置多指标共线性与降维建议,打开对应品种页即可看到哪些缓冲区在互相喂噪声,你只管决定留哪组低维特征。

常见问题

若指标缓冲区含负值且想保正交基底,倾向先用截断型SVD;若所有特征非负且要局部加性解释,NMF可能更贴盘口结构,两者都需防过度拟合。
回测准但实盘前几根柱线就漂移、相关变量一多训练耗时陡增,都是高维稀疏引发泛化崩坏的早期信号。
目前小布提供可释方差与共线热力参考,最终截断层数要你按品种波动 regime 定,它不替你锁参数。
输入矩阵混了负值、初始化随机种子不稳、或迭代上限太低都会导致分解不收敛,需先确认数据非负再调超参。
它按奇异值平方占比排累进曲线,拐点前的分量数即截断候选,但拐点未必等于交易最优维,得拿样本外准确率校。