xztraz
(Xztraz)
September 30, 2025, 8:24am
1
I’ve been trying to get Nordpool 15min data into HA and this is what i’ve managed so far.
Use the HA Core Nordopool integration that supports 15-min data (uninstall your hacs version from hacs first to be able to install this from devices)
Use a template to create graph data with corrections for vat and transfer costs
Use AIO energy management to automate cheap hours.
pasting code below
3 Likes
xztraz
(Xztraz)
September 30, 2025, 8:36am
2
Guide:
first install nordpool integration in HA (not the HACS version) and set up your area and such.
add two input number helpers (you want to have enabled “advanced mode”
under your profile pic in the lower left corner to be able to set stepsize for cost to 0.0001)
input_number.electricity_tax
input_number.electricity_purchase_costs
i use 25% for tax and 0.7625 SEK/kWh for cost
And This template below is used in configuration.yaml
Config Entry is from the nordpool integration (to find it go to development tools, actions, do a nordpool.get_price request and go to yaml mode to find the number)
otherwise modify script to match your areas and such
1 Like
xztraz
(Xztraz)
September 30, 2025, 8:59am
4
Energy Price Automation
To automate stuff based on 15-min data i’ve used aio_energy_management
install via HACS. add custom repository. past the github link
in configuration.yaml add something like this (change the config entry to your nordpool config entry)
this gives an binary sensor my_cheapest_hour that can be used in automations to turn on and off stuff based on cheapest times
aio_energy_management:
cheapest_hours:
- nordpool_official_config_entry: 01K6AEXZV4HDRNYVJQEJB2R5GT
unique_id: my_cheapest_hours
name: My Cheapest Hours
mtu: 15
first_hour: 19
last_hour: 18
starting_today: true
number_of_hours: 8
sequential: false
failsafe_starting_hour: 22
tip:
it’s also possible to put an input_number helper as the number_of_hours input.
example with a input number helper named “my_cheapest_hours_setting”
number_of_hours: input_number.my_cheapest_hours_setting
xztraz
(Xztraz)
September 30, 2025, 9:27am
5
Template for data retreival 15 min interval
and basic apexcharts graph
template goes into configuration.yaml
edit the config_entry: to your nordpool entity and other parameters to math your place
template:
- trigger:
- trigger: time_pattern
minutes: /15
- trigger: homeassistant
event: start
action:
- variables:
area: "SE3"
currency: "SEK"
country: "Sweden"
config_entry: "01K6AEXZV4HDRNYVJQEJB2R5GT"
price_sensor: "sensor.nord_pool_se3_current_price"
- action: nordpool.get_price_indices_for_date
data:
config_entry: "{{ config_entry }}"
date: "{{ now().date() }}"
areas: "{{ area }}"
currency: "{{ currency }}"
resolution: "15"
response_variable: today_price
sensor:
- name: Electricity Prices Today
unique_id: se3_electricity_prices_today
unit_of_measurement: "SEK/kWh"
icon: mdi:cash-clock
state: >
{% set current_price = (states(price_sensor) | float(0) * (1 + states('input_number.electricity_tax') | float(0)/100) + states('input_number.electricity_purchase_costs') | float(0)) | round(2) %}
{{ current_price }}
attributes:
country: "{{ country }}"
region: "{{ area }}"
currency: "{{ currency }}"
min: >
{% set entries = today_price[area] %}
{% if entries %}
{% set min_val = entries | map(attribute='price') | min | float(0) / 1000 %}
{{ (min_val * (1 + states('input_number.electricity_tax') | float(0)/100)
+ states('input_number.electricity_purchase_costs') | float(0)) | round(2) }}
{% else %} unavailable {% endif %}
max: >
{% set entries = today_price[area] %}
{% if entries %}
{% set max_val = entries | map(attribute='price') | max | float(0) / 1000 %}
{{ (max_val * (1 + states('input_number.electricity_tax') | float(0)/100)
+ states('input_number.electricity_purchase_costs') | float(0)) | round(2) }}
{% else %} unavailable {% endif %}
mean: >
{% set entries = today_price[area] %}
{% if entries %}
{% set avg_val = entries | map(attribute='price') | map('float') | sum / (entries | count) / 1000 %}
{{ (avg_val * (1 + states('input_number.electricity_tax') | float(0)/100)
+ states('input_number.electricity_purchase_costs') | float(0)) | round(2) }}
{% else %} unavailable {% endif %}
data: >
{% set electricity_tax = states('input_number.electricity_tax') | float(0) %}
{% set purchase_costs = states('input_number.electricity_purchase_costs') | float(0) %}
{% set data = namespace(prices=[]) %}
{% if today_price[area] | count > 0 %}
{% for state in today_price[area] %}
{% set corrected_start = as_datetime(state.start).astimezone().isoformat() %}
{% set corrected_end = as_datetime(state.end).astimezone().isoformat() %}
{% set val = (state.price/1000 * (1 + electricity_tax/100) + purchase_costs) | round(3, default=0) %}
{% set data.prices = data.prices + [{'start': corrected_start, 'end': corrected_end, 'value': val}] %}
{% endfor %}
{% endif %}
{{ data.prices }}
- trigger:
- trigger: time_pattern
minutes: "10"
- trigger: homeassistant
event: start
action:
- variables:
area: "SE3"
currency: "SEK"
country: "Sweden"
config_entry: "01K6AEXZV4HDRNYVJQEJB2R5GT"
- action: nordpool.get_price_indices_for_date
data:
config_entry: "{{ config_entry }}"
date: "{{ now().date() + timedelta(days=1) }}"
areas: "{{ area }}"
currency: "{{ currency }}"
resolution: "15"
response_variable: tomorrow_price
sensor:
- name: Electricity Prices Tomorrow
unique_id: se3_electricity_prices_tomorrow
unit_of_measurement: "SEK/kWh"
icon: mdi:cash-clock
state: >
{% set entries = tomorrow_price[area] %}
{% if entries %}
{% set avg_val = entries | map(attribute='price') | map('float') | sum / (entries | count) / 1000 %}
{{ (avg_val * (1 + states('input_number.electricity_tax') | float(0)/100)
+ states('input_number.electricity_purchase_costs') | float(0)) | round(2) }}
{% else %} unavailable {% endif %}
attributes:
country: "{{ country }}"
region: "{{ area }}"
currency: "{{ currency }}"
tomorrow_valid: >
{{ tomorrow_price.get(area) is defined and tomorrow_price[area] | count > 0 }}
min: >
{% set entries = tomorrow_price[area] %}
{% if entries %}
{% set min_val = entries | map(attribute='price') | min | float(0) / 1000 %}
{{ (min_val * (1 + states('input_number.electricity_tax') | float(0)/100)
+ states('input_number.electricity_purchase_costs') | float(0)) | round(2) }}
{% else %} unavailable {% endif %}
max: >
{% set entries = tomorrow_price[area] %}
{% if entries %}
{% set max_val = entries | map(attribute='price') | max | float(0) / 1000 %}
{{ (max_val * (1 + states('input_number.electricity_tax') | float(0)/100)
+ states('input_number.electricity_purchase_costs') | float(0)) | round(2) }}
{% else %} unavailable {% endif %}
mean: >
{% set entries = tomorrow_price[area] %}
{% if entries %}
{% set avg_val = entries | map(attribute='price') | map('float') | sum / (entries | count) / 1000 %}
{{ (avg_val * (1 + states('input_number.electricity_tax') | float(0)/100)
+ states('input_number.electricity_purchase_costs') | float(0)) | round(2) }}
{% else %} unavailable {% endif %}
data: >
{% set electricity_tax = states('input_number.electricity_tax') | float(0) %}
{% set purchase_costs = states('input_number.electricity_purchase_costs') | float(0) %}
{% set data = namespace(prices=[]) %}
{% if tomorrow_price[area] | count > 0 %}
{% for state in tomorrow_price[area] %}
{% set corrected_start = as_datetime(state.start).astimezone().isoformat() %}
{% set corrected_end = as_datetime(state.end).astimezone().isoformat() %}
{% set val = (state.price/1000 * (1 + electricity_tax/100) + purchase_costs) | round(3, default=0) %}
{% set data.prices = data.prices + [{'start': corrected_start, 'end': corrected_end, 'value': val}] %}
{% endfor %}
{% endif %}
{{ data.prices }}
and here is an updated (again) graph to show current, average, min and max
type: custom:apexcharts-card
experimental:
color_threshold: true
apex_config:
responsive:
- breakpoint: 500
options:
chart:
height: 300px
- breakpoint: 1200
options:
chart:
height: 640px
- breakpoint: 3000
options:
chart:
height: 800px
legend:
show: false
title:
floating: false
align: center
style:
fontSize: 20px
fontWeight: bold
xaxis:
labels:
datetimeFormatter:
hour: HH
plotOptions:
bar:
columnWidth: 100%
barGap: 0
graph_span: 2d
show:
last_updated: true
header:
title: Energy Price
show: true
show_states: true
colorize_states: true
span:
start: day
now:
show: true
label: Now
series:
- entity: sensor.electricity_prices_today
yaxis_id: SEK
type: column
show:
extremas: true
in_header: false
float_precision: 3
color: green
color_threshold:
- value: 0
color: "#00ffaa"
- value: 0.25
color: "#00ff55"
- value: 0.5
color: "#00ff00"
- value: 0.75
color: "#55ff00"
- value: 1
color: "#aaff00"
- value: 1.5
color: "#ffff00"
- value: 2
color: "#ffaa00"
- value: 2.5
color: "#ff5500"
- value: 3
color: "#ff0000"
- value: 4
color: "#ff0055"
- value: 5
color: "#ff00aa"
- value: 6
color: "#ff00ff"
- value: 7
color: "#ff34ff"
- value: 9
color: "#ff65ff"
- value: 11
color: "#ff98ff"
- value: 13
color: "#ffccff"
- value: 15
color: "#ffffff"
data_generator: |
const data = entity.attributes.data.map((entry) => {
// Center each 15-minute period correctly
const start = new Date(entry.start).getTime();
const end = new Date(entry.end).getTime();
const midpoint = start + (end - start) / 2;
return [midpoint, entry.value];
});
return data;
- entity: sensor.electricity_prices_tomorrow
yaxis_id: SEK
type: column
show:
extremas: true
in_header: false
float_precision: 3
color: green
color_threshold:
- value: 0
color: "#00ffaa"
- value: 0.25
color: "#00ff55"
- value: 0.5
color: "#00ff00"
- value: 0.75
color: "#55ff00"
- value: 1
color: "#aaff00"
- value: 1.5
color: "#ffff00"
- value: 2
color: "#ffaa00"
- value: 2.5
color: "#ff5500"
- value: 3
color: "#ff0000"
- value: 4
color: "#ff0055"
- value: 5
color: "#ff00aa"
- value: 6
color: "#ff00ff"
- value: 7
color: "#ff34ff"
- value: 9
color: "#ff65ff"
- value: 11
color: "#ff98ff"
- value: 13
color: "#ffccff"
- value: 15
color: "#ffffff"
data_generator: |
const data = entity.attributes.data.map((entry) => {
// Center each 15-minute period correctly
const start = new Date(entry.start).getTime();
const end = new Date(entry.end).getTime();
const midpoint = start + (end - start) / 2;
return [midpoint, entry.value];
});
return data;
- entity: sensor.electricity_prices_today
name: Current Price
yaxis_id: SEK
float_precision: 3
show:
in_header: true
in_chart: false
unit: Kr/kWh
color: white
- entity: sensor.electricity_prices_today
attribute: min
name: Min Today
yaxis_id: SEK
float_precision: 2
show:
in_header: true
in_chart: false
unit: Kr/kWh
color: green
- entity: sensor.electricity_prices_today
attribute: mean
name: Average Today
yaxis_id: SEK
float_precision: 2
show:
in_header: true
in_chart: false
unit: Kr/kWh
color: yellow
- entity: sensor.electricity_prices_today
attribute: max
name: Max Today
yaxis_id: SEK
float_precision: 2
show:
in_header: true
in_chart: false
unit: Kr/kWh
color: red
yaxis:
- id: SEK
min: 0
max: ~12
apex_config:
tickAmount: 6
forceNiceScale: true
title:
text: SEK
A more advanced graph can be found below
2 Likes
Dan1jel
(Danijel)
October 2, 2025, 5:51pm
8
Sorry for stealing you post, but do someone know what broke my min/max in the Apex chart? Cant get this to show
type: custom:apexcharts-card
now:
show: true
label: Aktuellt pris
color: var(--primary-color)
graph_span: 24h
show:
last_updated: true
span:
start: day
offset: +0H
header:
title: Elpriset idag/imorgon
show: true
show_states: true
colorize_states: true
floating: false
apex_config:
show:
offset_in_name: false
chart:
height: 250px
legend:
showForSingleSeries: true
plotOptions:
bar:
borderRadius: 0
yaxis:
min: 0
decimalsInFloat: 2
tickAmount: 10
forceNiceScale: true
hours_12: false
stacked: false
experimental:
color_threshold: true
all_series_config:
show:
legend_value: false
datalabels: false
extremas: true
in_brush: false
float_precision: 1
type: area
invert: false
fill_raw: zero
series:
- entity: sensor.nordpool_kwh_se3_sek_3_10_0
type: column
color: lightblue
color_threshold:
- value: 0
color: "#8be9fd"
- value: 1
color: "#50fa7b"
- value: 1.5
color: "#8be978"
- value: 2
color: "#f8f872"
- value: 2.5
color: "#ffb86c"
- value: 3
color: "#ff9859"
- value: 3.5
color: "#ff7846"
- value: 4
color: "#ff5555"
float_precision: 2
stroke_width: 0
name: Idag
unit: SEK/kWh
show:
legend_value: false
extremas: true
in_header: false
header_color_threshold: true
data_generator: |
return entity.attributes.today.map((price, index) => {
return [new Date().setHours(index/4,index%4*15,0), price];
});
- entity: sensor.nordpool_kwh_se3_sek_3_10_0
attribute: min
type: column
float_precision: 2
stroke_width: 2
name: Lägsta
show:
in_chart: false
in_header: true
legend_value: false
header_color_threshold: true
color_threshold:
- value: 0
color: "#8be9fd"
- value: 1
color: "#50fa7b"
- value: 1.5
color: "#8be978"
- value: 2
color: "#f8f872"
- value: 2.5
color: "#ffb86c"
- value: 3
color: "#ff9859"
- value: 3.5
color: "#ff7846"
- value: 4
color: "#ff5555"
- entity: sensor.nordpool_kwh_se3_sek_3_10_0
name: Nuvarande
type: column
show:
legend_value: false
extremas: true
in_header: true
header_color_threshold: true
in_chart: false
float_precision: 2
color_threshold:
- value: 0
color: "#8be9fd"
- value: 1
color: "#50fa7b"
- value: 1.5
color: "#8be978"
- value: 2
color: "#f8f872"
- value: 2.5
color: "#ffb86c"
- value: 3
color: "#ff9859"
- value: 3.5
color: "#ff7846"
- value: 4
color: "#ff5555"
- entity: sensor.nordpool_kwh_se3_sek_3_10_0
attribute: average
type: column
color: "#ffb86c"
float_precision: 2
stroke_width: 2
name: Idag Medel
show:
in_chart: false
in_header: false
legend_value: false
- entity: sensor.nordpool_kwh_se3_sek_3_10_0
attribute: max
type: column
float_precision: 2
stroke_width: 2
name: Högsta
show:
in_chart: false
in_header: true
legend_value: false
header_color_threshold: true
color_threshold:
- value: 0
color: "#8be9fd"
- value: 1
color: "#50fa7b"
- value: 1.5
color: "#8be978"
- value: 2
color: "#f8f872"
- value: 2.5
color: "#ffb86c"
- value: 3
color: "#ff9859"
- value: 3.5
color: "#ff7846"
- value: 4
color: "#ff5555"
- entity: sensor.nordpool_kwh_se3_sek_3_10_0
color: "#6272a4"
extend_to: false
name: Imorgon
unit: SEK/kWh
float_precision: 2
opacity: 0.5
stroke_width: 2
show:
extremas: true
in_header: false
legend_value: false
data_generator: |
return entity.attributes.tomorrow.map((price, index) => {
return [new Date().setHours(index/4,index%4*15,0), price];
});
1 Like
Saukko
(Saukko)
October 3, 2025, 7:30am
9
Hi all,
Thanks about adwising ohters. I have also question… first, I’m not a newbie, but I’ve made programs only true interface automation, not yaml-code directly. So, is there any way to get that 15min period nordpool energy price from that Nordpool Hacs, or do I have to make a code to the yaml side? And do You guys some ready simble code for yaml about that?
Kindly,
Aleksi S
It is in the first set of posts…and it is not (!) using hacs version, follow the link
Saukko
(Saukko)
October 3, 2025, 10:05am
11
Hi,
ok. I have tried, but not successed:(
But, am I understand correctly… if I just wait, the the Nordpool integration starts to spit 15 prices automatically in the future? (see picture).
If you use the non-hacs nordpool there is an action (fka service-call) that extracts the prices per 15min. , posts above show how to do this
DNN
October 4, 2025, 9:59am
13
Thanks for sharing this solution. I am using the core nordpool integration and have copied your second template version in my configuration.yaml but the sensor shows up as ‘Unknown’ and without attributes.
Does anyone have an idea what may be going wrong?
Thanks
PS Not sure if this is relevant, but I have separately setup a hub directly in the integration that fetches some data for the Netherlands, while your template of course is setup to fetch data for Sweden. I assumed that these are independent but am not sure. I have also setup the two input_numbers for tax and purchase costs per your specification.
EDIT: I failed to realise that I needed to enter my own config_entry code. Using the correct one fixed the issue.
xztraz
(Xztraz)
October 6, 2025, 8:23am
15
try copying the code again. i’ve changed some stuff. (remember to change the config entry and such)
the hacs version gave min and max and such too but i have not made that in this template yet.
also the data gets quite big when today and tomorrow prices are present so ha logs says 16k limit bladiblah… one solution is to derive the price sensor to a separate entity… i might have a look at that later. but try the code above until then.
xztraz
(Xztraz)
October 6, 2025, 9:35am
16
also. the nordpool integration seem to have a bug where it only gives 1h price for current price. tested with derived current price sensor but still 1h prices. so waiting for a bug fix for that. graphs and price automation works well thou.
xztraz
(Xztraz)
October 6, 2025, 3:01pm
17
updated and added some scripts and graph options.
1 Like
DNN
October 6, 2025, 6:51pm
18
Thanks a lot for sharing these scripts. I am so glad I do not have to figure this out myself!
I just wanted to note that for me (on core version 2025.10.1), the nordpool current price sensor updates every 15 minutes
Great stuff!
I have a question though. Somewhere along the line, you introduced the attribute tomorrow_valid, presumably to format the graph in case of absence of values for the next day. The check for this is if tomorrow_price is mapping. Currently, the tomorrow_price is an empty list and it still returns TRUE.
Could it be that the response of the action to get prices from Nord pool is an object like: { "NL": [] }? This is then a mapping in Jinja, so it returns TRUE.
I changed the code a bit:
{{ tomorrow_price is mapping }}
xztraz
(Xztraz)
October 7, 2025, 8:25am
20
allright. i have not upgradet yet. the min max and avg calculations might come in handy thou.
edit: upgraded and verified. yes the nordpol integration now gives 15 min data for state too. great! i’ll see if i can bake the min max and avg calc into the table template.
xztraz
(Xztraz)
October 7, 2025, 8:26am
21
great! i’ve taken help of ai to write/modify the code and then testing it a lot myself. this might have slipped thru my tests. thanks for checking the code. the attribute is just to mimic the original attributes from the hacs version. it might be unnecessary
xztraz
(Xztraz)
October 7, 2025, 9:52am
22
remade the checks in both the tomorrow valid part and table conversion to check for real data. seem to work. scripts and graph updated.
xztraz
(Xztraz)
October 7, 2025, 11:46am
23
this thread is for the built in nordpool integration. not hacs that you seem to use.
1 Like
Dan1jel
(Danijel)
October 7, 2025, 12:02pm
24
Oh i see, thanks, is there any big differences for them both? I tried built in before but it didnt work as good as the HACS version…