AI应用 量化交易NLP情感分析择时市场情绪

NLP情感分析与量化择时策略

NLP情感分析与量化择时策略

市场由人组成,人由情绪驱动。如果能量化市场情绪,你就能在拥挤的羊群中提前看到转折。

情绪驱动市场的证据

行为金融学的洞见

有效市场假说      行为金融学
价格反映所有信息  价格反映信息 + 情绪
     ↓                 ↓
  随机游走          可预测的偏差
  • 过度反应 → 恐慌性抛售 → 超卖机会
  • 反应不足 → 利好消息缓慢消化 → 趋势延续
  • 羊群效应 → 泡沫和崩盘的自增强循环

NLP 让我们有机会量化这些情绪。

NLP情绪分析技术栈

技术路线对比

方法复杂度准确率可解释性适用场景
词典法低60-70%高快速原型
传统ML中70-80%中中等数据量
FinBERT中80-88%中金融文本
GPT/Claude高85-92%低复杂语义
微调LLM高88-95%低领域定制

数据源分类

结构化数据源:
├── 财经新闻(Bloomberg, Reuters)
├── 公司公告和财报
├── 分析师研报
└── 央行政策声明

非结构化数据源:
├── 社交媒体(Twitter/X, Reddit, 雪球)
├── 论坛讨论(WallStreetBets, 股吧)
├── 搜索引擎趋势
└── 视频/播客转文字

实战一:基于 FinBERT 的情绪因子

FinBERT 快速上手

from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
import pandas as pd
import numpy as np

# 加载 FinBERT(在金融文本上微调的BERT)
model_name = ProsusAI/finbert
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)

def sentiment_analysis(texts, batch_size=32):
    批量情感分析
    sentiments = []

    for i in range(0, len(texts), batch_size):
        batch = texts[i:i+batch_size]
        inputs = tokenizer(
            batch, padding=True, truncation=True,
            max_length=512, return_tensors=pt
        )

        with torch.no_grad():
            outputs = model(**inputs)
            probs = torch.softmax(outputs.logits, dim=-1)

        # FinBERT 标签: 0=负面, 1=中性, 2=正面
        sentiments.append(probs.numpy())

    return np.concatenate(sentiments, axis=0)

# 示例:分析一组新闻标题
headlines = [
    Apple reports record quarterly earnings, beating analyst expectations,
    Market tumbles amid rising inflation fears and Fed uncertainty,
    Tesla faces regulatory investigation over autopilot safety concerns
]

probs = sentiment_analysis(headlines)
for h, p in zip(headlines, probs):
    sentiment = [负面, 中性, 正面][np.argmax(p)]
    print(f[{sentiment}] {h})
    print(f  负面:{p[0]:.1%} 中性:{p[1]:.1%} 正面:{p[2]:.1%}\n)

构建日频情绪因子

def build_daily_sentiment_factor(news_df, date_col=date,
                                  headline_col=headline):
    从新闻数据构建每日情绪因子

    # 对每天的所有新闻计算情绪
    daily_sentiments = []

    for date, group in news_df.groupby(date_col):
        texts = group[headline_col].tolist()
        probs = sentiment_analysis(texts)

        daily_sentiments.append({
            date: date,
            news_count: len(texts),
            sentiment_positive: probs[:, 2].mean(),  # 正面均值
            sentiment_negative: probs[:, 0].mean(),  # 负面均值
            sentiment_score: probs[:, 2].mean() - probs[:, 0].mean(),  # 净情绪
            sentiment_std: probs[:, 2].std(),  # 情绪分歧度
            max_positive: probs[:, 2].max(),
            max_negative: probs[:, 0].max(),
        })

    sentiment_df = pd.DataFrame(daily_sentiments)
    sentiment_df.set_index(date, inplace=True)

    # 衍生因子
    sentiment_df[sentiment_ma5] = sentiment_df[sentiment_score].rolling(5).mean()
    sentiment_df[sentiment_change] = sentiment_df[sentiment_score].diff()
    sentiment_df[sentiment_zscore] = (
        (sentiment_df[sentiment_score] - sentiment_df[sentiment_score].rolling(20).mean())
        / sentiment_df[sentiment_score].rolling(20).std()
    )

    return sentiment_df

实战二:用 GPT/Claude 做深度语义分析

Few-Shot 文本分类

import openai

SYSTEM_PROMPT = 你是一个专业的金融情绪分析系统。
分析给定的财经文本,输出JSON格式:

{
  sentiment: positive/negative/neutral,
  confidence: 0.0-1.0,
  market_impact: high/medium/low,
  key_factors: [因素1, 因素2],
  sentiment_score: -1.0到1.0之间的数值
}

评分标准:
- 1.0: 强烈利好(超预期盈利、重大政策利好)
- 0.5: 温和利好
- 0.0: 中性或信息混杂
- -0.5: 温和利空
- -1.0: 强烈利空(暴雷、监管处罚)


def deep_sentiment_analysis(text, model=gpt-4):
    用GPT进行深度情绪分析
    response = openai.ChatCompletion.create(
        model=model,
        messages=[
            {role: system, content: SYSTEM_PROMPT},
            {role: user, content: text}
        ],
        temperature=0.1,  # 低温度保证一致性
        response_format={type: json_object}
    )
    return json.loads(response.choices[0].message.content)

财报电话会议分析

财报电话会议的语气和措辞往往比财报数字本身更能预测未来:

def analyze_earnings_call(transcript):
    分析财报电话会议的文本

    prompt = f分析以下财报电话会议记录,重点关注:

1. 管理层语气变化(与过往对比)
2. 回避问题的次数和话题
3. 不确定性的语言指标(可能、取决于、具有挑战性等词频)
4. 对未来的具体承诺 vs 模糊表述

电话会议记录:
{transcript}

输出JSON:
{{
  tone_score: -1到1(-1=非常消极,1=非常积极),
  evasiveness_score: 0到1(越高=越回避问题),
  uncertainty_index: 不确定性词汇密度,
  concrete_vs_vague_ratio: 具体承诺/模糊表述的比值,
  red_flags: [红旗信号列表],
  summary: 两句话总结
}}

    return call_llm(prompt)

实战三:Reddit/社交媒体情绪

WallStreetBets 情绪数据

import requests
from datetime import datetime, timedelta

def fetch_reddit_sentiment(subreddit, ticker, days=7):
    获取Reddit上某股票的情绪

    # 使用Pushshift API获取帖子
    end_date = datetime.utcnow()
    start_date = end_date - timedelta(days=days)

    url = fhttps://api.pushshift.io/reddit/search/submission/
    params = {
        subreddit: subreddit,
        q: ticker,
        after: int(start_date.timestamp()),
        before: int(end_date.timestamp()),
        size: 500,
        sort: score
    }

    response = requests.get(url, params=params)
    posts = response.json()[data]

    # 提取文本和分数
    texts = [p.get(title, ) +   + p.get(selftext, ) for p in posts]
    scores = [p.get(score, 0) for p in posts]
    comments = [p.get(num_comments, 0) for p in posts]

    # NLP分析
    sentiments = sentiment_analysis(texts)

    return pd.DataFrame({
        text: texts,
        score: scores,
        comments: comments,
        sentiment_positive: sentiments[:, 2],
        sentiment_negative: sentiments[:, 0],
    })

散户情绪指标

def build_retail_sentiment_index(ticker):
    构建散户情绪综合指标

    data = fetch_reddit_sentiment(wallstreetbets, ticker)

    # 加权情绪(高分帖子权重大)
    weighted_sentiment = np.average(
        data[sentiment_positive] - data[sentiment_negative],
        weights=np.log1p(data[score]) * np.sqrt(data[comments] + 1)
    )

    # 情绪热度(讨论量)
    buzz_score = np.log1p(len(data))

    # 情绪一致性(分歧度)
    sentiment_dispersion = data[[sentiment_positive, sentiment_negative]].std().mean()

    return {
        sentiment: weighted_sentiment,
        buzz: buzz_score,
        dispersion: sentiment_dispersion,
        composite: weighted_sentiment * buzz_score / (1 + sentiment_dispersion)
    }

情绪因子与交易信号

择时策略框架

def generate_sentiment_signals(sentiment_df):
    从情绪因子生成交易信号

    signals = pd.DataFrame(index=sentiment_df.index)

    # 极端情绪反转信号
    # 当情绪过于乐观(>2σ),可能见顶
    # 当情绪过于悲观(<-2σ),可能见底
    signals[extreme_bearish] = sentiment_df[sentiment_zscore] < -2.0  # 买入机会
    signals[extreme_bullish] = sentiment_df[sentiment_zscore] > 2.0   # 卖出警告

    # 情绪趋势变化
    # 从悲观转为中性/乐观 = 买入信号
    signals[sentiment_turnaround] = (
        (sentiment_df[sentiment_zscore] > -0.5) &
        (sentiment_df[sentiment_zscore].shift(1) < -1.0)
    )

    # 情绪与价格背离(最强信号之一)
    # 价格下跌 + 情绪改善 = 潜在底部
    signals[bullish_divergence] = (
        (sentiment_df[sentiment_change] > 0) &
        (sentiment_df[price_change] < 0)
    )

    # 情绪一致性过滤
    # 情绪分歧过大时,信号可信度降低
    signals[high_confidence] = sentiment_df[sentiment_std] < 0.15

    # 最终信号
    signals[buy_signal] = (
        (signals[extreme_bearish] | signals[sentiment_turnaround]) &
        signals[high_confidence]
    )

    signals[sell_signal] = (
        signals[extreme_bullish] & signals[high_confidence]
    )

    return signals

实战建议

关键要点

  1. 多源融合 — 单一来源的情绪信号噪音太大,多源交叉验证
  2. 语境重要 — 利润下降在预期下降20%但实际降10%时是利好
  3. 时间衰减 — 新闻情绪的影响随时间快速衰减(通常1-5天)
  4. 极端值最有信息量 — 正常情绪波动预测力弱,极端情绪预测力强
  5. 与价格结合 — 情绪+量价分析 > 单独情绪 > 单独量价

常见陷阱

- 所有新闻都是中性偏正面 → 需要校准
- 机器难以理解讽刺和反讽 → Reddit情绪需要特殊处理
- 重大事件日新闻爆发 → 需要按事件去重而非按篇数
- 回译偏差 → 中文文本用中文模型(如FinBERT-Chinese)

市场情绪是可以量化的。NLP 给了我们一把读取市场心情的钥匙。但记住:当所有人都在用同一把钥匙时,锁就会换掉。不断寻找新的情绪数据源,是在这个领域保持优势的关键。