"Hit the right spot with your energy prices"

This custom integration allows you to fetch day-ahead electricity spot prices from various trustworthy global sources for use in automations, dashboards, and energy monitoring within Home Assistant.
- Installation
- Supported Price Sources & Regions
- Features
- Configuration
- Technical Features
- Usage Examples
- Troubleshooting
- For Developers
- Make sure HACS is installed
- Add this repository as a custom repository in HACS
- Click "Add"
- Search for "GE-Spot: Global Electricity Spot Prices"
- Click Install
- Restart Home Assistant
- Copy the
ge_spot
directory from this repository to your Home Assistant'scustom_components
directory - Restart Home Assistant
The integration supports multiple price data sources with automatic fallback capabilities:
- Nordpool - Prices for Nordic and Baltic countries
- Energi Data Service - Prices for Denmark
- Stromligning - Prices for Denmark
- ENTSO-E - European Network of Transmission System Operators for Electricity (requires API key)
- OMIE - Iberian Electricity Market Operator for Spain and Portugal
- EPEX SPOT - European Power Exchange for Central Europe (WIP)
- AEMO - Australian Energy Market Operator (WIP)
- ComEd - Commonwealth Edison hourly pricing for Chicago area (USA) (WIP)
- Amber - Amber Electric for Australia (WIP - Not yet fully integrated/listed in matrix)
The table below shows which price sources support specific regions:
Region | Description | Nordpool | ENTSO-E | Energi Data | Stromligning | EPEX (WIP) | OMIE | AEMO (WIP) | ComEd (WIP) | Amber (WIP) |
---|---|---|---|---|---|---|---|---|---|---|
SE1 | Sweden North | ✓ | ✓ | |||||||
SE2 | Sweden North-Central | ✓ | ✓ | |||||||
SE3 | Sweden South-Central | ✓ | ✓ | |||||||
SE4 | Sweden South | ✓ | ✓ | |||||||
DK1 | Denmark West | ✓ | ✓ | ✓ | ✓ | |||||
DK2 | Denmark East | ✓ | ✓ | ✓ | ✓ | |||||
NO1-4 | Norway Areas | ✓ | ✓ | |||||||
FI | Finland | ✓ | ✓ | |||||||
EE | Estonia | ✓ | ✓ | |||||||
LV | Latvia | ✓ | ||||||||
LT | Lithuania | ✓ | ||||||||
DE | Germany | ✓ | ✓ | |||||||
FR | France | ✓ | ✓ | |||||||
NL | Netherlands | ✓ | ✓ | |||||||
BE | Belgium | ✓ | ✓ | |||||||
AT | Austria | ✓ | ✓ | |||||||
ES | Spain | ✓ | ✓ | |||||||
PT | Portugal | ✓ | ✓ | |||||||
NSW1 | Australia NSW | ✓ | ✓ | |||||||
QLD1 | Australia Queensland | ✓ | ✓ | |||||||
SA1 | Australia South | ✓ | ✓ | |||||||
TAS1 | Australia Tasmania | ✓ | ✓ | |||||||
VIC1 | Australia Victoria | ✓ | ✓ | |||||||
US-IL | Chicago Area | ✓ |
For a complete list of supported areas and their currency mappings, see the Const files in the codebase.
- Simple configuration through the Home Assistant UI
- Region-specific setup options
- Source-agnostic sensors that provide consistent entity IDs regardless of data source
- Tomorrow's prices available after 13:00 CET (when published)
- Automatic fallback between data sources for the same region for increased reliability
- Seamless timezone handling to ensure correct hourly price display regardless of API source
- Currency conversion with dynamic exchange rates from the European Central Bank
- Timestamps in ISO format for compatibility with other systems
- Provides the following sensors for each configured region:
- Current Price: The electricity spot price for the current hour in your chosen display unit (e.g., cents/kWh).
- Next Hour Price: The electricity spot price for the upcoming hour.
- Average Price: The average electricity spot price calculated across all hours of the current day (today).
- Peak Price: The highest hourly electricity spot price encountered during the current day (today).
- Off-Peak Price: The lowest hourly electricity spot price encountered during the current day (today).
- Price Difference: The absolute difference between the Current Price and the Average Price for today. A negative value indicates the current price is below the daily average.
- Price Percentage: The relative difference between the Current Price and the Average Price for today, expressed as a percentage of the average price. A negative percentage indicates the current price is below the daily average.
- Tomorrow Average Price: The average electricity spot price calculated across all hours of the next day (tomorrow). This sensor becomes available once tomorrow's prices are published by the source (typically around 13:00-14:00 CET).
- Tomorrow Peak Price: The highest hourly electricity spot price forecast for the next day (tomorrow). Becomes available when tomorrow's prices are published.
- Tomorrow Off-Peak Price: The lowest hourly electricity spot price forecast for the next day (tomorrow). Becomes available when tomorrow's prices are published.
After installation:
- Go to Configuration → Integrations
- Click "Add Integration" and search for "GE-Spot: Global Electricity Spot Prices"
- Select your region/area from the dropdown
- Configure settings:
- Region/Area: Select your electricity price area (e.g., SE4, DK1)
- Source Priority: Order of data sources to try (first = highest priority)
- VAT Rate: Set your applicable VAT percentage (e.g., 25 for 25%)
- Update Interval: How often to refresh data (15, 30, or 60 minutes)
- Display Format: Choose between decimal (e.g., 0.15 EUR/kWh) or subunit (e.g., 15 cents/kWh)
- API Keys: For ENTSO-E, you'll need to register for an API key
- API Key Reuse: The integration will reuse API keys across different regions using the same source
The integration handles VAT correctly based on your configuration:
- Enter VAT as a percentage (e.g., 25 for 25%)
- VAT is applied to the converted price after any currency conversion
- Some sources like Stromligning already include VAT in their prices
The integration implements several mechanisms to ensure reliability:
- Automatic Retries: Failed API requests are retried with exponential backoff
- Source Fallbacks: If the primary source fails, alternative sources are tried automatically
- Data Caching: Successfully fetched data is cached and used if all API sources fail
- Rate Limiting: Minimum update interval of 15 minutes to respect API rate limits
- Special Update Windows: More frequent updates during price publication times (around 13:00 CET)
GE-Spot uses a robust multi-source approach to ensure reliable price data:
flowchart TD
%% ───────────── ENTRY ─────────────
Config["User configuration"] --> Coord["UnifiedPriceCoordinator"]
Coord --> UPM_Start["UnifiedPriceManager"]
%% ────────── MANAGER ──────────
subgraph UPM["UnifiedPriceManager"]
direction TB
RL{"Allowed to fetch (rate-limit passed)?"}
UseCache["CacheManager.get()"]
UseFallback["FallbackManager loop"]
Process["DataProcessor.process()"]
CacheStore["CacheManager.store()"]
Empty["Generate empty result"]
Final["Return to coordinator"]
RL -- no --> UseCache
RL -- yes --> UseFallback
UseCache -- hit --> Final
UseCache -- miss --> Empty
UseFallback --> FB_OK
FB_OK --> Process --> CacheStore --> Final
UseFallback -.-> FB_Fail
FB_Fail --> UseCache
Empty --> Final
end
%% ────── FALLBACK DETAIL ──────
subgraph FallbackLoop["FallbackManager"]
direction TB
More{{"more APIs?"}}
APICall["api.fetch_day_ahead_prices()"]
More --> APICall
APICall -- success --> FB_OK["raw data"]
APICall -- failure --> More
More -- none --> FB_Fail["fail"]
end
%% ────── OUTPUT ──────
Final --> Sensors["Update Home-Assistant sensors"]
The integration normalizes timestamps from different APIs to ensure correct hourly prices:
flowchart TD
API["API response with timestamps"] --> Parser["TimestampParser"]
Parser --> Converter["TimezoneConverter"]
subgraph TZ["TimezoneService internals"]
SourceTZ["Source-specific TZ"]
AreaTZ["Area TZ"]
HA_TZ["HA TZ"]
DST["DST handler"]
SourceTZ & AreaTZ & HA_TZ & DST --> Converter
end
Converter --> HourMatch["Hour-matching logic"]
HourMatch -->|current| Cur["Current-price sensor"]
HourMatch -->|next| Next["Next-hour sensor"]
HourMatch -->|all| All["Hourly prices"]
All -->|today| TStats["Today's stats"]
All -->|tomorrow| MStats["Tomorrow stats"]
TStats --> Avg["Day avg"] & Peak["Peak"] & Off["Off-peak"]
MStats --> MAvg["Tmrw avg"] & MPeak["Tmrw peak"] & MOff["Tmrw off-peak"]
- Timezone Awareness: Handles UTC, local time, and timezone-naive timestamps correctly
- Region-specific Handling: Applies appropriate timezone for each price area
- Home Assistant Integration: Uses your Home Assistant timezone setting for consistent display
The integration implements a comprehensive price conversion system that ensures accurate pricing across all regions and currencies:
flowchart TD
Raw["Raw API data"] --> Proc
subgraph Proc["DataProcessor"]
direction TB
Parse --> Validate --> Convert --> VAT --> Format --> Stats --> Out
Parse["Parse"]
Validate["Validate"]
Convert["CurrencyConverter"]
VAT["Apply VAT"]
Format["Format"]
Stats["Calc stats"]
Out["Output"]
end
Out --> CM["CacheManager.store()"] --> Coord["Coordinator data"]
Coord --> Sensors["HA sensors"]
subgraph CurrencyConverter
GetRates["Get rates"] --> DoConvert["Convert currency & unit"]
end
subgraph CurrencyService
CacheHit["Cache hit"]:::ok --> Return
CacheMiss["Cache miss"]:::err --> ECB["Fetch ECB"] --> Store --> Return
CacheHit -.-> Return
style CacheHit fill:#c9ffc9
style CacheMiss fill:#ffdede
end
The price conversion follows this detailed process:
sequenceDiagram
participant FallbackMgr as FallbackManager
participant Processor as DataProcessor
participant Converter as CurrencyConverter
participant CurrencySvc as CurrencyService
participant CacheMgr as CacheManager
participant ECB as ECB API
FallbackMgr->>Processor: Raw Price Data Structure
Processor->>Converter: async_convert_energy_price(raw_price, source_unit, source_currency)
Converter->>CurrencySvc: get_exchange_rates(base_currency, target_currency)
CurrencySvc->>CacheMgr: get_exchange_rate(key)
alt Cache Hit
CacheMgr-->>CurrencySvc: Return cached rate
else Cache Miss
CurrencySvc->>ECB: Fetch current rates
alt ECB API success
ECB-->>CurrencySvc: Current exchange rates
CurrencySvc->>CacheMgr: store_exchange_rate(key, rate, ttl)
else ECB API fails
CurrencySvc->>CurrencySvc: Use fallback rates
end
CurrencySvc-->>Converter: Return rates
end
Note over Converter: Step 1: Convert Currency (if needed)
Note over Converter: Step 2: Convert Energy Unit (e.g., MWh → kWh)
Note over Converter: Step 3: Apply VAT (if configured)
Note over Converter: Step 4: Convert to Subunit (if configured, e.g., EUR → cents)
Converter-->>Processor: Final Price (in target unit/currency/subunit)
Processor->>Processor: Calculate Statistics
Processor->>CacheMgr: Store Final Processed Data
CacheMgr-->>Processor: Confirmation
Processor-->>FallbackMgr: Return Final Data (to Coordinator)
GE-Spot makes currency conversion simple and reliable:
- Automatic Currency Detection: Appropriate currency selected based on region
- Dynamic Exchange Rates: Fresh rates from the European Central Bank
- Smart Caching: Exchange rates are cached for 24 hours to reduce API calls
- Scheduled Updates: Exchange rates are refreshed at 00:00, 06:00, 12:00, and 18:00
- Fallback Rates: Works even during network outages using cached values
- Display Flexibility: Show prices in main units (EUR/kWh) or subunits (cents/kWh, öre/kWh)
For example, converting from 69.16 EUR/MWh to öre/kWh for SE4 (with exchange rate 10.72):
- EUR to SEK: 69.16 × 10.72 = 741.40 SEK/MWh
- MWh to kWh: 741.40 ÷ 1000 = 0.7414 SEK/kWh
- Apply VAT (if any): 0.7414 × (1 + VAT rate)
- SEK to öre (if requested): 0.7414 × 100 = 74.14 öre/kWh
Different data sources will return different prices for the same hour due to what they include:
Source | Price Components | Notes |
---|---|---|
Energi Data Service/ENTSO-E | Raw spot price | Base wholesale electricity market price |
Nordpool | Raw spot price | May differ slightly due to timing/rounding |
Stromligning | Spot price + grid fees + tariffs + taxes | Full consumer electricity price |
ComEd (WIP) | Hourly real-time pricing | 5-minute and hourly average options |
EPEX (WIP) | Raw spot price | Similar to ENTSO-E |
OMIE | Raw spot price | Similar to ENTSO-E |
AEMO (WIP) | Raw spot price | Australian market specifics |
Amber (WIP) | Spot price + network fees + carbon costs | Includes additional components |
For Danish areas (DK1, DK2), the integration extracts only the electricity component from Stromligning to match other price sources. The full price breakdown with all components is available in the sensor attributes.
To ensure all sources report prices for the same hours despite different source timezones:
- Each API response is processed with explicit timezone awareness
- Timestamps are parsed in their original timezone context
- Hours are normalized to the Home Assistant timezone
- DST transitions are handled automatically
This ensures that, for example, the price for 14:00 is the same regardless of which API provided it.
The integration implements a sophisticated system for fetching tomorrow's electricity prices:
flowchart TD
Coord["UnifiedPriceCoordinator"] --> UPM["UnifiedPriceManager"]
UPM --> RL{"Rate-limit OK?"}
RL -- no --> Cache["Use cache"]
RL -- yes --> FM["FallbackManager"]
subgraph FM["Fallback loop"]
direction TB
More{{"APIs left?"}} --> APICall["api.fetch_day_ahead_prices()"]
APICall --> TZ["TimezoneService"]
TZ --> APICall
APICall -- success --> Raw["raw data"]
APICall -- fail --> More
More -- none --> Fail["fail"]
end
Raw --> Proc["DataProcessor"] --> Store["CacheManager.store()"] --> Done
Fail --> Cache
Cache --> Done
Done --> Sensors["Update HA sensors (today + tomorrow)"]
type: entities
entities:
- entity: sensor.gespot_current_price_se4
name: Current Electricity Price
- entity: sensor.gespot_next_hour_price_se4
name: Next Hour Price
- entity: sensor.gespot_day_average_price_se4
name: Today's Average
- entity: sensor.gespot_tomorrow_average_price_se4
name: Tomorrow's Average
type: custom:apexcharts-card
header:
show: true
title: Electricity Prices
show_states: true
series:
- entity: sensor.gespot_current_price_se4
attribute: today_with_timestamps
type: column
name: Today
group_by:
func: raw
- entity: sensor.gespot_current_price_se4
attribute: tomorrow_with_timestamps
type: column
name: Tomorrow
group_by:
func: raw
automation:
- alias: Turn on water heater when prices are low
trigger:
- platform: state
entity_id: sensor.gespot_current_price_se4
condition:
- condition: template
value_template: "{{ states('sensor.gespot_current_price_se4')|float < states('sensor.gespot_day_average_price_se4')|float * 0.8 }}"
action:
- service: switch.turn_on
entity_id: switch.water_heater
To integrate GE-Spot with the Energy Dashboard, you can create template sensors:
template:
- sensor:
- name: "Energy Cost Sensor"
unit_of_measurement: "SEK/kWh"
state: "{{ states('sensor.gespot_current_price_se4') }}"
Then set this sensor as your energy cost sensor in the Energy Dashboard settings.
If you experience issues:
- No Data/Empty Sensors: Check if your area is correctly supported by your selected source
- API Key Errors: For ENTSO-E, verify your API key is entered correctly
- Timezone Issues: Check if the hourly prices align with your expected hours
- Missing Tomorrow Prices: Tomorrow's prices are typically only available after 13:00 CET
-
Check sensor attributes for detailed information:
data_source
: Which API provided the dataactive_source
: Current active source being usedfallback_sources
: Alternative sources that succeededattempted_sources
: All sources that were triedusing_cached_data
: Whether cached data is being used
-
Check Home Assistant logs for errors related to
ge_spot
-
Verify network connectivity to the API endpoints
-
Try increasing the update interval if you experience frequent timeouts
- Configure multiple sources in your priority list
- Get an ENTSO-E API key for better fallback options
- Use longer update intervals (30 or 60 minutes)
- Check if your internet connection is stable
GE-Spot follows a modular architecture with clear separation of concerns:
custom_components/ge_spot/
├── __init__.py # Integration setup
├── config_flow.py # Configuration flow handler
├── manifest.json # Integration manifest
├── api/ # API clients for different price sources
│ ├── __init__.py
│ ├── base/ # Base classes for API functionality (adapter, fetcher, error handler)
│ ├── parsers/ # Dedicated parsers for each API source
│ └── [source_name].py # Specific API client implementations (e.g., nordpool.py)
├── config_flow/ # UI Configuration flow logic
│ ├── __init__.py
│ ├── implementation.py # Main config flow steps
│ ├── options.py # Options flow handler
│ ├── schemas.py # Voluptuous schemas for validation
│ └── validators.py # Custom validators
├── const/ # Constants (areas, sources, currencies, defaults, etc.)
├── coordinator/ # Data coordination and management
│ ├── __init__.py
│ ├── unified_price_manager.py # Orchestrates fetching, processing, caching
│ ├── fallback_manager.py # Handles trying sources in priority order
│ ├── data_processor.py # Processes raw data (timezone, currency, stats)
│ ├── cache_manager.py # Manages data caching
│ └── api_key_manager.py # Manages API keys
├── price/ # Price data processing and conversion
│ ├── __init__.py
│ ├── currency_converter.py # Handles currency/unit conversion
│ ├── currency_service.py # Manages exchange rate fetching/caching
│ └── statistics.py # Calculates price statistics (avg, peak, etc.)
├── sensor/ # Sensor entity implementations
│ ├── __init__.py
│ ├── base.py # Base sensor class
│ ├── electricity.py # Main electricity price sensor logic
│ └── price.py # Specific price sensor types (current, avg, etc.)
├── timezone/ # Timezone handling and conversion
│ ├── __init__.py
│ ├── service.py # Main timezone service
│ └── ... # Helper modules for parsing, DST, etc.
├── translations/ # UI translation strings
└── utils/ # Utility functions and classes
├── __init__.py
├── advanced_cache.py # Advanced caching implementation
├── data_validator.py # Data validation helpers
├── exchange_service.py # Fetches/caches ECB exchange rates
├── rate_limiter.py # Handles API rate limiting
└── ... # Other utilities (date range, unit conversion, etc.)
The API system is designed to be extensible and fault-tolerant:
- Modular Adapters: Each price source is implemented as a separate module in
custom_components/ge_spot/api/
, often inheriting from a base adapter inapi/base/
. - Source Prioritization: During configuration, users define the order in which API sources should be tried for their selected region.
- Fallback Mechanism: The
FallbackManager
iterates through the prioritized list of adapters for a region, attempting to fetch data until one succeeds. - Data Validation: Parsed data undergoes validation (e.g., using
utils/data_validator.py
) before further processing. - Error Handling: Base API classes and the fetching process include error handling, logging, and retry logic (often with backoff).
To add a new price source:
- Create a new API client module in
custom_components/ge_spot/api/
(e.g.,my_new_source.py
). - Implement the client logic, ideally inheriting from
api.base.BasePriceAPI
or a similar base class. This typically involves implementing methods likefetch_raw_data
and potentiallyparse_raw_data
(or creating a separate parser). - If needed, create a dedicated parser in
custom_components/ge_spot/api/parsers/
. - Register the new source:
- Add a constant for the source name in
const/sources.py
. - Map the source to its implementation class in
api/__init__.py
(in theSOURCE_MAP
). - Define which regions the source supports in
const/api.py
(inSOURCE_REGION_SUPPORT
).
- Add a constant for the source name in
- Add any relevant region/area codes and currency mappings in
const/areas.py
. - Update the configuration flow (
config_flow/schemas.py
,config_flow/implementation.py
) to allow users to select the new source in the priority list if applicable for their region. - Add tests for the new API client and parser.
API parsers aim to return a standardized dictionary format containing raw hourly prices, timezone, and currency information. This raw structure is then processed by the DataProcessor
into the final format used by sensors, which typically includes:
{
"current_price": float, # Current hour price (converted)
"next_hour_price": float, # Next hour price (converted)
"day_average_price": float, # Day average (converted)
"peak_price": float, # Day maximum (converted)
"off_peak_price": float, # Day minimum (converted)
"hourly_prices": dict, # Hourly prices "YYYY-MM-DDTHH:00:00+ZZ:ZZ" -> float (converted)
"tomorrow_hourly_prices": dict, # Tomorrow's hourly prices (if available, converted)
"raw_today": dict, # Raw hourly prices for today (before conversion)
"raw_tomorrow": dict, # Raw hourly prices for tomorrow (before conversion)
"source_data": { # Metadata about the fetch
"source": str, # Source name that provided the data
"currency": str, # Original currency from source
"timezone": str, # Original timezone from source
# ... other source-specific details
},
"conversion_details": { # Details about the conversion process
"target_currency": str,
"target_unit": str,
"vat_rate": float,
"exchange_rate": float,
# ...
},
"last_updated": str, # ISO timestamp of the update
# ... other calculated attributes
}
Note: The exact structure might vary slightly; check DataProcessor
and sensor attributes for the precise format.
The error handling system provides:
- Error Classification: Specific exceptions are often defined (e.g., in
api/base/exceptions.py
) and raised by API clients. - Error Recovery: The
FallbackManager
and underlying fetch mechanisms often include retries (sometimes with exponential backoff) for transient network or API errors. - Logging: Errors during fetching or processing are logged to Home Assistant logs for diagnostics.
- Fallback to Cache: If all sources fail, the system attempts to use previously cached data.
The fallback system ensures reliability:
- Prioritized List: Uses the user-configured source priority list for the region.
- Sequential Attempts: The
FallbackManager
tries fetching from each source in order. - First Success Wins: Uses the data from the first source in the list that returns valid data.
- Caching: If all sources in the priority list fail, it falls back to using cached data if available and not expired.
- Status Tracking: Sensor attributes often indicate which source succeeded (
data_source
), which were tried (attempted_sources
), and if cache was used (using_cached_data
).
The integration includes automated tests using pytest
and manual test scripts.
Located in tests/pytest/
. Organized into unit
, integration
, and lib
tests.
To run all pytest tests:
pytest tests/pytest/
To run specific categories (e.g., unit tests):
pytest tests/pytest/unit/
(Refer to tests/README.md
for more details on pytest structure and execution).
Located in tests/manual/
. These scripts often test against live APIs (API keys might be required via environment variables).
Example: Run a specific manual integration test:
python -m tests/manual.integration.nordpool_full_chain SE4
(Refer to tests/README.md
and individual script headers for usage instructions).
The integration includes several performance optimizations:
- Asynchronous Operations: Uses
asyncio
andhttpx.AsyncClient
for non-blocking I/O during API calls. - Source Prioritization: Avoids calling lower-priority sources if a higher-priority one succeeds quickly.
- Advanced Caching: Caches processed data (
CacheManager
) and exchange rates (ExchangeService
) with configurable TTLs to reduce redundant computations and API calls. - Rate Limiting: Implements rate limiting (
utils/rate_limiter.py
) to avoid overwhelming APIs and respect usage limits, especially during frequent update checks. - Parallel Fetching (Potential): While
utils/parallel_fetcher.py
exists, verify its current usage in the main fetch flow. This was a planned refactoring which never went live due to it's complexity.
This integration is licensed under the MIT License.