Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ that produced it; any parameter change touching a published day mints a
new methodology_id, and prior versions stay frozen and readable under
their own keyspaces. Newest first.

## 2026-09-14

New methodology versions on every public lane: `annex_a_v0_2_calc_v17`,
`annex_a2_v0_3_calc_v17`, `h100_sxm_v1_calc_v16`, `h200_sxm_v1_calc_v16`. Each
provider's vote is now a one-hour exponentially weighted average of its own
accepted prints, a provider whose print is rejected by the outlier fence keeps
voting its last accepted value as a carried vote, and every receipt discloses the
price it cast (`smoothed_vote_usd`). `./reproduce` verifies these versions from the
disclosed cast prices.

## 2026-09-08

`./reproduce` now re-derives the as-published history end to end under the version
Expand Down
9 changes: 7 additions & 2 deletions METHODOLOGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,9 @@ Where a recorded price is known wrong by rule and the true price was never captu

The index is not a weighted average. It is an interquantile mean over votes: the weighted mean of the central third of the vote mass.

Each passing provider casts its full liveness weight three times: at its price and at its price plus and minus its own standard deviation.
Each passing provider casts its full liveness weight three times: at its cast price and at its cast price plus and minus its own standard deviation.

Since the versioned change effective 2026-09-14, a provider's cast price is the time-based exponentially weighted average of its own accepted prints (half-life 1 hour, `calc_params.pre_smoothing_half_life_hours`), not the raw print itself; carried votes (section 8.6) re-cast the provider's frozen smoothed price. Everything else published for the provider — the chosen print, the outlier-check window, the price history — stays raw: smoothing moves vote centers only. Every voting receipt disclosed its exact cast price as `smoothed_vote_usd`, so any published value remains recomputable from its own receipts without rerunning the smoothing state.

```javascript
for each passing provider i:
Expand Down Expand Up @@ -480,14 +482,15 @@ Q_i = mean of q_{i,h} over the three forwards

### 8.6 Attendance

A provider's weight also reflects whether it shows up. Each scheduled observation marks every provider: 1 if it was read successfully and produced a price (a price held out by the outlier check of section 6.4 still counts as present, since the fence keeps a print out of the index, not out of the attendance record), 0 if it was read successfully and produced none, and unchanged if our own collection or parsing failed, since a provider is never penalized for our failure.
A provider's weight also reflects whether it shows up. Each scheduled observation marks every provider: 1 if it was read successfully and produced a price (a price held out by the outlier check of section 6.4 still counts as present when the provider's own vote priced the index, since the fence keeps a print out of the index, not out of the attendance record; when the fence-reject carry below substituted the vote instead, the provider counts absent — its receipt carries a `carried_vote_from` marker), 0 if it was read successfully and produced none, and unchanged if our own collection or parsing failed, since a provider is never penalized for our failure.

The attendance factor `A_i` is the exponentially weighted average of this series over the 90-day regression window, with its own attendance half-life, normalized so a provider present throughout has `A_i` = 1. A newly seated provider's scheduled observations before it joined count as 0, so its first print starts near zero; at the 6-hour half-life, sustained printing reaches full attendance in about two days.

The missing print itself is handled by cause:

- Our own collection or parsing failure: the provider's last accepted vote (price, vote sigma, and weight) is carried forward verbatim, and attendance is unchanged. The carried weight is the one recorded at the observation it came from, so it sits outside the weights newly allocated at the current observation, and the published weights of that observation sum to more than one by that amount. A carried price never advances the provider's own price series, so it enters neither the liveness regression nor the vote sigma, and it never counts toward the minimum passing panel.
- Provider read, no usable price: attendance falls and the consecutive no-price count advances. The provider's last usable price is carried forward and fades as attendance falls.
- Provider read, price rejected by the outlier check (fence-reject carry, effective 2026-09-14, `calc_params.liveness.fence_reject_carry`): the rejected print publishes unchanged as the provider's record, but the vote cast is the provider's booked smoothed price at its current fading weight, the receipt disclosing `carried_vote_from` (the carried-from observation, with `carry_basis`) beside `smoothed_vote_usd` (the cast price). The carried vote never advances the provider's price series, never counts as attendance, and never counts toward the minimum passing panel.
- Hard cutoff: past 96 consecutive observations without a usable price (24 hours), the provider receives no weight and casts no vote until a fresh print advances its state. Exclusion is decided from the pre-observation history, so the first accepted recovery price remains visible as receipt evidence but still carries no weight or vote; it re-admits the provider at the next scheduled observation. Our own failures never advance the count.

> **Why attendance?** A new provider should not receive full weight from its first print, and a provider that stops publishing should fade rather than vanish instantly or linger stale. The half-life sets the smooth fade during a temporary absence; the hard cutoff removes persistently absent sources. Entry, fade-out, and recovery all happen without per-provider judgment.
Expand Down Expand Up @@ -711,6 +714,7 @@ Values identical across the live SKUs. SKU-bound parameters (minimum panel, FX s
| Minimum variability | 3% of price | Floor under both sigmas; a frozen price otherwise has sd 0 |
| Warm-up | 10 | Observations before the test applies |
| Vote sigma window | 90 days | Window for the sigma used in votes and the stability band (section 7.1) |
| Vote pre-smoothing half-life | 1 hour | Each provider's cast price is the time-based EWMA of its own accepted prints (section 7.1); disclosed per receipt as `smoothed_vote_usd`. Effective 2026-09-14 |
| Test currency | as quoted | Prevents FX moves ejecting a provider |
| Review flag | 15% | Distance from panel average flagging review |
| Currency-change confirmation | 3 | Consecutive observations confirming a switch |
Expand All @@ -734,6 +738,7 @@ Identical on all panels. No per-provider values.
| Attendance half-life | 6 hours | How quickly provider-side missing prints lose influence, and returning providers regain it (section 8.6) |
| Attendance sensitivity (η) | 0.5 | Strength of attendance in allocation; at 0 the softmax is unchanged. Armed at 0.5 by the versioned change effective 2026-09-01 |
| Consecutive no-price limit | 96 observations (24 h) | Hard exclusion after sustained provider-side absence; our own collection failures never count (section 8.6) |
| Fence-reject carry | armed | An outlier-rejected print's provider re-casts its booked smoothed vote, classified carried (section 8.6); disclosed as `calc_params.liveness.fence_reject_carry`. Armed by the versioned change effective 2026-09-14 |
| Minimum observations | 10 | Per provider, per window: observations needed before that provider can be scored. Below it the score is undefined |
| Minimum variation | 1e-12 | The panel must have moved for prediction to be a meaningful question. Below this variance in what is predicted, the score is undefined rather than computed from nothing. Set above price-rounding noise (roughly 1e-14), below any real movement |
| Minimum panel for transition | 5 | Whole-panel, one time: providers that must report at the observation where the index permanently switches to derived weights. Distinct from minimum observations: that asks "does this provider have enough history?", this asks "is this observation broad enough to change the methodology?" |
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,17 @@ are labeled `back-calculated`.

The raw inputs are disclosed prices and dispersions, recorded currency and FX,
upstream status, carry basis, filter verdicts, timing, top-level flags, and
`calc_params`. Published attendance factors, liveness scores, and weights are
comparison outputs, never derivation inputs. Every artifact read is digest
verified. Missing required history or withheld raw inputs cause a full-reproduction
refusal. If `history_path` is absent, the command prints a notice and uses
`calc_params`. On vote-pre-smoothing generations
(`calc_params.pre_smoothing_half_life_hours`, effective 2026-09-14) each voting
receipt also disclosed `smoothed_vote_usd` — the exact cast price the engine
aggregated — and a fence-rejected row that still voted its booked price carries
a `carried_vote_from` marker; the reproduction votes those disclosed cast prices
rather than rerunning the engine's smoothing state, keeps the raw print as
evidence, and refuses loudly if a voting row omits the disclosure (never a
raw-price fallback). Published attendance factors, liveness scores, and
weights are comparison outputs, never derivation inputs. Every artifact read
is digest verified. Missing required history or withheld raw inputs cause a
full-reproduction refusal. If `history_path` is absent, the command prints a notice and uses
`current_version` (or the legacy flat record when no version pointer exists).

Exit 0: every verifiable value matched. Exit 1 means a mismatch, invalid artifact,
Expand Down
8 changes: 5 additions & 3 deletions config/index_panel_b200.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@
}
],
"calc": {
"methodology_id": "annex_a2_v0_3_calc_v14",
"description": "calc_v14 attendance-weighted quarter-hour panel mint (annex_a2_v0_3_calc_v14); METHODOLOGY.md is the binding methodology document",
"methodology_id": "annex_a2_v0_3_calc_v17",
"description": "calc_v17 EWMA-pre-smoothed (1h half-life) fence-reject-carry attendance-weighted quarter-hour panel mint (annex_a2_v0_3_calc_v17); METHODOLOGY.md is the binding methodology document",
"availability_verified_sources": ["vast"],
"min_sources_to_claim": 5,
"eligible_tiers": [
Expand All @@ -92,6 +92,7 @@
"filter_terms": "recorded_currency",
"composite_statistic": "median_ci_votes",
"iqm_alpha": 0.16666,
"pre_smoothing_half_life_hours": 1,
"vote_sigma_source": "dw_history",
"vote_sigma_floor_pct": 3.0,
"carry_forward_window_hours": 72,
Expand Down Expand Up @@ -137,7 +138,8 @@
"attendance_floor": 0.5,
"attendance_half_life_hours": 6,
"attendance_eta": 0.5,
"no_price_exclusion_hours": 24
"no_price_exclusion_hours": 24,
"fence_reject_carry": true
}
},
"members": [
Expand Down
8 changes: 5 additions & 3 deletions config/index_panel_b300.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@
}
],
"calc": {
"methodology_id": "annex_a_v0_2_calc_v14",
"description": "calc_v14 attendance-weighted quarter-hour panel mint (annex_a_v0_2_calc_v14); METHODOLOGY.md is the binding methodology document",
"methodology_id": "annex_a_v0_2_calc_v17",
"description": "calc_v17 EWMA-pre-smoothed (1h half-life) fence-reject-carry attendance-weighted quarter-hour panel mint (annex_a_v0_2_calc_v17); METHODOLOGY.md is the binding methodology document",
"availability_verified_sources": ["vast"],
"min_sources_to_claim": 5,
"eligible_tiers": [
Expand All @@ -92,6 +92,7 @@
"filter_terms": "recorded_currency",
"composite_statistic": "median_ci_votes",
"iqm_alpha": 0.16666,
"pre_smoothing_half_life_hours": 1,
"vote_sigma_source": "dw_history",
"vote_sigma_floor_pct": 3.0,
"carry_forward_window_hours": 72,
Expand Down Expand Up @@ -152,7 +153,8 @@
"attendance_floor": 0.5,
"attendance_half_life_hours": 6,
"attendance_eta": 0.5,
"no_price_exclusion_hours": 24
"no_price_exclusion_hours": 24,
"fence_reject_carry": true
}
},
"members": [
Expand Down
8 changes: 5 additions & 3 deletions config/index_panel_h100_sxm.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@
"GH200"
],
"calc": {
"methodology_id": "h100_sxm_v1_calc_v10",
"description": "calc_v10 attendance-weighted quarter-hour panel mint (h100_sxm_v1_calc_v10); METHODOLOGY.md is the binding methodology document",
"methodology_id": "h100_sxm_v1_calc_v16",
"description": "calc_v16 EWMA-pre-smoothed (1h half-life) fence-reject-carry attendance-weighted quarter-hour panel mint (h100_sxm_v1_calc_v16); METHODOLOGY.md is the binding methodology document",
"availability_verified_sources": ["lium", "vast"],
"min_sources_to_claim": 5,
"eligible_tiers": [
Expand All @@ -85,6 +85,7 @@
"filter_terms": "recorded_currency",
"composite_statistic": "median_ci_votes",
"iqm_alpha": 0.16666,
"pre_smoothing_half_life_hours": 1,
"vote_sigma_source": "dw_history",
"vote_sigma_floor_pct": 3.0,
"carry_forward_window_hours": 72,
Expand Down Expand Up @@ -134,7 +135,8 @@
"attendance_floor": 0.5,
"attendance_half_life_hours": 6,
"attendance_eta": 0.5,
"no_price_exclusion_hours": 24
"no_price_exclusion_hours": 24,
"fence_reject_carry": true
}
},
"members": [
Expand Down
8 changes: 5 additions & 3 deletions config/index_panel_h200_sxm.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@
"H20"
],
"calc": {
"methodology_id": "h200_sxm_v1_calc_v10",
"description": "calc_v10 attendance-weighted quarter-hour panel mint (h200_sxm_v1_calc_v10); METHODOLOGY.md is the binding methodology document",
"methodology_id": "h200_sxm_v1_calc_v16",
"description": "calc_v16 EWMA-pre-smoothed (1h half-life) fence-reject-carry attendance-weighted quarter-hour panel mint (h200_sxm_v1_calc_v16); METHODOLOGY.md is the binding methodology document",
"availability_verified_sources": [
"lium",
"vast"
Expand All @@ -85,6 +85,7 @@
"filter_terms": "recorded_currency",
"composite_statistic": "median_ci_votes",
"iqm_alpha": 0.16666,
"pre_smoothing_half_life_hours": 1,
"vote_sigma_source": "dw_history",
"vote_sigma_floor_pct": 3.0,
"carry_forward_window_hours": 72,
Expand Down Expand Up @@ -134,7 +135,8 @@
"attendance_floor": 0.5,
"attendance_half_life_hours": 6,
"attendance_eta": 0.5,
"no_price_exclusion_hours": 24
"no_price_exclusion_hours": 24,
"fence_reject_carry": true
}
},
"members": [
Expand Down
10 changes: 8 additions & 2 deletions reproduce
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,15 @@
# that version's own history. --version <n> selects one version throughout.
# The calculation uses raw disclosed inputs:
# per-source prices and dispersions, status/timing facts, filter verdicts,
# carried basis, and published calculation parameters. It derives the
# carried basis, and published calculation parameters — plus, on
# vote-pre-smoothing generations (calc_params.
# pre_smoothing_half_life_hours), each voting receipt's disclosed cast
# price smoothed_vote_usd and the carried_vote_from marker on fence-rejected
# rows: votes price the engine's own disclosed casts, never a rerun of
# its smoothing state, and a voting row missing the disclosure is a
# typed refusal (no raw-price fallback). It derives the
# attendance events and factors, liveness scores, weight vector, votes,
# IQM, and final value without consuming any published derived
# IQM, and final value without consuming any other published derived
# intermediate. Insufficient observable history is a typed refusal.
#
# Under --producer, the run replays a local collection record instead
Expand Down
57 changes: 57 additions & 0 deletions src/gpu_index/index/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,22 @@ def panel_calc_params(config: Dict[str, Any]) -> Dict[str, Any]:
if "iqm_alpha" in calc
else {}
),
# EWMA vote pre-smoothing (2026-09-14 generation): CONDITIONAL
# like iqm_alpha -- absent means raw vote centers, so every frozen
# predecessor's artifact bytes stay untouched and the D2 fence owns
# the flip. NOTE this engine mirror never RUNS the smoothing:
# compute_observation refuses an armed params set loudly (the
# supported reproduction of a smoothed generation is the published
# record's own disclosed cast prices, gpu_index.published).
**(
{
"pre_smoothing_half_life_hours": float(
calc["pre_smoothing_half_life_hours"]
)
}
if "pre_smoothing_half_life_hours" in calc
else {}
),
# Vote-sigma source (ruling 2026-08-27): CONDITIONAL exactly like
# iqm_alpha and the floor pair -- absent means the legacy
# filter-window vote tail (what every already-published artifact
Expand Down Expand Up @@ -873,6 +889,16 @@ def panel_calc_params(config: Dict[str, Any]) -> Dict[str, Any]:
if attendance_minted(dw)
else {}
),
# Fence-reject carry: CONDITIONAL sub-knob of the
# attendance triple, embedded only as literal True (the
# upstream emitter rule: absent = fence rejects drop the
# vote, today's bytes; load validation guarantees the strict
# bool and the attendance_eta > 0 precondition).
**(
{"fence_reject_carry": True}
if dw.get("fence_reject_carry")
else {}
),
"fallback_weights": {
m["source_id"]: m["weight"] for m in member_params
},
Expand Down Expand Up @@ -1480,6 +1506,15 @@ def classify_attendance_source(detail: Dict[str, Any]) -> Optional[str]:
# Rule D1: chosen exists but no trustworthy filter value --
# a print the provider published that we cannot use.
return EVENT_NO_PRICE
if isinstance(detail.get("carried_vote"), dict):
# a fence-rejected print whose VOTE was substituted
# from the state-2 carry book. status/chosen/filter are the
# untouched real print (replay and jump-reference reads need
# them unchanged) -- this disclosure block is the ONLY signal
# that the seat's vote, not its presence, was carried; reads
# absent exactly like the other carry_basis "no_price"
# shapes.
return EVENT_NO_PRICE
# Trusted print: accepted, sigma-FENCED, and
# currency-mismatch-pending all count PRESENT (the fence holds a
# print out of the INDEX, never out of the presence record).
Expand Down Expand Up @@ -1759,6 +1794,28 @@ def compute_observation(
f"be the same law"
)
params = calc_params
if "pre_smoothing_half_life_hours" in params or (
params.get("dynamic_weights") or {}
).get("fence_reject_carry"):
# fail-closed guard: this mirror carries no
# EWMA vote state and no fence-reject carry path, so running an
# armed lane here would price RAW vote centers (and drop
# fence-rejected seats' carried votes) under a methodology_id
# whose law says otherwise -- a silently plausible wrong index,
# the exact class this engine refuses everywhere else. Smoothed
# generations reproduce from the published record instead: every
# voting receipt disclosed its exact cast price
# (smoothed_vote_usd), which gpu_index.published.verify /
# gpu_index.published.full consume (./reproduce <sku>).
raise ValueError(
f"calc_params for {params.get('methodology_id')!r} carry "
"pre_smoothing_half_life_hours / dynamic_weights."
"fence_reject_carry: this producer mirror does not rerun the "
"EWMA vote pre-smoothing or the fence-reject carry, so it "
"cannot price an armed lane from the raw record -- reproduce "
"smoothed generations from the published record's disclosed "
"cast prices instead (./reproduce <sku>, gpu_index.published)"
)
if schedule is None:
from gpu_index.index.panel_config import panel_schedule

Expand Down
Loading
Loading