ZhuLinsen/daily_stock_analysis · error · ValueError

{field_name} must be between {minimum:g} and {maximum:g}

Error message

{field_name} must be between {minimum:g} and {maximum:g}

What it means

Raised by _float_in_range (src/services/alert_indicators.py:407) when a finite float field is outside its inclusive [minimum, maximum] bounds — used for rsi_threshold's threshold, which must be in [0.0, 100.0] because RSI is a bounded 0-100 oscillator (Wilder's smoothing per _calculate_rsi). The %g format renders bounds without trailing zeros ('0' and '100').

Source

Thrown at src/services/alert_indicators.py:407

    try:
        number = float(value)
    except (TypeError, ValueError) as exc:
        raise ValueError(f"invalid {field_name}: {value}") from exc
    if not isfinite(number):
        raise ValueError(f"{field_name} must be finite")
    return number


def _float_in_range(
    value: Any,
    field_name: str,
    *,
    minimum: float,
    maximum: float,
) -> float:
    number = _finite_float(value, field_name)
    if number < minimum or number > maximum:
        raise ValueError(f"{field_name} must be between {minimum:g} and {maximum:g}")
    return number


def _calculate_rsi(close: pd.Series, period: int) -> pd.Series:
    delta = close.diff()
    gain = delta.where(delta > 0, 0)
    loss = -delta.where(delta < 0, 0)
    # 使用 Wilder's EMA / SMMA 口径,不使用 rolling SMA。
    avg_gain = gain.ewm(alpha=1 / period, adjust=False).mean()
    avg_loss = loss.ewm(alpha=1 / period, adjust=False).mean()
    rs = avg_gain / avg_loss
    return (100 - (100 / (1 + rs))).fillna(50)


def _crossed_threshold(prev_value: float, curr_value: float, threshold: float, direction: str) -> bool:
    if direction == "above":
        return prev_value <= threshold < curr_value
    if direction == "below":

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Keep rsi_threshold's threshold within 0..100 (typical 70/30).
  2. Clamp user input client-side: Math.min(100, Math.max(0, v)).
  3. Remember direction interacts with the value: 'above' 70 for overbought, 'below' 30 for oversold.

Example fix

// before
{ "alert_type": "rsi_threshold", "parameters": { "period": 14, "direction": "above", "threshold": 120 } }

// after
{ "alert_type": "rsi_threshold", "parameters": { "period": 14, "direction": "above", "threshold": 70 } }
Defensive patterns

Strategy: validation

Validate before calling

if alert_type == 'rsi_threshold':
    t = float(params.get('threshold'))
    if not (0.0 <= t <= 100.0):
        raise ValueError('RSI threshold must be within 0..100 (try 70 overbought / 30 oversold)')
    params['threshold'] = t

Type guard

def is_valid_rsi_threshold(v) -> bool:
    try:
        return 0.0 <= float(v) <= 100.0
    except (TypeError, ValueError):
        return False

Try / catch

try:
    normalize_indicator_parameters(alert_type, params)
except ValueError as e:
    if 'must be between 0 and 100' in str(e):
        params['threshold'] = min(100.0, max(0.0, float(params['threshold'])))
        normalize_indicator_parameters(alert_type, params)
    else:
        raise

Prevention

When it happens

Trigger: rsi_threshold with {"threshold": 120}, {"threshold": -5}, or {"threshold": 100.5}. Threshold has no default for rsi_threshold, so an out-of-range value always comes from the payload; 0 and 100 themselves are accepted.

Common situations: Reusing CCI-style thresholds (±100 and beyond) on RSI; percentile-like inputs (1-99 scale multiplied incorrectly); copy-paste of price levels into an oscillator threshold field.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/6c9d3eefb6c76a8f. Report an issue: GitHub.