Daily arbitrage profits swing from €6 to €84—a 14x difference. This chart shows why single-day analysis misleads: you need a full year of simulation to understand what battery storage really earns.

The conclusion? At current Spanish market prices, pure arbitrage barely covers operating costs. Annual net profit of ~€7,000 on a €360,000 investment means arbitrage alone doesn’t justify the battery. But this analysis sets the foundation for understanding where battery storage does work.
Questions
- How do constrained daily profits vary across a month?
- What’s the distribution of daily profits (best days vs worst days)?
- Are there weekly patterns in profitability?
- What’s a realistic annual profit projection based on simulated data?
- How does this compare to theoretical (unconstrained) analysis?
Implementation
Load price data
We’ll analyze 30 days of OMIE electricity prices at 15-minute resolution. See Fetching Electricity Prices from OMIE for the data source.
import pandas as pd
import numpy as np
df_prices = pd.read_csv('data/prices.csv', index_col='datetime', parse_dates=True) Loaded 35040 periods across 365 days (2025-01-01 to 2025-12-31).
Define constrained simulation
We reuse the constrained model from the single-day article. The function finds optimal charge/discharge windows and applies:
- SOC limits: 10%-90% (80% usable capacity)
- Power limit: 150 kW maximum charge/discharge rate
- Efficiency: 90% round-trip (10% energy loss)
def simulate_day_constrained(prices, capacity=1000, soc_min=0.10, soc_max=0.90,
max_power=150, efficiency=0.90,
charge_periods=16, discharge_periods=16):
"""Simulate one day with all constraints applied."""
n = len(prices)
if n < charge_periods + discharge_periods:
return None
usable_capacity = capacity * (soc_max - soc_min)
hours = charge_periods * 0.25
# Energy limited by power constraint
energy_charged = min(max_power * hours, usable_capacity)
energy_discharged = energy_charged * efficiency
# Find optimal charge window
min_avg, best_charge_start = float('inf'), 0
for start in range(n - charge_periods + 1):
avg = prices[start:start + charge_periods].mean()
if avg < min_avg:
min_avg, best_charge_start = avg, start
charge_end = best_charge_start + charge_periods
# Find optimal discharge window (after charging)
max_avg, best_discharge_start = -float('inf'), charge_end
for start in range(charge_end, n - discharge_periods + 1):
avg = prices[start:start + discharge_periods].mean()
if avg > max_avg:
max_avg, best_discharge_start = avg, start
# Calculate profits
charge_cost = energy_charged * min_avg / 1000
discharge_revenue = energy_discharged * max_avg / 1000
theoretical_profit = capacity * (max_avg - min_avg) / 1000
constrained_profit = discharge_revenue - charge_cost
return {
'charge_price': min_avg,
'discharge_price': max_avg,
'spread': max_avg - min_avg,
'theoretical_profit': theoretical_profit,
'constrained_profit': constrained_profit,
'efficiency_ratio': constrained_profit / theoretical_profit if theoretical_profit > 0 else 0
} Run 30-day simulation
daily_results = []
for date, day_data in df_prices.groupby(df_prices.index.date):
prices = day_data['price_spain'].values
result = simulate_day_constrained(prices)
if result:
result['date'] = pd.Timestamp(date)
daily_results.append(result)
df_daily = pd.DataFrame(daily_results).set_index('date')
30-day totals:
- Theoretical profit: €29,084
- Constrained profit: €15,131 (51% of theoretical)
Profit variability
How much do daily constrained profits vary?

The standard deviation of €17.29 shows significant day-to-day variability. Some days yield over €84, while the worst day produced only €6.
Weekly patterns
Do constrained profits follow a weekly pattern?

Weekday vs Weekend:
- Weekdays: €41.46/day average
- Weekends: €41.45/day average
Annual projection
Using 30 days of simulated data, we project annual returns:

Financial analysis
Can arbitrage alone justify a battery investment?

After 15 years (with 2% annual degradation), cumulative returns are €-287,653.
Conclusions
Multi-day simulation reveals the reality of battery arbitrage:
- Average daily profit: €41.45 (constrained)
- Variability: €6 to €84 per day
- Annual projection (median): €14,078 gross
- Annual net (after O&M): €6,878
Key insight: With median daily profits of €41, annual gross revenue (€14,078) barely covers O&M costs (€7,200). Arbitrage alone doesn’t justify battery investment at current Spanish market prices.
Successful BESS projects stack multiple revenue streams. In Spain, PV hybridization emerges as the most viable near-term path—combining solar self-consumption with storage fundamentally changes the economics. We’ll explore this in the next article.
Keep reading
Related articles you might enjoy

Finding arbitrage opportunities in electricity markets
Analyze electricity prices to identify profitable charge/discharge windows for battery storage systems.
Read
Analyzing Spanish electricity market data from I90 files
Download, parse, and analyze I90 files from REE using Python — curtailment trends, generation mix, and price correlations for the Spanish electricity market.
Read
Fetching electricity prices from OMIE with Python
Access real-time and historical day-ahead electricity prices from the Spanish electricity market operator—no API key required.
Read