I can tell you how I tackle a similar need, albeit not for energy use I do use this method for a number of things like when my vacuums run so I can choose to delay them.
I create a date-time helper for when that something is going to happen, let’s say it’s the vacuum. I have automation that runs at that particular time in the helper. I landed on this because my house announces when the vacuum is going to run (or other things, as mentioned) and I have voice commands like “delay vacuum” which will add 90 minutes to that time and then cancel the running automation. When the automation does fully run it then calculates when the next time will be based on a script I wrote:
##########################################
# CALCULATE NEXT VACUUM RUN DATETIME
##########################################
get_next_datetime:
alias: Get Next Date/Time Based on Rules
mode: single
fields:
days:
description: "Day numbers to generate date/time with 0 being monday. Dict format, full day name, proper case."
example: "['Saturday', 'Tuesday', 'Thursday']"
hour:
description: "Hour of day to use if not the current time on a new day. 24 hour format"
example: 8
minutes:
description: "Minutes of hour to use if not the current time on a new day."
example: 55
mindays:
description: "Minimum number of days until next datetime."
example: 0
sequence:
- variables:
datetime: >-
{% set ns = namespace(nextdate = now()) %}
{% set isnow = false %}
{% set istoday = false %}
{% set result = '' %}
{% set time = "{:02d}".format(hour) + ":" + "{:02d}".format(minutes) %}
{% if mindays == null %}
{% set mindays = 0 %}
{% endif %}
{% if mindays > 0 %}
{# Calculate starting mindays from now #}
{% set startdate = now() + timedelta(days=mindays) %}
{% for l in [0, 1, 2, 3, 4, 5, 6, 7] %}
{% set ns.nextdate = startdate + timedelta(days=l) %}
{% if ns.nextdate.strftime('%A') in days %}
{% break %}
{% endif %}
{% endfor %}
{% elif now().strftime('%A') in days and now().strftime('%H:%M') == time %}
{# The day and time match the pattern, do nothing #}
{% set isnow = true %}
{% elif ns.nextdate.hour <= hour and ns.nextdate.minute < minutes and ns.nextdate.strftime('%A') in days %}
{# The day is today but the time is earlier, set for today, do nothing #}
{% set istoday = true %}
{% else %}
{# Start calculation from now forward #}
{% for l in [1, 2, 3, 4, 5, 6, 7] %}
{% set ns.nextdate = now() + timedelta(days=l) %}
{% if ns.nextdate.strftime('%A') in days %}
{% break %}
{% endif %}
{% endfor %}
{% endif %}
{{
{
'datetime': ns.nextdate.strftime('%Y-%m-%d ' + time + ':00'),
'day': ns.nextdate.strftime('%A'),
'time': time,
'isnow': isnow,
'istoday': istoday
}
}}
- stop:
response_variable: datetime
Which sets the next schedule time. In addition to vacuums I use this for restarting HA periodically, when to turn on/off my drive towers on my massive Plex system and more.
The idea is that this returns a JSON payload of values that the calling function or automation can read and parse to know what to set the date-time helper for (or if to run at all). I’m sure this could be refined, it was a quick and dirty solution for me and I wasn’t paying much attention to optimizing the Jinja2 code, just getting the job done.
While this may not be precisely what you are needing, it might give you some ideas how you could tackle this. For instance you could have it set for tomorrow at 9:00am, but between now and then perhaps you get a better price, it then changes it to that time.