机器学习交易系统中的隐马尔可夫模型·进阶篇
◍ 变分高斯HMM的贝叶斯训练逻辑
vhmm.VariationalGaussianHMM 用变分推理替代了 hmm.GaussianHMM 里的 EM 算法,本质是把潜在变量和模型参数当随机变量,去逼近真实后验。EM 只给点估计,变分法给出的是后验分布,这对交易里估计状态切换的不确定性更直接有用。 构造函数参数和 GaussianHMM 基本对齐:n_components、covariance_type、n_iter、tol 等都在。多出来的是 startprob_prior、transmat_prior、means_prior、beta_prior、dof_prior、scale_prior 这组先验项,设为 None 就走默认先验。beta_prior 管均值正态先验的精度,dof_prior 和 scale_prior 定义协方差矩阵的逆威沙特或逆伽马先验。 做外汇或贵金属序列建模时,先验相当于正则化。数据少的时候它能把已有认知塞进模型,压住过拟合。比如你觉得开仓状态初始分布该接近均匀,就把 startprob_prior 设成参数接近 1 的狄利克雷先验;对转移矩阵、均值、协方差的怀疑也可以用对应先验编码进去。 先验信息量决定后验偏移幅度。低方差的强先验会把后验明显拉向自己,大方差或均匀先验则放手让行情数据主导。MT5 外接 Python 跑这套时,先拿 EURUSD 的 H1 收盘价试不同 dof_prior,看状态数收敛差异再定。贵金属杠杆高、跳空频繁,先验选错容易给出失真状态概率,实盘前务必样本外验证。
「三类隐马尔可夫模型在行情序列上的取舍」
hmm.GaussianHMM 把每个隐藏状态里的观测值当成高斯分布来处理,胜在轻量、跑得快,适合日线或周线这种大体量且偏态不强的数据。一旦碰上高频 tick 或带重尾、尖峰的收益序列,正态假设就容易塌房,拟合质量明显下滑。 hmm.GMMHMM 用多个高斯叠出混合分布,能啃下多模态行情——比如剧烈波动期内多空收益结构分裂的场景。代价是参数规模膨胀,样本不够时过拟合概率陡增,训练耗时也比 GaussianHMM 高出一个量级。 vhmm.VariationalGaussianHMM 走贝叶斯变分推断,吐出的是参数后验而不是单点估计,小样本下靠先验做正则化挺实用。难点在于先验设歪了结果就偏,而且变分本身是近似解,和真后验有缝隙。 选哪个不看理论漂亮,要看手里数据:大样本+简单动力学用 GaussianHMM;分布歪或多峰上 GMMHMM;不确定性评估刚需或数据稀缺再考虑 Variational 版。外汇与贵金属波动带有高杠杆高风险,任何模型输出都只是概率倾向,实盘前必须用对数似然、MAPE、R^2 加金融专用指标在 MT5 历史数据上回测比对。
给时间序列挑先验协方差的几种路数
高斯隐马尔可夫模型里,每个隐藏状态的观测常被假设成多元正态分布,分布形态由均值向量和协方差矩阵共同决定。协方差矩阵管的是各分量之间的方向、形状和依赖强度,对价格序列这种强时序相关数据尤其关键。 在 hmmlearn 里,GaussianHMM 走 EM 算法,VariationalGaussianHMM 走变分贝叶斯。后者在数据少的时候更稳,但前提是先验不能乱给——先验协方差设错,轻则收敛卡住,重则过拟合到没法用。 实操上,covariance_type 先定结构:spherical 各维同方差、diagonal 各维独立、full 全自由、tied 多状态共享。GaussianHMM 用 covars_prior 和 covars_weight 调先验强度,VariationalGaussianHMM 则交给我们 scale_prior 和 dof_prior 去控。外汇与贵金属波动聚集明显,full 型虽灵活但样本不够时容易炸,小布建议先用 diagonal 跑通再换。
◍ 先验协方差矩阵的四种取法与 hmmlearn 落点
给隐马尔可夫模型喂协方差先验,第一步是选估计路线。最直接的是经验估计:对长度 T、维度 p 的时间序列 X 算样本协方差 Σ̂,对角线是方差、非对角是分量间协方差。它要求序列平稳、T 相对 p 足够大,否则维度一高矩阵就奇异,跑某些算法会直接报错。 数据少或维度高时,收缩法更稳。线性收缩公式 Rα = (1−α)S + αT,S 是样本阵、T 是目标阵(单位阵假设独立,对角阵假设无相关),α 在 [0,1] 里调。α 拉大能压住估计误差、防过拟合,但目标结构得你先对依赖关系有判断。 有领域知识就走信息先验:逆威沙特分布作共轭先验,尺度矩阵加自由度把老经验写进模型;没把握就用无信息先验,杰弗里斯或 LKJ 相关先验让数据主导,不过小样本下“弱先验”也难免漏影响。已知自相关就拆 ACF/PACF 定 AR/MA 阶数,AR(1) 的协方差结构只挂一个自相关系数。 在 MT5 配套的 Python 侧 hmmlearn 里,GaussianHMM 和 VariationalGaussianHMM 都能直接把经验或收缩结果塞进 covars_ 初始化。信息先验走 covars_prior/covars_weight(逆伽马来 spherical/diag,逆威沙特来 full/tied);变分类另给 scale_prior 与 dof_prior 控贝叶斯先验。无信息就把这些参数推到极弱——逆比例给极小值、比例给极大值。结构性假设同样是算好阵再写进 covars_。外汇与贵金属序列非平稳常见,先验选错可能放大回测偏差,上实盘前用历史段交叉验证。
「用隐马尔可夫模型切分黄金市场状态」
相比因果推断或聚类法,隐马尔可夫模型(HMM)把行情看作由隐藏状态驱动的可观测序列,适合在 MT5 外接 Python 环境里做状态识别对比。本小节只动「状态识别」这一段,训练与标签流程沿用原有单向趋势框架,黄金品种、做多方向、特征为移动窗口标准差均不变。 代码里默认挂了三个模型:GaussianHMM、GMMHMM、VariationalGaussianHMM,隐藏状态数默认 5,特征周期 5,n_iter=10。先用默认参数跑通,再谈调参——别一上来就猛训,容易看不出哪个改动起效。 默认参数下 GaussianHMM 和 GMMHMM 都能在测试集上给出可接受的权益曲线;VariationalGaussianHMM 首次训练像离群值,但重训几次后曲线反而可能更好。把 n_iter 提到 100,三种模型在新数据上结果都改善,GaussianHMM 因结构简、训练稍快。 再把状态数降到 3(n_iter 保持 100),三个模型仍可有效识别,只是 VariationalGaussianHMM 需多次重训才稳。外汇与贵金属杠杆高、滑点跳空频繁,HMM 状态仅是概率划分,实盘前务必在 MT5 策略测试器用历史数据复跑验证。
class="kw">import math class="kw">import pandas as pd from class="type">class="kw">datetime class="kw">import class="type">class="kw">datetime from catboost class="kw">import CatBoostClassifier from sklearn.model_selection class="kw">import train_test_split from sklearn.cluster class="kw">import KMeans from hmmlearn class="kw">import hmm, vhmm from bots.botlibs.labeling_lib class="kw">import * from bots.botlibs.tester_lib class="kw">import tester_one_direction from bots.botlibs.export_lib class="kw">import export_model_to_ONNX def markov_regime_switching(dataset, n_regimes: class="type">int, model_type="GMMHMM") -> pd.DataFrame: data = dataset[(dataset.index < hyper_params[&class="macro">#x27;forward&class="macro">#x27;]) & (dataset.index > hyper_params[&class="macro">#x27;backward&class="macro">#x27;])].copy() # Extract meta features meta_X = data.loc[:, data.columns.str.contains(&class="macro">#x27;meta_feature&class="macro">#x27;)] if meta_X.shape[class="num">1] > class="num">0: # Format data for HMM(requires 2D array) X = meta_X.values # Features normalization before training from sklearn.preprocessing class="kw">import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # Create and train the HMM model if model_type == "HMM": model = hmm.GaussianHMM( n_components=n_regimes, covariance_type="spherical", n_iter=class="num">10, ) elif model_type == "GMMHMM": model = hmm.GMMHMM( n_components=n_regimes, covariance_type="spherical", n_iter=class="num">10, n_mix=class="num">3, ) elif model_type == "VARHMM": model = vhmm.VariationalGaussianHMM( n_components=n_regimes, covariance_type="spherical", n_iter=class="num">10,
把隐状态塞进模型训练的收尾写法
上面的片段接着隐马尔可夫建模之后,把识别出的市场状态真正接入训练流程。先用 model.fit 在标准化特征上拟合,再用 predict 回写 hidden_states,样本数据就带上了 clusters 标签。 循环里对每个状态单独切片,少于 500 根的簇直接跳过并打印 too few samples,这是为了避免小样本过拟合——XAUUSD_H1 上某些边缘状态可能天然稀薄。 超参里 symbol 固定为 XAUUSD_H1,stop_loss 10.000、take_profit 5.000,回测窗口从 2000-01-01 到 2024-01-01,向前验证给到 2026-01-01。贵金属杠杆品种波动剧烈,这类参数仅代表历史区间设定,实盘表现可能偏离。 meta_data 把当前簇标成 1、其余标 0,再丢给 fit_final_models 做最终拟合,models 列表收集各状态模型。开 MT5 把 export_path 指向你的 Include 目录,改 symbol 或 periods 就能重跑这套状态划分。
model.fit(X_scaled) # Predict the hidden states(regimes) hidden_states = model.predict(X_scaled) # Assign states to clusters data[&class="macro">#x27;clusters&class="macro">#x27;] = hidden_states class="kw">return data for i in range(class="num">1): data = markov_regime_switching(dataset, n_regimes=hyper_params[&class="macro">#x27;n_clusters&class="macro">#x27;], model_type="HMM") sorted_clusters = data[&class="macro">#x27;clusters&class="macro">#x27;].unique() sorted_clusters.sort() for clust in sorted_clusters: clustered_data = data[data[&class="macro">#x27;clusters&class="macro">#x27;] == clust].copy() if len(clustered_data) < class="num">500: print(&class="macro">#x27;too few samples: {}&class="macro">#x27;.format(len(clustered_data))) class="kw">continue clustered_data = get_labels_one_direction(clustered_data, markup=hyper_params[&class="macro">#x27;markup&class="macro">#x27;], min=class="num">1, max=class="num">15, direction=hyper_params[&class="macro">#x27;direction&class="macro">#x27;]) print(f&class="macro">#x27;Iteration: {i}, Cluster: {clust}&class="macro">#x27;) clustered_data = clustered_data.drop([&class="macro">#x27;close&class="macro">#x27;, &class="macro">#x27;clusters&class="macro">#x27;], axis=class="num">1) meta_data = data.copy() meta_data[&class="macro">#x27;clusters&class="macro">#x27;] = meta_data[&class="macro">#x27;clusters&class="macro">#x27;].apply(lambda x: class="num">1 if x == clust else class="num">0) models.append(fit_final_models(clustered_data, meta_data.drop([&class="macro">#x27;close&class="macro">#x27;], axis=class="num">1))) hyper_params = { &class="macro">#x27;symbol&class="macro">#x27;: &class="macro">#x27;XAUUSD_H1&class="macro">#x27;, &class="macro">#x27;export_path&class="macro">#x27;: &class="macro">#x27;/drive_c/Program Files/MetaTrader class="num">5/MQL5/Include/Trend following/&class="macro">#x27;, &class="macro">#x27;model_number&class="macro">#x27;: class="num">0, &class="macro">#x27;markup&class="macro">#x27;: class="num">0.2, &class="macro">#x27;stop_loss&class="macro">#x27;: class="num">10.000, &class="macro">#x27;take_profit&class="macro">#x27;: class="num">5.000, &class="macro">#x27;periods&class="macro">#x27;: [i for i in range(class="num">5, class="num">300, class="num">30)], &class="macro">#x27;periods_meta&class="macro">#x27;: [class="num">5], &class="macro">#x27;backward&class="macro">#x27;: class="type">class="kw">datetime(class="num">2000, class="num">1, class="num">1), &class="macro">#x27;forward&class="macro">#x27;: class="type">class="kw">datetime(class="num">2024, class="num">1, class="num">1), &class="macro">#x27;full forward&class="macro">#x27;: class="type">class="kw">datetime(class="num">2026, class="num">1, class="num">1),