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

新增 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
+1 -1
View File
@@ -9623,5 +9623,5 @@
} }
], ],
"total": 42, "total": 42,
"regenerated_at": "2026-06-24 11:33" "regenerated_at": "2026-06-24 11:41"
} }
+61 -25
View File
@@ -25,7 +25,8 @@
"rr_ratio": 0.24, "rr_ratio": 0.24,
"action_note": "⚠️盈亏比偏低(1:0.9),不建议加仓", "action_note": "⚠️盈亏比偏低(1:0.9),不建议加仓",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 794.1
}, },
{ {
"code": "01478", "code": "01478",
@@ -52,7 +53,9 @@
"rr_ratio": 1.12, "rr_ratio": 1.12,
"action_note": "深套持有", "action_note": "深套持有",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 7.59,
"change_pct": -3.68
}, },
{ {
"code": "600739", "code": "600739",
@@ -79,7 +82,8 @@
"rr_ratio": 1.35, "rr_ratio": 1.35,
"action_note": "⚠️盈亏比偏低(1:1.3),不建议加仓", "action_note": "⚠️盈亏比偏低(1:1.3),不建议加仓",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 10.48
}, },
{ {
"code": "601899", "code": "601899",
@@ -106,7 +110,8 @@
"rr_ratio": 0.74, "rr_ratio": 0.74,
"action_note": "深套持有", "action_note": "深套持有",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 27.5
}, },
{ {
"code": "688639", "code": "688639",
@@ -133,7 +138,8 @@
"rr_ratio": 1.09, "rr_ratio": 1.09,
"action_note": "⚠️盈亏比偏低(1:1.1),不建议加仓", "action_note": "⚠️盈亏比偏低(1:1.1),不建议加仓",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 21.44
}, },
{ {
"code": "09988", "code": "09988",
@@ -160,7 +166,9 @@
"rr_ratio": 1.06, "rr_ratio": 1.06,
"action_note": "深套持有", "action_note": "深套持有",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 98.3,
"change_pct": -0.66
}, },
{ {
"code": "603259", "code": "603259",
@@ -187,7 +195,8 @@
"rr_ratio": 3.16, "rr_ratio": 3.16,
"action_note": "", "action_note": "",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 116.94
}, },
{ {
"code": "688981", "code": "688981",
@@ -214,7 +223,8 @@
"rr_ratio": 2.05, "rr_ratio": 2.05,
"action_note": "", "action_note": "",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 154.23
}, },
{ {
"code": "01888", "code": "01888",
@@ -241,7 +251,9 @@
"rr_ratio": 7.61, "rr_ratio": 7.61,
"action_note": "短炒强趋势持", "action_note": "短炒强趋势持",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 95.6,
"change_pct": 9.57
}, },
{ {
"code": "02202", "code": "02202",
@@ -268,7 +280,9 @@
"rr_ratio": 1.11, "rr_ratio": 1.11,
"action_note": "深套持有", "action_note": "深套持有",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 2.35,
"change_pct": -2.08
}, },
{ {
"code": "02388", "code": "02388",
@@ -295,7 +309,9 @@
"rr_ratio": 2.91, "rr_ratio": 2.91,
"action_note": "", "action_note": "",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 46.16,
"change_pct": -1.79
}, },
{ {
"code": "300750", "code": "300750",
@@ -322,7 +338,8 @@
"rr_ratio": 0.84, "rr_ratio": 0.84,
"action_note": "⚠️盈亏比偏低(1:0.8),不建议加仓", "action_note": "⚠️盈亏比偏低(1:0.8),不建议加仓",
"timing_signal": "弱势持有" "timing_signal": "弱势持有"
} },
"price": 394.78
}, },
{ {
"code": "01211", "code": "01211",
@@ -349,7 +366,9 @@
"rr_ratio": 1.13, "rr_ratio": 1.13,
"action_note": "深套持有", "action_note": "深套持有",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 74.45,
"change_pct": -1.85
}, },
{ {
"code": "00700", "code": "00700",
@@ -376,7 +395,9 @@
"rr_ratio": 1.0, "rr_ratio": 1.0,
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 417.4,
"change_pct": 0.63
}, },
{ {
"code": "00981", "code": "00981",
@@ -403,7 +424,9 @@
"rr_ratio": 3.0, "rr_ratio": 3.0,
"action_note": "", "action_note": "",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 84.15,
"change_pct": 8.09
}, },
{ {
"code": "09868", "code": "09868",
@@ -430,7 +453,9 @@
"rr_ratio": 22.09, "rr_ratio": 22.09,
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 49.58,
"change_pct": 0.45
}, },
{ {
"code": "600036", "code": "600036",
@@ -457,7 +482,8 @@
"rr_ratio": 1.1, "rr_ratio": 1.1,
"action_note": "⚠️盈亏比偏低(1:1.1),不建议加仓", "action_note": "⚠️盈亏比偏低(1:1.1),不建议加仓",
"timing_signal": "弱势持有" "timing_signal": "弱势持有"
} },
"price": 37.04
}, },
{ {
"code": "300548", "code": "300548",
@@ -484,7 +510,8 @@
"rr_ratio": 7.41, "rr_ratio": 7.41,
"action_note": "短炒强趋势持", "action_note": "短炒强趋势持",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 278.12
}, },
{ {
"code": "02318", "code": "02318",
@@ -511,7 +538,9 @@
"rr_ratio": 15.88, "rr_ratio": 15.88,
"action_note": "⚠️盈亏比偏低(1:1.2),不建议加仓", "action_note": "⚠️盈亏比偏低(1:1.2),不建议加仓",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 52.5,
"change_pct": -2.05
}, },
{ {
"code": "300035", "code": "300035",
@@ -538,7 +567,8 @@
"rr_ratio": 0.95, "rr_ratio": 0.95,
"action_note": "深套持有", "action_note": "深套持有",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 16.13
}, },
{ {
"code": "000700", "code": "000700",
@@ -565,7 +595,8 @@
"rr_ratio": 3.23, "rr_ratio": 3.23,
"action_note": "", "action_note": "",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 14.64
}, },
{ {
"code": "518880", "code": "518880",
@@ -592,7 +623,8 @@
"rr_ratio": 0.55, "rr_ratio": 0.55,
"action_note": "深套持有", "action_note": "深套持有",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 8.44
}, },
{ {
"code": "01088", "code": "01088",
@@ -619,7 +651,9 @@
"rr_ratio": 1.01, "rr_ratio": 1.01,
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 41.76,
"change_pct": -0.71
}, },
{ {
"code": "600563", "code": "600563",
@@ -646,7 +680,8 @@
"rr_ratio": 3.54, "rr_ratio": 3.54,
"action_note": "", "action_note": "",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 178.76
}, },
{ {
"code": "300690", "code": "300690",
@@ -673,7 +708,8 @@
"rr_ratio": 1.25, "rr_ratio": 1.25,
"action_note": "⚠️盈亏比偏低(1:1.2),不建议加仓", "action_note": "⚠️盈亏比偏低(1:1.2),不建议加仓",
"timing_signal": "持有" "timing_signal": "持有"
} },
"price": 22.87
} }
], ],
"cash": 20230.1, "cash": 20230.1,
+87
View File
@@ -394,6 +394,81 @@ def main():
return theo_pct, pct_actual, details, lots, lot_cost_total 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}元)") lines.append(f"【💡 操作建议】(当前{n}只自选可操作 | 总资产{total_assets:,.0f}元 现金{available_cash:,.0f}元)")
for s in actionable: for s in actionable:
@@ -478,6 +553,15 @@ def main():
continue continue
action_tag = "⚠️" if lots == 0 else "🛒" 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( lines.append(
f" {action_tag} {name}({code}) {pfx}{price:.2f} 买区{buy_low}~{buy_high} | " f" {action_tag} {name}({code}) {pfx}{price:.2f} 买区{buy_low}~{buy_high} | "
f"1手{lot:,.0f}元 RR={rr:.1f}{sl}{tp}\n" f"1手{lot:,.0f}元 RR={rr:.1f}{sl}{tp}\n"
@@ -486,6 +570,9 @@ def main():
f" 仓位:理论{theo_pct}%×总资产 | 建议{actual_pct}%{details}" f" 仓位:理论{theo_pct}%×总资产 | 建议{actual_pct}%{details}"
) )
if swap_text:
lines[-1] += f"\n {swap_text}"
# 分支描述 # 分支描述
branch_line = "" branch_line = ""
if branch_action != "hold": if branch_action != "hold":