BlogResourcesAboutGet Access

Product

  • CBAM Calculator
  • Pricing
  • Analytics
  • Market Insights
  • Product Tour
  • Testimonials

Resources

  • Blog
  • What Is CBAM?
  • CBAM Deadlines
  • Reporting Guide
  • CN Codes
  • All Resources

Solutions

  • CBAM Compliance
  • For Exporters
  • For SMEs
  • For Steel Importers
  • For Turkey

Industry

  • Aluminium
  • Cement
  • Fertilizer
  • India
  • China

Company

  • About
  • RuffStack
  • Privacy Policy
  • Terms of Service
© 2026 CbamTrack.CbamTrackCBAM compliance software for EU importers.
Back to Blog
Building an EU ETS Price Pipeline: Engineering Decisions and Tradeoffs
Engineering8 min read

Building an EU ETS Price Pipeline: Engineering Decisions and Tradeoffs

July 2, 2026
|R. Emrah Gökkaya

CBAM certificate costs are directly tied to EU ETS allowance prices — for the 2026 compliance year it's a quarterly average, from 2027 it switches to a weekly average of EU ETS auction closing prices. That sounds like a simple "call an API, store a number" problem. It isn't, and the reasons why shaped several of the more interesting engineering decisions behind CBAMTrack's certificate cost calculator.

Here's how the price tracker actually works, and why it's built the way it is.

Why "just call an API" doesn't hold up

A compliance tool can't have an "unlucky day." If the price feed goes down and someone generates a CBAM cost estimate off a stale or missing value, that's not a cosmetic bug — it's a number they might actually budget against. The design constraint from day one was: the system must never silently serve a wrong or missing price. It either serves a verified price or it visibly tells you it couldn't.

That single constraint is what turned this from a fetch-and-cache script into a proper fallback chain, with an explicit "stale as of [date]" flag surfaced in the UI whenever the most recent verified price can't be confirmed.

Each tier only activates if the one above it fails or returns data outside a sanity-check range — a >15% single-day swing gets flagged for review rather than trusted blindly. Carbon markets are volatile, but not usually that volatile.

Storing prices the way CBAM actually needs them

The tricky part isn't fetching a price — it's storing it in a shape that supports both compliance-year rules at once, since 2026 uses a quarterly average and 2027 onward uses a weekly average.

// Simplified schema
model EtsPrice {
  id                String   @id @default(cuid())
  date              DateTime @unique
  priceEurPerTonne  Decimal
  source            String   // which tier of the fallback chain produced this
  isStale           Boolean  @default(false)
  fetchedAt         DateTime @default(now())
}

Storing raw daily prices — not pre-computed averages — turned out to be the right call. When the 2026→2027 pricing rule change landed, there was no backfill needed. The averaging logic just reads a different window.

Postgres, not Redis

The obvious instinct for a "fetch a price, cache it, serve it fast" problem is Redis. We didn't use it, and don't expect to.

The read pattern for ETS prices isn't "give me the latest value as fast as possible" — it's "give me the volume-weighted average over this specific window, where the window definition itself depends on which compliance year we're calculating for." That's a query with a WHERE date BETWEEN and a GROUP BY, not a key lookup. Redis is excellent at the thing we don't need (sub-millisecond single-key reads) and clumsy at the thing we do need (windowed aggregation over a time series with a variable boundary rule).

Postgres gives us that aggregation for free, in SQL, against data that's already sitting next to every other table the calculation engine touches — no cross-service joins, no second system to keep in sync, no cache-invalidation bugs when a price gets corrected after the fact (which does happen; auction data occasionally gets revised after publication).

There's a simpler reason too: running lean matters. Every extra piece of infrastructure is one more thing to operate and monitor. A Redis instance earns its keep when there's a genuine hot-path latency problem. A handful of daily price rows, queried a few hundred times a day, doesn't have that problem — Postgres with an index on date handles it without any extra thought.

normalizePeriod() and displayPeriod()

CBAM reporting periods don't map cleanly onto calendar quarters once you factor in the transition between the quarterly-average rule (2026) and weekly-average rule (2027+). Two small helpers get used everywhere in the reporting engine, not just the price tracker:

// normalizePeriod: takes a raw date range and returns the
// canonical CBAM reporting period it belongs to, accounting
// for the 2026/2027 averaging-rule boundary
function normalizePeriod(date: Date): CbamPeriod { ... }

// displayPeriod: takes a normalized period and returns the
// human-readable label used across the UI and PDF reports
function displayPeriod(period: CbamPeriod): string { ... }

Having a single source of truth for "what period does this date belong to, and what do we call it" closed off a category of bug we hit early on: a report and its underlying price data disagreeing about which quarter they were describing, because one code path used raw dates and another used the period label.

What we'd do differently

If we were starting over, the sanity-check/anomaly-flagging logic would come before the happy-path fetch-and-store logic, not after. It got added reactively once a bad data day showed up in staging — a source returning a cached value from three weeks earlier without any indication it was stale. In a compliance product, "silently wrong" is a worse failure mode than "loudly broken."

Why this matters beyond CBAM

If you're building anything that depends on an external price or regulatory feed — carbon markets, FX rates, commodity indices — three questions are worth asking before writing the fetch logic:

  1. What happens the day your source is down?
  2. What happens the day your source returns a number that's technically valid but wrong?
  3. Does your storage layer assume today's aggregation rule will still be true next year?

Getting those answers right up front is cheaper than retrofitting them after someone asks why their number changed.


The certificate cost formula this price data feeds into — including the phase-in schedule and sector-specific benchmarks — is covered in how to calculate your CBAM certificate costs. Or try the live calculator directly.

R. Emrah Gökkaya

CbamTrack builds CBAM compliance software for EU importers. We help SMEs automate quarterly emission reporting with live ETS pricing and IR 2025/2621 compliant calculations.

Learn more about CbamTrack →

Ready to simplify your CBAM compliance?

Subscribe today and generate your first CBAM report in minutes.

Get Started
What Is CBAM?Reporting SoftwareCost CalculatorCompliance ToolsProduct Tour