ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

金融时间序列预测方法合集:CNN、LSTM、随机森林、ARMA全套代码

金融时间序列预测方法合集:CNN、LSTM、随机森林、ARMA全套代码 简介面向金融时序建模与量化分析学习者的一份机器学习与深度学习方法合集覆盖CNN、LSTM、随机森林、ARMA四类主流预测模型同时包含皮尔逊相关系数、动态时间规整、余弦相似度等相似度计算以及特征方差、混淆矩阵等配套评估工具适合需要横向比较多种模型效果、快速搭建实验流程的初学者和研究人员。资源共68个文件以19个Python脚本为核心附带30张结果图、8个CSV数据文件、Markdown说明文档、notebook与PDF等压缩包约7.84MB目录结构清晰便于按需求查阅。已有1752人学习使用。CNN、LSTM、随机森林、ARMA四个模块各自带有数据、图片和代码可直接复现相似度部分提供多种算法实现有助于对比序列距离其余工具模块补充了可视化绘图与评价指标脚本能够支撑模型训练、效果对比、误差评估与结果展示的一站式流程。1. 金融时间序列预测方法合集CNN、LSTM、随机森林、ARMA 全套代码先看它能不能直接用如果你手里有一份股价或成交量数据想用 CNN、LSTM、随机森林、ARMA 预测价格又不想从零拼四套代码这份金融时间序列预测方法合集很适合当起点。四个预测模型、三种相似度计算、混淆矩阵和评判指标绘图脚本全都放在同一个包里每个子项目都带 code、data、images 和 README课程设计、比赛做 baseline、模型对比都能直接用。它不是黑匣子脚本都能打开改参数重跑。适合刚接触时序预测的人快速建立对照也适合熟手当参考实现。下面直接拆文件、说参数、列坑。2. 文件结构与数据预处理窗口、差分、归一化这三个动作决定成败拿到包先别急着跑模型先把 data 和 code 之间的格式对齐。很多项目翻车不是模型不好而是数据形状不对。金融时间序列预测和图像分类不一样标签不是现成的需要自己从价格序列里构造这一步错了后面所有模型都是白跑。2.1 包内目录先分清四个子项目各自的边界这个压缩包的根目录是一个总 README下面挂着几个子项目。四个预测项目分别对应 CNN、LSTM、随机森林、ARMA另外两个目录叫 Financial-Time-Similarity 和 Finance-Time-Others分别处理相似度计算和方差、混淆矩阵。每个子项目里基本都有 code、data、images、README.md。README 会写运行顺序images 里是示例输出图data 里是 CSV 或其他格式的输入数据。我拿到包的习惯是先打开 data 下的 CSV 看一眼列名确定是价格、收益率还是已经归一化过的序列再决定要不要补差分步骤。ARMA 项目在原始目录里名字拼成了 Financilal-Prediction-ARMA这个不影响运行只是看目录时要多留个心眼。目录层次对新手不太友好因为代码文件都是直接放在 code 里没有细分子目录。所以运行时要注意当前工作目录最好把项目根目录加到sys.path或者直接在子项目根目录下执行脚本否则很容易遇到FileNotFoundError。另外不同模型要求的数据形态不一样CNN 和 LSTM 需要三维张量随机森林需要二维特征表ARMA 只需要一维序列。资源里每个子项目各有独立的 data 目录这说明它们的数据读取方式大概率不同替换成自己的数据时不要只改文件名还要改 reshape 逻辑。2.2 标签构造用原始价格还是差分收益率直接预测收盘价是大多数新手的第一反应但这样做出来的模型经常是“上一刻价格”的复制品。金融价格序列通常非平稳自相关性极强模型学到的是“今天约等于昨天”而不是真正捕捉到涨跌规律。常见做法是先做差分或对数收益让序列变成近似平稳的增量序列然后再预测。下面这段代码就是构造差分标签和滑动窗口样本的常见写法import pandas as pd import numpy as np df pd.read_csv(data/stock.csv, parse_dates[date]) df df.sort_values(date) # 时间序列必须按时间升序 # 用 pct_change 算收益率用 diff 算差分 df[ret] df[close].pct_change() df[diff] df[close].diff() df df.dropna() # 滑动窗口用前 window 个时间步预测下一个时间步 window 20 X, y [], [] for i in range(len(df) - window): X.append(df[diff].iloc[i:i window].values) y.append(df[diff].iloc[i window]) X np.array(X).reshape(-1, window, 1) y np.array(y)代码逻辑不复杂先把差分序列按窗口切成样本前 20 个点作为特征第 21 个点作为标签。这里的关键参数是window。日线数据我一般取 20 到 60因为一个月的交易日大约 20 天一个季度的交易日大约 60 天窗口太短抓不住周期窗口太长样本量骤减且噪声更多。如果你做的是日内分钟级数据窗口可以缩到 10 到 30。pct_change()在出现极端涨跌幅时会产生很大的异常值比如复权除权当天可能出现几倍的变化。建议在构造样本前对收益率做np.clip把上下限限制在例如-0.1到0.1。预测差分值之后要还原回价格公式是price[t] price[0] np.cumsum(diff_pred)。如果直接预测对数收益还原时要用累计乘积这个细节最容易算错。2.3 训练测试划分与归一化顺序切分不能打乱也不能混用时间序列的训练集和测试集划分和普通机器学习不一样不能随机打乱。你的模型只能用 2023 年之前的数据训练用 2023 年之后的数据测试。如果混入未来样本测试成绩会好得不真实实盘却完全失效。from sklearn.preprocessing import MinMaxScaler # 先切分再归一化顺序不能乱 train_size int(len(X) * 0.8) X_train, X_test X[:train_size], X[train_size:] y_train, y_test y[:train_size], y[train_size:] # 注意scaler 只用训练集 fit测试集只 transform scaler MinMaxScaler() X_train_flat X_train.reshape(-1, X_train.shape[-1]) X_test_flat X_test.reshape(-1, X_test.shape[-1]) scaler.fit(X_train_flat) X_train_scaled scaler.transform(X_train_flat).reshape(X_train.shape) X_test_scaled scaler.transform(X_test_flat).reshape(X_test.shape)这段代码里最重要的是scaler.fit(X_train_flat)只接触训练数据。如果先对整个数据集 fit 再切分训练过程中模型就从 min、max 里偷看了测试集的取值范围这属于典型的数据泄漏。在金融场景里这种泄漏会让验证效果虚高尤其当测试期间市场波动变大时误差被严重低估。归一化方法也可以根据模型换LSTM 和 CNN 用MinMaxScaler或StandardScaler都行。随机森林这类树模型对尺度不敏感甚至可以不归一化但如果你在特征里混合了价格和收益率最好还是标准化一下避免树的分裂被大数值列主导。2.4 评判指标别只看 loss回归任务这几项都得算资源里有一个专门的评判指标绘图模块说明作者也认为只看训练 loss 不够。金融时序预测通常同时看四类指标。指标含义什么时候重点关注RMSE均方根误差对大误差敏感想避免单次极端预测出错时MAE平均绝对误差对异常值更稳健数据里有明显跳空缺口时MAPE平均绝对百分比误差不同股票之间横向对比时R²决定系数判断模型是否比“均值预测”更好下面是一个可以直接复用的评估函数from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score def evaluate(y_true, y_pred): y_true np.asarray(y_true).ravel() y_pred np.asarray(y_pred).ravel() rmse mean_squared_error(y_true, y_pred) ** 0.5 mae mean_absolute_error(y_true, y_pred) r2 r2_score(y_true, y_pred) # 加 epsilon 防止真实值接近 0 时 MAPE 爆炸 epsilon 1e-8 mape np.mean(np.abs((y_true - y_pred) / (y_true epsilon))) * 100 return {RMSE: rmse, MAE: mae, R2: r2, MAPE: mape}这个函数返回字典画图时可以很方便地拼到标题里。注意 MAPE 在真实收益率接近 0 的时候会变得非常大所以金融序列里我更常用带分母保护的版本或者直接换用 sMAPE。资源里的“评判指标绘图”大概率就是把这些指标和预测曲线叠在一起输出成图后面第四章会详细展开。3. 四个模型逐个拆CNN、LSTM、随机森林、ARMA 的参数怎么设模型代码在资源里都已经写好但参数都是作者自己数据的经验值直接套用别人的数据不一定有效。这一章把每个模型的关键参数和选型逻辑拆开讲方便你改出适合自己的版本。3.1 CNN 与 LSTM深度模型搭起来先固定随机种子CNN 和 LSTM 在时序预测里经常被放在一起比较因为它们都要把数据组织成(samples, time_steps, features)三维格式。区别在于 LSTM 用门控机制记住长期依赖适合价格走势里那些相隔多个周期的关联CNN 的一维卷积则更擅长抓局部形态比如连续三根 K 线组成的短期突破模式。热搜里大量出现“CNN 和 RNN 的区别”“LSTM 神经网络”实际用起来最大差异就是感受野和记忆能力。先看 LSTM 的标准写法import numpy as np import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout # 固定随机种子否则结果每次都不一样 tf.random.set_seed(42) np.random.seed(42) model Sequential() model.add(LSTM(64, input_shape(X_train_scaled.shape[1], X_train_scaled.shape[2]))) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(lossmse, optimizeradam) history model.fit( X_train_scaled, y_train, epochs50, batch_size32, validation_data(X_test_scaled, y_test), verbose0 )参数里最敏感的是units64。这个值代表 LSTM 记忆单元的维度太小抓不住复杂依赖太大在小样本上会严重过拟合。金融日线数据通常只有几百到几千条样本我一般从 32 开始试最多不超过 128。dropout0.2是防止过拟合的关键尤其在训练集 loss 持续下降、验证集 loss 开始反弹的时候可以提到 0.3。epochs50对日线数据已经偏多我经常在 30 轮左右就早停因为金融序列信噪比低继续训练只会让模型记住噪声。CNN 的写法参数少一些from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten cnn Sequential() cnn.add(Conv1D(filters32, kernel_size3, activationrelu, input_shape(X_train_scaled.shape[1], X_train_scaled.shape[2]))) cnn.add(MaxPooling1D(pool_size2)) cnn.add(Flatten()) cnn.add(Dense(16, activationrelu)) cnn.add(Dense(1)) cnn.compile(lossmse, optimizeradam)filters32表示卷积层提取 32 种局部特征kernel_size3表示每次只看连续 3 个时间步对应“短周期形态”。如果你的window设成 60kernel_size可以适当提到 5感受野更大。MaxPooling1D(pool_size2)会把时间步减半如果窗口本来就短比如 10池化后只剩 5 个点信息损失偏大这时可以把pool_size改成 1也就是不池化。CNN 参数少、训练快但它的弱点是很难捕捉跨周期依赖适合做基线对比。3.2 随机森林与 ARMA传统模型在什么情况下能赢随机森林在时序预测里的口碑很分裂很多人用完之后说“预测值像台阶”。原因不是模型不行而是直接把树模型拿来预测原始价格。树模型没有外推能力训练集里没见过的高价区间它只能输出训练样本叶子节点的均值。所以正确用法是预测差分或收益率并且把特征从“过去的价格”扩展成“过去的变化量”。from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import TimeSeriesSplit # 构造滞后特征和滚动统计特征 df[lag1] df[close].shift(1) df[lag2] df[close].shift(2) df[ma5] df[close].rolling(5).mean() df[std5] df[close].rolling(5).std() df df.dropna() features [lag1, lag2, ma5, std5] X_rf df[features].iloc[:-1] # 去掉最后一行因为 y 要 shift y_rf df[close].diff().shift(-1).iloc[:-1] rf RandomForestRegressor( n_estimators200, max_depth6, min_samples_leaf5, random_state42 ) rf.fit(X_rf, y_rf)时间序列交叉验证不能用普通KFold否则会打乱顺序。sklearn 提供了TimeSeriesSplit它就是按顺序拆成多个时间窗口的适合用来验证稳定性。n_estimators200对时间序列已经够了堆到 1000 只会增加训练时间精度提升很小。max_depth6限制每棵树深度防止学到过于碎片化的规则。min_samples_leaf5保证每个叶子至少有 5 个样本避免输出极端值。随机森林的优势是训练快、特征重要性可解释在特征工程做得比较全的情况下中期预测效果往往不输 LSTM。ARMA 是四个模型里最“古典”的。它要求序列平稳所以第一步永远是 ADF 检验不平稳就拆分成差分项。import statsmodels.api as sm from statsmodels.tsa.stattools import adfuller # 对差分后的序列做平稳性检验 ts df[close].diff().dropna() adf_stat, p_value adfuller(ts)[:2] print(ADF p_value:, p_value) # 如果 p_value 0.05序列不平稳需要再次差分 if p_value 0.05: ts ts.diff().dropna() # 用 AIC 自动选择 p 和 q order_result sm.tsa.arma_order_select_ic(ts, max_ar5, max_ma5, icaic) p, q order_result[aic_min_order] print(best p, q:, p, q) # 建立 ARMA 模型 model_arma sm.tsa.ARIMA(ts, order(p, 0, q)) result model_arma.fit()注意 statsmodels 里的ARIMA(order(p, d, q))中d0时就是 ARMA我们提前手动差分是为了让d0时也能满足平稳性。arma_order_select_ic用 AIC 自动搜阶数max_ar和max_ma我都限制在 5 以内阶数给太大会导致过拟合和数值不稳定。ARMA 对明显的趋势、周期、突变都很敏感它的舒适区是那些去趋势后的残差序列或者是短期平稳的收益率序列。如果测试集里出现了模型中没见过的结构性变化ARMA 的预测会快速退化成均值回归。3.3 选型清单短序列、长序列、强噪声分别用哪个四个模型不是简单的谁比谁强而是适用边界完全不同。模型适用场景常见参数主要注意点CNN短窗口局部形态识别filters32, kernel_size3长依赖弱窗口别太长LSTM较长窗口、有跨周期依赖units64, dropout0.2小样本容易过拟合要固定随机种子随机森林特征工程丰富、中期预测n_estimators200, max_depth6不能外推目标变量别用原始价格ARMA平稳短期线性序列p,q 用 AIC 选先做 ADF不平稳就差分如果只预测下一交易日ARMA 和随机森林往往比 LSTM 稳定如果预测未来 10 天以上LSTM 配合差分目标更有可能捕捉到趋势。CNN 适合做快速基线和局部形态识别。这个资源的价值就在于四个模型都给了代码你可以用同一份数据把四个方法都跑一遍然后对比 RMSE 和方向准确率而不是听别人说哪种模型更好。4. 相似度计算与评估绘图预测好不好不能只看一条曲线预测曲线和真实曲线叠在一起肉眼看个大概是不够的。这个包里专门有个 Financial-Time-Similarity 目录提供皮尔逊相关系数、动态时间规整 DTW、余弦相似度三种算法。它们不只是给模型评估用也能用来做行情形态匹配比如找出历史上与当前走势最相似的区间。4.1 三种相似度Pearson、DTW、余弦相似度分别适合什么三种算法的侧重点完全不同。皮尔逊相关系数衡量线性相关性但对时间轴的错位非常敏感两条形状完全相同但错开几个点的序列相关系数可能很低。DTW 允许时间轴“扭曲”后再对齐非常适合比较形状相似但节奏略有差异的走势。余弦相似度只看方向对幅度大小不敏感适合比较两个序列是同步上涨还是背离。算法是否考虑时间顺序是否受幅度影响典型用途Pearson是点对点严格对齐是相关性分析线性关系DTW是但允许局部伸缩是形态匹配错位容忍Cosine否把序列当向量否方向比较涨跌一致性下面这段代码把三种算法一次性算出来from scipy.spatial.distance import euclidean from dtaidistance import dtw import numpy as np # 两条示例序列后面的点比前面略有延迟 a np.array([1.0, 2.0, 3.0, 4.0, 5.0]) b np.array([1.0, 2.0, 4.0, 4.5, 5.5]) # 1. 皮尔逊相关系数 pearson np.corrcoef(a, b)[0, 1] # 2. 余弦相似度 cos_sim np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) # 3. DTW 距离需要 pip install dtaidistance dtw_dist dtw.distance(a, b) print(fPearson: {pearson:.3f}) print(fCosine: {cos_sim:.3f}) print(fDTW: {dtw_dist:.3f})Pearson 的计算结果是相关度越大越相似余弦相似度也是越大越好范围在 -1 到 1 之间DTW 输出的是距离越小越相似。这里最容易误解的是如果序列幅度差异很大比如一条在 100 左右波动另一条在 1 左右波动Pearson 都可能给出接近 1 的相关但实际形状差异巨大。所以做形态匹配前最好先把序列 z-score 标准化再算相似度。DTW 的计算复杂度是 O(n²)序列长度超过几千时非常耗时。dtaidistance有 C 语言加速版本比纯 Python 实现快不少。如果你的数据是分钟级、长度上万可以先降采样或者分段后再算。资源里的 Financial-Time-Similarity 代码大概率也是封装了这三种算法你只需要把data目录下的序列换成自己的数据。4.2 画图脚本训练曲线、真实值、预测值和残差一张图看完资源里 images 目录下的图片就是这些脚本的示例输出。我一般会把四张子图拼成一张报告图loss 曲线、真实值 vs 预测值、残差直方图、残差序列图。这样一张图就能判断模型有没有过拟合以及预测误差是否随机分布。import matplotlib.pyplot as plt fig, axes plt.subplots(2, 2, figsize(12, 8)) # 左上损失曲线 axes[0, 0].plot(history.history[loss], labeltrain loss) axes[0, 0].plot(history.history[val_loss], labelval loss) axes[0, 0].legend() axes[0, 0].set_title(Loss Curve) # 右上真实值 vs 预测值 axes[0, 1].plot(y_test, labeltrue, alpha0.7) axes[0, 1].plot(y_pred, labelpred, alpha0.7) axes[0, 1].legend() axes[0, 1].set_title(Prediction vs True) # 左下残差直方图 residual np.asarray(y_test).ravel() - np.asarray(y_pred).ravel() axes[1, 0].hist(residual, bins30) axes[1, 0].set_title(Residual Histogram) # 右下残差序列 axes[1, 1].plot(residual) axes[1, 1].axhline(0, colorr, linestyle--) axes[1, 1].set_title(Residual Series) plt.tight_layout() plt.savefig(images/prediction_report.png, dpi150)判断标准很简单训练 loss 一直下降、验证 loss 先降后升就是过拟合需要加大 dropout、减少层数或提前早停残差直方图如果明显偏左或偏右说明模型存在系统性偏差残差序列如果还有明显的自相关性比如连接成串地高估或低估说明模型还没有把信息提取干净。对价格预测来说残差出现自相关通常意味着你只预测了价格本身但没把波动率结构考虑进去。4.3 除了 RMSE方向准确率也要算金融预测里预测值误差 0.5 有时不重要方向对不对才决定能不能做成交易策略。我习惯在评估函数里额外加一个方向准确率def direction_accuracy(y_true, y_pred): # 用 sign 判断涨跌方向是否一致 return np.mean(np.sign(y_true) np.sign(y_pred)) * 100 # 对差分序列做方向判断 print(Direction Accuracy: {:.2f}%.format(direction_accuracy(y_test, y_pred)))这里y_true和y_pred必须是差分或收益序列不能把原始价格直接丢进去。如果只对价格预测可用需要先np.diff。方向准确率超过 55% 在日线级别已经算不错50% 左右说明模型基本没有预测力。资源里的confuse_matrix.py做的就是类似工作只不过把方向判断换成了分类混淆矩阵用于查看预测涨跌和实际涨跌的对应关系。5. 避坑指南金融时序预测最容易翻车的五个地方这一章全部是实际跑模型时踩过的坑。每个坑都按“现象 → 原因 → 解决”来写拿这份资源跑数据前先对照一遍。5.1 数据预处理三个坑坑一归一化用了全量数据泄露未来信息。现象模型在测试集上的 RMSE 低得离谱训练集和验证集 loss 都很好但一换到新数据就废了。原因很多人写scaler.fit(data)时是把整个数据集包括测试集一起 fit 的。MinMaxScaler 会记录全量的 min 和 max测试集的取值范围在训练时就“被看到”了。时间序列里这种泄漏不致命但非常隐蔽表现就是成绩虚高。解决先按时间顺序切分训练集和测试集然后只对训练集fit再分别transform训练集和测试集。新数据进来时用同一个 scaler 做transform不要重新 fit。坑二直接预测原始价格loss 小得像假象。现象LSTM 和随机森林的预测曲线看起来比真实曲线滞后了一天RMSE 很小但你把预测值累加出来会发现根本没抓到趋势。原因价格序列非平稳且自相关极强模型学到的最优策略是“预测值等于最近一个训练样本的值”。对这种数据loss 很小不代表有预测能力。解决把目标变量从close改成close.diff()或close.pct_change()。预测差分后还原公式是price last_price np.cumsum(diff_pred)预测收益则用累计乘积。虽然 loss 会变大但这才是真实水平。坑三切分数据时做了 shuffle。现象模型在测试集上表现奇好而且预测误差不是随机分布的而是逐段吻合。原因洗牌打乱了时间顺序训练集里混进了未来数据。对于滑动窗口生成的样本相邻窗口本来就高度重叠一旦打乱测试样本的窗口里可能就包含了它自己的“下一次观察”。解决严格按时间升序切分用train_test_split时设shuffleFalse。做交叉验证时用TimeSeriesSplit不要用普通KFold。5.2 模型训练与复现的坑坑四LSTM 每次跑出来的结果都不一样甚至差异很大。现象同样的代码、同样的数据连续跑两次预测曲线和 loss 曲线都不一致。原因神经网络权重初始化和训练过程中的随机性没有被固定。GPU 上的某些算子本身具有非确定性TensorFlow 版本不同也会影响结果。解决在代码最开头统一设随机种子包括random.seed、np.random.seed、tf.random.set_seed。如果你的 TensorFlow 版本支持再加一句tf.config.experimental.enable_op_determinism()。这类问题很玄学我之前换了一次 TensorFlow 小版本结果就发生了变化所以做对比实验时一定要固定环境。坑五ARMA 或 ARIMA 报错、结果爆炸。现象statsmodels 报Non-stationary starting autoregressive parameters或者模型拟合出来的参数大得离谱预测值直接冲到上千。原因序列没有达到平稳性要求或者 p、q 阶数给得太大导致数值不稳定。很多人在不平稳序列上强行用 ARMA自然翻车。解决先跑adfuller看 p 值大于 0.05 就继续差分。用arma_order_select_ic选阶数时把max_ar和max_ma限制在 5 以内不要在几十个阶数里找最优。如果仍然不收敛可以把fit里的method参数改成css-mle并调大maxiter到 500 以上。6. 落地验证用你自己的数据跑一遍并输出一张组合报告图资源下载好以后别急着把 epochs 调到 500。我建议先拿一个小数据集走一遍端到端流程确认数据读取、标签构造、差分还原这三个环节没问题再开始调参。一个快速验证脚本大概长这样def quick_check(df, modellstm): df[diff] df[close].diff().fillna(0) X, y make_windows(df[diff].values, window20) X_train, X_test, y_train, y_test split(X, y, 0.8) if model lstm: # 用最小配置跑通 model_lstm Sequential() model_lstm.add(LSTM(32, input_shape(X_train.shape[1], 1))) model_lstm.add(Dense(1)) model_lstm.compile(lossmse, optimizeradam) model_lstm.fit(X_train, y_train, epochs20, verbose0) y_pred model_lstm.predict(X_test) # 输出指标和方向准确率 print(evaluate(y_test, y_pred)) print(Direction Accuracy: {:.2f}%.format(direction_accuracy(y_test, y_pred)))这一步不是为了追求最佳精度而是确认代码链路是通的。如果连最小配置都能稳定跑完再逐步增加units、dropout、特征数量。如果最小配置在方向上都没有优势那问题多半不在模型而在数据特征。完成基础验证后我强烈建议手动写一个滚动预测循环对测试集逐日预测每次只预测一步然后把真实值推入窗口继续预测下一天。这样能直观看出误差随着预测步长的累积速度。如果第 5 天的误差已经接近噪声水平说明模型不适合做长期预测要么改成直接多步预测要么只保留短步结果。从那以后我每次拿到新的金融数据集都强制自己先跑一遍quick_check再上 LSTM 和随机森林。这个习惯帮我省掉了大量无效调参时间希望帮到你。本文还有配套的精品资源点击获取
返回列表