ZhuLinsen/daily_stock_analysis · error · ValueError

periods require bars, but at most days can be requested

Error message

{alert_type} periods require {required_bars} bars, but at most {MAX_REQUESTED_DAYS} days can be requested

What it means

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.

Solutions

  1. Reduce periods so the required bars fit within 365 daily bars (e.g. macd slow_period + signal_period <= 364).
  2. 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.
  3. 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.

Example fix

// before
{ "alert_type": "kdj_cross", "parameters": { "period": 250, "k_period": 250, "d_period": 250 } } // needs 751 bars

// after
{ "alert_type": "kdj_cross", "parameters": { "period": 9, "k_period": 3, "d_period": 3 } } // needs 16 bars
Defensive patterns

Strategy: validation

Validate before calling

REQUIRED = {
    'ma_price_cross': lambda p: p['window'] + 1,
    'rsi_threshold': lambda p: p['period'] + 1,
    'macd_cross': lambda p: p['slow_period'] + p['signal_period'] + 1,
    'kdj_cross': lambda p: p['period'] + p['k_period'] + p['d_period'] + 1,
    'cci_threshold': lambda p: p['period'] + 1,
}
# fill defaults first, then:
if REQUIRED[alert_type](params) > 365:
    raise ValueError('periods need more than 365 daily bars; reduce them')

Type guard

def periods_fetchable(alert_type: str, p: dict) -> bool:
    return compute_required_bars(alert_type, p) <= 365

Try / catch

try:
    normalize_indicator_parameters(alert_type, params)
except ValueError as e:
    if 'bars, but at most 365 days' in str(e):
        return bad_request('reduce indicator periods: warm-up exceeds 365 daily bars')
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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

Appendix: source

Thrown at src/services/alert_indicators.py:361

    triggered = _crossed_threshold(prev_value, curr_value, threshold, direction)
    message = (
        f"{stock_code} CCI{period} {curr_value:.2f} crossed {direction} {threshold:.2f}"
        if triggered
        else f"{stock_code} CCI{period} {curr_value:.2f} did not edge-cross {direction} {threshold:.2f}"
    )
    return IndicatorEvaluation(
        status="triggered" if triggered else "not_triggered",
        observed_value=curr_value,
        threshold=threshold,
        message=message,
        data_timestamp=latest,
    )


def _ensure_required_bars_fetchable(alert_type: str, params: Dict[str, Any]) -> Dict[str, Any]:
    required_bars = compute_required_bars(alert_type, params)
    if required_bars > MAX_REQUESTED_DAYS:
        raise ValueError(
            f"{alert_type} periods require {required_bars} bars, "
            f"but at most {MAX_REQUESTED_DAYS} days can be requested"
        )
    return params


def _direction(value: Any, allowed: frozenset[str], *, default: str) -> str:
    direction = str(value or default).strip().lower()
    if direction not in allowed:
        raise ValueError(f"invalid direction: {direction}")
    return direction


def _int_in_range(value: Any, field_name: str, *, default: int, minimum: int = 2, maximum: int = 250) -> int:
    raw_value = default if value is None or value == "" else value
    try:
        number = int(raw_value)
    except (TypeError, ValueError) as exc:

View on GitHub (pinned to 5159bd72e8)