预测时间序列(第 2 部分):最小二乘支持向量机(LS-SVM)·综合运用
◍ 用 LS-SVM 给 XAUUSD 做日线预测的开端
LSSVMbot 这套 EA 在 MT5 里分两种跑法:虚拟模式只解算 LS-SVM 的 Gamma、Sigma 参数不做交易;交易模式才真正下单,也可顺带优化其他参数。虚拟模式下挂两个 LSSVM 实例,一个吃训练集、一个吃测试集,靠自定义条件挑参数。 Gamma 和 Sigma 不能直接在测试器里细扫,因为标准优化器只认固定步长。EA 改用 GammaIndex、SigmaIndex 当迭代器:真实值 = 基础值 ×(步长乘数 ^ 迭代次数)。举例,Gamma 基础 1、GammaStep 2,GammaIndex 跑 0–5,算法就测 1、2、4、8、16、32 这六档。 我们拿 XAUUSD 的 D1 实测:训练向量 VectorNumber=200(略少于一年,设 1000 会压垮方程组求解),测试向量 VectorNumber2=50,向量长度 VectorSize=20 即一个月,KernelNumber=0 不用 SOM,DifferencingOrder=0。交易阶段发现预测相对价格有微延迟,就把 StepsAhead 设 2 提前两步。 基础 Gamma=Sigma=1,乘数 GammaStep=SigmaStep=2,GammaIndex 扫 5–35、SigmaIndex 扫 5–20、步长 5。GammaIndex=15 时 Gamma 实算为 1×2^15 = 32768。2018 年起用开盘价、Estimator=R2 做虚拟优化,跑完按 R2 降序排,首条日志显示可用柱线不足:2018.01.02 只有 247 根,差 270 根,拖到 02.02 才够,训练历史从 2017.01.03 补起,R2 到 0.95985。 外汇与贵金属波动剧烈、杠杆高风险大,XAUUSD 虽少受单一国家消息冲击,但 LS-SVM 参数在长周期拟合后,短交易期内是否仍相关只能靠概率验证,不是必然。
enum CUSTOM_ESTIMATOR { RMSE, class=class="str">"cmt">// RMSE CC, class=class="str">"cmt">// correlation R2, class=class="str">"cmt">// R-squared PCT, class=class="str">"cmt">// % TRADING class=class="str">"cmt">// trading }; class="kw">input class="type">int _VectorNumber = class="num">250; class=class="str">"cmt">// VectorNumber(training) class="kw">input class="type">int _VectorNumber2 = class="num">25; class=class="str">"cmt">// VectorNumber(validating) class="kw">input class="type">int _VectorSize = class="num">20; class=class="str">"cmt">// VectorSize class="kw">input class="type">class="kw">double _Gamma = class="num">0; class=class="str">"cmt">// Gamma(class="num">0 - auto) class="kw">input class="type">class="kw">double _Sigma = class="num">0; class=class="str">"cmt">// Sigma(class="num">0 - auto) class="kw">input class="type">int _KernelNumber = class="num">0; class=class="str">"cmt">// KernelNumber(sqrt, class="num">0 - auto) class="kw">input class="type">int DifferencingOrder = class="num">1; class="kw">input class="type">int StepsAhead = class="num">0; class="kw">input class="type">int _GammaIndex = class="num">0; class=class="str">"cmt">// Gamma Power Iterator class="kw">input class="type">int _SigmaIndex = class="num">0; class=class="str">"cmt">// Sigma Power Iterator class="kw">input class="type">class="kw">double _GammaStep = class="num">0; class=class="str">"cmt">// Gamma Power Multiplier(class="num">0 - off) class="kw">input class="type">class="kw">double _SigmaStep = class="num">0; class=class="str">"cmt">// Sigma Power Multiplier(class="num">0 - off) class="kw">input CUSTOM_ESTIMATOR Estimator = R2; class="type">bool optimize() { if(Estimator != TRADING) lssvm.bindCrossValidator(test); iterate(_GammaIndex, _GammaStep, _SigmaIndex, _SigmaStep); class="type">bool success = lssvm.process();
「LSSVM 回测日志与多步预测下单逻辑」
上面这段 MT5 日志来自一次 2017.01.03–2018.02.02 的 LSSVM 优化回测,样本需 270 根 K 线。最优参数落在 G[15]=32768.0、S[5]=32.0,训练集 RMSE 0.21461、测试集 RMSE 0.26944,CC 分别为 0.97266 与 0.97985,测试集 R2 达 0.95985,PCT 命中率 96%,最终余额 10000 USD,OnTester 返回 0.95985。
代码里 OnTester() 直接把 customResult 交回优化器:若评估指标选 CC 取测试集 CC[1],选 RMSE 则取负号(误差越小越好),选 PCT 取命中率,否则取 R2。这样框架就能在参数扫描时自动挑出泛化最好的 Gamma/Sigma 组合。
预测段用 buildVector 取最新价向量,normalizeVector 后循环 StepsAhead 次:每次把输出数组左移、末位填入上一步预测值 z,再喂回 lssvm.approximate 做滚动多步外推。最后 denormalize 还原量纲,按 DifferencingOrder 0~3 分别用 open[2]、open[1]、open[0] 做差分还原得到 target。
下单判断只看 target 与当前开盘价 open[2] 的大小:target>=open[2] 记 mode=+1 倾向做多,否则 -1。若持仓方向 dir 与 mode 异号就先平再反手,用 SymbolInfoDouble 取实时ASK/BID发 OrderSend。外汇与贵金属杠杆高,信号仅代表模型倾向,实盘前务必在策略测试器用真实点差重跑。
if(success) { LSSVM::LSSVM_Error result; lssvm.checkAll(result); Print("Parameters: ", lssvm.getGamma(), " ", lssvm.getSigma()); Print(" training: ", result.RMSE[class="num">0], " ", result.CC[class="num">0], " ", result.R2[class="num">0], " ", result.PCT[class="num">0]); Print(" test: ", result.RMSE[class="num">1], " ", result.CC[class="num">1], " ", result.R2[class="num">1], " ", result.PCT[class="num">1]); customResult = Estimator == CC ? result.CC[class="num">1] : (Estimator == RMSE ? -result.RMSE[class="num">1] class=class="str">"cmt">// the lesser |absolute error value| the better : (Estimator == PCT ? result.PCT[class="num">1] : result.R2[class="num">1])); } class="kw">return success; } class="type">void OnTick() { ... if(Estimator != TRADING) { if(!processed) { processed = optimize(); } } ... } class="type">class="kw">double OnTester() { class="kw">return processed ? customResult : -class="num">1; } class="kw">static class="type">bool solved = class="kw">false; if(!solved) { class="kw">const class="type">bool opt = (class="type">bool)MQLInfoInteger(MQL_OPTIMIZATION) || (_GammaStep != class="num">0 && _SigmaStep != class="num">0); solved = opt ? optimize() : lssvm.process(); } if(solved) { class=class="str">"cmt">// test is used to read latest _VectorNumber2 prices if(!test.buildXYVectors()) { Print("No vectors"); class="kw">return; } test.normalizeXYVectors(); class="type">class="kw">double out[]; class=class="str">"cmt">// read latest vector if(!test.buildVector(out)) { Print("No last price"); class="kw">return; } test.normalizeVector(out); class="type">class="kw">double z = lssvm.approximate(out); for(class="type">int i = class="num">0; i < StepsAhead; i++) { ArrayCopy(out, out, class="num">0, class="num">1); out[ArraySize(out) - class="num">1] = z; z = lssvm.approximate(out); } z = test.denormalize(z); class="type">class="kw">double open[]; if(class="num">3 == CopyOpen(_Symbol, _Period, class="num">0, class="num">3, open)) class=class="str">"cmt">// open[class="num">1] - previous, open[class="num">2] - current { class="type">class="kw">double target = class="num">0; if(DifferencingOrder == class="num">0) { target = z; } else if(DifferencingOrder == class="num">1) { target = open[class="num">2] + z; } else if(DifferencingOrder == class="num">2) { target = class="num">2 * open[class="num">2] - open[class="num">1] + z; } else if(DifferencingOrder == class="num">3) { target = class="num">3 * open[class="num">2] - class="num">3 * open[class="num">1] + open[class="num">0] + z; } else { class=class="str">"cmt">// unsupported yet } class="type">int mode = target >= open[class="num">2] ? +class="num">1 : -class="num">1; class="type">int dir = CurrentOrderDirection(); if(dir * mode <= class="num">0) { if(dir != class="num">0) class=class="str">"cmt">// there is an order { OrdersCloseAll(); } if(mode != class="num">0) { class="kw">const class="type">int type = mode > class="num">0 ? OP_BUY : OP_SELL; class="kw">const class="type">class="kw">double p = type == OP_BUY ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); OrderSend(_Symbol, type, Lot, p, class="num">100, class="num">0, class="num">0); } } } }
把 LS-SVM 预测塞进自己的 MQL 项目
前面两篇把 EMD 分解和 LS-SVM 拟合的链路跑通了,落到实盘前先认清一个约束:外汇货币对的可预测性偏弱,受外部冲击大,仅靠历史报价做预测,效率会明显打折。回测和实盘里更稳的载体通常是贵金属、指数或均衡资产篮子,这类序列的节律和算法假设更贴。 算法能力再强,选对行情和选对参数同等重要。资源密集的计算不是目的,能对应到具体时间帧和品种特性才算用对了。ZIP 包里的 MQL5SVM 源码(46.52 KB)可直接嵌进你自己的方法里,但注意新版本 MQL5 引入了本地 vector 类型后,原示例有评论指出编译会报错,移植时要改容器声明。 外汇和贵金属都是高杠杆高风险品种,任何预测都只是概率倾向,别省掉保护性止损和消息面监控。代码能跑通不代表账户能活,风控永远在模型之外。