Inputs
- Price source. Binance public endpoint
GET /api/v3/klineswithsymbol=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 makequantile5yjitter intraday.
Constants
| Name | Value | Role |
|---|---|---|
GENESIS | 2009-01-03 | BTC genesis block date; anchor for coin age. |
MA_WINDOW | 200 | Rolling window for ma200. |
FIT_A | 5.84 | Slope of the log-log fitted fair-value curve. |
FIT_B | −17.01 | Intercept of the fitted curve. |
QUANTILE_MIN_OBS | 365 | Minimum valid AHR observations before quantile5y is reported. |
QUANTILE_WINDOW | 1825 | Rolling 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:
- Momentum —
c_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 cheapness —
c_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:
| Range | Zone |
|---|---|
< 0.45 | bargain · 抄底区 |
0.45 – 1.20 | DCA zone · 定投区 |
1.20 – 3.00 | caution · 谨慎区 |
> 3.00 | bubble · 泡沫区 |
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 observations | Window | windowKind | quantile5y |
|---|---|---|---|
< 365 | — | insufficient_samples | null |
365 – 1824 | all observations to date (expanding) | expanding | reported |
≥ 1825 | last 1,825 observations (rolling) | rolling_5y | reported |
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.
windowKindis 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.