数据科学与机器学习(第 07 部分):多项式回归(基础篇)
◍ 用多项式拟合非线性价格轨迹
在 MT5 里做价格建模,线性拟合经常兜不住震荡段和拐点密集的走势。把自变量做成 x、x²、x³ 这样的高阶项,就是多项式回归的思路,它能把一条弯弯曲曲的残差带压得更贴盘面。 原文给出的系列开篇目录里列了 31 一月 2023 发布的第 07 部分,当时页面阅读量是 1 437,说明这类非线性拟合在量化交易者里并非冷门玩具。 阶数选太低会欠拟合,选太高又会在样本外乱抖。后续会落到贝叶斯信息准则(BIC)来挑阶数,以及特征缩放避免高阶项数值爆炸——这两点在 MT5 的 Python/R 桥或纯 MQL5 矩阵运算里都得自己写。
从线性退一步看多项式回归
先不急着把整套回归模型铺开,回到更底层的视角。基础线性回归其实是大量机器学习模型的骨架,而多项式回归只是它的一个变体,核心仍是拟合自变量与因变量的关系,只是把特征做了幂次扩展。 在 MT5 的行情建模里,这类参数化方法值得留意:像多项式回归、支持向量机都属于带显式参数的路子,通用性较强。它们处理简单样本时能给出近乎直线的边界,遇到复杂分布又可以通过高阶项画出非线性分界面,这对价格行为里的拐点识别有参考意义。 外汇与贵金属杠杆高、跳空频繁,任何回归边界都只是历史样本的概率拟合,实盘使用前建议在策略测试器里用不同品种回测验证。
「多项式不是非得写满每一项」
多项式本质是把已有数据 x 做升幂,再乘上不同系数做缩放。比如一个三次式里,常数 5 对应 a0,-7 对应 a1,4 对应 a2,11.3 对应 a3,每个系数只决定对应幂次项的权重。 实际建模时,你不一定非要把 x 的每一项都写进方程。有些幂次项对拟合贡献极小,留空反而更干净,也降低过拟合概率。 看 MT5 里的多项式回归实现,重点在系数数组和幂次循环,而不是数学式子长得多完整。
◍ 多项式回归里的阶数怎么定
做回归拟合时,多项式的阶(用 n 表示)指的是表达式里 x 的最高次幂,而不是变量的个数。比如一条曲线写成 y = a0 + a1·x + a2·x² + a3·x³,不管前面挂了几个系数,只要最高次是 x³,它就是三阶多项式回归。 很多人会被表象带偏:有的方程里 x 的一次、二次、三次项系数齐全,排列像是 1、2、3;有的方程只显式写了两项,但其中一项是 x³。这时候别数项数,直接看最大的那个指数——它决定阶数。 在 MT5 里用多项式拟合汇率序列时,阶数选太高容易过拟合历史 K 线,选太低又抓不到拐点。外汇与贵金属波动高、跳空频繁,高阶拟合在历史样本上 R² 再高也只代表过去,实盘仍属高风险,参数要自己回测验证。
把线性回归升到 k 阶
多项式回归本质是一族可升阶的线性模型。最基础的一阶形式就是普通线性回归 y = β₀ + β₁x,它并无特殊,只是多项式阶数为 1 的特例。 当我们把自变量升到二次,得到 y = β₀ + β₁x + β₂x²,这就是二阶多项式回归。继续往上推,k 阶形式为 y = β₀ + β₁x + … + βₖxᵏ。 这里容易混淆:式子出现了 x²、xᵏ 这类非线性项,模型还算线性吗?答案是算。线性度约束的是系数 β,而不是自变量。β 之间仍是一次相加,x 升到多少幂都只是数据变换,拟合时依然走最小二乘的线性求解。 在 MT5 里验证这一点很简单:用 iCustom 或自行算 x 的各阶幂序列,再丢进线性回归函数,看残差结构即可。外汇与贵金属波动高、过拟合风险大,阶数越高越要警惕样本外失效。
「多项式阶数该怎么定」
线性模型在拟合非线性关系时经常失灵。以苹果股价与纳斯达克指数为例,前者是后者最大的权重成分之一,但二者同轴散点图明显不是一条直线,直接套用线性回归得到的信任度很低,据此做预测决策风险偏大。 解决思路是上多项式回归:把一个自变量升到任意次幂来逼近弯曲的轨迹。问题随之而来——阶数取几?阶太低欠拟合,阶太高又容易过拟合,尤其在外汇和贵金属这种高噪声市场,过拟合模型实盘大概率失效。 判断阶数前先认识贝叶斯信息准则(BIC)。它惩罚模型复杂度,BIC 值越小,模型在有限样本下的解释力越可信。逐阶跑多项式、比 BIC,是比肉眼瞅散点更硬的办法。 下面这段 MT5 函数用 CGraphics 在终端直接画散点图,把两个品种的向量塞进去就能看同轴分布。注意图形区域建在 0 号图表、左上偏移 (30,70)、宽高 440×320,点径设 10、填充开启。 别把正态当圣经 散点看着弯不代表多项式一定胜出,BIC 对比完若 2 阶与 3 阶差不到 2 点,优先选低阶,贵金属隔夜跳空会让高阶项变成噪声放大器。
class="type">bool ScatterPlot( class="type">class="kw">string obj_name, vector &x, vector &y, class="type">class="kw">string legend, class="type">class="kw">string x_axis_label = "x-axis", class="type">class="kw">string y_axis_label = "y-axis", class="type">class="kw">color clr = clrDodgerBlue, class="type">bool points_fill = true ) { if (!graph.Create(class="num">0,obj_name,class="num">0,class="num">30,class="num">70,class="num">440,class="num">320)) { printf("Failed to Create graphical object on the Main chart Err = %d",GetLastError()); class="kw">return(class="kw">false); } ChartSetInteger(class="num">0,CHART_SHOW,ChartShow); class="type">class="kw">double x_arr[], y_arr[]; pol_reg.vectortoArray(x,x_arr); pol_reg.vectortoArray(y,y_arr); CCurve *curve = graph.CurveAdd(x_arr,y_arr,clr,CURVE_POINTS); curve.PointsSize(class="num">10); curve.PointsFill(points_fill); curve.Name(legend); graph.XAxis().Name(x_axis_label); graph.XAxis().NameSize(class="num">10); graph.YAxis().Name(y_axis_label); graph.YAxis().NameSize(class="num">10); graph.FontSet("Lucida Console",class="num">10); graph.CurvePlotAll(); graph.Update(); class="kw">delete(curve); class="kw">return(true); } class="type">class="kw">string plot_name = "x vs y"; ObjectDelete(class="num">0,plot_name); ScatterPlot(plot_name,x_v,y_v,X_symbol,X_symbol,Y_symol,clrOrange);