feat: 新提拔自选重评补推——promote后detach启动backfill,90s后补跑未就绪重评并XMPP补推

This commit is contained in:
xxm
2026-08-18 10:26:36 +08:00
parent 45e71b9879
commit 529ffaa33a
2 changed files with 102 additions and 0 deletions
@@ -247,6 +247,15 @@ def main():
print(f"[PROMOTE] XMPP 通知已发送({promoted}只含完整重评)", flush=True) print(f"[PROMOTE] XMPP 通知已发送({promoted}只含完整重评)", flush=True)
except Exception as e: except Exception as e:
print(f"[PROMOTE] XMPP 通知失败: {e}", flush=True) print(f"[PROMOTE] XMPP 通知失败: {e}", flush=True)
# 2026-08-18 老莫:重评未就绪的必须补推——后台 detach 补跑重评+补推
if promoted > 0:
try:
_bf = subprocess.Popen(
["/home/hmo/MoFin/venv/bin/python", "/home/hmo/MoFin/deploy/profile-scripts/promote_reassess_backfill.py", "90"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
print(f"[PROMOTE] 后台补推已启动(PID={_bf.pid}, 90s后补跑未就绪重评并补推)", flush=True)
except Exception as e:
print(f"[PROMOTE] 后台补推启动失败: {e}", flush=True)
conn.close() conn.close()
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""promote_reassess_backfill.py — 新提拔自选的重评补推(老莫:未就绪的要补推)
promote 结束后 detach 启动:
1. 读 promote 记录的新提拔候选(holding_strategies 里 full_analysis 为空/短的 active 自选)
2. 对未就绪的补跑 per_stock_reassess300s 超时)
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)