☰
ARIMA-LSTM混合模型黄金价格预测:残差互补与实战解析
2026/9/26 7:04:21 网站建设 项目流程

简介:这是一套基于ARIMA-LSTM混合模型进行黄金价格高精度预测的金融量化研究项目资料,面向金融量化研究者、数据科学爱好者及黄金投资者。项目侧重将ARIMA对线性趋势的捕捉能力与LSTM对非线性特征的记忆优势结合,并引入利率、通胀、货币政策等宏观经济因素,为投资决策与风险管理提供高精度预测支持。压缩包共30个文件,大小3.58MB,其中xlsx数据表提供实验数据,pkl与h5分别为ARIMA和LSTM模型产物,py脚本与JSON配置支撑复现,19张可视化图及说明文档辅助理解建模过程,整体便于直接学习或二次开发。目前已有142人学习浏览。通过这份资料,读者能系统理解混合建模、残差分析与多模型对比方法,并结合技术指标和宏观经济因素开展黄金走势研判,是金融时间序列分析领域兼具理论与实践价值的参考工具。

1. 黄金价格预测的“高精度”到底卡在哪:ARIMA-LSTM 混合模型的分工逻辑

做金融量化的人大多有个共同感受:黄金价格是时间序列预测里最难啃的硬骨头。它不像股票有明确财报锚点,也不像汇率有清晰的利差框架,长期趋势被全球宏观驱动,短期波动被避险情绪和美元指数来回拉扯。单一 ARIMA 能抓住线性趋势却吃不下突发冲击,单一 LSTM 对长短依赖敏感却经常跟着趋势走而忽略局部拐点。这个基于 ARIMA-LSTM 混合模型的金融量化研究项目,核心思路是把两个模型串成一条流水线——ARIMA 负责线性主趋势,LSTM 负责残差里的非线性成分,用这套组合拳去逼近黄金价格的高精度时间序列预测。适合做金融量化研究、毕业论文或者策略信号验证的从业者。读完你得到的不是一个黑匣子,而是一套能复现、能调参、能解释的建模流程。

2. 为什么是 ARIMA-LSTM:线性残差与非线性残差的互补逻辑

2.1 ARIMA 在黄金价格上的优势与边界

ARIMA 是时间序列预测里的经典线性模型,三个参数 p、d、q 分别对应自回归阶数、差分阶数和移动平均阶数。它之所以在黄金价格上经常被当作基线,是因为黄金日线收盘价呈现明显的长期趋势和自相关结构——昨天涨跌对今天有影响,过去一周的均线位置对当前价格有牵引力。一阶差分之后序列会表现出相对稳定的统计特征,这正是 ARIMA 最擅长的区间。

但 ARIMA 有一个绕不开的边界:它假设未来的变化是过去线性模式的延续。黄金价格在非农数据公布、美联储议息会议、地缘冲突爆发时,会出现典型的非线性跳跃,ARIMA 对此的响应方式只是把异常值吸收进残差项,并不能解释这种跳跃的成因。更麻烦的是,黄金价格还会受到美元指数、实际利率、通胀预期等宏观因子的共同作用,这些因子与金价的关系往往不是直线,而是区间切换、滞后传导的复杂形态。ARIMA 在这种场景下做一步预测尚可,做多步递归预测时误差会迅速累积。

2.2 LSTM 为什么补得上 ARIMA 的缺口

长短期记忆网络(LSTM)通过输入门、遗忘门和输出门控制信息流动,能在几十步到几百步的范围内记住与当前预测相关的历史模式。把残差序列喂给 LSTM,本质上是在做这样一件事:让 LSTM 学习 ARIMA 看不懂的那部分信息,比如波动率集聚、均值回归中的非线性节奏、宏观因子冲击后的衰减路径。

这里有一个很容易被忽视的设计细节:LSTM 的输入不是原始价格,而是残差。如果直接把原始黄金价格喂给 LSTM,模型会被趋势项吸引绝大部分容量,学到的往往只是“昨天的价格接近今天的价格”这种平庸映射,测试集上的 RMSE 看起来不错,但一到拐点就露馅。残差序列近似平稳,LSTM 被迫去拟合真正的非线性结构,特征信号干净得多。

2.3 混合策略对比:串联残差建模 vs 并行加权融合

ARIMA-LSTM 混合模型在实践中有两种主流接法。第一种是串联残差建模:先用 ARIMA 拟合训练集,得到预测值和残差,再用 LSTM 以残差为目标做回归,最终预测值等于 ARIMA 预测值加上 LSTM 残差预测值。第二种是并行加权融合:ARIMA 和 LSTM 各自独立预测完整序列,再用一个权重系数把两个预测结果加权相加,权重可以用回归或者搜索确定。

我的做法倾向于串联残差建模,原因有两点。其一是可解释性——ARIMA 输出的线性趋势可以被单独检验,残差部分的提升幅度可以直接量化,汇报结果时能说清楚“混合模型比单模型好在哪里”;其二是稳定性——并行融合的权重在实际数据上表现漂移很大,训练集上搜索到的最优权重换个时间窗口就失效,而残差建模的 LSTM 只需要输出一个接近零的均值也能保住 ARIMA 的底线。当宏观因子波动剧烈时,残差序列的方差会变大,LSTM 对残差的预测能力也会下降,但整体预测不会跑偏到灾难性程度。

3. 数据准备与特征工程:先把这一层做厚,模型才不容易翻车

3.1 获取黄金价格与宏观因子数据:字段对齐与缺失值处理

这个项目的第一步不是建模,而是把数据表拼齐。黄金价格建议采用伦敦金或 COMEX 黄金连续合约的日线收盘价,因为这类数据连续性好、没有涨跌停板干扰。宏观因子按标题里的“宏观经济因素影响分析”来选,我一般至少配四列:美元指数(DXY)、美国 10 年期国债收益率(US10Y)、VIX 波动率指数、通胀预期或 CPI 同比。黄金在大多数时期与美元指数负相关,与实际利率负相关,与 VIX 在避险时段正相关,这几组关系能帮助 LSTM 学到有效的非线性组合。

数据对齐是第一个坑。宏观因子和黄金价格的交易日历并不完全一致,有些宏观数据在美股非交易时段发布,有些因子本身是月度数据。常见的做法是黄金价格为主表,宏观因子通过 outer join 对齐到交易日索引,非交易日产生的缺失用前向填充补齐,因为利率和 VIX 的隔夜跳变对当日开盘的影响远小于日间趋势。真正需要丢弃的是黄金价格本身的缺失值,连续停盘超过三个交易日的样本我会直接剔除,避免前向填充制造出本不存在的价格形态。

import pandas as pd # gold: 日期索引、close 列;macro: 日期索引、dxy/us10y/vix/cpi 列 df = gold.join(macro, how='outer') df = df.sort_index() # 宏观因子非交易日缺失用最近值填充 df[['dxy', 'us10y', 'vix', 'cpi']] = df[['dxy', 'us10y', 'vix', 'cpi']].ffill() # 黄金价格缺失直接删除,不要用插值 df = df.dropna(subset=['close']) # 再丢弃其余列仍有缺失的行,保持特征完整 df = df.dropna() df.to_csv('gold_features.csv', index=True)

这段代码的逻辑是先保证主价格序列干净,再补齐宏观因子。参数要点是 ffill 只用于宏观因子列,因为美债收益率和美元指数在非交易时段没有新报价,延续上一交易日数值是行业惯例;而 close 列如果出现缺失,说明那天市场没开盘或数据源断档,用插值会伪造一段真实不存在的价格走势,直接影响后续差分和残差计算。

3.2 平稳化与差分:不要过度差分

ARIMA 拟合前必须确认序列平稳,常用的是 ADF 检验。黄金价格原始序列的 ADF p 值通常远高于 0.05,一阶差分后 p 值会显著下降。这里的原则是:能用一阶差分解决就绝不用二阶差分。差分次数每多一次,信噪比就下降一截,因为差分在高频上放大了噪声,ARIMA 拟合的残差会变得更混乱。

LSTM 这边不需要差分,但要做归一化,而且归一化必须遵守一个铁律:只能用训练集的统计量。如果对整个数据集计算均值和标准差再做标准化,测试集的信息就通过统计量泄漏进了训练过程,这在金融时间序列预测里属于致命的未来函数。价格序列的归一化我习惯用 MinMaxScaler,把残差或价格缩放到 [0, 1] 区间,能让 LSTM 的梯度更新更稳定。

from statsmodels.tsa.stattools import adfuller close = df['close'].dropna() diff1 = close.diff().dropna() print(adfuller(close)[1]) # 原始序列 p 值 print(adfuller(diff1)[1]) # 一阶差分 p 值

如果原始序列 p 值为 0.47、差分后 p 值为 0.002,说明一阶差分足够。这里不要机械地追求 p 值越低越好,差分后的序列只要通过 5% 显著性检验就可以进入 ARIMA 建模。过度差分会把趋势信息削成高频噪声,ARIMA 的 AIC 反而会变大。

3.3 数据集划分与滚动窗口设计:防止未来函数

金融时间序列划分训练集和测试集,绝对不能随机打散,必须按时间顺序切。我常用的比例是训练集 70%、验证集 15%、测试集 15%,验证集用来做 LSTM 早停,测试集只在所有模型训练完毕后跑一次。这里要特别强调验证集和测试集都必须是连续的区块,不能用 K 折交叉验证,因为序列数据折叠后会让模型看到未来的片段。

滚动窗口方面,一步预测和递归多步预测的验证方式不同。ARIMA 可以用 fit 后的模型直接 predict 未来 N 个点,但这属于递归预测,误差会逐步累积。更接近实盘的做法是滚动预测:每次只预测下一天,然后真实值落入样本,窗口滑动,重新拟合模型。滚动预测的评估指标更可信,但计算成本高,我通常只在最终验证阶段使用。

train_len = int(len(df) * 0.7) val_len = int(len(df) * 0.15) train = df.iloc[:train_len] val = df.iloc[train_len:train_len + val_len] test = df.iloc[train_len + val_len:] # 切分之后再做归一化,scaler 只 fit 在 train 上 from sklearn.preprocessing import MinMaxScaler scaler_price = MinMaxScaler() train_close = scaler_price.fit_transform(train['close'].values.reshape(-1, 1)) val_close = scaler_price.transform(val['close'].values.reshape(-1, 1)) test_close = scaler_price.transform(test['close'].values.reshape(-1, 1))

注意 val 和 test 的 transform 都使用训练集拟合的 scaler,这是防止未来函数最直接的手段。如果误用了 fit_transform 在测试集上,测试集的分布已经被模型间接看到,最终的 RMSE 会虚高,实盘效果立刻现原形。

4. 建模实现:ARIMA 定阶、LSTM 训练、混合预测的完整通路

4.1 ARIMA 定阶:用自动搜索加人工确认,别只抄 p=1,d=1,q=1

ARIMA 定阶有两条路:一条是看自相关图(ACF)和偏自相关图(PACF),另一条是用 pmdarima 的 auto_arima 做网格搜索。我的习惯是先让 auto_arima 出一版参考阶数,再用 ACF/PACF 图人工确认,避免自动搜索选到过度复杂的参数。p 和 q 的搜索范围控制在 0 到 5,d 固定在 0 或 1,不需要季节性项——黄金日线数据没有稳定的周度或年度季节周期。

from pmdarima import auto_arima # 用训练集定阶,这是最容易被忽略的时间泄漏点 arima_model = auto_arima( train['close'], start_p=0, max_p=5, d=1, max_d=1, start_q=0, max_q=5, seasonal=False, stepwise=True, information_criterion='aic' ) print(arima_model.summary())

auto_arima 的 stepwise=True 会走启发式搜索,速度快,在 p/q 范围较小的时候足够用。information_criterion 选 aic 而不是 bic,因为金融数据样本量通常在几千条量级,AIC 对短期预测精度的响应更灵敏。定阶结果出来后,用 arima_model.predict(n_periods=len(val)) 在验证集上做一次快速预测,如果验证集的残差方差远大于训练集,说明阶数偏低,需要抬高 p 或 q。

4.2 残差提取与 LSTM 数据集构造

ARIMA 在训练集上做拟合预测后,用真实值减去预测值得到残差。这个残差序列就是 LSTM 的监督目标。要注意的是,LSTM 的输入特征不能只包含残差本身,我通常会把残差的历史窗口和前一天的宏观因子做拼接,让模型看得到“残差变化是在美元指数急跌背景下发生的”。这样拼接之后,LSTM 才有机会捕捉到宏观因子对金价的非线性影响,不然只给残差序列就和“用移动平均预测残差”差不多。

import numpy as np # resid 是 ARIMA 在训练集上的残差 resid = train['close'].values - arima_model.predict_in_sample() # 把残差和宏观因子拼成特征矩阵 feature_cols = ['dxy', 'us10y', 'vix'] train_feat = train[feature_cols].values resid_series = resid.reshape(-1, 1) # 构造滑窗样本:每个样本由 window 天的残差 + 当天宏观因子组成 def make_dataset(resid_arr, macro_arr, window=60): X, y = [], [] for i in range(window, len(resid_arr)): # 残差窗口展平 resid_window = resid_arr[i - window:i].flatten() # 宏观因子取当天值和前一日变化量 macro_today = macro_arr[i] macro_change = macro_arr[i] - macro_arr[i - 1] X.append(np.concatenate([resid_window, macro_today, macro_change])) y.append(resid_arr[i]) return np.array(X), np.array(y) X_train, y_train = make_dataset(resid_series, train_feat, window=60) print(X_train.shape, y_train.shape)

滑窗长度我一般设为 60,对应大约三个交易月,这个长度能让 LSTM 看到收益率拐点和宏观因子变化的完整周期。窗口太短抓不住中期趋势,窗口超过 120 会让训练样本数骤减——每多一天窗口就少一个样本,金融数据的样本量本来就不富裕。特征拼接这里,宏观因子同时放入当日值和前一日的差值,是让模型能区分“高位”和“正在上升”两种状态,这在金价与美元指数的负相关关系里非常关键。

4.3 LSTM 模型结构与训练参数:别一上来就堆大网络

LSTM 网络结构不需要过于复杂,黄金价格残差的非线性没有图像或语音那么深。我常用的结构是两层 LSTM 加 Dropout,再接一个全连接输出层。第一层 LSTM 用 return_sequences=True 输出完整序列,第二层只输出最后一步,这样既保留了局部模式提取能力,又不至于让参数量爆炸。

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model = Sequential() model.add(LSTM(units=64, return_sequences=True, input_shape=(X_train.shape[1], 1))) model.add(Dropout(0.2)) model.add(LSTM(units=32, return_sequences=False)) model.add(Dropout(0.2)) model.add(Dense(units=1)) model.compile(optimizer='adam', loss='mse', metrics=['mae']) early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=100, batch_size=32, callbacks=[early_stop], verbose=2 )

units=64 是起点,黄金价格残差的复杂度远低于自然语言,64 个单元足够表达多数非线性模式。Dropout 设置为 0.2,作用在每层 LSTM 的输出上,防止模型把训练集里的噪声当作规律记住。patience=10 是早停的耐心值,如果验证集 loss 连续 10 个 epoch 不下降就停止训练并回滚到最优权重。金融数据噪声大,靠固定 epoch 数训练很容易过拟合,早停是必须的。

训练过程中需要盯两个指标:训练集 loss 和验证集 loss。训练集 loss 持续下降而验证集 loss 高位震荡,说明模型在背诵噪声,应该降低 units 或增大 Dropout。如果两者都在高位下不去,说明残差序列里确实没有可学的结构,这时候要回到 ARIMA 定阶,把残差做得更“白”一点。

4.4 混合预测与效果评估:RMSE、MAPE、回测三件套

混合模型的最终预测值由两部分相加:ARIMA 的线性预测加上 LSTM 对残差的预测。评估时不能只看某一个指标,我会同时计算 RMSE、MAPE,并额外看一眼预测方向准确率——对于交易决策,方向比幅度更重要。下面的代码展示了测试集上的完整评估流程。

# 测试集 ARIMA 预测 test_len = len(test) arima_pred = arima_model.predict(n_periods=test_len) # 测试集宏观特征 X_test, y_test = make_sequential_dataset( np.array(test['close'].values - arima_pred), test[feature_cols].values, window=60 ) # LSTM 残差预测 resid_pred_scaled = model.predict(X_test).flatten() resid_pred = np.array([scaler_target.inverse_transform([[v]])[0][0] for v in resid_pred_scaled]) # 混合预测 = ARIMA 预测 + LSTM 残差预测 与真实值求 RMSE hybrid_pred = arima_pred[-len(resid_pred):] + resid_pred true_close = test['close'].values[-len(hybrid_pred):] def rmse_mape(true, pred): rmse = np.sqrt(np.mean((true - pred) ** 2)) mape = np.mean(np.abs((true - pred) / true)) * 100 return rmse, mape arima_rmse, arima_mape = rmse_mape(true_close, arima_pred[-len(hybrid_pred):]) hybrid_rmse, hybrid_mape = rmse_mape(true_close, hybrid_pred) # 方向准确率 direction_acc = np.mean(np.sign(np.diff(true_close)) == np.sign(np.diff(hybrid_pred))) print(f"ARIMA: RMSE={arima_rmse:.2f}, MAPE={arima_mape:.2f}%") print(f"Hybrid: RMSE={hybrid_rmse:.2f}, MAPE={hybrid_mape:.2f}%") print(f"Hybrid 方向准确率={direction_acc:.2%}")

这里要特别说明索引对齐:ARIMA 的预测起点是测试集开头,而 LSTM 因为滑窗会消耗前 60 个样本作为输入,所以两者的预测序列长度不同,混合前必须用切片对齐到最后一段公共区间。我见过不少翻车案例,就是没对齐直接相加,结果混合模型比单模型还差。

判断混合模型是否有效的标准很直接:混合后的 RMSE 和 MAPE 是否同时优于两个基线。如果只改善 RMSE 而 MAPE 变差,说明模型对大价格样本的拟合变好、小价格样本变差,这在黄金这种价格中枢不断上移的市场里并不可靠,需要进一步检查预测误差在不同价格区间的分布。

5. 避坑排查:金融时间序列预测最容易翻车的 5 个地方

5.1 归一化泄漏

现象:验证集和测试集上的 RMSE 很好看,但画出来的预测曲线整体滞后于真实曲线,滞后幅度大约是一到两个交易日。

原因:归一化时对全量数据调用了 fit_transform,测试集的均值、方差信息通过 scaler 泄漏给了训练过程。LSTM 学到的是“基于全局统计量的偏移”,而不是真正的动态模式,换到实盘新数据上马上失效。

解决:严格按时间切分后,只在训练集上 fit scaler,验证集和测试集只用 transform。这个规则适用于价格序列、残差序列和所有宏观因子列。

5.2 ARIMA 残差不白噪声

现象:LSTM 拟合残差的验证集 loss 居高不下,预测残差几乎等于一个常数。

原因:ARIMA 的 p、q 阶数选得过低,或者数据里存在结构性突变,导致残差序列里仍然有显著自相关。LSTM 拿到一个非平稳的残差序列,难以学到稳定的映射关系。

解决:对 ARIMA 残差做 Ljung-Box 检验。如果 p 值小于 0.05,先用 auto_arima 提高 p/q 上限重新拟合,再检验一次;如果仍然不白噪声,考虑在 ARIMA 中加入确定性的趋势项或外生变量。

5.3 混合模型效果不如单模型

现象:混合模型的 RMSE 比单独 LSTM 直接预测原始价格还要高。

原因有两种常见可能。第一种是残差序列方差太小,LSTM 的 MSE 损失函数把它忽略成接近零的输出,混合结果退化成纯粹的 ARIMA;第二种是滑窗特征里宏观因子噪声太大,LSTM 学到了错误的因子响应关系,反而干扰残差预测。

解决:先打印残差的标准差与价格标准差的比例,如果小于 1%,说明 ARIMA 已经吸收了绝大多数有效信息,混合模型的提升空间本就不大。此时可以改用并行加权融合,或者在 LSTM 的损失函数里把残差方差归一化后再训练。

5.4 递归预测误差滚雪球

现象:ARIMA 一步预测的 RMSE 正常,但 predict(n_periods=100) 的多步预测在后期完全偏离真实值。

原因:递归预测每一步都把上一步的预测值当作输入,一步误差会在后续步骤被放大,黄金价格的长记忆性让误差累积得更快。

解决:多步预测改用直接多步(direct multi-step)策略:对第 t+1 天、第 t+3 天、第 t+5 天分别建立独立的预测模型。评估时用滚动预测,训练时用真实值逐步推进,避免让模型学习“拿自己的错误输出当输入”。

5.5 宏观因子未来值不可得

现象:回测结果优秀,模拟盘却差得一塌糊涂,尤其是 CPI、非农数据发布后模型预测偏差巨大。

原因:回测时用了 CPI 等宏观因子的最终修订值,而这些数据在实盘当天是拿不到当月数值的。模型学了未来信息,回测自然虚高。

解决:宏观因子要么滞后一期,要么用市场预期值代替实际值。处理方式是构建因子列时统一 shift(1) 或 shift(报告期数),确保回测中的每个时间点使用的都是当时可获得的数据。

6. 从预测到决策:用滚动回测和信号胜率验证模型的实战边界

ARIMA-LSTM 混合模型跑通之后,你得到的是一组预测序列,但量化研究最终要落到决策上。我看一个预测模型靠不靠谱,从来不看测试集 RMSE,而是看两件事:滚动回测的稳定性,以及预测信号转成交易规则后的胜率与盈亏比。

滚动回测的做法是:把测试集切成若干段,每段结束时用当前所有可用的数据重新拟合 ARIMA 和 LSTM,再对下一段做预测。这个过程模拟了实盘中的定期重训,能暴露出模型在参数漂移下的真实表现。如果混合模型的优势在第一段明显、第二段消失、第三段反弹,说明它依赖特定的市场状态,使用时需要加一个状态过滤开关,比如只在真实波动率处于中低水平时启用 LSTM 残差校正。

信号验证的常用方法是把预测结果离散化:预测涨且实际涨记为一次成功做多,预测跌且实际跌记为一次成功做空。门槛是方向准确率至少 55%,盈亏比至少 1.5,否则模型给出的信号不足以覆盖交易成本和滑点。我会额外计算一段最长连续亏损次数,这个指标直接决定策略的资金管理参数,比单次预测的 RMSE 更贴近实战。

# 简化的信号评估 pred_dir = np.sign(np.diff(hybrid_pred)) true_dir = np.sign(np.diff(true_close)) win = (pred_dir == true_dir).mean() # 盈亏比:预测正确时的平均幅度 / 预测错误时的平均幅度 gain = np.abs(np.diff(true_close)[pred_dir == true_dir]).mean() loss = np.abs(np.diff(true_close)[pred_dir != true_dir]).mean() print(f"方向准确率:{win:.2%}") print(f"盈亏比:{gain / loss:.2f}")

我的个人习惯是每次迭代都先跑一次滚动回测再做信号验证,滚动回测不过关的模型,信号指标再好也直接放弃——因为那大概率是对特定历史区间的过拟合。ARIMA-LSTM 混合模型的真正价值,不是把 RMSE 压到多低,而是让你在解释不了市场的时候,至少能分清哪些预测来自线性趋势、哪些来自非线性修正,以及哪些是模型在硬猜。这套流程做完,如果你在黄金价格时间序列预测上拿到比单一模型更稳的样本外误差和方向胜率,说明你的混合模型设计是成立的;如果没有提升,问题的根源往往不在网络结构,而在数据和特征泄露。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询