fix: harden Structural S/R after OHLCV writes and surface cleanup failures
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:
@@ -1,5 +1,8 @@
|
||||
"""Price Store service: upsert and query OHLCV records."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import date, datetime
|
||||
|
||||
from sqlalchemy import select
|
||||
@@ -10,6 +13,8 @@ from app.exceptions import NotFoundError, ValidationError
|
||||
from app.models.ohlcv import OHLCVRecord
|
||||
from app.models.ticker import Ticker
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def _get_ticker(db: AsyncSession, symbol: str) -> Ticker:
|
||||
"""Look up a ticker by symbol. Raises NotFoundError if missing."""
|
||||
@@ -44,11 +49,22 @@ async def upsert_ohlcv(
|
||||
low: float,
|
||||
close: float,
|
||||
volume: int,
|
||||
*,
|
||||
refresh_sr: bool = True,
|
||||
) -> OHLCVRecord:
|
||||
"""Insert or update an OHLCV record for (ticker, date).
|
||||
|
||||
Validates business rules, resolves ticker, then uses
|
||||
ON CONFLICT DO UPDATE on the (ticker_id, date) unique constraint.
|
||||
|
||||
``refresh_sr`` (default True) recalculates persisted Structural S/R after
|
||||
the write so chart levels stay current. Batch ingestion passes
|
||||
``refresh_sr=False`` and refreshes once at the end of the ticker batch.
|
||||
|
||||
The OHLCV commit is authoritative: if S/R rebuild fails after a successful
|
||||
price write, the error is logged, the session is rolled back to clear
|
||||
poison, and the upsert still returns the persisted bar (caller can retry
|
||||
S/R via the scanner/ingestion pipeline).
|
||||
"""
|
||||
_validate_ohlcv(high, low, open_, close, volume, record_date)
|
||||
ticker = await _get_ticker(db, symbol)
|
||||
@@ -84,9 +100,32 @@ async def upsert_ohlcv(
|
||||
|
||||
indicator_cache.invalidate_ticker(ticker.symbol)
|
||||
|
||||
if refresh_sr:
|
||||
await _refresh_structural_sr_best_effort(db, ticker.symbol)
|
||||
|
||||
return record
|
||||
|
||||
|
||||
async def _refresh_structural_sr_best_effort(db: AsyncSession, symbol: str) -> bool:
|
||||
"""Rebuild Structural S/R; never fail a successful OHLCV write.
|
||||
|
||||
Returns True on success. On failure rolls the session back so a later
|
||||
operation on the same session is not poisoned by the failed unit of work.
|
||||
"""
|
||||
from app.services.sr_service import recalculate_sr_levels
|
||||
|
||||
try:
|
||||
await recalculate_sr_levels(db, symbol)
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception("Structural S/R refresh failed for %s after OHLCV write", symbol)
|
||||
try:
|
||||
await db.rollback()
|
||||
except Exception:
|
||||
logger.exception("Session rollback after S/R failure also failed for %s", symbol)
|
||||
return False
|
||||
|
||||
|
||||
async def query_ohlcv(
|
||||
db: AsyncSession,
|
||||
symbol: str,
|
||||
|
||||
Reference in New Issue
Block a user