特征向量和特征值:MetaTrader 5 中的探索性数据分析(基础篇)
用主成分给行情降维
在 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。实盘里这两道关任一通过,才建议继续做标准化与特征分解;外汇与贵金属杠杆高、跳空频繁,历史窗口的适配结论只代表那段样本,换周期可能直接失效。
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 以下。
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,观察载荷矩阵第二列符号和幅度的变化。
{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">//+------------------------------------------------------------------+