From 68e530a4be242ae1924aceb88168e441e7c83b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=A5=E5=BE=AE?= Date: Wed, 24 Jun 2026 11:48:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8D=A2=E4=BB=93=E8=AF=84=E4=BC=B0=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=EF=BC=9A=E6=B2=89=E6=B2=A1=E6=88=90=E6=9C=AC=E4=B8=8D?= =?UTF-8?q?=E5=8F=82=E4=B8=8E=E5=86=B3=E7=AD=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前逻辑:expected_gain > locked_loss * 1.5 → 沉没成本谬误 错的:把已发生的亏损当成了交易成本 修复后: 1. 已亏损是沉没成本,卖不卖都已损失,不参与决策 2. 只比较持有 old 票的未来预期 vs 换到 new 票的未来预期 3. 深套票(<-15%)默认=死钱,继续持有预期≈-5%~0% 4. 目标票(RR>=3+买入信号)才有换仓资格 5. 最多卖2只、不超过总资产50% 6. 优先选亏损比例小、市值大的(效率高) 输出示例(已实测): 换仓建议:卖双一科技(亏-15.9%);阿里(亏-21.7%)→腾69k→买海博思创1手(53k) 理由:已深套,回本需涨19~28%不现实,死钱换有信号票,止损-3%可控,+17%空间明确 --- data/decisions.json | 86 +++++++++++++++---------------- data/portfolio.json | 44 ++++++++-------- scripts/stale_push_wlin.py | 101 +++++++++++++++++++++++++------------ 3 files changed, 135 insertions(+), 96 deletions(-) diff --git a/data/decisions.json b/data/decisions.json index 3ad5e93..487d535 100644 --- a/data/decisions.json +++ b/data/decisions.json @@ -110,9 +110,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "000657_breakout_chase", @@ -260,9 +260,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "000700_breakout_chase", @@ -555,9 +555,9 @@ }, "priority": 99, "rationale": "没有分支匹配时的默认动作", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" } ], "created_at": "2026-06-24" @@ -772,9 +772,9 @@ }, "priority": 99, "rationale": "没有分支匹配时的默认动作", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" } ], "created_at": "2026-06-24" @@ -5482,9 +5482,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "300035_breakout_chase", @@ -5902,9 +5902,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "300308_breakout_chase", @@ -6133,9 +6133,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "300548_breakout_chase", @@ -6350,9 +6350,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "300690_breakout_chase", @@ -6652,9 +6652,9 @@ }, "priority": 99, "rationale": "没有分支匹配时的默认动作", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" } ], "created_at": "2026-06-24" @@ -6784,9 +6784,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "518880_breakout_chase", @@ -7008,9 +7008,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "600036_breakout_chase", @@ -7197,9 +7197,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "600519_breakout_chase", @@ -7478,9 +7478,9 @@ }, "priority": 99, "rationale": "没有分支匹配时的默认动作", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" } ], "created_at": "2026-06-24" @@ -7645,9 +7645,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "600739_breakout_chase", @@ -8044,9 +8044,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "601899_breakout_chase", @@ -8304,9 +8304,9 @@ }, "priority": 99, "rationale": "没有分支匹配时的默认动作", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" } ], "created_at": "2026-06-24" @@ -8401,9 +8401,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "688411_breakout_chase", @@ -8703,9 +8703,9 @@ }, "priority": 99, "rationale": "没有分支匹配时的默认动作", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" } ], "created_at": "2026-06-24" @@ -8870,9 +8870,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "688639_breakout_chase", @@ -9332,9 +9332,9 @@ }, "priority": 1, "rationale": "价格回调到支撑区,弱势市场低吸", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" }, { "id": "688802_breakout_chase", @@ -9613,9 +9613,9 @@ }, "priority": 99, "rationale": "没有分支匹配时的默认动作", - "trigger_count": 0, + "trigger_count": 1, "success_rate": null, - "last_triggered": null + "last_triggered": "2026-06-24" } ], "created_at": "2026-06-24" @@ -9623,5 +9623,5 @@ } ], "total": 42, - "regenerated_at": "2026-06-24 11:41" + "regenerated_at": "2026-06-24 11:47" } \ No newline at end of file diff --git a/data/portfolio.json b/data/portfolio.json index 459ba65..dde1c2d 100644 --- a/data/portfolio.json +++ b/data/portfolio.json @@ -54,8 +54,8 @@ "action_note": "深套持有", "timing_signal": "持有" }, - "price": 7.59, - "change_pct": -3.68 + "price": 7.66, + "change_pct": -2.79 }, { "code": "600739", @@ -167,8 +167,8 @@ "action_note": "深套持有", "timing_signal": "持有" }, - "price": 98.3, - "change_pct": -0.66 + "price": 98.85, + "change_pct": -0.1 }, { "code": "603259", @@ -252,8 +252,8 @@ "action_note": "短炒强趋势持", "timing_signal": "持有" }, - "price": 95.6, - "change_pct": 9.57 + "price": 96.3, + "change_pct": 10.37 }, { "code": "02202", @@ -281,8 +281,8 @@ "action_note": "深套持有", "timing_signal": "持有" }, - "price": 2.35, - "change_pct": -2.08 + "price": 2.37, + "change_pct": -1.25 }, { "code": "02388", @@ -310,8 +310,8 @@ "action_note": "", "timing_signal": "持有" }, - "price": 46.16, - "change_pct": -1.79 + "price": 46.26, + "change_pct": -1.57 }, { "code": "300750", @@ -367,8 +367,8 @@ "action_note": "深套持有", "timing_signal": "持有" }, - "price": 74.45, - "change_pct": -1.85 + "price": 74.8, + "change_pct": -1.38 }, { "code": "00700", @@ -396,8 +396,8 @@ "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "timing_signal": "持有" }, - "price": 417.4, - "change_pct": 0.63 + "price": 418.8, + "change_pct": 0.82 }, { "code": "00981", @@ -425,8 +425,8 @@ "action_note": "", "timing_signal": "持有" }, - "price": 84.15, - "change_pct": 8.09 + "price": 85.05, + "change_pct": 9.31 }, { "code": "09868", @@ -454,8 +454,8 @@ "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "timing_signal": "持有" }, - "price": 49.58, - "change_pct": 0.45 + "price": 50.0, + "change_pct": 1.22 }, { "code": "600036", @@ -539,8 +539,8 @@ "action_note": "⚠️盈亏比偏低(1:1.2),不建议加仓", "timing_signal": "持有" }, - "price": 52.5, - "change_pct": -2.05 + "price": 52.75, + "change_pct": -1.59 }, { "code": "300035", @@ -652,8 +652,8 @@ "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "timing_signal": "持有" }, - "price": 41.76, - "change_pct": -0.71 + "price": 41.84, + "change_pct": -0.52 }, { "code": "600563", diff --git a/scripts/stale_push_wlin.py b/scripts/stale_push_wlin.py index 5ae4bb8..9fada99 100644 --- a/scripts/stale_push_wlin.py +++ b/scripts/stale_push_wlin.py @@ -396,14 +396,25 @@ def main(): # ── 换仓评估 ────────────────────────────────────────────────────── 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)""" + """现金不足时评估是否卖差票换推荐股。 + + 核心逻辑:已发生的亏损是沉没成本,不参与决策。 + 只比较:持有 old 票的未来预期 vs 换到 new 票的未来预期。 + + 对深套票(<-15%)默认未来预期收益≈-5%~0%(死钱)。 + 对目标票(RR>=3+买入信号)默认未来预期收益≈(tp-price)/price×胜率。 + 如果目标票的未来收益预期显著更好,推荐切换。 + + 返回(推荐文案str, 缺口float)或 (None, gap) + """ gap = lot_cost_target - cash_in - if rr < 2.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 - if not any(kw in sig for kw in ["买入", "加仓", "建仓", "追"]): + 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 @@ -415,40 +426,50 @@ def main(): 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)}) + "mv": round(hmv), "pl_pct": round(hpl_pct, 1)}) + + # 只考虑深套票(<-15%)作为减仓候候选 — 死钱,不如换到有信号的位置 ph.sort(key=lambda x: x["pl_pct"]) - candidates = [h for h in ph if h["pl_pct"] < -10] + candidates = [h for h in ph if h["pl_pct"] < -15] 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"]) # 效率高的优先 + selected = [] cash_freed = 0 - locked_loss = 0 + total_mv_sold = 0 for h in candidates: if cash_freed >= gap: break cash_freed += h["mv"] - locked_loss += abs(h["pl"]) + total_mv_sold += h["mv"] selected.append(h) - if cash_freed < gap or len(selected) > 3: + + if cash_freed < gap or len(selected) > 2: 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) + + # 计算目标票的预期涨幅(到期权价值) + if tp and tp > 0: + target_gain_pct = (tp - price_in) / price_in * 100 # e.g. 16.9% 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 + target_gain_pct = rr * 3 # 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) + + new_budget = cash_in + cash_freed new_lots = int(new_budget / lot_cost_target) if lot_cost_target > 0 else 0 if new_lots == 0: return None, gap @@ -460,13 +481,31 @@ def main(): 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}倍,划算)") + + # 盈亏回本期望对比 + # 深套票继续持有预期:这些票已深套恢复遥遥无期,继续持有预期收益≈-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 = ( + f"换仓建议:卖{sell_desc}" + f"→腾{round(cash_freed):,}元" + f"→买{name}({code}) {new_lots}手({new_shares}股,{round(new_cost):,}元)" + f"占{new_pct}%仓位" + 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)}%)明确。" + ) return text, gap # 标准格式:每个可操作标的 — 大盘/行业/个股三面 + 仓位