Methodology

How AHR999 is computed

Every field in this dataset — ma200, ahr999, quantile5y — is recomputed daily from Binance BTCUSDT daily closes by open-source TypeScript. This page documents the exact formula, constants, and window rules, so any value is reproducible by hand.

Inputs

  • Price source. Binance public endpoint GET /api/v3/klines with symbol=BTCUSDT, interval=1d. Only the close of each daily kline is kept — the repository does not redistribute raw OHLCV data.
  • Backfill start. 2017-08-17, the earliest daily bar Binance serves for BTCUSDT.
  • Cutoff. today_UTC − 1 day, the last fully closed UTC day. The current UTC day is a partial bar that would make quantile5y jitter intraday.

Constants

NameValueRole
GENESIS2009-01-03BTC genesis block date; anchor for coin age.
MA_WINDOW200Rolling window for ma200.
FIT_A5.84Slope of the log-log fitted fair-value curve.
FIT_B−17.01Intercept of the fitted curve.
QUANTILE_MIN_OBS365Minimum valid AHR observations before quantile5y is reported.
QUANTILE_WINDOW1825Rolling window size (5 × 365) once enough observations exist.

Step by step

For each UTC day d with close c_d:

1 · 200-day moving average

ma200_d = mean(close[d-199 .. d])

If fewer than 200 closes have been observed (inclusive of d), then ma200_d = null, every downstream field is null, and windowKind = "insufficient_samples".

2 · Fitted fair value

The fitted curve treats BTC as an age-anchored asset, indexed by days since the genesis block:

coin_age_days_d = floor((d − GENESIS) / 1 day)
fitted_d        = 10 ^ (FIT_A · log10(coin_age_days_d) + FIT_B)
                = 10 ^ (5.84 · log10(coin_age_days_d) − 17.01)

3 · AHR999

ahr999_d = (c_d / ma200_d) · (c_d / fitted_d)

Two ratios multiplied:

  • Momentumc_d / ma200_d: how expensive BTC is versus its own 200-day cost basis. Above 1, the market trades above the DCA cost; below 1, under it.
  • Age-anchored cheapnessc_d / fitted_d: how expensive BTC is versus its fitted long-run trajectory.

The product compresses both signals into one scalar. The folk thresholds that appear on dashboards:

RangeZone
< 0.45bargain · 抄底区
0.45 – 1.20DCA zone · 定投区
1.20 – 3.00caution · 谨慎区
> 3.00bubble · 泡沫区

These thresholds are heuristic and not part of the formal definition.

4 · 5-year quantile

quantile5y_d is the empirical rank of ahr999_d within a window of recent valid AHR values (including the current value):

quantile5y_d = count(v in window : v ≤ ahr999_d) / window.length

The window depends on how many valid AHR observations exist so far:

Valid observationsWindowwindowKindquantile5y
< 365insufficient_samplesnull
365 – 1824all observations to date (expanding)expandingreported
≥ 1825last 1,825 observations (rolling)rolling_5yreported

Discrete rank vs. interpolation

The quantile is the discrete rank count(v ≤ current) / N, not numpy's default linear-interpolation quantile. Rationale:

  • The original 九神 community implementations all use the discrete rank.
  • With ~1,800 observations the rank resolution is ≈ 0.00055 — finer than any actionable reading of the number.
  • It has a crisp meaning: "what fraction of the last five years was this cheap or cheaper?"

You can verify it by hand at the first non-null quantile row, 2019-03-03, where quantile5y = 78 / 365 = 0.2136986301369863 exactly.

Differences from other implementations

vs. the original 九神 formula

We use identical constants (FIT_A = 5.84, FIT_B = −17.01, MA_WINDOW = 200, 5-year quantile). The only deliberate choices worth knowing:

  • Price source. Binance BTCUSDT. Some older analyses used CoinMarketCap or OKX; these diverge pre-2018 but converge to ≤ 0.1% once BTCUSDT volume dominates.
  • Explicit window kind. windowKind is exposed so consumers can filter out the 365-to-1,825 expanding-window warmup.

vs. 9992100.xyz

9992100.xyz is the most popular Chinese-language AHR999 dashboard. Small daily divergences (≤ 0.02 in absolute AHR999) are expected because it may update at a different UTC-adjacent cutoff and its price source and close-timestamp semantics are undocumented. Both sites should agree on the regime on any given day; don't chase numerical parity across unrelated implementations.

Reproduce end to end

pnpm sync:backfill
pnpm export:csv
pnpm verify   # repo checkpoints assert the first quantile / rolling rows exactly

Setting AHR999_SOURCE_BASELINE_PATH=/path/to/ahr999-daily.jsonl makes pnpm verify additionally run a row-by-row compare with < 1e-9 relative-error tolerance. Without it, the command still enforces the repo-local checkpoints and dataset invariants. The full source lives in the GitHub repository.