fix: 暴跌告警仅持仓触发 + 状态立即持久化防重复推送

This commit is contained in:
知微
2026-07-14 15:33:35 +08:00
parent 2c0d6ff587
commit b4a5020cf5
+37 -4
View File
@@ -385,9 +385,22 @@ def get_trigger_zones(trigger):
return zones return zones
def _cleanup_lock():
"""清理进程锁文件"""
try:
os.remove("/tmp/price_monitor.lock")
except Exception:
pass
def _handle_sigterm(signum, frame):
"""收到SIGTERM时清理锁文件后退出"""
_cleanup_lock()
sys.exit(0)
def run_once(round_label=""): def run_once(round_label=""):
"""执行一轮完整的监控流程""" """执行一轮完整的监控流程"""
import os # 必须在开头import,否则os变量会被后面的局部import绑定覆盖 import os, signal # 必须在开头import,否则os变量会被后面的局部import绑定覆盖
signal.signal(signal.SIGTERM, _handle_sigterm)
os.nice(10) # 降低优先级,避免与DB其他写操作抢占 os.nice(10) # 降低优先级,避免与DB其他写操作抢占
# ── 进程锁:同一时间只跑一个实例 ── # ── 进程锁:同一时间只跑一个实例 ──
_lk = "/tmp/price_monitor.lock" _lk = "/tmp/price_monitor.lock"
@@ -405,6 +418,7 @@ def run_once(round_label=""):
label = f" [{round_label}]" if round_label else "" label = f" [{round_label}]" if round_label else ""
start = time.time() start = time.time()
TIME_BUDGET = 90 # 预留30s给输出和清理,90s内必须完成核心逻辑
# === 第一步:一次性刷新所有价格 === # === 第一步:一次性刷新所有价格 ===
refreshed = refresh_data_prices() refreshed = refresh_data_prices()
@@ -477,6 +491,9 @@ def run_once(round_label=""):
if code not in state: if code not in state:
state[code] = {} state[code] = {}
# 时间预算检查:如果超时,跳过重评只做状态记录
_budget_low = (time.time() - start) > TIME_BUDGET
for key, label, lo, hi in zones: for key, label, lo, hi in zones:
in_zone = lo <= price <= hi in_zone = lo <= price <= hi
prev_in_zone = state[code].get(key, None) prev_in_zone = state[code].get(key, None)
@@ -485,7 +502,12 @@ def run_once(round_label=""):
if key == "stop_loss": if key == "stop_loss":
outputs.append(f"⚠️ {name}({code}) {price} → 跌破止损{hi}") outputs.append(f"⚠️ {name}({code}) {price} → 跌破止损{hi}")
record_event(code, name, "stop_loss", price, str(hi)) record_event(code, name, "stop_loss", price, str(hi))
# 止损触发 → 立即重评并推送给Dad # 止损触发 → 立即重评并推送给Dad(时间不够则直接推原始告警)
if _budget_low:
outputs.append(f" 📨 止损触发(超时跳过重评)→已推送Dad")
if _can_push(code, "stop_loss"):
push_to_xmpp(f"⚠️ {name}({code}) {price} → 跌破止损{hi}")
else:
try: try:
cost = d.get("cost", 0) or 0 cost = d.get("cost", 0) or 0
shares = d.get("shares", 0) or 0 shares = d.get("shares", 0) or 0
@@ -517,7 +539,12 @@ def run_once(round_label=""):
extra = f"{act}" extra = f"{act}"
outputs.append(f"{name}({code}) {price} → 进入{label}{lo}~{hi}{extra}") outputs.append(f"{name}({code}) {price} → 进入{label}{lo}~{hi}{extra}")
record_event(code, name, "entry_zone", price, f"{lo}~{hi}", label) record_event(code, name, "entry_zone", price, f"{lo}~{hi}", label)
# 进入区间 → 立即重评并推送给Dad # 进入区间 → 立即重评并推送给Dad(时间不够则跳过重评直接推原始告警)
if _budget_low:
if _can_push(code, key):
push_to_xmpp(f"{name}({code}) {price} → 进入{label}{lo}~{hi}")
outputs.append(f" 📨 区间触发(超时跳过重评)→已推送Dad")
else:
try: try:
cost = d.get("cost", 0) or 0 cost = d.get("cost", 0) or 0
shares = d.get("shares", 0) or 0 shares = d.get("shares", 0) or 0
@@ -553,9 +580,13 @@ def run_once(round_label=""):
# === 第三步:买入区偏离检测 + 自动重评 === # === 第三步:买入区偏离检测 + 自动重评 ===
reassesed_codes = [] reassesed_codes = []
# 先做急跌检测(所有持仓,不依赖买入区 # 先做急跌检测(持仓,自选股不推送暴跌告警
holdings_codes = {d["code"] for d in active if d.get("shares", 0) > 0}
for d in active: for d in active:
code = d["code"] code = d["code"]
# 非持仓跳过
if code not in holdings_codes:
continue
name = d.get("name", code) name = d.get("name", code)
price_info = prices.get(code) price_info = prices.get(code)
if not price_info: if not price_info:
@@ -578,6 +609,8 @@ def run_once(round_label=""):
outputs.append(msg) outputs.append(msg)
state.setdefault(code, {})["__sharp_decline_triggered"] = True state.setdefault(code, {})["__sharp_decline_triggered"] = True
state_updated = True state_updated = True
# 立即持久化,防止后续超时导致状态丢失而重复推送
save_state(state)
elif cp > -5: elif cp > -5:
# 反弹后清除告警标记,下次再跌还能报 # 反弹后清除告警标记,下次再跌还能报
state.setdefault(code, {}).pop("__sharp_decline_triggered", None) state.setdefault(code, {}).pop("__sharp_decline_triggered", None)