A Python-based simulation tool for Battery Energy Storage System (BESS) reserve provision, implementing the robust market-based management strategy described in:
Baltputnis, K., Broka, Z., Cingels, G., Sīlis, A., & Junghāns, G. (2024). Robust market-based battery energy storage management strategy for operation in European balancing markets. Journal of Energy Storage, 102, 114082. https://doi.org/10.1016/j.est.2024.114082
The input and output data concerning this paper is available on Zenodo: https://doi.org/10.5281/zenodo.18199324
The tool supports simultaneous FCR (Frequency Containment Reserve) and aFRR (automatic Frequency Restoration Reserve) market participation with market-based SOC restoration exclusively via the intraday market, fully compliant with EU System Operation Guideline requirements for Limited Energy Reservoirs (LERs).
The simulation implements a worst-case activation anticipation approach where non-delivery of contracted reserves is never permitted. Key principles:
- No activation overfulfilment or FCR deadband utilization for SOC restoration
- SOC restoration exclusively via scheduled ID market transactions
- Full compliance with EU SOG requirements for LER FCR providers
-
BESS SOC Management (in
simulation.py): Prepares ID market bids ensuring sufficient charge for worst-case reserve activation scenarios. Calculates worst-case energy requirements considering FCR, FRR capacity, scheduled ID trades, and self-discharge losses. -
LER Management in Alert State (in
bess_model.py): Implements reserve mode/normal mode transitions with 5-min aFRR full activation time for transitions. Tracks ΔTminLER criterion fulfillment (30 min full FCR activation during alert state) and manages recovery status post-alert (max 2 hours per SOG). -
Voluntary FRR Energy Bid Preparation (in
bess_model.py): Estimates additional FRR bids using surplus BESS capacity after ensuring worst-case scenario handling. Includes SOC violation subroutine to verify bids don't cause issues in other time periods.
-
Conservative Strategy (for LER): Takes advantage of lenient criteria for LER FCR providers during alert state. Lower worst-case energy requirements, less active ID trading.
-
Active Strategy (for non-LER): Requires continuous BESS availability irrespective of system state. More frequent ID trades but ensures full FCR provision without reserve mode transitions.
- FCR: Symmetric capacity product, ±200 mHz full activation, ±10 mHz deadband
- aFRR: 5-min full activation time
- Market Time Unit (MTU): 15 minutes
- ID GCT: Configurable (e.g., 60 min for Baltic, 5 min for Germany)
- Multi-BESS simulation: Two BESS units operating as a fleet with capacity allocation
- Reserve market participation: FCR and FRR capacity/energy market modeling
- LER support: Full implementation including reserve mode, ΔTminLER tracking, and recovery
- SOC management: Automatic ID market trading based on worst-case energy calculations
- Voluntary FRR bidding: Automatic bidding of available capacity with exhaustive worst-case validation
- Minimum cycling: Optional BESS-to-BESS energy exchange to meet cycling requirements
- Trade netting: Physical energy exchange lesser than sum of individual market deliveries
- Comprehensive output: Excel results and interactive matplotlib visualizations
- Python 3.8 or higher
- pip package manager
pip install numpy pandas matplotlib openpyxlbess_sim/
├── main.py # Entry point - run this
├── constants.py # Physical and market constants (MTU, time deltas)
├── utils.py # Utility functions (logging, rounding, ID clearing)
├── config.py # Configuration management from settings.xlsx
├── data_input.py # Frequency and FRR activation data loading
├── bess_model.py # BESS class: energy calculations, LER procedures, voluntary FRR
├── simulation.py # Main loop: SOC management, Alert State, reserve activation
├── output.py # Excel results and matplotlib figures
├── settings.xlsx # Simulation settings
├── output_template.xlsx # Excel output template
├── frequency_*.csv # Frequency input data
├── FRR_*.csv # FRR activation input data
└── README.md # This file
- BESS Technical Parameters: Power ratings, capacity, efficiency, SOC limits, losses
- BESS Simulation Settings: Initial SOC, LER qualification, availability, SOC management strategy
- Reserve Provision Settings: FCR/FRR capacity obligations (uniform or per-MTU)
- Market Settings: GCT timings (ID, FRR), preparation times, frequency parameters (deadband, full activation deviation), ΔTminLER, max recovery time
- Frequency file: CSV/Excel with
TimeandValuecolumns (1-second to 1-minute resolution recommended) - FRR activation file: CSV/Excel with
Start,End, andMWcolumns (positive = up-regulation)
python main.pyThe simulation resolution is configurable (1-minute recommended for balance of accuracy and performance).
-
Results folder: Named
{scenario_name} Run {n}/containing:- Excel results with detailed time-series
- Log file with console messages and warnings
- Copy of settings used
-
Results Excel: Per-timestep data for both BESS units:
- SOC trajectories
- Contracted FCR/FRR availability
- Activation expectations and actual delivery
- Delivery failures (if any)
- Reserve mode and recovery status
-
Interactive figures:
- Row 1: SOC trajectory and ID schedule with Alert State highlighting
- Row 2: FCR activation vs frequency deviation with Normal/Reserve mode indication
- Row 3: FRR activation vs contracted capacity
| Module | Description |
|---|---|
main.py |
Entry point, orchestrates simulation flow |
constants.py |
Time constants (MTU=15min, hour, etc.) |
config.py |
Reads settings.xlsx, provides configuration dataclasses |
data_input.py |
Loads and resamples frequency/FRR data |
bess_model.py |
BESS class with E_worst calculations, FCR/FRR logic, LER reserve mode, voluntary FRR bidding |
simulation.py |
Main simulation loop, SOC management decisions, Alert State detection, energy delivery |
output.py |
Excel output writing, matplotlib figure generation |
utils.py |
Helper functions (custom_print, true_round, ID_clearing) |
The strategy has been validated against:
- Extreme worst-case scenario: 6-hour continuous full FCR + aFRR activation
- Realistic scenarios: Full-year simulations with German (CE) and Finnish (Nordic) frequency data
- Sensitivity analysis: ID GCT varied from 15-105 minutes
