init repo

This commit is contained in:
2026-04-25 19:21:28 +08:00
commit 35414c74a2
26 changed files with 3529 additions and 0 deletions

430
analysis_engine.py Normal file
View File

@@ -0,0 +1,430 @@
"""
分析引擎 - 核心估值和财务分析模块
"""
import pandas as pd
import numpy as np
from typing import Dict, List, Tuple, Optional
import logging
from datetime import datetime, timedelta
from config import LOW_VALUE_CRITERIA
logger = logging.getLogger(__name__)
class StockAnalyzer:
def __init__(self):
self.criteria = LOW_VALUE_CRITERIA
def calculate_valuation_metrics(self, data: Dict) -> Dict:
"""计算估值指标"""
try:
key_metrics = data.get('key_metrics', {})
company_info = data.get('company_info', {})
# 基础估值指标
pe_ratio = key_metrics.get('pe_ratio', 0)
pb_ratio = key_metrics.get('pb_ratio', 0)
ps_ratio = key_metrics.get('ps_ratio', 0)
peg_ratio = key_metrics.get('peg_ratio', 0)
# 市值
market_cap = company_info.get('market_cap', 0)
# 计算估值分数 (0-100分分数越高表示越被低估)
valuation_score = 0
# PE比率评分
if pe_ratio > 0 and pe_ratio <= self.criteria['max_pe_ratio']:
pe_score = max(0, 100 - (pe_ratio / self.criteria['max_pe_ratio']) * 50)
valuation_score += pe_score * 0.3
# PB比率评分
if pb_ratio > 0 and pb_ratio <= self.criteria['max_pb_ratio']:
pb_score = max(0, 100 - (pb_ratio / self.criteria['max_pb_ratio']) * 50)
valuation_score += pb_score * 0.3
# PS比率评分
if ps_ratio > 0 and ps_ratio <= self.criteria['max_ps_ratio']:
ps_score = max(0, 100 - (ps_ratio / self.criteria['max_ps_ratio']) * 50)
valuation_score += ps_score * 0.2
# PEG比率评分
if peg_ratio > 0 and peg_ratio <= 1.0:
peg_score = max(0, 100 - peg_ratio * 50)
valuation_score += peg_score * 0.2
return {
'pe_ratio': pe_ratio,
'pb_ratio': pb_ratio,
'ps_ratio': ps_ratio,
'peg_ratio': peg_ratio,
'market_cap': market_cap,
'valuation_score': min(100, max(0, valuation_score)),
'is_undervalued': pe_ratio <= self.criteria['max_pe_ratio'] and
pb_ratio <= self.criteria['max_pb_ratio'] and
ps_ratio <= self.criteria['max_ps_ratio']
}
except Exception as e:
logger.error(f"计算估值指标失败: {e}")
return {'valuation_score': 0, 'is_undervalued': False}
def calculate_financial_health(self, data: Dict) -> Dict:
"""计算财务健康度"""
try:
key_metrics = data.get('key_metrics', {})
financial_statements = data.get('financial_statements', {})
# 获取最新财务数据
latest_financial = self._get_latest_financial_data(financial_statements)
# 流动性指标
current_ratio = key_metrics.get('current_ratio', 0)
quick_ratio = key_metrics.get('quick_ratio', 0)
# 偿债能力指标
debt_to_equity = key_metrics.get('debt_to_equity', 0)
debt_ratio = self._calculate_debt_ratio(latest_financial)
# 盈利能力指标
roe = key_metrics.get('return_on_equity', 0)
roa = key_metrics.get('return_on_assets', 0)
profit_margin = key_metrics.get('profit_margin', 0)
operating_margin = key_metrics.get('operating_margin', 0)
# 计算财务健康分数
health_score = 0
# 流动性评分 (30%)
if current_ratio >= self.criteria['min_current_ratio']:
liquidity_score = min(100, (current_ratio / 2.0) * 100)
health_score += liquidity_score * 0.3
# 偿债能力评分 (30%)
if debt_ratio <= self.criteria['max_debt_ratio']:
debt_score = max(0, 100 - (debt_ratio / self.criteria['max_debt_ratio']) * 100)
health_score += debt_score * 0.3
# 盈利能力评分 (40%)
if roe >= self.criteria['min_roe']:
profitability_score = min(100, (roe / 0.2) * 100) # 20% ROE为满分
health_score += profitability_score * 0.4
return {
'current_ratio': current_ratio,
'quick_ratio': quick_ratio,
'debt_to_equity': debt_to_equity,
'debt_ratio': debt_ratio,
'roe': roe,
'roa': roa,
'profit_margin': profit_margin,
'operating_margin': operating_margin,
'health_score': min(100, max(0, health_score)),
'is_healthy': (current_ratio >= self.criteria['min_current_ratio'] and
debt_ratio <= self.criteria['max_debt_ratio'] and
roe >= self.criteria['min_roe'])
}
except Exception as e:
logger.error(f"计算财务健康度失败: {e}")
return {'health_score': 0, 'is_healthy': False}
def calculate_growth_metrics(self, data: Dict) -> Dict:
"""计算成长性指标"""
try:
key_metrics = data.get('key_metrics', {})
financial_statements = data.get('financial_statements', {})
# 收入增长率
revenue_growth = key_metrics.get('revenue_growth', 0)
earnings_growth = key_metrics.get('earnings_growth', 0)
# 计算历史增长率
historical_growth = self._calculate_historical_growth(financial_statements)
# 成长性评分
growth_score = 0
# 收入增长率评分
if revenue_growth > 0:
revenue_score = min(100, revenue_growth * 100)
growth_score += revenue_score * 0.4
# 利润增长率评分
if earnings_growth > 0:
earnings_score = min(100, earnings_growth * 100)
growth_score += earnings_score * 0.4
# 历史增长率评分
if historical_growth > 0:
historical_score = min(100, historical_growth * 100)
growth_score += historical_score * 0.2
return {
'revenue_growth': revenue_growth,
'earnings_growth': earnings_growth,
'historical_growth': historical_growth,
'growth_score': min(100, max(0, growth_score)),
'is_growing': revenue_growth > 0.05 and earnings_growth > 0.05 # 5%以上增长
}
except Exception as e:
logger.error(f"计算成长性指标失败: {e}")
return {'growth_score': 0, 'is_growing': False}
def calculate_risk_metrics(self, data: Dict) -> Dict:
"""计算风险指标"""
try:
key_metrics = data.get('key_metrics', {})
stock_prices = data.get('stock_prices', pd.DataFrame())
# Beta系数
beta = key_metrics.get('beta', 1.0)
# 计算波动率
volatility = self._calculate_volatility(stock_prices)
# 计算最大回撤
max_drawdown = self._calculate_max_drawdown(stock_prices)
# 风险评分 (分数越高表示风险越低)
risk_score = 100
# Beta风险评分
if beta > 1.5:
risk_score -= (beta - 1.5) * 20
elif beta < 0.8:
risk_score -= (0.8 - beta) * 10
# 波动率风险评分
if volatility > 0.3: # 30%以上波动率
risk_score -= (volatility - 0.3) * 100
# 回撤风险评分
if max_drawdown > 0.2: # 20%以上回撤
risk_score -= (max_drawdown - 0.2) * 100
return {
'beta': beta,
'volatility': volatility,
'max_drawdown': max_drawdown,
'risk_score': max(0, min(100, risk_score)),
'risk_level': self._get_risk_level(risk_score)
}
except Exception as e:
logger.error(f"计算风险指标失败: {e}")
return {'risk_score': 50, 'risk_level': '中等'}
def perform_dcf_valuation(self, data: Dict) -> Dict:
"""执行DCF估值分析"""
try:
financial_statements = data.get('financial_statements', {})
key_metrics = data.get('key_metrics', {})
# 获取最新财务数据
latest_financial = self._get_latest_financial_data(financial_statements)
if not latest_financial:
return {'dcf_value': 0, 'dcf_score': 0}
# 基础参数
revenue = latest_financial.get('revenue', 0)
net_income = latest_financial.get('net_income', 0)
total_assets = latest_financial.get('total_assets', 0)
if revenue <= 0 or net_income <= 0:
return {'dcf_value': 0, 'dcf_score': 0}
# 假设参数
growth_rate = min(0.1, max(0.02, key_metrics.get('revenue_growth', 0.05))) # 2%-10%
discount_rate = 0.1 # 10%折现率
terminal_growth_rate = 0.03 # 3%永续增长率
# 计算自由现金流 (简化版)
fcf = net_income * 0.8 # 假设80%的净利润为自由现金流
# 5年预测
years = 5
dcf_value = 0
for year in range(1, years + 1):
future_fcf = fcf * ((1 + growth_rate) ** year)
discounted_fcf = future_fcf / ((1 + discount_rate) ** year)
dcf_value += discounted_fcf
# 终值计算
terminal_fcf = fcf * ((1 + growth_rate) ** years) * (1 + terminal_growth_rate)
terminal_value = terminal_fcf / (discount_rate - terminal_growth_rate)
discounted_terminal_value = terminal_value / ((1 + discount_rate) ** years)
total_dcf_value = dcf_value + discounted_terminal_value
# 获取当前股价进行对比
current_price = self._get_current_price(data)
if current_price > 0:
dcf_score = min(100, (total_dcf_value / current_price) * 50) # 50%以上溢价为满分
else:
dcf_score = 0
return {
'dcf_value': total_dcf_value,
'current_price': current_price,
'dcf_score': dcf_score,
'is_undervalued_dcf': total_dcf_value > current_price if current_price > 0 else False
}
except Exception as e:
logger.error(f"DCF估值分析失败: {e}")
return {'dcf_value': 0, 'dcf_score': 0}
def generate_investment_recommendation(self, analysis_results: Dict) -> Dict:
"""生成投资建议"""
try:
valuation_score = analysis_results.get('valuation_score', 0)
health_score = analysis_results.get('financial_health_score', 0)
growth_score = analysis_results.get('growth_score', 0)
risk_score = analysis_results.get('risk_score', 0)
# 计算综合评分
overall_score = (
valuation_score * 0.4 +
health_score * 0.3 +
growth_score * 0.2 +
risk_score * 0.1
)
# 生成建议
if overall_score >= 80:
recommendation = "强烈买入"
confidence = ""
elif overall_score >= 65:
recommendation = "买入"
confidence = "中高"
elif overall_score >= 50:
recommendation = "持有"
confidence = "中等"
elif overall_score >= 35:
recommendation = "观望"
confidence = "中低"
else:
recommendation = "卖出"
confidence = ""
# 风险提示
risk_warnings = []
if risk_score < 40:
risk_warnings.append("高风险投资")
if health_score < 50:
risk_warnings.append("财务健康度较低")
if growth_score < 30:
risk_warnings.append("成长性不足")
return {
'recommendation': recommendation,
'confidence': confidence,
'overall_score': overall_score,
'risk_warnings': risk_warnings,
'key_strengths': self._get_key_strengths(analysis_results),
'key_concerns': self._get_key_concerns(analysis_results)
}
except Exception as e:
logger.error(f"生成投资建议失败: {e}")
return {'recommendation': '无法分析', 'overall_score': 0}
def _get_latest_financial_data(self, financial_statements: Dict) -> Dict:
"""获取最新财务数据"""
if not financial_statements:
return {}
# 按年份排序,获取最新的数据
sorted_periods = sorted(financial_statements.keys(), reverse=True)
return financial_statements.get(sorted_periods[0], {})
def _calculate_debt_ratio(self, financial_data: Dict) -> float:
"""计算债务比率"""
total_liabilities = financial_data.get('total_liabilities', 0)
total_assets = financial_data.get('total_assets', 0)
if total_assets > 0:
return total_liabilities / total_assets
return 0
def _calculate_historical_growth(self, financial_statements: Dict) -> float:
"""计算历史增长率"""
if len(financial_statements) < 2:
return 0
# 获取最近两年的收入数据
sorted_periods = sorted(financial_statements.keys(), reverse=True)
if len(sorted_periods) < 2:
return 0
current_revenue = financial_statements[sorted_periods[0]].get('revenue', 0)
previous_revenue = financial_statements[sorted_periods[1]].get('revenue', 0)
if previous_revenue > 0:
return (current_revenue - previous_revenue) / previous_revenue
return 0
def _calculate_volatility(self, stock_prices: pd.DataFrame) -> float:
"""计算波动率"""
if stock_prices.empty or 'close' not in stock_prices.columns:
return 0
returns = stock_prices['close'].pct_change().dropna()
return returns.std() * np.sqrt(252) # 年化波动率
def _calculate_max_drawdown(self, stock_prices: pd.DataFrame) -> float:
"""计算最大回撤"""
if stock_prices.empty or 'close' not in stock_prices.columns:
return 0
prices = stock_prices['close']
peak = prices.expanding().max()
drawdown = (prices - peak) / peak
return abs(drawdown.min())
def _get_risk_level(self, risk_score: float) -> str:
"""获取风险等级"""
if risk_score >= 80:
return ""
elif risk_score >= 60:
return "中低"
elif risk_score >= 40:
return "中等"
elif risk_score >= 20:
return "中高"
else:
return ""
def _get_current_price(self, data: Dict) -> float:
"""获取当前股价"""
stock_prices = data.get('stock_prices', pd.DataFrame())
if not stock_prices.empty and 'close' in stock_prices.columns:
return stock_prices['close'].iloc[-1]
return 0
def _get_key_strengths(self, analysis_results: Dict) -> List[str]:
"""获取关键优势"""
strengths = []
if analysis_results.get('valuation_score', 0) > 70:
strengths.append("估值吸引力高")
if analysis_results.get('financial_health_score', 0) > 70:
strengths.append("财务健康度良好")
if analysis_results.get('growth_score', 0) > 70:
strengths.append("成长性优秀")
if analysis_results.get('risk_score', 0) > 70:
strengths.append("风险控制良好")
return strengths
def _get_key_concerns(self, analysis_results: Dict) -> List[str]:
"""获取关键担忧"""
concerns = []
if analysis_results.get('valuation_score', 0) < 40:
concerns.append("估值偏高")
if analysis_results.get('financial_health_score', 0) < 40:
concerns.append("财务健康度不佳")
if analysis_results.get('growth_score', 0) < 40:
concerns.append("成长性不足")
if analysis_results.get('risk_score', 0) < 40:
concerns.append("风险较高")
return concerns