Optimize signal read paths and enforce score invariants
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m11s
Deploy / deploy (push) Successful in 37s

This commit is contained in:
2026-07-11 13:36:59 +02:00
parent fdc49d0e28
commit 25364f8e99
12 changed files with 357 additions and 35 deletions
+88 -18
View File
@@ -17,11 +17,12 @@ from __future__ import annotations
import logging
import math
from collections import defaultdict
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace
import httpx
from sqlalchemy import select
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
@@ -407,17 +408,49 @@ async def _collect_sr_proximity(db: AsyncSession) -> list[tuple[str, str]]:
single alert. Scoped to the watchlist only — qualified tickers already get
their own 'qualified setup' alert, so S/R on them would be redundant.
"""
watchlist = await _watchlist_tickers(db)
if not watchlist:
return []
ticker_ids = [ticker_id for ticker_id, _ in watchlist]
latest_dates = (
select(
OHLCVRecord.ticker_id,
func.max(OHLCVRecord.date).label("latest_date"),
)
.where(OHLCVRecord.ticker_id.in_(ticker_ids))
.group_by(OHLCVRecord.ticker_id)
.subquery()
)
prices_result = await db.execute(
select(OHLCVRecord.ticker_id, OHLCVRecord.close).join(
latest_dates,
(OHLCVRecord.ticker_id == latest_dates.c.ticker_id)
& (OHLCVRecord.date == latest_dates.c.latest_date),
)
)
prices = {ticker_id: float(close) for ticker_id, close in prices_result.all()}
levels_result = await db.execute(
select(SRLevel).where(SRLevel.ticker_id.in_(ticker_ids))
)
levels_by_ticker: dict[int, list[dict]] = defaultdict(list)
for level in levels_result.scalars():
levels_by_ticker[level.ticker_id].append(
{
"price_level": level.price_level,
"strength": level.strength,
"type": level.type,
}
)
out: list[tuple[str, str]] = []
for tid, symbol in await _watchlist_tickers(db):
price = await _latest_close(db, tid)
for tid, symbol in watchlist:
price = prices.get(tid)
if not price:
continue
levels_result = await db.execute(select(SRLevel).where(SRLevel.ticker_id == tid))
levels = [
{"price_level": lv.price_level, "strength": lv.strength, "type": lv.type}
for lv in levels_result.scalars().all()
]
levels = levels_by_ticker[tid]
if not levels:
continue
@@ -445,17 +478,54 @@ async def _collect_score_drops(db: AsyncSession) -> list[tuple[str, str]]:
doesn't re-fire; let the watermark rise with the score so the next drop is
measured from the new high.
"""
out: list[tuple[str, str]] = []
for tid, symbol in await _watchlist_tickers(db):
comp_result = await db.execute(
select(CompositeScore.score).where(CompositeScore.ticker_id == tid)
)
row = comp_result.first()
if row is None or row[0] is None:
continue
current = float(row[0])
watchlist = await _watchlist_tickers(db)
if not watchlist:
return []
base = await _watermark(db, symbol)
ticker_ids = [ticker_id for ticker_id, _ in watchlist]
symbols = [symbol for _, symbol in watchlist]
scores_result = await db.execute(
select(CompositeScore.ticker_id, CompositeScore.score).where(
CompositeScore.ticker_id.in_(ticker_ids)
)
)
scores = {ticker_id: float(score) for ticker_id, score in scores_result.all()}
ranked_watermarks = (
select(
AlertLog.dedup_key,
AlertLog.value,
func.row_number()
.over(
partition_by=AlertLog.dedup_key,
order_by=(AlertLog.created_at.desc(), AlertLog.id.desc()),
)
.label("rank"),
)
.where(
AlertLog.alert_type == WATERMARK_TYPE,
AlertLog.dedup_key.in_(symbols),
)
.subquery()
)
watermarks_result = await db.execute(
select(ranked_watermarks.c.dedup_key, ranked_watermarks.c.value).where(
ranked_watermarks.c.rank == 1
)
)
watermarks = {
symbol: float(value)
for symbol, value in watermarks_result.all()
if value is not None
}
out: list[tuple[str, str]] = []
for tid, symbol in watchlist:
current = scores.get(tid)
if current is None:
continue
base = watermarks.get(symbol)
if base is None:
_log_alert(db, WATERMARK_TYPE, symbol, value=current) # seed, no alert
continue