Keep bot operational when forecast model is unavailable
This commit is contained in:
@@ -213,6 +213,7 @@ class TimeSeriesForecaster:
|
||||
probability=self.settings.time_series_min_probability_up,
|
||||
confidence=self.settings.time_series_min_confidence,
|
||||
)
|
||||
symbol_eligible = _calibration_symbol_eligible(calibration, symbol)
|
||||
entry = _torch_recurrent_entry(symbol, artifact)
|
||||
model = _torch_recurrent_model_name(symbol, artifact)
|
||||
clip = _clamp(_float_entry(entry or {}, "clip", 8.0), 1.0, 50.0)
|
||||
@@ -274,7 +275,8 @@ class TimeSeriesForecaster:
|
||||
)
|
||||
conservative_return_percent = min(expected_return_percent, q50_percent)
|
||||
block_entry = bool(
|
||||
(expected_return_percent <= -min_edge and probability_up <= 0.45)
|
||||
not symbol_eligible
|
||||
or (expected_return_percent <= -min_edge and probability_up <= 0.45)
|
||||
or (q50_percent <= -min_edge and probability_up <= 0.48)
|
||||
)
|
||||
reason = _reason(
|
||||
@@ -284,6 +286,8 @@ class TimeSeriesForecaster:
|
||||
skill=skill,
|
||||
block_entry=block_entry,
|
||||
)
|
||||
if not symbol_eligible:
|
||||
reason = "symbol excluded by train-only calibration"
|
||||
return TimeSeriesForecast(
|
||||
enabled=True,
|
||||
usable=True,
|
||||
@@ -344,7 +348,9 @@ class TimeSeriesForecaster:
|
||||
min_edge=min_edge,
|
||||
max_adjustment=self.settings.time_series_max_adjustment,
|
||||
)
|
||||
block_entry = bool(expected_return_percent <= -min_edge and probability_up <= 0.45)
|
||||
block_entry = bool(
|
||||
not symbol_eligible or (expected_return_percent <= -min_edge and probability_up <= 0.45)
|
||||
)
|
||||
reason = _reason(
|
||||
model=model,
|
||||
expected_return_percent=expected_return_percent,
|
||||
@@ -352,6 +358,8 @@ class TimeSeriesForecaster:
|
||||
skill=skill,
|
||||
block_entry=block_entry,
|
||||
)
|
||||
if not symbol_eligible:
|
||||
reason = "symbol excluded by train-only calibration"
|
||||
return TimeSeriesForecast(
|
||||
enabled=True,
|
||||
usable=True,
|
||||
@@ -496,6 +504,16 @@ def _calibrated_thresholds(
|
||||
}
|
||||
|
||||
|
||||
def _calibration_symbol_eligible(calibration: dict[str, Any], symbol: str | None) -> bool:
|
||||
if not isinstance(calibration, dict) or "eligible_symbols" not in calibration:
|
||||
return True
|
||||
eligible = calibration.get("eligible_symbols")
|
||||
if not isinstance(eligible, list) or not symbol:
|
||||
return False
|
||||
allowed = {str(value).strip().upper() for value in eligible if str(value).strip()}
|
||||
return symbol.strip().upper() in allowed
|
||||
|
||||
|
||||
def _model_freshness(artifact: dict[str, Any], max_age_hours: float) -> tuple[str, float | None, bool]:
|
||||
raw = str(artifact.get("created_at", "")).strip() if isinstance(artifact, dict) else ""
|
||||
if not raw:
|
||||
@@ -998,7 +1016,12 @@ def _torch_recurrent_entry(symbol: str | None, artifact: dict[str, Any]) -> dict
|
||||
entry = default if isinstance(default, dict) else None
|
||||
if not isinstance(entry, dict):
|
||||
return None
|
||||
if not isinstance(entry.get("state_dict"), dict):
|
||||
members = entry.get("ensemble_members")
|
||||
has_member_state = isinstance(members, list) and any(
|
||||
isinstance(member, dict) and isinstance(member.get("state_dict"), dict)
|
||||
for member in members
|
||||
)
|
||||
if not isinstance(entry.get("state_dict"), dict) and not has_member_state:
|
||||
return None
|
||||
return entry
|
||||
|
||||
|
||||
Reference in New Issue
Block a user