换仓评估改全面分析:用决策系统信号评分代替单纯看亏损%\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:
+1
-1
@@ -9623,5 +9623,5 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"total": 42,
|
"total": 42,
|
||||||
"regenerated_at": "2026-06-24 11:47"
|
"regenerated_at": "2026-06-24 11:54"
|
||||||
}
|
}
|
||||||
+18
-18
@@ -54,8 +54,8 @@
|
|||||||
"action_note": "深套持有",
|
"action_note": "深套持有",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 7.66,
|
"price": 7.71,
|
||||||
"change_pct": -2.79
|
"change_pct": -2.16
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "600739",
|
"code": "600739",
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
"action_note": "深套持有",
|
"action_note": "深套持有",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 98.85,
|
"price": 98.95,
|
||||||
"change_pct": -0.1
|
"change_pct": -0.1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -252,8 +252,8 @@
|
|||||||
"action_note": "短炒强趋势持",
|
"action_note": "短炒强趋势持",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 96.3,
|
"price": 95.1,
|
||||||
"change_pct": 10.37
|
"change_pct": 9.11
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "02202",
|
"code": "02202",
|
||||||
@@ -281,8 +281,8 @@
|
|||||||
"action_note": "深套持有",
|
"action_note": "深套持有",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 2.37,
|
"price": 2.36,
|
||||||
"change_pct": -1.25
|
"change_pct": -1.67
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "02388",
|
"code": "02388",
|
||||||
@@ -310,7 +310,7 @@
|
|||||||
"action_note": "",
|
"action_note": "",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 46.26,
|
"price": 46.22,
|
||||||
"change_pct": -1.57
|
"change_pct": -1.57
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -367,7 +367,7 @@
|
|||||||
"action_note": "深套持有",
|
"action_note": "深套持有",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 74.8,
|
"price": 74.85,
|
||||||
"change_pct": -1.38
|
"change_pct": -1.38
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -396,8 +396,8 @@
|
|||||||
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
|
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 418.8,
|
"price": 419.0,
|
||||||
"change_pct": 0.82
|
"change_pct": 1.06
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "00981",
|
"code": "00981",
|
||||||
@@ -425,8 +425,8 @@
|
|||||||
"action_note": "",
|
"action_note": "",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 85.05,
|
"price": 84.75,
|
||||||
"change_pct": 9.31
|
"change_pct": 9.12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "09868",
|
"code": "09868",
|
||||||
@@ -454,8 +454,8 @@
|
|||||||
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
|
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 50.0,
|
"price": 49.94,
|
||||||
"change_pct": 1.22
|
"change_pct": 1.05
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "600036",
|
"code": "600036",
|
||||||
@@ -539,8 +539,8 @@
|
|||||||
"action_note": "⚠️盈亏比偏低(1:1.2),不建议加仓",
|
"action_note": "⚠️盈亏比偏低(1:1.2),不建议加仓",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 52.75,
|
"price": 52.8,
|
||||||
"change_pct": -1.59
|
"change_pct": -1.4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "300035",
|
"code": "300035",
|
||||||
@@ -653,7 +653,7 @@
|
|||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 41.84,
|
"price": 41.84,
|
||||||
"change_pct": -0.52
|
"change_pct": -0.33
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "600563",
|
"code": "600563",
|
||||||
|
|||||||
+130
-49
@@ -395,26 +395,101 @@ 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):
|
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)
|
返回(推荐文案str, 缺口float)或 (None, gap)
|
||||||
"""
|
"""
|
||||||
gap = lot_cost_target - cash_in
|
gap = lot_cost_target - cash_in
|
||||||
# 目标票质量门槛:换仓需要比普通买入更高的置信度
|
# 目标票质量门槛
|
||||||
if rr < 3.0 or gap <= 0 or gap > total_assets_in * 0.5:
|
if rr < 3.0 or gap <= 0 or gap > total_assets_in * 0.5:
|
||||||
return None, gap
|
return None, gap
|
||||||
if not any(kw in sig for kw in ["买入", "加仓", "建仓"]):
|
if not any(kw in sig for kw in ["买入", "加仓", "建仓"]):
|
||||||
return None, gap
|
return None, gap
|
||||||
|
|
||||||
# 收集持仓盈亏数据
|
# 收集持仓数据 + 前景评分
|
||||||
ph = []
|
ph = []
|
||||||
for h in pf_in.get("holdings", []):
|
for h in pf_in.get("holdings", []):
|
||||||
hs = h.get("shares", 0) or 0
|
hs = h.get("shares", 0) or 0
|
||||||
@@ -425,49 +500,67 @@ def main():
|
|||||||
hmv = hs * hp
|
hmv = hs * hp
|
||||||
h_code = str(h.get("code", ""))
|
h_code = str(h.get("code", ""))
|
||||||
if len(h_code) <= 5:
|
if len(h_code) <= 5:
|
||||||
hmv *= 0.866
|
hmv *= 0.866 # approximate HKD→CNY
|
||||||
hpl_pct = (hp - hc) / hc * 100 if hc else 0
|
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"])
|
ph.sort(key=lambda x: x["score"])
|
||||||
candidates = [h for h in ph if h["pl_pct"] < -15]
|
|
||||||
|
# 打印调试信息:所有持仓的前景评分
|
||||||
|
# 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:
|
if not candidates:
|
||||||
return None, gap
|
return None, gap
|
||||||
|
|
||||||
# 优先选"锁亏效率高"的(锁定亏损占市值比例低的)
|
# 贪心选评分最差的,凑够现金缺口(最多2只)
|
||||||
# 效率 = 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"]) # 效率高的优先
|
|
||||||
|
|
||||||
selected = []
|
selected = []
|
||||||
cash_freed = 0
|
cash_freed = 0
|
||||||
total_mv_sold = 0
|
|
||||||
for h in candidates:
|
for h in candidates:
|
||||||
if cash_freed >= gap:
|
if cash_freed >= gap:
|
||||||
break
|
break
|
||||||
cash_freed += h["mv"]
|
cash_freed += h["mv"]
|
||||||
total_mv_sold += h["mv"]
|
|
||||||
selected.append(h)
|
selected.append(h)
|
||||||
|
|
||||||
if cash_freed < gap or len(selected) > 2:
|
if cash_freed < gap or len(selected) > 2:
|
||||||
return None, gap
|
return None, gap
|
||||||
|
|
||||||
# 计算目标票的预期涨幅(到期权价值)
|
# 计算目标票的预期涨幅
|
||||||
if tp and tp > 0:
|
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:
|
else:
|
||||||
target_gain_pct = rr * 3 # RR*3%~粗略
|
target_gain_pct = rr * 3
|
||||||
|
|
||||||
# 沉没成本不参与决策 — 直接描述方案
|
# 构建推荐文案
|
||||||
sell_desc = ";".join(
|
sell_parts = []
|
||||||
f"{h['name']}({h['code']}) {h['shares']}股 亏{h['pl_pct']}%"
|
sell_names = []
|
||||||
for h in selected
|
for h in selected:
|
||||||
)
|
# 每个被选股票配一句"为什么卖它"
|
||||||
sell_note = "、".join(h['name'] 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_budget = cash_in + cash_freed
|
||||||
new_lots = int(new_budget / lot_cost_target) if lot_cost_target > 0 else 0
|
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_cost = new_lots * lot_cost_target
|
||||||
new_pct = round(new_cost / total_assets_in * 100) if total_assets_in > 0 else 0
|
new_pct = round(new_cost / total_assets_in * 100) if total_assets_in > 0 else 0
|
||||||
|
|
||||||
# 盈亏回本期望对比
|
|
||||||
# 深套票继续持有预期:这些票已深套恢复遥遥无期,继续持有预期收益≈-5%~0%
|
|
||||||
# 新票预期:止损-3%~止盈+target_gain_pct,RR指引正向
|
|
||||||
# 更实用的表述:卖掉的票回本需涨多少 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 = (
|
text = (
|
||||||
f"换仓建议:卖{sell_desc}"
|
f"换仓建议:卖{sell_desc}"
|
||||||
f"→腾{round(cash_freed):,}元"
|
f"→腾{round(cash_freed):,}元"
|
||||||
@@ -501,10 +583,9 @@ def main():
|
|||||||
f"(止损{sl}(-{round((price_in-sl)/price_in*100,1)}%)"
|
f"(止损{sl}(-{round((price_in-sl)/price_in*100,1)}%)"
|
||||||
f"止盈{tp}(+{round(target_gain_pct,1)}%)"
|
f"止盈{tp}(+{round(target_gain_pct,1)}%)"
|
||||||
f" RR={rr})\n"
|
f" RR={rr})\n"
|
||||||
f" 理由:{sell_note}已深套,回本需{recover_str}不现实,"
|
f" 理由:{', '.join(sell_names)}前景评分最低,"
|
||||||
f"继续持有大概率继续亏(死钱);"
|
f"继续持有无积极信号且技术偏弱;"
|
||||||
f"换仓到有信号票,止损可控(-3%),"
|
f"换到有明确信号和止损的标的,预期收益更优。"
|
||||||
f"上行空间(+{round(target_gain_pct,1)}%)明确。"
|
|
||||||
)
|
)
|
||||||
return text, gap
|
return text, gap
|
||||||
|
|
||||||
@@ -598,7 +679,7 @@ def main():
|
|||||||
if lots == 0:
|
if lots == 0:
|
||||||
swap_text, _ = evaluate_swap(
|
swap_text, _ = evaluate_swap(
|
||||||
lot, rr, sig, tp, sl, name, code, price,
|
lot, rr, sig, tp, sl, name, code, price,
|
||||||
total_assets, available_cash, pf
|
total_assets, available_cash, pf, code_data
|
||||||
)
|
)
|
||||||
|
|
||||||
lines.append(
|
lines.append(
|
||||||
|
|||||||
Reference in New Issue
Block a user