ZhuLinsen/daily_stock_analysis · error · ValueError

fast_period must be < slow_period

Error message

fast_period must be < slow_period

What it means

Raised by the macd_cross branch of normalize_indicator_parameters (src/services/alert_indicators.py:65) when fast_period >= slow_period after both are parsed as integers. MACD is mathematically defined as fast EMA minus slow EMA, so the fast period must be strictly smaller; defaults are fast=12, slow=26.

Source

Thrown at src/services/alert_indicators.py:65

    if alert_type == "ma_price_cross":
        normalized = {
            "direction": _direction(parameters.get("direction"), ABOVE_BELOW_DIRECTIONS, default="above"),
            "window": _int_in_range(parameters.get("window"), "window", default=20),
        }
        return _ensure_required_bars_fetchable(alert_type, normalized)
    if alert_type == "rsi_threshold":
        normalized = {
            "direction": _direction(parameters.get("direction"), ABOVE_BELOW_DIRECTIONS, default="above"),
            "period": _int_in_range(parameters.get("period"), "period", default=12),
            "threshold": _float_in_range(parameters.get("threshold"), "threshold", minimum=0.0, maximum=100.0),
        }
        return _ensure_required_bars_fetchable(alert_type, normalized)
    if alert_type == "macd_cross":
        fast_period = _int_in_range(parameters.get("fast_period"), "fast_period", default=12)
        slow_period = _int_in_range(parameters.get("slow_period"), "slow_period", default=26)
        if fast_period >= slow_period:
            raise ValueError("fast_period must be < slow_period")
        normalized = {
            "direction": _direction(parameters.get("direction"), CROSS_DIRECTIONS, default="bullish_cross"),
            "fast_period": fast_period,
            "slow_period": slow_period,
            "signal_period": _int_in_range(parameters.get("signal_period"), "signal_period", default=9),
        }
        return _ensure_required_bars_fetchable(alert_type, normalized)
    if alert_type == "kdj_cross":
        normalized = {
            "direction": _direction(parameters.get("direction"), CROSS_DIRECTIONS, default="bullish_cross"),
            "period": _int_in_range(parameters.get("period"), "period", default=9),
            "k_period": _int_in_range(parameters.get("k_period"), "k_period", default=3),
            "d_period": _int_in_range(parameters.get("d_period"), "d_period", default=3),
        }
        return _ensure_required_bars_fetchable(alert_type, normalized)
    if alert_type == "cci_threshold":
        normalized = {
            "direction": _direction(parameters.get("direction"), ABOVE_BELOW_DIRECTIONS, default="above"),

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Set fast_period strictly less than slow_period (classic 12/26, or e.g. 5/35).
  2. When overriding one period, always send both explicitly so defaults cannot invert the relationship.
  3. Add a client-side check `fast < slow` in the alert form before submitting.

Example fix

// before
{ "alert_type": "macd_cross", "parameters": { "fast_period": 26, "slow_period": 12, "signal_period": 9 } }

// after
{ "alert_type": "macd_cross", "parameters": { "fast_period": 12, "slow_period": 26, "signal_period": 9 } }
Defensive patterns

Strategy: validation

Validate before calling

fast = params.get('fast_period', 12)
slow = params.get('slow_period', 26)
assert int(fast) < int(slow), 'send fast_period < slow_period (both when overriding either)'

Type guard

def is_valid_macd_periods(p: dict) -> bool:
    try:
        return int(p.get('fast_period', 12)) < int(p.get('slow_period', 26))
    except (TypeError, ValueError):
        return False

Try / catch

try:
    normalize_indicator_parameters('macd_cross', params)
except ValueError as e:
    if 'fast_period must be < slow_period' in str(e):
        params['fast_period'], params['slow_period'] = sorted([params['fast_period'], params['slow_period']])
        normalize_indicator_parameters('macd_cross', params)
    else:
        raise

Prevention

When it happens

Trigger: Creating or updating an alert with alert_type="macd_cross" and parameters like {"fast_period": 26, "slow_period": 12}, {"fast_period": 20, "slow_period": 20}, or omitting slow_period while overriding fast_period to a value >= 26 (e.g. fast_period=30 with default slow_period=26).

Common situations: Users swapping the two fields by intuition ('fast' sounds like the bigger number to some); overriding only one period and forgetting the other's default; UI form not cross-validating the pair before submit.

Related errors


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