fix: 暴跌告警仅持仓触发 + 状态立即持久化防重复推送
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user