卡尔曼滤波器在外汇均值回归策略中的应用(基础篇)
「用卡尔曼滤波抓外汇均值回归」
卡尔曼滤波器能把带噪的价格序列实时估出“真实”状态,在 MT5 里适合做均值回归类的动态中枢。对外汇与贵金属而言,这类品种高杠杆、跳空频繁,均值回归只是概率倾向,不等于价格必回。 原文给出的示例发布于 2025 年 11 月 10 日 15:04,截至统计口径有 1072 次查看、6 条讨论,说明该思路在实战圈有一定验证需求。 想在 MT5 自己跑一遍,核心是先建状态方程再用预测—修正两步迭代;下一节会直接给可粘贴的代码与逐行拆解。
◍ 卡尔曼滤波在报价噪声里的真实用途
卡尔曼滤波器本质是一个递归估计算法,核心动作是拿上一刻的状态预测值和当前新到的报价做加权融合,把高频噪声压下去,留下它认为的“真实状态”。对做价格行为的人来说,它比固定窗口的平滑工具更顺手的地方在于:权重会随着数据波动自动调节,不需要你手动改窗口长度。 在外汇和贵金属这种报价抖动极凶的市场里,它常被塞进均值回归类策略——因为价差回归的触发点往往被噪声淹没,滤波后的序列能更早暴露偏离。注意,杠杆品种波动具有高风险,滤波只降低观测误差,不消除方向误判概率。 本篇后续会先拆它的计算步骤与 MT5 实现,再用一个经典外汇均值回归例子跑一遍;最后把卡尔曼滤波和移动平均线放在若干货币对上做统计对比,看两者在噪声环境下的跟踪差异。
把卡尔曼滤波当会自适应的均线用
卡尔曼滤波是1960年提出的最优递归估计器,两步跑:先预测系统下一状态,再用新观测值更新、压低噪声。放到MT5里,你可以把它直接类比成一条会自己调权重的移动平均线——但比MA更狠的是,它按测量可信度实时改比例,而不是固定周期取平均。 在外汇和贵金属这种高杠杆、高波动市场里用,先记一句:它假设价格动态是线性的,突变会滞后,参数没调好反而比EMA还难用,属于高风险玩法。 常见落点有四个:均值回归里拿估计价当入场筛子;配对交易动态估价差调对冲比;趋势跟踪滤短噪抓长趋;波动率自适应估出来管仓位。 过程方差Q和测量方差R决定平滑度。Q高=信价格会变,灵敏但晃;R高=信旧估,平滑但慢。低Q+适中R稳,高Q+低R敏但噪。开MT5把Q从0.001调到0.1,曲线会从迟钝变抖,自己肉眼校一遍最实在。
「把均值回归写成可跑的 EA」
均值回归在半平稳的外汇数据上多年保持正期望,核心假设是价格不会长期停留在极端位。本策略用 100 周期、偏差 2.0 的布林带量化:上根收盘破下轨买、破上轨卖,价格穿中轨即平,同刻只留一个仓位,止损设为市价 1% 以挡肥尾。
- 分钟框被选中,是因它兼顾信号质量与触发频率。编码次序先写买卖函数,再初始化 magic 与指标句柄,最后在 OnTick 用每根 K 线触发逻辑,避免 tick 级重复开仓。
卡尔曼滤波作为反转确认:价格破下轨且高于滤波估值,才视为超卖企稳;500-EMA 则只用于顺趋势过滤。要换状态过滤器,改最终买卖判定那几行即可。外汇与贵金属杠杆高,回测通过不等于实盘可控,务必先在策略测试器跑图形验证。
class="macro">#include <Trade/Trade.mqh> CTrade trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Buy Function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void executeBuy(class="type">class="kw">string symbol) { class="type">class="kw">double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); class="type">class="kw">double lots=class="num">0.01; class="type">class="kw">double sl = ask*(class="num">1-class="num">0.01); trade.Buy(lots,symbol,ask,sl); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Sell Function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void executeSell(class="type">class="kw">string symbol) { class="type">class="kw">double bid = SymbolInfoDouble(symbol, SYMBOL_BID); class="type">class="kw">double lots=class="num">0.01; class="type">class="kw">double sl = bid*(class="num">1+class="num">0.01); trade.Sell(lots,symbol,bid,sl); } input class="type">int Magic = class="num">0; input class="type">int bbPeriod = class="num">100; input class="type">class="kw">double d = class="num">2.0; class="type">int barsTotal = class="num">0; class="type">int handleMa; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialization | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { handleBb = iBands(_Symbol,PERIOD_CURRENT,bbPeriod,class="num">0,d,PRICE_CLOSE); trade.SetExpertMagicNumber(Magic); class="kw">return INIT_SUCCEEDED; } class="type">int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; class="type">class="kw">double bbLower[], bbUpper[], bbMiddle[]; CopyBuffer(handleBb,UPPER_BAND,class="num">1,class="num">1,bbUpper); CopyBuffer(handleBb,LOWER_BAND,class="num">1,class="num">1,bbLower); CopyBuffer(handleBb,class="num">0,class="num">1,class="num">1,bbMiddle); class="type">bool NotInPosition = true;