stale_push_wlin: 30分钟同股同操作冷却
push_cooldown.json 记录每只股票每种操作的最后推送时间。
每次推送前检查 (code, action_type) 是否在30分钟内推过。
是 → 跳过该股(不出现在推送中)
全部跳过 → 整条消息静默不推
冷却键: {code}_{action_type}(如 300308_buy、688639_buy)
不同操作不受限:同一只股 买入→止损 隔10分钟也能推
不同股票不受限:华恒的buy不影响中际的buy
同步修复:港股每手股数香港股数(之前patch到旧文件没生效)
This commit is contained in:
+76
-17
@@ -17,6 +17,7 @@ import re
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
try:
|
||||
from urllib.request import Request, urlopen
|
||||
@@ -33,6 +34,7 @@ REGEN_SCRIPT = "/home/hmo/.hermes/profiles/position-analyst/scripts/per_stock_re
|
||||
REGEN_LOCK = "/tmp/.stale_push_wlin_regen.lock"
|
||||
MACRO_CTX = "/home/hmo/web-dashboard/data/macro_context.json"
|
||||
MARKET_JSON = "/home/hmo/web-dashboard/data/market.json"
|
||||
COOLDOWN_PATH = "/home/hmo/web-dashboard/data/push_cooldown.json"
|
||||
|
||||
NON_BUY_SIGNALS = ["观望", "弱势持有", "深套持有"]
|
||||
|
||||
@@ -156,6 +158,29 @@ def push_to_xmpp(text):
|
||||
print(f"[XMPP推送失败] {e}", file=sys.stderr)
|
||||
|
||||
|
||||
def load_cooldown():
|
||||
try:
|
||||
with open(COOLDOWN_PATH) as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def save_cooldown(cd):
|
||||
try:
|
||||
with open(COOLDOWN_PATH, "w") as f:
|
||||
json.dump(cd, f, indent=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def in_cooldown(code, action_type, cooldown_dict, minutes=30):
|
||||
key = f"{code}_{action_type}"
|
||||
last = cooldown_dict.get(key, 0)
|
||||
elapsed = time.time() - last
|
||||
return elapsed < minutes * 60, elapsed, key
|
||||
|
||||
|
||||
def main():
|
||||
r = subprocess.run(
|
||||
["python3", DETECTOR], capture_output=True, text=True, timeout=60
|
||||
@@ -178,6 +203,10 @@ def main():
|
||||
report = {"flagged": []}
|
||||
code_cur = {i["code"]: i.get("current", "") for i in report.get("flagged", [])}
|
||||
|
||||
# 加载冷却状态
|
||||
cooldown = load_cooldown()
|
||||
now_ts = time.time()
|
||||
|
||||
# 读 decisions.json 获取完整策略数据
|
||||
code_data = {}
|
||||
try:
|
||||
@@ -358,7 +387,13 @@ def main():
|
||||
if lots == 0:
|
||||
details = f"预算不足1手({budget:,.0f}/{lot_cost:,.0f}元)"
|
||||
else:
|
||||
shares = lots * (200 if code.startswith("688") else 100)
|
||||
if len(str(code)) == 5:
|
||||
hk_lot = hk_lot_size(code)
|
||||
shares = lots * hk_lot
|
||||
elif code.startswith("688"):
|
||||
shares = lots * 200
|
||||
else:
|
||||
shares = lots * 100
|
||||
details = f"{lots}手({shares}股,{lot_cost_total:,.0f}元)"
|
||||
|
||||
return theo_pct, pct_actual, details, lots, lot_cost_total
|
||||
@@ -426,6 +461,26 @@ def main():
|
||||
)
|
||||
|
||||
pfx = "" if len(code) == 6 else "HK$"
|
||||
|
||||
# 取分支动作类型
|
||||
branch_action = "hold"
|
||||
branch_rationale = ""
|
||||
if st and scenario_id:
|
||||
try:
|
||||
results = st.evaluate_branches(code, scenario_id, price, d.get("shares", 0), d.get("cost", 0))
|
||||
applicable = [r for r in results if r.get("applicable")]
|
||||
if applicable:
|
||||
best = min(applicable, key=lambda r: r.get("priority", 999))
|
||||
branch_action = best.get("action_type", "hold")
|
||||
branch_rationale = best.get("rationale", "")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 冷却检查:相同股+相同操作30分钟内不发
|
||||
cooled, elapsed, cd_key = in_cooldown(code, branch_action, cooldown)
|
||||
if cooled:
|
||||
continue
|
||||
|
||||
action_tag = "⚠️" if lots == 0 else "🛒"
|
||||
lines.append(
|
||||
f" {action_tag} {name}({code}) {pfx}{price:.2f} 买区{buy_low}~{buy_high} | "
|
||||
@@ -435,26 +490,30 @@ def main():
|
||||
f" 仓位:理论{theo_pct}%×总资产 | 建议{actual_pct}%({details})"
|
||||
)
|
||||
|
||||
# 读分支评估
|
||||
# 分支描述
|
||||
branch_line = ""
|
||||
if st and scenario_id:
|
||||
try:
|
||||
results = st.evaluate_branches(code, scenario_id, price, d.get("shares", 0), d.get("cost", 0))
|
||||
applicable = [r for r in results if r.get("applicable")]
|
||||
if applicable:
|
||||
# 取优先级最高的适用分支
|
||||
best = min(applicable, key=lambda r: r.get("priority", 999))
|
||||
action = best.get("action_type", "hold")
|
||||
rationale = best.get("rationale", "")
|
||||
branch_line = f" 【{scenario_label}→{action}】{rationale}"
|
||||
else:
|
||||
branch_line = f" 【{scenario_label}→持有】无匹配分支"
|
||||
except Exception:
|
||||
branch_line = ""
|
||||
if branch_action != "hold":
|
||||
branch_line = f" 【{scenario_label}→{branch_action}】{branch_rationale}"
|
||||
if branch_line:
|
||||
# 追加到上一个元素
|
||||
lines[-1] += f"\n{branch_line}"
|
||||
|
||||
# 记录推送时间(冷却计时用)
|
||||
cooldown[cd_key] = now_ts
|
||||
|
||||
save_cooldown(cooldown)
|
||||
|
||||
# 修正可操作数量(剔除冷却跳过后的实际数量)
|
||||
actual_n = len(lines) - (1 if macro_line else 0) - 1 # 减去市场背景 + 操作建议标题
|
||||
if actual_n != n:
|
||||
# 更新操作建议行
|
||||
for i, ln in enumerate(lines):
|
||||
if "【💡 操作建议】" in ln:
|
||||
lines[i] = f"【💡 操作建议】(当前{actual_n}只自选可操作 | 总资产{total_assets:,.0f}元 现金{available_cash:,.0f}元)"
|
||||
break
|
||||
|
||||
if actual_n <= 0:
|
||||
return 0 # 全部冷却中 → 静默,不推
|
||||
|
||||
lines.insert(0, f"【知微】自选买入提醒 {now} | 总资产{total_assets:,.0f}元")
|
||||
out = "\n".join(lines)
|
||||
print(out)
|
||||
|
||||
Reference in New Issue
Block a user