sync: price_monitor SIGALRM fix to scripts/ (legacy location)
This commit is contained in:
@@ -37,7 +37,8 @@ XMPP_USER = "hmo@yoin.fun"
|
|||||||
XMPP_BRIDGE = "http://127.0.0.1:5805/"
|
XMPP_BRIDGE = "http://127.0.0.1:5805/"
|
||||||
|
|
||||||
def push_to_xmpp(text):
|
def push_to_xmpp(text):
|
||||||
"""通过知微 HTTP bridge 推送到Dad私信"""
|
"""原始直推(已废弃直用)——保留给极少数必须原样的场景。
|
||||||
|
新代码请用 _push_action/_push_digest。"""
|
||||||
if not text.strip():
|
if not text.strip():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -51,6 +52,25 @@ def push_to_xmpp(text):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[XMPP推送失败] {e}", file=sys.stderr)
|
print(f"[XMPP推送失败] {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
# ── 分级推送(2026-07-21 信噪比纪律,红线#12)──
|
||||||
|
# ACTION: 破止损/重评确认的操作信号 — 直通不限速
|
||||||
|
# INFO: 未确认的进区提示 — 聚合成摘要,30min 限 1 条
|
||||||
|
def _push_action(category, text):
|
||||||
|
try:
|
||||||
|
from alert_helper import notify, ACTION
|
||||||
|
notify(category, text, ACTION)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ACTION推送失败] {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def _push_digest(category, text):
|
||||||
|
try:
|
||||||
|
from alert_helper import notify, INFO
|
||||||
|
notify(category, text, INFO)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[INFO推送失败] {e}", file=sys.stderr)
|
||||||
|
|
||||||
# ── 批量拉取价格 ──────────────────────────────────────────────────────────
|
# ── 批量拉取价格 ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def fetch_all_prices(codes):
|
def fetch_all_prices(codes):
|
||||||
@@ -387,10 +407,17 @@ def _handle_sigterm(signum, frame):
|
|||||||
_cleanup_lock()
|
_cleanup_lock()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
def _handle_sigalrm(signum, frame):
|
||||||
|
"""收到SIGALRM强制超时时清理锁文件后退出"""
|
||||||
|
_cleanup_lock()
|
||||||
|
print(f"[TIMEOUT] 本轮执行超时({signum}s),已清理锁文件", file=sys.stderr, flush=True)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
def run_once(round_label=""):
|
def run_once(round_label=""):
|
||||||
"""执行一轮完整的监控流程"""
|
"""执行一轮完整的监控流程"""
|
||||||
import os, signal # 必须在开头import,否则os变量会被后面的局部import绑定覆盖
|
import os, signal # 必须在开头import,否则os变量会被后面的局部import绑定覆盖
|
||||||
signal.signal(signal.SIGTERM, _handle_sigterm)
|
signal.signal(signal.SIGTERM, _handle_sigterm)
|
||||||
|
signal.signal(signal.SIGALRM, _handle_sigalrm)
|
||||||
os.nice(10) # 降低优先级,避免与DB其他写操作抢占
|
os.nice(10) # 降低优先级,避免与DB其他写操作抢占
|
||||||
# ── 进程锁:同一时间只跑一个实例 ──
|
# ── 进程锁:同一时间只跑一个实例 ──
|
||||||
_lk = "/tmp/price_monitor.lock"
|
_lk = "/tmp/price_monitor.lock"
|
||||||
@@ -405,6 +432,7 @@ def run_once(round_label=""):
|
|||||||
pass
|
pass
|
||||||
with open(_lk, "w") as _f:
|
with open(_lk, "w") as _f:
|
||||||
_f.write(str(os.getpid()))
|
_f.write(str(os.getpid()))
|
||||||
|
signal.alarm(120) # 硬上限120s,超时自动清理锁退出
|
||||||
|
|
||||||
label = f" [{round_label}]" if round_label else ""
|
label = f" [{round_label}]" if round_label else ""
|
||||||
start = time.time()
|
start = time.time()
|
||||||
@@ -460,6 +488,9 @@ def run_once(round_label=""):
|
|||||||
# 批量拉取这些股票的价格
|
# 批量拉取这些股票的价格
|
||||||
prices = fetch_all_prices(list(check_codes))
|
prices = fetch_all_prices(list(check_codes))
|
||||||
|
|
||||||
|
# 本轮进区事件收集(聚合成一条摘要推送,替代逐条轰炸)
|
||||||
|
_zone_entries = []
|
||||||
|
|
||||||
for d in active:
|
for d in active:
|
||||||
code = d["code"]
|
code = d["code"]
|
||||||
trig = d.get("trigger", {})
|
trig = d.get("trigger", {})
|
||||||
@@ -496,7 +527,7 @@ def run_once(round_label=""):
|
|||||||
if _budget_low:
|
if _budget_low:
|
||||||
outputs.append(f" 📨 止损触发(超时跳过重评)→已推送Dad")
|
outputs.append(f" 📨 止损触发(超时跳过重评)→已推送Dad")
|
||||||
if _can_push(code, "stop_loss"):
|
if _can_push(code, "stop_loss"):
|
||||||
push_to_xmpp(f"⚠️ {name}({code}) {price} → 跌破止损{hi}!")
|
_push_action("止损告警", f"⚠️ {name}({code}) {price} → 跌破止损{hi}!")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
cost = d.get("cost", 0) or 0
|
cost = d.get("cost", 0) or 0
|
||||||
@@ -512,7 +543,7 @@ def run_once(round_label=""):
|
|||||||
rr = result.get("rr_ratio", 0)
|
rr = result.get("rr_ratio", 0)
|
||||||
if _can_push(code, "stop_loss"):
|
if _can_push(code, "stop_loss"):
|
||||||
msg = f"🔔 {name}({code}) 价{price}→触发操作区间{max(buy_lo,0):.2f}~{buy_hi:.2f},已触发重评|RR={rr}"
|
msg = f"🔔 {name}({code}) 价{price}→触发操作区间{max(buy_lo,0):.2f}~{buy_hi:.2f},已触发重评|RR={rr}"
|
||||||
push_to_xmpp(msg)
|
_push_action("操作信号", msg)
|
||||||
outputs.append(f" 📨 止损重评→已推送Dad: {action}")
|
outputs.append(f" 📨 止损重评→已推送Dad: {action}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
outputs.append(f" ⚠️ 止损重评失败: {e}")
|
outputs.append(f" ⚠️ 止损重评失败: {e}")
|
||||||
@@ -529,11 +560,11 @@ 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 _budget_low:
|
||||||
if _can_push(code, key):
|
if _can_push(code, key):
|
||||||
push_to_xmpp(f"⚡ {name}({code}) {price} → 进入{label}{lo}~{hi}")
|
_zone_entries.append(f"{name}({code}) {price}→{label}{lo}~{hi}")
|
||||||
outputs.append(f" 📨 区间触发(超时跳过重评)→已推送Dad")
|
outputs.append(f" 📨 区间触发(超时)→记入摘要")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
cost = d.get("cost", 0) or 0
|
cost = d.get("cost", 0) or 0
|
||||||
@@ -552,7 +583,7 @@ def run_once(round_label=""):
|
|||||||
rr = result.get("rr_ratio", 0)
|
rr = result.get("rr_ratio", 0)
|
||||||
if _can_push(code, key):
|
if _can_push(code, key):
|
||||||
msg = f"🔔 {name}({code}) 价{price}→触发{zone_desc},已触发重评|RR={rr}"
|
msg = f"🔔 {name}({code}) 价{price}→触发{zone_desc},已触发重评|RR={rr}"
|
||||||
push_to_xmpp(msg)
|
_push_action("操作信号", msg)
|
||||||
outputs.append(f" 📨 区间触发重评→已推送Dad: {action}")
|
outputs.append(f" 📨 区间触发重评→已推送Dad: {action}")
|
||||||
else:
|
else:
|
||||||
reason = f"重评结果:{timing_signal},不构成操作建议"
|
reason = f"重评结果:{timing_signal},不构成操作建议"
|
||||||
@@ -568,6 +599,12 @@ def run_once(round_label=""):
|
|||||||
state[code][key] = False
|
state[code][key] = False
|
||||||
state_updated = True
|
state_updated = True
|
||||||
|
|
||||||
|
# === 第二步收尾:进区事件聚合成一条摘要推送(INFO级,30min限1条+截断)===
|
||||||
|
if _zone_entries:
|
||||||
|
_digest = f"📋 {len(_zone_entries)}只进入操作区:\n" + "\n".join(f"• {e}" for e in _zone_entries)
|
||||||
|
_push_digest("盘中触发", _digest)
|
||||||
|
outputs.append(f"📨 进区摘要({len(_zone_entries)}只)→已按INFO策略推送")
|
||||||
|
|
||||||
# === 第三步:买入区偏离检测 + 自动重评 ===
|
# === 第三步:买入区偏离检测 + 自动重评 ===
|
||||||
reassesed_codes = []
|
reassesed_codes = []
|
||||||
# 先做急跌检测(仅持仓,自选股不推送暴跌告警)
|
# 先做急跌检测(仅持仓,自选股不推送暴跌告警)
|
||||||
@@ -595,7 +632,7 @@ def run_once(round_label=""):
|
|||||||
stop_loss = d.get("stop_loss", 0)
|
stop_loss = d.get("stop_loss", 0)
|
||||||
sl_note = f" 止损{stop_loss}" if stop_loss else ""
|
sl_note = f" 止损{stop_loss}" if stop_loss else ""
|
||||||
msg = f"🔻 {name}({code}) {price} 暴跌{cp:.1f}%!{sl_note}"
|
msg = f"🔻 {name}({code}) {price} 暴跌{cp:.1f}%!{sl_note}"
|
||||||
push_to_xmpp(msg)
|
_push_action("急跌告警", msg)
|
||||||
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
|
||||||
@@ -716,6 +753,9 @@ def run_once(round_label=""):
|
|||||||
# 输出耗时
|
# 输出耗时
|
||||||
print(f"⏱{label} {elapsed:.1f}s", flush=True)
|
print(f"⏱{label} {elapsed:.1f}s", flush=True)
|
||||||
|
|
||||||
|
# 取消超时定时器(正常完成)
|
||||||
|
signal.alarm(0)
|
||||||
|
|
||||||
# 清理进程锁
|
# 清理进程锁
|
||||||
try:
|
try:
|
||||||
os.remove("/tmp/price_monitor.lock")
|
os.remove("/tmp/price_monitor.lock")
|
||||||
|
|||||||
Reference in New Issue
Block a user