ZhuLinsen/daily_stock_analysis · warning · HTTPException

invalid_params

invalid_params

Error message

analysis_date_from cannot be after analysis_date_to

What it means

Backtest endpoints call _validate_analysis_date_range (backtest.py:30-40) which returns HTTP 400 code=invalid_params when both analysis_date_from and analysis_date_to are supplied and from > to. It is a pure parameter-order sanity check executed before any service call, on /run, /results, /performance and /performance/{code}.

Source

Thrown at api/v1/endpoints/backtest.py:36

    PerformanceMetrics,
)
from api.v1.schemas.common import ErrorResponse
from src.services.backtest_service import BacktestService
from src.storage import DatabaseManager

logger = logging.getLogger(__name__)

router = APIRouter()

BacktestAnalysisPhaseQuery = Literal["premarket", "intraday", "postmarket", "unknown"]


def _validate_analysis_date_range(
    analysis_date_from: Optional[date],
    analysis_date_to: Optional[date],
) -> None:
    if analysis_date_from and analysis_date_to and analysis_date_from > analysis_date_to:
        raise HTTPException(
            status_code=400,
            detail={
                "error": "invalid_params",
                "message": "analysis_date_from cannot be after analysis_date_to",
            },
        )


@router.post(
    "/run",
    response_model=BacktestRunResponse,
    responses={
        200: {"description": "回测执行完成"},
        400: {"description": "请求参数错误", "model": ErrorResponse},
        500: {"description": "服务器错误", "model": ErrorResponse},
    },
    summary="触发回测",
    description="对历史分析记录进行回测评估,并写入 backtest_results/backtest_summaries",

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Swap or recompute the range client-side so from <= to before sending
  2. Add client-side validation disabling submit when the range is inverted
  3. If an inverted range should mean 'empty window', decide explicitly: either normalize (min/max) or refuse early — do not rely on the server to guess

Example fix

# before
params = {"analysis_date_from": end.isoformat(), "analysis_date_to": start.isoformat()}

# after
params = {
    "analysis_date_from": min(start, end).isoformat(),
    "analysis_date_to": max(start, end).isoformat(),
}
Defensive patterns

Strategy: validation

Validate before calling

def assert_valid_range(d_from: date, d_to: date) -> None:
    if d_from and d_to and d_from > d_to:
        raise ValueError("analysis_date_from must be <= analysis_date_to")

assert_valid_range(analysis_date_from, analysis_date_to)
# only now issue the backtest request

Try / catch

try:
    get_backtest(params)
except HTTPError as e:
    if e.response.status_code == 400 and 'cannot be after' in e.response.text:
        params['analysis_date_from'], params['analysis_date_to'] = sorted(
            [params['analysis_date_from'], params['analysis_date_to']])
        get_backtest(params)
    else:
        raise

Prevention

When it happens

Trigger: Query params like ?analysis_date_from=2026-01-31&analysis_date_to=2026-01-01 on any backtest endpoint; date pickers whose end-date field is bound to the from parameter; timezone-less date strings parsed in unexpected order; form defaults where 'to' is yesterday and 'from' is today.

Common situations: Swapped date inputs in a custom UI; client sending ISO datetime strings where only dates are expected (FastAPI parses date, but value order can surprise); monthly-range widgets that emit first-day/end-day in the wrong fields.

Related errors


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