DEV Community

grahammccain
grahammccain

Posted on

We built a stock screener that ranks by outcome — and only trusts magnitude, not direction

Every stock screener ranks by the same things: RSI under 30, price above the 50-day, a volume spike, a MACD cross. They rank by what the chart looks like right now. None of them rank by the thing you actually care about — what setups that looked like this did next.

So we built one that does. And building it forced us to confront an uncomfortable, useful truth about chart patterns.

The idea: rank by the cohort's outcome

Chart Library retrieves, for any (symbol, date), a cohort of ~40–50 historically similar setups across 10 years and 19K+ symbols, then looks at what those analogs actually did over the next 1/3/5/10 days — the full forward-return distribution, every number a real historical fact, not a generation.

The Outcome Screener runs that across the whole market and ranks names by their cohort's expected move. Instead of "show me oversold stocks," you ask "show me names whose look-alikes historically made the biggest moves next." That's a fundamentally different — and more honest — sort key.

The uncomfortable truth: shape predicts size, not direction

Here's what fell out when we validated it on 300K+ historical cohort observations:

  • Direction is ~unpredictable. The correlation between a cohort's historical mean return and the anchor's realized direction is ≈ −0.015 — a coin flip. A "bullish-looking" cohort wins 52% of the time vs a 54% baseline. There's no there there.
  • Magnitude is predictable. The correlation between cohort dispersion and the realized absolute move is ≈ +0.20, and it's monotonic across quintiles — wider cohort, bigger realized move, reliably.

So we made a deliberate product decision: the screener ranks by expected magnitude, and refuses to rank by predicted direction. Most "AI alpha" tools quietly do the opposite — they sell you a direction they can't actually call. We'd rather ship the signal that survives validation.

And expected magnitude is useful even without a direction call — it's exactly what you need for option premium, position sizing, and stop placement. Knowing a name is primed for a 12% move (vs a 4% one) changes how you trade it regardless of which way it breaks.

A detail that mattered: robust estimators

The first version ranked a mega-cap to the top with a "320% expected move." It wasn't signal — it was an outlier-corrupted standard deviation. Naive std/mean over a cohort's forward returns gets wrecked by a single 10x penny-stock day.

The fix was to estimate dispersion robustly:

  • For raw returns: MAD × 1.4826 (median absolute deviation — the consistent estimator of σ for a normal), falling back to std only when MAD degenerates.
  • For stored percentiles: IQR / 1.349 (the normal-consistent σ from the interquartile range), with a sanity guard.
import numpy as np

def robust_sigma(returns: np.ndarray) -> float:
    """Dispersion that ignores the one insane bar instead of being dragged by it."""
    med = np.median(returns)
    mad = np.median(np.abs(returns - med))
    sigma = 1.4826 * mad
    return sigma if sigma > 0 else float(np.std(returns))
Enter fullscreen mode Exit fullscreen mode

After the swap, the rankings became sane — and real. AMBA, for instance, surfaces with a ~12% expected move at ~1.9× its own baseline volatility, instead of a phantom mega-cap at 320%.

Use it three ways

  1. UI: chartlibrary.io/screener, plus Strategy Lab to backtest cohort-conditioned rules against base rates.
  2. API: GET /api/v1/screener returns ranked names with expected move + volatility ratio.
  3. Agent / MCP: pip install chartlibrary-mcp gives Claude, Cursor, or a LangChain agent the same cohort tools. It's in the official MCP Registry.

The takeaway

A screener that ranks by outcome instead of appearance is more useful — but only if it's honest about what's actually predictable. Shape tells you how big, not which way. Build on the part that survives the backtest, and say so out loud.

Top comments (0)