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
+```
+
+