From 9a984dd4dc5f15a31fb14e5b671bad04010391f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=A5=E5=BE=AE?= Date: Wed, 24 Jun 2026 11:59:55 +0800 Subject: [PATCH] =?UTF-8?q?6=E7=BB=B4=E8=AF=84=E5=88=86=E9=80=9A=E7=94=A8?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=20+=20=E6=B8=AF=E8=82=A1=E9=80=9AT+2?= =?UTF-8?q?=E5=BB=B6=E8=BF=9F=E6=A0=87=E6=B3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. stock_scorer.py — 共享的6维评分模块 - score_future_outlook(code, data) → (score, reasons) - rank_by_outlook(holdings, data) → 排序列表 - settlement_delay_note(sell_code, buy_code) → 结算延迟说明 - is_hk_stock(code) → 判断港股通标的 2. stale_push_wlin.py 改用共享模块(去掉本地函数定义) 3. 换仓评估增加港股通结算延迟检测: - 卖港股→买A股时标注⚠️T+2到账限制 - 本次推荐(招商银行+A股→海博思创)无需标注,全是A股 --- data/decisions.json | 2 +- data/portfolio.json | 38 ++++----- scripts/stale_push_wlin.py | 96 ++++------------------- scripts/stock_scorer.py | 156 +++++++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+), 99 deletions(-) create mode 100644 scripts/stock_scorer.py diff --git a/data/decisions.json b/data/decisions.json index 44b0eb4..2d98f73 100644 --- a/data/decisions.json +++ b/data/decisions.json @@ -9623,5 +9623,5 @@ } ], "total": 42, - "regenerated_at": "2026-06-24 11:54" + "regenerated_at": "2026-06-24 11:59" } \ No newline at end of file diff --git a/data/portfolio.json b/data/portfolio.json index fbc12a7..e56a202 100644 --- a/data/portfolio.json +++ b/data/portfolio.json @@ -54,8 +54,8 @@ "action_note": "深套持有", "timing_signal": "持有" }, - "price": 7.71, - "change_pct": -2.16 + "price": 7.67, + "change_pct": -2.54 }, { "code": "600739", @@ -167,7 +167,7 @@ "action_note": "深套持有", "timing_signal": "持有" }, - "price": 98.95, + "price": 98.85, "change_pct": -0.1 }, { @@ -252,8 +252,8 @@ "action_note": "短炒强趋势持", "timing_signal": "持有" }, - "price": 95.1, - "change_pct": 9.11 + "price": 94.7, + "change_pct": 8.54 }, { "code": "02202", @@ -281,7 +281,7 @@ "action_note": "深套持有", "timing_signal": "持有" }, - "price": 2.36, + "price": 2.35, "change_pct": -1.67 }, { @@ -310,8 +310,8 @@ "action_note": "", "timing_signal": "持有" }, - "price": 46.22, - "change_pct": -1.57 + "price": 46.1, + "change_pct": -1.91 }, { "code": "300750", @@ -367,8 +367,8 @@ "action_note": "深套持有", "timing_signal": "持有" }, - "price": 74.85, - "change_pct": -1.38 + "price": 74.75, + "change_pct": -1.45 }, { "code": "00700", @@ -396,8 +396,8 @@ "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "timing_signal": "持有" }, - "price": 419.0, - "change_pct": 1.06 + "price": 418.8, + "change_pct": 0.96 }, { "code": "00981", @@ -425,8 +425,8 @@ "action_note": "", "timing_signal": "持有" }, - "price": 84.75, - "change_pct": 9.12 + "price": 84.7, + "change_pct": 8.8 }, { "code": "09868", @@ -454,8 +454,8 @@ "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "timing_signal": "持有" }, - "price": 49.94, - "change_pct": 1.05 + "price": 49.92, + "change_pct": 1.13 }, { "code": "600036", @@ -540,7 +540,7 @@ "timing_signal": "持有" }, "price": 52.8, - "change_pct": -1.4 + "change_pct": -1.49 }, { "code": "300035", @@ -652,8 +652,8 @@ "action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓", "timing_signal": "持有" }, - "price": 41.84, - "change_pct": -0.33 + "price": 41.8, + "change_pct": -0.62 }, { "code": "600563", diff --git a/scripts/stale_push_wlin.py b/scripts/stale_push_wlin.py index 2f3d277..57ab782 100644 --- a/scripts/stale_push_wlin.py +++ b/scripts/stale_push_wlin.py @@ -23,6 +23,9 @@ try: from urllib.request import Request, urlopen except ImportError: from urllib2 import Request, urlopen +# 6维评分系统 +sys.path.insert(0, "/home/hmo/MoFin/scripts") +from stock_scorer import score_future_outlook, is_hk_stock, settlement_delay_note XMPP_BRIDGE = "http://127.0.0.1:5805/" XMPP_USER = "hmo@yoin.fun" @@ -395,80 +398,7 @@ def main(): return theo_pct, pct_actual, details, lots, lot_cost_total # ── 换仓评估 ────────────────────────────────────────────────────── - 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) + # score_future_outlook 从 stock_scorer 模块导入(6维评分) def evaluate_swap(lot_cost_target, rr, sig, tp, sl, name, code, price_in, total_assets_in, cash_in, pf_in, cd_in): @@ -476,8 +406,9 @@ def main(): 核心逻辑: - 已发生的亏损是沉没成本,不参与决策 - - 用"全面分析"法评估每个持仓的未来前景(基于决策系统既有数据) + - 用6维评分法评估每个持仓的未来前景(基于决策系统既有数据) - 优先卖前景最差的票,保留前景好的票(无论当前盈亏%) + - 卖港股→买A股需T+2到账,如果推荐此方案则标注延迟风险 - 对目标票(RR>=3+买入信号)才有换仓资格 返回(推荐文案str, 缺口float)或 (None, gap) @@ -503,8 +434,8 @@ def main(): hmv *= 0.866 # approximate HKD→CNY hpl_pct = (hp - hc) / hc * 100 if hc else 0 - # 全面评分(越低越差,越建议卖) - fscore = score_future_outlook(h_code, h, cd_in) + # 6维全面评分(越低越差,越建议卖) + fscore, _ = score_future_outlook(h_code, cd_in) ph.append({ "code": h_code, @@ -549,17 +480,22 @@ def main(): target_gain_pct = rr * 3 # 构建推荐文案 + buy_is_a = not is_hk_stock(code) # 目标是否是A股 sell_parts = [] sell_names = [] + settlement_warnings = [] for h in selected: # 每个被选股票配一句"为什么卖它" - reason = f"前景评分{h['score']}" + 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']) + # 检查结算延迟:卖港股→买A股 + if is_hk_stock(h['code']) and buy_is_a: + settlement_warnings.append(f"{h['name']}是港股通,卖出需T+2到账才能买A股") sell_desc = ";".join(sell_parts) new_budget = cash_in + cash_freed @@ -583,10 +519,12 @@ def main(): f"(止损{sl}(-{round((price_in-sl)/price_in*100,1)}%)" f"止盈{tp}(+{round(target_gain_pct,1)}%)" f" RR={rr})\n" - f" 理由:{', '.join(sell_names)}前景评分最低," + f" 理由:{', '.join(sell_names)}评分最低," f"继续持有无积极信号且技术偏弱;" f"换到有明确信号和止损的标的,预期收益更优。" ) + if settlement_warnings: + text += "\n ⚠️ " + " | ".join(settlement_warnings) return text, gap # 标准格式:每个可操作标的 — 大盘/行业/个股三面 + 仓位 diff --git a/scripts/stock_scorer.py b/scripts/stock_scorer.py new file mode 100644 index 0000000..18da6fe --- /dev/null +++ b/scripts/stock_scorer.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +""" +stock_scorer.py — 6维股票前景评分系统 + +用于全面评估持仓或自选股的前景。 +评分越低(负数越大)= 前景越差,越值得考虑卖出。 +评分越高(正数越大)= 前景越好,越值得持有或买入。 + +使用场景: +- 换仓评估(决定卖什么) +- 持仓审查(定期排名) +- 组合优化(识别需清理的票) + +调用方式: + from stock_scorer import score_future_outlook + score, reasons = score_future_outlook(code, decisions_dict) + + # 批量评估 + from stock_scorer import rank_by_outlook + rankings = rank_by_outlook(portfolio_holdings, decisions_dict) +""" + + +def score_future_outlook(code, decisions_data): + """6维评分:基于决策系统分析数据评估股票前景。 + + 评分维度(按重要度排序): + 1. timing_signal — 决策系统主信号(买入/持有/深套持有) + 2. 技术形态 — bearish/bullish/neutral + 3. 量价关系 — 买卖盘主导 + 4. 行业背景 — 板块强弱 + 5. 盈亏比RR — 预期收益/风险 + 6. 股票类别 — 蓝筹/深套/题材 + + Args: + code: 股票代码 + decisions_data: decisions.json 的 "decisions" 数组或 dict(code→数据) + + Returns: + (score, reasons) — score浮点数,reasons字符串列表 + """ + # 支持两种输入格式 + if isinstance(decisions_data, dict): + d = decisions_data.get(code, {}) + elif isinstance(decisions_data, list): + d = {} + for e in decisions_data: + if e.get("code") == code: + d = e + break + else: + return -999, ["无数据"] + + if not d: + return -999, ["无数据"] + + score = 0.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}') + elif rr < 1: + score -= 0.5 + reasons.append(f'RR{rr:.1f}<1') + else: + reasons.append(f'RR{rr:.1f}') + + # 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), reasons + + +def rank_by_outlook(holdings_list, decisions_data): + """批量评估持仓的前景,返回排序后的列表(最差排前)。 + + Args: + holdings_list: 持仓列表,每项有 code, name, shares, cost, price 等 + decisions_data: decisions.json 数据 + + Returns: + 排序后的列表,每项增加了 score, reasons 字段 + """ + results = [] + for h in holdings_list: + code = h.get("code", "") + if not code: + continue + score, reasons = score_future_outlook(code, decisions_data) + results.append({**h, "score": score, "reasons": reasons}) + + results.sort(key=lambda x: x["score"]) + return results + + +def is_hk_stock(code): + """判断是否为港股(港股通标的代码通常5位)""" + return len(str(code)) <= 5 + + +def is_a_stock(code): + """判断是否为A股(6位代码)""" + return len(str(code)) == 6 + + +def settlement_delay_note(sell_code, buy_code): + """返回资金结算延迟说明(如有)。""" + sell_is_hk = is_hk_stock(sell_code) + buy_is_hk = is_hk_stock(buy_code) + + if sell_is_hk and not buy_is_hk: + return "(港股通卖出需T+2到账后才能买A股,注意时间差)" + return ""