简介:这份资源是面向高校学生与量化投资初学者的深度学习实战项目包,可作为毕业设计、期末大作业或人工智能课程的参考案例,帮助读者理解如何将神经网络应用于股票价格预测与交易策略开发。压缩包共46个文件,约216KB,以23个Python源码文件为核心,辅以xml配置、csv策略数据、sqlite与db数据库文件、md说明文档及txt依赖清单,覆盖数据处理、模型构建、策略执行与模拟交易等模块。项目围绕数据预处理、RNN/LSTM/CNN等模型搭建、训练调优以及历史回测评估展开,代码结构清晰,便于按模块阅读与二次开发。目前已有133人学习下载,适合希望打通深度学习与量化投资交叉知识、积累完整项目经验的读者参考借鉴。
1. 从一份 600519 的 CSV 说起:这个深度学习量化策略包到底能跑出什么
很多人第一次接触量化投资,都是从一份贵州茅台的日线数据开始的。这个压缩包里就躺着一份600519.SH.CSV,旁边还有600519.db、600519.sqlite两个同源数据文件,以及data.sqlite这个总库。它不是一个只放论文和 PPT 的毕业设计壳子,而是一套能真正跑起来的 Python 工程:stock.py负责读行情,ML.py搭深度学习网络,main.py串起训练和预测,simulate_RUN.py做模拟撮合,strategy/目录下还有一套策略字典和默认参数表。
如果你正在找一份能改、能跑、能写进毕业设计或期末大作业的深度学习量化项目,这份代码的完整度是够的——数据、模型、回测、单元测试、依赖清单都在。它适合两类人:一类是想把 LSTM、CNN 这些模型真正落到时间序列上的学生,另一类是做过传统均线策略、想看看深度学习能不能在 A 股单票上跑出点东西的开发者。下面我按自己拆包的顺序,把这份资源从结构到跑通、再到避坑,完整过一遍。
2. 拆包先看骨架:目录结构、数据流与三个入口文件
2.1 目录分层与模块职责
拿到压缩包,别急着pip install,先把目录树看一遍。这份工程的层次感比很多同类毕设强,它把「数据」「模型」「策略」「回测」拆成了独立目录,而不是全塞进一个main.py。
| 路径 | 职责 | 关键文件 |
|---|---|---|
| 根目录 | 入口与配置 | main.py、ML.py、requirements.txt、README.md |
data/ | 行情数据与数据库 | 600519.SH.CSV、600519.db、600519.sqlite、data.sqlite |
process/ | 数据清洗与入库 | Process.py、SQLFrame.py、csv2sqlite.py |
DL/ | 深度学习网络 | network.py、GPU.py |
strategy/ | 策略定义与参数 | Strategy.py、chost.py、strategyDic/default.strategy.csv |
analysis/ | 分析与绘图 | draw.py、error.py、stock.py |
simulink/ | 模拟交易 | simulink.py |
tests/ | 单元测试 | test_frame.py、test_data_frame.py、test.py |
这个分层透露出的设计意图很明确:数据从 CSV 进,经process/清洗后落到 SQLite,DL/network.py从库里取序列做训练,strategy/Strategy.py把预测信号翻译成买卖动作,最后simulate_RUN.py跑一遍历史回测。整条链路是通的,不是各写各的。
2.2 数据流:从 CSV 到 SQLite 再到模型输入
process/csv2sqlite.py是数据入口的第一站。它的作用是把600519.SH.CSV这种带表头的行情文件写进600519.db,后续所有模块都从数据库读,避免每次训练都去解析 CSV。常见做法是用 pandas 读 CSV,再用 sqlite3 的to_sql落库,字段一般包括日期、开盘、最高、最低、收盘、成交量。
# process/csv2sqlite.py 的核心逻辑(按常见实现还原) import pandas as pd import sqlite3 def csv_to_sqlite(csv_path, db_path, table_name): # 读行情 CSV,parse_dates 把日期列转成 datetime,方便后续按时间切片 df = pd.read_csv(csv_path, parse_dates=['date']) # 按日期升序,时间序列模型对顺序极度敏感,乱序会直接毁掉训练 df = df.sort_values('date').reset_index(drop=True) conn = sqlite3.connect(db_path) # if_exists='replace' 保证重复执行不会叠加脏数据 df.to_sql(table_name, conn, if_exists='replace', index=False) conn.close() return df.shape if __name__ == '__main__': print(csv_to_sqlite('data/600519.SH.CSV', 'data/600519.db', 'stock_600519'))这段代码有三个参数要盯住:parse_dates决定日期列能不能被正确识别,漏了它后面按时间窗口切序列会报类型错误;sort_values('date')是时间序列的生命线,我见过有人跳过这步,模型在训练集上 loss 降得漂亮,一回测就崩;if_exists='replace'决定重复跑脚本时是覆盖还是追加,调试阶段用 replace,正式入库前建议先备份。
2.3 三个入口文件的分工
main.py、ML.py、simulate_RUN.py是三个不同层级的入口。ML.py偏模型层,负责定义网络结构、训练循环、保存权重;main.py偏调度层,把数据加载、模型训练、信号生成串起来;simulate_RUN.py偏回测层,拿预测结果去模拟买卖。很多人跑不通,是因为直接执行main.py却发现它依赖ML.py里尚未初始化的全局配置。稳妥的顺序是:先跑csv2sqlite.py确认数据入库,再单独跑ML.py确认模型能训练,最后才跑main.py和simulate_RUN.py。
提示:
requirements.txt里通常锁定了 tensorflow 或 pytorch 的版本,先看它再建虚拟环境,能省掉一半的依赖冲突。
3. 把模型跑起来:LSTM 网络结构、训练参数与 GPU 开关
3.1 为什么这类项目默认选 LSTM 而不是普通 RNN
DL/network.py里搭的是时间序列模型。量化行情是典型的长依赖序列,普通 RNN 在反向传播时梯度容易消失,几十个交易日前的信息传不回来;LSTM 靠输入门、遗忘门、输出门三个结构把长期信息留在细胞状态里,对「过去 60 天走势影响今天」这种模式更友好。这也是深度学习做量化的入门标配,动手深度学习那类教材里讲序列模型时也是拿它当主线。
网络输入一般是滑动窗口切出来的三维张量:[样本数, 时间步长, 特征数]。时间步长常见取 20、30、60,特征数取决于你喂了几列——只用收盘价就是 1,加上成交量、换手率就是 3 到 5。输出层通常接一个全连接,回归任务输出下一日收益率,分类任务输出涨跌概率。
# DL/network.py 的典型 LSTM 结构(按常见实现还原) import torch import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size=1, hidden_size=64, num_layers=2, output_size=1): super().__init__() # batch_first=True 让输入维度是 [batch, seq, feature],符合 pandas 切窗习惯 self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) # 全连接把最后一个时间步的隐状态映射成预测值 self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): # out 形状 [batch, seq, hidden],取最后一个时间步代表整段序列 out, _ = self.lstm(x) return self.fc(out[:, -1, :])hidden_size是模型容量的核心旋钮,64 到 128 是单票日线数据的常见区间,再大就容易过拟合;num_layers=2表示堆两层 LSTM,层数越多越能拟合复杂模式,但训练时间和过拟合风险同步上升。batch_first=True这个参数如果漏了,输入维度会被当成[seq, batch, feature],报错信息往往很隐晦,是新手翻车高发点。
3.2 训练参数怎么设:损失函数、优化器与早停
ML.py里的训练循环决定了模型能不能收敛。回归任务常用 MSE 或 HuberLoss,HuberLoss 对收益率里的极端值更稳;优化器 Adam 是默认选择,学习率从 1e-3 起步,配合ReduceLROnPlateau在验证 loss 不降时自动衰减。批大小 32 或 64,训练轮数设大一点但一定要加早停,否则模型会把训练集背下来。
# ML.py 训练循环的关键片段(按常见实现还原) import torch from torch import optim model = LSTMModel(input_size=1, hidden_size=64, num_layers=2) criterion = torch.nn.HuberLoss() # 对收益率异常值更鲁棒 optimizer = optim.Adam(model.parameters(), lr=1e-3) scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=5) best_val = float('inf') for epoch in range(200): model.train() for xb, yb in train_loader: optimizer.zero_grad() loss = criterion(model(xb), yb) loss.backward() optimizer.step() # 验证阶段必须 model.eval(),否则 dropout/bn 会污染评估 model.eval() with torch.no_grad(): val_loss = sum(criterion(model(xb), yb).item() for xb, yb in val_loader) scheduler.step(val_loss) if val_loss < best_val: best_val = val_loss torch.save(model.state_dict(), 'best.pth') # 只存最优权重这里每个参数都有讲究:HuberLoss的 delta 默认 1.0,收益率量级小的时候可以调小;patience=5表示验证 loss 连续 5 轮不降就降学习率;torch.save只存state_dict而不是整个模型对象,换环境加载时更不容易出兼容问题。model.eval()和torch.no_grad()这两行如果漏写,验证 loss 会虚高,早停会误判。
3.3 GPU 开关与显存边界
DL/GPU.py是设备管理模块,负责判断有没有可用显卡并把模型和数据搬过去。单票日线数据量不大,CPU 也能跑,但如果你把时间步长拉到 120、特征堆到十几列,GPU 能省不少时间。
# DL/GPU.py 的设备选择逻辑(按常见实现还原) import torch def get_device(): if torch.cuda.is_available(): # 打印显卡型号,方便确认没跑在集显上 print('using gpu:', torch.cuda.get_device_name(0)) return torch.device('cuda') return torch.device('cpu') device = get_device() model = model.to(device) # 数据也要同步搬到同一设备,否则会报 tensor 不在同一 device xb, yb = xb.to(device), yb.to(device)常见坑是模型搬了、数据没搬,报错信息是设备不匹配;另一个坑是显存不够时 batch 太大直接 OOM,把 batch 从 64 降到 16 通常能救回来。如果torch.cuda.is_available()返回 False,先确认装的是 GPU 版而不是 CPU 版框架,这个在深度学习环境配置里是高频问题。
4. 策略与回测:从预测信号到模拟撮合的完整链路
4.1 策略字典与默认参数表
strategy/strategyDic/default.strategy.csv是这个项目的策略配置中心。它把阈值、持仓比例、止损线这些参数外置成 CSV,改策略不用动代码,这对做对比实验特别友好。Strategy.py读这张表,chost.py负责信号到订单的翻译。
| 参数名 | 含义 | 常见取值 |
|---|---|---|
| buy_threshold | 预测收益率超过该值买入 | 0.005 ~ 0.01 |
| sell_threshold | 预测收益率低于该值卖出 | -0.01 ~ -0.005 |
| position_ratio | 单次建仓占总资金比例 | 0.3 ~ 0.5 |
| stop_loss | 单笔止损线 | -0.05 |
| window_size | 模型输入时间步长 | 20 / 30 / 60 |
阈值设得太低,交易频率飙升,手续费会把利润吃光;设得太高,信号稀疏,一年可能就几笔。我一般先用 0.005 跑一遍看信号分布,再根据换手率调。
4.2 回测执行:simulate_RUN.py 在做什么
simulate_RUN.py是回测主循环。它按交易日推进,每天拿模型对最近 window_size 天的数据做一次预测,根据阈值决定买、卖还是持有,然后按当日收盘价或次日开盘价撮合,记录净值和持仓。
# simulate_RUN.py 的回测主循环(按常见实现还原) import pandas as pd def run_backtest(df, model, cfg, device): cash, shares = 1.0, 0.0 # 初始资金归一化为 1 nav = [] # 每日净值曲线 for i in range(cfg['window_size'], len(df)): window = df.iloc[i-cfg['window_size']:i][['close']].values pred = model_predict(model, window, device) # 模型预测下一日收益 price = df.iloc[i]['close'] if pred > cfg['buy_threshold'] and cash > 0: shares = cash * cfg['position_ratio'] / price cash -= shares * price elif pred < cfg['sell_threshold'] and shares > 0: cash += shares * price shares = 0 nav.append(cash + shares * price) return pd.Series(nav, index=df.index[cfg['window_size']:])这段逻辑里,cash和shares的更新顺序不能反,先算买入再扣现金,否则会出现资金为负的假象。nav记录的是每日总资产,最后用analysis/draw.py画净值曲线,和买入持有基准对比。注意这里没有扣手续费和滑点,真实回测要加上,否则收益会被高估。
4.3 单元测试与数据校验
tests/目录下的test_frame.py、test_data_frame.py是很多人会忽略的部分,但它恰恰是这份工程比同类毕设靠谱的地方。测试覆盖了数据帧的列名、类型、缺失值处理,跑一遍能提前发现数据格式问题。
# 在项目根目录执行测试 python -m pytest tests/ -v如果测试报KeyError: 'close',说明 CSV 列名和代码里约定的不一致,去process/Process.py里改映射;如果报日期解析失败,检查 CSV 的日期格式是不是YYYY-MM-DD。先让测试全绿,再跑回测,能省掉大量「模型没问题但结果不对」的排查时间。
5. 避坑与排查:这份代码最容易翻车的五个地方
5.1 数据泄漏:归一化用了全量数据
现象是回测净值曲线漂亮得不像话,年化动辄翻倍。原因是归一化时用了整段数据的均值和方差,等于把未来信息泄漏给了训练集。正确做法是只用训练集统计量,再应用到验证和测试集,或者用滚动窗口归一化。这个坑在深度学习量化项目里几乎人人踩过一次。
5.2 时间序列乱序:sort_values 漏写
现象是模型训练 loss 正常下降,但预测结果和价格走势完全对不上。原因是 CSV 读进来没按日期排序,滑动窗口切出来的序列是乱的。解决就是在csv2sqlite.py和任何读数据的地方都加sort_values('date').reset_index(drop=True),并在测试里断言日期单调递增。
5.3 设备不匹配:模型在 GPU、数据在 CPU
现象是运行时报Expected all tensors to be on the same device。原因是model.to(device)之后忘了把每个 batch 的数据也搬过去。解决是在训练循环里统一xb, yb = xb.to(device), yb.to(device),或者封装一个to_device辅助函数。如果压根没有 GPU,确认torch.cuda.is_available()的返回值,别在 CPU 版框架上找显卡。
5.4 回测未扣成本:手续费和滑点被忽略
现象是策略换手率高但收益仍然为正,实盘根本做不到。原因是simulate_RUN.py里买卖按收盘价全额成交,没有手续费、印花税和滑点。解决是在每次买卖时扣掉固定比例成本,A 股常见按单边万分之几到千分之几估算,滑点按一个最小变动价位加。加完之后很多「高收益」策略会现原形。
5.5 依赖版本冲突:框架版本与代码不匹配
现象是import torch成功但一调用 LSTM 就报参数错误,或者pandas.to_sql报 API 变更。原因是requirements.txt里的版本和你环境里的不一致。解决是严格按requirements.txt建虚拟环境,pip install -r requirements.txt,不要图省事用全局环境。如果文件里没锁死版本,至少确认框架大版本和代码写法对得上。
6. 进阶玩法:把单票策略扩成多票轮动与参数扫描
跑通单票只是起点。这份工程的strategyDic设计天然支持参数扫描——把default.strategy.csv复制几份,改不同阈值组合,写个循环批量跑simulate_RUN.py,最后对比净值曲线挑最优参数。但要注意,参数在单票上扫出来的最优值往往过拟合,换一只票就失效,所以更稳的做法是拿一批股票一起扫,看参数在横截面上的稳定性。
再往上一层是多票轮动。data/目录现在只有 600519 一份数据,但csv2sqlite.py是通用的,把其他票的 CSV 按同样格式丢进去,改一下表名,就能建出多票库。然后每天对所有票做预测,按预测收益率排序,买最高的前 N 只,这就是最朴素的深度学习选股轮动。这一步的坑在于不同票的价格量级不同,归一化必须逐票做,不能混在一起。
# 多票参数扫描的骨架(按常见实现还原) import itertools, pandas as pd results = {} for buy_t, sell_t in itertools.product([0.003, 0.005, 0.008], [-0.008, -0.005, -0.003]): cfg = {'buy_threshold': buy_t, 'sell_threshold': sell_t, 'position_ratio': 0.4, 'window_size': 30} nav = run_backtest(df, model, cfg, device) # 用夏普比率而不是总收益做筛选,避免选中高波动参数 results[(buy_t, sell_t)] = nav.pct_change().mean() / nav.pct_change().std() best = max(results, key=results.get) print('best params:', best, 'sharpe:', results[best])这段扫描用夏普比率而不是总收益做筛选标准,是因为总收益容易被一两笔极端交易拉高,夏普更能反映风险调整后的表现。itertools.product生成参数组合,每个组合跑一遍回测,最后取夏普最高的。实际跑的时候建议把结果存成 CSV,方便后面画热力图看参数敏感性。
验证方法上,我习惯把数据按时间切成三段:前 60% 训练,中间 20% 验证调参,最后 20% 完全不碰,只在最终确认时跑一次。如果最后 20% 的表现和验证集差距巨大,说明参数过拟合了,得退回去简化模型或加正则。这个习惯是被坑出来的——早年我拿全量数据调参,回测夏普 2.0,实盘一上就亏,从那以后每次调参都强制留一段「没见过」的数据做最终检验。希望这份拆解能帮你少走点弯路,把这份资源真正跑出自己的结果。
本文还有配套的精品资源,点击获取