Keep bot operational when forecast model is unavailable
This commit is contained in:
@@ -1,13 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from tools.calibrate_torch_thresholds import (
|
||||
CalibrationResult,
|
||||
ForecastRecord,
|
||||
_average_selected_predictions,
|
||||
_apply_platt_calibration,
|
||||
_choose_recommendation,
|
||||
_full_backtest,
|
||||
_fit_platt_calibration,
|
||||
_entry_validation_skill,
|
||||
)
|
||||
from tools.train_torch_recurrent_forecaster import _ensemble_candidate
|
||||
|
||||
|
||||
def _result(*, trades: int, average: float, total: float, profit_factor: float) -> CalibrationResult:
|
||||
@@ -85,3 +90,81 @@ def test_entry_quality_never_falls_back_to_holdout_skill() -> None:
|
||||
|
||||
assert _entry_validation_skill(entry) == 0.12
|
||||
assert _entry_validation_skill({"skill": 0.99, "holdout_skill": 0.99}) == 0.0
|
||||
|
||||
|
||||
def test_batched_ensemble_averages_decoded_predictions() -> None:
|
||||
averaged = _average_selected_predictions(
|
||||
[
|
||||
{"expected_return": 0.01, "q50": 0.02, "probability_up": 0.6},
|
||||
{"expected_return": 0.03, "q50": 0.04, "probability_up": 0.8},
|
||||
]
|
||||
)
|
||||
|
||||
assert averaged == {
|
||||
"expected_return": 0.02,
|
||||
"q50": 0.03,
|
||||
"probability_up": 0.7,
|
||||
}
|
||||
|
||||
|
||||
def test_multi_seed_export_does_not_duplicate_first_member_weights() -> None:
|
||||
members = [
|
||||
{
|
||||
"validation_mae": 0.1,
|
||||
"state_dict": {"weight": [seed]},
|
||||
"head_weight": [[seed]],
|
||||
"head_bias": [seed],
|
||||
}
|
||||
for seed in (7, 19)
|
||||
]
|
||||
|
||||
exported = _ensemble_candidate(members, [7, 19])
|
||||
|
||||
assert exported["ensemble_size"] == 2
|
||||
assert exported["ensemble_seeds"] == [7, 19]
|
||||
assert len(exported["ensemble_members"]) == 2
|
||||
assert "state_dict" not in exported
|
||||
assert "head_weight" not in exported
|
||||
|
||||
|
||||
def test_single_seed_export_keeps_only_top_level_weights() -> None:
|
||||
exported = _ensemble_candidate(
|
||||
[
|
||||
{
|
||||
"validation_mae": 0.1,
|
||||
"state_dict": {"weight": [7]},
|
||||
"head_weight": [[7]],
|
||||
"head_bias": [7],
|
||||
}
|
||||
],
|
||||
[7],
|
||||
)
|
||||
|
||||
assert exported["ensemble_size"] == 1
|
||||
assert exported["state_dict"] == {"weight": [7]}
|
||||
assert "ensemble_members" not in exported
|
||||
|
||||
|
||||
def test_full_backtest_never_uses_global_threshold_for_ineligible_symbol() -> None:
|
||||
btc = [_record(index, 0.8, 1.0) for index in range(3)]
|
||||
eth = [_record(index, 0.8, 1.0) for index in range(3)]
|
||||
for record in eth:
|
||||
record.symbol = "ETHUSDT"
|
||||
thresholds = _result(trades=3, average=1.0, total=3.0, profit_factor=999.0)
|
||||
|
||||
replay = _full_backtest(
|
||||
btc + eth,
|
||||
thresholds,
|
||||
horizon=3,
|
||||
round_trip_cost=0.0,
|
||||
settings=SimpleNamespace(
|
||||
stop_loss_percent=0.04,
|
||||
take_profit_percent=0.035,
|
||||
stop_loss_exit_enabled=True,
|
||||
atr_trailing_multiplier=2.2,
|
||||
),
|
||||
symbol_thresholds={"BTCUSDT": thresholds},
|
||||
require_symbol_thresholds=True,
|
||||
)
|
||||
|
||||
assert {row["symbol"] for row in replay["symbol_breakdown"]} == {"BTCUSDT"}
|
||||
|
||||
Reference in New Issue
Block a user