From a957334990f1f484d9e04220959f0b051bdb6ade Mon Sep 17 00:00:00 2001 From: Julian Pawlowski Date: Sun, 12 Apr 2026 14:14:31 +0000 Subject: [PATCH] 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. --- docs/user/docs/sensor-reference.md | 3 + docs/user/docs/sensors-volatility.md | 181 +++++++++++++++++++++------ 2 files changed, 149 insertions(+), 35 deletions(-) diff --git a/docs/user/docs/sensor-reference.md b/docs/user/docs/sensor-reference.md index 85a4e6c..c85fb84 100644 --- a/docs/user/docs/sensor-reference.md +++ b/docs/user/docs/sensor-reference.md @@ -217,6 +217,9 @@ explanations of each sensor's purpose, attributes, and automation examples. | `day_pattern_today` | Today's Price Pattern | Preismuster Heute | Prismønster i dag | Prijspatroon Vandaag | Prismönster Idag | ✅ | | `day_pattern_tomorrow` | Tomorrow's Price Pattern | Preismuster Morgen | Prismønster i morgen | Prijspatroon Morgen | Prismönster Imorgon | ❌ | | `day_pattern_yesterday` | Yesterday's Price Pattern | Preismuster Gestern | Prismønster i går | Prijspatroon Gisteren | Prismönster Igår | ❌ | +| `price_rank_today` | Today's Price Rank | Preisrang heute | Prisrang i dag | Prijsrang vandaag | Prisrang idag | ✅ | +| `price_rank_today_tomorrow` | Today+Tomorrow Price Rank | Preisrang heute+morgen | Prisrang i dag+i morgen | Prijsrang vandaag+morgen | Prisrang idag+imorgon | ❌ | +| `price_rank_tomorrow` | Tomorrow's Price Rank | Preisrang morgen | Prisrang i morgen | Prijsrang morgen | Prisrang imorgon | ❌ | ## Binary Sensors ### Binary Sensors diff --git a/docs/user/docs/sensors-volatility.md b/docs/user/docs/sensors-volatility.md index b18c723..0b0f9e5 100644 --- a/docs/user/docs/sensors-volatility.md +++ b/docs/user/docs/sensors-volatility.md @@ -16,21 +16,23 @@ The sensor's state can be `low`, `moderate`, `high`, or `very_high`, based on co ## 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 | +| 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% @@ -39,15 +41,21 @@ Default thresholds are: 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` | -| `interval_count` | Number of price intervals included in the calculation | `96` | +| 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 @@ -61,19 +69,20 @@ For automations, it is strongly recommended to use the `price_volatility` attrib **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 + - 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 ```
@@ -88,25 +97,127 @@ You might be tempted to use the numeric `price_coefficient_variation_%` attribut **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 + - 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 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 | +| ------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------ | +| Today's Price Rank | All of today's 96 quarter-hour intervals | ✅ Yes | +| Tomorrow's Price Rank | All of tomorrow's 96 intervals (once available) | ❌ No | +| Today+Tomorrow Price Rank | 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 + +
+Show YAML: Start dishwasher in bottom quartile + +```yaml +automation: + - alias: "Start dishwasher at cheapest time of day" + trigger: + - platform: numeric_state + entity_id: sensor._today_s_price_rank + 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._tomorrow_s_price_rank') | float(100) < 25 }} + action: + - service: input_boolean.turn_off + entity_id: input_boolean.ev_charge_tonight +``` + +