diff --git a/scripts/stale_push_wlin.py b/scripts/stale_push_wlin.py index 58882e3d..d64f22cd 100644 --- a/scripts/stale_push_wlin.py +++ b/scripts/stale_push_wlin.py @@ -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)