数据科学与机器学习(第四十二部分):使用Python中的ARIMA模型进行外汇时间序列预测  您需要了解的一切·进阶篇
📈

数据科学与机器学习(第四十二部分):使用Python中的ARIMA模型进行外汇时间序列预测 您需要了解的一切·进阶篇

(2/3)· 从平稳性差分到SARIMA扩展,手把手拆解ARIMA在MT5外汇数据上的落地盲区

偏理论进阶 第 2/3 篇
很多交易者直接把原始汇率丢进ARIMA跑预测,忽略差分平稳这一步,结果模型拟合出的只是噪声。非平稳序列里的趋势会被误当成可预测规律,样本内好看、样本外全废。外汇贵金属杠杆高,拿这种信号下单等于蒙眼走钢丝。

欧元兑美元上跑通ARIMA(0,1,0)

确定好 p、d、q 之后,训练 ARIMA 的思路和传统机器学习一致:先切分训练集与测试集,在训练集上拟合,再回测预测并可视化,最后用损失函数评估样本外表现。 下面这段 Python 代码直接对 EURUSD 收盘价序列拟合了 ARIMA(0,1,0),也就是纯随机游走的差分模型。注意它和 MT5 里靠历史 K 线手动算差分不同,这里交给 statsmodels 一站式出报告。

MQL5 / C++
from statsmodels.tsa.arima.model class="kw">import ARIMA
arima_model = ARIMA(series, order=(class="num">0,class="num">1,class="num">0))
arima_model = arima_model.fit()
print(arima_model.summary())
逐行拆解:第一行引入 ARIMA 类;第二行用 order=(0,1,0) 指定不做自回归、一阶差分、无移动平均;第三行在 series(EURUSD 日线 Close)上执行拟合;第四行打印诊断摘要。 摘要里有个硬数据值得盯:No.Observations=4007,AIC 约 -27973,sigma2 为 5.427e-05,说明残差方差极小。但 Jarque-Bera=1370.86 且 Prob(JB)=0.00,残差显著非正态——外汇高频序列的厚尾特征在这露了馅。Ljung-Box(Q)=1.47、Prob(Q)=0.22,一阶自相关不明显,模型短期无显著漏捕结构。 开 MT5 导出 4000+ 根 EURUSD 日线,丢进 Python 复跑这套,重点看 JB 检验;若你的样本也非正态,别把正态假设当圣经,直接拿预测值做样本外比对更实在。外汇与贵金属属高风险品种,此类统计模型仅作概率参考,实盘须自担风险。

MQL5 / C++
from statsmodels.tsa.arima.model class="kw">import ARIMA
arima_model = ARIMA(series, order=(class="num">0,class="num">1,class="num">0))
arima_model = arima_model.fit()
print(arima_model.summary())

「用 ARIMA(0,1,0) 给收盘价跑一遍差分检验」

原文给出的诊断输出里,Prob(H) 双侧值落在 0.00,峰度读到 5.86,说明序列厚尾特征明显、原假设被强烈拒绝,直接拿原始收盘价建模并不合适。 下面这段 Python 是把 Close 序列按 8:2 切训练集与测试集,再用 ARIMA(0,1,0) 也就是随机游走的首次差分形式去拟合训练段,并打印摘要、画出实际与预测均值对比。 外汇与贵金属价格具备高杠杆与跳空风险,这类单变量差分模型只适合做惯性参考,实盘信号仍需结合价格行为结构过滤。

MQL5 / C++
series = df["Close"]
train_size = class="type">int(len(series) * class="num">0.8)
train, test = series[:train_size], series[train_size:]
from statsmodels.tsa.arima.model class="kw">import ARIMA
arima_model = ARIMA(train, order=(class="num">0,class="num">1,class="num">0))
arima_model = arima_model.fit()
print(arima_model.summary())
predicted = arima_model.predict(start=class="num">1, end=len(train))
plt.figure(figsize=(class="num">7,class="num">4))
plt.plot(train.index, train, label=&class="macro">#x27;Actual&class="macro">#x27;)
plt.plot(train.index, predicted, label=&class="macro">#x27;Forecasted mean&class="macro">#x27;, linestyle=&class="macro">#x27;--&class="macro">#x27;)
plt.title(&class="macro">#x27;Actual vs Forecast&class="macro">#x27;)
plt.legend()
plt.show()

◍ ARIMA样本外推演的实操分野

ARIMA 和常规机器学习框架的 predict 语义完全两码事。前者 predict 只回测样本内、要传起止索引;真要往外看一步,得调 forecast()。想用欧元兑美元今日收盘价推明日,就得把新数据 append 进模型,且 refit=False 避免重训,否则效率直接崩。 下面这段是样本外滚动更新的核心循环:每次把 test 里一条新观测塞进 results,不重拟合,立刻 forecast 下一步,最后把多出来的尾巴截掉。 回测函数吐出的硬指标值得盯:MAPE 约 0.0034,R² 0.993,Pearson 相关 0.9966。这说明在该段 EURUSD 数据上,一步向前预测倾向有极高贴合度,但外汇高杠杆品种随时变结构,这种精度不代表明天仍稳。 开 MT5 导一段日线收盘价到 Python,跑通上面循环,先验证你自己的品种 MAPE 是否也落在 0.003 附近,再谈实盘过滤。

MQL5 / C++
predicted = arima_model.predict(start=class="num">1, end=len(train))
print(arima_model.predict(steps=class="num">10))
# Fit initial model
model = ARIMA(train, order=(class="num">0, class="num">1, class="num">0)) 
results = model.fit()
# Initialize forecasts
forecasts = [results.forecast(steps=class="num">1).iloc[class="num">0]]  # First forecast
# Update with test data iteratively
for i in range(len(test)):
    # Append new observation without refitting
    results = results.append(test.iloc[i:i+class="num">1], refit=False)
    
    # Forecast next step
    forecasts.append(results.forecast(steps=class="num">1).iloc[class="num">0])
forecasts = forecasts[:-class="num">1] # remove the last element which is the predicted next value 
# Compare forecasts vs actual test data
plt.plot(test.index, test, label="Actual")
plt.plot(test.index, forecasts, label="Forecast", linestyle="--")
plt.legend()
class="kw">import sklearn.metrics as metric
from statsmodels.tsa.stattools class="kw">import acf
from scipy.stats class="kw">import pearsonr
def forecast_accuracy(forecast, actual):
    # Convert to numpy arrays if they aren&class="macro">#x27;t already
    forecast = np.asarray(forecast)
    actual = np.asarray(actual)
    
    metrics = {
        &class="macro">#x27;mape&class="macro">#x27;: metric.mean_absolute_percentage_error(actual, forecast),
        &class="macro">#x27;me&class="macro">#x27;: np.mean(forecast - actual),  # Mean Error
        &class="macro">#x27;mae&class="macro">#x27;: metric.mean_absolute_error(actual, forecast),
        &class="macro">#x27;mpe&class="macro">#x27;: np.mean((forecast - actual) / actual),  # Mean Percentage Error
        &class="macro">#x27;rmse&class="macro">#x27;: metric.mean_squared_error(actual, forecast, squared=False),
        &class="macro">#x27;corr&class="macro">#x27;: pearsonr(forecast, actual)[class="num">0],  # Pearson correlation
        &class="macro">#x27;minmax&class="macro">#x27;: class="num">1 - np.mean(np.minimum(forecast, actual) / np.maximum(forecast, actual)),
        &class="macro">#x27;acf1&class="macro">#x27;: acf(forecast - actual, nlags=class="num">1)[class="num">1],  # ACF of residuals at lag class="num">1
        "r2_score": metric.r2_score(forecast, actual)
    }
    class="kw">return metrics
forecast_accuracy(forecasts, test)
{&class="macro">#x27;mape&class="macro">#x27;: class="num">0.0034114761554881936,
 &class="macro">#x27;me&class="macro">#x27;: class="num">6.360279441117738e-05,
 &class="macro">#x27;mae&class="macro">#x27;: class="num">0.0037872155688622737,
 &class="macro">#x27;mpe&class="macro">#x27;: class="num">6.825424905960248e-05,
 &class="macro">#x27;rmse&class="macro">#x27;: class="num">0.005018824533752777,
 &class="macro">#x27;corr&class="macro">#x27;: class="num">0.99656297100796,
 &class="macro">#x27;minmax&class="macro">#x27;: class="num">0.0034008221524469695,
 &class="macro">#x27;acf1&class="macro">#x27;: class="num">0.04637470541528736,
 &class="macro">#x27;r2_score&class="macro">#x27;: class="num">0.9931220697334551}

用残差图判断ARIMA有没有白忙活

ARIMA建模后最该看的就是残差诊断图,它能直接告诉你模型是不是在有效提取价格序列里的信息,而不是单纯过拟合噪声。 标准化残差围绕0上下波动、方差基本均匀,直方图密度近似正态但均值略向右偏,说明拟合偏差不大。理论分位数图上绝大多数点贴着红色参考直线,若出现明显甩尾偏离,则分布存在偏斜、模型假设可能不成立。 自相关图(ACF)里残差若看不到显著超出置信带的滞后尖峰,即不存在自相关;一旦ACF显出周期性或趋势模式,意味着还有可解释信息残留,得往模型里加预测因子(如波动率、成交量)。 下面这段是调出诊断图的MQL5/Python式调用,在MT5的Python环境跑完statsmodels的ARIMA后直接出四宫格: results.plot_diagnostics(figsize=(8,8)) plt.show() 外汇与贵金属杠杆高、跳空频繁,残差诊断只是建模一环,实盘前务必用历史数据交叉验证,避免把样本内巧合当规律。

MQL5 / C++
results.plot_diagnostics(figsize=(class="num">8,class="num">8))
plt.show()

「给日线加上周期项:SARIMA 怎么用」

普通 ARIMA 只能吃掉趋势和噪声,碰上按固定节奏重复的波动就哑火。外汇里这种节奏很常见——比如某些交易时段波动率按日循环抬升,或周内特定几天呈现方向倾向,硬用非季节性模型会系统性漏掉这部分解释力。 SARIMAX 的记号是 (p,d,q)x(P,D,Q,S):前一组是常规自回归、差分、移动平均,后一组 P/D/Q 是季节性版本,S 就是周期长度。对日线数据而言,若按周循环,S 通常取 5;季节性差分不是用今天减昨天,而是用今天减上一个周期同一位置的值。 先用 auto_arima 自动搜参最省事,但注意它即便 seasonal=True,返回的阶数仍可能只是 (p,d,q)。手动再拟合一次 SARIMAX,并在元组末尾补上 S,才能真的把季节项装进去。 一段实测的自动搜索日志里,ARIMA(0,1,0)(0,0,0)[5] 的 AIC 达到 -35538.731,优于带截距的多数组合;最终模型 R² 约 0.986,拟合优度偏高,但外汇与贵金属属高风险品种,历史周期稳定不代表未来必然重复,实盘前务必用 MT5 导出的真实日线复核。 预测前要把数组前 S 个不完整周期的数据砍掉,否则前段残差会污染可视化。模型跑通后,可借 schedule 库按日定时从 MetaTrader 5 拉数据重训并出预测价,再经 Python 接口发单——这条链路今天就能在 MT5 终端里搭起来验。

MQL5 / C++
from pmdarima.arima class="kw">import auto_arima
# Auto-fit SARIMA(automatically detects P, D, Q, S)
auto_model = auto_arima(
    series,
    seasonal=True,            # Enable seasonality
    m=class="num">5,                      # Weeky cycle(class="num">5 days) for daily data
    trace=True,               # Show search progress
    stepwise=True,            # Faster optimization
    suppress_warnings=True,
    error_action="ignore"
)
print(auto_model.summary())
Performing stepwise search to minimize aic
ARIMA(class="num">2,class="num">1,class="num">2)(class="num">1,class="num">0,class="num">1)[class="num">5] intercept   : AIC=-class="num">35529.092, Time=class="num">3.81 sec
ARIMA(class="num">0,class="num">1,class="num">0)(class="num">0,class="num">0,class="num">0)[class="num">5] intercept   : AIC=-class="num">35537.068, Time=class="num">0.29 sec
ARIMA(class="num">1,class="num">1,class="num">0)(class="num">1,class="num">0,class="num">0)[class="num">5] intercept   : AIC=-class="num">35536.573, Time=class="num">0.97 sec
ARIMA(class="num">0,class="num">1,class="num">1)(class="num">0,class="num">0,class="num">1)[class="num">5] intercept   : AIC=-class="num">35536.570, Time=class="num">4.38 sec
ARIMA(class="num">0,class="num">1,class="num">0)(class="num">0,class="num">0,class="num">0)[class="num">5]              : AIC=-class="num">35538.731, Time=class="num">0.21 sec
ARIMA(class="num">0,class="num">1,class="num">0)(class="num">1,class="num">0,class="num">0)[class="num">5] intercept   : AIC=-class="num">35536.048, Time=class="num">0.67 sec
ARIMA(class="num">0,class="num">1,class="num">0)(class="num">0,class="num">0,class="num">1)[class="num">5] intercept   : AIC=-class="num">35536.024, Time=class="num">0.87 sec
ARIMA(class="num">0,class="num">1,class="num">0)(class="num">1,class="num">0,class="num">1)[class="num">5] intercept   : AIC=-class="num">35534.248, Time=class="num">0.92 sec
ARIMA(class="num">1,class="num">1,class="num">0)(class="num">0,class="num">0,class="num">0)[class="num">5] intercept   : AIC=-class="num">35537.492, Time=class="num">0.37 sec
ARIMA(class="num">0,class="num">1,class="num">1)(class="num">0,class="num">0,class="num">0)[class="num">5] intercept   : AIC=-class="num">35537.511, Time=class="num">0.55 sec

◍ 随机游走才是贵金属分钟序数的最优解

对 5009 条分钟级收盘价跑 SARIMAX 网格,ARIMA(1,1,1)(0,0,0)[5] 带截距的 AIC 为 -35535.683,单轮拟合 0.57 秒;而 ARIMA(0,1,0)(0,0,0)[5] 即纯一阶差分无自回归项,AIC 反而更低到 -35538.731,总拟合耗时 13.656 秒。模型选出的 sigma2 估计值 4.846e-05,z 值 80.005、P 值 0.000,残差方差极其显著地压在 4.73e-05~4.96e-05 区间。 Ljung-Box(L1) 的 Q 统计量 2.42、Prob(Q)=0.12,说明滞后一阶残差无自相关,随机游走假设站得住。但 Jarque-Bera 2028.68、Prob(JB)=0.00,异方差 H=0.34、偏度 0.08,分布明显厚尾且波动聚集——外汇与贵金属这种高频序列用纯随机模型大概率漏掉跳空风险。 开 MT5 把对应品种分钟数据导进 Python statsmodels,复跑 SARIMAX(0,1,0) 看 AIC 是否与你本地样本一致;若 Prob(Q) 也大于 0.05,就别在策略里硬加 AR 项,直接做差分滤波更省算力。

让小布替你跑这套诊断
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到序列平稳性初判与残差异常提示,把重复劳动交给小布,你专注决策。

常见问题

常用ADF检验p值大于0.05认定非平稳,再做一阶差分重测;仍不平稳继续加到二阶,但d一般不超过2,否则易丢信息。也可看自相关图衰减速度辅助。
目前小布内置的是平稳性初判与残差异常标记,完整ARIMA建模需在Python端跑,小布负责把盯盘侧的序列特征前置筛一遍,省去你手动导数据的麻烦。
欧元兑美元受欧美重叠交易时段影响,小时线常出现类日周期,SARIMA的P、D、Q季节参数有可能抓到这种结构,但需先用周期图确认显著性,否则易过拟合。
一次性外推会把模型误差累积放大,滚动窗口每次用最新实况重估参数,更贴近实盘递进信息流,概率上更稳。
说明残差还有自相关未被提取,原p或q阶数可能偏低,或者序列存在未被差分掉的季节性,需回看SARIMA扩展。