# 🏦 Macroeconomic Sentiment Analysis Pipeline A multi-head NLP system for scoring macroeconomic text — news articles, central bank policy statements, regulatory documents, tweets, and climate/ESG reports. ## Architecture The pipeline combines **four dictionary-based signal layers** with a **three-head transformer ensemble** through intelligent domain routing: ``` Input Text │ ├─► [Keyword Topic Router] ──► Policy/Macro ──► Financial-RoBERTa-Large │ ──► Climate/ESG ──► ClimateBERT │ ──► News/Corp ──► FinBERT │ ──► Social/Tweet ──► FinBERT │ ├─► [Loughran-McDonald (2011)] ──► polarity, subjectivity ├─► [Henry (2008)] ────────────► earnings tone ├─► [Sautner et al. (2023)] ───► climate risk/opportunity density ├─► [Macro Policy Dict] ───────► hawkish/dovish, crisis, uncertainty │ └─► [Dynamic Combiner] ──► Weighted fusion with crisis-adaptive weights ──► Final macro sentiment score [-1, +1] ``` ### Key Design Decisions 1. **Crisis-adaptive weighting**: When `crisis_signal` is high, dictionary weight increases (transformers may not generalize to unseen crisis patterns). 2. **Policy stance from dictionaries**: The hawk/dove dictionary gets 80% weight in policy_stance scoring, since the general sentiment models don't distinguish monetary policy direction. 3. **Topic routing**: Deterministic keyword-based router (no model required) — tested and working reliably across domains. ## Quick Start ```python from macro_sentiment import MacroSentimentPipeline pipe = MacroSentimentPipeline(device="cpu") # Score any macro text result = pipe("The Federal Reserve raised rates by 75bps citing persistent inflation.") print(result.summary()) # Sentiment: Positive (+0.472) | Policy: Very Hawkish (+0.999) | Crisis: Normal | Domain: policy # Access individual scores print(result.macro_sentiment) # [-1, +1] composite score print(result.policy_stance) # [-1 dovish, +1 hawkish] print(result.crisis_signal) # [0, 1] crisis intensity print(result.climate_exposure) # [0, 1] climate topic density print(result.uncertainty) # [0, 1] uncertainty level ``` ### Dictionary-Only Mode (no GPU required, instant) ```python pipe = MacroSentimentPipeline(load_transformers=False) result = pipe("Markets crashed amid recession fears.", mode="dict_only") ``` ### Full Ensemble Mode (all heads) ```python result = pipe.score("ECB signals patience on rate cuts.", mode="all") ``` ## Output Fields | Field | Range | Description | |-------|-------|-------------| | `macro_sentiment` | [-1, +1] | Composite sentiment score (weighted transformer + dictionary) | | `policy_stance` | [-1, +1] | Dovish (-1) to Hawkish (+1) — 80% dictionary, 20% transformer | | `financial_sentiment` | [-1, +1] | Financial news sentiment from active transformer head | | `climate_sentiment` | [-1, +1] | Climate risk (-1) to opportunity (+1) | | `crisis_signal` | [0, 1] | Crisis intensity (drives dynamic weight adjustment) | | `uncertainty` | [0, 1] | Macroeconomic uncertainty level | | `confidence` | [0, 1] | Model confidence (agreement + topic confidence + uncertainty) | | `detected_domain` | str | `policy` / `climate` / `financial_news` / `social` | | `topic` | str | Human-readable topic label | | `head_used` | str | Which transformer head was activated | | `lm_polarity` | [-1, +1] | Loughran-McDonald polarity | | `henry_polarity` | [-1, +1] | Henry (2008) earnings tone | | `climate_exposure` | [0, 1] | Sautner-style climate term density | | `raw_features` | dict | All 24+ dictionary + transformer features | ## Evaluation Results Evaluated on a combined test set of **4,333 samples** from 5 financial NLP datasets: | Method | Accuracy | F1 Macro | F1 Weighted | N Samples | |--------|----------|----------|-------------|-----------| | Dictionary Composite (LM+Henry) | 0.568 | 0.528 | 0.578 | 4,333 | | **Meta-Classifier (Dict features)** | **0.669** | **0.578** | **0.650** | 4,333 | | **Full Pipeline (TF + Dict)** | **0.620** | **0.601** | **0.635** | 100 | ### Per-Class Performance (Meta-Classifier) ``` precision recall f1-score support negative 0.64 0.47 0.54 711 neutral 0.70 0.85 0.77 2613 positive 0.55 0.35 0.42 1009 macro avg 0.63 0.56 0.58 4333 ``` ### Top Dictionary Features (by GradientBoosting importance) ``` henry_subjectivity: 0.2328 henry_negative_ratio: 0.2100 henry_polarity: 0.1184 lm_word_count: 0.0952 henry_positive_ratio: 0.0652 lm_negative_ratio: 0.0615 lm_subjectivity: 0.0519 lm_positive_ratio: 0.0433 lm_polarity: 0.0379 climate_opp_density: 0.0180 ``` ### Qualitative Evaluation (8 Macro Scenarios) | Scenario | Sentiment | Policy Stance | Crisis | Domain | |----------|-----------|---------------|--------|--------| | Fed Hawkish 75bps | +0.47 Positive ✓ | +1.00 Very Hawkish ✓ | Normal ✓ | policy ✓ | | Lehman Bankruptcy | -0.61 Very Negative ✓ | Neutral | HIGH CRISIS ✓ | financial ✓ | | Renewable Energy | +0.03 Neutral | Neutral | Normal ✓ | financial | Climate: Opportunity ✓ | | ECB Dovish Patience | +0.65 Very Positive ✓ | -0.60 Very Dovish ✓ | Normal ✓ | policy ✓ | | COVID Market Crash | -0.62 Very Negative ✓ | -1.00 Very Dovish ✓ | HIGH CRISIS ✓ | policy ✓ | | Tesla Surge | +0.75 Very Positive ✓ | Neutral ✓ | Normal ✓ | financial ✓ | | Bullish Tweet $AAPL | +0.73 Very Positive ✓ | Neutral ✓ | Normal ✓ | financial ✓ | | Mixed Signals/Uncertainty | -0.32 Negative | Neutral | Normal | financial | ## Dictionaries | Dictionary | Paper | Captures | Features | |-----------|-------|----------|----------| | **Loughran-McDonald** | [LM (2011)](https://doi.org/10.1111/j.1540-6261.2010.01625.x) | Financial negative/positive/uncertainty/litigious | 5 features | | **Henry** | [Henry (2008)](https://doi.org/10.1177/0021943608319388) | Earnings press release tone (116 pos + 144 neg words) | 4 features | | **Climate Exposure** | [Sautner et al. (2023)](https://doi.org/10.1093/rfs/hhad097) style | Climate risk/opportunity/regulatory term density | 8 features | | **Macro Policy** | Custom | Hawkish/dovish stance, crisis intensity, uncertainty | 7 features | Total: **24 dictionary features** per text input. ## Transformer Models | Head | Model | Domain | Parameters | |------|-------|--------|------------| | **FinBERT** | [`ProsusAI/finbert`](https://huggingface.co/ProsusAI/finbert) | Financial news | 110M | | **Financial-RoBERTa** | [`soleimanian/financial-roberta-large-sentiment`](https://huggingface.co/soleimanian/financial-roberta-large-sentiment) | Policy/formal text | 355M | | **ClimateBERT** | [`climatebert/distilroberta-base-climate-sentiment`](https://huggingface.co/climatebert/distilroberta-base-climate-sentiment) | Climate/ESG | 82M | ## Training Data ~20K train / ~4.3K test samples from 5 financial NLP datasets: | Dataset | HF ID | Train | Test | Domain | |---------|-------|-------|------|--------| | Financial PhraseBank | [`nickmuchi/financial-classification`](https://huggingface.co/datasets/nickmuchi/financial-classification) | 4,551 | 506 | News headlines | | Twitter Financial News | [`zeroshot/twitter-financial-news-sentiment`](https://huggingface.co/datasets/zeroshot/twitter-financial-news-sentiment) | 9,543 | 2,388 | Social/tweets | | Auditor Sentiment | [`FinanceInc/auditor_sentiment`](https://huggingface.co/datasets/FinanceInc/auditor_sentiment) | 3,877 | 969 | Audit reports | | FiQA | [`pauri32/fiqa-2018`](https://huggingface.co/datasets/pauri32/fiqa-2018) | 1,063 | 150 | Financial Q&A | | Climate Sentiment | [`climatebert/climate_sentiment`](https://huggingface.co/datasets/climatebert/climate_sentiment) | 1,000 | 320 | Climate/ESG | **Label distribution (train):** 15.9% negative, 58.4% neutral, 25.7% positive ## File Structure ``` macro_sentiment/ ├── __init__.py # Package exports ├── dictionaries.py # LM, Henry, Climate, Macro dictionary scorers ├── transformers_ensemble.py # FinBERT, RoBERTa, ClimateBERT heads + router ├── pipeline.py # Unified MacroSentimentPipeline ├── data_prep.py # Dataset loading and combination └── train_meta.py # Meta-classifier training script eval_results.json # Evaluation metrics requirements.txt # Dependencies ``` ## Fine-Tuning Guide To fine-tune FinBERT on the combined dataset: ```python # Use the included data_prep module from macro_sentiment.data_prep import load_combined_dataset ds = load_combined_dataset() # Returns DatasetDict with train/test splits # Fine-tune with standard HuggingFace Trainer from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments model = AutoModelForSequenceClassification.from_pretrained("ProsusAI/finbert", num_labels=3) tokenizer = AutoTokenizer.from_pretrained("ProsusAI/finbert") # Then use the fine-tuned model in the pipeline: pipe = MacroSentimentPipeline(device="cpu") # The FinBERTHead accepts a custom model_name parameter ``` ## Literature - Loughran & McDonald (2011). "When Is a Liability Not a Liability?" *Journal of Finance* - Henry (2008). "Are Investors Influenced by How Earnings Press Releases Are Written?" *Journal of Business Communication* - Sautner et al. (2023). "Firm-Level Climate Change Exposure." *Review of Financial Studies* - Araci (2019). [FinBERT: Financial Sentiment Analysis with Pre-Trained Language Models](https://arxiv.org/abs/1908.10063) - Huang et al. (2023). [ClimateBERT](https://arxiv.org/abs/2110.12010) ## License Apache 2.0