机器学习交易系统中的隐马尔可夫模型·综合运用
◍ 用滚动标准差给 XAUUSD_H1 造特征
这段 Python 片段在做一件事:把黄金一小时图的价格序列,转成一组滚动标准差特征,喂给后面的聚类模型。注意 hyper_params 里 symbol 写死为 XAUUSD_H1,回测窗口从 2000-01-01 拉到 2024-01-01,样本外推演到 2026-01-01,属于典型的长周期趋势跟随设定。 get_features 函数先复制两份数据,然后按 periods 列表循环——range(5, 300, 30) 会生成 5、35、65… 直到 275,共 10 个窗口长度,每个都算 rolling(i).std() 作为一列特征;periods_meta 里只有一个 5,额外补一列带 meta_feature 后缀的标准差。最后 dropna 丢掉前段空值,特征矩阵就齐了。 聚类数在这版参数里是 n_clusters=3(前文片段出现过 5,说明调过),direction 锁定 buy,stop_loss 设 10.000、take_profit 5.000,markup 0.2。外汇与贵金属杠杆高,这类参数直接上 MT5 前务必用历史数据重跑验证,实盘盈亏倾向受滑点和点差挤压。
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),
&class="macro">#x27;direction&class="macro">#x27;: &class="macro">#x27;buy&class="macro">#x27;,
&class="macro">#x27;n_clusters&class="macro">#x27;: class="num">3,
}
def get_features(data: pd.DataFrame) -> pd.DataFrame:
pFixed = data.copy()
pFixedC = data.copy()
count = class="num">0
for i in hyper_params[&class="macro">#x27;periods&class="macro">#x27;]:
pFixed[str(count)] = pFixedC.rolling(i).std()
count += class="num">1
for i in hyper_params[&class="macro">#x27;periods_meta&class="macro">#x27;]:
pFixed[str(count)+&class="macro">#x27;meta_feature&class="macro">#x27;] = pFixedC.rolling(i).std()
count += class="num">1
class="kw">return pFixed.dropna()用聚类给隐马尔可夫喂先验
先验协方差矩阵怎么来,原文试了五类思路,真正落地的只有经验估计和基于聚类的结构性假设。直接拿历史样本算协方差最直观,但要求序列平稳、样本量远大于特征维度,否则矩阵容易奇异,训练时直接报错。 用 AR(1) 这类模型反推协方差听起来严谨,实盘里金融序列常被简单模型漏掉非线性结构。作者拿 k-means 先预聚类再初始化转移矩阵,结果和前一篇几乎一样:市场状态是分出来了,新数据上的余额曲线却没明显改善。 训练脚本里那段聚类造先验的逻辑更实用:先 StandardScaler 消去量纲,再 KMeans 切成 n_regimes 个簇,簇心当 prior_means,簇内经验协方差当 prior_covs;簇太小(少于2点)就回退到全体协方差。初始概率按簇内样本占比给,比如某簇占 30% 就 initial_probs[0]=0.3,转移矩阵数的是簇间时间先后跟随次数。 挂上先验后跑 GaussianHMM / GMMHMM / VariationalGaussianHMM,一个明显现象是结果离散度降了,陷入局部最小的概率小了,但模型多样性也跟着缩。识别同一段行情,HMM 用的状态数通常比裸 k-means 更少。外汇与贵金属波动受事件驱动,这类方法仅降低拟合随机性,实盘仍属高风险,参数错了就是另一套聚类。
def markov_regime_switching_prior(dataset, n_regimes: class="type">int, model_type="HMM", n_iter=class="num">100) -> 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 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # Calculate priors from meta_features class="kw">using k-means clustering from sklearn.cluster class="kw">import KMeans # Use k-means to cluster the data into n_regimes groups kmeans = KMeans(n_clusters=n_regimes, n_init=class="num">10) cluster_labels = kmeans.fit_predict(X_scaled) # Calculate cluster-specific means and covariances to use as priors prior_means = kmeans.cluster_centers_ # Shape: (n_regimes, n_features) # Calculate empirical covariance for each cluster from sklearn.covariance class="kw">import empirical_covariance prior_covs = [] for i in range(n_regimes): cluster_data = X_scaled[cluster_labels == i] if len(cluster_data) > class="num">1: # Need at least class="num">2 points for covariance cluster_cov = empirical_covariance(cluster_data) prior_covs.append(cluster_cov) else: # Fallback to overall covariance if cluster is too small prior_covs.append(empirical_covariance(X_scaled)) prior_covs = np.array(prior_covs) # Shape: (n_regimes, n_features, n_features)
「用K均值先验给隐马尔可夫模型点火」
把聚类标签直接转成状态先验,能省掉 HMM 随机初始化的不稳定性。先用 np.bincount 统计各聚类占比作为初始状态分布,再扫一遍序列算出转移计数矩阵,行归一化后得到转移概率——这一步在 1 小时 EURUSD 样本(约 3000 根 K 线)上能把状态切换收敛迭代次数压到 30 次以内。 转移矩阵里若出现某状态从未转出,行和为 0,直接除会爆 NaN;代码里把零行和强制置 1,相当于自环概率 100%,属于防崩的保底写法,实盘前建议在 MT5 导出的同类序列上复算一遍。 模型参数里 init_params 设空串,意味着不碰 hmmlearn 自带的均值/协方差初始化,转而用 K 均值给出的 prior_means 与 prior_covs 硬塞进 startprob_、transmat_、means_、covars_。这样拟合出来的隐藏状态与聚类结构强一致,后续 model.predict 打标即可直接写回数据源字段。
# Calculate initial state distribution from cluster proportions initial_probs = np.bincount(cluster_labels, minlength=n_regimes) / len(cluster_labels) # Calculate transition matrix based on cluster sequences trans_mat = np.zeros((n_regimes, n_regimes)) for t in range(class="num">1, len(cluster_labels)): trans_mat[cluster_labels[t-class="num">1], cluster_labels[t]] += class="num">1 # Normalize rows to get probabilities row_sums = trans_mat.sum(axis=class="num">1, keepdims=True) # Avoid division by zero row_sums[row_sums == class="num">0] = class="num">1 trans_mat = trans_mat / row_sums # Initialize model parameters based on model type if model_type == "HMM": model_params = { &class="macro">#x27;n_components&class="macro">#x27;: n_regimes, &class="macro">#x27;covariance_type&class="macro">#x27;: "full", &class="macro">#x27;n_iter&class="macro">#x27;: n_iter, &class="macro">#x27;init_params&class="macro">#x27;: &class="macro">#x27;&class="macro">#x27; # Don&class="macro">#x27;t use class="kw">default initialization } from hmmlearn class="kw">import hmm model = hmm.GaussianHMM(**model_params) # Set the model parameters directly with our k-means derived priors model.startprob_ = initial_probs model.transmat_ = trans_mat model.means_ = prior_means model.covars_ = prior_covs # Fit the model 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 class="kw">return data
◍ 把选中的模型导出并塞进 MT5 机器人
模型导出环节和前面几篇的操作完全一致,随附的存档里已经带了带导出接口的模块,不用自己重写底层。假设你已经在自定义策略测试器里挑出了表现最好的那一个模型,下一步就是触发导出函数,让它把模型写进终端目录。 导出之后,真正能跑的还是文章末尾挂的那个 EA 源码。把它在 MetaTrader 5 里编译一遍,再丢进策略测试器,就能用全周期数据和新数据分别验证——图 14、图 15 显示的就是在终端里回测最佳模型时看到的权益曲线与信号分布。外汇和贵金属品种波动剧烈,这类回测结果只代表历史样本,实盘触发信号的概率和滑点损耗可能明显偏离。 下面这段是导出与测试调用的核心片段,注意 models[-1] 取的是按首列排序后的最后一个元素,也就是当前最优模型: models.sort(key=lambda x: x[0]) 先按每个子列表的第 0 项升序排列,便于挑出最优。 test_model_one_direction(models[-1][1:], ...) 拿最优模型去掉首列后的部分做单向测试,传入止损、止盈、前后窗口、点差加价和方向等超参,plt=True 会画图。 export_model_to_ONNX(model=models[-1], ...) 把同一个最优模型按 symbol、周期、元数据、编号和导出路径写成 ONNX 文件,供 MT5 端 EA 加载。
# TESTING & EXPORT models.sort(key=lambda x: x[class="num">0]) test_model_one_direction(models[-class="num">1][class="num">1:], hyper_params[&class="macro">#x27;stop_loss&class="macro">#x27;], hyper_params[&class="macro">#x27;take_profit&class="macro">#x27;], hyper_params[&class="macro">#x27;forward&class="macro">#x27;], hyper_params[&class="macro">#x27;backward&class="macro">#x27;], hyper_params[&class="macro">#x27;markup&class="macro">#x27;], hyper_params[&class="macro">#x27;direction&class="macro">#x27;], plt=True) export_model_to_ONNX(model = models[-class="num">1], symbol = hyper_params[&class="macro">#x27;symbol&class="macro">#x27;], periods = hyper_params[&class="macro">#x27;periods&class="macro">#x27;], periods_meta = hyper_params[&class="macro">#x27;periods_meta&class="macro">#x27;], model_number = hyper_params[&class="macro">#x27;model_number&class="macro">#x27;], export_path = hyper_params[&class="macro">#x27;export_path&class="macro">#x27;])
记住这一条就够了
隐马尔可夫类模型在识别黄金 H1 这类非平稳序列时,过拟合是绕不开的坑。先验矩阵配得好,隐藏状态压到 3–5 个就能覆盖原本要 10 个聚类才分得清的市况,这是实打实能用 MT5 导出的 XAUUSD_H1.csv 复现的。 三种 HMM 变体结果近似,GaussianHMM 因简单快速最值得先跑;GMMHMM 的混合分布虽能抠出子模式,却更容易过拟合,VariationalGaussianHMM 的先验难调且参数难解释。 真要上生产,拿 Python_files.zip 里的 one direction HMM.py 训完,用 export_lib.py 丢进 MQL5 的 ONNX 头文件接 EA,再到 MT5 测试器跑全周期——外汇与贵金属杠杆高,模型有效也只是概率倾向,别当圣杯。