换仓评估改全面分析:用决策系统信号评分代替单纯看亏损%\n\n老逻辑:按亏损比例排序,亏最少的先卖 → 错(阿里亏22%但基本面好)\n新逻辑:score_future_outlook() 多维度评分,按前景排序\n\n评分维度:\n1. timing_signal — 买入+3,深套持有-2,持有-0.5\n2. 技术形态 — bearish-1.5,bullish+1.5\n3. 量价关系 — 卖盘-1,买盘+1\n4. 行业背景 — 大跌-0.5\n5. 盈亏比RR — RR≥2得+1,RR<1得-0.5\n6. 股票类别 — 蓝筹+0.5,深套/弱势-0.5\n\n实测:招商银行(亏-3%)评分-4.5(弱势持有+银行大跌+技术偏空),\n双一科技(亏-16%)评分-4.0(持有+bearish+卖盘主导+弱势)\n→ 推荐卖这俩腾38,780元买海博思创

This commit is contained in:
知微
2026-06-24 11:54:36 +08:00
parent 68e530a4be
commit c7f15ebe0b
3 changed files with 149 additions and 68 deletions
+130 -49
View File
@@ -395,26 +395,101 @@ 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):
def score_future_outlook(code_in, h_data, cd_in):
"""基于决策系统已有的分析数据,评估持仓的未来前景评分。
评分越低(负数越大)= 前景越差,越值得考虑卖出。
评分越高(正数越大)= 前景越好,应该保留。
考虑维度(按重要度排序):
1. timing_signal — 决策系统输出的主信号
2. 技术形态 — bearish/bullish/neutral
3. 量价关系 — 买卖盘主导
4. 行业背景 — 板块强弱
5. 盈亏比RR — 预期收益/风险
6. 股票类别 — 蓝筹/深套/题材
"""
d = cd_in.get(code_in, {})
if not d:
return -999 # 无数据=前景未知,保守视为差
score = 0
reasons = []
# 1. timing_signal — 最直接的信号
signal = (d.get('timing_signal') or '').strip()
if '买入' in signal or '加仓' in signal:
score += 3
reasons.append('有买入信号')
elif '深套持有' in signal or '弱势持有' in signal:
score -= 2
reasons.append('深套/弱势持有')
elif signal in ('持有', '') or not signal:
score -= 0.5 # 中性偏弱(没有积极信号就是消极信号)
reasons.append('无积极信号')
# 2. 技术形态
tech = (d.get('tech_snapshot') or '') or ''
if '/bearish' in tech:
score -= 1.5
reasons.append('技术偏空')
elif '/bullish' in tech:
score += 1.5
reasons.append('技术偏多')
# 3. 量价关系
if '主动卖盘占优' in tech:
score -= 1
reasons.append('卖盘主导')
elif '主动买盘占优' in tech:
score += 1
reasons.append('买盘主导')
# 4. 行业背景(大跌/大涨)
sector = (d.get('sector_context') or '') or ''
if '大跌' in sector or '偏弱' in sector:
score -= 0.5
if '大涨' in sector or '偏强' in sector:
score += 0.5
# 5. 盈亏比RR
rr = d.get('rr_ratio', 0) or 0
if rr >= 2:
score += 1
reasons.append(f'RR{rr:.1f}≥2')
elif rr < 1:
score -= 0.5
reasons.append(f'RR{rr:.1f}<1')
# 6. 股票类别
cat = (d.get('stock_category') or '') or ''
if '蓝筹' in cat or '白马' in cat:
score += 0.5
elif '深套' in cat or '弱势' in cat:
score -= 0.5
return round(score, 1)
def evaluate_swap(lot_cost_target, rr, sig, tp, sl, name, code, price_in,
total_assets_in, cash_in, pf_in, cd_in):
"""现金不足时评估是否卖差票换推荐股。
核心逻辑:已发生的亏损是沉没成本,不参与决策。
只比较:持有 old 票的未来预期 vs 换到 new 票的未来预期。
对深套票(<-15%)默认未来预期收益≈-5%~0%(死钱)。
对目标票(RR>=3+买入信号)默认未来预期收益≈(tp-price)/price×胜率。
如果目标票的未来收益预期显著更好,推荐切换。
核心逻辑:
- 已发生的亏损是沉没成本,不参与决策
- 用"全面分析"法评估每个持仓的未来前景(基于决策系统既有数据)
- 优先卖前景最差的票,保留前景好的票(无论当前盈亏%
- 对目标票(RR>=3+买入信号)才有换仓资格
返回(推荐文案str, 缺口float)或 (None, gap)
"""
gap = lot_cost_target - cash_in
# 目标票质量门槛:换仓需要比普通买入更高的置信度
# 目标票质量门槛
if rr < 3.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
@@ -425,49 +500,67 @@ def main():
hmv = hs * hp
h_code = str(h.get("code", ""))
if len(h_code) <= 5:
hmv *= 0.866
hmv *= 0.866 # approximate HKD→CNY
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_pct": round(hpl_pct, 1)})
# 全面评分(越低越差,越建议卖)
fscore = score_future_outlook(h_code, h, cd_in)
ph.append({
"code": h_code,
"name": h.get("name", ""),
"shares": hs,
"price": hp,
"cost": hc,
"mv": round(hmv),
"pl_pct": round(hpl_pct, 1),
"score": fscore,
})
# 只考虑深套票(<-15%)作为减仓候候选 — 死钱,不如换到有信号的位置
ph.sort(key=lambda x: x["pl_pct"])
candidates = [h for h in ph if h["pl_pct"] < -15]
# 按前景评分升序(最差的排最前面)
ph.sort(key=lambda x: x["score"])
# 打印调试信息:所有持仓的前景评分
# print(f"[SWAP_DEBUG] 前景评分(越低越差):", file=sys.stderr)
# for x in ph[:10]:
# print(f" {x['name']}({x['code']}) 评分{x['score']} 亏{x['pl_pct']}% 市值{x['mv']:,}", file=sys.stderr)
# 只考虑评分<=0(前景差或中性偏弱)的作为减仓候选
candidates = [h for h in ph if h["score"] <= 0]
if not candidates:
return None, gap
# 优先选"锁亏效率高"的(锁定亏损占市值比例低的
# 效率 = mv / abs(pl) ,越大说明亏损占比越小
for h in candidates:
h["eff"] = round(h["mv"] / max(abs(h["pl_pct"]) / 100 * h["mv"], 1), 1)
candidates.sort(key=lambda x: -x["eff"]) # 效率高的优先
# 贪心选评分最差的,凑够现金缺口(最多2只
selected = []
cash_freed = 0
total_mv_sold = 0
for h in candidates:
if cash_freed >= gap:
break
cash_freed += h["mv"]
total_mv_sold += h["mv"]
selected.append(h)
if cash_freed < gap or len(selected) > 2:
return None, gap
# 计算目标票的预期涨幅(到期权价值)
# 计算目标票的预期涨幅
if tp and tp > 0:
target_gain_pct = (tp - price_in) / price_in * 100 # e.g. 16.9%
target_gain_pct = (tp - price_in) / price_in * 100
else:
target_gain_pct = rr * 3 # RR*3%~粗略
target_gain_pct = rr * 3
# 沉没成本不参与决策 — 直接描述方
sell_desc = "".join(
f"{h['name']}({h['code']}) {h['shares']}股 亏{h['pl_pct']}%"
for h in selected
)
sell_note = "".join(h['name'] for h in selected)
# 构建推荐文
sell_parts = []
sell_names = []
for h in selected:
# 每个被选股票配一句"为什么卖它"
reason = f"前景评分{h['score']}"
if h['pl_pct'] <= -30:
reason += "深套"
elif h['pl_pct'] <= -15:
reason += f"亏损{h['pl_pct']}%"
sell_parts.append(f"{h['name']}({h['code']}) {h['shares']}股 亏{h['pl_pct']}% ({reason})")
sell_names.append(h['name'])
sell_desc = "".join(sell_parts)
new_budget = cash_in + cash_freed
new_lots = int(new_budget / lot_cost_target) if lot_cost_target > 0 else 0
@@ -482,17 +575,6 @@ def main():
new_cost = new_lots * lot_cost_target
new_pct = round(new_cost / total_assets_in * 100) if total_assets_in > 0 else 0
# 盈亏回本期望对比
# 深套票继续持有预期:这些票已深套恢复遥遥无期,继续持有预期收益≈-5%~0%
# 新票预期:止损-3%~止盈+target_gain_pctRR指引正向
# 更实用的表述:卖掉的票回本需涨多少 vs 新票上涨潜力
recovery_needed = []
for h in selected:
if h["pl_pct"] < 0:
recover = round(-h["pl_pct"] / (100 + h["pl_pct"]) * 100, 1) if h["pl_pct"] > -100 else 999
recovery_needed.append(f"{h['name']}需涨{recover}%回本")
recover_str = "".join(recovery_needed)
text = (
f"换仓建议:卖{sell_desc}"
f"→腾{round(cash_freed):,}"
@@ -501,10 +583,9 @@ def main():
f"(止损{sl}(-{round((price_in-sl)/price_in*100,1)}%)"
f"止盈{tp}(+{round(target_gain_pct,1)}%)"
f" RR={rr}\n"
f" 理由:{sell_note}已深套,回本需{recover_str}不现实"
f"继续持有大概率继续亏(死钱)"
f"到有信号票,止损可控(-3%)"
f"上行空间(+{round(target_gain_pct,1)}%)明确。"
f" 理由:{', '.join(sell_names)}前景评分最低"
f"继续持有无积极信号且技术偏弱"
f"换到有明确信号和止损的标的,预期收益更优。"
)
return text, gap
@@ -598,7 +679,7 @@ def main():
if lots == 0:
swap_text, _ = evaluate_swap(
lot, rr, sig, tp, sl, name, code, price,
total_assets, available_cash, pf
total_assets, available_cash, pf, code_data
)
lines.append(