feat: 盘前全量重评(08:10)+自选退出机制+进出日志
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
"""premarket_full_review.py — 盘前全量重评
|
||||
|
||||
执行顺序:
|
||||
1. regenerate_all() 全量技术分析重评(持仓+自选)
|
||||
2. watchlist_auto_exit() 自选退出检查
|
||||
3. 输出摘要
|
||||
|
||||
调度:交易日 08:10(A股09:30开盘)
|
||||
"""
|
||||
import sys, os, json
|
||||
sys.path.insert(0, '/home/hmo/MoFin')
|
||||
|
||||
# Step 1: 全量重评
|
||||
print("=" * 50)
|
||||
print("📊 盘前全量重评开始")
|
||||
print("=" * 50)
|
||||
from strategy_lifecycle import regenerate_all
|
||||
result = regenerate_all(stdout=True)
|
||||
print(f"\n重评完成: {result.get('ok',0)}/{result.get('total',0)}成功")
|
||||
|
||||
# Step 2: 自选退出
|
||||
print("\n" + "=" * 50)
|
||||
print("🔍 自选退出检查")
|
||||
print("=" * 50)
|
||||
from scripts.watchlist_auto_exit import main as auto_exit
|
||||
exited = auto_exit(dry_run=False)
|
||||
|
||||
# Step 3: 写入摘要供开盘简报引用
|
||||
summary = {
|
||||
"premarket_at": __import__('datetime').datetime.now().isoformat(),
|
||||
"reassess": result,
|
||||
"auto_exit": [{"code": c, "name": n, "reason": r} for c, n, s, r in exited],
|
||||
"total_kept": result.get('total', 0) - len(exited),
|
||||
}
|
||||
os.makedirs("/tmp/mofin_premarket", exist_ok=True)
|
||||
with open("/tmp/mofin_premarket/summary.json", "w") as f:
|
||||
json.dump(summary, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n✅ 盘前重评完毕")
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user