64,670 hourly SEM prices (Oct 2018 – Feb 2026) + 7-year battery simulation All revenue and spread figures on this page are gross (before fees). D-TUoS and other network charges are not deducted. See Fee Structure for net impact.
C1 Data Quality
Records
64,670
Years
7.4
PF Revenue (gross)
EUR 93k /MW/yrEUR 4.65M /yr
Capture Rate
62%
How was this page built?
1
Price Data
64,670 hourly SEMOpx Day-Ahead Market prices scraped from SEMO’s published settlement data. Covers October 2018 through February 2026 (7+ years). Every hour validated for completeness (99.7% coverage).
2
Backtest Engine
Built a Python battery dispatch simulator (scripts/backtest_battery.py) modeling a 50 MW / 200 MWh battery with 85% round-trip efficiency. Tested two strategies: Fixed Schedule (charge 01–04, discharge 16–19 daily) and Perfect Foresight (optimal dispatch knowing all prices in advance).
3
Capture Rate Analysis
Computed the ratio of achieved spread to theoretical max spread for each strategy across all 2,557 days. Fixed Schedule captures 54% of theoretical maximum; Perfect Foresight captures 100% by definition.
4
Trend Regression
Fitted a linear trend to monthly capture rates to test whether spreads are compressing over time. Found a slight upward trend (EUR +2.1/MWh/year), but with high variance (R² = 0.08).
Limitations: The backtest uses Day-Ahead prices only — real trading also involves intraday and balancing markets. The Fixed Schedule strategy is conservative (a real operator would adapt). Perfect Foresight is an upper bound. The actual capture rate will be somewhere between 54% and 100%.
Nerd level: (serious time-series crunching — we had a great time with this dataset)
Annual Mean Day-Ahead Price EUR/MWh · Source: Energy-Charts / ENTSO-E
C1 — Actual SEM auction data
2021–2022 shaded zone marks the European energy crisis. Mean prices rose 3.6x from pre-crisis levels (EUR 44 to EUR 181).
Post-crisis prices (2023–2025) have stabilised around EUR 115/MWh — still 2.4x pre-crisis levels.
Source: sem_complete_hourly.csv, 64,670 records.
02Daily Price ShapeHow the 24-hour price curve has evolved · 2019–2025
Normalised Hourly Profile by Year Index (1.0 = daily mean) · click years to compare
The daily price curve is changing shape as renewables grow.
Three key shifts visible across 2019–2025:
1. Duck curve emerging. Midday prices (11:00–14:00) have fallen from 78% of peak (2022) to 63% (2025)
as solar capacity grew from ~250 MW to ~2,100 MW. By 2030, at 8 GW solar, midday could fall to 40–50% of peak —
creating a second daily arbitrage cycle (charge midday, discharge evening).
2. Peak shifted right. The daily price peak moved from 17:00 (every year 2019–2024) to 18:00 in 2025 —
the first shift in the dataset. Solar generation during winter afternoons is pushing the peak later, matching
the post-sunset demand spike.
3. Overnight trough is stable. The cheapest hour remains 03:00–04:00 across all years.
As wind penetration grows, this trough deepens (more sub-zero hours) but doesn’t shift in timing.
This means the fixed charge window (01:00–05:00) remains valid even as discharge timing evolves.
C1
Year-by-year peak, trough & midday comparison
Year
Peak hour
Trough hour
Peak/Trough
Midday/Peak
Solar (MW)
2019
17:00
03:00
2.31x
70%
~50
2020
17:00
03:00
3.05x
60%
~50
2021
17:00
03:00
2.09x
71%
~100
2022
17:00
03:00
1.70x
78%
~250
2023
17:00
03:00
1.73x
74%
~500
2024
17:00
03:00
1.90x
70%
~1,200
2025
18:00
03:00
1.88x
63%
~2,100
Midday/Peak: average price at 11:00–14:00 as % of daily peak price.
The 15-point drop from 78% (2022) to 63% (2025) is classic duck curve emergence driven by 8x growth in solar capacity.
2020 is anomalous (COVID demand collapse made overnight troughs extremely deep, depressing the ratio).
Source: data/price_profiles_analysis.json, time-of-day-profiles.md.
Each year’s hourly profile is normalised by its annual mean price (index 1.0 = annual average EUR/MWh).
This removes the effect of absolute price levels (e.g. the 2022 energy crisis) and reveals the underlying shape of the daily curve.
Click year buttons to toggle lines on/off. Source: data/price_profiles_analysis.json, 64,670 hourly observations.
03Peak vs Off-Peak SpreadThe arbitrage opportunity window · 2019–2025
Key finding: Post-crisis spreads widened 87% vs pre-crisis (avg EUR 23.9 vs EUR 17.1).
Even as absolute prices retreated from crisis peaks, the daily price range has remained elevated —
structurally positive for battery arbitrage economics.
C1
Spread = average price during peak hours (08–20 UTC) minus off-peak hours (00–08, 20–24 UTC).
This is a profile method (fixed time blocks) — it uses the same hours every day regardless of where the actual cheapest/dearest prices fall.
Actual daily best-hours spreads are typically 30–40% higher because individual days are more extreme than the annual average.
See Section 07 (Spread Anatomy) for the comparison.
These are gross market spreads before D-TUoS and other fees.
D-TUoS charges add ~EUR 26–29/MWh to import costs, meaning net realisable spread is significantly lower.
Source: sem_daily_summary.csv, peak_offpeak_spread column.
Revenue per MW per YearAnnual Revenue (50 MW)EUR · Two strategies compared
C1 — PF: LP-optimal on actual pricesC1 — FS: Deterministic scheduleC3 — Real-world capture between PF and FS
PF = achievable ceiling, not a theoretical fantasy.
Day-ahead prices are largely predictable from public inputs (wind/solar forecasts, demand, gas prices),
so a well-tuned algorithmic trading system can realistically capture 80–90% of PF.
FS = floor (fixed charge 02–06, discharge 17–21, zero intelligence — same windows 365 days/year).
The dashed line at EUR 80k marks the financial model assumption.
The gap between PF and FS is not “lost revenue” — most of it is recoverable with decent algorithms.
Source: backtest_results.csv. 50 MW battery, 4hr duration, 85% AC-AC RTE, daily SoC reset.
LP solved with scipy/HiGHS, zero solver failures across 2,695 days.
Revenue shown is gross (before D-TUoS, SEMO, and other fees).
Under current D-TUoS charges (EUR 3,115k/yr for 50 MW), net revenue after all fees is significantly lower.
See Fee Structure for the full fee burden (41.1% of gross revenue).
05Declining Capture RateHow much of the theoretical maximum does a fixed strategy capture?
What is capture rate? It’s the percentage of the theoretical maximum revenue that a simple fixed-schedule strategy actually earns.
A battery charging 01:00–04:00 and discharging 16:00–19:00 every day (Fixed Schedule) captured 73% of what a perfect oracle would earn in 2020, but only 54% in 2025.
Why is it falling? As renewable penetration increases, the cheap and expensive hours shift around more. On windy summer days, the cheapest hour might be 14:00 instead of 03:00.
A fixed strategy misses these shifts; an adaptive one captures them. The widening gap between PF and FS means trading sophistication matters more every year.
Important: The PF ceiling itself is not falling — only the fixed-schedule floor.
An ML-based optimizer that adapts daily using wind/demand/price forecasts stays close to PF regardless of how much renewables penetrate.
The declining capture rate is a problem for naive strategies, not for well-optimised ones.
C2
Capture rate = FS annual revenue ÷ PF annual revenue. Only 7 data points — trend could be cyclical rather than structural.
The decline from 73% → 54% represents EUR ~18k/MW/yr of “lost” revenue that better algorithms could recover.
Source: backtest_summary.json.
06Duration EconomicsSpread per MWh by battery duration · 1h vs 2h vs 4h
Achievable Spread per Hour of Storage EUR/MWh · Best N-hour charge/discharge windows
Each additional hour of storage captures slightly less per MWh — but in Ireland, the decline is gradual.
A 1h battery captures the single best charge/discharge hour: EUR 111/MWh in 2025.
A 4h battery averages EUR 93/MWh across its 4 best hours — only 16% less per MWh.
Why? The Irish SEM has broad peaks and troughs (8–9 cheap hours, 7 expensive hours daily), not sharp spikes.
Wind-driven low prices persist for 6–10 hours overnight, and evening peaks span 3–5 hours.
A 4h battery can fill and empty without hitting the steep part of the price curve.
The real limitation of 4h is optimization complexity.
You need to correctly identify 4 cheap hours and 4 expensive hours each day, not just 1–2.
With renewable penetration shifting price shapes daily, this is harder for fixed strategies —
but achievable for ML-based dispatch that adapts to wind/demand forecasts.
The per-MWh value is there; the challenge is capturing it.
C1
Backtest revenue per MWh of installed storage (2h vs 4h)
Year
2h (EUR/MWh/yr)
4h (EUR/MWh/yr)
2h/4h ratio
2019
16,545
13,261
125%
2020
16,060
12,634
127%
2021
34,317
27,601
124%
2022
43,452
35,394
123%
2023
26,041
20,891
125%
2024
30,394
24,952
122%
2025
34,700
28,827
120%
Revenue per MWh of installed storage, perfect foresight strategy. 2h = 50 MW / 100 MWh; 4h = 50 MW / 200 MWh.
The 2h battery earns 120–127% per MWh — each MWh “works harder.”
But per MW installed, the 4h earns 61% more (EUR 91k vs EUR 57k/MW/yr), and since doubling duration adds only ~33% to CAPEX
(grid connection, transformer, EPC are fixed MW costs), 4h has a higher ROI in every scenario tested.
Source: duration-comparison.md.
“Best N hours” = average spread between the N most expensive and N cheapest hours each day, reflecting the spread available to a battery of that duration.
All values are gross (before RTE losses and fees). Actual revenue is ~85% of spread (RTE) minus D-TUoS.
Source: scripts/analyze_duration.py.
07Spread AnatomyWhy different analyses give different numbers · mean of differences ≠ difference of means
Key insight: “the spread” depends entirely on how you measure it.
Two valid methods give very different numbers for the same price data:
Method A — Profile method (difference of means):
Look at the average 24-hour price curve for the year, pick the 4 cheapest and 4 dearest hours.
For 2025: cheapest 4h avg = EUR 87, dearest 4h avg = EUR 153. Spread = EUR 65/MWh.
This is what you’d estimate by eyeballing the daily price shape chart above.
Method B — Daily method (mean of differences):
For each of 365 days, find the 4 cheapest and 4 dearest hours, compute that day’s spread, then average across all days.
For 2025: EUR 93/MWh — 43% higher.
Why the gap? Individual days are more extreme than the average.
On windy nights, the cheapest hours drop to EUR 0–30; on cold evenings, peaks hit EUR 200–400.
These extremes wash out in the annual profile but create real daily trading opportunities.
Method B is the correct one for estimating arbitrage revenue — you trade each day, not the average day.
The LessWrong post’s ~9c/kWh (EUR 90/MWh) is consistent with our data.
Our daily 1h spread for 2024–2025 is EUR 102–111/MWh; the 4h daily spread is EUR 84–93/MWh.
At 1–2h duration without RTE adjustment, you get a number right around EUR 90.
Neither analysis is wrong — the number depends on duration, RTE treatment, time period,
and whether you compute daily then average (higher) or average then compute (lower).
What’s left to subtract: Round-trip efficiency (~85%) removes EUR 10–30/MWh depending on charge prices.
D-TUoS + SEMO fees remove another ~EUR 51/MWh under current policy (see Fee Structure).
With D-TUoS reform, fees drop to ~EUR 20/MWh.
C1
Algorithm details & Python source code
Three algorithms are used in this research, each answering a different question:
1. Best-N-Hours (spread analysis)
for each day in dataset:
sort all 24 hourly prices low → high
charge_avg = mean(cheapest N hours)
discharge_avg = mean(most expensive N hours)
gross_spread = discharge_avg − charge_avg
net_spread = discharge_avg − (charge_avg ÷ 0.85)
return mean(all daily net_spreads)
Used in:scripts/analyze_24month_spreads.py,
scripts/backtest_duration_comparison.py.
Hours need not be contiguous — the algorithm picks the absolute cheapest/dearest regardless of timing.
2. Perfect Foresight LP (backtest upper bound)
for each day, solve linear program:
maximise Σ(price_h × discharge_h − price_h × charge_h)
subject to:
0 ≤ charge_h ≤ 50 MW (power limit)
0 ≤ discharge_h ≤ 50 MW
SoC_min ≤ SoC_h ≤ SoC_max (capacity limits)
SoC_{h+1} = SoC_h + 0.85×charge_h − discharge_h
revenue = optimal objective value
Used in:scripts/backtest_battery.py (scipy.optimize.linprog / HiGHS solver).
Assumes the battery knows all 24 hourly prices in advance (oracle).
This is the theoretical maximum from day-ahead arbitrage — but achievable in practice
because day-ahead prices are published before gate closure, and ML forecasting
can predict them with ~88% accuracy (12% MAPE).
3. Fixed Schedule (backtest lower bound)
every day, same schedule:
charge at full power: 02:00–05:59 UTC
discharge at full power: 17:00–20:59 UTC
stop if battery is full or empty (SoC limits)
revenue = Σ(price_h × power) for discharge hours
− Σ(price_h × power ÷ 0.85) for charge hours
Used in:scripts/backtest_battery.py.
No intelligence, no adaptation. The worst a real operator would do.
Capture rate = FS revenue ÷ PF revenue.
4. Adaptive Day-Ahead (Monte Carlo simulation)
for each day:
forecast = actual_prices × (1 + noise) # 12% MAPE
solve LP using forecast prices # optimise against prediction
execute dispatch against actual prices # earn based on reality
revenue = Σ(actual_price_h × (discharge_h − charge_h))
Used in:scripts/monte_carlo_sim.py (1,000 scenarios × 365 days).
Most realistic strategy — sits between PF and FS.
Captures ~85–90% of PF revenue with realistic forecast error.
Profile spread computed from annual-average hourly prices (same data as Section 02 Daily Price Shape).
Daily spread computed by scripts/analyze_24month_spreads.py and
scripts/backtest_duration_comparison.py using 64,670 hourly records.
All values shown are gross (before RTE and fees). LP perfect foresight typically captures 5–15% more than the best-N-hours heuristic.
08Monthly SeasonalityRevenue & capture rate by month · click a year to compare
Monthly Revenue & Capture Rate EUR total (50 MW) + FS/PF % · select year
Key finding: Winter capture 68–76% vs summer 33–45%.
Summer months (May–Jul) see both lower absolute revenue and much worse fixed-schedule capture —
long daylight hours and high wind output flatten the daily price curve.
January alone earned more than May + June + July combined.
C1
What is capture rate?
Capture rate = Fixed Schedule revenue ÷ Perfect Foresight revenue.
It measures how much of the theoretically available arbitrage profit a simple, non-adaptive strategy actually captures.
Perfect Foresight (PF) is the upper bound — a strategy that knows tomorrow’s prices in advance and optimally schedules charge/discharge. No real operator achieves this, but it tells us the maximum possible revenue from day-ahead arbitrage.
Fixed Schedule (FS) is the lower bound — charge every night 01:00–04:00, discharge every evening 16:00–19:00, no exceptions. Zero intelligence, same windows 365 days a year.
A capture rate of 60% means the fixed schedule earns 60% of what a perfect oracle would earn. A real operator with forecasting and adaptive scheduling sits between these bounds, typically 65–80%.
When capture rate falls (as it has from 73% to 54% over 2019–2025), it means price patterns are becoming less predictable — the fixed windows miss more of the action. This is driven by increasing wind and solar changing when the cheap and expensive hours occur.
Revenue in EUR for the full 50 MW battery. Divide by 50 for per-MW figures. Capture rate = FS revenue ÷ PF revenue.
Source: backtest_results.csv, monthly aggregation.
09Negative PricesHours with sub-zero day-ahead prices · 2019–2025
Negative Price Hours per Year Count · Below EUR 0/MWh
Context: 486 zero-price hours and 781 negative-price hours in the full dataset —
driven by renewable surplus (especially wind). The 2020 spike (374 hours) reflects COVID demand collapse
combined with strong wind output. Post-crisis negative hours are low (40–58/yr) but the 2025 uptick
suggests increasing renewable penetration is beginning to push prices sub-zero more often.
C1
Negative prices are a buying opportunity for batteries. Most negative: EUR -41.09/MWh (2020).
Source: sem_complete_hourly.csv, count of price_eur_mwh < 0.
10What This MeansKey takeaways for the battery feasibility study
Market Data Takeaways
→PF revenue EUR 93k/MW/yrEUR 4.65M/yr (EUR 80k/MWEUR 4.0M ex-crisis) — and most of this is capturable.
Day-ahead prices are largely predictable from public inputs (wind/solar forecasts, demand, gas prices).
An ML-based trading system can realistically capture 80–90% of PF (EUR 74–84k/MW/yrEUR 3.7–4.2M/yr)
by adapting dispatch daily to forecast conditions.
The fixed schedule (54% capture) is a floor, not a realistic benchmark — nobody would actually trade this way.
C1C2
→4h duration earns most per MW, but each MWh works less hard than 2h or 1h.
Per MWh of installed storage, a 2h battery earns 120–125% of a 4h system.
Per MW installed, the 4h earns 61% more. Since doubling duration adds only ~33% to CAPEX
(grid, transformer, EPC are fixed MW costs), 4h has the best ROI.
The limitation of 4h is not absent spreads — hours 3–4 still earn ~85% per MWh of hours 1–2 in Ireland —
it’s that optimal dispatch across 4 hours requires better algorithms.
C1
→Fixed-schedule capture rate is declining, but PF is not.
FS capture fell from 73% (2020) to 54% (2025) as renewables make price shapes less predictable.
But the PF revenue ceiling is not declining — it actually rose from EUR 51k/MWEUR 2.5M (2020) to EUR 115k/MWEUR 5.8M (2025).
The growing gap between PF and FS means algorithmic quality matters more, not that the opportunity is shrinking.
C2
→Strong winter seasonality — summer months marginal.
Winter capture rates (68–76%) dwarf summer (33–45%). July 2025 PF revenue was just
EUR 4.8k/MW — the battery barely earns its keep. Revenue concentration in Q1 and Q4
creates cashflow volatility.
C1
→Data source: Energy-Charts API / ENTSO-E Transparency Platform (C1).
64,670 hourly records, 0.2% missing data (DST gaps), zero corrupt values. Day-ahead auction prices only —
does not include intraday, balancing, DS3 services, or capacity payments. These additional revenue
streams could add EUR 20–40k/MW/yrEUR 1.0–2.0M/yr but are not quantified here.
C1
Net assessment (gross, before fees): PF averages EUR 93k/MW/yrEUR 4.65M/yr
(EUR 80k/MWEUR 4.0M ex-crisis). With ML-optimised trading capturing 80–90% of PF, realistic gross revenue is
EUR 74–84k/MW/yrEUR 3.7–4.2M/yr for a 4h system. The revenue model uses EUR 69k/MW/yrEUR 3.45M/yr
(74% of PF) — conservative relative to what good algorithms achieve, but prudent for underwriting.
A 2h battery would earn 120–125% per MWh but only 62% per MW, yielding a lower ROI due to fixed infrastructure costs.
After fees: D-TUoS charges of EUR 62k/MW/yrEUR 3,115k/yr (41.1% of total gross revenue)
consume most of the arbitrage margin. At the base-case gross spread of EUR 55/MWh, D-TUoS alone absorbs approximately half
the arbitrage revenue. The gross spread must exceed ~EUR 90/MWh to clear an 8% hurdle rate without D-TUoS reform.
All figures on this page are gross; see Fee Structure and Profitability for net economics.
Data sources: Energy-Charts API (Fraunhofer ISE), sourced from ENTSO-E Transparency Platform.
SEM day-ahead auction prices, IE(SEM) bidding zone. Backtest executed with scipy/HiGHS LP solver on the full
64,670-hour dataset. All prices in EUR/MWh, nominal (no inflation adjustment). 2025 is a complete year;
2018 and 2026 are partial years excluded from annual comparisons.