TCI‑CRI‑H100$3.25TCI‑CRI‑H100‑7D— GAPTCI‑CRI‑H100‑MKT— GAPTCI‑CRI‑COMPUTE100.0TCI‑CRI‑H100$3.25TCI‑CRI‑H100‑7D— GAPTCI‑CRI‑H100‑MKT— GAPTCI‑CRI‑COMPUTE100.0TCI‑CRI‑H100$3.25TCI‑CRI‑H100‑7D— GAPTCI‑CRI‑H100‑MKT— GAPTCI‑CRI‑COMPUTE100.0
AS OF 2026-09-07 19:44:27 UTC
v0.3.0-dev iosco-aligned reproducible

The reference price, defined precisely.

TCI‑CRI‑H100 is the headline series: one NVIDIA H100 SXM GPU-hour, on-demand, per GPU, ex-VAT, from an EU/EEA data centre. Companion series cover market segments, additional GPU generations, and a chain-linked composite that follows the market across hardware cycles. Everything below is read from config/factors.yaml at build time, so no table on this page can drift from the calculation. Series identifiers quoted as code below are the stored keys, which the published files still use.

Version v0.3.0-dev Lock sha256:5c61ce6b3b8b577e… Print date 2026-09-07 Change procedure

Reference definition

GPU modelH100 SXM
TermOn-demand, no commitment
LocationEU/EEA data centre
Node size≥2 GPUs; sub-node offers excluded
UnitPer-GPU-hour, ex-VAT, excluding storage and metered egress
CurrencyUSD primary, EUR companion at the ECB reference rate (T−1)

Compute classes

A class prices its reference variant only. No assumed cross-variant normalisation factor enters the calculation path — a variant re-enters a class only once a factor is measured from same-venue, same-day, same-SKU pairs.

ClassReference variant
H100H100 SXM
H200H200 SXM
B200B200 SXM
B300B300 SXM
A100A100 SXM
H100PH100 PCIE

Market segments

The headline draws from marketplace, neocloud only. The constituent distribution is bimodal — the hyperscaler catalog sits 5.4 standard deviations away — so it is published as its own series rather than averaged in.

Hyperscaler

aws, azure, gcp

Catalog list prices. Published as their own series.

Marketplace

runpod, vast.ai

Executable quotes with a demonstrated node size.

Neocloud

datacrunch, genesis_cloud, hetzner, leaseweb, nebius, ovhcloud, scaleway, seeweb

Published list prices from specialist operators.

How the print is computed

Every offer is weighted by GPU count rather than counted once per provider: a median over six providers is a step function, a median over many weighted offers is locally smooth. Below the provider floor the value is published as a gap, never fabricated.

unit: offer # weighted over offers, not one price per provider
estimator: weighted_median
min_providers: 5 # below this: value=null, flag=insufficient_sources
min_offers: 5
trim: count-based (k=1 at n≥5, k=2 at n≥10, k=3 at n≥20)
smoothing_days: 7 # headline companion series only

TCI-CRI‑M · construction

Hash-locked. A change to any row requires a version bump, a CHANGELOG entry, and one publication's notice.

v0.3.0-dev · sha256:5c61ce6b…
  1. #StepParameter
  2. 01

    Reference unit

    One H100 SXM GPU-hour, on-demand, delivered from the EU/EEA, per GPU, ex-VAT, excluding storage and metered egress. A class prices its reference variant only.

    6 classes
  3. 02

    Unit filters

    Offers below 2 GPUs are excluded rather than normalised: the per-GPU discount saturates at 2 GPUs, while a 1-GPU offer carries a small-order premium. Sanity band $0.25–$25.00. Excluded tiers: interruptible, spot, community.

    ≥2 GPU
  4. 03

    Market segment

    The constituent distribution is bimodal, so series never average across the neocloud/hyperscaler gap. The headline draws from marketplace, neocloud.

    3 segments
  5. 04

    Staleness

    Manually verified static entries warn at 45 days and are excluded at 90.

    90 d
  6. 05

    Provider weight

    A provider with any executable offer is weighted 2× a list-only provider. Capacity does not enter at provider level: it is unobservable for every list source.

    ×2.0
  7. 06

    Concentration cap

    No provider may exceed this share of total weight; the excess is redistributed pro-rata to a fixed point. Mathematically inert at n=4; binds from n=5. Every print publishes whether it bound.

    25%
  8. 07

    Offer spread

    Each provider's share is spread across its own offers in proportion to observed capacity, capped at 64 GPUs, defaulting to 8 where capacity is unobservable.

    cap 64
  9. 08

    Trim

    Count-based: clamp the k highest and k lowest offer prices to the k-th order statistic. Percentile winsorising is inert at this panel size — at n=6 both p5/p95 and p10/p90 resolve to (min, max) and clamp nothing.

    n≥5→k=1 · n≥10→k=2 · n≥20→k=3
  10. 09

    Estimator

    Weighted median over offers: the first price at which cumulative weight reaches 50%. It always lands on a price someone actually quoted.

    over offers
  11. 10

    Publication gate

    Fewer than 5 qualifying providers or fewer than 5 qualifying offers publishes no value, flagged insufficient_sources / insufficient_offers. There is no fallback waterfall.

    ≥5 / ≥5
  12. 11

    EUR companion

    USD value divided by the most recent ECB reference rate dated on or before the print date, refused beyond 7 days old. The ECB publishes after the cut-off, so the EUR leg is T−1 by construction.

    ≤7 d
  13. 12

    Smoothing companion

    The headline's 7-day mean, requiring at least 4 non-null days. Published beside the headline, never as it.

    7 d
  14. 13

    Composite

    Chain-linked over class sub-indices from a base of 100, class shares floored at 5% and capped at 75%. A class that gaps drops out of that day's link.

    base 100
  15. 14

    Quality flag

    A constituent moving more than 30% day-over-day is flagged for review and is never silently excluded.

    30%
  16. 15

    Revisions

    Observations and prints are append-only, enforced by database triggers. An error is corrected as a new revision flagged correction; prior revisions stay queryable forever.

    append-only

Audit and governance hooks

How a third party checks this print without asking us

# the full constituent set behind any print
python -m tci.run constituents --date 2026-09-07 --series EU-CRI-H100

# the weight review in effect on a date, recomputed from stored observations
python -m tci.run weights --date 2026-09-07

# regenerate this document and the lock from config
python -m tci.run docs
  1. A1

    Methodology lock

    A sha256 over factors.yaml, sovereign.yaml, index.py, normalise.py and weights.py. CI fails whenever the working tree stops matching it.sha256:5c61ce6b3b8b577e91deb2d48844b4e6c1a218290df5959b1f28d5de358a6c82

    v0.3.0-dev
  2. A2

    Machine-readable prints

    The full published history, latest revision per date and series, plus today's snapshot with its constituent set.

    CSV · JSON
  3. A3

    Complaints

    Any print may be challenged. Acknowledged within 7 days; the outcome is published with the next print, whether it is a correction or a rationale for no change.

    P13

TCI Methodology — v0.3.0-dev

TCI (The Compute Indices) is a daily reference price for renting AI compute delivered from data centres physically located in the EU/EEA. Headline series: TCI-CRI-H100, in USD per GPU-hour with a EUR companion at the ECB reference rate.

TCI is a price-transparency benchmark, not a settlement benchmark. Every print is reproducible by any third party from public sources using the published code. It is not transaction-based and must not be referenced in a financial contract. The conditions that would have to be met before settlement use is credible are published in §9.

1. Unit definition

One TCI-CRI-H100 unit = one NVIDIA H100_SXM 80GB GPU-hour, on_demand (no term commitment), datacenter-hosted, delivered from the EU/EEA, per-GPU, ex-VAT, excluding storage and metered egress.

A class prices its reference variant only. Cross-variant normalisation by assumed factors was removed in v0.3.0: H100 SXM and H100 PCIe are different products, and H100→H200 normalisers disagree in sign depending on whether you divide by BF16 PFLOPs (+31%) or HBM bandwidth (−9%). A variant may enter a class only under a factor measured from same-venue, same-day, same-SKU pairs.

ClassReference variantVariants (factor)
H100H100_SXMH100_SXM (x1)
H200H200_SXMH200_SXM (x1)
B200B200_SXMB200_SXM (x1)
B300B300_SXMB300_SXM (x1)
A100A100_SXMA100_SXM (x1)
H100PH100_PCIEH100_PCIE (x1)

Excluded outright: variants not listed above, community/consumer hosts, interruptible/spot tiers (interruptible, spot, community), term-committed prices, offers below 2 GPUs, prices outside the sanity band [$0.25, $25.00] per GPU-hour.

Why the node floor is 2, not 8. Measured within one venue on one day, the per-GPU discount saturates at 2 GPUs (1×=1.000, 2×=0.951, 4×=0.916, 8×=0.916), so 2/4/8-GPU offers are mutually comparable within ~4% and need no adjustment, while the 1-GPU offer carries a ~9% small-order premium and is excluded rather than normalised away. The former 8-GPU floor admitted marketplace inventory on 1 collection day in 10 and discarded essentially all of the index's price discovery.

2. Data hierarchy and market segments (IOSCO P8)

Executable marketplace asks are preferred over list prices and carry a weight multiplier. Overlay data (power prices) never enters the calculation. Competitor indices are never ingested.

The constituent distribution is bimodal — measured separation of 5.4 standard deviations between the neocloud/marketplace cluster and the hyperscaler catalog cluster. Averaging across that gap yields a number no one quotes, so series are segregated by market segment and never drawn across it:

SeriesPopulation
EU-CRI-H100 (headline)marketplace, neocloud
EU-CRI-H100-MKTmarketplace
EU-CRI-H100-HShyperscaler
EU-CRI-H100-SOVmarketplace, neocloud (further filtered by config/sovereign.yaml)

3. Aggregation (exact algorithm)

Per UTC day and series. The unit of aggregation is the offer, not the provider.

  1. Collect all observations for the day passing the unit filters above, restricted to the series' market-segment population.
  2. Static (manually verified) entries older than 90 days are excluded as stale (warning from 45 days).
  3. Provider weight = 2 if the provider has any executable offer, else 1. Capacity does not enter here: it is unobservable for every list source, so a capacity term at provider level is fiction that made a rate card disclosing nothing outrank a marketplace disclosing a real 2-GPU offer.
  4. Concentration cap: no provider may exceed 25% of total weight; excess is redistributed pro-rata, iterated to a fixed point (the cap relaxes to 100/n when n providers cannot satisfy it). Note this cap is mathematically inert at n=4 — it forces exactly equal shares — and binds only for n≥5. Every print publishes whether it bound.
  5. Spread each provider's share across its own offers in proportion to offer capacity. Capacity is genuinely observable here, between offers from the same venue.
  6. Trim: clamp the k highest and k lowest offer prices to the k-th order statistic from each end, k by panel size (n≥5 → k=1; n≥10 → k=2; n≥20 → k=3). Count-based, because nearest-rank percentile winsorising is inert at this panel size — at n=6 both p5/p95 and p10/p90 resolve to (min, max) and clamp nothing.
  7. Weighted median over offers: sort by price ascending; the value is the first price at which cumulative weight reaches 50%. A median over ~6 providers has a delta of 1.0 to one constituent and 0.0 to every other; a median over many capacity-weighted offers is locally smooth and always lands on a price someone actually quoted.
  8. Publish gate: fewer than 5 qualifying providers or fewer than 5 qualifying offers → no value is published, flagged insufficient_sources / insufficient_offers. There is no fallback waterfall. A gap is credible; a fabricated print is fatal.
  9. EUR companion = USD value ÷ the most recent ECB EUR/USD reference rate dated on or before the print date. The ECB publishes ~14:00 UTC, after the 11:00 UTC cut-off, so the EUR leg is T-1 by construction and says so rather than pretending otherwise. Providers quoting natively in EUR are converted from their native amount at print time.
  10. Headline companion: 7-day mean of daily prints (requires ≥4 non-null days).

Constituent prices moving more than 30% day-over-day are flagged (jump) for manual review but are not excluded. A print with no executable input is flagged no_executable_input — a list-price-only print says so on its face.

3.1 Continuity series

The headline is the raw daily cross-section, not a chain-linked level. A Laspeyres link with a same-day divisor reset contributes exactly zero return on panel entry and exit, which makes in-panel up-moves permanent while down-moves taken via exit and re-entry are laundered out — a ratchet worth roughly +7.8% per cycle for a constituent at the 25% cap. A chained level is published beside the headline as a labelled companion, and the divergence between them is a published health metric with a 5% review trigger.

3.2 TCI-CRI-COMPUTE (chain-linked class composite)

The composite aggregates the class series into one level (base 100 at its first print), chain-linked so that reweighting never jumps the published level:

  • At each weight review, class basket shares = each class's share of total observed qualifying capacity over the window. With ≥2 eligible classes, shares are capped at 75% and floored at 5% (pro-rata redistribution, as in §3 step 6).
  • Daily: composite(t) = composite(t−1) × Σ share × (class(t) / class(t−1)), summed over classes with a published value on both endpoints, shares renormalised over those classes. A class that gaps drops out of that day's link; if no class links, the composite gaps (no_linkable_series).
  • As the observed market migrates across hardware generations (H100 → B200), the basket follows mechanically — no methodology change required.

4. Series

SeriesConstituents
EU-CRI-H100all qualifying H100-class providers (headline)
EU-CRI-H100-7D7-day mean of the headline
EU-CRI-H100-SOVEU/EEA-headquartered operators: datacrunch, genesis_cloud, hetzner, leaseweb, nebius, ovhcloud, scaleway, seeweb
EU-CRI-H100-MKTmarketplace segment only
EU-CRI-H100-NCneocloud segment only
EU-CRI-H100-HShyperscaler catalog segment only
EU-CRI-H100-PCIEH100 PCIe, priced as its own class (no assumed SXM factor)
class series (EU-CRI-A100, EU-CRI-H200, EU-CRI-B300, …)one per observed class in §1; published once ≥5 providers exist (gapped, with audit trail, before that)
EU-CRI-COMPUTEchain-linked composite of class series (§3.2); a level, not a $/hr price

EU-CRI-H100-CLOUD was retired in v0.3.0 and is not published. Its historical values remain in index_history.csv under the methodology version that produced them.

Segment size and the publication gate. A sub-population series can only print when its segment holds at least min_providers (5) qualifying providers. EU-CRI-H100-MKT and EU-CRI-H100-HS draw on segments smaller than that and therefore gap by construction rather than by circumstance. This is stated here because a permanent gap and a temporary one look identical on the dashboard, and the difference matters to a reader.

5. Revisions and corrections

Raw observations and published prints are append-only (enforced by database triggers). Errors are corrected in the next print as a new revision flagged correction; prior revisions remain queryable forever. The full constituent set for any print is available via python -m tci.run constituents --date YYYY-MM-DD.

6. Changes to this methodology

Any change to the parameters below or the calculation code requires a version bump, a CHANGELOG entry, and one publication's notice before taking effect — enforced mechanically via METHODOLOGY.lock in CI. See GOVERNANCE.md.

7. Current parameters (config/factors.yaml, verbatim)

# EU-CRI methodology parameters. Every number that affects a published print lives here.
# Changing anything in this file (or sovereign.yaml, index.py, normalise.py, weights.py)
# requires a methodology_version bump + CHANGELOG entry - enforced by CI (see GOVERNANCE.md).
methodology_version: "0.3.0-dev"   # '-dev' relaxes the lock guard pre-launch; drop it at launch

reference_unit:
  gpu_model: H100_SXM        # headline reference variant (class H100 below)
  term: on_demand            # no term commitment
  location: EU_EEA           # datacenter physically in the EU/EEA
  # v0.3.0 removed `node_gpus: 8`. The node-size floor now lives in filters.min_gpu_count
  # and is 2, not 8 - see the measurement recorded there.
  # Per-GPU-hour, ex-VAT, excluding metered egress. USD primary, EUR companion at the
  # ECB reference rate (T-1: the ECB publishes ~14:00 UTC, after our 11:00 cut-off, so
  # the EUR leg structurally cannot use same-day FX - see METHODOLOGY.md).

# Compute classes the index observes. A class prices its reference variant ONLY.
# v0.3.0: cross-variant normalisation factors are removed. H100 SXM and H100 PCIe are
# different products, and H100->H200 normalisers disagree in SIGN depending on whether
# you divide by BF16 PFLOPs (+31%) or HBM bandwidth (-9%). A variant may re-enter a class
# only with a factor MEASURED from same-venue, same-day, same-SKU pairs (see
# `measured_factors` below). No assumed factors are permitted in the calculation path.
model_classes:
  H100:   {reference_variant: H100_SXM,  variants: {H100_SXM: 1.0}}
  H200:   {reference_variant: H200_SXM,  variants: {H200_SXM: 1.0}}
  B200:   {reference_variant: B200_SXM,  variants: {B200_SXM: 1.0}}
  B300:   {reference_variant: B300_SXM,  variants: {B300_SXM: 1.0}}
  A100:   {reference_variant: A100_SXM,  variants: {A100_SXM: 1.0}}
  H100P:  {reference_variant: H100_PCIE, variants: {H100_PCIE: 1.0}}   # PCIe priced separately

# Market segment per provider. This is the tier-segregation control and it is
# methodology-affecting: the constituent population is bimodal (measured 2026-08-15,
# cluster separation 5.4 sd between the neocloud/marketplace cluster [2.16..3.85] and the
# hyperscaler cluster [5.44..7.36]). Averaging across that gap produces a number nobody
# quotes. Providers not listed default to 'neocloud'.
segments:
  marketplace: [vast.ai, runpod]
  hyperscaler: [aws, gcp, azure]
  neocloud:    [nebius, datacrunch, seeweb, scaleway, ovhcloud, hetzner, genesis_cloud,
                leaseweb]

# Which segments each published series draws from. The headline deliberately excludes the
# hyperscaler catalog tier - those are the two highest and least-moving prices in the panel
# and they belong in the series where they are the correct object of measurement.
series_populations:
  headline:    [marketplace, neocloud]
  marketplace: [marketplace]
  hyperscaler: [hyperscaler]
  sovereign:   [marketplace, neocloud]   # further filtered by config/sovereign.yaml

filters:
  min_gpu_count: 2           # v0.3.0: was 8. Measured within-venue same-day (vast.ai
                             # 2026-07-18) the per-GPU discount SATURATES at 2 GPUs:
                             # 1x=1.000, 2x=0.951, 4x=0.916, 8x=0.916. So 2/4/8-GPU offers
                             # are mutually comparable within ~4% and need no factor, while
                             # the 1-GPU offer carries a ~9% small-order premium and is
                             # excluded rather than normalised. The old 8-GPU floor passed
                             # 1 of 10 vast.ai days and threw away ALL marketplace price
                             # discovery (9 distinct EU H100 prices, +12.3% range).
  price_floor_usd: 0.25      # junk-listing guard: asks below this are excluded
  price_ceiling_usd: 25.0    # sanity ceiling
  datacenter_verified_only: true
  exclude_tiers: [interruptible, spot, community]

aggregation:
  unit: offer                # v0.3.0: aggregate over OFFERS, not one price per provider.
                             # A median over ~6 providers is a step function with delta 1.0
                             # to one name and 0.0 to the rest. A median over many
                             # GPU-count-weighted offers is the SOFR construction and is
                             # locally smooth. Density is what makes the median sound.
  estimator: weighted_median # over offers, weighted by GPU count (capacity)
  trim_k:                    # count-based trim: clamp the k highest and k lowest.
    - {min_n: 0,  k: 0}      # Percentile winsorising is INERT at this panel size: at n=6
    - {min_n: 5,  k: 1}      # nearest-rank p5/p95 AND p10/p90 both resolve to (min, max)
    - {min_n: 10, k: 2}      # and clamp nothing. The shipped [5,95] was a no-op for the
    - {min_n: 20, k: 3}      # entire history of the index. Count-based trimming binds at
                             # every n.
  min_providers: 5           # below this: value=null, flag='insufficient_sources'.
                             # A gap stays a gap - no fallback waterfall, no fabrication.
  min_offers: 5              # and at least this many surviving offers
  smoothing_days: 7          # headline companion series: 7-day mean (>=4 non-null required)

weights:
  capacity_cap: 64           # daily capacity term = min(observed qualifying GPUs, cap)
  default_capacity: 8        # ... where capacity is unobservable (all list-price rows)
  executable_multiplier: 2.0 # executable-quote sources weighted double list-price sources
  max_weight_share_pct: 25   # per-PROVIDER aggregate cap, applied after offers are summed
                             # within a provider. NOTE: this cap is mathematically inert at
                             # n=4 (max(25, 100/n) forces exactly equal shares - verified
                             # 0.0 deviation over 50k random weight vectors). It binds again
                             # only once the panel is wider; `cap_bound` is published per
                             # print so a reader can see whether it did anything.
  review:                    # scheduled, data-driven weight reviews (fixed rule, no discretion)
    anchor_weekday: 1        # ISO weekday a review takes effect (1 = Monday, i.e. weekly)
    window_days: 28          # trailing observation window ending the day before the review
    min_history_days: 5      # fewer collection days than this in the window -> bootstrap

composite:                   # EU-CRI-COMPUTE: chain-linked basket of class sub-indices
  base_value: 100.0          # level on the composite's first print
  min_class_share_pct: 5     # floor for an eligible class at review (applies with >=2 classes)
  max_class_share_pct: 75    # cap for a class at review (applies with >=2 classes)

# Continuity series. v0.3.0 DEMOTES chain-linking from the headline to a labelled companion.
# A Laspeyres link with a same-day divisor reset contributes exactly zero return on panel
# entry/exit, which makes in-panel up-moves permanent while down-moves taken via exit and
# re-entry are laundered out - a ratchet worth ~+7.8% per cycle for a 25% constituent.
# The headline is therefore the raw cross-section (reproducible, no drift, no ratchet); the
# chained level is published beside it and the divergence between them is a health metric.
continuity:
  publish_chained: true
  base_value: 100.0
  divergence_review_pct: 5   # chained vs raw divergence above this triggers a documented review

fx:
  max_age_days: 7            # refuse an FX rate older than this (flag the print instead)
  # HARD RULE enforced in code: fx_date <= print date. Two 2026-07 prints were computed
  # with an FX rate published on 2026-07-21, i.e. AFTER the print date - a look-ahead
  # introduced by backfill. Covered by a regression test.

staleness:                   # static (manually verified) provider entries
  warn_days: 45              # loader warns
  exclude_days: 90           # excluded from the index with exclusion_reason='stale'

quality:
  jump_flag_pct: 30          # day-over-day provider move that flags (never excludes)

# Factors measured from same-venue, same-day, same-SKU observations. Published for
# transparency; NONE of these currently enter the calculation path. A factor may only be
# promoted into `model_classes` after it is supported by >=3 independent venue-days.
measured_factors:
  node_size_per_gpu:         # vast.ai, EU H100_SXM, same-day ratios vs the 1-GPU offer
    "2": 0.9514              # n=3 venue-days
    "4": 0.9159              # n=1  - provisional
    "8": 0.9158              # n=1  - provisional
  fabric_infiniband_premium: 1.107   # Azure westeurope ND96isr vs ND96is_noIB, n=1 pair.
                                     # PROVISIONAL - measures Azure's internal price ladder,
                                     # not the market's. Not in the calculation path.

eu_eea_countries: [AT, BE, BG, HR, CY, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IT,
                   LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, ES, SE, IS, LI, NO]
# NOTE: CH (Switzerland) is deliberately absent - Switzerland is in EFTA but NOT the EEA.

8. IOSCO Principles mapping

IOSCO principleImplementation
Benchmark design & data sufficiency (P6–P7)Observable, executable marketplace quotes anchored; ≥5-source rule; weights set by observed data on a fixed schedule; 25% concentration cap; no expert judgement in the calculation path
Hierarchy of data inputs (P8)Executable quotes weighted 2.0x over list prices inside the weight review; overlay data never enters the calculation
Transparency of methodology (P9, P11)This document is auto-generated from config, semver-versioned; every parameter is a visible config value; every weight review is stored and queryable
Changes to methodology (P12)CHANGELOG + one-publication notice before any parameter change takes effect; hash-locked in CI. Scheduled weight reviews follow the fixed published formula and are data updates, not methodology changes
Quality of the administrator (P4–P5)Named author; stated conflicts (the author may trade venues the index observes — disclosed); corrections never silent
Complaints & audit trail (P13, P16)Raw observations and weight reviews immutable in SQLite; any reader can request the constituent set for a given day

TCI is a research publication. It is not investment advice and is not administered as a benchmark under EU Regulation 2016/1011; it may not be used as a reference price in financial instruments.

Author and administrator: Mark Rusch (rusch.mh@gmail.com). Conflicts: the author may hold positions on venues whose prices the index observes; see GOVERNANCE.md.