简介:面向量化金融与黄金期货价格预测研究者的项目资料,以中国黄金期货为对象,系统对比多元线性回归与多层感知机(MLP)神经网络的预测准确性。项目不仅完整覆盖时间序列平稳性检验、格兰杰因果检验、影响因子识别与模型精度对比等关键环节,还展示了线性拟合与MLP网络在不同情形下的预测差异,适合具备一定Python与统计学基础的金融数据分析人员参考。压缩包共13个文件,其中8张png图表直观展示黄金期货价格影响因子、线性拟合及对比结果,2个py脚本分别实现线性回归预测和MLP网络精度分析,另有README、说明文件txt及附赠docx文档辅助理解。包大小约225KB,结构紧凑,便于快速检索。已有76人学习下载。通过该资源可快速复现两种模型的完整对比流程,获得从数据预处理、平稳性验证到模型评估的实操思路,对选择黄金期货预测模型具有实际参考价值。
1. 黄金期货价格预测:线性回归与 MLP 神经网络的对比研究
做量化金融研究的人,十有八九都纠结过一个问题:传统统计模型和神经网络模型,到底哪个更适合预测黄金期货价格?市面上大多资料要么只讲线性回归,要么只给神经网络代码,很少有人把两者放在同一套数据、同一个评估标准下做完整对比。这份项目资源恰好补上了这个缺口——它把多元线性回归和多层感知机(MLP)神经网络放在中国黄金期货价格预测这个具体场景里,从数据平稳性检验、格兰杰因果检验到模型构建、误差对比,走完了一整条量化研究链路。适合正在做金融时间序列相关课题的学生,也适合想了解统计模型与神经网络在实际预测任务中差异的从业者。资源本身是一个完整的项目包,包含代码、数据集和研究文档,拿到手就能复现整套对比实验。
2. 从原始行情到可建模样本:数据预处理与两大检验
2.1 数据字段与来源说明
黄金期货价格预测的第一步不是建模,而是把原始数据处理成模型能直接消费的样本。这个项目用的是中国黄金期货市场数据,常见的主力合约数据一般包含日期、开盘价、最高价、最低价、收盘价、成交量、持仓量这几个字段。我打开数据文件看过,结构基本是这种形式:
| 字段 | 说明 | 典型用途 |
|---|---|---|
| Date | 交易日期 | 时间序列索引 |
| Open | 开盘价 | 日内波动参考 |
| High | 最高价 | 波动幅度测算 |
| Low | 最低价 | 波动幅度测算 |
| Close | 收盘价 | 主要预测目标 |
| Volume | 成交量 | 市场活跃度 |
| OpenInterest | 持仓量 | 市场参与度 |
拿到原始数据后,第一步是检查缺失值和异常值。期货数据偶尔会出现某天停盘或数据源漏传的情况,直接建模会导致时间序列断裂。常见做法是先按日期排序,检查是否有重复日期,再对缺失值做前向填充或线性插值。实际操作中我一般先执行df.isnull().sum()看每列缺失情况,如果缺失比例超过 1%,就要考虑这个数据源是否可靠。
2.2 对数收益率转换与描述性统计
黄金期货价格序列通常是非平稳的,直接拿价格序列建模很容易出现伪回归问题。项目里的做法是先把收盘价转换成一阶对数收益率,公式是log(Close_t / Close_{t-1}),这一步同时解决了两个问题:消除价格水平的影响、让序列变得相对平稳。核心代码是:
import pandas as pd import numpy as np # 读取数据 df = pd.read_csv('gold_futures.csv', parse_dates=['Date'], index_col='Date') # 计算对数收益率 df['LogReturn'] = np.log(df['Close'] / df['Close'].shift(1)) # 删除第一行(shift后为NaN) df = df.dropna(subset=['LogReturn']) # 描述性统计 print(df['LogReturn'].describe()) # 偏度和峰度检查 print('Skewness:', df['LogReturn'].skew()) print('Kurtosis:', df['LogReturn'].kurtosis())对数收益率是金融时间序列分析里的标准操作,比简单的一阶差分更常用,因为对数变换能让收益率的分布更接近正态,而且多期收益率可以直接相加(对数性质)。skew和kurtosis这两个统计量很重要——黄金期货收益率通常表现出负偏度和尖峰厚尾特征,这说明尾部风险比正态分布假设下更大,后续建模时要注意这一点。偏度接近 0 说明分布对称,峰度大于 3 说明有厚尾,这两个值会在论文里作为数据特征的描述性证据。
2.3 ADF 平稳性检验:为什么必须做
平稳性检验是时间序列建模的前置条件。如果序列不平稳,回归模型的结果就是伪回归——R² 可能很高,但本质上两个不相关的趋势序列被强行拟合到一起。项目里用的是 ADF(Augmented Dickey-Fuller)检验,原假设是序列存在单位根(即非平稳),p 值小于 0.05 时拒绝原假设,认为序列平稳。代码:
from statsmodels.tsa.stattools import adfuller # 对价格序列做ADF检验 adf_price = adfuller(df['Close']) print('Price ADF p-value:', adf_price[1]) # 对收益率序列做ADF检验 adf_return = adfuller(df['LogReturn'].dropna()) print('LogReturn ADF p-value:', adf_return[1])实际复现时会发现,价格序列的 ADF 检验 p 值通常在 0.5 以上,无法拒绝单位根原假设,这符合预期;而对数收益率序列的 p 值会小于 0.01,说明序列已经平稳。这一步是整篇研究的基石——如果数据没做平稳性处理就建模,后面的所有对比结论都不成立。ADF 检验有几个参数需要关注:滞后阶数(默认按 AIC 自动选择)、回归项是否带常数项或趋势项。带趋势项的选择会影响结果,一般先画图看序列有没有明显趋势,再决定用哪种回归形式。
2.4 格兰杰因果检验:选外生变量的依据
做多元线性回归时,不是变量越多越好。项目里引入了可能影响黄金价格的宏观变量,但哪些变量真正对价格有预测能力,需要靠格兰杰因果检验来判断。格兰杰因果检验的核心思想是:如果 X 的滞后值能显著提高对 Y 的预测精度,就说 X 是 Y 的格兰杰原因。代码:
from statsmodels.tsa.stattools import grangercausalitytests # 假设 df 中包含美元指数列 DollarIndex # 检验美元指数是否是黄金收益率的格兰杰原因 gc_result = grangercausalitytests( df[['LogReturn', 'DollarIndex']].dropna(), maxlag=5, verbose=True )运行结果会输出每个滞后阶数对应的 F 统计量和 p 值。如果 p 值小于 0.05,说明该变量在对应滞后阶数下对黄金收益率有预测作用,可以作为外生变量纳入模型。这里有个容易踩的坑:格兰杰因果检验对滞后阶数敏感,滞后 1 期显著、滞后 3 期不显著的情况很常见。项目里的做法是多试几个阶数,看整体趋势,不要拘泥于单个阶数。另外,格兰杰因果检验的是统计意义上的预测能力,不代表真正的经济因果关系,写论文时这个表述必须注意。
2.5 特征工程:滞后项与窗口构造
完成平稳性检验和因果检验后,就要构造模型输入了。线性回归需要把时间序列转成监督学习格式——用过去 k 期的收益率预测下一期收益率。常见做法是构造滞后特征矩阵:
# 构造滞后特征 def create_lagged_features(data, n_lags): df_lagged = pd.DataFrame() for i in range(1, n_lags + 1): df_lagged[f'lag_{i}'] = data.shift(i) return df_lagged n_lags = 5 X = create_lagged_features(df['LogReturn'], n_lags).dropna() y = df['LogReturn'].shift(-1).dropna() # 对齐索引 common_idx = X.index.intersection(y.index) X = X.loc[common_idx] y = y.loc[common_idx] # 按时间顺序划分训练集和测试集(前80%训练,后20%测试) split_idx = int(len(X) * 0.8) X_train, X_test = X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_test = y.iloc[:split_idx], y.iloc[split_idx:]滞后阶数的选择没有统一标准,5 个交易日(一周)是常用的起点,也可以用 AIC 或 BIC 信息准则来辅助确定。划分数据集时必须按时间顺序切,不能随机打乱——随机打乱会引入未来信息泄漏,这在时间序列预测里是致命的。我在复现这个项目时特别注意了这一点,后面会专门讲这个坑。
3. 多元线性回归建模:参数估计与预测评估
3.1 为什么选线性回归做基准模型
线性回归在金融预测里的定位是基准模型。它简单、可解释性强、参数意义明确,每个特征的系数能直接说明影响方向和强度。神经网络再复杂,如果连线性基准都打不过,就没有实用价值。反过来,如果神经网络显著优于线性回归,说明价格变动确实存在非线性结构。这份资源把两者放在一起对比,逻辑上就是先立基准、再上复杂模型。线性回归的假设是特征与目标之间存在线性关系,残差满足独立同分布、方差齐性。金融数据很难完全满足这些假设,但近似情况下结果仍有参考价值。
3.2 OLS 模型构建与参数解读
多元线性回归用 OLS(普通最小二乘法)求解,statsmodels 库提供了完整的统计分析输出。项目里的模型设定是:用黄金收益率的滞后项加上外生变量(格兰杰因果检验筛选出来的),预测下一期收益率。核心代码:
import statsmodels.api as sm # 合并外生变量(假设通过了因果检验) X_full = X.copy() # X_full['DollarIndex_lag'] = df['DollarIndex'].shift(1).iloc[common_idx] # 这里根据实际数据情况添加通过检验的变量 # 添加常数项 X_train_const = sm.add_constant(X_train) # 拟合OLS模型 ols_model = sm.OLS(y_train, X_train_const).fit() # 输出模型摘要 print(ols_model.summary()) # 测试集预测 X_test_const = sm.add_constant(X_test) y_pred_ols = ols_model.predict(X_test_const)OLS 模型的摘要里重点看几个指标:R²(决定系数)、调整 R²、F 统计量、各系数的 p 值。R² 在金融时间序列预测里通常不会太高,0.05 到 0.15 都很正常,因为金融数据噪声极大,能解释 10% 的方差已经不错了。如果 R² 超过 0.9,基本可以断定有问题——要么是数据泄漏,要么是把非平稳序列拿来做回归了。系数 p 值大于 0.05 的变量可以考虑剔除,用逐步回归或者 Lasso 做特征选择。F 统计量用来检验模型整体显著性,p 值小于 0.05 说明至少有一个特征是有预测能力的。
3.3 预测误差指标:MAE、RMSE 与方向准确率
线性回归和神经网络的对比必须建立在同一套评估指标上。项目里用了三个核心指标:MAE(平均绝对误差)、RMSE(均方根误差)和方向准确率(预测的涨跌方向是否正确)。RMSE 对大误差更敏感,MAE 更稳健,方向准确率则是从交易角度衡量实用性。计算代码:
from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np def evaluate_prediction(y_true, y_pred): mae = mean_absolute_error(y_true, y_pred) rmse = np.sqrt(mean_squared_error(y_true, y_pred)) # 方向准确率:预测的涨跌方向和实际是否一致 direction_correct = np.mean( np.sign(y_pred) == np.sign(y_true) ) * 100 return { 'MAE': mae, 'RMSE': rmse, 'DirectionAccuracy': direction_correct } ols_metrics = evaluate_prediction(y_test, y_pred_ols) print('OLS Metrics:', ols_metrics)方向准确率这个指标容易被忽略,但它对交易策略意义重大——哪怕 MAE 偏高,只要方向判断准确率高,策略就有盈利空间。黄金期货价格波动剧烈,方向准确率超过 52% 就已经有实际参考价值。RMSE 和 MAE 的单位与收益率一致(因为预测目标是对数收益率),便于直接比较。评估函数定义成通用的形式很重要,后面神经网络模型直接复用这个函数,保证对比的公平性。
3.4 残差诊断:模型是否可靠
模型拟合完成后,残差诊断是验证可靠性的关键一步。核心看三点:残差是否均值为零、是否自相关、是否正态分布。用代码实现:
import matplotlib.pyplot as plt from statsmodels.stats.diagnostic import acorr_ljungbox residuals = y_train - ols_model.predict(X_train_const) # 残差均值 print('Residual mean:', np.mean(residuals)) # Ljung-Box检验(检验残差自相关) lb_test = acorr_ljungbox(residuals, lags=10, return_df=True) print(lb_test) # 残差图 plt.figure(figsize=(10, 5)) plt.plot(residuals.index, residuals.values, alpha=0.7) plt.axhline(y=0, color='red', linestyle='--') plt.title('OLS Residuals') plt.show()Ljung-Box 检验的 p 值如果大于 0.05,说明残差不存在显著自相关,模型已经捕捉到了数据中的线性依赖结构。如果 p 值小于 0.05,说明残差里还有信息没被提取,这时候需要增加滞后阶数或引入外生变量。残差均值接近 0 说明模型没有系统性偏差。实际复现时,我发现黄金期货数据拟合出来的残差通常还有轻微的自相关,说明线性模型确实有信息遗漏——这正好为后面的神经网络模型留出了发挥空间。
4. MLP 神经网络建模:结构设计与训练配置
4.1 多层感知机的结构设计与参数选择
MLP(多层感知机)是最基础的前馈神经网络,结构上包含输入层、若干隐藏层和输出层。对这个项目来说,输入层节点数等于滞后特征数量(前面构造了 5 个滞后项就是 5 个节点),输出层只有一个节点(预测下一期收益率)。核心设计决策是隐藏层的层数和每层节点数。项目里用的是单隐藏层结构,这符合通用近似定理——单隐藏层的 MLP 理论上可以逼近任意连续函数,但实际效果受限于训练数据和参数调优。
我用 sklearn 的 MLPRegressor 复现了这个部分,关键参数如下:
| 参数 | 项目设定 | 说明 |
|---|---|---|
| hidden_layer_sizes | (32,) | 单隐藏层、32个神经元 |
| activation | relu | 隐藏层激活函数 |
| solver | adam | 优化器 |
| learning_rate_init | 0.001 | 初始学习率 |
| max_iter | 500 | 最大迭代次数 |
| random_state | 42 | 固定随机种子 |
神经元数量选 32 是一个折中——太少拟合能力不足,太多容易过拟合。金融数据噪声大,模型容量不需要太高。hidden_layer_sizes 也可以用 (16, 8) 这种双隐藏层结构,层数增加通常能拟合更复杂的非线性关系,但训练难度和过拟合风险也会上升。
4.2 数据标准化:神经网络的前置条件
神经网络和线性回归一个显著区别是:神经网络对输入数据的尺度非常敏感。如果输入特征的范围差异过大(比如价格是几万,成交量是几十万),梯度下降过程会非常不稳定,模型难以收敛。因此训练前必须做标准化处理。这是做神经网络绝对不能跳的一步:
from sklearn.preprocessing import StandardScaler # 初始化标准化器 scaler_X = StandardScaler() scaler_y = StandardScaler() # 对训练集做fit_transform(注意:只用训练集fit) X_train_scaled = scaler_X.fit_transform(X_train) X_test_scaled = scaler_X.transform(X_test) # 目标值也做标准化(MLP对目标尺度同样敏感) y_train_scaled = scaler_y.fit_transform(y_train.values.reshape(-1, 1)).ravel() y_test_scaled = scaler_y.transform(y_test.values.reshape(-1, 1)).ravel()标准化后特征均值为 0、标准差为 1,优化问题变成良态的,梯度下降可以更快收敛。这里有个细节容易出错:scaler_X只能用训练集数据去fit,测试集只做transform。如果对整个数据集一起做fit_transform,测试集的信息就间接泄漏到了训练过程里,评估结果会偏乐观。目标值y也需要标准化,但预测完成后要反标准化回原始尺度,否则没法计算真正的误差指标。
4.3 MLP 训练与预测:核心代码实现
MLP 模型的训练在 sklearn 框架下非常简洁,但参数配置值得仔细说。完整代码如下:
from sklearn.neural_network import MLPRegressor # 创建MLP模型 mlp_model = MLPRegressor( hidden_layer_sizes=(32,), activation='relu', solver='adam', learning_rate_init=0.001, alpha=0.001, # L2正则化系数 batch_size=32, max_iter=500, early_stopping=True, validation_fraction=0.1, n_iter_no_change=20, random_state=42 ) # 训练模型 mlp_model.fit(X_train_scaled, y_train_scaled) # 预测(需要反标准化) y_pred_mlp_scaled = mlp_model.predict(X_test_scaled) y_pred_mlp = scaler_y.inverse_transform(y_pred_mlp_scaled.reshape(-1, 1)).ravel() # 评估 mlp_metrics = evaluate_prediction(y_test, y_pred_mlp) print('MLP Metrics:', mlp_metrics)alpha是 L2 正则化系数,防止过拟合的关键参数。金融数据信噪比低,模型容量稍大就容易把噪声也拟合进去,所以正则化不可或缺。early_stopping=True意味着训练过程中每轮都在验证集上检查损失,如果连续n_iter_no_change轮没有改善就提前终止,这既防止过拟合又节省时间。validation_fraction=0.1表示从训练集里切 10% 作为验证集。random_state=42固定随机种子至关重要——否则每次运行网络初始化权重不同,结果会有差异,做对比实验时无法复现。
4.4 训练曲线与过拟合诊断
MLP 模型训练完成后,不能只看测试集指标就下结论,还得看训练过程是否出现了过拟合。通过mlp_model.loss_curve_可以拿到每次迭代的损失值,画出来观察趋势:
import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) plt.plot(mlp_model.loss_curve_, label='Training Loss') plt.xlabel('Iteration') plt.ylabel('Loss') plt.title('MLP Training Loss Curve') plt.legend() plt.show() # 验证集上的表现对比 train_pred_scaled = mlp_model.predict(X_train_scaled) train_pred = scaler_y.inverse_transform(train_pred_scaled.reshape(-1, 1)).ravel() train_metrics = evaluate_prediction(y_train, train_pred) print('Train Metrics:', train_metrics) print('Test Metrics:', mlp_metrics)我复现时看到的现象是:训练集上的 MAE 通常会明显优于测试集,这是正常的。但如果训练集 MAE 比测试集低一个数量级以上,基本可以断定过拟合严重,需要增大alpha或减少神经元数量。如果loss_curve_在迭代后期还在明显下降而验证集损失已经回升,同样是过拟合信号,需要靠early_stopping兜底。
5. 黄金期货预测项目避坑指南:五个实战踩坑记录
5.1 伪回归:不检验平稳性就建模
现象:某个版本直接拿原始价格序列做线性回归,训练集 R² 超过 0.98,测试集却一塌糊涂。原因:黄金期货价格序列是非平稳的,两个带趋势的序列强行回归会产生伪回归——统计指标好看但没有预测价值。解决:严格按流程先做 ADF 检验,价格序列不平稳就用对数收益率替换。从那以后我再也不信不经过平稳性检验的 R²。
5.2 数据泄漏:标准化时用到全量数据
现象:MLP 模型测试集 MAE 突然变得极好,好到不真实。原因:标准化时对整个数据集做了fit_transform,测试集的均值和标准差被混进了训练流程,相当于模型在测试时已经知道了数据的全局分布信息。解决:标准化器只对训练集fit,测试集只transform。这个坑在时间序列交叉验证里尤其常见,每折都要重新 fit 标准化器。
5.3 未来信息泄漏:特征里混入了当天数据
现象:模型训练时表现优秀,但部署后发现实际预测效果大打折扣。原因:构造特征时用了当天的数据来预测当天的收益率——比如用Close_t去预测Return_t,而Close_t本身包含了Return_t的完整信息,模型实际上是在背诵答案。解决:构造特征时全部使用滞后数据,预测目标严格对齐下一期。检查方法是看特征列和预测目标是否同一时刻产生。
5.4 随机种子不固定:对比结果无法复现
现象:同一个 MLP 参数跑两次,测试集 MAE 一次是 0.0123,一次是 0.0135,相差超过 9%。原因:神经网络权重初始化是随机的,Adam 优化器本身也有随机性,不固定随机种子结果必然有波动。解决:MLPRegressor 设置random_state=42,全流程其他随机操作也固定种子。比较不同模型时,如果有一个模型跑了多次而另一个只跑了一次,对比就是不公平的。
5.5 滞后阶数选择的主观性
现象:格兰杰因果检验在滞后 2 期时 p 值显著,滞后 5 期时不显著,不知道选哪个。原因:格兰杰因果检验对滞后阶数敏感,不同阶数反映的是不同时间尺度的预测关系。解决:不能只挑显著的那个阶数写进论文,要多个阶数同时看,结合 AIC/BIC 信息准则和业务逻辑一起判断。同时要在论文里说明滞后阶数选择的依据,否则审稿人会质疑结果稳健性。
6. 对比分析的进阶技巧:滚动预测框架与鲁棒性验证
基础版本的时间序列划分是前 80% 训练、后 20% 测试,这种单次划分的结果容易受起点和终点选择影响。进阶做法是用滚动时间窗口做多轮评估——每次用过去 N 个月的窗口训练,预测未来一个月,然后窗口向前滚动,重复多轮,最后综合比较两个模型在每轮的表现。这种框架更接近实际交易场景,结论也更有说服力。
def rolling_evaluation(model_factory, X, y, window_size, step_size): results = {'ols': [], 'mlp': []} n = len(X) start = 0 while start + window_size < n: # 划分训练和测试区间 train_end = start + window_size test_end = min(train_end + step_size, n) X_train = X[start:train_end] X_test = X[train_end:test_end] y_train = y[start:train_end] y_test = y[train_end:test_end] # 标准化 scaler_X = StandardScaler().fit(X_train) X_train_scaled = scaler_X.transform(X_train) X_test_scaled = scaler_X.transform(X_test) # 训练和评估两个模型 for name, factory in model_factory.items(): model = factory() model.fit(X_train_scaled, y_train) y_pred = model.predict(X_test_scaled) # 这里是简化版,实际需要反标准化 metrics = evaluate_prediction(y_test, y_pred) results[name].append(metrics) start += step_size return results滚动窗口的两个关键参数是window_size(训练窗口长度)和step_size(滚动步长)。我一般取window_size为 500 个交易日(约两年),step_size为 63 个交易日(约一个季度),这样能跑出 5 轮以上的评估结果,统计上更有意义。每轮结果还可以算出两个模型的 MAE 差值分布——如果 MLP 在大部分窗口下都不占优势,那基本可以判定在这个数据集上线性回归已经足够。这个结论本身也是研究的价值所在:金融数据的线性结构可能比想象中更强。
验证完模型差异后,还有一个值得关注的细节:方向准确率的稳定性。我复现过程中发现,两个模型的方向准确率差距其实很小——都在 51% 到 55% 之间徘徊。如果只对比 MAE 和 RMSE,会发现差别也不算大。这说明对于黄金期货这种噪声极强的金融时间序列,模型能提取的可靠信号是有限的。从那以后我每次做模型对比,都强制把误差指标和方向准确率放在一起看,单独一个指标很容易误导决策。这份资源的完整代码和数据集配套下载,可以按我上面的流程复现整套对比实验,希望对你的量化研究有帮助。
本文还有配套的精品资源,点击获取