{"record":{"id":"b22dbcdb18b35634","repo":"ZhuLinsen/daily_stock_analysis","slug":"alert-type-periods-require-required-bars-bars","errorCode":null,"errorMessage":"{alert_type} periods require {required_bars} bars, but at most {MAX_REQUESTED_DAYS} days can be requested","messagePattern":"(.+?) periods require (.+?) bars, but at most (.+?) days can be requested","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/alert_indicators.py","lineNumber":361,"sourceCode":"    triggered = _crossed_threshold(prev_value, curr_value, threshold, direction)\n    message = (\n        f\"{stock_code} CCI{period} {curr_value:.2f} crossed {direction} {threshold:.2f}\"\n        if triggered\n        else f\"{stock_code} CCI{period} {curr_value:.2f} did not edge-cross {direction} {threshold:.2f}\"\n    )\n    return IndicatorEvaluation(\n        status=\"triggered\" if triggered else \"not_triggered\",\n        observed_value=curr_value,\n        threshold=threshold,\n        message=message,\n        data_timestamp=latest,\n    )\n\n\ndef _ensure_required_bars_fetchable(alert_type: str, params: Dict[str, Any]) -> Dict[str, Any]:\n    required_bars = compute_required_bars(alert_type, params)\n    if required_bars > MAX_REQUESTED_DAYS:\n        raise ValueError(\n            f\"{alert_type} periods require {required_bars} bars, \"\n            f\"but at most {MAX_REQUESTED_DAYS} days can be requested\"\n        )\n    return params\n\n\ndef _direction(value: Any, allowed: frozenset[str], *, default: str) -> str:\n    direction = str(value or default).strip().lower()\n    if direction not in allowed:\n        raise ValueError(f\"invalid direction: {direction}\")\n    return direction\n\n\ndef _int_in_range(value: Any, field_name: str, *, default: int, minimum: int = 2, maximum: int = 250) -> int:\n    raw_value = default if value is None or value == \"\" else value\n    try:\n        number = int(raw_value)\n    except (TypeError, ValueError) as exc:","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/alert_indicators.py#L343-L379","documentation":"Raised by _ensure_required_bars_fetchable (src/services/alert_indicators.py:361) during parameter normalization: compute_required_bars (e.g. slow_period + signal_period + 1 for MACD, or period + k_period + d_period + 1 for KDJ) yields a bar count exceeding MAX_REQUESTED_DAYS = 365. Since the evaluator can request at most 365 daily bars, parameter sets needing more history are rejected up front as unfetchable.","triggerScenarios":"macd_cross with slow_period=250 and signal_period=250 (needs 501 bars); kdj_cross with period=k_period=d_period=250 (needs 751 bars); any combination where the indicator's warm-up window sums past 365 trading days. Each individual field is capped at 250 by _int_in_range, but their sum can exceed 365.","commonSituations":"Users maximizing periods for 'smoother' signals without realizing warm-up sums; configs migrated from another platform with a higher daily-history cap; defaults are safe (12+26+1, 9+3+3+1) so this only fires with explicit overrides.","solutions":["Reduce periods so the required bars fit within 365 daily bars (e.g. macd slow_period + signal_period <= 364).","Compute the requirement client-side before submitting: macd => slow+signal+1, kdj => period+k+d+1, ma => window+1, rsi/cci => period+1; keep it <= 365.","If longer history is genuinely needed, raise MAX_REQUESTED_DAYS in alert_indicators.py after confirming the data source can actually serve that many daily bars."],"exampleFix":"// before\n{ \"alert_type\": \"kdj_cross\", \"parameters\": { \"period\": 250, \"k_period\": 250, \"d_period\": 250 } } // needs 751 bars\n\n// after\n{ \"alert_type\": \"kdj_cross\", \"parameters\": { \"period\": 9, \"k_period\": 3, \"d_period\": 3 } } // needs 16 bars","handlingStrategy":"validation","validationCode":"REQUIRED = {\n    'ma_price_cross': lambda p: p['window'] + 1,\n    'rsi_threshold': lambda p: p['period'] + 1,\n    'macd_cross': lambda p: p['slow_period'] + p['signal_period'] + 1,\n    'kdj_cross': lambda p: p['period'] + p['k_period'] + p['d_period'] + 1,\n    'cci_threshold': lambda p: p['period'] + 1,\n}\n# fill defaults first, then:\nif REQUIRED[alert_type](params) > 365:\n    raise ValueError('periods need more than 365 daily bars; reduce them')","typeGuard":"def periods_fetchable(alert_type: str, p: dict) -> bool:\n    return compute_required_bars(alert_type, p) <= 365","tryCatchPattern":"try:\n    normalize_indicator_parameters(alert_type, params)\nexcept ValueError as e:\n    if 'bars, but at most 365 days' in str(e):\n        return bad_request('reduce indicator periods: warm-up exceeds 365 daily bars')\n    raise","preventionTips":["Remember the per-field cap is 250 but the summed-bars cap is 365 — sums, not single fields, trip this.","Show a live 'required history: N bars' hint in the UI computed with the same formulas.","Never migrate long-period configs from platforms with bigger history windows without checking this bound."],"tags":["validation","alerts","indicators","limits"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}