ZhuLinsen/daily_stock_analysis · error · ValueError

eval_window_days must be positive

Error message

eval_window_days must be positive

What it means

Backtest engine guard (evaluate path 1, src/core/backtest_engine.py): eval_days = int(config.eval_window_days) must be a positive integer. Zero or negative windows are nonsensical for forward-return evaluation (you cannot measure performance over a non-positive number of bars), so the engine raises rather than producing a degenerate evaluation. Note this is reached only after start_price passed its own validity check.

Source

Thrown at src/core/backtest_engine.py:188

        Notes:
        - Daily bars cannot determine intraday ordering. If stop-loss and
          take-profit are both touched in the same bar, we record
          first_hit="ambiguous" and assume stop-loss first for simulated exit.
        """

        if start_price is None or start_price <= 0:
            return {
                "analysis_date": analysis_date,
                "operation_advice": operation_advice,
                "position_recommendation": cls.infer_position_recommendation(operation_advice),
                "direction_expected": cls.infer_direction_expected(operation_advice),
                "eval_status": "error",
            }

        eval_days = int(config.eval_window_days)
        if eval_days <= 0:
            raise ValueError("eval_window_days must be positive")

        if len(forward_bars) < eval_days:
            return {
                "analysis_date": analysis_date,
                "operation_advice": operation_advice,
                "position_recommendation": cls.infer_position_recommendation(operation_advice),
                "direction_expected": cls.infer_direction_expected(operation_advice),
                "eval_status": "insufficient_data",
                "eval_window_days": eval_days,
            }

        window_bars = list(forward_bars[:eval_days])
        end_close = window_bars[-1].close
        highs = [b.high for b in window_bars if b.high is not None]
        lows = [b.low for b in window_bars if b.low is not None]
        max_high = max(highs) if highs else None
        min_low = min(lows) if lows else None

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Set eval_window_days to a positive integer (e.g. 30) in the backtest config / env.
  2. If the value comes from user input or env, clamp/validate it (>= 1) before constructing the config.
  3. Re-run the backtest to confirm the evaluation completes.

Example fix

# before
config.eval_window_days = 0

# after
config.eval_window_days = 30
Defensive patterns

Strategy: validation

Validate before calling

eval_days = int(config.eval_window_days)
if eval_days <= 0:
    raise ValueError("eval_window_days must be a positive integer")

Type guard

def is_valid_eval_window(value: object) -> bool:
    try:
        return int(value) > 0  # type: ignore[arg-type]
    except (TypeError, ValueError):
        return False

Prevention

When it happens

Trigger: A BacktestConfig with eval_window_days = 0, a negative number, or a value that int() truncates to <= 0 (e.g. 0.5). Raised during evaluation of an operation advice with a valid positive start price.

Common situations: Config typo (0 instead of 30); env var EVAL_WINDOW_DAYS set to empty/0; programmatic config where a default was never applied; passing days as float < 1.

Related errors


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