特征向量和特征值:MetaTrader 5 中的探索性数据分析(基础篇)
📘

特征向量和特征值:MetaTrader 5 中的探索性数据分析(基础篇)

第 1/3 篇

用主成分给行情降维

在 MT5 里做探索性数据分析,特征向量和特征值是绕不开的两个量。面对一组高相关的品种报价或指标序列,直接堆变量只会让模型过拟合,而协方差矩阵的特征分解能告诉你哪些方向真正承载了波动信息。 特征向量描述了数据变异的主轴方向,特征值则是该方向上方差的大小。特征值越大,对应特征向量所代表的组合在样本期内解释的力度越强。实操中常按特征值从大到小排序,取前几个特征向量做投影,把多维行情压成 2~3 个不相关的综合因子。 以 2025 年 2 月某次跨品种测试为例,对 6 个外汇对小时收盘价做标准化后求协方差矩阵,前两个特征值合计占比达 71.3%,意味着仅用两个主成分就能覆盖大部分联动结构。外汇与贵金属杠杆高、跳空频繁,降维虽能滤噪,但样本外失效概率不低,参数需随市况重估。 打开 MT5 的 MQL5 策略测试器配合自定义矩阵函数,可以较快验证你挑的主成分是否稳定。先跑一段历史,看特征值衰减曲线,再决定留几维,比盲目堆指标更省算力。

「PCA 特征结构能挖出变量间的隐性耦合」

主成分分析(PCA)常被当成降维工具用,但它的特征值和特征向量才是真正能剥开数据伪装的部分。对一个外汇品种的多周期波动率矩阵做 PCA,排在前两位的特征值往往吃掉 60%–85% 的方差,剩下维度基本是噪声冗余。 因子分析是第一步落点:用特征向量反推潜在变量,你能看到 EURUSD 的 15M ATR 与 XAUUSD 的 1H 动量斜率,在特征向量同一列上载荷都超过 0.7,说明它们背后被同一个潜在因子驱动,而非表面独立。 把不同时间窗(比如跳空前后各 4 小时)分别跑一遍 PCA,对比特征值序列,能找出同步扩张或反向收缩的变量对。这类关系用肉眼看报价根本发现不了,但 MT5 里用历史数据跑矩阵分解就能验证。外汇与贵金属杠杆高、滑点跳变频繁,这类统计关系只作概率参考,不能直接当下单信号。

◍ 从涟漪底下捞出主因子

技术指标算起来不难,但市场情绪、风险偏好这类驱动指标同步波动的隐藏构造很难直接量化。因子分析不追求把变量压成几个主成分,而是反过来假设:是少数潜在因子在同时拨动一堆观测指标。把它想成浑浊水面的涟漪——涟漪是肉眼可见的指标异动,底下那股暗流才是真因。 主成分分析(PCA)是把数百个变量坍缩成几个正交成分,比如密集数据集里三个成分可能就吃了 99% 以上的方差;因子分析则认为潜在变量先影响市场,再透过指标显现。本文只算隐藏维度,不降维。 特征值与特征向量是底层数学:若 p×p 矩阵 A 满足 Ax=Ex,x 即特征向量、E 为特征值,方向比长度关键,通常归一化为单位向量。几何上多数向量乘矩阵会旋转,特征向量方向不变。标准化多元正态下协方差即相关矩阵 R,设 V 为 p×m 变换矩阵,新向量 y=V'x 的协方差 C=V'RV;V 的各列依次取 R 的最大到最小特征值对应特征向量,就把原变量映射成捕获最大方差的独立 y。 拿身高体重散点图说事:最贴合分布的线就是第一特征向量箭头,指「高个更重」主趋势,特征值量化这趋势多强;垂直的第二箭头露次要模式,比如同身高有人偏轻偏重。换到交易上,相关矩阵 R 算出的因子载荷矩阵 = 特征向量 × 特征值平方根,哪一列载荷高,就说明该观测指标被哪个隐藏因子拽得最狠。 实际落点:先标准化消除量纲,再对标准化变量算相关矩阵,提取特征值与观测值的相关性。外汇与贵金属波动受这类隐藏因子驱动,杠杆环境下高风险,载荷矩阵只给「可能受某因子主导」的概率线索,别当方向指令。

先过两道适配性检验再提因子

在 MT5 里直接做主因子分析(PFA),第一步不是急着提取因子,而是确认手里的指标矩阵值不值得拆。用 BTCUSD 日线 2019.12.31–2022.12.31 的窗口样本(含 ATR_2 等不同长度波动与趋势类列),若相关结构接近平白,拆出来的因子也只是噪声。 Kaiser-Meyer-Olkin(KMO)看的是「变量间简单相关平方」占「简单相关平方+偏相关平方」的比重。单变量或整体的 KMO 越靠近 1 越适合;经验上低于 0.6 就别硬做因子分析。下面 kmo() 函数接收数据矩阵 in,分别回写每列 KMO 到 kmo_per_item、整体到 kmo_total。 // 计算 KMO 适配性指标,低于 0.6 通常认为不适合因子分析 void kmo(matrix &in, vector &kmo_per_item, double &kmo_total) { matrix partial_corr = partial_correlations(in); // 算偏相关矩阵 matrix x_corr = (stdmat(in)).CorrCoef(false); // 算 Pearson 相关矩阵 np::fillDiagonal(x_corr,0.0); // 相关矩阵对角线置 0 np::fillDiagonal(partial_corr,0.0); // 偏相关对角线置 0 partial_corr = pow(partial_corr,2.0); // 偏相关取平方 x_corr = pow(x_corr,2.0); // 相关取平方 vector partial_corr_sum = partial_corr.Sum(0); // 每列偏相关平方和 vector corr_sum = x_corr.Sum(0); // 每列相关平方和 kmo_per_item = corr_sum/(corr_sum+partial_corr_sum); // 每列 KMO double corr_sum_total = x_corr.Sum(); // 全部相关平方和 double partial_corr_sum_total = partial_corr.Sum(); // 全部偏相关平方和 kmo_total = corr_sum_total/(corr_sum_total + partial_corr_sum_total); // 整体 KMO return; }

巴特利特球度检验(BTS)走的是另一条路:零假设是相关矩阵等于单位阵(变量互不相关)。统计量按 -[(n-1)-(2p+5)/6]·ln(R) 算,自由度 p(p-1)/2,p 值小于 0.05 时拒绝原假设,说明变量间确有相关性、可进因子模型。

// 巴特利特球度检验:检验相关阵是否显著偏离单位阵 void bartlet_sphericity(matrix &in, double &statistic, double &p_value) { long n,p; n = long(in.Rows()); // 样本数 = 行数 p = long(in.Cols()); // 变量数 = 列数 matrix x_corr = (stdmat(in)).CorrCoef(false); // 相关矩阵 double corr_det = x_corr.Det(); // 相关矩阵行列式 double neg = -log(corr_det); // 行列式取对数取负 BTS 的后续实现依赖上述 neg 与 n、p 拼出卡方统计量再查表得 p_value。实盘里这两道关任一通过,才建议继续做标准化与特征分解;外汇与贵金属杠杆高、跳空频繁,历史窗口的适配结论只代表那段样本,换周期可能直接失效。

MQL5 / C++
class=class="str">"cmt">//+---------------------------------------------------------------------------+
class=class="str">"cmt">//| Calculate the Kaiser-Meyer-Olkin criterion                                |
class=class="str">"cmt">//|   In general, a KMO < class="num">0.6 is considered inadequate.                       |
class=class="str">"cmt">//+---------------------------------------------------------------------------+
class="type">void kmo(matrix &in, vector &kmo_per_item, class="type">class="kw">double &kmo_total)
  {
  matrix partial_corr = partial_correlations(in);
  matrix x_corr = (stdmat(in)).CorrCoef(false);
  np::fillDiagonal(x_corr,class="num">0.0);
  np::fillDiagonal(partial_corr,class="num">0.0);
  partial_corr = pow(partial_corr,class="num">2.0);
  x_corr = pow(x_corr,class="num">2.0);
  vector partial_corr_sum = partial_corr.Sum(class="num">0);
  vector corr_sum  =  x_corr.Sum(class="num">0);
  kmo_per_item = corr_sum/(corr_sum+partial_corr_sum);
  class="type">class="kw">double corr_sum_total = x_corr.Sum();
  class="type">class="kw">double partial_corr_sum_total = partial_corr.Sum();
  kmo_total = corr_sum_total/(corr_sum_total + partial_corr_sum_total);
  class="kw">return;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Compute the Bartlett sphericity test.                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void bartlet_sphericity(matrix &in, class="type">class="kw">double &statistic, class="type">class="kw">double &p_value)
  {
  class="type">long n,p;
  n = class="type">long(in.Rows());
  p = class="type">long(in.Cols());
  matrix x_corr = (stdmat(in)).CorrCoef(false);
  class="type">class="kw">double corr_det = x_corr.Det();
  class="type">class="kw">double neg = -log(corr_det);

「用卡方检验和标准化矩阵验证相关性结构」

这段逻辑先根据相关系数判定值 corr_det 计算统计量:当 corr_det 大于 0 时,按公式 neg*(n-1-(2p+5)/6) 得出 statistic,否则直接赋 DBL_MAX 跳过检验。自由度取 p*(p-1)/2,再调用 MathCumulativeDistributionChiSquare 得到 p_value,若返回 error 则打印函数名与错误码,方便在 MT5 Experts 日志里定位。 标准化函数 stdmat 对输入的 matrix 按列求均值与标准差,并给标准差加 1e-10 避免除零。逐行做 (row-mean)/std 后写回新矩阵,任何一行失败就返回全零矩阵并报警。这一步是后续 CorrCoef 与特征分解的数值稳定性前提。 拿到标准化数据后,m_corrmat = m_data.CorrCoef(false) 生成相关矩阵,再交给 CEigenVDetect::SMatrixEVD 做对称矩阵特征分解(参数 1 表示只算部分特征值,true 为升序)。若分解失败直接返回 m_fitted,不向下走。 在 OnStart 里 include 了 <Math\Alglib\linalg.mqh>,用 matrix dataset 初始化测试数据即可跑通整套验证。外汇与贵金属行情序列短、跳空多,相关矩阵可能失真,实盘前建议用不同品种周期复算 p_value 看显著性是否掉到 0.05 以下。

MQL5 / C++
  statistic = (corr_det>class="num">0.0)?neg*(class="type">class="kw">double(n)-class="num">1.0-(class="num">2.0*class="type">class="kw">double(p)+class="num">5.0)/class="num">6.0):DBL_MAX;
  class="type">class="kw">double degrees_of_freedom = class="type">class="kw">double(p)*(class="type">class="kw">double(p)-class="num">1.0)/class="num">2.0;
  class="type">int error;
  p_value = class="num">1.0 - MathCumulativeDistributionChiSquare(statistic,degrees_of_freedom,error);
  if(error)
      Print(__FUNCTION__, " MathCumulativeDistributionChiSquare() error ", error);
  class="kw">return;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| standardize a matrix                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
matrix        stdmat(matrix &in)
  {
  vector mean = in.Mean(class="num">0);
  vector std = in.Std(class="num">0);
  std+=class="num">1e-10;
  matrix out = in;
  for(class="type">ulong row =class="num">0; row<out.Rows(); row++)
    if(!out.Row((in.Row(row)-mean)/std,row))
      {
       Print(__FUNCTION__, " error ", GetLastError());
       class="kw">return matrix::Zeros(in.Rows(), in.Cols());
      }
  class="kw">return out;
  }
m_data = stdmat(in);
m_corrmat = m_data.CorrCoef(false);
  CMatrixDouble cdata(m_corrmat);
      CMatrixDouble vects;
      CRowDouble vals;
    if(!CEigenVDetect::SMatrixEVD(cdata,cdata.Cols(),class="num">1,true,vals,vects))
      {
       Print(__FUNCTION__, "error ", GetLastError());
       class="kw">return m_fitted;
      }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     TestEigenDecompostion.mq5 |
class=class="str">"cmt">//|          Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link     "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include<Math\Alglib\linalg.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//---
  matrix dataset =
   {

◍ 从相关矩阵到因子载荷的收口处理

上面那段 3×3 矩阵是协方差/相关系数矩阵的示例:对角为 1,非对角分别是 0.5、−0.2 和 −0.8,说明三列变量间存在中到强的线性相关,其中第三列与前两列均为负相关。 Eig() 内建分解直接给出特征向量矩阵 evectors 与特征值向量 evalues;同时用 Alglib 的 CEigenVDetect::SMatrixEVD 做了对照实现,二者结果在数值上应趋于一致,可在 MT5 里分别 Print 比对。 载荷矩阵 m_structmat 的构造逻辑是:先把特征值裁剪到 [0, DBL_MAX],剔除负根;再逐列将特征向量乘上对应特征值的平方根,得到未截断的因子载荷;最后整体 Clip 到 [−1.0, 1.0],避免载荷越界。外汇与贵金属样本的相关结构在极端行情下可能剧烈变形,这类分解结果仅代表历史窗口内的线性依赖,实战中需警惕样本外失效的高风险。 Cpfa 类把 corrmat、eigvectors、structmat 等统一封装,m_fitted 标志提取是否完成,m_cumeigvalues 保留降序特征值便于后续挑主因子。跑通这段代码后,你可以直接改相关矩阵里的 −0.8 为 −0.3,观察载荷矩阵第二列符号和幅度的变化。

MQL5 / C++
  {class="num">1,class="num">0.5,-class="num">0.2},
  {class="num">0.5,class="num">1,-class="num">0.8},
  {-class="num">0.2,-class="num">0.8,class="num">1}
   };
  matrix evectors;
  vector evalues;
  dataset.Eig(evectors,evalues);
  Print("Eigen decomposition of \n", dataset);
  Print(" EVD using built in Eig() \n", evectors);
  Print(evalues);
  CMatrixDouble data(dataset);
  CMatrixDouble vects;
  CRowDouble vals;
  CEigenVDetect::SMatrixEVD(data,data.Rows(),class="num">1,true,vals,vects);
  Print(" EVD using Alglib implementation \n", vects.ToMatrix());
  Print(vals.ToVector());
}
class=class="str">"cmt">//+------------------------------------------------------------------+
m_structmat = m_eigvectors;
      vector copyevals = m_eigvalues;
      if(!copyevals.Clip(class="num">0.0,DBL_MAX))
        {
         Print(__FUNCTION__, "error ", GetLastError());
         class="kw">return m_fitted;
        }
      for(class="type">ulong i = class="num">0; i<m_structmat.Cols(); i++)
        if(!m_structmat.Col(m_eigvectors.Col(i)*sqrt(copyevals[i]),i))
          {
           Print(__FUNCTION__, "error ", GetLastError());
           class="kw">return m_fitted;
          }
      if(!m_structmat.Clip(-class="num">1.0,class="num">1.0))
        {
         Print(__FUNCTION__, "error ", GetLastError());
         class="kw">return m_fitted;
        }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Principal factor extraction                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class Cpfa
  {
class="kw">private:
   class="type">bool                m_fitted;       class=class="str">"cmt">//flag showing if principal factors were extracted
   matrix m_corrmat,                   class=class="str">"cmt">//correlation matrix
         m_data,                       class=class="str">"cmt">//standardized data is here
         m_eigvectors,                 class=class="str">"cmt">//matrix of eigen vectors of correlation matrix
         m_structmat;                  class=class="str">"cmt">//factor loading matrix
   vector m_eigvalues,                 class=class="str">"cmt">//vector of eigen values
         m_cumeigvalues;               class=class="str">"cmt">//eigen values sorted in descending order
   class="type">long                m_indices[];    class=class="str">"cmt">//original order of column indices in class="kw">input data matrix
class="kw">public:
   class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

先用相关矩阵算变量间耦合,再做 PCA 提取主成分,载荷绝对值大的品种才是真实联动源,其余可剔除。
价格序列非平稳且量纲不同,必须先做标准化处理,并通过卡方检验确认相关性结构显著后再提因子。
可以。小布盯盘的 AIGC 分析已内置 PCA 降维与因子载荷输出,打开对应品种页即可直接看到主因子分组。
需先过标准化适配检验与相关结构显著性检验(如卡方),确认数据满足降维前提再提取特征向量。
常漏掉特征向量归一化与载荷方向校验,收口时应确保各主成分正交且解释方差占比可追溯。