File size: 8,671 Bytes
4068b1c
 
 
 
 
 
 
 
 
 
 
 
522f497
4068b1c
 
 
 
 
 
 
 
 
 
 
522f497
 
 
 
 
 
 
4068b1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f385a4c
33afa05
522f497
33afa05
522f497
eee5a39
f385a4c
eee5a39
f385a4c
33afa05
f385a4c
 
33afa05
522f497
f385a4c
 
 
 
 
 
33afa05
 
f385a4c
 
 
 
 
522f497
 
 
 
 
 
f385a4c
522f497
 
 
f385a4c
 
522f497
eee5a39
522f497
f385a4c
522f497
 
 
 
 
f385a4c
522f497
 
 
f385a4c
522f497
 
 
 
eee5a39
522f497
eee5a39
522f497
 
33afa05
522f497
 
 
eee5a39
33afa05
522f497
eee5a39
522f497
33afa05
522f497
33afa05
522f497
 
 
 
 
 
 
 
33afa05
 
522f497
eee5a39
522f497
 
 
 
 
eee5a39
522f497
eee5a39
522f497
 
 
 
 
 
 
 
 
 
 
 
eee5a39
522f497
eee5a39
522f497
f385a4c
522f497
eee5a39
522f497
 
 
 
f385a4c
eee5a39
522f497
 
eee5a39
522f497
eee5a39
522f497
 
 
f385a4c
522f497
 
 
 
 
 
f385a4c
 
33afa05
522f497
eee5a39
522f497
eee5a39
522f497
 
 
 
 
eee5a39
522f497
33afa05
522f497
 
 
 
 
33afa05
522f497
33afa05
f385a4c
33afa05
f385a4c
33afa05
f385a4c
 
522f497
 
 
 
 
 
 
 
 
 
33afa05
 
 
 
 
522f497
f385a4c
522f497
f385a4c
522f497
f385a4c
 
522f497
 
33afa05
 
f385a4c
33afa05
522f497
 
 
 
 
33afa05
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
---
library_name: transformers
license: apache-2.0
tags:
- text-classification
- sentiment-analysis
- finance
- finbert
- roberta
- climate
- esg
- macroeconomics
- multilingual
datasets:
- nickmuchi/financial-classification
- zeroshot/twitter-financial-news-sentiment
- FinanceInc/auditor_sentiment
- pauri32/fiqa-2018
- climatebert/climate_sentiment
metrics:
- accuracy
- f1
language:
- en
- ar
- de
- es
- fr
- hi
- it
- pt
pipeline_tag: text-classification
model-index:
- name: macro-sentiment-finbert
  results:
  - task:
      type: text-classification
      name: Financial Sentiment Analysis
    metrics:
    - name: Accuracy (RoBERTa-Large)
      type: accuracy
      value: 0.913
    - name: F1 Macro (RoBERTa-Large)
      type: f1
      value: 0.902
    - name: Accuracy (FinBERT)
      type: accuracy
      value: 0.897
    - name: F1 Macro (FinBERT)
      type: f1
      value: 0.881
---

# 🏦 Macroeconomic Sentiment Analysis

**What it does:** Takes any financial or economic text and tells you whether the sentiment is positive, negative, or neutral β€” plus whether the tone is hawkish/dovish, whether it signals a crisis, and how much it relates to climate/ESG. Now supports **multilingual text** (v0.3.0).

**Who it's for:** Anyone analyzing news articles, central bank statements, earnings reports, financial tweets, or climate/ESG disclosures β€” in English or other languages.

---

## The Simple Way (3 lines of Python)

```python
from transformers import pipeline

classifier = pipeline("text-classification", model="peyterho/finbert-macro-sentiment")

classifier("Tesla shares surged 15% after beating earnings expectations.")
# β†’ [{'label': 'positive', 'score': 0.998}]

classifier("Markets crashed amid recession fears and massive layoffs.")
# β†’ [{'label': 'negative', 'score': 0.997}]
```

### Which model should I pick?

| Model | Size | Speed | Accuracy | Best for |
|-------|------|-------|----------|----------|
| [`peyterho/finbert-macro-sentiment`](https://huggingface.co/peyterho/finbert-macro-sentiment) | 110M | Fast | 89.7% | General financial text β€” good default choice |
| [`peyterho/financial-roberta-large-macro-sentiment`](https://huggingface.co/peyterho/financial-roberta-large-macro-sentiment) | 355M | Slower | **91.3%** | When accuracy matters most |
| [`peyterho/climatebert-macro-sentiment`](https://huggingface.co/peyterho/climatebert-macro-sentiment) | 82M | Fastest | 88.9% | Climate/ESG text |

---

## Multilingual Support (v0.3.0)

The pipeline now auto-detects non-English text and routes it to an XLM-RoBERTa multilingual sentiment model.

**Supported languages:** English, Arabic, French, German, Hindi, Italian, Portuguese, Spanish β€” plus reasonable performance on many others.

```python
from macro_sentiment import TransformerEnsemble

ensemble = TransformerEnsemble(device="cpu")

# German β€” auto-routed to multilingual head
result = ensemble.score_routed("EZB signalisiert Geduld bei Zinssenkungen.")
print(result["head_used"])           # "multilingual"
print(result["detected_language"])   # "de"
print(result["sentiment_score"])     # [-1, +1]

# French
result = ensemble.score_routed("La BCE maintient ses taux directeurs inchangΓ©s.")
print(result["head_used"])           # "multilingual"

# English β€” still routed to domain-specific heads as before
result = ensemble.score_routed("Fed raised rates by 75bps.")
print(result["head_used"])           # "policy" (RoBERTa-Large)
```

For better language detection accuracy, install `langdetect`:
```bash
pip install langdetect
```

To disable multilingual routing (v0.2.0 behavior):
```python
ensemble = TransformerEnsemble(multilingual_model=None)
```

---

## Fine-Tune on Your Own Data

Got your own labelled financial text? Fine-tune any head in one command:

```bash
python -m macro_sentiment.finetune \
    --data my_labels.csv \
    --text-column headline \
    --label-column sentiment \
    --base-model peyterho/finbert-macro-sentiment \
    --output my-org/my-custom-model \
    --push-to-hub
```

**Your CSV/TSV/JSON/JSONL just needs two columns:**

| headline | sentiment |
|----------|-----------|
| Company profits soared to record highs | positive |
| Stock prices crashed amid panic selling | negative |
| Revenue remained flat quarter over quarter | neutral |

Labels accept: `positive`/`negative`/`neutral` (or `bullish`/`bearish`, `pos`/`neg`, `risk`/`opportunity`, or integers `0`/`1`/`2`).

**Options:**
```
--base-model   Which model to start from (default: peyterho/finbert-macro-sentiment)
--epochs       Training epochs (default: 4)
--lr           Learning rate (default: 2e-5)
--batch-size   Batch size (default: 32)
--max-length   Max token length (default: 128)
--push-to-hub  Push result to Hugging Face Hub
--output       Local dir or Hub model ID
```

The script auto-detects label remapping from the model's config, applies class weighting for imbalanced data, and selects the best checkpoint by macro F1.

---

## The Full Pipeline

For hawkish/dovish stance, crisis detection, climate exposure, and uncertainty scoring:

```bash
pip install transformers torch pysentiment2 scikit-learn numpy datasets huggingface_hub
python -c "from huggingface_hub import snapshot_download; snapshot_download('peyterho/macro-sentiment-finbert', local_dir='macro-sentiment-finbert')"
cd macro-sentiment-finbert
```

```python
from macro_sentiment import MacroSentimentPipeline

pipe = MacroSentimentPipeline(device="cpu")

result = pipe("The Federal Reserve raised rates by 75bps citing persistent inflation.")
print(result.summary())
# β†’ Sentiment: ... | Policy: Very Hawkish (+0.999) | Crisis: Normal | Domain: policy

result.macro_sentiment   # -1.0 to +1.0
result.policy_stance     # -1.0 (dovish) to +1.0 (hawkish)
result.crisis_signal     # 0.0 (calm) to 1.0 (crisis)
result.uncertainty       # 0.0 (certain) to 1.0 (uncertain)
result.climate_exposure  # 0.0 to 1.0
```

---

## Accuracy

### In-domain (4,333 test samples from training datasets)

| Model | Accuracy | F1 Macro |
|-------|----------|----------|
| RoBERTa-Large (fine-tuned) | **91.3%** | **0.902** |
| FinBERT (fine-tuned) | 89.7% | 0.881 |
| ClimateBERT (fine-tuned) | 88.9% | 0.872 |

### Out-of-domain (datasets NOT in training)

| Model | Stock News (30K) | JB PhraseBank (785) |
|-------|------------------|---------------------|
| RoBERTa-Large | **72.1% acc / 0.727 F1** | **94.1% acc / 0.936 F1** |
| FinBERT | 67.8% / 0.677 | 92.4% / 0.913 |
| ClimateBERT | 64.7% / 0.644 | 92.5% / 0.921 |

The Stock News OOD dataset ([ic-fspml/stock_news_sentiment](https://huggingface.co/datasets/ic-fspml/stock_news_sentiment)) uses 5-class labels mapped to 3-class, with different annotation conventions β€” the 20pp accuracy drop is expected and honest. The JB PhraseBank OOD dataset ([Jean-Baptiste/financial_news_sentiment_mixte_with_phrasebank_75](https://huggingface.co/datasets/Jean-Baptiste/financial_news_sentiment_mixte_with_phrasebank_75)) is a similar domain and holds up well.

---

## All Output Fields

| Field | Range | What it means |
|-------|-------|---------------|
| `macro_sentiment` | -1 to +1 | Overall sentiment |
| `policy_stance` | -1 to +1 | Dovish ← 0 β†’ Hawkish |
| `financial_sentiment` | -1 to +1 | Raw sentiment from active head |
| `climate_sentiment` | -1 to +1 | Climate risk ← 0 β†’ opportunity |
| `crisis_signal` | 0 to 1 | Crisis intensity |
| `uncertainty` | 0 to 1 | Uncertainty level |
| `confidence` | 0 to 1 | Model confidence |
| `climate_exposure` | 0 to 1 | Climate topic density |
| `detected_domain` | text | policy / climate / financial_news / social |
| `head_used` | text | Which model was activated |

## File Structure

```
macro_sentiment/
β”œβ”€β”€ __init__.py                 # Package entry point (v0.3.0)
β”œβ”€β”€ dictionaries.py             # Four financial dictionaries
β”œβ”€β”€ transformers_ensemble.py    # Four AI heads + language-aware router
β”œβ”€β”€ pipeline.py                 # Full pipeline combining everything
β”œβ”€β”€ finetune.py                 # Custom fine-tuning CLI
β”œβ”€β”€ data_prep.py                # Dataset loading
└── train_meta.py               # Meta-classifier training
eval_results.json               # In-domain metrics
eval_ood_results.json           # Out-of-domain metrics
```

## References

- Araci (2019). [FinBERT](https://arxiv.org/abs/1908.10063)
- Loughran & McDonald (2011). *Journal of Finance*
- Henry (2008). *Journal of Business Communication*
- Sautner et al. (2023). *Review of Financial Studies*
- Barbieri et al. (2022). [XLM-T: Multilingual Language Models for Twitter](https://arxiv.org/abs/2104.12250)

## License
Apache 2.0