Optimizing hydro storage and electricity trading
The transmission system moves energy across space. Storage moves it through time. Linear programming (LP) models can optimize when and where to move energy based on market price and system constraints.
Approach
British Columbia and Uruguay have electricity systems anchored by large hydroelectric generation facilities. Both jurisdictions trade energy across their borders, creating opportunities for optimization. The key difference between regions is that Uruguay's system sets prices for sale into multiple markets, while British Columbia trades against spot prices in multiple markets.
We formulated a linear programming (LP) model in AMPL to represent a simplified hydroelectric system trading electricity into multiple neighboring markets. The model captures the essential characteristics of both systems, including storage capacity, generation limits, and market prices. For British Columbia, we compare model outputs for one reference year, FY2025 (April 2024 to March 2025), to published financial reporting to validate the model's accuracy and reliability. For Uruguay, we compare the 2023 and 2024 model outputs to published market price data. The same LP model is used for both jurisdictions, with different constraints and different input data.
British Columbia FY2025 (April 2024 to March 2025)
Uruguay 2024
- Hydro
- Thermal
- Other
- Intertie
Results
The model performs relatively well across both jurisdictions as a simplified representation of the hydroelectric systems.
British Columbia. Monthly net exports across Mid-C, CAISO, and Alberta correlate at 0.582, but modeled trade volumes exceed actual trades (−9.1 TWh predicted against a province-wide −7.8 TWh). The monthly hydro shape lands within 0.79 percentage points of StatCan's. Price is where it is plainly beaten: it sells into the US at $58.32/MWh against BC's realised $85.52, and buys at $23.35 against $33.87. Three markets cannot reach most of the counterparties BC actually trades with, and the capture price is where that shows up.
Uruguay. The model hourly price tracks ADME's sanctioned spot at r = 0.853, averaging $90.88/MWh against $87.61, and monthly net interchange correlates at 0.829. The water value tracks ADME's own published one at r = 0.938, but sits $28.80/MWh under it on average.
Figure 01 — Trading margin
The model trades more, in both directions
Trade volumes by market (red) against BC’s reported trade (gray). Exports are up and imports are down. The model only trades against the three markets shown.
Net position: model −9,083 GWh against −7,838 reported
Net position: model −9,083 GWh against −7,838 reported
Figure 02 — Reservoir storage
The model fills and drains when the reservoirs do
Measured levels are metres and the model is TWh of energy; nothing public converts one to the other, so each series is normalised over its own range and only the timing compares. Williston tracks at r = 0.98.
Figure 03 — Water value
The water value holds flat; the price wanders
The value of stored water — the shadow price of the storage balance — is one number all year, $41.61/MWh, while the Mid-C price moves day to day around it. A run that ends where it started and never touches a reservoir bound has nothing to reprice.
The water value is one number all year, $41.61. The Mid-C price moves $93.80 across the same days.
Figure 01 — Trading margin
The model sells into Argentina where ADME did not
Volumes by interconnection against ADME’s own metered interchange. The model exported 968 GWh to Argentina against a reported 211: Argentine prices were high in the drought and the LP sold into them, while ADME’s mandate is supply security and not margin.
Net position: model −450 GWh against −1,159 reported
Net position: model −450 GWh against −1,159 reported
Figure 02 — Reservoir storage
The rolling run tracks the reservoir, not just its shape
Unlike British Columbia, the measured counterpart here is in the model’s own units — Bonete’s level converted to cascade energy — so the levels are comparable and not only the timing. The run follows the drought down at r = 0.94.
Figure 03 — Water value
The water value moves, because the reservoir does
The surface reprices storage every week rather than holding one number all year, so the water value runs from $-0 to $222/MWh against ADME’s sanctioned spot. It is the same dual as British Columbia’s, in a year where storage was actually scarce.
The water value moves $222.01 across the year. The adme spot moves $250.00, about 1 times as far.
Methodology
The LP model is a stylized representation of hydro systems that trade into neighboring markets. Results are illustrative rather than predictive. The model objective is to maximize annual trading margin over 8,760 hourly periods. This is achieved by scheduling hydro generation, other generation, imports and exports, and storage levels while respecting physical and operational constraints. In total, the model has 78,840 variables, and a full year solves in 1.8 to 4.5 seconds across the five full-year runs behind this case study and its companions.
Model formulation
The model formulation is:
maximize TradingMargin:
Revenue and trading costs
Trading revenue: Exports sold and imports bought at each market's hourly price, which can be negative.
Price at each market in each hour $/MWh parameter · price
Exports sold to each market MW variable · X
Imports bought from each market MW variable · Y
Revenue and trading costs
Wheeling cost: The all-in cost of scheduling to or from a market, paid on flows both ways.
Wheeling cost to or from each market $/MWh parameter · wheel
Exports sold to each market MW variable · X
Imports bought from each market MW variable · Y
Revenue and trading costs
Offer-curve discount: Each deeper tranche of exports sells at a wider discount to the price.
Discount on each tranche of the offer curve $/MWh parameter · disc
Exports at each tranche of the offer curve MW variable · Xk
Revenue and trading costs
Bid-curve premium: Each deeper tranche of imports costs a higher premium over the price.
Premium on each tranche of the bid curve $/MWh parameter · bid_prem
Imports at each tranche of the bid curve MW variable · Yk
Operating costs and penalties
Shortfall penalty: A penalty on each MWh the run ends below its storage target.
Penalty per MWh of storage shortfall $/MWh parameter · short_penalty
Energy the run ends below its storage target MWh variable · Short
Operating costs and penalties
Thermal running cost: The running cost of each thermal unit's output.
Running cost of each thermal unit $/MWh parameter · th_cost
Thermal generation, by unit MW variable · Th
Operating costs and penalties
Reserve penalty: A penalty on each MWh of the deepest draw below the reserve floor.
Penalty per MWh of the deepest reserve draw $/MWh parameter · reserve_penalty
The deepest draw below the reserve floor, across all hours MWh variable · Below
Operating costs and penalties
Spill tie-breaker: A token cost on water released without generating, so that storing it wins a tie with spilling it.
Token cost on water released without generating $/MWh parameter · spill_cost
Water released without generating MWh/h variable · Spill
Terminal value
Terminal value, curve: The water left at the end, valued on a concave curve of final storage.
Value of the water left at the end $ variable · Tv
Terminal value
Terminal value, flat: Or valued at a flat rate per MWh. A run uses one terminal value or neither, never both; full-year runs use neither and hold the storage target instead.
Flat water value $/MWh parameter · wv
Storage at the end of the last hour MWh variable · S
Revenue and trading costs
4 terms: What the schedule earns at market, less what moving it there costs.
Operating costs and penalties
4 terms: Fuel, and the soft limits a run may buy its way past.
Terminal value
2 terms: What the water left in storage is worth when the run stops.
Learn more about the model variables, constraints, and assumptions
Model Variables
The model includes 78840 variables to represent the electric system and trading environment across two jurisdictions.
Every hour
- Hydro generation MWFrom the must-run minimum to capacity
- Storage at the end of the hour MWhBetween the reservoir floor and ceiling
- Water released without generating MWh/hUp to the spill limit, where a case sets one
- Exports sold to each market MWUp to that market's export limit
- Imports bought from each market MWUp to that market's import limit
- Exports at each tranche of the offer curve MWUp to the tranche's depth
- Imports at each tranche of the bid curve MWUp to the tranche's depth
- Thermal generation, by unit MWUp to the unit's capacity
- Must-run energy curtailed rather than generated MWUp to that hour's must-run surplus
Once per run
- Energy the run ends below its storage target MWhUp to the target itself
- The deepest draw below the reserve floor, across all hours MWhUp to the reserve itself
- Value of the water left at the end $No more than the lowest cut at final storage, and zero when a run has no cuts
Constraints
Model constraints define the feasible solution space the model can solve for. Our model includes 12 constraints to represent the electric system and trading environment across two jurisdictions.
Assumptions
The LP model is a demonstration only, not a full representation of the system. It reduces dimensionality by aggregating all the hydro plants into a single reservoir, and it ignores environmental flow constraints, head effects, and river routing.
Key assumptions of the model include:
- The trading of gas, environmental attributes, and other commodities are excluded.
- Only the markets modelled for a system exist. Where it actually trades beyond them, that energy has nowhere to go, which pulls the modelled capture price below the realized one.
- Must-take supply — independent power, wind, solar, biomass, and treaty exchanges — is netted off load rather than dispatched. The model absorbs it and cannot move it.
- Domestic load is inelastic: served in every hour, and never shed.
- Trade is limited only by the rating of each path. There are no scheduling rules, transmission products, counterparty or credit limits, and no distinction between day-ahead and real time, so buying and selling in the same hour costs only the wheeling charge.
- Losses are carried entirely by that flat wheeling charge, and no reserve, ancillary-service or capacity obligation applies beyond the storage floor.
- Every hour is alike: no unit commitment, no start costs, no outages or maintenance, and nothing below hourly resolution.
- British Columbia's storage plants are aggregated into a single reservoir, and its walls — a ceiling of 25 TWh and a floor of 3 TWh — are illustrative rather than published capacities. Neither is reached: the reference year turns between 6.8 and 20.8 TWh, so the bounds decide nothing and the storage target does.
- Intertie ratings, wheeling charges and the 3,000 MW minimum generation floor are likewise illustrative parameters rather than filed ratings or tariffs. The floor moves the answer more than any other assumed parameter, and it is the one with no source behind it.
- Storage is fixed at both ends of a run, so the reservoir path follows from that target rather than predicting it.
- Where inflow is not published it is backed out of the storage balance, which makes it net of evaporation and seepage, and flat within each day.
Data sources
The model uses published data as inputs and to verify model outputs, using synthetic data only where necessary. A full list of data sources can be found at the link below. Assumed parameters are labelled as assumptions and are never fitted to the series used to validate. Currencies are converted to USD for consistency.
British Columbia. Key data sources include: BC Hydro hourly load, total energy production, observed water levels, and electricity spot prices in Mid-C, CAISO, and AESO. The within-year inflow shape is synthetic, and its reservoir bounds, intertie ratings, wheeling charges, and 3,000 MW generation floor are illustrative parameters rather than measurements.
Uruguay. Key data sources include: ADME hourly generation, demand, interchange, water levels, and the marginal cost of operation in Brazil and Argentina. Inflow is back-calculated from observed levels, generation, and spill.
Expand the list of references and data sources
Hourly market data
[1]
CAISO OASIS — Malin 500 kV node MALIN_5_N101, day-ahead
The hourly California price. OASIS keeps only a rolling window, so the archive here cannot be re-fetched.
[2]
CAISO OASIS — BPA EIM aggregation point ELAP_BPAT-APND, real-time
The Mid-Columbia price, and the model's largest market.
[3]
AESO public pool price API
The hourly Alberta price, published in Canadian dollars.
[4]
BC Hydro balancing authority hourly load report
The hourly load shape, rescaled to BC Hydro's own integrated system.
Published accounts
[5]
BC Hydro: Annual Report 2025/26
Every annual energy figure the build uses, FY2022 to FY2026, and hydro generation and capacity plant by plant.
[6]
BC Hydro: F2026–F2027 Revenue Requirements Application
Appendix F Attachment 1, Schedule 4.0 gives system trade at transfer prices, a cross-check on the annual report. Appendix A gives Powerex trade income by year, as a comparator only.
[7]
BC Hydro: Service Plan 2026/27 to 2028/29
BC Hydro's own exchange-rate and Mid-Columbia forecast assumptions.
[8]
BC Hydro: IPP Supply Lists
Splits independent power into run-of-river and everything else.
Currency
[9]
Bank of Canada Valet, series FXUSDCAD
Converts the Alberta pool price to US dollars, day by day. Every price the model sees is in US dollars.
Monthly yardstick (scoring only)
[10]
Statistics Canada tables 25-10-0015-01 and 25-10-0016-01
Monthly generation by type and producer class, and monthly receipts, deliveries and availability: the hydro shape test, and the interprovincial and US flows.
[11]
Canada Energy Regulator, monthly electricity exports and imports
The only public monthly value of British Columbia's exports.
ADME, the system operator
[12]
ADME datos abiertos
Hourly generation, demand and interchange; Río Negro plant telemetry, which gives storage, generation and inflow; thermal output plant by plant; declared plant costs; the sanctioned spot price every validation scores against; ADME's own water value; and Salto Grande flows.
[13]
ADME procedures — P004 on export offers, and Liquidación SPOT y Facturación
Why a neighbour's price is treated as a ceiling rather than a market, and the basis for the reported price cap. Followed, not fetched.
Neighbouring markets
Model Improvements
Higher resolution of reservoir inflow and dispatch within the energy system
The model includes no cascade, no head effect, no river routing, and no environmental flow constraints beyond a flat minimum. Real systems are a network of reservoirs with local conditions that bind at different times.
Gross-flow path limits
Counter-flows are not netted and losses are not modelled, so the interties are treated more strictly than they behave.
More information
The code behind this case study — the model formulation in AMPL, the Python wrappers that build its inputs and iterate it, and the results of the runs reported here — is on GitHub.
Let's connect if you want to discuss how LP can be applied to your own optimization problems.