金融情感分析
概述
金融情感分析 (Financial Sentiment Analysis) 旨在从文本数据中提取市场参与者的情绪 (Sentiment) 信号,并将其转化为可用于投资决策的量化因子。本文比较基于词典 (Lexicon-based) 和深度学习 (Deep Learning) 两大技术路线,重点介绍 FinBERT 模型,以及情感信号作为 Alpha 因子的构建方法。
情感评分 (Sentiment Scoring)
情感分析的核心任务是将文本映射为情感分数 \(s \in [-1, 1]\) 或离散标签 \(c \in \{\text{positive}, \text{neutral}, \text{negative}\}\)。
文档级情感
句子级情感
方面级情感 (Aspect-level)
对文本中提及的不同主题(如营收、利润、前景)分别打分。
金融情感 vs 通用情感
金融语境下的情感与日常语言存在显著差异。例如,"liability"在通用语境中偏负面,在金融语境中是中性会计术语;"volatile"在日常用语中偏负面,在金融分析中可能指高波动率机会。领域特定的处理至关重要。
基于词典的方法 (Lexicon-based Approaches)
Loughran-McDonald 金融情感词典
专为金融文本设计的情感词典,包含约 4,000 个金融情感词汇,分为六类:正面 (Positive)、负面 (Negative)、不确定 (Uncertainty)、诉讼 (Litigious)、强约束 (Modal Strong)、弱约束 (Modal Weak)。
情感分数计算:
from collections import Counter
def lm_sentiment(text, pos_words, neg_words):
"""Loughran-McDonald 词典情感评分"""
tokens = text.lower().split()
word_counts = Counter(tokens)
n_pos = sum(word_counts[w] for w in pos_words if w in word_counts)
n_neg = sum(word_counts[w] for w in neg_words if w in word_counts)
if n_pos + n_neg == 0:
return 0.0
return (n_pos - n_neg) / (n_pos + n_neg)
词典方法的局限
- 无法处理否定 (Negation):如 "not profitable"
- 忽略上下文 (Context):同一词在不同语境含义不同
- 缺乏语序信息:词袋模型 (Bag-of-Words) 的固有缺陷
- 无法识别新词和隐喻
深度学习方法
FinBERT
FinBERT 是基于 BERT 架构在金融文本上微调的预训练语言模型。其训练流程:
- 领域预训练 (Domain Pre-training):在大规模金融语料(如财报、新闻、分析报告)上继续 MLM 预训练
- 情感微调 (Sentiment Fine-tuning):在标注的金融情感数据集上微调分类头
其中 \(h_{[\text{CLS}]}\) 为 BERT 编码器输出的 [CLS] 标记表示。
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
# 加载 FinBERT
tokenizer = AutoTokenizer.from_pretrained("ProsusAI/finbert")
model = AutoModelForSequenceClassification.from_pretrained("ProsusAI/finbert")
def finbert_sentiment(texts, batch_size=32):
"""批量计算 FinBERT 情感分数"""
model.eval()
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)
# columns: [positive, negative, neutral]
scores = probs[:, 0] - probs[:, 1] # positive - negative
sentiments.extend(scores.tolist())
return sentiments
FinBERT vs 通用 BERT
实证研究表明,FinBERT 在金融情感分类任务上的 F1 分数比通用 BERT 高约 5-10 个百分点,尤其在处理金融专业术语和隐含情感时优势显著。
其他深度学习方案
| 模型 | 特点 | 适用场景 |
|---|---|---|
| FinBERT | 金融领域预训练 | 英文金融文本 |
| RoBERTa-Finance | 更大语料、更长训练 | 通用金融NLP |
| ChatGPT/GPT-4 | Zero-shot 能力强 | 灵活的情感标注 |
| 中文 FinBERT | 中文金融语料 | A股研报、公告 |
情感因子构建 (Sentiment as Alpha Factor)
新闻情感因子
将新闻情感汇聚为个股层面的日度因子:
其中 \(\mathcal{N}_{i,t}\) 为与股票 \(i\) 在日期 \(t\) 相关的新闻集合,\(w_n\) 为权重(可基于新闻重要性、时间衰减等)。
时间衰减加权
新闻情感的影响随时间指数衰减:
其中 \(\tau\) 为衰减半衰期 (Half-life),通常设为 1-5 个交易日。
import numpy as np
def build_sentiment_factor(news_df, decay_halflife=3):
"""构建带时间衰减的情感因子"""
decay_rate = np.log(2) / decay_halflife
factors = {}
for stock_id in news_df['stock_id'].unique():
stock_news = news_df[news_df['stock_id'] == stock_id]
for date in trading_dates:
relevant_news = stock_news[stock_news['date'] <= date]
if len(relevant_news) == 0:
factors[(stock_id, date)] = 0.0
continue
time_diff = (date - relevant_news['date']).dt.days
weights = np.exp(-decay_rate * time_diff)
sentiment = np.average(
relevant_news['sentiment_score'],
weights=weights
)
factors[(stock_id, date)] = sentiment
return factors
情感动量与反转
- 情感动量 (Sentiment Momentum):\(\Delta S_{i,t} = S_{i,t} - S_{i,t-k}\)
- 情感离散度 (Sentiment Dispersion):\(\text{Std}(S_{n})\),衡量市场分歧
情感因子的陷阱
情感因子的有效性高度依赖于:(1) 文本数据的覆盖率和及时性;(2) 情感模型的准确性;(3) 因子与市场的时滞关系。过于依赖公开新闻的情感因子可能已被市场充分定价 (Priced In)。
小结
金融情感分析已从简单的词频统计发展到基于预训练语言模型的深层语义理解。FinBERT 等领域专用模型显著提升了情感识别的准确性。作为 Alpha 因子,情感信号需要与传统量价因子互补使用,并通过严格的样本外验证确认其增量预测能力 (Incremental Predictive Power)。