A practical guide to understanding and using the Peaky Flexers electricity market model
← Back to the modelPeaky Flexers is a welfare-maximising linear program (LP) for an electricity market. It answers a deceptively simple question: if we had to design an electricity system from scratch, what mix of generation and storage capacity would maximise the total value that consumers get from electricity, minus the cost of providing it?
The model works with three types of supply technology—zero-variable-cost renewables (ZVC), gas peakers, and battery storage—and a demand side that can flex: consumers can shift demand to cheaper periods, expand consumption when prices are low, or curtail when the cost of supply exceeds the value of the electricity to them.
The key output is a set of market-clearing prices—one for each time period. These prices emerge naturally from the optimisation: they are the shadow prices on the energy-balance constraints, representing the cost to the system of serving one more megawatt-hour of demand in that period. In a well-functioning market, these are the prices that would prevail.
The model page has four input sections. Each represents a distinct part of the electricity system. Here is what each one means and why it is structured the way it is.
Real electricity demand varies hour by hour across the year. Rather than modelling all 8,760 hours individually, the model uses a Load Duration Curve (LDC)—a standard tool in energy economics that sorts hours by demand level.
The year is divided into three blocks:
| Block | What it represents | Default hours |
|---|---|---|
| Winter Peak | The coldest, darkest hours when demand is highest | 300 |
| Shoulder | A large middle band—spring, autumn, and milder winter days | 3,000 |
| Low Demand | Summer and overnight hours when demand is lowest and (often) renewables are abundant | 5,460 |
You only set the hours for each block. The total should sum to 8,760 (one year). The demand levels come from the tier definitions below.
The wind does not blow constantly during winter, nor does the sun always shine in summer. To capture this intermittency, each LDC block is split into two sub-blocks:
For each sub-block you set two parameters:
| Parameter | Meaning |
|---|---|
| % Hours | What fraction of the block's hours fall in this sub-block (must sum to 100%) |
| % Renewables Availability | What fraction of installed ZVC capacity is available during these hours |
For example, the default Winter Peak is split 10/90: for just 10% of winter peak hours (30 hours), only 15% of installed ZVC capacity is available. For the remaining 90% (270 hours), 80% is available. This creates a large price differential between the two sub-blocks, and it is precisely this differential that makes storage valuable within a block—storage can charge when renewables are plentiful and discharge when they are scarce, even within the same season.
Not all electricity demand is equally valuable. A hospital's life-support system has a very different willingness to pay than a dishwasher that could run at 3am instead of 6pm. The model captures this by dividing demand in each block into three tiers:
For each tier you set three parameters:
| Parameter | Meaning |
|---|---|
| Quantity (GW) | How many gigawatts of this tier's demand are present during the block |
| VoLL (£/MWh) | Value of Lost Load: the maximum price this demand is willing to pay. If the clearing price exceeds VoLL, this demand goes unserved ("curtailed"). |
| Shift Cost (£/MWh) | The cost of moving this demand to a later, cheaper block. Demand can only shift downward: Winter Peak → Shoulder → Low Demand. It cannot shift upward. |
Expandable demand represents new consumption that would switch on if prices fell low enough—think of energy-intensive industries that would locate near cheap power, or electric heating that becomes attractive when electricity prices drop. You specify a quantity (GW) and an activation value (£/MWh). If the clearing price drops below this value, the expandable demand activates.
The model does not pre-set any generation capacities. Instead, it decides how much of each technology to build, balancing capital costs against the value of the electricity they produce. There are three technologies:
Renewables (wind, solar) and nuclear. Once built, they produce electricity at zero fuel cost, but their output depends on weather conditions (captured by the availability profile above). The only input is the annualised capital cost (£/kW/year).
A flexible thermal plant that can run whenever needed but burns gas. You set:
Storage has two capacity dimensions:
The energy cost is amortised over the number of cycles per year you specify (default 365), since a battery that cycles daily spreads its capital cost over many more MWh than one that cycles once a year.
Round-trip efficiency (default 95%) means that for every MWh charged, only 0.95 MWh is available to discharge. The 5% is lost as heat. This creates an implicit cost: the storage must buy cheap and sell dear enough to cover not just capital costs but also this energy loss.
The model asks: what system design and operating pattern makes society best off?
It simultaneously decides:
The objective is to maximise net welfare: the total value that consumers place on the electricity they receive, minus the total cost of providing it (both capital and operating costs).
Think of it as a social planner trying to make the best possible use of available resources. The planner values each MWh of served demand at its VoLL, credits shifted demand at VoLL minus the shift cost, and charges for gas generation at its variable cost and all technologies at their capital cost.
The optimisation is subject to physical and economic constraints:
The market-clearing prices are not directly chosen. They emerge as shadow prices on the energy-balance constraints—they tell you the marginal value to society of one additional MWh of supply in each sub-block. In an efficient market, these would be the wholesale electricity prices.
For those who want the mathematics, here is the LP formulation. The model maximises:
Where b indexes blocks, i indexes sub-blocks within each block, t indexes demand tiers, h are hours, K are capacity variables, and C are capital costs. The variable cost of gas includes any carbon adder: cgasvar = variable_cost + carbon_price × emission_factor.
Subject to (for all sub-blocks b,i):
Shadow prices on the energy-balance constraints, divided by the sub-block's hours, give clearing prices in £/MWh.
After you click Optimise the System, the results page shows several sections. Here is what each one tells you.
A row of metric cards showing the headline results at a glance:
Two charts side by side:
Together, these tell you how the system spends its investment budget. A system dominated by ZVC will have high ZVC capital cost but zero variable cost. A system with more gas will have lower capital cost but higher variable cost.
This shows the raw demand profile before any optimisation. Sub-blocks are sorted from highest to lowest GW, and the three demand tiers are stacked within each sub-block (low-value at the bottom, high-value at the top). This is what the demand side "looks like" before the model decides what to do with it.
Use this chart as a reference point. After you look at the optimised results, come back here to see how much has changed—how much demand was shifted, curtailed, or served by different sources.
This is the post-optimisation counterpart of the input LDC. It shows total generation in each sub-block, broken down by source (ZVC in green, gas in red, storage discharge in blue). Sub-blocks are sorted by total generation from left to right.
Each sub-block is labelled with its clearing price. Hatched areas indicate curtailed demand.
A 2×3 grid of stacked bar charts (one per sub-block). Each chart has two bars:
All six charts share the same Y-axis so you can visually compare the scale of demand and supply across sub-blocks. The title of each chart shows the clearing price.
Storage charging appears as demand (it consumes electricity) while storage discharging appears as supply (it provides electricity). This is physically correct—charging is a load on the system.
The same structure as the GW charts, but multiplied by hours to show energy (GWh). Each sub-block has its own Y-axis scale because the blocks have very different durations (300 hours vs 5,460 hours), which would make the shorter blocks invisible if they shared an axis.
These charts are important for understanding the total energy balance. A sub-block might have low GW but high GWh if it lasts many hours. Storage is particularly visible here: you can see how much energy in GWh is being moved around the system.
Per-block Sankey diagrams showing what happens to demand in each block. Left-side nodes are demand sources (tiers + shifted-in demand + storage charging). Right-side nodes are destinations:
The width of each flow band is proportional to the GW. This chart only appears when there is flexibility activity (shifting, curtailment, or storage) in the system.
A system-level Sankey diagram showing energy transfers between sub-blocks. This is fundamentally different from the flexibility Sankeys above: while those show what happens within a block, this shows what moves between blocks.
Left nodes are energy exporters (sub-blocks that charge storage or shed demand). Right nodes are energy importers (sub-blocks that discharge storage or receive shifted demand), plus efficiency losses.
The diagram visually distinguishes two types of flows:
This is perhaps the most informative single chart. It reveals the full pattern of energy arbitrage: where is energy coming from, where is it going, and how much is lost to efficiency?
Two charts plus summary metrics:
The final section gives you the full numerical detail for each of the 6 sub-blocks: every demand tier (available, served, lost, shifted out), every supply technology (dispatched vs available), expandable demand activation, storage level, and the clearing price.
This is where you go to verify specific numbers or understand exactly what is happening in a particular sub-block. If a Sankey diagram shows an unexpected flow, the detail tables will tell you exactly which tiers and technologies are involved.
Peaky Flexers includes a built-in AI assistant powered by Anthropic's Claude. Click the AI Chat button in the top-right corner to open the chat panel.
The AI has access to the complete model source code, your current input values, and your latest optimisation results. This means it can reason about your specific scenario—not just generic electricity economics. Good questions include:
Here is an example of the kind of conversation you might have:
The price of £441/MWh in Winter Peak Low Renewables reflects extreme scarcity. During these 30 hours, only 15% of installed ZVC capacity is available. The marginal consumer willing to pay up to their VoLL sets the price.
Looking at the results: all 10 GW of High-value demand (VoLL £15,000/MWh) is served, all 20 GW of Mid-value demand (VoLL £800/MWh) is served, but some Low-value demand (VoLL £100/MWh) is being curtailed. The clearing price settles between the VoLL of the last tier being served and the marginal cost of the next unit of supply.
Storage is discharging at full power here, and gas peakers are running flat out. The high price is what is needed to justify the capital investment in gas peakers that only run for a few hundred hours per year.
To use the AI chat, you need an Anthropic API key. The model defaults to Claude Opus 4.6, which provides excellent analysis of the model's economics. To get a key:
Your API key is stored in your browser's local storage and is only sent to the Peaky Flexers server to proxy requests to Anthropic. It is never stored on the server.
The Clear Chat button resets the conversation history, which is useful when you change input parameters and run a new optimisation—the old context will no longer match the new results.