卡尔曼滤波器预测价格方向:基础篇厘清噪声与主趋势分离原理(基础篇)
(1/3)·多数交易者被短期噪音牵着走,却说不清滤波器的预测与校正到底在算什么
◍ 用卡尔曼滤波给价格方向做一步预测
卡尔曼滤波原本是工程里的状态估计器,放到 MT5 里可以当成一种对价格序列的递归平滑与预测工具。它不假设价格服从某种固定分布,而是用上一刻的估计和当前观测值不断修正,输出一条比均线更‘跟手’的方向轨迹。 在 MQL5 里实现单变量卡尔曼预测,核心就是两个矩阵运算:先验估计和更新增益。下面这段是最小可用的预测步代码,跑在 OnCalc 里就能拿到下一根可能的方向。 外汇与贵金属波动带杠杆,卡尔曼给出的只是概率意义上的倾向,不是确定信号,实盘前务必用历史数据验证参数稳定性。
class="type">class="kw">double KalmanPredict(class="type">class="kw">double price, class="type">class="kw">double& x_est, class="type">class="kw">double& p_est, class="type">class="kw">double q, class="type">class="kw">double r) { class=class="str">"cmt">// 先验估计:状态不变模型,预测值等于上次估计 class="type">class="kw">double x_pred = x_est; class=class="str">"cmt">// 先验误差协方差 class="type">class="kw">double p_pred = p_est + q; class=class="str">"cmt">// 卡尔曼增益 class="type">class="kw">double k = p_pred / (p_pred + r); class=class="str">"cmt">// 更新估计值 x_est = x_pred + k * (price - x_pred); class=class="str">"cmt">// 更新误差协方差 p_est = (class="num">1 - k) * p_pred; class=class="str">"cmt">// 返回预测方向(当前估计相对上次的变化) class="kw">return x_est - x_pred; }
用卡尔曼滤波剥离市场噪音
货币与贵金属价格图表里,波动的频率和幅度从来不齐整。短线受消息和流动性扰动,长线才反映多空主力的真实意图。交易者画趋势线或挂指标,本质都是在做同一件事:把主趋势从短期噪音里捞出来。外汇和贵金属杠杆高,噪音诱发的假突破极易扫损,分辨信号真伪是存活前提。 我在这套思路里换用卡尔曼滤波器做分离。数字滤波器在交易里不是新东西,之前用过低通滤波器压高频杂波,但参数钝化后滞后明显。卡尔曼的优势是递推更新、带不确定性加权,对突变走势的跟随比固定窗低通更紧。 下一篇会直接给 MT5 的 MQL5 实现并跑历史数据,你可以先开终端把 EURUSD 的 M5 拉出来,肉眼标一段震荡,等代码贴出后对照滤波输出看噪音压制效果。
「卡尔曼滤波怎么给价格去噪」
卡尔曼滤波器本质是一套处理带噪观测的递推算法,最初为含统计噪声和不完整数据的动态系统设计。价格图表本身就是典型的动态系统,所以它能直接套用:外推(预测)和更新(校正)两步循环,把含噪报价收敛成一条更可信的状态轨迹。 第一步外推,用状态转换模型 F_k 把前一步滤波值 x̂_{k-1} 推到当前预测 x_k,同时外推误差协方差 P_k = F_k P̂_{k-1} F_kᵀ + Q_k。Q_k 是过程噪声矩阵,新闻冲击等未知控制量实际被归进这里。 第二步更新,先算实测 z_k 与预测的差 y_k = z_k - H_k x_k(H_k 常取 1,即报价直接对照预测)。再算误差协方差 S_k = H_k P_k H_kᵀ + R_k,R_k 是报价本身的测量噪声。增益 K_k = P_k H_kᵀ S_k⁻¹ 决定该信模型还是信盘面,最终 x̂_k = x̂_{k-1} + K_k y_k,P̂_k = (I - K_k H_k) P_k。 在 MT5 里验证时,把收盘价当 z_k、H_k=1,用单状态模型跑完一轮,你会看到 K_k 在震荡市偏向 0.2~0.4 区间,平滑后曲线对突刺的滞后明显小于简单移动平均。外汇与贵金属杠杆高、滑点随机,这套滤波只降低噪声不代表方向判断胜率提升,参数 Q_k/R_k 比值要自己回测调。
◍ 把卡尔曼滤波落进 MT5 的收盘价流
卡尔曼滤波器原本为线性系统而生,乘个系数就能推下一步;但外汇和贵金属的价线是非线性、步间比率漂移且相邻状态关系不明的。硬解法是套用自回归模型当动态过程模型,用柱线收盘价驱动离散版滤波。 实现上先建 CKalman 类,在初始化里给变量赋初值。噪声协方差用分析周期 Close 偏差的均方根,过程噪声协方差则靠自回归模型值的均方根偏差估出来;系统初值取最后 10 个收盘价的算术平均。 拿到全部初值后,Forecast 公有函数做单步前瞻:先查自回归模型算没算,必要时补算,再把状态转换系数存进 cda_F[0] 并顺移,重算状态与出错概率,返回的是新柱线预测收盘价。收到真实收盘价后,Correction 函数按理论式校正状态并返回更新值。 外汇和贵金属波动剧烈,这套滤波只降低噪声、不消除跳空风险,实盘前请在 MT5 策略测试器用历史数据验证系数稳定性。
class CKalman { class="kw">private: class=class="str">"cmt">//--- class="type">uint ci_HistoryBars; class=class="str">"cmt">//用于分析的柱线数量 class="type">uint ci_Shift; class=class="str">"cmt">//计算自回归的偏移 class="type">class="kw">string cs_Symbol; class=class="str">"cmt">//品名 ENUM_TIMEFRAMES ce_Timeframe; class=class="str">"cmt">//时间帧 class="type">class="kw">double cda_AR[]; class=class="str">"cmt">//自回归系数 class="type">int ci_IP; class=class="str">"cmt">//自回归系数的数量 class="type">class="kw">datetime cdt_LastCalculated; class=class="str">"cmt">//最后计算时间; class="type">bool cb_AR_Flag; class=class="str">"cmt">//自回归计算的标志 class=class="str">"cmt">//--- 卡尔曼滤波器的数值 class="type">class="kw">double cd_X; class=class="str">"cmt">// X class="type">class="kw">double cda_F[]; class=class="str">"cmt">// F 数组 class="type">class="kw">double cd_P; class=class="str">"cmt">// P class="type">class="kw">double cd_Q; class=class="str">"cmt">// Q class="type">class="kw">double cd_y; class=class="str">"cmt">// y class="type">class="kw">double cd_S; class=class="str">"cmt">// S class="type">class="kw">double cd_R; class=class="str">"cmt">// R
卡尔曼类里的自回归初始化细节
上面这段是 CKalman 类的构造与自回归预处理的拼装代码,核心在把历史收盘价转成相邻比值序列,再据此定模型阶数。构造函数默认吃 6240 根 H1 棒线、偏移 0、品种留空时自动取 _Symbol,初始测量噪声协方差 cd_P 设 1、卡尔曼增益 cd_K 设 0.9,这两个值直接决定滤波对新报价的跟随锐度。 Autoregression() 先卡 Bars() 数量是否够 ci_HistoryBars,不够直接返 false;随后用 CopyClose 取 ci_HistoryBars+1 个收盘价,注意是从 ci_Shift+1 开始倒序存,ArraySetAsSeries(close,true) 后 close[0] 是最新根。 比值序列 cda_QuotesCenter[i]=close[i]/close[i+1] 是后面 Levinson 递归的输入。模型阶数 ci_IP 用 50*MathLog10(NumTS) 四舍五入,且硬卡不超过 NumTS*0.7——比如 NumTS=6240 时 log10 约 3.795,ci_IP 算出来约 190,远小于 4368 的上限,实际就取 190。外汇与贵金属波动跳变多,这套阶数上限能压住过拟合,但高杠杆下仍属高风险,参数照搬前先在 MT5 策略测试器跑一遍。 让小布替你跑这套 把 cd_K 从 0.9 改成 0.5 再回测同品种,滤波平滑度会明显上升、信号滞后也加大,适合对比震荡与趋势段的表现差异。
class="type">class="kw">double cd_K; class=class="str">"cmt">// K class="kw">public: CKalman(class="type">uint bars=class="num">6240, class="type">uint shift=class="num">0, class="type">class="kw">string symbol=NULL, ENUM_TIMEFRAMES period=PERIOD_H1); ~CKalman(); class="type">void Clear_AR_Flag(class="type">void) { cb_AR_Flag=class="kw">false; } }; CKalman::CKalman(class="type">uint bars, class="type">uint shift, class="type">class="kw">string symbol, ENUM_TIMEFRAMES period) { ci_HistoryBars = bars; cs_Symbol = (symbol==NULL ? _Symbol : symbol); ce_Timeframe = period; cb_AR_Flag = class="kw">false; ci_Shift = shift; cd_P = class="num">1; cd_K = class="num">0.9; } class="type">bool Autoregression(class="type">void); class="type">bool LevinsonRecursion(class="kw">const class="type">class="kw">double &R[],class="type">class="kw">double &A[],class="type">class="kw">double &K[]); class="type">bool CKalman::Autoregression(class="type">void) { class=class="str">"cmt">//--- 检查数据不足 if(Bars(cs_Symbol,ce_Timeframe)<(class="type">int)ci_HistoryBars) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="type">class="kw">double cda_QuotesCenter[]; class=class="str">"cmt">//计算的数据 class=class="str">"cmt">//--- 令所有价格可用 class="type">class="kw">double close[]; class="type">int NumTS=CopyClose(cs_Symbol,ce_Timeframe,ci_Shift+class="num">1,ci_HistoryBars+class="num">1,close)-class="num">1; if(NumTS<=class="num">0) class="kw">return class="kw">false; ArraySetAsSeries(close,true); if(ArraySize(cda_QuotesCenter)!=NumTS) { if(ArrayResize(cda_QuotesCenter,NumTS)<NumTS) class="kw">return class="kw">false; } for(class="type">int i=class="num">0;i<NumTS;i++) cda_QuotesCenter[i]=close[i]/close[i+class="num">1]; class=class="str">"cmt">// 计算系数 ci_IP=(class="type">int)MathRound(class="num">50*MathLog10(NumTS)); if(ci_IP>NumTS*class="num">0.7) ci_IP=(class="type">int)MathRound(NumTS*class="num">0.7); class=class="str">"cmt">// 自回归模型的顺序 class="type">class="kw">double cor[],tdat[]; if(ci_IP<=class="num">0 || ArrayResize(cor,ci_IP)<ci_IP || ArrayResize(cda_AR,ci_IP)<ci_IP || ArrayResize(tdat,ci_IP)<ci_IP) class="kw">return class="kw">false; class="type">class="kw">double a=class="num">0; for(class="type">int i=class="num">0;i<NumTS;i++)