From 1f2fb011a73dd21f416c4dcc5a556f78fe118676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D1=83=D1=80=D0=BD=D0=B0=D1=82=20=D0=90=D0=BD=D0=B4?= =?UTF-8?q?=D1=80=D0=B5=D0=B9?= Date: Tue, 14 Jul 2026 23:58:05 +0300 Subject: [PATCH] fix: honor explicit calibration horizon --- tests/test_calibrate_thresholds.py | 9 +++++++++ tools/calibrate_torch_thresholds.py | 13 ++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/test_calibrate_thresholds.py b/tests/test_calibrate_thresholds.py index 6ac1b97..408aab4 100644 --- a/tests/test_calibrate_thresholds.py +++ b/tests/test_calibrate_thresholds.py @@ -8,6 +8,7 @@ from tools.calibrate_torch_thresholds import ( _average_selected_predictions, _apply_platt_calibration, _build_torch_model, + _calibration_horizon, _calibration_symbols, _choose_recommendation, _full_backtest, @@ -78,6 +79,14 @@ def test_calibration_symbols_reject_malformed_artifact_symbols() -> None: assert _calibration_symbols("", (), {"symbols": []}) == [] +def test_explicit_calibration_horizon_selects_existing_multi_horizon_output() -> None: + entry = {"target_horizon": 12, "target_horizons": [3, 6, 12, 24]} + + assert _calibration_horizon(entry, 24, explicit=True) == 24 + assert _calibration_horizon(entry, 20, explicit=True) == 24 + assert _calibration_horizon(entry, 24, explicit=False) == 12 + + def test_calibration_does_not_fallback_to_too_few_trades() -> None: selected = _choose_recommendation( [_result(trades=1, average=2.0, total=2.0, profit_factor=999.0)], diff --git a/tools/calibrate_torch_thresholds.py b/tools/calibrate_torch_thresholds.py index 481a823..49b4c36 100644 --- a/tools/calibrate_torch_thresholds.py +++ b/tools/calibrate_torch_thresholds.py @@ -120,6 +120,7 @@ def main() -> None: trend_candles=trend_candles, artifact=artifact, horizon=horizon, + horizon_is_explicit=args.horizon > 0, round_trip_cost=round_trip_cost, min_candles=max(30, settings.time_series_min_candles), calibration_window=args.calibration_window, @@ -329,6 +330,15 @@ def _calibration_symbols( return [str(symbol).strip().upper() for symbol in artifact_symbols if str(symbol).strip()] +def _calibration_horizon(entry: dict[str, Any], requested: int, *, explicit: bool) -> int: + horizons = _entry_target_horizons(entry) + if explicit and requested > 0: + if horizons: + return min(horizons, key=lambda value: abs(value - requested)) + return requested + return _entry_horizon(entry, requested) + + def _forecast_records( *, symbol: str, @@ -337,6 +347,7 @@ def _forecast_records( trend_candles: list[Candle], artifact: dict[str, Any], horizon: int, + horizon_is_explicit: bool, round_trip_cost: float, min_candles: int, calibration_window: int, @@ -355,7 +366,7 @@ def _forecast_records( trend_candles=trend_candles, ) closes = [float(candle.close) for candle in candles] - decision_horizon = _entry_horizon(entry, horizon) + decision_horizon = _calibration_horizon(entry, horizon, explicit=horizon_is_explicit) start = max(min_candles, int(float(entry.get("lookback", 64)))) end = len(candles) - decision_horizon - 1 holdout_start_timestamp = int(float(entry.get("holdout_start_timestamp", 0) or 0))