68 lines
2.2 KiB
Python
68 lines
2.2 KiB
Python
"""预警引擎:拉取实时价格,评估规则,触发即生成站内事件。"""
|
||
from __future__ import annotations
|
||
|
||
import datetime as dt
|
||
|
||
from sqlalchemy import select
|
||
|
||
import akshare_service as svc
|
||
import notifier
|
||
from db import get_session
|
||
from models import AlertRule, AlertEvent
|
||
|
||
KIND_LABEL = {
|
||
"price_above": "价格突破",
|
||
"price_below": "价格跌破",
|
||
"pct_above": "涨幅达到",
|
||
"pct_below": "跌幅达到",
|
||
}
|
||
|
||
|
||
def _hit(kind, threshold, q):
|
||
price, pct = q["price"], q["pct"]
|
||
if kind == "price_above":
|
||
return price >= threshold, price
|
||
if kind == "price_below":
|
||
return price <= threshold, price
|
||
if kind == "pct_above":
|
||
return pct >= threshold, pct
|
||
if kind == "pct_below":
|
||
return pct <= -abs(threshold), pct
|
||
return False, price
|
||
|
||
|
||
def check_alerts():
|
||
with get_session() as s:
|
||
rules = s.execute(select(AlertRule).where(AlertRule.status == "active")).scalars().all()
|
||
if not rules:
|
||
return {"checked": 0, "triggered": 0}
|
||
codes = list({r.code for r in rules})
|
||
quotes = svc.realtime_quotes(codes)
|
||
triggered = 0
|
||
push_msgs = []
|
||
for r in rules:
|
||
q = quotes.get(r.code)
|
||
if not q:
|
||
continue
|
||
hit, val = _hit(r.kind, r.threshold, q)
|
||
r.last_value = q["price"]
|
||
if hit:
|
||
unit = "" if r.kind.startswith("price") else "%"
|
||
msg = (f"{q['name']} {KIND_LABEL.get(r.kind, r.kind)} {r.threshold}{unit}"
|
||
f"(现价 {q['price']},{q['pct']:+.2f}%)")
|
||
s.add(AlertEvent(rule_id=r.id, code=r.code, name=q["name"], message=msg, value=val))
|
||
r.status = "triggered"
|
||
r.triggered_at = dt.datetime.now()
|
||
triggered += 1
|
||
push_msgs.append(msg)
|
||
s.commit()
|
||
|
||
# 触发后向已配置渠道推送(站外)
|
||
if push_msgs and notifier.any_enabled():
|
||
try:
|
||
notifier.notify("【智策预警】" + (push_msgs[0] if len(push_msgs) == 1 else f"{len(push_msgs)} 条预警触发"),
|
||
"\n".join(push_msgs))
|
||
except Exception:
|
||
pass
|
||
return {"checked": len(rules), "triggered": triggered}
|