{"record":{"id":"6db0a5480e768dae","repo":"ZhuLinsen/daily_stock_analysis","slug":"eval-window-days-must-be-a-positive-integer","errorCode":null,"errorMessage":"eval_window_days must be a positive integer","messagePattern":"eval_window_days must be a positive integer","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/backtest_service.py","lineNumber":71,"sourceCode":"        analysis_date_to: Optional[date] = None,\n        limit: int = 200,\n    ) -> Dict[str, Any]:\n        config = get_config()\n\n        if analysis_date_from and analysis_date_to and analysis_date_from > analysis_date_to:\n            raise ValueError(\"analysis_date_from cannot be after analysis_date_to\")\n\n        query_code = self._normalize_code(code)\n        diagnostic_code = self._normalize_code_for_display(code)\n\n        if eval_window_days is None:\n            eval_window_days = getattr(config, \"backtest_eval_window_days\", 10)\n        if (\n            isinstance(eval_window_days, bool)\n            or not isinstance(eval_window_days, int)\n            or eval_window_days <= 0\n        ):\n            raise ValueError(\"eval_window_days must be a positive integer\")\n        if min_age_days is None:\n            min_age_days = getattr(config, \"backtest_min_age_days\", 14)\n\n        engine_version = getattr(config, \"backtest_engine_version\", \"v1\")\n        neutral_band_pct = float(getattr(config, \"backtest_neutral_band_pct\", 2.0))\n\n        eval_config = EvaluationConfig(\n            eval_window_days=int(eval_window_days),\n            neutral_band_pct=neutral_band_pct,\n            engine_version=str(engine_version),\n        )\n\n        limit_int = int(limit)\n        candidates = self._get_run_candidates(\n            code=query_code,\n            min_age_days=int(min_age_days),\n            limit=limit_int,\n            eval_window_days=int(eval_window_days),","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/backtest_service.py#L53-L89","documentation":"BacktestService.run_backtest validates eval_window_days before running an evaluation: it must be a non-boolean int greater than 0. When the argument is None the service falls back to config.backtest_eval_window_days (default 10), so the error fires either on a bad explicit argument or a corrupt config value. It is raised as a plain ValueError, which the FastAPI backtest endpoint translates to HTTP 400 invalid_params.","triggerScenarios":"Calling POST /api/v1/backtest/run (or BacktestService.run_backtest) with eval_window_days=0, a negative int, a string like \"10\", a float like 7.5, or True/False; or setting backtest_eval_window_days=0/-1/\"7d\" in .env/config so the fallback value itself is invalid.","commonSituations":"Typos in .env (BACKTEST_EVAL_WINDOW_DAYS=10.5 or empty string parsed as 0), passing a bool because the value came from a JSON query string (\"true\" coerced), or a frontend sending the field as a string from a form input.","solutions":["Pass eval_window_days as a positive int (e.g. 10) or omit it so the config default (10) applies.","Check the effective config: python -c \"from src.config.config import get_config; print(get_config().backtest_eval_window_days)\" and fix .env / BACKTEST_EVAL_WINDOW_DAYS.","If the value arrives from a client as a string, coerce with int() and validate > 0 before calling the service (the API schema should already enforce ge=1, le=120)."],"exampleFix":"# before\nservice.run_backtest(code=\"600519\", eval_window_days=\"20\")\n\n# after\nservice.run_backtest(code=\"600519\", eval_window_days=20)","handlingStrategy":"validation","validationCode":"def valid_eval_window(v) -> bool:\n    return isinstance(v, int) and not isinstance(v, bool) and v > 0\n\nif eval_window_days is not None and not valid_eval_window(eval_window_days):\n    raise HTTPException(400, \"eval_window_days must be a positive integer\")\nservice.run_backtest(code=code, eval_window_days=eval_window_days)","typeGuard":"def isPositiveInt(v: object) -> bool:\n    return isinstance(v, int) and not isinstance(v, bool) and v > 0","tryCatchPattern":"try:\n    service.run_backtest(code=code, eval_window_days=eval_window_days)\nexcept ValueError as exc:\n    if \"eval_window_days\" in str(exc):\n        return JSONResponse(status_code=400, content={\"error\": \"invalid_params\", \"message\": str(exc)})\n    raise","preventionTips":["Declare eval_window_days as Optional[int] with ge=1 in request schemas so FastAPI rejects bad input first.","Keep BACKTEST_EVAL_WINDOW_DAYS a plain positive integer in .env.","Never pass bools where ints are expected; add isinstance(v, bool) guards in coercion helpers."],"tags":["backtest","validation","parameter-validation"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}