ARTICLE DETAIL

资讯详情

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

深度学习预测股票价格与量化交易的Python项目实战解析

深度学习预测股票价格与量化交易的Python项目实战解析 简介这是一份基于深度学习的股票价格预测与量化交易策略Python项目资料包也是计算机科学与技术专业的本科毕业设计成果适合机器学习入门者与金融科技方向学生。项目围绕LSTM时序预测模型与多维技术指标展开完整覆盖数据预处理、特征工程、模型训练、策略回测与绩效评估等标准化流程并引入波动率过滤与风险平价机制控制回撤可让学习者系统理解深度量化策略的构建方式。整包共1692个文件以Python源码与pyc文件为主包含配置文件、可执行脚本、技术文档、研究报告及答辩演示文稿等压缩包仅16.25MB目录分层清晰。已有143人学习下载。包内不仅包含可直接运行的完整源码还有详细技术文档、多种算法实现对比及详细注释关键环节均经过测试验证可作为毕业设计参考、期末大作业模板或量化投资入门研究案例。1. 深度学习预测股票价格这套 Python 源码能落地多少深度学习做股票价格预测与量化交易策略这几年在毕业设计和期末大作业里出现频率极高但多数网上项目只给一段 LSTM 训练代码数据怎么洗、回测怎么做、报告怎么落全要自己补。这套基于深度学习的股票价格预测与量化交易策略 Python 源码打包了从数据预处理、滑动窗口特征、LSTM 模型训练到信号生成、回测评估和研报文档的完整链路解压之后沿着文档就能把全流程跑通。适合三类人毕业设计需要完整交付物的学生课程期末大作业想拿高分的以及想快速验证深度学习算法到底能不能用于交易决策但不想从零搭回测框架的从业者。先给结论模型训练只是中间一环真正决定这个项目好坏的是它把数据处理、训练、策略、回测串成了闭环而踩坑基本都集中在后半段。2. 数据与特征工程为什么直接拿 K 线喂 LSTM 会翻车2.1 数据源与复权处理前复权是默认但除权日要小心数据层是整套源码的地基。常见做法是从 tushare、akshare 或 baostock 拉日线数据落到本地 CSV再交给预处理脚本。这里有一个新手最容易忽略的坑股票除权除息那天价格会产生跳空如果不做复权处理模型会把这次跳空当成真实的市场波动去学习结果就是预测值在除权日附近出现明显毛刺方向准确率也被拖低。源码里用的是前复权价这也是国内量化入门最常见的处理方式。前复权的原理是拿复权因子把历史价格统一到当前价格基准这样历史价格序列就是连续的模型学到的是价格连续变化而不是某天突然跌了 30% 的假象。严格讲前复权需要把后复权价再除以最新一期的复权因子代码里如果只写close * adj_factor得到的是后复权价两者趋势等价但数值基准不同混用会导致回测和实盘对不上。我一般会在 data 目录保留一份原始不复权数据复权结果单独落盘方便排查模型上周跑得好好的这周突然变差这种玄学问题。import pandas as pd df pd.read_csv(data/600000.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) # 前复权后复权价再除以最新复权因子 if adj_factor in df.columns: df[close_adj] df[close] * df[adj_factor] / df[adj_factor].iloc[-1] df[open_adj] df[open] * df[adj_factor] / df[adj_factor].iloc[-1] else: df[close_adj] df[close] df[open_adj] df[open] df df.dropna(subset[close_adj, volume]).reset_index(dropTrue)这段代码的逻辑是先按日期排序并重置索引因为后续做滑动窗口时索引错一位标签就会整体错位。复权因子字段来自数据接口返回的 adj_factor 列用它计算前复权价。dropna是为了过滤停牌导致的空值行。实际使用中我建议把成交额、换手率这些列暂时剔除只保留 open/high/low/close/volume 五列先跑通基线模型再加特征对比效果。数据完整性检查也要在这里做掉日线数据里经常混着停牌日空值、新股上市早期的极端波动。处理停牌日常见做法是直接丢行而不是补 0因为补 0 会让模型学到股价横盘的假象而实际停牌日根本无法交易这个信息对交易决策毫无帮助。2.2 特征窗口与标签设计滑点数据别直接丢给模型LSTM 的输入是形状为 (batch, sequence_length, feature_dim) 的三维张量。sequence_length 是回看多少个交易日源码默认 20对应约一个月的交易日数feature_dim 就是喂给模型的列数。这里有个常见误用有人直接把整个 DataFrame 丢进模型date、股票代码这些列也一起进去等于让模型去学哪一天和哪只股票这些信息与预测下一日价格毫无关系只会放大过拟合。标签设计上源码用的是回归方式预测下一个交易日的收盘价。也有人用分类方式做涨跌二分类但从我拆过的项目看回归加阈值判断比直接分类更稳定因为分类器把涨跌幅度完全丢掉了后续做仓位控制时没有置信度依据。归一化用 MinMaxScaler每列独立缩放到 [0,1]。注意 scaler 的 fit 只能放在训练段验证段和测试段只做 transform这个点涉及数据泄露后面避坑章会重点展开。import numpy as np from sklearn.preprocessing import MinMaxScaler feature_cols [open_adj, high_adj, low_adj, close_adj, volume] scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(df[feature_cols].values) SEQ_LEN 20 # 回看 20 个交易日 def make_windows(data, seq_lenSEQ_LEN): X, y [], [] for i in range(seq_len, len(data) - 1): # 留出 i1 作为预测目标 X.append(data[i - seq_len:i]) y.append(data[i 1, 3]) # close_adj 在列索引 3 return np.array(X), np.array(y) X, y make_windows(scaled) print(X.shape, y.shape) # (样本数, 20, 5) / (样本数,)这个循环从 seq_len 开始到len(data) - 1结束保证 y 取的是窗口末尾后一天的数据也就是用过去 20 天预测第 21 天的收盘价。y 取data[i 1, 3]是因为 feature_cols 里 close_adj 排在第 4 位。这是整套源码里最容易改错的地方——很多人写成data[i, 3]变成用当天预测当天回测里看不出来实盘一用就废。sequence_length 这个参数最影响结果太短5 以内模型只学到近几天噪声太长60 以上训练变慢且容易记住历史形态20 到 30 是比较稳的区间。2.3 训练 / 验证 / 测试集切分时间序列不能随机分割分类任务里常见的train_test_split(random_state42)在时序项目里是灾难随机抽样会把未来的数据混进训练集验证集和测试集里出现模型已经见过这段行情的情况回测收益高得离谱实盘直接打回原形。时间序列必须按时间顺序切分常见比例是 7:2:1 或者 7:1.5:1.5。源码里按样本行数切因为 X 已经按时间排序直接切片就是最干净的切法total len(X) train_end int(total * 0.7) val_end int(total * 0.85) X_train, y_train X[:train_end], y[:train_end] X_val, y_val X[train_end:val_end], y[train_end:val_end] X_test, y_test X[val_end:], y[val_end:]三个集合之间没有重叠测试集永远是时间上最后的那部分。这个切法配合后面讲的滚动再训练才能证明模型不是背答案。熟悉 sklearn 的人这时候容易手滑加一个shuffleTrue记住时序数据切完训练、验证、测试三块之后每一块内部也不该再 shuffle因为 LSTM 的序列本身是有时间顺序的窗口内部顺序打乱等于把因果链扯断。很多期末大作业只看训练集 loss 下降曲线验证集和测试集完全不用答辩时一问就露馅。建议至少把验证集 loss 曲线也画出来训练 loss 降、验证 loss 不降过拟合信号就已经很明显了。3. 模型训练与调参LSTM 参数怎么设才不过拟合3.1 网络结构单层 LSTM 和堆叠 LSTM 怎么选模型部分源码核心用的是 LSTM这是深度学习算法里处理时间序列最稳的入门选择。hidden_size 默认 64num_layers 默认 1。单层 LSTM 适合金融数据这种信噪比极低的任务堆叠两层 LSTM 虽然表达能力更强但金融时序里真正能利用的规律很弱网络变深反而把噪声一并学进去表现为训练集 loss 很低、验证集和回测一塌糊涂这是典型的过拟合幻觉。如果你做过图像或 NLP 项目会嫌这个结构太简单但股票价格预测的难点不在模型容量而在特征设计和标签对齐。我拆过的项目里把 LSTM 换成 GRU、加上 Attention 带来的提升远不如把数据泄露修掉带来的提升大。源码里的结构是 LSTM 后接一个全连接层输出标量这就是完整的预测头。想补 LSTM 理论基础的拿《动手深度学习》里循环神经网络那一章对着看足够应付答辩了。import torch import torch.nn as nn class StockLSTM(nn.Module): def __init__(self, input_size5, hidden_size64, num_layers1, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0 ) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch, seq_len, feature_dim) out, _ self.lstm(x) # 取最后一个时间步的隐藏状态做预测 last out[:, -1, :] return self.fc(last)batch_firstTrue表示输入形状是 (batch, seq_len, feature_dim)out[:, -1, :]取序列最后一个时间步的输出对应用完整窗口信息预测下一日。参数取舍上input_size 必须等于 feature_dim这里是 5dropout 在单层 LSTM 上实际上不生效这是 PyTorch 的机制限制别看到 dropout0.2 就以为有正则效果真正发挥作用需要 num_layers 至少为 2。3.2 训练参数与早停学习率、批次、随机种子怎么配训练部分源码用的是 MSE 损失因为标签是连续价格。优化器选 Adam默认 lr1e-3batch_size 取 64epochs 设置 100 但配合早停机制。金融时序噪声大训练到后期验证集 loss 往往先降后升所以必须保存验证集最优的模型权重而不是最后一个 epoch 的模型这是训练这类项目的默认动作。随机种子不锁复现就是玄学。深度学习训练的随机性来自 PyTorch 的参数初始化、DataLoader 的 shuffle 顺序、GPU 上原子操作顺序。锁种子并不能保证 GPU 完全可复现但至少能保证 CPU 环境下每次跑出的结果一致这对毕设报告和答辩至关重要。import random import numpy as np import torch def seed_everything(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) seed_everything(42) loss_fn nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) best_val float(inf) patience 0 for epoch in range(100): model.train() for xb, yb in train_loader: optimizer.zero_grad() pred model(xb).squeeze() loss loss_fn(pred, yb) loss.backward() optimizer.step() model.eval() val_loss 0.0 with torch.no_grad(): for xb, yb in val_loader: pred model(xb).squeeze() val_loss loss_fn(pred, yb).item() * len(xb) val_loss / len(val_loader.dataset) if val_loss best_val: best_val val_loss torch.save(model.state_dict(), best_model.pt) patience 0 else: patience 1 if patience 10: print(fearly stop at epoch {epoch}) breakseed_everything 必须在模型初始化之前调用否则模型参数初始化的随机性已经发生锁了等于没锁。早停 patience10 表示验证集连续 10 个 epoch 没创新低就停。核心训练参数按下面这张表调就行参数建议值说明learning_rate1e-3 ~ 5e-4大于 1e-2 会震荡loss 可能直接发散batch_size32 ~ 128日线数据量不大64 够用epochs100带早停固定 100 不早停容易过拟合patience10验证集连续 10 轮不降就停常见翻车点是 loss 一开始就 nan先检查数据里有没有 0 成交量或停牌日空值MinMax 归一化分母出现 0 时结果就是 nan训练集有 nan 的话 Adam 会把整个参数空间带崩。另一个隐蔽问题是 DataLoader 的 drop_last 参数末批样本数不足 batch_size 时Loss 计算取平均会体现不出全局水平建议训练时设置drop_lastTrue省去这个变量。3.3 模型评估回归指标和方向准确率模型评估不能只看 loss。MSE 和 RMSE 反映的是价格预测的绝对误差但策略真正关心的是方向——明天是涨还是跌。源码里提供了方向准确率预测下一日涨跌方向与实际涨跌方向一致的比例。两个指标要一起看RMSE 衡量校准程度方向准确率衡量收益潜力。from sklearn.metrics import mean_absolute_error, mean_squared_error preds predict(model, X_test, scaler) # 注意逆归一化回原价格 actuals y_test_original mae mean_absolute_error(actuals, preds) rmse mean_squared_error(actuals, preds, squaredFalse) direction_acc np.mean(np.sign(preds[1:] - actuals[:-1]) np.sign(actuals[1:] - actuals[:-1]))方向准确率的计算逻辑是preds[t] 预测的是 actuals[t1] 的价格所以预测涨跌方向要拿preds[1:]和actuals[:-1]做差实际涨跌方向拿actuals[1:]和actuals[:-1]做差两者相等才算预测对。这个指标能到 0.55 以上就是有价值的模型了别指望 0.7、0.8 那种数字真出现了大概率是数据泄露或者标签错位。4. 量化策略与回测预测信号如何变成可交易决策4.1 信号生成阈值、仓位、止损参数怎么设预测模型输出的是明天的价格但预测涨了不等于要买。源码里信号生成用的常见做法是预测收益率超过某个阈值才买入低于负阈值则清仓或做空。阈值默认 0.0020.2%因为日线级别的价格波动里低于这个数连手续费都覆盖不了。如果你在网上搜 python 量化交易策略代码会发现大量片段只给信号生成、不给仓位管理和止损这套源码把阈值、仓位比例、止损线都做成了可配置参数这是它比那些半成品片段完整的地方。PRED preds # 模型输出的下一日收盘价预测 CLOSE actuals # 当日实际收盘价 THRESHOLD 0.002 # 预测收益阈值 POSITION 0.8 # 单次最大仓位 STOP_LOSS 0.03 # 亏损 3% 强制平仓 signal np.zeros(len(PRED)) for t in range(1, len(PRED)): pred_ret PRED[t] / CLOSE[t - 1] - 1 if pred_ret THRESHOLD: signal[t] POSITION elif pred_ret -THRESHOLD: signal[t] -POSITION # 介于两者之间 signal 保持 0空仓等待pred_ret 用 CLOSE[t-1] 做基准因为模型预测的是 t1 日价格而 CLOSE[t-1] 是发出预测时能得到的最新收盘价。threshold 是策略敏感度的核心参数0.002 偏保守信号少但每次出手的信息质量更高0.0005 信号频繁但收益会被手续费吃掉一大块。POSITION 默认不超过 0.8保留 0.2 现金本身就是风控这个习惯比模型参数更值钱。需要注意 A 股做空受限signal 为负时实盘里更多是清仓观望而不是开空仓。源码里有的版本内置了多空模式开关毕设场景我建议只用多头和空仓两档别在报告里去谈做空答辩时逻辑更干净。4.2 回测引擎手续费、滑点、涨跌停过滤回测是整套资源里最容易被低估的部分。源码自带的回测引擎是向量化实现核心思想是把每天的持仓、资金变化、净值用数组一次算出来速度快、代码短。但手续费和滑点必须算进去A 股佣金双边万 2.5 左右卖出还要加印花税2023 年 8 月之后减半征收为 0.05%合起来单边大约万 7.5双边万 15。滑点按开盘价下单和实际成交价的差额估小盘股给 0.1% 到 0.2% 不过分。这些成本不进回测收益外观看翻倍实盘直接亏损。涨跌停过滤是另一个黑匣子涨停板买不上跌停板也卖不出。回测里如果不加过滤策略会在涨停日买入成功然后享受次日继续上涨的盈利但实际交易根本成交不了。我一般这样处理当日涨幅大于等于 9.8% 且信号为买入时强制把信号置为 0跌幅小于等于 -9.8% 且信号为卖出时同样置为 0。def backtest(signal, actuals, fee_rate0.0015, slippage0.001): cash 1_000_000 position 0.0 pnl [] for t in range(1, len(signal)): if signal[t] 0: pnl.append(cash position * actuals[t - 1]) continue # 涨跌停过滤涨停买不进跌停卖不出 daily_ret actuals[t] / actuals[t - 1] - 1 if signal[t] 0 and daily_ret 0.098: pnl.append(cash position * actuals[t - 1]) continue if signal[t] 0 and daily_ret -0.098: pnl.append(cash position * actuals[t - 1]) continue trade_price actuals[t] * (1 slippage if signal[t] 0 else 1 - slippage) cost abs(signal[t]) * trade_price * fee_rate cash - signal[t] * trade_price cost position signal[t] pnl.append(cash position * actuals[t]) return np.array(pnl)fee_rate0.0015 是双边费率包含佣金、印花税和过户费按 100 万本金、每次 0.8 仓位计算单次交易成本大约 1200 元。slippage 是滑点假设实盘中开盘跳空大的日子实际成交成本更高。这段回测的逻辑是信号为 0 时只记录持仓市值有信号但触发了涨跌停过滤就跳过成交正常成交时按信号方向加减仓位并扣除手续费和滑点。最终返回的是每日净值序列可以直接画资金曲线。注意涨停判断的阈值沪深主板是 10%代码里用 9.8% 是为了过滤掉四舍五入和 ST 股 5% 的干扰创业板、科创板涨跌幅是 20%阈值要改成 19.8%北交所是 30%。这个阈值在源码里做成了参数换市场前先改掉。4.3 评估指标收益、夏普比率与最大回撤回测跑完源码里集成了一套指标计算函数。最有参考价值的是年化收益率、夏普比率、最大回撤和胜率。夏普比率是超额收益除以收益波动代表承担单位风险换取的回报最大回撤衡量净值从峰值跌下来的最大幅度决定了策略在实盘中能不能拿得住。回测里年化 20% 但回撤 40% 的策略与年化 12% 回撤 8% 的策略相比实盘中往往后者活得久。指标计算方式参考界限年化收益率净值累计收益 / 持仓年数回测 10%~20% 已是理想区间夏普比率(年化收益 - 无风险利率) / 年化波动大于 1 及格大于 1.5 少见最大回撤max(1 - 净值 / 历史峰值)低于 15% 更容易实盘持有胜率盈利交易次数 / 总交易次数0.5 左右需结合盈亏比看单看收益会被骗最大回撤才是实盘持仓体验的核心。如果你跑出来夏普 3、回撤 5%先不要急着发朋友圈按第 5 章的清单逐条排查大概率是某一项检查没过。5. 常见问题与避坑数据泄露到回测幻觉的四个血泪经验这一章是整套源码的排查手册。我拆这类资源时几乎每次都在这几类问题上翻车也帮人排过不少——现象雷同根因就那么几个。5.1 预测很准回测却亏钱现象模型 loss 很低方向准确率 0.6 以上但回测资金曲线一路向下。原因最常见的是标签对齐错了。make_windows 里写成y.append(data[i, 3])预测目标变成当天收盘价模型学到的是今天价格是多少而不是明天价格会怎样预测值与真实值高度接近但没有任何前瞻信息交易决策等于拿当天已实现的价格做开仓依据自然赚不到钱。解决把 y 改成data[i 1, 3]并且检查训练数据长度确认 X 的最后一个样本与预测目标之间没有重叠。检查方法很简单把模型预测曲线和真实价格曲线画在一起如果预测曲线比真实曲线滞后一天左右基本就是这个原因。5.2 训练集表现好验证集一塌糊涂现象训练 loss 降到很低验证 loss 一直下不去两条曲线像剪刀一样分开。原因两个来源串在一起。一是时序切分用了 sklearn 的随机切分验证集里混进了训练期前后的行情模型在背答案二是归一化 scaler 在全部数据上执行了 fit_transform验证集的均值和最大值信息已经提前进入训练集属于典型的数据泄露。解决切分改用第 2 章的时序切片scaled 只对训练段执行fit_transform验证段和测试段用同一个 scaler 只做transform。改完之后验证集 loss 变高是正常的说明之前那个低 loss 是假象。5.3 回测收益翻倍实盘完全不是这么回事现象回测年化 80%、夏普 3实盘跑一周就亏净值曲线和回测对不上。原因回测里没有算手续费、滑点或者涨跌停日成交了而实际根本买不进、卖不出。另一个隐蔽原因是前视偏差比如在 t 日收盘前用了 t 日收盘后才会产生的数据指标做决策。解决把 fee_rate 至少设到 0.0015双边含佣金和印花税slippage 设 0.001 到 0.002并加涨跌停过滤。如果过滤完之后收益腰斩说明原始策略的收益集中在无法真实成交的极端行情里这本身就是重要结论毕设里写出来反而是加分项。5.4 每次跑结果都不一样复现不了现象同一个模型、同一份数据隔天再跑 loss 和预测结果完全不同报告里没法写。原因随机种子没锁。Python 的 random、numpy、PyTorch 各自维护随机数状态只锁其中一个没有用。DataLoader 的 shuffleTrue 也会引入顺序随机性。解决按第 3 章的 seed_everything 在导入库之后、读数据之前调用DataLoader 里指定固定 seed 的 generator。这里给一段补丁代码g torch.Generator() g.manual_seed(42) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue, generatorg)另外如果你是在 pycharm 或 vscode 里配置 Python 环境跑这个项目确保依赖版本和研报里记录的 requirements 一致。PyTorch 小版本升级会改变某些算子的实现细节同样影响复现。锁完随机种子之后CPU 环境基本能复现GPU 上有小浮动属正常但趋势不会变。5.5 涨跌停日策略突然失灵现象回测里某段时间资金曲线直线跳水凑巧发生在连续大涨或大跌的几天。原因涨停日买入信号触发后实际无法成交但回测假设已经持仓净值按涨停价计算产生了纸面盈利跌停日止损单也成交不了亏损被放大。两者叠加回测曲线在极端行情里严重失真。解决在回测循环里加涨跌停过滤把当日涨幅大于等于 9.8% 的买入信号置 0跌幅小于等于 -9.8% 的卖出信号置 0。创业板、科创板阈值改成 19.8%北交所改成 29.8%做成参数即可不要把阈值写死在回测函数里。6. 进阶技巧用滚动再训练验证模型稳定性别被单次回测骗了6.1 Walk-Forward 验证每半年重训一次单次训练单次测试切分只能证明模型在最后那段行情里有效不能证明策略能持续运转。金融市场特征是漂移的两年前有效的规律放到现在可能完全失效。我跑这类项目最后都会做一次 walk-forward 验证把历史数据按2 年训练 半年测试的窗口滚动每次滚动重新训练模型、重新回测最后把所有测试段的资金曲线拼接成一条完整曲线。每一段窗口的代码结构是这样results pd.DataFrame() for start in range(0, len(X) - TRAIN_LEN - TEST_LEN, STEP_LEN): train X[start:start TRAIN_LEN] test X[start TRAIN_LEN:start TRAIN_LEN TEST_LEN] model StockLSTM() train_on(model, train) preds predict(model, test) # 记录该窗口的夏普和最大回撤 results.loc[start] [sharpe(preds), max_drawdown(preds)]TRAIN_LEN、TEST_LEN、STEP_LEN 分别是训练长度、测试长度和滚动步长比如 500、125、125对应两年训练、半年测试、每季度滚动。滚动窗口跑完如果各窗口指标波动很大——比如第一个窗口夏普 2.5第三个窗口变成 -0.3——说明模型只在特定行情里有效不具备稳健性。这个验证结果放进毕业设计报告里比单次切分有说服力得多。6.2 预测时间对齐检查一个必做的十分钟检查任何时序预测项目都要做一次滞后对齐检查把模型输出和真实值画在同一张图上如果预测曲线看起来像真实曲线向右平移了一截说明模型学到的是今天的价格接近昨天而不是预测未来。import matplotlib.pyplot as plt plt.figure(figsize(12, 4)) plt.plot(actuals[-100:], labelactual) plt.plot(preds[-100:], labelpredict) plt.legend() plt.show()如果预测曲线明显比真实曲线滞后优先去查 make_windows 的 y 是不是取了data[i, 3]把这个修掉之后大部分对齐问题都能解决。我拆这个包的时候花掉的时间大头不在模型调参而在修正第 5 章那几类问题——尤其是标签错位和归一化数据泄露都是回测里亏钱之后才回头定位的。从那以后我每次跑完一个新项目都会强制先做一遍滞后对齐检查和涨跌停过滤审计再去看收益曲线。这套源码的价值在于它把完整链路摊开了你可以拿它交差也能把它改造成自己的策略框架。希望帮到你。本文还有配套的精品资源点击获取
返回列表