#!/usr/bin/env python3 # -*- coding: utf-8 -*- """promote_reassess_backfill.py — 新提拔自选的重评补推(老莫:未就绪的要补推) promote 结束后 detach 启动: 1. 读 promote 记录的新提拔候选(holding_strategies 里 full_analysis 为空/短的 active 自选) 2. 对未就绪的补跑 per_stock_reassess(300s 超时) 3. 收集就绪的完整重评,通过 XMPP 补推给老莫 """ import sys, os, json, time, subprocess, sqlite3, urllib.request from datetime import datetime DB = "/home/hmo/MoFin/data/mofin.db" PY = "/home/hmo/MoFin/venv/bin/python" REASSESS = "/home/hmo/MoFin/deploy/profile-scripts/per_stock_reassess.py" XMPP = "http://127.0.0.1:5805/" TIMEOUT = 300 MAX_RETRY = 2 def xmpp_push(body): try: req = urllib.request.Request( XMPP, data=json.dumps({"to": "hmo@yoin.fun", "body": body, "type": "chat"}).encode(), headers={"Content-Type": "application/json"}) urllib.request.urlopen(req, timeout=10) return True except Exception as e: print(f" ⚠️ XMPP 补推失败: {e}", flush=True) return False def main(wait_sec=90): # 给首次重评完成时间(promote 触发的 REASSESS 可能还在跑) print(f"[BACKFILL] 等待 {wait_sec}s 后检查未就绪重评...", flush=True) time.sleep(wait_sec) conn = sqlite3.connect(DB, timeout=30) conn.execute("PRAGMA busy_timeout=30000") # 最近 30 分钟新提拔的自选,full_analysis 为空/短(未就绪) rows = conn.execute(""" SELECT code, name, strategy_name, rr_ratio, LENGTH(full_analysis) as fa_len, created_at FROM holding_strategies WHERE status='active' AND decision_type='自选策略' AND created_at >= datetime('now', 'localtime', '-30 minutes') AND (full_analysis IS NULL OR LENGTH(full_analysis) < 50) ORDER BY created_at DESC """).fetchall() conn.close() if not rows: print("[BACKFILL] 无未就绪重评", flush=True) return print(f"[BACKFILL] 发现 {len(rows)} 只未就绪重评,逐一补跑...", flush=True) backfilled = [] for code, name, strat, rr, fa_len, created in rows: ok = False for attempt in range(MAX_RETRY): try: r = subprocess.run([PY, REASSESS, code], capture_output=True, text=True, timeout=TIMEOUT) if r.returncode == 0: ok = True break else: print(f" ⚠️ {code} 重评失败(尝试{attempt+1}): {r.stderr.strip()[:100]}", flush=True) except Exception as e: print(f" ⚠️ {code} 重评异常(尝试{attempt+1}): {e}", flush=True) time.sleep(5) if ok: backfilled.append((code, name, strat, rr)) if not backfilled: print("[BACKFILL] 补跑后仍无就绪报告,跳过补推", flush=True) return # 收集就绪的完整报告 conn = sqlite3.connect(DB, timeout=30) codes = ",".join("?" * len(backfilled)) ready = conn.execute( f"SELECT code, name, strategy_name, rr_ratio, full_analysis FROM holding_strategies " f"WHERE status='active' AND code IN ({codes})", tuple(c[0] for c in backfilled)).fetchall() conn.close() body = f"📤 重评报告补推({len(ready)}只新入自选的重评已就绪)\n" for i, (code, name, strat, rr, fa) in enumerate(ready, 1): nm = name or code body += f"\n━━━ [{i}] {code} {nm} ━━━\n" body += f"策略: {strat or 'unknown'} | RR: {rr or 'N/A'}\n" if fa and len(str(fa)) > 50: body += f"【12维重评】\n{fa}\n" else: body += "(仍未能生成,已放弃补推)\n" if len(body) > 6000: body = body[:6000] + "\n…(内容过长已截断)" ok = xmpp_push(body) print(f"[BACKFILL] 补推{'成功' if ok else '失败'}: {len(ready)} 只", flush=True) if __name__ == "__main__": wait = int(sys.argv[1]) if len(sys.argv) > 1 else 90 main(wait)