Files
MoFin/scripts/stock_scorer.py
hmo 6abc2e45b0 refactor: phase 0-2 MoFin architecture reform — single source of truth
Phase 0 (止血):
- mo_models.py: unified calc_total_assets(), is_hk_stock(), get_hk_rate() — single source of truth
- Fixed 3 files missing frozen_cash: holdings_reconciliation, server, import_holding_xls
- Fixed stale_push_wlin: unified is_hk_stock detection, removed hardcoded 0.866
- Fixed price_monitor: consolidated 2 duplicate total_assets blocks into mo_models calls
- Fixed stock_scorer: replaced broken len()<=5 is_hk_stock heuristic
- Fixed strategy_lifecycle: replaced non-existent currency_utils import with mo_models

Phase 1 (DSA adapter):
- mo_provider.py: wraps DSA DataFetcherManager (16 fetchers, auto-fallback)
  - TDX relay as primary, DSA as backup for realtime/kline/news/fundamentals

Phase 2 (Integration):
- mo_bridge.py: injects DSA market review + news context into MoFin analysis prompts
- Graceful degradation if DSA not installed

Infrastructure:
- mo_config.py: centralized Config singleton replacing scattered hardcoded paths
- All 11 changed files pass python compile check

Impact: total_assets now computed in ONE place (mo_models).
        is_hk_stock now ONE implementation (no more false negatives).
        HK rate now ONE source (hk_rate API → cache → 0.87 fallback).
        No more hardcoded 0.866/0.8664/0.8700 divergence.
2026-06-29 23:25:54 +08:00

152 lines
4.5 KiB
Python

#!/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
import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from mo_models import is_hk_stock, is_a_stock
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 ""