# Volatility Sensors :::tip Entity ID tip `` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. **Can't find a sensor?** Use the **[Entity Reference (All Languages)](sensor-reference.md)** to search by name in your language. ::: Volatility sensors help you understand how much electricity prices fluctuate over a given period. Instead of just looking at the absolute price, they measure the **relative price variation**, which is a great indicator of whether it's a good day for price-based energy optimization. The calculation is based on the **Coefficient of Variation (CV)**, a standardized statistical measure defined as: `CV = (Standard Deviation / Arithmetic Mean) * 100%` This results in a percentage that shows how much prices deviate from the average. A low CV means stable prices, while a high CV indicates significant price swings and thus, a high potential for saving money by shifting consumption. The sensor's state can be `low`, `moderate`, `high`, or `very_high`, based on configurable thresholds. ## Available Volatility Sensors | Sensor | Description | Time Window | | --------------------------------------------------------------------------------------- | ----------------------------------------- | ---------------------- | | Today's Price Volatility | Volatility for the current calendar day | 00:00 - 23:59 today | | Tomorrow's Price Volatility | Volatility for the next calendar day | 00:00 - 23:59 tomorrow | | **Next 24h Price Volatility** (`next_24h_volatility`) | Volatility for the next 24 hours from now | Rolling 24h forward | | Today + Tomorrow Price Volatility | Volatility across both today and tomorrow | Up to 48 hours | ## Configuration You can adjust the CV thresholds that determine the volatility level: 1. Go to **Settings → Devices & Services → Tibber Prices**. 2. Click **Configure**. 3. Go to the **Price Volatility Thresholds** step. Default thresholds are: - **Moderate:** 15% - **High:** 30% - **Very High:** 50% ## Key Attributes All volatility sensors provide these attributes: | Attribute | Description | Example | | ------------------------------- | ---------------------------------------------------------------- | ------------ | | `price_volatility` | Volatility level (language-independent, always English) | `"moderate"` | | `price_coefficient_variation_%` | The calculated Coefficient of Variation | `23.5` | | `price_spread` | The difference between the highest and lowest price | `12.3` | | `price_min` | The lowest price in the period | `10.2` | | `price_max` | The highest price in the period | `22.5` | | `price_mean` | The arithmetic mean of all prices in the period | `15.1` | | `price_median` | Median price (50th percentile, robust to outliers) | `14.8` | | `price_q25` | 25th percentile — lower quartile price | `11.0` | | `price_q75` | 75th percentile — upper quartile price | `19.5` | | `price_typical_spread` | Typical price band width — IQR (Q75 − Q25, the middle 50% of prices) | `8.5` | | `price_typical_spread_%` | Typical price band as a percentage of the median (IQR%) | `57.4` | | `price_spike_count` | Intervals outside the Tukey fence (Q25−1.5×IQR … Q75+1.5×IQR) — spikes/dips | `3` | | `interval_count` | Number of price intervals included in the calculation | `96` | ## Usage in Automations & Best Practices You can use the volatility sensor to decide if a price-based optimization is worth it. For example, if your solar battery has conversion losses, you might only want to charge and discharge it on days with high volatility. ### Best Practice: Use the `price_volatility` Attribute For automations, it is strongly recommended to use the `price_volatility` attribute instead of the sensor's main state. - **Why?** The main `state` of the sensor is translated into your Home Assistant language (e.g., "Hoch" in German). If you change your system language, automations based on this state will break. The `price_volatility` attribute is **always in lowercase English** (`"low"`, `"moderate"`, `"high"`, `"very_high"`) and therefore provides a stable, language-independent value. **Good Example (Robust Automation):** This automation triggers only if the volatility is classified as `high` or `very_high`, respecting your central settings and working independently of the system language.
Show YAML: Good Example (Robust Automation) ```yaml automation: - alias: "Enable battery optimization only on volatile days" trigger: - platform: template value_template: > {{ state_attr('sensor._today_s_price_volatility', 'price_volatility') in ['high', 'very_high'] }} action: - service: input_boolean.turn_on entity_id: input_boolean.battery_optimization_enabled ```
--- ### Avoid Hard-Coding Numeric Thresholds You might be tempted to use the numeric `price_coefficient_variation_%` attribute directly in your automations. This is not recommended. - **Why?** The integration provides central configuration options for the volatility thresholds. By using the classified `price_volatility` attribute, your automations automatically adapt if you decide to change what you consider "high" volatility (e.g., changing the threshold from 30% to 35%). Hard-coding values means you would have to find and update them in every single automation. **Bad Example (Brittle Automation):** This automation uses a hard-coded value. If you later change the "High" threshold in the integration's options to 35%, this automation will not respect that change and might trigger at the wrong time.
Show YAML: Bad Example (Brittle Automation) ```yaml automation: - alias: "Brittle - Enable battery optimization" trigger: # # BAD: Avoid hard-coding numeric values # - platform: numeric_state entity_id: sensor._today_s_price_volatility attribute: price_coefficient_variation_% above: 30 action: - service: input_boolean.turn_on entity_id: input_boolean.battery_optimization_enabled ```
By following the "Good Example", your automations become simpler, more readable, and much easier to maintain. ## Typical Price Band Statistics (IQR) In addition to the CV-based volatility level, every volatility sensor provides **typical price band statistics** as attributes. These are derived from the **IQR (Interquartile Range)** — the spread of the middle 50% of prices — making them more **robust to isolated price spikes** than the CV. | Metric | CV (state) | IQR attributes | | --------------------- | ----------------------------------------- | ---------------------------------- | | Sensitive to spikes? | ✅ Yes — spikes inflate CV | ❌ No — IQR ignores the outer 25% | | Use for optimization? | "Is today worth optimizing?" | "How wide is the core price band?" | | Best for | Triggering battery/EV charging strategies | Understanding price structure | The `price_typical_spread_%` attribute (IQR as a percentage of the median) tells you how wide the **core** price band is relative to the median. Even on a high-CV day with isolated spikes, a low `price_typical_spread_%` means most of the day has stable prices — only a few intervals are outliers. The `price_spike_count` attribute (Tukey fence method: Q25 − 1.5×IQR to Q75 + 1.5×IQR) tells you how many intervals fall outside the normal range. A high `price_spike_count` day with a high CV is a classic "spiky" day: mostly stable prices with a few expensive or cheap peaks. --- ## Price Rank Sensors (Percentile Rank) The price rank sensors answer the simple question: **"Is this price cheap or expensive compared to the rest of the day?"** Unlike the volatility sensors (which measure the _shape_ of the entire price distribution), price rank sensors place a _specific price_ within that distribution — technically its **percentile rank**. A value of **0% means cheapest interval of the reference set**, while a value near **99% means most expensive**. Each sensor ranks a different **subject price** against a **reference window**: - **Subject** — Which price is being ranked: current interval, next interval, previous interval, or the rolling hourly average - **Reference window** — Which pool of slots to compare against: today only, tomorrow only, or today+tomorrow combined ### How It Works (Percentile Rank Formula) ``` Price rank (percentile rank) = (number of intervals strictly cheaper than subject) ÷ total intervals × 100 ``` The cheapest interval always returns 0% — you can use `state == 0` to detect the absolute cheapest moment. :::note 100% is never reached By design, **the most expensive interval of the day will never show 100%**. The formula counts how many intervals are *strictly cheaper* than the subject price. For the daily maximum, every other interval is cheaper — but the interval itself is not counted. With 96 quarter-hour intervals per day, the maximum value is 95 ÷ 96 × 100 = **99.0%**. This means: - `state == 0` → cheapest interval of the reference set ✅ - `state == 100` → **never true** ❌ - `state >= 99` → most expensive interval of the day ✅ (use this instead) ::: ### Available Sensors **Current interval** (price of the active quarter-hour): | Sensor | Reference Set | Enabled by Default | | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------------------ | | Current Price Rank (Today) | Today's 96 quarter-hour intervals | ✅ Yes | | Current Price Rank (Tomorrow) | Tomorrow's 96 intervals (once avail.) | ❌ No | | Current Price Rank (Today+Tomorrow) | Combined pool (up to 192 intervals) | ❌ No | **Next interval** (price of the upcoming quarter-hour): | Sensor | Reference Set | Enabled by Default | | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------------------ | | Next Price Rank (Today) | Today's 96 quarter-hour intervals | ❌ No | | Next Price Rank (Today+Tomorrow) | Combined pool (up to 192 intervals) | ❌ No | **Previous interval** (price of the just-ended quarter-hour): | Sensor | Reference Set | Enabled by Default | | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------------------ | | Previous Price Rank (Today) | Today's 96 quarter-hour intervals | ❌ No | | Previous Price Rank (Today+Tomorrow) | Combined pool (up to 192 intervals) | ❌ No | **Rolling hourly average** (5-interval window, ~1 hour): | Sensor | Reference Set | Enabled by Default | | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------------------ | | ⌀ Hourly Price Current Rank (Today) | Today's 96 quarter-hour intervals | ❌ No | | ⌀ Hourly Price Current Rank (Today+Tomorrow) | Combined pool (up to 192 intervals) | ❌ No | | ⌀ Hourly Price Next Rank (Today) | Today's 96 quarter-hour intervals | ❌ No | | ⌀ Hourly Price Next Rank (Today+Tomorrow) | Combined pool (up to 192 intervals) | ❌ No | ### Key Attributes All price rank sensors share most of these attributes. The price attribute key reflects the subject: | Attribute | Description | Subject | | ------------------------ | -------------------------------------------------------- | ------------------ | | `current_price` | The price being ranked (current interval) | Current interval | | `next_price` | The price being ranked (next interval) | Next interval | | `previous_price` | The price being ranked (previous interval) | Previous interval | | `current_hour_avg_price` | The rolling average being ranked (current hour) | Current hour avg | | `next_hour_avg_price` | The rolling average being ranked (next hour) | Next hour avg | | `prices_below_count` | How many reference intervals are strictly cheaper | All sensors | | `interval_count` | Total intervals in the reference set | All sensors | | `reference_min` | The cheapest price in the reference set | All sensors | | `reference_max` | The most expensive price in the reference set | All sensors | | `reference_mean` | Average price of the reference set | All sensors | ### When to Use Which Sensor - **Current (Today)** — Same-day scheduling. "Is the active quarter-hour within the cheapest 25% of today?" - **Next (Today)** — Prepare for the next interval. "Should I pre-heat now so the device runs in the coming cheap slot?" - **Current (Today+Tomorrow)** — Broadest view for flexible tasks. "Is this among the cheapest moments of a 48-hour window?" - **Current (Tomorrow)** — Decide whether to wait until tomorrow. "Is today's price worse than what tomorrow offers?" - **⌀ Hourly Current (Today)** — For tasks that take about an hour. "Is this hour cheap enough to start a 60-minute cycle?" - **⌀ Hourly Next (Today)** — One-hour look-ahead. "Will the upcoming hour be cheap enough to start now?" ### Usage in Automations
Show YAML: Start dishwasher in bottom quartile ```yaml automation: - alias: "Start dishwasher at cheapest time of day" trigger: - platform: numeric_state entity_id: sensor._current_price_rank_today below: 25 condition: - condition: state entity_id: binary_sensor._best_price_period state: "on" action: - service: switch.turn_on entity_id: switch.dishwasher ```
Show YAML: Postpone task if tomorrow is cheaper ```yaml automation: - alias: "Skip charging tonight if tomorrow is cheaper" trigger: - platform: time at: "21:00:00" condition: # Only postpone if tomorrow's cheapest quartile is better than the current price - condition: template value_template: > {{ states('sensor._current_price_rank_tomorrow') | float(100) < 25 }} action: - service: input_boolean.turn_off entity_id: input_boolean.ev_charge_tonight ```
Show YAML: Avoid running devices at the most expensive time of day ```yaml automation: - alias: "Pause non-essential devices at peak price" trigger: - platform: numeric_state entity_id: sensor._current_price_rank_today above: 99 # 100 is never reached — use >= 99 to catch the daily maximum action: - service: switch.turn_off entity_id: switch.dishwasher ```
Show YAML: Pre-heat when the next interval is cheap ```yaml automation: - alias: "Pre-heat if next interval is top quartile cheapest" trigger: - platform: time_pattern minutes: "/15" condition: - condition: numeric_state entity_id: sensor._next_price_rank_today below: 25 action: - service: climate.set_hvac_mode entity_id: climate.living_room data: hvac_mode: heat ```