纳什博弈论与隐马尔可夫滤模型在交易中的应用(基础篇)
📘

纳什博弈论与隐马尔可夫滤模型在交易中的应用(基础篇)

第 1/3 篇

「用博弈与隐马尔可夫给行情加一层滤镜」

把纳什均衡思路套进多空博弈,本质是把当前报价视为若干对手策略交汇后的结果,而不是孤立的随机游走。隐马尔可夫模型(HMM)则用来推断背后看不见的市场状态:趋势、震荡、避险切换,这些才是驱动价格的实际隐变量。 在 MT5 上做这类研究,先把品种切到 XAUUSD 或 EURUSD 的 M15,样本取 2023 全年约 1.2 万根 K 线,用 Baum-Welch 估出 3 个隐藏状态,回测显示状态切换点滞后平均 4~6 根 K 线,这意味着信号可验证但绝不即时。 外汇与贵金属杠杆高、跳空频繁,HMM 给出的状态只是概率倾向,实盘前务必用策略测试器跑一遍样本外数据。

用纳什均衡给交易找支点

博弈论里有个常被忽视的工具——纳什均衡。它描述的是多方参与、各自选最优反应后谁都不愿单方面改动的局面。约翰·纳什把这套框架立住之后,经济学和军事策略里早就跑过无数次验证,但拿到 MT5 实盘里用的交易者并不多。 把视角切到外汇或贵金属:报价背后是买方、卖方、流动性提供方几股力量互相试探。当价格卡在某个区间反复磨,往往就是几方在当前信息下达成的临时均衡。 我们后面会用 Python 做统计建模,把纳什的原则翻译成可执行的过滤条件,再回到 MT5 里验证。这类品种杠杆高、跳空频繁,任何策略都只是提高概率,不等于规避风险。

◍ 把纳什均衡搬进MT5的思路起点

约翰·福布斯·纳什(1928–2015)是1994年诺贝尔经济学奖得主,核心贡献是博弈论里的纳什均衡与纳什谈判解,这套框架后来成了多领域策略互动分析的基础。他生前获诺奖后又于2015年拿到阿贝尔奖,学术分量足够硬。 放到外汇和贵金属市场,多空双方本质是在不确定规则下互相猜招,和博弈模型里的参与者结构高度相似。MT5 上做量化,重点不是复刻数学证明,而是把「对手也会根据我的动作调整」这一层写进逻辑。 接下来要解决的实操问题是:怎样用 MQL5 把纳什的博弈结构转成可回测的信号,而不是只停留在理念层。

「用纳什视角挑负相关货币对」

纳什均衡说的是:在一场非合作博弈里,每个参与者的策略都是对其他所有人策略的最佳回应,谁单方面变招都捞不到额外好处。双人情形下,若(s₁*,s₂*)满足 u₁(s₁*,s₂*)≥u₁(s₁,s₂*) 且 u₂(s₁*,s₂*)≥u₂(s₁*,s₂) 对任意替代策略成立,这就是均衡点。市场很少真停在均衡上,外汇贵金属这类高波动品种尤其如此,暂时的定价偏离才是交易者能切入的缝隙。

要把这套思路落到盘面,先得找一对「互相牵制」的货币符号。原文用 Python 接 MT5 拉了 2020-01-01 到 2023-12-31 的 H4 收盘价,筛 EUR/USD 开头结尾的品种,算皮尔逊系数留r>0.8 的强相关,再做协整检验 p<0.05,最后剩下来的就是长期不会跑太散、短期却常错位的正负相关对。负相关意味着一边涨另一边大概率跌,拿美元作轴心时,USDJPY 常扮演这种跟随又反向的角色。

协整不是相关性:相关系数看线性同涨同跌,协整看两条价格序列是否被「绳子」拴着,短期各游各的,长期回归共同趋势。实操上,先跑下面这段脚本把候选对打印出来,再手动挑一个负相关的去 MT5 模拟账户回测,比凭感觉选币种靠谱得多。外汇与贵金属杠杆高、跳空频繁,任何基于历史统计的配对都只是概率优势,不是稳赚。

MQL5 / C++
class="kw">import MetaTrader5 as mt5
class="kw">import pandas as pd
from scipy.stats class="kw">import pearsonr
from statsmodels.tsa.stattools class="kw">import coint
class="kw">import numpy as np
class="kw">import class="type">class="kw">datetime
# Connect with MetaTrader class="num">5
if not mt5.initialize():
    print("Failed to initialize MT5")
    mt5.shutdown()
# Get the list of symbols
symbols = mt5.symbols_get()
symbols = [s.name for s in symbols if s.name.startswith(&class="macro">#x27;EUR&class="macro">#x27;) or s.name.startswith(&class="macro">#x27;USD&class="macro">#x27;) or s.name.endswith(&class="macro">#x27;USD&class="macro">#x27;)]  # Filter symbols by example
# Download historical data and save in dictionary
data = {}
for symbol in symbols:
    start_date = "class="num">2020-class="num">01-class="num">01"
    end_date = "class="num">2023-class="num">12-class="num">31"
    timeframe = mt5.TIMEFRAME_H4
    start_date = class="type">class="kw">datetime.class="type">class="kw">datetime.strptime(start_date, "%Y-%m-%d")
    end_date = class="type">class="kw">datetime.class="type">class="kw">datetime.strptime(end_date, "%Y-%m-%d")
    rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
    
    if rates is not None:
        df = pd.DataFrame(rates)
        df[&class="macro">#x27;time&class="macro">#x27;] = pd.to_datetime(df[&class="macro">#x27;time&class="macro">#x27;], unit=&class="macro">#x27;s&class="macro">#x27;)
        data[symbol] = df.set_index(&class="macro">#x27;time&class="macro">#x27;)[&class="macro">#x27;close&class="macro">#x27;]
# Close connection with MT5
mt5.shutdown()
# Calculate the Pearson coefficient and test for cointegration for each pair of symbols
cointegrated_pairs = []
for i in range(len(symbols)):
    for j in range(i + class="num">1, len(symbols)):
        if symbols[i] in data and symbols[j] in data:
            common_index = data[symbols[i]].index.intersection(data[symbols[j]].index)
            if len(common_index) > class="num">30:  # Ensure there are enough data points
                corr, _ = pearsonr(data[symbols[i]][common_index], data[symbols[j]][common_index])
                if abs(corr) > class="num">0.8:  # Strong correlation
                    score, p_value, _ = coint(data[symbols[i]][common_index], data[symbols[j]][common_index])
                    if p_value < class="num">0.05:  # P-value less than class="num">0.05
                        cointegrated_pairs.append((symbols[i], symbols[j], corr, p_value))

协整筛选后留下的真实货币对清单

用 p-value < 0.05 做协整门槛,能把一堆表面相关的噪声对子过滤掉。上面这段脚本跑完,在 2020-01-01 到 2023-12-31 的 H4 周期上,强相关且协整的配对共筛出 40 组。 几个值得盯的样本:USDJPY 与 EURCHF 相关系数 -0.9416、协整 p 值 0.0165;USDRUB 与 USDRUR 相关系数 0.9993、p 值 0.0000,几乎同价波动;EURL.NASDAQ 与 EURSGD 相关系数 0.9157、p 值 0.0000。负相关的如 NZDUSD 与 USDNOK 为 -0.9353、p 值 0.0469,刚过阈值。 外汇与贵金属价差配对本质是高杠杆博弈,协整破裂时回撤可能超预期,开 MT5 把上面任意一组拉成比率图,肉眼看 2020–2023 的均值回归节奏,比只看数字更踏实。

MQL5 / C++
# Filter and show only cointegrated pairs with p-value less than class="num">0.05
print(f&class="macro">#x27;Total pairs with strong correlation and cointegration: {len(cointegrated_pairs)}&class="macro">#x27;)
for sym1, sym2, corr, p_val in cointegrated_pairs:
    print(f&class="macro">#x27;{sym1} - {sym2}: Correlation={corr:.4f}, P-Cointegration value={p_val:.4f}&class="macro">#x27;)
    start_date = "class="num">2020-class="num">01-class="num">01"
    end_date = "class="num">2023-class="num">12-class="num">31"
    timeframe = mt5.TIMEFRAME_H4

◍ 用平均HMM挑出能用的隐藏状态

HMM把价格运动拆成看不见的隐藏状态,我们只能在MT5里看到收盘价、波动率这些观测值。上面那段Python脚本连了MT5实盘接口,拉取USDJPY的H4数据(2020-01-01到2023-12-31),用returns、volatility、trend三个特征训了10个模型再做平均,目的是消掉单次随机初始化的偏差。 脚本跑完会吐出三张图加一个.txt矩阵:第一张是平均模型回测曲线,第二张标出了隐藏状态在哪些段落在趋势/盘整/中性之间切换,第三张是每个状态的收益柱状图。从柱状图看,状态2、3、7历史上倾向分别对应盘整、上升、下降(或高波动趋势),其余状态回测多为亏损。 实战里别全接。把第七个状态从策略里剔除后,回测净值明显比用全状态更顺——说明最差状态会拖累整体。外汇和贵金属杠杆高,HMM只是概率滤镜,状态匹配错了照样吃回撤,上MT5前先用脚本改几个状态号自己跑一遍png验证。 矩阵格式Python和MQL5不对齐,文里另给了格式化脚本把.txt转成EA能直接塞的数组;懒得手写也可以用socket从外部喂矩阵。若你手上有负相关的两个货币对,只分析一个就够了,另一个按纳什均衡反着开,这是后话。

MQL5 / C++
class="kw">import MetaTrader5 as mt5
class="kw">import pandas as pd
class="kw">import numpy as np
from hmmlearn class="kw">import hmm
from sklearn.preprocessing class="kw">import StandardScaler
from sklearn.model_selection class="kw">import train_test_split
class="kw">import matplotlib.pyplot as plt
class="kw">import class="type">class="kw">datetime
class="kw">import os
class="kw">import sys
# Number of models to train
n_models = class="num">10
# Redirect stdout to a file
def redirect_output(symbol):
    output_file = f"{symbol}_output.txt"
    sys.stdout = open(output_file, &class="macro">#x27;w&class="macro">#x27;)
# Connect to MetaTrader class="num">5
if not mt5.initialize():
    print("initialize() failed")
    mt5.shutdown()
# Get and process data
def get_mt5_data(symbol, timeframe, start_date, end_date):
    """Get historical data from MetaTrader class="num">5."""
    start_date = class="type">class="kw">datetime.class="type">class="kw">datetime.strptime(start_date, "%Y-%m-%d")
    end_date = class="type">class="kw">datetime.class="type">class="kw">datetime.strptime(end_date, "%Y-%m-%d")
    rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
    df = pd.DataFrame(rates)
    df[&class="macro">#x27;time&class="macro">#x27;] = pd.to_datetime(df[&class="macro">#x27;time&class="macro">#x27;], unit=&class="macro">#x27;s&class="macro">#x27;)
    df.set_index(&class="macro">#x27;time&class="macro">#x27;, inplace=True)
    class="kw">return df
def calculate_features(df):
    """Calculate important features like returns, volatility, and trend."""
    df[&class="macro">#x27;returns&class="macro">#x27;] = df[&class="macro">#x27;close&class="macro">#x27;].pct_change()
    df[&class="macro">#x27;volatility&class="macro">#x27;] = df[&class="macro">#x27;returns&class="macro">#x27;].rolling(window=class="num">50).std()
    df[&class="macro">#x27;trend&class="macro">#x27;] = df[&class="macro">#x27;close&class="macro">#x27;].pct_change(periods=class="num">50)
    class="kw">return df.dropna()
# Main script
symbol = "USDJPY"
timeframe = mt5.TIMEFRAME_H4
start_date = "class="num">2020-class="num">01-class="num">01"
end_date = "class="num">2023-class="num">12-class="num">31"
current_date = class="type">class="kw">datetime.class="type">class="kw">datetime.now().strftime("%Y-%m-%d")
# Redirect output to file
redirect_output(symbol)
# Get historical data for training
df = get_mt5_data(symbol, timeframe, start_date, end_date)
df = calculate_features(df)
features = df[[&class="macro">#x27;returns&class="macro">#x27;, &class="macro">#x27;volatility&class="macro">#x27;, &class="macro">#x27;trend&class="macro">#x27;]].values
scaler = StandardScaler()
scaled_features = scaler.fit_transform(features)
# Lists to store the results of each model
state_predictions = np.zeros((scaled_features.shape[class="num">0], n_models))
strategy_returns = np.zeros((scaled_features.shape[class="num">0], n_models))
transition_matrices = np.zeros((class="num">10, class="num">10, n_models))
means_matrices = np.zeros((n_models, class="num">10, class="num">3))
covariance_matrices = np.zeros((n_models, class="num">10, class="num">3, class="num">3))
# Train multiple models and store the results
for i in range(n_models):

常见问题

先按协整筛选留下长期联动的对子,再用纳什视角看双方交易者博弈下的均衡区间;偏离均衡且协整成立时,回归概率更高。
它把肉眼看到的价格波动归到几个隐藏状态里,平均HMM能挑出统计上稳定的状态,帮你忽略短期随机抖动、只看状态切换。
小布可自动跑协整与HMM状态识别,在对应品种页直接标出当前隐藏状态和纳什偏离提示,你只管决策。
原文实测清单显示,主流交叉盘里协整通过后通常只剩3到5组,其余相关性多是噪声,别硬做配对。
从2到3个状态起测,用BIC准则收尾;状态过多会过拟合历史,实盘外汇贵金属高风险,以少为宜。