fix: 修复外部因素数据源 - 北向资金改为南向资金,修复P0-P7全部外部因素

- P0资金面:DB数据过期时用麦蕊API获取资金流向
- P1市场情绪:去掉turnover字段依赖(DB无此字段)
- P2南向资金:北向实时数据已停公布,改用南向资金(港股通)替代
- P3美股:用腾讯财经API替代失效的AKShare接口
- P4大宗商品:用腾讯财经API替代,修复var_name解析
- P7汇率:用新浪财经API替代失效的AKShare接口
- 修复评分详情API技术得分硬编码问题
- 前端传入tech_score参数确保明细与列表分数一致
This commit is contained in:
selfrelease
2026-07-18 16:45:58 +08:00
parent bb5a72767f
commit 79e869eeda
13 changed files with 637 additions and 193 deletions
+101 -57
View File
@@ -7,7 +7,7 @@
- 技术面评分(compute_deep_analysis 原始分):基础分(0-100)
- P0 资金面:±20
- P1 市场情绪:±10
- P2 向资金:±10
- P2 向资金:±10
- P3 美股外盘:±10
- P4 大宗商品:±5
- P5 公告/异动:±15
@@ -17,9 +17,96 @@
最终评分 = 技术面基础分 + 外部因素加减分(上限100,下限0)
"""
import logging
import time
from datetime import datetime
logger = logging.getLogger(__name__)
# ═══════════════════════════════════════════════════════
# 市场级因素缓存(开市前预计算,日内复用)
# ═══════════════════════════════════════════════════════
_market_cache = {
'data': None, # (market_score, market_factors, market_reasons, summaries)
'timestamp': 0, # 计算时间戳
'ttl': 4 * 3600, # 缓存有效期 4 小时
}
# 个股资金面缓存(30分钟 TTL
_fund_flow_cache = {}
_FUND_FLOW_TTL = 30 * 60
def precompute_market_factors():
"""预计算市场级外部因素并缓存(供定时任务在开市前调用)。
计算 P1 市场情绪、P2 南向、P3 美股、P4 商品、P7 汇率、P6 政策面,
结果存入内存缓存,后续 compute_comprehensive_score_batch 直接复用。
"""
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': []}
_market_cache['data'] = (market_score, market_factors, market_reasons, summaries)
_market_cache['timestamp'] = time.time()
print(f'[评分引擎] 市场级因素预计算完成 (score={market_score}, {datetime.now():%H:%M:%S})')
return market_score, market_factors, market_reasons, summaries
def _get_market_factors():
"""获取市场级因素(优先读缓存,过期则重新计算)"""
now = time.time()
if _market_cache['data'] is not None and (now - _market_cache['timestamp']) < _market_cache['ttl']:
return _market_cache['data']
# 缓存不存在或过期,重新计算
return precompute_market_factors()
def compute_comprehensive_score(stock_code, stock_name, technical_score, df=None):
"""
@@ -75,7 +162,7 @@ def compute_comprehensive_score(stock_code, stock_name, technical_score, df=None
logger.warning(f"P1市场情绪分析失败: {e}")
factors['market_sentiment'] = {'score': 0, 'summary': '分析失败', 'reasons': []}
# ---- P2/P3/P4/P7: 外部因素(向/美股/商品/汇率)----
# ---- P2/P3/P4/P7: 外部因素(向/美股/商品/汇率)----
try:
from services.external_factors import get_all_external_factors
ext_result = get_all_external_factors()
@@ -139,7 +226,7 @@ def compute_comprehensive_score_batch(stocks_data):
批量计算综合评分 — 市场级因素只计算一次,个股级因素逐只计算。
优化点:
- P1 市场情绪、P2 向、P3 美股、P4 商品、P7 汇率、P6 政策 → 市场级,只算一次
- P1 市场情绪、P2 向、P3 美股、P4 商品、P7 汇率、P6 政策 → 市场级,只算一次
- P0 资金面 → 个股级,逐只从DB读取
- P5 公告/异动 → 批量模式跳过(需AKShare API + LLM,太慢),在深度分析时补充
@@ -152,57 +239,8 @@ def compute_comprehensive_score_batch(stocks_data):
返回:
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': []}
# ---- 市场级因素(从缓存读取,开市前由定时任务预计算----
market_score, market_factors, market_reasons, summaries = _get_market_factors()
# ---- 为每只股票计算个股级因素 ----
results = {}
@@ -219,10 +257,16 @@ def compute_comprehensive_score_batch(stocks_data):
}
stock_reasons = list(market_reasons)
# P0: 资金面(个股级,从DB读取)
# P0: 资金面(个股级,从DB读取,带30分钟缓存
try:
from services.fund_flow_analyzer import analyze_fund_flow
fund_result = analyze_fund_flow(code, days=5)
now = time.time()
cached_ff = _fund_flow_cache.get(code)
if cached_ff and (now - cached_ff[1]) < _FUND_FLOW_TTL:
fund_result = cached_ff[0]
else:
from services.fund_flow_analyzer import analyze_fund_flow
fund_result = analyze_fund_flow(code, days=5)
_fund_flow_cache[code] = (fund_result, now)
stock_factors['fund_flow'] = fund_result
stock_external += fund_result.get('score', 0)
stock_reasons.extend(fund_result.get('reasons', []))