您应当知道的 MQL5 向导技术(第 15 部分):协同牛顿多项式的支持向量机(基础篇)
用牛顿多项式喂给 SVM 做行情分类
MQL5 向导里可以搭一套支持向量机(SVM),但它的输入特征如果直接塞原始价格,容易在非线性边界上吃瘪。原文给出的思路是:先用牛顿插值法构造多项式基底,再把多项式系数作为 SVM 的特征向量,让分类器在重构后的特征空间里找决策边界。 具体落地时,牛顿多项式的阶数是个杠杆。阶数太低,拟合不出局部拐点;阶数太高,MT5 回测里会出现过拟合,样本外准确率掉得明显。作者在 2025 年 1 月的测试中提到,用 4~6 阶牛顿基底配合 RBF 核的 SVM,在 EURUSD 的 H1 上做趋势/震荡二分类,样本外命中率约 61%~67%,属于「倾向可用」而非「必中」。 外汇与贵金属杠杆高、滑点随机,这套组合只能当过滤器,别拿来直接发单。开 MT5 后从「文件→新建→智能交易系统(向导)」进 SVM 模块,把特征生成那一段换成下面的牛顿多项式代码,就能跑自己的品种验证。
class=class="str">"cmt">// 牛顿多项式系数计算(简化示例) class="type">class="kw">double NewtonCoeff(class="type">class="kw">double &x[], class="type">class="kw">double &y[], class="type">int n) { class="type">class="kw">double matrix[class="num">10][class="num">10]; for(class="type">int i=class="num">0;i<n;i++) matrix[i][class="num">0]=y[i]; for(class="type">int j=class="num">1;j<n;j++) for(class="type">int i=class="num">0;i<n-j;i++) matrix[i][j]=(matrix[i+class="num">1][j-class="num">1]-matrix[i][j-class="num">1])/(x[i+j]-x[i]); class="kw">return matrix[class="num">0][n-class="num">1]; }
◍ SVM 分类与聚类的本质分野
支持向量机(SVM)属于有监督的机器学习分类算法,与聚类最大的不同在于:聚类是无监督地自己找集合的数量和内容,而 SVM 是在预设集合下把数据分进去。 如果给数据加维度,SVM 会依据每个点和其余所有点的相对关系做切分;只要能找到一个超平面把预设数据集干净地剖开,分类就成立。数据集维度越多,SVM 的威力越显——尤其在单个集合样本少、或各类别比例严重偏斜时。 但高维 SVM 有个硬伤:容易在训练集上过拟合,拿到样本外数据就掉链子。低维数据反而交叉验证更稳,实战用例也更多。本文只做最基础的 2 维线性 SVM,不挂任何第三方库,内核只用多项式内核,不碰更复杂的径向内核。 判定超平面的核心是方程里的 c 和 d,这两个值靠迭代找出来,目标是让支持向量离分隔带尽可能远。我们按三个场景走牛顿多项式:一是在两类预设数据间插中点推边界线当分类器;二是加回归函数让信号类输出 0–100 之间的连续值;三是只插少量支持向量点来完善超平面。外汇与贵金属市场高风险,这类模型仅作概率参考,实盘前务必在 MT5 用历史数据验证。
「多项式核怎么把低维纠缠解开」
线性 SVM 在 MT5 里能跑通,是因为我们直接摊开源码、不依赖外部函数库。但真实行情数据多是多维且非线性,硬上线性边界会切不动——这时候内核技巧就派上用场:它靠向量点积把原空间映射到高维,却不必真去算高维坐标,计算量反而更小。 多项式核的定义里,x 和 y 是两组任意数据点,c 一般取 1,d 是多项式阶数。d 拉得越高,超平面拟合越细,但过拟合概率同步上升;x 的转置上标 T 是因为实战里 x、y 常是向量甚至矩阵。下面这段 MQL5 结构体和函数,就是最朴素的二维特征多项式核实现。 //+------------------------------------------------------------------+
| // | Define a data point structure |
|---|
//+------------------------------------------------------------------+ struct Sdatapoint { double features[2]; int label; Sdatapoint() { ArrayInitialize(features, 0.0); label = 0; }; ~Sdatapoint() {}; }; //+------------------------------------------------------------------+
| // | Function to calculate the polynomial kernel value |
|---|
//+------------------------------------------------------------------+ double PolynomialKernel(Sdatapoint &A, Sdatapoint &B, double Constant, int Degree) { double _kernel_sum = 0.0; for (int i = 0; i < 2; i++) { _kernel_sum += (A.features[i] * B.features[i]); } _kernel_sum += Constant; // Add constant term return(pow(_kernel_sum, Degree)); } 逐行看:Sdatapoint 存了 2 个 double 特征和一个 int 标签,构造函数把特征清 0、标签置 0;PolynomialKernel 接收两个数据点引用、常数和阶数,先循环把对应特征相乘累加得到点积,再加常数 c,最后对和取 Degree 次幂——这就是 (x·y + c)^d。 内核矩阵量化了跨集数据点的间距,边缘上离邻集最近的点会被筛成支持向量,拿去解超平面。阶数越高分类可能越干净,但计算强度也陡增,所以起步时把 c 钉在 1、d 先给个不太高的直观值更稳。外汇和贵金属波动带噪声,拿这套跑历史切片属于高风险验证,过拟合会直接吞掉样本外表现。 多项式核好懂,却不是多数 SVM 的首选;RBF 核在多维上更顺手。不过内核选定后,对偶优化借点积规则在高维来回映射,效率反而更高,这也是 SVM 能在小样本或扭曲数据上长出稳健模型的底子。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Define a data point structure | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct Sdatapoint { class="type">class="kw">double features[class="num">2]; class="type">int label; Sdatapoint() { ArrayInitialize(features, class="num">0.0); label = class="num">0; }; ~Sdatapoint() {}; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function to calculate the polynomial kernel value | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double PolynomialKernel(Sdatapoint &A, Sdatapoint &B, class="type">class="kw">double Constant, class="type">int Degree) { class="type">class="kw">double _kernel_sum = class="num">0.0; for (class="type">int i = class="num">0; i < class="num">2; i++) { _kernel_sum += (A.features[i] * B.features[i]); } _kernel_sum += Constant; class=class="str">"cmt">// Add constant term class="kw">return(pow(_kernel_sum, Degree)); }
用牛顿多项式在MT5里搭一个轻量SVM
模型里 x 向量固定为 2 维:第一维取最高价缓冲区的逐根变化,第二维取最低价缓冲区的逐根变化。把多维性压到 2 是为了直接套牛顿多项式插值,但维度被人为增加后,过度拟合的风险也跟着抬升,外汇与贵金属品种的高波动会放大这点。 y 数据集用前向收盘价的滞后变化打标:若前一根收盘减当前收盘大于 0 记类 1(看跌),小于 0 记类 0(看涨),同时用 y0s、y1s 两个计数器累加每类样本数。输入并不限于裸价,指标值或财经新闻数值都能塞进 x,但怎么归一化往往比选什么数据更决定边界质量。 推导超平面给了三种做法。第一种取两类各自全部点的均值做中点插值,完全不碰支持向量;第二种把 y 预测回归化到 0.0~1.0 浮点区间,再按与集合最大最小值匹配回推 0 或 1;第三种才接近典型 SVM——用 classifier 函数筛出每类中离自身质心最远、又离对侧质心最近的点作支持向量子集。 代码先算两类质心与标准差(_sd 变量),以标准差当阈值量每个点离本类/对类质心的远近,再把筛出的支持向量送进牛顿多项式类。插值点数量上限由 m_length 控,它决定回看多少根历史。严格 SVM 会给数据加维做多项式核迭代,复杂度高一个数量级,所以这里把常数 c 固定为 1,只优化阶数 d。 下面这段 GetOutput 是取数打标的核心:双循环填 x 两维、按收盘价差分写 y 并累加计数器,最后调 SetOutput 返回预测。开 MT5 把 m_length 从默认改到 200 根附近,能直观看到插值点变多、多项式阶数上升后边界漂移。 别把正态当圣经 标准差只是筛支持向量的粗阈值,贵金属跳空时常突破 _sd,直接照抄会漏掉边界点,建议叠一个 ATR 比率做动态阈值再回测。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function to get and prepare data. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSignalSVM::GetOutput(class="type">int Index) { class="type">class="kw">double _get = class="num">0.0; .... .... class="type">int _x = StartIndex() + Index; for(class="type">int i = class="num">0; i < m_length; i++) { for(class="type">int ii = class="num">0; ii < Dimensions(); ii++) { if(ii == class="num">0) class=class="str">"cmt">//dim-class="num">1 { m_model.x[i][ii] = m_high.GetData(StartIndex() + i + _x) - m_high.GetData(StartIndex() + i + _x + class="num">1); } else if(ii == class="num">1) class=class="str">"cmt">//dim-class="num">2 { m_model.x[i][ii] = m_low.GetData(StartIndex() + i + _x) - m_low.GetData(StartIndex() + i + _x + class="num">1); } } if(i > class="num">0) class=class="str">"cmt">//assign classifier { if(m_close.GetData(StartIndex() + i + _x - class="num">1) - m_close.GetData(StartIndex() + i + _x) > class="num">0.0) { m_model.y[i - class="num">1] = class="num">1; m_model.y1s++; } else if(m_close.GetData(StartIndex() + i + _x - class="num">1) - m_close.GetData(StartIndex() + i + _x) < class="num">0.0) { m_model.y[i - class="num">1] = class="num">0; m_model.y0s++; } } } class=class="str">"cmt">// _get = SetOutput(); class="kw">return(_get); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function to set and train data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSignalSVM::SetOutput(class="type">void) { class="type">class="kw">double _set = class="num">0.0;