One Year of Buying Electricity Cleverly — First by Hand, Then by Script

One Year of Buying Electricity Cleverly — First by Hand, Then by Script

A year ago today, on 18 July 2025, I plugged in a battery and started buying my electricity cleverly rather than just paying for it. For the first nine months I did the timing by hand — reading the overnight rates each day and triggering the charges off a note in Home Assistant. Only in late April did I hand the job to a Python script I had built, and since then it has made 3,901 decisions — one every half hour — about whether to charge from the grid now, wait for a cheaper slot later in the day, or hold out until tomorrow. I forgot it was there most days. It didn't.

Over the year the battery saved about £233 against what the same electricity would have cost on the price cap — and it did that by buying cleverly, not by using less. The battery pulled around 1,060 kWh from the grid at an average of 11.7p a unit, while the cap charged more than double that for the identical kilowatt-hours. The gap between those two prices is the entire game.

The best moments were the ones where the grid paid me to take power. Octopus Agile went negative across 391 half-hours over the year — wholesale electricity so oversupplied the price drops below zero — and I caught them — by hand at first, the planner later — filling the battery while I was effectively paid to charge. The most extreme slot hit minus 12.05p a unit. On days like that the battery isn't saving money, it's earning it.

Solar joined the story a few weeks later, on 10 August, once the panels in the garden were logging properly. The array grew all year — I started with three panels on salvaged frames and kept adding more until it reached seven, a full 700 watts.

Across those eleven months it generated 390 kWh, peaking at 3.45 kWh on a clear July day once the whole array was up. About 126 kWh of that fed straight into the battery: free electricity displacing units I'd otherwise have bought.

By any normal measure the array is trivial. A typical home install is a 4-kilowatt roof of ten or twelve big panels — mine is 700 watts of seven small ones on salvaged frames at ground level. But it's sized for how I actually live: in a one-bed flat I use about 5.4 kWh of electricity a day, and the panels averaged 1.2 kWh — roughly a fifth of everything I used, without leaving the garden.

That yearly average flatters the winter and hides the summer. In December and January the panels were almost decorative, covering 2 to 5% of a day's use. By May and June they were covering half the month's electricity outright — the same 700 watts, just pointed at a higher sun.

And it was cheap to build. The whole system came to £2,203 — around £750 of panels and £1,450 of battery and electronics — against the £8,000 to £10,000 a roof-mounted solar-and-battery install with a certified fitter would run. No scaffolding, no MCS paperwork, no roof: just a garden, some salvaged frames, and a battery on a board indoors.

What surprised me most wasn't the money, it was the composure. The battery never once fell through its floor since the planner took over — the lowest it ever reached was 22.3%, and on eight occasions a safety-net rule quietly topped it up before a grey afternoon could run it flat. It has deferred charging 226 times, gambling that a cheaper slot was coming. Mostly the gamble paid off.

There's an uncomfortable truth under all of this. The kit has shrunk the energy half of my bill so far that the fixed daily standing charge now dominates what's left. In my best month the electricity I actually used cost £26 against a £75 cap equivalent — but the standing charge doesn't care whether I generate my own power or not. The battery won the part of the bill it's allowed to fight.

The year in numbers
£233saved vs the price cap
1,899 kWhelectricity used over the year
3,901half-hourly charge decisions
1,060 kWhbought for the battery at 11.7p avg
391negative-price half-hours caught (low −12.05p)
390 kWhsolar generated (best day 3.45 kWh)
22.3%lowest the battery ever got — floor never breached
Savings are electricity units against the price-cap unit rate (what the same kWh would have cost with no battery and no Agile) — not the dual-fuel bill, which also carries gas and the standing charge the kit can't touch.

Those are the results. The rest of this post is how they were earned — because a clever buyer that occasionally lets the battery run flat, or freezes when the network hiccups, isn't much use. The real work of the last few months was two less glamorous things: teaching the planner to protect itself, and moving it out of my blog into a house of its own.

Chapter one: it learned not to get caught out

The original logic was tuned to spend as little as possible — exactly the sort of thing that talks itself into deferring a charge on a grey afternoon, then watches the battery slide toward empty. Cheap is no good if the lights go out. So the fix came in two layers.

The first is predictive. Before the planner commits to a run of cheap slots, it simulates where the battery's charge will actually go between now and the first of them. If that path would dip below a safety margin — five points above the hard floor — it quietly inserts a bridging charge to carry the gap. It plans not to need rescuing.

The second is reactive, for when the plan meets reality. If the charge actually falls into the danger band, the code that drives the charger overrides everything else and forces it on, as long as the rate is under a sensible ceiling. Belt, and braces.

The failure that actually bit me was subtler. The planner wakes on the hour and half-hour; if Home Assistant is restarting at that exact moment, the tick is skipped and the charger stays in whatever state it was last left in. One May evening that left it charging at 28.6p for twenty-two minutes before I noticed.

Now a separate five-minute watchdog checks the switch against what the plan wants and corrects it. It's idempotent — it only writes when the state is wrong — so it can't fight the main planner, it just covers for it when the network drops a beat.

Safe first, cheap second — the table below is the year's tally.

The safety record — since the planner took over
Neverthe battery fell through its floor
22.3%the lowest it ever reached
the reactive override forced a charge
226×a charge was deferred for a cheaper slot

Chapter two: it moved out of the blog

The second change is architectural, and I'm quieter about it because nothing visible moved. The planner was born inside this blog's codebase — same repository, same database file, the reporting and the decision-making tangled together.

That always sat wrong. A blog is a blog; the thing that decides whether to pull a kilowatt into a live battery is not — and it shouldn't be able to break because I fumbled an edit to a travel post.

So I pulled them apart. The planner now lives in its own repository, with its own database of rates and decisions. The blog keeps a database that knows only about blog things. Neither can reach into the other's tables.

They talk over a message bus instead. The planner broadcasts a compact summary of its latest decision and its next 48 hours; the blog subscribes. When my solar dashboard loads, none of it is reading the planner's database live — it's reading the last thing the planner published. The blog could fall over entirely and the battery would carry on charging exactly as planned, none the wiser.

That's the version of grown-up I actually wanted. The interesting logic hasn't changed much — it still compares, it still counts the pennies. It's just harder to break now, and when it does break it fails in the right direction. Three months in, the little script that runs my flat's electricity is already something I'd trust to run unattended — which was the whole point of building it.

Chapter three: the record that lasted a day

I would love to end there. I could not. This post was a day old when the sentence above — trust it to run unattended — was put to the test, and failed. At two the next morning the battery slid to 18.6%, straight through the floor I had just sworn it never crossed.

The cause was Chapter two's sting in the tail. Moving the planner into its own house left a stale copy of the old code behind — and the five-minute watchdog was still reading it. That copy predated the emergency override, so every five minutes it switched the charger back off, cancelling the rescue the live code kept ordering. My safety net was throttling the thing it was built to save.

The fix was a single line — point the watchdog at its own code, never a neighbour's — plus two tests that break the build if it ever drifts again, and a sweep closing the same trap in two other scripts. I wrote the whole night up here. The lesson stuck: “we moved it” and “we moved all of it” are different sentences, and a safety net I have never watched fail is only decoration.

The constant that was right on average

One number inside the planner deserves its own confession. For most of the year it assumed a flat 30% of solar generation reaches the battery — the rest feeds the house directly. When I finally audited it against a year of measured data, the lifetime average came out at 0.302. You will rarely see a magic number vindicated that precisely.

One year of the measured solar-to-battery fraction: 30-day rolling ratio swinging from 0.03 in midwinter to 0.35 in midsummer around the flat 0.30 constant
A year of the measured fraction — the 30-day rolling ratio the planner now uses, against the flat constant it replaced.

And yet the chart shows why the right average was still the wrong number. The true fraction swings from 0.03 in the depths of December — the panels barely covering the house's daytime load, let alone spilling into the battery — to 0.35 in high summer. The flat constant under-credited the summer sun by a sixth, and in winter it credited solar that never existed: phantom kilowatt-hours the planner counted on and the battery never received, in exactly the season the battery can least afford optimism.

So the constant retired. The planner now measures its own delivery — the same rolling thirty days of yield-versus-battery-intake you see in green — and plans on that, falling back to the old 0.30 only when deep winter makes the ratio too noisy to trust. The day it went live, the forward plan dropped a charging slot for the following afternoon: the model finally believed the sun would deliver what a year of data said it would.

A year in, the thing I'd tell anyone weighing this up is that the battery is only half of it. The other half is an unglamorous script that never sleeps, never forgets to read the overnight rates, and treats every half-hour as a small decision worth getting right. £233 isn't a fortune — but I did nothing to earn it except let the machine pay attention.

The scoreboard has since grown posts of its own: the one number that judges the planner, and whether I'm actually winning.

Thinking of switching energy supplier? We both get £50.

Switch to Octopus Energy →