Alternative Data and NLP in Quant Strategies › Key Parameters
Quantitative FinanceAll chapters Alternative Data and NLP in Quant Strategies › Key ParametersOne section · Sections in this chapter8 / 18 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Key parameters for dictionary-based sentiment analysis.
- lm_positive: Set of words indicating positive sentiment in the financial domain. Domain-specific dictionaries like Loughran-McDonald perform better than general sentiment lexicons because words like "liability" or "volatile" have different connotations in finance.
- lm_negative: Set of words indicating negative sentiment. Must be carefully curated to avoid false positives from neutral financial terminology.
- normalization_method: How to aggregate word counts into scores. Here we use net count divided by total words. Normalization by document length prevents longer documents from mechanically receiving higher absolute scores.
Machine Learning Sentiment Classification
Machine learning approaches learn sentiment patterns from labeled training data, capturing complex relationships that dictionary methods miss. These models recognize that beat estimates is positive even if neither word appears in a sentiment dictionary.
For financial sentiment, training data typically comes from:
- Analyst recommendation changes (upgrade = positive, downgrade = negative)
- Stock returns following news (positive return = positive sentiment)
- Human-labeled samples from specialized providers
Let's implement a complete sentiment classification pipeline using real financial news data:
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
# Simulated financial news dataset with labeled sentiment
# In practice, this would come from labeled datasets or proxy labels
financial_headlines = [
# Positive headlines
("Apple reports record quarterly revenue, beats analyst expectations", 1),
("Strong job growth signals economic expansion", 1),
("Company announces dividend increase and share buyback program", 1),
("Merger creates synergies expected to boost earnings", 1),
("New product launch exceeds sales targets", 1),
("Upgraded to buy rating on improved outlook", 1),
("Revenue growth accelerates amid strong demand", 1),
("Profit margins expand on cost efficiencies", 1),
("Company wins major contract worth billions", 1),
("Earnings beat estimates for fifth consecutive quarter", 1),
# Negative headlines
("Company misses earnings estimates, shares plunge", -1),
("Regulators launch investigation into accounting practices", -1),
("CEO resigns amid declining performance", -1),
("Quarterly loss widens on falling demand", -1),
("Company announces layoffs as sales decline", -1),
("Downgraded to sell on deteriorating fundamentals", -1),
("Supply chain disruptions hurt profit margins", -1),
("Company faces lawsuit over product defects", -1),
("Revenue forecast cut due to weak demand", -1),
("Credit rating downgraded on rising debt levels", -1),
# Neutral headlines
("Company reports quarterly results in line with expectations", 0),
("Management provides guidance for next fiscal year", 0),
("Annual shareholder meeting scheduled for May", 0),
("Company announces leadership transition", 0),
("Quarterly revenue unchanged from prior year", 0),
("Analyst maintains hold rating on stock", 0),
]
# Expand dataset by creating variations
np.random.seed(42)
texts = [h[0] for h in financial_headlines]
labels = [h[1] for h in financial_headlines]
# Convert to TF-IDF features
tfidf = TfidfVectorizer(max_features=100, ngram_range=(1, 2))
X = tfidf.fit_transform(texts)
y = np.array(labels)
# Split data
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# Train logistic regression classifier
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_train, y_train)
# Evaluate
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)Sentiment Classification Results:
==================================================
Training samples: 18
Test samples: 8
Test Accuracy: 25.00%
Classification Report:
precision recall f1-score support
Negative 0.33 0.33 0.33 3
Neutral 0.00 0.00 0.00 2
Positive 0.20 0.33 0.25 3
accuracy 0.25 8
macro avg 0.18 0.22 0.19 8
weighted avg 0.20 0.25 0.22 8
The model learns to associate certain words and phrases with sentiment categories. Let's examine what features the model considers most predictive:
import numpy as np
# Get feature importances from logistic regression coefficients
feature_names = tfidf.get_feature_names_out()
coefficients = model.coef_
# For multiclass, we have coefficients for each class
# Class order: -1 (negative), 0 (neutral), 1 (positive)
# Extract top features for each class
top_features_by_class = {}
for class_idx, class_name in enumerate(["Negative", "Neutral", "Positive"]):
coef = coefficients[class_idx]
top_indices = np.argsort(coef)[-5:][::-1]
top_features_by_class[class_name] = [
(feature_names[idx], coef[idx]) for idx in top_indices if coef[idx] > 0
]Most Predictive Features by Sentiment Class: -------------------------------------------------- Negative indicators: downgraded (+0.388) performance (+0.349) practices (+0.328) launch (+0.291) plunge (+0.230) Neutral indicators: for (+0.330) meeting (+0.305) may (+0.305) meeting scheduled (+0.305) maintains (+0.265) Positive indicators: quarter (+0.278) program (+0.275) earnings (+0.243) strong demand (+0.227) company announces (+0.222)
The learned coefficients quantify each feature's contribution to sentiment prediction. Positive coefficients indicate words that increase the probability of that sentiment class, while negative coefficients decrease it. The magnitude reflects predictive strength. The learned coefficients reveal intuitive patterns: words like "beat," "strong," and "growth" have positive coefficients, while "miss," "decline," and "downgrade" have negative coefficients. The model captures both individual words and bigrams (word pairs), enabling it to distinguish "earnings beat" from "earnings miss" despite both containing "earnings." Phrase-level features like "layoffs" and "record quarterly" carry additional sentiment that individual words alone cannot capture.
Key parameters for machine learning sentiment classification.
- max_features: Maximum number of TF-IDF features to extract here 100. Higher values capture more vocabulary but increase dimensionality and risk of overfitting.
- ngram_range: Range of n-gram sizes to consider, here 1 to 2 meaning individual words and word pairs. Including bigrams helps capture phrases like "beat estimates" that have different meaning than constituent words.
- test_size: Proportion of data held out for testing, here 0.3 or 30%. Essential for evaluating generalization performance.
- max_iter: Maximum iterations for logistic regression solver (here 1000). Must be sufficient for convergence.
- random_state: Random seed for reproducibility of train/test splits and model initialization.
Deep Learning Approaches
Modern NLP has been transformed by deep learning, particularly transformer architectures. Pre-trained models like FinBERT, a BERT variant fine-tuned on financial text, achieve state-of-the-art performance on financial sentiment tasks by understanding context, handling negation, and generalizing across varied expressions of similar concepts.
# Note: In production, you would use actual FinBERT or similar models
# This demonstrates the interface pattern
# Example using transformers library (conceptual - requires model download)
# from transformers import AutoTokenizer, AutoModelForSequenceClassification
# import torch
# For demonstration, we'll create a mock FinBERT-like interface
class MockFinBERT:
"""Simulates FinBERT sentiment analysis for demonstration."""
def __init__(self):
# Keywords that influence sentiment
self.positive_signals = [
"beat",
"exceed",
"strong",
"growth",
"profit",
"upgrade",
"outperform",
"record",
"surge",
]
self.negative_signals = [
"miss",
"decline",
"weak",
"loss",
"downgrade",
"disappoint",
"fall",
"cut",
"concern",
"risk",
]
self.negation_words = [
"not",
"no",
"never",
"neither",
"without",
"lack",
]
def analyze(self, text):
"""Return sentiment probabilities for text."""
words = text.lower().split()
# Check for negation
has_negation = any(w in self.negation_words for w in words)
pos_count = sum(
1 for w in words if any(p in w for p in self.positive_signals)
)
neg_count = sum(
1 for w in words if any(n in w for n in self.negative_signals)
)
# Simulate probability distribution
if pos_count > neg_count:
base_probs = [0.1, 0.2, 0.7]
elif neg_count > pos_count:
base_probs = [0.7, 0.2, 0.1]
else:
base_probs = [0.25, 0.5, 0.25]
# Negation flips positive and negative
if has_negation:
base_probs = [base_probs[2], base_probs[1], base_probs[0]]
return {
"negative": base_probs[0],
"neutral": base_probs[1],
"positive": base_probs[2],
}
finbert = MockFinBERT()
# Test cases that highlight deep learning advantages
test_cases = [
"Earnings exceeded expectations significantly",
"Earnings did not meet expectations", # Negation handling
"The company reported a surprising lack of growth", # Complex negation
"Despite challenges, revenue remained strong", # Contrast handling
]Sentiment Analysis with Context-Aware Model: ============================================================ Text: "Earnings exceeded expectations significantly" Sentiment: POSITIVE (confidence: 70.0%) Probabilities: neg=0.10, neu=0.20, pos=0.70 Text: "Earnings did not meet expectations" Sentiment: NEUTRAL (confidence: 50.0%) Probabilities: neg=0.25, neu=0.50, pos=0.25 Text: "The company reported a surprising lack of growth" Sentiment: NEGATIVE (confidence: 70.0%) Probabilities: neg=0.70, neu=0.20, pos=0.10 Text: "Despite challenges, revenue remained strong" Sentiment: POSITIVE (confidence: 70.0%) Probabilities: neg=0.10, neu=0.20, pos=0.70
The context-aware model demonstrates superior handling of linguistic nuance compared to dictionary methods. For Earnings did not meet expectations, the model correctly identifies negative sentiment by understanding that not meet negates the neutral phrase meet expectations. For Despite challenges, revenue remained strong, the model recognizes the contrastive structure and correctly emphasizes the positive outcome rather than the negative setup. These examples show how transformer-based architectures capture semantic relationships that simpler bag-of-words approaches miss.
Deep learning models excel at handling linguistic complexity, including negation, sarcasm, hedging language, and implicit sentiment. The phrase did not meet expectations is correctly classified as negative despite containing neither obvious negative words nor being a simple negation of a positive phrase. This contextual understanding represents a substantial advance over dictionary methods.