Python code: measure headline sentiment with your own lexicon

Python code: measure headline sentiment with your own lexicon
Anyone who runs a small business, a warehouse or the books of a small company lives surrounded by headlines: the index rises sharply, oil falls, the central bank leaves rates unchanged, a large platform gets a fine. Reading them every day is quick; writing down one by one whether the tone sounds good, bad or indifferent is a separate job, and a job that turns subjective the moment you look away. This piece solves that with a short Python program that scores the tone of thirty headlines and compares it with a label written by hand. The result is honest: the rule matches twenty-seven of the thirty headlines, and the program shows, without makeup, the three cases where it gets them wrong. The ZIP brings the program commented in English, the file with the thirty headlines, the real sample output and the instructions; nothing else has to be installed, because everything uses the standard library of Python.
⬇ Download the code (ZIP)Notice: this is educational material, and it is not a recommendation to buy or to sell any asset. The sample headlines and prices are invented and they are not real news; they exist only for practice. Historical data is quoted for practice purposes only. A past result guarantees nothing in the future. Before you invest, ask a licensed professional.
What the ZIP contains
The package is small and it can be reviewed in full before you run it. Everything works without an internet connection, without accounts and without any kind of sign-up.
| File | What it is | What it is for |
|---|---|---|
sentimiento_titulares.py | The program, commented line by line in English | Reads the headlines, scores them, compares them and saves the result |
datos/headlines.csv | The thirty sample headlines | It is the input; you can replace it with your own headlines |
salida_ejemplo.txt | The real output, already executed | To compare with what you get on your own computer |
README.md | The instructions of the package | The same steps as this article, in a short version |
When you run it, the program creates the salida folder and writes the file sentimiento.csv inside it, ready to open in Excel or in any spreadsheet.
The input file, column by column
| Column | Example | What it means |
|---|---|---|
date | 2026-04-06 | The day of the headline, in year-month-day format |
headline | The index rises sharply and marks a new record close | The full text of the headline, just as you would read it |
label | positivo | The hand-written classification: positivo, negativo or neutro |
The first row of the file holds the column names and the program uses them exactly as they are: if you rename a column, the program stops finding it. You can paste your own headlines into that same file, one per line, and write in the third column how you would classify each one by hand; that column is what later tells you whether the lexicon got it right. Headlines may contain commas: the reader respects the text and does not split it in half. With this table you already have what you need to swap the data folder for your own and practise with headlines from your own industry.
What the program does
The program does not guess and it does not learn: it applies a rule written in two word lists. The steps, in order, are these:
- It reads the headline file and splits the three columns: date, text and manual label.
- It cleans each headline: lower case, no accents, no punctuation, and it splits the text into single words.
- It counts how many of those words show up in the positive list and how many in the negative one.
- It works out a score between minus one and plus one: positives minus negatives, divided by the total number of matches.
- It classifies the score with a threshold: above zero point one zero it is positive, below minus zero point one zero it is negative, and in between it is neutral.
- It compares that class with the manual label the file brought and marks whether it matched or not.
- It prints the table of the thirty headlines, the summary of hits, the three most positive, the three most negative and the list of misses.
- It saves everything into an output file with plain numbers, ready to add up and filter in a spreadsheet.
How to run it
You need Python 3.11 or higher, and nothing else. No libraries are installed, no email or card is asked for, and the program never connects to the internet.
- Download the ZIP with the green button in this article and unzip it into a folder, for example on your desktop.
- Install Python from the official site if you do not have it yet, ticking the box that adds Python to the system during setup.
- Open the system terminal: on Windows the command prompt or PowerShell; on Mac or Linux, the Terminal app.
- Type
cd, a space and the path of the folder you unzipped, then press Enter. - Type
python sentimiento_titulares.pyand press Enter.
If a message says the command is not recognised, close and reopen the terminal, or install Python again with the add-to-system box ticked. If the message says it cannot find the headline file, check that the terminal is open in the right folder: that folder has to contain the program and the datos folder. If you open the program with a double click, the window closes when it finishes and you never get to read the result; running it from the terminal is always better.
The code, explained
The heart of the program is a lexicon of its own: a pocket dictionary written by hand. There is no artificial intelligence of any kind here, no trained model and no cloud service. It is two comma-separated word lists, one with a good tone and one with a bad tone, and the program only recognises the words that are inside them.
POSITIVAS = "rises,record,optimism,solid,boosts,recovers,grows,gain,gains,better,confidence,improves,approves,soars,rebound,opportunity,buying,best,highs,accelerates".split(",")
NEGATIVAS = "falls,fear,fears,crisis,recession,risk,concern,uncertainty,drop,slump,plunge,losses,weak,weakens,warning,fine,panic,lows,cut,sanction".split(",")
# # That is exactly why it fails: on irony and on any word that is not in the list.
Each list holds twenty words and it is written in lower case and without accents on purpose, because the program cleans the headline the same way before comparing. That decision has a price: any word that is not in the lists adds nothing, and a bad headline written with new words is left with no signal at all.
def contar(palabras: list[str]) -> tuple[int, int]:
"""Counts how many lexicon words show up: the positive ones and the negative ones."""
positivas = sum(1 for palabra in palabras if palabra in POSITIVAS)
negativas = sum(1 for palabra in palabras if palabra in NEGATIVAS)
return positivas, negativas
The counting is literal: it walks the words and adds one for every match. Then the score divides the difference by the total number of matches, so that a long headline does not win just for having more text. With two positive words and one negative, the score comes out at zero point three three.
def clasificar(valor: Decimal) -> str:
"""Classifies the score with the threshold: positive, negative or neutral."""
if valor > UMBRAL:
return CLASE_POS # # More positives than negatives and above the threshold
if valor < -UMBRAL:
return CLASE_NEG # # More negatives than positives and below the threshold
return CLASE_NEU # # Tied, or far too few words: there is no signal
The classification uses a threshold, not an exact zero. That way a tie between a good word and a bad one lands in the middle and is marked neutral, which is the prudent choice: when the signals contradict each other, the program admits it has nothing to say.
def trampas(filas: list[dict]) -> None:
"""Shows where the lexicon fails and why (irony and words outside the list)."""
fallos = [fila for fila in filas if not fila["acierto"]]
print()
print("=" * ANCHO_LINEA)
print("WHERE THE LEXICON FAILS (ON PURPOSE)")
print("=" * ANCHO_LINEA)
print("This lexicon is NOT artificial intelligence: it is two hand-written word lists.")
print("It fails on IRONY: the same adjective can sound positive inside a headline that is")
print("actually bad, and it also fails on the words that are not in the list.")
print("The workshop left 3 trap headlines on purpose so that the failure shows:")
print(f"Headlines where the lexicon and the manual label disagree: {len(fallos)}")
for fila in fallos:
print(f" {fila['fecha']} manual label {fila['manual']} / lexicon says {fila['clase']}"
f" {recortar(fila['titular'], 52)}")
This is the most honest part of the workshop: the program does not hide its failures, it looks for them, lists them with their date and explains why they happened. A tool that reports where it goes wrong is far more useful than one that only shows its hits.
It is also worth knowing where the real data comes from that this family of workshops uses as a historical reference for practice: bitcoin is downloaded from CoinGecko, at api.coingecko.com, and the index comes from the Federal Reserve of St. Louis, series SP500, whose address is https://fred.stlouisfed.org/series/SP500. Both files were downloaded on 2026-09-25 and they are used only to practise calculations. In this piece the input file is invented headlines, not prices, and its format is the one in the table above.
What you will see on screen
The first thing that appears is the notice of the program and the size of the two lists. Right after that comes the table of the thirty headlines, with the score, the class the lexicon worked out, the manual label and a last column that says whether it matched:
======================================================================================================================= HEADLINE SENTIMENT Didactic Python code · Kardex Tauro · kardex-tauro.muisca.co ======================================================================================================================= NOTICE: educational material. It is not a recommendation to buy or to sell. The sample headlines and prices are INVENTED; historical data is quoted only for practice. A past result guarantees nothing: ask a licensed professional. ----------------------------------------------------------------------------------------------------------------------- Headlines read: 30 The 30 headlines in datos/headlines.csv are INVENTED for practice: they are not real news. Positive words (20): rises, record, optimism, solid, boosts, recovers, grows, gain, gains, better, confidence, improves, approves, soars, rebound, opportunity, buying, best, highs, accelerates Negative words (20): falls, fear, fears, crisis, recession, risk, concern, uncertainty, drop, slump, plunge, losses, weak, weakens, warning, fine, panic, lows, cut, sanction Our own lexicon: two hand-written lists (no artificial intelligence), but it fails on irony and on the words that are not in the list.
After the table, the summary puts the numbers in plain sight and checks that everything ties:
======================================================================================================================= SUMMARY ======================================================================================================================= Hits (the lexicon matches the manual label): 27 Misses: 3 Hit rate: 90.00 % hits + misses = headlines read (30): TIES a headline with no lexicon words scores 0.00: TIES Probe headline: The market opens unchanged and the index stays stable -> 0.00
And at the end comes the most valuable section of the exercise, where the program gives itself away. The three trap headlines the workshop left on purpose are these:
======================================================================================================================= This lexicon is NOT artificial intelligence: it is two hand-written word lists. It fails on IRONY: the same adjective can sound positive inside a headline that is actually bad, and it also fails on the words that are not in the list. The workshop left 3 trap headlines on purpose so that the failure shows: Headlines where the lexicon and the manual label disagree: 3 2026-05-25 manual label negativo / lexicon says NEUTRAL Widespread losses: the index erases the month's g... 2026-06-01 manual label negativo / lexicon says NEUTRAL A fund is under investigation for alleged insider... 2026-06-25 manual label neutro / lexicon says POSITIVE The price falls but analysts see a buying opportu...
Look at them calmly, because the whole limit of the idea is there. "Widespread losses: the index erases the month's gains" is clearly a negative headline for any reader, but the program finds one positive word and one negative word, ties them and concludes that the tone is neutral. "A fund is under investigation for alleged insider trading" carries no word from the lists either, so it goes through with no signal and stays neutral. And the mistake runs the other way too: "The price falls but analysts see a buying opportunity" has two good words and one bad one, so the program calls it positive, even though by hand we labelled it neutral because the headline shows both sides of the news.
In round numbers: twenty-seven hits out of thirty, that is, about ninety per cent. That sounds high, and it is high for a handful of hand-written words; but it is not magic, it is arithmetic, and the three misses you have just seen are the ones that show up whenever the language gets creative.
Common mistakes and tips
- If the terminal says
pythonis not recognised, on some machines the command is calledpyorpython3: try those before reinstalling anything. - Run the program from the folder you unzipped; if you launch it from another folder it will not find the headline file.
- The manual label column takes no other words: always write positivo, negativo or neutro, in lower case and without accents.
- Do not add extra columns to the input file; if you want to keep notes, leave them in a separate file.
- If a headline ends up neutral and you disagree, first check whether its words are in the list: almost every surprise comes from there.
- The output file is overwritten every time you run the program; if you want to keep two versions, rename the file before running it again.
- Work with thirty or forty headlines at a time. With many more, the hand-checking gets heavy and the conclusions get fragile.
- Extend the lists with words from your own industry, but keep a copy before you change them: that way you can compare the before and after on the same headline file.
When this is not enough
A hand-written lexicon is a good starting point and a bad decision tool. It scores a fixed vocabulary very well and it is lost as soon as the headline plays with the language. Beside that, headlines are not inventory: they do not say how many units sit in the warehouse or what the last purchase cost, and no word list will ever balance a ledger. When the business grows, when there are several warehouses, when purchases, sales and stock have to be crossed with the accounts, the next step is not another word list but a real system. Kardex Tauro is free and keeps the inventory of a small company in order without forcing it to write code, and software is the answer once a spreadsheet or a homemade script is no longer enough.
Notice: it is worth repeating, because it is the most important thing on this page: this is educational material and it is not a recommendation to buy or to sell; the sample headlines are invented and they are not real news; historical data is quoted only for practice and a past result guarantees nothing. The source of the real data, CoinGecko and the Federal Reserve of St. Louis with the SP500 series, is cited above with its address visible, and the ZIP carries the disclaimer in writing so that you can keep it next to the code. Before you invest, ask a licensed professional.
⬇ Download the code (ZIP)Download the package, run it first with the sample headlines and then with your own: the most valuable part of the exercise is finding the words your list does not know yet.