From 294133d60524445ebd71b8d9cdabce0783954c62 Mon Sep 17 00:00:00 2001 From: hmo Date: Wed, 22 Jul 2026 11:01:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8E=A8=E8=8D=90):=20=E6=8D=A2=E4=BB=93?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E2=80=94=E2=80=94=E7=8E=B0=E9=87=91=E4=B8=8D?= =?UTF-8?q?=E8=B6=B3=E6=97=B6=E8=87=AA=E5=8A=A8=E7=BB=99=E5=87=BA=E5=87=8F?= =?UTF-8?q?=E5=BC=B1=E5=8A=BF=E6=8C=81=E4=BB=93=E6=8D=A2=E5=85=A5=E6=8E=92?= =?UTF-8?q?=E9=98=9F=E6=8E=A8=E8=8D=90=E7=9A=84=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mofin_db.py | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/mofin_db.py b/mofin_db.py index cb18f17c..7c1750f1 100644 --- a/mofin_db.py +++ b/mofin_db.py @@ -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')