SECTION 1: LEARNING OBJECTIVES

By the end of this lesson, you will be able to:

  • Understand the importance of unstructured data in financial decision-making – news, earnings calls, SEC filings, social media, and analyst reports.

  • Apply fundamental NLP techniques – tokenisation, stopword removal, stemming, lemmatisation, and TF-IDF vectorisation – to financial text.

  • Implement sentiment analysis on financial news headlines using pre-trained models (VADER, FinBERT).

  • Understand the concept of word embeddings (Word2Vec, GloVe, FastText) and their application to financial text.

  • Apply topic modelling (LDA) to discover latent themes in financial documents.

  • Use BERT (and domain-specific FinBERT) for advanced natural language understanding in finance.

  • Extract structured information from unstructured financial documents (Named Entity Recognition – NER).

  • Build a simple earnings call sentiment classifier using Python and Hugging Face transformers.

  • Understand the business applications of NLP in banking – news-based trading signals, credit risk assessment from filings, customer service automation, and compliance monitoring.


SECTION 2: WHY NLP IN FINANCE?

Financial markets generate vast amounts of unstructured text data every day:

  • News articles – hundreds of thousands per day.

  • Earnings call transcripts – thousands per quarter.

  • SEC filings (10-K, 10-Q) – tens of thousands per year.

  • Regulatory announcements – hundreds per month.

  • Social media (Twitter/X) – millions of financial-related posts daily.

  • Analyst reports – thousands from sell-side firms.

  • Central bank communications – speeches, minutes, policy statements.

The challenge: Traditional models focus on structured numerical data (prices, volumes, ratios). They miss the rich information contained in text. NLP bridges this gap.

Key applications in banking:

 
 
Application NLP Technique Business Value
News-based trading signals Sentiment analysis, event detection Generate alpha, improve timing of trades.
Credit risk assessment Text classification, NER Analyse borrower’s news, social media, and filings for early warning signals.
Regulatory compliance Text classification, NER Detect potential misconduct (e.g., insider trading, market manipulation).
Customer service Text classification, chatbots Automate responses to customer queries, improve satisfaction.
M&A target identification Topic modelling, NER Identify potential acquisition targets from news and filings.
Earnings prediction Sentiment analysis from call transcripts Predict earnings surprises, improve estimates.

SECTION 3: FUNDAMENTAL NLP TECHNIQUES

3.1 Text Preprocessing

Raw text contains noise that must be cleaned before analysis.

 
 
Technique Description Example
Tokenisation Split text into individual words (tokens). “The bank is profitable” → [“The”, “bank”, “is”, “profitable”]
Lowercasing Convert all text to lowercase. “Bank” → “bank”
Stopword Removal Remove common words (e.g., “the”, “is”, “and”). “The bank is profitable” → [“bank”, “profitable”]
Stemming Reduce words to their root form (rule-based). “profitable” → “profit”
Lemmatisation Reduce words to their dictionary form (context-aware). “running” → “run”, “better” → “good”
Punctuation/Number Removal Remove non-alphabetic characters. “profit!” → “profit”
3.2 Vectorisation – Representing Text as Numbers
 
 
Method Description Use Case
Bag of Words (BoW) Count occurrences of each word in a document. Simple, interpretable, but loses word order.
TF-IDF Term Frequency × Inverse Document Frequency – weights words by importance. Better than BoW; downweights common words.
Word Embeddings Dense vector representations of words (e.g., Word2Vec, GloVe). Captures semantic meaning; used as input for neural networks.
Sentence Embeddings Vector representation of entire sentences (e.g., BERT, Sentence-BERT). State-of-the-art for semantic similarity and classification.
3.3 TF-IDF – Mathematical Definition

TF-IDF(t,d,D)=TF(t,d)×IDF(t,D)

  • TF(t, d) = Number of times term t appears in document d ÷ Total terms in document d.

  • IDF(t, D) = log( Total documents ÷ Number of documents containing term t ).

Intuition: Words that appear frequently in a specific document but rarely across all documents are more informative.


SECTION 4: SENTIMENT ANALYSIS IN FINANCE

Sentiment analysis determines the emotional tone of text – positive, negative, or neutral.

4.1 Lexicon-Based Methods (VADER)

VADER (Valence Aware Dictionary and sEntiment Reasoner) is a rule-based sentiment analysis tool specifically attuned to social media text.

  • Uses a lexicon of words with pre-assigned sentiment scores.

  • Accounts for intensity modifiers (e.g., “very”, “extremely”).

  • Recognises common social media expressions (e.g., “LOL”, “💔”).

Example:
“The bank reported a strong quarter with record profits.” → VADER score: 0.85 (strong positive).

4.2 Domain-Specific Models (FinBERT)

FinBERT is a BERT model pre-trained on financial text (financial news, SEC filings, earnings call transcripts).

  • Why FinBERT? Financial language has unique vocabulary and context (e.g., “bullish”, “bearish”, “yield curve”, “quantitative easing”). General-purpose models (like regular BERT) may miss domain-specific nuances.

  • Performance: FinBERT outperforms general-purpose models on financial sentiment tasks.

4.3 Transformer-Based Sentiment Analysis

Transformers (BERT, RoBERTa, FinBERT) are the state-of-the-art for text classification tasks.

How it works:

  1. Tokenise the input text.

  2. Pass through the transformer model (multiple layers of self-attention).

  3. Take the [CLS] token (the sentence-level representation) or average token embeddings.

  4. Pass through a classification head.

  5. Output sentiment probabilities (positive/negative/neutral).


SECTION 5: IMPLEMENTATION IN PYTHON – NLP FOR FINANCE

We will implement:

  1. Text preprocessing and TF-IDF vectorisation.

  2. Sentiment analysis using VADER.

  3. Sentiment analysis using FinBERT (Hugging Face).

  4. Topic modelling (LDA) on financial news.

python
# ===================================================================
# MODULE 6, LESSON 1: NATURAL LANGUAGE PROCESSING FOR FINANCE
# ===================================================================

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import re
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
import warnings
warnings.filterwarnings('ignore')

# Download NLTK data (only need to run once)
# nltk.download('stopwords')
# nltk.download('wordnet')
# nltk.download('punkt')
# nltk.download('punkt_tab')
# nltk.download('omw-1.4')

# Set style
sns.set_style("whitegrid")
np.random.seed(42)

print("="*70)
print("NLP FOR FINANCE – SENTIMENT ANALYSIS AND TEXT MINING")
print("="*70)

# ----------------------------------------------------------------
# PART A: SAMPLE FINANCIAL TEXT DATA
# ----------------------------------------------------------------

# Sample financial news headlines and earnings call excerpts
financial_texts = [
    "The Federal Reserve raised interest rates by 25 basis points, signalling confidence in the economy.",
    "Bank of America reported record quarterly profits, beating analyst expectations by a wide margin.",
    "Inflation fears are driving a sell-off in technology stocks, with the NASDAQ down 3% today.",
    "Goldman Sachs announces layoffs of 3,000 employees amid a challenging market environment.",
    "JPMorgan Chase's CEO warns of an impending recession, citing global economic uncertainties.",
    "Tesla's stock surged 15% after announcing better-than-expected delivery numbers.",
    "The yield curve inverted today, a key signal that a recession may be on the horizon.",
    "Citigroup is investing $2 billion in climate tech startups, focusing on renewable energy solutions.",
    "The dollar strengthened against major currencies after the Fed's hawkish comments.",
    "JP Morgan upgrades Apple to 'overweight', citing strong iPhone demand and services growth.",
    "Bearish sentiment is spreading across Wall Street as earnings season disappoints.",
    "The ECB announced a pause on rate hikes, sending European markets higher.",
    "Wells Fargo settles money laundering charges for $3 billion, agreeing to implement new controls.",
    "Bitcoin falls below $30,000 as regulatory concerns weigh on the cryptocurrency market.",
    "BlackRock recommends investors increase exposure to bonds amid market volatility."
]

# Add some earnings call excerpts (longer texts)
earnings_excerpts = [
    """We are pleased to report another quarter of strong financial performance. Revenue grew 12% year-over-year, driven by robust demand across all business segments. Our investment banking division performed particularly well, with fees increasing 18% compared to the same quarter last year. Looking forward, we remain cautiously optimistic about the macroeconomic environment.""",
    
    """The current quarter was challenging as we faced headwinds from rising interest rates and inflationary pressures. Consumer spending has softened, and we have seen higher-than-expected defaults in our credit card portfolio. We are taking proactive measures to strengthen our balance sheet and reduce costs. We expect a recovery in the second half of the year."""
]

financial_texts.extend(earnings_excerpts)

# Create DataFrame
text_df = pd.DataFrame({
    'text': financial_texts,
    'doc_id': range(1, len(financial_texts)+1)
})

print("Sample Financial Text:")
print(text_df['text'].head(3).to_string(index=False))

# ----------------------------------------------------------------
# PART B: TEXT PREPROCESSING
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART B: Text Preprocessing")
print("-"*60)

# Initialise tools
stop_words = set(stopwords.words('english'))
lemmatizer = WordNetLemmatizer()

def preprocess_text(text, lemmatize=True, remove_stopwords=True):
    """
    Clean and preprocess text.
    """
    # Convert to lowercase
    text = text.lower()
    # Remove punctuation and numbers (keep only alphabetic and spaces)
    text = re.sub(r'[^a-zA-Z\s]', '', text)
    # Remove extra whitespace
    text = re.sub(r'\s+', ' ', text).strip()
    # Tokenise
    tokens = text.split()
    # Remove stopwords
    if remove_stopwords:
        tokens = [t for t in tokens if t not in stop_words]
    # Lemmatise
    if lemmatize:
        tokens = [lemmatizer.lemmatize(t) for t in tokens]
    # Rejoin
    return ' '.join(tokens)

# Apply preprocessing
text_df['text_clean'] = text_df['text'].apply(preprocess_text)

print("Original vs Cleaned (first document):")
print(f"Original: {text_df['text'].iloc[0]}")
print(f"Cleaned:  {text_df['text_clean'].iloc[0]}")

# ----------------------------------------------------------------
# PART C: TF-IDF VECTORISATION
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART C: TF-IDF Vectorisation")
print("-"*60)

# Create TF-IDF matrix
tfidf_vectorizer = TfidfVectorizer(max_features=50, stop_words='english')
tfidf_matrix = tfidf_vectorizer.fit_transform(text_df['text_clean'])

# Show top features
feature_names = tfidf_vectorizer.get_feature_names_out()
feature_importance = np.array(tfidf_matrix.sum(axis=0)).flatten()
feature_df = pd.DataFrame({
    'word': feature_names,
    'tfidf_sum': feature_importance
}).sort_values('tfidf_sum', ascending=False)

print("Top 10 TF-IDF Features (most important words across all documents):")
print(feature_df.head(10).to_string(index=False))

# ----------------------------------------------------------------
# PART D: VADER SENTIMENT ANALYSIS
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART D: VADER Sentiment Analysis")
print("-"*60)

# Initialise VADER
analyzer = SentimentIntensityAnalyzer()

def get_vader_sentiment(text):
    """Get VADER sentiment scores."""
    scores = analyzer.polarity_scores(text)
    return scores['compound'], scores['pos'], scores['neu'], scores['neg']

# Apply to all documents
text_df[['compound', 'pos_score', 'neu_score', 'neg_score']] = text_df['text'].apply(
    lambda x: pd.Series(get_vader_sentiment(x))
)

# Classify sentiment
def classify_sentiment(compound):
    if compound >= 0.05:
        return 'Positive'
    elif compound <= -0.05:
        return 'Negative'
    else:
        return 'Neutral'

text_df['vader_sentiment'] = text_df['compound'].apply(classify_sentiment)

print("VADER Sentiment Distribution:")
print(text_df['vader_sentiment'].value_counts())

print("\nSample Documents with VADER Scores:")
sample_docs = text_df[['text', 'compound', 'vader_sentiment']].head(10)
print(sample_docs.to_string(index=False))

# ----------------------------------------------------------------
# PART E: FINBERT SENTIMENT ANALYSIS (Hugging Face Transformers)
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART E: FinBERT Sentiment Analysis")
print("-"*60)

# Check if transformers is installed
try:
    from transformers import AutoTokenizer, AutoModelForSequenceClassification
    import torch
    
    # Load FinBERT model
    model_name = "ProsusAI/finbert"
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModelForSequenceClassification.from_pretrained(model_name)
    
    def get_finbert_sentiment(text):
        """Get FinBERT sentiment (positive, negative, neutral)."""
        inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=512, padding=True)
        outputs = model(**inputs)
        predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
        # Labels: 0 = positive, 1 = negative, 2 = neutral
        sentiment = torch.argmax(predictions, dim=1).item()
        probs = predictions.detach().numpy().flatten()
        return sentiment, probs
    
    # Test on sample texts
    test_texts = [
        "The company reported record profits and strong revenue growth.",
        "The bank faces significant challenges due to rising defaults and regulatory scrutiny.",
        "The market is experiencing volatility but remains resilient."
    ]
    
    print("FinBERT Sentiment Analysis (Sample):")
    for txt in test_texts:
        sentiment, probs = get_finbert_sentiment(txt)
        labels = ['Positive', 'Negative', 'Neutral']
        print(f"\nText: {txt}")
        print(f"Sentiment: {labels[sentiment]} (Pos: {probs[0]:.3f}, Neg: {probs[1]:.3f}, Neu: {probs[2]:.3f})")
    
    # Apply to all documents (limit for speed)
    print("\nApplying FinBERT to all documents (this may take a moment)...")
    finbert_results = []
    for txt in text_df['text']:
        sentiment, probs = get_finbert_sentiment(txt)
        finbert_results.append({
            'finbert_label': ['Positive', 'Negative', 'Neutral'][sentiment],
            'finbert_pos': probs[0],
            'finbert_neg': probs[1],
            'finbert_neu': probs[2]
        })
    
    finbert_df = pd.DataFrame(finbert_results)
    text_df = pd.concat([text_df, finbert_df], axis=1)
    
    print("\nFinBERT Sentiment Distribution:")
    print(text_df['finbert_label'].value_counts())
    
    # Compare VADER vs FinBERT
    agreement = (text_df['vader_sentiment'] == text_df['finbert_label']).mean()
    print(f"\nAgreement between VADER and FinBERT: {agreement:.1%}")
    
except ImportError:
    print("Transformers library not installed. Install with: pip install transformers torch")
    print("Skipping FinBERT demonstration.")
    # Create placeholder columns for compatibility
    text_df['finbert_label'] = text_df['vader_sentiment']
    text_df['finbert_pos'] = 0
    text_df['finbert_neg'] = 0
    text_df['finbert_neu'] = 0

# ----------------------------------------------------------------
# PART F: TOPIC MODELLING WITH LDA
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART F: Topic Modelling (Latent Dirichlet Allocation)")
print("-"*60)

# Use CountVectorizer for LDA (requires word counts)
count_vectorizer = CountVectorizer(max_features=100, stop_words='english')
count_matrix = count_vectorizer.fit_transform(text_df['text_clean'])
feature_names_lda = count_vectorizer.get_feature_names_out()

# Fit LDA model (2 topics)
n_topics = 3
lda = LatentDirichletAllocation(n_components=n_topics, random_state=42, max_iter=100)
lda.fit(count_matrix)

# Get topic-word distribution
def display_topics(model, feature_names, n_words=8):
    """Display top words for each topic."""
    for topic_idx, topic in enumerate(model.components_):
        top_words = [feature_names[i] for i in topic.argsort()[:-n_words-1:-1]]
        print(f"Topic {topic_idx+1}: {', '.join(top_words)}")

print(f"Topics (LDA, n={n_topics}):")
display_topics(lda, feature_names_lda, n_words=8)

# Assign topic to each document
doc_topic = lda.transform(count_matrix)
text_df['dominant_topic'] = np.argmax(doc_topic, axis=1) + 1

print("\nTopic Assignment per Document:")
topic_df = text_df[['doc_id', 'text_clean', 'dominant_topic']].head(10)
print(topic_df.to_string(index=False))

# ----------------------------------------------------------------
# PART G: NAMED ENTITY RECOGNITION (NER) – CONCEPTUAL
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART G: Named Entity Recognition (NER)")
print("-"*60)

print("""
NER extracts structured information from unstructured text:

Entities typically identified:
  - ORGANISATION: Bank of America, Goldman Sachs, Federal Reserve
  - PERSON: Jamie Dimon, Janet Yellen
  - LOCATION: New York, London, United States
  - DATE: Q4 2023, January 15th
  - MONEY: $3 billion, 25 basis points
  - PERCENT: 15%, 2.5%
  - PRODUCT: iPhone, Tesla Model 3

In finance, NER is used for:
  - Extracting company names from news articles for trading signals.
  - Identifying regulatory references in filings.
  - Building knowledge graphs of financial entities.

Example using spaCy:
  import spacy
  nlp = spacy.load('en_core_web_sm')
  doc = nlp("Goldman Sachs reported profits of $5 billion in Q4 2023.")
  for ent in doc.ents:
      print(ent.text, ent.label_)
  # Output: Goldman Sachs ORG, $5 billion MONEY, Q4 2023 DATE
""")

# ----------------------------------------------------------------
# PART H: BUSINESS APPLICATIONS AND RECOMMENDATIONS
# ----------------------------------------------------------------

print("\n" + "="*70)
print("PART H: Business Applications and Recommendations")
print("="*70)

print("""
1. News-Based Trading Signals:
   - Use sentiment analysis (FinBERT) on real-time news feeds.
   - Generate buy/sell signals based on sentiment changes.
   - Backtest with historical data to validate alpha generation.

2. Earnings Call Analysis:
   - Analyse sentiment and tone of management during earnings calls.
   - Correlate sentiment with stock price movements post-announcement.
   - Identify forward-looking statements (e.g., "guidance", "outlook").

3. Credit Risk Assessment:
   - Analyse borrower's news and social media for early warning signals.
   - Use topic modelling to identify risk factors (e.g., "layoffs", "lawsuits", "regulatory").

4. Regulatory Compliance:
   - Monitor communications for potential misconduct (e.g., insider trading).
   - Use NER to identify sensitive entities and relationships.

5. Customer Service Automation:
   - Implement chatbots with intent recognition and sentiment analysis.
   - Improve response times and customer satisfaction.

6. M&A Target Identification:
   - Use topic modelling and NER to scan news and filings for potential acquisition targets.
   - Identify companies in related industries with positive sentiment.

Recommendations:
  - For high-frequency trading, use lightweight models (VADER, TF-IDF).
  - For deep analysis (e.g., regulatory compliance), use FinBERT/Transformers.
  - Always combine NLP with traditional quantitative signals.
  - Validate NLP models on financial data specific to your domain.
""")

SECTION 6: ADVANCED NLP – TRANSFORMERS AND BERT

6.1 The Transformer Architecture

Transformers, introduced in the paper “Attention is All You Need” (Vaswani et al., 2017), revolutionised NLP.

Key components:

  • Self-attention: Computes the importance of each word relative to others in the sentence.

  • Multi-head attention: Multiple attention heads capture different relationships.

  • Positional encoding: Adds information about word order (since transformers are not sequential).

The attention mechanism:

Attention(Q,K,V)=softmax(QKTdk)V

where Q (query), K (key), and V (value) are projections of the input.

6.2 BERT (Bidirectional Encoder Representations from Transformers)

Key features:

  • Bidirectional: Learns context from both left and right (unlike previous models like GPT, which are unidirectional).

  • Pre-training tasks: Masked Language Model (MLM) and Next Sentence Prediction (NSP).

  • Fine-tuning: Adapt BERT to downstream tasks (sentiment, NER, question answering).

FinBERT: BERT pre-trained on financial text (annual reports, earnings calls, news).

Why FinBERT outperforms general BERT on financial tasks:

  • Domain-specific vocabulary (e.g., “yield curve”, “quantitative easing”).

  • Domain-specific language patterns (e.g., regulatory language).

  • Domain-specific sentiment nuances.


SECTION 7: SUMMARY FOR THE DATA PRACTITIONER

  • NLP unlocks the value of unstructured financial text – news, filings, earnings calls, social media.

  • Preprocessing (tokenisation, stopword removal, lemmatisation) is essential for clean text analysis.

  • TF-IDF is a simple but effective vectorisation method for document representation.

  • VADER is a fast, rule-based sentiment analyser suitable for social media and short texts.

  • FinBERT is the state-of-the-art for financial sentiment, trained on domain-specific text.

  • Topic modelling (LDA) discovers latent themes in large document collections.

  • Named Entity Recognition (NER) extracts structured information (companies, people, dates, amounts).

  • In practice, use a combination of methods: fast lexicons for real-time, deep models for accurate analysis.


SECTION 8: RECOMMENDED NEXT STEPS

  1. Apply sentiment analysis to a real dataset (e.g., Yahoo Finance headlines, SEC filings).

  2. Build a news-based trading signal using FinBERT sentiment.

  3. Use spaCy or Hugging Face for NER on financial documents.

  4. Explore fine-tuning FinBERT on a custom financial classification task.

  5. Learn about Large Language Models (LLMs) and their applications in finance.

  6. Prepare for the next lesson on Generative AI and Large Language Models for Finance.


[END OF LESSON 1 – MODULE 6]