换仓评估逻辑:现金不足时自动分析卖差票换推荐股

新增 evaluate_swap() 函数:
1. 仅对RR>=2.0且含买入关键词的强信号触发
2. 扫描持仓按亏损排序,找最少卖出组合凑现金缺口
3. 预期盈利 > 锁定亏损×1.5 才推荐切换
4. 最多卖3只,单次换仓不超总资产50%
5. 不划算时维持原预算不足1手消息

已验证:海博思创(688411) RR=5.6但预期盈利9k<锁定亏损46k×1.5
→ 不推荐切换,正确。沐曦如果触发也会按同一逻辑判断。
This commit is contained in:
知微
2026-06-24 11:42:26 +08:00
parent b145dd47c3
commit 92815aac06
3 changed files with 149 additions and 26 deletions
+87
View File
@@ -394,6 +394,81 @@ def main():
return theo_pct, pct_actual, details, lots, lot_cost_total
# ── 换仓评估 ──────────────────────────────────────────────────────
def evaluate_swap(lot_cost_target, rr, sig, tp, sl, name, code, price_in, total_assets_in, cash_in, pf_in):
"""现金不足时评估是否卖差票换推荐股。仅在目标信号强时触发。
返回(推荐文案str, 需补充的现金缺口float)或 (None, gap)"""
gap = lot_cost_target - cash_in
if rr < 2.0 or gap <= 0 or gap > total_assets_in * 0.5:
return None, gap
if not any(kw in sig for kw in ["买入", "加仓", "建仓", ""]):
return None, gap
# 收集持仓,按盈亏率升序(最差排前)
ph = []
for h in pf_in.get("holdings", []):
hs = h.get("shares", 0) or 0
hp = h.get("price", 0) or 0
hc = h.get("cost", 0) or 0
if hs <= 0 or hp <= 0:
continue
hmv = hs * hp
h_code = str(h.get("code", ""))
if len(h_code) <= 5:
hmv *= 0.866
hpl = (hp - hc) * hs
hpl_pct = (hp - hc) / hc * 100 if hc else 0
ph.append({"code": h_code, "name": h.get("name", ""),
"shares": hs, "price": hp, "cost": hc,
"mv": round(hmv), "pl": round(hpl),
"pl_pct": round(hpl_pct, 1)})
ph.sort(key=lambda x: x["pl_pct"])
candidates = [h for h in ph if h["pl_pct"] < -10]
if not candidates:
return None, gap
selected = []
cash_freed = 0
locked_loss = 0
for h in candidates:
if cash_freed >= gap:
break
cash_freed += h["mv"]
locked_loss += abs(h["pl"])
selected.append(h)
if cash_freed < gap or len(selected) > 3:
return None, gap
# 估算目标预期盈利(到止盈)
if tp and tp > 0 and lot_cost_target > 0:
target_gain_pct = (tp - price_in) / price_in
expected_gain = lot_cost_target * max(target_gain_pct, 0.05)
else:
target_gain_pct = rr * 0.03
expected_gain = lot_cost_target * target_gain_pct
if expected_gain <= locked_loss * 1.5:
return None, gap
sell_desc = "".join(f"{h['name']}({h['code']}) {h['shares']}股 亏{h['pl_pct']}%"
for h in selected)
sell_total = cash_freed
new_budget = cash_in + sell_total
new_lots = int(new_budget / lot_cost_target) if lot_cost_target > 0 else 0
if new_lots == 0:
return None, gap
if code.startswith("688"):
new_shares = new_lots * 200
elif len(code) <= 5:
new_shares = new_lots * hk_lot_size(code)
else:
new_shares = new_lots * 100
new_cost = new_lots * lot_cost_target
new_pct = round(new_cost / total_assets_in * 100) if total_assets_in > 0 else 0
ratio_vs_loss = round(expected_gain / locked_loss, 1) if locked_loss else 0
text = (f"换仓建议:卖{sell_desc}"
f"→腾{round(sell_total):,}元(锁定亏损{locked_loss:,}"
f"→买{name}({code}) {new_lots}手({new_shares}股,{round(new_cost):,}元)"
f"{new_pct}%仓位"
f"(目标{tp} +{round(target_gain_pct*100,1)}%预期={round(expected_gain):,}"
f"≈锁定亏损的{ratio_vs_loss}倍,划算)")
return text, gap
# 标准格式:每个可操作标的 — 大盘/行业/个股三面 + 仓位
lines.append(f"【💡 操作建议】(当前{n}只自选可操作 | 总资产{total_assets:,.0f}元 现金{available_cash:,.0f}元)")
for s in actionable:
@@ -478,6 +553,15 @@ def main():
continue
action_tag = "⚠️" if lots == 0 else "🛒"
# 换仓评估:现金不足时评估是否卖差票换推荐股
swap_text = None
if lots == 0:
swap_text, _ = evaluate_swap(
lot, rr, sig, tp, sl, name, code, price,
total_assets, available_cash, pf
)
lines.append(
f" {action_tag} {name}({code}) {pfx}{price:.2f} 买区{buy_low}~{buy_high} | "
f"1手{lot:,.0f}元 RR={rr:.1f}{sl}{tp}\n"
@@ -485,6 +569,9 @@ def main():
f" 技术{ss['强撑']}{ss['弱撑']}{ss['弱压']}{ss['强压']} | 信号{sig}\n"
f" 仓位:理论{theo_pct}%×总资产 | 建议{actual_pct}%{details}"
)
if swap_text:
lines[-1] += f"\n {swap_text}"
# 分支描述
branch_line = ""