[{"data":1,"prerenderedAt":1023},["ShallowReactive",2],{"post-\u002Fblog\u002F2026\u002Fpredictiondb-forecasting-german-power-prices-from-free-weather":3},{"id":4,"title":5,"body":6,"categories":999,"date":1003,"description":1004,"extension":1005,"image":741,"meta":1006,"navigation":1007,"path":1008,"seo":1009,"stem":1010,"tags":1011,"__hash__":1022},"blog\u002Fblog\u002F2026\u002Fpredictiondb-forecasting-german-power-prices-from-free-weather.md","PredictionDB – Digging for Next Week's Power Price in Free Weather Data",{"type":7,"value":8,"toc":987},"minimark",[9,39,48,60,63,70,75,82,111,118,178,181,185,271,275,278,281,344,358,369,376,380,387,405,412,436,439,488,495,499,506,509,591,594,676,683,690,693,707,717,721,724,730,733,736,742,745,842,848,851,880,883,889,896,903,907,910,934,938,941,974,984],[10,11,13,14],"div",{"style":12},"display: flex; justify-content: flex-end; margin: 0 0 1rem;","\n  ",[15,16,23,24,23,35,13],"a",{"href":17,"target":18,"rel":19,"style":22},"https:\u002F\u002Fgithub.com\u002Fthe78mole\u002FPredictionDB","_blank",[20,21],"noopener","noreferrer","display: inline-flex; align-items: center; gap: 0.4rem; text-decoration: none; color: inherit; font-size: 0.9rem; padding: 0.35rem 0.7rem; border: 1px solid #d0d7de; border-radius: 0.5rem;","\n    ",[25,26,31],"svg",{"height":27,"width":27,"viewBox":28,"fill":29,"ariaHidden":30},"18","0 0 16 16","currentColor","true",[32,33],"path",{"d":34},"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z",[36,37,38],"span",{},"the78mole\u002FPredictionDB",[40,41,42,43,47],"p",{},"Moles are famously bad at weather. Down in the burrow it's always 9 °C and slightly damp, so when it comes to the sky I have to rely on instruments. And lately the sky has started to matter a lot: whether the next days are sunny or windy decides whether the power price at noon is 12 ct\u002FkWh or −2 ct\u002FkWh, and what my battery should be doing ",[44,45,46],"em",{},"right now",".",[40,49,50,51,55,56,59],{},"Tomorrow's prices are no secret. The day-ahead auction publishes them every day around 13:00, and SMARD or Energy-Charts show them to anyone for free. But by then half of today's decisions are already made. Should the battery fill up from the roof this morning – or stay half-empty, because tomorrow noon will probably go negative and pay me for charging? Should a big storage hold its energy through a dull Tuesday because Thursday evening looks expensive? A one-day window can't answer that. What you actually want is a rough idea of the ",[52,53,54],"strong",{},"whole coming week",", and you want it in the morning, ",[44,57,58],{},"before"," the auction.",[40,61,62],{},"There are services that sell exactly that. They want a contract and a monthly fee. Meanwhile NOAA publishes a global weather model four times a day for free, the German grid operators publish load and generation, and the exchange prices end up on SMARD and Energy-Charts. All the raw material is lying around in the open. So I started digging a tunnel from those piles of free data to a week-ahead forecast of German grid load and power prices.",[40,64,65,66,69],{},"That tunnel is ",[52,67,68],{},"PredictionDB",". It isn't finished – there's no public API yet, and the neural networks are still a line in the roadmap. But it already does something useful, and I'll get to that before the end of the post.",[71,72,74],"h2",{"id":73},"the-goal","The Goal",[40,76,77,78,81],{},"Forecast, for Germany (bidding zone DE-LU), using ",[52,79,80],{},"only free data sources",":",[83,84,85,92,98,104],"ul",{},[86,87,88,91],"li",{},[52,89,90],{},"Grid load"," in Germany, 15 min resolution, up to 16 days ahead",[86,93,94,97],{},[52,95,96],{},"Day-ahead price"," DE-LU for the delivery days D+1 to D+7, as quantiles (P10\u002FP50\u002FP90) – issued every morning at 08 UTC, before the auction closes; scenarios up to D+14 later",[86,99,100,103],{},[52,101,102],{},"Wind, PV and run-of-river generation"," as helper targets",[86,105,106,107,110],{},"Later: forecasts for your ",[44,108,109],{},"own"," PV, wind or small hydro plant via an API, and heat pump demand and scheduling",[40,112,113,114,117],{},"The price for D+1 is the least interesting of these, because a few hours later it's known exactly. The forecast earns its keep on the days ",[44,115,116],{},"after"," that, and in the morning hours before 13:00:",[119,120,121,134],"table",{},[122,123,124],"thead",{},[125,126,127,131],"tr",{},[128,129,130],"th",{},"Decision",[128,132,133],{},"Needs",[135,136,137,146,154,162,170],"tbody",{},[125,138,139,143],{},[140,141,142],"td",{},"Charge the battery from PV this morning, or keep room for tomorrow's negative noon?",[140,144,145],{},"Tomorrow's price shape – before it's published",[125,147,148,151],{},[140,149,150],{},"Hold a large storage over a cheap, windy day for an expensive evening later in the week?",[140,152,153],{},"D+2 … D+5",[125,155,156,159],{},[140,157,158],{},"Charge the EV once this week – today, or is it worth waiting for the weekend?",[140,160,161],{},"Which days are cheap, not just which hours",[125,163,164,167],{},[140,165,166],{},"Pre-heat with the heat pump today, or wait for a cheaper day?",[140,168,169],{},"D+1 … D+3",[125,171,172,175],{},[140,173,174],{},"How much capacity is worth keeping free at all?",[140,176,177],{},"The spread over the coming days",[40,179,180],{},"The whole thing runs as a Docker Compose stack in a VM on my Proxmox box, and it's built so that moving it to the cloud is a configuration change, not a rewrite.",[71,182,184],{"id":183},"the-stack","The Stack",[119,186,187,197],{},[122,188,189],{},[125,190,191,194],{},[128,192,193],{},"Layer",[128,195,196],{},"Technology",[135,198,199,207,215,223,231,239,247,255,263],{},[125,200,201,204],{},[140,202,203],{},"Language \u002F tooling",[140,205,206],{},"Python 3.12, uv workspace (monorepo), Ruff, mypy, pytest",[125,208,209,212],{},[140,210,211],{},"Database",[140,213,214],{},"PostgreSQL 17 + TimescaleDB (hypertables with compression)",[125,216,217,220],{},[140,218,219],{},"Object storage",[140,221,222],{},"Garage (S3 API, single node)",[125,224,225,228],{},[140,226,227],{},"Weather",[140,229,230],{},"NOAA GFS 0.25°, 384 h, four runs per day, GRIB2 partial downloads",[125,232,233,236],{},[140,234,235],{},"Energy data",[140,237,238],{},"SMARD, ENTSO-E Transparency, Energy-Charts, ECB, World Bank, Netztransparenz",[125,240,241,244],{},[140,242,243],{},"Plant inventory",[140,245,246],{},"Marktstammdatenregister (MaStR) full export, quarterly",[125,248,249,252],{},[140,250,251],{},"Forecasting",[140,253,254],{},"LightGBM, two-stage, quantile loss",[125,256,257,260],{},[140,258,259],{},"Monitoring",[140,261,262],{},"Grafana, Prometheus, cAdvisor, node-exporter",[125,264,265,268],{},[140,266,267],{},"Deployment",[140,269,270],{},"GHCR image per green CI run, systemd timer on the VM rolls it out",[71,272,274],{"id":273},"weather-without-drowning-in-it","Weather Without Drowning in It",[40,276,277],{},"GFS is big. Download every variable of every run at full resolution and you're suddenly running a weather archive instead of a forecasting tool. Storing the grid values as database rows would be even worse – billions of rows for a handful of years.",[40,279,280],{},"So the weather pipeline is strictly layered, bronze → silver → gold:",[119,282,283,296],{},[122,284,285],{},[125,286,287,290,293],{},[128,288,289],{},"Tier",[128,291,292],{},"Where",[128,294,295],{},"What",[135,297,298,313,326],{},[125,299,300,303,310],{},[140,301,302],{},"Bronze",[140,304,305,306],{},"S3 bucket ",[307,308,309],"code",{},"wx-bronze",[140,311,312],{},"Original GRIB2 per forecast step, cropped to 44–58° N \u002F 0–20° E, CCSDS-packed, with a manifest (endpoint, SHA-256, messages, missing variables)",[125,314,315,318,323],{},[140,316,317],{},"Silver",[140,319,305,320],{},[307,321,322],{},"wx-silver",[140,324,325],{},"One Zarr v3 store per run, Int16 with scale\u002Foffset, Blosc\u002FZstd – about 13 MB per run",[125,327,328,331,337],{},[140,329,330],{},"Gold",[140,332,333,334],{},"TimescaleDB ",[307,335,336],{},"feat.nwp_value",[140,338,339,340,343],{},"Weather features per ",[44,341,342],{},"plant cluster",", run and valid time",[40,345,346,349,350,353,354,357],{},[307,347,348],{},"wx-ingest"," never downloads a whole GRIB file. It reads the ",[307,351,352],{},".idx"," index, merges the byte ranges of the 22 fields it actually needs (temperature, dew point, wind at 10 m and 100 m, gusts, radiation, clouds, precipitation, snow, soil temperature and moisture, …) and fetches only those via HTTP range requests. If NOMADS hiccups, it falls back to the AWS mirror, then Google. I wrote a small library for this, ",[307,355,356],{},"gribkit",", instead of using Herbie – I wanted the cropping and re-packing in the same step.",[40,359,360,361,364,365,368],{},"The trick that turns \"weather\" into \"energy\" is the gold tier. ",[307,362,363],{},"mastr-ingest"," imports the German plant register every quarter, geolocates every wind turbine and PV array, and clusters them per technology. ",[307,366,367],{},"wx-process"," then computes, for every GFS run, capacity-weighted features per cluster: wind speed extrapolated to hub height, a generic power curve, PV capacity factor with temperature correction, snow on the panels. The model never sees \"wind at grid point 52.25° N \u002F 8.5° E\" – it sees \"expected output of onshore wind cluster c07, weighted by the capacity installed there\".",[40,370,371,372,375],{},"And because the register goes back years, the stack reconstructs the ",[52,373,374],{},"installed capacity per cluster per day"," from commissioning and decommissioning dates. Training on 2019 data with 2026 capacity would be a quietly lying model.",[71,377,379],{"id":378},"two-stages-and-a-strict-what-did-we-know","Two Stages and a Strict \"What Did We Know?\"",[40,381,382,383,386],{},"The first model, ",[307,384,385],{},"de_lu_v1",", is deliberately boring: gradient boosting with LightGBM, trained in two to three minutes on a laptop CPU.",[83,388,389,395],{},[86,390,391,394],{},[52,392,393],{},"Stage 1"," forecasts load, onshore wind, offshore wind and PV. Wind and PV are learned as capacity factors and multiplied back by the capacity on the delivery day.",[86,396,397,400,401,404],{},[52,398,399],{},"Stage 2"," forecasts the price, using everything from stage 1 plus its P50 forecasts and the ",[52,402,403],{},"residual load"," derived from them (load minus wind minus PV). The less the renewables cover, the more expensive the power plant that sets the price.",[40,406,407,408,411],{},"Every target gets three boosters (P10, P50, P90) with pinball loss. Stage 2 is trained on ",[44,409,410],{},"out-of-fold"," stage 1 predictions – otherwise it would learn to trust perfect inputs it will never get in production.",[413,414,415,422,429],"details",{},[416,417,418,421],"summary",{},[52,419,420],{},"Boosters and pinball loss, in two paragraphs"," (click to expand)",[40,423,424,425,428],{},"A ",[52,426,427],{},"booster"," is what LightGBM calls a trained gradient-boosting model. It's not one big clever tree but a long chain of small, dumb decision trees. The first tree makes a rough guess, the second one is trained only on what the first got wrong, the third on what the first two still got wrong, and so on. Each tree adds a small correction (scaled down by the learning rate, here 0.05), so no single tree can wreck the result. Here each booster is 400 trees with at most 63 leaves each. Sum them up and you get the forecast for one target and one quantile – which is why there are 15 boosters per model version: 5 targets × 3 quantiles.",[40,430,431,432,435],{},"The ",[52,433,434],{},"pinball loss"," is what tells a booster whether it is supposed to forecast the middle or an edge of the distribution. For a quantile τ, a forecast that ends up too low costs τ × the miss, and a forecast that ends up too high costs (1 − τ) × the miss. For P90 (τ = 0.9), undershooting is expensive and overshooting is cheap. Say the forecast is 120 €\u002FMWh: if the actual price is 150, the loss is 0.9 × 30 = 27; if it's 100, only 0.1 × 20 = 2. The cheapest place for the model to sit is where only 10 % of the actual values land above it – exactly the definition of P90. P10 works the other way round, and for P50 both sides cost the same, which makes it the median (and the loss just half the absolute error). Three boosters with three different τ give you a band instead of a single number – and for a battery, \"probably 40, but could be 120\" is a very different instruction from a plain 40.",[40,437,438],{},"The part I spent the most time on is leakage. A forecast is issued daily at 08 UTC, before the day-ahead auction closes at noon. Every feature row is built with exactly what was public at that moment:",[119,440,441,451],{},[122,442,443],{},[125,444,445,448],{},[128,446,447],{},"Data",[128,449,450],{},"Known from",[135,452,453,461,469,480],{},[125,454,455,458],{},[140,456,457],{},"Measured load and generation",[140,459,460],{},"end of interval + 2 h",[125,462,463,466],{},[140,464,465],{},"Day-ahead price for a delivery day",[140,467,468],{},"13:00 local time on the day before",[125,470,471,474],{},[140,472,473],{},"GFS run",[140,475,476,479],{},[307,477,478],{},"run_time"," + 6 h (fully processed)",[125,481,482,485],{},[140,483,484],{},"Installed capacity",[140,486,487],{},"MaStR snapshot, reconstructed per day",[40,489,490,491,494],{},"Every energy value in the database carries a ",[307,492,493],{},"published"," timestamp for exactly this reason. A backtest that peeks into the future looks fantastic and is worth nothing.",[71,496,498],{"id":497},"how-good-is-it-and-how-far-out","How Good Is It – and How Far Out?",[40,500,501,502,505],{},"The backtest runs as an expanding window over 2025 to October 2026, against the dumb-but-honest baselines (yesterday's price, last week's load) and the grid operators' own day-ahead forecasts. ",[307,503,504],{},"de_lu_v2"," adds only free data on top of v1 – day-ahead prices of the 14 neighbouring zones, fuel prices with the EUR\u002FUSD rate, and self-computed market values.",[40,507,508],{},"First the one day where there's something to compare against:",[119,510,511,524],{},[122,512,513],{},[125,514,515,518,521],{},[128,516,517],{},"D+1, same hours 2025 – 10\u002F2026",[128,519,520],{},"Price MAE",[128,522,523],{},"Load MAE",[135,525,526,541,556,570,581],{},[125,527,528,533,538],{},[140,529,530],{},[307,531,532],{},"de_lu_v2\u002Ffrei",[140,534,535],{},[52,536,537],{},"26.4 €\u002FMWh",[140,539,540],{},"1,757 MW",[125,542,543,548,551],{},[140,544,545],{},[307,546,547],{},"de_lu_v2\u002Ffrei_niveau",[140,549,550],{},"27.1 €\u002FMWh",[140,552,553],{},[52,554,555],{},"1,719 MW",[125,557,558,564,567],{},[140,559,560,563],{},[307,561,562],{},"kalender"," (v1, no weather)",[140,565,566],{},"28.3 €\u002FMWh",[140,568,569],{},"1,833 MW",[125,571,572,575,578],{},[140,573,574],{},"Yesterday's price",[140,576,577],{},"28.7 €\u002FMWh",[140,579,580],{},"–",[125,582,583,586,588],{},[140,584,585],{},"Grid operators' day-ahead load forecast",[140,587,580],{},[140,589,590],{},"1,981 MW",[40,592,593],{},"The load forecast beats the TSOs' official one by about 11 %, and the price model beats \"tomorrow will be like today\" for the first time. That's a useful sanity check, but nobody needs my D+1 price at 14:00. The interesting part is how the forecast holds up further out:",[119,595,596,626],{},[122,597,598],{},[125,599,600,605,608,611,614,617,620,623],{},[128,601,602,603],{},"Price, ",[307,604,532],{},[128,606,607],{},"D+1",[128,609,610],{},"D+2",[128,612,613],{},"D+3",[128,615,616],{},"D+4",[128,618,619],{},"D+5",[128,621,622],{},"D+6",[128,624,625],{},"D+7",[135,627,628,653],{},[125,629,630,633,636,639,642,645,647,650],{},[140,631,632],{},"MAE (€\u002FMWh)",[140,634,635],{},"26.4",[140,637,638],{},"29.4",[140,640,641],{},"29.7",[140,643,644],{},"30.1",[140,646,644],{},[140,648,649],{},"29.9",[140,651,652],{},"30.2",[125,654,655,658,661,664,666,669,671,674],{},[140,656,657],{},"Rank correlation of the hours within a day",[140,659,660],{},"0.868",[140,662,663],{},"0.867",[140,665,663],{},[140,667,668],{},"0.866",[140,670,660],{},[140,672,673],{},"0.871",[140,675,660],{},[40,677,678,679,682],{},"The error grows by only about 15 % from tomorrow to a week ahead. And the second row is the one a battery cares about: the rank correlation says how well the model gets the ",[44,680,681],{},"order"," of the hours right – which hours of a day are the cheap ones and which the expensive ones. It stays at 0.87 across the whole week. For charging and discharging, the order of the hours matters far more than the exact euro value. The same goes for the EV that only gets plugged in once a week: the question isn't \"what will Saturday cost to the cent\" but \"is Saturday noon clearly cheaper than tonight, or am I waiting for nothing?\"",[40,684,685],{},[686,687],"img",{"alt":688,"src":689},"MAE gain of the v2 feature groups over v1 per lead day, for grid load (left) and day-ahead price (right)","\u002Fimages\u002Fblog\u002F2026\u002F10\u002Fpredictiondb-v2-mae-gain.png",[40,691,692],{},"Two honest caveats, because a mole that oversells its tunnel ends up in a collapsed one:",[83,694,695,701],{},[86,696,697,700],{},[52,698,699],{},"This is still without weather in the price model."," The forecast above knows calendar, holidays, installed capacity, recent prices, neighbours and fuels – but not whether next Thursday is sunny. The GFS features are only available as gold-tier data from January 2026, too short to train on. Wind and PV for D+3 onwards are exactly what decides whether a day is cheap, so this is the biggest lever left. A short test with half a year of weather already looked promising for D+1 wind (2.4 GW MAE versus 8.3 GW for persistence), but six months of training is not enough to judge it.",[86,702,703,706],{},[52,704,705],{},"The quantile bands are too narrow."," P10–P90 contains 66–75 % of the actual values instead of 80 %. Calibrating that is on the list.",[40,708,709,710,713,714,716],{},"One more finding was about ",[44,711,712],{},"why"," v2 got better. Neighbour prices, fuels and market values each improved the price by 4–5 %, but all of them together barely more than each alone. Suspicious. The cross-check: just giving the model a 28-day average of its ",[44,715,109],{}," price – no new source at all – already gets 3.6 %. Most of the gain is a longer price memory, not new information.",[71,718,720],{"id":719},"the-part-that-is-already-useful","The Part That Is Already Useful",[40,722,723],{},"Here's where I stopped digging forward for a moment and dug sideways. Before forecasting prices for a battery, it's worth knowing what a battery can earn from prices at all. Once you have nine years of hourly German day-ahead prices in a clean TimescaleDB, with a training snapshot as three Parquet files, you can answer questions that have nothing to do with forecasting – and that a lot of people are currently guessing about.",[40,725,726,729],{},[52,727,728],{},"The question:"," a 10 kWp PV system (45°, facing south) sells its power at the exchange price. How much more does it earn if a battery shifts the noon output into the expensive evening hours?",[40,731,732],{},"The setup is deliberately realistic: the battery doesn't get to know the future. Every day at 13:00, when tomorrow's prices are published, it solves a linear program over the next 35 hours and executes it until 13:00 the following day. PV yield comes from PVGIS (2005–2023) and, for 2024\u002F25, from the German PV feed-in scaled to the same plant. Round-trip efficiency 90 %, no charging from the grid.",[40,734,735],{},"First, the reason this question has become interesting at all:",[40,737,738],{},[686,739],{"alt":740,"src":741},"Average day-ahead price profile April–September for 2019–2025, showing the noon dip deepening to about 1 ct\u002FkWh while evenings stay at 13–14 ct\u002FkWh","\u002Fimages\u002Fblog\u002F2026\u002F10\u002Fpredictiondb-price-profile-summer.png",[40,743,744],{},"Until 2021 the price was nearly flat across the day. Since 2023, the PV boom pushes summer noon prices down to 1–2 ct\u002FkWh while the evening stays at 13–14 ct. Hours with negative prices went from 143 (2021) to 482 (2025). In 2025, PV power was worth only about half the average price. And that gap is exactly what a battery eats for breakfast:",[119,746,747,766],{},[122,748,749],{},[125,750,751,754,757,760,763],{},[128,752,753],{},"Year",[128,755,756],{},"Direct feed-in",[128,758,759],{},"+ 10 kWh \u002F 10 kW",[128,761,762],{},"+ 20 kWh \u002F 15 kW",[128,764,765],{},"+ 30 kWh \u002F 15 kW",[135,767,768,785,802,819],{},[125,769,770,773,776,779,782],{},[140,771,772],{},"2019",[140,774,775],{},"382 €",[140,777,778],{},"46 €",[140,780,781],{},"78 €",[140,783,784],{},"100 €",[125,786,787,790,793,796,799],{},[140,788,789],{},"2021",[140,791,792],{},"758 €",[140,794,795],{},"127 €",[140,797,798],{},"210 €",[140,800,801],{},"261 €",[125,803,804,807,810,813,816],{},[140,805,806],{},"2023",[140,808,809],{},"769 €",[140,811,812],{},"212 €",[140,814,815],{},"370 €",[140,817,818],{},"478 €",[125,820,821,824,827,832,837],{},[140,822,823],{},"2025",[140,825,826],{},"546 €",[140,828,829],{},[52,830,831],{},"342 €",[140,833,834],{},[52,835,836],{},"615 €",[140,838,839],{},[52,840,841],{},"798 €",[40,843,844],{},[686,845],{"alt":846,"src":847},"Extra revenue of the three battery sizes per year, 2019 to 2025","\u002Fimages\u002Fblog\u002F2026\u002F10\u002Fpredictiondb-storage-extra-revenue.png",[40,849,850],{},"In 2025 the battery adds 63–146 % on top of what the bare PV system earns at the exchange. Including PV forecast errors and a worse efficiency (85 %), about 290 \u002F 510 \u002F 640 € per year remain. A few things surprised me:",[83,852,853,862,868,874],{},[86,854,855,858,859,861],{},[52,856,857],{},"For a small home battery, the 13:00 horizon is almost enough."," A 10–30 kWh battery charged only from its own PV earns just 0–2 % more with perfect knowledge of the entire year. It fills up every day and empties in the evening or next morning, so it rarely has to look further. That changes as soon as the storage gets bigger relative to the PV, may charge from the grid (which raises the extra revenue by another 55–85 % in the same analysis), or has to decide ",[44,860,58],{}," 13:00 – and those are exactly the cases the week-ahead forecast is built for.",[86,863,864,867],{},[52,865,866],{},"The first 10 kWh are worth the most."," About 34 €\u002FkWh\u002Fyear, the next 10 kWh 27 €, the third 18 €. Beyond 20 kWh there are too many days without enough cheap noon power to fill it.",[86,869,870,873],{},[52,871,872],{},"Location barely matters."," North vs. south Germany changes the yield by −3 to +10 %, the battery's extra revenue hardly at all – it fills up almost every day either way.",[86,875,876,879],{},[52,877,878],{},"Break-even:"," with realistic numbers averaged over 2023–2025 and 15 years without interest, the battery may cost roughly 350 \u002F 300 \u002F 250 € per kWh installed to pay for itself through shifting alone. With 2025 prices, 430 \u002F 380 \u002F 320 €\u002FkWh.",[40,881,882],{},"Here's what one week in June 2025 looks like with the 20 kWh battery – charging in the cheapest noon hours, often during negative prices, and dumping it into the evening peak:",[40,884,885],{},[686,886],{"alt":887,"src":888},"Example week from 9 June 2025: PV yield, feed-in with battery and state of charge on top, day-ahead price below","\u002Fimages\u002Fblog\u002F2026\u002F10\u002Fpredictiondb-storage-example-week.png",[40,890,891,892,895],{},"A note before anyone forwards this to their installer: this applies to systems that sell at the exchange price – direct marketing, systems out of EEG funding, or new systems under the 2025 ",[44,893,894],{},"Solarspitzengesetz"," that get nothing during negative prices. With a fixed feed-in tariff, shifting earns exactly zero. And self-consumption isn't included; replacing 30+ ct grid power is usually worth more than any arbitrage.",[40,897,898,899,902],{},"There are two follow-up analyses in the repo: one comparing south vs. east-west vs. east-west plus a south facade for a 19.9 kWp \u002F 50 kWh setup (south wins, even though east-west power has the slightly better market value), and a 2027 scenario where household power at the exchange gets 5 ct more expensive but noon stays at zero. In that scenario the battery's extra revenue nearly doubles – because a battery earns from the ",[44,900,901],{},"spread",", not the price level. Every analysis is a script plus a README with tables and charts, and each release attaches them as rendered PDFs.",[71,904,906],{"id":905},"small-side-tunnels","Small Side Tunnels",[40,908,909],{},"A few other things fell out of the data along the way:",[83,911,912,922,928],{},[86,913,914,917,918,921],{},[52,915,916],{},"Is the 6-hours-newer GFS run worth waiting for?"," Barely – 2–8 % lower MSE for temperature, wind and radiation, 15–20 % for pressure. But for day-ahead PV it's the difference that matters, because the 06Z run only completes after the auction closes. Using a ",[44,919,920],{},"partial"," run makes it usable in time.",[86,923,924,927],{},[52,925,926],{},"Has GFS v16 gotten worse since 2021?"," No. Absolute errors went up slightly, but the weather got harder to predict in the same measure (the persistence baseline degraded twice as fast). Only global radiation genuinely improved.",[86,929,930,933],{},[52,931,932],{},"Can I compute the official market values myself from free data?"," Base and solar: almost exactly. Wind: systematically 3–5 % too high, and the error grows with the number of negative-price hours – most likely because curtailment is handled differently in the TSOs' extrapolation.",[71,935,937],{"id":936},"whats-next","What's Next",[40,939,940],{},"Most of the plumbing is in place: GFS ingest with fallbacks and backfill, energy data with source cross-checks, the MaStR clustering, the weather features, training, a model registry with champion\u002Fchallenger promotion, a prediction service and a Grafana status dashboard. Still on the roadmap:",[83,942,943,946,953,959,962,965,968,971],{},[86,944,945],{},"The GFS history since 2021 as gold-tier features, so the price model finally sees next week's wind and sun",[86,947,948,949,952],{},"Re-running the battery analysis with ",[44,950,951],{},"forecast"," instead of known prices – the real test of what a week-ahead view is worth",[86,954,955,956,958],{},"Calibrated quantile bands, a 28-day load level, and ",[307,957,504],{}," running as challenger against the v1 champion",[86,960,961],{},"Partial GFS runs for day-ahead PV",[86,963,964],{},"More weather models (ECMWF open data, ICON, GEFS ensembles) – ensemble spread would make a nice uncertainty feature",[86,966,967],{},"Sequence models (TFT, TiDE) that have to beat LightGBM first",[86,969,970],{},"The FastAPI layer for forecasting your own PV, wind or hydro plant",[86,972,973],{},"Heat, cooling and heat pump scheduling – the weather variables for that are already being collected",[40,975,976,977,983],{},"The code is on GitHub under the Business Source License 1.1, which allows internal use; check the ",[15,978,982],{"href":979,"rel":980},"https:\u002F\u002Fgithub.com\u002Fthe78mole\u002FPredictionDB\u002Fblob\u002Fmain\u002FLICENSE",[981],"nofollow","license"," before you build a business on it. The docs are in German, the numbers are in any language.",[40,985,986],{},"Back underground now. Somewhere between the GRIB files and next week's price curve there's a forecast that tells my battery on Monday morning what Thursday evening will pay – and I intend to dig until I hit it.",{"title":988,"searchDepth":989,"depth":989,"links":990},"",2,[991,992,993,994,995,996,997,998],{"id":73,"depth":989,"text":74},{"id":183,"depth":989,"text":184},{"id":273,"depth":989,"text":274},{"id":378,"depth":989,"text":379},{"id":497,"depth":989,"text":498},{"id":719,"depth":989,"text":720},{"id":905,"depth":989,"text":906},{"id":936,"depth":989,"text":937},[1000,1001,1002],"Dev","Energy","Tools","2026-10-25","Tomorrow's prices are free at 13:00 anyway – a half-built stack that forecasts German load and power prices for the whole coming week, so a battery can plan further than tonight.","md",{},true,"\u002Fblog\u002F2026\u002Fpredictiondb-forecasting-german-power-prices-from-free-weather",{"title":5,"description":1004},"blog\u002F2026\u002Fpredictiondb-forecasting-german-power-prices-from-free-weather",[1012,1013,1014,1015,1016,1017,1018,1019,1020,1021],"forecasting","machine-learning","lightgbm","noaa-gfs","grib2","timescaledb","day-ahead","price-forecast","photovoltaics","battery-storage","iNYZOFOVYspqXegDfbeE0bh643ASAVmrCYzGWtlVIi8",1792938388750]