Sizing a Home Battery From Your Own Load Curve
Most of what passes for a home battery size calculator in the UK asks you for one number: annual kWh. You type 4,800, it divides by 365, multiplies by some fudge factor, and tells you that you need 10 kWh. That answer is a coincidence dressed up as arithmetic. Your annual total contains no information about when you use electricity, and when is the only thing a battery cares about. Two households on 4,800 kWh a year, one with an EV charging at 2am and one with electric showers at 7am, want batteries that differ by a factor of two.
You almost certainly already have the data to do this properly. If your meter is SMETS2 and you have ever exported a CSV from an app, you have half-hourly readings: 17,520 rows for a full year, one per 30-minute settlement period. This page is about turning those rows into a defensible capacity number, including the bits the widgets skip, like round-trip losses, inverter power limits, and whether your cheap-rate window is even long enough to fill the battery you are about to buy.
Why annual kWh divided by 365 is the wrong starting point
The quantity a battery actually monetises is not your daily consumption. It is your consumption outside the cheap import window, capped by what the battery can hold, summed over the year. Those three constraints bite at different times and none of them appear in an annual total.
Take Octopus Go as the reference case: five cheap hours from 00:30 to 05:30. A household averaging 13.3 kWh a day might already draw 2.1 kWh of that inside the window (fridge, freezer, standby, router, a dehumidifier on a timer). The battery is only ever competing for the remaining 11.2 kWh. Size against 13.3 and you have bought roughly 2 kWh of capacity that will never discharge into anything.
Then there is the shape of the distribution. The mean is 11.2 kWh, but the median in that same dataset is 9.8 and the 90th percentile is 16.4. A battery sized at the mean is undersized on 40% of days and oversized on the rest. Ofgem’s typical domestic consumption value for a medium single-rate household sits around 2,700 kWh, which tells you that anyone reading this page is already well outside the population those rules of thumb were calibrated on.
Getting half-hourly data out of your smart meter
Four routes, roughly in order of how much control they give you.
The Octopus API is the cleanest if you are a customer and have consented to half-hourly readings in your account settings. Your API key is the username, password blank:
curl -u "sk_live_xxxxxxxx:" \
"https://api.octopus.energy/v1/electricity-meter-points/1200012345678/meters/21E1234567/consumption/?period_from=2025-10-01T00:00Z&period_to=2026-09-30T00:00Z&page_size=25000"
{"consumption":0.412,"interval_start":"2025-10-01T00:00:00+01:00","interval_end":"2025-10-01T00:30:00+01:00"}
{"consumption":0.389,"interval_start":"2025-10-01T00:30:00+01:00","interval_end":"2025-10-01T01:00:00+01:00"}
Hildebrand’s Bright app works across suppliers and exposes the Glowmarkt API at api.glowmarkt.com/api/v0-1/, which gives you the same granularity plus near-real-time readings if you add their CAD dongle. Loop and Utrack will show you the curve in-app and export CSV, though with less useful timestamps. The old n3rgy consumer feed has been unreliable, so verify it still serves data before building anything on it.
If you already run Home Assistant, a Glow CAD publishing to MQTT gives you 10-second resolution, which is overkill for sizing but invaluable later when you want to see the actual shape of your evening peak rather than its half-hourly average. The averaging matters: a 7 kW shower for eight minutes reads as 0.93 kWh in a half-hour bucket, which looks like a 1.9 kW load and hides the fact that your battery inverter would have been flat out and still importing.
The one calculation that matters
Load the CSV, bucket by day, and sum only the periods outside your cheap window. In pandas that is four lines:
df = pd.read_csv("consumption.csv", parse_dates=["interval_start"])
df = df.set_index("interval_start").tz_convert("Europe/London")
peak = df.between_time("05:30", "00:29")["consumption"]
daily = peak.resample("D").sum()
print(daily.describe())
count 365.000000
mean 11.204384
std 3.512960
min 4.310000
25% 8.720000
50% 9.840000
75% 13.090000
max 24.660000
Note the tz_convert. Half the botched analyses I have seen come from treating a year of readings as UTC, which smears the cheap window by an hour for seven months and quietly corrupts every number downstream.
Now the sizing question becomes: for each candidate usable capacity U, how much energy does the battery actually deliver? For each day it is min(U, daily_demand). Sum over the year. That single expression, daily.clip(upper=U).sum(), is the whole model.
The marginal kWh test, with real numbers
Running that clip across candidate sizes for the household above, at a 26p day rate and 8.5p night rate. Because you must import roughly 1.12 kWh to deliver 1 kWh at 89% AC-to-AC round trip, the effective spread is 26 − (8.5 ÷ 0.89) = 16.4p per delivered kWh, not the headline 17.5p.
| Usable kWh | Annual kWh delivered | Marginal kWh from last 2 kWh | Marginal annual saving | Marginal cost @ £420/kWh | Payback on that step |
|---|---|---|---|---|---|
| 2 | 730 | 730 | £120 | £840 | 7.0 yr |
| 4 | 1,453 | 723 | £119 | £840 | 7.1 yr |
| 6 | 2,146 | 693 | £114 | £840 | 7.4 yr |
| 8 | 2,756 | 610 | £100 | £840 | 8.4 yr |
| 10 | 3,267 | 511 | £84 | £840 | 10.0 yr |
| 12 | 3,632 | 365 | £60 | £840 | 14.0 yr |
| 14 | 3,851 | 219 | £36 | £840 | 23.3 yr |
| 16 | 3,979 | 128 | £21 | £840 | 40.0 yr |
| 18 | 4,037 | 58 | £10 | £840 | 87.5 yr |
The shape is the point. Capacity does not have a payback period; each increment of capacity has its own, and the increments get worse fast once you pass the median day. Against a ten-year product warranty, this household stops at 10 kWh usable and would need a strong reason to go to 12.
Two corrections to apply before you trust your own version of this table. First, the first increment is not really £840, because it carries the inverter, the installation day, the fused spur and the G99 paperwork; a realistic first-block cost is £3,500 to £5,000, which pushes the whole-system payback well past what the marginal column suggests. Second, subtract standby. A hybrid inverter idling at 30 W burns 263 kWh a year, worth roughly £68 at the day rate, and that cost is flat regardless of capacity. It eats a fifth of the value of a 6 kWh battery and is why very small batteries rarely make sense in the UK at all.
Power limits will cost you more than you expect
Energy capacity is only half the specification. If your evening peak hits 4.8 kW and your battery inverter discharges at 3.0 kW, you import the difference at the day rate no matter how full the battery is.
Quantify it from the same dataset. Convert each half-hour to average kW (consumption * 2), clip at the inverter rating, and sum the excess. For the household above with a 3.0 kW limit, that came to 318 kWh a year of unavoidable peak-rate import, about £52. Moving to a 5.0 kW unit recovered nearly all of it. Half-hourly averaging understates this, so treat your answer as a floor rather than an estimate.
Worth knowing which products sit where: GivEnergy’s Gen 3 hybrids come in 3.0, 3.6, 5.0 and 6.0 kW; Fox ESS H1 spans 3.7 to 6.0; Sunsynk’s 5 kW single-phase unit is a common choice on mixed systems; a Tesla Powerwall 3 will do 11.04 kW continuous and 13.5 kWh usable, which is a different class of kit and priced accordingly. The 3.68 kW figure you keep seeing is the G98 fast-track export limit per phase, not a discharge limit for serving your own house.
Can you actually fill it overnight?
Five hours on Octopus Go, 10 kWh usable, and you need 10 ÷ 0.95 ÷ 5 = 2.1 kW of sustained charge rate before you account for the house drawing from the same supply. That is comfortable. Push to 16 kWh usable and you need 3.4 kW, which some AC-coupled retrofits simply cannot deliver, and your battery quietly starts most winter days at 80%.
Tariff choice changes the arithmetic more than any spec sheet does. Intelligent Octopus Go gives six hours from 23:30, which relaxes the charge-rate constraint by 20%. Cosy Octopus takes a different approach with three cheap windows including a midday one, and a midday top-up can halve the capacity you need, because you are no longer asking one charge to cover 19 hours. If you have not yet settled on a tariff, model the battery against two or three of them before you buy, and read the broader trade-offs in Battery and Tariff Optimisation, since the tariff decision constrains the hardware decision rather than the other way round.
What solar does to the answer
Add a 4.0 kWp south-facing array (PVGIS puts that near 3,500 kWh a year in the Midlands) and your battery now has two jobs competing for the same kWh. In late June the surplus after self-consumption can reach 14 kWh on a clear day, which saturates a 10 kWh battery by early afternoon and leaves the rest to export.
Here is where people oversize. The temptation is to buy capacity to catch that June surplus, but the value of a stored solar kWh is only the gap between your import rate and your export rate. At 26p import and 15p on Octopus Outgoing Fixed, that gap is 11p before losses, roughly 9p after. The overnight arbitrage spread was 16.4p. Solar surplus is worth about half as much per kWh as cheap-rate import, and it only exists for four or five months. Run the clip model twice, once on winter days and once on summer days, and you will usually find the winter case sets your capacity and the summer case adds almost nothing.
Nominal, usable, and reading the datasheet honestly
Every number in this page has been usable kWh. Datasheets mix conventions. A Pylontech US5000 is 4.8 kWh nominal at 95% depth of discharge, so 4.56 usable, and three of them give you 13.7 rather than the 14.4 you would assume. GivEnergy’s All in One is quoted at 13.5 kWh with usable capacity below that. Tesla quotes Powerwall 3 as 13.5 kWh usable directly.
Then apply degradation. Most LFP warranties guarantee 60% to 70% capacity retention at ten years or a fixed throughput in MWh. If you size at exactly 10 kWh usable today, you are running at roughly 8 kWh by year eight, and the clip model says that costs you about £60 a year by then. Sizing one increment above your marginal-payback cutoff is often defensible on those grounds alone. Sizing three increments above it is not.
Using AI tools on this without getting confidently wrong answers
Feeding a year of half-hourly CSV to Claude or ChatGPT with code execution works well, because the analysis is genuinely just resampling and clipping. What does not work is asking for the sizing recommendation directly: you will get a plausible paragraph built on assumed tariff rates and an assumed efficiency, and no way to tell which assumption drove the answer.
Ask instead for the intermediate artefacts. “Resample this to daily totals excluding 00:30 to 05:30 Europe/London, then produce a table of annual delivered kWh for usable capacities 2 through 20 in steps of 2.” Check the row count is 17,520 (or 17,568 in a leap year, with the DST days at 46 and 50 periods). Check the annual total against your actual bills. Then apply your own tariff numbers to the delivered-kWh column yourself, because that is the step where a hallucinated 15p becomes a £900 hardware decision.
The load curve you modelled is a snapshot of a household that no longer exists the moment anything changes. Buy an EV and your out-of-window demand may fall, because most charging happens inside the cheap window and the battery is suddenly competing for a much smaller prize. Fit a heat pump and January’s daily demand can double while August’s barely moves, which is exactly the pattern that justifies capacity the old curve rejected. Re-run the clip against fresh data after any of those, and after any tariff change: it is four lines of code and it is the difference between a battery that pays and one that idles at 30 W.