import type { Metadata } from 'next'; import Link from 'next/link'; import type { ReactNode } from 'react'; import { ASK_ANOMALY_HIGH_RATIO, ASK_ANOMALY_LOW_RATIO, ASK_MIN_CONFIDENCE, ASK_MIN_MATCH_CONFIDENCE, ASK_MIN_SAMPLE, DEAL_REVIEW_THRESHOLD, DEAL_THRESHOLD, FEE_SCHEDULES, FEE_SCHEDULE_AS_OF, MAX_PLAUSIBLE_CHANGE, PREMIUM_THRESHOLD } from '@rareindex/valuation'; import { FeeConfidenceBadge, feeScheduleSummary } from '@/components/market/fee-schedule'; import { PageHeader } from '@/components/ui/page-header'; import { Badge, Card } from '@/components/ui/primitives'; import { cn } from '@/lib/format'; export const metadata: Metadata = { title: 'Methodology Center', description: 'How every RareIndex number is produced: RareIndex Valuation (RIV), confidence, value range, comparables, the ask-vs-RIV anomaly gate, liquidity, rarity, momentum, outliers, sales verification, indices, FX and data-honesty rules.', }; export const revalidate = 3600; /* * §203 Methodology Center. Constants that gate numbers on the site are imported from @rareindex/valuation so * this page cannot drift from the implementation. Constants that live as literals in the engine files are * quoted with their file so a reader can verify them (valuation.ts, scores.ts, outliers.ts, premiums.ts, * indices/chain.ts, workers/indices/run.ts). */ const pct = (v: number, digits = 0) => `${(v * 100).toFixed(digits)} %`; const SECTIONS: Array<{ id: string; title: string }> = [ { id: 'principles', title: 'Data honesty' }, { id: 'riv', title: 'RIV — RareIndex Valuation' }, { id: 'confidence', title: 'Confidence' }, { id: 'range', title: 'Value range' }, { id: 'distribution', title: 'Price distribution' }, { id: 'comparables', title: 'Comparables & grade premiums' }, { id: 'ask-vs-riv', title: 'Ask vs RIV & the anomaly gate' }, { id: 'liquidity', title: 'Liquidity Score' }, { id: 'rarity', title: 'Rarity Score' }, { id: 'momentum', title: 'Momentum & Trending' }, { id: 'outliers', title: 'Outliers' }, { id: 'verification', title: 'Sales verification' }, { id: 'indices', title: 'Indices' }, { id: 'fx', title: 'Currencies' }, { id: 'freshness', title: 'Freshness & limits' }, { id: 'fees', title: 'Auction fees & all-in cost' }, ]; export default function MethodologyPage() { return (
RareIndex never fabricates transactions, valuations, bids, populations, sales, historical prices or certification data. Where evidence is missing the interface shows Data unavailable or Not enough data. Valuations are estimates from observed public sales, not offers or appraisals; RareIndex does not authenticate items.
Inputs are valid transactions of the variant: status valid, price > 0, single item (bundles and multi-quantity lots excluded). The default window is 365 days; when fewer than five recent sales exist and older ones do, it is extended to 3 years and confidence is capped at 70 %.
A guide value is a price published by a price guide or marketplace (e.g. a “market price” cell). It is an observation, not a transaction; in the interface it appears as Guide · source, never as RIV, and guide-based RIVs are labelled with a capped confidence. The headline RIV of an asset is the valuation of its representative variant: the default (raw / base) variant when it has a transaction-based RIV with ≥ {ASK_MIN_SAMPLE} sales and confidence ≥ {pct(ASK_MIN_CONFIDENCE)}, otherwise the qualifying variant with the most sales. Period changes are measured on that variant's own series so a change of representative never shows as a price move.
packages/valuation/src/valuation.ts · workers/valuation/run.ts
Labels:
The band is always at least ±5 % around RIV. On asset pages it is drawn as the shaded band around the RIV line; the current RIV and latest sale are marked on the range meter.
For the variant and window, RareIndex reports count, minimum, P25, median, P75, maximum and the 10 % trimmed mean of verified sale prices in USD at historical FX. Flagged outliers (§11) are shown in sales tables for audit but excluded from these statistics.
For each category, grader and grade, the grade premium is the median, across assets, of median(graded price) / median(raw price) of the same asset, from at least 8 paired assets. A comparable from grade A to grade B is adjusted by premium(B) / premium(A). Premiums exist only where paired evidence exists: PSA 10, BGS 10 and CGC 10 are never assumed equivalent, and no cross-grader factor is applied without data.
Comparable sales on asset pages carry a transparent similarity score (same set, same grade, same variant, price proximity). Grade curves (price by grade) and premiums are published on the Grades tab and on /grading.
packages/valuation/src/premiums.ts (computeGradePremiums, adjustmentFactor)depth = min(1, −discount / 50 %), sample = log10(1 + n) / log10(41). The components are multiplicative: a huge discount on an illiquid or poorly identified asset cannot score high. The score is 0 for anything that is not a gated discount. Period changes of a valuation beyond ±{MAX_PLAUSIBLE_CHANGE * 100} % are likewise treated as data artefacts and not published as movers.
Value opportunities and Deal Radar are analytical data, not investment advice: a discount can also mean a misidentified, damaged or incomplete item.
packages/valuation/src/scores.ts (assessAsk, dealScore, isPlausibleChange) · workers/valuation/run.tsLabels used in the interface: Highly liquid ≥ 75, Moderately liquid ≥ 50, Illiquid ≥ 25, Extremely illiquid below.
packages/valuation/src/scores.ts (liquidityScore)Weights are renormalised over the signals that exist for the asset; a population growing more than 20 % between reports multiplies the score by 0.9. With no signal at all the score is null. Population figures come only from published grading-company reports and are shown with their report date.
packages/valuation/src/scores.ts (rarityScore)Horizons 7 d / 30 d / 90 d / 1 y; Δprice is the change of the representative variant's RIV against its own history. Trending is the geometric mean of the available momenta mapped to 0–1 (price 30 d, transaction volume, listing depth); it requires at least two components. Movers rails require ≥ {ASK_MIN_SAMPLE} transactions, confidence ≥ {pct(ASK_MIN_CONFIDENCE)}, ≥ 3 sales in the last year and a plausible move (≤ ±{MAX_PLAUSIBLE_CHANGE * 100} %).
packages/valuation/src/scores.ts (momentumScore, trendingScore)
Within a variant with at least five valid sales, the modified z-score of each log price, 0.6745 · (x − median) / MAD, is computed; sales beyond 3.5 are flagged abnormally_high_price / abnormally_low_price and set to status flagged (when MAD is 0, anything beyond 3× the median is flagged). Bundles and multi-quantity lots are excluded at ingestion. Flagged and excluded sales stay visible in sales tables for audit and are written to the audit log with their reason; they do not enter RIV, distributions or indices.
A sale is a completed-transaction record captured from a public source (auction result page, completed marketplace listing, price-guide sold feed). Each sale stores its status (valid, flagged, excluded), an identification confidence (0–1: the weaker of the record's own confidence and the entity-resolution confidence; deterministic identifiers 0.99, canonical key 0.96, fuzzy match ≤ 0.90), the trust score of its source (0–1, weighting valuations), flags (bundle, zero_price, low_identification_confidence, outlier reasons) and whether the buyer premium is known to be included.
Record-sale and “verified transactions” views use status valid with confidence ≥ 0.8. Duplicate captures are collapsed by source, external id or URL, date and price. RareIndex does not yet publish a separate “likely / unverified” verification ladder or cross-source duplicate merging of the same physical transaction; where a transaction is only reported second-hand it appears as a price observation, not a sale.
Indices without a published value are listed as in development with their constituent count against the minimum, outside the primary ticker. Every daily value stores its constituent count and coverage. A repeat-sales estimator exists for research and is not a published index.
packages/indices/src/chain.ts · workers/indices/run.ts
Each sale stores price, currency, price_usd, fx_rate and fx_date. Rates are ECB reference rates (via frankfurter), base USD, applied at the transaction date (previous business day for weekends and holidays); historical sales are never converted at today's rate. A missing rate blocks the record rather than approximating it. Display currencies (USD, CAD, EUR, GBP, JPY, …) are converted at view time from the stored USD value.
Connectors run on schedules tuned to each source; every statistic shows its last update and sample size. Categories with few public transactions show fewer valuations and no index rather than a fabricated one. Listing prices are not confirmed transactions; past performance does not guarantee future results; RareIndex does not authenticate items and its valuations are not appraisals.
Live coverage, connectors and FX ranges →
Live lots are assessed on the same basis: the current bid (or, without a bid, the low estimate — labelled “est.”) is converted to USD at the current ECB rate, the premium is added, and the result is compared with the RIV of the lot's variant under the same gates as listings (transaction-based RIV, ≥ {ASK_MIN_SAMPLE} sales, confidence ≥ {pct(ASK_MIN_CONFIDENCE)}, plausibility band, review threshold). Opening prices without a bid are never called deals on their own.
“Published” = read from the house's terms at that date. “≈ Approximate” = widely reported schedule not re-verified — every all-in figure built on it is shown with ≈ and the pill “premium added (≈ estimated)”. Houses not listed fall back to a labelled default of 22 %.
| House | Buyer's premium | Currency | Confidence |
|---|---|---|---|
| {s.source ? {s.name} : s.name} | {feeScheduleSummary(s)} | {s.currency} |
{String(n).padStart(2, '0')}
{lead}
{children}
; } function Code({ children }: { children: ReactNode }) { return{children};
}
function Formula({ children }: { children: ReactNode }) {
return {children}
; } function Ref({ children }: { children: ReactNode }) { returnSource: {children}
; } function Kind({ tone, name, desc }: { tone: 'gain' | 'index' | 'rarity' | 'alert'; name: string; desc: string }) { return ({desc}
| {h} | ))}
|---|
| {c} | ))}