Tibber future (tomorrow) prices statistics

I’d like to share with the community the new template sensor I’m working on in order to have some statistics from Tibber future electricity prices (and it can easily be adapted to other similar sensors, aka NordPool).

Why am I working on this?
I’m not there yet, but my goal is to find the best time to run appliances based on the known future prices of electricity. Let say my dishwasher takes 0.25kWh in the eco mode (3 hours + 1 drying, where the power consumption is irrelevant) and 0.45kWh in the express mode (1 hour). I’d like to create a Blueprint to find the best program to run and run it at the right time.
But this is to the near future… I still not there yet (and any inspiration will be very welcome).

Dependences:
You have to create a rest sensor based on this example from @p1ranha:

What is currently available?
For now, I have created a sensor to give me some statistics from the available future price info (the rest of today and tomorrow, when available).

With the current capabilities, the sensor is returning the status of the sensor.tibber_prices and adding to that the following attributes:

  • future_prices: A list with times and prices, filtered only to the prices in the future (the past prices are removed).
  • current_price: The same as your Tibber’s sensor current price, but extracted from the list, so there is a possibility that rounding makes a bit of a difference if you compare both.
  • min: The lowest price between all the future information available.
  • max: The highest price between all the future information available.
  • average: The average price between all the future information available.
  • current_price_level: The price level as defined by Tibber (please see table bellow), but instead of using the past 3 days of data to calculate the average, it will use the future prices for the average and compare the current price against that.
  • min_3h, min_4h, min_6h, min_8h, min_12h, min_16h, min_24h, max_3h, max_4h, …, average_24h, …, current_price_level_24h: the same as above, but instead of using all the future prices on the average calculation, it will use only the time interval (3h, 4h, 6h, 8h, 12h, 16h or 24h) indicated.

These are the price levels as defined by Tibber (copied from this post from @Naesstrom, but also available on Tibber’s api documentation):

There’s more to come, but please test what is currently available, if you can, and provide some feedback.

template:
  - sensor:
    - name: Tibber future statistics
      state: "{{ states('sensor.tibber_prices') }}"
      attributes:
        all_prices: "{{ (state_attr('sensor.tibber_prices', 'today') + state_attr('sensor.tibber_prices', 'tomorrow')) }}"
        future_prices: "{{ state_attr('sensor.tibber_future_statistics', 'all_prices') | selectattr('startsAt', 'gt', (now() - timedelta(hours=1)) | string | replace(' ','T')) | list }}"
        current_price: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', now() | string | replace(' ','T')) | map(attribute='total') | first | float(0) }} "
        min: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | map(attribute='total') | min | float(0) }}"
        max: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | map(attribute='total') | max | float(0) }}"
        average:  "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | map(attribute='total') | average | float(0) }}"
        current_price_level: >-
          {% set price_cur = state_attr('sensor.tibber_future_statistics', 'current_price') | float(0) %}
          {% set price_avg = state_attr('sensor.tibber_future_statistics', 'average') | float(0) %}
          {% if price_cur == 0 or price_avg == 0 %}
            unknown
          {% else %}
            {% set price_ratio = (price_cur / price_avg) %}
            {% if price_ratio >= 1.4 %}
              VERY_EXPENSIVE
            {% elif price_ratio >= 1.15 %}
              EXPENSIVE
            {% elif price_ratio <= 0.6 %}
              VERY_CHEAP
            {% elif price_ratio <= 0.9 %}
              CHEAP
            {% else %}
              NORMAL
            {% endif %}
          {% endif %}
        min_3h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=2)) | string | replace(' ','T')) | map(attribute='total') | min | float(0) }}"
        max_3h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=2)) | string | replace(' ','T')) | map(attribute='total') | max | float(0) }}"
        avg_3h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=2)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) }}"
        current_price_level_3h: >-
          {% set price_cur = state_attr('sensor.tibber_future_statistics', 'current_price') | float(0) %}
          {% set price_avg = state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=2)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) %}
          {% if price_cur == 0 or price_avg == 0 %}
            unknown
          {% else %}
            {% set price_ratio = (price_cur / price_avg) %}
            {% if price_ratio >= 1.4 %}
              VERY_EXPENSIVE
            {% elif price_ratio >= 1.15 %}
              EXPENSIVE
            {% elif price_ratio <= 0.6 %}
              VERY_CHEAP
            {% elif price_ratio <= 0.9 %}
              CHEAP
            {% else %}
              NORMAL
            {% endif %}
          {% endif %}
        min_4h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=3)) | string | replace(' ','T')) | map(attribute='total') | min | float(0) }}"
        max_4h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=3)) | string | replace(' ','T')) | map(attribute='total') | max | float(0) }}"
        avg_4h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=3)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) }}"
        current_price_level_4h: >-
          {% set price_cur = state_attr('sensor.tibber_future_statistics', 'current_price') | float(0) %}
          {% set price_avg = state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=3)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) %}
          {% if price_cur == 0 or price_avg == 0 %}
            unknown
          {% else %}
            {% set price_ratio = (price_cur / price_avg) %}
            {% if price_ratio >= 1.4 %}
              VERY_EXPENSIVE
            {% elif price_ratio >= 1.15 %}
              EXPENSIVE
            {% elif price_ratio <= 0.6 %}
              VERY_CHEAP
            {% elif price_ratio <= 0.9 %}
              CHEAP
            {% else %}
              NORMAL
            {% endif %}
          {% endif %}
        min_6h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=5)) | string | replace(' ','T')) | map(attribute='total') | min | float(0) }}"
        max_6h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=5)) | string | replace(' ','T')) | map(attribute='total') | max | float(0) }}"
        avg_6h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=5)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) }}"
        current_price_level_6h: >-
          {% set price_cur = state_attr('sensor.tibber_future_statistics', 'current_price') | float(0) %}
          {% set price_avg = state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=5)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) %}
          {% if price_cur == 0 or price_avg == 0 %}
            unknown
          {% else %}
            {% set price_ratio = (price_cur / price_avg) %}
            {% if price_ratio >= 1.4 %}
              VERY_EXPENSIVE
            {% elif price_ratio >= 1.15 %}
              EXPENSIVE
            {% elif price_ratio <= 0.6 %}
              VERY_CHEAP
            {% elif price_ratio <= 0.9 %}
              CHEAP
            {% else %}
              NORMAL
            {% endif %}
          {% endif %}
        min_8h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=7)) | string | replace(' ','T')) | map(attribute='total') | min | float(0) }}"
        max_8h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=7)) | string | replace(' ','T')) | map(attribute='total') | max | float(0) }}"
        avg_8h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=7)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) }}"
        current_price_level_8h: >-
          {% set price_cur = state_attr('sensor.tibber_future_statistics', 'current_price') | float(0) %}
          {% set price_avg = state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=7)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) %}
          {% if price_cur == 0 or price_avg == 0 %}
            unknown
          {% else %}
            {% set price_ratio = (price_cur / price_avg) %}
            {% if price_ratio >= 1.4 %}
              VERY_EXPENSIVE
            {% elif price_ratio >= 1.15 %}
              EXPENSIVE
            {% elif price_ratio <= 0.6 %}
              VERY_CHEAP
            {% elif price_ratio <= 0.9 %}
              CHEAP
            {% else %}
              NORMAL
            {% endif %}
          {% endif %}
        min_12h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=11)) | string | replace(' ','T')) | map(attribute='total') | min | float(0) }}"
        max_12h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=11)) | string | replace(' ','T')) | map(attribute='total') | max | float(0) }}"
        avg_12h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=11)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) }}"
        current_price_level_12h: >-
          {% set price_cur = state_attr('sensor.tibber_future_statistics', 'current_price') | float(0) %}
          {% set price_avg = state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=11)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) %}
          {% if price_cur == 0 or price_avg == 0 %}
            unknown
          {% else %}
            {% set price_ratio = (price_cur / price_avg) %}
            {% if price_ratio >= 1.4 %}
              VERY_EXPENSIVE
            {% elif price_ratio >= 1.15 %}
              EXPENSIVE
            {% elif price_ratio <= 0.6 %}
              VERY_CHEAP
            {% elif price_ratio <= 0.9 %}
              CHEAP
            {% else %}
              NORMAL
            {% endif %}
          {% endif %}
        min_16h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=15)) | string | replace(' ','T')) | map(attribute='total') | min | float(0) }}"
        max_16h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=15)) | string | replace(' ','T')) | map(attribute='total') | max | float(0) }}"
        avg_16h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=15)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) }}"
        current_price_level_16h: >-
          {% set price_cur = state_attr('sensor.tibber_future_statistics', 'current_price') | float(0) %}
          {% set price_avg = state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=15)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) %}
          {% if price_cur == 0 or price_avg == 0 %}
            unknown
          {% else %}
            {% set price_ratio = (price_cur / price_avg) %}
            {% if price_ratio >= 1.4 %}
              VERY_EXPENSIVE
            {% elif price_ratio >= 1.15 %}
              EXPENSIVE
            {% elif price_ratio <= 0.6 %}
              VERY_CHEAP
            {% elif price_ratio <= 0.9 %}
              CHEAP
            {% else %}
              NORMAL
            {% endif %}
          {% endif %}
        min_24h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=23)) | string | replace(' ','T')) | map(attribute='total') | min | float(0) }}"
        max_24h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=23)) | string | replace(' ','T')) | map(attribute='total') | max | float(0) }}"
        avg_24h: "{{ state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=23)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) }}"
        current_price_level_24h: >-
          {% set price_cur = state_attr('sensor.tibber_future_statistics', 'current_price') | float(0) %}
          {% set price_avg = state_attr('sensor.tibber_future_statistics', 'future_prices') | selectattr('startsAt', 'lt', (now() + timedelta(hours=23)) | string | replace(' ','T')) | map(attribute='total') | average | float(0) %}
          {% if price_cur == 0 or price_avg == 0 %}
            unknown
          {% else %}
            {% set price_ratio = (price_cur / price_avg) %}
            {% if price_ratio >= 1.4 %}
              VERY_EXPENSIVE
            {% elif price_ratio >= 1.15 %}
              EXPENSIVE
            {% elif price_ratio <= 0.6 %}
              VERY_CHEAP
            {% elif price_ratio <= 0.9 %}
              CHEAP
            {% else %}
              NORMAL
            {% endif %}
          {% endif %}
15 Likes

This is great. I will test this and give you some feedback when I can.

1 Like

Thanks for the template, works great.
Just a thought, today around noon, before the next days prices are available, the calculations for 12, 16, 24 h are not really valid. Maybe the different time ranges should be split up in separate sensors and evaluate to unknown/unavailable when there is not enough prices available.

min_8h: 0.2323
max_8h: 0.5809
avg_8h: 0.3795875
current_price_level_8h: NORMAL
min_12h: 0.0933
max_12h: 0.5809
avg_12h: 0.30754545454545457
current_price_level_12h: EXPENSIVE
min_16h: 0.0933
max_16h: 0.5809
avg_16h: 0.30754545454545457
current_price_level_16h: EXPENSIVE
min_24h: 0.0933
max_24h: 0.5809
avg_24h: 0.30754545454545457
current_price_level_24h: EXPENSIVE
friendly_name: Tibber future statistics

Anyway, personally I will probably try to modify it to show average prices for time windows instead. Not really interested in average price for the next 16 h but each 4 h range in the upcoming 16 h would be more relevant in my case.

I also want more the price for each 4h range in the future… I will find some time to work on this and come back here.

3 Likes

Is this the same as you posted here?

I was able to get the other template to work but not the one in this thread.

Hey Edward. That is great work.
I have used it, but the units in the Y axis are not fine enough. It would be great if you could set a resolution of 0.001 instead of 0.1. How should I do this?
Bild

@sfre Looks like you are using apexchart-card. To get more visual precision you are looking for following config options:

for the y-axis:

decimals: 3

for the entities:

float_precision: 3

Think this will get you there.

1 Like

This is a great template thanks!

I was wondering: I’d like to determine which window of 4 hours (my car takes 4 hours to charge) has the lowest average price the upcoming 48 hours (or whatever data is available).

Any thoughts on how to get such a calculation in a template as well?

Think I solved my own question using chat gpt :slight_smile:

{% set hourly_prices = states.sensor.tibber_future_statistics.attributes.future_prices %}
{% set no = namespace(min_avg = None) %}
{% set st = namespace(min_start = 0) %}
{% set period_length = 4 %}
{% for i in range(hourly_prices|length - period_length + 1) %}
  {% set ns = namespace(sum = 0.000) %}
  {% for j in range(period_length) %}
    {% set ns.sum = ns.sum + hourly_prices[i + j].total %}
  {% endfor %}
  {% set avg = ns.sum / period_length %}
  {% if no.min_avg is none or avg < no.min_avg %}
      {% set no.min_avg = avg %}
      {% set st.min_start = i %}
  {% endif %}
{% endfor %}
{% set start_period = hourly_prices[st.min_start].startsAt %}
{{ start_period }}
{{ no.min_avg }}
{% set end_period = hourly_prices[st.min_start + period_length - 1].startsAt %}
1 Like

Look at the cheapest energy hours macro. This can do that for you in just two lines of jinja.

Installing that through HACS shows “repo not found” errors.

You probably need to enable experimental features in HACS to use it to download custom templates. (it isn’t an integration). All details should be mentioned in the readme from the github.