refactor(config): optimize volatility thresholds with separate ranges and improved UX

Volatility Threshold Optimization:
- Replaced global MIN/MAX_VOLATILITY_THRESHOLD (0-100%) with six separate
  constants for overlapping ranges per threshold level
- MODERATE: 5.0-25.0% (was: 0-100%)
- HIGH: 20.0-40.0% (was: 0-100%)
- VERY_HIGH: 35.0-80.0% (was: 0-100%)
- Added detailed comments explaining ranges and cascading requirements

Validators:
- Added three specific validation functions (one per threshold level)
- Added cross-validation ensuring MODERATE < HIGH < VERY_HIGH
- Added fallback to existing option values for completeness check
- Updated error keys to specific messages per threshold level

UI Improvements:
- Changed NumberSelector mode: BOX → SLIDER (consistency with other config steps)
- Changed step size: 0.1% → 1.0% (better UX, sufficient precision)
- Updated min/max ranges to match new validation constants

Translations:
- Removed: "invalid_volatility_threshold" (generic)
- Added: "invalid_volatility_threshold_moderate/high/very_high" (specific ranges)
- Added: "invalid_volatility_thresholds" (cross-validation error)
- Updated all 5 languages (de, en, nb, nl, sv)

Files modified:
- config_flow_handlers/options_flow.py: Updated validation logic
- config_flow_handlers/schemas.py: Updated NumberSelector configs
- config_flow_handlers/validators.py: Added specific validators + cross-validation
- const.py: Replaced global constants with six specific constants
- translations/*.json: Updated error messages (5 languages)

Impact: Users get clearer validation errors with specific ranges shown,
better UX with sliders and appropriate step size, and guaranteed
threshold ordering (MODERATE < HIGH < VERY_HIGH).
This commit is contained in:
Julian Pawlowski 2025-11-21 17:31:07 +00:00
parent 0fd98554ae
commit 14b68a504b
9 changed files with 151 additions and 35 deletions

View file

@ -29,7 +29,10 @@ from custom_components.tibber_prices.config_flow_handlers.validators import (
validate_price_trend_falling, validate_price_trend_falling,
validate_price_trend_rising, validate_price_trend_rising,
validate_relaxation_attempts, validate_relaxation_attempts,
validate_volatility_threshold, validate_volatility_threshold_high,
validate_volatility_threshold_moderate,
validate_volatility_threshold_very_high,
validate_volatility_thresholds,
) )
from custom_components.tibber_prices.const import ( from custom_components.tibber_prices.const import (
CONF_BEST_PRICE_FLEX, CONF_BEST_PRICE_FLEX,
@ -51,6 +54,9 @@ from custom_components.tibber_prices.const import (
CONF_VOLATILITY_THRESHOLD_HIGH, CONF_VOLATILITY_THRESHOLD_HIGH,
CONF_VOLATILITY_THRESHOLD_MODERATE, CONF_VOLATILITY_THRESHOLD_MODERATE,
CONF_VOLATILITY_THRESHOLD_VERY_HIGH, CONF_VOLATILITY_THRESHOLD_VERY_HIGH,
DEFAULT_VOLATILITY_THRESHOLD_HIGH,
DEFAULT_VOLATILITY_THRESHOLD_MODERATE,
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
DOMAIN, DOMAIN,
) )
from homeassistant.config_entries import ConfigFlowResult, OptionsFlow from homeassistant.config_entries import ConfigFlowResult, OptionsFlow
@ -359,22 +365,41 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
if user_input is not None: if user_input is not None:
# Validate moderate volatility threshold # Validate moderate volatility threshold
if CONF_VOLATILITY_THRESHOLD_MODERATE in user_input and not validate_volatility_threshold( if CONF_VOLATILITY_THRESHOLD_MODERATE in user_input and not validate_volatility_threshold_moderate(
user_input[CONF_VOLATILITY_THRESHOLD_MODERATE] user_input[CONF_VOLATILITY_THRESHOLD_MODERATE]
): ):
errors[CONF_VOLATILITY_THRESHOLD_MODERATE] = "invalid_volatility_threshold" errors[CONF_VOLATILITY_THRESHOLD_MODERATE] = "invalid_volatility_threshold_moderate"
# Validate high volatility threshold # Validate high volatility threshold
if CONF_VOLATILITY_THRESHOLD_HIGH in user_input and not validate_volatility_threshold( if CONF_VOLATILITY_THRESHOLD_HIGH in user_input and not validate_volatility_threshold_high(
user_input[CONF_VOLATILITY_THRESHOLD_HIGH] user_input[CONF_VOLATILITY_THRESHOLD_HIGH]
): ):
errors[CONF_VOLATILITY_THRESHOLD_HIGH] = "invalid_volatility_threshold" errors[CONF_VOLATILITY_THRESHOLD_HIGH] = "invalid_volatility_threshold_high"
# Validate very high volatility threshold # Validate very high volatility threshold
if CONF_VOLATILITY_THRESHOLD_VERY_HIGH in user_input and not validate_volatility_threshold( if CONF_VOLATILITY_THRESHOLD_VERY_HIGH in user_input and not validate_volatility_threshold_very_high(
user_input[CONF_VOLATILITY_THRESHOLD_VERY_HIGH] user_input[CONF_VOLATILITY_THRESHOLD_VERY_HIGH]
): ):
errors[CONF_VOLATILITY_THRESHOLD_VERY_HIGH] = "invalid_volatility_threshold" errors[CONF_VOLATILITY_THRESHOLD_VERY_HIGH] = "invalid_volatility_threshold_very_high"
# Cross-validation: Ensure MODERATE < HIGH < VERY_HIGH
if not errors:
existing_options = self.config_entry.options
moderate = user_input.get(
CONF_VOLATILITY_THRESHOLD_MODERATE,
existing_options.get(CONF_VOLATILITY_THRESHOLD_MODERATE, DEFAULT_VOLATILITY_THRESHOLD_MODERATE),
)
high = user_input.get(
CONF_VOLATILITY_THRESHOLD_HIGH,
existing_options.get(CONF_VOLATILITY_THRESHOLD_HIGH, DEFAULT_VOLATILITY_THRESHOLD_HIGH),
)
very_high = user_input.get(
CONF_VOLATILITY_THRESHOLD_VERY_HIGH,
existing_options.get(CONF_VOLATILITY_THRESHOLD_VERY_HIGH, DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH),
)
if not validate_volatility_thresholds(moderate, high, very_high):
errors["base"] = "invalid_volatility_thresholds"
if not errors: if not errors:
self._options.update(user_input) self._options.update(user_input)

View file

@ -67,7 +67,9 @@ from custom_components.tibber_prices.const import (
MAX_PRICE_TREND_FALLING, MAX_PRICE_TREND_FALLING,
MAX_PRICE_TREND_RISING, MAX_PRICE_TREND_RISING,
MAX_RELAXATION_ATTEMPTS, MAX_RELAXATION_ATTEMPTS,
MAX_VOLATILITY_THRESHOLD, MAX_VOLATILITY_THRESHOLD_HIGH,
MAX_VOLATILITY_THRESHOLD_MODERATE,
MAX_VOLATILITY_THRESHOLD_VERY_HIGH,
MIN_GAP_COUNT, MIN_GAP_COUNT,
MIN_PERIOD_LENGTH, MIN_PERIOD_LENGTH,
MIN_PRICE_RATING_THRESHOLD_HIGH, MIN_PRICE_RATING_THRESHOLD_HIGH,
@ -75,7 +77,9 @@ from custom_components.tibber_prices.const import (
MIN_PRICE_TREND_FALLING, MIN_PRICE_TREND_FALLING,
MIN_PRICE_TREND_RISING, MIN_PRICE_TREND_RISING,
MIN_RELAXATION_ATTEMPTS, MIN_RELAXATION_ATTEMPTS,
MIN_VOLATILITY_THRESHOLD, MIN_VOLATILITY_THRESHOLD_HIGH,
MIN_VOLATILITY_THRESHOLD_MODERATE,
MIN_VOLATILITY_THRESHOLD_VERY_HIGH,
PEAK_PRICE_MIN_LEVEL_OPTIONS, PEAK_PRICE_MIN_LEVEL_OPTIONS,
) )
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.const import CONF_ACCESS_TOKEN
@ -215,11 +219,11 @@ def get_volatility_schema(options: Mapping[str, Any]) -> vol.Schema:
), ),
): NumberSelector( ): NumberSelector(
NumberSelectorConfig( NumberSelectorConfig(
min=MIN_VOLATILITY_THRESHOLD, min=MIN_VOLATILITY_THRESHOLD_MODERATE,
max=MAX_VOLATILITY_THRESHOLD, max=MAX_VOLATILITY_THRESHOLD_MODERATE,
step=0.1, step=1.0,
unit_of_measurement="%", unit_of_measurement="%",
mode=NumberSelectorMode.BOX, mode=NumberSelectorMode.SLIDER,
), ),
), ),
vol.Optional( vol.Optional(
@ -232,11 +236,11 @@ def get_volatility_schema(options: Mapping[str, Any]) -> vol.Schema:
), ),
): NumberSelector( ): NumberSelector(
NumberSelectorConfig( NumberSelectorConfig(
min=MIN_VOLATILITY_THRESHOLD, min=MIN_VOLATILITY_THRESHOLD_HIGH,
max=MAX_VOLATILITY_THRESHOLD, max=MAX_VOLATILITY_THRESHOLD_HIGH,
step=0.1, step=1.0,
unit_of_measurement="%", unit_of_measurement="%",
mode=NumberSelectorMode.BOX, mode=NumberSelectorMode.SLIDER,
), ),
), ),
vol.Optional( vol.Optional(
@ -249,11 +253,11 @@ def get_volatility_schema(options: Mapping[str, Any]) -> vol.Schema:
), ),
): NumberSelector( ): NumberSelector(
NumberSelectorConfig( NumberSelectorConfig(
min=MIN_VOLATILITY_THRESHOLD, min=MIN_VOLATILITY_THRESHOLD_VERY_HIGH,
max=MAX_VOLATILITY_THRESHOLD, max=MAX_VOLATILITY_THRESHOLD_VERY_HIGH,
step=0.1, step=1.0,
unit_of_measurement="%", unit_of_measurement="%",
mode=NumberSelectorMode.BOX, mode=NumberSelectorMode.SLIDER,
), ),
), ),
} }

View file

@ -21,7 +21,9 @@ from custom_components.tibber_prices.const import (
MAX_PRICE_TREND_FALLING, MAX_PRICE_TREND_FALLING,
MAX_PRICE_TREND_RISING, MAX_PRICE_TREND_RISING,
MAX_RELAXATION_ATTEMPTS, MAX_RELAXATION_ATTEMPTS,
MAX_VOLATILITY_THRESHOLD, MAX_VOLATILITY_THRESHOLD_HIGH,
MAX_VOLATILITY_THRESHOLD_MODERATE,
MAX_VOLATILITY_THRESHOLD_VERY_HIGH,
MIN_GAP_COUNT, MIN_GAP_COUNT,
MIN_PERIOD_LENGTH, MIN_PERIOD_LENGTH,
MIN_PRICE_RATING_THRESHOLD_HIGH, MIN_PRICE_RATING_THRESHOLD_HIGH,
@ -29,7 +31,9 @@ from custom_components.tibber_prices.const import (
MIN_PRICE_TREND_FALLING, MIN_PRICE_TREND_FALLING,
MIN_PRICE_TREND_RISING, MIN_PRICE_TREND_RISING,
MIN_RELAXATION_ATTEMPTS, MIN_RELAXATION_ATTEMPTS,
MIN_VOLATILITY_THRESHOLD, MIN_VOLATILITY_THRESHOLD_HIGH,
MIN_VOLATILITY_THRESHOLD_MODERATE,
MIN_VOLATILITY_THRESHOLD_VERY_HIGH,
) )
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.helpers.aiohttp_client import async_create_clientsession
@ -219,18 +223,78 @@ def validate_price_rating_thresholds(threshold_low: int, threshold_high: int) ->
return threshold_low < threshold_high return threshold_low < threshold_high
def validate_volatility_threshold(threshold: float) -> bool: def validate_volatility_threshold_moderate(threshold: float) -> bool:
""" """
Validate volatility threshold percentage. Validate moderate volatility threshold.
Args: Args:
threshold: Volatility threshold percentage (0.0 to 100.0) threshold: Moderate volatility threshold percentage (5.0 to 25.0)
Returns: Returns:
True if threshold is valid (MIN_VOLATILITY_THRESHOLD to MAX_VOLATILITY_THRESHOLD) True if threshold is valid (MIN_VOLATILITY_THRESHOLD_MODERATE to MAX_VOLATILITY_THRESHOLD_MODERATE)
""" """
return MIN_VOLATILITY_THRESHOLD <= threshold <= MAX_VOLATILITY_THRESHOLD return MIN_VOLATILITY_THRESHOLD_MODERATE <= threshold <= MAX_VOLATILITY_THRESHOLD_MODERATE
def validate_volatility_threshold_high(threshold: float) -> bool:
"""
Validate high volatility threshold.
Args:
threshold: High volatility threshold percentage (20.0 to 40.0)
Returns:
True if threshold is valid (MIN_VOLATILITY_THRESHOLD_HIGH to MAX_VOLATILITY_THRESHOLD_HIGH)
"""
return MIN_VOLATILITY_THRESHOLD_HIGH <= threshold <= MAX_VOLATILITY_THRESHOLD_HIGH
def validate_volatility_threshold_very_high(threshold: float) -> bool:
"""
Validate very high volatility threshold.
Args:
threshold: Very high volatility threshold percentage (35.0 to 80.0)
Returns:
True if threshold is valid (MIN_VOLATILITY_THRESHOLD_VERY_HIGH to MAX_VOLATILITY_THRESHOLD_VERY_HIGH)
"""
return MIN_VOLATILITY_THRESHOLD_VERY_HIGH <= threshold <= MAX_VOLATILITY_THRESHOLD_VERY_HIGH
def validate_volatility_thresholds(
threshold_moderate: float,
threshold_high: float,
threshold_very_high: float,
) -> bool:
"""
Cross-validate all three volatility thresholds together.
Ensures that MODERATE < HIGH < VERY_HIGH to maintain logical classification
boundaries. Each threshold represents an escalating level of price volatility.
Args:
threshold_moderate: Moderate volatility threshold (5.0 to 25.0)
threshold_high: High volatility threshold (20.0 to 40.0)
threshold_very_high: Very high volatility threshold (35.0 to 80.0)
Returns:
True if all thresholds are valid individually AND maintain proper ordering
"""
# Validate individual ranges first
if not validate_volatility_threshold_moderate(threshold_moderate):
return False
if not validate_volatility_threshold_high(threshold_high):
return False
if not validate_volatility_threshold_very_high(threshold_very_high):
return False
# Ensure cascading order: MODERATE < HIGH < VERY_HIGH
return threshold_moderate < threshold_high < threshold_very_high
def validate_price_trend_rising(threshold: int) -> bool: def validate_price_trend_rising(threshold: int) -> bool:

View file

@ -112,8 +112,16 @@ MIN_PRICE_RATING_THRESHOLD_HIGH = 5 # Minimum value for high rating threshold (
MAX_PRICE_RATING_THRESHOLD_HIGH = 50 # Maximum value for high rating threshold MAX_PRICE_RATING_THRESHOLD_HIGH = 50 # Maximum value for high rating threshold
# Volatility threshold limits # Volatility threshold limits
MIN_VOLATILITY_THRESHOLD = 0.0 # Minimum volatility threshold percentage # MODERATE threshold: practical range 5% to 25% (entry point for noticeable fluctuation)
MAX_VOLATILITY_THRESHOLD = 100.0 # Maximum volatility threshold percentage # HIGH threshold: practical range 20% to 40% (significant price swings)
# VERY_HIGH threshold: practical range 35% to 80% (extreme volatility)
# Ensure cascading: MODERATE < HIGH < VERY_HIGH with ~5% minimum gaps
MIN_VOLATILITY_THRESHOLD_MODERATE = 5.0 # Minimum for moderate volatility threshold
MAX_VOLATILITY_THRESHOLD_MODERATE = 25.0 # Maximum for moderate volatility threshold (must be < HIGH)
MIN_VOLATILITY_THRESHOLD_HIGH = 20.0 # Minimum for high volatility threshold (must be > MODERATE)
MAX_VOLATILITY_THRESHOLD_HIGH = 40.0 # Maximum for high volatility threshold (must be < VERY_HIGH)
MIN_VOLATILITY_THRESHOLD_VERY_HIGH = 35.0 # Minimum for very high volatility threshold (must be > HIGH)
MAX_VOLATILITY_THRESHOLD_VERY_HIGH = 80.0 # Maximum for very high volatility threshold
# Price trend threshold limits # Price trend threshold limits
MIN_PRICE_TREND_RISING = 1 # Minimum rising trend threshold MIN_PRICE_TREND_RISING = 1 # Minimum rising trend threshold

View file

@ -207,7 +207,10 @@
"invalid_price_rating_low": "Untere Preis-Bewertungsschwelle muss zwischen -50% und -5% liegen", "invalid_price_rating_low": "Untere Preis-Bewertungsschwelle muss zwischen -50% und -5% liegen",
"invalid_price_rating_high": "Obere Preis-Bewertungsschwelle muss zwischen 5% und 50% liegen", "invalid_price_rating_high": "Obere Preis-Bewertungsschwelle muss zwischen 5% und 50% liegen",
"invalid_price_rating_thresholds": "Untere Schwelle muss kleiner als obere Schwelle sein", "invalid_price_rating_thresholds": "Untere Schwelle muss kleiner als obere Schwelle sein",
"invalid_volatility_threshold": "Volatilitätsschwelle muss zwischen 0% und 100% liegen", "invalid_volatility_threshold_moderate": "Moderate Volatilitätsschwelle muss zwischen 5% und 25% liegen",
"invalid_volatility_threshold_high": "Hohe Volatilitätsschwelle muss zwischen 20% und 40% 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_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"
}, },

View file

@ -207,7 +207,10 @@
"invalid_price_rating_low": "Low price rating threshold must be between -50% and -5%", "invalid_price_rating_low": "Low price rating threshold must be between -50% and -5%",
"invalid_price_rating_high": "High price rating threshold must be between 5% and 50%", "invalid_price_rating_high": "High price rating threshold must be between 5% and 50%",
"invalid_price_rating_thresholds": "Low threshold must be less than high threshold", "invalid_price_rating_thresholds": "Low threshold must be less than high threshold",
"invalid_volatility_threshold": "Volatility threshold must be between 0% and 100%", "invalid_volatility_threshold_moderate": "Moderate volatility threshold must be between 5% and 25%",
"invalid_volatility_threshold_high": "High volatility threshold must be between 20% and 40%",
"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_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%"
}, },

View file

@ -207,7 +207,10 @@
"invalid_price_rating_low": "Lav prisvurderingsgrense må være mellom -50% og -5%", "invalid_price_rating_low": "Lav prisvurderingsgrense må være mellom -50% og -5%",
"invalid_price_rating_high": "Høy prisvurderingsgrense må være mellom 5% og 50%", "invalid_price_rating_high": "Høy prisvurderingsgrense må være mellom 5% og 50%",
"invalid_price_rating_thresholds": "Lav grense må være mindre enn høy grense", "invalid_price_rating_thresholds": "Lav grense må være mindre enn høy grense",
"invalid_volatility_threshold": "Volatilitetsgrense må være mellom 0% og 100%", "invalid_volatility_threshold_moderate": "Moderat volatilitetsgrense må være mellom 5% og 25%",
"invalid_volatility_threshold_high": "Høy volatilitetsgrense må være mellom 20% og 40%",
"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_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%"
}, },

View file

@ -207,7 +207,10 @@
"invalid_price_rating_low": "Lage prijsbeoordelingsdrempel moet tussen -50% en -5% liggen", "invalid_price_rating_low": "Lage prijsbeoordelingsdrempel moet tussen -50% en -5% liggen",
"invalid_price_rating_high": "Hoge prijsbeoordelingsdrempel moet tussen 5% en 50% liggen", "invalid_price_rating_high": "Hoge prijsbeoordelingsdrempel moet tussen 5% en 50% liggen",
"invalid_price_rating_thresholds": "Lage drempel moet lager zijn dan hoge drempel", "invalid_price_rating_thresholds": "Lage drempel moet lager zijn dan hoge drempel",
"invalid_volatility_threshold": "Volatiliteitsdrempel moet tussen 0% en 100% liggen", "invalid_volatility_threshold_moderate": "Gematigde volatiliteitsdrempel moet tussen 5% en 25% liggen",
"invalid_volatility_threshold_high": "Hoge volatiliteitsdrempel moet tussen 20% en 40% liggen",
"invalid_volatility_threshold_very_high": "Zeer hoge volatiliteitsdrempel moet tussen 35% en 80% liggen",
"invalid_volatility_thresholds": "Drempels moeten in oplopende volgorde zijn: gematigd < hoog < zeer hoog",
"invalid_price_trend_rising": "Stijgende trenddrempel moet tussen 1% en 50% liggen", "invalid_price_trend_rising": "Stijgende trenddrempel moet tussen 1% en 50% liggen",
"invalid_price_trend_falling": "Dalende trenddrempel moet tussen -50% en -1% liggen" "invalid_price_trend_falling": "Dalende trenddrempel moet tussen -50% en -1% liggen"
}, },

View file

@ -207,7 +207,10 @@
"invalid_price_rating_low": "Låg prisklassificeringströskel måste vara mellan -50% och -5%", "invalid_price_rating_low": "Låg prisklassificeringströskel måste vara mellan -50% och -5%",
"invalid_price_rating_high": "Hög prisklassificeringströskel måste vara mellan 5% och 50%", "invalid_price_rating_high": "Hög prisklassificeringströskel måste vara mellan 5% och 50%",
"invalid_price_rating_thresholds": "Låg tröskel måste vara mindre än hög tröskel", "invalid_price_rating_thresholds": "Låg tröskel måste vara mindre än hög tröskel",
"invalid_volatility_threshold": "Volatilitetströskel måste vara mellan 0% och 100%", "invalid_volatility_threshold_moderate": "Måttlig volatilitetströskel måste vara mellan 5% och 25%",
"invalid_volatility_threshold_high": "Hög volatilitetströskel måste vara mellan 20% och 40%",
"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_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%"
}, },