
简介一套基于深度学习的股票分析预测系统Python源码主要服务于Python期末大作业、课程设计与毕业设计等场景面向计算机、人工智能相关专业的高年级本科生及进阶学习者项目代码结构完整经过本地测试与调试可稳定运行。系统将多层感知机与长短期记忆网络LSTM组合成混合预测模型按“数据下载—指标计算—特征工程—模型训练—策略回测”流程组织文档中说明了数据预处理、超参数优化与模型评估方法并提供6张股价预测效果图辅助理解。压缩包共25个文件核心为6个Python脚本另包含CSV历史行情数据、预测结果JPG图片、README说明、依赖清单、License、Git配置及备份文件整体大小约6.34MB数据按data、train_data、stocks等目录存放层次清楚。目前已有96人学习下载代码均带中文注释可在本地快速复现实验流程并继续扩展新算法或新数据源适合作为期末项目答辩与二次开发的基础。1. 基于深度学习的股票分析预测系统在期末项目里到底该做成什么样先说结论期末项目想拿高分靠的不是把LSTM跑通而是把「数据→模型→交易信号→回测→可演示」这条链完整搭起来。很多同学交上去的是一个Jupyter Notebook里面画了几张预测值和真实值叠在一起的图老师一问“你这个信号敢实盘吗”就哑火。这个标题真正要交付的是一个有输入、有输出、有评价指标的股票分析预测系统深度学习只是中间那段引擎。本文按我自己的落地习惯带你从数据获取一路走到Web展示顺带把最容易让成绩翻车的数据泄漏、未来函数和过拟合问题提前拆掉。适合已经把Python基础语法过了一遍、想在一个月内交出一份能讲清楚原理又能现场演示的项目的人。2. 数据获取与预处理从akshare拉行情到构造监督学习样本2.1 数据源选型akshare为什么比tushare更适合期末项目做股票预测第一步是拿数据。常见选择是tushare和akshare。tushare老牌但很多接口需要积分积分不够连日线数据都拿不全期末项目时间紧我不想在权限上耗。akshare是另一个路子它把公开网页接口包装成统一API免费、不用注册就能拿A股、港股、美股和指数数据。它的缺点是接口偶尔会因目标网站改版而失效但作为期末项目够用了。安装很简单pip install akshare pandas numpy matplotlib如果你的Python环境是3.8以上一般不会有依赖冲突。装完先拉一段沪深300指数日线试试import akshare as ak # 拉取沪深300指数日线数据 df ak.stock_zh_index_daily_em(symbolsh000300) df df.rename(columns{date: 日期, open: 开盘, high: 最高, low: 最低, close: 收盘, volume: 成交量}) print(df.tail())这段代码里的stock_zh_index_daily_em是东方财富接口返回的列名是英文我们改成中文方便后面处理。如果你要个股用ak.stock_zh_a_hist(symbol000001, perioddaily, start_date20200101, end_date20231231, adjustqfq)其中symbol是股票代码adjustqfq表示前复权。前复权很重要因为分红送股会导致价格跳空不复权的话模型会把这种跳空当成真实波动预测就被带偏了。拿到数据后第一件事是检查空值和重复行df.dropna(inplaceTrue) df.drop_duplicates(subset日期, inplaceTrue) df.sort_values(日期, inplaceTrue) df.reset_index(dropTrue, inplaceTrue)排序必须按日期升序深度学习模型天然按时间依赖建模顺序反了等于白训。2.2 特征工程技术指标怎么算标签怎么定股票预测不是光把收盘价喂进去就完事。常见的做法是构造一组技术指标作为特征比如动量、波动率、均线偏离、成交量变化等。这一步决定了模型能看多远。下面代码用pandas计算几个经典特征import pandas as pd import numpy as np df[回报率] df[收盘].pct_change() df[MA5] df[收盘].rolling(5).mean() df[MA20] df[收盘].rolling(20).mean() df[价格_MA5偏离] (df[收盘] - df[MA5]) / df[MA5] df[波动率] df[回报率].rolling(10).std() df[成交量变化] df[成交量].pct_change() # 删除前20行因为rolling窗口没算满 df.dropna(inplaceTrue) df.reset_index(dropTrue, inplaceTrue)特征列可以继续加但注意两点一是不要用未来数据比如当天收盘后才知道的指标只能用于预测下一天不能用来预测当天二是特征之间相关性太高会让模型学不到新信息。我一般先用df.corr()扫一眼剔除和收盘价相关性超过0.99的列比如直接用收盘价算出来的MA5在窗口内的相关性就很高留下偏离度比留下MA本身更有用。然后是标签。预测目标有两种回归预测下一日收盘价或者分类预测下一日涨跌。期末项目我建议做分类原因后面会讲。标签定义如下df[目标] (df[收盘].shift(-1) df[收盘]).astype(int) df.dropna(subset[目标], inplaceTrue)shift(-1)把下一天的收盘价搬到今天这一行如果高于今天收盘价标签为1否则为0。这样模型学习的是「基于今天及之前的信息判断明天是否上涨」符合交易逻辑。注意不要用shift(0)那就变成用今天预测今天属于未来函数。2.3 构造滑动窗口样本序列长度怎么定深度学习模型处理时间序列时输入不是一个样本点而是一段窗口。比如用过去20天的特征预测明天的涨跌。下面这段代码把DataFrame转成[样本数, 窗口长度, 特征数]的三维数组def make_sequences(data, seq_len20, feat_colsNone, target_col目标): if feat_cols is None: feat_cols [c for c in data.columns if c not in [日期, 目标]] xs, ys [], [] for i in range(len(data) - seq_len): x data[feat_cols].iloc[i:i seq_len].values y data[target_col].iloc[i seq_len] xs.append(x) ys.append(y) return np.array(xs, dtypenp.float32), np.array(ys, dtypenp.float32) seq_len 20 X, y make_sequences(df, seq_lenseq_len) print(X.shape, y.shape) # (样本数, 20, 特征数) (样本数,)窗口长度seq_len是可以调的参数。20对应一个月左右对短线预测比较常见。如果做的是中长线可以取60如果数据量很小窗口太大会导致样本数不足。这里有个原则窗口长度至少要和你想让模型记住的趋势周期匹配但不要超过总样本数的十分之一。这个三维数组的格式正好是PyTorch里LSTM的输入格式后面直接喂给模型。3. 模型选型与训练LSTM、Transformer还是CNN先把基线跑通3.1 期末项目到底该选哪个模型很多同学一上来就想用Transformer觉得LSTM老。但期末项目追求的是“能解释、能训练、能拿分”。我把常用模型做了个对比模型适用场景训练难度答辩友好度我的建议LSTM时间序列、序列依赖低稳定高行内人都懂首选基线GRU与LSTM接近更低高LSTM跑不动可换CNN一维局部特征提取低中可做对比实验Transformer长序列、复杂依赖高容易过拟合低解释难数据量大再考虑我的做法是先跑LSTM当基线拿到一组结果后再换GRU或一维CNN做对比。老师最喜欢问“你为什么选这个模型”有对比实验就能回答“我在同样条件下比较了LSTM和GRULSTM在验证集胜率上高2个百分点所以选它。”3.2 PyTorch实现LSTM预测模型的核心代码用PyTorch写一个两层LSTM加全连接分类头的模型完整代码如下import torch import torch.nn as nn class StockLSTM(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, num_classes2, dropout0.3): super(StockLSTM, self).__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.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Dropout(0.3), nn.Linear(32, num_classes) ) def forward(self, x): # x: (batch_size, seq_len, input_size) out, _ self.lstm(x) # 取最后一个时间步的输出 last_hidden out[:, -1, :] return self.fc(last_hidden)逻辑说明batch_firstTrue表示输入张量第一维是批次方便直接喂之前构造的X。LSTM的hidden_size是隐藏层神经元数num_layers2表示堆叠两层第二层能捕捉更高层的时间特征。dropout放在LSTM层之间和全连接里抑制过拟合。out[:, -1, :]取的是序列最后一个时间步的隐藏状态因为我们要预测的是第seq_len1天的涨跌最后一天的状态已经汇总了整段窗口的信息。3.3 训练参数设置序列长度、学习率、batch size、早停训练前先把数据切分。注意时间序列不能用随机切分必须按时间顺序切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:]这里80%做训练20%做测试而且测试集严格在训练集之后避免模型“偷看”未来数据。接着做标准化。很多教程在切分前对整个X做标准化这是数据泄漏后面避坑章详细说。正确做法是只计算训练集的均值方差再应用到测试集from sklearn.preprocessing import StandardScaler # X的形状是(样本数, seq_len, 特征数)需要把特征维拉平再标准化 n_samples, n_seq, n_feat X.shape X_train_reshaped X_train.reshape(-1, n_feat) scaler StandardScaler().fit(X_train_reshaped) def scale_transform(X): return X.reshape(-1, n_feat).pipe(lambda arr: scaler.transform(arr)).reshape(X.shape) # 这样写不对直接手动上面的pipe写法有问题我们直接用常规写法X_train_reshaped X_train.reshape(-1, n_feat) X_train_scaled scaler.transform(X_train_reshaped).reshape(X_train.shape) X_test_reshaped X_test.reshape(-1, n_feat) X_test_scaled scaler.transform(X_test_reshaped).reshape(X_test.shape)注意测试集的标准化用的都是scaler对象它只见过训练集的数据。标准化要把每个特征列的均值方差都算一遍reshapen后再transform是对的。训练循环的核心参数如下import torch.optim as optim from torch.utils.data import TensorDataset, DataLoader batch_size 64 learning_rate 1e-3 epochs 50 train_dataset TensorDataset(torch.tensor(X_train_scaled), torch.tensor(y_train)) train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleFalse, drop_lastTrue) model StockLSTM(input_sizen_feat) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lrlearning_rate) # 早停参数 best_val_loss float(inf) patience 10 counter 0 for epoch in range(epochs): model.train() total_loss 0 for xb, yb in train_loader: optimizer.zero_grad() out model(xb) loss criterion(out, yb) loss.backward() optimizer.step() total_loss loss.item() # 验证集上计算损失 model.eval() with torch.no_grad(): val_out model(torch.tensor(X_test_scaled)) val_loss criterion(val_out, torch.tensor(y_test)).item() if val_loss best_val_loss: best_val_loss val_loss counter 0 torch.save(model.state_dict(), best_model.pt) print(fEpoch {epoch}: train_loss{total_loss/len(train_loader):.4f}, val_loss{val_loss:.4f}, 已保存) else: counter 1 if counter patience: print(早停触发) break参数含义shuffleFalse因为时间序列不能打乱顺序同一个batch里的样本必须是连续的时间段。drop_lastTrue防止最后一个batch样本数量不整齐影响BatchNorm之类的层LSTM没BatchNorm但保持习惯。learning_rate1e-3是Adam常用的初始值如果损失剧烈震荡降到5e-4试试。早停的patience10意思是连续10个epoch验证损失不降低就停目的是防止过拟合同时省时间。训练完成后载入最优模型model.load_state_dict(torch.load(best_model.pt)) model.eval()4. 从预测到信号涨跌分类与回测别只画一条预测曲线4.1 回归预测的坑为什么直接预测价格容易翻车如果做回归输出层只有一个神经元学习率稍微调不好损失就变成NaN。更关键的是股票价格是非平稳序列今天6000点明天稍微波动几十点均方误差都被大数值主导模型学到的其实是“价格平均水平”而不是“涨跌方向”。而且期末答辩时老师看一眼预测曲线和真实曲线重合度很高会追问“你这个预测是不是先把未来值混进来了”。实际上很多回归模型因为用了未来信息才看起来漂亮这是个容易引火烧身的坑。所以我的建议是做成二分类预测明天涨还是跌。这样做有几个好处损失函数用交叉熵更稳定评价指标用准确率、F1、胜率更直观回测策略也好实现——预测涨就买入预测跌就卖出或空仓。4.2 涨跌分类的阈值与损失函数细节分类模型输出的是两个类别的logit经过softmax后变成概率。判断预测类别时取概率较大者import torch.nn.functional as F with torch.no_grad(): logits model(torch.tensor(X_test_scaled)) probs F.softmax(logits, dim1) pred_class probs[:, 1].numpy() # 明天上涨的概率 # 默认阈值0.5可以根据验证集调整 threshold 0.5 pred_signal (pred_class threshold).astype(int)阈值不一定非0.5。如果模型预测上涨概率略高于0.5但实际胜率一般可以把阈值提高到0.6只做把握更大的信号。调整阈值的方式是在验证集上扫描best_threshold 0.5 best_acc 0 for t in np.arange(0.4, 0.7, 0.05): acc ((pred_class t).astype(int) y_test).mean() if acc best_acc: best_acc acc best_threshold t print(最优阈值:, best_threshold, 准确率:, best_acc)这里有个容易犯的错用测试集调阈值也算一种数据泄漏。正确做法是再从训练集尾部切一段做验证集调好阈值后再在测试集上评估。期末项目如果样本量不大可以简单一点但答辩时你要能说清楚“阈值的设定没有用过测试集”。4.3 简单回测框架收益率、胜率、最大回撤怎么算预测信号必须放到交易场景里检验。这个回测框架很简单假设每天开盘按信号买入次日收盘卖出不计手续费df_test df.iloc[train_size:].copy() # 注意要和X_test切分位置对应 df_test[预测概率] pred_class df_test[实际涨跌] df_test[收盘].shift(-1) df_test[收盘] # 生成信号预测上涨则持仓否则空仓 df_test[仓位] (pred_class best_threshold).astype(int) # 每日收益率有仓位则当日收益率无仓位则0 df_test[当日收益率] df_test[收盘].pct_change() * df_test[仓位] df_test.dropna(inplaceTrue) # 累计收益曲线 df_test[累计收益] (1 df_test[当日收益率]).cumprod() - 1 print(累计收益率:, df_test[累计收益].iloc[-1]) # 胜率只统计有交易的日子 trade_days df_test[df_test[仓位] 1] win_rate (trade_days[当日收益率] 0).mean() print(策略胜率:, win_rate) # 最大回撤 cum (1 df_test[当日收益率]).cumprod() peak cum.cummax() drawdown (cum - peak) / peak max_drawdown drawdown.min() print(最大回撤:, max_drawdown)回测的基准是买持有策略也就是仓位始终为1。你可以在图里画两条累计收益曲线一条是模型策略一条是买入持有。期末答辩时哪怕模型策略收益没跑赢基准只要你能从胜率、回撤角度分析原因也是加分项。怕就怕只有一张预测图没有交易逻辑老师自然认为你是“对着历史数据画线”。5. 期末项目避坑指南数据泄漏、归一化、过拟合和答辩追问5.1 数据泄漏归一化用了全样本统计量预测结果虚高现象训练出来的模型在测试集准确率高达0.95但一换新数据就崩。原因在切分数据集之前就对全部数据做了StandardScaler.fit测试集的均值方差已经混进了模型看到的分布等于考试前偷看了答案。解决先切分再对训练集fit然后用同一个scaler.transform处理测试集。我在2.3节已经给出了正确写法这是整个项目里最容易被忽视但最致命的问题。如果你用的是sklearn.pipeline也务必把fit限制在训练集上。5.2 训练集和测试集时间顺序shuffle毁所有现象用train_test_split默认的随机切分模型指标不错但回测完全不行。原因随机切分会让训练集里包含测试集以后的数据模型提前“看到”了未来行情尤其是趋势行情里哪怕泄露一天的顺序都有可能让预测虚高。解决严格按照时间顺序切分X_train X[:train_size]X_test X[train_size:]。另外在DataLoader里设置shuffleFalse保证batch内部顺序不被打乱。如果你想让模型看到更多顺序变化可以在每个epoch内用固定的torch.Generator().manual_seed(42)打乱batch顺序但样本顺序不能乱。5.3 未来函数技术指标里用了当日收盘价预测当日涨跌现象预测标签是“明天涨跌”但特征里包含今天的收盘价、今天的量比这些在明天开盘时是已知的没问题但如果你把shift(-1)应用在特征列上把明天的指标放进今天的样本那就成了未来函数。我见过有人把均线直接算在包含未来数据的整个DataFrame上然后iloc切窗窗口末端的指标其实包含了窗口后一天的数据模型自然“神准”。解决所有特征只能用截止当日的数据计算测试时不看未来任何信息。一个简单验证方法是把特征矩阵的最后一列打印出来检查它是不是能由df截止当前行推算。5.4 过拟合与随机种子复现性要求现象第一次跑损失降到0.3第二次跑降到0.6结果不稳定。原因PyTorch默认的权重初始化是随机的加上Dropout的随机性模型每次训练结果都会不同。期末项目要求可复现所以需要固定随机种子import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True set_seed(42)如果你发现训练集损失降得很低、验证集损失却回升那明显过拟合。手段有三个增大dropout到0.5、减小hidden_size到32、增加训练数据换更长历史区间。期末项目不追求极致精度模型稳定、可解释、可复现才是得分关键。5.5 答辩追问模型解释性差怎么圆场老师大概率会问“深度学习是个黑匣子你怎么证明它不是过拟合”你至少准备三个回答第一我们已经做了训练集/测试集时间隔离和标准化泄漏控制第二对比了买入持有策略模型在最大回撤上更小第三可以随机打乱标签做对照组如果打乱后准确率掉到50%附近说明模型学到了真实模式不是死记硬背。下面代码可以现场演示random_seed 42 rng np.random.RandomState(random_seed) y_shuffled rng.permutation(y_train) # 打乱训练标签 # 用这个乱标签重新训练10个epoch记录测试准确率你会发现接近0.5这个“负对照”实验非常加分它直接回应了“模型是不是乱猜”的质疑。6. 把系统做成可演示的Web界面FlaskECharts展示预测与回测结果期末项目如果只是Notebook演示时对着代码讲老师容易走神。花一个晚上用Flask套一个简单页面把K线、预测信号、累计收益曲线放进去整个项目的完成度瞬间上一个档次。Flask是Python里最轻量的Web框架不需要前端基础就能用。先装依赖pip install flask建立一个app.py把模型预测的结果导出为JSON前端用ECharts渲染。核心代码思路如下from flask import Flask, jsonify, render_template import json app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/api/predictions) def predictions(): # 这是你在训练时保存的测试集预测结果 result { dates: df_test[日期].tolist(), actual_close: df_test[收盘].tolist(), pred_signal: pred_signal.tolist(), cum_return: df_test[累计收益].tolist(), benchmark_return: (df_test[收盘] / df_test[收盘].iloc[0] - 1).tolist() } return jsonify(result) if __name__ __main__: app.run(debugTrue)前端templates/index.html里引入ECharts的CDN用fetch拉/api/predictions把实际收盘价画成K线把预测信号画在K线下方再用一条折线画累计收益。ECharts的K线配置比较繁琐但网上模板很多重点是把pred_signal映射成红涨绿跌的标记。我自己的教训是演示用的数据量不要太大。有一次把十年日线全塞进去页面加载卡顿答辩现场以为程序崩了。取最近200个交易日足够说明问题。另外在页面上加一个“数据日期范围”“训练集/测试集切分点”“模型参数”的说明卡片老师一眼就能确认这不是一次性预测未来而是有严格时间隔离的历史回测。这个细节能让答辩时间从十分钟缩短到五分钟因为大部分追问都被页面提前回答了。最后说句实在的股票预测系统没有谁能稳定赚钱期末项目的目标是验证“深度学习在这个场景下的能力边界”。你做出一套数据管道、模型训练、回测评估、可视化展示的完整系统比任何浮夸的收益率都更有价值。希望这份笔记能帮你少踩几个坑把项目做到自己心里有底的程度。本文还有配套的精品资源点击获取