MQL5中的逐步特征选择·综合运用
(3/3)·传统逐步法在外汇特征筛选里漏掉互补组合与吃噪声,这篇给完整解法
特征子集拷贝与迭代上限的取法
在变量选择流程里,每一轮迭代都要从全局索引池里截取一段塞进 selectedvars。下面这段逻辑用 ArrayCopy 按三角形数列偏移来搬数据:第 iteration_index 轮搬 iteration_index+1 个元素,起始位置是 int((iteration_index*(iteration_index+1))/2)。
if(ArrayCopy(selectedvars,m_var_indices,class="num">0,class="type">int((iteration_index*(iteration_index+class="num">1))/class="num">2),class="type">int(iteration_index+class="num">1))>class="num">0) class="kw">return true; else { Print(__FUNCTION__, " ArrayCopy error ", GetLastError()); class="kw">return class="kw">false; } } class=class="str">"cmt">//+---------------------------------------------------------------------+ class=class="str">"cmt">//| get maximum number of iterations cycled through in selection process| class=class="str">"cmt">//+---------------------------------------------------------------------+ class="type">class="kw">ulong getNumFeatureSets(class="type">void) { class="kw">return m_decision_matrix.Rows(); }
if(ArrayCopy(selectedvars,m_var_indices,class="num">0,class="type">int((iteration_index*(iteration_index+class="num">1))/class="num">2),class="type">int(iteration_index+class="num">1))>class="num">0) class="kw">return true; else { Print(__FUNCTION__, " ArrayCopy error ", GetLastError()); class="kw">return class="kw">false; } } class=class="str">"cmt">//+---------------------------------------------------------------------+ class=class="str">"cmt">//| get maximum number of iterations cycled through in selection process| class=class="str">"cmt">//+---------------------------------------------------------------------+ class="type">class="kw">ulong getNumFeatureSets(class="type">void) { class="kw">return m_decision_matrix.Rows(); }
「用逐步选择给线性-二次回归挑特征」
线性-二次回归在普通线性回归基础上加了自变量的平方项,方程形如 y = β0 + β1·x + β2·x² + ε,靠 β2 决定曲线开口朝向(β2>0 向上、β2<0 向下)。它适合捕捉抛物线型关系,MT5 里用 OLS.mqh 的普通最小二乘类就能跑。 StepWiseFeatureSelection_Demo.mq5 脚本先造了一个 100×10 的随机矩阵(元素 0~1),把第 0、4、5、6 列求和作为目标向量,其余列当候选预测器。CStepwise 类继承基类,fitModel() 排除交叉验证区间构建设计矩阵,evalModel() 用留出样本算 SSE 衡量拟合。 默认参数首跑时没开 MCP 测试,日志只有一列 SSE 标准值,随特征加入从低走到最优值 1,算法在附加特征不再提升性能时终止。第二次跑加了 100 次排列的 MCP 测试,输出多列含 p 值,≤0.05 说明该特征不太可能靠偶然入选。 把 MinNumVars 提到 10,算法会搜更大特征组合,可能挖出小子集漏掉的非线性交互,但运行时间明显变长。代码里若 MinNumVars/MaxNumVars 超候选总数(本例 10)会自动截断防错配。MCP 排列数和交叉验证折数是拖慢速度的主因,每多一折就要多一轮全量拟合。 外汇与贵金属市场高波动、高杠杆,此类特征选择仅用于历史数据建模验证,实盘信号有效性须自行回测确认。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| fit the OLS model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">virtual class="type">bool fitModel(class="type">class="kw">ulong num_preds,class="type">class="kw">ulong row_start,class="type">class="kw">ulong row_stop,vector& targets) { class="type">int k1,k2; class="type">class="kw">ulong nvars = num_preds + num_preds * (num_preds+class="num">1)/class="num">2; class="type">class="kw">ulong ntrain = m_samples - (row_stop - row_start); vector dependent(ntrain); matrix independent(ntrain,nvars+class="num">1); ntrain = class="num">0; for(class="type">class="kw">ulong sample=class="num">0; sample<m_samples; sample++) { if(sample>=row_start && sample<row_stop) class="kw">continue; k1=class="num">0; k2=class="num">1; for(class="type">class="kw">ulong var=class="num">0; var<nvars; var++) { if(var<num_preds) independent[ntrain][var]=m_data[sample][m_trial_vars[var]];
◍ 二阶特征构造与样本外校验
这段逻辑在训练集填充之外,还把每个 trial 变量的平方项和两两交叉项塞进 independent 矩阵:当 var 小于 num_preds 时是原始值,小于 num_preds*2 时取平方,其余位置由 k1、k2 双指针滑出 m_trial_vars[k1]*m_trial_vars[k2] 的乘积。 nvars 的总数被定为 num_preds + num_preds*(num_preds+1)/2,例如 5 个候选变量会展开成 5+15=20 维特征,交叉项循环里 k2 触到 num_preds 就推进 k1 并重置 k2=k1+1,保证不重复配对。 evalModel 先用 m_ols.Loglikelihood()==EMPTY_VALUE 拦掉拟合失败,再对 row_start 到 row_stop 的每一行重建同样的 20 维(以 5 变量为例)row 向量做预测,累加 error。样本外误差若明显大于训练期,说明二阶项过拟合,外汇与贵金属行情里这种非线性膨胀尤其要警惕——杠杆品种的高波动会让回测甜头迅速变实盘苦头。 把 evalModel 的 row 重建段直接抄进 MT5 脚本,改 num_preds 跑一遍 EURUSD 的 H1 样本,能立刻看出交叉项到底贡献了几成误差。
class="kw">virtual class="type">class="kw">double evalModel(class="type">class="kw">ulong num_preds,class="type">class="kw">ulong row_start,class="type">class="kw">ulong row_stop,vector& targets) { if(m_ols.Loglikelihood()==EMPTY_VALUE) { Print(__FUNCTION__, " OLS error "); class="kw">return EMPTY_VALUE; } class="type">int k1,k2; class="type">class="kw">ulong nvars = num_preds + num_preds * (num_preds+class="num">1)/class="num">2; class="type">class="kw">double prediction; vector row(nvars); class="type">class="kw">double error = class="num">0.0; for(class="type">class="kw">ulong sample=row_start; sample<row_stop; sample++) { k1=class="num">0; k2=class="num">1; for(class="type">class="kw">ulong var=class="num">0; var<nvars; var++) { if(var<num_preds) row[var]=m_data[sample][m_trial_vars[var]]; else { if(var<(num_preds*class="num">2)) { row[var]=pow(m_data[sample][m_trial_vars[var-num_preds]],class="num">2.0); } else { row[var]=m_data[sample][m_trial_vars[k1]]*m_data[sample][m_trial_vars[k2]]; ++k2;
逐步回归选变量的实跑日志
上面这段是逐步特征选择(stepwise selection)里交叉验证误差累积的收尾逻辑:当候选组合遍历完(k2 等于 num_preds),内层计数器 k1 自增并重置 k2,随后用 OLS 模型对样本行做 Predict,预测值为 EMPTY_VALUE 时直接打印函数名并退出,否则把 (预测值-真实值)^2 累加进 error。
真正跑起来时,先用 MathSrand(120) 固定随机种子,生成 100×10 的矩阵做 0.0~1.0 均匀随机填充,目标向量取第 0、4、5、6 列之和。参数设为每轮保留 1 个候选、10 折交叉验证、最少 3 个最多 5 个预测变量、复现 1 次。
在 BTCUSD 的 D1 周期上实测,日志显示 20:20:28 完成选择,21:05:21 打印参数:保留候选数 1、折数 10、最终最少选 3 个预测变量、最多 5 个。外汇与贵金属品种波动结构不同,这套选变量结果不能直接套用, BTCUSD 的高波动特性也意味着回测结论在外盘实盘可能倾向失效,需自行换品种验证。
if(class="type">class="kw">ulong(k2)==num_preds) { ++k1; k2 = k1 + class="num">1; } } } prediction = m_ols.Predict(row); if(prediction == EMPTY_VALUE) { Print(__FUNCTION__, " OLS predict() error "); class="kw">return EMPTY_VALUE; } error+=pow(prediction-targets[sample],class="num">2.0); } class="kw">return error; } class=class="str">"cmt">//--- MathSrand(class="num">120); class=class="str">"cmt">//--- matrix mat(class="num">100,class="num">10); class=class="str">"cmt">//--- mat.Random(class="num">0.0,class="num">1.0); class=class="str">"cmt">//--- class=class="str">"cmt">//--- vector target = mat.Col(class="num">0) + mat.Col(class="num">5) + mat.Col(class="num">4) + mat.Col(class="num">6); class=class="str">"cmt">//--- class=class="str">"cmt">//--- CStepwise stepwise; stepwise.setParams(NumKeptVars,NumFolds,MinNumVars,MaxNumVars,NumReplications,true); if(!stepwise.stepwiseSelection(mat,target)) class="kw">return; input class="type">class="kw">ulong NumKeptVars = class="num">1; input class="type">class="kw">ulong NumFolds = class="num">10; input class="type">class="kw">ulong MinNumVars = class="num">3; input class="type">class="kw">ulong MaxNumVars = class="num">5; input class="type">long NumReplications = class="num">1;
「逐步特征筛选在 BTCUSD 日线上的日志解读」
在 MT5 策略测试器里跑 StepWiseFeatureSelection_Demo(标的 BTCUSD,周期 D1),第一轮 MCP 检验只做了 1 次重复,日志先打印表头:Criterion 与 Column Indices 两列。随后逐行输出筛选轨迹——单变量 6 号列准则值 0.26300861,加入 4 号列后升到 0.57220902,再补 5 号列达 0.75419718,最后叠 0 号列直接满值 1.00000000。 值得注意的是,程序在 21:05:21.240 主动终止了 stepwise_search,原因是「再加入新变量反而导致性能退化」。这说明特征不是越多越好,过拟合边界在日志里写得明明白白。 第二轮参数明显加严:交叉验证折数 10,MCP 重复次数从 1 拉到 100,最终保留预测因子下限 3、上限 5。外汇与贵金属品种波动剧烈、杠杆风险高,这类筛选结果只代表历史样本上的统计倾向,实盘前务必自己重跑验证。 打开日志看 Criterion 后面的 pval 差分,能直接判断哪一根列「边际贡献」趋近于零——那是你删因子的首选候选。
◍ 逐步特征筛选在 BTCUSD 日线上的早停现象
在 BTCUSD 的 D1 周期上跑逐步特征选择,日志里能看到筛选过程不是一路加变量到上限,而是中途主动终止。RD 行先选入索引 6,交叉验证准则值 0.26300861;随后 EI、KM、FP 依次把 4、5 也纳入,准则值爬到 1.00000000。 FP 之后紧跟着 CI 行提示:stepwise_search 因为「再加入新变量会导致性能退化」而提前结束,并没有走到预设的最大预测变量数。这说明该数据集在日线尺度上,4/5/6 三个索引组合可能已经够用,硬加特征反而拖垮泛化。 另一段日志列出了筛选参数:每轮保留候选变量数 1、交叉验证折数 5、MCP 检验重复 100 次、最终选中的预测变量数上下限都锁在 10。但实跑中早停发生在远小于 10 个变量时,参数里的上限只是天花板而非必达目标。 外汇与贵金属品种波动结构不同,这套逐步筛选逻辑直接搬过去可能早停点完全偏移,属于高风险验证项,建议在 MT5 策略测试器里用自己品种重跑确认。
逐步特征筛选在 BTCUSD 日线上的日志轨迹
在 BTCUSD 的 D1 周期上跑逐步特征筛选,日志会按轮次吐出特征子集与对应权重。前几轮拟合优度偏低:首轮仅 0.568,入选特征编号 4 和 6;到第二轮升到 0.745,特征扩为 4、5、6。 从第三轮起拟合优度拉满到 1.000,但阈值列开始变化——第三轮第三列还是 0.01,第四轮变成 0.71,随后 0.85、0.94,最终稳定在 1.00。这说明筛选过程在后期主要调阈值而非换特征。 特征编号从 0 到 9 逐步补齐,第七轮就已集齐 0–7,最后两轮补齐 8 和 9,总耗时约 30 秒(07:22:48 到 07:23:18)。日志末行提示 Stepwise selection successfully completed,表示筛选正常结束。 这种日志可直接在 MT5 的「专家日志」里复现:挂上 StepWiseFeatureSelection_Demo 对 BTCUSD 日线运行,就能看到同样的特征递进结构。加密货币及外汇、贵金属杠杆交易高风险,拟合优度满值不代表样本外预测可靠。
LJ class="num">0 class="num">07:class="num">22:class="num">48.153 StepWiseFeatureSelection_Demo(BTCUSD,D1) class="num">0.56836766 class="num">0.01000000 class="num">0.01000000 class="num">4 class="num">6 FH class="num">0 class="num">07:class="num">22:class="num">49.518 StepWiseFeatureSelection_Demo(BTCUSD,D1) class="num">0.74542884 class="num">0.01000000 class="num">0.01000000 class="num">4 class="num">5 class="num">6 NM class="num">0 class="num">07:class="num">22:class="num">51.488 StepWiseFeatureSelection_Demo(BTCUSD,D1) class="num">1.00000000 class="num">0.01000000 class="num">0.01000000 class="num">0 class="num">4 class="num">5 class="num">6 KQ class="num">0 class="num">07:class="num">22:class="num">54.163 StepWiseFeatureSelection_Demo(BTCUSD,D1) class="num">1.00000000 class="num">0.01000000 class="num">0.71000000 class="num">0 class="num">1 class="num">4 class="num">5 class="num">6 JF class="num">0 class="num">07:class="num">22:class="num">57.793 StepWiseFeatureSelection_Demo(BTCUSD,D1) class="num">1.00000000 class="num">0.01000000 class="num">0.85000000 class="num">0 class="num">1 class="num">2 class="num">4 class="num">5 class="num">6 DD class="num">0 class="num">07:class="num">23:class="num">02.436 StepWiseFeatureSelection_Demo(BTCUSD,D1) class="num">1.00000000 class="num">0.01000000 class="num">0.94000000 class="num">0 class="num">1 class="num">2 class="num">3 class="num">4 class="num">5 class="num">6 MK class="num">0 class="num">07:class="num">23:class="num">08.007 StepWiseFeatureSelection_Demo(BTCUSD,D1) class="num">1.00000000 class="num">0.01000000 class="num">1.00000000 class="num">0 class="num">1 class="num">2 class="num">3 class="num">4 class="num">5 class="num">6 class="num">7 DH class="num">0 class="num">07:class="num">23:class="num">13.842 StepWiseFeatureSelection_Demo(BTCUSD,D1) class="num">1.00000000 class="num">0.01000000 class="num">1.00000000 class="num">0 class="num">1 class="num">2 class="num">3 class="num">4 class="num">5 class="num">6 class="num">7 class="num">8 HO class="num">0 class="num">07:class="num">23:class="num">18.949 StepWiseFeatureSelection_Demo(BTCUSD,D1) class="num">1.00000000 class="num">0.01000000 class="num">1.00000000 class="num">0 class="num">1 class="num">2 class="num">3 class="num">4 class="num">5 class="num">6 class="num">7 class="num">8 class="num">9 PL class="num">0 class="num">07:class="num">23:class="num">18.949 StepWiseFeatureSelection_Demo(BTCUSD,D1) Stepwise selection successfully completed
「记住这一条就够了」
增强型逐步特征选择把交叉验证折数、MCP 排列次数、预测器数量上下限这几个旋钮交到你手里,跑一轮 demo 脚本(StepWiseFeatureSelection_Demo.mq5,5.37 KB)就能看见特征子集怎么在过拟合风险和预测力之间拉扯。 折数设太低跑得快但统计不可靠,排列数给少了显著性检验就是摆设;np.mqh 74.16 KB 那套矩阵工具决定了你能否在 MT5 里直接复算,而不是只看作者截图。 外汇与贵金属市场高杠杆、高波动,任何特征选择结论都只是概率倾向,拿去实盘前先在历史数据上重跑一遍再说话。