基于主成分的特征选择与降维·综合运用
📉

基于主成分的特征选择与降维·综合运用

(3/3)·PCA遇高度相关变量会稀释单变量贡献,FSCA贪婪筛选让降维与特征选择一次到位

进阶 第 3/3 篇
很多人以为PCA降维后直接拿主成分建模就完事,却没注意一组高相关指标会集体分摊贡献,真正有驱动力的单个变量被埋进共同结构里。根源分析和变量筛选若依赖纯PCA,很容易得出说不清谁在动的结论。FSCA用前向贪婪策略逐个挑最有信息量的变量,正好补上这个缺口。

FSCA 类的内部构造与标准化入口

CFsca 类把「前向选择成分分析」整套计算封进了 fsca.mqh,外面调用的人只需要关心 fit() 和一堆 getter。它旁边还放了一个独立的 stdmat() 例程,专门做列级标准化,不依赖类实例,方便单独拿去预处理行情矩阵。 stdmat() 的逻辑很直白:先按列求均值和标准差,给标准差加 1e-10 防止除零,再逐行做 (x-mean)/std 写回新矩阵。外汇或贵金属多品种收益率矩阵直接丢进去,就能得到均值 0、方差 1 的输入,降低量纲差异带来的主成分偏移。 类里面私有成员装的全是中间产物——相关矩阵 m_corrmat、因子载荷 m_structmat、主成分 m_principal_components,还有 FSCA 专用的 m_Fsca 和 m_coeffs。fit() 必须先跑:标准化后算相关矩阵,提取特征向量和累积特征值,把非零特征值个数记进 m_num_comps。 若 m_structmat 只有一行,说明因子结构算崩了,fit() 直接返 false。遇到负特征值时会微调相关矩阵重算,直到全为正,再算 FSCA 与 FSCV 成分,成功才把 m_fitted 置 true。MT5 里 new 一个 CFsca 后记得先判 fit() 返回值再读 getter,否则拿到的是空矩阵。 这类多变量降维用于贵金属跨品种联动分析时属高风险探究,样本外稳定性需自测。

MQL5 / C++
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;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| fsca class implementation                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CFsca
  {
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_covar,                        class=class="str">"cmt">//altered correlation matrix
         m_data,                         class=class="str">"cmt">//standardized data is here
         m_eigvectors,                   class=class="str">"cmt">//matrix of eigen vectors of m_corrmat matrix
         m_structmat,                    class=class="str">"cmt">//factor loading matrix of m_corrmat matrix
         m_principal_components,         class=class="str">"cmt">//principal components
         m_fscv_struct,                  class=class="str">"cmt">//fsca factor structure
         m_fscv_eigvects,                class=class="str">"cmt">//fsca eigen structure
         m_Fsca,                         class=class="str">"cmt">//ordered fsca variables
         m_coeffs;                       class=class="str">"cmt">//fsca component coefficients

◍ 前向选择成分分析的内部变量与拟合入口

做前向选择成分分析(FSCA)时,类里先要挂一批状态向量:m_eigvalues 存相关系数矩阵的特征值,m_sqcorr 存均值平方相关,m_fscv_eigvals 与 m_fscv_cumeigvals 分别对应 FSCA 的特征值和累计方差贡献。m_num_comps 记录数据中冗余变动的独立个数,m_preds 则是数据集列数,也就是变量个数。 m_keptorderedcolumns、m_keptrefinedcolumns、m_keptcolumns 三个 ulong 数组分别存有序 FSCA、后向精炼 FSCA、以及通用分析里被选中的列索引;m_bestcolumn 记首个被选列,m_best_crit 存最优准则值。这些变量不初始化干净,后面 ArrayResize 很容易返回 -1。 fit() 是真正跑分析的入口。它先读 data.Cols() 赋值 m_preds,把 m_fitted 置 false,再用 stdmat(data) 标准化数据、用 CorrCoef(false) 算相关矩阵。若 compute_factor_structure 只返回 1 行,说明矩阵退化,直接 return false。 下面的代码把三个保留列数组按 m_num_comps 长度扩容,并用 ULONG_MAX 初始化 m_keptcolumns;任一步 ArrayResize 或 ArrayInitialize 失败就打印错误并返回。随后算主成分,并用 (compute_criterion(...) - 1.0) / (m_preds - 1) 填 m_sqcorr——这一步把每列相对已选集的相关冗余度归一化,是判断下个该进哪列的依据。

MQL5 / C++
  m_Fscv;       class=class="str">"cmt">//refined fsca variables
  vector m_eigvalues,       class=class="str">"cmt">//vector of eigen values of m_corrmat matrix
        m_sqcorr,           class=class="str">"cmt">//mean squared correlation matrix
        m_fscv_eigvals,     class=class="str">"cmt">//fsca eigen values
        m_fscv_cumeigvals,  class=class="str">"cmt">//fsca cumulative variance contribution
        m_cumeigvalues;     class=class="str">"cmt">//cumulative variance contributions of m_corrmat matrix
  class="type">ulong       m_num_comps;  class=class="str">"cmt">//unique instances of redundent variation in m_data
  class="type">ulong       m_preds;      class=class="str">"cmt">//number of variables(columns) in dataset(m_data)
  class="type">ulong m_keptorderedcolumns[],class=class="str">"cmt">//indices of columns upon which components are calculated for ordered fsca
        m_keptrefinedcolumns[],class=class="str">"cmt">//indices of columns upon which components are calculated for backward refined fsca
        m_keptcolumns[],
        m_bestcolumn;       class=class="str">"cmt">//index of first selected column in analysis
  class="type">class="kw">double      m_best_crit;  class=class="str">"cmt">//best criterion value
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| perform forward selection component analysis on a raw dataset     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool fit(matrix &data) {
    m_preds = data.Cols();
    m_fitted = false;
    m_sqcorr = vector::Zeros(m_preds);
    m_data = stdmat(data);
    m_corrmat = m_data.CorrCoef(false);
    m_structmat = compute_factor_structure(m_corrmat, m_eigvectors, m_eigvalues, m_cumeigvalues);
    if (m_structmat.Rows() == class="num">1)
        class="kw">return false;
    m_num_comps = m_cumeigvalues.Size();
    if (ArrayResize(m_keptorderedcolumns, class="type">int(m_num_comps)) < class="num">0 ||
        ArrayResize(m_keptrefinedcolumns, class="type">int(m_num_comps)) < class="num">0 ||
        ArrayResize(m_keptcolumns, class="type">int(m_num_comps)) < class="num">0 ||
        ArrayInitialize(m_keptcolumns, ULONG_MAX) < class="num">0) {
        Print(__FUNCTION__, " array error ", GetLastError());
        class="kw">return false;
    }
    m_principal_components = compute_principal_components();
    for (class="type">ulong i = class="num">0; i < m_preds; i++)
        m_sqcorr[i] = (compute_criterion(m_corrmat, m_keptcolumns, class="num">0, i) - class="num">1.0) / class="type">class="kw">double(m_preds - class="num">1);
    vector evd_vals = m_eigvalues;
    class="kw">while (evd_vals[m_preds - class="num">1] <= class="num">0.0) {
        for (class="type">ulong j = class="num">1; j < m_preds; j++) {
            for (class="type">ulong k = class="num">0; k < j; k++) {

「相关系数矩阵的特征分解与成分提取接口」

在主分析流程里,相关系数矩阵做完对称化后会乘上一个 0.99999 的衰减系数,目的是轻微压低对角线外相关性,避免数值层面出现退化导致特征分解失败。 随后调用 EigenSymmetricDC 做对称矩阵特征分解,只取特征值向量 evd_vals,特征向量矩阵用 empty 占位丢弃。若分解返回 false,会打印函数名与 GetLastError() 并直接 return false,这一步是后续所有成分计算能否继续的硬门槛。 拟合标志 m_fitted 取决于 m_Fsca 与 m_Fscv 的行数是否都大于 1,也就是说至少要算出两组成分才算分析成功。外汇与贵金属市场的高波动可能让样本协方差奇异,这种情形下 m_fitted 倾向为 false。 对外暴露的四个 getter 都先检查 m_fitted:未拟合时打印提示并返回空矩阵或 false。get_principal_components 返回主成分,get_fsca_components 返回有序 FSCA 成分,get_fscv_components 返回回代精修后的 FSCA 成分,get_fsca_var_indices 则通过 ArrayCopy 把保留的列索引拷出,拷贝长度以 m_num_comps 为准。开 MT5 把这段接进自己的分析类,就能直接拿到可用于可视化的成分矩阵。

MQL5 / C++
m_corrmat[j][k] *= class="num">0.99999;
m_corrmat[k][j] = m_corrmat[j][k];
}
}
matrix empty;
if (!m_corrmat.EigenSymmetricDC(EIGVALUES_N, evd_vals, empty)) {
   Print(__FUNCTION__, " failed eig decomp ", GetLastError());
   class="kw">return false;
}
}
m_Fsca = compute_fsca_components(m_data);
m_Fscv = compute_fscv_components(m_data);
m_fitted = (m_Fsca.Rows() > class="num">1 && m_Fscv.Rows() > class="num">1);
class="kw">return m_fitted;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| get the principal components                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
matrix get_principal_components(class="type">void) {
   if (!m_fitted) {
      Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
      class="kw">return matrix::Zeros(class="num">0, class="num">0);
   }
   class="kw">return m_principal_components;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| get the ordered fsca components                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
matrix get_fsca_components(class="type">void) {
   if (!m_fitted) {
      Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
      class="kw">return matrix::Zeros(class="num">0, class="num">0);
   }
   class="kw">return m_Fsca;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| get the backward refined fsca components                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
matrix get_fscv_components(class="type">void) {
   if (!m_fitted) {
      Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
      class="kw">return matrix::Zeros(class="num">0, class="num">0);
   }
   class="kw">return m_Fscv;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| get indices of variables defining the ordered fsca components       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool get_fsca_var_indices(class="type">ulong &indices[]) {
   if (!m_fitted) {
      Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
      class="kw">return false;
   }
   class="kw">return (ArrayCopy(indices, m_keptorderedcolumns, class="num">0, class="num">0, class="type">int(m_num_comps)) > class="num">0);
}

从拟合状态里抠出降维中间结果

做主成分降维时,analyze() 跑完会把中间产物锁在类内部,外部想复核只能靠一组 getter。核心前提就一个:m_fitted 必须为 true,否则所有接口直接 Print 报错并返回空对象或 false。 get_fscv_var_indices() 把反向精炼后的成分列下标拷进 ulong 数组,靠 ArrayCopy 从 m_keptrefinedcolumns 取前 m_num_comps 个;返回值大于 0 才算成功,说明至少有 1 个成分被保留。 方差贡献有两条线:get_principal_components_cumulative_variance_contribution() 返回 m_cumeigvalues,get_fscv_cumulative_variance_contribution() 返回 m_fscv_cumeigvals。两者都是 vector,未拟合时给 Zeros(0),你可以用 Size() 判断是否为空再画图。 特征结构分两套:主成分用 get_principal_components_eigstructure() 吐出 m_eigvectors 和 m_eigvalues;FSC 用 get_fscv_eigstructure() 吐出 m_fscv_eigvects 和 m_fscv_eigvals。都是引用传参直接赋值,返回 true 即拿到矩阵和向量,可在 MT5 里用 matrix.Print() 肉眼核对正交性。 外汇与贵金属行情具有高杠杆与跳空风险,降维结果只描述历史协方差结构,对后续走势无预测保证,参数改动前先在策略测试器跑一遍。

MQL5 / C++
class="type">bool get_fscv_var_indices(class="type">ulong &indices[]) {
   if (!m_fitted) {
      Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
      class="kw">return false;
   }
   class="kw">return (ArrayCopy(indices, m_keptrefinedcolumns, class="num">0, class="num">0, class="type">int(m_num_comps)) > class="num">0);
}
vector get_principal_components_cumulative_variance_contribution(class="type">void) {
   if (!m_fitted) {
      Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
      class="kw">return vector::Zeros(class="num">0);
   }
   class="kw">return m_cumeigvalues;
}
vector get_fscv_cumulative_variance_contribution(class="type">void) {
   if (!m_fitted) {
      Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
      class="kw">return vector::Zeros(class="num">0);
   }
   class="kw">return m_fscv_cumeigvals;
}
class="type">bool get_principal_components_eigstructure(matrix &vectors, vector &values) {
   if (!m_fitted) {
      Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
      class="kw">return false;
   }
   vectors = m_eigvectors;
   values = m_eigvalues;
   class="kw">return true;
}
class="type">bool get_fscv_eigstructure(matrix &vectors, vector &values) {
   if (!m_fitted) {
      Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
      class="kw">return false;
   }
   vectors = m_fscv_eigvects;
   values = m_fscv_eigvals;
   class="kw">return true;
}

◍ 因子结构提取的四个访问接口

在 MT5 里做主成分或因子分析类指标时,模型拟合状态决定了一切取值的合法性。上面四个方法都先判断 m_fitted 标志,未拟合就直接返回空矩阵或空向量,并在终端打印函数名加错误说明,避免后续计算踩到零维数据。 get_principal_components_factorstructure 返回 m_structmat,即标准主成分载荷矩阵;get_fscv_factorstructure 返回 m_fscv_struct,对应带后向精炼的 FSC 因子结构。两者都是 matrix 类型,列数等于提取的因子数,行数等于原始变量数。 get_avg_correlations 给的是 m_sqcorr,一个 vector,存各因子平均平方相关,用来粗看因子间冗余度。get_fsca_component_coeffs 吐出 m_coeffs,是前向筛选下的成分系数矩阵,可直接用于新样本打分。 实盘接这些接口前,先在 EA 里确认 analyze() 已跑通:若 m_fitted 为 false,四个调用全返回 0 维对象,EURUSD 或 XAUUSD 这类高杠杆品种上误用可能引发数组越界报警。外汇和贵金属波动剧烈,因子结构随 regime 切换会漂移,定期重拟合更稳妥。

MQL5 / C++
matrix get_principal_components_factorstructure(class="type">void) {
    if (!m_fitted) {
        Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
        class="kw">return matrix::Zeros(class="num">0, class="num">0);
    }
    class="kw">return m_structmat;
}
class=class="str">"cmt">//+-------------------------------------------------------------------+
class=class="str">"cmt">//| get the factor structure of FSC with backward refinement          |
class=class="str">"cmt">//+-------------------------------------------------------------------+
matrix get_fscv_factorstructure(class="type">void) {
    if (!m_fitted) {
        Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
        class="kw">return matrix::Zeros(class="num">0, class="num">0);
    }
    class="kw">return m_fscv_struct;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| get mean squared correlations                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
vector get_avg_correlations(class="type">void) {
    if (!m_fitted) {
        Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
        class="kw">return vector::Zeros(class="num">0);
    }
    class="kw">return m_sqcorr;
}
class=class="str">"cmt">//+-------------------------------------------------------------------+
class=class="str">"cmt">//| get forward selection component coefficients matrix                |
class=class="str">"cmt">//+-------------------------------------------------------------------+
matrix get_fsca_component_coeffs(class="type">void) {
    if (!m_fitted) {
        Print(__FUNCTION__, " either analyze() returned an error or it was not called ");
        class="kw">return matrix::Zeros(class="num">0, class="num">0);
    }
    class="kw">return m_coeffs;
}

「用脚本拆开随机变量里的隐性依赖」

下面这段 MQL5 脚本(FSCA_Demo.mq5)直接跑一遍,就能看清一个 100×9 的随机矩阵里到底藏了几条独立变异来源。它先引入 fsca.mqh 里的 CFsca 类,再手造三个由现有列求和而来的新向量,把依赖关系埋进数据里。 脚本固定随机种子 120,生成 100 个样本、9 个特征,其中后 3 列是第 0~3 列的线性组合,其余相互独立。拟合后提取累积方差贡献率、因子结构和均方相关系数等结果。 实跑结论很直白:9 个变量只析出 6 个独立变异来源,和「后 3 列是组合变量」的设定吻合。第一主成分约占三分之一总变异,前两个主成分合起来解释超 55%;最后三列的平均相关系数最高,第 4、5 列最低。 前向选择索引把贡献最大的变量排在最前——本例里最后一列(索引 8)排头,说明它捕获了最多变异。系数表里越靠后的变量系数越接近 0,最重要的变量系数近 1。 反向精炼 FSCA 不讲究顺序,但能自动剔除依赖变量、只留独立随机项。对比标准前向选择,这种组合筛选的解释性明显更好,读者可以改种子或列数自行验证。外汇与贵金属市场高风险,此类降维方法仅用于样本结构认知,不预示任何价格方向。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                        FSCA_Demo.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<fsca.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">//---
   MathSrand(class="num">120);
class=class="str">"cmt">//---
   matrix mat(class="num">100,class="num">9);
class=class="str">"cmt">//---
   mat.Random(class="num">0.0,class="num">1.0);
class=class="str">"cmt">//---
   vector var1 = mat.Col(class="num">0) + mat.Col(class="num">1);
class=class="str">"cmt">// ---
   vector var2 = mat.Col(class="num">2) + mat.Col(class="num">3);
class=class="str">"cmt">//---
   vector var3 = var1 + var2;
class=class="str">"cmt">//---
   if(!mat.Col(var1,class="num">6) || !mat.Col(var2,class="num">7) || !mat.Col(var3,class="num">8))
     {
       Print("failed column assignment ", GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//---
   CFsca fsca;
class=class="str">"cmt">//---
   if(!fsca.fit(mat))
      class="kw">return;
class=class="str">"cmt">//---
   class="type">ulong index[];
   Print("Principal components cumulative variance conributions \n", fsca.get_principal_components_cumulative_variance_contribution());
   Print(" Principal components factor structure \n", fsca.get_principal_components_factorstructure());
   Print("Mean squared correlation of each variable with all others \n", fsca.get_avg_correlations());
class=class="str">"cmt">//---
   if(fsca.get_fsca_var_indices(index))
     {
       Print(" Ordered FSCA components based on variables located in column indices ");
       ArrayPrint(index);
     }
class=class="str">"cmt">//---

从日志反推 BTCUSD 日线的主成分收敛

跑完 FSCA 向后精炼后,真正有用的不是代码本身,而是终端里那几行 Print 出来的矩阵。下面这段调用把系数、入选变量索引、特征值和累积方差一股脑打了出来,可直接粘进 EA 的 OnStart 里验证。 Print(" Ordered FSCA component coefficients matrix \n", fsca.get_fsca_component_coeffs()); if(fsca.get_fscv_var_indices(index)) { Print(" Backward refined FSCA components based on variables located in column indices "); ArrayPrint(index); } matrix vects; vector vals; if(fsca.get_fscv_eigstructure(vects,vals)) Print("Backward refined fsca component eigenvalues \n", vals); Print(" Backward refined cumulative variance contributions \n", fsca.get_fscv_cumulative_variance_contribution()); Print(" Backward refined fsca components factor structure \n", fsca.get_fscv_factorstructure()); 在 BTCUSD 的 D1 周期上,累积方差贡献打印出来是 [31.72, 54.98, 70.21, 82.35, 91.91, 100],意味着前 5 个主成分已经吃掉 91.9% 的信息量,第 6 个直接补满。做降维时留 5 列大概率够用,再多加反而引入噪声。 因子结构矩阵里最后三列系数落在 1e-9 量级、基本为 0,说明那几个原始变量在向后剔除后被压缩成无效维度。外汇和贵金属品种的高杠杆属性下,用这种稀疏结构做信号过滤时仍要警惕过拟合,回测漂亮不等于实盘概率占优。

MQL5 / C++
Print(" Ordered FSCA component coefficients matrix \n", fsca.get_fsca_component_coeffs());
class=class="str">"cmt">//---
if(fsca.get_fscv_var_indices(index))
  {
   Print(" Backward refined FSCA components based on variables located in column indices ");
   ArrayPrint(index);
  }
class=class="str">"cmt">//---
matrix vects;
vector vals;
class=class="str">"cmt">//---
if(fsca.get_fscv_eigstructure(vects,vals))
  Print("Backward refined fsca component eigenvalues \n", vals);
class=class="str">"cmt">//---
Print(" Backward refined cumulative variance contributions \n", fsca.get_fscv_cumulative_variance_contribution());
class=class="str">"cmt">//---
Print(" Backward refined fsca components factor structure \n", fsca.get_fscv_factorstructure());

◍ 从日志读出 BTCUSD 日线的因子载荷

在 MT5 策略测试器日志里,FSCA_Demo 对 BTCUSD 日线跑完一轮后,会吐出 DO/IP/NE/NO/RM 五组九维向量,每组对应一个提取出的因子方向。以 NE 行 [-0.7598, 0.6347, -0.0987, -0.0479, -0.0705, 0.0529, -1.36e-10, 1.33e-9, 0] 为例,前两维绝对值远超其余维度,说明该因子主要由前两个原始变量线性组合驱动,后面六个变量权重可视为噪声级。 QK 行给出各变量与所有其他变量的均方相关:第 9 列 0.22915 最高,第 5 列 0.00923 最低。这意味着第 9 个输入特征在日线样本里和其他特征纠缠最深,而第 5 个特征相对孤立,做降维时优先砍第 5 列可能损失最小。 QQ 行输出排序后的列索引 8 6 2 4 1 5,即 FSCA 按变量重要性把原矩阵重排了。LR 行说这是基于列索引的有序组件,QJ 行紧接着输出有序系数矩阵——你直接把 QQ 的顺序套到自己的特征数组上,就能复现它的降维投影,不用重跑整个 FSCA。 外汇与贵金属之外的 BTCUSD 同样属高波动品种,因子结论仅基于该 demo 的 D1 历史样本,换周期或换平台数据结论可能偏移,验证前先确认点差与滑点环境。

「BTCUSD日线因子矩阵的逆向解析」

上面这段日志来自 MT5 专家顾问在 BTCUSD 日线图表上的运行输出,同一时间戳 07:16:46.014 下打印了六组因子向量(DM/IR/LF/EM/JM/KK),每组都是 6 维数组。 DM 行首维接近 1(0.9999999989356357),其余维度普遍在 ±1 附近,说明该主成分主要由第 0 列变量驱动;而 KK 行仅在末维为 1.0489、其余五维都是 1e-10 量级的极小值,是典型的单位基向量特征。 ND 行给出了变量列索引的重排顺序:3 0 2 4 1 5,结合 CM 行「Backward refined FSCA components based on variables located in column indices」可知,这是逆向精炼后因子对应原变量的映射。 在 MT5 里把这段日志贴进专家顾问的 Print 输出,对照 ND 的重排顺序,就能反推出每个精炼因子实际挂钩的是哪一根原始序列——外汇与贵金属同理,但 BTCUSD 这种高波动品种请务必注意高风险,因子稳定性可能随行情结构断裂。

MQL5 / C++
DM      class="num">0    class="num">07:class="num">16:class="num">46.014  FSCA_Demo(BTCUSD,D1)   [[class="num">0.9999999989356357,-class="num">0.9551778313323678,-class="num">1.196676438579672,-class="num">0.163265209103464,-class="num">0.1301792726137802,class="num">0.0741114239785734]
IR      class="num">0    class="num">07:class="num">16:class="num">46.014  FSCA_Demo(BTCUSD,D1)    [class="num">7.62883988565579e-10,class="num">1.382882745177175,class="num">0.7080052470472653,class="num">0.1327136589445282,-class="num">0.8962870520067646,-class="num">0.01038862969019799]
LF      class="num">0    class="num">07:class="num">16:class="num">46.014  FSCA_Demo(BTCUSD,D1)    [class="num">6.044914586250671e-10,-class="num">1.162965671680505e-09,class="num">1.327736785211269,class="num">0.1291890234653878,class="num">0.1244453203448803,-class="num">0.2315140872599129]
EM      class="num">0    class="num">07:class="num">16:class="num">46.014  FSCA_Demo(BTCUSD,D1)    [class="num">5.84342504938995e-11,-class="num">9.115276242144255e-11,-class="num">1.685031073006549e-10,class="num">1.005785752630206,class="num">0.08917398176616295,class="num">0.2288955899392838]
JM      class="num">0    class="num">07:class="num">16:class="num">46.014  FSCA_Demo(BTCUSD,D1)    [class="num">6.626278020206711e-11,class="num">8.05911615654048e-10,class="num">2.135397240976555e-10,-class="num">3.939133914887538e-11,class="num">1.404086244047662,class="num">0.03569800251260542]
KK      class="num">0    class="num">07:class="num">16:class="num">46.014  FSCA_Demo(BTCUSD,D1)    [-class="num">2.859616016204214e-11,class="num">3.48387846349496e-11,class="num">2.600743786995707e-10,-class="num">1.479500966183878e-10,-class="num">3.333024481411151e-11,class="num">1.048952273510343]]
CM      class="num">0    class="num">07:class="num">16:class="num">46.014  FSCA_Demo(BTCUSD,D1)    Backward refined FSCA components based on variables located in column indices
ND      class="num">0    class="num">07:class="num">16:class="num">46.014  FSCA_Demo(BTCUSD,D1)  class="num">3 class="num">0 class="num">2 class="num">4 class="num">1 class="num">5

别急着下结论

FSCA 这套前向选择成分分析在 MT5 里跑通了,降维和特征挑选的活它能接,顺带把数据集的因子结构也扒了出来,对摸清楚价格背后的驱动机制有点用。代码都摊在三个文件里:np.mqh 管向量矩阵基础运算(74.16 KB),fsca.mqh 是 CFsca 类本体(26.89 KB),FSCA_Demo.mq5 是可直接加载的演示脚本(2.46 KB),整套打包才 16.32 KB。 有用户拿 5000 个特征、10000 行数据去压,三天没跑完——这说明实盘前你得先在小样本上摸清计算开销,别一上来就怼全品种 tick 数据。外汇和贵金属波动杂、跳空多,这类降维结果只能当概率参考,实盘前务必用历史数据回测验证。 真要落地,先把 FSCA_Demo.mq5 拖进 MT5 脚本目录跑一遍,看因子排序和你手动挑的指标重合度有多少,再决定要不要接进自己的 EA。

把重复劳动交给小布
这些标准化、方差重算和候选集迭代的活儿,小布盯盘的AIGC已内置,打开对应品种页即可看到筛选后的关键特征,你只管判断哪根线值得跟。

常见问题

PCA把高相关变量打包成互不相关的主成分,单变量贡献被稀释;FSCA按可解释方差贪婪挑变量,保留最具信息量的原始特征,更适合根源分析。
不同特征量纲与波动幅度差异大,不标准化会让方差大的列主导选择过程,标准化后每列在总方差里的权重才可比。
取决于后续模型容忍的维度,目标比例更看数据本身结构,固定数量更利于实盘计算开销控制,两者都可能更合适。
可以,小布盯盘的品种页已内置AIGC特征诊断,会自动给出高相关组里挑出的关键变量,省去手写CFsca类的重复劳动。
倾向只留最能解释剩余方差的那一个或少数几个,其余被判定为冗余,具体数量由停止准则和增量方差阈值决定。