Withhold stale-score trade recommendations

This commit is contained in:
2026-07-11 16:56:52 +02:00
parent 292b9934b1
commit 727b147c81
3 changed files with 115 additions and 1 deletions
+44 -1
View File
@@ -13,7 +13,7 @@ import logging
from collections.abc import Callable
from datetime import date, datetime, timedelta, timezone
from sqlalchemy import and_, func, select
from sqlalchemy import and_, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from app.exceptions import NotFoundError
@@ -58,6 +58,28 @@ async def _get_ticker(db: AsyncSession, symbol: str) -> Ticker:
return ticker
async def _mark_ticker_scores_stale(db: AsyncSession, symbol: str) -> None:
"""Prevent a failed refresh from being presented as a current signal."""
result = await db.execute(
select(Ticker.id).where(Ticker.symbol == symbol.strip().upper())
)
ticker_id = result.scalar_one_or_none()
if ticker_id is None:
raise NotFoundError(f"Ticker not found: {symbol.strip().upper()}")
await db.execute(
update(DimensionScore)
.where(DimensionScore.ticker_id == ticker_id)
.values(is_stale=True)
)
await db.execute(
update(CompositeScore)
.where(CompositeScore.ticker_id == ticker_id)
.values(is_stale=True)
)
await db.commit()
def _compute_quality_score(
rr: float,
strength: int,
@@ -116,8 +138,11 @@ async def _apply_live_recommendation_context(
select(DimensionScore).where(DimensionScore.ticker_id.in_(ticker_ids))
)
dims_by_ticker: dict[int, dict[str, float]] = {}
stale_score_ticker_ids: set[int] = set()
for ds in dim_result.scalars().all():
dims_by_ticker.setdefault(ds.ticker_id, {})[ds.dimension] = float(ds.score)
if ds.is_stale:
stale_score_ticker_ids.add(ds.ticker_id)
comp_result = await db.execute(
select(CompositeScore)
@@ -153,6 +178,18 @@ async def _apply_live_recommendation_context(
live_row["composite_score"] = float(comp.score)
live_row["context_as_of"]["score_computed_at"] = comp.computed_at
if (
comp is None
or comp.is_stale
or ticker_id in stale_score_ticker_ids
):
live_row["confidence_score"] = None
live_row["recommended_action"] = "NEUTRAL"
live_row["reasoning"] = "Score refresh pending; recommendation withheld."
live_row["risk_level"] = "High"
live_rows.append(live_row)
continue
dimension_scores = dims_by_ticker.get(ticker_id)
sentiment = sentiments.get(ticker_id)
if sentiment is not None:
@@ -591,6 +628,12 @@ async def scan_all_tickers(
except Exception:
await db.rollback()
logger.exception("Error refreshing scores for %s", symbol)
try:
await _mark_ticker_scores_stale(db, symbol)
except Exception:
await db.rollback()
logger.exception("Could not mark scores stale for %s", symbol)
continue
try:
setups = await scan_ticker(