Skip to main content
Call /v2/snapshot for the headline multiples, then /v2/valuation for the own-history context that tells you whether those multiples are high or low for this stock. Both routes are on the Free plan, so the full check costs two calls and no upgrade. Don’t reach for /v2/fundamentals here; that returns the full per-quarter arrays, and most valuation prompts only need the lean direction layer /v2/valuation already carries. The one thing this guide exists to teach: a low percentile is not a cheapness verdict. It is a rank within the symbol’s own past range. Whether a low rank is interesting depends entirely on why the multiple fell, which the response hands you directly.

The {value, vs_own_history, change} pattern

On /v2/snapshot, pe_ttm, ps_ttm, and friends each ship as a {value, vs_5y} object in the fundamentals_quick block — a value with a coarse vs_5y band. A raw pe_ttm of 31 is not yet an answer: 31 is low for some names and rich for others. /v2/valuation resolves that by wrapping every multiple as {value, vs_own_history}, and adding a change decomposition on pe_ttm, ps_ttm, and the anchor multiple:
"pe_ttm": {
  "value": 31.23,
  "vs_own_history": {
    "percentile": { "3y": 1, "5y": 0, "10y": 8 },
    "median_5y": 57.66,
    "label": "near_5y_low",
    "n": 1238
  },
  "change": {
    "1y": { "price_pct": 45.91, "fundamental_pct": 109.97, "driver": "fundamentals_outran_price" },
    "3y": { "price_pct": 422.01, "fundamental_pct": 3373.4, "driver": "fundamentals_outran_price" }
  }
}
Read it in three parts:
  • value is the current multiple: 31.23x. This is the absolute level. NVDA at 31x earnings is not cheap in absolute terms — plenty of profitable megacaps trade in the teens.
  • vs_own_history ranks that level inside the symbol’s own past. percentile is a map keyed by lookback window — { "3y": 1, "5y": 0, "10y": 8 } says today’s P/E sits at the 1st percentile of the last three years, the 0th of the last five, and the 8th of the last ten. A window appears only when there is enough clean history for it; a young symbol may show one window or none rather than three identical, false ranks. median_5y is the magnitude anchor (57.66x — the multiple has usually been far higher). label bands the 5-year percentile into one closed reading; n is the clean sample size behind it (1238 daily points here).
  • change is the decomposition that makes the rank legible: how much the price leg moved versus the fundamental leg over 1 year and 3 years, and a driver naming which leg moved the multiple.

The label vocabulary — and what near_5y_low does NOT mean

label is one of five strings, scoped to the window in the name so you can never quote it as an absolute claim:
label5-year percentilereading
near_5y_low≤ 20near the low end of its OWN 5-year range
below_5y_median20–45below its own 5-year median
near_5y_median45–55around its own 5-year median
above_5y_median55–80above its own 5-year median
near_5y_high≥ 80near the high end of its own 5-year range
When only three years of clean history qualify, the labels carry a _3y_ infix (near_3y_low, and so on) so the scope is always explicit. near_5y_low means low versus this symbol’s own five-year range. It does not mean cheap, undervalued, a buy, or low versus peers, sector, or any notion of fair value. A stock can sit at near_5y_low and still be one of the most expensive names in its industry — exactly the NVDA case above, where a near_5y_low P/E is still 31x. Report the label as what it is: a position within the symbol’s own history.

window_divergence: the rank depends on the lookback you chose

window_divergence: true fires when the included percentiles disagree by at least 35 points. It rides on the vs_own_history of whichever multiple diverges, not always pe_ttm. For live NVDA it appears on ps_ttm, whose percentile is { "3y": 4, "5y": 19, "10y": 47 } — the answer to “is this multiple low?” depends heavily on whether you look back three years or ten. When it is true, do not report a single percentile as the rank; say the rank is lookback-dependent and cite the windows. The flag is omitted entirely (not set to false) when the windows agree, or when fewer than two windows qualified, because one window cannot certify agreement — that is why NVDA’s pe_ttm (5-year 0, 10-year 8, an 8-point spread) carries no flag.

Decomposing a low percentile: did price drop, or did earnings grow?

A low percentile has two completely different causes, and they call for opposite write-ups:
  • The price leg fell — the stock sold off while fundamentals held. The multiple compressed because the numerator shrank.
  • The fundamental leg grew faster than price — earnings or sales ran ahead of the stock. The multiple compressed because the denominator outran the numerator.
The change block tells you which, per horizon. price_pct is the move in the price leg (market cap for P/x multiples, enterprise value for EV/x); fundamental_pct is the move in the matching fundamental (TTM EPS for P/E, revenue for P/S); driver classifies the pair. Note that the legs combine multiplicatively, not additively — (1 + price_pct/100) / (1 + fundamental_pct/100) reconstructs the change in the multiple — so never add the two percentages. The driver is one of:
drivermeaning
fundamentals_outran_pricethe fundamental grew faster than price; the multiple shrank because earnings/sales ran ahead
price_outran_fundamentalsprice grew faster than the fundamental; the multiple expanded
price_fell_faster_than_fundamentalsboth fell, price more; the multiple shrank on a selloff
fundamentals_fell_faster_than_priceboth fell, the fundamental more; the multiple expanded as earnings collapsed
moved_togetherneither leg meaningfully outran the other
In the NVDA example the 1-year driver is fundamentals_outran_price: price rose 45.91% while TTM EPS rose 109.97%. The P/E is at a 5-year low not because the stock dropped — it rose sharply — but because earnings more than doubled it. That is a very different story from a name whose multiple is low because the price collapsed, and your answer must say which one this is. When a horizon can’t be decomposed, you get an envelope instead of numbers: { "available": false, "reason": "fundamental_sign_change" } when EPS crossed zero, "negative_earnings_both_periods" when it was negative at both ends, or "insufficient_history_for_1y_attribution" when the daily history doesn’t reach back a full year. A multiple with a low percentile but no usable change block is weaker evidence — say so.

history_regime: is the percentile comparing today’s company to one that no longer exists?

A top-level history_regime block guards the percentile axis itself:
"history_regime": {
  "window": "5y",
  "regime_break": true,
  "drivers": [
    { "metric": "revenue", "change_x": 11.6, "direction": "growth" },
    { "metric": "eps", "change_x": 38.4, "direction": "growth" }
  ]
}
regime_break: true means revenue or TTM EPS swung by at least 3x inside the lookback — the business changed scale enough that ranking today’s multiple against its five-year history is partly ranking it against a company that no longer exists. change_x is the swing as a multiple (revenue 11.6x, EPS 38.4x here), direction whether it was growth or contraction. When this fires, the percentile is less trustworthy as a “cheap or rich” gauge: the denominator of the old multiples belonged to a much smaller company. regime_break: false is a real assertion (the scale was stable); a { "available": false, "reason": "insufficient_statement_history" } envelope means there weren’t eight quarters to judge. Read change and history_regime together: a low percentile, driven by fundamentals_outran_price, in a name with regime_break: true, is the signature of a fast-growing company whose multiple compressed as it scaled — not a discount waiting to be collected.

Worked example: is NVDA expensive?

One snapshot call, one valuation call. The valuation response for NVDA (anchor block plus the P/E multiple, regime block, and the lean trajectory shown; the live response also returns ps_ttm, pb, the EV multiples, a yields block, and returns_on_capital):
GET /v2/valuation?symbol=NVDA (trimmed)
{
  "data": {
    "symbol": "NVDA",
    "as_of": "2026-06-08T13:07:46Z",
    "freshness": "end_of_day",
    "market_status": "closed",
    "cache_age_seconds": 0,
    "market_cap_usd": 4983930000000,
    "enterprise_value_usd": 4915706000000,
    "multiples": {
      "pe_ttm": {
        "value": 31.23,
        "vs_own_history": {
          "percentile": { "3y": 1, "5y": 0, "10y": 8 },
          "median_5y": 57.66,
          "label": "near_5y_low",
          "n": 1238
        },
        "change": {
          "1y": { "price_pct": 45.91, "fundamental_pct": 109.97, "driver": "fundamentals_outran_price" },
          "3y": { "price_pct": 422.01, "fundamental_pct": 3373.4, "driver": "fundamentals_outran_price" }
        }
      }
    },
    "history_regime": {
      "window": "5y",
      "regime_break": true,
      "drivers": [
        { "metric": "revenue", "change_x": 11.6, "direction": "growth" },
        { "metric": "eps", "change_x": 38.4, "direction": "growth" }
      ]
    },
    "valuation_context": {
      "anchor": "pe_ttm",
      "rationale": "positive_earnings_period",
      "value": 31.23,
      "label": "near_5y_low"
    },
    "fundamentals_trajectory": {
      "revenue_growth": { "latest_yoy_pct": 70.68, "trend": "stable" },
      "eps_growth": { "latest_yoy_pct": 109.97, "trend": "accelerating" },
      "gross_margin": { "latest_pct": 74.93, "trend": "increasing" }
    }
  }
}
The MCP equivalent is stockcontext_valuation({ "symbol": "NVDA" }) and returns the identical envelope. valuation_context is the headline: it names which multiple to anchor on (anchor), why (rationale), and repeats that multiple’s value and label — but deliberately not a standalone percentile, so the headline can’t be quoted as a verdict. Use the per-multiple vs_own_history for the supporting detail. The correct read assembles the parts rather than quoting the label:
NVDA’s P/E is near the low end of its own five-year range — but that is a statement about its history, not a cheapness call. The anchor P/E is 31.23x (near_5y_low), against a 5-year median of 57.66x. That low rank is not a selloff: over the past year price rose 45.91% while trailing EPS rose 109.97%, so the driver is fundamentals_outran_price — the multiple compressed because earnings outran the stock, not because the stock fell. history_regime.regime_break is true (revenue swung ~11.6x and EPS ~38.4x over the window), so the five-year percentile partly ranks today’s NVDA against a much smaller company. On pe_ttm the windows agree (5-year 0, 10-year 8) so no window_divergence fires; it does fire on ps_ttm, where the 3-year (4) and 10-year (47) percentiles tell different stories. Growth is still strong (revenue +70.68% YoY stable, EPS +109.97% accelerating). At 31x earnings in absolute terms, this is a high-quality megacap whose multiple is low for itself while still rich versus the broad market — not a discount. Freshness: end_of_day, as_of 2026-06-08T13:07:46Z, market closed.
Note what that answer does not say: it never calls NVDA cheap, undervalued, or a buy. “Low versus its own five-year P/E, because earnings outran price” is a description of returned data. A recommendation is not — and “cheap” is a claim the data does not support.

Non-payers: the dividend field is absent, not zero

A multiple or yield that doesn’t apply comes back as an unavailable envelope, never a fabricated 0. TSLA pays no dividend, so its yields block reports:
GET /v2/valuation?symbol=TSLA (yields, illustrative)
"yields": {
  "fcf_yield_pct": 0.45,
  "earnings_yield_pct": 0.25,
  "dividend_yield_pct": {
    "available": false,
    "reason": "no_dividend_payer"
  },
  "buyback_yield_pct_ttm": 0.0,
  "total_payout_yield_pct_ttm": 0.0
}
Read available: false as “StockContext has no dividend yield because the company doesn’t pay one,” and write that. Reporting a 0% yield would imply a paying stock that yields nothing, which is a different and wrong claim. Where NVDA’s anchor reads near_5y_low, a name whose multiples all read near_5y_high is sitting near the top of its own range — but apply the same discipline: that is a position within its own history, decomposed the same way (did price run, or did fundamentals shrink?), not a “premium” or “sell” verdict.

ETFs: valuation is unsupported

ETFs have no earnings, so equity multiples don’t exist for them. /v2/valuation?symbol=SPY returns HTTP 200 with freshness: "unsupported", asset_type: "etf", and reason: "etf_no_valuation_multiples", not an error to retry. Say multiples aren’t available for that asset type and move on; the freshness reference covers the unsupported state and how to branch on it.

Going deeper: what is the price priced for?

The percentile tells you where a multiple sits in its own range. It does not tell you what growth the current price requires. When you need that — the actionable counterpart to a percentile — call /v2/priced-in: it solves the FCF growth rate today’s price already embeds and compares it to the company’s own realized growth. See the reverse-DCF guide.

Compare basket

Screen 2–12 names first, then run this check on the finalists only.