Dynamic battery arbitrage against Czech day-ahead electricity prices
Quick Start β’ Lambda Deploy β’ How It Works β’ Configuration
Automatically charge your AlphaESS battery when electricity is cheap and discharge when expensive. Uses 15-minute price slots from OTE (Czech day-ahead market) to maximize savings through smart arbitrage cycles.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π° CHEAP (Valley) πΈ EXPENSIVE (Peak) β
β ββββββββββββββββ βββββββββββββββββββ β
β 03:00-06:00 @ 45β¬ 17:00-20:00 @ 180β¬ β
β β CHARGE β β DISCHARGE β β
β Grid β Battery Battery β Home β
β β
β Spread: 135 β¬/MWh β¨ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Feature | Description |
|---|---|
| π Dynamic Detection | Auto-detects valleys & peaks from daily price patterns |
| π Arbitrage Cycles | Pairs charge windows with discharge windows for max spread |
| π Battery-Aware | Sizes windows based on actual SOC and capacity |
| π 15-min Granularity | Uses OTE's 96 daily price slots for precision |
| βοΈ Serverless Ready | Deploy to AWS Lambda or run locally |
- Python 3.12+
- uv (fast Python package manager)
- AlphaESS Open API credentials
# Clone
git clone https://github.com/michaelkrasa/AlphaESS-charging-optimizer.git
cd AlphaESS-charging-optimizer
# Install dependencies
uv syncCreate a .env file with your credentials:
cp .env.example .env
# Edit .env with your values# Required - AlphaESS API
APP_ID=your_alpha_ess_app_id
APP_SECRET=your_alpha_ess_app_secret
SERIAL_NUMBER=your_system_serialTune behavior in config.yaml:
charge_rate_kw: 5 # Battery charge rate in kW
price_multiplier: 1.2 # Valley/peak threshold vs daily mean
min_soc: 10 # Don't discharge below this %
max_soc: 100 # Charge target %# Default: Single optimization for today (run at midnight)
uv run python -m src.optimizer
# Dry run for a specific day (no API changes)
uv run python -m src.optimizer --date 15Run as a serverless function - no server required, pay only for execution time.
# 1. Configure (edit .env with AWS settings)
cp .env.example .env
# 2. Deploy to AWS
./deploy-lambda.shThe script will:
- β Build Docker image (arm64 for Graviton)
- β Push to Amazon ECR
- β Update Lambda function
- β Configure environment variables
| Setting | Value |
|---|---|
| Architecture | arm64 (Graviton) |
| Timeout | 30 seconds |
| Memory | 256 MB |
| Trigger | EventBridge @ 00:00 UTC daily |
Lambda runs once at 00:00 daily and optimizes for the current day. No configuration needed - just schedule it via EventBridge.
# Daily at 00:00 UTC (01:01 CET)
aws events put-rule \
--name "ess-daily-optimization" \
--schedule-expression "cron(1 0 * * ? *)"ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β Fetch 96 ββββββΆβ Detect ββββββΆβ Build β
β Price Slots β β Valleys & β β Arbitrage β
β (15-min) β β Peaks β β Cycles β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β
βΌ
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β Done! β
βββββββ Program βββββββ Size to β
β β β AlphaESS β β Battery β
β β β API β β SOC β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
- Smooth prices with moving average to reduce noise
- Calculate daily mean price
- Detect valleys:
price < mean / price_multiplier - Detect peaks:
price > mean Γ price_multiplier - Find mid-day dips between peaks for extra opportunities
- Each valley pairs with the next sequential peak
- Discharge windows extend to cover all profitable hours
- Up to 2 cycles per day (AlphaESS API limitation)
- Reads actual SOC from device
- Pulls capacity (gross Γ usable %)
- Sizes charge windows to actual need
- Accounts for consumption between windows
| Variable | Required | Description |
|---|---|---|
APP_ID |
β | AlphaESS API app ID |
APP_SECRET |
β | AlphaESS API secret |
SERIAL_NUMBER |
β | Your ESS serial number |
AWS_ACCOUNT_ID |
Lambda | AWS account for ECR |
ECR_REPO |
Lambda | ECR repository name |
| Setting | Default | Description |
|---|---|---|
charge_rate_kw |
5 | Battery charge rate in kW |
price_multiplier |
1.2 | Threshold factor vs daily mean |
min_soc |
10 | Minimum discharge SOC % |
max_soc |
100 | Target charge SOC % |
avg_day_load_kw |
1.8 | Avg household load for SOC estimation |
min_window_slots |
2 | Minimum window size (Γ15 min) |
smoothing_window |
2 | Price smoothing window (Γ15 min) |
βββ src/ # Core application modules
β βββ optimizer.py # Main optimizer orchestration
β βββ models.py # Data models (PriceWindow, ArbitrageCycle, etc.)
β βββ price_analyzer.py # Price analysis and valley/peak detection
β βββ battery_manager.py # Battery state calculations
β βββ ess_client.py # AlphaESS API client
β βββ price_cache.py # Price caching logic
βββ utils/ # Utility scripts
β βββ fetch_december_prices.py # Price data fetcher for testing
βββ tests/ # Test suite
β βββ test_ess.py # Main test suite
β βββ test_december_2025.py # December 2025 price data tests
β βββ test_data/ # Test price data files
βββ config.py # Configuration loader
βββ config.yaml # Optimization settings
βββ lambda_handler.py # AWS Lambda entry point
βββ Dockerfile # Lambda container (arm64)
βββ deploy-lambda.sh # One-command AWS deployment
βββ .env.example # Environment template
βββ pyproject.toml # Project dependencies (UV)
# Run all tests
uv run pytest tests/ -v
# Run specific test file
uv run pytest tests/test_ess.py -v
# Run December 2025 data tests
uv run pytest tests/test_december_2025.py -v# Run daily at 00:00 (midnight + 1 minute)
1 0 * * * cd /path/to/AlphaESS-charging-optimizer && uv run python -m src.optimizerSee Lambda Deployment section above.
- Target market: Czech OTE day-ahead prices (15-min granularity)
- API limitation: Max 2 charge + 2 discharge windows per day
- Schedule: Run at 00:00 daily to optimize for that day (prices are published the day before)
MIT
Happy arbitrage! β‘π