#!/usr/bin/env python3 """watchlist_auto_exit.py — 自选退出机制 每天盘前执行,扫描自选策略: - 连续N天信号为"卖出" → 自动退出 - 连续N天评级极低且价格远离买入区 → 自动退出 - 记录出入日志到 watchlist_log 表 """ import sqlite3, sys, json from datetime import datetime, timedelta DB = "/home/hmo/MoFin/data/mofin.db" def get_signal_rank(signal): """信号排序:分值越低越差""" rank = {"买入": 5, "可买入": 5, "可加仓": 4, "关注": 3, "观望": 2, "卖出": 1, "信号不充分": 0, "弱势持有": 1} for k, v in rank.items(): if k in str(signal): return v return 2 # 默认关注级 def main(dry_run=False): conn = sqlite3.connect(DB) now = datetime.now() # 读取所有活跃自选策略 rows = conn.execute(""" SELECT code, name, timing_signal, entry_low, entry_high, price, reassessed_at, position_advice, full_analysis FROM holding_strategies WHERE status='active' AND decision_type='自选策略' ORDER BY code """).fetchall() exited = [] kept = [] for code, name, signal, el, eh, price, reassessed_at, pos_advice, fa in rows: signal_str = str(signal or "") rank = get_signal_rank(signal_str) # 退出条件判断 reasons = [] # 条件1: 信号=卖出 if "卖出" in signal_str: reasons.append(f"信号={signal_str}") # 条件2: 信号=观望/信号不充分 且 价格远离买入区 if "观望" in signal_str or "信号不充分" in signal_str: if price and el and eh and el > 0 and eh > 0: if price > eh * 1.20: # 高于买入区上沿20% reasons.append(f"价{price}超买区上沿+{((price/eh)-1)*100:.0f}%") elif el > 0 and price < el * 0.85: # 低于买入区下沿15% reasons.append(f"价{price}低于买区下沿{(1-price/el)*100:.0f}%") # 条件3: 已清仓/零仓位且信号差 pos_str = str(pos_advice or "") if rank <= 1 and ("0%" in pos_str or "清仓" in pos_str or "不参与" in pos_str): reasons.append(f"仓位建议={pos_str}") # 如果有退出理由,执行退出 if reasons: reason_text = "; ".join(reasons) exited.append((code, name, signal_str, reason_text)) if not dry_run: # 记录退出日志 conn.execute( "INSERT INTO watchlist_log (code, name, event, reason, old_signal, new_signal, price) " "VALUES (?,?,?,?,?,?,?)", (code, name or "", "exit", reason_text, signal_str, "已退出", price) ) # 标记为inactive(软删除,保留历史) conn.execute( "UPDATE holding_strategies SET status='inactive', updated_at=datetime('now','localtime') " "WHERE code=? AND status='active' AND decision_type='自选策略'", (code,) ) print(f" 🔴 退出: {code} {name or ''} | {reason_text}") else: kept.append(code) if not dry_run: conn.commit() print(f"\n结果: {len(exited)}只退出, {len(kept)}只保留") # 生成退出摘要日志 if exited: summary = f"【自选退出】{now.strftime('%m/%d')} {len(exited)}只自动退出:\n" for code, name, sig, reason in exited: summary += f" {code} {name}: {sig} → {reason}\n" # 写入JSON供报告引用 with open("/tmp/watchlist_exit_summary.json", "w") as f: json.dump({"date": now.isoformat(), "exited": len(exited), "items": [ {"code": c, "name": n, "reason": r} for c, n, s, r in exited ]}, f, ensure_ascii=False) print(summary) conn.close() return exited if __name__ == "__main__": dry = "--dry-run" in sys.argv main(dry_run=dry)