简介:这套毕业设计项目实现基于文本分析的股票预测系统,覆盖财经新闻爬取、文本特征处理、数据清洗与RNN时间序列预测等环节,适合计算机、人工智能、电子信息、物联网等专业学生用于毕业设计、课程设计或项目初期演示。压缩包共18个文件,包含5个Python源码、6个XML配置、2个CSV数据文件、2个Markdown说明、1份设计报告docx及1个字节码文件,整体仅288KB,目录组织清晰,便于按需查阅代码、配置与文档。源码利用numpy从零实现RNN模型,提供训练、预测与数据加载等模块,并针对批量处理、无激活函数等变体准备了多个可执行脚本,经过测试运行稳定,可快速复现股票预测流程,也方便在原有框架上做二次开发。附带的设计报告可辅助撰写毕业论文或设计文档,README则有助于理解项目结构。目前已有43人学习下载,尤其适合零基础入门或需要快速搭建预测系统的毕业设计学生。
1. 文本分析预测股票:先搞清它到底解决什么问题
毕业设计选“基于文本分析的股票预测系统”,通常不是因为你对炒股多感兴趣,而是想找一个“有数据、有模型、有界面、能讲出故事”的题目。这个方向的本质是用新闻、公告、股吧评论这类非结构化文本,抽取情感和主题信号,再和价格数据一起送入模型,预测未来几个交易日的涨跌方向。它解决的不是“如何稳定赚钱”,而是“文本信息到底能不能提升预测效果”——这个命题清晰、可量化,也适合写成项目报告。
我见过太多人栽在同一个误区:拿到项目就去调模型,天天折腾LSTM和注意力机制,最后回测结果一塌糊涂,答辩时说不出文本特征到底起了什么作用。更扎实的路线是先把数据对齐、特征构造、评估口径这三件事做对,再谈模型。这篇笔记会按“数据管道 → 文本特征 → 建模回测 → 避坑 → 升级验证”的顺序展开,目标读者是正在做毕业设计、需要短期跑通并写出有说服力报告的同学,也包括想快速验证文本因子是否有效的量化入门者。整套方案用Python实现,代码量不大,但每步都有值得抠的细节。
2. 数据管道先行:新闻、公告、股吧文本如何采集并落到交易日
2.1 文本预测最容易被忽视的环节是时间对齐
做股票预测的文本分析,第一原则不是算法多先进,而是每一句文本都必须能对应到一个具体的交易日。新闻发布时间是自然日,股票交易只有工作日,节假日发的消息要落到下一个交易日;盘后发布的公告影响的是次日开盘。这个对齐逻辑如果做错,模型就会“偷看未来”——拿当天收盘后才发布的新闻去预测当天的涨跌,回测指标会虚高,实盘完全失效。
我一般用 akshare 拉交易日历,用 pandas 做重采样。akshare 是免费开源的数据接口,适合毕业设计;tushare 需要积分,但数据结构更规整,两者选一个就行,数据源别混用。
import akshare as ak import pandas as pd # 取交易日历,2020-2024 共约 1200 个交易日 calendar = ak.tool_trade_date_hist_sina() calendar["trade_date"] = pd.to_datetime(calendar["trade_date"]) calendar = calendar[ (calendar["trade_date"] >= "2020-01-01") & (calendar["trade_date"] <= "2024-12-31") ]["trade_date"].tolist() # 把自然日新闻时间映射到下一个交易日 def align_to_trade_day(dt, cal): dt = pd.Timestamp(dt) # 当天是交易日且时间早于 15:00,视为当日信号 if dt.date() in cal and dt.hour < 15: return dt.date() # 否则顺延到下一个交易日 future = [d for d in cal if d >= dt.date()] return future[0] if future else None news_df["signal_date"] = news_df["publish_time"].apply( lambda x: align_to_trade_day(x, calendar) )这里有个关键参数:15:00 作为日内分界线。A 股收盘时间就是 15:00,之前发布的新闻可以影响当天尾盘或次日,之后的只能算次日信号。如果你想保守一点,把分界线改成 09:30,即所有当日新闻都只作用于下一个交易日,回测会稍微难看一点,但更接近真实可交易状态。两个口径在报告里至少要说明选哪个,以及为什么。
2.2 文本源怎么选:全量抓取不如聚焦三类来源
不少同学一上来就想抓全量新闻,结果爬到一堆重复的财经快讯和水文,对预测毫无帮助。做毕业设计,文本源聚焦三类就够:个股新闻标题、公司公告摘要、股吧评论。这三类覆盖了“市场情绪、公司基本面、散户预期”三个维度,特征互补,而且数据量可控。
个股新闻用新浪财经的个股新闻接口或东方财富的新闻流;公告用巨潮资讯的 PDF 摘要不方便解析,直接用 akshare 的公告接口拿标题即可;股吧评论可以用东方财富股吧的接口,但注意评论噪声极大,建议只取当天热度前 20 条,而不是全量。
import requests import jieba import re def clean_text(text): # 去掉URL、@符号、多余空格,保留中文、英文、数字 text = re.sub(r"http\S+", "", text) text = re.sub(r"[@#]\w+", "", text) text = re.sub(r"\s+", " ", text) return text.strip() def tokenize(text): words = jieba.lcut(text) stopwords = set([line.strip() for line in open("stopwords.txt", encoding="utf-8")]) return [w for w in words if w not in stopwords and len(w) > 1] news_df["tokens"] = news_df["title"].apply(clean_text).apply(tokenize)停用词表直接在网上找一份常用中文停用词就行,里面有“的、了、是、在”这类虚词,实际上金融文本还有自己的停用词: “公司”“今日”“股份”这类词出现频率太高,但对涨跌没有区分度,我会在预处理时额外加进去。分词这一步用到的是 jieba 默认词典,对股票简称识别不好,比如“中国平安”可能被切成“中国/平安”,后续可以加载自定义词典,把股票简称和常见金融术语加进去——这是提升文本特征质量性价比最高的一步。
2.3 标签构造:预测三日方向比预测次日更稳定
文本信息对股价的影响通常不是当天就完成,而是持续两三个交易日。我建议标签用“未来 3 个交易日累计收益”的方向,而不是单一次日涨跌。原因是短期噪声太大,次日涨跌受盘口和资金面影响远大于消息面,预测准确率很难超过 55%;三日的累计收益更能体现文本因子的有效性,回测曲线也平滑一些。
import akshare as ak # 拿个股日线数据 price = ak.stock_zh_a_hist( symbol="600036", period="daily", start_date="20200101", end_date="20241231", adjust="qfq" ) price["trade_date"] = pd.to_datetime(price["日期"]) price = price.set_index("trade_date").sort_index() # 未来3日累计收益,label=1 表示上涨 price["future_ret_3d"] = price["收盘"].shift(-3) / price["收盘"] - 1 price["label"] = (price["future_ret_3d"] > 0).astype(int) # 与新闻特征表按信号日期对齐 data = news_features.merge( price[["label"]], left_on="signal_date", right_index=True, how="inner" )参数 shift(-3) 是核心:它把第 t 天的收益横移到第 t-3 天,让模型在第 t-3 天就可以用已知信息做预测。这里没有用到未来数据,因为第 t-3 天收盘时,未来三天的收益还不存在——但建模时必须扔掉最后三行,否则标签是 NaN。另外注意复权因子,分红送股会导致价格跳空,必须用前复权数据,我在代码里已经设了 adjust="qfq"。
3. 把文本变成特征:从词频、情感得分到主题向量的完整流程
3.1 TF-IDF 起步,但别把它当作唯一武器
文本向量化最稳妥的做法是 TF-IDF 打底,因为它可解释、计算快、不容易过拟合。做完 TF-IDF 之后,把矩阵按 signal_date 分组取均值,就得到“每天所有新闻的综合向量”。问题在于维度太高,几百篇新闻、每篇几千维,直接喂给模型会稀疏到没法训练。所以要做降维,常见做法是保留 TF-IDF 权重最高的 500 个词,或者用 TruncatedSVD 压缩到 50 维。
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import TruncatedSVD # 训练集和测试集分离后再fit,避免数据泄露 tfidf = TfidfVectorizer(max_features=500, ngram_range=(1, 2), min_df=3) X_tfidf = tfidf.fit_transform(news_df["clean_text"]) svd = TruncatedSVD(n_components=50, random_state=42) X_reduced = svd.fit_transform(X_tfidf) news_df["tfidf_pc"] = list(X_reduced)max_features 取 500 不是拍脑袋。选太少,高频词区分度低;选太多,低频词全是噪声。配合 ngram_range=(1, 2),把“业绩预增”“股东减持”这类双词搭配也纳入特征,比单纯单词能捕捉更多语义。min_df=3 要求词至少在 3 篇文档中出现,滤掉生僻词。TruncatedSVD 的 50 维是经验值,你可以 20/50/100 三档都试试,在验证集上看哪个稳定——不要只盯着训练集准确率。
3.2 情感得分:用 SnowNLP 做日度情绪指标
情感得分是文本预测里最直观的特征。中文情感分析常用 SnowNLP,因为它开箱即用,不需要标注数据。它的缺点是通用语料训练,对金融文本不太友好,比如“暴跌”它得分低,但“回落”“调整”这类中性偏负面词它不一定分得清。另一个选择是专门针对股票评论微调过的模型,但毕业设计时间有限,先用 SnowNLP 跑基线,再去换更复杂的方案。
拿到每条新闻的情感得分后,按 signal_date 聚合:算当天平均情感、最大最小情感、情感标准差。标准差是个容易被忽略但很有用的特征——一天内新闻情绪分歧大,往往意味着市场对消息解读不一致,次日波动率可能上升。
from snownlp import SnowNLP def get_sentiment(text): if len(text.strip()) < 5: return 0.5 try: return SnowNLP(text).sentiments except: return 0.5 news_df["sentiment"] = news_df["clean_text"].apply(get_sentiment) daily_emo = news_df.groupby("signal_date").agg( sent_mean=("sentiment", "mean"), sent_std=("sentiment", "std"), sent_max=("sentiment", "max"), sent_min=("sentiment", "min"), news_cnt=("sentiment", "count"), )aggregate 里的小心机:news_cnt 表示当天新闻数量,它本身就是一个有效特征——新闻爆量往往是事件驱动的信号。实战里有个翻车点:SnowNLP 对长文本处理很慢,几千条新闻跑下来要好几分钟。所以我会先做一次长度过滤,只对 cleaned_text 的前 200 个字算情感,速度能快 5 倍,准确率几乎没有损失。
3.3 舆情情感倾向分析的扩展:加入主题维度
如果你想让系统更有辨识度,可以把“情感分析”升级成“情感+主题”的二维特征。这就用到了热词里常说的舆情情感倾向分析,思路是:把新闻按主题聚类(利好、利空、中性,或更细的业绩、减持、研发、政策),然后统计每个主题当天的情感均值。比如“减持”主题下情感极差,效果比单一平均情感得分更强烈。
主题识别不用上大模型,用 LDA 主题模型或者简单的规则匹配就行。规则匹配在金融文本里非常可靠——“预增、中标、签约、回购”是明确利好词;“预减、诉讼、减持、质押、违规”是明确利空词。建立两个词典,数每天利好词和利空词出现的次数,构造一个“舆情净多因子”。
bullish_words = ["预增", "中标", "回购", "签约", "增持", "突破"] bearish_words = ["预减", "诉讼", "减持", "质押", "违规", "亏损"] def count_signal(words): bull = sum([w in words for w in bullish_words]) bear = sum([w in words for w in bearish_words]) return bull - bear # tokens 是分词结果列表 news_df["signal_score"] = news_df["tokens"].apply(count_signal) daily_signal = news_df.groupby("signal_date")["signal_score"].sum()这套规则的价值在于可解释性。答辩时老师问“你的模型在逻辑上怎么说得通”,你可以直接指出某一天的“减持”词频上升导致 signal_score 降低,模型输出偏空——这种案例比任何黑匣子模型都更有说服力。注意词典要自己维护和调试,一开始可能只有十几个词,跑几天看结果,把误判的案例拉出来补充规则,变成你自己的迭代武器。
4. 建模与回测:用 LightGBM 把文本信号变成预测
4.1 为什么选 LightGBM 而不是 LSTM
文本股票预测系统里最常见的模型误区是为了用深度学习而用深度学习。LSTM 处理时间序列文本当然可以,但你的数据集撑死了不到 1200 个交易日,再按 7:3 划分,训练样本只有 800 多个,LSTM 学不到长期依赖,反而容易把噪声背下来,验证集上惨不忍睹。LightGBM 这类梯度提升树在中小样本、表格型特征上表现极其稳定,训练快、不用调太多参数、还能输出特征重要性,这才是毕业设计该选的模型。
特征拼好后,把 TF-IDF 主成分、情感四维、舆情因子、价格技术指标(比如过去 5 日收益率、成交量变化率)合并成一个 DataFrame,删掉有 NaN 的行,然后按时间顺序切分训练集和验证集。时间序列切分不能用随机切分,否则模型会看到未来数据。
import pandas as pd import lightgbm as lgb # feature_cols 是所有特征列,label_col 是 0/1 标签 X = data[feature_cols].copy() y = data["label"].copy() # 按时间顺序切分:前 70% 训练,后 30% 验证 split_idx = int(len(X) * 0.7) X_train, X_val = X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_val = y.iloc[:split_idx], y.iloc[split_idx:] # 模型训练参数 params = { "objective": "binary", "learning_rate": 0.05, "n_estimators": 500, "num_leaves": 31, "max_depth": 5, "min_child_samples": 20, "feature_fraction": 0.8, "bagging_fraction": 0.8, "bagging_freq": 1, "random_state": 42, "verbosity": -1, } model = lgb.LGBMClassifier(**params) model.fit( X_train, y_train, eval_set=[(X_val, y_val)], eval_metric="auc", callbacks=[lgb.early_stopping(stopping_rounds=50)], )几个必调参数:learning_rate 从 0.1 降到 0.05,能明显抑制过拟合,代价是训练变慢,但这里数据量小,慢不到哪去;num_leaves=31 对应树的复杂度,数据小就别开大到 100 以上,叶子越多越容易把训练集中个别异常样本记住;min_child_samples=20 保证每个叶子节点至少 20 个样本,防止模型在极端情况下分裂出单样本节点。这些参数不是最优,但足以作为基线,后续要调试的话先调 num_leaves 和 learning_rate,其他参数保持不动。
4.2 评估指标别只看准确率,看方向准确率和 AUC
二分类模型的默认评估是准确率,但股票预测里“涨”和“跌”的样本比例通常接近 50:50,准确率达到 52% 就算有信息量,55% 已经是不错的因子。问题是模型很容易预测出极端概率,比如输出 0.85 的上涨概率,结果实际跌了——这其实是过拟合的表现。因此我建议同时报告 AUC 和方向准确率,AUC 衡量排序能力,方向准确率衡量实战可用性。
from sklearn.metrics import accuracy_score, roc_auc_score # 预测概率和类别 y_prob = model.predict_proba(X_val)[:, 1] y_pred = (y_prob >= 0.5).astype(int) print("验证集 AUC:", roc_auc_score(y_val, y_prob)) print("验证集方向准确率:", accuracy_score(y_val, y_pred)) # 只看概率最高的 30% 样本,验证“高置信预测”的质量 top_idx = y_prob >= np.percentile(y_prob, 70) print("高置信区方向准确率:", accuracy_score(y_val[top_idx], y_pred[top_idx]))高置信区准确率是判断模型是否有实际信号的试金石。如果全量准确率 53%,但高置信区准确率 58%,说明模型虽然整体弱,但在倾向强烈时有效,这样的因子可以在系统中以“信号强度”的形式输出,让用户自己判断。如果高置信区准确率反而低于全量,说明模型概率校准是坏的,需要检查特征是不是混入了未来数据,或者 label 构造错了。
4.3 回测可视化:把预测信号画出来,报告直接可用
做完预测模型,还要做一个简单的回测可视化,展示“按信号交易的累计收益曲线”。常见做法是每日收盘前根据当天特征生成次日预测,如果预测上涨就持有,预测下跌就空仓,按此模拟资金曲线。
import matplotlib.pyplot as plt import numpy as np # 将预测概率映射为仓位:>0.55 满仓,<0.45 空仓,中间不变 data_val = data.iloc[split_idx:].copy() data_val["position"] = np.where(y_prob > 0.55, 1, np.where(y_prob < 0.45, 0, np.nan)) data_val["position"] = data_val["position"].ffill().fillna(0) # 用验证集的实际日收益算资金曲线 data_val["daily_ret"] = data_val["涨停价_close"].pct_change() # 实际每天涨跌幅 data_val["strategy_ret"] = data_val["position"].shift(1) * data_val["daily_ret"] data_val["strategy_curve"] = (1 + data_val["strategy_ret"]).cumprod() # 基准:买入持有 data_val["benchmark_curve"] = (1 + data_val["daily_ret"]).cumprod() plt.plot(data_val.index, data_val["strategy_curve"], label="文本策略") plt.plot(data_val.index, data_val["benchmark_curve"], label="买入持有") plt.legend() plt.savefig("backtest.png", dpi=150)position 的阈值 0.55/0.45 是故意留的不对称区间,中间 0.45~0.55 视为看不清,保持原有仓位,这样能减少噪声交易的磨损。资金曲线画出来后要注意,验证集最后三天的收益是算不出来的,因为未来三日收益是 NaN,画图前要 dropna。回测图是项目报告里的压轴图,比任何文字都直观——但前提是别美化,真实回撤也要展示,答辩老师最反感只看收益不看回撤的报告。
5. 避坑:文本股票预测的 6 个高频雷区
做这个方向的毕业设计,我自己踩过、也帮别人排过太多问题。挑 6 个最常见的写在这里,每条都按“现象 → 原因 → 解决”来,遇到问题直接对应查。
5.1 回测大获全胜,实盘完全失效
现象:回测资金曲线完美上涨,年化收益率超过 100%,但换个时间段就断崖式下跌。
原因:特征或标签里混入了未来数据。最常见的是用“当天收盘后才知道的数据”去预测当天的涨跌,比如新闻发布时间是 15:03,但是被对齐到了当天而不是次日;或者停牌日、除权日没有处理,pct_change 算出了虚假收益。
解决:建立数据时间戳审计流程。对每一列特征,明确写出“该特征在 t 日收盘时是否已知”,写不出来的就重做。回测一定要用 walk_forward 方式,按时间滚动训练,不要一次性 70/30 切分后就当最终结论。我通常加一步检查:把 label 随机打乱后重新训练,如果模型仍然有效,说明特征里混进了泄露信息。
5.2 情感得分全在 0.4~0.6 之间,毫无区分度
现象:观察每天的 sentiment 均值,发现几乎全部落在 0.4 到 0.6 之间,和随机噪声一样,模型完全用不上。
原因:SnowNLP 的训练语料偏向电商评论,对财经文本的极性判断不够敏感。“股价回落”在财经语境里是利空,但在通用情感模型里可能被判为中性,因为“回落”不是典型情感词。
解决:用规则因子做主力,情感分数做辅助。维护利好利空词典,用 signal_score 代替 sentiment 作为主特征;或者用专门面向金融文本的情感模型,比如在雪球、股吧数据上微调过的模型,但这类模型需要额外下载,训练时间和显存成本较高,毕业设计用规则因子就够了,节省时间给报告写作和答辩准备。
5.3 新闻爬虫被封或返回空数据
现象:程序刚跑通了,第二天再跑就报错,返回 403 或空列表。
原因:爬虫请求频率太高,或没有携带 User-Agent,被网站反爬策略拦截。akshare 这类接口本身比较稳定,但新闻接口或股吧接口没有统一封装,很多人直接裸 request 请求,很容易触发风控。
解决:请求之间加随机 sleep(2~5 秒),设置合理的 User-Agent,并且把抓到的数据落地成 CSV 或 Parquet,当天抓完当天备份,后续调试直接用本地文件,不用每次跑都重新爬一遍。如果你要做多只股票,务必控制总量——50 只股票的新闻和评论已经足够展示系统能力,不需要全市场抓取。
5.4 样本不平衡导致模型永远预测“不涨”
现象:验证集准确率很高,但把预测结果打印出来发现全是 0,模型把每只股票都预测成“跌”或“不涨”。
原因:虽然 A 股长期看上涨天数略多于下跌天数,但如果你按“未来三天涨幅超过 3%”作为正例,正例比例可能不足 30%,模型学到的就是大多数类。
解决:先检查 y 的分布,如果正例比例低于 40%,考虑调整标签阈值,比如用“涨幅超过 1%”代替“涨幅为正”;或者用 LightGBM 的 scale_pos_weight 参数,按负例/正例比例设置权重,不用手动采样。注意这个参数不是越大越好,过大的权重会让模型疯狂预测上涨但全是假信号,建议从 1 开始,逐步调到 2,观察高置信区准确率的变化。
5.5 压缩包里的数据文件损坏,答辩前打不开项目
现象:你从同学或网上下载的“毕业设计含报告.zip”解压时报错,或者打开 Excel 数据文件乱码,代码能跑但结果对不上。
原因:zip 文件在传输过程中损坏,或者下载工具截断了文件;有些 zip 还有伪加密标志,解压工具会误判需要密码。
解决:下载后先用 7-Zip 验证压缩包完整性,“测试压缩文件”功能会检查 CRC 校验。如果报错,重新下载,不要抱着侥幸心理继续解压。如果你是自己打包交付,务必关闭伪加密选项,用标准 zip 格式,并且把数据文件和代码分开打包,不要把所有文件打成一个巨大压缩包,容易在中途出错。这一条看似和模型无关,但每年都有毕业生因为交付物打不开,答辩当场翻车。
5.6 复现别人的代码,结果跑不出相同结果
现象:网上找的开源项目,按 README 一路装依赖,最后预测结果和文章里对不上,或者干脆运行报错。
原因:环境不一致,涉及 Python 版本、numpy/pandas 版本、akshare 接口更新,甚至中文字体缺失导致图表不显示。开源项目写的时候可能用了当时的接口,后来数据源改版,代码就失效了。
解决:建议新建独立的 conda 环境,固定关键依赖版本,把 requirements.txt 或 environment.yml 整理进自己的项目里。akshare 这类数据接口不要固定版本追新,追新反而容易破坏原来的接口参数。遇到报错先看是接口报错还是模型报错,接口报错就检查字段名变化,模型报错再检查数据格式。这个排查习惯,比任何模型调参都更重要。
6. 从日频预测到事件驱动:一个可升级的验证套路
到这里,你已经有一个完整的基线系统:文本特征 + LightGBM + 回测可视化。但毕业设计要想拿高分,光有基线不够,你需要一个“升级验证”的路径,证明系统可以延伸。我建议尝试从“每日预测”升级到“事件驱动预测”,思路是:不预测每一天的涨跌,而是检测事件信号(比如当天新闻数量暴增、舆情净多因子超过阈值、情感得分单日跳变),只在这些事件发生后的次日进行交易。
这种设计的价值在于避开“每天都要做决策”的噪声,只在信息足够强烈的日子出手,更贴近真实投资者的操作习惯。代码上只需要在预测之后加一道门槛:
# 事件信号:新闻数量大于过去20日均值2倍,且舆情净多因子为正 event_mask = ( daily_news_cnt > daily_news_cnt.rolling(20).mean() * 2 ) & (daily_signal > 0) # 只在事件触发后的交易日持仓 data_val["event_position"] = 0 data_val.loc[event_mask, "event_position"] = 1 data_val["event_position"] = data_val["event_position"].shift(1).fillna(0)事件驱动的逻辑说明:rolling(20) 是过去 20 个交易日的移动平均,用来定义“新闻量暴增”的基准;乘以 2 是阈值,这个参数可以调成 1.5 或 3,会影响触发频率和信号强度,要在报告里展示敏感性分析。shift(1) 确保事件当天收盘后才建立仓位,第二天才能交易——这是避免前视偏差的关键。
把事件驱动策略和每日预测策略的资金曲线画在一起,验证集上如果事件策略的夏普比率明显高于每日策略,就说明你的系统具备“在噪声中识别有效信号”的能力。这个结论比单纯“预测准确率 53%”有说服力得多,答辩时老师也更愿意深挖这种设计动机。
文本股票预测系统做到这个程度,已经是一个完整且有说服力的毕业设计:数据管道有对齐逻辑,特征构造有情感和舆情因子,模型选择有对比理由,回测评估有防泄露意识,升级路径有事件驱动验证。我自己的教训是:别把时间花在堆叠复杂模型上,把数据对齐和评估口径做干净,系统自然站得住。最后强调一句,系统代码可以复用,但报告一定要自己写透,把每个设计决策的理由讲清楚,才是项目报告的真正价值。希望帮到你。
本文还有配套的精品资源,点击获取