Alphanume

Insights

How to Build a Pre-Earnings Options Screen

Alphanume Team · June 30, 2026

A pre-earnings screen joins two independent facts per name: a long-run track record of how its earnings move gets priced, and where its implied vol sits right now. Either alone is half a trade.

A per-name earnings track record answers one question: does this name tend to overprice its earnings move? That is half of a trade. The other half is a question about right now: is the market charging its usual rich price this quarter, or has something already pushed vol somewhere unusual?

Those are different axes, and keeping them separate is what makes the screen work. Tendency is slow-moving, a property of the name and the crowd that trades it. State is fast, a property of this specific week. A chronic overpricer whose implied vol sits at its 52-week floor is a very different setup from the same name with IV at the 95th percentile of its own year: the record says sell in both cases, but in one of them you would be selling insurance at clearance prices. A screen that only reads the record cannot tell those two situations apart. This post wires the two together into the thing a desk actually keeps: a pre-earnings watchlist.

Ingredient one: the per-name track record

The first leg comes from earnings history scored event by event. For each name, the most recent row of Alphanume's Earnings Move History dataset carries its running record: n_events_to_date (sample size), hit_rate_to_date (how often the implied move overshot the realized one), and avg_over_under_to_date (by how much, in percentage points). Together they say how often and how badly this name's straddle has overshot, over how many events. The chronic overpricers post covers how to read these fields and why the point-in-time "_to_date" construction matters.

Ingredient two: current vol context

The second leg is IV rank: where the name's current implied vol sits inside its own trailing 52-week range, 0 at the year's low, 100 at the year's high, with IV percentile as the companion reading (the two differ in an instructive way). This is the "expensive compared to its own history" dial, available per name per day from the IV Rank dataset, and it is exactly the state variable the track record lacks.

The two-by-two that sorts every candidate

Cross the two ingredients and every name falls into one of four boxes:

Track record

IV rank now

Read

Chronic overpricer

High

The habitual edge, plus extra cushion priced in right now

Chronic overpricer

Low

The record says sell, but you would be selling cheap vol; thin cushion

Weak or short record

High

Vol is stretched, but no per-name evidence the event side is mispriced

Weak or short record

Low

Nothing here; move on

The screen exists to sort names into that table before a reporting week, not during it. Only the top-left box is a strong candidate, and note what it takes to land there: two independent conditions, either of which can be true without the other.

Building it in code

Mechanically, the screen is a join: pull the latest row per name from each endpoint, keep the fields above, and sort by the size of the historical bias. On the vol pull, request settled end-of-day rows rather than provisional intraday values (the feed's only_final flag), because a research screen should read finalized numbers. The core loop fits in a page:

import pandas as pd

rows = []
for t in tickers:
    earn = get("earnings-move-history", ticker=t)
    vol = get("iv-rank", ticker=t, only_final="true")

    e = max(earn["data"], key=lambda r: r["date"])
    v = max(vol["data"], key=lambda r: r["date"])

    rows.append({
        "ticker": t,
        "events": e["n_events_to_date"],
        "hit_rate": e["hit_rate_to_date"],
        "avg_over_under_pp": e["avg_over_under_to_date"],
        "iv_rank": v["iv_rank"],
    })

screen = pd.DataFrame(rows).sort_values("avg_over_under_pp", ascending=False)

Then gate it: require, say, hit_rate above 70 and iv_rank above 50, and only names clearing both survive. That gated table is the watchlist. The earnings history docs and IV rank docs cover the full field lists and query parameters.

Attacking the watchlist before it becomes positions

A screen that stops at the sorted table is a listicle. Before any name on it becomes a position, four checks, in order of how badly skipping them hurts:

  • Tails. The whole risk of this trade is one row. A name can overprice 15 quarters in a row and then move 25 percent against an 8 percent implied on the 16th, and a short straddle loses on the square of the surprise. Look at the worst realized move in the record, not just the mean gap. Averages recruit you; tails fire you.
  • Regime. A track record accumulated across two calm years says little about a reporting season inside a vol spike. On full history, split each record by year and check the bias survives outside the calm stretch.
  • Sample size. The table prints the events column first for a reason. Below roughly 12 events you have a story, not evidence, and the screen should drop the name no matter how pretty the other columns look.
  • Costs. The edge is measured in percentage points of implied minus realized; event-week spreads, fees, and assignment risk subtract in the same units. A +1.5 point average bias can vanish entirely into the bid-ask of an illiquid chain, which is why liquidity belongs inside the screen, not after it.

And one line stated plainly: this is a study of how an event has been priced, not a recommendation to sell straddles. A watchlist is a set of hypotheses ranked by prior evidence; the checklist is what keeps it from becoming a set of positions ranked by greed.

Run the screen in your browser

The pre-earnings screen lesson in Alphanume Learn's Systematic Trading with Market Data course walks this exact build: both endpoints, the join, the gate, and then the attack, with real Python running against live data in your browser and the lesson grading what your code prints. It is the capstone of the earnings module, which starts from straddles as forecasts and builds up through the per-name track records.

The first module of the course is free, no account needed: begin with the ten-minute first lesson or read the full syllabus. The remaining modules, including this screen, come with an Alphanume Pro membership (pricing), which also unlocks full access to the datasets the screen runs on.