当前位置: 代码网 > it编程>前端脚本>Python > 如何用Python分析股票市场情绪?代码+数据+避坑指南全解析

如何用Python分析股票市场情绪?代码+数据+避坑指南全解析

2026年10月02日 • Python •我要评论
想用python做股市情感分析却不知从何下手?本文提供完整源代码和实战教程,教你从股吧评论采集、情感打分到情绪指数计算,并通过回测验证指标有效性,涵盖snownlp和bert两种方法,附避坑指南,助你

想用python做股市情感分析却不知从何下手?本文提供完整源代码和实战教程,教你从股吧评论采集、情感打分到情绪指数计算,并通过回测验证指标有效性,涵盖snownlp和bert两种方法,附避坑指南,助你快速构建可解释的投资者情绪量化模型,为投资决策提供参考。

1. 为什么股市情感分析值得用 python 自己做:先绕过黑匣子再谈决策

我最早接触投资者情绪分析,是在 2021 年白酒板块高位震荡那阵子。当时手上有一套商业舆情系统的试用权限,每天输出一个 0 到 100 的“情绪指数”,可没人能说清它到底读了哪些帖子、用的是什么词表、为什么某天突然从 70 跳到 30。后来我决定自己用 python 写一套情感分析源代码,把从数据采集到情绪分数的每一步都攥在自己手里。这条路走下来最大的体会是:股市情感分析真正难的不是模型选型,而是把“情绪”这个词拆成可计算、可回测、可解释的工程指标。

这套方案适合谁?如果你已经在用 python 做量化交易策略代码,想引入舆情因子但不想被商业接口的定价绑住;或者你是做python数据分析与可视化出身,手里有爬虫能力,想验证“股吧评论能不能预测次日涨跌”。这里不追求大而全的金融大模型,而是给出一条从数据源到情绪分数再到信号映射的最小可行路径。整条链路用到的都是常见库:requests、pandas、snownlp,再加一个可选的中文 bert 微调。你能看到每一行代码在算什么,也能在回测里亲眼看到情绪指标何时失效。

2. 数据源与采集策略:股吧、新闻与互动易的取舍

2.1 选数据源先看信噪比:不是所有文本都值得分析

做股市情感分析的第一步不是写爬虫,而是想清楚要分析谁的话。常见文本源有三类:股吧评论、财经新闻、上市公司互动平台回复。这三类文本的信噪比差异极大。股吧评论量最大,但内容充斥着“主力拉升”“明天涨停”这类无信息噪音,而且同一个 id 可能一天发几十条重复喊单。财经新闻质量高,但同一事件会被多家媒体转发,去重之后样本量骤降。互动平台回复属于官方口径,几乎不含负面情绪,对情绪分析的价值主要是事件追踪而不是情感度量。

我的选择是:主分析股吧帖子标题与正文,新闻作为辅助校准。原因很简单,股吧文本能真实反映散户情绪,而 a 股市场的短期波动恰恰受散户情绪影响明显。你需要接受一个事实:股吧数据里可能有 30% 以上的文本是无效噪音,但正是这些噪音构成了情绪本身。分析噪音而不是过滤噪音,这是情感分析与传统文本挖掘的本质区别。

采集时要注意的时间细节:增量抓取而不是全量抓取。东方财富股吧的帖子列表是按更新时间倒序排列的,但如果你每次都从第一页开始抓,会漏掉盘中的高频发帖高峰。我会用 since_id 或时间戳断点做增量采集,每 5 分钟轮询一次,把新帖子的标题、正文、发布时间、作者、阅读数、评论数落库。盘后补一次全量回补,应对盘中的翻页漏抓。

2.2 爬虫最小实现:requests + 解析 + 落库

下面是一段极简的东方财富股吧帖子采集代码,只抓标题和发布时间。注意,这里用公开的 json 接口,不需要登录。实际部署时建议加随机延时和代理池,我在第五节避坑里会细说,代码先保持最小可运行。

import requests
import pandas as pd
import time
from datetime import datetime

# 东方财富股吧帖子列表接口
# 参数说明: 
#   page=1 页码, pagesize=50 每页条数
#   field=258 表示按时间排序, 其余字段控制返回的列
def fetch_guba_posts(stock_code="600519", page=1):
    url = "https://gbapi.eastmoney.com/list"
    params = {
        "code": stock_code,
        "p": page,
        "ps": 50,
        "type": "2",  # 2=全部帖子
        "sid": "", 
        "field": "258",  # 按发布时间排序
        "dpt": "wz.astock"
    }
    headers = {
        "user-agent": "mozilla/5.0 (windows nt 10.0; win64; x64)",
        "referer": f"https://guba.eastmoney.com/list,{stock_code}.html"
    }
    resp = requests.get(url, params=params, headers=headers, timeout=10)
    data = resp.json()
    
    rows = []
    for item in data.get("result", {}).get("posts", []):
        rows.append({
            "post_id": item.get("postid"),
            "title": item.get("post_title"),
            "content": item.get("post_content"),
            "publish_time": datetime.fromtimestamp(item.get("post_publish_time", 0)),
            "read_count": item.get("post_read_count"),
            "comment_count": item.get("post_comments_count")
        })
    return rows

# 采集最近10页, 注意检查翻页停止条件
stock_code = "600519"
all_posts = []
for page in range(1, 11):
    posts = fetch_guba_posts(stock_code, page)
    if not posts:
        break
    all_posts.extend(posts)
    time.sleep(2)  # 控制请求频率, 别打太狠

df = pd.dataframe(all_posts)
df = df.drop_duplicates(subset="post_id")  # 去重, 翻页可能重复
df.to_csv(f"{stock_code}_posts.csv", index=false, encoding="utf-8-sig")
print(f"抓取完成, 共 {len(df)} 条帖子")

这段代码有几个关键点。 field=258 是按发布时间排序,实测这个参数能让翻页时尽量减少重复,但如果盘中发帖太快,第 1 页和第 2 页之间依然可能插入新帖导致遗漏,所以增量采集要记录最后一条帖子的时间戳,下次爬取时用它做过滤而不是只看页码。参数 type=2 是全部帖子的类型枚举,如果只想要精华帖可以改成 type=1 ,但我不建议过滤,因为情绪分析需要的是全量样本,精华帖是编辑筛选过的,会扭曲情绪分布。

落库格式用 csv 还是 sqlite 取决于你的数据量。单个股票一天大约生成 2000 到 5000 条帖子,一年也就百万级别,csv 完全够用。如果你同时监控 50 只股票,建议用 sqlite 或者直接落到 parquet,按日期分区,避免后续回测时反复读大文件。

2.3 数据清洗:去除重复、广告与无意义喊单

股吧数据的脏远超想象。最常见的三类干扰:同一条内容被多个账号转发、招聘广告和“加群荐股”引流帖、以及只包含股票代码加一个感叹词的喊单帖。清洗顺序很重要,先做精确去重,再做规则过滤,最后做长度过滤。

# 对已采集的df做清洗
df = df.drop_duplicates(subset=["title", "publish_time"])

# 过滤广告: 关键词命中即丢弃
ad_keywords = ["加微信", "qq群", "代客理财", "会员招募", "私聊", "点击链接"]
mask = df["title"].str.contains("|".join(ad_keywords), na=false)
df = df[~mask]

# 过滤超短文本: 少于10个汉字视为无效喊单
import re
def chinese_chars(text):
    if not isinstance(text, str):
        return 0
    return len(re.findall(r"[\u4e00-\u9fa5]", text))

df["char_len"] = df["title"].str.cat(df["content"], sep=" ").apply(chinese_chars)
df = df[df["char_len"] >= 10]

# 重置索引, 避免后续处理时索引断裂
df = df.reset_index(drop=true)

清洗之后样本量通常剩原来的 60% 到 70%。这个折损是正常的,不要为了保数量而放松过滤。尤其是“代客理财”类引流帖,它们的情绪往往是刻意制造的,要么极度乐观要么极度恐慌,留着会严重污染后续的情绪分布计算。

3. 把股吧评论变成情绪分数:词表打分与 bert 两条路线

3.1 情感分析基线:snownlp 与金融词典的碰撞

拿到清洗后的文本,第一个能跑通的情感分析方案是直接用现成的 python 库。snownlp 是中文情感分析里最常被拿来做基线的库,但它训练语料来自电商评论,对“跌停”“抄底”“割肉”这些金融词的判断并不准确。我实测过“今天割肉了,亏惨”这句话,snownlp 打出的情感分是 0.68,偏向正向,因为它把“割肉”理解成了餐饮场景。这就是为什么不能只依赖通用情感分析库做金融场景。

正确的做法是:用 snownlp 作为基线,同时在金融词典之上做修正。修正的方式有两种。第一种是词典覆盖法:维护一个金融领域的情感词表,把“跌停”“爆仓”“套牢”标记为强负面词,把“涨停”“利好”“突破”标记为强正面词,当一句话命中这些词时,直接覆盖 snownlp 的原始分数。第二种是后验概率法:用词典词作为特征,训练一个逻辑回归来修正 snownlp 输出。实际工程里第一种就够用,简单可解释,出问题也容易排查。

金融情感词典的来源,公开可用的有知网情感词典(hownet)、台湾大学中文情感词典(ntusd),以及一些论文里公开的金融情绪词表。你需要做的是把这些词典合并去重,再手工补充 a 股特色词汇,比如“天地板”“核按钮”“大面”这类。下面是我整理的一个最小可用词典结构和修正逻辑:

# 金融情感词典的最小实现
# 正负向词表: 每个词带一个强度权重, 范围0.5~2.0
positive_words = {
    "涨停": 2.0, "利好": 1.5, "突破": 1.2, "反包": 1.5,
    "主升": 1.8, "超预期": 1.2, "加仓": 0.8, "抄底": 1.0
}
negative_words = {
    "跌停": -2.0, "利空": -1.5, "爆仓": -2.0, "套牢": -1.5,
    "割肉": -1.2, "退市": -2.0, "st": -1.0, "减持": -1.2,
    "跳水": -1.8, "核按钮": -2.0
}

import re
import snownlp

def financial_sentiment(text):
    # 先跑snownlp基线, 再按词典做覆盖修正
    base_score = snownlp.snownlp(text).sentiments
    # 打分为0~1, 转成-1~1便于对比
    base_score = (base_score - 0.5) * 2
    
    words = re.findall(r"[\u4e00-\u9fa5]{2,4}", text)
    dict_score = 0.0
    hit_count = 0
    for word in words:
        if word in positive_words:
            dict_score += positive_words[word]
            hit_count += 1
        elif word in negative_words:
            dict_score += negative_words[word]
            hit_count += 1
    
    # 如果词典强度超过基线, 直接采用词典分数
    if abs(dict_score) > abs(base_score) and hit_count >= 2:
        # 归一化到[-1, 1]区间
        final_score = max(-1.0, min(1.0, dict_score / (hit_count * 1.5)))
    else:
        final_score = base_score
    return final_score

这个逻辑的关键是覆盖条件。当词典命中的词达到 2 个以上且词典强度超过基线时,才采用词典分数。为什么要有这个阈值?因为单靠一个词容易误判,比如“不看好”里包含“看好”,但语境是负面的。两个词同时命中时,语境置信度更高,覆盖更安全。参数 1.5 是经验值,用来压缩词频带来的分数膨胀,你可以根据自己的语料调。

3.2 微调一个金融 bert 分类器:数据标注与训练流程

词典打分法的天花板很快会出现:复杂句式如“虽然业绩暴雷但股价已经跌无可跌”会让词典覆盖法完全失效。这就是为什么更进一步的做法是微调一个中文 bert 模型做情感分类。这里不推荐从零训练模型,而是基于公开的中文预训练模型,比如哈工大的 macbert 或者 ernie,在金融文本上做二次微调。

微调需要标注数据。最省事的做法是:先用词典打分法跑一遍股吧历史数据,抽取分数绝对值最高的样本,人工校验一批作为种子训练集。比如取总分前 2000 条和后 2000 条,剔除明显标注错误的,得到大概 3000 条左右的有效标注样本。然后训练一个二分类模型,再用这个模型去预测全量数据,取置信度高的样本作为候选训练集,迭代扩充。

# 使用transformers库微调macbert的示例骨架
# 需要安装: pip install transformers torch datasets
import torch
from transformers import autotokenizer, automodelforsequenceclassification, trainer, trainingarguments
from datasets import dataset

# 标注数据格式: text + label, label 0=负面 1=正面
train_data = [
    {"text": "今天终于回本了,清仓走人", "label": 1},
    {"text": "跌成狗了,管理层在干嘛", "label": 0},
    # ... 更多标注样本, 建议至少2000条
]

# 加载macbert中文模型
# 该模型基于哈工大开源的中文macbert预训练权重
model_name = "hfl/chinese-macbert-base"
tokenizer = autotokenizer.from_pretrained(model_name)
model = automodelforsequenceclassification.from_pretrained(model_name, num_labels=2)

def tokenize_fn(examples):
    # max_length=128: 股吧帖子标题加正文绝大多数在100字以内
    return tokenizer(examples["text"], truncation=true, padding="max_length", max_length=128)

dataset = dataset.from_list(train_data).map(tokenize_fn, batched=true)

training_args = trainingarguments(
    output_dir="./finetuned_sentiment",
    learning_rate=2e-5,
    per_device_train_batch_size=16,
    num_train_epochs=3,
    weight_decay=0.01,
    save_strategy="epoch",
)

trainer = trainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
)
trainer.train()

训练脚本本身不复杂,复杂度在标注质量和训练迭代上。 learning_rate=2e-5 是预训练模型微调的标准起始值,调低了收敛慢,调高了容易灾难性遗忘。 num_train_epochs=3 在 3000 条样本下是合理的,超过 5 轮就会出现严重的过拟合,验证集准确率反而下降。训练结束后用保存的模型对全量股吧数据做推理,得到每条帖子的正负概率,取正类概率作为情绪分数。

3.3 两条路线怎么选:算力预算决定一切

如果你只有一台普通笔记本,没有独立 gpu,老老实实用词典修正法。bert 微调的 cpu 推理速度大约是每秒 2 到 5 条,一天 5000 条帖子要跑 20 到 40 分钟,这还只是单只股票的规模。如果你监控几十只股票,没有 gpu 基本跑不动。

如果你有条件租用云 gpu 或者在本地有一块 8gb 显存的显卡,bert 路线明显更值得投入。它对复杂句式的理解远好于词典法,尤其是在处理反讽、转折时,情绪分数平滑得多,回测信号的稳定性也更好。还有一条折中路线:用 bert 给全量历史数据打标签,然后用这些标签训练一个轻量的 textcnn 或 fasttext 模型用于实盘。好处是推理时不需要 gpu,速度快,精度损失也在可接受范围内。这就是我目前生产环境的做法:离线 bert 标注,在线轻量模型推理。

4. 从情绪分数到交易参考:聚合、差分与涨跌关联

4.1 时间窗口内的情绪聚合:为什么要做残差而不是平均值

单条帖子的情绪分数没有意义,需要聚合到时间维度才能和行情数据对齐。常见的聚合窗口是 5 分钟、30 分钟、日线。我这里直接讲日线聚合的做法,因为 a 股散户情绪最有效的频率就是日线级别:晚上复盘的情绪决定了第二天的开盘决策。

聚合方式有一个容易忽略的陷阱:直接对情绪分数求算术平均会丢失信息。涨跌停时的股吧情绪分布是极端双峰的,很多人发帖“终于涨停了太爽了”,同时另一拨人发帖“一字板买不进去气死了”,平均值可能落在中性附近,完全失去信号意义。我一般用两种指标:一是情感分数的加权均值,权重取帖子阅读数的对数,代表传播影响力;二是正面帖子占比与负面帖子占比的差值,也就是乐观残差。

import pandas as pd
import numpy as np

def aggregate_daily_sentiment(df):
    # df: 包含 post_time, sentiment_score, read_count 三列
    df = df.copy()
    df["date"] = pd.to_datetime(df["post_time"]).dt.date
    # 阅读数取对数压缩极大值
    df["weight"] = np.log1p(df["read_count"] + 1)
    
    # 按日期聚合: 加权均值
    daily = df.groupby("date").apply(
        lambda x: np.average(x["sentiment_score"], weights=x["weight"])
    ).reset_index(name="weighted_sentiment")
    
    # 正面/负面占比
    df["polarity"] = np.where(df["sentiment_score"] > 0.05, 1, 
                     np.where(df["sentiment_score"] < -0.05, -1, 0))
    polarity_ratio = df.groupby("date")["polarity"].value_counts(normalize=true).unstack()
    
    daily = daily.join(polarity_ratio, on="date")
    # bullish_bearish_spread: 正面占比减负面占比
    daily["spread"] = daily.get(1, 0) - daily.get(-1, 0)
    return daily

# 调用示例
daily_agg = aggregate_daily_sentiment(df)
print(daily_agg.tail())

权重用 np.log1p 是对数压缩的常见操作。原始阅读数可能从几十到几十万,直接做加权会让头部爆款帖子主导整体情绪。对数压缩后,10 万阅读的帖子权重是 11.5,100 阅读的帖子权重是 4.6,差距从 1000 倍缩小到 2.5 倍。如果你希望情绪更贴近“大众平均”而非“舆论热点”,可以完全不加权,直接平均。两种口径我都跑过回测,加权版本更适合捕捉反转信号,不加权版本更适合捕捉趋势。具体选哪个,取决于你用情绪指标做什么样的交易策略。

4.2 情绪与涨跌幅的时滞分析:t 日情绪对应 t+1 收益

拿到日度情绪序列后,要做的事是和行情序列对齐,计算两者的相关性和时滞关系。这个步骤决定了你的情绪指标到底能不能用、该怎么用。

# 合并行情数据: daily_agg 是上一步的结果
# price_df: 列包含 date, open, close, high, low
merged = daily_agg.merge(price_df, on="date", how="inner")
merged["next_ret"] = merged["close"].pct_change().shift(-1)  # t+1收益率
merged["ret"] = merged["close"].pct_change()                  # t日收益率

# 计算当前情绪与未来收益的相关性
from scipy.stats import pearsonr
corr_t1, p_value_t1 = pearsonr(merged["spread"], merged["next_ret"])
corr_t0, p_value_t0 = pearsonr(merged["spread"], merged["ret"])
print(f"当日情绪 vs 次日收益 相关系数: {corr_t1:.4f}, p值: {p_value_t1:.4f}")
print(f"当日情绪 vs 当日收益 相关系数: {corr_t0:.4f}, p值: {p_value_t0:.4f}")

这里重点看两个指标的对比。如果没有做任何处理, corr_t0 往往是显著正相关,因为当日涨跌本身会影响发帖情绪,这是同步关系,对预测没有价值。 corr_t1 才是关键:如果显著为正,说明当日情绪乐观,次日更可能上涨,情绪是领先指标;如果显著为负,说明情绪是反向指标,过热后容易回调。a 股散户社区的实际经验是:情绪与次日收益多为负相关或弱相关,因为“一致预期”过头后经常反转。

计算相关性时记得检查 p 值。样本量少于 100 个交易日的情况下,即使相关系数有 0.2,p 值也可能不显著。我建议至少积累 3 个月以上的数据再做这个分析,否则结论的置信度太低。

4.3 情绪极值作为事件信号:用分位数触发条件

相关性分析证明了情绪指标有效之后,下一步才是构建可执行的信号。最常见的用法是分位数触发:当情绪指标进入历史极值区域时,触发交易信号。比如正向情绪达到近 60 日最高分位时,如果此时股价已经连续上涨,那大概率是情绪过热,触发减仓信号;如果股价没怎么涨但情绪已经极度乐观,说明有资金在“偷偷吸筹”,触发加仓信号。

merged["spread_zscore"] = (merged["spread"] - merged["spread"].rolling(20).mean()) / merged["spread"].rolling(20).std()
merged["signal"] = 0
# 情绪过热且当日涨幅偏大: 看空信号
merged.loc[(merged["spread_zscore"] > 1.5) & (merged["ret"] > 0.03), "signal"] = -1
# 情绪极冷且跌幅有限: 看多信号
merged.loc[(merged["spread_zscore"] < -1.5) & (merged["ret"] > -0.02), "signal"] = 1

spread_zscore 用的是 20 日滚动窗口的标准化分数,而不是绝对阈值。这是因为不同股票、不同市场情绪基线差异很大:大盘蓝筹的股吧讨论天然比小盘股理性,绝对值阈值很难通用。滚动标准化的好处是自适应,窗口内情绪分布变化时阈值自动调整。1.5 和 -1.5 是经验值,对应大约 6% 到 7% 的分位边界。你可以拿历史数据做参数扫描,选不同阈值组合,看哪组的信号胜率最高。

5. 避坑指南:股市情感分析上线前的 5 个常见问题

5.1 爬虫数据断更导致情绪指标跳变

现象:某天情绪分数突然断崖式下跌或飙升,检查发现当天抓到的帖子数量只有平时的 20%。原因:目标网站改版,接口返回结构变化,或者触发了反爬验证,导致请求失败但代码没报错。解决:采集脚本里加一个数据量监控,对比当日抓取量与近 10 日均值,偏差超过 50% 就触发告警。处理方式是把情感分析结果和抓取量一起落库,做回测时发现异常值直接剔除当天的情绪数据,不要强行修正。

5.2 snownlp 对金融术语的误判率远超预期

现象:用词典修正之前的基线结果做回测,发现情绪指标和收益率的相关系数接近 0。排查发现大量“我今天割肉了”被识别为正面情绪。原因:snownlp 的底模基于电商评论,对金融术语没有语义先验。解决:不要只依赖词典覆盖,一定要把词典命中后的文本单独聚类检查,看是否有高频错判词。我自己就维护了一个“金融黑话词表”,比如“核按钮”“天地板”“过山车”这些词,全部手工标注后加入词典。

5.3 数据标注不一致导致 bert 微调效果不如词典法

现象:微调后的 bert 在验证集上准确率 90%,但实盘回测效果反而不如词典法。原因:标注者的主观一致性太差。同一句“这票我拿着心里没底”,有人标负面,有人标中性。模型学到的是标注者的个人倾向,不是情绪本身。解决:标注规范里明确“情绪”指对未来股价走势的预期方向,而不是对持仓盈亏的感受。标注完成后计算 cohen‘s kappa 一致性系数,低于 0.6 就重新培训标注规则。另外,把中性样本单独归为一类,不要用 0-1 二分类硬标注中性情绪。

5.4 情绪指标与行情同步,而不是领先

现象:回测时发现当日情绪和当日涨幅相关系数 0.5,你误以为可以据此开发策略,实盘却连续亏损。原因:这是同步相关性,不是预测能力。股吧帖子本来就是盘后复盘时发的,自然受当日行情影响。解决:所有情绪指标必须和 t+1 及更远的收益对齐做验证。相关性检验时,优先看情绪与次日收益的关系,如果两者显著,再考虑开发信号。如果只有当日相关性,这个指标只能用于解释行情,不能用于预测。

5.5 回测过拟合:参数扫出高胜率,实盘就翻车

现象:把 zscore 阈值从 1.0 扫到 2.5,找到 2.3 时胜率最高 68%,但实盘两个月就亏损。原因:滚动窗口和阈值的每个参数组合都相当于在同一个数据集上反复测试,样本量只有 200 多天,过拟合概率很大。解决:把数据分成三段,前 60% 做参数扫描,中间 20% 做验证,后 20% 做最终检验。验证段和检验段在参数扫描过程中不能碰。另外降低参数粒度,比如 zscore 只测 1.0/1.5/2.0 三档,窗口只测 10/20/40 天三档,参数空间越大过拟合风险越高。

6. 情绪分析到辅助决策的最后一公里:一个可复用的回测脚本

把前面所有环节串起来,最实用的一个动作是写一个端到端的回测函数。输入是股吧数据 csv 和行情 csv,输出是信号明细和一段统计摘要。这样你每次调整清洗规则、情感模型或聚合方式,都可以一键重跑对比,而不是手动拼流程。

下面是我常用的回测骨架,去掉了具体策略逻辑,保留核心框架:

import pandas as pd
import numpy as np
from scipy.stats import pearsonr

def backtest_sentiment_strategy(post_csv, price_csv, zscore_threshold=1.5):
    # 读数据
    df = pd.read_csv(post_csv, parse_dates=["post_time"])
    price = pd.read_csv(price_csv, parse_dates=["date"])
    
    # 1. 清洗
    # 2. 情感打分: 这里用简化版, 实际可替换成你的模型推理
    df["sentiment"] = df["title"].apply(financial_sentiment)
    
    # 3. 日度聚合
    daily = aggregate_daily_sentiment(df)
    
    # 4. 合并行情
    merged = daily.merge(price, on="date", how="inner")
    merged["ret"] = merged["close"].pct_change()
    merged["next_ret"] = merged["close"].pct_change().shift(-1)
    merged["zscore"] = (merged["spread"] - merged["spread"].rolling(20).mean()) / merged["spread"].rolling(20).std()
    
    # 5. 生成信号
    merged["signal"] = 0
    merged.loc[(merged["zscore"] < -zscore_threshold) & (merged["ret"] > -0.02), "signal"] = 1
    merged.loc[(merged["zscore"] > zscore_threshold) & (merged["ret"] > 0.03), "signal"] = -1
    
    # 6. 统计信号胜率: 次日收益与信号同向为胜
    valid = merged.dropna(subset=["signal", "next_ret"])
    win_long = (valid["signal"] == 1) & (valid["next_ret"] > 0)
    win_short = (valid["signal"] == -1) & (valid["next_ret"] < 0)
    hit_rate = (win_long.sum() + win_short.sum()) / len(valid)
    
    print(f"信号总数: {len(valid)}")
    print(f"胜率: {hit_rate:.2%}")
    print(f"次日平均收益: {valid['next_ret'].mean():.4%}")
    return merged

这个函数的关键参数有三个: zscore_threshold 控制信号触发边界, 20 是滚动窗口, -0.02 和 0.03 是行情过滤条件。实际使用时会发现,胜率高不一定赚钱,因为平均盈利和平均亏损的幅度不对等。我习惯在回测里额外输出“信号次日平均收益/全样本基准平均收益”的比值,这个值大于 1 才好继续优化。顺便提一句,实盘时不要把信号当唯一依据,我自己的经验是情绪信号做“过滤器”比做“触发源”更稳,先用技术指标圈定候选标的,再用情绪指标做二次确认。

最后说一个我反复踩过的习惯:每次调整情感分析策略后,我会把新旧版本的日度情绪序列做差分对比,如果差值分布异常大,先查是不是清洗规则引入的偏差,而不是急着跑回测。情绪数据是相对稳定的,突变多半说明程序出了问题。现在版本迭代要先看数据形态再看策略绩效,希望你也能养成这个顺序。整套方案跑下来,你收获的不只是一个情绪指标,而是一套能自我验证、能响应改版的决策参考系统。希望帮到你。

到此这篇关于如何用python分析股票市场情绪?代码+数据+避坑指南全解析的文章就介绍到这了,更多相关python分析股票市场情绪内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

赞 (0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com