您应当知道的 MQL5 向导技术(第 15 部分):协同牛顿多项式的支持向量机·进阶篇
「SVM 信号里的插值与时序判定」
这段逻辑先把两个矩阵 _a、_b 交给 Classifier 做支撑向量归类,只有两者行数乘积大于 0 才继续,避免空集合进入计算。随后用 _a.Rows() * _b.Rows() 作为新矩阵行数,把两组坐标按 0.5 加权做线性插值,生成 _interpolate 供后续牛顿法拟合。 插值矩阵拆出第 0 列给 _x、第 1 列给 _y,权重向量 _w 长度设为 m_model.y0s * m_model.y1s,并强制 _w[0]=_y[0] 做初值锚定。m_newton 用这组输入输出训练后,在 m_model.x[0][0] 处求 _zz,若 _yy 小于 _zz 则 _set 给 100.0,反之给 -100.0,方向判定依赖曲面在该点的相对位置。 Regressor 函数用 fmax/fmin 框出 X、Y 与待测点 XX、YY 的极值区间,返回值是两个维度归一偏离度的均值:当坐标越靠近历史极值边界,回归系数越接近 1;若区间极差小于 Point(),分母用 Point() 兜底防除零。最终 _set 乘上该系数,意味着外汇与贵金属这类高波动品种里,信号强度会随价格逼近关键边界而放大,但仅代表概率倾向,实盘需自担高风险。 Classifier 内部先判断 m_model.y0s * m_model.y1s > 0 才建 1 行质心矩阵,再按 m_length 遍历标签为 0 的样本累积,说明正负样本未混类时才做质心提取,否则支撑向量集可能失效。
matrix _a,_b; Classifier(_a,_b); if(_a.Rows() * _b.Rows() > class="num">0) { matrix _interpolate; _interpolate.Init(_a.Rows() * _b.Rows(), Dimensions()); for(class="type">int i = class="num">0; i < class="type">int(_a.Rows()); i++) { for(class="type">int ii = class="num">0; ii < class="type">int(_b.Rows()); ii++) { _interpolate[(i*_b.Rows())+ii][class="num">0] = class="num">0.5 * (_a[i][class="num">0] + _b[ii][class="num">0]); _interpolate[(i*_b.Rows())+ii][class="num">1] = class="num">0.5 * (_a[i][class="num">1] + _b[ii][class="num">1]); } } vector _w; vector _x = _interpolate.Col(class="num">0); vector _y = _interpolate.Col(class="num">1); _w.Init(m_model.y0s * m_model.y1s); _w[class="num">0] = _y[class="num">0]; m_newton.Set(_w, _x, _y); class="type">class="kw">double _xx = m_model.x[class="num">0][class="num">0], _yy = m_model.x[class="num">0][class="num">1], _zz = class="num">0.0; m_newton.Get(_w, _xx, _zz); if(_yy < _zz) { _set = class="num">100.0; } else if(_yy > _zz) { _set = -class="num">100.0; } _set *= Regressor(_x, _y, _xx, _yy); } class="kw">return(_set); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Regressor for the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSignalSVM::Regressor(vector &X, vector &Y, class="type">class="kw">double XX, class="type">class="kw">double YY) { class="type">class="kw">double _x_max = fmax(X.Max(), XX); class="type">class="kw">double _x_min = fmin(X.Min(), XX); class="type">class="kw">double _y_max = fmax(Y.Max(), YY); class="type">class="kw">double _y_min = fmin(Y.Min(), YY); class="kw">return(class="num">0.5 * ((class="num">1.0 - ((_x_max - XX) / fmax(m_symbol.Point(), _x_max - _x_min))) + (class="num">1.0 - ((_y_max - YY) / fmax(m_symbol.Point(), _y_max - _y_min))))); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;Classifier&class="macro">#x27; for the model that identifies Support Vector points | class=class="str">"cmt">//| for each set. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSignalSVM::Classifier(matrix &A, matrix &B) { if(m_model.y0s * m_model.y1s > class="num">0) { matrix _a_centroid, _b_centroid; _a_centroid.Init(class="num">1, Dimensions()); _b_centroid.Init(class="num">1, Dimensions()); for(class="type">int i = class="num">0; i < m_length; i++) { if(m_model.y[i] == class="num">0)
两类样本的中心距与交叉离散度
上面这段逻辑干的事很直接:先按标签 y 把样本拆成 0 类和 1 类,各自累加特征 x[0]、x[1] 求总和,再除以样本数 y0s、y1s 得到两个类别的质心坐标。_a_centroid 对应 0 类中心,_b_centroid 对应 1 类中心,都是二维点。 质心出来后并没有完,紧接着算四个离散度:_a_sd 是 0 类样本到自身质心的平均欧氏距离,_b_sd 是 1 类到自身质心的平均距离;而 _ab_sd 是 0 类样本误算到 1 类质心的平均距离,_ba_sd 反过来。这四个值除以各自样本数后,等于把类内紧度和类间混淆度都量化了。 在 MT5 里把这段塞进训练函数,跑完打印四个 double,你能直接看到两类是否分得开。若 _a_sd 和 _b_sd 明显小于 _ab_sd、_ba_sd,说明特征空间里两类聚得紧、互相渗透少,后续分类器才可能给出较高概率的判别。外汇与贵金属行情受杠杆与跳空影响,这类统计距离只反映历史样本结构,实盘信号失效风险高。
{ _a_centroid[class="num">0][class="num">0] += m_model.x[i][class="num">0];
_a_centroid[class="num">0][class="num">1] += m_model.x[i][class="num">1];
}
else if(m_model.y[i] == class="num">1)
{ _b_centroid[class="num">0][class="num">0] += m_model.x[i][class="num">0];
_b_centroid[class="num">0][class="num">1] += m_model.x[i][class="num">1];
}
}
_a_centroid[class="num">0][class="num">0] /= m_model.y0s;
_a_centroid[class="num">0][class="num">1] /= m_model.y0s;
_b_centroid[class="num">0][class="num">0] /= m_model.y1s;
_b_centroid[class="num">0][class="num">1] /= m_model.y1s;
class="type">class="kw">double _a_sd = class="num">0.0, _b_sd = class="num">0.0;
class="type">class="kw">double _ab_sd = class="num">0.0, _ba_sd = class="num">0.0;
for(class="type">int i = class="num">0; i < m_length; i++)
{ if(m_model.y[i] == class="num">0)
{ class="type">class="kw">double _0 = class="num">0.0;
_0 += pow(_a_centroid[class="num">0][class="num">0] - m_model.x[i][class="num">0], class="num">2.0);
_0 += pow(_a_centroid[class="num">0][class="num">1] - m_model.x[i][class="num">1], class="num">2.0);
_a_sd += sqrt(_0);
class="type">class="kw">double _1 = class="num">0.0;
_1 += pow(_b_centroid[class="num">0][class="num">0] - m_model.x[i][class="num">0], class="num">2.0);
_1 += pow(_b_centroid[class="num">0][class="num">1] - m_model.x[i][class="num">1], class="num">2.0);
_ab_sd += sqrt(_1);
}
else if(m_model.y[i] == class="num">1)
{ class="type">class="kw">double _1 = class="num">0.0;
_1 += pow(_b_centroid[class="num">0][class="num">0] - m_model.x[i][class="num">0], class="num">2.0);
_1 += pow(_b_centroid[class="num">0][class="num">1] - m_model.x[i][class="num">1], class="num">2.0);
_b_sd += sqrt(_1);
class="type">class="kw">double _0 = class="num">0.0;
_0 += pow(_a_centroid[class="num">0][class="num">0] - m_model.x[i][class="num">0], class="num">2.0);
_0 += pow(_a_centroid[class="num">0][class="num">1] - m_model.x[i][class="num">1], class="num">2.0);
_ba_sd += sqrt(_0);
}
}
_a_sd /= m_model.y0s;
_ab_sd /= m_model.y0s;
_b_sd /= m_model.y1s;
_ba_sd /= m_model.y1s;◍ 两类样本的离群点筛分逻辑
这段循环干的事很直接:遍历训练集 m_length 个样本,按标签 y[i] 是 0 还是 1 分别算它到两个质心(_a_centroid、_b_centroid)的欧氏距离平方,再开方比阈值。 对标签为 0 的点,先累加横纵两维与 A 类质心的差的平方得 _0,再算与 B 类质心的差的平方得 _1。只有当 sqrt(_0) ≥ _a_sd 且 _ab_sd ≤ sqrt(_1) 时,才把该点塞进矩阵 A——意味着它离自己类质心够远、又足够靠近对方类边界,倾向被判定为 A 类离群候选。 标签为 1 的分支完全对称:算完到 B 质心距离 _1 和到 A 质心距离 _0,满足 sqrt(_1) ≥ _b_sd 且 _ba_sd ≤ sqrt(_0) 才写入 B。两个 Resize 调用每次只加 1 行、列数取 Dimensions(),在 MT5 里跑时注意这种动态扩容对大样本(比如 m_length 过万)会有明显的内存重分配开销。 外汇与贵金属行情里用这类二分类离群筛查,本质是在高噪声序列中找边界模糊样本;实盘前务必用历史 tick 回测确认 _a_sd / _b_sd 取值,错误阈值可能把正常波动误判为信号,杠杆品种风险偏高。
for(class="type">int i = class="num">0; i < m_length; i++) { if(m_model.y[i] == class="num">0) { class="type">class="kw">double _0 = class="num">0.0; _0 += pow(_a_centroid[class="num">0][class="num">0] - m_model.x[i][class="num">0], class="num">2.0); _0 += pow(_a_centroid[class="num">0][class="num">1] - m_model.x[i][class="num">1], class="num">2.0); class="type">class="kw">double _1 = class="num">0.0; _1 += pow(_b_centroid[class="num">0][class="num">0] - m_model.x[i][class="num">0], class="num">2.0); _1 += pow(_b_centroid[class="num">0][class="num">1] - m_model.x[i][class="num">1], class="num">2.0); if(sqrt(_0) >= _a_sd && _ab_sd <= sqrt(_1)) { A.Resize(A.Rows()+class="num">1,Dimensions()); A[A.Rows()-class="num">1][class="num">0] = m_model.x[i][class="num">0]; A[A.Rows()-class="num">1][class="num">1] = m_model.x[i][class="num">1]; } } else if(m_model.y[i] == class="num">1) { class="type">class="kw">double _1 = class="num">0.0; _1 += pow(_b_centroid[class="num">0][class="num">0] - m_model.x[i][class="num">0], class="num">2.0); _1 += pow(_b_centroid[class="num">0][class="num">1] - m_model.x[i][class="num">1], class="num">2.0); class="type">class="kw">double _0 = class="num">0.0; _0 += pow(_a_centroid[class="num">0][class="num">0] - m_model.x[i][class="num">0], class="num">2.0); _0 += pow(_a_centroid[class="num">0][class="num">1] - m_model.x[i][class="num">1], class="num">2.0); if(sqrt(_1) >= _b_sd && _ba_sd <= sqrt(_0)) { B.Resize(B.Rows()+class="num">1,Dimensions()); B[B.Rows()-class="num">1][class="num">0] = m_model.x[i][class="num">0]; B[B.Rows()-class="num">1][class="num">1] = m_model.x[i][class="num">1]; } } } }
「三种 SVM 组装方式的回测对照」
把文末三个信号文件丢进 MQL5 向导,就能拼出一个 EA。前两种做法里,超平面是直接对所有点插值、不筛支持向量,或者只多挂了一层回归;第三种则先在数据集里筛出支持向量,再推导超平面。 测试品种锁 EURJPY、2023 年日线,按真实 tick 跑限价单,没挂止盈止损。三种方式输入参数数量一致,但支持向量法交易次数明显更少,性能却好出一截。 加回归那版只比纯插值略好一点,成交次数几乎没变;真正改变结果的是预先筛支持向量这一步。 看 MT5 报告别只盯净值曲线。我更倾向同时比对平均盈利/平均亏损,以及平均连胜与平均连败的比值——把这些揉进“期望值”指标里,比单纯用盈利除以总交易数(预期收益)更有洞察。三份报告里,支持向量法的期望值量级接近另外两版的 10 倍。外汇与贵金属属高风险,样本仅一年日线,结论需更多前向测试验证。
记住这一条就够了
SVM 靠多项式内核的点积可逆性把高维分类化简,这层“内核技巧”在 MQL5 里若自己写极易卡在计算量上,所以文末附的 Cnewton.mqh(4.14 KB)用牛顿多项式另推超平面,编译时不少读者报过 'Cnewton.mqh not found',需从 ZIP 把库放进 Include\my 才能跑通。 MQL5 向导的增值常被忽略:它能同时组装多个信号类(如 SignalWZ_15.mqh 8.66 KB 与尾随、资金管理类)做初步筛策略,比从零写 EA 更快,外汇与贵金属杠杆品种下这类快速验证仍属高风险,样本外失效概率不低。 把 SVM 多种实现当现有策略的备案或增补,而不是孤立系统,才是这套思路落地的实处——开 MT5 导进那几个 mqh,先跑向导生成的组合,再决定要不要改信号类参数。