Files
MoFin/scripts/promote_candidates.py
T
知微 9239ab40c0 docs: 更新CHANGELOG + 开发日志记录07/13全部变更
- XMPP Bot非阻塞/本地XMPP/MUC修复
- price_monitor推送冷却持久化
- 深套股规则重构+换股规划swap-plan
- 周月线技术分析增强
- 健康Tab写无读清零
- stale_push_wlin/per_stock_reassess/promote_candidates更新
- static/index.html + mofin_health.json + server.py同步
2026-07-13 12:09:25 +08:00

131 lines
4.9 KiB
Python

#!/usr/bin/env python3
"""promote_candidates.py — 自动提拔候选股入自选
从 candidates 表读未提拔的候选,评估后自动加入 holding_strategies。
"""
import sys, json, sqlite3
from pathlib import Path
from datetime import datetime
DB_PATH = Path("/home/hmo/MoFin/data/mofin.db")
def main():
conn = sqlite3.connect(str(DB_PATH))
conn.row_factory = sqlite3.Row
# 读未提拔候选(按评分降序)
rows = conn.execute("""
SELECT c.code, c.name, c.score_final, c.entry_range, c.stop_loss, c.target
FROM candidates c
WHERE (c.promoted IS NULL OR c.promoted = 0)
AND (c.dropped IS NULL OR c.dropped = 0)
AND c.score_final >= 4
ORDER BY c.score_final DESC
""").fetchall()
if not rows:
print("[PROMOTE] 无待提拔候选")
conn.close()
return
promoted = 0
for r in rows:
code = str(r[0])
name = r[1] or code
score = r[2] or 0
entry_range = r[3] or ""
sl = r[4] or 0
tp = r[5] or 0
# 解析 entry_range
el, eh = 0, 0
if "~" in entry_range:
parts = entry_range.split("~")
try:
el = float(parts[0])
eh = float(parts[1])
except: pass
# 查是否已在 holding_strategies
exists = conn.execute(
"SELECT id FROM holding_strategies WHERE code=? AND status='active'",
(code,)
).fetchone()
if exists:
conn.execute("UPDATE candidates SET promoted=1 WHERE code=?", (code,))
print(f" ⏭ {code} {name} 已在自选中,标记promoted")
continue
# 验证实时价格:无有效价格的候选股不入自选(防假数据污染)
try:
import subprocess, json as _jj
_r = subprocess.run(["python3", "/home/hmo/.hermes/profiles/position-analyst/scripts/stock_quote.py", code],
capture_output=True, text=True, timeout=10)
_q = _jj.loads(_r.stdout)
if float(_q.get("price", 0)) <= 0:
print(f" ⏭ {code} {name} 无实时价格,跳过")
continue
except Exception as _e:
print(f" ⏭ {code} {name} 价格获取失败({_e}),跳过")
continue
# 构建策略
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
timing_signal = "买入" if score >= 7 else "关注"
price_est = (el + eh) / 2 if el > 0 and eh > 0 else 0
reason_text = []
if el > 0: reason_text.append(f"买{el}~{eh}")
if sl > 0: reason_text.append(f"损{sl}")
if tp > 0: reason_text.append(f"盈{tp}")
if sl > 0 and tp > 0 and price_est > 0:
rr = (tp - price_est) / (price_est - sl) if (price_est - sl) > 0 else 0
reason_text.append(f"RR{rr:.1f}")
reason_text.append(f"评分{score}")
action = " | ".join(reason_text) if reason_text else f"市场扫描发现(评分{score})"
conn.execute("""
INSERT INTO holding_strategies
(code, name, price, entry_low, entry_high, stop_loss, take_profit,
timing_signal, action, decision_type, strategy_type, status,
rr_ratio, stock_category, created_at, updated_at,
sector_context, quality_check)
VALUES (?,?,?,?,?,?,?,?,?,'自选策略','scan',
'active',0,'关注',?,?,'', 'pending')
""", (code, name, 0, el, eh, sl, tp, timing_signal, action, now, now))
conn.execute("UPDATE candidates SET promoted=1 WHERE code=?", (code,))
promoted += 1
print(f" ✅ {code} {name} 评分{score} → 已加入自选({timing_signal})", flush=True)
# 触发全量重评(生成完整9维策略)
try:
import subprocess as _sp
r = _sp.run(["python3", "/home/hmo/MoFin/scripts/per_stock_reassess.py", code],
capture_output=True, text=True, timeout=60)
if r.returncode == 0:
print(f" 重评完成", flush=True)
else:
print(f" 重评失败: {r.stderr.strip()[:100]}", flush=True)
except Exception as e:
print(f" 重评异常: {e}", flush=True)
conn.commit()
print(f"\n[PROMOTE] 本次提拔{promoted}只", flush=True)
# 推XMPP
if promoted > 0:
try:
import urllib.request
msg = f"📈 自动提拔{promoted}只候选入自选"
payload = json.dumps({"to": "hmo@yoin.fun", "body": msg, "type": "chat"}).encode()
req = urllib.request.Request("http://127.0.0.1:5805/", data=payload,
headers={"Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=5)
except Exception:
pass
conn.close()
if __name__ == "__main__":
main()