Files
stock/stock-html/services/score_engine.py
T

266 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
综合评分引擎 — 整合所有影响因素到统一评分体系
将技术面(基础50%+)与外部因素(加减分项)整合为最终评分。
权重分配:
- 技术面评分(compute_deep_analysis 原始分):基础分(0-100)
- P0 资金面:±20
- P1 市场情绪:±10
- P2 北向资金:±10
- P3 美股外盘:±10
- P4 大宗商品:±5
- P5 公告/异动:±15
- P6 政策面:±10
- P7 汇率:±3
最终评分 = 技术面基础分 + 外部因素加减分(上限100,下限0)
"""
import logging
logger = logging.getLogger(__name__)
def compute_comprehensive_score(stock_code, stock_name, technical_score, df=None):
"""
综合评分引擎 — 整合技术面和所有外部因素
参数:
stock_code: 股票代码
stock_name: 股票名称
technical_score: float — 技术面基础评分(0-100,来自 compute_deep_analysis
df: K线DataFrame(用于异动检测,可选)
返回:
dict: {
'technical_score': float, # 技术面基础分
'external_score': int, # 外部因素总加减分
'final_score': int, # 最终综合评分(0-100)
'verdict': str, # 最终评级
'factors': dict, # 各因素详细数据
'all_reasons': list, # 所有评分原因
'summary': str, # 综合白话总结
}
"""
factors = {}
all_reasons = []
external_score = 0
summaries = []
# ---- P0: 主力资金进出 ----
try:
from services.fund_flow_analyzer import analyze_fund_flow
fund_result = analyze_fund_flow(stock_code, days=5)
factors['fund_flow'] = fund_result
external_score += fund_result.get('score', 0)
all_reasons.extend(fund_result.get('reasons', []))
s = fund_result.get('summary', '')
if s and '暂无' not in s:
summaries.append(f'资金面:{s}')
except Exception as e:
logger.warning(f"P0资金面分析失败: {e}")
factors['fund_flow'] = {'score': 0, 'summary': '分析失败', 'reasons': []}
# ---- P1: 市场情绪指标 ----
try:
from services.market_sentiment import calc_market_sentiment
sentiment_result = calc_market_sentiment()
factors['market_sentiment'] = sentiment_result
external_score += sentiment_result.get('score', 0)
all_reasons.extend(sentiment_result.get('reasons', []))
s = sentiment_result.get('sentiment', '')
if s and '无数据' not in s:
summaries.append(f'市场情绪:{s}(涨跌停{sentiment_result.get("limit_up_count",0)}:{sentiment_result.get("limit_down_count",0)}')
except Exception as e:
logger.warning(f"P1市场情绪分析失败: {e}")
factors['market_sentiment'] = {'score': 0, 'summary': '分析失败', 'reasons': []}
# ---- P2/P3/P4/P7: 外部因素(北向/美股/商品/汇率)----
try:
from services.external_factors import get_all_external_factors
ext_result = get_all_external_factors()
factors['external'] = ext_result
external_score += ext_result.get('total_score', 0)
all_reasons.extend(ext_result.get('all_reasons', []))
s = ext_result.get('summary', '')
if s:
summaries.append(s)
except Exception as e:
logger.warning(f"P2-P7外部因素分析失败: {e}")
factors['external'] = {'total_score': 0, 'summary': '分析失败', 'all_reasons': []}
# ---- P5/P6: 公告/异动/政策 ----
try:
from services.news_analyzer import analyze_news_factors
news_result = analyze_news_factors(stock_code, stock_name, df)
factors['news'] = news_result
external_score += news_result.get('total_score', 0)
all_reasons.extend(news_result.get('all_reasons', []))
s = news_result.get('summary', '')
if s:
summaries.append(s)
except Exception as e:
logger.warning(f"P5/P6消息面分析失败: {e}")
factors['news'] = {'total_score': 0, 'summary': '分析失败', 'all_reasons': []}
# ---- 最终评分 ----
# 外部因素加减分上限:±40(避免喧宾夺主)
external_score = max(-40, min(40, external_score))
final_score = max(0, min(100, int(technical_score + external_score)))
# 最终评级
if final_score >= 80:
verdict = '强烈看多'
elif final_score >= 65:
verdict = '看多'
elif final_score >= 50:
verdict = '中性偏多'
elif final_score >= 35:
verdict = '中性偏空'
else:
verdict = '看空'
# 综合总结
summary = ' | '.join(summaries) if summaries else '暂无外部因素数据'
return {
'technical_score': round(technical_score, 0),
'external_score': external_score,
'final_score': final_score,
'verdict': verdict,
'factors': factors,
'all_reasons': all_reasons,
'summary': summary,
}
def compute_comprehensive_score_batch(stocks_data):
"""
批量计算综合评分 — 市场级因素只计算一次,个股级因素逐只计算。
优化点:
- P1 市场情绪、P2 北向、P3 美股、P4 商品、P7 汇率、P6 政策 → 市场级,只算一次
- P0 资金面 → 个股级,逐只从DB读取
- P5 公告/异动 → 批量模式跳过(需AKShare API + LLM,太慢),在深度分析时补充
参数:
stocks_data: list[dict],每个元素包含:
- stock_code: str 股票代码
- stock_name: str 股票名称(可选)
- technical_score: float 技术得分(0-100
返回:
dict: {stock_code: {technical_score, external_score, final_score, verdict, factors, all_reasons, summary}}
"""
# ---- 市场级因素(只计算一次)----
market_score = 0
market_factors = {}
market_reasons = []
summaries = []
# P1: 市场情绪
try:
from services.market_sentiment import calc_market_sentiment
sentiment_result = calc_market_sentiment()
market_factors['market_sentiment'] = sentiment_result
market_score += sentiment_result.get('score', 0)
market_reasons.extend(sentiment_result.get('reasons', []))
s = sentiment_result.get('sentiment', '')
if s and '无数据' not in s:
summaries.append(
f'市场情绪:{s}(涨跌停{sentiment_result.get("limit_up_count", 0)}:'
f'{sentiment_result.get("limit_down_count", 0)}'
)
except Exception as e:
logger.warning(f"批量P1市场情绪分析失败: {e}")
market_factors['market_sentiment'] = {'score': 0, 'summary': '分析失败', 'reasons': []}
# P2/P3/P4/P7: 外部因素(北向/美股/商品/汇率)
try:
from services.external_factors import get_all_external_factors
ext_result = get_all_external_factors()
market_factors['external'] = ext_result
market_score += ext_result.get('total_score', 0)
market_reasons.extend(ext_result.get('all_reasons', []))
s = ext_result.get('summary', '')
if s:
summaries.append(s)
except Exception as e:
logger.warning(f"批量P2-P7外部因素分析失败: {e}")
market_factors['external'] = {'total_score': 0, 'summary': '分析失败', 'all_reasons': []}
# P6: 政策面(市场级,只算一次)
try:
from services.news_analyzer import get_policy_news, analyze_policy_impact
policy_news = get_policy_news(days=3)
policy_result = analyze_policy_impact(policy_news)
market_factors['policy'] = policy_result
market_score += policy_result.get('score', 0)
market_reasons.extend(policy_result.get('reasons', []))
s = policy_result.get('summary', '')
if s and '失败' not in s:
summaries.append(f'政策面:{s}')
except Exception as e:
logger.warning(f"批量P6政策面分析失败: {e}")
market_factors['policy'] = {'score': 0, 'summary': '分析失败', 'reasons': []}
# ---- 为每只股票计算个股级因素 ----
results = {}
for stock in stocks_data:
code = stock.get('stock_code', '')
name = stock.get('stock_name', '')
tech_score = stock.get('technical_score', 50)
stock_external = market_score
stock_factors = {
'market_sentiment': market_factors.get('market_sentiment', {}),
'external': market_factors.get('external', {}),
'policy': market_factors.get('policy', {}),
}
stock_reasons = list(market_reasons)
# P0: 资金面(个股级,从DB读取)
try:
from services.fund_flow_analyzer import analyze_fund_flow
fund_result = analyze_fund_flow(code, days=5)
stock_factors['fund_flow'] = fund_result
stock_external += fund_result.get('score', 0)
stock_reasons.extend(fund_result.get('reasons', []))
except Exception as e:
logger.warning(f"批量P0资金面分析失败 {code}: {e}")
stock_factors['fund_flow'] = {'score': 0, 'summary': '分析失败', 'reasons': []}
# P5: 公告/异动 — 批量模式跳过(需AKShare API + LLM,在深度分析时补充)
stock_factors['news'] = {
'total_score': 0, 'summary': '批量模式跳过,请使用深度分析查看',
'all_reasons': [],
}
# 外部得分上限 ±40
stock_external = max(-40, min(40, stock_external))
final_score = max(0, min(100, int(tech_score + stock_external)))
# 评级
if final_score >= 80:
verdict = '强烈看多'
elif final_score >= 65:
verdict = '看多'
elif final_score >= 50:
verdict = '中性偏多'
elif final_score >= 35:
verdict = '中性偏空'
else:
verdict = '看空'
results[code] = {
'technical_score': round(tech_score, 0),
'external_score': stock_external,
'final_score': final_score,
'verdict': verdict,
'factors': stock_factors,
'all_reasons': stock_reasons,
'summary': ' | '.join(summaries) if summaries else '',
}
return results