Skip to main content
Call /v2/compare?symbols=A,B,C to put 2–12 tickers in one scorecard, then run deeper routes on just the names worth a second look. Compare is a flat projection of snapshot and technicals fields: it ranks nothing and recommends nothing. The point is to skip three or four full snapshot calls and decide which symbols deserve them.

Symbol count is strict

The endpoint takes 2 to 12 symbols. One symbol is PARAM_INVALID (compare needs something to compare against, so use /v2/snapshot for a single name). Thirteen or more is PARAM_INVALID too; this is enforced and live-verified, not advisory. Both return HTTP 400, and the PARAM_INVALID message names the offending parameter and the bound (\symbols` needs at least 2 tickers to compare; got 1and`symbols` accepts at most 12 tickers; got 13`), so you can branch on it. Validating the count yourself before the call is still cheaper than a round trip.

Worked example: NVDA vs AMD vs INTC

Defaults give you price, 1-year return, 1-year return versus SPY, P/E, RSI, and a trend flag: enough to triage three chip names. The full response (it’s small enough to show whole):
GET /v2/compare?symbols=NVDA,AMD,INTC
{
  "data": {
    "as_of": "2026-06-06T07:56:48Z",
    "freshness": "end_of_day",
    "market_status": "closed",
    "cache_age_seconds": 2662,
    "fields_requested": [
      "price", "pct_1y", "vs_spy_pct_1y", "pe_ttm", "rsi_14", "trending"
    ],
    "symbols": [
      { "symbol": "NVDA", "price": 205.1,  "pct_1y": 46.71,  "vs_spy_pct_1y": 20.92,  "pe_ttm": 31.23,   "rsi_14": 44.01, "trending": false },
      { "symbol": "AMD",  "price": 466.38, "pct_1y": 303.13, "vs_spy_pct_1y": 277.34, "pe_ttm": 151.8,   "rsi_14": 54.55, "trending": true  },
      { "symbol": "INTC", "price": 99.17,  "pct_1y": 396.1,  "vs_spy_pct_1y": 370.31, "pe_ttm": -156.07, "rsi_14": 43.72, "trending": true, "flags": ["pe_ttm_negative_not_meaningful"] }
    ],
    "fields_omitted_by_symbol": {}
  }
}
The MCP form is stockcontext_compare({ "symbols": ["NVDA", "AMD", "INTC"] }), same envelope back. Read it in two passes. fields_requested tells you exactly which six metrics every row carries, in order; never read a value that isn’t in that list. fields_omitted_by_symbol is empty here, meaning all six resolved for all three names; when it isn’t empty, it maps each symbol to the fields that came back unavailable, and those fields are dropped from the row entirely rather than set to null. An absent field is “StockContext couldn’t supply this for this symbol,” not zero. The scorecard:
SymbolPrice1y %vs SPY 1yP/E (ttm)RSI 14Trending
NVDA205.10+46.71+20.9231.2344.01no
AMD466.38+303.13+277.34151.8054.55yes
INTC99.17+396.10+370.31-156.0743.72yes
The honest one-liner from this data:
Over the past year INTC (+396%) and AMD (+303%) crushed NVDA (+47%) and both still register as trending; NVDA carries by far the lowest P/E (31.23), AMD the highest (151.8), and INTC’s is negative (-156.07) because it’s loss-making on a trailing basis, so P/E isn’t a valuation read for it. As of 2026-06-06T07:56:48Z, end of day, market closed.
That’s a description, not a pick. The negative INTC P/E is the kind of value you must carry through verbatim rather than smooth over: it signals trailing losses, and silently dropping the sign would invent a profitable company. Compare also flags it for you: any row with a non-meaningful multiple carries a flags array, here ["pe_ttm_negative_not_meaningful"]. The array appears only when a caveat applies (other values include ev_ebitda_ttm_negative_not_meaningful, and the REIT variants pe_ttm_reit_pe_distorted / ev_ebitda_ttm_reit_pe_distorted) and is omitted entirely otherwise; read it before treating any multiple in the row as a clean number.

Fields and defaults

Omit fields and you get the six defaults above: price, pct_1y, vs_spy_pct_1y, pe_ttm, rsi_14, trending. Pass fields= (CSV in REST, an array in MCP) to request any subset of these 23: price, pct_1d, pct_1m, pct_ytd, pct_1y, vs_spy_pct_1y, range_52w_position_pct, drawdown_from_high_pct, market_cap_usd, pe_ttm, ps_ttm, pb, ev_ebitda_ttm, fcf_yield_pct, dividend_yield_pct, beta_5y_weekly, rsi_14, rsi_zone, trending, golden_cross_active, vs_sma_200_pct, distance_from_52w_high_pct. ETFs in a basket omit the stock-only fields (market_cap_usd, pe_ttm, and the rest), which then show up under fields_omitted_by_symbol for that ticker. That’s expected, not a failure.

Then deepen the finalists

Compare narrows the field; it doesn’t settle it. The mistake is calling snapshot, valuation, fundamentals, and insider for all four names before the scorecard: that’s the work compare exists to defer. Once two names are left, run the full read on just those. For “which is more reasonably priced?”, the valuation check gives each survivor its multiples against five years of its own history, the context the flat pe_ttm in this scorecard can’t.

Valuation check

Take a finalist’s P/E and read it against its own 5-year distribution.