机器学习交易系统中的隐马尔可夫模型·进阶篇
📘

机器学习交易系统中的隐马尔可夫模型·进阶篇

第 2/3 篇

◍ 变分高斯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 策略测试器用历史数据复跑验证。

MQL5 / C++
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 就能重跑这套状态划分。

MQL5 / C++
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),

常见问题

变分贝叶斯训练能给出参数后验分布而非单点估计,对样本少的贵金属段更稳,普通EM容易过拟合到噪声里。
样本协方差在短窗口方差爆炸,缩水(shrinkage)或Ledoit-Wolf估计更抗造,hmmlearn里可直接传预处理后的矩阵。
小布已内置隐马尔可夫状态诊断,打开黄金品种页就能看到当前隐状态归类与切换提示,不用本地跑模型。
不能。状态只描述 regime 倾向(震荡/趋势/高波动),需叠加价格行为确认,外汇贵金属高风险勿裸用。
用滚动窗口或walk-forward,隐状态只对截至当前bar的历史拟合,严禁全样本求状态再回灌训练。