← All projects
Data · Energy forecasting

depower

A daily forecast of German day-ahead electricity prices, locked in before the auction closes and scored in public once the real prices arrive.

Role
Data science & full-stack development
Timeline
2026
Type
Personal project
Stack
Python, LightGBM, Next.js
Overview

Any forecast can look good in hindsight. depower commits to its numbers before the market does.

Germany’s wholesale electricity prices for each hour of tomorrow are set in an auction at 12:00 today. Every morning before that, depower forecasts all 24 prices with a likely range for each hour, using grid data from SMARD and ECMWF weather forecasts. Each forecast is logged once and can’t be overwritten. When the real prices are published, it gets scored in public. Live forecasts began on 3 November 2026, for delivery the next day.

The first live forecast, made on 3 November 2026 for the next day, with the simulated backtest scorecard beside it.
The first live forecast, made on 3 November 2026 for the next day, with the simulated backtest scorecard beside it.
Challenge & solution

What made it hard, and how I approached it.

The challenge

A forecast is only honest if it uses what was known at the time

  • One value published after the auction is enough to make a backtest look better than the model could ever be live.
  • SMARD keeps no history of when values were published, and some forecast series only seem to appear after the auction closes.
  • A good error score proves little on its own. It needs simple baselines to beat, and a clear line between simulated and real results.
The solution

Make leaking hard, then publish the misses

  • Every cleaned row carries an available-at time, and features can only read through a view that hides anything newer than the 11:30 cutoff.
  • Tests overwrite post-cutoff values with absurd numbers and confirm no feature changes. An audit of all 23,136 real feature rows found none past its cutoff.
  • Each log entry stores the hash of the one before and CI fails if a forecast changes. The dashboard shows backtest and live results separately.
Key features

Built around what people actually need.

Tomorrow’s prices

24 hourly forecasts with P10–P90 ranges and the cheapest and priciest windows, as bars, ranges or a table.

Public track record

Forecasts scored against published prices and two simple baselines, with live and backtest results kept apart.

EV charging calculator

Finds the cheapest hours to charge within your plug-in window and estimates the saving, on wholesale prices only.

depower on a phone
Responsive

Built for any screen

The dashboard folds into a single column on a phone, with every view one tap away.

  • Cards stack into one readable column
  • Navigation moves to a bar at the bottom of the screen
  • English and German, following the browser’s language
In the data

The backtest, including where it falls short.

Every number here comes from the project’s published report: 365 days from 1 November 2025 to 31 October 2026, with the model retrained monthly on earlier data only. These are simulated forecasts, not live results. Hover or tap for exact values.

Leakage rules

What the model may see at 11:30, half an hour before the auction.

Each bar is the time span an input can contribute. A bar only crosses the cutoff if those values were already published by then. Hover or tap a row to see why.

Source: german-day-ahead-forecast, leakage rules in the README. Bars start at the chart’s edge; history reaches further back.

Error by hour

It beats both baselines at every hour. The evening peak is still its hardest.

Mean absolute error (MAE) by Berlin hour. Overall, the model misses by 19.10 EUR/MWh, against 31.28 for repeating yesterday’s price and 40.17 for repeating last week’s. Click a name to hide it.

Source: data/backtest/report.json, by_hour, 8,760 scored hours.

Error by month

September and October 2026 were its weakest months by far.

MAE was 31.8 and 32.6 EUR/MWh, against 11.3 to 20.1 in every other month. Switch views to see how often real prices landed inside the P10–P90 range.

Source: data/backtest/report.json, by_month.

Unusual prices

Price spikes are the main weakness.

In the highest 5% of prices, the model only narrowly beats yesterday’s price, and its range misses nearly half of those hours. Periods are grouped by actual price, after the fact.

Source: data/backtest/report.json, by_regime. Top 5% means at or above 215.72 EUR/MWh.

Screens

A closer look at the product.

How it works

Under the hood.

PythonLightGBMDuckDBpandasTypeScriptNext.jsRechartsshadcn/uiDockersystemdHetznerGitHub Actions
Takeaway

What I learned.

My first backtest’s P10–P90 ranges covered only 58.5% of real prices when they should have covered 80%. Calibration brought that to 77.9%, but I chose that fix after seeing the result, so the number may be slightly flattering, and the project says so. That taught me a forecast earns trust less through its error score than by showing what it could see, where it fails and what a simple baseline would have done.

Next project

APEX F1 Analytics

A historical Formula 1 dashboard built on OpenF1 data — races, telemetry, tire strategy and championships, with an AI race analyst on top.

View case study →
Let’s connect

Have an idea
worth exploring?