266 lines
10 KiB
Python
266 lines
10 KiB
Python
"""
|
||
综合评分引擎 — 整合所有影响因素到统一评分体系
|
||
|
||
将技术面(基础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
|