feat: 推送流程重构—进操作区间→重评→(可操作→推荐|不可操作→说明)

This commit is contained in:
知微
2026-07-09 10:26:53 +08:00
parent 96d9071b8f
commit a147085f1a
+70 -16
View File
@@ -14,10 +14,7 @@ no_agent模式:有推送→输出;无→静默
搭配 cron: no_agent=True, 交易日每30分跑一次
"""
import subprocess
import sys
import re
import json
import os
import sys, re, json, os, time
import threading
import time
from datetime import datetime, time
@@ -149,6 +146,37 @@ COOLDOWN_PATH = "/home/hmo/web-dashboard/data/push_cooldown.json"
NON_BUY_SIGNALS = ["观望", "弱势持有", "深套持有"]
# 重评冷却:4小时内不重复重评同一股票
# Dad确认流程: 进区间→重评→(可操作→推荐|不可操作→说明)→冷却期内不再重评+不推重复
REASSESS_COOLDOWN_HOURS = 4
def get_last_reassess_time(code: str):
"""从holding_strategies查最近重评时间"""
try:
db = get_conn()
row = db.execute(
"SELECT updated_at FROM holding_strategies WHERE code=? AND status IN ('active','updated') ORDER BY updated_at DESC LIMIT 1",
(code,)
).fetchone()
db.close()
if row and row[0]:
return datetime.strptime(row[0][:19], '%Y-%m-%d %H:%M:%S')
except Exception:
pass
return None
def is_due_for_reassess(code: str, hours=None) -> bool:
"""检查股票是否到重评时间:无历史记录或上次重评超过hours小时"""
if hours is None:
hours = REASSESS_COOLDOWN_HOURS
last = get_last_reassess_time(code)
if last is None:
return True # 从未重评过→需要
elapsed = datetime.now() - last
return elapsed.total_seconds() > hours * 3600
def load_macro_line():
"""加载大盘和市场的简要描述"""
@@ -422,11 +450,12 @@ def main():
if macro_line:
lines.append(f"【市场背景】{macro_line}")
# [关键修复: 2026-06-25] 所有预推票先重评,再出报告
# 不只是 stale 的重评,所有在买入区的自选都先刷新策略,确保推荐不滞后
to_reassess = list(set(s[1] for s in stocks) | set(s[1] for s in stale_list))
if to_reassess:
trigger_regen_sync(to_reassess)
# [关键修复: 2026-07-09] Dad确认流程:进区间→重评→(可操作→推荐|不可操作→说明)
# 冷却期内不再重复重评同一股票:查DB holding_strategies.updated_at
all_codes_in_zone = list(set(s[1] for s in stocks) | set(s[1] for s in stale_list))
needs_reassess = [c for c in all_codes_in_zone if is_due_for_reassess(c)]
if needs_reassess:
trigger_regen_sync(needs_reassess)
# 重评完成,re-read 最新策略(从DB)
code_data = {}
try:
@@ -449,18 +478,33 @@ def main():
# 重建 stocks 列表,用新数据判断(不再用旧 is_stale 标记,因为已全部重评)
stocks = []
zone_notes = [] # 在操作区间但不可操作→发说明
for (name, code, price, buy_low, buy_high, cur, is_stale) in all_candidates:
# 重评后重新检查 actionability(用新 timing_signal
sig = code_data.get(code, {}).get("timing_signal", "")
if not is_actionable(cur, sig):
continue
# 策略不完整(RR=0 或无止损/无止盈)的不推
d = code_data.get(code, {})
rr = d.get("rr_ratio", 0) or 0
sl = d.get("stop_loss", 0) or 0
tp = d.get("take_profit", 0) or 0
if rr <= 0 or sl <= 0 or tp <= 0:
# 判断重评后的可操作性
reason = ""
if not is_actionable(cur, sig):
reason = f"信号'{sig}'非可操作方向"
elif rr <= 0 or sl <= 0 or tp <= 0:
reason = f"策略不完整(RR={rr} 损={sl} 盈={tp})"
elif any(kw in sig for kw in ["等企稳", "信号不充分"]):
reason = f"信号'{sig}',暂不建议操作"
if reason:
# 冷却检查:同股同原因4小时内不发(匹配重评冷却)
ck = f"zone_note_{code}"
now_ts = datetime.now().timestamp()
last = cooldown.get(ck, 0)
if now_ts - last > REASSESS_COOLDOWN_HOURS * 3600:
zone_notes.append((name, code, price, buy_low, buy_high, reason, sig))
cooldown[ck] = now_ts
continue
lot = lot_cost(code, price)
ratio = lot / cash if cash > 0 else 999
stocks.append((name, code, price, buy_low, buy_high, lot, ratio))
@@ -535,8 +579,8 @@ def main():
actionable.append(s)
if not actionable:
return 0 # 无操作信号 → 静默,不推
if not actionable and not zone_notes:
return 0 # 无推荐也无区间说明 → 静默
# 加载基本面缓存(PE等)
fund_cache = {}
@@ -953,6 +997,16 @@ def main():
lines.append("【⏳ 提前准备(T+2港股提前出清)】")
lines.extend(t2_lines)
# 操作区间内但重评后不可操作的→发说明
if zone_notes:
lines.append("")
lines.append("【📋 操作区间提醒】进入操作区间,但重评后不构成推荐操作:")
for name, code, price, buy_low, buy_high, reason, sig in zone_notes:
lines.append(
f" {name}({code}) 价{price:.2f} 区间{buy_low}~{buy_high} "
f"→ 重评结果: {reason}"
)
lines.insert(0, f"【知微】自选买入提醒 {now} | 总资产{total_assets:,.0f}")
out = "\n".join(lines)
print(out)