ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于文本分析的股票预测系统设计:情绪特征全链路解析

基于文本分析的股票预测系统设计:情绪特征全链路解析 简介这是一套围绕文本分析与股票预测的毕业设计项目资源完整提供工程源码与项目报告适合计算机相关专业的学生、教师及开发者用于毕业设计、课程设计或入门进阶。压缩包内共18个文件以5个Python脚本为运行核心配合2个CSV数据文件、2个Markdown说明、1份Word设计报告以及xml/iml项目配置文件整体仅288KB结构紧凑目录划分清晰便于直接运行和二次开发目前已有43人学习。核心部分包含基于NumPy实现RNN的股票时间序列预测代码提供普通、非批量、无激活函数等多个变体脚本便于对照理解不同训练策略随附的设计报告对算法原理与实现过程有清晰梳理源码经过测试便于复现并观察不同设置对预测效果的影响项目说明文档也能帮助快速上手适合需要复现预测流程、撰写项目文档或扩展新功能的同学与从业者。1. 基于文本分析的股票预测到底在预测什么毕业设计选择基于文本分析的股票预测系统很多人的第一反应是“这东西真的能预测吗”。我的回答是它预测的不是精准点位也不是明天的开盘价而是市场对某类消息的消化方向和强度。这套系统的本质是把新闻标题、公司公告、社区讨论这些非结构化文本转成可量化的市场情绪信号再和量价数据一起交给模型。整个设计与实现过程并不依赖什么神秘模型真正值钱的部分是“文本到特征”的那段脏活。这个方向的落地价值很直接对毕设而言它既有自然语言处理的展示点又有金融数据的直观结果答辩时能用图表讲清楚对从业者而言它验证的是“情绪是否领先价格”这一假设后续可以平滑迁移到舆情系统、事件驱动策略等方向。适合的人群是正在纠结选题的计算机/金融工程学生想用公开数据做量化实验的开发者以及想给已有技术指标体系加一个“信息面”维度的个人投资者。下面按一条我从数据采集到模型评估的完整链路展开所有步骤都以可复现为前提。2. 设计与实现的五个模块数据流怎么打通2.1 从新闻到特征把系统切分成五层常见的做法是把系统拆成五层数据采集层、文本处理层、特征工程层、模型层、回测验证层。很多人上来先写模型调参这是最容易翻车的顺序。文本分析的链路是“一头脏一头重”采集层的新闻源是否稳定、文本处理层对财经语料的适配度直接决定后面模型的上限模型反而最省力XGBoost顶到上限就够了。我习惯先画出数据流图再动手写代码新闻源和行情接口在采集层产出原始文本与日线数据文本处理层负责清洗、分词、情感打分特征工程层把情绪分数按股票、按交易日聚合成特征模型层吃特征做分类或回归回测层负责模拟交易并输出净值曲线。每一层只依赖上一层的稳定接口比如预测模型永远不直接读原始新闻只读特征表。这样哪一环出了问题查起来边界清晰后面写项目报告时结构也直接可用。2.2 行情数据回补时间戳对齐是第一个大坑行情数据常见做法是直接用开源接口或第三方库拉取日线数据核心不是“怎么拿到”而是“怎么对齐”。新闻发布时间和K线时间不是一个粒度新闻出现在 14:55它只影响下一个交易日的开盘情绪而不能直接算进当天的收盘特征。我一般会对齐到“交易日号 当日新闻截止时间戳”。一个简单但有效的规则交易日 T 的文本特征只统计 T-1 日 15:00 之后到 T 日 15:00 之前的新闻这样避免用当天收盘前无法实际获得的信息去预测当天。时间戳对齐的字段如下对齐维度字段说明日期trade_date统一取自然日不是工作日股票stock_code用 6 位代码不带市场前缀行情时间K线时间戳日线下单时间 15:00新闻时间publish_ts精确到分钟只取前 30 分钟内的缓存2.3 存储选型MySQL 表怎么建特征表要冗余快照数据量不大不需要上 Hadoop 或数仓MySQL 足够。真正需要动脑的是表结构和快照策略。新闻表、行情表、特征表三张核心表就够了。新闻表设计最需要留意的字段是publish_ts和source。同一家媒体可能多次发布同一公告必须在入库前去重。我一般按(stock_code, title_hash, publish_date)建唯一索引title_hash用 MD5 就行避免全文本比对。CREATE TABLE news ( id BIGINT PRIMARY KEY AUTO_INCREMENT, stock_code VARCHAR(10) NOT NULL, title TEXT NOT NULL, content MEDIUMTEXT, publish_ts DATETIME NOT NULL, source VARCHAR(50), title_hash CHAR(32) GENERATED ALWAYS AS (MD5(title)) STORED, UNIQUE KEY uk_news (stock_code, title_hash, publish_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个表的关键逻辑是GENERATED ALWAYS AS生成列MySQL 会自动算 MD5入库时重复标题自然被唯一索引拦住。publish_date是冗余字段因为后续按日聚合时会频繁按日期过滤存一个日期字段不用每次都对publish_ts做函数运算查询快很多。特征表要留“快照冗余”的习惯即每天算好的特征追加写入而不是覆盖更新。这样回测时可以精确还原某一天模型实际看到的特征值避免因为回 fillna 导致的未来数据泄露这是整个设计与实现过程中最容易踩的数据污染源。3. 文本分析核心清洗、情感打分与特征聚合3.1 清洗文本先解决公告和新闻格式不一致从不同渠道拿回来的文本格式五花八门有的是带 HTML 标签的网页内容有的是 PDF 转出来的乱序文本还有广告拼接。“清洗不够彻底”会让后面所有情绪词频统计失真。我的清洗函数至少做四件事去标签、去广告行、统一全半角、过滤无意义字符。注意不要过度清洗比如“st”这种股票标记是有含义的不能当停用词删掉。import re def clean_text(raw: str) - str: # 去掉 HTML 标签和转义符 text re.sub(r[^], , raw) text re.sub(rnbsp;|amp;|quot;, , text) # 统一全半角中文标点转全角英文和数字保持半角 text text.replace(\u3000, ).replace(, ).replace(, ) # 去掉常见的页脚广告 text re.sub(r来源.*?|责任编辑.*, , text) # 压缩空白 text re.sub(r\s, , text).strip() return text这套逻辑的核心在于“规则分层”HTML 处理在前标点统一在后广告过滤最后。顺序反了会导致广告特征残留在文本里。参数\u3000是全角空格从 PDF 或网页复制来的文本里很常见不处理掉分词时会把它当成词边界直接干扰词频统计。清洗完成后要给文本标上来源类别。新闻、公告、互动易问答三类的语言风格差异极大互动易里“请问公司有没有和华为合作”这种问句和公告里的陈述句情感倾向的判定方式完全不同。我一般把清洗函数包装成一个clean_and_tag(row)在返回的 DataFrame 里新增doc_type字段。3.2 情感打分自建词典和通用模型混合用通用情感分析库直接给财经文本打分准确率往往惨不忍睹。“业绩预增”在通用模型里是中性偏正“股东减持”是中性偏负但通用模型可能把“减持计划实施完毕”这种利空出尽的句子判成负面直接导致情绪特征反向。我采用自建金融词典 通用模型兜底的混合策略先跑一个 3000 词左右的金融情感词典对词典命中的词语按权重累计整体分数低于阈值时再用通用模型做二次判断。import jieba import re FIN_DICT { 预增: 2.0, 增持: 2.0, 中标: 2.0, 回购: 1.5, 超预期: 2.5, 亏损: -2.5, 减持: -2.0, 立案调查: -3.0, 退市风险: -3.5, 商誉减值: -2.0 } NEG_TOTAL -1.0 # 通用模型兜底触发的阈值 POS_TOTAL 1.0 def score_text(text: str) - dict: words jieba.lcut(text) score 0.0 hits [] for w in words: if w in FIN_DICT: score FIN_DICT[w] hits.append(w) if score NEG_TOTAL or score POS_TOTAL: return {score: round(score, 2), method: dict, hits: hits} # 兜底用snownlp对长文本做一次整体情绪判断 from snownlp import SnowNLP s SnowNLP(text) if s.sentiments 0.6: score 0.5 elif s.sentiments 0.4: score - 0.5 return {score: round(score, 2), method: hybrid, hits: hits}这里的逻辑是词典命中且情绪明确的句子直接信词典词典没拿准的再用通用模型修正。NEG_TOTAL-1.0和POS_TOTAL1.0这两个阈值不能设成 0因为很多财经句子是混合情绪比如“业绩预增但股东减持”正面 2.0 和负面 -2.0 抵消后为 0这时不该套用通用模型去翻盘而是保留原样。词典命中词列表hits要存下来后续做消融实验和报告时能直接展示“模型到底是看了哪些词才给出这个分”这在毕业设计答辩中很加分。3.3 把情绪变成特征日频聚合不能只看均值单条新闻的情绪分没有意义真正进入模型的是某只股票在某个交易日窗口上的情绪分布。最容易被忽视的一点是“只算均值”这会把“一条极端负面新闻”和“十条温和负面新闻”混在一起。我一般生成这几类聚合特征当日新闻总量、情绪均值、情绪标准差、正向占比、负向占比、极端负向计数。其中“极端负向占比”往往是最有区分度的特征因为个股的短期暴跌通常由突发事件驱动一条 score 低于 -2 的新闻比十条 -0.5 的新闻更能解释次日下跌。def daily_agg(df): df df.copy() # 极端负向score -2.0 的新闻 df[is_extreme_neg] (df[score] -2.0).astype(int) # 极端正向score 2.0 的新闻 df[is_extreme_pos] (df[score] 2.0).astype(int) agg df.groupby([stock_code, trade_date]).agg( news_cnt(score, count), score_mean(score, mean), score_std(score, std), pos_ratio(score, lambda x: (x 0).mean()), neg_ratio(score, lambda x: (x 0).mean()), extreme_neg(is_extreme_neg, sum), extreme_pos(is_extreme_pos, sum) ).reset_index() # 标准差为空说明当天只有一条新闻补0而不是丢弃 agg[score_std] agg[score_std].fillna(0) return agg这里的核心参数是分桶阈值。score -2.0和score 2.0是根据词典强度设定的极端档位如果你改了自己的词典权重阈值要同步调整。最好的办法是先跑一遍全量数据画出 score 的分布直方图再看 90 分位对应的分数是多少用分位点去定极端阈值而不是拍脑袋写一个数。4. 预测模型标签怎么定、特征怎么拼、模型怎么选4.1 标签定义未来 3 日收益怎么变成分类任务预测系统最常见的“翻车”是把标签定义成“明天涨还是跌”然后训练一个模型回测还特别好看。问题在于你用的是 T 日收盘后的特征去预测 T1 的行情但 T1 开盘可能存在跳空这个跳空已经在隔夜情绪里反应了你的特征根本看不见。稳妥的做法是用“未来 3 日收益”做标签给市场留出反应时间也更符合情绪扩散的节奏。import pandas as pd import numpy as np def make_label(price_df, horizon3, up_th0.03, down_th-0.03): df price_df.copy() # 未来 horizon 日的收盘价相对于当日收盘价的收益率 df[future_ret] df.groupby(stock_code)[close].shift(-horizon) / df[close] - 1 # 分类上涨、下跌、震荡三类 df[label] np.nan df.loc[df[future_ret] up_th, label] 1 # 上涨 df.loc[df[future_ret] down_th, label] -1 # 下跌 df.loc[(df[future_ret] down_th) (df[future_ret] up_th), label] 0 df.dropna(subset[label], inplaceTrue) return df[[stock_code, trade_date, future_ret, label]]参数horizon3和up_th0.03是有讲究的。A 股日涨跌幅限制是 10%ST 是 5%3 日内累计 3% 算是一个不算夸张但显著的方向信号。如果你做的是指数预测阈值要调小到 1%做个股建议先画出未来收益的分布再定阈值让三类样本尽量均衡。shift(-horizon)是向量化操作比 for 循环快一个量级但要注意按股票分组否则不同股票的日期会错位。4.2 特征拼接量价特征和情绪特征合流文本特征算完之后要和行情数据按(stock_code, trade_date)拼接。这里要特别小心行情数据是左表情绪特征是右表用左连接绝不能用内连接否则停牌的股票直接消失了回测时会产生大量“无持仓日”的假象。feature_df price_df.merge( agg_df, on[stock_code, trade_date], howleft ) # 情绪特征的缺失值无新闻日的情绪视为中性均值而不是0 feature_df[[news_cnt, score_mean, score_std]] feature_df[ [news_cnt, score_mean, score_std] ].fillna({news_cnt: 0, score_mean: 0.0, score_std: 0.0})无新闻日的score_mean不填 0 更合理因为 0 在词典里代表“中性”而实际是“没消息”。我给的建议是无新闻日让score_mean等于历史 20 日均值代表这只股票的常态情绪热度news_cnt0时再单独加一个is_silent0/1 特征。模型需要知道“没新闻”和“新闻是中性”是两种完全不同的状态这在事件驱动策略里尤其重要。量价特征我一般只保留 5 日均线偏离度、20 日波动率、当日换手率这三个避免技术面特征把情绪特征压制住。4.3 模型选择先跑 XGBoost 基线LSTM 要足够数据再上很多人一上来就堆 LSTM结果样本量只有几千条过拟合严重回测曲线漂亮得像是画出来的实盘直接崩。我的建议是先用 XGBoost 跑出基线等验证了情绪特征真的有效再用 LSTM 去对比而且 LSTM 只作为优选项不是必选。import xgboost as xgb from sklearn.model_selection import TimeSeriesSplit # 按时间顺序切分禁止随机打乱 tscv TimeSeriesSplit(n_splits5) params { max_depth: 4, learning_rate: 0.05, subsample: 0.8, colsample_bytree: 0.8, eval_metric: mlogloss } for train_idx, val_idx in tscv.split(X): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx] dtrain xgb.DMatrix(X_train, labely_train) dval xgb.DMatrix(X_val, labely_val) model xgb.train(params, dtrain, num_boost_round200, evals[(dval, val)], early_stopping_rounds20)TimeSeriesSplit是时间序列任务里最容易被人忽略的一点它保证训练集永远在验证集之前避免未来的数据泄露。max_depth4是防止树模型在金融数据上过拟合的常规设置。early_stopping_rounds20帮助收敛金融数据噪声大早停在验证集最优时点通常比硬跑 200 轮更可靠。LSTM 的前提条件是样本量至少 3 万条以上且要先验证文本特征的加入能提升 XGBoost 的指标再去考虑序列模型。5. 避坑与常见问题一遍遍排查出来的血泪经验5.1 情绪分数与行情相关性几乎为零先查这三点现象跑完相关性分析情绪均值与次日涨跌的 Pearson 系数只有 0.02看起来整个文本分析方向完全不成立。原因有三层一是新闻样本量太少每天只有几条新闻统计噪声大于信号二是新闻源太泛把宏观经济新闻也算进了个股情绪这类消息对个股影响不是同向的三是时间戳错位新闻发布时间用的是入库时间而不是原始 publish_ts导致明明在 15:00 之后发布的新闻被算进了当天特征。解决把样本拉长到两年以上个股日均新闻量低于 3 条的直接剔除出样本新闻源按“个股公告 个股新闻”过滤去掉宏观类别时间戳统一用原始发布时间并在入库时记录publish_ts与created_at两个字段供核对。5.2 回测很赚实盘亏多半是未来函数和数据泄露现象模型在回测里年化收益 40%最大回撤不到 5%实盘跑了两周连续亏损。原因最常见的不是过拟合而是特征表里有未来数据。比较隐蔽的泄露路径是把“当日全天的新闻情绪”用在了“预测当日开盘”上另一个高发点是用 rolling_mean 时没有shift(1)导致当日的滚动均值已经包含了当日的未来行情。解决给所有量价特征统一套shift(1)每个特征的回看窗口截止到 T-1 日收盘。回测时用一个独立的feature_snapshot表表中每条记录是“在 T 日收盘后模型实际能看到的特征值”任何事后再算的列都进不了这张表。这个习惯能挡住 90% 的泄露问题。5.3 分词把“业绩预增”切成碎片情感词匹配频繁失败现象词典里明明有“预增”和“超预期”但打出来的命中列表为空人工抽查发现“业绩预增”被 jieba 切成了“业绩”和“预增”两个词。原因jieba 的默认词典是通用语料金融术语没有被分词模型优先识别。另一个高发点是公司简称被切开比如“贵州茅台”被切成了“贵州”和“茅台”导致后续个股关联对不上。解决切词前补充自定义词典把股票简称、行业术语、常见公告动词加进去。import jieba custom_words [业绩预增, 超预期, 股东减持, 立案调查, 商誉减值, 贵州茅台] for w in custom_words: jieba.add_word(w, freq10000)freq10000的参数含义是让这个词的优先级显著高于通用词的切分概率。词典维护有个技巧每两周跑一次分词结果把被切错的词手动加进去迭代几轮后命中率会稳定在 90% 以上。5.4 停牌、退市、ST 股污染回测数据现象回测净值曲线在某个时间段突然直线拉升检查发现持仓里出现了 ST 股连续涨停但这类股票的流动性根本支撑不了实际成交。原因没有在数据处理阶段把停牌日和 ST 状态过滤掉模型学会了押注连续涨停的 ST 股票这本质上是在记忆市场规则而不是预测。解决回测前统一过滤is_st标记、排除停牌日当日成交量为 0 的股票、剔除上市不满 60 天的次新股。这一步写在数据预处理函数里而不是回测时才处理否则特征表里依然残留脏数据。5.5 指标只有准确率答辩时一问就露怯现象项目报告里只写了准确率 0.62被评委追问“基准是什么、这个准确率的行为是随机的投资准确率”立刻卡壳。原因金融预测问题是典型的三分类不平衡问题“上涨、下跌、震荡”各占比并不相同准确率无法体现模型是否真的捕捉到了情绪信号。解决除准确率外至少报告 AUC、F1 和“方向命中率”。方向命中率指预测涨跌方向与实际方向一致的占比这个指标与“基准 50%”对比更直观。报告里还应放一张“预测置信度分桶下的真实收益”表看模型给出高置信度上涨时实际收益是否确实更高这是证明特征有效性的最有力证据。6. 从预测到可交付报告用消融实验证明“文本分析”真的有用最后这一层是把系统做成一个能站在答辩台上讲清楚的项目。整套系统的关键不是预测模型跑得多高而是让评委看一眼就知道“文本分析”这个环节到底贡献了多少。我常用两组实验来支撑一是特征消融二是滚动 IC 分析。特征消融实验的做法很简单在 XGBoost 下分别训练“纯量价特征”和“量价 情绪特征”两版模型对比同一时间段内的 AUC 和方向命中率。这两个数的差就是文本分析的边际贡献。如果差值为负说明情感词典或聚合方式有问题而不是模型的问题回到第 3 章去调词典阈值。滚动 IC 是一个更细粒度的验证技巧每天算一次情绪特征与未来 3 日收益的 Spearman 秩相关系数然后看这个 IC 序列的均值和稳定性。均值大于 0.03 并保持正值的星期占比超过 60%才说明情绪特征具备预测力。你可以在回测代码里直接输出这个值作为报告中“文本特征有效性验证”小节的主角。这样做的好处是它和具体模型无关可以直接比较不同情感词典版本的优劣。我做完这个方向后留下一个习惯所有中间产物都留原始备份无论新闻清洗前还是打分后的结果都存一份 CSV项目报告里被询问“这个分数是怎么算出来的”时能立刻拿出中间数据和截图回应。文本分析的每一步都是可解释的黑匣子你能解释到多少项目就立得住多少。希望这篇笔记和里面的思路能帮你在自己的设计与实现过程中少走几段弯路。本文还有配套的精品资源点击获取
返回列表