A FastAPI service that scores financial text with the Loughran-McDonald dictionary and returns category proportions, a composite uncertainty-constraining signal, and a bullish, bearish, or neutral reading.
curl -s -X POST http://127.0.0.1:8000/analyze \
-H "Content-Type: application/json" \
-d '{"text": "Results may vary materially; management cannot predict outcomes and may be constrained by regulation and litigation."}'{
"scores": {
"negative": 0.0625,
"uncertainty": 0.25,
"constraining": 0.0625,
"litigious": 0.125,
"net_sentiment": -0.0625,
"composite_signal": 0.015625
},
"signal": { "direction": "bearish", "confidence": "high" },
"metadata": { "total_words": 16, "dictionary_matches": 7 }
}Real response, abridged. The full response also includes the remaining categories, tone, an explanation string, and processing time.
General-purpose sentiment lists were built for everyday English, and finance breaks them. Words like "liability", "tax", and "cost" read as negative in a general lexicon and are routine in a 10-K. Loughran and McDonald (2011) built word lists from financial filings to fix this, but they ship as a spreadsheet, not as something a pipeline can call.
FilingTone wraps that dictionary in a typed HTTP API. Send text, get transparent, dictionary-only scores back in milliseconds, with the matched words available on request so every number can be traced to its source. There is no model to retrain and no black box to defend.
Requires Python 3.11+. The Loughran-McDonald dictionary ships in data/, so there is nothing else to download.
git clone https://github.com/amayakmt/financial-nlp-api.git
cd financial-nlp-api
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
uvicorn app.main:app --reloadOpen the interactive docs at http://127.0.0.1:8000/docs, or run the curl call above.
Prefer Docker?
docker build -t financial-nlp-api .
docker run -p 8000:8000 financial-nlp-api| Method | Path | Description |
|---|---|---|
| GET | / |
Redirects to /docs |
| GET | /health |
Service status and number of dictionary terms loaded |
| POST | /analyze |
Score one text |
| POST | /analyze/batch |
Score 1 to 50 texts, each tagged with your own id |
POST /analyze takes:
text(required): at least 10 characters.include_word_details(optional, defaultfalse): whentrue, returns the matched words for each category, unique and in first-seen order.
curl -s -X POST http://127.0.0.1:8000/analyze \
-H "Content-Type: application/json" \
-d '{"text": "Strong growth and record profits improved our outlook considerably.", "include_word_details": true}'curl -s -X POST http://127.0.0.1:8000/analyze/batch \
-H "Content-Type: application/json" \
-d '{"texts": [
{"id": "filing-a", "text": "Strong growth and record profits improved our outlook considerably."},
{"id": "filing-b", "text": "Litigation may constrain our ability to operate under uncertain regulation."}
]}'Results come back in request order with a batch_metadata block (total_processed, total_time_ms). Batch mode never returns word details. If any item fails validation, the whole request returns 400 and the error names the offending id.
- Lowercase the text and extract alphabetic tokens (
a-zruns only). - Count tokens in each Loughran-McDonald category: positive, negative, uncertainty, constraining, litigious, strong modal, weak modal.
- Divide each count by total words to get proportions.
| Field | Definition |
| --- | --- |
|
net_sentiment| (positive count − negative count) / total words | |tone| positive / (positive + negative), or0.5when neither appears | |composite_signal| uncertainty proportion × constraining proportion |
signal.direction and signal.confidence come from fixed, hand-set thresholds, checked in this order:
| Condition | Direction | Confidence |
|---|---|---|
| composite > 0.0005 and net sentiment < −0.01 | bearish | high |
| composite > 0.0003 and net sentiment < 0 | bearish | moderate |
| net sentiment > 0.02 and composite < 0.0002 | bullish | moderate |
| net sentiment > 0.01 and composite < 0.0003 | bullish | low |
| anything else | neutral | low |
These thresholds are heuristics for screening, not investment advice. The composite is a transparent feature, in the spirit of signals studied on long-horizon panels of 10-K filings, that you can feed into your own research.
- Dictionary-only: no negation handling ("not profitable" still counts "profitable") and no context.
- English text only. Numbers and punctuation are dropped, so
10-Ktokenizes tok. - Very short or number-only text is rejected: input needs at least 10 characters and one alphabetic token.
By default the app loads data/lm_dictionary.csv. Point it at another copy with LM_DICTIONARY_PATH (absolute, or relative to the project root):
export LM_DICTIONARY_PATH=/path/to/lm_dictionary.csvOn Windows PowerShell, use $env:LM_DICTIONARY_PATH = "C:\path\to\lm_dictionary.csv". CORS is open to all origins, so tighten allow_origins in app/main.py before exposing the service publicly.
git clone https://github.com/amayakmt/financial-nlp-api.git
cd financial-nlp-apipython -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt pytest httpxpytest tests/ -vuvicorn app.main:app --reloadapp/main.py # FastAPI routes and app lifecycle
app/scorer.py # Tokenization, category counts, composite signal, thresholds
app/dictionary.py # Loads and caches the Loughran-McDonald CSV
app/models.py # Pydantic request and response schemas
data/ # Loughran-McDonald Master Dictionary (CSV)
tests/ # Scorer tests
Fork the repository and open a pull request to main with a focused change and a test for any behavior change.
The code is released under the MIT License. The dictionary is the work of Tim Loughran and Bill McDonald and is distributed through the Notre Dame Software Repository for Accounting and Finance under its own terms. Check those terms before any commercial use.
Loughran, T. and McDonald, B. (2011). When Is a Liability Not a Liability? Textual Analysis, Dictionaries, and 10-Ks. Journal of Finance, 66(1), 35-65.