Compare commits

..

6 commits

Author SHA1 Message Date
Julian Pawlowski
e1da4cfa89 chroe(dev): update Home Assistant version in bootstrap script to 2026.4.1
Some checks are pending
Deploy Docusaurus Documentation (Dual Sites) / Build and Deploy Documentation Sites (push) Waiting to run
Lint / Ruff (push) Waiting to run
Validate / Hassfest validation (push) Waiting to run
Validate / HACS validation (push) Waiting to run
2026-04-09 17:13:03 +00:00
Julian Pawlowski
50dc874274 feat(brand): add local brand images for HA brands proxy API
Added brand/ directory to custom_components/tibber_prices/ with all
8 supported PNG variants, generated from existing SVGs in images/:

- icon.png / dark_icon.png (256×256)
- icon@2x.png / dark_icon@2x.png (512×512)
- logo.png / dark_logo.png (500×128)
- logo@2x.png / dark_logo@2x.png (1000×256)

Local brand images automatically take priority over CDN images and
are served via the HA brands proxy API (/api/brands/integration/).
Silently ignored on HA < 2026.3, no changes to manifest.json needed.

Updated AGENTS.md to document the brand/ directory under "ALLOWED in root".

Impact: Integration icon and logo now display correctly in HA ≥ 2026.3
without requiring a separate submission to the HA brands repository.
2026-04-09 17:06:13 +00:00
Julian Pawlowski
124824a2ea test(sensors): update timer assignment tests for renamed/new trend sensors
test_trend_sensors_use_quarter_hour_timer():
  - Replaced price_trend_Xh keys with price_outlook_Xh
  - Added 7 price_trajectory_Xh keys to the assertion list
  - Updated docstring from "price trend" to "price outlook/trajectory"

Impact: Test suite passes with renamed and new sensor keys.
2026-04-09 16:09:14 +00:00
Julian Pawlowski
cbf5e1a3fe docs(sensors): rename trend→outlook, document price_trajectory sensors
Updated user documentation to reflect renamed and new sensors.

sensors.md:
  - Section renamed "Simple Trend Sensors" → "Price Outlook Sensors (1h–12h)"
  - All price_trend_Xh entity references → price_outlook_Xh
  - Callout box updated: explains that outlook sensors can mislead at a
    price minimum and recommends combining with trajectory sensors
  - New section "Price Trajectory Sensors (2h–12h)" added before
    "Current Price Trend":
    - Table showing which halves are compared per window
    - Callout box with the 4 outlook+trajectory combination patterns
      (falling+rising = AT the minimum, etc.)
    - Key attributes table (first_half_avg, second_half_avg, half_diff_%)
  - "Trend Sensors vs Average Sensors" → "Outlook & Trajectory Sensors vs
    Average Sensors"

icon-colors.md:
  - "Price trend sensors (e.g., price_trend_3h)" → "Price outlook sensors
    (e.g., price_outlook_3h)"
  - Example entity updated to sensor.<home_name>_price_outlook_3h

automation-examples.md:
  - All price_trend_1h/2h/3h/4h/6h references → price_outlook_Xh
  - current_price_trend and next_price_trend_change unchanged (correct names)

Impact: Documentation matches actual entity names. New trajectory section
helps users understand when to use outlook vs trajectory sensors together.
2026-04-09 16:09:04 +00:00
Julian Pawlowski
2b96ccc650 feat(translations): add price_outlook_Xh and price_trajectory_Xh strings
Renamed 8 price_trend_Xh entries to price_outlook_Xh and added 15 new
price_trajectory_Xh entries (2h–12h) in all 5 languages (de, en, nb, nl, sv).

translations/ (HA-native: name + 5 states per sensor):
  - EN: "Price Outlook (Xh)" / "Price Trajectory (Xh)"
  - DE: "Preisausblick (Xh)" / "Preisverlauf (Xh)"
  - NB: "Prisutblikk (Xt)" / "Prisforløp (Xt)"
  - NL: "Prijsvooruitzicht (Xu)" / "Prijstrajectorie (Xu)"
  - SV: "Prisöversikt (Xh)" / "Prisutveckling (Xh)"

custom_translations/ (description + long_description + usage_tips):
  - Outlook descriptions updated to explain window-average comparison
    semantics (not price direction)
  - Trajectory descriptions explain first-half vs second-half logic and
    the "outlook: falling + trajectory: rising = you're AT the minimum" pattern
  - Trajectory long_description and usage_tips in English for all languages;
    description field in native language

Impact: Entity display names update to reflect the corrected semantic meaning.
2026-04-09 16:08:54 +00:00
Julian Pawlowski
33f57ff077 feat(sensors)!: rename price_trend_Xh → price_outlook_Xh, add price_trajectory_Xh
Renamed 8 sensors to clarify what they actually measure, and added 7 new
sensors for a different (and often more useful) calculation.

--- WHY THE RENAME ---

The old name "price_trend_Xh" implied the sensor shows where prices are
heading. It doesn't — it compares CURRENT price vs the FUTURE WINDOW AVERAGE.
At a price minimum, it shows "strongly_falling" (because the cheap minimum
pulls the average below your current high price), which is the opposite of
intuitive. The name "price_outlook_Xh" correctly conveys: "is now cheaper
or more expensive than the next Nh on average?"

--- NEW: price_trajectory_Xh ---

These sensors compare FIRST HALF vs SECOND HALF of the window, revealing
actual price direction within the window:

  price_trajectory_2h: avg(hour 1) vs avg(hour 2)
  price_trajectory_3h: avg(first 1.5h) vs avg(second 1.5h)
  price_trajectory_4h: avg(first 2h) vs avg(second 2h)
  price_trajectory_5h: avg(first 2.5h) vs avg(second 2.5h)
  price_trajectory_6h: avg(first 3h) vs avg(second 3h)
  price_trajectory_8h: avg(first 4h) vs avg(second 4h)
  price_trajectory_12h: avg(first 6h) vs avg(second 6h)

The key use case: at a price minimum, price_outlook_Xh shows "strongly_falling"
but price_trajectory_Xh shows "rising" — correctly revealing the upcoming
reversal. "outlook: falling + trajectory: rising" = you're AT the minimum.

--- IMPLEMENTATION ---

sensor/calculators/trend.py:
  - get_price_outlook_value() (was: get_price_trend_value())
  - New: get_price_trajectory_value(*, hours: int)
  - New: _calculate_first_half_average(hours, next_interval_start)
  - New: get_trajectory_attributes() → first_half_avg, second_half_avg, half_diff_%
  - clear_trend_cache() also resets _trajectory_attributes

sensor/definitions.py:
  - 8 SensorEntityDescription entries: key/translation_key price_trend_Xh → price_outlook_Xh
  - New PRICE_TRAJECTORY_SENSORS tuple (2h–5h enabled by default, 6h/8h/12h disabled)

sensor/value_getters.py:
  - 8 lambda entries renamed
  - 7 new trajectory lambda entries added

sensor/attributes/trend.py:
  - startswith("price_trend_") → startswith("price_outlook_")
  - New elif branch routing price_trajectory_* to cached trajectory_attributes

sensor/core.py:
  - startswith checks updated for both prefix families
  - cached_data dict extended with "trajectory_attributes"

coordinator/constants.py:
  - TIME_SENSITIVE_ENTITY_KEYS: 8 renamed + 7 new trajectory keys added

config_flow_handlers/entity_check.py:
  - volatility + price_trend affected-entity lists: 8 renamed + 7 new

BREAKING CHANGE: Sensors price_trend_1h, price_trend_2h, price_trend_3h,
price_trend_4h, price_trend_5h, price_trend_6h, price_trend_8h,
price_trend_12h have been removed without a deprecation period.

Migration:
  Replace price_trend_Xh → price_outlook_Xh everywhere (automations,
  dashboards, templates). Behavior is identical — only the entity name
  changed. If you want to detect actual price direction within the window
  (e.g. "are prices rising or falling right now?"), use the new
  price_trajectory_Xh sensors instead.

Impact: Users must update automations and dashboards. Entity IDs change from
sensor.<home>_price_trend_Xh to sensor.<home>_price_outlook_Xh. New
price_trajectory_Xh sensors provide complementary direction information.
2026-04-09 16:08:42 +00:00
31 changed files with 1214 additions and 353 deletions

View file

@ -313,6 +313,7 @@ After successful refactoring:
- Platform modules: `__init__.py`, `sensor.py` (deprecated, now `sensor/`), `binary_sensor.py` (deprecated, now `binary_sensor/`), future platforms - Platform modules: `__init__.py`, `sensor.py` (deprecated, now `sensor/`), `binary_sensor.py` (deprecated, now `binary_sensor/`), future platforms
- Core integration files: `const.py`, `manifest.json`, `services.yaml`, `diagnostics.py`, `data.py` - Core integration files: `const.py`, `manifest.json`, `services.yaml`, `diagnostics.py`, `data.py`
- Translation directories: `translations/`, `custom_translations/` - Translation directories: `translations/`, `custom_translations/`
- Brand images: `brand/` (icon.png, dark_icon.png, logo.png, dark_logo.png + `@2x` variants) — served via HA brands proxy API (HA ≥ 2026.4), silently ignored on older versions
**❌ PROHIBITED in root:** **❌ PROHIBITED in root:**
- Utility modules (use `/utils/` package instead) - Utility modules (use `/utils/` package instead)

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -69,14 +69,21 @@ STEP_TO_SENSOR_KEYS: dict[str, list[str]] = {
# Also affects trend sensors (adaptive thresholds) # Also affects trend sensors (adaptive thresholds)
"current_price_trend", "current_price_trend",
"next_price_trend_change", "next_price_trend_change",
"price_trend_1h", "price_outlook_1h",
"price_trend_2h", "price_outlook_2h",
"price_trend_3h", "price_outlook_3h",
"price_trend_4h", "price_outlook_4h",
"price_trend_5h", "price_outlook_5h",
"price_trend_6h", "price_outlook_6h",
"price_trend_8h", "price_outlook_8h",
"price_trend_12h", "price_outlook_12h",
"price_trajectory_2h",
"price_trajectory_3h",
"price_trajectory_4h",
"price_trajectory_5h",
"price_trajectory_6h",
"price_trajectory_8h",
"price_trajectory_12h",
], ],
# Best Price settings affect best price binary sensor and timing sensors # Best Price settings affect best price binary sensor and timing sensors
"best_price": [ "best_price": [
@ -106,14 +113,21 @@ STEP_TO_SENSOR_KEYS: dict[str, list[str]] = {
"price_trend": [ "price_trend": [
"current_price_trend", "current_price_trend",
"next_price_trend_change", "next_price_trend_change",
"price_trend_1h", "price_outlook_1h",
"price_trend_2h", "price_outlook_2h",
"price_trend_3h", "price_outlook_3h",
"price_trend_4h", "price_outlook_4h",
"price_trend_5h", "price_outlook_5h",
"price_trend_6h", "price_outlook_6h",
"price_trend_8h", "price_outlook_8h",
"price_trend_12h", "price_outlook_12h",
"price_trajectory_2h",
"price_trajectory_3h",
"price_trajectory_4h",
"price_trajectory_5h",
"price_trajectory_6h",
"price_trajectory_8h",
"price_trajectory_12h",
], ],
} }

View file

@ -62,14 +62,22 @@ TIME_SENSITIVE_ENTITY_KEYS = frozenset(
"current_price_trend", "current_price_trend",
"next_price_trend_change", "next_price_trend_change",
# Price trend sensors # Price trend sensors
"price_trend_1h", "price_outlook_1h",
"price_trend_2h", "price_outlook_2h",
"price_trend_3h", "price_outlook_3h",
"price_trend_4h", "price_outlook_4h",
"price_trend_5h", "price_outlook_5h",
"price_trend_6h", "price_outlook_6h",
"price_trend_8h", "price_outlook_8h",
"price_trend_12h", "price_outlook_12h",
# Price trajectory sensors (first-half vs second-half window comparison)
"price_trajectory_2h",
"price_trajectory_3h",
"price_trajectory_4h",
"price_trajectory_5h",
"price_trajectory_6h",
"price_trajectory_8h",
"price_trajectory_12h",
# Trailing/leading 24h calculations (based on current interval) # Trailing/leading 24h calculations (based on current interval)
"trailing_price_average", "trailing_price_average",
"leading_price_average", "leading_price_average",

View file

@ -227,45 +227,80 @@
"long_description": "Zeigt den Durchschnittspreis für die nächsten 48 Intervalle (12 Stunden) beginnend ab dem nächsten 15-Minuten-Intervall.", "long_description": "Zeigt den Durchschnittspreis für die nächsten 48 Intervalle (12 Stunden) beginnend ab dem nächsten 15-Minuten-Intervall.",
"usage_tips": "Absolute Preisschwelle: Strategische Entscheidungen mit Preisobergrenzen. Fahre nur fort, wenn der 12h-Durchschnitt unter deinem maximal akzeptablen Preis liegt. Gut für verschiebbare Großlasten." "usage_tips": "Absolute Preisschwelle: Strategische Entscheidungen mit Preisobergrenzen. Fahre nur fort, wenn der 12h-Durchschnitt unter deinem maximal akzeptablen Preis liegt. Gut für verschiebbare Großlasten."
}, },
"price_trend_1h": { "price_outlook_1h": {
"description": "Preistrend für die nächste Stunde", "description": "Preisausblick für die nächste Stunde",
"long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten Stunde (4 Intervalle). Alle Trend-Sensoren (1h12h) haben dieselbe Basis: dein aktueller Preis — sie unterscheiden sich nur im Zeitfenster. Größere Fenster umfassen mehr Zukunftsstunden und glätten kurzfristige Spitzen. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).", "long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten Stunde (4 Intervalle). Alle Ausblick-Sensoren (1h12h) haben dieselbe Basis: dein aktueller Preis — sie unterscheiden sich nur im Zeitfenster. Größere Fenster umfassen mehr Zukunftsstunden und glätten kurzfristige Spitzen. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).",
"usage_tips": "Entscheidungshilfe: 'steigend' = JETZT HANDELN, dein aktueller Preis ist günstiger als die nächste Stunde. 'fallend' = WARTEN, günstigere Preise kommen. 'stabil' = Timing egal. Häufiges Missverständnis: 'steigend' bedeutet NICHT 'zu spät' — es heißt, jetzt ist gerade ein guter Preis! Funktioniert unabhängig vom absoluten Preisniveau." "usage_tips": "Entscheidungshilfe: 'steigend' = JETZT HANDELN, dein aktueller Preis ist günstiger als der Stunden-Durchschnitt. 'fallend' = WARTEN, der Fenster-Durchschnitt ist günstiger als jetzt. 'stabil' = Timing egal. Häufiges Missverständnis: 'steigend' bedeutet NICHT 'zu spät' — es heißt, jetzt ist gerade ein guter Preis! Funktioniert unabhängig vom absoluten Preisniveau."
}, },
"price_trend_2h": { "price_outlook_2h": {
"description": "Preistrend für die nächsten 2 Stunden", "description": "Preisausblick für die nächsten 2 Stunden",
"long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 2 Stunden (8 Intervalle) — das gesamte Zeitfenster ab jetzt, nicht nur der spätere Teil. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).", "long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 2 Stunden (8 Intervalle). 'steigend' = aktueller Preis liegt unter dem 2h-Fenster-Durchschnitt; 'fallend' = Fenster-Durchschnitt ist günstiger als jetzt. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).",
"usage_tips": "Haushaltsgeräte: 'steigend' = jetzt starten, du hast gerade einen guten Preis. 'fallend' = bessere Preise kommen in 2h, verschiebe wenn möglich. 'stabil' = egal, starte nach Bedarf. Nicht auf 'stabil' warten — bei 'steigend' ist JETZT der beste Zeitpunkt." "usage_tips": "Haushaltsgeräte: 'steigend' = jetzt starten, du hast gerade einen guten Preis. 'fallend' = bessere Preise kommen im 2h-Fenster, verschiebe wenn möglich. 'stabil' = egal, starte nach Bedarf. Kombiniere mit price_trajectory_2h um zu unterscheiden ob Preise noch fallen oder schon steigen."
}, },
"price_trend_3h": { "price_outlook_3h": {
"description": "Preistrend für die nächsten 3 Stunden", "description": "Preisausblick für die nächsten 3 Stunden",
"long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 3 Stunden (12 Intervalle) — das gesamte Zeitfenster ab jetzt, nicht nur der spätere Teil. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).", "long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 3 Stunden (12 Intervalle). 'steigend' = aktueller Preis liegt unter dem 3h-Fenster-Durchschnitt; 'fallend' = Fenster-Durchschnitt ist günstiger als jetzt. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).",
"usage_tips": "Eco-Programme: 'steigend' = Eco-Zyklus jetzt starten, Preise steigen ab hier. 'fallend' = warten, günstigeres Fenster kommt. Kombiniere mit avg-Sensor: starte wenn Trend 'steigend' oder 'stabil' UND avg < dein Limit. Funktioniert in jeder Saison." "usage_tips": "Eco-Programme: 'steigend' = Eco-Zyklus jetzt starten, Preise steigen im Fenster-Durchschnitt. 'fallend' = warten, günstigeres Fenster kommt. Kombiniere mit avg-Sensor: starte wenn Ausblick 'steigend' oder 'stabil' UND avg < dein Limit."
}, },
"price_trend_4h": { "price_outlook_4h": {
"description": "Preistrend für die nächsten 4 Stunden", "description": "Preisausblick für die nächsten 4 Stunden",
"long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 4 Stunden (16 Intervalle) — das gesamte Zeitfenster ab jetzt, nicht nur der spätere Teil. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).", "long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 4 Stunden (16 Intervalle). 'steigend' = aktueller Preis liegt unter dem 4h-Fenster-Durchschnitt; 'fallend' = Fenster-Durchschnitt ist günstiger als jetzt. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).",
"usage_tips": "Wärmepumpe/Batterie: 'steigend' = jetzt laden, du bist an einem relativen Tiefpunkt. 'fallend' = warten auf besseres Ladefenster. 'stabil' = laden nach Bedarf. Funktioniert unabhängig vom Preisniveau — findet relative beste Zeit ob 10 oder 50 Cent." "usage_tips": "Wärmepumpe/Batterie: 'steigend' = jetzt laden, du bist an einem relativen Tiefpunkt. 'fallend' = warten auf besseres Fenster im Durchschnitt. Wichtig: 'stark fallend' an einem Preistief bedeutet das Fenster ist im Schnitt günstiger — kombiniere mit price_trajectory_4h um zu sehen ob die Preise noch fallen oder schon aufsteigen."
}, },
"price_trend_5h": { "price_outlook_5h": {
"description": "Preistrend für die nächsten 5 Stunden", "description": "Preisausblick für die nächsten 5 Stunden",
"long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 5 Stunden (20 Intervalle) — das gesamte Zeitfenster ab jetzt, nicht nur der spätere Teil. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).", "long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 5 Stunden (20 Intervalle). 'steigend' = aktueller Preis liegt unter dem 5h-Fenster-Durchschnitt; 'fallend' = Fenster-Durchschnitt ist günstiger als jetzt. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).",
"usage_tips": "Erweiterte Zyklen: 'steigend' oder 'stabil' = guter Zeitpunkt zum Starten, Preise werden nicht günstiger. 'fallend' = warten wenn dein Zeitplan es erlaubt. Passt sich dem Markt an — findet bestes relatives Timing in jedem Preisumfeld." "usage_tips": "Erweiterte Zyklen: 'steigend' oder 'stabil' = guter Zeitpunkt zum Starten, Preise werden im Fenster-Schnitt nicht günstiger. 'fallend' = warten wenn dein Zeitplan es erlaubt."
}, },
"price_trend_6h": { "price_outlook_6h": {
"description": "Preistrend für die nächsten 6 Stunden", "description": "Preisausblick für die nächsten 6 Stunden",
"long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 6 Stunden (24 Intervalle) — das gesamte Zeitfenster ab jetzt, nicht nur der spätere Teil. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).", "long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 6 Stunden (24 Intervalle). 'steigend' = aktueller Preis liegt unter dem 6h-Fenster-Durchschnitt; 'fallend' = Fenster-Durchschnitt ist günstiger als jetzt. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).",
"usage_tips": "Abendentscheidungen: 'steigend' = Strom jetzt nutzen, solange er relativ günstig ist. 'fallend' = Abend-/Nachtpreise werden besser, warte wenn möglich. Passt sich automatisch an Winter/Sommer-Preisniveaus an — keine festen Schwellenwerte nötig." "usage_tips": "Abendentscheidungen: 'steigend' = Strom jetzt nutzen, solange er relativ günstig ist. 'fallend' = Abend-/Nachtpreise werden im Durchschnitt besser, warte wenn möglich."
}, },
"price_trend_8h": { "price_outlook_8h": {
"description": "Preistrend für die nächsten 8 Stunden", "description": "Preisausblick für die nächsten 8 Stunden",
"long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 8 Stunden (32 Intervalle) — das gesamte Zeitfenster ab jetzt, nicht nur der spätere Teil. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).", "long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 8 Stunden (32 Intervalle). 'steigend' = aktueller Preis liegt unter dem 8h-Fenster-Durchschnitt; 'fallend' = Fenster-Durchschnitt ist günstiger als jetzt. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).",
"usage_tips": "Nachtplanung: 'steigend' = heute Nacht/morgen wird teurer, Strom jetzt nutzen. 'fallend' = Nachtpreise werden günstiger, Warten lohnt sich. 'stabil' = starte nach Bedarf. Funktioniert ganzjährig ohne manuelle Schwellenwert-Anpassungen." "usage_tips": "Nachtplanung: 'steigend' = heute Nacht/morgen wird im Schnitt teurer, Strom jetzt nutzen. 'fallend' = Nachtpreise werden im Schnitt günstiger, Warten lohnt sich."
}, },
"price_trend_12h": { "price_outlook_12h": {
"description": "Preistrend für die nächsten 12 Stunden", "description": "Preisausblick für die nächsten 12 Stunden",
"long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 12 Stunden (48 Intervalle) — das gesamte Zeitfenster ab jetzt, nicht nur der spätere Teil. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).", "long_description": "Vergleicht deinen aktuellen Preis mit dem Durchschnitt aller Intervalle der nächsten 12 Stunden (48 Intervalle). 'steigend' = aktueller Preis liegt unter dem 12h-Fenster-Durchschnitt; 'fallend' = Fenster-Durchschnitt ist günstiger als jetzt. Steigend/fallend ab ±3%, stark ab ±9% (konfigurierbar, volatilitätsadaptiv).",
"usage_tips": "Strategische Entscheidungen: 'steigend' = du bist an einem Tiefpunkt, guter Zeitpunkt für stromintensive Aufgaben. 'fallend' = deutlich bessere Preise kommen, warte wenn möglich. Am besten kombiniert mit avg-Sensor für absolute Preisobergrenzen." "usage_tips": "Strategische Entscheidungen: 'steigend' = du bist an einem Tiefpunkt relativ zu den nächsten 12h, guter Zeitpunkt für stromintensive Aufgaben. 'fallend' = deutlich bessere Preise kommen im Schnitt, warte wenn möglich."
},
"price_trajectory_2h": {
"description": "Preisverlauf innerhalb des nächsten 2-Stunden-Fensters",
"long_description": "Compares the average of the first hour (4 intervals) with the average of the second hour (4 intervals) within the next 2-hour window. 'rising' = second half more expensive than first half — prices are climbing within the window. 'falling' = second half cheaper — prices are dropping within the window. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "At a price minimum: price_outlook_2h may show 'falling' (window average is below current), but price_trajectory_2h shows 'rising' (second half more expensive than first) — revealing the upcoming reversal. Power combination: 'outlook: falling + trajectory: rising' = you're AT the minimum, act now."
},
"price_trajectory_3h": {
"description": "Preisverlauf innerhalb des nächsten 3-Stunden-Fensters",
"long_description": "Compares the average of the first 1.5 hours with the average of the second 1.5 hours within the next 3-hour window. 'rising' = prices are climbing over the 3h window; 'falling' = prices are dropping. Reveals the direction of price movement independent of the current price level. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Appliance timing: 'outlook: strongly_falling + trajectory: rising' = you're at or past the minimum, the window average is low but prices are already recovering — start now."
},
"price_trajectory_4h": {
"description": "Preisverlauf innerhalb des nächsten 4-Stunden-Fensters",
"long_description": "Compares the average of the first 2 hours with the average of the second 2 hours within the next 4-hour window. 'rising' = prices are climbing over the 4h window; 'falling' = prices are dropping. Complementary to price_outlook_4h: outlook answers 'is NOW cheap vs the window average?', trajectory answers 'are prices rising or falling within the window?'. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Heat pump charging: 'rising' = first half window is cheaper, charge now before prices climb. 'falling' = second half window is cheaper, wait if you can. Combine with outlook: if both rising, very strong signal to act now."
},
"price_trajectory_5h": {
"description": "Preisverlauf innerhalb des nächsten 5-Stunden-Fensters",
"long_description": "Compares the average of the first 2.5 hours with the average of the second 2.5 hours within the next 5-hour window. 'rising' = prices are climbing over the 5h window; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Eco/long cycles: 'outlook: rising + trajectory: rising' = clear signal to start now. 'outlook: falling + trajectory: rising' = you're near the bottom, good time to start before costs climb."
},
"price_trajectory_6h": {
"description": "Preisverlauf innerhalb des nächsten 6-Stunden-Fensters",
"long_description": "Compares the average of the first 3 hours with the average of the second 3 hours within the next 6-hour window. 'rising' = prices are climbing over the 6h window; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Evening/overnight planning: 'falling' at evening peak = overnight will be cheaper, postpone. 'rising' in the morning = current morning prices are the day's low — good time for high consumption."
},
"price_trajectory_8h": {
"description": "Preisverlauf innerhalb des nächsten 8-Stunden-Fensters",
"long_description": "Compares the average of the first 4 hours with the average of the second 4 hours within the next 8-hour window. 'rising' = prices are climbing over the 8h window; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Overnight charging: 'rising' during evening = first half of night is cheapest, start charging earlier. 'falling' = second half of night will be cheapest, delay start."
},
"price_trajectory_12h": {
"description": "Preisverlauf innerhalb des nächsten 12-Stunden-Fensters",
"long_description": "Compares the average of the first 6 hours with the average of the second 6 hours within the next 12-hour window. 'rising' = prices are climbing over the 12h window; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Day-ahead planning: 'rising' at midnight = first half of day is cheaper, schedule morning loads. 'falling' = prices drop in the second part of the day, afternoon/evening scheduling is better."
}, },
"current_price_trend": { "current_price_trend": {
"description": "Aktuelle Preistrend-Richtung und wie lange sie anhält", "description": "Aktuelle Preistrend-Richtung und wie lange sie anhält",

View file

@ -227,45 +227,80 @@
"long_description": "Shows the average price for the next 48 intervals (12 hours) starting from the next 15-minute interval.", "long_description": "Shows the average price for the next 48 intervals (12 hours) starting from the next 15-minute interval.",
"usage_tips": "Absolute price threshold: Strategic decisions with price caps. Only proceed if 12h average is below your maximum acceptable price. Good for postponable large loads." "usage_tips": "Absolute price threshold: Strategic decisions with price caps. Only proceed if 12h average is below your maximum acceptable price. Good for postponable large loads."
}, },
"price_trend_1h": { "price_outlook_1h": {
"description": "Price trend for the next hour", "description": "Price outlook for the next hour",
"long_description": "Compares your current price with the average of all intervals in the next hour (4 intervals). All trend sensors (1h12h) share the same base: your current price — they differ only in window size. Larger windows include more future hours and smooth out short-term spikes. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).", "long_description": "Compares your current price with the average of all intervals in the next hour (4 intervals). All outlook sensors (1h12h) share the same base: your current price — they differ only in window size. Larger windows include more future hours and smooth out short-term spikes. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Decision guide: 'rising' = ACT NOW, your current price is cheaper than the next hour. 'falling' = WAIT, cheaper prices coming. 'stable' = timing doesn't matter. Common misconception: 'rising' does NOT mean 'too late' — it means right now is a good price! Works regardless of absolute price level." "usage_tips": "Decision guide: 'rising' = ACT NOW, your current price is cheaper than the next hour average. 'falling' = WAIT, the next hour average is cheaper than now. 'stable' = timing doesn't matter. Common misconception: 'rising' does NOT mean 'too late' — it means right now is a good price! Works regardless of absolute price level."
}, },
"price_trend_2h": { "price_outlook_2h": {
"description": "Price trend for the next 2 hours", "description": "Price outlook for the next 2 hours",
"long_description": "Compares your current price with the average of all intervals in the next 2 hours (8 intervals) — the entire window from now, not just the later part. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).", "long_description": "Compares your current price with the average of all intervals in the next 2 hours (8 intervals). 'rising' = current price is below the 2h window average; 'falling' = window average is cheaper than now. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Appliances: 'rising' = start now, you're at a good price. 'falling' = better prices coming in 2h, postpone if possible. 'stable' = doesn't matter, start when convenient. Don't wait for 'stable' — if it's 'rising', NOW is actually the best time." "usage_tips": "Appliances: 'rising' = start now, you're at a good price relative to the next 2h. 'falling' = better prices ahead, postpone if possible. 'stable' = doesn't matter, start when convenient. Pair with price_trajectory_2h to distinguish 'falling now, rising later' from 'falling throughout'."
}, },
"price_trend_3h": { "price_outlook_3h": {
"description": "Price trend for the next 3 hours", "description": "Price outlook for the next 3 hours",
"long_description": "Compares your current price with the average of all intervals in the next 3 hours (12 intervals) — the entire window from now, not just the later part. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).", "long_description": "Compares your current price with the average of all intervals in the next 3 hours (12 intervals). 'rising' = current price is below the 3h window average; 'falling' = window average is cheaper than now. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Eco programs: 'rising' = start the eco cycle now, prices go up from here. 'falling' = wait, cheaper window coming. Combine with avg sensor for price cap: start when trend is 'rising' or 'stable' AND avg < your limit. Works in any season." "usage_tips": "Eco programs: 'rising' = start the eco cycle now, prices go up from here on average. 'falling' = wait, cheaper overall window coming. Combine with avg sensor for price cap: start when outlook is 'rising' or 'stable' AND avg < your limit."
}, },
"price_trend_4h": { "price_outlook_4h": {
"description": "Price trend for the next 4 hours", "description": "Price outlook for the next 4 hours",
"long_description": "Compares your current price with the average of all intervals in the next 4 hours (16 intervals) — the entire window from now, not just the later part. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).", "long_description": "Compares your current price with the average of all intervals in the next 4 hours (16 intervals). 'rising' = current price is below the 4h window average; 'falling' = window average is cheaper than now. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Heat pump/battery: 'rising' = charge now, you're at a relative low. 'falling' = wait for better charging window. 'stable' = charge when needed. Works regardless of absolute price — finds relative best time whether prices are 10 or 50 cents." "usage_tips": "Heat pump/battery: 'rising' = charge now, you're at a relative low. 'falling' = wait for better window on average. Important: 'strongly_falling' at a price minimum means the 4h average is much cheaper — but combine with price_trajectory_4h to tell if prices are still dropping or already bouncing back."
}, },
"price_trend_5h": { "price_outlook_5h": {
"description": "Price trend for the next 5 hours", "description": "Price outlook for the next 5 hours",
"long_description": "Compares your current price with the average of all intervals in the next 5 hours (20 intervals) — the entire window from now, not just the later part. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).", "long_description": "Compares your current price with the average of all intervals in the next 5 hours (20 intervals). 'rising' = current price is below the 5h window average; 'falling' = window average is cheaper than now. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Extended operations: 'rising' or 'stable' = good time to start, prices won't be cheaper. 'falling' = wait if your schedule allows. Adapts to market conditions — finds best relative timing in any price environment." "usage_tips": "Extended operations: 'rising' or 'stable' = good time to start, prices won't be cheaper on average. 'falling' = wait if your schedule allows. Adapts to market conditions — finds best relative timing in any price environment."
}, },
"price_trend_6h": { "price_outlook_6h": {
"description": "Price trend for the next 6 hours", "description": "Price outlook for the next 6 hours",
"long_description": "Compares your current price with the average of all intervals in the next 6 hours (24 intervals) — the entire window from now, not just the later part. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).", "long_description": "Compares your current price with the average of all intervals in the next 6 hours (24 intervals). 'rising' = current price is below the 6h window average; 'falling' = window average is cheaper than now. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Evening decisions: 'rising' = use electricity now while it's relatively cheap. 'falling' = evening/night prices will be better, wait if possible. Automatically adjusts to winter/summer price levels — no fixed thresholds needed." "usage_tips": "Evening decisions: 'rising' = use electricity now while it's relatively cheap. 'falling' = evening/night prices will be better on average, wait if possible. Automatically adjusts to winter/summer price levels — no fixed thresholds needed."
}, },
"price_trend_8h": { "price_outlook_8h": {
"description": "Price trend for the next 8 hours", "description": "Price outlook for the next 8 hours",
"long_description": "Compares your current price with the average of all intervals in the next 8 hours (32 intervals) — the entire window from now, not just the later part. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).", "long_description": "Compares your current price with the average of all intervals in the next 8 hours (32 intervals). 'rising' = current price is below the 8h window average; 'falling' = window average is cheaper than now. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Night planning: 'rising' = tonight/tomorrow will be more expensive, use power now. 'falling' = night prices will be cheaper, worth waiting. 'stable' = start when convenient. Works year-round without manual threshold adjustments." "usage_tips": "Night planning: 'rising' = tonight/tomorrow will be more expensive on average, use power now. 'falling' = night prices will be cheaper, worth waiting. 'stable' = start when convenient. Works year-round without manual threshold adjustments."
}, },
"price_trend_12h": { "price_outlook_12h": {
"description": "Price trend for the next 12 hours", "description": "Price outlook for the next 12 hours",
"long_description": "Compares your current price with the average of all intervals in the next 12 hours (48 intervals) — the entire window from now, not just the later part. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).", "long_description": "Compares your current price with the average of all intervals in the next 12 hours (48 intervals). 'rising' = current price is below the 12h window average; 'falling' = window average is cheaper than now. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Strategic decisions: 'rising' = you're at a low point, good time for high-consumption tasks. 'falling' = significantly better prices coming, wait if possible. Best combined with avg sensor price cap for absolute limits." "usage_tips": "Strategic decisions: 'rising' = you're at a low point relative to the next 12h, good time for high-consumption tasks. 'falling' = significantly better prices coming on average, wait if possible. Best combined with avg sensor price cap for absolute limits."
},
"price_trajectory_2h": {
"description": "Price direction within the next 2-hour window",
"long_description": "Compares the average of the first hour (4 intervals) with the average of the second hour (4 intervals) within the next 2-hour window. 'rising' = second half more expensive than first half — prices are climbing within the window. 'falling' = second half cheaper — prices are dropping within the window. Reveals the direction of price movement inside the outlook window. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "At a price minimum: price_outlook_2h may show 'falling' (because window average is below current), but price_trajectory_2h shows 'rising' (second half more expensive than first) — revealing the upcoming reversal. Power combination: 'outlook: falling + trajectory: rising' = you're AT the minimum, act now. 'outlook: falling + trajectory: falling' = prices still dropping, keep waiting."
},
"price_trajectory_3h": {
"description": "Price direction within the next 3-hour window",
"long_description": "Compares the average of the first 1.5 hours with the average of the second 1.5 hours within the next 3-hour window. 'rising' = prices are climbing over the 3h window; 'falling' = prices are dropping. Reveals the direction of price movement independent of the current price level. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Appliance timing: 'outlook: strongly_falling + trajectory: rising' = you're at or past the minimum, the window average is low but prices are already recovering — start now. 'outlook: stable + trajectory: rising' = quiet market with an upward drift — no urgency but slight favor to starting now."
},
"price_trajectory_4h": {
"description": "Price direction within the next 4-hour window",
"long_description": "Compares the average of the first 2 hours with the average of the second 2 hours within the next 4-hour window. 'rising' = prices are climbing over the 4h window; 'falling' = prices are dropping. Complementary to price_outlook_4h: outlook answers 'is NOW cheap vs the window average?', trajectory answers 'are prices rising or falling within the window?'. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Heat pump charging: 'rising' = first half window is cheaper, charge now before prices climb. 'falling' = second half window is cheaper, wait if you can. 'stable' = prices flat throughout 4h — charge when convenient. Combine with outlook: if both rising, very strong signal to act now."
},
"price_trajectory_5h": {
"description": "Price direction within the next 5-hour window",
"long_description": "Compares the average of the first 2.5 hours with the average of the second 2.5 hours within the next 5-hour window. 'rising' = prices are climbing over the 5h window; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Eco/long cycles: combine both sensors — 'outlook: rising + trajectory: rising' = clear signal to start the long program now. 'outlook: falling + trajectory: rising' = you're near the bottom, good time to start before costs climb. 'outlook: falling + trajectory: falling' = wait, still getting cheaper."
},
"price_trajectory_6h": {
"description": "Price direction within the next 6-hour window",
"long_description": "Compares the average of the first 3 hours with the average of the second 3 hours within the next 6-hour window. 'rising' = prices are climbing over the 6h window; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Evening/overnight planning: 'falling' at evening peak = overnight will be cheaper, postpone. 'rising' in the morning = current morning prices are the day's low — good time for high consumption. Pairs well with daily volatility sensors to decide if 6h window optimization is worthwhile."
},
"price_trajectory_8h": {
"description": "Price direction within the next 8-hour window",
"long_description": "Compares the average of the first 4 hours with the average of the second 4 hours within the next 8-hour window. 'rising' = prices are climbing over the 8h window; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Overnight charging: 'rising' during evening = first half of night is cheapest, start charging earlier. 'falling' = second half of night will be cheapest, delay start. Useful for EV smart charging where you only need half the night's window."
},
"price_trajectory_12h": {
"description": "Price direction within the next 12-hour window",
"long_description": "Compares the average of the first 6 hours with the average of the second 6 hours within the next 12-hour window. 'rising' = prices are climbing over the 12h window; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Day-ahead planning: 'rising' at midnight = first half of day is cheaper, schedule morning loads. 'falling' = prices drop in the second part of the day, afternoon/evening scheduling is better. Useful for large scale decisions: hot water systems, home battery charge scheduling."
}, },
"current_price_trend": { "current_price_trend": {
"description": "Current price trend direction and how long it will last", "description": "Current price trend direction and how long it will last",

View file

@ -227,45 +227,80 @@
"long_description": "Viser gjennomsnittsprisen for de neste 48 intervallene (12 timer) fra og med neste 15-minutters intervall.", "long_description": "Viser gjennomsnittsprisen for de neste 48 intervallene (12 timer) fra og med neste 15-minutters intervall.",
"usage_tips": "Absolutt pristerskel: Strategiske beslutninger med pristak. Fortsett kun hvis 12t gjennomsnitt er under din maksimalt akseptable pris. Bra for utsettbare store belastninger." "usage_tips": "Absolutt pristerskel: Strategiske beslutninger med pristak. Fortsett kun hvis 12t gjennomsnitt er under din maksimalt akseptable pris. Bra for utsettbare store belastninger."
}, },
"price_trend_1h": { "price_outlook_1h": {
"description": "Pristrend for neste time", "description": "Prisutblikk for neste time",
"long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller den neste timen (4 intervaller). Alle trendsensorer (1t12t) har samme utgangspunkt: din nåværende pris — de skiller seg bare i vindustørrelse. Større vinduer dekker flere fremtidige timer og jevner ut kortsiktige topper. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).", "long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller den neste timen (4 intervaller). Alle utblikk-sensorer (1t12t) har samme utgangspunkt: din nåværende pris — de skiller seg bare i vindustørrelse. Større vinduer dekker flere fremtidige timer og jevner ut kortsiktige topper. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).",
"usage_tips": "Beslutningshjelp: 'stigende' = HANDLE NÅ, din nåværende pris er gunstigere enn neste time. 'fallende' = VENT, billigere priser kommer. 'stabil' = timing spiller ingen rolle. Vanlig misforståelse: 'stigende' betyr IKKE 'for sent' — det betyr at nå er en god pris! Fungerer uavhengig av absolutt prisnivå." "usage_tips": "Beslutningshjelp: 'stigende' = HANDLE NÅ, din nåværende pris er gunstigere enn vindusgjennomsnittet. 'fallende' = VENT, vindusgjennomsnittet er billigere enn nå. 'stabil' = timing spiller ingen rolle. Vanlig misforståelse: 'stigende' betyr IKKE 'for sent' — det betyr at nå er en god pris!"
}, },
"price_trend_2h": { "price_outlook_2h": {
"description": "Pristrend for neste 2 timer", "description": "Prisutblikk for neste 2 timer",
"long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 2 timene (8 intervaller) — hele vinduet fra nå, ikke bare den senere delen. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).", "long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 2 timene (8 intervaller). 'stigende' = nåværende pris er under 2t-vindusgjennomsnittet; 'fallende' = vindusgjennomsnittet er billigere enn nå. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).",
"usage_tips": "Apparater: 'stigende' = start nå, du har en god pris. 'fallende' = bedre priser kommer om 2t, utsett hvis mulig. 'stabil' = spiller ingen rolle, start når det passer. Ikke vent på 'stabil' — ved 'stigende' er NÅ det beste tidspunktet." "usage_tips": "Apparater: 'stigende' = start nå, du har en god pris i forhold til neste 2t. 'fallende' = bedre priser venter, utsett hvis mulig. Kombiner med price_trajectory_2h for å skille 'faller nå, stiger senere' fra 'faller gjennom hele vinduet'."
}, },
"price_trend_3h": { "price_outlook_3h": {
"description": "Pristrend for neste 3 timer", "description": "Prisutblikk for neste 3 timer",
"long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 3 timene (12 intervaller) — hele vinduet fra nå, ikke bare den senere delen. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).", "long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 3 timene (12 intervaller). 'stigende' = nåværende pris er under 3t-vindusgjennomsnittet; 'fallende' = vindusgjennomsnittet er billigere enn nå. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).",
"usage_tips": "Eco-programmer: 'stigende' = start eco-syklusen nå, prisene stiger herfra. 'fallende' = vent, billigere vindu kommer. Kombiner med avg-sensor: start når trend er 'stigende' eller 'stabil' OG avg < din grense. Fungerer i enhver sesong." "usage_tips": "Eco-programmer: 'stigende' = start eco-syklusen nå, prisene er i snitt høyere fremover. 'fallende' = vent, billigere gjennomsnitt kommer. Kombiner med avg-sensor: start når utblikk er 'stigende' eller 'stabil' OG avg < din grense."
}, },
"price_trend_4h": { "price_outlook_4h": {
"description": "Pristrend for neste 4 timer", "description": "Prisutblikk for neste 4 timer",
"long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 4 timene (16 intervaller) — hele vinduet fra nå, ikke bare den senere delen. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).", "long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 4 timene (16 intervaller). 'stigende' = nåværende pris er under 4t-vindusgjennomsnittet; 'fallende' = vindusgjennomsnittet er billigere enn nå. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).",
"usage_tips": "Varmepumpe/batteri: 'stigende' = lad nå, du er på et relativt lavpunkt. 'fallende' = vent på bedre ladevindu. 'stabil' = lad etter behov. Fungerer uavhengig av prisnivå — finner relativ beste tid enten prisene er 10 eller 50 øre." "usage_tips": "Varmepumpe/batteri: 'stigende' = lad nå, du er på et relativt lavpunkt. 'fallende' = vent på bedre gjennomsnittsvindu. Kombiner med price_trajectory_4h for å se om prisene fortsatt faller eller allerede stiger igjen."
}, },
"price_trend_5h": { "price_outlook_5h": {
"description": "Pristrend for neste 5 timer", "description": "Prisutblikk for neste 5 timer",
"long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 5 timene (20 intervaller) — hele vinduet fra nå, ikke bare den senere delen. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).", "long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 5 timene (20 intervaller). 'stigende' = nåværende pris er under 5t-vindusgjennomsnittet; 'fallende' = vindusgjennomsnittet er billigere enn nå. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).",
"usage_tips": "Utvidede sykluser: 'stigende' eller 'stabil' = godt tidspunkt å starte, prisene blir ikke billigere. 'fallende' = vent hvis planen din tillater det. Tilpasser seg markedet — finner beste relative timing i ethvert prismiljø." "usage_tips": "Utvidede sykluser: 'stigende' eller 'stabil' = godt tidspunkt å starte. 'fallende' = vent hvis planen din tillater det."
}, },
"price_trend_6h": { "price_outlook_6h": {
"description": "Pristrend for neste 6 timer", "description": "Prisutblikk for neste 6 timer",
"long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 6 timene (24 intervaller) — hele vinduet fra nå, ikke bare den senere delen. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).", "long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 6 timene (24 intervaller). 'stigende' = nåværende pris er under 6t-vindusgjennomsnittet; 'fallende' = vindusgjennomsnittet er billigere enn nå. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).",
"usage_tips": "Kveldsbeslutninger: 'stigende' = bruk strøm nå mens den er relativt billig. 'fallende' = kvelds-/nattprisene blir bedre, vent hvis mulig. Justerer automatisk til vinter/sommer prisnivåer — ingen faste terskler nødvendig." "usage_tips": "Kveldsbeslutninger: 'stigende' = bruk strøm nå mens den er relativt billig. 'fallende' = kvelds-/nattprisene blir bedre i snitt, vent hvis mulig."
}, },
"price_trend_8h": { "price_outlook_8h": {
"description": "Pristrend for neste 8 timer", "description": "Prisutblikk for neste 8 timer",
"long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 8 timene (32 intervaller) — hele vinduet fra nå, ikke bare den senere delen. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).", "long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 8 timene (32 intervaller). 'stigende' = nåværende pris er under 8t-vindusgjennomsnittet; 'fallende' = vindusgjennomsnittet er billigere enn nå. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).",
"usage_tips": "Nattplanlegging: 'stigende' = i natt/i morgen blir dyrere, bruk strøm nå. 'fallende' = nattprisene blir billigere, verdt å vente. 'stabil' = start etter behov. Fungerer hele året uten manuelle terskeljusteringer." "usage_tips": "Nattplanlegging: 'stigende' = i natt/i morgen blir dyrere i snitt, bruk strøm nå. 'fallende' = nattprisene blir billigere, verdt å vente."
}, },
"price_trend_12h": { "price_outlook_12h": {
"description": "Pristrend for de neste 12 timene", "description": "Prisutblikk for de neste 12 timene",
"long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 12 timene (48 intervaller) — hele vinduet fra nå, ikke bare den senere delen. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).", "long_description": "Sammenligner din nåværende pris med gjennomsnittet av alle intervaller de neste 12 timene (48 intervaller). 'stigende' = nåværende pris er under 12t-vindusgjennomsnittet; 'fallende' = vindusgjennomsnittet er billigere enn nå. Stigende/fallende ved ±3%, sterkt ved ±9% (konfigurerbart, volatilitetstilpasset).",
"usage_tips": "Strategiske beslutninger: 'stigende' = du er på et lavpunkt, godt tidspunkt for strømkrevende oppgaver. 'fallende' = betydelig bedre priser kommer, vent hvis mulig. Best kombinert med avg-sensor for absolutte prisgrenser." "usage_tips": "Strategiske beslutninger: 'stigende' = du er på et lavpunkt relativt til neste 12t, godt tidspunkt for strømkrevende oppgaver. 'fallende' = betydelig bedre priser i snitt venter, vent hvis mulig."
},
"price_trajectory_2h": {
"description": "Prisretning innenfor neste 2-timers vindu",
"long_description": "Compares the average of the first hour (4 intervals) with the average of the second hour (4 intervals) within the next 2-hour window. 'rising' = second half more expensive than first half — prices are climbing within the window. 'falling' = second half cheaper — prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "At a price minimum: price_outlook_2h may show 'falling' (window average is below current), but price_trajectory_2h shows 'rising' (second half more expensive than first) — revealing the upcoming reversal. 'outlook: falling + trajectory: rising' = you're AT the minimum, act now."
},
"price_trajectory_3h": {
"description": "Prisretning innenfor neste 3-timers vindu",
"long_description": "Compares the average of the first 1.5 hours with the average of the second 1.5 hours within the next 3-hour window. 'rising' = prices are climbing over the 3h window; 'falling' = prices are dropping. Reveals direction of movement independent of current price level. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Appliance timing: 'outlook: strongly_falling + trajectory: rising' = you're at or past the minimum, prices are already recovering — start now."
},
"price_trajectory_4h": {
"description": "Prisretning innenfor neste 4-timers vindu",
"long_description": "Compares the average of the first 2 hours with the average of the second 2 hours within the next 4-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Complements price_outlook_4h: outlook answers 'is NOW cheap vs window average?', trajectory answers 'are prices rising or falling within the window?'. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Heat pump charging: 'rising' = first half is cheaper, charge now. 'falling' = second half is cheaper, wait. Combine with outlook: if both rising, very strong signal to act now."
},
"price_trajectory_5h": {
"description": "Prisretning innenfor neste 5-timers vindu",
"long_description": "Compares the average of the first 2.5 hours with the average of the second 2.5 hours within the next 5-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Long cycles: 'outlook: rising + trajectory: rising' = clear signal to start now. 'outlook: falling + trajectory: rising' = you're near the bottom, good time to start before costs climb."
},
"price_trajectory_6h": {
"description": "Prisretning innenfor neste 6-timers vindu",
"long_description": "Compares the average of the first 3 hours with the average of the second 3 hours within the next 6-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Evening/overnight planning: 'falling' at evening peak = overnight will be cheaper, postpone. 'rising' in the morning = current morning prices are the low — good time for high consumption."
},
"price_trajectory_8h": {
"description": "Prisretning innenfor neste 8-timers vindu",
"long_description": "Compares the average of the first 4 hours with the average of the second 4 hours within the next 8-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Overnight charging: 'rising' during evening = first half of night is cheapest, start charging earlier. 'falling' = second half of night will be cheapest, delay start."
},
"price_trajectory_12h": {
"description": "Prisretning innenfor neste 12-timers vindu",
"long_description": "Compares the average of the first 6 hours with the average of the second 6 hours within the next 12-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "Day-ahead planning: 'rising' at midnight = first half of day is cheaper, schedule morning loads. 'falling' = prices drop in second part of the day, afternoon/evening scheduling is better."
}, },
"current_price_trend": { "current_price_trend": {
"description": "Nåværende pristrend-retning og hvor lenge den varer", "description": "Nåværende pristrend-retning og hvor lenge den varer",

View file

@ -227,45 +227,80 @@
"long_description": "Toont de gemiddelde prijs voor de volgende 48 intervallen (12 uur) vanaf het volgende 15-minuten interval.", "long_description": "Toont de gemiddelde prijs voor de volgende 48 intervallen (12 uur) vanaf het volgende 15-minuten interval.",
"usage_tips": "Absolute prijsdrempel: Strategische beslissingen met prijslimieten. Ga alleen door als 12u gemiddelde onder je maximaal acceptabele prijs is. Goed voor uitgestelde grote belastingen." "usage_tips": "Absolute prijsdrempel: Strategische beslissingen met prijslimieten. Ga alleen door als 12u gemiddelde onder je maximaal acceptabele prijs is. Goed voor uitgestelde grote belastingen."
}, },
"price_trend_1h": { "price_outlook_1h": {
"description": "Prijstrend voor het volgende uur", "description": "Prijsvooruitzicht voor het volgende uur",
"long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in het volgende uur (4 intervallen). Alle trendsensoren (1u12u) delen hetzelfde uitgangspunt: je huidige prijs — ze verschillen alleen in venstergrootte. Grotere vensters omvatten meer toekomstige uren en vlakken kortstondige pieken af. Stijgend/dalend bij ±3%, sterk bij ±9% (configureerbaar, volatiliteitsadaptief).", "long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in het volgende uur (4 intervallen). Alle vooruitzicht-sensoren (1u12u) delen hetzelfde uitgangspunt: je huidige prijs — ze verschillen alleen in venstergrootte. Stijgend/dalend bij ±3%, sterk bij ±9% (configureerbaar, volatiliteitsadaptief).",
"usage_tips": "Beslissingshulp: 'stijgend' = HANDEL NU, je huidige prijs is goedkoper dan het volgende uur. 'dalend' = WACHT, goedkopere prijzen komen. 'stabiel' = timing maakt niet uit. Veelvoorkomend misverstand: 'stijgend' betekent NIET 'te laat' — het betekent dat nu een goede prijs is! Werkt onafhankelijk van absoluut prijsniveau." "usage_tips": "Beslissingshulp: 'stijgend' = HANDEL NU, je huidige prijs is goedkoper dan het venstergemiddelde. 'dalend' = WACHT, het venstergemiddelde is goedkoper dan nu. 'stabiel' = timing maakt niet uit."
}, },
"price_trend_2h": { "price_outlook_2h": {
"description": "Prijstrend voor de volgende 2 uur", "description": "Prijsvooruitzicht voor de volgende 2 uur",
"long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 2 uur (8 intervallen) — het hele venster vanaf nu, niet alleen het latere deel. Stijgend/dalend bij ±3%, sterk bij ±9% (configureerbaar, volatiliteitsadaptief).", "long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 2 uur (8 intervallen). 'stijgend' = huidige prijs is onder het 2u-venstergemiddelde; 'dalend' = venstergemiddelde is goedkoper dan nu. Stijgend/dalend bij ±3%, sterk bij ±9% (configureerbaar, volatiliteitsadaptief).",
"usage_tips": "Apparaten: 'stijgend' = start nu, je hebt een goede prijs. 'dalend' = betere prijzen komen over 2u, stel uit indien mogelijk. 'stabiel' = maakt niet uit, start wanneer het uitkomt. Wacht niet op 'stabiel' — bij 'stijgend' is NU het beste moment." "usage_tips": "Apparaten: 'stijgend' = start nu, je hebt een goede prijs t.o.v. de volgende 2u. 'dalend' = betere gemiddelde prijzen komen, stel uit indien mogelijk. Combineer met price_trajectory_2h om te onderscheiden of prijzen nog dalen of al stijgen."
}, },
"price_trend_3h": { "price_outlook_3h": {
"description": "Prijstrend voor de volgende 3 uur", "description": "Prijsvooruitzicht voor de volgende 3 uur",
"long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 3 uur (12 intervallen) — het hele venster vanaf nu, niet alleen het latere deel. Stijgend/dalend bij ±3%, sterk bij ±9% (configureerbaar, volatiliteitsadaptief).", "long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 3 uur (12 intervallen). 'stijgend' = huidige prijs is onder het 3u-venstergemiddelde; 'dalend' = venstergemiddelde is goedkoper dan nu. Stijgend/dalend bij ±3%, sterk bij ±9%.",
"usage_tips": "Eco-programma's: 'stijgend' = start de eco-cyclus nu, prijzen stijgen vanaf hier. 'dalend' = wacht, goedkoper venster komt. Combineer met avg-sensor: start wanneer trend 'stijgend' of 'stabiel' EN avg < je limiet. Werkt in elk seizoen." "usage_tips": "Eco-programma's: 'stijgend' = start de eco-cyclus nu, prijzen zijn gemiddeld hoger in het venster. 'dalend' = wacht, goedkoper gemiddeld venster komt."
}, },
"price_trend_4h": { "price_outlook_4h": {
"description": "Prijstrend voor de volgende 4 uur", "description": "Prijsvooruitzicht voor de volgende 4 uur",
"long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 4 uur (16 intervallen) — het hele venster vanaf nu, niet alleen het latere deel. Stijgend/dalend bij ±3%, sterk bij ±9% (configureerbaar, volatiliteitsadaptief).", "long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 4 uur (16 intervallen). 'stijgend' = huidige prijs is onder het 4u-venstergemiddelde; 'dalend' = venstergemiddelde is goedkoper dan nu. Stijgend/dalend bij ±3%, sterk bij ±9%.",
"usage_tips": "Warmtepomp/batterij: 'stijgend' = laad nu, je zit op een relatief dieptepunt. 'dalend' = wacht op beter laadvenster. 'stabiel' = laad wanneer nodig. Werkt ongeacht prijsniveau — vindt relatief beste tijd of prijzen nu 10 of 50 cent zijn." "usage_tips": "Warmtepomp/batterij: 'stijgend' = laad nu, je zit op een relatief dieptepunt. Combineer met price_trajectory_4h om te zien of prijzen nog dalen of al stijgen."
}, },
"price_trend_5h": { "price_outlook_5h": {
"description": "Prijstrend voor de volgende 5 uur", "description": "Prijsvooruitzicht voor de volgende 5 uur",
"long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 5 uur (20 intervallen) — het hele venster vanaf nu, niet alleen het latere deel. Stijgend/dalend bij ±3%, sterk bij ±9% (configureerbaar, volatiliteitsadaptief).", "long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 5 uur (20 intervallen). 'stijgend' = huidige prijs is onder het 5u-venstergemiddelde; 'dalend' = venstergemiddelde is goedkoper dan nu. Stijgend/dalend bij ±3%, sterk bij ±9%.",
"usage_tips": "Uitgebreide cycli: 'stijgend' of 'stabiel' = goed moment om te starten, prijzen worden niet goedkoper. 'dalend' = wacht als je planning het toelaat. Past zich aan de markt aan — vindt beste relatieve timing in elke prijsomgeving." "usage_tips": "Uitgebreide cycli: 'stijgend' of 'stabiel' = goed moment om te starten. 'dalend' = wacht als je planning het toelaat."
}, },
"price_trend_6h": { "price_outlook_6h": {
"description": "Prijstrend voor de volgende 6 uur", "description": "Prijsvooruitzicht voor de volgende 6 uur",
"long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 6 uur (24 intervallen) — het hele venster vanaf nu, niet alleen het latere deel. Stijgend/dalend bij ±3%, sterk bij ±9% (configureerbaar, volatiliteitsadaptief).", "long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 6 uur (24 intervallen). 'stijgend' = huidige prijs is onder het 6u-venstergemiddelde; 'dalend' = venstergemiddelde is goedkoper dan nu. Stijgend/dalend bij ±3%, sterk bij ±9%.",
"usage_tips": "Avandbeslissingen: 'stijgend' = gebruik stroom nu terwijl het relatief goedkoop is. 'dalend' = avond-/nachtprijzen worden beter, wacht indien mogelijk. Past automatisch aan winter/zomer prijsniveaus aan — geen vaste drempels nodig." "usage_tips": "Avandbeslissingen: 'stijgend' = gebruik stroom nu terwijl het relatief goedkoop is. 'dalend' = avond-/nachtprijzen worden gemiddeld beter, wacht indien mogelijk."
}, },
"price_trend_8h": { "price_outlook_8h": {
"description": "Prijstrend voor de volgende 8 uur", "description": "Prijsvooruitzicht voor de volgende 8 uur",
"long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 8 uur (32 intervallen) — het hele venster vanaf nu, niet alleen het latere deel. Stijgend/dalend bij ±3%, sterk bij ±9% (configureerbaar, volatiliteitsadaptief).", "long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de volgende 8 uur (32 intervallen). 'stijgend' = huidige prijs is onder het 8u-venstergemiddelde; 'dalend' = venstergemiddelde is goedkoper dan nu. Stijgend/dalend bij ±3%, sterk bij ±9%.",
"usage_tips": "Nachtplanning: 'stijgend' = vanavond/morgen wordt duurder, gebruik stroom nu. 'dalend' = nachtprijzen worden goedkoper, wachten loont. 'stabiel' = start wanneer het uitkomt. Werkt het hele jaar door zonder handmatige drempelaanpassingen." "usage_tips": "Nachtplanning: 'stijgend' = vanavond/morgen wordt gemiddeld duurder, gebruik stroom nu. 'dalend' = nachtprijzen worden goedkoper, wachten loont."
}, },
"price_trend_12h": { "price_outlook_12h": {
"description": "Prijstrend voor de komende 12 uur", "description": "Prijsvooruitzicht voor de komende 12 uur",
"long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de komende 12 uur (48 intervallen) — het hele venster vanaf nu, niet alleen het latere deel. Stijgend/dalend bij ±3%, sterk bij ±9% (configureerbaar, volatiliteitsadaptief).", "long_description": "Vergelijkt je huidige prijs met het gemiddelde van alle intervallen in de komende 12 uur (48 intervallen). 'stijgend' = huidige prijs is onder het 12u-venstergemiddelde; 'dalend' = venstergemiddelde is goedkoper dan nu. Stijgend/dalend bij ±3%, sterk bij ±9%.",
"usage_tips": "Strategische beslissingen: 'stijgend' = je zit op een dieptepunt, goed moment voor stroomintensieve taken. 'dalend' = aanzienlijk betere prijzen komen, wacht indien mogelijk. Het beste gecombineerd met avg-sensor voor absolute prijslimieten." "usage_tips": "Strategische beslissingen: 'stijgend' = je zit op een dieptepunt t.o.v. de volgende 12u, goed moment voor stroomintensieve taken. 'dalend' = aanzienlijk betere gemiddelde prijzen komen."
},
"price_trajectory_2h": {
"description": "Prijsrichting binnen het volgende 2-uurs venster",
"long_description": "Compares the average of the first hour with the average of the second hour within the next 2-hour window. 'rising' = second half more expensive than first half. 'falling' = second half cheaper. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "At a price minimum: price_outlook_2h may show 'falling' (window average is below current), but price_trajectory_2h shows 'rising' (second half more expensive) — revealing the upcoming reversal. 'outlook: falling + trajectory: rising' = you're AT the minimum, act now."
},
"price_trajectory_3h": {
"description": "Prijsrichting binnen het volgende 3-uurs venster",
"long_description": "Compares the average of the first 1.5 hours with the average of the second 1.5 hours within the next 3-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Appliance timing: 'outlook: strongly_falling + trajectory: rising' = you're at or past the minimum, prices are already recovering — start now."
},
"price_trajectory_4h": {
"description": "Prijsrichting binnen het volgende 4-uurs venster",
"long_description": "Compares the average of the first 2 hours with the average of the second 2 hours within the next 4-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Complements price_outlook_4h. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Heat pump charging: 'rising' = first half is cheaper, charge now. 'falling' = second half is cheaper, wait. Combine with outlook: if both rising, very strong signal to act now."
},
"price_trajectory_5h": {
"description": "Prijsrichting binnen het volgende 5-uurs venster",
"long_description": "Compares the average of the first 2.5 hours with the average of the second 2.5 hours within the next 5-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Long cycles: 'outlook: rising + trajectory: rising' = clear signal to start now. 'outlook: falling + trajectory: rising' = you're near the bottom, good time to start."
},
"price_trajectory_6h": {
"description": "Prijsrichting binnen het volgende 6-uurs venster",
"long_description": "Compares the average of the first 3 hours with the average of the second 3 hours within the next 6-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Evening/overnight planning: 'falling' at evening peak = overnight will be cheaper, postpone. 'rising' in the morning = current morning prices are the low."
},
"price_trajectory_8h": {
"description": "Prijsrichting binnen het volgende 8-uurs venster",
"long_description": "Compares the average of the first 4 hours with the average of the second 4 hours within the next 8-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Overnight charging: 'rising' during evening = first half of night is cheapest, start charging earlier. 'falling' = second half will be cheapest, delay start."
},
"price_trajectory_12h": {
"description": "Prijsrichting binnen het volgende 12-uurs venster",
"long_description": "Compares the average of the first 6 hours with the average of the second 6 hours within the next 12-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Day-ahead planning: 'rising' at midnight = first half of day is cheaper, schedule morning loads. 'falling' = afternoon/evening scheduling is better."
}, },
"current_price_trend": { "current_price_trend": {
"description": "Huidige prijstrend-richting en hoe lang deze aanhoudt", "description": "Huidige prijstrend-richting en hoe lang deze aanhoudt",
@ -275,7 +310,7 @@
"next_price_trend_change": { "next_price_trend_change": {
"description": "Wanneer de volgende significante prijstrendwijziging zal plaatsvinden", "description": "Wanneer de volgende significante prijstrendwijziging zal plaatsvinden",
"long_description": "Scant de komende 24 uur (96 intervallen) om te vinden wanneer de prijstrend-richting zal veranderen. Alleen richtingswijzigingen tellen: stijgend/sterk stijgend vormen één groep, dalend/sterk dalend een andere, stabiel is apart. Een verandering van stijgend naar sterk stijgend is GEEN trendwijziging. Gebruikt volatiliteit-adaptieve drempelwaarden (standaard: ±3%/±9%) met hysterese (standaard: 3 opeenvolgende intervallen). Retourneert het tijdstempel wanneer de wijziging wordt verwacht.", "long_description": "Scant de komende 24 uur (96 intervallen) om te vinden wanneer de prijstrend-richting zal veranderen. Alleen richtingswijzigingen tellen: stijgend/sterk stijgend vormen één groep, dalend/sterk dalend een andere, stabiel is apart. Een verandering van stijgend naar sterk stijgend is GEEN trendwijziging. Gebruikt volatiliteit-adaptieve drempelwaarden (standaard: ±3%/±9%) met hysterese (standaard: 3 opeenvolgende intervallen). Retourneert het tijdstempel wanneer de wijziging wordt verwacht.",
"usage_tips": "Gebeurtenisgestuurde automatisering: Trigger acties WANNEER trend wijzigt, niet OVER X uur. Voorbeeld: 'Laad EV wanneer volgende trendwijziging dalende prijzen toont' of 'Start vaatwasser voordat prijzen stijgen'. Vult tijdvenster-sensors aan (price_trend_Xh) die beantwoorden 'ZULLEN prijzen over X uur hoger zijn?'" "usage_tips": "Gebeurtenisgestuurde automatisering: Trigger acties WANNEER trend wijzigt, niet OVER X uur. Voorbeeld: 'Laad EV wanneer volgende trendwijziging dalende prijzen toont' of 'Start vaatwasser voordat prijzen stijgen'. Vult tijdvenster-sensors aan (price_outlook_Xh) die beantwoorden 'ZULLEN prijzen over X uur hoger zijn?'"
}, },
"trend_change_in_minutes": { "trend_change_in_minutes": {
"description": "Tijd tot de volgende prijstrendwijziging", "description": "Tijd tot de volgende prijstrendwijziging",

View file

@ -227,45 +227,80 @@
"long_description": "Visar genomsnittspriset för nästa 48 intervaller (12 timmar) från och med nästa 15-minuters intervall.", "long_description": "Visar genomsnittspriset för nästa 48 intervaller (12 timmar) från och med nästa 15-minuters intervall.",
"usage_tips": "Absolut priströskel: Strategiska beslut med pristak. Fortsätt endast om 12t genomsnitt är under ditt maximalt acceptabla pris. Bra för uppskjutbara stora laster." "usage_tips": "Absolut priströskel: Strategiska beslut med pristak. Fortsätt endast om 12t genomsnitt är under ditt maximalt acceptabla pris. Bra för uppskjutbara stora laster."
}, },
"price_trend_1h": { "price_outlook_1h": {
"description": "Pristrend för nästa timme", "description": "Prisöversikt för nästa timme",
"long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under nästa timme (4 intervaller). Alla trendsensorer (1t12t) delar samma utgångspunkt: ditt nuvarande pris — de skiljer sig bara i fönsterstorlek. Större fönster täcker fler framtida timmar och jämnar ut kortsiktiga toppar. Stigande/fallande vid ±3%, kraftigt vid ±9% (konfigurerbart, volatilitetsadaptivt).", "long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under nästa timme (4 intervaller). Alla översiktssensorer (1h12h) delar samma utgångspunkt: ditt nuvarande pris — de skiljer sig bara i fönsterstorlek. Stigande/fallande vid ±3%, kraftigt vid ±9% (konfigurerbart, volatilitetsadaptivt).",
"usage_tips": "Beslutsstöd: 'stigande' = AGERA NU, ditt nuvarande pris är billigare än nästa timme. 'fallande' = VÄNTA, billigare priser kommer. 'stabil' = timing spelar ingen roll. Vanligt missförstånd: 'stigande' betyder INTE 'för sent' — det betyder att nu är ett bra pris! Fungerar oberoende av absolut prisnivå." "usage_tips": "Beslutstöd: 'stigande' = AGERA NU, ditt nuvarande pris är billigare än fönstergenomsnittet. 'fallande' = VÄNTA, fönstergenomsnittet är billigare än nu. 'stabil' = timing spelar ingen roll."
}, },
"price_trend_2h": { "price_outlook_2h": {
"description": "Pristrend för nästa 2 timmar", "description": "Prisöversikt för nästa 2 timmar",
"long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 2 timmarna (8 intervaller) — hela fönstret från nu, inte bara den senare delen. Stigande/fallande vid ±3%, kraftigt vid ±9% (konfigurerbart, volatilitetsadaptivt).", "long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 2 timmarna (8 intervaller). 'stigande' = nuvarande pris under 2h-fönstergenomsnittet; 'fallande' = fönstergenomsnittet är billigare än nu. Stigande/fallande vid ±3%, kraftigt vid ±9%.",
"usage_tips": "Apparater: 'stigande' = starta nu, du har ett bra pris. 'fallande' = bättre priser kommer om 2t, skjut upp om möjligt. 'stabil' = spelar ingen roll, starta när det passar. Vänta inte på 'stabil' — vid 'stigande' är NU den bästa tiden." "usage_tips": "Apparater: 'stigande' = starta nu, du har ett bra pris jämfört med nästa 2h. Kombinera med price_trajectory_2h för att se om priserna fortfarande faller eller redan stiger."
}, },
"price_trend_3h": { "price_outlook_3h": {
"description": "Pristrend för nästa 3 timmar", "description": "Prisöversikt för nästa 3 timmar",
"long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 3 timmarna (12 intervaller) — hela fönstret från nu, inte bara den senare delen. Stigande/fallande vid ±3%, kraftigt vid ±9% (konfigurerbart, volatilitetsadaptivt).", "long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 3 timmarna (12 intervaller). 'stigande' = nuvarande pris under 3h-fönstergenomsnittet; 'fallande' = fönstergenomsnittet är billigare än nu. Stigande/fallande vid ±3%, kraftigt vid ±9%.",
"usage_tips": "Eco-program: 'stigande' = starta eco-cykeln nu, priserna stiger härifrån. 'fallande' = vänta, billigare fönster kommer. Kombinera med avg-sensor: starta när trend är 'stigande' eller 'stabil' OCH avg < din gräns. Fungerar under alla säsonger." "usage_tips": "Eco-program: 'stigande' = starta eco-cykeln nu, priser är i genomsnitt högre i fönstret. 'fallande' = vänta, billigare genomsnittligt fönster kommer."
}, },
"price_trend_4h": { "price_outlook_4h": {
"description": "Pristrend för nästa 4 timmar", "description": "Prisöversikt för nästa 4 timmar",
"long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 4 timmarna (16 intervaller) — hela fönstret från nu, inte bara den senare delen. Stigande/fallande vid ±3%, kraftigt vid ±9% (konfigurerbart, volatilitetsadaptivt).", "long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 4 timmarna (16 intervaller). 'stigande' = nuvarande pris under 4h-fönstergenomsnittet; 'fallande' = fönstergenomsnittet är billigare än nu. Stigande/fallande vid ±3%, kraftigt vid ±9%.",
"usage_tips": "Värmepump/batteri: 'stigande' = ladda nu, du är på en relativ lågpunkt. 'fallande' = vänta på bättre laddningsfönster. 'stabil' = ladda efter behov. Fungerar oavsett prisnivå — hittar relativ bästa tid oavsett om priserna är 10 eller 50 öre." "usage_tips": "Värmepump/batteri: 'stigande' = ladda nu, du är på en relativ lågpunkt. Kombinera med price_trajectory_4h för att se om priserna fortfarande faller eller redan stiger."
}, },
"price_trend_5h": { "price_outlook_5h": {
"description": "Pristrend för nästa 5 timmar", "description": "Prisöversikt för nästa 5 timmar",
"long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 5 timmarna (20 intervaller) — hela fönstret från nu, inte bara den senare delen. Stigande/fallande vid ±3%, kraftigt vid ±9% (konfigurerbart, volatilitetsadaptivt).", "long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 5 timmarna (20 intervaller). 'stigande' = nuvarande pris under 5h-fönstergenomsnittet; 'fallande' = fönstergenomsnittet är billigare än nu. Stigande/fallande vid ±3%, kraftigt vid ±9%.",
"usage_tips": "Utökade cykler: 'stigande' eller 'stabil' = bra tid att starta, priserna blir inte billigare. 'fallande' = vänta om din planering tillåter det. Anpassar sig till marknaden — hittar bästa relativa timing i vilken prismiljö som helst." "usage_tips": "Utökade cykler: 'stigande' eller 'stabil' = bra tid att starta. 'fallande' = vänta om din planering tillåter det."
}, },
"price_trend_6h": { "price_outlook_6h": {
"description": "Pristrend för nästa 6 timmar", "description": "Prisöversikt för nästa 6 timmar",
"long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 6 timmarna (24 intervaller) — hela fönstret från nu, inte bara den senare delen. Stigande/fallande vid ±3%, kraftigt vid ±9% (konfigurerbart, volatilitetsadaptivt).", "long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 6 timmarna (24 intervaller). 'stigande' = nuvarande pris under 6h-fönstergenomsnittet; 'fallande' = fönstergenomsnittet är billigare än nu. Stigande/fallande vid ±3%, kraftigt vid ±9%.",
"usage_tips": "Kvällsbeslut: 'stigande' = använd el nu medan den är relativt billig. 'fallande' = kvälls-/nattpriserna blir bättre, vänta om möjligt. Justerar automatiskt till vinter/sommar prisnivåer — inga fasta trösklar behövs." "usage_tips": "Kvällsbeslut: 'stigande' = använd el nu medan den är relativt billig. 'fallande' = kvälls-/nattpriserna blir bättre i genomsnitt, vänta om möjligt."
}, },
"price_trend_8h": { "price_outlook_8h": {
"description": "Pristrend för nästa 8 timmar", "description": "Prisöversikt för nästa 8 timmar",
"long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 8 timmarna (32 intervaller) — hela fönstret från nu, inte bara den senare delen. Stigande/fallande vid ±3%, kraftigt vid ±9% (konfigurerbart, volatilitetsadaptivt).", "long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 8 timmarna (32 intervaller). 'stigande' = nuvarande pris under 8h-fönstergenomsnittet; 'fallande' = fönstergenomsnittet är billigare än nu. Stigande/fallande vid ±3%, kraftigt vid ±9%.",
"usage_tips": "Nattplanering: 'stigande' = ikväll/imorgon blir dyrare, använd el nu. 'fallande' = nattpriserna blir billigare, värt att vänta. 'stabil' = starta efter behov. Fungerar året runt utan manuella tröskeljusteringar." "usage_tips": "Nattplanering: 'stigande' = ikväll/imorgon blir dyrare i genomsnitt, använd el nu. 'fallande' = nattpriserna blir billigare, värt att vänta."
}, },
"price_trend_12h": { "price_outlook_12h": {
"description": "Pristrend för nästa 12 timmar", "description": "Prisöversikt för nästa 12 timmar",
"long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 12 timmarna (48 intervaller) — hela fönstret från nu, inte bara den senare delen. Stigande/fallande vid ±3%, kraftigt vid ±9% (konfigurerbart, volatilitetsadaptivt).", "long_description": "Jämför ditt nuvarande pris med genomsnittet av alla intervaller under de nästa 12 timmarna (48 intervaller). 'stigande' = nuvarande pris under 12h-fönstergenomsnittet; 'fallande' = fönstergenomsnittet är billigare än nu. Stigande/fallande vid ±3%, kraftigt vid ±9%.",
"usage_tips": "Strategiska beslut: 'stigande' = du är på en lågpunkt, bra tid för strömkrävande uppgifter. 'fallande' = avsevärt bättre priser kommer, vänta om möjligt. Bäst kombinerad med avg-sensor för absoluta prisgränser." "usage_tips": "Strategiska beslut: 'stigande' = du är på en lågpunkt relativt till nästa 12h, bra tid för strömkrävande uppgifter. 'fallande' = avsevärt bättre genomsnittspriser kommer."
},
"price_trajectory_2h": {
"description": "Prisutveckling inom nästa 2-timmars fönster",
"long_description": "Compares the average of the first hour with the average of the second hour within the next 2-hour window. 'rising' = second half more expensive than first half. 'falling' = second half cheaper. Rising/falling at ±3%, strongly at ±9% (configurable, volatility-adaptive).",
"usage_tips": "At a price minimum: price_outlook_2h may show 'falling' (window average is below current), but price_trajectory_2h shows 'rising' (second half more expensive) — revealing the upcoming reversal. 'outlook: falling + trajectory: rising' = you're AT the minimum, act now."
},
"price_trajectory_3h": {
"description": "Prisutveckling inom nästa 3-timmars fönster",
"long_description": "Compares the average of the first 1.5 hours with the average of the second 1.5 hours within the next 3-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Appliance timing: 'outlook: strongly_falling + trajectory: rising' = you're at or past the minimum, prices are already recovering — start now."
},
"price_trajectory_4h": {
"description": "Prisutveckling inom nästa 4-timmars fönster",
"long_description": "Compares the average of the first 2 hours with the average of the second 2 hours within the next 4-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Complements price_outlook_4h. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Heat pump charging: 'rising' = first half is cheaper, charge now. 'falling' = second half is cheaper, wait. Combine with outlook: if both rising, very strong signal to act now."
},
"price_trajectory_5h": {
"description": "Prisutveckling inom nästa 5-timmars fönster",
"long_description": "Compares the average of the first 2.5 hours with the average of the second 2.5 hours within the next 5-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Long cycles: 'outlook: rising + trajectory: rising' = clear signal to start now. 'outlook: falling + trajectory: rising' = you're near the bottom, good time to start."
},
"price_trajectory_6h": {
"description": "Prisutveckling inom nästa 6-timmars fönster",
"long_description": "Compares the average of the first 3 hours with the average of the second 3 hours within the next 6-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Evening/overnight planning: 'falling' at evening peak = overnight will be cheaper, postpone. 'rising' in the morning = current morning prices are the low."
},
"price_trajectory_8h": {
"description": "Prisutveckling inom nästa 8-timmars fönster",
"long_description": "Compares the average of the first 4 hours with the average of the second 4 hours within the next 8-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Overnight charging: 'rising' during evening = first half of night is cheapest, start charging earlier. 'falling' = second half will be cheapest, delay start."
},
"price_trajectory_12h": {
"description": "Prisutveckling inom nästa 12-timmars fönster",
"long_description": "Compares the average of the first 6 hours with the average of the second 6 hours within the next 12-hour window. 'rising' = prices are climbing; 'falling' = prices are dropping. Rising/falling at ±3%, strongly at ±9%.",
"usage_tips": "Day-ahead planning: 'rising' at midnight = first half of day is cheaper, schedule morning loads. 'falling' = afternoon/evening scheduling is better."
}, },
"current_price_trend": { "current_price_trend": {
"description": "Nuvarande pristrend-riktning och hur länge den varar", "description": "Nuvarande pristrend-riktning och hur länge den varar",
@ -275,7 +310,7 @@
"next_price_trend_change": { "next_price_trend_change": {
"description": "När nästa betydande pristrendändring kommer att inträffa", "description": "När nästa betydande pristrendändring kommer att inträffa",
"long_description": "Skannar de nästa 24 timmarna (96 intervaller) för att hitta när pristrend-riktningen kommer att ändras. Bara riktningsändringar räknas: stigande/kraftigt stigande är en grupp, fallande/kraftigt fallande en annan, stabil är egen. En ändring från stigande till kraftigt stigande är INTE en trendändring. Använder volatilitetsadaptiva tröskelvärden (standard: ±3%/±9%) med hysteres (standard: 3 på varandra följande intervaller). Returnerar tidstämpeln när ändringen förväntas.", "long_description": "Skannar de nästa 24 timmarna (96 intervaller) för att hitta när pristrend-riktningen kommer att ändras. Bara riktningsändringar räknas: stigande/kraftigt stigande är en grupp, fallande/kraftigt fallande en annan, stabil är egen. En ändring från stigande till kraftigt stigande är INTE en trendändring. Använder volatilitetsadaptiva tröskelvärden (standard: ±3%/±9%) med hysteres (standard: 3 på varandra följande intervaller). Returnerar tidstämpeln när ändringen förväntas.",
"usage_tips": "Händelsestyrd automatisering: Utlös åtgärder NÄR trenden ändras, inte OM X timmar. Exempel: 'Ladda EV när nästa trendändring visar fallande priser' eller 'Starta diskmaskin innan priserna stiger'. Kompletterar tidsfönster-sensorer (price_trend_Xh) som svarar på 'KOMMER priserna att vara högre om X timmar?'" "usage_tips": "Händelsestyrd automatisering: Utlös åtgärder NÄR trenden ändras, inte OM X timmar. Exempel: 'Ladda EV när nästa trendändring visar fallande priser' eller 'Starta diskmaskin innan priserna stiger'. Kompletterar tidsfönster-sensorer (price_outlook_Xh) som svarar på 'KOMMER priserna att vara högre om X timmar?'"
}, },
"trend_change_in_minutes": { "trend_change_in_minutes": {
"description": "Tid till nästa pristrendändring", "description": "Tid till nästa pristrendändring",

View file

@ -28,8 +28,10 @@ def _add_timing_or_volatility_attributes(
def _add_cached_trend_attributes(attributes: dict, key: str, cached_data: dict) -> None: def _add_cached_trend_attributes(attributes: dict, key: str, cached_data: dict) -> None:
"""Add cached trend attributes if available.""" """Add cached trend attributes if available."""
if key.startswith("price_trend_") and cached_data.get("trend_attributes"): if key.startswith("price_outlook_") and cached_data.get("trend_attributes"):
attributes.update(cached_data["trend_attributes"]) attributes.update(cached_data["trend_attributes"])
elif key.startswith("price_trajectory_") and cached_data.get("trajectory_attributes"):
attributes.update(cached_data["trajectory_attributes"])
elif key == "current_price_trend" and cached_data.get("current_trend_attributes"): elif key == "current_price_trend" and cached_data.get("current_trend_attributes"):
# Add cached attributes (timestamp already set by platform) # Add cached attributes (timestamp already set by platform)
attributes.update(cached_data["current_trend_attributes"]) attributes.update(cached_data["current_trend_attributes"])

View file

@ -2,13 +2,14 @@
Trend calculator for price trend analysis sensors. Trend calculator for price trend analysis sensors.
This module handles all trend-related calculations: This module handles all trend-related calculations:
- Simple price trends (1h-12h future comparison) - Price outlook (1h-12h): Current price vs average of the next N hours
- Price trajectory (2h-12h): First-half vs second-half average in the window (shows turning points)
- Current trend (pure future-based 3h outlook with volatility adjustment) - Current trend (pure future-based 3h outlook with volatility adjustment)
- Next trend change prediction (with configurable N-interval hysteresis, default 3) - Next trend change prediction (with configurable N-interval hysteresis, default 3)
- Trend duration tracking (lightweight price direction scan with noise tolerance) - Trend duration tracking (lightweight price direction scan with noise tolerance)
Caching strategy: Caching strategy:
- Simple trends: Cached per sensor update to ensure consistency between state and attributes - Outlook/Trajectory: Cached per sensor update to ensure consistency between state and attributes
- Current trend + next change: Cached centrally for 60s to avoid duplicate calculations - Current trend + next change: Cached centrally for 60s to avoid duplicate calculations
""" """
@ -31,7 +32,7 @@ if TYPE_CHECKING:
) )
# Constants # Constants
MIN_HOURS_FOR_LATER_HALF = 3 # Minimum hours needed to calculate later half average MIN_HOURS_FOR_LATER_HALF = 1 # Minimum hours needed to calculate half-window averages (activates at 2h+)
class TibberPricesTrendCalculator(TibberPricesBaseCalculator): class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
@ -39,9 +40,10 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
Calculator for price trend sensors. Calculator for price trend sensors.
Handles three types of trend analysis: Handles three types of trend analysis:
1. Simple trends (price_trend_1h-12h): Current vs next N hours average 1. Outlook sensors (price_outlook_1h-12h): Current vs next N hours average
2. Current trend (current_price_trend): Pure future-based 3h outlook with volatility adjustment 2. Trajectory sensors (price_trajectory_2h-12h): First half vs second half of window
3. Next change (next_price_trend_change): Scan forward with configurable N-interval hysteresis (default 3) 3. Current trend (current_price_trend): Pure future-based 3h outlook with volatility adjustment
4. Next change (next_price_trend_change): Scan forward with configurable N-interval hysteresis (default 3)
Caching: Caching:
- Simple trends: Per-sensor cache (_cached_trend_value, _trend_attributes) - Simple trends: Per-sensor cache (_cached_trend_value, _trend_attributes)
@ -62,9 +64,10 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
def __init__(self, coordinator: "TibberPricesDataUpdateCoordinator") -> None: def __init__(self, coordinator: "TibberPricesDataUpdateCoordinator") -> None:
"""Initialize trend calculator with caching state.""" """Initialize trend calculator with caching state."""
super().__init__(coordinator) super().__init__(coordinator)
# Per-sensor trend caches (for price_trend_Nh sensors) # Per-sensor caches (for price_outlook_Xh and price_trajectory_Xh sensors)
self._cached_trend_value: str | None = None self._cached_trend_value: str | None = None
self._trend_attributes: dict[str, Any] = {} self._trend_attributes: dict[str, Any] = {}
self._trajectory_attributes: dict[str, Any] = {}
# Centralized trend calculation cache (for current_price_trend + next_price_trend_change) # Centralized trend calculation cache (for current_price_trend + next_price_trend_change)
self._trend_calculation_cache: dict[str, Any] | None = None self._trend_calculation_cache: dict[str, Any] | None = None
self._trend_calculation_timestamp: datetime | None = None self._trend_calculation_timestamp: datetime | None = None
@ -72,11 +75,12 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
self._current_trend_attributes: dict[str, Any] | None = None self._current_trend_attributes: dict[str, Any] | None = None
self._trend_change_attributes: dict[str, Any] | None = None self._trend_change_attributes: dict[str, Any] | None = None
def get_price_trend_value(self, *, hours: int) -> str | None: def get_price_outlook_value(self, *, hours: int) -> str | None:
""" """
Calculate price trend comparing current interval vs next N hours average. Calculate price outlook comparing current interval vs average of the next N hours.
This is for simple trend sensors (price_trend_1h through price_trend_12h). This is for price_outlook_Xh sensors. Answers: "Is the average of the next Xh
cheaper or more expensive than right now?"
Results are cached per sensor to ensure consistency between state and attributes. Results are cached per sensor to ensure consistency between state and attributes.
Args: Args:
@ -284,10 +288,131 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
return trend_info["minutes_until_change"] return trend_info["minutes_until_change"]
def get_price_trajectory_value(self, *, hours: int) -> str | None:
"""
Calculate price trajectory by comparing first-half vs second-half window average.
This is for price_trajectory_Xh sensors. Answers: "Are prices rising or falling
within the next Xh window?" — revealing turning points that price_outlook_Xh misses.
Example at a price minimum (12:00):
- price_outlook_4h: "strongly_falling" (Ø next 4h is below current high)
- price_trajectory_4h: "rising" (second half is more expensive than first half)
Combined signal: act now, reversal is coming within the window.
Args:
hours: Number of hours in the window (must be >= 2)
Returns:
Trend state: "rising" | "falling" | "stable", or None if unavailable
"""
if hours < 2: # noqa: PLR2004
return None
if not self.has_data():
return None
current_interval = self.coordinator.get_current_interval()
if not current_interval or "total" not in current_interval:
return None
current_interval_price = float(current_interval["total"])
time = self.coordinator.time
current_starts_at = time.get_interval_time(current_interval)
if current_starts_at is None:
return None
next_interval_start = time.get_next_interval_start()
# Get first-half and second-half averages
first_half_avg = self._calculate_first_half_average(hours, next_interval_start)
second_half_avg = self._calculate_later_half_average(hours, next_interval_start)
if first_half_avg is None or second_half_avg is None:
return None
# Get configured thresholds (same as outlook sensors for consistency)
threshold_rising = self.config.get("price_trend_threshold_rising", 3.0)
threshold_falling = self.config.get("price_trend_threshold_falling", -3.0)
threshold_strongly_rising = self.config.get("price_trend_threshold_strongly_rising", 9.0)
threshold_strongly_falling = self.config.get("price_trend_threshold_strongly_falling", -9.0)
volatility_threshold_moderate = self.config.get("volatility_threshold_moderate", 15.0)
volatility_threshold_high = self.config.get("volatility_threshold_high", 30.0)
min_abs_diff = self.config.get("price_trend_min_price_change", 0.005)
min_abs_diff_strongly = self.config.get("price_trend_min_price_change_strongly", 0.015)
# Build volatility window from full outlook period
today_prices = self.intervals_today
tomorrow_prices = self.intervals_tomorrow
all_intervals = today_prices + tomorrow_prices
lookahead_intervals = self.coordinator.time.minutes_to_intervals(hours * 60)
current_idx = None
for idx, interval in enumerate(all_intervals):
if time.get_interval_time(interval) == current_starts_at:
current_idx = idx
break
if current_idx is not None:
volatility_window = all_intervals[current_idx : current_idx + lookahead_intervals]
else:
volatility_window = all_intervals[:lookahead_intervals]
# Compare first half vs second half: does price rise or fall across the window?
trajectory_state, diff_pct, trend_value, vol_factor = calculate_price_trend(
first_half_avg,
second_half_avg,
threshold_rising=threshold_rising,
threshold_falling=threshold_falling,
threshold_strongly_rising=threshold_strongly_rising,
threshold_strongly_falling=threshold_strongly_falling,
min_abs_diff=min_abs_diff,
min_abs_diff_strongly=min_abs_diff_strongly,
volatility_adjustment=True,
lookahead_intervals=lookahead_intervals,
all_intervals=volatility_window,
volatility_threshold_moderate=volatility_threshold_moderate,
volatility_threshold_high=volatility_threshold_high,
)
factor = get_display_unit_factor(self.config_entry)
time_obj = self.coordinator.time
total_intervals = time_obj.minutes_to_intervals(hours * 60)
first_half_count = total_intervals // 2
second_half_count = total_intervals - first_half_count
self._trajectory_attributes = {
"timestamp": next_interval_start,
"trend_value": trend_value,
f"trajectory_{hours}h_%": round(diff_pct, 1),
f"first_half_{hours}h_avg": round(first_half_avg * factor, 2),
f"second_half_{hours}h_avg": round(second_half_avg * factor, 2),
f"first_half_{hours}h_diff_from_current_%": round(
((first_half_avg - current_interval_price) / abs(current_interval_price)) * 100, 1
)
if current_interval_price != 0
else None,
f"second_half_{hours}h_diff_from_current_%": round(
((second_half_avg - current_interval_price) / abs(current_interval_price)) * 100, 1
)
if current_interval_price != 0
else None,
"first_half_interval_count": first_half_count,
"second_half_interval_count": second_half_count,
"volatility_factor": vol_factor,
}
return trajectory_state
def get_trend_attributes(self) -> dict[str, Any]: def get_trend_attributes(self) -> dict[str, Any]:
"""Get cached trend attributes for simple trend sensors (price_trend_Nh).""" """Get cached outlook attributes for price_outlook_Xh sensors."""
return self._trend_attributes return self._trend_attributes
def get_trajectory_attributes(self) -> dict[str, Any]:
"""Get cached trajectory attributes for price_trajectory_Xh sensors."""
return self._trajectory_attributes
def get_current_trend_attributes(self) -> dict[str, Any] | None: def get_current_trend_attributes(self) -> dict[str, Any] | None:
"""Get cached attributes for current_price_trend sensor.""" """Get cached attributes for current_price_trend sensor."""
return self._current_trend_attributes return self._current_trend_attributes
@ -297,9 +422,10 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
return self._trend_change_attributes return self._trend_change_attributes
def clear_trend_cache(self) -> None: def clear_trend_cache(self) -> None:
"""Clear simple trend cache (called on coordinator update).""" """Clear outlook/trajectory trend cache (called on coordinator update)."""
self._cached_trend_value = None self._cached_trend_value = None
self._trend_attributes = {} self._trend_attributes = {}
self._trajectory_attributes = {}
def clear_calculation_cache(self) -> None: def clear_calculation_cache(self) -> None:
"""Clear centralized trend calculation cache (called on coordinator update).""" """Clear centralized trend calculation cache (called on coordinator update)."""
@ -310,6 +436,54 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
# PRIVATE HELPER METHODS # PRIVATE HELPER METHODS
# ======================================================================== # ========================================================================
def _calculate_first_half_average(self, hours: int, next_interval_start: datetime) -> float | None:
"""
Calculate average price for the first half of the future time window.
This is the counterpart to _calculate_later_half_average and together they
enable trajectory calculation (first half vs second half comparison).
Args:
hours: Total hours in the prediction window
next_interval_start: Start timestamp of the next interval
Returns:
Average price for the first half intervals, or None if insufficient data
"""
if not self.has_data():
return None
today_prices = self.intervals_today
tomorrow_prices = self.intervals_tomorrow
all_prices = today_prices + tomorrow_prices
if not all_prices:
return None
time = self.coordinator.time
total_intervals = time.minutes_to_intervals(hours * 60)
first_half_intervals = total_intervals // 2
interval_duration = time.get_interval_duration()
first_half_end = next_interval_start + (interval_duration * first_half_intervals)
# Collect prices in the first half: [next_interval_start, first_half_end)
first_prices = []
for price_data in all_prices:
starts_at = time.get_interval_time(price_data)
if starts_at is None:
continue
if next_interval_start <= starts_at < first_half_end:
price = price_data.get("total")
if price is not None:
first_prices.append(float(price))
if first_prices:
return calculate_mean(first_prices)
return None
def _calculate_later_half_average(self, hours: int, next_interval_start: datetime) -> float | None: def _calculate_later_half_average(self, hours: int, next_interval_start: datetime) -> float | None:
""" """
Calculate average price for the later half of the future time window. Calculate average price for the later half of the future time window.

View file

@ -326,7 +326,7 @@ class TibberPricesSensor(TibberPricesEntity, RestoreSensor):
self.coordinator.time = time_service self.coordinator.time = time_service
# Clear cached trend values on time-sensitive updates # Clear cached trend values on time-sensitive updates
if self.entity_description.key.startswith("price_trend_"): if self.entity_description.key.startswith(("price_outlook_", "price_trajectory_")):
self._trend_calculator.clear_trend_cache() self._trend_calculator.clear_trend_cache()
# Clear trend calculation cache for trend sensors # Clear trend calculation cache for trend sensors
elif self.entity_description.key in ( elif self.entity_description.key in (
@ -366,7 +366,7 @@ class TibberPricesSensor(TibberPricesEntity, RestoreSensor):
def _handle_coordinator_update(self) -> None: def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator.""" """Handle updated data from the coordinator."""
# Clear cached trend values when coordinator data changes # Clear cached trend values when coordinator data changes
if self.entity_description.key.startswith("price_trend_"): if self.entity_description.key.startswith(("price_outlook_", "price_trajectory_")):
self._trend_calculator.clear_trend_cache() self._trend_calculator.clear_trend_cache()
# Also clear calculation cache (e.g., when threshold config changes) # Also clear calculation cache (e.g., when threshold config changes)
self._trend_calculator.clear_calculation_cache() self._trend_calculator.clear_calculation_cache()
@ -1140,6 +1140,7 @@ class TibberPricesSensor(TibberPricesEntity, RestoreSensor):
cached_data.update( cached_data.update(
{ {
"trend_attributes": self._trend_calculator.get_trend_attributes(), "trend_attributes": self._trend_calculator.get_trend_attributes(),
"trajectory_attributes": self._trend_calculator.get_trajectory_attributes(),
"current_trend_attributes": self._trend_calculator.get_current_trend_attributes(), "current_trend_attributes": self._trend_calculator.get_current_trend_attributes(),
"trend_change_attributes": self._trend_calculator.get_trend_change_attributes(), "trend_change_attributes": self._trend_calculator.get_trend_change_attributes(),
"volatility_attributes": self._volatility_calculator.get_volatility_attributes(), "volatility_attributes": self._volatility_calculator.get_volatility_attributes(),

View file

@ -529,11 +529,11 @@ FUTURE_TREND_SENSORS = (
suggested_display_precision=2, suggested_display_precision=2,
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
), ),
# Price trend forecast sensors (will prices be higher/lower in X hours?) # Price outlook forecast sensors (is the average of the next Xh cheaper/more expensive than now?)
# Default enabled: 1h-5h # Default enabled: 1h-5h
SensorEntityDescription( SensorEntityDescription(
key="price_trend_1h", key="price_outlook_1h",
translation_key="price_trend_1h", translation_key="price_outlook_1h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics state_class=None, # Enum values: no statistics
@ -541,8 +541,8 @@ FUTURE_TREND_SENSORS = (
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
), ),
SensorEntityDescription( SensorEntityDescription(
key="price_trend_2h", key="price_outlook_2h",
translation_key="price_trend_2h", translation_key="price_outlook_2h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics state_class=None, # Enum values: no statistics
@ -550,8 +550,8 @@ FUTURE_TREND_SENSORS = (
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
), ),
SensorEntityDescription( SensorEntityDescription(
key="price_trend_3h", key="price_outlook_3h",
translation_key="price_trend_3h", translation_key="price_outlook_3h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics state_class=None, # Enum values: no statistics
@ -559,8 +559,8 @@ FUTURE_TREND_SENSORS = (
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
), ),
SensorEntityDescription( SensorEntityDescription(
key="price_trend_4h", key="price_outlook_4h",
translation_key="price_trend_4h", translation_key="price_outlook_4h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics state_class=None, # Enum values: no statistics
@ -568,8 +568,8 @@ FUTURE_TREND_SENSORS = (
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
), ),
SensorEntityDescription( SensorEntityDescription(
key="price_trend_5h", key="price_outlook_5h",
translation_key="price_trend_5h", translation_key="price_outlook_5h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics state_class=None, # Enum values: no statistics
@ -578,8 +578,8 @@ FUTURE_TREND_SENSORS = (
), ),
# Disabled by default: 6h, 8h, 12h # Disabled by default: 6h, 8h, 12h
SensorEntityDescription( SensorEntityDescription(
key="price_trend_6h", key="price_outlook_6h",
translation_key="price_trend_6h", translation_key="price_outlook_6h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics state_class=None, # Enum values: no statistics
@ -587,8 +587,8 @@ FUTURE_TREND_SENSORS = (
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
key="price_trend_8h", key="price_outlook_8h",
translation_key="price_trend_8h", translation_key="price_outlook_8h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics state_class=None, # Enum values: no statistics
@ -596,8 +596,89 @@ FUTURE_TREND_SENSORS = (
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
key="price_trend_12h", key="price_outlook_12h",
translation_key="price_trend_12h", translation_key="price_outlook_12h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=False,
),
)
# ----------------------------------------------------------------------------
# 5b. PRICE TRAJECTORY SENSORS (first-half vs second-half window comparison)
# ----------------------------------------------------------------------------
# These sensors reveal turning points: is the price rising or falling WITHIN
# the window? Complements price_outlook_Xh sensors.
#
# Example at a price minimum (12:00):
# - price_outlook_4h: "strongly_falling" (Ø next 4h is below current high)
# - price_trajectory_4h: "rising" (second half avg > first half avg)
# → Combined: act now, reversal is coming within the window.
#
# Coverage starts at 2h (minimum for meaningful first/second half split).
# Default enabled: 2h-5h
PRICE_TRAJECTORY_SENSORS = (
SensorEntityDescription(
key="price_trajectory_2h",
translation_key="price_trajectory_2h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_trajectory_3h",
translation_key="price_trajectory_3h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_trajectory_4h",
translation_key="price_trajectory_4h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_trajectory_5h",
translation_key="price_trajectory_5h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
# Disabled by default: 6h, 8h, 12h
SensorEntityDescription(
key="price_trajectory_6h",
translation_key="price_trajectory_6h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="price_trajectory_8h",
translation_key="price_trajectory_8h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="price_trajectory_12h",
translation_key="price_trajectory_12h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics state_class=None, # Enum values: no statistics
@ -970,6 +1051,7 @@ ENTITY_DESCRIPTIONS = (
*WINDOW_24H_SENSORS, *WINDOW_24H_SENSORS,
*FUTURE_MEAN_SENSORS, *FUTURE_MEAN_SENSORS,
*FUTURE_TREND_SENSORS, *FUTURE_TREND_SENSORS,
*PRICE_TRAJECTORY_SENSORS,
*VOLATILITY_SENSORS, *VOLATILITY_SENSORS,
*BEST_PRICE_TIMING_SENSORS, *BEST_PRICE_TIMING_SENSORS,
*PEAK_PRICE_TIMING_SENSORS, *PEAK_PRICE_TIMING_SENSORS,

View file

@ -206,15 +206,23 @@ def get_value_getter_mapping( # noqa: PLR0913 - needs all calculators as parame
"current_price_trend": trend_calculator.get_current_trend_value, "current_price_trend": trend_calculator.get_current_trend_value,
"next_price_trend_change": trend_calculator.get_next_trend_change_value, "next_price_trend_change": trend_calculator.get_next_trend_change_value,
"trend_change_in_minutes": lambda: _minutes_to_hours(trend_calculator.get_trend_change_in_minutes_value()), "trend_change_in_minutes": lambda: _minutes_to_hours(trend_calculator.get_trend_change_in_minutes_value()),
# Price trend sensors # Price outlook sensors (current price vs average of next Xh)
"price_trend_1h": lambda: trend_calculator.get_price_trend_value(hours=1), "price_outlook_1h": lambda: trend_calculator.get_price_outlook_value(hours=1),
"price_trend_2h": lambda: trend_calculator.get_price_trend_value(hours=2), "price_outlook_2h": lambda: trend_calculator.get_price_outlook_value(hours=2),
"price_trend_3h": lambda: trend_calculator.get_price_trend_value(hours=3), "price_outlook_3h": lambda: trend_calculator.get_price_outlook_value(hours=3),
"price_trend_4h": lambda: trend_calculator.get_price_trend_value(hours=4), "price_outlook_4h": lambda: trend_calculator.get_price_outlook_value(hours=4),
"price_trend_5h": lambda: trend_calculator.get_price_trend_value(hours=5), "price_outlook_5h": lambda: trend_calculator.get_price_outlook_value(hours=5),
"price_trend_6h": lambda: trend_calculator.get_price_trend_value(hours=6), "price_outlook_6h": lambda: trend_calculator.get_price_outlook_value(hours=6),
"price_trend_8h": lambda: trend_calculator.get_price_trend_value(hours=8), "price_outlook_8h": lambda: trend_calculator.get_price_outlook_value(hours=8),
"price_trend_12h": lambda: trend_calculator.get_price_trend_value(hours=12), "price_outlook_12h": lambda: trend_calculator.get_price_outlook_value(hours=12),
# Price trajectory sensors (first-half vs second-half window, reveals turning points)
"price_trajectory_2h": lambda: trend_calculator.get_price_trajectory_value(hours=2),
"price_trajectory_3h": lambda: trend_calculator.get_price_trajectory_value(hours=3),
"price_trajectory_4h": lambda: trend_calculator.get_price_trajectory_value(hours=4),
"price_trajectory_5h": lambda: trend_calculator.get_price_trajectory_value(hours=5),
"price_trajectory_6h": lambda: trend_calculator.get_price_trajectory_value(hours=6),
"price_trajectory_8h": lambda: trend_calculator.get_price_trajectory_value(hours=8),
"price_trajectory_12h": lambda: trend_calculator.get_price_trajectory_value(hours=12),
# Diagnostic sensors # Diagnostic sensors
"data_timestamp": get_data_timestamp, "data_timestamp": get_data_timestamp,
# Data lifecycle status sensor # Data lifecycle status sensor

View file

@ -619,8 +619,8 @@
"next_avg_12h": { "next_avg_12h": {
"name": "⌀ Preis nächste 12h" "name": "⌀ Preis nächste 12h"
}, },
"price_trend_1h": { "price_outlook_1h": {
"name": "Preistrend (1h)", "name": "Preisausblick (1h)",
"state": { "state": {
"strongly_rising": "Stark steigend", "strongly_rising": "Stark steigend",
"rising": "Steigend", "rising": "Steigend",
@ -629,8 +629,8 @@
"strongly_falling": "Stark fallend" "strongly_falling": "Stark fallend"
} }
}, },
"price_trend_2h": { "price_outlook_2h": {
"name": "Preistrend (2h)", "name": "Preisausblick (2h)",
"state": { "state": {
"strongly_rising": "Stark steigend", "strongly_rising": "Stark steigend",
"rising": "Steigend", "rising": "Steigend",
@ -639,8 +639,8 @@
"strongly_falling": "Stark fallend" "strongly_falling": "Stark fallend"
} }
}, },
"price_trend_3h": { "price_outlook_3h": {
"name": "Preistrend (3h)", "name": "Preisausblick (3h)",
"state": { "state": {
"strongly_rising": "Stark steigend", "strongly_rising": "Stark steigend",
"rising": "Steigend", "rising": "Steigend",
@ -649,8 +649,8 @@
"strongly_falling": "Stark fallend" "strongly_falling": "Stark fallend"
} }
}, },
"price_trend_4h": { "price_outlook_4h": {
"name": "Preistrend (4h)", "name": "Preisausblick (4h)",
"state": { "state": {
"strongly_rising": "Stark steigend", "strongly_rising": "Stark steigend",
"rising": "Steigend", "rising": "Steigend",
@ -659,8 +659,8 @@
"strongly_falling": "Stark fallend" "strongly_falling": "Stark fallend"
} }
}, },
"price_trend_5h": { "price_outlook_5h": {
"name": "Preistrend (5h)", "name": "Preisausblick (5h)",
"state": { "state": {
"strongly_rising": "Stark steigend", "strongly_rising": "Stark steigend",
"rising": "Steigend", "rising": "Steigend",
@ -669,8 +669,8 @@
"strongly_falling": "Stark fallend" "strongly_falling": "Stark fallend"
} }
}, },
"price_trend_6h": { "price_outlook_6h": {
"name": "Preistrend (6h)", "name": "Preisausblick (6h)",
"state": { "state": {
"strongly_rising": "Stark steigend", "strongly_rising": "Stark steigend",
"rising": "Steigend", "rising": "Steigend",
@ -679,8 +679,8 @@
"strongly_falling": "Stark fallend" "strongly_falling": "Stark fallend"
} }
}, },
"price_trend_8h": { "price_outlook_8h": {
"name": "Preistrend (8h)", "name": "Preisausblick (8h)",
"state": { "state": {
"strongly_rising": "Stark steigend", "strongly_rising": "Stark steigend",
"rising": "Steigend", "rising": "Steigend",
@ -689,8 +689,78 @@
"strongly_falling": "Stark fallend" "strongly_falling": "Stark fallend"
} }
}, },
"price_trend_12h": { "price_outlook_12h": {
"name": "Preistrend (12h)", "name": "Preisausblick (12h)",
"state": {
"strongly_rising": "Stark steigend",
"rising": "Steigend",
"stable": "Stabil",
"falling": "Fallend",
"strongly_falling": "Stark fallend"
}
},
"price_trajectory_2h": {
"name": "Preisverlauf (2h)",
"state": {
"strongly_rising": "Stark steigend",
"rising": "Steigend",
"stable": "Stabil",
"falling": "Fallend",
"strongly_falling": "Stark fallend"
}
},
"price_trajectory_3h": {
"name": "Preisverlauf (3h)",
"state": {
"strongly_rising": "Stark steigend",
"rising": "Steigend",
"stable": "Stabil",
"falling": "Fallend",
"strongly_falling": "Stark fallend"
}
},
"price_trajectory_4h": {
"name": "Preisverlauf (4h)",
"state": {
"strongly_rising": "Stark steigend",
"rising": "Steigend",
"stable": "Stabil",
"falling": "Fallend",
"strongly_falling": "Stark fallend"
}
},
"price_trajectory_5h": {
"name": "Preisverlauf (5h)",
"state": {
"strongly_rising": "Stark steigend",
"rising": "Steigend",
"stable": "Stabil",
"falling": "Fallend",
"strongly_falling": "Stark fallend"
}
},
"price_trajectory_6h": {
"name": "Preisverlauf (6h)",
"state": {
"strongly_rising": "Stark steigend",
"rising": "Steigend",
"stable": "Stabil",
"falling": "Fallend",
"strongly_falling": "Stark fallend"
}
},
"price_trajectory_8h": {
"name": "Preisverlauf (8h)",
"state": {
"strongly_rising": "Stark steigend",
"rising": "Steigend",
"stable": "Stabil",
"falling": "Fallend",
"strongly_falling": "Stark fallend"
}
},
"price_trajectory_12h": {
"name": "Preisverlauf (12h)",
"state": { "state": {
"strongly_rising": "Stark steigend", "strongly_rising": "Stark steigend",
"rising": "Steigend", "rising": "Steigend",

View file

@ -619,8 +619,8 @@
"next_avg_12h": { "next_avg_12h": {
"name": "⌀ Price Next 12h" "name": "⌀ Price Next 12h"
}, },
"price_trend_1h": { "price_outlook_1h": {
"name": "Price Trend (1h)", "name": "Price Outlook (1h)",
"state": { "state": {
"strongly_rising": "Strongly Rising", "strongly_rising": "Strongly Rising",
"rising": "Rising", "rising": "Rising",
@ -629,8 +629,8 @@
"strongly_falling": "Strongly Falling" "strongly_falling": "Strongly Falling"
} }
}, },
"price_trend_2h": { "price_outlook_2h": {
"name": "Price Trend (2h)", "name": "Price Outlook (2h)",
"state": { "state": {
"strongly_rising": "Strongly Rising", "strongly_rising": "Strongly Rising",
"rising": "Rising", "rising": "Rising",
@ -639,8 +639,8 @@
"strongly_falling": "Strongly Falling" "strongly_falling": "Strongly Falling"
} }
}, },
"price_trend_3h": { "price_outlook_3h": {
"name": "Price Trend (3h)", "name": "Price Outlook (3h)",
"state": { "state": {
"strongly_rising": "Strongly Rising", "strongly_rising": "Strongly Rising",
"rising": "Rising", "rising": "Rising",
@ -649,8 +649,8 @@
"strongly_falling": "Strongly Falling" "strongly_falling": "Strongly Falling"
} }
}, },
"price_trend_4h": { "price_outlook_4h": {
"name": "Price Trend (4h)", "name": "Price Outlook (4h)",
"state": { "state": {
"strongly_rising": "Strongly Rising", "strongly_rising": "Strongly Rising",
"rising": "Rising", "rising": "Rising",
@ -659,8 +659,8 @@
"strongly_falling": "Strongly Falling" "strongly_falling": "Strongly Falling"
} }
}, },
"price_trend_5h": { "price_outlook_5h": {
"name": "Price Trend (5h)", "name": "Price Outlook (5h)",
"state": { "state": {
"strongly_rising": "Strongly Rising", "strongly_rising": "Strongly Rising",
"rising": "Rising", "rising": "Rising",
@ -669,8 +669,8 @@
"strongly_falling": "Strongly Falling" "strongly_falling": "Strongly Falling"
} }
}, },
"price_trend_6h": { "price_outlook_6h": {
"name": "Price Trend (6h)", "name": "Price Outlook (6h)",
"state": { "state": {
"strongly_rising": "Strongly Rising", "strongly_rising": "Strongly Rising",
"rising": "Rising", "rising": "Rising",
@ -679,8 +679,8 @@
"strongly_falling": "Strongly Falling" "strongly_falling": "Strongly Falling"
} }
}, },
"price_trend_8h": { "price_outlook_8h": {
"name": "Price Trend (8h)", "name": "Price Outlook (8h)",
"state": { "state": {
"strongly_rising": "Strongly Rising", "strongly_rising": "Strongly Rising",
"rising": "Rising", "rising": "Rising",
@ -689,8 +689,78 @@
"strongly_falling": "Strongly Falling" "strongly_falling": "Strongly Falling"
} }
}, },
"price_trend_12h": { "price_outlook_12h": {
"name": "Price Trend (12h)", "name": "Price Outlook (12h)",
"state": {
"strongly_rising": "Strongly Rising",
"rising": "Rising",
"stable": "Stable",
"falling": "Falling",
"strongly_falling": "Strongly Falling"
}
},
"price_trajectory_2h": {
"name": "Price Trajectory (2h)",
"state": {
"strongly_rising": "Strongly Rising",
"rising": "Rising",
"stable": "Stable",
"falling": "Falling",
"strongly_falling": "Strongly Falling"
}
},
"price_trajectory_3h": {
"name": "Price Trajectory (3h)",
"state": {
"strongly_rising": "Strongly Rising",
"rising": "Rising",
"stable": "Stable",
"falling": "Falling",
"strongly_falling": "Strongly Falling"
}
},
"price_trajectory_4h": {
"name": "Price Trajectory (4h)",
"state": {
"strongly_rising": "Strongly Rising",
"rising": "Rising",
"stable": "Stable",
"falling": "Falling",
"strongly_falling": "Strongly Falling"
}
},
"price_trajectory_5h": {
"name": "Price Trajectory (5h)",
"state": {
"strongly_rising": "Strongly Rising",
"rising": "Rising",
"stable": "Stable",
"falling": "Falling",
"strongly_falling": "Strongly Falling"
}
},
"price_trajectory_6h": {
"name": "Price Trajectory (6h)",
"state": {
"strongly_rising": "Strongly Rising",
"rising": "Rising",
"stable": "Stable",
"falling": "Falling",
"strongly_falling": "Strongly Falling"
}
},
"price_trajectory_8h": {
"name": "Price Trajectory (8h)",
"state": {
"strongly_rising": "Strongly Rising",
"rising": "Rising",
"stable": "Stable",
"falling": "Falling",
"strongly_falling": "Strongly Falling"
}
},
"price_trajectory_12h": {
"name": "Price Trajectory (12h)",
"state": { "state": {
"strongly_rising": "Strongly Rising", "strongly_rising": "Strongly Rising",
"rising": "Rising", "rising": "Rising",

View file

@ -619,8 +619,8 @@
"next_avg_12h": { "next_avg_12h": {
"name": "⌀ Pris neste 12t" "name": "⌀ Pris neste 12t"
}, },
"price_trend_1h": { "price_outlook_1h": {
"name": "Pristrend (1t)", "name": "Prisutblikk (1t)",
"state": { "state": {
"strongly_rising": "Sterkt stigende", "strongly_rising": "Sterkt stigende",
"rising": "Stigende", "rising": "Stigende",
@ -629,8 +629,8 @@
"strongly_falling": "Sterkt fallende" "strongly_falling": "Sterkt fallende"
} }
}, },
"price_trend_2h": { "price_outlook_2h": {
"name": "Pristrend (2t)", "name": "Prisutblikk (2t)",
"state": { "state": {
"strongly_rising": "Sterkt stigende", "strongly_rising": "Sterkt stigende",
"rising": "Stigende", "rising": "Stigende",
@ -639,8 +639,8 @@
"strongly_falling": "Sterkt fallende" "strongly_falling": "Sterkt fallende"
} }
}, },
"price_trend_3h": { "price_outlook_3h": {
"name": "Pristrend (3t)", "name": "Prisutblikk (3t)",
"state": { "state": {
"strongly_rising": "Sterkt stigende", "strongly_rising": "Sterkt stigende",
"rising": "Stigende", "rising": "Stigende",
@ -649,8 +649,8 @@
"strongly_falling": "Sterkt fallende" "strongly_falling": "Sterkt fallende"
} }
}, },
"price_trend_4h": { "price_outlook_4h": {
"name": "Pristrend (4t)", "name": "Prisutblikk (4t)",
"state": { "state": {
"strongly_rising": "Sterkt stigende", "strongly_rising": "Sterkt stigende",
"rising": "Stigende", "rising": "Stigende",
@ -659,8 +659,8 @@
"strongly_falling": "Sterkt fallende" "strongly_falling": "Sterkt fallende"
} }
}, },
"price_trend_5h": { "price_outlook_5h": {
"name": "Pristrend (5t)", "name": "Prisutblikk (5t)",
"state": { "state": {
"strongly_rising": "Sterkt stigende", "strongly_rising": "Sterkt stigende",
"rising": "Stigende", "rising": "Stigende",
@ -669,8 +669,8 @@
"strongly_falling": "Sterkt fallende" "strongly_falling": "Sterkt fallende"
} }
}, },
"price_trend_6h": { "price_outlook_6h": {
"name": "Pristrend (6t)", "name": "Prisutblikk (6t)",
"state": { "state": {
"strongly_rising": "Sterkt stigende", "strongly_rising": "Sterkt stigende",
"rising": "Stigende", "rising": "Stigende",
@ -679,8 +679,8 @@
"strongly_falling": "Sterkt fallende" "strongly_falling": "Sterkt fallende"
} }
}, },
"price_trend_8h": { "price_outlook_8h": {
"name": "Pristrend (8t)", "name": "Prisutblikk (8t)",
"state": { "state": {
"strongly_rising": "Sterkt stigende", "strongly_rising": "Sterkt stigende",
"rising": "Stigende", "rising": "Stigende",
@ -689,8 +689,78 @@
"strongly_falling": "Sterkt fallende" "strongly_falling": "Sterkt fallende"
} }
}, },
"price_trend_12h": { "price_outlook_12h": {
"name": "Pristrend (12t)", "name": "Prisutblikk (12t)",
"state": {
"strongly_rising": "Sterkt stigende",
"rising": "Stigende",
"stable": "Stabil",
"falling": "Fallende",
"strongly_falling": "Sterkt fallende"
}
},
"price_trajectory_2h": {
"name": "Prisforløp (2t)",
"state": {
"strongly_rising": "Sterkt stigende",
"rising": "Stigende",
"stable": "Stabil",
"falling": "Fallende",
"strongly_falling": "Sterkt fallende"
}
},
"price_trajectory_3h": {
"name": "Prisforløp (3t)",
"state": {
"strongly_rising": "Sterkt stigende",
"rising": "Stigende",
"stable": "Stabil",
"falling": "Fallende",
"strongly_falling": "Sterkt fallende"
}
},
"price_trajectory_4h": {
"name": "Prisforløp (4t)",
"state": {
"strongly_rising": "Sterkt stigende",
"rising": "Stigende",
"stable": "Stabil",
"falling": "Fallende",
"strongly_falling": "Sterkt fallende"
}
},
"price_trajectory_5h": {
"name": "Prisforløp (5t)",
"state": {
"strongly_rising": "Sterkt stigende",
"rising": "Stigende",
"stable": "Stabil",
"falling": "Fallende",
"strongly_falling": "Sterkt fallende"
}
},
"price_trajectory_6h": {
"name": "Prisforløp (6t)",
"state": {
"strongly_rising": "Sterkt stigende",
"rising": "Stigende",
"stable": "Stabil",
"falling": "Fallende",
"strongly_falling": "Sterkt fallende"
}
},
"price_trajectory_8h": {
"name": "Prisforløp (8t)",
"state": {
"strongly_rising": "Sterkt stigende",
"rising": "Stigende",
"stable": "Stabil",
"falling": "Fallende",
"strongly_falling": "Sterkt fallende"
}
},
"price_trajectory_12h": {
"name": "Prisforløp (12t)",
"state": { "state": {
"strongly_rising": "Sterkt stigende", "strongly_rising": "Sterkt stigende",
"rising": "Stigende", "rising": "Stigende",

View file

@ -619,8 +619,8 @@
"next_avg_12h": { "next_avg_12h": {
"name": "⌀ Prijs Komende 12u" "name": "⌀ Prijs Komende 12u"
}, },
"price_trend_1h": { "price_outlook_1h": {
"name": "Prijstrend (1u)", "name": "Prijsvooruitzicht (1u)",
"state": { "state": {
"strongly_rising": "Sterk stijgend", "strongly_rising": "Sterk stijgend",
"rising": "Stijgend", "rising": "Stijgend",
@ -629,8 +629,8 @@
"strongly_falling": "Sterk dalend" "strongly_falling": "Sterk dalend"
} }
}, },
"price_trend_2h": { "price_outlook_2h": {
"name": "Prijstrend (2u)", "name": "Prijsvooruitzicht (2u)",
"state": { "state": {
"strongly_rising": "Sterk stijgend", "strongly_rising": "Sterk stijgend",
"rising": "Stijgend", "rising": "Stijgend",
@ -639,8 +639,8 @@
"strongly_falling": "Sterk dalend" "strongly_falling": "Sterk dalend"
} }
}, },
"price_trend_3h": { "price_outlook_3h": {
"name": "Prijstrend (3u)", "name": "Prijsvooruitzicht (3u)",
"state": { "state": {
"strongly_rising": "Sterk stijgend", "strongly_rising": "Sterk stijgend",
"rising": "Stijgend", "rising": "Stijgend",
@ -649,8 +649,8 @@
"strongly_falling": "Sterk dalend" "strongly_falling": "Sterk dalend"
} }
}, },
"price_trend_4h": { "price_outlook_4h": {
"name": "Prijstrend (4u)", "name": "Prijsvooruitzicht (4u)",
"state": { "state": {
"strongly_rising": "Sterk stijgend", "strongly_rising": "Sterk stijgend",
"rising": "Stijgend", "rising": "Stijgend",
@ -659,8 +659,8 @@
"strongly_falling": "Sterk dalend" "strongly_falling": "Sterk dalend"
} }
}, },
"price_trend_5h": { "price_outlook_5h": {
"name": "Prijstrend (5u)", "name": "Prijsvooruitzicht (5u)",
"state": { "state": {
"strongly_rising": "Sterk stijgend", "strongly_rising": "Sterk stijgend",
"rising": "Stijgend", "rising": "Stijgend",
@ -669,8 +669,8 @@
"strongly_falling": "Sterk dalend" "strongly_falling": "Sterk dalend"
} }
}, },
"price_trend_6h": { "price_outlook_6h": {
"name": "Prijstrend (6u)", "name": "Prijsvooruitzicht (6u)",
"state": { "state": {
"strongly_rising": "Sterk stijgend", "strongly_rising": "Sterk stijgend",
"rising": "Stijgend", "rising": "Stijgend",
@ -679,8 +679,8 @@
"strongly_falling": "Sterk dalend" "strongly_falling": "Sterk dalend"
} }
}, },
"price_trend_8h": { "price_outlook_8h": {
"name": "Prijstrend (8u)", "name": "Prijsvooruitzicht (8u)",
"state": { "state": {
"strongly_rising": "Sterk stijgend", "strongly_rising": "Sterk stijgend",
"rising": "Stijgend", "rising": "Stijgend",
@ -689,8 +689,78 @@
"strongly_falling": "Sterk dalend" "strongly_falling": "Sterk dalend"
} }
}, },
"price_trend_12h": { "price_outlook_12h": {
"name": "Prijstrend (12u)", "name": "Prijsvooruitzicht (12u)",
"state": {
"strongly_rising": "Sterk stijgend",
"rising": "Stijgend",
"stable": "Stabiel",
"falling": "Dalend",
"strongly_falling": "Sterk dalend"
}
},
"price_trajectory_2h": {
"name": "Prijstrajectorie (2u)",
"state": {
"strongly_rising": "Sterk stijgend",
"rising": "Stijgend",
"stable": "Stabiel",
"falling": "Dalend",
"strongly_falling": "Sterk dalend"
}
},
"price_trajectory_3h": {
"name": "Prijstrajectorie (3u)",
"state": {
"strongly_rising": "Sterk stijgend",
"rising": "Stijgend",
"stable": "Stabiel",
"falling": "Dalend",
"strongly_falling": "Sterk dalend"
}
},
"price_trajectory_4h": {
"name": "Prijstrajectorie (4u)",
"state": {
"strongly_rising": "Sterk stijgend",
"rising": "Stijgend",
"stable": "Stabiel",
"falling": "Dalend",
"strongly_falling": "Sterk dalend"
}
},
"price_trajectory_5h": {
"name": "Prijstrajectorie (5u)",
"state": {
"strongly_rising": "Sterk stijgend",
"rising": "Stijgend",
"stable": "Stabiel",
"falling": "Dalend",
"strongly_falling": "Sterk dalend"
}
},
"price_trajectory_6h": {
"name": "Prijstrajectorie (6u)",
"state": {
"strongly_rising": "Sterk stijgend",
"rising": "Stijgend",
"stable": "Stabiel",
"falling": "Dalend",
"strongly_falling": "Sterk dalend"
}
},
"price_trajectory_8h": {
"name": "Prijstrajectorie (8u)",
"state": {
"strongly_rising": "Sterk stijgend",
"rising": "Stijgend",
"stable": "Stabiel",
"falling": "Dalend",
"strongly_falling": "Sterk dalend"
}
},
"price_trajectory_12h": {
"name": "Prijstrajectorie (12u)",
"state": { "state": {
"strongly_rising": "Sterk stijgend", "strongly_rising": "Sterk stijgend",
"rising": "Stijgend", "rising": "Stijgend",

View file

@ -619,8 +619,8 @@
"next_avg_12h": { "next_avg_12h": {
"name": "⌀ Pris nästa 12h" "name": "⌀ Pris nästa 12h"
}, },
"price_trend_1h": { "price_outlook_1h": {
"name": "Pristrend (1h)", "name": "Prisöversikt (1h)",
"state": { "state": {
"strongly_rising": "Kraftigt stigande", "strongly_rising": "Kraftigt stigande",
"rising": "Stigande", "rising": "Stigande",
@ -629,8 +629,8 @@
"strongly_falling": "Kraftigt fallande" "strongly_falling": "Kraftigt fallande"
} }
}, },
"price_trend_2h": { "price_outlook_2h": {
"name": "Pristrend (2h)", "name": "Prisöversikt (2h)",
"state": { "state": {
"strongly_rising": "Kraftigt stigande", "strongly_rising": "Kraftigt stigande",
"rising": "Stigande", "rising": "Stigande",
@ -639,8 +639,8 @@
"strongly_falling": "Kraftigt fallande" "strongly_falling": "Kraftigt fallande"
} }
}, },
"price_trend_3h": { "price_outlook_3h": {
"name": "Pristrend (3h)", "name": "Prisöversikt (3h)",
"state": { "state": {
"strongly_rising": "Kraftigt stigande", "strongly_rising": "Kraftigt stigande",
"rising": "Stigande", "rising": "Stigande",
@ -649,8 +649,8 @@
"strongly_falling": "Kraftigt fallande" "strongly_falling": "Kraftigt fallande"
} }
}, },
"price_trend_4h": { "price_outlook_4h": {
"name": "Pristrend (4h)", "name": "Prisöversikt (4h)",
"state": { "state": {
"strongly_rising": "Kraftigt stigande", "strongly_rising": "Kraftigt stigande",
"rising": "Stigande", "rising": "Stigande",
@ -659,8 +659,8 @@
"strongly_falling": "Kraftigt fallande" "strongly_falling": "Kraftigt fallande"
} }
}, },
"price_trend_5h": { "price_outlook_5h": {
"name": "Pristrend (5h)", "name": "Prisöversikt (5h)",
"state": { "state": {
"strongly_rising": "Kraftigt stigande", "strongly_rising": "Kraftigt stigande",
"rising": "Stigande", "rising": "Stigande",
@ -669,8 +669,8 @@
"strongly_falling": "Kraftigt fallande" "strongly_falling": "Kraftigt fallande"
} }
}, },
"price_trend_6h": { "price_outlook_6h": {
"name": "Pristrend (6h)", "name": "Prisöversikt (6h)",
"state": { "state": {
"strongly_rising": "Kraftigt stigande", "strongly_rising": "Kraftigt stigande",
"rising": "Stigande", "rising": "Stigande",
@ -679,8 +679,8 @@
"strongly_falling": "Kraftigt fallande" "strongly_falling": "Kraftigt fallande"
} }
}, },
"price_trend_8h": { "price_outlook_8h": {
"name": "Pristrend (8h)", "name": "Prisöversikt (8h)",
"state": { "state": {
"strongly_rising": "Kraftigt stigande", "strongly_rising": "Kraftigt stigande",
"rising": "Stigande", "rising": "Stigande",
@ -689,8 +689,78 @@
"strongly_falling": "Kraftigt fallande" "strongly_falling": "Kraftigt fallande"
} }
}, },
"price_trend_12h": { "price_outlook_12h": {
"name": "Pristrend (12h)", "name": "Prisöversikt (12h)",
"state": {
"strongly_rising": "Kraftigt stigande",
"rising": "Stigande",
"stable": "Stabil",
"falling": "Fallande",
"strongly_falling": "Kraftigt fallande"
}
},
"price_trajectory_2h": {
"name": "Prisutveckling (2h)",
"state": {
"strongly_rising": "Kraftigt stigande",
"rising": "Stigande",
"stable": "Stabil",
"falling": "Fallande",
"strongly_falling": "Kraftigt fallande"
}
},
"price_trajectory_3h": {
"name": "Prisutveckling (3h)",
"state": {
"strongly_rising": "Kraftigt stigande",
"rising": "Stigande",
"stable": "Stabil",
"falling": "Fallande",
"strongly_falling": "Kraftigt fallande"
}
},
"price_trajectory_4h": {
"name": "Prisutveckling (4h)",
"state": {
"strongly_rising": "Kraftigt stigande",
"rising": "Stigande",
"stable": "Stabil",
"falling": "Fallande",
"strongly_falling": "Kraftigt fallande"
}
},
"price_trajectory_5h": {
"name": "Prisutveckling (5h)",
"state": {
"strongly_rising": "Kraftigt stigande",
"rising": "Stigande",
"stable": "Stabil",
"falling": "Fallande",
"strongly_falling": "Kraftigt fallande"
}
},
"price_trajectory_6h": {
"name": "Prisutveckling (6h)",
"state": {
"strongly_rising": "Kraftigt stigande",
"rising": "Stigande",
"stable": "Stabil",
"falling": "Fallande",
"strongly_falling": "Kraftigt fallande"
}
},
"price_trajectory_8h": {
"name": "Prisutveckling (8h)",
"state": {
"strongly_rising": "Kraftigt stigande",
"rising": "Stigande",
"stable": "Stabil",
"falling": "Fallande",
"strongly_falling": "Kraftigt fallande"
}
},
"price_trajectory_12h": {
"name": "Prisutveckling (12h)",
"state": { "state": {
"strongly_rising": "Kraftigt stigande", "strongly_rising": "Kraftigt stigande",
"rising": "Stigande", "rising": "Stigande",

View file

@ -124,14 +124,14 @@ automation:
# Conditions: Prices are falling AND we're approaching cheap levels # Conditions: Prices are falling AND we're approaching cheap levels
- condition: template - condition: template
value_template: > value_template: >
{% set trend_value = state_attr('sensor.<home_name>_price_trend_3h', 'trend_value') | int(0) %} {% set trend_value = state_attr('sensor.<home_name>_price_outlook_3h', 'trend_value') | int(0) %}
{% set level = state_attr('sensor.<home_name>_current_electricity_price', 'rating_level') %} {% set level = state_attr('sensor.<home_name>_current_electricity_price', 'rating_level') %}
{{ trend_value <= -1 and level in ['CHEAP', 'NORMAL'] }} {{ trend_value <= -1 and level in ['CHEAP', 'NORMAL'] }}
# AND: The next 3 hours will be cheaper on average # AND: The next 3 hours will be cheaper on average
- condition: template - condition: template
value_template: > value_template: >
{% set current = states('sensor.<home_name>_current_electricity_price') | float %} {% set current = states('sensor.<home_name>_current_electricity_price') | float %}
{% set future_avg = state_attr('sensor.<home_name>_price_trend_3h', 'next_3h_avg') | float %} {% set future_avg = state_attr('sensor.<home_name>_price_outlook_3h', 'next_3h_avg') | float %}
{{ future_avg < current }} {{ future_avg < current }}
action: action:
- service: water_heater.set_temperature - service: water_heater.set_temperature
@ -189,9 +189,9 @@ automation:
Falling = current price is HIGHER than future average = wait. Falling = current price is HIGHER than future average = wait.
trigger: trigger:
- platform: state - platform: state
entity_id: sensor.<home_name>_price_trend_1h entity_id: sensor.<home_name>_price_outlook_1h
- platform: state - platform: state
entity_id: sensor.<home_name>_price_trend_6h entity_id: sensor.<home_name>_price_outlook_6h
condition: condition:
# Only consider if best price period is NOT active # Only consider if best price period is NOT active
# (if it IS active, a separate automation handles it) # (if it IS active, a separate automation handles it)
@ -204,8 +204,8 @@ automation:
- conditions: - conditions:
- condition: template - condition: template
value_template: > value_template: >
{% set t1 = state_attr('sensor.<home_name>_price_trend_1h', 'trend_value') | int(0) %} {% set t1 = state_attr('sensor.<home_name>_price_outlook_1h', 'trend_value') | int(0) %}
{% set t6 = state_attr('sensor.<home_name>_price_trend_6h', 'trend_value') | int(0) %} {% set t6 = state_attr('sensor.<home_name>_price_outlook_6h', 'trend_value') | int(0) %}
{{ t1 >= 1 and t6 >= 1 }} {{ t1 >= 1 and t6 >= 1 }}
sequence: sequence:
- service: climate.set_temperature - service: climate.set_temperature
@ -217,8 +217,8 @@ automation:
- conditions: - conditions:
- condition: template - condition: template
value_template: > value_template: >
{% set t1 = state_attr('sensor.<home_name>_price_trend_1h', 'trend_value') | int(0) %} {% set t1 = state_attr('sensor.<home_name>_price_outlook_1h', 'trend_value') | int(0) %}
{% set t6 = state_attr('sensor.<home_name>_price_trend_6h', 'trend_value') | int(0) %} {% set t6 = state_attr('sensor.<home_name>_price_outlook_6h', 'trend_value') | int(0) %}
{{ t1 <= -1 and t6 >= 1 }} {{ t1 <= -1 and t6 >= 1 }}
sequence: sequence:
# Short-term dip — wait for it to bottom out # Short-term dip — wait for it to bottom out
@ -231,7 +231,7 @@ automation:
- conditions: - conditions:
- condition: template - condition: template
value_template: > value_template: >
{% set t6 = state_attr('sensor.<home_name>_price_trend_6h', 'trend_value') | int(0) %} {% set t6 = state_attr('sensor.<home_name>_price_outlook_6h', 'trend_value') | int(0) %}
{{ t6 <= -1 }} {{ t6 <= -1 }}
sequence: sequence:
- service: climate.set_temperature - service: climate.set_temperature
@ -414,8 +414,8 @@ automation:
action: action:
# Compare different future windows to find cheapest start # Compare different future windows to find cheapest start
- variables: - variables:
next_2h: "{{ state_attr('sensor.<home_name>_price_trend_2h', 'next_2h_avg') | float(999) }}" next_2h: "{{ state_attr('sensor.<home_name>_price_outlook_2h', 'next_2h_avg') | float(999) }}"
next_4h: "{{ state_attr('sensor.<home_name>_price_trend_4h', 'next_4h_avg') | float(999) }}" next_4h: "{{ state_attr('sensor.<home_name>_price_outlook_4h', 'next_4h_avg') | float(999) }}"
daily_avg: "{{ state_attr('sensor.<home_name>_price_today', 'price_median') | float(999) }}" daily_avg: "{{ state_attr('sensor.<home_name>_price_today', 'price_median') | float(999) }}"
- service: notify.mobile_app - service: notify.mobile_app
data: data:

View file

@ -43,7 +43,7 @@ Many sensors provide the `icon_color` attribute for dynamic styling. To see if a
- Price level sensors (e.g., `current_price_level`) - Price level sensors (e.g., `current_price_level`)
- Price rating sensors (e.g., `current_price_rating`) - Price rating sensors (e.g., `current_price_rating`)
- Volatility sensors (e.g., `today_s_price_volatility`) - Volatility sensors (e.g., `today_s_price_volatility`)
- Price trend sensors (e.g., `price_trend_3h`) - Price outlook sensors (e.g., `price_outlook_3h`)
- Binary sensors (e.g., `best_price_period`, `peak_price_period`) - Binary sensors (e.g., `best_price_period`, `peak_price_period`)
- Timing sensors (e.g., `best_price_time_until_start`, `best_price_progress`) - Timing sensors (e.g., `best_price_time_until_start`, `best_price_progress`)
@ -282,8 +282,8 @@ cards:
]]] ]]]
- type: custom:button-card - type: custom:button-card
entity: sensor.<home_name>_price_trend_3h entity: sensor.<home_name>_price_outlook_3h
name: Next 3h Trend name: Next 3h Outlook
show_state: true show_state: true
styles: styles:
icon: icon:

View file

@ -552,32 +552,35 @@ automation:
## Trend Sensors ## Trend Sensors
Trend sensors help you understand **where prices are heading**. They answer the question: "Should I use electricity now, or wait?" Trend sensors help you understand **whether to act now or wait**. The integration provides two complementary families:
The integration provides two families of trend sensors for different use cases: - **Price Outlook Sensors (1h12h):** Compare current price vs. future window average — "Is now cheaper than the next Nh on average?"
- **Price Trajectory Sensors (2h12h):** Compare first half vs. second half of the window — "Are prices rising or falling *within* the window?"
### Simple Trend Sensors (1h12h) ### Price Outlook Sensors (1h12h)
These sensors compare the **current price** with the **average price** of the next N hours: These sensors compare the **current price** with the **average price** of the next N hours:
| Sensor | Compares Against | | Sensor | Compares Against |
|--------|-----------------| |--------|-----------------|
| **Price Trend (1h)** | Average of next 1 hour | | **Price Outlook (1h)** | Average of next 1 hour |
| **Price Trend (2h)** | Average of next 2 hours | | **Price Outlook (2h)** | Average of next 2 hours |
| **Price Trend (3h)** | Average of next 3 hours | | **Price Outlook (3h)** | Average of next 3 hours |
| **Price Trend (4h)** | Average of next 4 hours | | **Price Outlook (4h)** | Average of next 4 hours |
| **Price Trend (5h)** | Average of next 5 hours | | **Price Outlook (5h)** | Average of next 5 hours |
| **Price Trend (6h)** | Average of next 6 hours | | **Price Outlook (6h)** | Average of next 6 hours |
| **Price Trend (8h)** | Average of next 8 hours | | **Price Outlook (8h)** | Average of next 8 hours |
| **Price Trend (12h)** | Average of next 12 hours | | **Price Outlook (12h)** | Average of next 12 hours |
:::info Same Starting Point — All Sensors Use Your Current Price :::info Same Starting Point — All Outlook Sensors Use Your Current Price
All trend sensors share the **same base: your current 15-minute price**. They differ only in how far ahead they average. The windows **overlap** — the 3h average includes ALL intervals from the 1h and 2h windows, plus one more hour. All outlook sensors share the **same base: your current 15-minute price**. They differ only in how far ahead they average. The windows **overlap** — the 3h average includes ALL intervals from the 1h and 2h windows, plus one more hour.
**This means:** **This means:**
- `price_trend_3h` shows "current price vs. average of the **entire** next 3 hours" — **not** "what happens between hour 2 and hour 3" - `price_outlook_3h` shows "current price vs. average of the **entire** next 3 hours" — **not** "what happens between hour 2 and hour 3"
- If 1h shows `falling` but 6h shows `rising`: near-term prices are below your current price, but looking at the full 6h window (which includes expensive evening hours), the overall average is above your current price - If 1h shows `falling` but 6h shows `rising`: near-term prices are below your current price, but looking at the full 6h window (which includes expensive evening hours), the overall average is above your current price
- Larger windows smooth out short-term fluctuations — a 30-minute price spike affects the 1h average more than the 6h average - Larger windows smooth out short-term fluctuations — a 30-minute price spike affects the 1h average more than the 6h average
**⚠️ At a price minimum, outlook sensors can be misleading!** If you're at the minimum and prices are about to rise, `price_outlook_3h` may still show `strongly_falling` because the cheap minimum pulls the 3h average below your current high price. Use `price_trajectory_3h` to see the direction *within* the window.
::: :::
**States:** Each sensor has one of five states: **States:** Each sensor has one of five states:
@ -626,6 +629,42 @@ stateDiagram-v2
**Tip:** The `trend_value` attribute (`-2` to `+2`) is ideal for automations — use numeric comparisons instead of matching translated state strings. **Tip:** The `trend_value` attribute (`-2` to `+2`) is ideal for automations — use numeric comparisons instead of matching translated state strings.
### Price Trajectory Sensors (2h12h)
These sensors compare the **first half** of the future window against the **second half** — revealing the price *direction within* the window.
| Sensor | Compares |
|--------|----------|
| **Price Trajectory (2h)** | Avg of hour 1 vs avg of hour 2 |
| **Price Trajectory (3h)** | Avg of first 1.5h vs avg of second 1.5h |
| **Price Trajectory (4h)** | Avg of first 2h vs avg of second 2h |
| **Price Trajectory (5h)** | Avg of first 2.5h vs avg of second 2.5h |
| **Price Trajectory (6h)** | Avg of first 3h vs avg of second 3h |
| **Price Trajectory (8h)** | Avg of first 4h vs avg of second 4h |
| **Price Trajectory (12h)** | Avg of first 6h vs avg of second 6h |
**States:** Same 5-level scale as outlook sensors (`strongly_falling` → `strongly_rising`).
:::info Why trajectory sensors complement outlook sensors
**At a price minimum** — the exact moment you should act — `price_outlook_3h` may show `strongly_falling` because the cheap minimum pulls the entire 3h average below your current high price. But `price_trajectory_3h` shows `rising` because the second half (after the minimum) is more expensive than the first half.
| Combination | Interpretation |
|-------------|----------------|
| Outlook `falling` + Trajectory `rising` | **You're AT the minimum** — act now |
| Outlook `falling` + Trajectory `falling` | Prices still dropping — wait |
| Outlook `rising` + Trajectory `rising` | Strong signal to act now |
| Outlook `rising` + Trajectory `falling` | Short spike, then cheaper — wait |
:::
**Key attributes:**
| Attribute | Description | Example |
|-----------|-------------|---------|
| `trend_value` | Numeric value for automations (-2 to +2) | `1` |
| `first_half_avg` | Average price in first half of window | `12.4` |
| `second_half_avg` | Average price in second half of window | `18.1` |
| `half_diff_%` | Percentage difference (second vs first half) | `46.0` |
### Current Price Trend ### Current Price Trend
**Entity ID:** `sensor.<home_name>_current_price_trend` **Entity ID:** `sensor.<home_name>_current_price_trend`
@ -701,16 +740,16 @@ A natural intuition is to treat trend states like a stock ticker:
#### Basic Automation Pattern #### Basic Automation Pattern
For most appliances (dishwasher, washing machine, dryer), a single trend sensor is enough: For most appliances (dishwasher, washing machine, dryer), a single outlook sensor is enough:
```yaml ```yaml
# Example: Start dishwasher when prices are favorable # Example: Start dishwasher when prices are favorable
trigger: trigger:
- platform: state - platform: state
entity_id: sensor.my_home_price_trend_3h entity_id: sensor.my_home_price_outlook_3h
condition: condition:
- condition: numeric_state - condition: numeric_state
entity_id: sensor.my_home_price_trend_3h entity_id: sensor.my_home_price_outlook_3h
attribute: trend_value attribute: trend_value
# rising (1) or strongly_rising (2) = act now # rising (1) or strongly_rising (2) = act now
above: 0 above: 0
@ -724,7 +763,7 @@ action:
When short-term and long-term trends disagree, you get richer insight: When short-term and long-term trends disagree, you get richer insight:
| 1h Trend | 6h Trend | Interpretation | Recommendation | | 1h Outlook | 6h Outlook | Interpretation | Recommendation |
|----------|----------|---------------|----------------| |----------|----------|---------------|----------------|
| `rising` | `rising` | Prices going up across the board | **Start now** | | `rising` | `rising` | Prices going up across the board | **Start now** |
| `falling` | `falling` | Prices dropping across the board | **Wait** | | `falling` | `falling` | Prices dropping across the board | **Wait** |
@ -740,12 +779,12 @@ On your dashboard, trend sensors give an instant overview:
- 🔴 All **rising/strongly_rising** → "Start everything you can — it only gets more expensive" - 🔴 All **rising/strongly_rising** → "Start everything you can — it only gets more expensive"
- 🟡 **Mixed** → Compare short-term vs. long-term sensors, or check the Best Price Period sensor - 🟡 **Mixed** → Compare short-term vs. long-term sensors, or check the Best Price Period sensor
### Trend Sensors vs Average Sensors ### Outlook & Trajectory Sensors vs Average Sensors
Both sensor families provide future price information, but serve different purposes: Both sensor families provide future price information, but serve different purposes:
| | Trend Sensors | Average Sensors | | | Outlook/Trajectory Sensors | Average Sensors |
|--|---------------|-----------------| |--|---------------------------|-----------------|
| **Purpose** | Dashboard display, quick visual overview | Automations, precise numeric comparisons | | **Purpose** | Dashboard display, quick visual overview | Automations, precise numeric comparisons |
| **Output** | Classification (falling/stable/rising) | Exact price values (ct/kWh) | | **Output** | Classification (falling/stable/rising) | Exact price values (ct/kWh) |
| **Best for** | "Should I worry about prices?" | "Is the future average below 15 ct?" | | **Best for** | "Should I worry about prices?" | "Is the future average below 15 ct?" |

View file

@ -54,8 +54,8 @@ uv pip install --requirement requirements.txt
############################################################################### ###############################################################################
# HA_VERSION can be overridden from the environment, e.g.: # HA_VERSION can be overridden from the environment, e.g.:
# HA_VERSION=2026.3.4 script/bootstrap # HA_VERSION=2026.4.1 script/bootstrap
HA_VERSION=${HA_VERSION:-"2026.3.4"} HA_VERSION=${HA_VERSION:-"2026.4.1"}
HA_CORE_BASE_URL="https://raw.githubusercontent.com/home-assistant/core/${HA_VERSION}" HA_CORE_BASE_URL="https://raw.githubusercontent.com/home-assistant/core/${HA_VERSION}"
HA_TMP_DIR="$HOME/.ha_requirements" HA_TMP_DIR="$HOME/.ha_requirements"

View file

@ -143,7 +143,7 @@ def test_future_mean_sensors_use_quarter_hour_timer() -> None:
def test_trend_sensors_use_quarter_hour_timer() -> None: def test_trend_sensors_use_quarter_hour_timer() -> None:
""" """
Test that price trend sensors use Timer #2. Test that price outlook/trajectory sensors use Timer #2.
Trend analysis depends on current interval position and Trend analysis depends on current interval position and
needs updates at quarter-hour boundaries. needs updates at quarter-hour boundaries.
@ -151,14 +151,21 @@ def test_trend_sensors_use_quarter_hour_timer() -> None:
trend_sensors = [ trend_sensors = [
"current_price_trend", "current_price_trend",
"next_price_trend_change", "next_price_trend_change",
"price_trend_1h", "price_outlook_1h",
"price_trend_2h", "price_outlook_2h",
"price_trend_3h", "price_outlook_3h",
"price_trend_4h", "price_outlook_4h",
"price_trend_5h", "price_outlook_5h",
"price_trend_6h", "price_outlook_6h",
"price_trend_8h", "price_outlook_8h",
"price_trend_12h", "price_outlook_12h",
"price_trajectory_2h",
"price_trajectory_3h",
"price_trajectory_4h",
"price_trajectory_5h",
"price_trajectory_6h",
"price_trajectory_8h",
"price_trajectory_12h",
] ]
for sensor_key in trend_sensors: for sensor_key in trend_sensors: