尧图精选

深度学习量化交易实战:从数据清洗到回测避坑指南

🕒 发布时间:2026/10/1 6:16:16 📁 来源:尧图网络
简介融合深度学习与股票量化方向的完整项目源码面向毕业设计、课程设计或相关课题自学可直接作为代码参考。项目采用Python编写核心利用卷积神经网络CNN、循环神经网络RNN等模型分析历史行情预测股价走势并生成可回测的交易策略。压缩包共49个文件以42个Python脚本为主另含2个Jupyter Notebook和配置文件、说明文档整体仅2.93MB脚本覆盖数据下载与预处理、多类深度学习模型、策略回测与监控、pytest单元测试等模块目录层次清楚。已有91人学习适合具备Python与机器学习基础的读者深入实践。通过该资源可直观了解从数据准备、模型训练到策略落地的完整工程流程为论文实验或课程报告提供可复用代码框架也能帮助理解NLinear、DLinear、TFT等主流时序模型的应用方式。1. 基于深度学习的股票量化交易打开这个 ZIP 之前要先把预期摆正很多人拿到「基于深度学习的股票量化交易.zip」的第一反应是终于有个能把神经网络扔进 K 线里自动赚钱的现成方案了。真实情况会泼一盆冷水——深度学习在量化交易里解决的是「从数据里学出一个可交易信号」的问题不是「预测明天涨跌」的玄学问题。这个 ZIP 里真正值钱的东西通常是数据清洗流程、特征工程、模型训练脚本和回测框架的组合而不是某个能稳定盈利的「圣杯模型」。适合打开这个 ZIP 的是两类人一类是有 Python 基础、想把手里的行情数据喂给神经网络、看看能不能替代手工因子挖掘的开发者另一类是被传统多因子策略搞烦了、想用序列模型试试水的中级量化爱好者。动手之前先想清楚一个问题你期望模型输出什么——是涨跌概率、未来 N 日收益率还是直接给你买卖信号这个选择决定了后面所有代码怎么组织也决定了你大概率不会在回测阶段就被垃圾结果劝退。2. 解压后先看什么一个能落地的深度学习量化项目模块大概长这样拿到 ZIP 后别急着找 train.py 跑起来。先花二十分钟把目录结构和配置文件过一遍搞清楚数据从哪来、特征在哪造、模型在哪儿被调用比你直接改参数跑一晚上有意义得多。2.1 项目目录结构先对文件布局有数再动手改代码一个常见的目录结构长这样不同项目会有出入但套路基本一致. ├── README.md ├── requirements.txt ├── configs/ │ └── config.yaml # 全局参数数据范围、特征列表、模型超参、回测参数 ├── src/ │ ├── data/ │ │ ├── loader.py # 行情数据读取与预处理 │ │ └── dataset.py # 构造滑动窗口样本 │ ├── features/ │ │ └── engineer.py # 特征计算与标签构造 │ ├── models/ │ │ ├── lstm_model.py # 序列模型定义 │ │ └── train.py # 训练、验证、保存权重 │ ├── backtest/ │ │ └── engine.py # 信号转持仓、绩效计算 │ └── utils/ │ └── logger.py ├── notebooks/ │ └── explore.ipynb # 数据探索和特征可视化 └── outputs/ ├── checkpoints/ # 模型权重 └── reports/ # 回测报告先读 README 和 config.yaml比读源码更省时间。README 一般会写明数据格式要求和运行顺序config.yaml 里的数据路径、特征列名、序列长度这些参数决定你跑起来后会不会在第一步就报「KeyError」或者「维度不匹配」。这个结构的核心思想是数据、模型、回测三者解耦——你换一个模型比如把 LSTM 换成 Transformer不需要动回测代码你换一套数据比如从日线换成分钟线不需要动模型定义。如果你拿到的 ZIP 把所有代码压在一个巨型 notebook 里那你的第一件事是先把数据加载和模型训练拆开不然后面每调一次参都要重跑前面所有单元格。2.2 数据模块拿到行情数据第一步是清洗与切分行情数据最常见的格式是 CSV两列最关键的交易日期、收盘价。但真正能喂给模型的数据至少要包含 OHLCV 五列有些项目还会带上成交量加权均价。第一步不是建模是把数据变成按时间排序的、干净的、对齐的 DataFrameimport pandas as pd df pd.read_csv(data/600519.csv, parse_dates[trade_date]) df df.sort_values(trade_date).reset_index(dropTrue) # 清洗去重、去停牌、按交易日重采样 df df.drop_duplicates(subsettrade_date) df df[df[volume] 0] # 成交量大于0排除停牌日 # 收益率列预测目标的基础 df[ret] df[close].pct_change() # 划分训练/验证/测试集按时间切分严禁随机打乱 split_date pd.Timestamp(2022-01-01) train_df df[df[trade_date] split_date] test_df df[df[trade_date] split_date] print(ftrain: {train_df.shape}, test: {test_df.shape})这里最关键的一行是df df[df[volume] 0]——停牌日不参与训练不然模型会学到「连续三天零成交后突然涨停」这种没有可交易意义的模式。另一个关键点数据切分必须按时间不能用 sklearn 的 train_test_split 做随机切分。时间序列的随机切分会让训练集里混入未来数据验证集里出现过去数据回测结果虚高得离谱。2.3 特征工程与标签构造模型学的是「预测什么」比模型结构更重要深度学习模型再强也只能从你给它的输入里找规律。常见特征分三类原始行情OHLCV、收益率、技术指标均线、RSI、布林带、横截面特征个股相对大盘的超额收益。一个务实的做法是先动手算几组经典特征跑通流程后再慢慢加# 技术指标特征 df[ma_5] df[close].rolling(5).mean() df[ma_20] df[close].rolling(20).mean() df[momentum] df[close] / df[close].shift(10) - 1.0 df[volatility] df[ret].rolling(20).std() # 标签构造未来5日收益率可交易、可回测 df[label] df[close].shift(-5) / df[close] - 1.0 # 删除含缺失值的行滚动窗口需要足够长度 df df.dropna().reset_index(dropTrue)注意shift(-5)这个方向——标签用的是未来数据这叫超前标签是监督学习的正常设计但任何超前信息都不能出现在特征里。一个常见的低级错误Scaler标准化器在包含未来的全体数据上 fit导致特征里混入「未来统计信息」这在回测里发现不了但实盘一定翻车。正确做法下文避坑章节会详细讲。特征的数量和类型远比模型层数影响大。我一般建议先做 1020 个特征别一上来搞两百个——深度模型过拟合从来不需要那么多输入而且特征多了训练时间成倍增长排查问题也更麻烦。3. 用 PyTorch 搭一个可跑的序列预测模型从数据加载到训练循环动手训练之前先确认环境里装好了 PyTorch。深度学习框架的环境配置是很多新手第一个劝退点conda 换源、CUDA 版本不对、装 CPU 版还是 GPU 版……建议直接用 Miniconda 新建独立环境Python 3.9 以上PyTorch 选对应自己 CUDA 版本的 wheel 包先跑通 CPU 版再上 GPU能省掉大量环境踩坑时间。3.1 数据加载器把时间序列切成滑动窗口模型没法吃整段 K 线需要切成固定长度的窗口。窗口长度lookback决定模型能看到多长的历史常用 2060 个交易日对应约 13 个月。窗口太短学不到中期趋势太长会引入与预测目标无关的噪声import torch from torch.utils.data import Dataset class StockDataset(Dataset): def __init__(self, features, labels, lookback30): self.features torch.tensor(features, dtypetorch.float32) self.labels torch.tensor(labels, dtypetorch.float32) self.lookback lookback def __len__(self): return len(self.features) - self.lookback def __getitem__(self, idx): # 返回从 idx 开始的 lookback 行特征和对应标签 x self.features[idx: idx self.lookback] y self.labels[idx self.lookback - 1] # 标签对齐到窗口最后一天 return x, y这里标签对齐到窗口最后一天意思是「用最近 30 天的数据预测第 30 天之后的 5 日收益」。如果你觉得预测步长太长也可以把label改成 shift(-1) 构造的次日收益但那样信号噪声会大很多模型很难学出稳定规律。3.2 模型定义LSTM 是基准别一上来就上 Transformer序列建模的首选是 LSTM原因有两个一是它对中等长度序列3060 步的学习效率高二是超参数相对好调、不容易出现训练不收敛。CNN 也可以做但更适合提取局部模式而不是长期依赖Transformer 效果好但数据量需求大股票日线数据通常只有几千条容易过拟合。一个可用的 LSTM 模型长这样import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, dropout0.3): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.regressor nn.Sequential( nn.Linear(hidden_size, 16), nn.ReLU(), nn.Linear(16, 1) ) def forward(self, x): # x shape: (batch, lookback, input_size) out, _ self.lstm(x) # 取最后一个时间步的输出 last_step out[:, -1, :] return self.regressor(last_step).squeeze(-1)hidden_size64是经验起点值特征少可以降到 32特征多可以升到 128。num_layers2是深度和稳定性的折中再深就容易梯度消失或者明显过拟合。dropout0.3是正则化关键建议在 0.20.5 之间调过大欠拟合过小失去作用。注意 PyTorch 的 LSTM 只有在num_layers2时 dropout 参数才生效。3.3 训练循环与早停进度指标看验证集不看训练集训练循环本身不难写难的是如何判断该停。最可靠的做法是验证集早停——每训练一轮在验证集上算一次 loss连续 N 轮不下降就停保留最优权重import torch.optim as optim model LSTMModel(input_sizetrain_dataset.features.shape[-1]) optimizer optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.5) loss_fn nn.MSELoss() train_loader torch.utils.data.DataLoader(train_dataset, batch_size256, shuffleTrue) val_loader torch.utils.data.DataLoader(val_dataset, batch_size256, shuffleFalse) best_loss float(inf) patience 0 EPOCHS 60 for epoch in range(EPOCHS): model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() pred model(x_batch) loss loss_fn(pred, y_batch) loss.backward() optimizer.step() # 验证 model.eval() val_losses [] with torch.no_grad(): for x_val, y_val in val_loader: pred model(x_val) val_losses.append(loss_fn(pred, y_val).item()) avg_val_loss sum(val_losses) / len(val_losses) scheduler.step() # 早停逻辑 if avg_val_loss best_loss: best_loss avg_val_loss torch.save(model.state_dict(), outputs/checkpoints/best_model.pth) patience 0 else: patience 1 if patience 15: print(fearly stop at epoch {epoch}) break print(fepoch {epoch}, val_loss: {avg_val_loss:.6f})三点说明。第一训练集可以用 shuffleTrue因为样本之间有重叠滑动窗口随机顺序等价于加了噪声正则但验证集绝对不能 shuffle它必须保持时间顺序才能模拟「用过去预测未来」的真实场景。第二weight_decay1e-4配合早停是对抗股票数据过拟合的两道主要防线比调模型结构更有效。第三学习率1e-3是 Adam 的常见起点如果验证 loss 前期剧烈震荡降到3e-4如果 loss 一直不降先检查标签是否构造错比如全是 NaN再考虑调学习率。4. 回测与参数设置让模型从「会预测」变成「能交易」模型训练完预测结果只是一串数字必须经过信号生成和回测才能说「这个策略行不行」。这一步是区分「模型调参工程师」和「量化策略工程师」的分水岭——前者只看 loss后者看交易成本和滑点后的真实收益。4.1 从预测到信号阈值、排序、仓位预测值怎么变成交易动作常见有三种做法。单标的预测收益率 阈值做多 负阈值做空A 股只能做多做空得靠股指期货。多标的把预测值排序买入前 K 只。也可以用预测值直接作为仓位权重。一个简单可跑的版本import numpy as np # predictions: 模型对每日的预测收益率已按时间排序 # close_prices: 对应日期的收盘价 signal np.where(predictions 0.01, 1.0, 0.0) # 预测5日收益1%才买入 positions pd.Series(signal, indextest_df.index).shift(1) # 次日开盘执行 # 简化回测按收盘价成交费用按双边万三 滑点万五估算 cost_rate 0.0003 0.0005 strategy_ret positions * test_df[ret] - cost_rate * positions.diff().abs() cumulative (1 strategy_ret).cumprod()这里有两个容易出错的细节。shift(1)是防止未来函数——当天的信号必须等到次日才能成交否则你是在「用当天的预测买当天的收盘价」这在回测里算出天堂收益实盘根本复现不了。阈值0.01是根据标签构造时的预测周期定的预测 5 日收益 1% 约等于年化 50%是个不算低的门槛如果你预测的是次日收益阈值要大幅调低到 0.002 左右。4.2 回测指标年化、夏普、最大回撤一个都不能少很多 demo 项目回测只输出一个 final_return这远远不够。至少要看四个指标指标计算公式简化合格线参考说明年化收益率cumulative ** (252 / 天数) - 1 15%别只看这个要结合回撤夏普比率日均收益均值 / 日均收益标准差 * sqrt(252) 1.0衡量每单位风险的超额收益最大回撤max(1 - cumulative / cumulative.cummax()) 25%策略最惨时从峰值跌多少胜率盈利交易次数 / 总交易次数 50%结合盈亏比一起看这些指标的代码实现都不复杂cumulative.cummax()就能算最大回撤。但真正重要的是读指标的方式年化 30%、夏普 0.8、最大回撤 40% 这个组合不如年化 18%、夏普 1.5、最大回撤 12% 值得投入——前者是过山车后者才是可实盘的策略。如果你看到回测报告里年化收益超过 80% 而最大回撤只有 5%先别高兴大概率是数据泄漏或者没算交易成本。4.3 关键参数设置训练频率、持仓周期、止损规则回测框架搭好后几个「模型无关但策略生死攸关」的参数要定明白。重训练频率市场风格会漂移模型需要定期用新数据重新训练。常见做法是每周或每月滚动重训一次。重训频率过高成本大过低模型会「过期」。我一般先做月度滚动的回测——每个月用之前 3 年的数据训练然后用下个月的数据做样本外测试循环往复。持仓周期这由标签的预测周期决定。预测 5 日收益就持仓大约 5 个交易日再换仓。不要用「预测 5 日」的模型做「每日换仓」的交易交易频率和预测周期不匹配手续费会吃光所有利润。止损规则深度学习模型的预测置信度不是均匀的在波动大的日子里预测很容易错。加一条硬止损单笔亏损超过 5% 强制平仓。这在回测引擎里就是一行if position_entry_ret -0.05: position 0但对最终收益曲线的形状影响巨大。5. 避坑深度学习量化交易的 5 个经典翻车现场这一节浓缩了我见过的、以及自己踩过的坑。每一条都是「回测漂亮、实盘拉胯」的常见元凶。5.1 数据泄漏Scaler 在全量数据上 fit回测夏普 4.0实盘亏到自闭现象模型在验证集上表现惊人夏普 3.5 以上最大回撤控制在 5% 以内但一上模拟盘就稳定亏损。原因最常见的是标准化/归一化操作违规。有人用sklearn.preprocessing.StandardScaler对整个数据集fit_transform再切训练测试集。问题是 Scalers 已经在「看见过」测试集统计量均值、方差等于把未来的分布信息写进了训练数据。模型不是学会了预测而是学会了作弊。解决先按时间切分数据再在训练集上scaler.fit()然后用同一个 scaler 分别transform训练集和测试集并且把 scaler 保存下来后续每一次滚动重训都只用当时已发生的数据拟合。代码就三行# 先切分再标准化 scaler StandardScaler().fit(X_train) X_train_scaled scaler.transform(X_train) X_test_scaled scaler.transform(X_test) # 不 fit 测试集5.2 停牌股和涨跌停板模型「买入」了根本买不到的价格现象回测里某只股票连续涨停模型次日「买入」吃到了大肉但实盘时这支股票开盘即封死涨停你根本成交不了。原因回测引擎没有处理涨跌停和停牌规则。对 A 股来说涨停板上买不进、跌停板上卖不出这是交易的基本约束但很多 demo 回测完全忽略。解决数据清洗时把次日涨停或一字涨停的样本从验证和测试集中剔除或者回测引擎里加约束如果目标股票当天开盘价处于涨跌停价附近强制信号置为 0。同时在数据预处理时过滤停牌日前文已经做了volume0过滤并在回测里把「无法成交」的滑点惩罚计入成本。5.3 过拟合到噪声训练 loss 一路降验证 loss 从第 3 轮开始飙升现象LSTM 层数加到 4hidden_size 开到 256训练 loss 降到 0.001但验证集 loss 是训练集的 10 倍以上回测曲线像心电图。原因股票数据的信噪比极低有研究表明日收益率中真正可预测的部分可能不到 5%模型能力越强越容易把噪声当成规律背下来。深度学习模型本来就有强大的拟合能力在小样本时间序列上过拟合几乎是必然的区别只是早晚。解决三件事。第一把hidden_size降到 3264num_layers保持 2第二weight_decay从1e-4提到1e-3同时把 dropout 从 0.3 提到 0.5第三早停的 patience 从 15 降到 8宁可少学几轮也不要过拟合。这三板斧下来验证 loss 通常会变得比训练 loss 高但不再离谱回测曲线也从「心电图」变成「带小波浪的上升曲线」。5.4 手续费和滑点被忽略回测年化 25%实际到手可能是亏的现象回测报告中交易次数一年超过 100 次年化收益 25%但跑模拟盘三个月后发现账户在缩水。原因回测时没扣交易成本或者只扣了佣金没扣滑点。A 股双边佣金约万 2.5万 3卖出还有印花税千 1加上冲击成本一次高频换仓的实际成本可以到 1% 以上。一年 100 次交易就是 100% 的成本——策略年化 25% 根本覆盖不了。解决回测引擎中把成本参数显式化——佣金按双边万 2.5 计印花税卖出按千 1 计滑点按每个信号 0.1% 计对中小市值股票保守一点可以到 0.2%。把成本写进回测逻辑里你会发现很多「盈利策略」在加完成本后直接变成负收益这逼着你降低换手率——这是好事低换手策略的实盘表现通常比高频策略稳得多。5.5 样本外失效同一套参数换个时间段就失灵现象模型在 2019-2021 年的测试集上表现优秀但换成 2022-2024 年的数据直接跑输基准甚至亏损。原因单一时间段的回测只能证明「在市场某种状态下模型有效」。A 股市场风格切换剧烈2020 年的消费抱团、2022 年的赛道崩塌、2024 年的小盘微盘股行情市场规律完全不同。一个只在牛市环境里学到规律的模型到熊市或震荡市必然失效。解决回测至少覆盖三种市场状态——上涨趋势、下跌趋势、震荡市各取一年做样本外测试。更进一步的做法是滚动回测也叫 walk-forward optimization用过去 3 年训练、下 1 个月测试然后每月前移把整个历史像拉链一样走一遍最后统计所有样本外月份的累计表现。如果滚动回测的收益曲线是稳定的斜向上而不是「一年暴涨、一年暴跌」才能说明策略有真实的泛化能力。6. 进阶用滚动训练和置信度过滤提高策略真实收益率回测框架跑通、常见坑排掉之后有两个值得投入时间的方向能明显拉开你和其他「调参选手」的差距。第一个方向是滚动重训练管道。不要训练一次就完事而是把「训练-预测-回测」封装成一个可循环的管道。以月度滚动为例从 2019 年开始每个月初用截至上月底的 3 年数据训练模型预测当月每个交易日的信号月底结算绩效然后下个月重复。这个管道本质上是一个 for 循环但它的价值在于——你获得了大量相互独立的样本外预测结果可以用这些结果做统计检验而不是只在某一个测试集上自我感觉良好。同样的框架下你也能对比「每月重训」和「每季度重训」哪个更稳定。第二个方向是预测置信度过滤。LSTM 回归输出的预测值本身没有置信度但你可以用多次预测的标准差来估计——比如用 dropout 开启的蒙特卡洛采样预测 20 次如果每次预测的方向一致且波动小说明模型对当前这个样本「有信心」如果 20 次预测里有涨有跌说明输入样本落在模型不擅长的区域。做法是 inference 时打开model.train()模式dropout 依然生效预测 N 次取均值、算标准差然后过滤掉标准差过大的样本——宁可不交易也不要乱交易。这个技巧能不能救命在我自己的经历里它能过滤掉大量市场风格突变期的错误信号比在损失函数上做文章划算得多。我现在的习惯是任何深度学习量化策略先跑完月度滚动回测、看完三种市场状态下的表现、再上模拟盘观察至少两个月然后才敢把真金白银放进去。这个流程也是我给所有拿到这类项目代码的读者唯一的建议——模型训练只是开始验证和纪律才是决定最终盈亏的部分。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →