每年到了毕业设计季,“股票预测系统”一定是计算机专业的热门选题,后台管理系统、机器学习、数据可视化全都能沾上,难度适中又显得有分量。很多同学一上来就问我:Django + Vue.js 这套前后端分离的组合,到底怎么落地一个能跑的股票预测系统?量化交易分析怎么做才不像花架子?可视化怎么做得让老师眼前一亮?
这篇文章我就把这个项目的完整设计思路、编码实现、踩坑记录从头到尾说一遍。整体节奏按“先说清楚为什么这么设计,再动手怎么写代码”来,你可以把它当一份项目拆解手册,也可以直接对照自己的工程查漏补缺。项目本身不复杂,但能把它讲透、做完整,在本科毕业设计里绝对是一份扎实的作品。
1. 项目整体设计:先搭骨架再摸细节
1.1 技术栈选型:Django 和 Vue.js 各自解决什么问题
选 Django + Vue.js,本质上是在选一条“前后端分离 + 快速成型”的路线。Django 作为后端框架,最突出的优势是自带 ORM、Admin 后台、认证体系和成熟的第三方生态,写接口的效率比手撸 Flask 高不少,尤其是涉及用户登录、数据模型、查询接口这些毕业设计必考的模块,Django 能帮你省掉一大半重复劳动。
Vue.js 则负责页面交互和数据展示。股票预测系统最核心的页面就是数据看板、K线图、回测报告,这些全是强交互场景,用 template 字符串拼接或者 jQuery 操作 DOM 会写得非常痛苦。Vue 的数据驱动思路天然适合图表组件,配合 Element UI 做后台布局、ECharts 做可视化,界面质感很快就能拉起来。
还有一个现实原因:前后端分离的项目在答辩时更好讲。你可以把前端、后端、算法模型、数据库分成四个独立模块,条理清晰,老师顺着你的架构图往下问,你答起来也更有底气。
1.2 功能模块划分与数据流向
一个完整的股票预测系统,至少应该包含以下几个模块:
| 模块 | 职责 | 核心要点 |
|---|---|---|
| 数据采集模块 | 获取历史行情数据 | 日线数据、复权处理、数据入库 |
| 数据清洗与特征工程 | 处理缺失值、生成技术指标 | MA、RSI、MACD、涨跌幅 |
| 股票预测模块 | 基于历史数据预测未来涨跌 | 数据划分、模型训练、结果持久化 |
| 量化策略分析模块 | 执行交易策略、回测验证 | 双均线策略、交易成本、绩效评估 |
| 可视化模块 | K线展示、收益曲线、指标图表 | Vue + ECharts,接口对接 |
| 用户模块 | 登录、注册、历史记录 | Django 自带认证 + JWT 扩展 |
数据流向要闭环:原始数据 → 清洗加工 → 特征序列 → 输入模型 → 预测结果 → 前端展示;同时特征序列也会进入策略回测模块 → 生成交易信号 → 计算收益曲线 → 同样的可视化通道输出。整个系统有一个清晰的数据主线,这是答辩时最容易被认可的地方。
1.3 工作量评估:一个人多久能做完
按正常节奏,每天投入三到四小时,三周左右能完成核心功能,四周可以做到可以演示的程度。时间大头不在写代码,而在三件事:数据处理是否干净、模型预测是否稳定、接口和图表是否对得上。
我给的建议是不要一上来就把所有功能铺开。先做通一条链路:采集一支股票的数据 → 清洗入库 → 用简单的均线策略生成信号 → 前端画出 K 线图和买卖点 → 再扩展预测模型和用户系统。链路通了,后面加功能只是时间问题。
2. 数据层:股票数据采集、清洗与存储
2.1 数据来源怎么选:直接用现成库还是自己爬
做股票预测,数据是地基。我见过不少同学在第一步就卡住了,跑去爬网页,结果被反爬机制搞得怀疑人生,一个下午只拿到两天数据。真实项目里,有几个成熟的方案:
- 直接调用第三方数据接口,比如 akshare、tushare,它们封装了行情数据获取逻辑,一行代码就能拿到某只股票的历史日线数据;
- 如果只想在本地做算法验证,可以从公开数据集下载 CSV 文件导入数据库;
- 部分开源项目维护了可直接导入的 SQL 文件,适合先把界面跑通再做替换。
你自己写网络爬虫不是不行,但本科毕业设计的重点在系统设计和算法应用,不建议把时间浪费在解析网页、应对反爬这些偏运维的事情上。用现成数据源把项目搭起来,等核心功能稳定了,如果你想展示爬虫能力,再补一个小的增量更新功能,那才是锦上添花。
2.2 清洗和复权:90% 的坑都在这
拿到原始数据后,第一件事不是急着训练模型,而是做清洗。日线数据最容易出现的问题有这么几类:
- 停牌日缺失:股票停牌时没有成交记录,直接用
dropna()会把时间轴弄乱,建议对缺失日期做前向填充,或者只在交易日范围内做计算; - 字段异常:成交量为 0、最高价低于开盘价这类脏数据,要用过滤逻辑剔除;
- 除权除息:送股、分红会导致股价突变,不处理的话,预测模型会把这些跳空当成正常波动,结果一塌糊涂。正规做法是使用前复权或后复权数据,让价格序列保持连续。
清洗完成后,再把数据统一存储到 Django 的 ORM 模型里。一个建议是给日期和股票代码建立联合唯一索引,避免数据重复插入。
2.3 特征工程:让预测模型有话可说
预测模型不管用线性回归还是 LSTM,输入的都应该是特征序列,而不是原始 K 线数据。最常用的特征包括:
- 简单移动平均线 MA5、MA10、MA20、MA60;
- 涨跌幅、振幅、换手率;
- 成交量变化率;
- 相对强弱指标 RSI;
- 指数平滑异同移动平均线 MACD 的 DIF 和 DEA 值。
特征工程这一步,其实就是平时所说的“大数据预处理”。很多同学把“大数据”理解成 Hadoop 集群,觉得一个毕设没必要碰大数据,但实际上,预处理、清洗、特征提取这套方法论本身就是在用数据工程的思路解决问题。等数据量再上几个量级,这套流程可以直接平移到 Spark 或 Flink 上,只是存储和计算引擎换掉了而已。
3. 股票预测模块:从模型选型到落地接口
3.1 预测任务定义:回归还是分类
股票预测通常有两种任务定义方式,你得先想清楚再动手:
- 回归任务:预测未来一天的收盘价或未来 N 天的价格序列;
- 分类任务:预测未来 N 天上涨还是下跌,输出一个概率值。
从毕业设计的角度,我更推荐分类任务。原因很实际:回归任务误差大,解释起来容易被动,老师问“那你这个预测准确率怎么看”不好回答;分类任务则可以把问题简化成“做多还是做空”,标准化评估指标如准确率、F1 值、混淆矩阵都在,图表也好展示。
实际代码里,通常是用过去 T 天的特征序列预测第 T+1 天到第 T+N 天的累计涨跌方向,比如if future_ret > 0: label = 1 else: 0。
3.2 模型对比与初版选择
预测模型的选型,一般有三种方案:
| 模型 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 逻辑回归 | 简单、可解释性强 | 非线性拟合能力弱 | 基线模型 |
| XGBoost / LightGBM | 处理表格数据能力强 | 调参复杂 | 主推方案 |
| LSTM | 能处理长序列依赖 | 训练慢、易过拟合、调参难度大 | 学术提升、对比实验 |
我建议的路线是:先用逻辑回归或 XGBoost 把完整链路跑通,拿到一套稳定的评估结果,再考虑用 LSTM 做对比。很多同学一上来就搭 LSTM,训练时间长、结果不稳定,最后演示翻车,这个坑一定要避开。
另外要提一个热词很高的现象——“模型预测股票涨跌,每次结果不一样”。这不是 Bug,而是客观规律。原因在于模型训练时数据划分如果用了随机抽样、模型参数随机初始化、甚至训练集顺序打乱,都会带来结果波动。解决方法是设置随机种子,比如random.seed(42)、np.random.seed(42),并且保持训练集按时间顺序划分。如果仍有波动,可以多次预测取平均,作为最终输出。
3.3 防止数据泄漏:最隐蔽的硬伤
数据泄漏是股票预测项目里最容易被忽略、也最致命的问题。拿昨天到今天的全部数据去训练模型,再预测昨天的涨跌,对今天没有任何参考意义。正确的做法是严格按照时间顺序切分数据:
from sklearn.model_selection import train_test_split # 按时间顺序生成索引,严禁 shuffle=True X = df[feature_cols].values y = df['label'].values train_size = int(len(df) * 0.7) X_train, X_valid = X[:train_size], X[train_size:] y_train, y_valid = y[:train_size], y[train_size:]此外还要注意特征标准化时,只能用训练集的均值方差去归一化验证集和测试集,避免信息泄露。这点在答辩时专门对比说明,会很不经意地加分。
3.4 把模型包成 API
预测结果最终是要给前端展示的。Django 里建议写好模型训练脚本,把训练好的权重保存下来,然后通过 API 接口实时调用:
import joblib from rest_framework.decorators import api_view from rest_framework.response import Response model = joblib.load('models/stock_predict.pkl') @api_view(['POST']) def predict(request): code = request.data.get('code') df = prepare_features(code) features = df[feature_cols].iloc[-1].values.reshape(1, -1) prob = model.predict_proba(features)[0] return Response({ 'code': code, 'probability_up': round(prob[1], 4), 'suggestion': '看多' if prob[1] > 0.55 else '看空' })建议把概率阈值设得高一点,宁可预测为“观望”,也不要频繁输出“看多看空”,这样演示效果更真实。
4. 量化交易策略与回测引擎
4.1 策略思路:双均线逻辑最简单的验证方式
量化交易模块听起来高大上,但毕业设计不需要做高频策略,把经典策略跑通、能解释清楚是黄金标准。我最推荐的就是双均线策略,也叫金叉死叉策略。
核心逻辑很直观:
- 快速均线上穿慢速均线时,产生买入信号;
- 快速均线下穿慢速均线时,产生卖出信号。
用通俗的话说,均线代表一段时间内的平均成本,短期均线突破长期均线说明短期势头向上,适合入场;反过来则是趋势走弱,适合离场。
策略参数可以做成可配置的,比如默认快线 5 日、慢线 20 日。放一个参数调节入口,回测时可以切换不同的组合,对比效果并形成报告,这在毕设里是加分的交互设计。
4.2 回测引擎的完整流程
回测的意义,是用历史数据验证你的交易规则能不能赚钱、赚多少、风险有多大。一个最小可用的回测引擎要处理这几件事:
核心代码可以这样写:
class BacktestEngine: def __init__(self, initial_cash=100000, commission=0.0003): self.cash = initial_cash self.position = 0 self.commission = commission self.history = [] def run(self, df): for i in range(len(df)): price = df.loc[i, 'close'] signal = df.loc[i, 'signal'] # 1=买入, -1=卖出 if signal == 1 and self.position <= 0: self.position = int(self.cash / price * (1 - self.commission)) self.cash -= self.position * price * (1 + self.commission) elif signal == -1 and self.position > 0: self.cash += self.position * price * (1 - self.commission) self.position = 0 total_value = self.cash + self.position * price self.history.append({'date': df.loc[i, 'date'], 'value': total_value}) return pd.DataFrame(self.history)实际开发中,我会加上持仓成本记录,方便输出每笔交易的盈亏明细;同时预设股票池数量、起止日期,让用户可以在前端选择不同的回测区间,而不是只有一段写死的日期,演示起来更有说服力。
4.3 绩效指标怎么算、报告怎么出
回测结束之后,光给一张净值曲线图还不够,要配上量化的绩效指标。答辩时老师最常问的问题是:你这策略到底好不好?这时候你需要拿出一组数据来回应。
回测报告建议做成 JSON 接口返回,前端用表格展示,部分指标用卡片突出显示。这里有个注意点:回测赚了多少钱不重要,重要的是你能解释清楚收益是怎么来的、回撤为什么这么大、手续费对收益的影响有多少。诚实呈现局限,比写一个战无不胜的曲线有说服力得多。
5. 前端可视化能下功夫的地方
5.1 页面层次设计与功能入口
前端界面建议采用经典的后台管理布局:顶部导航 + 侧边菜单 + 主内容区。我一般把系统分成四个页面:
- 首页仪表盘:项目说明、系统概览、最近行情摘要;
- 股票分析页:搜索股票、展示 K 线图、技术指标、预测结果卡片;
- 策略回测页:选择股票和回测区间,展示净值曲线、买卖点、绩效表格;
- 用户中心:登录注册、历史回测记录。
这套结构在视觉上完整,也覆盖了系统全部核心功能。用 Element UI 的现成组件可以很快搭出来,不需要自己在 CSS 上花太多精力。
5.2 后端接口设计和调试
前后端分离最怕接口约定不一致,联调时互相扯皮。我的建议是先定接口文档再写页面,用一份简单的约定:状态码统一为code,成功为 0,失败返回非 0;数据主体统一放在data字段。
核心接口大概有这些:
| 接口 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 获取股票列表 | GET | /api/stock/list/ | 支持代码和名称模糊搜索 |
| 获取 K 线数据 | GET | /api/stock/kline/?code=000001&days=250 | 返回 OHLCV 数据 |
| 获取预测结果 | POST | /api/predict/ | 入参股票代码,返回上涨概率 |
| 执行策略回测 | POST | /api/backtest/ | 入参代码、区间、周期参数,返回净值曲线和指标 |
| 登录 | POST | /api/login/ | 返回 JWT Token |
| 获取回测记录 | GET | /api/backtest/history/ | 当前用户历史记录 |
Django 后端记得配置跨域,settings.py里加:
CORS_ALLOW_ALL_ORIGINS = True开发阶段可以先全放开,部署上线再收紧到指定域名,省去很多联调时莫名其妙的跨域报错。
5.3 ECharts 集成与常见渲染问题
ECharts 是可视化模块的核心,K 线图、折线图、柱状图它都能搞定。技术栈是 Vue + ECharts 的项目,我踩过几个典型的坑:
- 图表必须等 DOM 渲染完成后再初始化,一般在
mounted()里执行; - 组件被
v-if销毁重建时,要用dispose()释放实例,否则内存会越积越高; - 窗口大小变化时图表不会自动缩放,需要监听
resize事件调用chart.resize(); - K 线图的数据格式是
[open, close, low, high],顺序写错图表会直接乱掉。
示例组件结构:
<template> <div ref="chart" style="height: 420px"></div> </template> <script> import * as echarts from 'echarts' export default { name: 'KlineChart', props: ['klineData'], mounted() { this.chart = echarts.init(this.$refs.chart) this.renderChart() window.addEventListener('resize', this.handleResize) }, methods: { renderChart() { const dates = this.klineData.map(d => d.date) const ohlc = this.klineData.map(d => [d.open, d.close, d.low, d.high]) const volumes = this.klineData.map(d => d.volume) this.chart.setOption({ xAxis: { type: 'category', data: dates }, yAxis: { scale: true }, series: [{ type: 'candlestick', data: ohlc }] }) }, handleResize() { this.chart && this.chart.resize() } }, beforeDestroy() { window.removeEventListener('resize', this.handleResize) this.chart && this.chart.dispose() } } </script>前端展示的数据多来自后端接口,接口慢的时候配合数据加载状态,哪怕只用简单的v-loading,整个系统的完成度也会高不少。
6. 常见问题速查与答辩经验
6.1 开发阶段最容易踩的坑
这个项目的完整度难点不在某个单点功能,而在模块之间调试的链路太长。我把自己经历过和带学生时见过的高频问题整理了一下:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 前端请求接口报跨域错误 | Django 未配置 CORS | 安装并配置 django-cors-headers |
| 预测准确率极低,约等于猜硬币 | 特征泄漏 / 未按时间划分数据 | 重新检查数据划分,去掉未来信息 |
| 训练过程每次都输出不同结果 | 未固定随机数种子 | 统一设置 seed,固定模型初始化 |
| K 线图显示错乱 | OHLC 数据顺序写错 | 按 ECharts 标准顺序传入 |
| 数据库插入数据重复 | 缺少唯一约束 | 给(stock, date)添加联合唯一索引 |
| 前端调接口返回慢 | 查询没有走索引 | 常用字段加索引,或加缓存 |
| 回测不计算交易费用 | 佣金和滑点被忽略 | 在撮合逻辑中加入费用系数 |
除这些之外,还有一个容易忽略的问题:模型文件放在项目里,但上线部署时忘记把训练权重文件一起带上,导致接口直接 500。这对答辩演示是灾难性的。建议在部署脚本里显式声明依赖的数据和模型文件。
6.2 答辩时怎么把项目讲出亮点
答辩时间有限,一定不要在系统登录、页面布局这种基础功能上花太多时间。真正值得讲的是三道关卡:
第一,数据链路。你能清楚说出原始数据进来之后,每一层做了什么处理,最终如何变成特征矩阵和信号,这体现了数据工程的能力。
第二,预测模型。准确的模型选择动机、训练集验证集划分方式、如何防止数据泄漏、模型结果的波动原因是什么、怎么规避,这些比模型本身更重要。
第三,回测闭环。讲清楚策略思想、手续费对收益的影响、最大回撤的含义、净值曲线和基准比对的结论,这就把一个简单策略做成了完整的量化分析流程。
遇到回答不上的问题,坦白说“这个问题我在项目里还没有深入,但我理解的大概方向是……”也比支支吾吾强很多。
6.3 项目后续还能怎么扩展
做完一个能跑的系统之后,其实还有几个性价比很高的扩展方向,可以作为论文里的“后期展望”或者完全体系统实现:
- 增加股票池管理和自动推荐,用户登录后能收藏关注的股票;
- 把定时数据更新脚本做成定时任务,系统自动拉取最新行情并更新预测;
- 接入 WebSocket 做实时数据推送,前端页面数据主动刷新,前后端实时联动直接拉高演示效果;
- 把预测从单只股票扩展到“沪深 300 成分股扫描”,每天筛选出概率最高的前 10 只股票,然后再结合策略跑一个组合回测。
这些扩展点并不是画饼,它们的难度加分梯度很清晰,你可以根据自己的时间和能力逐个落地。
最后简单聊几句我的实际感受。股票预测这个题目,真正难的不是 Django 怎么建模型、Vue 怎么写页面、也不是 LSTM 怎么调参,而是把数据、算法、策略、展示完整地串起来,并对每一步的“为什么”都有清晰的解释。很多同学做到一半想换题,往往是因为一开始直接从模型开始做,忽略了数据链路和回测设计。反过来,如果你先搭一个能跑通的最小闭环,后面的每一层功能都是在这个框架上自然生长出来的。
实际操作中的一个小建议是:全程用 Git 管理代码,在每个阶段结束时交一次功能演示录像。这样你既能看到自己的进度,答辩时如果当时演示环境出问题,录像还能兜底。这套方法我带过的学生用下来,普遍反馈比临时抱佛脚靠谱得多。