ARIMA外汇预测基础:用Python给EURUSD时间序列建模的入门全景(基础篇)
(1/3)·从时间序列定义到ARIMA三参数,一篇补齐非季节性外汇预测的理论地基
很多交易者把历史价格直接丢进任意模型就等信号,却分不清单变量和多变量预测的根本差异。ARIMA只吃自身滞后值,硬塞 exogenous 变量只会让残差悄悄失真。先搞懂平稳性和差分,才谈得上拿它碰 EURUSD 的分钟线。
「从时间序列预测切入ARIMA」
做价格行为分析的交易者,常把K线当成纯形态游戏,但EURUSD的小时图里藏着明显的自相关结构,这恰恰是时间序列模型能啃下的骨头。 所谓时间序列预测,核心是用历史观测值的顺序依赖关系外推未来,外汇与贵金属这类高波动品种,用错了滞后阶数就会把噪声当信号,杠杆市场高风险,模型输出只能当概率参考。 本系列以MetaTrader 5导出的EURUSD报价为样本,先讲清ARIMA的三个核心件:差分阶数d、自回归p、移动平均q,再落到Python里真跑一遍,而不是空谈理论。
时间序列预测的两个核心变量与分类
时间序列预测的本质,是用按时间排序的历史数据点去推断未来取值。任何能拿来做时序分析的数据,最少要有两个字段:一个是时间本身,作为自变量标记每条观测发生的时刻;另一个是目标变量,也就是你真正想往前推的数值,比如每日收盘价、每小时气温或每分钟访问量。 剥开花哨定义,落地时只分两种做法。单变量预测只用目标变量自身的历史去猜未来,例如拿股票收盘价推收盘价;多变量预测则引入其他输入特征共同建模。本文后续要用的 ARIMA 属于前者,它不依赖外部因子,只吃自己的历史。 对外汇与贵金属交易者而言,纯单变量模型在跳空、数据中断的行情里容易失真,这是高风险品种的客观约束,用 ARIMA 前先确认数据连续性。
◍ 拆开 ARIMA 的三个旋钮
ARIMA 全名是自回归积分移动平均,本质是用序列自己的滞后值和滞后误差去解释当下、外推未来。只要一段行情数据不是纯随机白噪声、带点可捕捉的规律且非季节性,就能塞进这个框架里跑预测。 模型由 (p, d, q) 三个阶数定义,每个数都是独立旋钮。p 是自回归阶数,决定拿多少期过去的 Y 当解释变量;q 是移动平均阶数,控制把几期过去的预测误差纳入方程。 d 最容易被忽略:它代表让序列变平稳所需的最少差分次数。平稳一般用「当期减前一期」实现,原序列已经平稳时 d=0,复杂些的可能要差两三次才压住趋势和周期。 在 MT5 里用 iMA 或自写差分函数先确认 d,再回头定 p、q,比盲目调参更省事。外汇与贵金属波动受事件驱动,ARIMA 仅刻画历史惯性,实盘高风险,信号失效概率不低。
「拆开ARIMA的三个字母」
ARIMA这个名字直接对应三块机制:AR、I、MA。把这三块拆开看,比盯着完整公式容易懂得多。 AR(p)是自回归部分,思路接近线性回归,但预测因子用的是序列自己的滞后值。当前值 y_t 由常数项、p个滞后项 y_{t-1}…y_{t-p} 乘上各自系数 φ,再加误差 ε_t 得到。系数 φ 大小直接反映某期滞后对现在的影响权重。 I(d)做差分。对一个观测值减去它的前期观测值,目的是消掉趋势和季节性,逼出平稳序列——均值和方差在一段时间内恒定。不平稳的序列喂给模型,拟合的往往是噪声而不是规律,这一步不能跳。 MA(q)看的是当前观测和过去预测误差的关系。模型用滞后误差项 ε_{t-1}…乘上 θ 参数,估算随机冲击的残留影响,对抓短期波动特别有用。外汇与贵金属价格受突发消息驱动明显,MA部分捕捉这类冲击的能力值得在MT5里实测验证。
用Python给EURUSD定ARIMA参数
ARIMA把自回归(AR)、差分(I)、移动平均(MA)拼在一起,真正卡人的地方是(p,d,q)三个阶数怎么取。参数选歪了,模型要么欠拟合要么过度差分,拿去推汇率下一步纯属碰运气。外汇和贵金属杠杆高,这类统计模型只给概率倾向,不能直接当入场指令。 AR阶数p看偏自相关函数(PACF)图。PACF剔掉了中间滞后项的干扰,只留序列和某一滞后项的纯相关;图里相关系数掉到接近0且不再显著的那个滞后阶,就是p的上限。跑EURUSD日线1000根bar的PACF,0阶之后全不显著,所以p=0。 d差分阶数靠ADF检验把关。原假设是序列非平稳,p值小于0.05才拒绝、认定平稳。EURUSD收盘价ADF的p值跑出来是0.3707,远大于0.05,说明本来就不平稳。做一阶差分后ACF快速衰减,二阶差分没明显更好,故d=1。 MA阶数q对应ACF图,看消掉平稳序列残留自相关要几个滞后误差项。同样样本下q最佳也是0。手动看图和auto_arima自动选参结果一致,说明这套原始方法在EURUSD日线上够用。 别把正态当圣经:ADF只验平稳性,不保证差分后分布适合交易,真要上MT5验证建议先小样本跑一遍再放大。
pip install -r requirements.txt # Importing required libraries class="kw">import pandas as pd class="kw">import numpy as np class="kw">import MetaTrader5 as mt5 # Use auto_arima to automatically select best ARIMA parameters class="kw">import seaborn as sns class="kw">import matplotlib.pyplot as plt class="kw">import warnings class="kw">import os # Suppress warning messages for cleaner output warnings.filterwarnings("ignore") # Set seaborn plot style for better visualization sns.set_style("darkgrid") # Getting(EUR/USD OHLC data) from MetaTrader5 mt5_exe_file = r"c:\Users\Omega Joctan\AppData\Roaming\Pepperstone MetaTrader class="num">5\terminal64.exe" # Change this to your MetaTrader5 path if not mt5.initialize(mt5_exe_file): print("Failed to initialize Metatrader5, error = ",mt5.last_error) exit() # select a symbol into the market watch symbol = "EURUSD" timeframe = mt5.TIMEFRAME_D1 if not mt5.symbol_select(symbol, True): print(f"Failed to select {symbol}, error = {mt5.last_error}") mt5.shutdown() exit() rates = mt5.copy_rates_from_pos(symbol, timeframe, class="num">1, class="num">1000) # Get class="num">1000 bars historically df = pd.DataFrame(rates) print(df.head(class="num">5)) print(df.shape) from statsmodels.graphics.tsaplots class="kw">import plot_pacf class="kw">import matplotlib.pyplot as plt plt.figure(figsize=(class="num">6,class="num">4)) plot_pacf(series.diff().dropna(), lags=class="num">5) plt.title("Partial Autocorrelation Plot") plt.xlabel(&class="macro">#x27;Lag&class="macro">#x27;) # X-axis label plt.ylabel(&class="macro">#x27;PACF&class="macro">#x27;) # Y-axis label plt.savefig("pacf plot.png") plt.show() plt.figure(figsize=(class="num">7,class="num">5)) sns.lineplot(df, x=df.index, y="Close") plt.savefig("close prices.png") from statsmodels.tsa.stattools class="kw">import adfuller series = df["Close"] result = adfuller(series) print(f&class="macro">#x27;p-value: {result[class="num">1]}&class="macro">#x27;) p-value: class="num">0.3707268514544181 # Original Series fig, axes = plt.subplots(class="num">3, class="num">2, sharex=True, figsize=(class="num">9, class="num">9)) axes[class="num">0, class="num">0].plot(series); axes[class="num">0, class="num">0].set_title(&class="macro">#x27;Original Series&class="macro">#x27;) plot_acf(series, ax=axes[class="num">0, class="num">1]) # 1st Differencing axes[class="num">1, class="num">0].plot(series.diff().dropna()); axes[class="num">1, class="num">0].set_title(&class="macro">#x27;1st Order Differencing&class="macro">#x27;) plot_acf(series.diff().dropna(), ax=axes[class="num">1, class="num">1]) # 2nd Differencing
◍ 用 Python 给价差序列定阶
把原始序列做一阶、二阶差分后分别画时序图和 ACF,能直观看出 d=1 或 d=2 哪个更接近平稳。对欧元兑美元这类高流动性品种,差分后用 ADF 检验,一阶差分 p-value 落到 0.0,二阶同样 0.0,说明一阶就已经能把单位根剔除干净。 接着看 PACF 截尾位置,lags 拉到 20,一阶差分后 PACF 在 lag=1 之后快速衰减,暗示 MA 项不用堆太高。用 auto_arima 跑非季节性逐步搜索,trace 打开后 AIC 对比一目了然:ARIMA(0,1,0) 无截距项 AIC=-35538.731 最优,比带截距的 (0,1,0) 还低一点,总拟合耗时 6.5 秒左右。 这套流程不依赖 MT5,但结论可以直接拿去对照 MT5 里自带的 ARIMA 类指标参数。外汇与贵金属杠杆高、价差跳变频繁,模型阶数只是概率意义上的最优,实盘前务必用历史数据重跑验证。
axes[class="num">2, class="num">0].plot(series.diff().diff()); axes[class="num">2, class="num">0].set_title(&class="macro">#x27;2nd Order Differencing&class="macro">#x27;) plot_acf(series.diff().diff().dropna(), ax=axes[class="num">2, class="num">1]) plt.savefig("acf plots.png") plt.show() result = adfuller(series.diff().dropna()) print(f&class="macro">#x27;p-value d=class="num">1: {result[class="num">1]}&class="macro">#x27;) result = adfuller(series.diff().diff().dropna()) print(f&class="macro">#x27;p-value d=class="num">2: {result[class="num">1]}&class="macro">#x27;) p-value d=class="num">1: class="num">0.0 p-value d=class="num">2: class="num">0.0 plt.figure(figsize=(class="num">7,class="num">5)) plot_pacf(series.diff().dropna(), lags=class="num">20) plt.title("Partial Autocorrelation Plot") plt.xlabel(&class="macro">#x27;Lag&class="macro">#x27;) # X-axis label plt.ylabel(&class="macro">#x27;PACF&class="macro">#x27;) # Y-axis label plt.savefig("pacf plot finding q.png") plt.show() from pmdarima.arima class="kw">import auto_arima model = auto_arima(series, seasonal=False, trace=True) print(model.summary()) Performing stepwise search to minimize aic ARIMA(class="num">2,class="num">1,class="num">2)(class="num">0,class="num">0,class="num">0)[class="num">0] intercept : AIC=-class="num">35532.282, Time=class="num">3.21 sec ARIMA(class="num">0,class="num">1,class="num">0)(class="num">0,class="num">0,class="num">0)[class="num">0] intercept : AIC=-class="num">35537.068, Time=class="num">0.49 sec ARIMA(class="num">1,class="num">1,class="num">0)(class="num">0,class="num">0,class="num">0)[class="num">0] intercept : AIC=-class="num">35537.492, Time=class="num">0.59 sec ARIMA(class="num">0,class="num">1,class="num">1)(class="num">0,class="num">0,class="num">0)[class="num">0] intercept : AIC=-class="num">35537.511, Time=class="num">0.74 sec ARIMA(class="num">0,class="num">1,class="num">0)(class="num">0,class="num">0,class="num">0)[class="num">0] : AIC=-class="num">35538.731, Time=class="num">0.25 sec ARIMA(class="num">1,class="num">1,class="num">1)(class="num">0,class="num">0,class="num">0)[class="num">0] intercept : AIC=-class="num">35535.683, Time=class="num">1.22 sec Best model: ARIMA(class="num">0,class="num">1,class="num">0)(class="num">0,class="num">0,class="num">0)[class="num">0] Total fit time: class="num">6.521 seconds