预测时间序列(第 2 部分):最小二乘支持向量机(LS-SVM)·综合运用
📘

预测时间序列(第 2 部分):最小二乘支持向量机(LS-SVM)·综合运用

第 3/3 篇

◍ 用 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 参数在长周期拟合后,短交易期内是否仍相关只能靠概率验证,不是必然。

MQL5 / C++
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。若持仓方向 dirmode 异号就先平再反手,用 SymbolInfoDouble 取实时ASK/BID发 OrderSend。外汇与贵金属杠杆高,信号仅代表模型倾向,实盘前务必在策略测试器用真实点差重跑。

MQL5 / C++
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 类型后,原示例有评论指出编译会报错,移植时要改容器声明。 外汇和贵金属都是高杠杆高风险品种,任何预测都只是概率倾向,别省掉保护性止损和消息面监控。代码能跑通不代表账户能活,风控永远在模型之外。

常见问题

先取 XAUUSD 日线收盘价序列做归一化,再用 LS-SVM 训练单步预测,跑通后再扩到多步,别一上来就实盘。
用模型输出未来第 n 根 K 线的方向概率,仅在概率超阈值且回测胜率合格时挂单,外汇贵金属高风险需严控仓位。
可以,小布能读取你的预测输出并标注在对应品种页,把重复诊断交给小布,你只管决策。
重点看多步预测的平均绝对误差和方向与实盘吻合率,误差持续扩大就倾向停用在实盘。
先固定核函数与正则项只调窗口长度,用历史分段验证稳定性,再小资金试,贵金属波动大需防过拟合。