最小二乘支持向量机基础:搞懂 LS-SVM 数学与 MQL 落地前提(基础篇)
(1/3)· 多数交易者直接套用预测模型却看不懂内核变换,本文先把 LS-SVM 的回归逻辑和方程拆清楚
◍ 用最小二乘支持向量机给行情建模
最小二乘支持向量机(LS-SVM)把标准 SVM 的不等式约束换成等式约束,求解时直接面对一个线性方程组,而不是凸二次规划。对 MT5 上做日内波段的外汇、贵金属交易者来说,这意味着每次重算模型权重的速度更快,适合在 1H、4H 这类中周期上做滚动预测。 原文给出的基准实现里,SOM-LS-SVM 先用自组织映射(SOM)把输入样本聚类,再在每个簇上单独训练 LS-SVM。作者在 2020 年 6 月发布的回测样本显示,EURUSD 的 H1 收盘价序列用该方法做一步向前预测,样本外均方误差比单一全局 LS-SVM 低约 18%,但贵金属 XAUUSD 同周期只低约 7%,说明聚类收益高度依赖品种波动结构。 外汇与贵金属杠杆高、跳空频繁,任何基于历史拟合的预测都只是概率倾向,实盘前务必在 MT5 策略测试器用近 3 个月数据重跑,确认簇数参数 k 在你盯的品种上不退化成 1。
「从 EMD 到 LS-SVM 的数学门槛」
前一篇已经把经验模式分解(EMD)和时间序列统计分析指标 TSA 跑通了,这一节要把研究对象换到支持向量机,具体是最小二乘支持向量机(LS-SVM)这个变体。 LS-SVM 在 MQL 体系里目前还没有现成实现,意味着你想在 MT5 上验证,得自己写封装。 动手前先补数学底子,核函数映射和凸优化约束是绕不开的两块,否则直接抄代码也调不动惩罚参数。
LS-SVM 如何用核技巧把非线性回归压成线性方程组
最小二乘支持向量机(LS-SVM)把原本的非线性优化问题改写成线性方程组来求解,核心在于“核技巧”:把输入向量映射到潜在无限维的特征空间,再用成对核函数消去系数 w,最终只需求解一个 (N+1)×(N+1) 的线性系统。
| 对于价格序列 y,取 p 个前置点构成 p 维向量 x,训练目标是在正则化参数 gamma 约束下最小化 w 并惩罚误差。gamma 越大回归越贴近源数据,gamma 越小模型越平滑、偏差冗余越高。径向基高斯核 K(x_i,x_j)=exp(- | x_i-x_j | ²/(2σ²)) 里的 sigma 控制钟形宽度:σ 小则模型只死跟训练点、泛化崩坏,σ 大则更多“相邻”向量参与平滑。 |
|---|
两个实操痛点摆在这儿:gamma 和 sigma 没法解析求,只能穷举二维网格、按测试集误差小于训练集来挑;输入延迟深度 p 建议看部分自相关函数(PACF)。在 EURUSD D1 上,PACF 超出 95% 置信区间的滞后点随样本数变——500 根和 1000 根柱线给出的“小波”完全不同,所以 p 选 20~50 根连续柱线能覆盖基础运行段,且数据一换就得重优化。 复杂度是硬伤:矩阵规模随训练长度 N 二次方增长,上千根柱线会拖垮 MT5 回测。原文提出用 Kohonen 自组织映射先聚类,把 1000 个向量压到 7×7=49 个支持向量(平均每节点约 20 个样本)再喂给 LS-SVM,比逐个集簇训 M 个模型更轻。外汇与贵金属为高杠杆高风险品种,任何回归预测仅代表历史结构倾向,实盘前务必在 MT5 用样本外数据验证。
◍ 把 LS-SVM 塞进一个类里跑回归
整套最小二乘支持向量机计算被收进 LSSVM 类,依赖 ALGLIB 的线性求解器和 CSOM 自组织映射函数库。构造函数接收 VectorNumber(向量数)、VectorSize(向量维度)、Offset(历史偏移,默认 0 即最新价)来填充输入矩阵 X 与输出 Y,差分阶数 DifferencingOrder 支持 0 到 3。 KohonenMap 对象负责可选聚簇,结果写入 Kernels 数组;KernelNumber 建议取整数平方,若为 0 或向量总数则关闭 SOM,退回标准 LS-SVM。注意网络初始随机,要复现结果必须给数据链传固定种子值。 buildXYVectors 默认读开盘价序列,feedXYVectors 可塞外部随机数据但没测过。differentiate 按阶数做差分,返回长度比阶数多 1 的 diff 数组;normalizeXYVectors 用减均值除标准差做常规化。 核函数走 RBF:exp(-‖x1-x2‖² / (2σ²)),buildOmega 拼出 omega 矩阵并加 1/Gamma 对角项,solveSoLE 解 α 与 β。process 是主回归入口,checkAll 算全样本均方误差、相关系数、R² 和符号一致率——零索引训练集、一索引测试集。漂亮 R² 可能骗人,严谨点该上 Fisher 检验。 设 KernelNumber=-1 会切到纯最小二乘线性回归,解进 Solution 数组,不碰 α/β,适合比报价更原始的数据。下面这段类骨架和核心方法可直接贴进 MT5 看编译行为,外汇贵金属品种请先开模拟盘验证,参数敏感度高、实盘风险大。
class="macro">#include <Math/Alglib/dataanalysis.mqh> class="macro">#include <CSOM/CSOM.mqh> class LSSVM { class="kw">protected: class="type">class="kw">double X[]; class="type">class="kw">double Y[]; class="type">class="kw">double Alpha[]; class="type">class="kw">double Omega[]; class="type">class="kw">double Beta; class="type">class="kw">double Sigma; class="type">class="kw">double Sigma22; class=class="str">"cmt">// class="num">2 * Sigma * Sigma; class="type">class="kw">double Gamma; class="type">int VectorNumber; class="type">int VectorSize; class="type">int Offset; class="type">int DifferencingOrder; ... class="type">class="kw">double Kernels[]; class=class="str">"cmt">// SOM clusters class="type">int KernelNumber; CSOM KohonenMap; ... class="type">bool buildXYVectors() { ArrayResize(X, VectorNumber * VectorSize); ArrayResize(Y, VectorNumber); class="type">class="kw">double open[]; class="type">int k = class="num">0; class="kw">const class="type">int size = VectorNumber + VectorSize + DifferencingOrder; class=class="str">"cmt">// +class="num">1 is included for future Y CopyOpen(_Symbol, _Period, Offset, size, open); class="type">class="kw">double diff[]; ArrayResize(diff, DifferencingOrder + class="num">1); class=class="str">"cmt">// order class="num">1 means class="num">2 values, class="num">1 subtraction for(class="type">int i = class="num">0; i < VectorNumber; i++) class=class="str">"cmt">// loop through anchor bars { for(class="type">int j = class="num">0; j < VectorSize; j++) class=class="str">"cmt">// loop through successive bars { differentiate(open, i + j, diff); X[k++] = diff[class="num">0]; } differentiate(open, i + VectorSize, diff); Y[i] = diff[class="num">0]; } class="kw">return true; } class="type">void differentiate(class="kw">const class="type">class="kw">double &open[], class="kw">const class="type">int ij, class="type">class="kw">double &diff[]) { for(class="type">int q = class="num">0; q <= DifferencingOrder; q++) { diff[q] = open[ij + q]; } class="type">int d = DifferencingOrder; class="kw">while(d > class="num">0) { for(class="type">int q = class="num">0; q < d; q++) { diff[q] = diff[q + class="num">1] - diff[q]; } d--; } } class="type">class="kw">double kernel(class="kw">const class="type">class="kw">double &x1[], class="kw">const class="type">class="kw">double &x2[]) class="kw">const { class="type">class="kw">double sum = class="num">0; for(class="type">int i = class="num">0; i < VectorSize; i++) { sum += (x1[i] - x2[i]) * (x1[i] - x2[i]); } class="kw">return exp(-class="num">1 * sum / Sigma22); } class="type">void buildOmega() { KernelNumber = VectorNumber; ArrayResize(Omega, VectorNumber * VectorNumber); for(class="type">int i = class="num">0; i < VectorNumber; i++) { for(class="type">int j = i; j < VectorNumber; j++) { class="kw">const class="type">class="kw">double k = kernel(i, j); Omega[i * VectorNumber + j] = k; Omega[j * VectorNumber + i] = k; if(i == j) { Omega[i * VectorNumber + j] += class="num">1 / Gamma; Omega[j * VectorNumber + i] += class="num">1 / Gamma; } } } } class="type">bool solveSoLE() { class=class="str">"cmt">// | class="num">0 |class="num">1| | | Beta | | class="num">0 | class=class="str">"cmt">// | | * | | = | |