diff --git a/src/analysis/backtest_cost_guard.py b/src/analysis/backtest_cost_guard.py index 2f4a5bb..8f2cf98 100644 --- a/src/analysis/backtest_cost_guard.py +++ b/src/analysis/backtest_cost_guard.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass +import math @dataclass(frozen=True) @@ -19,7 +20,11 @@ def validate_backtest_cost_model( required_sessions: list[str], ) -> None: """Raise ValueError when required cost assumptions are missing/invalid.""" - if model.commission_bps is None or model.commission_bps < 0: + if ( + model.commission_bps is None + or not math.isfinite(model.commission_bps) + or model.commission_bps < 0 + ): raise ValueError("commission_bps must be provided and >= 0") if not model.unfavorable_fill_required: raise ValueError("unfavorable_fill_required must be True") @@ -40,8 +45,8 @@ def validate_backtest_cost_model( ) for sess, bps in slippage.items(): - if bps < 0: + if not math.isfinite(bps) or bps < 0: raise ValueError(f"slippage bps must be >= 0 for session={sess}") for sess, rate in failure.items(): - if rate < 0 or rate > 1: + if not math.isfinite(rate) or rate < 0 or rate > 1: raise ValueError(f"failure rate must be within [0,1] for session={sess}") diff --git a/tests/test_backtest_cost_guard.py b/tests/test_backtest_cost_guard.py index 417925f..6c73a30 100644 --- a/tests/test_backtest_cost_guard.py +++ b/tests/test_backtest_cost_guard.py @@ -57,3 +57,27 @@ def test_unfavorable_fill_requirement_cannot_be_disabled() -> None: ) with pytest.raises(ValueError, match="unfavorable_fill_required must be True"): validate_backtest_cost_model(model=model, required_sessions=["KRX_REG"]) + + +@pytest.mark.parametrize("bad_commission", [float("nan"), float("inf"), float("-inf")]) +def test_non_finite_commission_rejected(bad_commission: float) -> None: + model = BacktestCostModel( + commission_bps=bad_commission, + slippage_bps_by_session={"KRX_REG": 10.0}, + failure_rate_by_session={"KRX_REG": 0.02}, + unfavorable_fill_required=True, + ) + with pytest.raises(ValueError, match="commission_bps"): + validate_backtest_cost_model(model=model, required_sessions=["KRX_REG"]) + + +@pytest.mark.parametrize("bad_slippage", [float("nan"), float("inf"), float("-inf")]) +def test_non_finite_slippage_rejected(bad_slippage: float) -> None: + model = BacktestCostModel( + commission_bps=5.0, + slippage_bps_by_session={"KRX_REG": bad_slippage}, + failure_rate_by_session={"KRX_REG": 0.02}, + unfavorable_fill_required=True, + ) + with pytest.raises(ValueError, match="slippage bps"): + validate_backtest_cost_model(model=model, required_sessions=["KRX_REG"])