feat(推荐): 换仓策略——现金不足时自动给出减弱势持仓换入排队推荐的方案
This commit is contained in:
+39
-6
@@ -1237,29 +1237,60 @@ def flush_rec_digest(max_items=5):
|
||||
items.sort(key=lambda x: x.get('rr') or 0, reverse=True)
|
||||
top = items[:max_items]
|
||||
|
||||
# ── 现金预算(决定操盘建议)──
|
||||
# ── 现金预算(决定操盘建议 + 换仓策略)──
|
||||
cash_note = ""
|
||||
rotation_note = ""
|
||||
try:
|
||||
conn = _sq.connect("/home/hmo/MoFin/data/mofin.db")
|
||||
conn.row_factory = _sq.Row
|
||||
r = conn.execute("SELECT cash, total_assets FROM portfolio_summary WHERE id=1").fetchone()
|
||||
conn.close()
|
||||
if r and r[1]:
|
||||
cash, total = r[0] or 0, r[1]
|
||||
budget_pct = cash / total * 100
|
||||
cum = 0.0
|
||||
buys = []
|
||||
queued = []
|
||||
for it in top:
|
||||
import re as _re
|
||||
m = _re.search(r'(\d+(?:\.\d+)?)\s*%', it.get('position') or '')
|
||||
pct = float(m.group(1)) if m else 8.0
|
||||
if cum + pct <= budget_pct + 1e-9:
|
||||
buys.append(f"{it.get('name') or it['code']}≈{pct:.0f}%")
|
||||
buys.append((it, pct))
|
||||
cum += pct
|
||||
else:
|
||||
queued.append((it, pct))
|
||||
cash_note = (f"现金{cash/10000:.1f}万({budget_pct:.1f}%)|按预算本次可执行: "
|
||||
+ ("、".join(buys) if buys else "无(预算不足,先排队观察)")
|
||||
+ ("、".join(f"{b[0].get('name') or b[0]['code']}≈{b[1]:.0f}%" for b in buys) if buys else "无")
|
||||
+ (f"(合计≈{cum:.0f}%)" if buys else ""))
|
||||
except Exception:
|
||||
pass
|
||||
# ── 换仓策略:有排队推荐时,找可减的弱持仓来腾挪 ──
|
||||
if queued:
|
||||
weak = conn.execute("""
|
||||
SELECT hs.code, hs.name, hs.timing_signal, h.position_pct, h.cost, lp.price, lp.change_pct
|
||||
FROM holding_strategies hs
|
||||
JOIN holdings h ON hs.code = h.code AND h.is_active = 1
|
||||
LEFT JOIN live_prices lp ON hs.code = lp.code
|
||||
WHERE hs.status='active' AND h.shares > 0
|
||||
AND hs.timing_signal IN ('弱势持有','观望','持有')
|
||||
ORDER BY CASE hs.timing_signal WHEN '弱势持有' THEN 0 WHEN '观望' THEN 1 ELSE 2 END,
|
||||
h.position_pct DESC
|
||||
""").fetchall()
|
||||
if weak:
|
||||
need_pct = queued[0][1]
|
||||
plan = []
|
||||
freed = 0.0
|
||||
for w in weak:
|
||||
if freed >= need_pct:
|
||||
break
|
||||
plan.append(w)
|
||||
freed += w["position_pct"] or 0
|
||||
q0 = queued[0][0]
|
||||
rotation_note = (f"🔄 换仓建议:现金不足买 {q0.get('name') or q0['code']}(RR={q0.get('rr') or 0})→ "
|
||||
f"可减 {'+'.join(f\"{w['name']}\" for w in plan)}"
|
||||
f"({','.join(sorted({w['timing_signal'] for w in plan}))},"
|
||||
f"腾出≈{freed:.0f}%仓位)换入")
|
||||
conn.close()
|
||||
except Exception as _re:
|
||||
print(f" [REC] 换仓计算异常: {_re}", flush=True)
|
||||
|
||||
lines = [f"📈 新增推荐 {len(items)} 只(按RR排序):"]
|
||||
for i, it in enumerate(top):
|
||||
@@ -1277,6 +1308,8 @@ def flush_rec_digest(max_items=5):
|
||||
lines.append(f"…另有 {len(items) - max_items} 只详见盯盘推荐操作区")
|
||||
if cash_note:
|
||||
lines.append("💰 " + cash_note)
|
||||
if rotation_note:
|
||||
lines.append(rotation_note)
|
||||
try:
|
||||
import sys as _s, os as _o2
|
||||
_s.path.insert(0, '/home/hmo/MoFin/deploy/profile-scripts')
|
||||
|
||||
Reference in New Issue
Block a user