{"record":{"id":"42fc5d5f4a70894d","repo":"ZhuLinsen/daily_stock_analysis","slug":"analysis-date-from-cannot-be-after-analysis-date-t","errorCode":null,"errorMessage":"analysis_date_from cannot be after analysis_date_to","messagePattern":"analysis_date_from cannot be after analysis_date_to","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/backtest_service.py","lineNumber":59,"sourceCode":"        self.db = db_manager or DatabaseManager.get_instance()\n        self.repo = BacktestRepository(self.db)\n        self.stock_repo = StockRepository(self.db)\n\n    def run_backtest(\n        self,\n        *,\n        code: Optional[str] = None,\n        force: bool = False,\n        eval_window_days: Optional[int] = None,\n        min_age_days: Optional[int] = None,\n        analysis_date_from: Optional[date] = None,\n        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","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/backtest_service.py#L41-L77","documentation":"Plain ValueError raised at the top of the backtest batch run method when both analysis_date_from and analysis_date_to are supplied and from > to. It is a request-argument sanity check that fires before any normalization or data access, so an invalid window fails fast.","triggerScenarios":"Calling the backtest service/API with analysis_date_from='2026-06-01' and analysis_date_to='2026-05-01' — swapped date pickers, wrong ISO order, or timezone/day-boundary mistakes.","commonSituations":"UI date-range pickers returning end-before-start; client sending dates in DD/MM/YYYY parsed as YYYY-MM-DD; DST or server-timezone shifts making an 'same day' range invert; hardcoded test dates left in config.","solutions":["Swap or correct the arguments so analysis_date_from <= analysis_date_to (both ISO YYYY-MM-DD).","Validate/normalize the range client-side before calling the API.","If the range is optional, send only one bound or neither instead of an inverted pair."],"exampleFix":"# before\nrun_backtests(analysis_date_from=date(2026, 6, 1), analysis_date_to=date(2026, 5, 1))\n\n# after\nrun_backtests(analysis_date_from=date(2026, 5, 1), analysis_date_to=date(2026, 6, 1))","handlingStrategy":"validation","validationCode":"if analysis_date_from and analysis_date_to and analysis_date_from > analysis_date_to:\n    analysis_date_from, analysis_date_to = analysis_date_to, analysis_date_from  # or reject with 400","typeGuard":"function isValidDateRange(from?: string, to?: string): boolean {\n  if (!from || !to) return true;\n  return new Date(from) <= new Date(to);\n}","tryCatchPattern":"try:\n    result = backtest_service.run_backtests(\n        analysis_date_from=from_date, analysis_date_to=to_date\n    )\nexcept ValueError as exc:\n    return JSONResponse(status_code=400, content={\"detail\": str(exc)})","preventionTips":["Enforce from <= to in date-range pickers before enabling submit.","Send dates as ISO YYYY-MM-DD and construct them with explicit timezone handling.","Treat this ValueError as a 400 at the API boundary since it signals a bad request, not a server fault."],"tags":["backtest","validation","date-range"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}