fix: harden Structural S/R after OHLCV writes and surface cleanup failures
Deploy / lint (push) Successful in 8s
Deploy / test (push) Successful in 1m18s
Deploy / deploy (push) Successful in 40s

Honor custom S/R tolerance as a transient detect, refresh levels after OHLCV
mutations without failing committed price writes, report per-ticker S/R
rebuild failures from admin cleanup, and warn in the admin UI when refresh is partial.
This commit is contained in:
2026-07-18 13:44:34 +02:00
parent b0e33e1606
commit cad4b49e7c
11 changed files with 181 additions and 23 deletions
+36 -11
View File
@@ -857,17 +857,42 @@ async def get_sr_levels(
symbol: str,
tolerance: float | None = None,
) -> list[SRLevel]:
"""Return persisted Structural S/R levels, strength descending.
"""Return Structural S/R for a ticker, strength descending.
Read-only: does not recompute or rewrite. Pipeline/ingestion call
``recalculate_sr_levels`` to refresh. ``tolerance`` is kept for API
compatibility and ignored on read (it only applies at recalculation).
Default (``tolerance is None``): read persisted levels only — no rewrite.
Pipeline/ingestion call ``recalculate_sr_levels`` after OHLCV changes.
When ``tolerance`` is set: build a **transient** detect view with that merge
tolerance and do not persist it (custom merge for API clients). Transient
rows use negative ids so they cannot be confused with stored levels.
"""
del tolerance # API compat only; levels were stored at last recalculation
if tolerance is None:
ticker = await _get_ticker(db, symbol)
result = await db.execute(
select(SRLevel)
.where(SRLevel.ticker_id == ticker.id)
.order_by(SRLevel.strength.desc())
)
return list(result.scalars().all())
from types import SimpleNamespace
ticker = await _get_ticker(db, symbol)
result = await db.execute(
select(SRLevel)
.where(SRLevel.ticker_id == ticker.id)
.order_by(SRLevel.strength.desc())
)
return list(result.scalars().all())
records = await query_ohlcv(db, symbol)
if not records:
return []
_, highs, lows, closes, volumes = _extract_ohlcv(records)
detected = detect_sr_levels(highs, lows, closes, volumes, tolerance)
now = datetime.utcnow()
# Ephemeral objects with the SRLevel attribute shape the router expects.
return [
SimpleNamespace( # type: ignore[return-value]
id=-(i + 1),
price_level=lvl["price_level"],
type=lvl["type"],
strength=lvl["strength"],
detection_method=lvl["detection_method"],
created_at=now,
)
for i, lvl in enumerate(detected)
]