DOCUMENTATION

Screener

Describe a setup in plain English, or set the bounds yourself. Either way the screen runs on live quotes and our own stored daily bars — and tells you, per query, exactly which of your criteria it enforced.

The boundary, stated once

Price, RSI, day change, ATR, distance below the 60-session high and sector are the filters it applies. Other criteria are ignored.

That sentence is not marketing copy with a caveat attached — it is derived from the filter engine itself, and a test fails the build if the two stop agreeing. Ask for something outside it and the results panel shows an Ignored chip naming what was dropped, rather than quietly returning rows that answer a narrower question than you asked.

Every filter

Ten numeric bounds and a sector list. All are optional; combining them narrows. A bound of 0 is a real bound, not an empty box.

URL keyMeaning
min_priceLast traded price at or above this.
max_priceLast traded price at or below this.
min_rsiRSI(14) at or above this — computed from our stored daily closes.
max_rsiRSI(14) at or below this. Classic “oversold” is 30 and under.
min_change_pctsignedToday’s percent change at or above this. Use a negative number for “down less than”.
max_change_pctsignedToday’s percent change at or below this. “Down more than 2%” is −2.
min_atr_pctATR(14) at or above this percent of price — the wide-daily-range end.
max_atr_pctATR(14) at or below this percent of price — the calm end.
min_pct_from_highAt least this far below the 60-session high — the beaten-down end.
max_pct_from_highNo more than this far below the 60-session high. 0 means at the high.
sectorsOne or more sectors from our universe. A symbol with no sector on record is excluded when you constrain sector — never admitted on a guess.

Those keys are the URL, so any screen is a shareable link — and the same keys the API takes.

Where the numbers come from

Price and today’s change are live vendor quotes. RSI(14), ATR(14) and the 60-session high are computed here, from the 60 daily bars we store per symbol — not read from a vendor field, so they are reproducible and consistent across every surface.

A symbol whose history is too thin to compute a bound is excluded when you constrain that bound, and shows an em dash rather than a zero. The screener will drop a row before it will guess one.

Plain English, and when no model runs

A typed sentence is read by a language model, which extracts bounds from the list above; the response names which model did it. Setting the bounds yourself runs no model at all — the filters are already structured, so they are sent as-is and the screen is deterministic. The same filter engine applies them either way.

A model asked for “high-volume breakouts under $50” will happily invent a volume constraint. That is what the Ignored chips are for: the screen reports every bound it was handed and could not enforce.

What it deliberately cannot do

Freshness

The default ranked scan is cached and served straight from Redis for 60s, then refreshed off the request path; the page shows the real age of what you are looking at, and marks a scan served during a feed outage as stale rather than live. A custom screen always runs fresh against the snapshot.

Open the screener →