Skip to content

Stock Screener

market-feed/screener filters a list of symbols against a set of criteria using live quote data. All criteria are evaluated with AND logic — a symbol must pass every criterion to be included.

Basic usage

ts
import { screen } from "market-feed/screener";
import { MarketFeed } from "market-feed";

const feed = new MarketFeed();

// Find large-caps that gained > 1.5% today on above-average volume
const results = await screen(feed, ["AAPL", "MSFT", "GOOGL", "TSLA", "NVDA", "META"], {
  criteria: [
    { type: "market_cap_above", value: 100_000_000_000 },  // > $100B market cap
    { type: "change_pct_above", value: 1.5 },               // up > 1.5% today
    { type: "volume_above",     value: 10_000_000 },        // > 10M shares traded
  ],
  limit: 5,
});

for (const r of results) {
  console.log(`${r.symbol}: $${r.quote.price.toFixed(2)} (+${r.quote.changePercent.toFixed(2)}%)`);
}

Criterion types

TypePasses when
price_abovequote.price > value
price_belowquote.price < value
change_pct_abovequote.changePercent > value
change_pct_belowquote.changePercent < value (use negative values for down days)
volume_abovequote.volume > value
volume_belowquote.volume < value
volume_vs_avg_abovequote.volume > quote.avgVolume * value — e.g. value: 2 means 2× average volume (pass-through if avgVolume undefined)
volume_vs_avg_belowquote.volume < quote.avgVolume * value (pass-through if avgVolume undefined)
market_cap_abovequote.marketCap > value (excluded if marketCap is undefined)
market_cap_belowquote.marketCap < value (excluded if marketCap is undefined)
52w_high_pct_belowPrice is within N% of the 52-week high
52w_low_pct_abovePrice is at least N% above the 52-week low
custom{ type: "custom", fn: (quote: Quote) => boolean }

Custom predicate

ts
const results = await screen(feed, symbols, {
  criteria: [
    // P/E proxy: price-to-earnings approximation using market cap and revenue
    {
      type: "custom",
      fn: (q) => q.price > 50 && (q.volume ?? 0) > q.avgVolume! * 1.5, // volume surge
    },
  ],
});

52-week range criteria

ts
// Stocks within 5% of their 52-week high (near breakout territory)
{ type: "52w_high_pct_below", value: 5 }

// Stocks that have recovered at least 20% from their 52-week low
{ type: "52w_low_pct_above", value: 20 }

INFO

If quote.fiftyTwoWeekHigh or quote.fiftyTwoWeekLow is undefined, the criterion passes automatically (does not filter).

Batching large symbol lists

For large universes, batch the quote() calls to avoid rate limits:

ts
const sp500 = ["AAPL", "MSFT", /* ...498 more */];

const results = await screen(feed, sp500, {
  criteria: [
    { type: "change_pct_above", value: 3 },
    { type: "volume_above", value: 5_000_000 },
  ],
  batchSize: 50,   // fetch 50 symbols per quote() call
  limit: 20,       // stop after 20 matches
});

Options

ts
interface ScreenerOptions {
  /** Array of criteria — ALL must pass (AND logic). */
  criteria: ScreenerCriterion[];

  /**
   * Max symbols per quote() call.
   * Useful for providers with per-request symbol limits.
   * Default: all symbols in one call.
   */
  batchSize?: number;

  /**
   * Max results to return.
   * Stops processing as soon as this count is reached.
   * Default: all matching symbols.
   */
  limit?: number;
}

Result shape

ts
interface ScreenerResult {
  symbol: string;
  quote: Quote;
  matchedCriteria: number;  // always equals criteria.length
}

Duck-typed source

screen() accepts any object with a quote(symbols[]) → Quote[] method. This means it works with MarketFeed, any individual provider, or a test mock:

ts
import { PolygonProvider } from "market-feed";

const polygon = new PolygonProvider({ apiKey: process.env.POLYGON_KEY! });

// Use a single provider directly (no fallback, no cache)
const results = await screen(polygon, symbols, { criteria });

Common patterns

Momentum scan — top movers

ts
const results = await screen(feed, universe, {
  criteria: [
    { type: "change_pct_above", value: 5 },
    { type: "volume_above",     value: 1_000_000 },
  ],
  limit: 10,
});

Near 52-week high with volume confirmation

ts
const results = await screen(feed, universe, {
  criteria: [
    { type: "52w_high_pct_below", value: 3 },    // within 3% of yearly high
    { type: "volume_above",       value: 500_000 },
    { type: "price_above",        value: 10 },    // exclude micro-caps / penny stocks
  ],
});

Oversold large-caps

ts
const results = await screen(feed, universe, {
  criteria: [
    { type: "change_pct_below",  value: -5 },
    { type: "market_cap_above",  value: 10_000_000_000 }, // > $10B
  ],
});

Volume surge — relative to average

ts
// Stocks trading at 3× or more their 30-day average volume
const results = await screen(feed, universe, {
  criteria: [
    { type: "volume_vs_avg_above", value: 3 },
    { type: "price_above",         value: 5 }, // exclude penny stocks
  ],
});

Released under the MIT License.