Bundle signal alert notifications
This commit is contained in:
@@ -6,6 +6,7 @@ from datetime import date, datetime, timedelta, timezone
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.models.alert import AlertLog
|
||||
from app.models.ohlcv import OHLCVRecord
|
||||
@@ -113,6 +114,26 @@ async def test_score_drop_seeds_then_alerts(session):
|
||||
assert await svc._watermark(session, "AAA") == 60.0
|
||||
|
||||
|
||||
def test_format_qualified_includes_current_price_and_target_move():
|
||||
text = svc._format_qualified({
|
||||
"symbol": "AAPL",
|
||||
"direction": "long",
|
||||
"current_price": 196.42,
|
||||
"entry_price": 195.80,
|
||||
"target": 207.50,
|
||||
"stop_loss": 190.20,
|
||||
"rr_ratio": 2.1,
|
||||
"confidence_score": 76.0,
|
||||
"targets": [{"probability": 63.0}],
|
||||
})
|
||||
|
||||
assert "now 196.42" in text
|
||||
assert "entry 195.80" in text
|
||||
assert "target 207.50 (+5.6%)" in text
|
||||
assert "stop 190.20" in text
|
||||
assert "P(target) 63%" in text
|
||||
|
||||
|
||||
async def _add_ticker(session, symbol: str, *, watchlisted: bool, close: float,
|
||||
levels: list[tuple[float, str, int]]) -> int:
|
||||
user = await session.get(User, 1)
|
||||
@@ -142,6 +163,8 @@ async def test_sr_proximity_merges_close_levels_to_one_alert(session):
|
||||
cvx = [m for m in msgs if "CVX" in m[1]]
|
||||
assert len(cvx) == 1
|
||||
assert "183.00–185.00" in cvx[0][1]
|
||||
assert "now 182.00 -> 183.00–185.00 (+0.5%)" in cvx[0][1]
|
||||
assert "strength 100" in cvx[0][1]
|
||||
|
||||
|
||||
async def test_sr_proximity_skips_non_watchlist_unqualified(session):
|
||||
@@ -172,6 +195,61 @@ async def test_dispatch_no_credentials(session):
|
||||
assert res["status"] == "no_credentials"
|
||||
|
||||
|
||||
async def test_dispatch_bundles_discovery_alerts_and_logs_each_item(session, monkeypatch):
|
||||
async def fake_collect_qualified(_db):
|
||||
return [
|
||||
("qualified:AAPL:long", "🟢 <b>AAPL LONG</b> | now 196.42 | target 207.50 (+5.6%)"),
|
||||
("qualified:TSLA:short", "🔴 <b>TSLA SHORT</b> | now 292.10 | target 276.50 (-5.3%)"),
|
||||
]
|
||||
|
||||
async def fake_collect_sr(_db):
|
||||
return [
|
||||
("sr:MSFT:resistance", "📍 <b>MSFT</b> resistance | now 508.20 -> 512.00 (+0.7%)"),
|
||||
]
|
||||
|
||||
sent: list[str] = []
|
||||
|
||||
async def fake_send(_client, _token, _chat_id, text):
|
||||
sent.append(text)
|
||||
|
||||
monkeypatch.setattr(svc, "_collect_qualified", fake_collect_qualified)
|
||||
monkeypatch.setattr(svc, "_collect_sr_proximity", fake_collect_sr)
|
||||
monkeypatch.setattr(svc, "_send", fake_send)
|
||||
|
||||
await svc.update_alert_config(
|
||||
session,
|
||||
enabled=True,
|
||||
bot_token="token",
|
||||
telegram_chat_id="chat",
|
||||
score_drop_enabled=False,
|
||||
digest_enabled=False,
|
||||
regime_quadrant_enabled=False,
|
||||
trade_closed_enabled=False,
|
||||
)
|
||||
|
||||
res = await svc.dispatch_alerts(session)
|
||||
|
||||
assert res == {"status": "ok", "sent": 1, "candidates": 3}
|
||||
assert len(sent) == 1
|
||||
assert "<b>Signal run</b> — 3 new alert(s)" in sent[0]
|
||||
assert "<b>Qualified setups</b>" in sent[0]
|
||||
assert "<b>Near support/resistance</b>" in sent[0]
|
||||
assert "AAPL LONG" in sent[0]
|
||||
assert "MSFT" in sent[0]
|
||||
|
||||
rows = (
|
||||
await session.execute(
|
||||
select(AlertLog.alert_type, AlertLog.dedup_key)
|
||||
.order_by(AlertLog.alert_type, AlertLog.dedup_key)
|
||||
)
|
||||
).all()
|
||||
assert rows == [
|
||||
("qualified", "qualified:AAPL:long"),
|
||||
("qualified", "qualified:TSLA:short"),
|
||||
("sr_proximity", "sr:MSFT:resistance"),
|
||||
]
|
||||
|
||||
|
||||
async def _add_closed_trade(session, symbol: str, reason: str, *,
|
||||
close: float = 110.0, closed_hours_ago: float = 1.0) -> None:
|
||||
if await session.get(User, 1) is None:
|
||||
|
||||
Reference in New Issue
Block a user