基于马尔可夫链的矩阵预测模型·进阶篇
(2/3)·接上篇概念铺垫,本篇用概率矩阵把价格、时间、成交量编织成可迭代的市场状态机
接上篇,我们继续深挖马尔可夫矩阵预测模型。很多人把市场当连续函数去拟合,却忽略了价格本身就能被离散化成状态——本篇要做的,是把混沌行情转译成概率语言。
价格、时间、成交量三维拆法
只盯K线容易漏掉市场真正的驱动结构。把行情拆成价格、时间、成交量三个维度,分别回答「正在发生什么」「何时容易发生」「为什么动」,三者联动才更接近真实博弈。 价格维度用经典工具刻画状态:趋势、均线、震荡指标、形态。时间维度不是简单看钟点,而是把日内和月度季节性编码进模型,重大波动往往在特定窗口概率更高。成交量维度看参与强度,分时动态和累积/派发类指标能揭示价格变动的底层推力。 时间处理有个细节容易踩坑:直接用0–23小时整数,23:59到00:00会被模型当成巨大跳跃。用正弦/余弦把小时映射成连续圆周坐标,断层就消失了。下面这段提取逻辑可直接在MT5导出的CSV上跑(Python端),验证三维度字段怎么来。 代码里价格部分给了9/21/50三条EMA及两两交叉信号;时间部分除星期和小时外,重点在hour_sin/hour_cos的连续编码;成交量用14期均量做相对强度rel_volume,单根K线成交量偏离均量越大,行情延续倾向越强(外汇、贵金属属高风险,信号仅作概率参考)。
def add_indicators(df): # --- PRICE INDICATORS --- df[&class="macro">#x27;ema_9&class="macro">#x27;] = df[&class="macro">#x27;close&class="macro">#x27;].ewm(span=class="num">9, adjust=False).mean() df[&class="macro">#x27;ema_21&class="macro">#x27;] = df[&class="macro">#x27;close&class="macro">#x27;].ewm(span=class="num">21, adjust=False).mean() df[&class="macro">#x27;ema_50&class="macro">#x27;] = df[&class="macro">#x27;close&class="macro">#x27;].ewm(span=class="num">50, adjust=False).mean() df[&class="macro">#x27;ema_cross_9_21&class="macro">#x27;] = (df[&class="macro">#x27;ema_9&class="macro">#x27;] > df[&class="macro">#x27;ema_21&class="macro">#x27;]).astype(class="type">int) df[&class="macro">#x27;ema_cross_21_50&class="macro">#x27;] = (df[&class="macro">#x27;ema_21&class="macro">#x27;] > df[&class="macro">#x27;ema_50&class="macro">#x27;]).astype(class="type">int) # --- TIME INDICATORS --- df[&class="macro">#x27;hour&class="macro">#x27;] = df[&class="macro">#x27;time&class="macro">#x27;].dt.hour df[&class="macro">#x27;day_of_week&class="macro">#x27;] = df[&class="macro">#x27;time&class="macro">#x27;].dt.dayofweek df[&class="macro">#x27;hour_sin&class="macro">#x27;] = np.sin(class="num">2 * np.pi * df[&class="macro">#x27;hour&class="macro">#x27;] / class="num">24) df[&class="macro">#x27;hour_cos&class="macro">#x27;] = np.cos(class="num">2 * np.pi * df[&class="macro">#x27;hour&class="macro">#x27;] / class="num">24) # --- VOLUME INDICATORS --- df[&class="macro">#x27;tick_volume&class="macro">#x27;] = df[&class="macro">#x27;tick_volume&class="macro">#x27;].astype(class="type">class="kw">float) df[&class="macro">#x27;volume_change&class="macro">#x27;] = df[&class="macro">#x27;tick_volume&class="macro">#x27;].pct_change(class="num">1) df[&class="macro">#x27;volume_ma_14&class="macro">#x27;] = df[&class="macro">#x27;tick_volume&class="macro">#x27;].rolling(class="num">14).mean() df[&class="macro">#x27;rel_volume&class="macro">#x27;] = df[&class="macro">#x27;tick_volume&class="macro">#x27;] / df[&class="macro">#x27;volume_ma_14&class="macro">#x27;]
「用聚类把行情切成可数的状态」
把连续的价格、时间、成交量直接喂给模型,得到的是一团看不出规律的混沌;真正能拿来统计概率的,是先把它们离散成有限个「状态」。这里每组特征各自跑 K-means,分成 3 个聚类,价格、时间、成交量三组理论上能交叉出 27 种组合,但当前实现只取 9 种最具代表性的状态——这是在估计稳健性和细分度之间做的折中,状态太少会丢信息,太多则每个状态样本量不够、概率估计会飘。 聚类的好处是不用先验假设「什么叫趋势、什么叫震荡」,模型只啃历史数据结构,自己就把高波动段、盘整段、单边段归到不同堆里。你在 MT5 里导出的 tick 或分钟级特征,只要按组整理好,就能复现这套切分。 下面这段是状态聚类的核心函数:每组独立建一个 KMeans(n_clusters=3,random_state=42 保证可复现,n_init=10 避免局部最优),拟合完把标签和模型都存回来。注意它刻意跳过了名为 'all' 的组,避免把混在一起的总特征误当一组去切。 外汇与贵金属杠杆高、跳空频繁,聚类出的状态只在历史样本分布内有效,换周期或换品种后须重新拟合,否则状态含义可能漂移。
def create_state_clusters(feature_groups, n_clusters_per_group=class="num">3): group_clusters = {} kmeans_models = {} for group_name, group_data in feature_groups.items(): if group_name != &class="macro">#x27;all&class="macro">#x27;: kmeans = KMeans(n_clusters=n_clusters_per_group, random_state=class="num">42, n_init=class="num">10) clusters = kmeans.fit_predict(group_data[&class="macro">#x27;data&class="macro">#x27;]) group_clusters[group_name] = clusters kmeans_models[group_name] = kmeans class="kw">return group_clusters, kmeans_models
◍ 双矩阵驱动的马尔可夫状态推演
预测模型的内核由两张矩阵撑起:状态转移概率矩阵,以及每次转移对应的下一根 K 线收阳概率矩阵。两者拼起来就是一条马尔可夫链——下一刻市场处于什么格局,只挂钩当前状态,和更早的走势痕迹无关。 转移矩阵记的是「从 A 状态跳到 B 状态」的发生频率。比如盘整态之后,市场可能以某个概率切进趋势变动,也可能继续原地横盘,这张表把这种动态结构量化了出来。 上涨概率矩阵更进一层:它为每一种状态跳转标注了下一根 K 线收盘价高于开盘价的概率。于是你不只能猜市场去哪个状态,还能顺带判断价格方向偏多还是偏空。外汇与贵金属波动杠杆高,这类概率仅作倾向参考,实盘仍需风控。 把两张矩阵画出来,常会撞见反直觉的规律。某些状态自我延续性极强,形成稳定格局;另一些只是过路状态,迅速让位给新结构。这种转移结构的不对称,才是读懂盘面动态逻辑的钥匙。 下面这段聚合脚本把状态序列塞进 9×9 的矩阵里做计数与归一,直接可在 MT5 外接 Python 环境跑一遍验证你的状态标签。
def combine_state_clusters(group_clusters, labels): # Fill the transition matrix and rise matrix for i in range(len(states) - class="num">1): curr_state = states[i] next_state = states[i + class="num">1] # Increase the transition counter transition_matrix[curr_state, next_state] += class="num">1 # If the next candle is bullish, increase the rise counter if i + class="num">1 < len(labels) and labels[i + class="num">1] == class="num">1: rise_matrix[curr_state, next_state] += class="num">1 # Normalization of the transition matrix state_transitions = np.zeros((class="num">9, class="num">9)) for i in range(class="num">9): row_sum = np.sum(transition_matrix[i, :]) if row_sum > class="num">0: state_transitions[i, :] = transition_matrix[i, :] / row_sum
用概率树把目光放到三根K线之后
马尔可夫框架真正有意思的地方在迭代:它不只给你下一根K线是涨是跌的二分类结论,而是把所有可能路径按转移概率做加权,输出一整张概率分布,并附带置信度。对做外汇或贵金属的人来说,这种分布比单点信号更利于仓位和止损安排——这类品种杠杆高、跳空频繁,盲目信单点结论风险很大。 模型可以顺着转移矩阵连推多步,生成未来 N 根 K 线的概率树。单步预测往往抓不到的偏向性漂移,在第三步、第四步的边际概率里会显形,比如连续三步后多头累计概率从 0.52 爬到 0.61,这种倾向值得盯盘时参考而非当成定论。 下面这段 Python 风格的函数演示了单步加权概率怎么算,逻辑可直接平移进 MQL5 的矩阵运算: def predict_with_matrix(state_transitions, rise_probability_matrix, current_state): # 取当前状态到各下一状态的转移概率向量 next_state_probs = state_transitions[current_state, :] # 初始化加权上涨概率与总概率累加器 weighted_prob = 0 total_prob = 0 # 遍历每个可达下一状态,用转移概率乘对应上涨条件概率并累加 for next_state, prob in enumerate(next_state_probs): weighted_prob += prob * rise_probability_matrix[current_state, next_state] total_prob += prob # 若总概率大于0则做归一化,避免概率和质量不闭合 if total_prob > 0: weighted_prob = weighted_prob / total_prob # 以0.5为界出方向预测,置信度取偏离0.5更远的那一侧 prediction = 1 if weighted_prob > 0.5 else 0 confidence = max(weighted_prob, 1 - weighted_prob) 在 MT5 里把 state_transitions 换成你统计的跳转矩阵,rise_probability_matrix 换成条件上涨概率,就能在 EA 里实时吐出带置信度的多步预测。记得用近 3000 根 K 线滚动重估矩阵,否则旧样本会让概率树慢慢失真。
def predict_with_matrix(state_transitions, rise_probability_matrix, current_state): # Probabilities of transition to the next state next_state_probs = state_transitions[current_state, :] # Calculation of the weighted rise probability taking into account all possible transitions weighted_prob = class="num">0 total_prob = class="num">0 for next_state, prob in enumerate(next_state_probs): weighted_prob += prob * rise_probability_matrix[current_state, next_state] total_prob += prob # Normalization(if necessary) if total_prob > class="num">0: weighted_prob = weighted_prob / total_prob # Forecast prediction = class="num">1 if weighted_prob > class="num">0.5 else class="num">0 confidence = max(weighted_prob, class="num">1 - weighted_prob)
「模型透明才能信得过信号」
拿马尔可夫链和神经网络比,前者不是黑箱:状态、转移概率都是可查的统计量,你拿到预测的同时能反推市场处在什么结构里。这点对做价格行为的老手尤其有用——逻辑看得见,才敢按信号下单。 上面这段 Python 思路可以直接平移到 MT5 的因子分析里:按价格、时间、成交量分组做 K-means,取每个簇中心离零的绝对距离当特征重要性,排前 3 的就是要盯的核心指标。外汇和贵金属波动受事件驱动,过滤掉不显著的噪声因子,能少亏在无意义抖动上。 状态簇往往对应趋势、盘整、反转这类经典形态。统计模型和裸 K 分析因此能接上桥:比如某簇中心显示波动率与成交量因子权重最高,那当前大概率不是窄幅整理,而是变盘前兆,得调紧止损。
for group_name in [&class="macro">#x27;price&class="macro">#x27;, &class="macro">#x27;time&class="macro">#x27;, &class="macro">#x27;volume&class="macro">#x27;]: features = feature_groups[group_name][&class="macro">#x27;features&class="macro">#x27;] kmeans = kmeans_models[group_name] cluster_centers = kmeans.cluster_centers_ for cluster_idx in range(class="num">3): center = cluster_centers[cluster_idx] # We obtain the importance of each feature as its deviation from zero at the center of the cluster importances = np.abs(center) sorted_idx = np.argsort(-importances) top_features = [(features[i], importances[i]) for i in sorted_idx[:class="num">3]]