☰
新闻情绪驱动的股票预测:文本分析到LSTM的完整实现
2026/10/13 5:07:54 网站建设 项目流程

简介:资源为基于文本分析的股票预测系统毕业设计完整项目,内含可运行源码、设计报告与说明文档,面向计算机相关专业的学生、教师及科研人员,适合用于毕业设计、课程设计、作业提交或初期项目演示。项目以Python为实现语言,结合文本分析技术对股票数据进行处理,核心部分利用NumPy从零搭建RNN时间序列预测模型,包含数据加载、模型训练与预测等多个模块,并配有CSV格式样本数据、XML工程配置、Markdown使用说明及Word版设计报告,目录结构清晰,便于快速定位与二次开发。整个压缩包共18个文件,大小仅288KB,轻量便携、部署简单。目前已有43人学习下载,源码经过全面测试运行稳定,既适合编程初学者对照学习,也支持有基础的用户在此基础上扩展更多功能,直接应用于自己的课程或毕业设计。

1. 把新闻文本变成股价特征:文本分析预测到底在预测什么

早上九点开盘前,一条关于某公司产品获批的新闻突然推送,股价当天直接拉出长阳;两周后一条股东减持公告出来,股价又是另一个走势。这种纯由文字信息驱动的行情波动,正是基于文本分析的股票预测系统想用代码量化的东西——把新闻、公告、帖子通过自然语言处理转成情绪数值,再和行情数据一起喂给模型,预测下一交易日的涨跌方向。这个选题在毕业设计里一直很受欢迎,一条链路串起数据采集、中文分词、情绪得分、机器学习和回测评估,每一段都容易展示成果。下文按我实现这类系统的常见顺序往下走:先定架构和数据链路,再做特征与模型,最后给一套可本地跑通的最小代码和排雷清单。

2. 系统架构与数据链路:从抓新闻到出预测结果要过几道闸

整个系统的数据流比想象中长:文本源采集、清洗、情绪打分、行情对齐、特征拼接、模型训练、回测评估。中间任何一个环节对不上,后面全是白做。我一般先把这条链路画成一张带时间戳的流程图,标清楚“每个特征在哪个时刻是已知的”,这比先写代码重要得多,因为股票预测最大的敌人就是未来函数——把未来信息偷偷放进训练集,回测再漂亮也是自欺欺人。

2.1 文本源选型:财经新闻、公司公告和股吧帖子怎么取舍

很多毕设一开始就纠结“我要爬哪个平台”,其实更该纠结的是“我要哪种文本”。三类来源各自的问题完全不同:

文本源优点缺点适合做什么
财经新闻覆盖面广、及时性强噪声大,标题党多日均情绪基线
公司公告准确度高、影响大频率低、披露滞后事件驱动信号
股吧/论坛帖子散户情绪直接小作文多、清洗难情绪极值预警

我一般建议毕设以新闻为主力,公告做辅助。原因很实际:新闻API和公开页面的爬虫结构相对稳定,公告页面的字段复杂且时常改版;股吧帖子虽然情绪浓度高,但清洗工作量翻倍,答辩时很难说清楚“你是怎么过滤垃圾帖的”。如果在论文里加一段消融实验,对比“只加新闻情绪”和“加上股吧情绪”的预测效果差异,反而是个不错的加分点。

这里顺带说一句,歌词文本分析里常用到的情感标签思路,和金融情绪判别其实是一个套路:前者给歌词打“伤感/励志”标签,后者给新闻打“利好/利空”标签,底层都是构建情感词典或训练文本分类器,方法上完全可以互相借鉴。

2.2 情绪计算方案三档:词典法、SnowNLP 和中文 BERT

文本变数值这件事,有三档做法,工作量从低到高:

简单做法是情感词典法:准备一个积极词表和一个消极词表,对新闻标题和正文分词后统计命中次数,加权得到情绪分。优点是快、可解释,答辩时老师问“你为什么要用这个词表”,你直接打开记事本给人家看。

中间做法是 SnowNLP 这类现成中文情感分析库,但它的默认模型是用电商和酒店评论语料训的,用在财经新闻上会有“系统崩溃”被判断成消极的乌龙——公司系统崩溃股价确实可能跌,但新闻本身的基调未必是负面。我有一次跑完整条管线才意识到情感分数几乎恒定在 0.5 附近,就是因为语料领域不匹配。

更专业的做法是中文 BERT 类模型,例如用金融语料微调过的模型来做文本分类。效果通常最好,但需要 GPU 显存,训练时间翻几倍,对一个毕设来说成本和性价比要看你的机器配置。

我的建议是:词典法打底,保证链路能跑通;如果有余力,再换 BERT 做对比实验,论文里写“词典法作为强基线,BERT 作为上限参考”,结构很完整。

2.3 行情和文本怎么对上:交易日历、除权除息和时间戳口径

文本有了情绪分,行情有了价格序列,接下来的核心问题就是“怎么对齐”:

第一,交易日问题。周末和节假日没有行情,但新闻照发。如果直接把周一新闻的情绪分对齐到周一的价格上,那就把周六周日两天的信息灌进了同一时刻。我一般用交易日历做映射——把新闻时间戳向后映射到下一个交易日,或者按最近一个交易日聚合,但必须显式写清楚你的对齐规则。

第二,除权除息问题。股票分红送股后价格会跳空,比如股价从 50 跳到 25。如果不做复权处理,模型会把这个跳空当成暴跌信号,这可是灾难性的错误。行情数据必须使用前复权或后复权数据。

第三,时间戳口径问题。新闻是盘中发布还是盘后发布,直接影响它属于 T 日还是 T+1 日。保守做法是:T 日收盘后到 T+1 日开盘前的新闻,全部作为 T+1 日的输入特征,这样保证在 T+1 日开盘时这些信息确实已经公开。宁可损失一点时效性,也不能引入未来数据。

3. 特征工程与序列建模:情绪分数怎么和行情数据做化合反应

很多新手拿到情绪得分后,直接把它当成一个普通特征塞给模型,然后发现效果和没加一模一样。这很正常,因为单条新闻的情绪分只是孤立信号,模型看不到上下文。真正有价值的做法是先把情绪分加工成“有历史”的特征,再和行情序列拼接。

3.1 情绪特征构造:滑动窗口、差分值,还有情绪分歧度

我常用的二木特征组合是这样的:

  • 情绪均值(窗口 3 天):捕捉短期新闻基调
  • 情绪差分(今日均值减 5 日均值):捕捉情绪的突变,比如突发利空
  • 情绪波动率(窗口 3 天标准差):衡量新闻情绪分歧度。分歧越大,往往后续波动越大
  • 新闻数量:某种程度上反映关注度,按天计数加进特征矩阵

注意,这些窗口计算只能用过去的数据向前滚动,不能用今天到未来的信息求均值。实现的时候我会刻意把窗口边界写成左闭右开,避免把自己绕晕。

这一点和歌词文本分析里的“段落级情绪分布”很类似:只看一句歌词很难判断整首歌的情感走向,要看情绪随时间的起伏曲线。做成证券也一样,短窗口的情绪均值相当于一句歌词,滑动窗口的情绪曲线才是一整首歌。

3.2 预测目标设计:是预测涨跌,还是预测收益区间

确定预测目标比选模型更关键。我见过很多毕设一上来就做三分类——涨、跌、平,结果中间那类边界模糊,模型训练时学了个寂寞。我的建议是:先做二分类,目标变量定义为“未来一个交易日收益率是否大于阈值”。

具体操作:计算 T+1 日收盘价相对 T 日收盘价的涨跌幅,大于等于 0 标记为 1,小于 0 标记为 0。你可以留一个 0.2% 的小缓冲带,把涨跌幅在正负 0.2% 之间的样本当成平局丢掉,能有效减少噪声。

回归预测也是可选的,但直接预测具体收益率对模型要求更高,预测准确率普遍很惨,答辩时不好看。分类输出概率反而可以做成“信号强度”,后续回测时用概率阈值控制买不买,逻辑非常顺。

3.3 模型选型:LSTM、GRU 与简单时序模型的取舍

毕设里最常见的模型组合是 LSTM。但 LSTM 不是银弹,它需要足够多数据才能学出规律。我自己的经验是,单只股票几百条日频数据训练 LSTM,效果往往还不如一个带正则化的逻辑回归加滞后特征。所以我的建议是分两步走:

第一步,用逻辑回归或梯度提升树跑一个强基线,先确认情绪特征有没有增量信息。很多情况下,新闻情绪的特征重要性排在前三,这个结果就足够支撑论文了。第二步,如果数据量大(多股票几年日频数据,或换用小时级数据),再上 LSTM 或 GRU,并用 GRU 作为 LSTM 的对照组——GRU 参数少,收敛快,在小数据集上常常表现更好。

至于 Transformer,金融时序上并不天然优于 LSTM,而且训练难度大,毕设里通常作为延伸讨论即可,不需要真的跑结果。

4. 本地跑通最小闭环:从数据抓取、情绪打分到训练回测的完整代码

下面这套代码是我在本地跑通完整链路的最小配置。环境依赖只有akshare、pandas、jieba、tensorflow和matplotlib,不需要额外安装数据库。代码按数据流顺序拆成四段,每一段都可以单独运行查看中间结果。

4.1 行情数据:用 akshare 拿复权K线和成交量

import akshare as ak import pandas as pd # 拉取工行日线行情,使用前复权数据 df = ak.stock_zh_a_hist( symbol="600519", period="daily", start_date="20200101", end_date="20231231", adjust="qfq" # qfq 前复权,消除除权除息跳空 ) # 重命名字段为英文,方便后续处理 df = df.rename(columns={ "日期": "date", "开盘": "open", "收盘": "close", "最高": "high", "最低": "low", "成交量": "volume", "成交额": "amount" }) df["date"] = pd.to_datetime(df["date"]) df = df.sort_values("date").reset_index(drop=True) print(df.head())

这里最关键的参数是adjust="qfq"。如果你漏掉这一步,遇到分红送股的股票时,价格序列会出现人为的大坑,模型会把正常的除权当成暴跌信号。排序时我再强调一次sort_values("date")不能省,因为接口返回的数据不保证按时间升序,而时序模型对顺序极其敏感——排序错误会导致样本切分完全错位。

4.2 文本情绪:jieba 分词加情感词典跑出第一版分数

import jieba import re def load_dict(): pos_words = ["上涨", "增长", "突破", "利好", "新高", "盈利", "超预期", "增持"] neg_words = ["下跌", "亏损", "减持", "利空", "风险", "新低", "违约", "诉讼"] negate_words = ["不", "未", "无", "并非", "没有"] degree_words = {"大幅": 2.0, "明显": 1.5, "略微": 0.7, "严重": 2.0} return pos_words, neg_words, negate_words, degree_words POS_WORDS, NEG_WORDS, NEGATE_WORDS, DEGREE_WORDS = load_dict() def score_text(text: str) -> float: # 用正则替换掉非中文和空格,降低特征噪声 text = re.sub(r"[^\u4e00-\u9fa5]", "", text) words = jieba.lcut(text) score = 0.0 negate_flag = False degree = 1.0 # 逐词扫描,遇到否定词翻转极性,程度副词调整权重 for w in words: if w in NEGATE_WORDS: negate_flag = True elif w in DEGREE_WORDS: degree = DEGREE_WORDS[w] elif w in POS_WORDS: score += degree * (-1 if negate_flag else 1) negate_flag = False degree = 1.0 elif w in NEG_WORDS: score -= degree * (-1 if negate_flag else 1) negate_flag = False degree = 1.0 # 归一化到 0~1 区间,方便后续和行情特征拼接 normalized = 1 / (1 + (score * 0.5) ** 2) return normalized if score >= 0 else 1 - normalized

这段代码的核心是“否定词翻转”和“程度词加权”,这两行决定了情绪打分不是简单计数。比如“没有风险”会被识别为中性偏正面,而不是按字面统计成两个负面词。如果你发现所有新闻的情绪分都集中在 0.4~0.6 之间,先检查词典覆盖率——词典太小是毕设里最典型的翻车原因。你可以把新闻标题打印出来人工看几条,再把分词结果打印出来,确认词表真的命中。

4.3 构造训练样本:序列切分必须防标签泄漏

import numpy as np FEATURE_COLS = ["pct_chg", "volume_ratio", "sentiment"] def build_samples(df, lookback=20, gap=0.0): # df 必须按日期升序排列 # 特征:T 日之前 lookback 天的数据 # 标签:T+1 日相对 T 日的涨跌方向 arr = df[FEATURE_COLS].values X, y = [], [] for i in range(lookback, len(df) - 1): # 取 i-lookback 到 i-1 的窗口,即截至 T-1 日的历史 X_seq = arr[i - lookback:i, :] cur_close = df["close"].iloc[i - 1] next_close = df["close"].iloc[i] ret = (next_close - cur_close) / cur_close # 使用 gap 参数过滤收益绝对值过小的样本 if abs(ret) < gap: continue X.append(X_seq) y.append(1 if ret > 0 else 0) return np.array(X), np.array(y)

特别注意:循环从lookback开始,标签用的是df["close"].iloc[i]和iloc[i-1]的差值,也就是说预测的是第i天的涨跌,但特征窗口只到第i-1天为止。这是整段代码里最不能出错的地方。我曾经见过有人把标签错位到i+1,导致模型在训练时已经看到了当天的全部行情信息,回测准确率冲到 78%,实盘直接原形毕露。

4.4 LSTM 模型训练和回测:用最简单的结构出一版可答辩结果

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping from sklearn.metrics import confusion_matrix def build_lstm(input_shape): model = Sequential([ LSTM(64, return_sequences=True, input_shape=input_shape), Dropout(0.2), LSTM(32), Dropout(0.2), Dense(16, activation="relu"), Dense(1, activation="sigmoid") ]) model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"]) return model model = build_lstm((lookback, len(FEATURE_COLS))) early_stop = EarlyStopping(monitor="val_loss", patience=5, restore_best_weights=True) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=30, batch_size=32, callbacks=[early_stop], verbose=1 ) # 回测:用模型概率大于阈值时持仓 prob = model.predict(X_test).flatten() pred = (prob > 0.5).astype(int) # 只看准确率远远不够,打印混淆矩阵 print(confusion_matrix(y_test, pred))

这里return_sequences=True的参数别删,它让第一层 LSTM 输出完整序列给第二层继续提取时间依赖。patience=5的早停是在验证集 loss 连续 5 轮不降时自动回滚到最优权重,这是防止过拟合最有效的后悔药。

回测时我不能只贴代码不提醒:模拟交易不能用“预测涨了就按 T 日收盘价买入”,因为 T 日收盘时你根本没有预测结果。正确做法是 T 日收盘后出预测信号,T+1 日开盘再执行,这样才能保证你的信号在真正交易时是已知的。

5. 毕业设计避坑指南:数据泄漏、样本失衡与回测幻觉的系统排查

这一章直接给排查清单。以下四个坑我都在实现和评审中见过,每条都按“现象→原因→解决”来写,你遇到类似问题可以直接对照排查。

5.1 回测收益惊艳,实盘立刻翻车

现象:回测曲线年化收益 80%,超额收益曲线完美平滑,你自己都不太相信。但跑到实盘模拟上,连续几周亏损。

原因:这是最经典的未来函数泄漏。要么新闻情绪分了带进了 T 日收盘以后才发布的信息,要么序列切分时标签窗口和特征窗口有重叠。前面 4.3 节代码里我已经示范了正确切法,但很多人改了自己的数据结构后就会忽视。

解决:审计数据流中每一个时间戳。具体做法是写一段断言代码,检查特征矩阵所有行的日期都严格早于标签对应的日期。我习惯在构建样本时加一句assert (feature_time < label_time).all(),宁可程序报错跑不下去,也不让错误数据悄悄混进去。

5.2 准确率冲上 85%,但细看是在预测“不涨”

现象:模型整体准确率很高,但你把预测结果按“真涨”和“真跌”拆开看,发现它对上涨样本的召回率不到 20%,几乎只会输出“不涨”。

原因:样本不平衡。当行情处于震荡期时,“不涨”的样本占比远高于“涨”,模型只要学一个“永远输出 0”的策略,准确率就已经很高了。这在金融数据里太常见了,尤其是用日频数据时。

解决:不要只看 accuracy。打印混淆矩阵,计算 positive 类的精确率和召回率,必要时给少数类加更高的类别权重。class_weight={0: 1.0, 1: 1.5}是 keras 里一两行能直接解决的问题,很多答辩老师会盯着这个细节问,提前准备好对你有利。

5.3 Loss 正常下降,但加了情绪特征和没加完全一样

现象:训练过程 loss 正常下降,验证集表现也正常,但你做个对比实验,发现把情绪特征置为常熟后,预测结果几乎不变。

原因:情绪分数没有区分度。最常见的情况是情感词典覆盖率太低,绝大多数新闻文本一个词都没命中,情绪分数全是默认值。我之前用 SnowNLP 处理财经新闻时,就吃过这个亏——分数全落在 0.45~0.55 之间,方差几乎为零。

解决:查看情绪分列的标准差,小于 0.1 就说明特征失效。把词典做大,或者换用带金融语料的模型。验证时间不要花在模型调参上,先去检查特征本身有没有信息量。

5.4 同一个模型换一只股票就崩

现象:A 股上训练好的模型,换到 B 股上准确率直接跌到随机水平,甚至出现 loss 为 NaN。

原因:A 股和 B 股的数据质量不同步。有些股票停牌天数多,时间序列存在大量空档;另一些因为除权除息没有复权,价格序列里有巨大的伪跳变。序列模型对这些异常特别敏感,一个异常值就能把归一化参数带偏。

解决:在数据预处理阶段统一做三件事——填充停牌日缺失值(用前值填充或直接删除停牌段)、强制使用前复权数据、按时间顺序而非随机方式切分训练集和验证集。随机切分在这里是重大错误,因为它会把前后相邻的样本同时分到训练集和测试集,造成时间上的交叉泄漏。

6. 从能跑到能答辩:回测评估的严谨细节和报告里的加分写法

模型训练完,预测信号有了,回测曲线也画出来了,这时候毕设才做到一半。剩下的一半在于怎么证明结果是可靠的,答辩时怎么经得住追问。

我最推荐的验证方法是时间序列滚动交叉验证:不要用一次随机切分,而是把数据按时间分成五段,第一段训练、第二段测试,第一二段训练、第三段测试,依此类推。这样不仅避免了前面说的时间混叠,还能观察到模型在不同市场环境下的稳定性——如果 2022 年那段行情里模型表现崩溃,你在论文里写出来反而比藏着更能体现你的思考深度。

回测指标也别只写累计收益率。答辩时你至少需要准备四个数:胜率、最大回撤、夏普比率,以及相对基准的超额收益。如果只用一条净值曲线就想证明模型有效,老师一眼就能看出来你没有评估系统设计的经验。我通常把“每日情绪分值分布图”也放在附录里,它既能展示文本分析的中间产出,又能呼应选题标题里“文本分析”这个技术点。

报告撰写有一个很实用的视角:别把系统包装成“能赚钱的黑匣子”,而是包装成“验证文本信息是否对股价有预测能力的实验系统”。这个立场更诚实,也更好答辩——准确率 55% 时,前者是失败项目,后者是有价值的学术结论。你可以写“本实验验证了新闻情绪在特定时间尺度上具有显著预测能力”,这句话在论文评审里比“本系统年化收益 50%”安全得多,因为后者大概率会被追问到穿帮。

最后说一个我自己的习惯:每调完一组超参,就强制自己写一行文字记录“这次改了什么,为什么改,结果变化如何”。答辩时老师最常问的其实是“你试过哪些参数”,有这份记录在,你的回答会非常具体。LSTM 的隐藏层数和学习率这类超参,虽然实操时总有那么点玄学成分,但记录下来之后,至少你能讲清楚你是带着依据在调,而不是靠运气在蒙。希望这些经验能让你少走几趟弯路,也帮你把这份毕业设计真正做成一个能立得住的东西。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询