Learn · 15 of 21

Screening: universes, funnels and fair comparisons

What a screen is and is not, liquidity floors, relative-to-industry fields and survivorship.

A screen is a list of conditions that every stock must pass: price at least $5, return on invested capital above 15%, P/E below the industry median. It is a filter, not a forecast. It does not say which of the survivors is best, it does not weigh one rule against another, and it says nothing about what the stocks will do next. Its job is to cut thousands of companies down to a set you can study, or to define the candidates a ranking and a backtest will work on.

Every condition in Stoxa is a field, an operator and a number. Conditions are joined with and; an or group of conditions counts as one entry, so "cheap on P/E or on EV/EBIT, and ROIC above 15%" is two entries. There is no formula language: the fields themselves carry the comparisons people usually write formulas for, as the relative-to-group fields below show.

The as-of rule applies to every field

A screen runs on a date. Every field is computed from what was public on that date: fundamentals from filings with a filing date on or before it, prices up to its close, macro values from the vintage published by then (see point-in-time data). A trailing P/E on 30 June 2022 divides the price of that day by the four quarters of earnings filed by that day, not by the quarter that ended on 25 June and was filed in late July. Screening "today" is the same rule with today's date. That is what makes a screen on a past date a fair stand-in for what you could have run then, and what makes a backtest of the screen honest.

Universes: decide who is eligible first

A universe is the starting population, applied before the screen's own rules. Stoxa has five built-in universes and lets you save your own, either as a filter list or as a list of tickers:

UniverseDefinition
allno restriction
liquidprice ≥ $5 and market cap ≥ $300M
large_capmarket cap ≥ $10B
mid_capmarket cap ≥ $2B and < $10B
small_capmarket cap ≥ $300M and < $2B

Why a liquidity floor matters: the cheapest stocks on any multiple are disproportionately tiny, thinly traded and close to distress. A screen over everything finds them first, and a backtest over everything books returns on stocks you could not have bought in size without moving the price, or at all. Spreads on a $50M company can be several percent; the cost model (see transaction costs) charges up to 4% per trade for the least liquid names. A $5 price floor and a $300M size floor remove most of that problem. Stoxa's liquid universe is a price and size floor because the snapshot has no dollar-volume field yet; the cost model does use the real 20-day dollar volume.

A useful habit: put hard exclusions in the universe ("no financials", "nothing under $5") and preferences in a ranking ("cheaper is better"). A universe rule removes stocks; a ranking keeps everyone and only orders them.

Funnel counts: how many survive each rule

After a run, the screener shows how many stocks are left after each rule, applied in order, starting from the universe. Reading the funnel tells you which rule does the work:

Worked example

Universe liquid: 3,900 stocks. After "P/E (TTM) < 15": 1,700. After "ROIC > 15%": 440. After "net debt/EBITDA < 2": 410.

The leverage rule removes 30 names; it barely matters. The ROIC rule removes three in four of the cheap stocks, so the screen is really a quality screen with a value floor. If 410 is too many to study, tightening ROIC changes the result far more than tightening leverage. A rule that removes almost nothing is a candidate for deletion; a rule that removes almost everything deserves a second look at its threshold.

Missing values fail a condition: a company with no P/E (negative earnings) does not pass "P/E < 15". The funnel makes that visible; a rule on a sparsely reported field can quietly remove half the universe.

Relative-to-group fields

A P/E of 15 is expensive for a utility and cheap for a software company. Comparing a multiple across sectors mostly sorts sectors, not companies: an absolute "P/E < 15" screen fills up with banks, insurers and energy producers in most years. Sector-neutral comparisons fix this by asking how a company stands against its peers. For 18 base metrics (the main multiples, margins, returns on capital, growth, 6- and 12-month price returns, leverage and volatility) Stoxa stores six relative fields per snapshot, 108 in all:

FieldMeaning
pe_pct_univpercentile 0 to 100 among all stocks on the date
pe_pct_sectorpercentile within the company's sector
pe_pct_industrypercentile within the company's industry
pe_med_sector, pe_med_industrythe sector or industry median
pe_vs_industrythe value divided by the industry median (1.0 = at the median)

Percentiles are direction-aware: higher always means better, so the cheapest P/E in an industry scores 100, the least leveraged scores 100, the least volatile scores 100. With nn valid values in the group and ties given their average position,

pcti=100⋅positionin\text{pct}_i = 100 \cdot \frac{\text{position}_i}{n}

where position 1 is the worst value and nn the best. The best stock scores exactly 100 and the worst 100/n100/n.

Worked example

An industry with five companies and P/Es of 8, 12, 15, 20 and 30. Lower is better, so the order from worst to best is 30, 20, 15, 12, 8: percentiles 20, 40, 60, 80, 100. The median is 15, so the company at 12 has pe_vs_industry = 12 / 15 = 0.8: 20% cheaper than the typical peer.

"P/E percentile in industry ≥ 70" keeps the cheapest 30% of every industry, so a screen gets cheap banks and cheap software companies alike instead of only banks.

Three rules keep the fields honest:

  • Missing stays missing. A company without a P/E gets no percentile, neither a low nor a high one, and medians use only the valid values.
  • Minimum group size of 3. A group with fewer than 3 valid values gets no percentile or median: being "best of two" says nothing. Companies with no sector or industry get none for that scope.
  • No ratio on a non-positive median. _vs_industry is empty when the industry median is zero or negative, where "1 = at the median" has no meaning (for example median revenue growth below zero).

Sector and industry come from the company's SIC code as it is today, not as it was on the screen date. For most companies this never changes; for a company that moved industries, past comparisons use the new peers. The values being compared are point-in-time; only the classification is current.

Survivorship bias

A backtest that only contains companies alive today is biased upward: the bankruptcies, the takeovers at a discount and the slow failures are missing, and those are exactly the stocks a cheap-looking screen would have bought. Stoxa does not patch this by inventing data. A company that delists simply stops having prices: a backtest drops it from the period average after its last price, and the simulator sells it at its last close. Nothing is back-filled.

The honest limit is coverage. The universe is built from the SEC's list of companies with tickers, kept from the day each company was first added. Companies that disappeared before they were ever added are not in it, so tests over the early years of the data (fundamentals start around 2009) miss some failures and read somewhat better than reality. Treat old periods with extra suspicion, and prefer rules whose advantage holds in the recent half of a test as well.

In the appThe Screener page: the universe picker above the rules, the funnel count at the right of each rule after a run, and the "relative" category in the field picker. "Save as universe" turns the current rules into a starting population for screens, backtests and simulations.

Go deeper