From 92815aac06704410dd3ba0b91fa313679110b8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=A5=E5=BE=AE?= Date: Wed, 24 Jun 2026 11:42:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8D=A2=E4=BB=93=E8=AF=84=E4=BC=B0=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=9A=E7=8E=B0=E9=87=91=E4=B8=8D=E8=B6=B3=E6=97=B6?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=88=86=E6=9E=90=E5=8D=96=E5=B7=AE=E7=A5=A8?= =?UTF-8?q?=E6=8D=A2=E6=8E=A8=E8=8D=90=E8=82=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 evaluate_swap() 函数: 1. 仅对RR>=2.0且含买入关键词的强信号触发 2. 扫描持仓按亏损排序,找最少卖出组合凑现金缺口 3. 预期盈利 > 锁定亏损×1.5 才推荐切换 4. 最多卖3只,单次换仓不超总资产50% 5. 不划算时维持原预算不足1手消息 已验证:海博思创(688411) RR=5.6但预期盈利9k<锁定亏损46k×1.5 → 不推荐切换,正确。沐曦如果触发也会按同一逻辑判断。 --- data/decisions.json | 2 +- data/portfolio.json | 86 ++++++++++++++++++++++++++----------- scripts/stale_push_wlin.py | 87 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 26 deletions(-) diff --git a/data/decisions.json b/data/decisions.json index 8922927..3ad5e93 100644 --- a/data/decisions.json +++ b/data/decisions.json @@ -9623,5 +9623,5 @@ } ], "total": 42, - "regenerated_at": "2026-06-24 11:33" + "regenerated_at": "2026-06-24 11:41" } \ No newline at end of file diff --git a/data/portfolio.json b/data/portfolio.json index aaefb46..459ba65 100644 --- a/data/portfolio.json +++ b/data/portfolio.json @@ -25,7 +25,8 @@ "rr_ratio": 0.24, "action_note": "⚠️盈亏比偏低(1:0.9),不建议加仓", "timing_signal": "持有" - } + }, + "price": 794.1 }, { "code": "01478", @@ -52,7 +53,9 @@ "rr_ratio": 1.12, "action_note": "深套持有", "timing_signal": "持有" - } + }, + "price": 7.59, + "change_pct": -3.68 }, { "code": "600739", @@ -79,7 +82,8 @@ "rr_ratio": 1.35, "action_note": "⚠️盈亏比偏低(1:1.3),不建议加仓", "timing_signal": "持有" - } + }, + "price": 10.48 }, { "code": "601899", @@ -106,7 +110,8 @@ "rr_ratio": 0.74, "action_note": "深套持有", "timing_signal": "持有" - } + }, + "price": 27.5 }, { "code": "688639", @@ -133,7 +138,8 @@ "rr_ratio": 1.09, "action_note": "⚠️盈亏比偏低(1:1.1),不建议加仓", "timing_signal": "持有" - } + }, + "price": 21.44 }, { "code": "09988", @@ -160,7 +166,9 @@ "rr_ratio": 1.06, "action_note": "深套持有", "timing_signal": "持有" - } + }, + "price": 98.3, + "change_pct": -0.66 }, { "code": "603259", @@ -187,7 +195,8 @@ "rr_ratio": 3.16, "action_note": "", "timing_signal": "持有" - } + }, + "price": 116.94 }, { "code": "688981", @@ -214,7 +223,8 @@ "rr_ratio": 2.05, "action_note": "", "timing_signal": "持有" - } + }, + "price": 154.23 }, { "code": "01888", @@ -241,7 +251,9 @@ "rr_ratio": 7.61, "action_note": "短炒强趋势持", "timing_signal": "持有" - } + }, + "price": 95.6, + "change_pct": 9.57 }, { "code": "02202", @@ -268,7 +280,9 @@ "rr_ratio": 1.11, "action_note": "深套持有", "timing_signal": "持有" - } + }, + "price": 2.35, + "change_pct": -2.08 }, { "code": "02388", @@ -295,7 +309,9 @@ "rr_ratio": 2.91, "action_note": "", "timing_signal": "持有" - } + }, + "price": 46.16, + "change_pct": -1.79 }, { "code": "300750", @@ -322,7 +338,8 @@ "rr_ratio": 0.84, "action_note": "⚠️盈亏比偏低(1:0.8),不建议加仓", "timing_signal": "弱势持有" - } + }, + "price": 394.78 }, { "code": "01211", @@ -349,7 +366,9 @@ "rr_ratio": 1.13, "action_note": "深套持有", "timing_signal": "持有" - } + }, + "price": 74.45, + "change_pct": -1.85 }, { "code": "00700", @@ -376,7 +395,9 @@ "rr_ratio": 1.0, "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "timing_signal": "持有" - } + }, + "price": 417.4, + "change_pct": 0.63 }, { "code": "00981", @@ -403,7 +424,9 @@ "rr_ratio": 3.0, "action_note": "", "timing_signal": "持有" - } + }, + "price": 84.15, + "change_pct": 8.09 }, { "code": "09868", @@ -430,7 +453,9 @@ "rr_ratio": 22.09, "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "timing_signal": "持有" - } + }, + "price": 49.58, + "change_pct": 0.45 }, { "code": "600036", @@ -457,7 +482,8 @@ "rr_ratio": 1.1, "action_note": "⚠️盈亏比偏低(1:1.1),不建议加仓", "timing_signal": "弱势持有" - } + }, + "price": 37.04 }, { "code": "300548", @@ -484,7 +510,8 @@ "rr_ratio": 7.41, "action_note": "短炒强趋势持", "timing_signal": "持有" - } + }, + "price": 278.12 }, { "code": "02318", @@ -511,7 +538,9 @@ "rr_ratio": 15.88, "action_note": "⚠️盈亏比偏低(1:1.2),不建议加仓", "timing_signal": "持有" - } + }, + "price": 52.5, + "change_pct": -2.05 }, { "code": "300035", @@ -538,7 +567,8 @@ "rr_ratio": 0.95, "action_note": "深套持有", "timing_signal": "持有" - } + }, + "price": 16.13 }, { "code": "000700", @@ -565,7 +595,8 @@ "rr_ratio": 3.23, "action_note": "", "timing_signal": "持有" - } + }, + "price": 14.64 }, { "code": "518880", @@ -592,7 +623,8 @@ "rr_ratio": 0.55, "action_note": "深套持有", "timing_signal": "持有" - } + }, + "price": 8.44 }, { "code": "01088", @@ -619,7 +651,9 @@ "rr_ratio": 1.01, "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "timing_signal": "持有" - } + }, + "price": 41.76, + "change_pct": -0.71 }, { "code": "600563", @@ -646,7 +680,8 @@ "rr_ratio": 3.54, "action_note": "", "timing_signal": "持有" - } + }, + "price": 178.76 }, { "code": "300690", @@ -673,7 +708,8 @@ "rr_ratio": 1.25, "action_note": "⚠️盈亏比偏低(1:1.2),不建议加仓", "timing_signal": "持有" - } + }, + "price": 22.87 } ], "cash": 20230.1, diff --git a/scripts/stale_push_wlin.py b/scripts/stale_push_wlin.py index 70996e0..5ae4bb8 100644 --- a/scripts/stale_push_wlin.py +++ b/scripts/stale_push_wlin.py @@ -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 = ""