数据科学与机器学习(第四十二部分):使用Python中的ARIMA模型进行外汇时间序列预测 您需要了解的一切·进阶篇
(2/3)· 从平稳性差分到SARIMA扩展,手把手拆解ARIMA在MT5外汇数据上的落地盲区
欧元兑美元上跑通ARIMA(0,1,0)
确定好 p、d、q 之后,训练 ARIMA 的思路和传统机器学习一致:先切分训练集与测试集,在训练集上拟合,再回测预测并可视化,最后用损失函数评估样本外表现。 下面这段 Python 代码直接对 EURUSD 收盘价序列拟合了 ARIMA(0,1,0),也就是纯随机游走的差分模型。注意它和 MT5 里靠历史 K 线手动算差分不同,这里交给 statsmodels 一站式出报告。
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())
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) 也就是随机游走的首次差分形式去拟合训练段,并打印摘要、画出实际与预测均值对比。 外汇与贵金属价格具备高杠杆与跳空风险,这类单变量差分模型只适合做惯性参考,实盘信号仍需结合价格行为结构过滤。
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 附近,再谈实盘过滤。
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() 外汇与贵金属杠杆高、跳空频繁,残差诊断只是建模一环,实盘前务必用历史数据交叉验证,避免把样本内巧合当规律。
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 终端里搭起来验。
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 项,直接做差分滤波更省算力。