ZhuLinsen/daily_stock_analysis · error · ValueError
entry_low must be less than or equal to entry_high
Error message
entry_low must be less than or equal to entry_high
What it means
ValueError from DecisionSignalService._validate_entry_range (src/services/decision_signal_service.py:1263): when both entry_low and entry_high are present, entry_low must be <= entry_high; an inverted or equal-crossed-wrong range is rejected. Runs after individual price validation (397/398), so both bounds are already valid positive floats when this check executes.
Source
Thrown at src/services/decision_signal_service.py:1263
return float(value)
except (TypeError, ValueError) as exc:
raise ValueError(f"{field_name} must be a number") from exc
@classmethod
def _optional_price_float(cls, value: Any, field_name: str) -> Optional[float]:
number = cls._optional_float(value, field_name)
if number is None:
return None
if not math.isfinite(number) or number <= 0:
raise ValueError(f"{field_name} must be a finite positive number")
return number
@staticmethod
def _validate_entry_range(fields: Dict[str, Any]) -> None:
entry_low = fields.get("entry_low")
entry_high = fields.get("entry_high")
if entry_low is not None and entry_high is not None and entry_low > entry_high:
raise ValueError("entry_low must be less than or equal to entry_high")
@staticmethod
def _optional_int(value: Any, field_name: str) -> Optional[int]:
if value in (None, ""):
return None
try:
return int(value)
except (TypeError, ValueError) as exc:
raise ValueError(f"{field_name} must be an integer") from exc
@staticmethod
def _parse_datetime(value: Any) -> Optional[datetime]:
if value in (None, ""):
return None
if isinstance(value, datetime):
return to_utc_naive_datetime(value)
if isinstance(value, str):
text = value.strip()View on GitHub (pinned to 5159bd72e8)
Solutions
- Sort the pair before sending: lo, hi = sorted(filter(None, [entry_low, entry_high])).
- Fix the producer: ensure the JSON schema/prompt asks for [low, high] and validate before persisting.
- When copying from persisted sniper points, apply the same ordering _entry_range(ideal_buy, secondary_buy) uses.
- Add a client-side assert so the bug is caught in tests, not in production writes.
Example fix
# before
service.create_signal({..., "entry_low": 25.0, "entry_high": 20.0}) # inverted → ValueError
# after
lo, hi = sorted([entry_low, entry_high])
service.create_signal({..., "entry_low": lo, "entry_high": hi}) Defensive patterns
Strategy: validation
Validate before calling
lo, hi = payload.get('entry_low'), payload.get('entry_high')
if lo is not None and hi is not None and lo > hi:
payload['entry_low'], payload['entry_high'] = hi, lo # or reject with a clear client error Type guard
def is_valid_entry_range(payload: dict) -> bool:
lo, hi = payload.get('entry_low'), payload.get('entry_high')
return lo is None or hi is None or lo <= hi Prevention
- Sort the [low, high] pair at every ingestion point; never trust producer ordering.
- Specify [low, high] ordering explicitly in prompts/schemas producing buy ranges.
- Add a unit test asserting entry_low <= entry_high for all created signals.
When it happens
Trigger: create/update payloads with entry_low: 25.0, entry_high: 20.0 — typically caused by swapping the two fields at a call site, or by deriving bounds from unordered data (e.g. taking min/max of the wrong columns, or ideal_buy/secondary_buy points persisted in reversed order, mirroring _entry_range in the reassess service).
Common situations: Field-order confusion when building dicts positionally; LLM emitting buy ranges as [high, low]; scraped tables whose columns shift; refactors renaming entry_min/entry_max to entry_low/entry_high with values left in old positions; timezone/adjustment transforms flipping a narrow range after rounding.
Related errors
- {field_name} must be a finite positive number
- score must be between 0 and 100
- 配置校验失败
- invalid price: {rule.get('price')}
- price must be > 0
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/de45b540e958cbdb.
Report an issue: GitHub.