6维评分通用模块 + 港股通T+2延迟标注
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股
This commit is contained in:
+1
-1
@@ -9623,5 +9623,5 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"total": 42,
|
"total": 42,
|
||||||
"regenerated_at": "2026-06-24 11:54"
|
"regenerated_at": "2026-06-24 11:59"
|
||||||
}
|
}
|
||||||
+19
-19
@@ -54,8 +54,8 @@
|
|||||||
"action_note": "深套持有",
|
"action_note": "深套持有",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 7.71,
|
"price": 7.67,
|
||||||
"change_pct": -2.16
|
"change_pct": -2.54
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "600739",
|
"code": "600739",
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
"action_note": "深套持有",
|
"action_note": "深套持有",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 98.95,
|
"price": 98.85,
|
||||||
"change_pct": -0.1
|
"change_pct": -0.1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -252,8 +252,8 @@
|
|||||||
"action_note": "短炒强趋势持",
|
"action_note": "短炒强趋势持",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 95.1,
|
"price": 94.7,
|
||||||
"change_pct": 9.11
|
"change_pct": 8.54
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "02202",
|
"code": "02202",
|
||||||
@@ -281,7 +281,7 @@
|
|||||||
"action_note": "深套持有",
|
"action_note": "深套持有",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 2.36,
|
"price": 2.35,
|
||||||
"change_pct": -1.67
|
"change_pct": -1.67
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -310,8 +310,8 @@
|
|||||||
"action_note": "",
|
"action_note": "",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 46.22,
|
"price": 46.1,
|
||||||
"change_pct": -1.57
|
"change_pct": -1.91
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "300750",
|
"code": "300750",
|
||||||
@@ -367,8 +367,8 @@
|
|||||||
"action_note": "深套持有",
|
"action_note": "深套持有",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 74.85,
|
"price": 74.75,
|
||||||
"change_pct": -1.38
|
"change_pct": -1.45
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "00700",
|
"code": "00700",
|
||||||
@@ -396,8 +396,8 @@
|
|||||||
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
|
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 419.0,
|
"price": 418.8,
|
||||||
"change_pct": 1.06
|
"change_pct": 0.96
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "00981",
|
"code": "00981",
|
||||||
@@ -425,8 +425,8 @@
|
|||||||
"action_note": "",
|
"action_note": "",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 84.75,
|
"price": 84.7,
|
||||||
"change_pct": 9.12
|
"change_pct": 8.8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "09868",
|
"code": "09868",
|
||||||
@@ -454,8 +454,8 @@
|
|||||||
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
|
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 49.94,
|
"price": 49.92,
|
||||||
"change_pct": 1.05
|
"change_pct": 1.13
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "600036",
|
"code": "600036",
|
||||||
@@ -540,7 +540,7 @@
|
|||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 52.8,
|
"price": 52.8,
|
||||||
"change_pct": -1.4
|
"change_pct": -1.49
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "300035",
|
"code": "300035",
|
||||||
@@ -652,8 +652,8 @@
|
|||||||
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
|
"action_note": "⚠️盈亏比偏低(1:1.0),不建议加仓",
|
||||||
"timing_signal": "持有"
|
"timing_signal": "持有"
|
||||||
},
|
},
|
||||||
"price": 41.84,
|
"price": 41.8,
|
||||||
"change_pct": -0.33
|
"change_pct": -0.62
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "600563",
|
"code": "600563",
|
||||||
|
|||||||
+17
-79
@@ -23,6 +23,9 @@ try:
|
|||||||
from urllib.request import Request, urlopen
|
from urllib.request import Request, urlopen
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from urllib2 import Request, urlopen
|
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_BRIDGE = "http://127.0.0.1:5805/"
|
||||||
XMPP_USER = "hmo@yoin.fun"
|
XMPP_USER = "hmo@yoin.fun"
|
||||||
@@ -395,80 +398,7 @@ def main():
|
|||||||
return theo_pct, pct_actual, details, lots, lot_cost_total
|
return theo_pct, pct_actual, details, lots, lot_cost_total
|
||||||
|
|
||||||
# ── 换仓评估 ──────────────────────────────────────────────────────
|
# ── 换仓评估 ──────────────────────────────────────────────────────
|
||||||
def score_future_outlook(code_in, h_data, cd_in):
|
# score_future_outlook 从 stock_scorer 模块导入(6维评分)
|
||||||
"""基于决策系统已有的分析数据,评估持仓的未来前景评分。
|
|
||||||
|
|
||||||
评分越低(负数越大)= 前景越差,越值得考虑卖出。
|
|
||||||
评分越高(正数越大)= 前景越好,应该保留。
|
|
||||||
|
|
||||||
考虑维度(按重要度排序):
|
|
||||||
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,
|
def evaluate_swap(lot_cost_target, rr, sig, tp, sl, name, code, price_in,
|
||||||
total_assets_in, cash_in, pf_in, cd_in):
|
total_assets_in, cash_in, pf_in, cd_in):
|
||||||
@@ -476,8 +406,9 @@ def main():
|
|||||||
|
|
||||||
核心逻辑:
|
核心逻辑:
|
||||||
- 已发生的亏损是沉没成本,不参与决策
|
- 已发生的亏损是沉没成本,不参与决策
|
||||||
- 用"全面分析"法评估每个持仓的未来前景(基于决策系统既有数据)
|
- 用6维评分法评估每个持仓的未来前景(基于决策系统既有数据)
|
||||||
- 优先卖前景最差的票,保留前景好的票(无论当前盈亏%)
|
- 优先卖前景最差的票,保留前景好的票(无论当前盈亏%)
|
||||||
|
- 卖港股→买A股需T+2到账,如果推荐此方案则标注延迟风险
|
||||||
- 对目标票(RR>=3+买入信号)才有换仓资格
|
- 对目标票(RR>=3+买入信号)才有换仓资格
|
||||||
|
|
||||||
返回(推荐文案str, 缺口float)或 (None, gap)
|
返回(推荐文案str, 缺口float)或 (None, gap)
|
||||||
@@ -503,8 +434,8 @@ def main():
|
|||||||
hmv *= 0.866 # approximate HKD→CNY
|
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
|
||||||
|
|
||||||
# 全面评分(越低越差,越建议卖)
|
# 6维全面评分(越低越差,越建议卖)
|
||||||
fscore = score_future_outlook(h_code, h, cd_in)
|
fscore, _ = score_future_outlook(h_code, cd_in)
|
||||||
|
|
||||||
ph.append({
|
ph.append({
|
||||||
"code": h_code,
|
"code": h_code,
|
||||||
@@ -549,17 +480,22 @@ def main():
|
|||||||
target_gain_pct = rr * 3
|
target_gain_pct = rr * 3
|
||||||
|
|
||||||
# 构建推荐文案
|
# 构建推荐文案
|
||||||
|
buy_is_a = not is_hk_stock(code) # 目标是否是A股
|
||||||
sell_parts = []
|
sell_parts = []
|
||||||
sell_names = []
|
sell_names = []
|
||||||
|
settlement_warnings = []
|
||||||
for h in selected:
|
for h in selected:
|
||||||
# 每个被选股票配一句"为什么卖它"
|
# 每个被选股票配一句"为什么卖它"
|
||||||
reason = f"前景评分{h['score']}"
|
reason = f"评分{h['score']}"
|
||||||
if h['pl_pct'] <= -30:
|
if h['pl_pct'] <= -30:
|
||||||
reason += "深套"
|
reason += "深套"
|
||||||
elif h['pl_pct'] <= -15:
|
elif h['pl_pct'] <= -15:
|
||||||
reason += f"亏损{h['pl_pct']}%"
|
reason += f"亏损{h['pl_pct']}%"
|
||||||
sell_parts.append(f"{h['name']}({h['code']}) {h['shares']}股 亏{h['pl_pct']}% ({reason})")
|
sell_parts.append(f"{h['name']}({h['code']}) {h['shares']}股 亏{h['pl_pct']}% ({reason})")
|
||||||
sell_names.append(h['name'])
|
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)
|
sell_desc = ";".join(sell_parts)
|
||||||
|
|
||||||
new_budget = cash_in + cash_freed
|
new_budget = cash_in + cash_freed
|
||||||
@@ -583,10 +519,12 @@ 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" 理由:{', '.join(sell_names)}前景评分最低,"
|
f" 理由:{', '.join(sell_names)}评分最低,"
|
||||||
f"继续持有无积极信号且技术偏弱;"
|
f"继续持有无积极信号且技术偏弱;"
|
||||||
f"换到有明确信号和止损的标的,预期收益更优。"
|
f"换到有明确信号和止损的标的,预期收益更优。"
|
||||||
)
|
)
|
||||||
|
if settlement_warnings:
|
||||||
|
text += "\n ⚠️ " + " | ".join(settlement_warnings)
|
||||||
return text, gap
|
return text, gap
|
||||||
|
|
||||||
# 标准格式:每个可操作标的 — 大盘/行业/个股三面 + 仓位
|
# 标准格式:每个可操作标的 — 大盘/行业/个股三面 + 仓位
|
||||||
|
|||||||
@@ -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 ""
|
||||||
Reference in New Issue
Block a user