Earnings News Monitor: Build It in Python (2026)

Tasha Tatum

Tasha Tatum

·

24 minuten lesen

Earnings News Monitor: Build It in Python (2026)

Build an Earnings and Company-News Monitor

An earnings news monitor is a watchlist-driven service that fuses two streams over the same set of tickers — the scheduled earnings calendar (you know when a company reports) and the unscheduled company-news feed (you don't know when a story breaks) — and surfaces each item weighted by how close it falls to the report date. Most tutorials build only half of that. They either poll a news API by ticker, or they pull an earnings calendar and stop. This guide builds the whole thing in Python: one watchlist, two layers, an earnings-window relevance rule, and a poll loop with persistence.

This tutorial is for FinTech developers building a company news watchlist or a stock news tracker that has to know the difference between a routine Tuesday headline and a story landing the day before a megacap reports. By the end you will have a runnable monitor and a numeric framework for deciding what to surface.

Disclosure: This tutorial is published by APITube (apitube.io), a news API provider, and the live code calls its API for the news layer. The method is vendor-neutral: any real-time financial news API that exposes entity tagging and a published-date filter works for the news side, and any earnings-calendar source (or your own database of report dates) works for the calendar side. APITube does not expose an earnings-calendar endpoint — so this monitor deliberately pairs it with an external calendar, and the code is explicit about that seam. This is an engineering tutorial, not investment advice; the relevance scores decide what to surface, not what to trade.

Key takeaways:

  • A monitor that watches only the calendar or only the news is half-blind — the value is in fusing them over one watchlist.
  • The earnings calendar is scheduled data; company news is unscheduled data. They need different polling strategies but a shared relevance model.
  • The earnings window — roughly T-5 to T+2 trading days around a report date — is when company news matters most. Weight by proximity, not just presence.
  • The whole monitor is a watchlist, two fetchers, a fusion rule, and a persisted poll loop — about 120 lines of Python.

Table of contents

Why calendar-only or news-only monitoring is half-blind

Here is the contrarian point the vendor docs won't make: a calendar-only monitor and a news-only monitor are each blind to half of what moves a stock around earnings.

A calendar-only tool knows Nvidia reports on June 17 after the close. It does not know that, two days earlier, a supply-chain story or an analyst pre-announcement leak is already moving the name. The calendar tells you when the scheduled event lands; it says nothing about the unscheduled news clustering around it.

A news-only tool has the opposite blindness. It ingests every Nvidia headline in real time but treats them all the same. It cannot tell that a mundane product blurb on a quiet week and a margin-warning headline the day before the report deserve completely different urgency. Without the calendar, every headline is context-free.

The market is split along exactly this line. News-first APIs such as Finnhub's company-news endpoint and Newsfilter.io — which advertises indexing articles in under 500 milliseconds with NLP ticker tagging — give you a fast stream with no notion of when a company reports. Calendar-first APIs such as Financial Modeling Prep's earnings calendar give you report dates and EPS estimates with no news attached. Almost nobody fuses them. That gap is the entire reason to build this yourself rather than buy one box.

The fix is not a smarter news feed or a richer calendar. It is a small piece of glue: a monitor that holds one watchlist, asks the calendar "when does this ticker report?" and the news API "what's breaking on this ticker?", then combines the two into a single relevance number.

The two layers: scheduled vs unscheduled

A company news watchlist has to handle two fundamentally different kinds of data. Treating them the same is the mistake. Here is the distinction that drives the whole design:

DimensionScheduled layer (earnings calendar)Unscheduled layer (company news)
Data typeReport date, EPS estimate, time-of-day (BMO/AMC)Headlines, body, entities, sentiment, source
TriggerKnown in advance, fixed on a dateArrives any time, unpredictably
SourceEarnings-calendar API or your DBReal-time news API (/news/everything)
Polling cadenceRefresh daily — dates rarely change intradayPoll every 15–60s — news is continuous
What it tells youWhen to pay attentionWhat is actually happening
Failure if used aloneMisses pre/post-announcement newsCan't tell routine noise from earnings-critical news

The scheduled layer is cheap and slow-moving: a ticker's next report date changes maybe once a quarter, so you refresh it once a day and cache it. The unscheduled layer is the firehose: you poll it continuously and keep per-ticker state so you never reprocess the same window.

The monitor's job is to be the place where these two meet. It uses the scheduled layer as context and the unscheduled layer as content. A headline is just a headline until the calendar tells you it landed inside an earnings window — then it's an earnings surprise alert. Unlike a news-only feed, which treats every headline identically, a fused monitor reads each headline through the calendar, which means the same margin-warning story scores far higher the day before a report than three weeks out.

The earnings-window framework

This is the rule that fuses the layers, and it's where most "stock news tracker" tutorials have nothing to say. The earnings window is the band of trading days around a report date when company news carries the most signal. Define it numerically and weight news by proximity to the report.

The defaults below are a starting point — tune them against your own data — but the structure matters more than the exact numbers:

Position relative to report dateBusiness-day offsetRelevance multiplierRationale
Peak windowT-1, T, T+1×3Day before, day of, day after — pre-announcement leaks, the print itself, and the first reaction
Hot windowT-5 to T-2, plus T+2×2Run-up and immediate aftermath; analysts reposition, guidance leaks circulate
Outside the windowbeyond T-5 / after T+2×1Normal baseline; news still matters but isn't earnings-driven

Two design choices are deliberate. First, the window is measured in trading days, not calendar days — a report on a Monday means the prior Friday is T-1, not three calendar days back. Counting weekdays keeps the window aligned with when markets actually react. Second, the multiplier is asymmetric: the window opens wider before the report (T-5) than it closes after (T+2), because the run-up is longer and noisier than the snap reaction.

The multiplier doesn't replace a base relevance score — it scales it. A weak headline inside the peak window and a strong headline far outside it can land at similar final scores, which is exactly right: a throwaway blurb shouldn't fire just because earnings are tomorrow, and a margin warning shouldn't be ignored just because the report is three weeks out. If you want a richer base score — surprise, source credibility, coverage spikes — our companion guide, Build Real-Time Market-Moving News Alerts, develops that scoring model in depth. This article keeps the base deliberately simple so the fusion logic stays in focus.

Build it: watchlist → calendar → news → fuse → persist

The monitor has five parts: a watchlist of tickers, a calendar of report dates, a news fetcher, a fusion rule (base relevance × window multiplier), and a persisted poll loop. Start with one real news request.

curl -s "https://api.apitube.io/v1/news/everything?api_key=YOUR_KEY&title=Nvidia&category.id=business&language.code=en&published_at.start=2026-06-15T00:00:00Z&per_page=50"

Each article carries the fields the fusion rule needs — entity tagging, categories, source ranking, and a story id for dedup:

{
  "results": [
    {
      "id": 3211554477,
      "href": "https://www.reuters.com/technology/nvidia-...",
      "published_at": "2026-06-15T13:42:00.000Z",
      "title": "Nvidia guides Q3 revenue above Wall Street estimates",
      "language": { "code": "en" },
      "story": { "id": 3211554477 },
      "is_duplicate": false,
      "source": { "domain": "reuters.com", "rankings": { "opr": 8 } },
      "sentiment": { "overall": { "score": 0.42, "polarity": "positive" } },
      "entities": [
        { "id": "nvidia-corp", "name": "Nvidia", "type": "organization", "frequency": 5 }
      ],
      "categories": [
        { "id": "business", "name": "business" },
        { "id": "earnings", "name": "earnings" }
      ]
    }
  ]
}

The watchlist and the calendar

The watchlist maps a ticker to the company name as the news API tags it. The calendar maps a ticker to its next report date — and this is the honest seam: APITube has no earnings endpoint, so these dates come from an external earnings-calendar source (FMP, your broker's data, or a hand-maintained dict). Keep the calendar a plain dict so the fusion logic doesn't care where the dates came from.

from datetime import date

# ticker -> company name as the news API tags it
WATCHLIST = {
    "NVDA": "Nvidia",
    "AAPL": "Apple",
    "TSLA": "Tesla",
}

# ticker -> next earnings report date.
# APITube has NO earnings-calendar endpoint — these come from an external
# source (e.g. FMP /earnings-calendar) or your own database. Refresh daily.
EARNINGS_CALENDAR = {
    "NVDA": date(2026, 6, 17),   # reports after market close
    "AAPL": date(2026, 7, 30),
    "TSLA": date(2026, 7, 22),
}

The earnings-window multiplier

Count business days between today and the report date, signed (negative = before the report), then map the offset to a multiplier. This is the framework from the previous section, in code:

from datetime import date, timedelta

def business_days_between(start: date, end: date) -> int:
    """Signed count of weekdays from start to end (negative if end is earlier)."""
    if start == end:
        return 0
    step = 1 if end > start else -1
    days, cur = 0, start
    while cur != end:
        cur += timedelta(days=step)
        if cur.weekday() < 5:          # Mon–Fri only
            days += step
    return days

def window_multiplier(today: date, report_date: date | None) -> float:
    if report_date is None:
        return 1.0
    offset = business_days_between(today, report_date)
    if offset in (-1, 0, 1):
        return 3.0                     # peak: T-1, report day, T+1
    if -5 <= offset <= 2:
        return 2.0                     # hot window: T-5..T-2 and T+2
    return 1.0                         # outside the window

Base relevance and fusion

The base score stays intentionally small — entity prominence, a finance-category match, and source credibility. The window multiplier does the earnings-aware work:

FINANCE_CATS = {"business", "earnings", "stock market",
                "mergers and acquisitions", "economy"}

def base_relevance(article: dict, company: str) -> float:
    score = 0.0
    for e in article.get("entities", []):
        if e.get("name") == company and e.get("type") == "organization":
            score += min(e.get("frequency", 1), 5) * 4   # up to 20
            break
    cats = {c.get("name") for c in article.get("categories", [])}
    if cats & FINANCE_CATS:
        score += 10
    if (article.get("source", {}).get("rankings", {}) or {}).get("opr", 0) >= 6:
        score += 5                                       # credible source
    return score

def relevance(article: dict, company: str, today: date,
              report_date: date | None) -> tuple[float, float]:
    mult = window_multiplier(today, report_date)
    return base_relevance(article, company) * mult, mult

The persisted poll loop

The loop fetches news per ticker since the last seen timestamp, computes fused relevance, dedupes by story.id, and surfaces anything above a threshold. Per-ticker last_seen and a global seen_stories set are the persistence — in production these live in Redis, not memory.

import time, requests
from datetime import datetime, timezone

BASE = "https://api.apitube.io/v1/news/everything"
API_KEY = "YOUR_KEY"
SURFACE_THRESHOLD = 30.0
POLL_SECONDS = 30

def fetch_news(company: str, since_iso: str) -> list[dict]:
    r = requests.get(BASE, params={
        "api_key": API_KEY,
        "title": company,                 # or entity.id for tighter matching
        "category.id": "business",
        "language.code": "en",
        "published_at.start": since_iso,
        "per_page": 50,
    }, timeout=10)
    r.raise_for_status()
    return r.json().get("results", [])

def surface(ticker: str, a: dict, score: float, mult: float):
    flag = "🔥 earnings window" if mult > 1 else ""
    print(f"[{score:5.0f}] {ticker} {flag} — {a['title']}")
    print(f"        {a['source']['domain']} · {a['published_at']}")

def run_monitor():
    seen_stories: set = set()
    last_seen = {t: datetime.now(timezone.utc).isoformat() for t in WATCHLIST}
    while True:
        today = datetime.now(timezone.utc).date()
        for ticker, company in WATCHLIST.items():
            try:
                articles = fetch_news(company, last_seen[ticker])
            except Exception as e:
                print(f"{ticker} fetch error:", e)
                continue
            report_date = EARNINGS_CALENDAR.get(ticker)
            for a in sorted(articles, key=lambda x: x["published_at"]):
                sid = (a.get("story") or {}).get("id") or a["id"]
                if sid in seen_stories or a.get("is_duplicate"):
                    continue
                score, mult = relevance(a, company, today, report_date)
                if score >= SURFACE_THRESHOLD:
                    surface(ticker, a, score, mult)
                    seen_stories.add(sid)
                last_seen[ticker] = max(last_seen[ticker], a["published_at"])
        time.sleep(POLL_SECONDS)

That's the whole monitor. The Reuters guidance headline above — entity Nvidia at frequency 5 (20) + finance category (10) + credible source OPR 8 (5) = 35 base — lands two business days before Nvidia's June 17 report, inside the peak window (×3), for a fused score of 105. The same headline three weeks earlier scores 35. Same story, very different urgency, and the monitor knows the difference because it fused the calendar with the news.

Going to production

A demo loop becomes a real earnings news monitor with a few hardening steps:

  1. Persist state across restarts. Move seen_stories and last_seen to Redis with a TTL so a redeploy doesn't re-fire yesterday's alerts or skip a window.
  2. Refresh the calendar on a schedule. Pull the earnings calendar once a day (report dates shift, especially confirmations), and cache it. Don't fetch it in the hot loop.
  3. Resolve entities, don't just match titles.title=Nvidia is a blunt filter; prefer entity.id so "Apple" the company never collides with "apple" the fruit. Entity-type tagging (type == "organization") is your disambiguator.
  4. Tighten the poll around windows. Widen POLL_SECONDS for tickers far from earnings; tighten it for tickers in the peak window. Spend your rate limit where the signal is.
  5. Respect rate limits. Back off on HTTP 429. Newsfilter-class latency (sub-500ms indexing) only helps if you're actually polling fast enough to see it during the window.
  6. Log every score, not just the surfaced ones. You can only tune the multipliers and threshold if you can later join scores against realized price moves.

If you're still choosing the news side, evaluate feeds on entity tagging quality, category depth (does "earnings" exist as a category?), source credibility signals, and date-filter precision — the inputs this monitor depends on. Our News API Buyer's Guide 2026 compares providers on exactly those axes.

Frequently asked questions

How do I get real-time company news by ticker?

You get real-time company news by ticker by polling a news API's everything-style endpoint filtered to that company, using a published_at.start cursor so each call returns only new articles. With APITube that's GET /v1/news/everything?title=Nvidia&category.id=business&published_at.start=<last_seen>; for tighter matching use the entity.id filter instead of title. Map each ticker to the company name (or entity id) the API uses, and keep a per-ticker last-seen timestamp so you never reprocess the same window.

What is the best API for earnings news?

There's no single best one, because earnings news is two data types. For the news layer, use a real-time financial news API with strong entity tagging and a finance category (APITube, Finnhub, Newsfilter.io). For the calendar layer — report dates and EPS estimates — use an earnings-calendar API such as Financial Modeling Prep. The best "earnings news monitor" is the glue you write to fuse the two, because no single vendor does both well.

How do you build a stock news monitor?

You build a stock news monitor in five parts: a watchlist mapping tickers to entities, an earnings calendar of report dates, a news fetcher that polls by ticker with a date cursor, a fusion rule that multiplies base relevance by an earnings-window factor, and a persisted poll loop that dedupes by story id. The whole thing is about 120 lines of Python — the architecture in this tutorial is a complete, runnable starting point.

How do you track earnings announcements automatically?

You track earnings announcements automatically by pulling an earnings-calendar API daily into a ticker -> report_date map and caching it. Then, in your news monitor, compute the business-day distance between today and each ticker's report date and use it to weight incoming news — heavier inside the T-5 to T+2 window. Refresh the calendar on a schedule (dates get confirmed and occasionally moved) rather than fetching it inside the real-time loop.

Conclusion

An earnings news monitor earns its name only when it fuses both layers: the scheduled earnings calendar that tells you when to pay attention, and the unscheduled company-news feed that tells you what is actually happening. Build only one and you have half a tool — a calendar that's silent during the run-up, or a news feed that can't tell a margin warning from a product blurb.

The fusion is small: a shared watchlist, an earnings-window multiplier measured in trading days, and a persisted poll loop. Start with the code above, log every score against realized moves, and tune the window and threshold to your own book. Then point it at your real watchlist and let it tell you which headlines actually matter this week.

Try Apitube free → apitube.io

Resources

APITube - News API

Verwandte Artikel

How to Create a News Summarization Pipeline with GPT
Developer Guides

How to Create a News Summarization Pipeline with GPT

Build a production GPT news summarization pipeline: chain selection, cost math at volume, hallucination grounding, prompt caching, and build-vs-buy.

So reduzieren Sie die Kosten für die News-API: 8 Strategien (sparen Sie 70–90 %)</h4><h2></h2>
Developer Guides

So reduzieren Sie die Kosten für die News-API: 8 Strategien (sparen Sie 70–90 %)</h4><h2></h2>

Ein Audit-zuerst-Leitfaden zum Senken der Ausgaben für Nachrichten-APIs um 70-90%: Fingerprinting Ihrer Aufrufe, Tiering der Cache-TTL nach Nachrichtengeschwindigkeit, Ersetzen von totem Polling durch Webhooks und Massenexport von Backfills. Kostenrechnung pro 100.000 Einheiten Code.

10 News-API-Filtermuster, die Sie kennen sollten (2026)</h4><h2></h2>
Developer Guides

10 News-API-Filtermuster, die Sie kennen sollten (2026)</h4><h2></h2>

10 ausführbare Nachrichten-API-Filtermuster – Titelsuche, Daten, Entitäten, Sentiment, Quellen und zusammengesetzte Abfragen – mit Curl-, Python- und JSON-Beispielen sowie der Falle, die Ihren ersten Tag verschwendet.

News API Quick Start: Ihre erste Anfrage in 5 Minuten</h4><h2></h2>
Developer Guides

News API Quick Start: Ihre erste Anfrage in 5 Minuten</h4><h2></h2>

Rufen Sie in 5 Minuten einen echten News-API-Endpunkt auf — Curl, JavaScript und Python nebeneinander, mit einer kommentierten JSON-Antwort und Korrekturen für die 401/429-Fehler, auf die Sie tatsächlich stoßen werden.

Wir verwenden Cookies

Indem Sie auf "Akzeptieren" klicken, stimmen Sie der Speicherung von Cookies auf Ihrem Gerät zu Funktions- und Analysezwecken zu.