mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03:40 +00:00
feat(sensors): add 5-level price trend scale with configurable thresholds
Extends trend sensors from 3-level (rising/stable/falling) to 5-level scale (strongly_rising/rising/stable/falling/strongly_falling) for finer granularity. Changes: - Add PRICE_TREND_MAPPING with integer values (-2, -1, 0, +1, +2) matching PRICE_LEVEL_MAPPING pattern for consistent automation comparisons - Add configurable thresholds for strongly_rising (default: 6%) and strongly_falling (default: -6%) independent from base thresholds - Update calculate_price_trend() to return 3-tuple: (trend_state, diff_pct, trend_value) - Add trend_value attribute to all trend sensors for numeric comparisons - Update sensor entity descriptions with 5-level options - Add validation with cross-checks (strongly_rising > rising, etc.) - Update icons: chevron-double-up/down for strong trends, trending-up/down for normal Files changed: - const.py: PRICE_TREND_* constants, PRICE_TREND_MAPPING, config constants - utils/price.py: Extended calculate_price_trend() signature and return value - sensor/calculators/trend.py: Pass new thresholds, handle 3-tuple return - sensor/definitions.py: 5-level options for all 9 trend sensors - sensor/core.py: 5-level icon mapping - entity_utils/icons.py: 5-level trend icons - config_flow_handlers/: validators, schemas, options_flow for new settings - translations/*.json: Labels and error messages (en, de, nb, sv, nl) - tests/test_percentage_calculations.py: Updated for 3-tuple return Impact: Users get more nuanced trend information for automation decisions. New trend_value attribute enables numeric comparisons (e.g., > 0 for any rise). Existing automations using "rising"/"falling"/"stable" continue to work.
This commit is contained in:
parent
972cbce1d3
commit
5fc1f4db33
15 changed files with 510 additions and 137 deletions
|
|
@ -33,6 +33,8 @@ from custom_components.tibber_prices.config_flow_handlers.validators import (
|
||||||
validate_price_rating_thresholds,
|
validate_price_rating_thresholds,
|
||||||
validate_price_trend_falling,
|
validate_price_trend_falling,
|
||||||
validate_price_trend_rising,
|
validate_price_trend_rising,
|
||||||
|
validate_price_trend_strongly_falling,
|
||||||
|
validate_price_trend_strongly_rising,
|
||||||
validate_relaxation_attempts,
|
validate_relaxation_attempts,
|
||||||
validate_volatility_threshold_high,
|
validate_volatility_threshold_high,
|
||||||
validate_volatility_threshold_moderate,
|
validate_volatility_threshold_moderate,
|
||||||
|
|
@ -54,6 +56,8 @@ from custom_components.tibber_prices.const import (
|
||||||
CONF_PRICE_RATING_THRESHOLD_LOW,
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
||||||
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
||||||
CONF_PRICE_TREND_THRESHOLD_RISING,
|
CONF_PRICE_TREND_THRESHOLD_RISING,
|
||||||
|
CONF_PRICE_TREND_THRESHOLD_STRONGLY_FALLING,
|
||||||
|
CONF_PRICE_TREND_THRESHOLD_STRONGLY_RISING,
|
||||||
CONF_RELAXATION_ATTEMPTS_BEST,
|
CONF_RELAXATION_ATTEMPTS_BEST,
|
||||||
CONF_RELAXATION_ATTEMPTS_PEAK,
|
CONF_RELAXATION_ATTEMPTS_PEAK,
|
||||||
CONF_VOLATILITY_THRESHOLD_HIGH,
|
CONF_VOLATILITY_THRESHOLD_HIGH,
|
||||||
|
|
@ -493,6 +497,34 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
|
||||||
):
|
):
|
||||||
errors[CONF_PRICE_TREND_THRESHOLD_FALLING] = "invalid_price_trend_falling"
|
errors[CONF_PRICE_TREND_THRESHOLD_FALLING] = "invalid_price_trend_falling"
|
||||||
|
|
||||||
|
# Validate strongly rising trend threshold
|
||||||
|
if CONF_PRICE_TREND_THRESHOLD_STRONGLY_RISING in user_input and not validate_price_trend_strongly_rising(
|
||||||
|
user_input[CONF_PRICE_TREND_THRESHOLD_STRONGLY_RISING]
|
||||||
|
):
|
||||||
|
errors[CONF_PRICE_TREND_THRESHOLD_STRONGLY_RISING] = "invalid_price_trend_strongly_rising"
|
||||||
|
|
||||||
|
# Validate strongly falling trend threshold
|
||||||
|
if CONF_PRICE_TREND_THRESHOLD_STRONGLY_FALLING in user_input and not validate_price_trend_strongly_falling(
|
||||||
|
user_input[CONF_PRICE_TREND_THRESHOLD_STRONGLY_FALLING]
|
||||||
|
):
|
||||||
|
errors[CONF_PRICE_TREND_THRESHOLD_STRONGLY_FALLING] = "invalid_price_trend_strongly_falling"
|
||||||
|
|
||||||
|
# Cross-validation: Ensure rising < strongly_rising and falling > strongly_falling
|
||||||
|
if not errors:
|
||||||
|
rising = user_input.get(CONF_PRICE_TREND_THRESHOLD_RISING)
|
||||||
|
strongly_rising = user_input.get(CONF_PRICE_TREND_THRESHOLD_STRONGLY_RISING)
|
||||||
|
falling = user_input.get(CONF_PRICE_TREND_THRESHOLD_FALLING)
|
||||||
|
strongly_falling = user_input.get(CONF_PRICE_TREND_THRESHOLD_STRONGLY_FALLING)
|
||||||
|
|
||||||
|
if rising is not None and strongly_rising is not None and rising >= strongly_rising:
|
||||||
|
errors[CONF_PRICE_TREND_THRESHOLD_STRONGLY_RISING] = (
|
||||||
|
"invalid_trend_strongly_rising_less_than_rising"
|
||||||
|
)
|
||||||
|
if falling is not None and strongly_falling is not None and falling <= strongly_falling:
|
||||||
|
errors[CONF_PRICE_TREND_THRESHOLD_STRONGLY_FALLING] = (
|
||||||
|
"invalid_trend_strongly_falling_greater_than_falling"
|
||||||
|
)
|
||||||
|
|
||||||
if not errors:
|
if not errors:
|
||||||
# Store flat data directly in options (no section wrapping)
|
# Store flat data directly in options (no section wrapping)
|
||||||
self._options.update(user_input)
|
self._options.update(user_input)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ from custom_components.tibber_prices.const import (
|
||||||
CONF_PRICE_RATING_THRESHOLD_LOW,
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
||||||
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
||||||
CONF_PRICE_TREND_THRESHOLD_RISING,
|
CONF_PRICE_TREND_THRESHOLD_RISING,
|
||||||
|
CONF_PRICE_TREND_THRESHOLD_STRONGLY_FALLING,
|
||||||
|
CONF_PRICE_TREND_THRESHOLD_STRONGLY_RISING,
|
||||||
CONF_RELAXATION_ATTEMPTS_BEST,
|
CONF_RELAXATION_ATTEMPTS_BEST,
|
||||||
CONF_RELAXATION_ATTEMPTS_PEAK,
|
CONF_RELAXATION_ATTEMPTS_PEAK,
|
||||||
CONF_VIRTUAL_TIME_OFFSET_DAYS,
|
CONF_VIRTUAL_TIME_OFFSET_DAYS,
|
||||||
|
|
@ -66,6 +68,8 @@ from custom_components.tibber_prices.const import (
|
||||||
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
||||||
DEFAULT_PRICE_TREND_THRESHOLD_FALLING,
|
DEFAULT_PRICE_TREND_THRESHOLD_FALLING,
|
||||||
DEFAULT_PRICE_TREND_THRESHOLD_RISING,
|
DEFAULT_PRICE_TREND_THRESHOLD_RISING,
|
||||||
|
DEFAULT_PRICE_TREND_THRESHOLD_STRONGLY_FALLING,
|
||||||
|
DEFAULT_PRICE_TREND_THRESHOLD_STRONGLY_RISING,
|
||||||
DEFAULT_RELAXATION_ATTEMPTS_BEST,
|
DEFAULT_RELAXATION_ATTEMPTS_BEST,
|
||||||
DEFAULT_RELAXATION_ATTEMPTS_PEAK,
|
DEFAULT_RELAXATION_ATTEMPTS_PEAK,
|
||||||
DEFAULT_VIRTUAL_TIME_OFFSET_DAYS,
|
DEFAULT_VIRTUAL_TIME_OFFSET_DAYS,
|
||||||
|
|
@ -86,6 +90,8 @@ from custom_components.tibber_prices.const import (
|
||||||
MAX_PRICE_RATING_THRESHOLD_LOW,
|
MAX_PRICE_RATING_THRESHOLD_LOW,
|
||||||
MAX_PRICE_TREND_FALLING,
|
MAX_PRICE_TREND_FALLING,
|
||||||
MAX_PRICE_TREND_RISING,
|
MAX_PRICE_TREND_RISING,
|
||||||
|
MAX_PRICE_TREND_STRONGLY_FALLING,
|
||||||
|
MAX_PRICE_TREND_STRONGLY_RISING,
|
||||||
MAX_RELAXATION_ATTEMPTS,
|
MAX_RELAXATION_ATTEMPTS,
|
||||||
MAX_VOLATILITY_THRESHOLD_HIGH,
|
MAX_VOLATILITY_THRESHOLD_HIGH,
|
||||||
MAX_VOLATILITY_THRESHOLD_MODERATE,
|
MAX_VOLATILITY_THRESHOLD_MODERATE,
|
||||||
|
|
@ -99,6 +105,8 @@ from custom_components.tibber_prices.const import (
|
||||||
MIN_PRICE_RATING_THRESHOLD_LOW,
|
MIN_PRICE_RATING_THRESHOLD_LOW,
|
||||||
MIN_PRICE_TREND_FALLING,
|
MIN_PRICE_TREND_FALLING,
|
||||||
MIN_PRICE_TREND_RISING,
|
MIN_PRICE_TREND_RISING,
|
||||||
|
MIN_PRICE_TREND_STRONGLY_FALLING,
|
||||||
|
MIN_PRICE_TREND_STRONGLY_RISING,
|
||||||
MIN_RELAXATION_ATTEMPTS,
|
MIN_RELAXATION_ATTEMPTS,
|
||||||
MIN_VOLATILITY_THRESHOLD_HIGH,
|
MIN_VOLATILITY_THRESHOLD_HIGH,
|
||||||
MIN_VOLATILITY_THRESHOLD_MODERATE,
|
MIN_VOLATILITY_THRESHOLD_MODERATE,
|
||||||
|
|
@ -745,6 +753,23 @@ def get_price_trend_schema(options: Mapping[str, Any]) -> vol.Schema:
|
||||||
mode=NumberSelectorMode.SLIDER,
|
mode=NumberSelectorMode.SLIDER,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
vol.Optional(
|
||||||
|
CONF_PRICE_TREND_THRESHOLD_STRONGLY_RISING,
|
||||||
|
default=int(
|
||||||
|
options.get(
|
||||||
|
CONF_PRICE_TREND_THRESHOLD_STRONGLY_RISING,
|
||||||
|
DEFAULT_PRICE_TREND_THRESHOLD_STRONGLY_RISING,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
): NumberSelector(
|
||||||
|
NumberSelectorConfig(
|
||||||
|
min=MIN_PRICE_TREND_STRONGLY_RISING,
|
||||||
|
max=MAX_PRICE_TREND_STRONGLY_RISING,
|
||||||
|
step=1,
|
||||||
|
unit_of_measurement="%",
|
||||||
|
mode=NumberSelectorMode.SLIDER,
|
||||||
|
),
|
||||||
|
),
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
||||||
default=int(
|
default=int(
|
||||||
|
|
@ -762,6 +787,23 @@ def get_price_trend_schema(options: Mapping[str, Any]) -> vol.Schema:
|
||||||
mode=NumberSelectorMode.SLIDER,
|
mode=NumberSelectorMode.SLIDER,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
vol.Optional(
|
||||||
|
CONF_PRICE_TREND_THRESHOLD_STRONGLY_FALLING,
|
||||||
|
default=int(
|
||||||
|
options.get(
|
||||||
|
CONF_PRICE_TREND_THRESHOLD_STRONGLY_FALLING,
|
||||||
|
DEFAULT_PRICE_TREND_THRESHOLD_STRONGLY_FALLING,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
): NumberSelector(
|
||||||
|
NumberSelectorConfig(
|
||||||
|
min=MIN_PRICE_TREND_STRONGLY_FALLING,
|
||||||
|
max=MAX_PRICE_TREND_STRONGLY_FALLING,
|
||||||
|
step=1,
|
||||||
|
unit_of_measurement="%",
|
||||||
|
mode=NumberSelectorMode.SLIDER,
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ from custom_components.tibber_prices.const import (
|
||||||
MAX_PRICE_RATING_THRESHOLD_LOW,
|
MAX_PRICE_RATING_THRESHOLD_LOW,
|
||||||
MAX_PRICE_TREND_FALLING,
|
MAX_PRICE_TREND_FALLING,
|
||||||
MAX_PRICE_TREND_RISING,
|
MAX_PRICE_TREND_RISING,
|
||||||
|
MAX_PRICE_TREND_STRONGLY_FALLING,
|
||||||
|
MAX_PRICE_TREND_STRONGLY_RISING,
|
||||||
MAX_RELAXATION_ATTEMPTS,
|
MAX_RELAXATION_ATTEMPTS,
|
||||||
MAX_VOLATILITY_THRESHOLD_HIGH,
|
MAX_VOLATILITY_THRESHOLD_HIGH,
|
||||||
MAX_VOLATILITY_THRESHOLD_MODERATE,
|
MAX_VOLATILITY_THRESHOLD_MODERATE,
|
||||||
|
|
@ -30,6 +32,8 @@ from custom_components.tibber_prices.const import (
|
||||||
MIN_PRICE_RATING_THRESHOLD_LOW,
|
MIN_PRICE_RATING_THRESHOLD_LOW,
|
||||||
MIN_PRICE_TREND_FALLING,
|
MIN_PRICE_TREND_FALLING,
|
||||||
MIN_PRICE_TREND_RISING,
|
MIN_PRICE_TREND_RISING,
|
||||||
|
MIN_PRICE_TREND_STRONGLY_FALLING,
|
||||||
|
MIN_PRICE_TREND_STRONGLY_RISING,
|
||||||
MIN_RELAXATION_ATTEMPTS,
|
MIN_RELAXATION_ATTEMPTS,
|
||||||
MIN_VOLATILITY_THRESHOLD_HIGH,
|
MIN_VOLATILITY_THRESHOLD_HIGH,
|
||||||
MIN_VOLATILITY_THRESHOLD_MODERATE,
|
MIN_VOLATILITY_THRESHOLD_MODERATE,
|
||||||
|
|
@ -337,3 +341,31 @@ def validate_price_trend_falling(threshold: int) -> bool:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return MIN_PRICE_TREND_FALLING <= threshold <= MAX_PRICE_TREND_FALLING
|
return MIN_PRICE_TREND_FALLING <= threshold <= MAX_PRICE_TREND_FALLING
|
||||||
|
|
||||||
|
|
||||||
|
def validate_price_trend_strongly_rising(threshold: int) -> bool:
|
||||||
|
"""
|
||||||
|
Validate strongly rising price trend threshold.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
threshold: Strongly rising trend threshold percentage (2 to 100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if threshold is valid (MIN_PRICE_TREND_STRONGLY_RISING to MAX_PRICE_TREND_STRONGLY_RISING)
|
||||||
|
|
||||||
|
"""
|
||||||
|
return MIN_PRICE_TREND_STRONGLY_RISING <= threshold <= MAX_PRICE_TREND_STRONGLY_RISING
|
||||||
|
|
||||||
|
|
||||||
|
def validate_price_trend_strongly_falling(threshold: int) -> bool:
|
||||||
|
"""
|
||||||
|
Validate strongly falling price trend threshold.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
threshold: Strongly falling trend threshold percentage (-100 to -2)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if threshold is valid (MIN_PRICE_TREND_STRONGLY_FALLING to MAX_PRICE_TREND_STRONGLY_FALLING)
|
||||||
|
|
||||||
|
"""
|
||||||
|
return MIN_PRICE_TREND_STRONGLY_FALLING <= threshold <= MAX_PRICE_TREND_STRONGLY_FALLING
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ CONF_PRICE_LEVEL_GAP_TOLERANCE = "price_level_gap_tolerance"
|
||||||
CONF_AVERAGE_SENSOR_DISPLAY = "average_sensor_display" # "median" or "mean"
|
CONF_AVERAGE_SENSOR_DISPLAY = "average_sensor_display" # "median" or "mean"
|
||||||
CONF_PRICE_TREND_THRESHOLD_RISING = "price_trend_threshold_rising"
|
CONF_PRICE_TREND_THRESHOLD_RISING = "price_trend_threshold_rising"
|
||||||
CONF_PRICE_TREND_THRESHOLD_FALLING = "price_trend_threshold_falling"
|
CONF_PRICE_TREND_THRESHOLD_FALLING = "price_trend_threshold_falling"
|
||||||
|
CONF_PRICE_TREND_THRESHOLD_STRONGLY_RISING = "price_trend_threshold_strongly_rising"
|
||||||
|
CONF_PRICE_TREND_THRESHOLD_STRONGLY_FALLING = "price_trend_threshold_strongly_falling"
|
||||||
CONF_VOLATILITY_THRESHOLD_MODERATE = "volatility_threshold_moderate"
|
CONF_VOLATILITY_THRESHOLD_MODERATE = "volatility_threshold_moderate"
|
||||||
CONF_VOLATILITY_THRESHOLD_HIGH = "volatility_threshold_high"
|
CONF_VOLATILITY_THRESHOLD_HIGH = "volatility_threshold_high"
|
||||||
CONF_VOLATILITY_THRESHOLD_VERY_HIGH = "volatility_threshold_very_high"
|
CONF_VOLATILITY_THRESHOLD_VERY_HIGH = "volatility_threshold_very_high"
|
||||||
|
|
@ -101,6 +103,10 @@ DEFAULT_PRICE_LEVEL_GAP_TOLERANCE = 1 # Max consecutive intervals to smooth out
|
||||||
DEFAULT_AVERAGE_SENSOR_DISPLAY = "median" # Default: show median in state, mean in attributes
|
DEFAULT_AVERAGE_SENSOR_DISPLAY = "median" # Default: show median in state, mean in attributes
|
||||||
DEFAULT_PRICE_TREND_THRESHOLD_RISING = 3 # Default trend threshold for rising prices (%)
|
DEFAULT_PRICE_TREND_THRESHOLD_RISING = 3 # Default trend threshold for rising prices (%)
|
||||||
DEFAULT_PRICE_TREND_THRESHOLD_FALLING = -3 # Default trend threshold for falling prices (%, negative value)
|
DEFAULT_PRICE_TREND_THRESHOLD_FALLING = -3 # Default trend threshold for falling prices (%, negative value)
|
||||||
|
# Strong trend thresholds default to 2x the base threshold.
|
||||||
|
# These are independently configurable to allow fine-tuning of "strongly" detection.
|
||||||
|
DEFAULT_PRICE_TREND_THRESHOLD_STRONGLY_RISING = 6 # Default strong rising threshold (%)
|
||||||
|
DEFAULT_PRICE_TREND_THRESHOLD_STRONGLY_FALLING = -6 # Default strong falling threshold (%, negative value)
|
||||||
# Default volatility thresholds (relative values using coefficient of variation)
|
# Default volatility thresholds (relative values using coefficient of variation)
|
||||||
# Coefficient of variation = (standard_deviation / mean) * 100%
|
# Coefficient of variation = (standard_deviation / mean) * 100%
|
||||||
# These thresholds are unitless and work across different price levels
|
# These thresholds are unitless and work across different price levels
|
||||||
|
|
@ -161,6 +167,11 @@ MIN_PRICE_TREND_RISING = 1 # Minimum rising trend threshold
|
||||||
MAX_PRICE_TREND_RISING = 50 # Maximum rising trend threshold
|
MAX_PRICE_TREND_RISING = 50 # Maximum rising trend threshold
|
||||||
MIN_PRICE_TREND_FALLING = -50 # Minimum falling trend threshold (negative)
|
MIN_PRICE_TREND_FALLING = -50 # Minimum falling trend threshold (negative)
|
||||||
MAX_PRICE_TREND_FALLING = -1 # Maximum falling trend threshold (negative)
|
MAX_PRICE_TREND_FALLING = -1 # Maximum falling trend threshold (negative)
|
||||||
|
# Strong trend thresholds have higher ranges to allow detection of significant moves
|
||||||
|
MIN_PRICE_TREND_STRONGLY_RISING = 2 # Minimum strongly rising threshold (must be > rising)
|
||||||
|
MAX_PRICE_TREND_STRONGLY_RISING = 100 # Maximum strongly rising threshold
|
||||||
|
MIN_PRICE_TREND_STRONGLY_FALLING = -100 # Minimum strongly falling threshold (negative)
|
||||||
|
MAX_PRICE_TREND_STRONGLY_FALLING = -2 # Maximum strongly falling threshold (must be < falling)
|
||||||
|
|
||||||
# Gap count and relaxation limits
|
# Gap count and relaxation limits
|
||||||
MIN_GAP_COUNT = 0 # Minimum gap count
|
MIN_GAP_COUNT = 0 # Minimum gap count
|
||||||
|
|
@ -447,6 +458,14 @@ VOLATILITY_MODERATE = "MODERATE"
|
||||||
VOLATILITY_HIGH = "HIGH"
|
VOLATILITY_HIGH = "HIGH"
|
||||||
VOLATILITY_VERY_HIGH = "VERY_HIGH"
|
VOLATILITY_VERY_HIGH = "VERY_HIGH"
|
||||||
|
|
||||||
|
# Price trend constants (calculated values with 5-level scale)
|
||||||
|
# Used by trend sensors: momentary, short-term, mid-term, long-term
|
||||||
|
PRICE_TREND_STRONGLY_FALLING = "strongly_falling"
|
||||||
|
PRICE_TREND_FALLING = "falling"
|
||||||
|
PRICE_TREND_STABLE = "stable"
|
||||||
|
PRICE_TREND_RISING = "rising"
|
||||||
|
PRICE_TREND_STRONGLY_RISING = "strongly_rising"
|
||||||
|
|
||||||
# Sensor options (lowercase versions for ENUM device class)
|
# Sensor options (lowercase versions for ENUM device class)
|
||||||
# NOTE: These constants define the valid enum options, but they are not used directly
|
# NOTE: These constants define the valid enum options, but they are not used directly
|
||||||
# in sensor/definitions.py due to import timing issues. Instead, the options are defined inline
|
# in sensor/definitions.py due to import timing issues. Instead, the options are defined inline
|
||||||
|
|
@ -472,6 +491,15 @@ VOLATILITY_OPTIONS = [
|
||||||
VOLATILITY_VERY_HIGH.lower(),
|
VOLATILITY_VERY_HIGH.lower(),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Trend options for enum sensors (lowercase versions for ENUM device class)
|
||||||
|
PRICE_TREND_OPTIONS = [
|
||||||
|
PRICE_TREND_STRONGLY_FALLING,
|
||||||
|
PRICE_TREND_FALLING,
|
||||||
|
PRICE_TREND_STABLE,
|
||||||
|
PRICE_TREND_RISING,
|
||||||
|
PRICE_TREND_STRONGLY_RISING,
|
||||||
|
]
|
||||||
|
|
||||||
# Valid options for best price maximum level filter
|
# Valid options for best price maximum level filter
|
||||||
# Sorted from cheap to expensive: user selects "up to how expensive"
|
# Sorted from cheap to expensive: user selects "up to how expensive"
|
||||||
BEST_PRICE_MAX_LEVEL_OPTIONS = [
|
BEST_PRICE_MAX_LEVEL_OPTIONS = [
|
||||||
|
|
@ -514,6 +542,16 @@ PRICE_RATING_MAPPING = {
|
||||||
PRICE_RATING_HIGH: 1,
|
PRICE_RATING_HIGH: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Mapping for comparing price trends (used for sorting and automation comparisons)
|
||||||
|
# Values range from -2 (strongly falling) to +2 (strongly rising), with 0 = stable
|
||||||
|
PRICE_TREND_MAPPING = {
|
||||||
|
PRICE_TREND_STRONGLY_FALLING: -2,
|
||||||
|
PRICE_TREND_FALLING: -1,
|
||||||
|
PRICE_TREND_STABLE: 0,
|
||||||
|
PRICE_TREND_RISING: 1,
|
||||||
|
PRICE_TREND_STRONGLY_RISING: 2,
|
||||||
|
}
|
||||||
|
|
||||||
# Icon mapping for price levels (dynamic icons based on level)
|
# Icon mapping for price levels (dynamic icons based on level)
|
||||||
PRICE_LEVEL_ICON_MAPPING = {
|
PRICE_LEVEL_ICON_MAPPING = {
|
||||||
PRICE_LEVEL_VERY_CHEAP: "mdi:gauge-empty",
|
PRICE_LEVEL_VERY_CHEAP: "mdi:gauge-empty",
|
||||||
|
|
|
||||||
|
|
@ -85,19 +85,25 @@ def get_dynamic_icon(
|
||||||
|
|
||||||
|
|
||||||
def get_trend_icon(key: str, value: Any) -> str | None:
|
def get_trend_icon(key: str, value: Any) -> str | None:
|
||||||
"""Get icon for trend sensors."""
|
"""Get icon for trend sensors using 5-level trend scale."""
|
||||||
# Handle next_price_trend_change TIMESTAMP sensor differently
|
# Handle next_price_trend_change TIMESTAMP sensor differently
|
||||||
# (icon based on attributes, not value which is a timestamp)
|
# (icon based on attributes, not value which is a timestamp)
|
||||||
if key == "next_price_trend_change":
|
if key == "next_price_trend_change":
|
||||||
return None # Will be handled by sensor's icon property using attributes
|
return None # Will be handled by sensor's icon property using attributes
|
||||||
|
|
||||||
if not key.startswith("price_trend_") or not isinstance(value, str):
|
if not key.startswith("price_trend_") and key != "current_price_trend":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if not isinstance(value, str):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 5-level trend icons: strongly uses double arrows, normal uses single
|
||||||
trend_icons = {
|
trend_icons = {
|
||||||
"rising": "mdi:trending-up",
|
"strongly_rising": "mdi:chevron-double-up", # Strong upward movement
|
||||||
"falling": "mdi:trending-down",
|
"rising": "mdi:trending-up", # Normal upward trend
|
||||||
"stable": "mdi:trending-neutral",
|
"stable": "mdi:trending-neutral", # No significant change
|
||||||
|
"falling": "mdi:trending-down", # Normal downward trend
|
||||||
|
"strongly_falling": "mdi:chevron-double-down", # Strong downward movement
|
||||||
}
|
}
|
||||||
return trend_icons.get(value)
|
return trend_icons.get(value)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
# Get configured thresholds from options
|
# Get configured thresholds from options
|
||||||
threshold_rising = self.config.get("price_trend_threshold_rising", 5.0)
|
threshold_rising = self.config.get("price_trend_threshold_rising", 5.0)
|
||||||
threshold_falling = self.config.get("price_trend_threshold_falling", -5.0)
|
threshold_falling = self.config.get("price_trend_threshold_falling", -5.0)
|
||||||
|
threshold_strongly_rising = self.config.get("price_trend_threshold_strongly_rising", 6.0)
|
||||||
|
threshold_strongly_falling = self.config.get("price_trend_threshold_strongly_falling", -6.0)
|
||||||
volatility_threshold_moderate = self.config.get("volatility_threshold_moderate", 15.0)
|
volatility_threshold_moderate = self.config.get("volatility_threshold_moderate", 15.0)
|
||||||
volatility_threshold_high = self.config.get("volatility_threshold_high", 30.0)
|
volatility_threshold_high = self.config.get("volatility_threshold_high", 30.0)
|
||||||
|
|
||||||
|
|
@ -115,11 +117,13 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
lookahead_intervals = self.coordinator.time.minutes_to_intervals(hours * 60)
|
lookahead_intervals = self.coordinator.time.minutes_to_intervals(hours * 60)
|
||||||
|
|
||||||
# Calculate trend with volatility-adaptive thresholds
|
# Calculate trend with volatility-adaptive thresholds
|
||||||
trend_state, diff_pct = calculate_price_trend(
|
trend_state, diff_pct, trend_value = calculate_price_trend(
|
||||||
current_interval_price,
|
current_interval_price,
|
||||||
future_mean,
|
future_mean,
|
||||||
threshold_rising=threshold_rising,
|
threshold_rising=threshold_rising,
|
||||||
threshold_falling=threshold_falling,
|
threshold_falling=threshold_falling,
|
||||||
|
threshold_strongly_rising=threshold_strongly_rising,
|
||||||
|
threshold_strongly_falling=threshold_strongly_falling,
|
||||||
volatility_adjustment=True, # Always enabled
|
volatility_adjustment=True, # Always enabled
|
||||||
lookahead_intervals=lookahead_intervals,
|
lookahead_intervals=lookahead_intervals,
|
||||||
all_intervals=all_intervals,
|
all_intervals=all_intervals,
|
||||||
|
|
@ -127,11 +131,14 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
volatility_threshold_high=volatility_threshold_high,
|
volatility_threshold_high=volatility_threshold_high,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Determine icon color based on trend state
|
# Determine icon color based on trend state (5-level scale)
|
||||||
|
# Strongly rising/falling uses more intense colors
|
||||||
icon_color = {
|
icon_color = {
|
||||||
"rising": "var(--error-color)", # Red/Orange for rising prices (expensive)
|
"strongly_rising": "var(--error-color)", # Red for strongly rising (very expensive)
|
||||||
"falling": "var(--success-color)", # Green for falling prices (cheaper)
|
"rising": "var(--warning-color)", # Orange/Yellow for rising prices
|
||||||
"stable": "var(--state-icon-color)", # Default gray for stable prices
|
"stable": "var(--state-icon-color)", # Default gray for stable prices
|
||||||
|
"falling": "var(--success-color)", # Green for falling prices (cheaper)
|
||||||
|
"strongly_falling": "var(--success-color)", # Green for strongly falling (great deal)
|
||||||
}.get(trend_state, "var(--state-icon-color)")
|
}.get(trend_state, "var(--state-icon-color)")
|
||||||
|
|
||||||
# Convert prices to display currency unit based on configuration
|
# Convert prices to display currency unit based on configuration
|
||||||
|
|
@ -140,6 +147,7 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
# Store attributes in sensor-specific dictionary AND cache the trend value
|
# Store attributes in sensor-specific dictionary AND cache the trend value
|
||||||
self._trend_attributes = {
|
self._trend_attributes = {
|
||||||
"timestamp": next_interval_start,
|
"timestamp": next_interval_start,
|
||||||
|
"trend_value": trend_value,
|
||||||
f"trend_{hours}h_%": round(diff_pct, 1),
|
f"trend_{hours}h_%": round(diff_pct, 1),
|
||||||
f"next_{hours}h_avg": round(future_mean * factor, 2),
|
f"next_{hours}h_avg": round(future_mean * factor, 2),
|
||||||
"interval_count": lookahead_intervals,
|
"interval_count": lookahead_intervals,
|
||||||
|
|
@ -414,6 +422,8 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
return {
|
return {
|
||||||
"rising": self.config.get("price_trend_threshold_rising", 5.0),
|
"rising": self.config.get("price_trend_threshold_rising", 5.0),
|
||||||
"falling": self.config.get("price_trend_threshold_falling", -5.0),
|
"falling": self.config.get("price_trend_threshold_falling", -5.0),
|
||||||
|
"strongly_rising": self.config.get("price_trend_threshold_strongly_rising", 6.0),
|
||||||
|
"strongly_falling": self.config.get("price_trend_threshold_strongly_falling", -6.0),
|
||||||
"moderate": self.config.get("volatility_threshold_moderate", 15.0),
|
"moderate": self.config.get("volatility_threshold_moderate", 15.0),
|
||||||
"high": self.config.get("volatility_threshold_high", 30.0),
|
"high": self.config.get("volatility_threshold_high", 30.0),
|
||||||
}
|
}
|
||||||
|
|
@ -428,7 +438,7 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
current_index: Index of current interval
|
current_index: Index of current interval
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Momentum direction: "rising", "falling", or "stable"
|
Momentum direction: "strongly_rising", "rising", "stable", "falling", or "strongly_falling"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Look back 1 hour (4 intervals) for quick reaction
|
# Look back 1 hour (4 intervals) for quick reaction
|
||||||
|
|
@ -451,15 +461,25 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
weighted_sum = sum(price * weight for price, weight in zip(trailing_prices, weights, strict=True))
|
weighted_sum = sum(price * weight for price, weight in zip(trailing_prices, weights, strict=True))
|
||||||
weighted_avg = weighted_sum / sum(weights)
|
weighted_avg = weighted_sum / sum(weights)
|
||||||
|
|
||||||
# Calculate momentum with 3% threshold
|
# Calculate momentum with thresholds
|
||||||
|
# Using same logic as 5-level trend: 3% for normal, 6% (2x) for strong
|
||||||
momentum_threshold = 0.03
|
momentum_threshold = 0.03
|
||||||
diff = (current_price - weighted_avg) / weighted_avg
|
strong_momentum_threshold = 0.06
|
||||||
|
diff = (current_price - weighted_avg) / abs(weighted_avg) if weighted_avg != 0 else 0
|
||||||
|
|
||||||
if diff > momentum_threshold:
|
# Determine momentum level based on thresholds
|
||||||
return "rising"
|
if diff >= strong_momentum_threshold:
|
||||||
if diff < -momentum_threshold:
|
momentum = "strongly_rising"
|
||||||
return "falling"
|
elif diff > momentum_threshold:
|
||||||
return "stable"
|
momentum = "rising"
|
||||||
|
elif diff <= -strong_momentum_threshold:
|
||||||
|
momentum = "strongly_falling"
|
||||||
|
elif diff < -momentum_threshold:
|
||||||
|
momentum = "falling"
|
||||||
|
else:
|
||||||
|
momentum = "stable"
|
||||||
|
|
||||||
|
return momentum
|
||||||
|
|
||||||
def _combine_momentum_with_future(
|
def _combine_momentum_with_future(
|
||||||
self,
|
self,
|
||||||
|
|
@ -472,43 +492,60 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
"""
|
"""
|
||||||
Combine momentum analysis with future outlook to determine final trend.
|
Combine momentum analysis with future outlook to determine final trend.
|
||||||
|
|
||||||
|
Uses 5-level scale: strongly_rising, rising, stable, falling, strongly_falling.
|
||||||
|
Momentum intensity is preserved when future confirms the trend direction.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
current_momentum: Current momentum direction (rising/falling/stable)
|
current_momentum: Current momentum direction (5-level scale)
|
||||||
current_price: Current interval price
|
current_price: Current interval price
|
||||||
future_mean: Average price in future window
|
future_mean: Average price in future window
|
||||||
context: Dict with all_intervals, current_index, lookahead_intervals, thresholds
|
context: Dict with all_intervals, current_index, lookahead_intervals, thresholds
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Final trend direction: "rising", "falling", or "stable"
|
Final trend direction (5-level scale)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if current_momentum == "rising":
|
# Use calculate_price_trend for consistency with 5-level logic
|
||||||
# We're in uptrend - does it continue?
|
|
||||||
return "rising" if future_mean >= current_price * 0.98 else "falling"
|
|
||||||
|
|
||||||
if current_momentum == "falling":
|
|
||||||
# We're in downtrend - does it continue?
|
|
||||||
return "falling" if future_mean <= current_price * 1.02 else "rising"
|
|
||||||
|
|
||||||
# current_momentum == "stable" - what's coming?
|
|
||||||
all_intervals = context["all_intervals"]
|
all_intervals = context["all_intervals"]
|
||||||
current_index = context["current_index"]
|
current_index = context["current_index"]
|
||||||
lookahead_intervals = context["lookahead_intervals"]
|
lookahead_intervals = context["lookahead_intervals"]
|
||||||
thresholds = context["thresholds"]
|
thresholds = context["thresholds"]
|
||||||
|
|
||||||
lookahead_for_volatility = all_intervals[current_index : current_index + lookahead_intervals]
|
lookahead_for_volatility = all_intervals[current_index : current_index + lookahead_intervals]
|
||||||
trend_state, _ = calculate_price_trend(
|
future_trend, _, _ = calculate_price_trend(
|
||||||
current_price,
|
current_price,
|
||||||
future_mean,
|
future_mean,
|
||||||
threshold_rising=thresholds["rising"],
|
threshold_rising=thresholds["rising"],
|
||||||
threshold_falling=thresholds["falling"],
|
threshold_falling=thresholds["falling"],
|
||||||
|
threshold_strongly_rising=thresholds["strongly_rising"],
|
||||||
|
threshold_strongly_falling=thresholds["strongly_falling"],
|
||||||
volatility_adjustment=True,
|
volatility_adjustment=True,
|
||||||
lookahead_intervals=lookahead_intervals,
|
lookahead_intervals=lookahead_intervals,
|
||||||
all_intervals=lookahead_for_volatility,
|
all_intervals=lookahead_for_volatility,
|
||||||
volatility_threshold_moderate=thresholds["moderate"],
|
volatility_threshold_moderate=thresholds["moderate"],
|
||||||
volatility_threshold_high=thresholds["high"],
|
volatility_threshold_high=thresholds["high"],
|
||||||
)
|
)
|
||||||
return trend_state
|
|
||||||
|
# Check if momentum and future trend are aligned (same direction)
|
||||||
|
momentum_rising = current_momentum in ("rising", "strongly_rising")
|
||||||
|
momentum_falling = current_momentum in ("falling", "strongly_falling")
|
||||||
|
future_rising = future_trend in ("rising", "strongly_rising")
|
||||||
|
future_falling = future_trend in ("falling", "strongly_falling")
|
||||||
|
|
||||||
|
if momentum_rising and future_rising:
|
||||||
|
# Both indicate rising - use the stronger signal
|
||||||
|
if current_momentum == "strongly_rising" or future_trend == "strongly_rising":
|
||||||
|
return "strongly_rising"
|
||||||
|
return "rising"
|
||||||
|
|
||||||
|
if momentum_falling and future_falling:
|
||||||
|
# Both indicate falling - use the stronger signal
|
||||||
|
if current_momentum == "strongly_falling" or future_trend == "strongly_falling":
|
||||||
|
return "strongly_falling"
|
||||||
|
return "falling"
|
||||||
|
|
||||||
|
# Conflicting signals or stable momentum - trust future trend calculation
|
||||||
|
return future_trend
|
||||||
|
|
||||||
def _calculate_standard_trend(
|
def _calculate_standard_trend(
|
||||||
self,
|
self,
|
||||||
|
|
@ -534,11 +571,13 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
current_price = float(current_interval["total"])
|
current_price = float(current_interval["total"])
|
||||||
|
|
||||||
standard_lookahead_volatility = all_intervals[current_index : current_index + standard_lookahead]
|
standard_lookahead_volatility = all_intervals[current_index : current_index + standard_lookahead]
|
||||||
current_trend_3h, _ = calculate_price_trend(
|
current_trend_3h, _, _ = calculate_price_trend(
|
||||||
current_price,
|
current_price,
|
||||||
standard_future_mean,
|
standard_future_mean,
|
||||||
threshold_rising=thresholds["rising"],
|
threshold_rising=thresholds["rising"],
|
||||||
threshold_falling=thresholds["falling"],
|
threshold_falling=thresholds["falling"],
|
||||||
|
threshold_strongly_rising=thresholds["strongly_rising"],
|
||||||
|
threshold_strongly_falling=thresholds["strongly_falling"],
|
||||||
volatility_adjustment=True,
|
volatility_adjustment=True,
|
||||||
lookahead_intervals=standard_lookahead,
|
lookahead_intervals=standard_lookahead,
|
||||||
all_intervals=standard_lookahead_volatility,
|
all_intervals=standard_lookahead_volatility,
|
||||||
|
|
@ -606,11 +645,13 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
|
|
||||||
# Calculate trend at this past point
|
# Calculate trend at this past point
|
||||||
lookahead_for_volatility = all_intervals[i : i + intervals_in_3h]
|
lookahead_for_volatility = all_intervals[i : i + intervals_in_3h]
|
||||||
trend_state, _ = calculate_price_trend(
|
trend_state, _, _ = calculate_price_trend(
|
||||||
price,
|
price,
|
||||||
future_mean,
|
future_mean,
|
||||||
threshold_rising=thresholds["rising"],
|
threshold_rising=thresholds["rising"],
|
||||||
threshold_falling=thresholds["falling"],
|
threshold_falling=thresholds["falling"],
|
||||||
|
threshold_strongly_rising=thresholds["strongly_rising"],
|
||||||
|
threshold_strongly_falling=thresholds["strongly_falling"],
|
||||||
volatility_adjustment=True,
|
volatility_adjustment=True,
|
||||||
lookahead_intervals=intervals_in_3h,
|
lookahead_intervals=intervals_in_3h,
|
||||||
all_intervals=lookahead_for_volatility,
|
all_intervals=lookahead_for_volatility,
|
||||||
|
|
@ -678,11 +719,13 @@ class TibberPricesTrendCalculator(TibberPricesBaseCalculator):
|
||||||
|
|
||||||
# Calculate trend at this future point
|
# Calculate trend at this future point
|
||||||
lookahead_for_volatility = all_intervals[i : i + intervals_in_3h]
|
lookahead_for_volatility = all_intervals[i : i + intervals_in_3h]
|
||||||
trend_state, _ = calculate_price_trend(
|
trend_state, _, _ = calculate_price_trend(
|
||||||
current_price,
|
current_price,
|
||||||
future_mean,
|
future_mean,
|
||||||
threshold_rising=thresholds["rising"],
|
threshold_rising=thresholds["rising"],
|
||||||
threshold_falling=thresholds["falling"],
|
threshold_falling=thresholds["falling"],
|
||||||
|
threshold_strongly_rising=thresholds["strongly_rising"],
|
||||||
|
threshold_strongly_falling=thresholds["strongly_falling"],
|
||||||
volatility_adjustment=True,
|
volatility_adjustment=True,
|
||||||
lookahead_intervals=intervals_in_3h,
|
lookahead_intervals=intervals_in_3h,
|
||||||
all_intervals=lookahead_for_volatility,
|
all_intervals=lookahead_for_volatility,
|
||||||
|
|
|
||||||
|
|
@ -987,11 +987,13 @@ class TibberPricesSensor(TibberPricesEntity, RestoreSensor):
|
||||||
key = self.entity_description.key
|
key = self.entity_description.key
|
||||||
value = self.native_value
|
value = self.native_value
|
||||||
|
|
||||||
# Icon mapping for trend directions
|
# Icon mapping for trend directions (5-level scale)
|
||||||
trend_icons = {
|
trend_icons = {
|
||||||
|
"strongly_rising": "mdi:chevron-double-up",
|
||||||
"rising": "mdi:trending-up",
|
"rising": "mdi:trending-up",
|
||||||
"falling": "mdi:trending-down",
|
|
||||||
"stable": "mdi:trending-neutral",
|
"stable": "mdi:trending-neutral",
|
||||||
|
"falling": "mdi:trending-down",
|
||||||
|
"strongly_falling": "mdi:chevron-double-down",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Special handling for next_price_trend_change: Icon based on direction attribute
|
# Special handling for next_price_trend_change: Icon based on direction attribute
|
||||||
|
|
|
||||||
|
|
@ -548,7 +548,7 @@ FUTURE_TREND_SENSORS = (
|
||||||
icon="mdi:trending-up", # Dynamic: trending-up/trending-down/trending-neutral based on current trend
|
icon="mdi:trending-up", # Dynamic: trending-up/trending-down/trending-neutral based on current trend
|
||||||
device_class=SensorDeviceClass.ENUM,
|
device_class=SensorDeviceClass.ENUM,
|
||||||
state_class=None, # Enum values: no statistics
|
state_class=None, # Enum values: no statistics
|
||||||
options=["rising", "falling", "stable"],
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
),
|
),
|
||||||
# Next trend change sensor (when will trend change?)
|
# Next trend change sensor (when will trend change?)
|
||||||
|
|
@ -570,7 +570,7 @@ FUTURE_TREND_SENSORS = (
|
||||||
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
|
||||||
options=["rising", "falling", "stable"],
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
|
|
@ -580,7 +580,7 @@ FUTURE_TREND_SENSORS = (
|
||||||
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
|
||||||
options=["rising", "falling", "stable"],
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
|
|
@ -590,7 +590,7 @@ FUTURE_TREND_SENSORS = (
|
||||||
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
|
||||||
options=["rising", "falling", "stable"],
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
|
|
@ -600,7 +600,7 @@ FUTURE_TREND_SENSORS = (
|
||||||
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
|
||||||
options=["rising", "falling", "stable"],
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
|
|
@ -610,7 +610,7 @@ FUTURE_TREND_SENSORS = (
|
||||||
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
|
||||||
options=["rising", "falling", "stable"],
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
),
|
),
|
||||||
# Disabled by default: 6h, 8h, 12h
|
# Disabled by default: 6h, 8h, 12h
|
||||||
|
|
@ -621,7 +621,7 @@ FUTURE_TREND_SENSORS = (
|
||||||
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
|
||||||
options=["rising", "falling", "stable"],
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
|
|
@ -631,7 +631,7 @@ FUTURE_TREND_SENSORS = (
|
||||||
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
|
||||||
options=["rising", "falling", "stable"],
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
|
|
@ -641,7 +641,7 @@ FUTURE_TREND_SENSORS = (
|
||||||
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
|
||||||
options=["rising", "falling", "stable"],
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -283,14 +283,18 @@
|
||||||
},
|
},
|
||||||
"price_trend": {
|
"price_trend": {
|
||||||
"title": "📈 Preistrend-Schwellenwerte",
|
"title": "📈 Preistrend-Schwellenwerte",
|
||||||
"description": "**Konfiguriere Schwellenwerte für Preistrend-Sensoren. Diese Sensoren vergleichen den aktuellen Preis mit dem Durchschnitt der nächsten N Stunden, um festzustellen, ob die Preise steigen, fallen oder stabil sind.**",
|
"description": "**Konfiguriere Schwellenwerte für Preistrend-Sensoren.** Diese Sensoren vergleichen den aktuellen Preis mit dem Durchschnitt der nächsten N Stunden, um festzustellen, ob die Preise steigen, fallen oder stabil sind.\n\n**5-Stufen-Skala:** Nutzt stark_fallend (-2), fallend (-1), stabil (0), steigend (+1), stark_steigend (+2) für Automations-Vergleiche über das trend_value Attribut.",
|
||||||
"data": {
|
"data": {
|
||||||
"price_trend_threshold_rising": "Steigend-Schwelle",
|
"price_trend_threshold_rising": "Steigend-Schwelle",
|
||||||
"price_trend_threshold_falling": "Fallend-Schwelle"
|
"price_trend_threshold_strongly_rising": "Stark steigend-Schwelle",
|
||||||
|
"price_trend_threshold_falling": "Fallend-Schwelle",
|
||||||
|
"price_trend_threshold_strongly_falling": "Stark fallend-Schwelle"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"price_trend_threshold_rising": "Prozentwert, um wie viel der Durchschnitt der nächsten N Stunden über dem aktuellen Preis liegen muss, damit der Trend als 'steigend' gilt. Beispiel: 5 bedeutet Durchschnitt ist mindestens 5% höher → Preise werden steigen. Typische Werte: 5-15%. Standard: 5%",
|
"price_trend_threshold_rising": "Prozentwert, um wie viel der Durchschnitt der nächsten N Stunden über dem aktuellen Preis liegen muss, damit der Trend als 'steigend' gilt. Beispiel: 3 bedeutet Durchschnitt ist mindestens 3% höher → Preise werden steigen. Typische Werte: 3-10%. Standard: 3%",
|
||||||
"price_trend_threshold_falling": "Prozentwert (negativ), um wie viel der Durchschnitt der nächsten N Stunden unter dem aktuellen Preis liegen muss, damit der Trend als 'fallend' gilt. Beispiel: -5 bedeutet Durchschnitt ist mindestens 5% niedriger → Preise werden fallen. Typische Werte: -5 bis -15%. Standard: -5%"
|
"price_trend_threshold_strongly_rising": "Prozentwert für 'stark steigend'-Trend. Muss höher sein als die steigend-Schwelle. Beispiel: 6 bedeutet Durchschnitt ist mindestens 6% höher → Preise werden deutlich steigen. Typische Werte: 6-15%. Standard: 6%",
|
||||||
|
"price_trend_threshold_falling": "Prozentwert (negativ), um wie viel der Durchschnitt der nächsten N Stunden unter dem aktuellen Preis liegen muss, damit der Trend als 'fallend' gilt. Beispiel: -3 bedeutet Durchschnitt ist mindestens 3% niedriger → Preise werden fallen. Typische Werte: -3 bis -10%. Standard: -3%",
|
||||||
|
"price_trend_threshold_strongly_falling": "Prozentwert (negativ) für 'stark fallend'-Trend. Muss niedriger (negativer) sein als die fallend-Schwelle. Beispiel: -6 bedeutet Durchschnitt ist mindestens 6% niedriger → Preise werden deutlich fallen. Typische Werte: -6 bis -15%. Standard: -6%"
|
||||||
},
|
},
|
||||||
"submit": "↩ Speichern & Zurück"
|
"submit": "↩ Speichern & Zurück"
|
||||||
},
|
},
|
||||||
|
|
@ -356,7 +360,11 @@
|
||||||
"invalid_volatility_threshold_very_high": "Sehr hohe Volatilitätsschwelle muss zwischen 35% und 80% liegen",
|
"invalid_volatility_threshold_very_high": "Sehr hohe Volatilitätsschwelle muss zwischen 35% und 80% liegen",
|
||||||
"invalid_volatility_thresholds": "Schwellenwerte müssen aufsteigend sein: moderat < hoch < sehr hoch",
|
"invalid_volatility_thresholds": "Schwellenwerte müssen aufsteigend sein: moderat < hoch < sehr hoch",
|
||||||
"invalid_price_trend_rising": "Steigender Trendschwellenwert muss zwischen 1% und 50% liegen",
|
"invalid_price_trend_rising": "Steigender Trendschwellenwert muss zwischen 1% und 50% liegen",
|
||||||
"invalid_price_trend_falling": "Fallender Trendschwellenwert muss zwischen -50% und -1% liegen"
|
"invalid_price_trend_falling": "Fallender Trendschwellenwert muss zwischen -50% und -1% liegen",
|
||||||
|
"invalid_price_trend_strongly_rising": "Stark steigender Trendschwellenwert muss zwischen 2% und 100% liegen",
|
||||||
|
"invalid_price_trend_strongly_falling": "Stark fallender Trendschwellenwert muss zwischen -100% und -2% liegen",
|
||||||
|
"invalid_trend_strongly_rising_less_than_rising": "Stark steigend-Schwelle muss größer als steigend-Schwelle sein",
|
||||||
|
"invalid_trend_strongly_falling_greater_than_falling": "Stark fallend-Schwelle muss kleiner (negativer) als fallend-Schwelle sein"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"entry_not_found": "Tibber Konfigurationseintrag nicht gefunden.",
|
"entry_not_found": "Tibber Konfigurationseintrag nicht gefunden.",
|
||||||
|
|
@ -592,73 +600,91 @@
|
||||||
"price_trend_1h": {
|
"price_trend_1h": {
|
||||||
"name": "Preistrend (1h)",
|
"name": "Preistrend (1h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Stark steigend",
|
||||||
"rising": "Steigend",
|
"rising": "Steigend",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallend",
|
"falling": "Fallend",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Stark fallend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_2h": {
|
"price_trend_2h": {
|
||||||
"name": "Preistrend (2h)",
|
"name": "Preistrend (2h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Stark steigend",
|
||||||
"rising": "Steigend",
|
"rising": "Steigend",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallend",
|
"falling": "Fallend",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Stark fallend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_3h": {
|
"price_trend_3h": {
|
||||||
"name": "Preistrend (3h)",
|
"name": "Preistrend (3h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Stark steigend",
|
||||||
"rising": "Steigend",
|
"rising": "Steigend",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallend",
|
"falling": "Fallend",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Stark fallend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_4h": {
|
"price_trend_4h": {
|
||||||
"name": "Preistrend (4h)",
|
"name": "Preistrend (4h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Stark steigend",
|
||||||
"rising": "Steigend",
|
"rising": "Steigend",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallend",
|
"falling": "Fallend",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Stark fallend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_5h": {
|
"price_trend_5h": {
|
||||||
"name": "Preistrend (5h)",
|
"name": "Preistrend (5h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Stark steigend",
|
||||||
"rising": "Steigend",
|
"rising": "Steigend",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallend",
|
"falling": "Fallend",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Stark fallend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_6h": {
|
"price_trend_6h": {
|
||||||
"name": "Preistrend (6h)",
|
"name": "Preistrend (6h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Stark steigend",
|
||||||
"rising": "Steigend",
|
"rising": "Steigend",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallend",
|
"falling": "Fallend",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Stark fallend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_8h": {
|
"price_trend_8h": {
|
||||||
"name": "Preistrend (8h)",
|
"name": "Preistrend (8h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Stark steigend",
|
||||||
"rising": "Steigend",
|
"rising": "Steigend",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallend",
|
"falling": "Fallend",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Stark fallend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_12h": {
|
"price_trend_12h": {
|
||||||
"name": "Preistrend (12h)",
|
"name": "Preistrend (12h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Stark steigend",
|
||||||
"rising": "Steigend",
|
"rising": "Steigend",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallend",
|
"falling": "Fallend",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Stark fallend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"current_price_trend": {
|
"current_price_trend": {
|
||||||
"name": "Aktueller Preistrend",
|
"name": "Aktueller Preistrend",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Stark steigend",
|
||||||
"rising": "Steigend",
|
"rising": "Steigend",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallend",
|
"falling": "Fallend",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Stark fallend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"next_price_trend_change": {
|
"next_price_trend_change": {
|
||||||
|
|
|
||||||
|
|
@ -294,14 +294,18 @@
|
||||||
},
|
},
|
||||||
"price_trend": {
|
"price_trend": {
|
||||||
"title": "📈 Price Trend Thresholds",
|
"title": "📈 Price Trend Thresholds",
|
||||||
"description": "**Configure thresholds for price trend sensors. These sensors compare current price with the average of the next N hours to determine if prices are rising, falling, or stable.**",
|
"description": "**Configure thresholds for price trend sensors.** These sensors compare current price with the average of the next N hours to determine if prices are rising, falling, or stable.\n\n**5-Level Scale:** Uses strongly_falling (-2), falling (-1), stable (0), rising (+1), strongly_rising (+2) for automation comparisons via trend_value attribute.",
|
||||||
"data": {
|
"data": {
|
||||||
"price_trend_threshold_rising": "Rising Threshold",
|
"price_trend_threshold_rising": "Rising Threshold",
|
||||||
"price_trend_threshold_falling": "Falling Threshold"
|
"price_trend_threshold_strongly_rising": "Strongly Rising Threshold",
|
||||||
|
"price_trend_threshold_falling": "Falling Threshold",
|
||||||
|
"price_trend_threshold_strongly_falling": "Strongly Falling Threshold"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"price_trend_threshold_rising": "Percentage that the average of the next N hours must be above the current price to qualify as 'rising' trend. Example: 5 means average is at least 5% higher → prices will rise. Typical values: 5-15%. Default: 5%",
|
"price_trend_threshold_rising": "Percentage that the average of the next N hours must be above the current price to qualify as 'rising' trend. Example: 3 means average is at least 3% higher → prices will rise. Typical values: 3-10%. Default: 3%",
|
||||||
"price_trend_threshold_falling": "Percentage (negative) that the average of the next N hours must be below the current price to qualify as 'falling' trend. Example: -5 means average is at least 5% lower → prices will fall. Typical values: -5 to -15%. Default: -5%"
|
"price_trend_threshold_strongly_rising": "Percentage for 'strongly rising' trend. Must be higher than rising threshold. Example: 6 means average is at least 6% higher → prices will rise significantly. Typical values: 6-15%. Default: 6%",
|
||||||
|
"price_trend_threshold_falling": "Percentage (negative) that the average of the next N hours must be below the current price to qualify as 'falling' trend. Example: -3 means average is at least 3% lower → prices will fall. Typical values: -3 to -10%. Default: -3%",
|
||||||
|
"price_trend_threshold_strongly_falling": "Percentage (negative) for 'strongly falling' trend. Must be lower (more negative) than falling threshold. Example: -6 means average is at least 6% lower → prices will fall significantly. Typical values: -6 to -15%. Default: -6%"
|
||||||
},
|
},
|
||||||
"submit": "↩ Save & Back"
|
"submit": "↩ Save & Back"
|
||||||
},
|
},
|
||||||
|
|
@ -356,7 +360,11 @@
|
||||||
"invalid_volatility_threshold_very_high": "Very high volatility threshold must be between 35% and 80%",
|
"invalid_volatility_threshold_very_high": "Very high volatility threshold must be between 35% and 80%",
|
||||||
"invalid_volatility_thresholds": "Thresholds must be in ascending order: moderate < high < very high",
|
"invalid_volatility_thresholds": "Thresholds must be in ascending order: moderate < high < very high",
|
||||||
"invalid_price_trend_rising": "Rising trend threshold must be between 1% and 50%",
|
"invalid_price_trend_rising": "Rising trend threshold must be between 1% and 50%",
|
||||||
"invalid_price_trend_falling": "Falling trend threshold must be between -50% and -1%"
|
"invalid_price_trend_falling": "Falling trend threshold must be between -50% and -1%",
|
||||||
|
"invalid_price_trend_strongly_rising": "Strongly rising trend threshold must be between 2% and 100%",
|
||||||
|
"invalid_price_trend_strongly_falling": "Strongly falling trend threshold must be between -100% and -2%",
|
||||||
|
"invalid_trend_strongly_rising_less_than_rising": "Strongly rising threshold must be greater than rising threshold",
|
||||||
|
"invalid_trend_strongly_falling_greater_than_falling": "Strongly falling threshold must be less (more negative) than falling threshold"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"entry_not_found": "Tibber configuration entry not found.",
|
"entry_not_found": "Tibber configuration entry not found.",
|
||||||
|
|
@ -592,73 +600,91 @@
|
||||||
"price_trend_1h": {
|
"price_trend_1h": {
|
||||||
"name": "Price Trend (1h)",
|
"name": "Price Trend (1h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Strongly Rising",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
|
"stable": "Stable",
|
||||||
"falling": "Falling",
|
"falling": "Falling",
|
||||||
"stable": "Stable"
|
"strongly_falling": "Strongly Falling"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_2h": {
|
"price_trend_2h": {
|
||||||
"name": "Price Trend (2h)",
|
"name": "Price Trend (2h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Strongly Rising",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
|
"stable": "Stable",
|
||||||
"falling": "Falling",
|
"falling": "Falling",
|
||||||
"stable": "Stable"
|
"strongly_falling": "Strongly Falling"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_3h": {
|
"price_trend_3h": {
|
||||||
"name": "Price Trend (3h)",
|
"name": "Price Trend (3h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Strongly Rising",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
|
"stable": "Stable",
|
||||||
"falling": "Falling",
|
"falling": "Falling",
|
||||||
"stable": "Stable"
|
"strongly_falling": "Strongly Falling"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_4h": {
|
"price_trend_4h": {
|
||||||
"name": "Price Trend (4h)",
|
"name": "Price Trend (4h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Strongly Rising",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
|
"stable": "Stable",
|
||||||
"falling": "Falling",
|
"falling": "Falling",
|
||||||
"stable": "Stable"
|
"strongly_falling": "Strongly Falling"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_5h": {
|
"price_trend_5h": {
|
||||||
"name": "Price Trend (5h)",
|
"name": "Price Trend (5h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Strongly Rising",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
|
"stable": "Stable",
|
||||||
"falling": "Falling",
|
"falling": "Falling",
|
||||||
"stable": "Stable"
|
"strongly_falling": "Strongly Falling"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_6h": {
|
"price_trend_6h": {
|
||||||
"name": "Price Trend (6h)",
|
"name": "Price Trend (6h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Strongly Rising",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
|
"stable": "Stable",
|
||||||
"falling": "Falling",
|
"falling": "Falling",
|
||||||
"stable": "Stable"
|
"strongly_falling": "Strongly Falling"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_8h": {
|
"price_trend_8h": {
|
||||||
"name": "Price Trend (8h)",
|
"name": "Price Trend (8h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Strongly Rising",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
|
"stable": "Stable",
|
||||||
"falling": "Falling",
|
"falling": "Falling",
|
||||||
"stable": "Stable"
|
"strongly_falling": "Strongly Falling"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_12h": {
|
"price_trend_12h": {
|
||||||
"name": "Price Trend (12h)",
|
"name": "Price Trend (12h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Strongly Rising",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
|
"stable": "Stable",
|
||||||
"falling": "Falling",
|
"falling": "Falling",
|
||||||
"stable": "Stable"
|
"strongly_falling": "Strongly Falling"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"current_price_trend": {
|
"current_price_trend": {
|
||||||
"name": "Current Price Trend",
|
"name": "Current Price Trend",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Strongly Rising",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
|
"stable": "Stable",
|
||||||
"falling": "Falling",
|
"falling": "Falling",
|
||||||
"stable": "Stable"
|
"strongly_falling": "Strongly Falling"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"next_price_trend_change": {
|
"next_price_trend_change": {
|
||||||
|
|
|
||||||
|
|
@ -283,14 +283,18 @@
|
||||||
},
|
},
|
||||||
"price_trend": {
|
"price_trend": {
|
||||||
"title": "📈 Pristrendterskler",
|
"title": "📈 Pristrendterskler",
|
||||||
"description": "**Konfigurer terskler for pristrendsensorer. Disse sensorene sammenligner nåværende pris med gjennomsnittet av de neste N timene for å bestemme om prisene stiger, faller eller er stabile.**",
|
"description": "**Konfigurer terskler for pristrendsensorer. Disse sensorene sammenligner nåværende pris med gjennomsnittet av de neste N timene for å bestemme om prisene stiger sterkt, stiger, er stabile, faller eller faller sterkt.**",
|
||||||
"data": {
|
"data": {
|
||||||
"price_trend_threshold_rising": "Stigende terskel",
|
"price_trend_threshold_rising": "Stigende terskel",
|
||||||
"price_trend_threshold_falling": "Fallende terskel"
|
"price_trend_threshold_strongly_rising": "Sterkt stigende terskel",
|
||||||
|
"price_trend_threshold_falling": "Fallende terskel",
|
||||||
|
"price_trend_threshold_strongly_falling": "Sterkt fallende terskel"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"price_trend_threshold_rising": "Prosentverdi som gjennomsnittet av de neste N timene må være over den nåværende prisen for å kvalifisere som 'stigende' trend. Eksempel: 5 betyr gjennomsnittet er minst 5% høyere → prisene vil stige. Typiske verdier: 5-15%. Standard: 5%",
|
"price_trend_threshold_rising": "Prosentverdi som gjennomsnittet av de neste N timene må være over den nåværende prisen for å kvalifisere som 'stigende' trend. Eksempel: 3 betyr gjennomsnittet er minst 3% høyere → prisene vil stige. Typiske verdier: 3-10%. Standard: 3%",
|
||||||
"price_trend_threshold_falling": "Prosentverdi (negativ) som gjennomsnittet av de neste N timene må være under den nåværende prisen for å kvalifisere som 'synkende' trend. Eksempel: -5 betyr gjennomsnittet er minst 5% lavere → prisene vil falle. Typiske verdier: -5 til -15%. Standard: -5%"
|
"price_trend_threshold_strongly_rising": "Prosentverdi som gjennomsnittet av de neste N timene må være over den nåværende prisen for å kvalifisere som 'sterkt stigende' trend. Må være høyere enn stigende terskel. Typiske verdier: 6-20%. Standard: 6%",
|
||||||
|
"price_trend_threshold_falling": "Prosentverdi (negativ) som gjennomsnittet av de neste N timene må være under den nåværende prisen for å kvalifisere som 'synkende' trend. Eksempel: -3 betyr gjennomsnittet er minst 3% lavere → prisene vil falle. Typiske verdier: -3 til -10%. Standard: -3%",
|
||||||
|
"price_trend_threshold_strongly_falling": "Prosentverdi (negativ) som gjennomsnittet av de neste N timene må være under den nåværende prisen for å kvalifisere som 'sterkt synkende' trend. Må være lavere (mer negativ) enn fallende terskel. Typiske verdier: -6 til -20%. Standard: -6%"
|
||||||
},
|
},
|
||||||
"submit": "↩ Lagre & tilbake"
|
"submit": "↩ Lagre & tilbake"
|
||||||
},
|
},
|
||||||
|
|
@ -356,7 +360,11 @@
|
||||||
"invalid_volatility_threshold_very_high": "Svært høy volatilitetsgrense må være mellom 35% og 80%",
|
"invalid_volatility_threshold_very_high": "Svært høy volatilitetsgrense må være mellom 35% og 80%",
|
||||||
"invalid_volatility_thresholds": "Grensene må være i stigende rekkefølge: moderat < høy < svært høy",
|
"invalid_volatility_thresholds": "Grensene må være i stigende rekkefølge: moderat < høy < svært høy",
|
||||||
"invalid_price_trend_rising": "Stigende trendgrense må være mellom 1% og 50%",
|
"invalid_price_trend_rising": "Stigende trendgrense må være mellom 1% og 50%",
|
||||||
"invalid_price_trend_falling": "Fallende trendgrense må være mellom -50% og -1%"
|
"invalid_price_trend_falling": "Fallende trendgrense må være mellom -50% og -1%",
|
||||||
|
"invalid_price_trend_strongly_rising": "Sterkt stigende trendgrense må være mellom 2% og 100%",
|
||||||
|
"invalid_price_trend_strongly_falling": "Sterkt fallende trendgrense må være mellom -100% og -2%",
|
||||||
|
"invalid_trend_strongly_rising_less_than_rising": "Sterkt stigende-grense må være høyere enn stigende-grense",
|
||||||
|
"invalid_trend_strongly_falling_greater_than_falling": "Sterkt fallende-grense må være lavere (mer negativ) enn fallende-grense"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"entry_not_found": "Tibber-konfigurasjonsoppføring ikke funnet.",
|
"entry_not_found": "Tibber-konfigurasjonsoppføring ikke funnet.",
|
||||||
|
|
@ -592,73 +600,91 @@
|
||||||
"price_trend_1h": {
|
"price_trend_1h": {
|
||||||
"name": "Pristrend (1t)",
|
"name": "Pristrend (1t)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterkt stigende",
|
||||||
"rising": "Stigende",
|
"rising": "Stigende",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallende",
|
"falling": "Fallende",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Sterkt fallende"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_2h": {
|
"price_trend_2h": {
|
||||||
"name": "Pristrend (2t)",
|
"name": "Pristrend (2t)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterkt stigende",
|
||||||
"rising": "Stigende",
|
"rising": "Stigende",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallende",
|
"falling": "Fallende",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Sterkt fallende"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_3h": {
|
"price_trend_3h": {
|
||||||
"name": "Pristrend (3t)",
|
"name": "Pristrend (3t)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterkt stigende",
|
||||||
"rising": "Stigende",
|
"rising": "Stigende",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallende",
|
"falling": "Fallende",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Sterkt fallende"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_4h": {
|
"price_trend_4h": {
|
||||||
"name": "Pristrend (4t)",
|
"name": "Pristrend (4t)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterkt stigende",
|
||||||
"rising": "Stigende",
|
"rising": "Stigende",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallende",
|
"falling": "Fallende",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Sterkt fallende"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_5h": {
|
"price_trend_5h": {
|
||||||
"name": "Pristrend (5t)",
|
"name": "Pristrend (5t)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterkt stigende",
|
||||||
"rising": "Stigende",
|
"rising": "Stigende",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallende",
|
"falling": "Fallende",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Sterkt fallende"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_6h": {
|
"price_trend_6h": {
|
||||||
"name": "Pristrend (6t)",
|
"name": "Pristrend (6t)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterkt stigende",
|
||||||
"rising": "Stigende",
|
"rising": "Stigende",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallende",
|
"falling": "Fallende",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Sterkt fallende"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_8h": {
|
"price_trend_8h": {
|
||||||
"name": "Pristrend (8t)",
|
"name": "Pristrend (8t)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterkt stigende",
|
||||||
"rising": "Stigende",
|
"rising": "Stigende",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallende",
|
"falling": "Fallende",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Sterkt fallende"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_12h": {
|
"price_trend_12h": {
|
||||||
"name": "Pristrend (12t)",
|
"name": "Pristrend (12t)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterkt stigende",
|
||||||
"rising": "Stigende",
|
"rising": "Stigende",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallende",
|
"falling": "Fallende",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Sterkt fallende"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"current_price_trend": {
|
"current_price_trend": {
|
||||||
"name": "Nåværende pristrend",
|
"name": "Nåværende pristrend",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterkt stigende",
|
||||||
"rising": "Stigende",
|
"rising": "Stigende",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallende",
|
"falling": "Fallende",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Sterkt fallende"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"next_price_trend_change": {
|
"next_price_trend_change": {
|
||||||
|
|
|
||||||
|
|
@ -283,14 +283,18 @@
|
||||||
},
|
},
|
||||||
"price_trend": {
|
"price_trend": {
|
||||||
"title": "📈 Prijstrend Drempelwaarden",
|
"title": "📈 Prijstrend Drempelwaarden",
|
||||||
"description": "**Configureer drempelwaarden voor prijstrend sensoren. Deze sensoren vergelijken de huidige prijs met het gemiddelde van de volgende N uur om te bepalen of prijzen stijgen, dalen of stabiel zijn.**",
|
"description": "**Configureer drempelwaarden voor prijstrend sensoren. Deze sensoren vergelijken de huidige prijs met het gemiddelde van de volgende N uur om te bepalen of prijzen sterk stijgen, stijgen, stabiel zijn, dalen of sterk dalen.**",
|
||||||
"data": {
|
"data": {
|
||||||
"price_trend_threshold_rising": "Stijgende Drempel",
|
"price_trend_threshold_rising": "Stijgende Drempel",
|
||||||
"price_trend_threshold_falling": "Dalende Drempel"
|
"price_trend_threshold_strongly_rising": "Sterk Stijgende Drempel",
|
||||||
|
"price_trend_threshold_falling": "Dalende Drempel",
|
||||||
|
"price_trend_threshold_strongly_falling": "Sterk Dalende Drempel"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"price_trend_threshold_rising": "Percentage dat het gemiddelde van de volgende N uur boven de huidige prijs moet zijn om te kwalificeren als 'stijgende' trend. Voorbeeld: 5 betekent dat het gemiddelde minimaal 5% hoger is → prijzen zullen stijgen. Typische waarden: 5-15%. Standaard: 5%",
|
"price_trend_threshold_rising": "Percentage dat het gemiddelde van de volgende N uur boven de huidige prijs moet zijn om te kwalificeren als 'stijgende' trend. Voorbeeld: 3 betekent dat het gemiddelde minimaal 3% hoger is → prijzen zullen stijgen. Typische waarden: 3-10%. Standaard: 3%",
|
||||||
"price_trend_threshold_falling": "Percentage (negatief) dat het gemiddelde van de volgende N uur onder de huidige prijs moet zijn om te kwalificeren als 'dalende' trend. Voorbeeld: -5 betekent dat het gemiddelde minimaal 5% lager is → prijzen zullen dalen. Typische waarden: -5 tot -15%. Standaard: -5%"
|
"price_trend_threshold_strongly_rising": "Percentage dat het gemiddelde van de volgende N uur boven de huidige prijs moet zijn om te kwalificeren als 'sterk stijgende' trend. Moet hoger zijn dan stijgende drempel. Typische waarden: 6-20%. Standaard: 6%",
|
||||||
|
"price_trend_threshold_falling": "Percentage (negatief) dat het gemiddelde van de volgende N uur onder de huidige prijs moet zijn om te kwalificeren als 'dalende' trend. Voorbeeld: -3 betekent dat het gemiddelde minimaal 3% lager is → prijzen zullen dalen. Typische waarden: -3 tot -10%. Standaard: -3%",
|
||||||
|
"price_trend_threshold_strongly_falling": "Percentage (negatief) dat het gemiddelde van de volgende N uur onder de huidige prijs moet zijn om te kwalificeren als 'sterk dalende' trend. Moet lager (meer negatief) zijn dan dalende drempel. Typische waarden: -6 tot -20%. Standaard: -6%"
|
||||||
},
|
},
|
||||||
"submit": "↩ Opslaan & Terug"
|
"submit": "↩ Opslaan & Terug"
|
||||||
},
|
},
|
||||||
|
|
@ -356,7 +360,11 @@
|
||||||
"invalid_volatility_threshold_very_high": "Zeer hoge volatiliteit drempel moet tussen 35% en 80% zijn",
|
"invalid_volatility_threshold_very_high": "Zeer hoge volatiliteit drempel moet tussen 35% en 80% zijn",
|
||||||
"invalid_volatility_thresholds": "Drempelwaarden moeten in oplopende volgorde zijn: gematigd < hoog < zeer hoog",
|
"invalid_volatility_thresholds": "Drempelwaarden moeten in oplopende volgorde zijn: gematigd < hoog < zeer hoog",
|
||||||
"invalid_price_trend_rising": "Stijgende trend drempel moet tussen 1% en 50% zijn",
|
"invalid_price_trend_rising": "Stijgende trend drempel moet tussen 1% en 50% zijn",
|
||||||
"invalid_price_trend_falling": "Dalende trend drempel moet tussen -50% en -1% zijn"
|
"invalid_price_trend_falling": "Dalende trend drempel moet tussen -50% en -1% zijn",
|
||||||
|
"invalid_price_trend_strongly_rising": "Sterk stijgende trend drempel moet tussen 2% en 100% zijn",
|
||||||
|
"invalid_price_trend_strongly_falling": "Sterk dalende trend drempel moet tussen -100% en -2% zijn",
|
||||||
|
"invalid_trend_strongly_rising_less_than_rising": "Sterk stijgende drempel moet hoger zijn dan stijgende drempel",
|
||||||
|
"invalid_trend_strongly_falling_greater_than_falling": "Sterk dalende drempel moet lager (meer negatief) zijn dan dalende drempel"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"entry_not_found": "Tibber-configuratie-item niet gevonden.",
|
"entry_not_found": "Tibber-configuratie-item niet gevonden.",
|
||||||
|
|
@ -592,73 +600,91 @@
|
||||||
"price_trend_1h": {
|
"price_trend_1h": {
|
||||||
"name": "Prijstrend (1u)",
|
"name": "Prijstrend (1u)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterk stijgend",
|
||||||
"rising": "Stijgend",
|
"rising": "Stijgend",
|
||||||
|
"stable": "Stabiel",
|
||||||
"falling": "Dalend",
|
"falling": "Dalend",
|
||||||
"stable": "Stabiel"
|
"strongly_falling": "Sterk dalend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_2h": {
|
"price_trend_2h": {
|
||||||
"name": "Prijstrend (2u)",
|
"name": "Prijstrend (2u)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterk stijgend",
|
||||||
"rising": "Stijgend",
|
"rising": "Stijgend",
|
||||||
|
"stable": "Stabiel",
|
||||||
"falling": "Dalend",
|
"falling": "Dalend",
|
||||||
"stable": "Stabiel"
|
"strongly_falling": "Sterk dalend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_3h": {
|
"price_trend_3h": {
|
||||||
"name": "Prijstrend (3u)",
|
"name": "Prijstrend (3u)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterk stijgend",
|
||||||
"rising": "Stijgend",
|
"rising": "Stijgend",
|
||||||
|
"stable": "Stabiel",
|
||||||
"falling": "Dalend",
|
"falling": "Dalend",
|
||||||
"stable": "Stabiel"
|
"strongly_falling": "Sterk dalend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_4h": {
|
"price_trend_4h": {
|
||||||
"name": "Prijstrend (4u)",
|
"name": "Prijstrend (4u)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterk stijgend",
|
||||||
"rising": "Stijgend",
|
"rising": "Stijgend",
|
||||||
|
"stable": "Stabiel",
|
||||||
"falling": "Dalend",
|
"falling": "Dalend",
|
||||||
"stable": "Stabiel"
|
"strongly_falling": "Sterk dalend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_5h": {
|
"price_trend_5h": {
|
||||||
"name": "Prijstrend (5u)",
|
"name": "Prijstrend (5u)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterk stijgend",
|
||||||
"rising": "Stijgend",
|
"rising": "Stijgend",
|
||||||
|
"stable": "Stabiel",
|
||||||
"falling": "Dalend",
|
"falling": "Dalend",
|
||||||
"stable": "Stabiel"
|
"strongly_falling": "Sterk dalend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_6h": {
|
"price_trend_6h": {
|
||||||
"name": "Prijstrend (6u)",
|
"name": "Prijstrend (6u)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterk stijgend",
|
||||||
"rising": "Stijgend",
|
"rising": "Stijgend",
|
||||||
|
"stable": "Stabiel",
|
||||||
"falling": "Dalend",
|
"falling": "Dalend",
|
||||||
"stable": "Stabiel"
|
"strongly_falling": "Sterk dalend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_8h": {
|
"price_trend_8h": {
|
||||||
"name": "Prijstrend (8u)",
|
"name": "Prijstrend (8u)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterk stijgend",
|
||||||
"rising": "Stijgend",
|
"rising": "Stijgend",
|
||||||
|
"stable": "Stabiel",
|
||||||
"falling": "Dalend",
|
"falling": "Dalend",
|
||||||
"stable": "Stabiel"
|
"strongly_falling": "Sterk dalend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_12h": {
|
"price_trend_12h": {
|
||||||
"name": "Prijstrend (12u)",
|
"name": "Prijstrend (12u)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterk stijgend",
|
||||||
"rising": "Stijgend",
|
"rising": "Stijgend",
|
||||||
|
"stable": "Stabiel",
|
||||||
"falling": "Dalend",
|
"falling": "Dalend",
|
||||||
"stable": "Stabiel"
|
"strongly_falling": "Sterk dalend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"current_price_trend": {
|
"current_price_trend": {
|
||||||
"name": "Huidige Prijstrend",
|
"name": "Huidige Prijstrend",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Sterk stijgend",
|
||||||
"rising": "Stijgend",
|
"rising": "Stijgend",
|
||||||
|
"stable": "Stabiel",
|
||||||
"falling": "Dalend",
|
"falling": "Dalend",
|
||||||
"stable": "Stabiel"
|
"strongly_falling": "Sterk dalend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"next_price_trend_change": {
|
"next_price_trend_change": {
|
||||||
|
|
|
||||||
|
|
@ -283,14 +283,18 @@
|
||||||
},
|
},
|
||||||
"price_trend": {
|
"price_trend": {
|
||||||
"title": "📈 Pristrendtrösklar",
|
"title": "📈 Pristrendtrösklar",
|
||||||
"description": "**Konfigurera tröskelvärden för pristrendsensorer. Dessa sensorer jämför aktuellt pris med genomsnittet av de nästa N timmarna för att bestämma om priserna stiger, faller eller är stabila.**",
|
"description": "**Konfigurera tröskelvärden för pristrendsensorer. Dessa sensorer jämför aktuellt pris med genomsnittet av de nästa N timmarna för att bestämma om priserna stiger kraftigt, stiger, är stabila, faller eller faller kraftigt.**",
|
||||||
"data": {
|
"data": {
|
||||||
"price_trend_threshold_rising": "Stigande tröskel",
|
"price_trend_threshold_rising": "Stigande tröskel",
|
||||||
"price_trend_threshold_falling": "Fallande tröskel"
|
"price_trend_threshold_strongly_rising": "Kraftigt stigande tröskel",
|
||||||
|
"price_trend_threshold_falling": "Fallande tröskel",
|
||||||
|
"price_trend_threshold_strongly_falling": "Kraftigt fallande tröskel"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"price_trend_threshold_rising": "Procentandel som genomsnittet av de nästa N timmarna måste vara över det aktuella priset för att kvalificera som 'stigande' trend. Exempel: 5 betyder att genomsnittet är minst 5% högre → priserna kommer att stiga. Typiska värden: 5-15%. Standard: 5%",
|
"price_trend_threshold_rising": "Procentandel som genomsnittet av de nästa N timmarna måste vara över det aktuella priset för att kvalificera som 'stigande' trend. Exempel: 3 betyder att genomsnittet är minst 3% högre → priserna kommer att stiga. Typiska värden: 3-10%. Standard: 3%",
|
||||||
"price_trend_threshold_falling": "Procentandel (negativ) som genomsnittet av de nästa N timmarna måste vara under det aktuella priset för att kvalificera som 'fallande' trend. Exempel: -5 betyder att genomsnittet är minst 5% lägre → priserna kommer att falla. Typiska värden: -5 till -15%. Standard: -5%"
|
"price_trend_threshold_strongly_rising": "Procentandel som genomsnittet av de nästa N timmarna måste vara över det aktuella priset för att kvalificera som 'kraftigt stigande' trend. Måste vara högre än stigande tröskel. Typiska värden: 6-20%. Standard: 6%",
|
||||||
|
"price_trend_threshold_falling": "Procentandel (negativ) som genomsnittet av de nästa N timmarna måste vara under det aktuella priset för att kvalificera som 'fallande' trend. Exempel: -3 betyder att genomsnittet är minst 3% lägre → priserna kommer att falla. Typiska värden: -3 till -10%. Standard: -3%",
|
||||||
|
"price_trend_threshold_strongly_falling": "Procentandel (negativ) som genomsnittet av de nästa N timmarna måste vara under det aktuella priset för att kvalificera som 'kraftigt fallande' trend. Måste vara lägre (mer negativ) än fallande tröskel. Typiska värden: -6 till -20%. Standard: -6%"
|
||||||
},
|
},
|
||||||
"submit": "↩ Spara & tillbaka"
|
"submit": "↩ Spara & tillbaka"
|
||||||
},
|
},
|
||||||
|
|
@ -356,7 +360,11 @@
|
||||||
"invalid_volatility_threshold_very_high": "Mycket hög volatilitetströskel måste vara mellan 35% och 80%",
|
"invalid_volatility_threshold_very_high": "Mycket hög volatilitetströskel måste vara mellan 35% och 80%",
|
||||||
"invalid_volatility_thresholds": "Trösklar måste vara i stigande ordning: måttlig < hög < mycket hög",
|
"invalid_volatility_thresholds": "Trösklar måste vara i stigande ordning: måttlig < hög < mycket hög",
|
||||||
"invalid_price_trend_rising": "Stigande trendtröskel måste vara mellan 1% och 50%",
|
"invalid_price_trend_rising": "Stigande trendtröskel måste vara mellan 1% och 50%",
|
||||||
"invalid_price_trend_falling": "Fallande trendtröskel måste vara mellan -50% och -1%"
|
"invalid_price_trend_falling": "Fallande trendtröskel måste vara mellan -50% och -1%",
|
||||||
|
"invalid_price_trend_strongly_rising": "Kraftigt stigande trendtröskel måste vara mellan 2% och 100%",
|
||||||
|
"invalid_price_trend_strongly_falling": "Kraftigt fallande trendtröskel måste vara mellan -100% och -2%",
|
||||||
|
"invalid_trend_strongly_rising_less_than_rising": "Kraftigt stigande-tröskel måste vara högre än stigande-tröskel",
|
||||||
|
"invalid_trend_strongly_falling_greater_than_falling": "Kraftigt fallande-tröskel måste vara lägre (mer negativ) än fallande-tröskel"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"entry_not_found": "Tibber-konfigurationspost hittades inte.",
|
"entry_not_found": "Tibber-konfigurationspost hittades inte.",
|
||||||
|
|
@ -592,73 +600,91 @@
|
||||||
"price_trend_1h": {
|
"price_trend_1h": {
|
||||||
"name": "Pristrend (1h)",
|
"name": "Pristrend (1h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Kraftigt stigande",
|
||||||
"rising": "Stigande",
|
"rising": "Stigande",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallande",
|
"falling": "Fallande",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Kraftigt fallande"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_2h": {
|
"price_trend_2h": {
|
||||||
"name": "Pristrend (2h)",
|
"name": "Pristrend (2h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Kraftigt stigande",
|
||||||
"rising": "Stigande",
|
"rising": "Stigande",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallande",
|
"falling": "Fallande",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Kraftigt fallande"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_3h": {
|
"price_trend_3h": {
|
||||||
"name": "Pristrend (3h)",
|
"name": "Pristrend (3h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Kraftigt stigande",
|
||||||
"rising": "Stigande",
|
"rising": "Stigande",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallande",
|
"falling": "Fallande",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Kraftigt fallande"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_4h": {
|
"price_trend_4h": {
|
||||||
"name": "Pristrend (4h)",
|
"name": "Pristrend (4h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Kraftigt stigande",
|
||||||
"rising": "Stigande",
|
"rising": "Stigande",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallande",
|
"falling": "Fallande",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Kraftigt fallande"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_5h": {
|
"price_trend_5h": {
|
||||||
"name": "Pristrend (5h)",
|
"name": "Pristrend (5h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Kraftigt stigande",
|
||||||
"rising": "Stigande",
|
"rising": "Stigande",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallande",
|
"falling": "Fallande",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Kraftigt fallande"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_6h": {
|
"price_trend_6h": {
|
||||||
"name": "Pristrend (6h)",
|
"name": "Pristrend (6h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Kraftigt stigande",
|
||||||
"rising": "Stigande",
|
"rising": "Stigande",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallande",
|
"falling": "Fallande",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Kraftigt fallande"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_8h": {
|
"price_trend_8h": {
|
||||||
"name": "Pristrend (8h)",
|
"name": "Pristrend (8h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Kraftigt stigande",
|
||||||
"rising": "Stigande",
|
"rising": "Stigande",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallande",
|
"falling": "Fallande",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Kraftigt fallande"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"price_trend_12h": {
|
"price_trend_12h": {
|
||||||
"name": "Pristrend (12h)",
|
"name": "Pristrend (12h)",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Kraftigt stigande",
|
||||||
"rising": "Stigande",
|
"rising": "Stigande",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallande",
|
"falling": "Fallande",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Kraftigt fallande"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"current_price_trend": {
|
"current_price_trend": {
|
||||||
"name": "Aktuell pristrend",
|
"name": "Aktuell pristrend",
|
||||||
"state": {
|
"state": {
|
||||||
|
"strongly_rising": "Kraftigt stigande",
|
||||||
"rising": "Stigande",
|
"rising": "Stigande",
|
||||||
|
"stable": "Stabil",
|
||||||
"falling": "Fallande",
|
"falling": "Fallande",
|
||||||
"stable": "Stabil"
|
"strongly_falling": "Kraftigt fallande"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"next_price_trend_change": {
|
"next_price_trend_change": {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@ from custom_components.tibber_prices.const import (
|
||||||
PRICE_LEVEL_MAPPING,
|
PRICE_LEVEL_MAPPING,
|
||||||
PRICE_LEVEL_NORMAL,
|
PRICE_LEVEL_NORMAL,
|
||||||
PRICE_RATING_NORMAL,
|
PRICE_RATING_NORMAL,
|
||||||
|
PRICE_TREND_FALLING,
|
||||||
|
PRICE_TREND_MAPPING,
|
||||||
|
PRICE_TREND_RISING,
|
||||||
|
PRICE_TREND_STABLE,
|
||||||
|
PRICE_TREND_STRONGLY_FALLING,
|
||||||
|
PRICE_TREND_STRONGLY_RISING,
|
||||||
VOLATILITY_HIGH,
|
VOLATILITY_HIGH,
|
||||||
VOLATILITY_LOW,
|
VOLATILITY_LOW,
|
||||||
VOLATILITY_MODERATE,
|
VOLATILITY_MODERATE,
|
||||||
|
|
@ -1130,15 +1136,27 @@ def calculate_price_trend( # noqa: PLR0913 - All parameters are necessary for v
|
||||||
threshold_rising: float = 3.0,
|
threshold_rising: float = 3.0,
|
||||||
threshold_falling: float = -3.0,
|
threshold_falling: float = -3.0,
|
||||||
*,
|
*,
|
||||||
|
threshold_strongly_rising: float = 6.0,
|
||||||
|
threshold_strongly_falling: float = -6.0,
|
||||||
volatility_adjustment: bool = True,
|
volatility_adjustment: bool = True,
|
||||||
lookahead_intervals: int | None = None,
|
lookahead_intervals: int | None = None,
|
||||||
all_intervals: list[dict[str, Any]] | None = None,
|
all_intervals: list[dict[str, Any]] | None = None,
|
||||||
volatility_threshold_moderate: float = DEFAULT_VOLATILITY_THRESHOLD_MODERATE,
|
volatility_threshold_moderate: float = DEFAULT_VOLATILITY_THRESHOLD_MODERATE,
|
||||||
volatility_threshold_high: float = DEFAULT_VOLATILITY_THRESHOLD_HIGH,
|
volatility_threshold_high: float = DEFAULT_VOLATILITY_THRESHOLD_HIGH,
|
||||||
) -> tuple[str, float]:
|
) -> tuple[str, float, int]:
|
||||||
"""
|
"""
|
||||||
Calculate price trend by comparing current price with future average.
|
Calculate price trend by comparing current price with future average.
|
||||||
|
|
||||||
|
Uses a 5-level trend scale with integer values for automation comparisons:
|
||||||
|
- strongly_falling (-2): difference <= strongly_falling_threshold
|
||||||
|
- falling (-1): difference <= falling_threshold
|
||||||
|
- stable (0): difference between thresholds
|
||||||
|
- rising (+1): difference >= rising_threshold
|
||||||
|
- strongly_rising (+2): difference >= strongly_rising_threshold
|
||||||
|
|
||||||
|
The strong thresholds are independently configurable (not derived from base
|
||||||
|
thresholds), allowing fine-grained control over trend sensitivity.
|
||||||
|
|
||||||
Supports volatility-adaptive thresholds: when enabled, the effective threshold
|
Supports volatility-adaptive thresholds: when enabled, the effective threshold
|
||||||
is adjusted based on price volatility in the lookahead period. This makes the
|
is adjusted based on price volatility in the lookahead period. This makes the
|
||||||
trend detection more sensitive during stable periods and less noisy during
|
trend detection more sensitive during stable periods and less noisy during
|
||||||
|
|
@ -1152,6 +1170,8 @@ def calculate_price_trend( # noqa: PLR0913 - All parameters are necessary for v
|
||||||
future_average: Average price of future intervals
|
future_average: Average price of future intervals
|
||||||
threshold_rising: Base threshold for rising trend (%, positive, default 3%)
|
threshold_rising: Base threshold for rising trend (%, positive, default 3%)
|
||||||
threshold_falling: Base threshold for falling trend (%, negative, default -3%)
|
threshold_falling: Base threshold for falling trend (%, negative, default -3%)
|
||||||
|
threshold_strongly_rising: Threshold for strongly rising (%, positive, default 6%)
|
||||||
|
threshold_strongly_falling: Threshold for strongly falling (%, negative, default -6%)
|
||||||
volatility_adjustment: Enable volatility-adaptive thresholds (default True)
|
volatility_adjustment: Enable volatility-adaptive thresholds (default True)
|
||||||
lookahead_intervals: Number of intervals in trend period for volatility calc
|
lookahead_intervals: Number of intervals in trend period for volatility calc
|
||||||
all_intervals: Price intervals (today + tomorrow) for volatility calculation
|
all_intervals: Price intervals (today + tomorrow) for volatility calculation
|
||||||
|
|
@ -1159,9 +1179,10 @@ def calculate_price_trend( # noqa: PLR0913 - All parameters are necessary for v
|
||||||
volatility_threshold_high: User-configured high volatility threshold (%)
|
volatility_threshold_high: User-configured high volatility threshold (%)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (trend_state, difference_percentage)
|
Tuple of (trend_state, difference_percentage, trend_value)
|
||||||
trend_state: "rising" | "falling" | "stable"
|
trend_state: PRICE_TREND_* constant (e.g., "strongly_rising")
|
||||||
difference_percentage: % change from current to future ((future - current) / current * 100)
|
difference_percentage: % change from current to future ((future - current) / current * 100)
|
||||||
|
trend_value: Integer value from -2 to +2 for automation comparisons
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
Volatility adjustment factor:
|
Volatility adjustment factor:
|
||||||
|
|
@ -1172,12 +1193,13 @@ def calculate_price_trend( # noqa: PLR0913 - All parameters are necessary for v
|
||||||
"""
|
"""
|
||||||
if current_interval_price == 0:
|
if current_interval_price == 0:
|
||||||
# Avoid division by zero - return stable trend
|
# Avoid division by zero - return stable trend
|
||||||
return "stable", 0.0
|
return PRICE_TREND_STABLE, 0.0, PRICE_TREND_MAPPING[PRICE_TREND_STABLE]
|
||||||
|
|
||||||
# Apply volatility adjustment if enabled and data available
|
# Apply volatility adjustment if enabled and data available
|
||||||
effective_rising = threshold_rising
|
effective_rising = threshold_rising
|
||||||
effective_falling = threshold_falling
|
effective_falling = threshold_falling
|
||||||
volatility_factor = 1.0
|
effective_strongly_rising = threshold_strongly_rising
|
||||||
|
effective_strongly_falling = threshold_strongly_falling
|
||||||
|
|
||||||
if volatility_adjustment and lookahead_intervals and all_intervals:
|
if volatility_adjustment and lookahead_intervals and all_intervals:
|
||||||
volatility_factor = _calculate_lookahead_volatility_factor(
|
volatility_factor = _calculate_lookahead_volatility_factor(
|
||||||
|
|
@ -1185,22 +1207,25 @@ def calculate_price_trend( # noqa: PLR0913 - All parameters are necessary for v
|
||||||
)
|
)
|
||||||
effective_rising = threshold_rising * volatility_factor
|
effective_rising = threshold_rising * volatility_factor
|
||||||
effective_falling = threshold_falling * volatility_factor
|
effective_falling = threshold_falling * volatility_factor
|
||||||
|
effective_strongly_rising = threshold_strongly_rising * volatility_factor
|
||||||
|
effective_strongly_falling = threshold_strongly_falling * volatility_factor
|
||||||
|
|
||||||
# Calculate percentage difference from current to future
|
# Calculate percentage difference from current to future
|
||||||
# CRITICAL: Use abs() for negative prices to get correct percentage direction
|
# CRITICAL: Use abs() for negative prices to get correct percentage direction
|
||||||
# Example: current=-10, future=-5 → diff=5, pct=5/abs(-10)*100=+50% (correctly shows rising)
|
# Example: current=-10, future=-5 → diff=5, pct=5/abs(-10)*100=+50% (correctly shows rising)
|
||||||
if current_interval_price == 0:
|
diff_pct = ((future_average - current_interval_price) / abs(current_interval_price)) * 100
|
||||||
# Edge case: avoid division by zero
|
|
||||||
diff_pct = 0.0
|
|
||||||
else:
|
|
||||||
diff_pct = ((future_average - current_interval_price) / abs(current_interval_price)) * 100
|
|
||||||
|
|
||||||
# Determine trend based on effective thresholds
|
# Determine trend based on effective thresholds (5-level scale)
|
||||||
if diff_pct >= effective_rising:
|
# Check "strongly" conditions first (more extreme), then regular conditions
|
||||||
trend = "rising"
|
if diff_pct >= effective_strongly_rising:
|
||||||
|
trend = PRICE_TREND_STRONGLY_RISING
|
||||||
|
elif diff_pct >= effective_rising:
|
||||||
|
trend = PRICE_TREND_RISING
|
||||||
|
elif diff_pct <= effective_strongly_falling:
|
||||||
|
trend = PRICE_TREND_STRONGLY_FALLING
|
||||||
elif diff_pct <= effective_falling:
|
elif diff_pct <= effective_falling:
|
||||||
trend = "falling"
|
trend = PRICE_TREND_FALLING
|
||||||
else:
|
else:
|
||||||
trend = "stable"
|
trend = PRICE_TREND_STABLE
|
||||||
|
|
||||||
return trend, diff_pct
|
return trend, diff_pct, PRICE_TREND_MAPPING[trend]
|
||||||
|
|
|
||||||
|
|
@ -99,20 +99,26 @@ def test_bug10_trend_diff_negative_current_price() -> None:
|
||||||
future_average = -0.05
|
future_average = -0.05
|
||||||
threshold_rising = 10.0
|
threshold_rising = 10.0
|
||||||
threshold_falling = -10.0
|
threshold_falling = -10.0
|
||||||
|
threshold_strongly_rising = 20.0
|
||||||
|
threshold_strongly_falling = -20.0
|
||||||
|
|
||||||
trend, diff_pct = calculate_price_trend(
|
trend, diff_pct, trend_value = calculate_price_trend(
|
||||||
current_interval_price=current_interval_price,
|
current_interval_price=current_interval_price,
|
||||||
future_average=future_average,
|
future_average=future_average,
|
||||||
threshold_rising=threshold_rising,
|
threshold_rising=threshold_rising,
|
||||||
threshold_falling=threshold_falling,
|
threshold_falling=threshold_falling,
|
||||||
|
threshold_strongly_rising=threshold_strongly_rising,
|
||||||
|
threshold_strongly_falling=threshold_strongly_falling,
|
||||||
volatility_adjustment=False, # Disable to simplify test
|
volatility_adjustment=False, # Disable to simplify test
|
||||||
)
|
)
|
||||||
|
|
||||||
# Difference: -5 - (-10) = 5 ct
|
# Difference: -5 - (-10) = 5 ct
|
||||||
# Percentage: 5 / abs(-10) * 100 = +50% (correctly shows rising)
|
# Percentage: 5 / abs(-10) * 100 = +50% (correctly shows rising)
|
||||||
|
# With 5-level scale: +50% >= 20% strongly_rising threshold => strongly_rising
|
||||||
assert diff_pct > 0, "Percentage should be positive (price rising toward zero)"
|
assert diff_pct > 0, "Percentage should be positive (price rising toward zero)"
|
||||||
assert diff_pct == pytest.approx(50.0, abs=0.1), "Should be +50%"
|
assert diff_pct == pytest.approx(50.0, abs=0.1), "Should be +50%"
|
||||||
assert trend == "rising", "Trend should be 'rising' (above 10% threshold)"
|
assert trend == "strongly_rising", "Trend should be 'strongly_rising' (above strongly_rising threshold)"
|
||||||
|
assert trend_value == 2, "Trend value should be 2 for strongly_rising"
|
||||||
|
|
||||||
|
|
||||||
def test_bug10_trend_diff_negative_falling_deeper() -> None:
|
def test_bug10_trend_diff_negative_falling_deeper() -> None:
|
||||||
|
|
@ -126,20 +132,26 @@ def test_bug10_trend_diff_negative_falling_deeper() -> None:
|
||||||
future_average = -0.15 # -15 ct (more negative = cheaper)
|
future_average = -0.15 # -15 ct (more negative = cheaper)
|
||||||
threshold_rising = 10.0
|
threshold_rising = 10.0
|
||||||
threshold_falling = -10.0
|
threshold_falling = -10.0
|
||||||
|
threshold_strongly_rising = 20.0
|
||||||
|
threshold_strongly_falling = -20.0
|
||||||
|
|
||||||
trend, diff_pct = calculate_price_trend(
|
trend, diff_pct, trend_value = calculate_price_trend(
|
||||||
current_interval_price=current_interval_price,
|
current_interval_price=current_interval_price,
|
||||||
future_average=future_average,
|
future_average=future_average,
|
||||||
threshold_rising=threshold_rising,
|
threshold_rising=threshold_rising,
|
||||||
threshold_falling=threshold_falling,
|
threshold_falling=threshold_falling,
|
||||||
|
threshold_strongly_rising=threshold_strongly_rising,
|
||||||
|
threshold_strongly_falling=threshold_strongly_falling,
|
||||||
volatility_adjustment=False,
|
volatility_adjustment=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Difference: -15 - (-10) = -5 ct
|
# Difference: -15 - (-10) = -5 ct
|
||||||
# Percentage: -5 / abs(-10) * 100 = -50% (correctly shows falling)
|
# Percentage: -5 / abs(-10) * 100 = -50% (correctly shows falling)
|
||||||
|
# With 5-level scale: -50% <= -20% strongly_falling threshold => strongly_falling
|
||||||
assert diff_pct < 0, "Percentage should be negative (price falling deeper)"
|
assert diff_pct < 0, "Percentage should be negative (price falling deeper)"
|
||||||
assert diff_pct == pytest.approx(-50.0, abs=0.1), "Should be -50%"
|
assert diff_pct == pytest.approx(-50.0, abs=0.1), "Should be -50%"
|
||||||
assert trend == "falling", "Trend should be 'falling' (below -10% threshold)"
|
assert trend == "strongly_falling", "Trend should be 'strongly_falling' (below strongly_falling threshold)"
|
||||||
|
assert trend_value == -2, "Trend value should be -2 for strongly_falling"
|
||||||
|
|
||||||
|
|
||||||
def test_bug10_trend_diff_zero_current_price() -> None:
|
def test_bug10_trend_diff_zero_current_price() -> None:
|
||||||
|
|
@ -152,18 +164,23 @@ def test_bug10_trend_diff_zero_current_price() -> None:
|
||||||
future_average = 0.05
|
future_average = 0.05
|
||||||
threshold_rising = 10.0
|
threshold_rising = 10.0
|
||||||
threshold_falling = -10.0
|
threshold_falling = -10.0
|
||||||
|
threshold_strongly_rising = 20.0
|
||||||
|
threshold_strongly_falling = -20.0
|
||||||
|
|
||||||
trend, diff_pct = calculate_price_trend(
|
trend, diff_pct, trend_value = calculate_price_trend(
|
||||||
current_interval_price=current_interval_price,
|
current_interval_price=current_interval_price,
|
||||||
future_average=future_average,
|
future_average=future_average,
|
||||||
threshold_rising=threshold_rising,
|
threshold_rising=threshold_rising,
|
||||||
threshold_falling=threshold_falling,
|
threshold_falling=threshold_falling,
|
||||||
|
threshold_strongly_rising=threshold_strongly_rising,
|
||||||
|
threshold_strongly_falling=threshold_strongly_falling,
|
||||||
volatility_adjustment=False,
|
volatility_adjustment=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Edge case: current=0 → diff_pct should be 0.0 (avoid division by zero)
|
# Edge case: current=0 → diff_pct should be 0.0 (avoid division by zero)
|
||||||
assert diff_pct == 0.0, "Should return 0.0 to avoid division by zero"
|
assert diff_pct == 0.0, "Should return 0.0 to avoid division by zero"
|
||||||
assert trend == "stable", "Should be stable when diff is 0%"
|
assert trend == "stable", "Should be stable when diff is 0%"
|
||||||
|
assert trend_value == 0, "Trend value should be 0 for stable"
|
||||||
|
|
||||||
|
|
||||||
def test_bug10_trend_diff_positive_prices_unchanged() -> None:
|
def test_bug10_trend_diff_positive_prices_unchanged() -> None:
|
||||||
|
|
@ -176,19 +193,25 @@ def test_bug10_trend_diff_positive_prices_unchanged() -> None:
|
||||||
future_average = 0.15 # 15 ct (rising)
|
future_average = 0.15 # 15 ct (rising)
|
||||||
threshold_rising = 10.0
|
threshold_rising = 10.0
|
||||||
threshold_falling = -10.0
|
threshold_falling = -10.0
|
||||||
|
threshold_strongly_rising = 20.0
|
||||||
|
threshold_strongly_falling = -20.0
|
||||||
|
|
||||||
trend, diff_pct = calculate_price_trend(
|
trend, diff_pct, trend_value = calculate_price_trend(
|
||||||
current_interval_price=current_interval_price,
|
current_interval_price=current_interval_price,
|
||||||
future_average=future_average,
|
future_average=future_average,
|
||||||
threshold_rising=threshold_rising,
|
threshold_rising=threshold_rising,
|
||||||
threshold_falling=threshold_falling,
|
threshold_falling=threshold_falling,
|
||||||
|
threshold_strongly_rising=threshold_strongly_rising,
|
||||||
|
threshold_strongly_falling=threshold_strongly_falling,
|
||||||
volatility_adjustment=False,
|
volatility_adjustment=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Difference: 15 - 10 = 5 ct
|
# Difference: 15 - 10 = 5 ct
|
||||||
# Percentage: 5 / 10 * 100 = +50%
|
# Percentage: 5 / 10 * 100 = +50%
|
||||||
|
# With 5-level scale: +50% >= 20% strongly_rising threshold => strongly_rising
|
||||||
assert diff_pct == pytest.approx(50.0, abs=0.1), "Should be +50%"
|
assert diff_pct == pytest.approx(50.0, abs=0.1), "Should be +50%"
|
||||||
assert trend == "rising", "Should be rising"
|
assert trend == "strongly_rising", "Should be strongly_rising (above strongly_rising threshold)"
|
||||||
|
assert trend_value == 2, "Trend value should be 2 for strongly_rising"
|
||||||
|
|
||||||
|
|
||||||
def test_bug11_later_half_diff_calculation_note() -> None:
|
def test_bug11_later_half_diff_calculation_note() -> None:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue