docs(sensors): document price rank sensors and IQR volatility band attributes

Update sensors-volatility.md to cover the three new price rank sensors and the
IQR-based volatility attributes (typical price band / price spike count).
Section headers include technical terms in parentheses for experts:
"Typical Price Band Statistics (IQR)" and "Price Rank Sensors (Percentile Rank)".
Attribute tables list Tukey fence formulas and plain-language explanations
side-by-side.

Regenerate sensor-reference.md to include price_rank_today,
price_rank_tomorrow, and price_rank_today_tomorrow with translations for all
five supported languages.

Impact: Users have full documentation for the new sensors including examples,
formulas, and a multi-language lookup table.
This commit is contained in:
Julian Pawlowski 2026-04-12 14:14:31 +00:00
parent 0ca52f8d3c
commit a957334990
2 changed files with 149 additions and 35 deletions

View file

@ -217,6 +217,9 @@ explanations of each sensor's purpose, attributes, and automation examples.
| <span id="ref-day_pattern_today" class="entity-anchor"></span>`day_pattern_today` | Today's Price Pattern | Preismuster Heute | Prismønster i dag | Prijspatroon Vandaag | Prismönster Idag | ✅ | | <span id="ref-day_pattern_today" class="entity-anchor"></span>`day_pattern_today` | Today's Price Pattern | Preismuster Heute | Prismønster i dag | Prijspatroon Vandaag | Prismönster Idag | ✅ |
| <span id="ref-day_pattern_tomorrow" class="entity-anchor"></span>`day_pattern_tomorrow` | Tomorrow's Price Pattern | Preismuster Morgen | Prismønster i morgen | Prijspatroon Morgen | Prismönster Imorgon | ❌ | | <span id="ref-day_pattern_tomorrow" class="entity-anchor"></span>`day_pattern_tomorrow` | Tomorrow's Price Pattern | Preismuster Morgen | Prismønster i morgen | Prijspatroon Morgen | Prismönster Imorgon | ❌ |
| <span id="ref-day_pattern_yesterday" class="entity-anchor"></span>`day_pattern_yesterday` | Yesterday's Price Pattern | Preismuster Gestern | Prismønster i går | Prijspatroon Gisteren | Prismönster Igår | ❌ | | <span id="ref-day_pattern_yesterday" class="entity-anchor"></span>`day_pattern_yesterday` | Yesterday's Price Pattern | Preismuster Gestern | Prismønster i går | Prijspatroon Gisteren | Prismönster Igår | ❌ |
| <span id="ref-price_rank_today" class="entity-anchor" data-refs="sensors-volatility#available-sensors"></span>`price_rank_today` | Today's Price Rank | Preisrang heute | Prisrang i dag | Prijsrang vandaag | Prisrang idag | ✅ |
| <span id="ref-price_rank_today_tomorrow" class="entity-anchor" data-refs="sensors-volatility#available-sensors"></span>`price_rank_today_tomorrow` | Today+Tomorrow Price Rank | Preisrang heute+morgen | Prisrang i dag+i morgen | Prijsrang vandaag+morgen | Prisrang idag+imorgon | ❌ |
| <span id="ref-price_rank_tomorrow" class="entity-anchor" data-refs="sensors-volatility#available-sensors"></span>`price_rank_tomorrow` | Tomorrow's Price Rank | Preisrang morgen | Prisrang i morgen | Prijsrang morgen | Prisrang imorgon | ❌ |
## Binary Sensors ## Binary Sensors
### Binary Sensors ### Binary Sensors

View file

@ -16,21 +16,23 @@ The sensor's state can be `low`, `moderate`, `high`, or `very_high`, based on co
## Available Volatility Sensors ## Available Volatility Sensors
| Sensor | Description | Time Window | | Sensor | Description | Time Window |
|---|---|---| | --------------------------------------------------------------------------------------- | ----------------------------------------- | ---------------------- |
| <EntityRef id="today_volatility">Today's Price Volatility</EntityRef> | Volatility for the current calendar day | 00:00 - 23:59 today | | <EntityRef id="today_volatility">Today's Price Volatility</EntityRef> | Volatility for the current calendar day | 00:00 - 23:59 today |
| <EntityRef id="tomorrow_volatility">Tomorrow's Price Volatility</EntityRef> | Volatility for the next calendar day | 00:00 - 23:59 tomorrow | | <EntityRef id="tomorrow_volatility">Tomorrow's Price Volatility</EntityRef> | 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 | | **Next 24h Price Volatility** (`next_24h_volatility`) | Volatility for the next 24 hours from now | Rolling 24h forward |
| <EntityRef id="today_tomorrow_volatility">Today + Tomorrow Price Volatility</EntityRef> | Volatility across both today and tomorrow | Up to 48 hours | | <EntityRef id="today_tomorrow_volatility">Today + Tomorrow Price Volatility</EntityRef> | Volatility across both today and tomorrow | Up to 48 hours |
## Configuration ## Configuration
You can adjust the CV thresholds that determine the volatility level: You can adjust the CV thresholds that determine the volatility level:
1. Go to **Settings → Devices & Services → Tibber Prices**. 1. Go to **Settings → Devices & Services → Tibber Prices**.
2. Click **Configure**. 2. Click **Configure**.
3. Go to the **Price Volatility Thresholds** step. 3. Go to the **Price Volatility Thresholds** step.
Default thresholds are: Default thresholds are:
- **Moderate:** 15% - **Moderate:** 15%
- **High:** 30% - **High:** 30%
- **Very High:** 50% - **Very High:** 50%
@ -39,15 +41,21 @@ Default thresholds are:
All volatility sensors provide these attributes: All volatility sensors provide these attributes:
| Attribute | Description | Example | | Attribute | Description | Example |
|---|---|---| | ------------------------------- | ---------------------------------------------------------------- | ------------ |
| `price_volatility` | Volatility level (language-independent, always English) | `"moderate"` | | `price_volatility` | Volatility level (language-independent, always English) | `"moderate"` |
| `price_coefficient_variation_%` | The calculated Coefficient of Variation | `23.5` | | `price_coefficient_variation_%` | The calculated Coefficient of Variation | `23.5` |
| `price_spread` | The difference between the highest and lowest price | `12.3` | | `price_spread` | The difference between the highest and lowest price | `12.3` |
| `price_min` | The lowest price in the period | `10.2` | | `price_min` | The lowest price in the period | `10.2` |
| `price_max` | The highest price in the period | `22.5` | | `price_max` | The highest price in the period | `22.5` |
| `price_mean` | The arithmetic mean of all prices in the period | `15.1` | | `price_mean` | The arithmetic mean of all prices in the period | `15.1` |
| `interval_count` | Number of price intervals included in the calculation | `96` | | `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 (Q251.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 ## Usage in Automations & Best Practices
@ -61,19 +69,20 @@ For automations, it is strongly recommended to use the `price_volatility` attrib
**Good Example (Robust Automation):** **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. 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.
<details> <details>
<summary>Show YAML: Good Example (Robust Automation)</summary> <summary>Show YAML: Good Example (Robust Automation)</summary>
```yaml ```yaml
automation: automation:
- alias: "Enable battery optimization only on volatile days" - alias: "Enable battery optimization only on volatile days"
trigger: trigger:
- platform: template - platform: template
value_template: > value_template: >
{{ state_attr('sensor.<home_name>_today_s_price_volatility', 'price_volatility') in ['high', 'very_high'] }} {{ state_attr('sensor.<home_name>_today_s_price_volatility', 'price_volatility') in ['high', 'very_high'] }}
action: action:
- service: input_boolean.turn_on - service: input_boolean.turn_on
entity_id: input_boolean.battery_optimization_enabled entity_id: input_boolean.battery_optimization_enabled
``` ```
</details> </details>
@ -88,25 +97,127 @@ You might be tempted to use the numeric `price_coefficient_variation_%` attribut
**Bad Example (Brittle 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. 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.
<details> <details>
<summary>Show YAML: Bad Example (Brittle Automation)</summary> <summary>Show YAML: Bad Example (Brittle Automation)</summary>
```yaml ```yaml
automation: automation:
- alias: "Brittle - Enable battery optimization" - alias: "Brittle - Enable battery optimization"
trigger: trigger:
# #
# BAD: Avoid hard-coding numeric values # BAD: Avoid hard-coding numeric values
# #
- platform: numeric_state - platform: numeric_state
entity_id: sensor.<home_name>_today_s_price_volatility entity_id: sensor.<home_name>_today_s_price_volatility
attribute: price_coefficient_variation_% attribute: price_coefficient_variation_%
above: 30 above: 30
action: action:
- service: input_boolean.turn_on - service: input_boolean.turn_on
entity_id: input_boolean.battery_optimization_enabled entity_id: input_boolean.battery_optimization_enabled
``` ```
</details> </details>
By following the "Good Example", your automations become simpler, more readable, and much easier to maintain. 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 the current 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 the _current price_ within that distribution — technically its **percentile rank**. A value of **0% means cheapest interval of the day**, while a value near **99% means most expensive**.
### How It Works (Percentile Rank Formula)
```
Price rank (percentile rank) = (number of intervals strictly cheaper than now) ÷ total intervals × 100
```
The cheapest interval always returns 0% — you can use `state == 0` to detect the absolute cheapest moment.
### Available Sensors
| Sensor | Reference Set | Enabled by Default |
| ------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------ |
| <EntityRef id="price_rank_today">Today's Price Rank</EntityRef> | All of today's 96 quarter-hour intervals | ✅ Yes |
| <EntityRef id="price_rank_tomorrow">Tomorrow's Price Rank</EntityRef> | All of tomorrow's 96 intervals (once available) | ❌ No |
| <EntityRef id="price_rank_today_tomorrow">Today+Tomorrow Price Rank</EntityRef> | Combined pool (up to 192 intervals) | ❌ No |
### Key Attributes
All price rank sensors share these attributes:
| Attribute | Description | Example |
| -------------------- | --------------------------------------------- | ------- |
| `current_price` | The price being ranked | `14.2` |
| `prices_below_count` | How many intervals are strictly cheaper | `23` |
| `interval_count` | Total intervals in the reference set | `96` |
| `reference_min` | The cheapest price in the reference set | `8.1` |
| `reference_max` | The most expensive price in the reference set | `27.3` |
| `reference_mean` | Average price of the reference set | `15.8` |
### When to Use Which Sensor
- **`price_rank_today`** — For same-day scheduling. "Is now within the cheapest quarter of today? (< 25%)"
- **`price_rank_tomorrow`** — To compare today's price against what tomorrow offers. "Is it worth waiting until tomorrow?"
- **`price_rank_today_tomorrow`** — Broadest view for flexible tasks. "Is this among the cheapest moments of a 48-hour window?"
### Usage in Automations
<details>
<summary>Show YAML: Start dishwasher in bottom quartile</summary>
```yaml
automation:
- alias: "Start dishwasher at cheapest time of day"
trigger:
- platform: numeric_state
entity_id: sensor.<home_name>_today_s_price_rank
below: 25
condition:
- condition: state
entity_id: binary_sensor.<home_name>_best_price_period
state: "on"
action:
- service: switch.turn_on
entity_id: switch.dishwasher
```
</details>
<details>
<summary>Show YAML: Postpone task if tomorrow is cheaper</summary>
```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.<home_name>_tomorrow_s_price_rank') | float(100) < 25 }}
action:
- service: input_boolean.turn_off
entity_id: input_boolean.ev_charge_tonight
```
</details>