毕业设计做到股票预测这个方向的同学,十有八九都经历过同样的纠结:传统的时间序列模型写出来没什么亮点,纯深度学习又容易被评委问“你的创新点在哪”。我当初选的是Django+LLM大模型股票行情预测系统这个组合,核心思路是让大模型来做两件事——一是生成量化交易策略的信号解释,二是用自然语言把预测结果讲成人话。整套系统兼顾了大数据处理、量化交易策略、Web全栈开发和前沿的大模型应用,作为计算机专业的毕业设计,覆盖面够广,技术深度也有地方可挖。
这篇文章把我在实际搭建这套系统时踩过的坑、验证过的方案、以及关键代码逻辑都整理出来,不是那种贴几个概念图就完事的模板文,而是真正能让你从零搭出一套可演示系统的实操记录。无论是想照着自己复现,还是打算在这个方向上做二次开发,都值得花几分钟认真看看。
1. 项目整体设计与技术选型思路
1.1 为什么选Django做Web框架
后台管理、数据库模型、用户认证这些功能,是毕设答辩时评委一定会关注的功能完整性指标。Django自带的后台管理界面做得非常完善,我几乎没有额外写任何管理前端代码,就直接拿到了一个可以管理用户、股票池、预测记录的后台。对于需要快速验证业务逻辑、同时要体现工程规范性的毕业设计来说,Django的“全家桶”模式远比Flask这种微框架更省心。
另一个关键点是Django的ORM。股票数据需要频繁按日期、股票代码做时间序列查询,Django ORM对这类查询的支持很成熟,配合数据库索引能拿到不错的性能。加上Django的模板系统和REST Framework,前后端分离或者不分离都可以灵活选择,这一点在后期调整展示方案时帮了我大忙。
1.2 LLM在系统里的定位
LLM大模型在整个系统里不是用来预测股价涨跌的——一开始我也试过直接让大模型预测价格,效果非常不靠谱,这个后面详细说。最终敲定的定位是让LLM做三件事:
第一,对量化策略产生的交易信号生成自然语言解读。比如策略在某个交易日发出了买入信号,大模型会根据当天的市场环境、技术指标状态、持仓情况,生成一段类似分析师口吻的解释,告诉用户“为什么会有这个信号”。
第二,对预测结果做可视化前的数据总结。模型预测输出的是价格序列,普通用户看着一串数字没有概念,LLM可以把关键变化点提炼成简报。
第三,提供一个对话交互入口。用户可以在系统里直接问“最近一个月沪深300的走势风险如何”,系统会结合数据库里的最新行情数据、指标计算结果和预测结果,让LLM生成回答。
这三件事都避开了让LLM去做数值预测的深坑,又充分体现了大模型在自然语言处理和知识整合上的优势,在毕业设计里属于性价比极高的组合方式。
1.3 整体技术架构
整个系统的分层结构是这样的:
- 数据层:使用akshare和tushare获取行情数据,存储到MySQL,按日线和分钟线两种粒度建表
- 服务层:包括数据清洗模块、技术指标计算模块(TA-Lib)、策略引擎、预测模型调度模块
- 分析层:传统时间序列模型(LSTM、Prophet)+ 量化策略(MA双均线、RSI超卖、MACD金叉死叉等基础策略)
- LLM层:通过API调用大模型服务,包括信号解释、数据总结、对话问答三个功能模块
- 展示层:Django模板+ECharts可视化,包含仪表盘、个股详情、策略回测报告、对话界面
这套架构的特点是每一层之间的耦合度都控制得比较好,比如把tushare换成baostock,只需要改数据层;把LLM接口从某个厂商换成另一个,只需要改LLM层的封装。毕设答辩时很加分的一点是,你可以把这种低耦合设计作为系统亮点讲出来。
2. 核心功能模块与实现要点
2.1 数据采集与预处理
股票数据是整个系统的燃料,数据质量决定了后续所有分析的可信度。我当时用的是akshare,免费且不需要注册token,对毕设场景非常友好。不过它有个问题——数据接口偶尔会变动,今天能跑的代码过两周可能就报错,所以一定要在数据采集模块外面做一层异常捕获和重试机制。
采集逻辑上,建议先把股票基础列表存下来,包括股票代码、名称、所属板块,然后再按天增量拉取日K线数据。K线数据至少要包含开高低收、成交量、成交额、换手率这几个核心字段。考虑到数据量增长和回测需求,我在daily_k线表上建了(ts_code, trade_date)的联合索引,这样按股票查时间序列时会快很多。
数据预处理有几点值得注意:
- 复权处理:如果用前复权数据做回测,建议在数据落地时就一次性处理好,不要每算一遍策略就重算一次
- 停牌处理:停牌日没有交易数据,需要做向前填充,避免时间序列出现断点
- 异常值过滤:单日涨跌幅超过20%的数据点(新股上市或极端行情)需要标记,避免污染训练集
- 训练集/测试集切分:按时间顺序切,不能随机切,这是时间序列项目和普通机器学习项目最大的区别
2.2 技术指标计算
技术指标我统一用TA-Lib计算,这个库的优点是计算速度快、结果和主流行情软件一致。毕设里不推荐自己手写指标公式,一来是容易出错,二来是评委如果拿同花顺或东方财富的指标数值来对比,数值对不上的话会显得不够严谨。
常用的指标组合我整理成了这样一个配置:
- 趋势类:MA5、MA10、MA20、MA60、MACD(DIF、DEA、HIST)
- 摆动类:RSI(6、12、24)、KDJ(K、D、J)、BIAS
- 量能类:VOL、OBV、量比
- 波动类:BOLL(上中下轨)、ATR
指标计算完成后,需要统一做标准化处理再输入预测模型。我用的是z-score标准化,但要特别注意:标准化的均值和标准差必须只用训练集的数据计算,然后用同样的参数去转换测试集,否则会引入未来信息,造成数据泄漏,让回测结果虚高。
2.3 量化交易策略引擎
策略引擎是整个系统的规则核心。我给系统实现了三类基础策略,覆盖了技术分析里最常用的逻辑:
MA双均线策略的逻辑是金叉买入、死叉卖出,简单而且可视化效果好。回测结果在震荡行情里会有较多无效交易,实盘中会被手续费磨损,这个要在系统里把手续费率设成双边万三,相对真实一些。
RSI超卖回归策略的逻辑是RSI低于阈值买入、高于阈值卖出,适合震荡市。这个策略的缺点是单边上涨行情里会过早止盈,丢了后面的利润空间。
MACD金叉死叉策略是在金叉买入、死叉卖出基础上,叠加了DIF与股价的背离判断作为过滤条件,相比纯双均线会减少一部分假信号。
策略引擎我抽象了一个BaseStrategy接口,每个策略继承接口实现generate_signal方法,返回哪天买入、哪天卖出、当前持仓状态这些信息。这样做的好处是后续扩展新策略的时候不需要改动其他模块的代码,直接在策略注册表里加一项就行。
2.4 预测模型模块
预测模型用了两种思路做对比——LSTM和Prophet。LSTM属于深度学习模型,能够捕捉时间序列的非线性特征;Prophet是Facebook开源的加性模型,对趋势和季节性有比较强的解释性。两者结合可以让毕业设计有一个模型对比的实验环节。
LSTM部分,我用的是PyTorch实现,输入特征选择了过去20个交易日的收盘价、成交量、RSI、MACD这几列,经过归一化后喂给一个两层的LSTM网络,隐藏层维度设为64,输出预测的下一交易日收盘价。训练集用前70%的数据,验证集用后30%。
这里有一个关键的小细节:预测结果一定要做反归一化,并且把预测值和真实值放在同一个坐标系里去画对比图。我在开发时遇到过忘记做反归一化,导致预测值全部归一数值、跟真实价格差了三个数量级的情况。这种极端数值错误在答辩演示时会特别尴尬,务必检查。
Prophet模型的输入只需要ds和y两列,使用起来比较简单。不过股票价格序列里的节假日效应和突发性事件,Prophet处理得并不好。而且它的预测区间在长期预测时会快速发散,所以我的系统里Prophet主要用于短期趋势判断,不直接输出明确的交易信号。
2.5 LLM大模型集成方案
LLM集成是整个系统里最有亮点、也是最容易失控的部分。我用的是调用大模型API的方式,具体选哪家可以根据自己的预算和可用性决定。
LLM层的封装我做了一个统一接口,核心是设计好prompt模板。以策略信号解释为例,模板是:
你是一名专业的量化交易分析师。根据以下信息,用通俗易懂的语言解释今天的交易信号: 股票名称:{stock_name} 股票代码:{stock_code} 交易日期:{date} 策略名称:{strategy_name} 触发信号:{signal} 当前技术指标:{indicators_text} 最近5日行情摘要:{recent_market_text} 请从以下角度输出分析: 1. 信号触发的原因 2. 当前市场环境的简要判断 3. 可能的风险点 4. 后续需要关注的关键价位或指标变化注意,我在prompt里传的是行情摘要和指标文本,不是把原始行情数据全部丢给大模型。大模型对超长文本的处理能力有限,而且信息太杂乱会降低输出的稳定性。先让程序从数据库里把关键指标取出来,格式化成文本,再拼接到prompt里,效果要稳定得多。
这里面还有个安全细节:调用大模型API的密钥绝不能硬编码在前端代码里,也不能提交到Git仓库。我是把密钥放在环境变量里加载,同时用一个独立的配置文件管理,这个文件通过.gitignore排除在版本控制之外。一旦密钥泄漏,别人就能拿着你的key无限调用,产生费用。这类鉴权信息管理的问题,在毕设文档里可以作为一个安全意识亮点写进去。
2.6 系统核心页面设计
系统页面我做了五个主要界面:
仪表盘页面展示三大板块,包括主要指数和自选股的当日行情快照、系统最新的预测信号、以及策略运行状态概览。自选股支持手动添加,数据上做了缓存,不会每次打开页面都重新拉取行情。
个股详情页是核心展示页面,有四张图:K线图叠加MA均线、成交量柱状图、MACD指标图、预测价格对比图。四张图联动,鼠标悬浮显示同一日期的所有数据,用ECharts实现。这里值得提一下,ECharts在数据更新时需要用setOption而不是重新初始化实例,否则图表会闪烁且交互状态会丢失。
策略回测页展示策略净值曲线、基准指数净值曲线、最大回撤、夏普比率、胜率、交易次数等指标。回测看板对评委直观理解量化交易很重要,如果回测收益曲线跑赢基准,整个项目的说服力会大幅提升。
预测结果页展示模型对未来N个交易日的价格预测,并生成配套的LLM解读文案。这个页面是LLM能力的直接展示区,也是答辩时的加分点。
对话分析页提供一个输入框,可以输入自然语言问题,例如“当前持仓的风险如何”,系统结合实时数据和指标生成回答。这里要注意,大模型的回答内容应当基于系统提供的数据,防止它“自由发挥”编造行情数据,所以我在prompt中强制要求“只能基于给定的数据回答,不得编造行情数字”。
3. 实操过程与核心环节实现
3.1 环境准备与项目初始化
先列一下我用的环境版本,方便你复现时对齐:
- Python 3.10
- Django 4.2
- PyTorch 2.0+
- TA-Lib 0.4.28
- MySQL 8.0
- Redis(用于缓存行情数据和LLM调用结果)
Python依赖建议用虚拟环境管理,不要直接装在系统Python里。我习惯在项目根目录下执行:
python -m venv venv source venv/bin/activate # Windows上用 venv\Scripts\activate pip install django djangorestframework pymysql torch ta-lib akshare prophet openai celeryDjango项目初始化可以用以下几个命令:
django-admin startproject stock_system cd stock_system python manage.py startapp stocks python manage.py startapp strategies python manage.py startapp prediction python manage.py startapp llm_chat我按业务域拆分了四个子应用,stocks管数据,strategies管策略,prediction管预测,llm_chat管大模型交互。每个应用各司其职,命名清晰,评阅老师在阅读代码时会比较容易上手。
3.2 数据库模型设计
核心表结构我梳理了这样几张表:
StockInfo表存股票基础信息,字段包括ts_code、symbol、name、area、industry、market、list_date。这里ts_code我用的是类似600000.SH的格式,方便和多个数据源对齐。
DailyK线表是每日行情数据,字段包括ts_code、trade_date、open、high、low、close、pre_close、change、pct_chg、vol、amount。这张表的数据量最大,务必在ts_code和trade_date上建立联合唯一索引,同时加上普通索引供日期范围查询。
StrategyConfig表存策略参数配置,字段包括strategy_name、params_json、description、is_active。策略参数通过JSON字段存储的好处是扩展新策略时不需要频繁改表结构,Django的JSONField直接映射MySQL的JSON类型。
SignalRecord表存策略产生的交易信号,每次策略引擎跑出买卖信号都会写入一条记录,并关联当天的行情快照。
PredictionRecord表存预测结果,包括模型名称、预测日期、预测价格、置信区间、生成时间。一次预测可能生成未来5日或10日的结果,所以预测值用JSON字段保存一条序列。
LLMChatRecord表存对话记录,包括用户问题、系统回答、相关的股票池和上下文摘要。
3.3 数据采集任务实现
行情数据采集用Celery的定时任务驱动。每天早上开盘前定时拉取前一交易日数据,收盘后再拉一次当日数据做校对。
核心拉取代码逻辑大致是这样:
# tasks.py from celery import shared_task import akshare as ak @shared_task def fetch_daily_kline(ts_code: str): # 这里用akshare的stock_zh_a_hist接口 df = ak.stock_zh_a_hist( symbol=ts_code.split(".")[0], period="daily", start_date="20200101", end_date="20241231", adjust="qfq" ) # 字段名映射:akshare返回中文列名,转成系统的英文字段 df = df.rename(columns={ "日期": "trade_date", "开盘": "open", "收盘": "close", "最高": "high", "最低": "low", "成交量": "vol", "成交额": "amount", "涨跌幅": "pct_chg", }) # 批量写入数据库,用upsert避免重复 from django.db import transaction with transaction.atomic(): for _, row in df.iterrows(): obj, created = DailyKLine.objects.update_or_create( ts_code=ts_code, trade_date=row["trade_date"], defaults={ "open": row["open"], "high": row["high"], "low": row["low"], "close": row["close"], "vol": row["vol"], "amount": row["amount"], "pct_chg": row["pct_chg"], } )这里的update_or_create很方便,即使网络中断导致部分数据缺失,下次重跑也能补齐,不会产生重复记录。批量写入时逐行调用ORM会有点慢,但毕设场景的数据量完全能接受。如果以后要处理全市场5000多只股票,建议改成bulk_create配合ON DUPLICATE KEY UPDATE。
3.4 技术指标计算与特征工程
拿到的原始K线只有最基础的行情字段,量化策略和预测模型都需要基于衍生指标才能工作。我单独写了一个feature_engine模块,输入是一个股票的DataFrame,输出是加好各类指标列的DataFrame:
import talib as ta import numpy as np import pandas as pd def calculate_features(df: pd.DataFrame) -> pd.DataFrame: df = df.sort_values("trade_date").reset_index(drop=True) close = df["close"].values high = df["high"].values low = df["low"].values volume = df["vol"].values # 均线 df["ma5"] = ta.SMA(close, timeperiod=5) df["ma10"] = ta.SMA(close, timeperiod=10) df["ma20"] = ta.SMA(close, timeperiod=20) df["ma60"] = ta.SMA(close, timeperiod=60) # MACD df["dif"], df["dea"], df["hist"] = ta.MACD(close, fastperiod=12, slowperiod=26, signalperiod=9) # RSI df["rsi6"] = ta.RSI(close, timeperiod=6) df["rsi12"] = ta.RSI(close, timeperiod=12) df["rsi24"] = ta.RSI(close, timeperiod=24) # KDJ df["k"], df["d"] = ta.STOCH(high, low, close, fastk_period=9, slowk_period=3, slowd_period=3) df["j"] = 3 * df["k"] - 2 * df["d"] # BOLL df["boll_upper"], df["boll_mid"], df["boll_lower"] = ta.BBANDS(close, timeperiod=20, nbdevup=2, nbdevdn=2) # ATR df["atr"] = ta.ATR(high, low, close, timeperiod=14) # 量比:成交量/过去5日均量 df["vol_ma5"] = ta.SMA(volume, timeperiod=5) df["vol_ratio"] = volume / (df["vol_ma5"] + 1e-10) return df算完指标后,需要做一次全表检查,确认没有无穷值和极端缺失值。某些指标在数据前几行会出现NaN,这些行在训练和回测时需要过滤掉,或者用前向填充补齐。
3.5 策略引擎与回测逻辑
策略引擎我设计了两种运行模式:信号模式和历史回测模式。信号模式是在每个交易日收盘后,对当天最新的数据计算指标,判断是否有买卖信号;历史回测模式则是在历史K线上逐日复现信号,模拟真实交易。
回测核心逻辑需要一个撮合引擎,记录资金、持仓、交易费用、每日净值:
def run_backtest(df_with_signals, initial_cash=1000000, fee_rate=0.0003): cash = initial_cash position = 0 equity_curve = [] trades = [] last_signal = None for idx, row in df_with_signals.iterrows(): price = row["close"] signal = row["signal"] # "buy" / "sell" / None if signal == "buy" and last_signal != "buy": # 全仓买入,买入手续费 available_cash = cash * (1 - fee_rate) position = available_cash / price cash = 0 last_signal = "buy" trades.append({"date": row["trade_date"], "type": "buy", "price": price, "cash": cash + position * price}) elif signal == "sell" and position > 0 and last_signal != "sell": # 全仓卖出,卖出手续费 cash = position * price * (1 - fee_rate) position = 0 last_signal = "sell" trades.append({"date": row["trade_date"], "type": "sell", "price": price, "cash": cash + position * price}) equity = cash + position * price equity_curve.append({"date": row["trade_date"], "equity": equity}) return equity_curve, trades回测后计算几个核心绩效指标:
年化收益率等于终点净值除以起点净值的年化增长;最大回撤是净值曲线中从峰顶到后续最低点的最大跌幅,可以用累计最高净值和当前净值差值除以前者得到;夏普比率是日收益率均值减无风险利率后除以收益率标准差,再乘以252的平方根做年化,我的代码里用0.03作为年化无风险利率;胜率只统计平仓交易,盈利次数除以总平仓次数。
这里必须提醒,策略回测的结果很依赖参数设置。比如MA双均线策略如果把窗口设成5和10,在A股某些年份可能收益很高,换到另一段时间就严重回撤。所以系统里做了一次参数网格搜索,分别试(5,20)、(10,30)、(5,60)等组合,把回测结果最好的一组作为默认参数,同时把完整参数对比表保存在系统里,作为策略设计过程的佐证。
3.6 LSTM预测模型实现
LSTM模型的训练脚本独立放在一个train_lstm.py里面,不随Django应用启动。我用PyTorch实现,核心代码如下:
import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset class StockLSTM(nn.Module): def __init__(self, input_size=5, hidden_size=64, num_layers=2, output_size=1): super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=0.2) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): out, _ = self.lstm(x) out = self.fc(out[:, -1, :]) return out输入特征维度取5,分别是标准化后的close、vol、rsi12、macd_hist和vol_ratio,序列长度为20个交易日。batch_size用64,学习率用0.001,Adam优化器。损失函数用MSE。
训练时我加了一个早停机制,验证集loss连续10个epoch没有下降,就终止训练并保存最优模型。这个机制能有效缓解过拟合,尤其是在股票数据量不大、噪声又高的情况下。
训练完成后的模型保存在models/目录下,Django预测模块通过joblib或torch.load加载模型权重。要注意的是,预测模块加载模型时的输入特征顺序必须和训练时完全一致,建议把特征列名和标准化参数一起打包保存,这样即使代码版本更新,也不会出现特征顺序错乱的问题。
3.7 Django与LLM集成实现
LLM集成的核心模块是一个LLMService类,内部封装了prompt管理、API调用和结果解析:
import os import json import requests class LLMService: def __init__(self): self.api_key = os.environ.get("LLM_API_KEY") self.base_url = os.environ.get("LLM_BASE_URL") self.model = os.environ.get("LLM_MODEL", "default-model") self.timeout = 30 def chat(self, messages, temperature=0.3): headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } payload = { "model": self.model, "messages": messages, "temperature": temperature } try: resp = requests.post(self.base_url, headers=headers, json=payload, timeout=self.timeout) resp.raise_for_status() data = resp.json() return data["choices"][0]["message"]["content"] except Exception as e: return f"LLM服务调用异常:{str(e)}", def explain_signal(self, signal_record): prompt = build_signal_explain_prompt(signal_record) messages = [ {"role": "system", "content": "你是专业的量化交易分析师。"}, {"role": "user", "content": prompt} ] return self.chat(messages, temperature=0.3)这个封装有几个好处:环境变量集中管理密钥,prompt和调用逻辑分离,超时和异常处理统一管理。即使模型服务暂时不可用,系统也能返回提示信息,而不是直接崩溃。
3.8 系统部署与稳定性优化
本地开发环境用Django自带的runserver就够了,但演示时如果出现崩溃,体验会非常糟糕,尤其是答辩现场。我做了一次面向演示的稳定化处理:
生产环境用waitress代替runserver。waitress是纯Python的WSGI服务器,安装简单,不需要额外配置Nginx,对毕设场景足够。启动命令是:
waitress-serve --listen=0.0.0.0:8000 stock_system.wsgi:application行情数据展示时,前端和后端之间加了一层Redis缓存。同一个股票K线请求在10分钟内的多次访问直接走缓存,避免每次刷新页面都去查询数据库或调用数据源接口。这一层缓存让页面响应速度明显提升,演示时快速翻页不会出现卡顿。
后端返回的JSON里避免返回NaN。JavaScript对NaN的序列化会解析成null,导致前端图表显示异常。我在所有数据接口出口统一做了一次处理,把NaN转成字符串"null"或直接过滤掉。
4. 常见问题与排查技巧实录
4.1 数据获取阶段
akshare接口突然变化是头号问题。一周前还在用的列名,可能因为上游数据源改版就消失了。我的应对方案是在数据采集函数里加了一层字段名校验,如果预期的列不存在,提前抛出有明确提示的异常,并打印实际返回的列名列表,方便快速定位。另外不要只依赖一个数据源,我在系统里预留了tushare的备用实现,虽然需要token,但作为保底方案心里有底。
复权数据坑也不少。如果是前复权数据,最新价等于实际价格,但是历史价格会被调整。回测时如果拿前复权数据算收益率没问题,但如果拿它和未复权的当日涨幅做对比,就会对不上。我最终统一采用前复权数据做策略计算,在系统页面上对用户标注“价格已做前复权处理”。
4.2 指标计算阶段
TA-Lib在Windows上安装不顺利是一个经典问题。直接pip install talib通常会失败,需要在PyPI上下载对应Python版本的whl文件安装。实际上仔细看一下报错日志,一般都是缺少C编译环境或找不到库文件,用预编译的whl文件装是最省心的路径。
指标计算后出现大量NaN和inf也很常见,尤其是数据刚刚上市、成交量极低或者长期停牌后复牌的股票。我的策略模块里加了统一的数据过滤函数,计算指标后只保留从第一行同时拥有全部指标值开始的数据,后续所有策略和预测都基于这段连续数据,避免NaN污染。
4.3 模型训练与预测问题
LSTM预测结果时好时坏,这是一个必须正视的事实。单独拿LSTM做预测,在训练集上前期的拟合效果可能不错,但一到未来数据就容易失效。我的系统里特意没有把LSTM的预测结果直接当交易信号,而是作为“趋势参考”,信号触发还是依靠可解释的量化策略规则。这在学术上叫“预测与决策分离”,是一个非常值得在毕业论文里展开讨论的设计思路。
还有一个细节问题是训练集和测试集切分时的数据泄漏。有些同学习惯把整个数据集标准化后再切分,这样测试集的信息已经参与了标准化参数的计算,回测结果会偏乐观。我改用只基于训练集计算均值和标准差,再用训练集参数去标准化测试集,代码上多几行,但结果可信度提升很大。
4.4 LLM集成问题
大模型返回内容不稳定是最常见的问题。同样的prompt,有时候输出一行结论,有时候输出一大段分析,而且偶尔会把历史数据中的数字说错。我的解决办法是设置temperature低一点,控制在0.2到0.3之间,同时在prompt里明确要求“请基于给定的数据回答,不要输出数据中不存在的信息”。输出之后,还用一个解析函数检查关键数字格式是否合理,比如金额是否为正、日期格式是否正确。
密钥管理问题我在前面提过,必须放在环境变量里。Django的settings.py可以直接通过os.environ读取。另外不要把密钥提交到Git仓库,我甚至建议把.env文件加入白名单检查,防止某次提交时不小心带上去。
4.5 前端展示问题
ECharts在数据更新时出现闪烁和重绘是个常见体验问题。原因是每次刷新页面都用chart.init重新初始化实例,正确的做法是首次用init,之后更新数据用setOption,这样动画过渡和交互状态都能保留。
前后端数据交互时日期格式不统一也踩过坑。Python的datetime序列化成JSON后是YYYY-MM-DD格式,但有时带有时区后缀,ECharts的坐标轴类型如果设置成time,对这两种格式的兼容性不一样。我最后统一在后端序列化时格式化为字符串"YYYY-MM-DD",前端坐标轴类型设置为category,彻底解决日期错位的问题。
4.6 性能问题
股票数据量上来后,页面首次加载会变得很慢。一开始我的列表页直接查出全表数据,然后一股脑全部序列化成JSON返回,首屏加载要好几秒。后来加了分页和日期范围参数,默认只返回最近一年的数据,同时用Redis缓存热点查询,页面加载速度降到1秒以内。
LLM调用耗时也是个隐患,一次完整对话可能耗时几秒甚至十几秒。我在前端做了动画加载提示,后端用异步任务处理对话请求。Django自带的异步支持还不太成熟,我用了Celery把LLM调用放到后台任务去执行,前端轮询结果。这个改造让用户体验好了很多,否则用户点击对话后页面一直转圈,会误以为系统崩溃。
5. 手动实操上手指南
5.1 在本地完整跑通系统
如果你拿到一套完整的源码包,建议按照下面的顺序从零开始跑通:
第一步,准备基础环境。安装Python 3.10和MySQL,创建虚拟环境并安装requirements.txt。依赖安装完成后,先执行python manage.py migrate初始化数据库表结构。
第二步,准备数据。修改config.py中的数据源配置和数据库连接信息。先跑一次数据采集脚本,建议只采1到2只股票最近半年的日K线,比如平安银行或贵州茅台,日K线接口稳定且数据质量高。这一步能让你快速验证数据链路是否正常,不需要一开始就全市场采集。
第三步,运行策略回测。登入Django后台,创建一条策略配置,选择MA双均线策略,然后手动触发一次该股票的回测任务。回测完成后,页面上能看到净值曲线和绩效指标。
第四步,先离线训练好LSTM模型。运行train_lstm.py脚本,把模型权重保存到models目录下。然后执行预测脚本,生成未来5日的预测值并保存到预测表中。
第五步,配置并调用LLM服务。在环境变量中填入API密钥,然后进入对话页面,输入一个问题,验证LLM服务能正常返回。如果暂时没有大模型的API,可以把LLM模块做成mock模式,返回预设的回复模板,确保正常演示流程不中断。
第六步,启动waitress,用浏览器访问系统。依次检查仪表盘、个股详情、策略回测、预测结果、对话页面是否能正常展示。
5.2 演示前一定要做的几件事
答辩演示前,我强烈建议准备一套完整的演示脚本,并提前手动运行一遍所有功能,而不是在现场临时操作数据采集。万一现场网络不稳定或者数据源接口波动,数据拉不出来,整个演示就会卡住。
演示数据建议提前准备好,把要展示的几只股票数据全部预拉到本地MySQL中,并且关闭自动数据更新任务。这样演示过程完全离线可控,不受外部环境影响。
LLM模块准备一个备用方案。如果现场调用大模型API失败,画面会很尴尬。我在系统里做了一个本地mock开关,当LLM_API_KEY没有配置或调用失败时,自动返回高质量的预置分析模板,保证演示流程不断档。
在答辩演示LSTM预测时,要坦白说明预测模型的局限性和未来改进方向。评委听到“模型在历史回测上表现良好,但实际预测准确率受市场环境影响较大,未来可以引入Attention机制或更多宏观特征”这类表述,通常会对你的系统理解和思考深度有好的印象。
6. 关于系统扩展与后续优化的思考
做完这套系统,自己实际体会最深的一点是,把大模型引入量化分析时,不要执着于让大模型去预测股价,它的优势在于语义理解和知识组织,而不是数值外推。真正合理的架构是让传统量化模型负责计算和信号生成,让大模型负责解释、总结和人机交互,各做各擅长的事。
后续还有几个方向上值得继续挖掘。在预测模型层面,可以把LSTM升级成Transformer甚至引入注意力机制,或者在多股票之间做联动预测,结合行业板块指数。在策略层面,可以加入风险控制模块,比如设置动态止盈止损、按ATR比例控制仓位。在LLM应用层面,可以做RAG增强,让大模型能够检索历史新闻、公告和研报片段作为回答背景,也可以让LLM输出结构化JSON格式的多因子策略参数,实现简单的自动策略生成效果。
如果你是在这个毕设方向上起步,希望这篇实战记录能帮你少踩一些坑。尤其是数据泄漏、复权处理、LLM定位这几个关键决策,想清楚再动手,会比盲目堆功能顺利得多。