{"record":{"id":"3d52d3008d032573","repo":"ZhuLinsen/daily_stock_analysis","slug":"fast-period-must-be-slow-period","errorCode":null,"errorMessage":"fast_period must be < slow_period","messagePattern":"fast_period must be < slow_period","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/alert_indicators.py","lineNumber":65,"sourceCode":"\n    if alert_type == \"ma_price_cross\":\n        normalized = {\n            \"direction\": _direction(parameters.get(\"direction\"), ABOVE_BELOW_DIRECTIONS, default=\"above\"),\n            \"window\": _int_in_range(parameters.get(\"window\"), \"window\", default=20),\n        }\n        return _ensure_required_bars_fetchable(alert_type, normalized)\n    if alert_type == \"rsi_threshold\":\n        normalized = {\n            \"direction\": _direction(parameters.get(\"direction\"), ABOVE_BELOW_DIRECTIONS, default=\"above\"),\n            \"period\": _int_in_range(parameters.get(\"period\"), \"period\", default=12),\n            \"threshold\": _float_in_range(parameters.get(\"threshold\"), \"threshold\", minimum=0.0, maximum=100.0),\n        }\n        return _ensure_required_bars_fetchable(alert_type, normalized)\n    if alert_type == \"macd_cross\":\n        fast_period = _int_in_range(parameters.get(\"fast_period\"), \"fast_period\", default=12)\n        slow_period = _int_in_range(parameters.get(\"slow_period\"), \"slow_period\", default=26)\n        if fast_period >= slow_period:\n            raise ValueError(\"fast_period must be < slow_period\")\n        normalized = {\n            \"direction\": _direction(parameters.get(\"direction\"), CROSS_DIRECTIONS, default=\"bullish_cross\"),\n            \"fast_period\": fast_period,\n            \"slow_period\": slow_period,\n            \"signal_period\": _int_in_range(parameters.get(\"signal_period\"), \"signal_period\", default=9),\n        }\n        return _ensure_required_bars_fetchable(alert_type, normalized)\n    if alert_type == \"kdj_cross\":\n        normalized = {\n            \"direction\": _direction(parameters.get(\"direction\"), CROSS_DIRECTIONS, default=\"bullish_cross\"),\n            \"period\": _int_in_range(parameters.get(\"period\"), \"period\", default=9),\n            \"k_period\": _int_in_range(parameters.get(\"k_period\"), \"k_period\", default=3),\n            \"d_period\": _int_in_range(parameters.get(\"d_period\"), \"d_period\", default=3),\n        }\n        return _ensure_required_bars_fetchable(alert_type, normalized)\n    if alert_type == \"cci_threshold\":\n        normalized = {\n            \"direction\": _direction(parameters.get(\"direction\"), ABOVE_BELOW_DIRECTIONS, default=\"above\"),","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/alert_indicators.py#L47-L83","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Set fast_period strictly less than slow_period (classic 12/26, or e.g. 5/35).","When overriding one period, always send both explicitly so defaults cannot invert the relationship.","Add a client-side check `fast < slow` in the alert form before submitting."],"exampleFix":"// before\n{ \"alert_type\": \"macd_cross\", \"parameters\": { \"fast_period\": 26, \"slow_period\": 12, \"signal_period\": 9 } }\n\n// after\n{ \"alert_type\": \"macd_cross\", \"parameters\": { \"fast_period\": 12, \"slow_period\": 26, \"signal_period\": 9 } }","handlingStrategy":"validation","validationCode":"fast = params.get('fast_period', 12)\nslow = params.get('slow_period', 26)\nassert int(fast) < int(slow), 'send fast_period < slow_period (both when overriding either)'","typeGuard":"def is_valid_macd_periods(p: dict) -> bool:\n    try:\n        return int(p.get('fast_period', 12)) < int(p.get('slow_period', 26))\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    normalize_indicator_parameters('macd_cross', params)\nexcept ValueError as e:\n    if 'fast_period must be < slow_period' in str(e):\n        params['fast_period'], params['slow_period'] = sorted([params['fast_period'], params['slow_period']])\n        normalize_indicator_parameters('macd_cross', params)\n    else:\n        raise","preventionTips":["Always send both periods together, never override just one.","Validate fast < slow in the form's submit handler with a visible error.","Use the canonical 12/26 unless you have a reason; document non-default MACD settings."],"tags":["validation","alerts","macd","indicators"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}