Problem: Battery asset owners in ERCOT face a shifting revenue landscape — ancillary service revenues have fallen ~90% since 2023, while real-time energy arbitrage now dominates. This tool helps owners understand their historical revenue stack and optimize capacity allocation between revenue streams.
Demo: Streamlit dashboard showing revenue breakdown, price duration curves, and $/kW-month metrics.
# Clone the repo
git clone https://github.com/seeshuraj/ercot-bess-analyzer.git
cd ercot-bess-analyzer
# Create virtual environment
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Run the dashboard
streamlit run app.py- Energy Arbitrage Simulation: Perfect-foresight dispatch using threshold-based heuristic
- Ancillary Services Revenue: Capacity payment model (optimal single product selection)
- Revenue Stacking: Combined daily revenue from both streams
- Price Duration Curve: Visualize price volatility
- Configurable Battery: Adjust capacity, duration, RTE, and AS reserve fraction
| Parameter | Description | Default |
|---|---|---|
| Capacity (MW) | Battery power capacity | 100 MW |
| Duration (hours) | Battery energy storage duration | 4 hours |
| Round-Trip Efficiency | Charge/discharge efficiency | 85% |
| AS Reserve (%) | Capacity reserved for ancillary services | 20% |
| ERCOT Zone | Settlement location | HB_NORTH |
Perfect-foresight dispatch simulation using threshold approach:
- Charge during lowest 25% of prices
- Discharge during highest 75% of prices
- Subject to State-of-Charge constraints
Capacity payment model - simplified approach:
- In ERCOT, a battery cannot be simultaneously committed to all AS products
- This model selects the highest-clearing AS product each day
- Revenue = Best AS clearing price × MW committed × 24 hours × 85% availability factor
Note: This is an upper-bound benchmark. Real operators use day-ahead price forecasts and co-optimize across AS products hourly. Modo's Benchmarking Pro uses similar methodology for historical analysis.
Based on a 100 MW / 4hr battery at HB_NORTH with 20% AS reserve:
- Total revenue: $469,460 over 30 days (~$4.69/kW-month)
- Energy arbitrage: 45% of total revenue
- AS revenue: 55% of total revenue (Reg Up dominated at avg $8.50/MW-hr)
- Peak arbitrage day: 2026-02-17 at $46,443 (price spike event)
Note: the AS-heavy split (55%) reflects synthetic data calibration. Real 2025–2026 ERCOT data would show energy arbitrage dominating (~70%+) due to ~90% decline in ancillary service revenues since 2023 driven by BESS market saturation and the December 2025 RTC+B market redesign.
ercot-bess-analyzer/
├── README.md
├── Resume_Seeshuraj.pdf # Applicant resume
├── requirements.txt
├── app.py # Streamlit dashboard
├── src/
│ ├── data_fetcher.py # Data loading/caching
│ ├── synthetic_data.py # Realistic synthetic market data
│ ├── dispatch_model.py # Battery dispatch logic
│ └── revenue_calculator.py # Revenue stacking
└── data/ # Cached data (gitignored)
- Primary: ERCOT Market Information System (MIS) via gridstatus library
- Note: ERCOT's public API may block requests from cloud environments (403 errors). Run locally for real data.
- API Specs: ERCOT/api-specs - Official WSDL/XSD definitions
- Fallback: Realistic synthetic data based on ERCOT market patterns
- Perfect foresight: Real optimization uses day-ahead forecasts
- Simplified AS dispatch: Assumes optimal single product selection per day; real dispatch co-optimizes hourly
- Simplified dispatch: Could use LP/MPC for better results
- No degradation: Battery degradation not modeled
- Fixed AS split: Could optimize dynamic capacity allocation
Built for Modo Energy Take-Home Task (March 2026)
Built with Streamlit, Plotly, Pandas, and NumPy
