Optimizing HVAC Energy Savings with Nordpool 15-min Pricing: The Theory *Part 1 of 3: Understanding the Concept*

Introduction

Since October 2025, electric markets in Finland and Nordpool has moved to
15-minute electricity pricing data. This granular data reveals significant
price spikes lasting just 15-45 minutes - opportunities that hourly averaging
completely misses.

:zap: About This Series

While this series uses HVAC heating as the primary example, the optimization
principles apply to any controllable electrical load:

  • Heat pumps and air conditioning
  • Water heaters and boilers
  • EV charging systems
  • Pool pumps and spa heaters
  • Industrial equipment with thermal/energy storage

The key requirement: Your load must tolerate short interruptions or have some
form of energy storage capacity (thermal mass, battery, water tank, etc.).

Traditional HVAC energy-saving approaches:

  • :x: Turn off heating during the most expensive hour
  • :x: Ignore building thermal mass
  • :x: Result in temperature drops and comfort loss

This series presents a smarter approach: A Python-based optimization system that:

  • :white_check_mark: Exploits 15-minute price peaks
  • :white_check_mark: Respects building thermodynamics
  • :white_check_mark: Maintains comfort while saving 10-20% on peak costs

My heating system: Two air-source heat pumps (ILPs) as the primary heat source, with hydronic radiator heating set very low as backup. This hybrid approach provides flexibility for intelligent optimization.

The Core Problem

Traditional Approach

17:00 - Expensive hour detected
17:00 - Turn off HVAC completely
18:00 - Turn on HVAC
18:00 - Building is cold, recovery takes hours

Result: Maybe discomfort, slower recovery, suboptimal actual savings.

My Approach: Intelligent Load Reduction

13:00 - PREHEAT: Warm building above target (store thermal energy)
17:00 - REDUCED HEATING: HVAC fan-only + radiators minimum
21:00 - RECOVERY: Gentle return to normal operation
01:00 - System back to baseline

Result: Minimal temperature drop, even heat distribution, optimal energy use, maximum savings.

The Three Phases Explained

Phase 1: Preheat (Pre-heating)

Duration: Same as cutoff period
HVAC Mode: Active heating (+1Β°C above target)
Radiators: Elevated temperature (target + offset)
Fan: High speed
Purpose: Store thermal energy in building mass

Your building is a thermal battery. By heating it 1-2Β°C above target before an expensive period, you store energy in walls, floors, and air that can sustain comfort during the reduced heating phase.

Cost multiplier: 1.5Γ—
Why? Heating above target requires more energy due to increased heat loss at higher Ξ”T.

Phase 2: Reduced Heating (Cutoff)

Duration: 1-4 hours (optimized dynamically)
HVAC Mode: Fan-only (circulates air without heating)
Radiators: Minimum temperature setting
Fan: High speed (maintains air circulation)
Purpose: Minimize electricity consumption during peak prices

The heat pump switches to fan-only mode to maintain air circulation and even temperature distribution throughout the building, while radiators are set to minimum. This allows the building’s thermal mass to sustain comfort with minimal active heating.

Why not completely off? Fan circulation prevents cold spots and maintains even temperature distribution using stored thermal energy. The fan draws minimal power (~50W) compared to active heating (2000-5000W).

Weather-adaptive duration:

  • In extreme cold (<0Β°C) or heat (>24Β°C), the system automatically reduces max cutoff to 3 hours to maintain comfort
  • In mild conditions (0-24Β°C), full 4-hour cutoffs are allowed

Phase 3: Recovery

Duration: Same as cutoff period
HVAC Mode: Active heating
Fan: Medium to high speed
Radiators: Return to normal offset
Purpose: Return to baseline without shock-loading

Gentle ramp-up to normal operation. Slightly elevated consumption to restore baseline quickly without temperature overshoot.

Cost multiplier: 1.2Γ—
Why? You’re catching up from a slightly lower temperature, requiring brief elevated output.

The Optimization Algorithm

Dynamic Price Difference Scaling

The Problem: How do you compare a 1-hour cutoff vs a 4-hour cutoff?

A 1-hour cutoff targets a sharp price spike:

  • 17:30-18:30: Peak price 36.6 c/kWh
  • Average of 1h: ~30 c/kWh
  • Requires high price difference: 3.5 c/kWh

A 4-hour cutoff spans multiple price levels:

  • 16:00-20:00: Mix of 5-35 c/kWh
  • Average of 4h: ~15 c/kWh
  • If we required 3.5Γ— 4 = 14 c/kWh difference, we’d never find 4h cutoffs

Solution: Dynamic scaling

required_price_diff = base_requirement Γ— (1h / cutoff_duration)

1h cutoff: 3.5 Γ— (1/1) = 3.5 c/kWh
2h cutoff: 3.5 Γ— (1/2) = 1.75 c/kWh
4h cutoff: 3.5 Γ— (1/4) = 0.88 c/kWh

Calibrating the base requirement (3.5 c/kWh):
This value isn’t arbitrary - I calculated it from my real heating data and Nordpool price history. By analyzing which cutoff periods actually delivered 10%+ savings over several months, I found that 3.5 c/kWh price difference for 1-hour cutoffs consistently met this threshold for my specific building.

Your building will likely need different values depending on:

  • Thermal mass (concrete, wood, insulation)
  • Heat pump efficiency (SCOP values)
  • Backup heating system (radiators, resistive)
  • Building size and layout

Part 3 of this series will show you how to calibrate this value for your own home using historical data analysis.

This allows the algorithm to find both sharp peaks and broader elevated periods while ensuring each cutoff delivers meaningful savings for your specific building characteristics.

15-Minute Precision

The system tests:

  • Start time: Every 15 minutes (16:00, 16:15, 16:30…)
  • Duration: Every 15 minutes (1.0h, 1.25h, 1.5h, 1.75h, 2.0h…)
  • Total candidates: ~100-200 per day

Why not test every possible combination?
Testing every start Γ— every duration would create 1000+ candidates β†’ memory overflow in Home Assistant templates.

Python script solution: Handles this easily, selecting the top ~100 candidates that meet criteria.

Real-World Example

October 1, 2025 - Extreme Price Day

Peak price: 36.6 c/kWh at 17:45
Minimum price: 0.3 c/kWh at 03:00
Average: 6.2 c/kWh

Optimized schedule:

  • 13:00-17:00: Preheat
  • 17:00-21:00: Reduced heating (4h)
  • 21:00-01:00: Recovery

Costs:

  • Without optimization: 33.82 kWh-value
  • With optimization: 21.67 kWh-value
  • Savings: 12.15 (35.9%)

Price difference: 2.70 c/kWh (average cutoff vs average preheat+recovery)
Scaled requirement: 0.88 c/kWh (for 4h cutoff) :white_check_mark:

October 3, 2025 - Moderate Price Day

Peak price: 7.66 c/kWh at 17:45
Minimum price: 0.25 c/kWh at 00:45
Average: 2.0 c/kWh

Optimized schedule:

  • 12:15-16:15: Preheat
  • 16:15-20:15: Reduced heating (4h) ← 15-min optimization!
  • 20:15-00:15: Recovery

Why 16:15 instead of 16:00?
The 16:00-16:15 slot is relatively cheap (2.45 c/kWh). By starting at 16:15, the system:

  • Avoids cutoff during that cheaper period
  • Includes the 20:00-20:15 slot in cutoff (2.90 c/kWh)
  • Result: +4.1% additional savings vs hourly optimization

Key Insights

  1. Building thermal mass is a battery: Store energy when cheap, use when expensive
  2. Symmetry is critical: Preheat and recovery durations must match cutoff
  3. Dynamic scaling is necessary: Different cutoff lengths need different thresholds
  4. 15-minute precision matters: 4% additional savings over hourly precision
  5. Fan-only maintains comfort: Air circulation uses minimal energy while distributing stored heat
  6. Weather-adaptive optimization: Cutoff duration adjusted automatically based on outdoor temperature
    • Summer (>24Β°C) or Winter (<0Β°C): 3h max cutoff, 2.5 c/kWh threshold
    • Mild seasons (0-24Β°C): 4h max cutoff, 3.5 c/kWh threshold
    • Why? Extreme temperatures increase heat loss β†’ shorter cutoffs maintain comfort

What’s Your Experience?

This approach has saved me 10-20% on peak electricity costs while maintaining perfect comfort levels. The building never drops temperature significantly during cutoffs, and the system runs completely automatically.

But I want to hear from you:

  • Would you be interested in the complete Python implementation?
  • What about step-by-step configuration guides?

Ready for the Technical Deep-Dive?

I have the complete working code and real-world configuration examples ready to share, including:

Part 2 - Python Implementation:

  • Full Python script with detailed comments
  • Template sensors for Home Assistant integration
  • Automated scheduling and HVAC control
  • Debug tools and troubleshooting guides

Part 3 - Configuration & Real-World Results:

  • Step-by-step installation guide
  • Parameter calibration for your specific home
  • Weather-adaptive automation (auto-adjust cutoff parameters)
  • Frontend dashboards and mobile optimization
  • Performance monitoring and fine-tuning

:+1: If this interests you, please give a like or leave a comment!
:speech_balloon: Share your own energy-saving challenges and experiences below

I’ll continue with the technical implementation if there’s enough community interest. :heart:


:package: UPDATE: Parts 2 & 3 Now Available!

The complete implementation is here! :tada:

:link: GitHub Repository: nordpool-spot-cutoff-optimizer

What’s included:

  • :snake: Part 2: Python Implementation - The actual optimization algorithm
  • :house: Part 3: Integration Examples - Complete HVAC & water heater setups
  • :gear: Ready-to-use Python script with copy-paste configurations
  • :clipboard: Step-by-step installation guides

Now available for immediate deployment to your Home Assistant setup!

Have fun tweaking your electric/HVAC systems! :wrench::zap:

(And maybe save some money while you’re at it) :moneybag:


P.S. If you break your heating system, that’s between you and your thermostat. We just provide the math! :wink:


Questions about the theory or calculations? Discussion starts below! :point_down:

5 Likes

Part 2: The Cutoff Optimizer - Python Implementation

Part 2 of 3: Technical Deep-Dive

This document covers the core optimization algorithm and Home Assistant integration.
β†’ Discuss on Home Assistant Community

GitHub Repository

:link: Complete code and examples: nordpool-spot-cutoff-optimizer

All documentation, Python script, and integration examples are available in the repository above.


Introduction

In Part 1, we explored the theory behind load cutoff optimization using 15-minute Nordpool pricing. Now it’s time to build the actual system.

This part covers:

  • :white_check_mark: Core optimization algorithm (Dynamic Programming)
  • :white_check_mark: Python script for Home Assistant
  • :white_check_mark: Template sensors and integration
  • :white_check_mark: How to adapt it for YOUR load

Key principle: The optimizer is load-agnostic. It finds optimal cutoff schedules and outputs them as sensors. YOU implement the actual load control in Part 3.


Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 1. Nordpool Integration (HACS)                          β”‚
β”‚    β”œβ”€ sensor.nordpool_kwh_fi_eur_3_10_0                β”‚
β”‚    β”œβ”€ raw_today (96 Γ— 15min OR 24 Γ— 1h)                β”‚
β”‚    └─ raw_tomorrow (96 Γ— 15min OR 24 Γ— 1h)             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 2. Python Script: nordpool_cutoff_optimizer.py         β”‚
β”‚    β”œβ”€ Read price data from Nordpool sensor             β”‚
β”‚    β”œβ”€ Dynamic Programming optimization                  β”‚
β”‚    β”œβ”€ Find globally optimal cutoff schedule(s)         β”‚
β”‚    └─ Output: sensor.nordpool_cutoff_periods_python    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 3. Template Sensors (Home Assistant)                    β”‚
β”‚    β”œβ”€ sensor.cutoff_current_period                      β”‚
β”‚    β”œβ”€ sensor.cutoff_phase (preheat/cutoff/recovery)    β”‚
β”‚    └─ Extract data from periods list                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 4. YOUR Automations (YOU implement this in Part 3)     β”‚
β”‚    β”œβ”€ When cutoff_phase = "preheat" β†’ YOUR action      β”‚
β”‚    β”œβ”€ When cutoff_phase = "shutdown" β†’ YOUR action     β”‚
β”‚    └─ When cutoff_phase = "recovery" β†’ YOUR action     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Core Algorithm: Dynamic Programming

The optimizer uses Dynamic Programming to find the globally optimal cutoff schedule(s).

Why Dynamic Programming?

Alternative approach (Greedy):

  • Find the most expensive hour
  • Apply cutoff there
  • Problem: Might miss better opportunities or create overlaps

Dynamic Programming approach:

  • Test all possible cutoff combinations
  • Consider preheat and recovery costs
  • Find globally optimal solution
  • Supports multiple cutoffs per day (e.g., morning + evening peaks)

Algorithm Steps

1. Get price data (today + tomorrow) from Nordpool sensor
2. Resample to 15-min intervals if data is hourly
3. For each possible cutoff candidate:
   a. Calculate cutoff duration (0.5h, 0.75h, 1h, ... up to max)
   b. Calculate preheat start (same duration before cutoff)
   c. Calculate recovery end (same duration after cutoff)
   d. Compute costs:
      - baseline_cost = avg_price Γ— duration
      - cutoff_cost = cutoff_price Γ— residual_load (e.g. 0.1)
      - preheat_cost = preheat_price Γ— preheat_multiplier (e.g. 1.5)
      - recovery_cost = recovery_price Γ— recovery_multiplier (e.g. 1.2)
      - total_cost = cutoff + preheat + recovery
      - savings = baseline_cost - total_cost
   e. Check if savings > threshold
4. Use Dynamic Programming to select non-overlapping cutoffs
5. Output schedule(s) to sensor attributes

Key Parameters

These are read from input_number entities you configure:

Parameter Default Description
base_price_diff 3.5 c/kWh Min price diff for 1h cutoff
max_cutoff_duration 4.0 hours Max cutoff length
min_cutoff_duration 0.5 hours Min cutoff length
preheat_multiplier 1.5 Cost multiplier for preheat phase
recovery_multiplier 1.2 Cost multiplier for recovery
residual_multiplier 0.1 Residual load during cutoff (10%)
min_savings_pct 10% Min savings to activate

These are calibrated for YOUR system in Part 3.


Python Script Output

Output Sensor: sensor.nordpool_cutoff_periods_python

The script creates a sensor with a list of cutoff periods:

sensor.nordpool_cutoff_periods_python:
  state: "2"  # Number of cutoffs today/tomorrow
  attributes:
    periods:
      - preheat_start: "2025-10-07T13:00:00+03:00"
        shutdown_start: "2025-10-07T17:00:00+03:00"
        shutdown_end: "2025-10-07T21:00:00+03:00"
        recovery_start: "2025-10-07T21:00:00+03:00"
        recovery_end: "2025-10-08T01:00:00+03:00"
        savings_pct: 35.9
        baseline_cost: 33.82
        optimized_cost: 21.67
        cutoff_duration_hours: 4.0

      - preheat_start: "2025-10-08T06:00:00+03:00"
        shutdown_start: "2025-10-08T08:00:00+03:00"
        shutdown_end: "2025-10-08T10:00:00+03:00"
        recovery_start: "2025-10-08T10:00:00+03:00"
        recovery_end: "2025-10-08T12:00:00+03:00"
        savings_pct: 18.2
        baseline_cost: 28.45
        optimized_cost: 23.27
        cutoff_duration_hours: 2.0

    data_resolution: "15min"
    optimization_method: "dynamic_programming"

Key feature: The periods list can contain multiple cutoffs (e.g., morning + evening peaks).


Template Sensors: Extract Current Phase

You need template sensors to extract which phase is currently active:

Minimal Example: Current Phase Sensor

template:
  - sensor:
      - name: "Cutoff Current Phase"
        unique_id: cutoff_current_phase
        state: >
          {% set periods = state_attr('sensor.nordpool_cutoff_periods_python', 'periods') %}
          {% if not periods %}
            normal
          {% else %}
            {% set now_ts = now().timestamp() %}
            {% set ns = namespace(phase='normal') %}
            {% for period in periods %}
              {% set preheat_ts = as_timestamp(period.preheat_start) %}
              {% set shutdown_ts = as_timestamp(period.shutdown_start) %}
              {% set shutdown_end_ts = as_timestamp(period.shutdown_end) %}
              {% set recovery_end_ts = as_timestamp(period.recovery_end) %}

              {% if now_ts >= preheat_ts and now_ts < shutdown_ts %}
                {% set ns.phase = 'preheat' %}
              {% elif now_ts >= shutdown_ts and now_ts < shutdown_end_ts %}
                {% set ns.phase = 'shutdown' %}
              {% elif now_ts >= shutdown_end_ts and now_ts < recovery_end_ts %}
                {% set ns.phase = 'recovery' %}
              {% endif %}
            {% endfor %}
            {{ ns.phase }}
          {% endif %}

This sensor will show:

  • normal - No active cutoff
  • preheat - Currently preheating
  • shutdown - Currently in cutoff phase
  • recovery - Currently recovering

Weather-Adaptive Optimization

The script automatically adjusts cutoff parameters based on outdoor temperature:

# Inside the script (you don't need to configure this)
outdoor_temp = float(hass.states.get('sensor.outdoor_temperature').state)

if outdoor_temp < 0 or outdoor_temp > 24:
    # Extreme weather: shorter cutoffs
    max_cutoff_duration = 3.0  # hours
else:
    # Mild weather: longer cutoffs allowed
    max_cutoff_duration = 4.0  # hours (from input_number)

Why? Extreme temperatures increase heat loss β†’ shorter cutoffs maintain comfort.

You configure: The outdoor temperature sensor entity ID in the script.


Installation

Step 1: Enable Python Scripts

Add to configuration.yaml:

python_script:

Restart Home Assistant.


Step 2: Copy Python Script

  1. Download nordpool_cutoff_optimizer.py from this repository
  2. Place it in /config/python_scripts/
  3. File should be: /config/python_scripts/nordpool_cutoff_optimizer.py

Step 3: Add Input Numbers

Add these to configuration.yaml (or use packages):

input_number:
  cutoff_base_price_diff:
    name: "Base Price Difference"
    min: 0.5
    max: 10.0
    step: 0.1
    initial: 3.5
    unit_of_measurement: "c/kWh"

  cutoff_max_duration:
    name: "Max Cutoff Duration"
    min: 0.5
    max: 6.0
    step: 0.25
    initial: 4.0
    unit_of_measurement: "hours"

  cutoff_preheat_multiplier:
    name: "Preheat Cost Multiplier"
    min: 1.0
    max: 2.0
    step: 0.1
    initial: 1.5

  cutoff_recovery_multiplier:
    name: "Recovery Cost Multiplier"
    min: 1.0
    max: 2.0
    step: 0.1
    initial: 1.2

  cutoff_residual_load:
    name: "Cutoff Residual Load"
    min: 0.0
    max: 0.5
    step: 0.05
    initial: 0.1

  cutoff_min_savings_pct:
    name: "Min Savings Threshold"
    min: 0
    max: 30
    step: 1
    initial: 10
    unit_of_measurement: "%"

Restart Home Assistant.


Step 4: Add Template Sensor

Add the β€œCutoff Current Phase” sensor from above to your configuration.yaml:

template:
  - sensor:
      - name: "Cutoff Current Phase"
        unique_id: cutoff_current_phase
        state: >
          # ... (copy from above)

Restart Home Assistant.


Step 5: Automation to Run Script

Create an automation that runs the optimizer:

automation:
  - alias: "Run Nordpool Cutoff Optimizer"
    trigger:
      # Run every 15 minutes
      - platform: time_pattern
        minutes: "/15"

      # Run when tomorrow's prices arrive
      - platform: state
        entity_id: sensor.nordpool_kwh_fi_eur_3_10_0
        attribute: raw_tomorrow

    action:
      - service: python_script.nordpool_cutoff_optimizer
        data: {}

The script will now run automatically!


Testing Without Load Control

You can test the optimizer without connecting it to any loads:

1. Check the Output Sensor

Go to: Developer Tools β†’ States β†’ Search for sensor.nordpool_cutoff_periods_python

You should see:

  • State: Number of cutoffs (e.g., β€œ2”)
  • Attributes: periods list with all cutoff details

2. Monitor Current Phase

Watch sensor.cutoff_current_phase throughout the day:

  • Should be normal most of the time
  • Changes to preheat before expensive periods
  • Changes to shutdown during expensive periods
  • Changes to recovery after cutoffs

3. Verify Schedule Matches Prices

Compare cutoff times with Nordpool prices:

  • Cutoffs should be during expensive hours
  • Preheat should be during cheaper hours before peaks

Only after verification, implement actual load control (Part 3).


Debugging

Enable Debug Logging

Add to configuration.yaml:

logger:
  default: info
  logs:
    homeassistant.components.python_script: debug

Check Sensor Attributes

Developer Tools β†’ States β†’ sensor.nordpool_cutoff_periods_python

Look for:

  • All timestamps present in periods
  • Reasonable savings_pct values
  • Cutoffs during expensive hours

Common Issues

Issue Fix
No cutoff detected Lower base_price_diff or min_savings_pct
Cutoff during cheap hours Increase base_price_diff
Cutoff too long Lower max_cutoff_duration
Script error Check logs, verify Nordpool sensor exists

Performance

  • Execution time: ~0.5-2 seconds
  • Memory usage: ~5-10 MB
  • CPU usage: Negligible (runs every 15 min)

Safe for Raspberry Pi and low-power systems.


Next Steps

β†’ Part 3: Integration Examples - How to connect the optimizer to YOUR specific loads (HVAC, water heater, etc.)


Key Takeaways

  • The optimizer is load-agnostic - it only finds schedules
  • Output: sensor.nordpool_cutoff_periods_python with periods list
  • Multiple cutoffs per day are supported (morning + evening peaks)
  • YOU implement actual load control based on sensor.cutoff_current_phase
  • Dynamic Programming finds globally optimal solution
  • Weather-adaptive adjustments maintain comfort
  • Test thoroughly before connecting to real loads

Questions? Discuss in the Home Assistant Community thread

Part 3: Integration Examples - Making It Work for Your Home

Part 3 of 3: Configuration & Real-World Results

This document shows how to integrate the cutoff optimizer with YOUR specific loads.
β†’ Discuss on Home Assistant Community

GitHub Repository & Examples

:link: Complete repository: nordpool-spot-cutoff-optimizer

The repository contains:

  • :page_facing_up: Full documentation (docs/ folder)
  • :snake: Python script (python_scripts/nordpool_cutoff_optimizer.py)
  • :hammer_and_wrench: Copy-paste ready configuration examples (this post)
  • :clipboard: Installation checklist and troubleshooting

Introduction

In Part 1 we covered the theory, and Part 2 provided the Python implementation. Now it’s time to integrate the optimizer with YOUR system.

This part covers:

  • :white_check_mark: Quick installation checklist
  • :white_check_mark: Parameter calibration for YOUR building
  • :white_check_mark: HVAC integration example (complete working system)
  • :white_check_mark: Water heater integration example
  • :white_check_mark: Creating automations for your loads
  • :white_check_mark: Dashboard examples
  • :white_check_mark: Troubleshooting and optimization

Installation Checklist

Follow these steps in order:

:ballot_box_with_check: Phase 1: Prerequisites

  • Home Assistant 2024.10+ installed
  • Nordpool HACS integration installed
  • 15-minute data enabled in Nordpool integration settings
  • Verify sensor.nordpool_kwh_fi_eur_3_10_0 exists and shows raw_today with 96 slots (or 24 if 1h data)
  • Python Scripts integration enabled in configuration.yaml:

python_script:

:ballot_box_with_check: Phase 2: Install Optimizer

  • Download nordpool_cutoff_optimizer.py from GitHub repo
  • Copy to /config/python_scripts/nordpool_cutoff_optimizer.py
  • Add input_number entities (see Part 2)
  • Restart Home Assistant
  • Verify script runs: Check Developer Tools β†’ Services β†’ python_script.nordpool_cutoff_optimizer

:ballot_box_with_check: Phase 3: Add Template Sensors

  • Add sensor.cutoff_current_phase template (see Part 2)
  • Restart Home Assistant
  • Verify sensor exists in Developer Tools β†’ States

:ballot_box_with_check: Phase 4: Create Automation

  • Add automation to run optimizer every 15 minutes (see Part 2)
  • Verify automation triggers
  • Check sensor.nordpool_cutoff_periods_python has data

:ballot_box_with_check: Phase 5: Test Without Load Control

  • Monitor sensor.cutoff_current_phase throughout the day
  • Verify cutoffs occur during expensive hours
  • Check logs for errors

Only proceed to Phase 6 after verifying the optimizer works correctly!

:ballot_box_with_check: Phase 6: Implement Load Control

  • Create automations for YOUR specific loads (see examples below)
  • Test in dry-run mode first (log actions, don’t control loads)
  • Monitor for 24-48 hours
  • Enable full control when confident

Parameter Calibration

The optimizer needs parameters calibrated for YOUR specific building and system.

Key Parameters to Calibrate

Parameter Purpose How to Calibrate
base_price_diff Minimum price difference to trigger cutoff Start with 3.5 c/kWh, adjust based on results
max_cutoff_duration Maximum cutoff length Start with 4h, reduce if temperature drops too much
preheat_multiplier Cost of preheating 1.5 for poor insulation, 1.3 for well-insulated
recovery_multiplier Cost of recovery 1.2 typical, 1.1 for good thermal mass
residual_load Load during cutoff 0.1 for fan-only, 0.05 for complete off

Calibration Method 1: Historical Data Analysis

If you have 1-2 months of data:

  1. Note your average heating consumption during different outdoor temperatures
  2. Identify expensive price days where cutoff would have occurred
  3. Estimate savings based on actual consumption patterns
  4. Adjust base_price_diff so optimizer finds 2-3 cutoffs per week

Example:

  • Your heating: 5 kWh/hour at 0Β°C outdoor temp
  • Average price: 5 c/kWh
  • Peak price: 15 c/kWh
  • Price difference: 10 c/kWh β†’ Should trigger cutoff
  • If no cutoff β†’ Lower base_price_diff to 2.5 c/kWh

Calibration Method 2: A/B Testing

Start conservative, iterate:

  1. Week 1: Set base_price_diff = 5.0 c/kWh (few cutoffs)
  • Monitor temperature impact
  • Check savings
  1. Week 2: Lower to 3.5 c/kWh (more cutoffs)
  • Compare comfort vs Week 1
  • Check savings increase
  1. Week 3: Lower to 2.5 c/kWh (many cutoffs)
  • If temperature drops >1Β°C β†’ Too aggressive
  • If comfort OK β†’ Keep this setting

Rule of thumb: If cutoffs cause >1Β°C temperature drop, increase base_price_diff or reduce max_cutoff_duration.


Example 1: HVAC System Integration

This example shows a real working system with:

  • 2Γ— air-source heat pumps (primary heating)
  • Hydronic radiator heating (backup)
  • Weather-adaptive control

Architecture


sensor.cutoff_current_phase
↓
β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”
β”‚         β”‚
preheat   shutdown   recovery
β”‚         β”‚         β”‚
↓         ↓         ↓
[Heat]   [Fan Only]  [Heat]
+Rads     +Rads Min   +Rads

Template Sensors for HVAC


template:

- sensor:

# Current cutoff phase

    - name: "Cutoff Current Phase"
unique_id: cutoff_current_phase
state: >
{% set periods = state_attr('sensor.nordpool_cutoff_periods_python', 'periods') %}
{% if not periods %}
normal
{% else %}
{% set now_ts = now().timestamp() %}
{% set ns = namespace(phase='normal') %}
{% for period in periods %}
{% set preheat_ts = as_timestamp(period.preheat_start) %}
{% set shutdown_ts = as_timestamp(period.shutdown_start) %}
{% set shutdown_end_ts = as_timestamp(period.shutdown_end) %}
{% set recovery_end_ts = as_timestamp(period.recovery_end) %}

      {% if now_ts >= preheat_ts and now_ts < shutdown_ts %}
        {% set ns.phase = 'preheat' %}
      {% elif now_ts >= shutdown_ts and now_ts < shutdown_end_ts %}
        {% set ns.phase = 'shutdown' %}
      {% elif now_ts >= shutdown_end_ts and now_ts < recovery_end_ts %}
        {% set ns.phase = 'recovery' %}
      {% endif %}
    {% endfor %}
    {{ ns.phase }}
    {% endif %}


# Next cutoff info (for dashboard)

    - name: "Next Cutoff Time"
unique_id: next_cutoff_time
state: >
{% set periods = state_attr('sensor.nordpool_cutoff_periods_python', 'periods') %}
{% if periods %}
{% set now_ts = now().timestamp() %}
{% set future = periods | selectattr('shutdown_start', '>', now().isoformat()) | list %}
{% if future %}
{{ as_timestamp(future.shutdown_start) | timestamp_custom('%H:%M') }}
{% else %}
No cutoff scheduled
{% endif %}
{% else %}
No data
{% endif %}
attributes:
duration: >
{% set periods = state_attr('sensor.nordpool_cutoff_periods_python', 'periods') %}
{% if periods %}
{% set future = periods | selectattr('shutdown_start', '>', now().isoformat()) | list %}
{% if future %}
{{ future.cutoff_duration_hours }} hours
{% endif %}
{% endif %}
estimated_savings: >
{% set periods = state_attr('sensor.nordpool_cutoff_periods_python', 'periods') %}
{% if periods %}
{% set future = periods | selectattr('shutdown_start', '>', now().isoformat()) | list %}
{% if future %}
{{ future.savings_pct }}%
{% endif %}
{% endif %}

Automations for HVAC Control

Automation 1: Preheat Phase


automation:

- alias: "Cutoff - HVAC Preheat"
description: "Increase heating 1Β°C before cutoff period"
trigger:
    - platform: state
entity_id: sensor.cutoff_current_phase
to: "preheat"
action:


# Heat pumps: Increase target temperature

    - service: climate.set_temperature
target:
entity_id:
- climate.heat_pump_1
- climate.heat_pump_2
data:
temperature: >
{{ state_attr('climate.heat_pump_1', 'temperature') | float(21) + 1 }}


# Radiators: Increase target temperature

    - service: number.set_value
target:
entity_id: number.radiator_target_temp
data:
value: >
{{ states('number.radiator_target_temp') | float(20) + 2 }}


# Optional: Increase fan speed

    - service: climate.set_fan_mode
target:
entity_id:
- climate.heat_pump_1
- climate.heat_pump_2
data:
fan_mode: "high"

Automation 2: Shutdown Phase


- alias: "Cutoff - HVAC Shutdown"
description: "Reduce heating during expensive period"
trigger:
    - platform: state
entity_id: sensor.cutoff_current_phase
to: "shutdown"
action:


# Heat pumps: Switch to fan-only mode

    - service: climate.set_hvac_mode
target:
entity_id:
- climate.heat_pump_1
- climate.heat_pump_2
data:
hvac_mode: "fan_only"


# Keep fan running for air circulation

    - service: climate.set_fan_mode
target:
entity_id:
- climate.heat_pump_1
- climate.heat_pump_2
data:
fan_mode: "high"


# Radiators: Set to minimum

    - service: number.set_value
target:
entity_id: number.radiator_target_temp
data:
value: 15  \# Minimum safe temperature

Automation 3: Recovery Phase


- alias: "Cutoff - HVAC Recovery"
description: "Return to normal heating after cutoff"
trigger:
    - platform: state
entity_id: sensor.cutoff_current_phase
to: "recovery"
action:


# Heat pumps: Return to heating mode

    - service: climate.set_hvac_mode
target:
entity_id:
- climate.heat_pump_1
- climate.heat_pump_2
data:
hvac_mode: "heat"


# Heat pumps: Normal target temperature

    - service: climate.set_temperature
target:
entity_id:
- climate.heat_pump_1
- climate.heat_pump_2
data:
temperature: 21  \# Your normal setpoint


# Radiators: Return to normal

    - service: number.set_value
target:
entity_id: number.radiator_target_temp
data:
value: 20  \# Your normal radiator temp


# Fan: Return to normal speed

    - service: climate.set_fan_mode
target:
entity_id:
- climate.heat_pump_1
- climate.heat_pump_2
data:
fan_mode: "auto"

Automation 4: Return to Normal


- alias: "Cutoff - HVAC Normal"
description: "Ensure normal operation after recovery"
trigger:
    - platform: state
entity_id: sensor.cutoff_current_phase
to: "normal"
action:


# Same as recovery, but ensures everything is reset

    - service: climate.set_hvac_mode
target:
entity_id:
- climate.heat_pump_1
- climate.heat_pump_2
data:
hvac_mode: "heat"
    - service: climate.set_temperature
target:
entity_id:
- climate.heat_pump_1
- climate.heat_pump_2
data:
temperature: 21

Safety Overrides

Important: Always include safety overrides!


automation:

- alias: "Cutoff - Safety Override Cold"
description: "Cancel cutoff if too cold"
trigger:
    - platform: numeric_state
entity_id: sensor.indoor_temperature
below: 19  \# Your minimum comfort threshold
condition:
    - condition: state
entity_id: sensor.cutoff_current_phase
state: "shutdown"
action:


# Force return to heating

    - service: climate.set_hvac_mode
target:
entity_id:
- climate.heat_pump_1
- climate.heat_pump_2
data:
hvac_mode: "heat"


# Send notification

    - service: notify.mobile_app
data:
title: "Cutoff Override"
message: "Indoor temp too low, heating restored"


Example 2: Water Heater Integration

Simpler system: Water tank acts as thermal storage.

Template Sensors


template:

- sensor:
    - name: "Water Heater Cutoff Phase"
unique_id: water_heater_cutoff_phase
state: >
{% set periods = state_attr('sensor.nordpool_cutoff_periods_python', 'periods') %}
{% if not periods %}
normal
{% else %}
{% set now_ts = now().timestamp() %}
{% set ns = namespace(phase='normal') %}
{% for period in periods %}
{% set preheat_ts = as_timestamp(period.preheat_start) %}
{% set shutdown_ts = as_timestamp(period.shutdown_start) %}
{% set shutdown_end_ts = as_timestamp(period.shutdown_end) %}

      {% if now_ts >= preheat_ts and now_ts < shutdown_ts %}
        {% set ns.phase = 'preheat' %}
      {% elif now_ts >= shutdown_ts and now_ts < shutdown_end_ts %}
        {% set ns.phase = 'shutdown' %}
      {% endif %}
    {% endfor %}
    {{ ns.phase }}
    {% endif %}

Automations


automation:

- alias: "Water Heater - Preheat"
trigger:
    - platform: state
entity_id: sensor.water_heater_cutoff_phase
to: "preheat"
action:


# Increase water temp by 5Β°C

    - service: water_heater.set_temperature
target:
entity_id: water_heater.main
data:
temperature: >
{{ state_attr('water_heater.main', 'temperature') | float(60) + 5 }}
- alias: "Water Heater - Shutdown"
trigger:
    - platform: state
entity_id: sensor.water_heater_cutoff_phase
to: "shutdown"
action:


# Turn off water heater

    - service: water_heater.turn_off
target:
entity_id: water_heater.main
- alias: "Water Heater - Return to Normal"
trigger:
    - platform: state
entity_id: sensor.water_heater_cutoff_phase
to: "normal"
action:


# Return to normal temperature

    - service: water_heater.set_temperature
target:
entity_id: water_heater.main
data:
temperature: 60  \# Normal setpoint


Dashboard Example

Create a cutoff monitoring dashboard:


type: vertical-stack
cards:

- type: markdown
content: |

# Nordpool Cutoff Optimizer

**Current Phase:** {{ states('sensor.cutoff_current_phase') | title }}

**Next Cutoff:** {{ states('sensor.next_cutoff_time') }}
**Duration:** {{ state_attr('sensor.next_cutoff_time', 'duration') }}
**Est. Savings:** {{ state_attr('sensor.next_cutoff_time', 'estimated_savings') }}
- type: entities
title: Optimizer Parameters
entities:
    - entity: input_number.cutoff_base_price_diff
    - entity: input_number.cutoff_max_duration
    - entity: input_number.cutoff_min_savings_pct
- type: history-graph
title: Temperature During Cutoffs
entities:
    - entity: sensor.indoor_temperature
    - entity: sensor.cutoff_current_phase
hours_to_show: 24
- type: custom:apexcharts-card
title: Nordpool Prices with Cutoffs
graph_span: 48h
header:
show: true
series:
    - entity: sensor.nordpool_kwh_fi_eur_3_10_0
name: Electricity Price
type: line
color: blue
    - entity: sensor.cutoff_current_phase
name: Cutoff Phase
type: column
transform: |
return x === 'shutdown' ? 1 : 0;
color: red


Troubleshooting

Common Issues

Problem Cause Solution
No cutoffs detected Price threshold too high Lower base_price_diff to 2.5 c/kWh
Too many cutoffs Threshold too low Increase base_price_diff to 4.0 c/kWh
Temperature drops >1Β°C Cutoff too long Reduce max_cutoff_duration to 3h
Recovery takes too long Poor thermal mass Increase recovery_multiplier to 1.3
Script errors Missing sensor Check Nordpool sensor exists

Debug Mode

Enable detailed logging:


logger:
default: info
logs:
homeassistant.components.python_script: debug
homeassistant.components.automation: debug


Expected Results

Results vary significantly based on your specific setup:

Factors Affecting Performance

Factor Impact on Results
Building insulation Better insulation = longer cutoffs possible
Thermal mass High mass (concrete) = better energy storage
Heat pump efficiency Higher SCOP = more cost-effective
Outdoor temperature Extreme temps require shorter cutoffs
Price volatility Higher peaks = better savings potential
Backup heating Multiple heat sources = more flexibility

What to Monitor

Track these metrics during your testing period:

Temperature metrics:

  • Indoor temperature during cutoffs (target: < 1Β°C drop)
  • Recovery time to normal temperature
  • Temperature variation between rooms

Energy metrics:

  • Total electricity consumption before/after
  • Peak hour consumption reduction
  • Actual savings vs estimated savings

Comfort metrics:

  • Perceived comfort level (subjective)
  • Cold spot occurrence
  • Recovery comfort (overheating?)

Typical Results Range

Based on system design theory and building thermodynamics:

  • Savings on cutoff days: 10-35% of peak hour costs
  • Temperature impact: 0.3-1.0Β°C drop during cutoff
  • Optimal cutoff duration: 2-4 hours depending on weather
  • Recovery time: Equal to cutoff duration

Your mileage WILL vary! Start conservative and iterate based on real data.

Community Results

We encourage users to share their results in the Home Assistant Community forum.

Consider sharing:

  • Building type and insulation level
  • Heat pump model and SCOP
  • Outdoor temperature range
  • Cutoff duration and frequency
  • Actual savings percentage
  • Temperature impact

Future enhancement: We may create a collaborative data analysis project to build calibration guidelines based on community-submitted data including weather forecast integration.


Next Steps

  1. :white_check_mark: Complete installation checklist
  2. :white_check_mark: Calibrate parameters for YOUR building
  3. :white_check_mark: Test for 1 week in monitoring mode
  4. :white_check_mark: Enable full automation
  5. :white_check_mark: Fine-tune based on results
  6. :white_check_mark: Share your results in the community!

Contributing Your Example

If you’ve successfully integrated this with a different system, please share!

Submit to GitHub:

  • Fork the repository
  • Create examples/your_system/
  • Include README, configuration, and results
  • Submit Pull Request

Discuss in Community:

  • Share your setup in the forum thread
  • Help others with questions
  • Improve the documentation

Summary

  • The optimizer is generic - you adapt it to YOUR loads
  • Start with conservative parameters and iterate
  • Monitor temperature during cutoffs
  • Always include safety overrides
  • Test thoroughly before full automation
  • Share your results to help others

Questions? Discuss in the Home Assistant Community thread

This is exactly what I was searching for! Some of the electricity companies here in the Netherlands also starting inementing 15min dynamic priceblocks. Mine not yet, but I think it doesn’t matter for your project.

Is there something I/we can test? The easiest way is a HACS integration I think, with a good readme. Reading this post, that is not a problem for you.

My house has a Mitsubishi Heavy Industries Airco, floor heating and radiators. I’m controlling the Airco now with the HACS integration Versatile Thermostat, wich not thinks about prices…

1 Like

This is an excellent project!! My compliments Niko!

I have tried something similar by taking the 8 most expensive hours, put them in order hight to low, then takinh out the sequential hours; I do not want my bathroom floor to be swithed off for more than one hour otherwise re-heating will take too long, especially at -10 to -24 degrees outsside. Then I put the hours in a calendar and can use it in automations (in combination with procing levels, outside temp and working day).

But now in Finland we switched to a 15 min interval pricing and that multiplied all my helpers with 4 and turned my very basic scripting into an unmanagable hell…

So if you are willing to share the details of your setup, it would at least help me out a lot…

Best , Olaf

1 Like

Thank you for the interest on the project!

The HACS coding/ integration implementation might not be suitable to explain why, what and how to implement in the most diverse range of housing options and energy solutions. My idea now is to give one the tools how to implement in one’s own house what I have done in my house.

But never the less, I think you find the code snippets and explanation posts at least inspiring to tweak your own system.

See you soon on the part 2.

I feel you, trust me! Countless hours on my own system trying to optimize it for surging electricity prices.

As interest is here, I will see you soon at the part 2. :slight_smile:

Excellent - thank you!

:gift: BONUS: Using the Official Nordpool Integration

Alternative implementation for the core integration


Background

The cutoff optimizer was originally designed for the HACS Nordpool custom component which provides convenient sensor attributes with all price data. However, Home Assistant now has an official Nordpool integration (since 2024.12) that works differently.

Key differences:

  • HACS version: Exposes prices as sensor attributes (raw_today, raw_tomorrow)
  • Official version: Uses service calls to fetch price data (nordpool.get_prices_for_date)

Since I don’t personally use the official integration, this guide is theoretical but based on the official documentation and community feedback.


:warning: Important Considerations

Why HACS might be better for this use case:

HACS Custom Component advantages:

  • All price data readily available in sensor attributes :white_check_mark:
  • No need for trigger templates :white_check_mark:
  • Simpler integration with Python scripts :white_check_mark:
  • More features out-of-the-box (VAT, additional costs built-in) :white_check_mark:

Official Integration advantages:

  • Maintained by Home Assistant core team :white_check_mark:
  • Guaranteed 15-minute MTU support after transition :white_check_mark:
  • No HACS dependency :white_check_mark:
  • Official support channel :white_check_mark:

Can they coexist?

No - They share the same domain (nordpool), so only one can be installed at a time.


Adapting the Python Script

The main challenge is that the official integration doesn’t expose raw_today and raw_tomorrow attributes. Instead, you need to call services to get price data.

Option 1: Pre-fetch prices with a trigger template

Create a helper template sensor that fetches prices and stores them:

template:
  - trigger:
      - trigger: time_pattern
        minutes: /15
      - trigger: homeassistant
        event: start
    action:
      - action: nordpool.get_prices_for_date
        data:
          config_entry: YOUR_CONFIG_ENTRY_ID  # Get this from Developer Tools
          date: "{{ now().date() }}"
          areas: FI
          currency: EUR
        response_variable: today_price
      
      - action: nordpool.get_prices_for_date
        data:
          config_entry: YOUR_CONFIG_ENTRY_ID
          date: "{{ now().date() + timedelta(days=1) }}"
          areas: FI
          currency: EUR
        response_variable: tomorrow_price
    
    sensor:
      - name: "Nordpool Price Data"
        unique_id: nordpool_price_data_helper
        state: "{{ now().isoformat() }}"
        attributes:
          raw_today: >
            {% set data = namespace(prices=[]) %}
            {% if today_price is mapping %}
              {% for state in today_price['FI'] %}
                {% set data.prices = data.prices + [{'start': state.start, 'end': state.end, 'value': state.price / 1000}] %}
              {% endfor %}
            {% endif %}
            {{ data.prices }}
          
          raw_tomorrow: >
            {% set data = namespace(prices=[]) %}
            {% if tomorrow_price is mapping %}
              {% for state in tomorrow_price['FI'] %}
                {% set data.prices = data.prices + [{'start': state.start, 'end': state.end, 'value': state.price / 1000}] %}
              {% endfor %}
            {% endif %}
            {{ data.prices }}

How to get your config_entry ID:

  1. Go to Developer Tools β†’ Actions
  2. Select nordpool.get_prices_for_date
  3. Choose your Nordpool instance
  4. Switch to YAML mode
  5. Copy the config_entry value

Option 2: Modify the Python script

You would need to modify nordpool_cutoff_optimizer.py to:

  1. Call the nordpool.get_prices_for_date service instead of reading sensor attributes
  2. Parse the response format (which is slightly different)
  3. Handle the different data structure

Example modification (conceptual):

# OLD (HACS version):
nordpool_sensor = hass.states.get('sensor.nordpool_kwh_fi_eur_3_10_0')
raw_today = nordpool_sensor.attributes.get('raw_today', [])
raw_tomorrow = nordpool_sensor.attributes.get('raw_tomorrow', [])

# NEW (Official version):
# Read from the helper template sensor created above
price_helper = hass.states.get('sensor.nordpool_price_data_helper')
raw_today = price_helper.attributes.get('raw_today', [])
raw_tomorrow = price_helper.attributes.get('raw_tomorrow', [])

The advantage of Option 1 (helper template) is that you don’t need to modify the Python script at all - just point it to the new sensor entity.


Data Format Differences

HACS format:

{
  'start': '2025-10-07T00:00:00+03:00',
  'end': '2025-10-07T00:15:00+03:00',
  'value': 5.23  # c/kWh
}

Official format (from service call):

{
  'start': '2025-10-07T00:00:00Z',  # UTC!
  'end': '2025-10-07T00:15:00Z',
  'price': 52.3  # EUR/MWh (note: 1000x larger!)
}

Key differences:

  1. Timezone: Official returns UTC, HACS returns local time
  2. Units: Official uses EUR/MWh, HACS uses c/kWh
  3. Key names: price vs value

The helper template above handles these conversions.


Complete Setup Steps

1. Install Official Nordpool Integration

  • Go to Settings β†’ Devices & Services
  • Click + Add Integration
  • Search for β€œNord Pool”
  • Configure with your area and currency

2. Create Helper Template

Copy the helper template from Option 1 above to your configuration.yaml (or use packages).

Important: Replace YOUR_CONFIG_ENTRY_ID with your actual config entry ID!

3. Modify Script Configuration

In the Python script input configuration, change the Nordpool sensor entity:

# Change this line in your script configuration:
# OLD:
NORDPOOL_SENSOR = 'sensor.nordpool_kwh_fi_eur_3_10_0'

# NEW:
NORDPOOL_SENSOR = 'sensor.nordpool_price_data_helper'

4. Verify Data Format

Check that the helper sensor attributes match the expected format:

- Developer Tools β†’ States
- Find: sensor.nordpool_price_data_helper
- Check attributes: raw_today and raw_tomorrow

They should look like the HACS format (with value key, c/kWh units).


Limitations & Caveats

:warning: I haven’t tested this myself - I use the HACS version which works perfectly for this use case.

Potential issues:

  1. Performance: Calling services every 15 minutes might be slower than reading attributes
  2. Timing: Service calls might fail if API is unavailable
  3. Complexity: More moving parts = more potential failure points
  4. 15-minute transition: Untested how this behaves during the MTU transition

Recommendation: If the HACS version works for you, stick with it. The official integration is better suited for simpler use cases (Energy Dashboard, basic automations).


Why This Matters

The official integration is great for most users who just want current/next/average prices. But for advanced optimization like our cutoff scheduler, having all price data easily accessible (as the HACS version provides) is significantly simpler.

This is not a criticism of the official integration - it’s designed for different use cases and intentionally keeps things simple and maintainable.


Community Feedback Needed! :pray:

If you use the official Nordpool integration with this optimizer:

  • Did the helper template work?
  • Any issues with timing or data format?
  • Performance problems?
  • Better solutions?

Please share your experience! This will help improve the documentation for official integration users.


This is a community-contributed guide. If you successfully adapt the system for the official integration, please share your configuration!


Questions? Discuss below!