470 lines
16 KiB
Python
470 lines
16 KiB
Python
|
|
"""
|
|||
|
|
投资分析引擎
|
|||
|
|
提供基本面分析、技术面分析和估值分析功能
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import pandas as pd
|
|||
|
|
import numpy as np
|
|||
|
|
from typing import Dict, List, Tuple, Optional
|
|||
|
|
from dataclasses import dataclass
|
|||
|
|
from enum import Enum
|
|||
|
|
|
|||
|
|
|
|||
|
|
class InvestmentRating(Enum):
|
|||
|
|
"""投资评级"""
|
|||
|
|
BUY = "买入"
|
|||
|
|
ACCUMULATE = "增持"
|
|||
|
|
HOLD = "持有"
|
|||
|
|
REDUCE = "减持"
|
|||
|
|
SELL = "卖出"
|
|||
|
|
|
|||
|
|
|
|||
|
|
class RiskLevel(Enum):
|
|||
|
|
"""风险等级"""
|
|||
|
|
LOW = "低风险"
|
|||
|
|
LOW_MEDIUM = "中低风险"
|
|||
|
|
MEDIUM = "中等风险"
|
|||
|
|
MEDIUM_HIGH = "中高风险"
|
|||
|
|
HIGH = "高风险"
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class TechnicalAnalysis:
|
|||
|
|
"""技术分析结果"""
|
|||
|
|
trend: str # 趋势判断
|
|||
|
|
support_level: float # 支撑位
|
|||
|
|
resistance_level: float # 阻力位
|
|||
|
|
volatility: float # 波动率
|
|||
|
|
ma_status: str # 均线状态
|
|||
|
|
technical_summary: str # 技术总结
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class FundamentalAnalysis:
|
|||
|
|
"""基本面分析结果"""
|
|||
|
|
roe: float # 净资产收益率
|
|||
|
|
roe_comment: str # ROE评价
|
|||
|
|
pe_ttm: float # 市盈率TTM
|
|||
|
|
pb_lf: float # 市净率LF
|
|||
|
|
valuation_comment: str # 估值评价
|
|||
|
|
fundamental_summary: str # 基本面总结
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class InvestmentRecommendation:
|
|||
|
|
"""投资建议"""
|
|||
|
|
rating: InvestmentRating
|
|||
|
|
target_price: float
|
|||
|
|
risk_level: RiskLevel
|
|||
|
|
recommendation_text: str
|
|||
|
|
key_factors: List[str]
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AnalysisEngine:
|
|||
|
|
"""
|
|||
|
|
投资分析引擎
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
# ROE评价标准
|
|||
|
|
ROE_THRESHOLDS = {
|
|||
|
|
'excellent': 20,
|
|||
|
|
'good': 15,
|
|||
|
|
'average': 10,
|
|||
|
|
'poor': 5
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# PE评价标准(参考值,实际应根据行业调整)
|
|||
|
|
PE_THRESHOLDS = {
|
|||
|
|
'undervalued': 15,
|
|||
|
|
'fair': 25,
|
|||
|
|
'overvalued': 35
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# PB评价标准
|
|||
|
|
PB_THRESHOLDS = {
|
|||
|
|
'undervalued': 1.5,
|
|||
|
|
'fair': 3.0,
|
|||
|
|
'overvalued': 5.0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def __init__(self, price_df: pd.DataFrame, financial_data: Dict):
|
|||
|
|
"""
|
|||
|
|
初始化分析引擎
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
price_df: 价格数据DataFrame
|
|||
|
|
financial_data: 财务数据字典
|
|||
|
|
"""
|
|||
|
|
self.price_df = price_df.copy()
|
|||
|
|
self.financial_data = financial_data
|
|||
|
|
|
|||
|
|
# 计算涨跌幅和收益率
|
|||
|
|
if 'pct_chg' not in self.price_df.columns and 'close' in self.price_df.columns:
|
|||
|
|
self.price_df['pct_chg'] = self.price_df['close'].pct_change() * 100
|
|||
|
|
|
|||
|
|
def analyze_technical(self) -> TechnicalAnalysis:
|
|||
|
|
"""
|
|||
|
|
技术分析
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
TechnicalAnalysis对象
|
|||
|
|
"""
|
|||
|
|
df = self.price_df
|
|||
|
|
|
|||
|
|
# 计算均线
|
|||
|
|
if 'ma5' not in df.columns:
|
|||
|
|
df['ma5'] = df['close'].rolling(window=5).mean()
|
|||
|
|
if 'ma10' not in df.columns:
|
|||
|
|
df['ma10'] = df['close'].rolling(window=10).mean()
|
|||
|
|
if 'ma20' not in df.columns:
|
|||
|
|
df['ma20'] = df['close'].rolling(window=20).mean()
|
|||
|
|
|
|||
|
|
latest = df.iloc[-1]
|
|||
|
|
|
|||
|
|
# 趋势判断
|
|||
|
|
if latest['close'] > latest['ma5'] > latest['ma10'] > latest['ma20']:
|
|||
|
|
trend = "强势上涨"
|
|||
|
|
elif latest['close'] > latest['ma5'] > latest['ma10']:
|
|||
|
|
trend = "震荡上行"
|
|||
|
|
elif latest['close'] < latest['ma5'] < latest['ma10'] < latest['ma20']:
|
|||
|
|
trend = "强势下跌"
|
|||
|
|
elif latest['close'] < latest['ma5'] < latest['ma10']:
|
|||
|
|
trend = "震荡下行"
|
|||
|
|
else:
|
|||
|
|
trend = "震荡整理"
|
|||
|
|
|
|||
|
|
# 计算支撑位和阻力位(基于近期高低点)
|
|||
|
|
recent_data = df.tail(20)
|
|||
|
|
support_level = recent_data['low'].min()
|
|||
|
|
resistance_level = recent_data['high'].max()
|
|||
|
|
|
|||
|
|
# 计算波动率
|
|||
|
|
returns = df['close'].pct_change().dropna()
|
|||
|
|
volatility = returns.std() * np.sqrt(52) * 100 # 年化波动率
|
|||
|
|
|
|||
|
|
# 均线状态
|
|||
|
|
if latest['ma5'] > latest['ma20']:
|
|||
|
|
ma_status = "多头排列"
|
|||
|
|
elif latest['ma5'] < latest['ma20']:
|
|||
|
|
ma_status = "空头排列"
|
|||
|
|
else:
|
|||
|
|
ma_status = "均线粘合"
|
|||
|
|
|
|||
|
|
# 技术总结
|
|||
|
|
if trend in ["强势上涨", "震荡上行"] and ma_status == "多头排列":
|
|||
|
|
technical_summary = "技术面呈上升趋势,建议关注回调买入机会。"
|
|||
|
|
elif trend in ["强势下跌", "震荡下行"]:
|
|||
|
|
technical_summary = "技术面呈下降趋势,建议观望或减仓。"
|
|||
|
|
else:
|
|||
|
|
technical_summary = "技术面呈震荡格局,建议高抛低吸。"
|
|||
|
|
|
|||
|
|
return TechnicalAnalysis(
|
|||
|
|
trend=trend,
|
|||
|
|
support_level=round(support_level, 2),
|
|||
|
|
resistance_level=round(resistance_level, 2),
|
|||
|
|
volatility=round(volatility, 2),
|
|||
|
|
ma_status=ma_status,
|
|||
|
|
technical_summary=technical_summary
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def analyze_fundamental(self) -> FundamentalAnalysis:
|
|||
|
|
"""
|
|||
|
|
基本面分析
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
FundamentalAnalysis对象
|
|||
|
|
"""
|
|||
|
|
# 获取ROE
|
|||
|
|
roe = self.financial_data.get('roe_wgt', 0) or self.financial_data.get('roe', 0)
|
|||
|
|
|
|||
|
|
# ROE评价
|
|||
|
|
if roe >= self.ROE_THRESHOLDS['excellent']:
|
|||
|
|
roe_comment = "ROE表现优异,公司盈利能力强劲"
|
|||
|
|
elif roe >= self.ROE_THRESHOLDS['good']:
|
|||
|
|
roe_comment = "ROE表现良好,盈利能力较强"
|
|||
|
|
elif roe >= self.ROE_THRESHOLDS['average']:
|
|||
|
|
roe_comment = "ROE处于行业平均水平"
|
|||
|
|
else:
|
|||
|
|
roe_comment = "ROE偏低,需关注盈利能力改善"
|
|||
|
|
|
|||
|
|
# 获取PE和PB
|
|||
|
|
pe_ttm = self.financial_data.get('pe_ttm', 0) or self.price_df['pe_ttm'].iloc[-1] if 'pe_ttm' in self.price_df.columns else 0
|
|||
|
|
pb_lf = self.financial_data.get('pb_lf', 0) or self.price_df['pb_lf'].iloc[-1] if 'pb_lf' in self.price_df.columns else 0
|
|||
|
|
|
|||
|
|
# 估值评价
|
|||
|
|
if pe_ttm <= self.PE_THRESHOLDS['undervalued'] and pb_lf <= self.PB_THRESHOLDS['undervalued']:
|
|||
|
|
valuation_comment = "估值处于历史低位,具备较高安全边际"
|
|||
|
|
elif pe_ttm <= self.PE_THRESHOLDS['fair']:
|
|||
|
|
valuation_comment = "估值处于合理区间"
|
|||
|
|
elif pe_ttm <= self.PE_THRESHOLDS['overvalued']:
|
|||
|
|
valuation_comment = "估值略高,需关注业绩成长性"
|
|||
|
|
else:
|
|||
|
|
valuation_comment = "估值偏高,注意追高风险"
|
|||
|
|
|
|||
|
|
# 基本面总结
|
|||
|
|
if roe >= self.ROE_THRESHOLDS['good'] and pe_ttm <= self.PE_THRESHOLDS['fair']:
|
|||
|
|
fundamental_summary = "公司基本面健康,盈利能力强,估值合理,具备投资价值。"
|
|||
|
|
elif roe >= self.ROE_THRESHOLDS['good']:
|
|||
|
|
fundamental_summary = "公司盈利能力较强,但估值偏高,建议等待回调机会。"
|
|||
|
|
else:
|
|||
|
|
fundamental_summary = "公司基本面一般,建议谨慎观望。"
|
|||
|
|
|
|||
|
|
return FundamentalAnalysis(
|
|||
|
|
roe=round(roe, 2) if roe else 0,
|
|||
|
|
roe_comment=roe_comment,
|
|||
|
|
pe_ttm=round(pe_ttm, 2) if pe_ttm else 0,
|
|||
|
|
pb_lf=round(pb_lf, 2) if pb_lf else 0,
|
|||
|
|
valuation_comment=valuation_comment,
|
|||
|
|
fundamental_summary=fundamental_summary
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def generate_recommendation(self,
|
|||
|
|
current_price: float,
|
|||
|
|
fundamental: FundamentalAnalysis = None,
|
|||
|
|
technical: TechnicalAnalysis = None) -> InvestmentRecommendation:
|
|||
|
|
"""
|
|||
|
|
生成投资建议
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
current_price: 当前价格
|
|||
|
|
fundamental: 基本面分析结果
|
|||
|
|
technical: 技术面分析结果
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
InvestmentRecommendation对象
|
|||
|
|
"""
|
|||
|
|
if fundamental is None:
|
|||
|
|
fundamental = self.analyze_fundamental()
|
|||
|
|
if technical is None:
|
|||
|
|
technical = self.analyze_technical()
|
|||
|
|
|
|||
|
|
# 综合评分
|
|||
|
|
score = 0
|
|||
|
|
|
|||
|
|
# 基本面评分
|
|||
|
|
if fundamental.roe >= self.ROE_THRESHOLDS['excellent']:
|
|||
|
|
score += 3
|
|||
|
|
elif fundamental.roe >= self.ROE_THRESHOLDS['good']:
|
|||
|
|
score += 2
|
|||
|
|
elif fundamental.roe >= self.ROE_THRESHOLDS['average']:
|
|||
|
|
score += 1
|
|||
|
|
|
|||
|
|
# 估值评分
|
|||
|
|
if fundamental.pe_ttm <= self.PE_THRESHOLDS['undervalued']:
|
|||
|
|
score += 3
|
|||
|
|
elif fundamental.pe_ttm <= self.PE_THRESHOLDS['fair']:
|
|||
|
|
score += 2
|
|||
|
|
elif fundamental.pe_ttm <= self.PE_THRESHOLDS['overvalued']:
|
|||
|
|
score += 1
|
|||
|
|
|
|||
|
|
# 技术面评分
|
|||
|
|
if technical.trend in ["强势上涨", "震荡上行"]:
|
|||
|
|
score += 2
|
|||
|
|
elif technical.trend == "震荡整理":
|
|||
|
|
score += 1
|
|||
|
|
|
|||
|
|
# 风险等级
|
|||
|
|
if technical.volatility > 50:
|
|||
|
|
risk_level = RiskLevel.HIGH
|
|||
|
|
elif technical.volatility > 35:
|
|||
|
|
risk_level = RiskLevel.MEDIUM_HIGH
|
|||
|
|
elif technical.volatility > 25:
|
|||
|
|
risk_level = RiskLevel.MEDIUM
|
|||
|
|
elif technical.volatility > 15:
|
|||
|
|
risk_level = RiskLevel.LOW_MEDIUM
|
|||
|
|
else:
|
|||
|
|
risk_level = RiskLevel.LOW
|
|||
|
|
|
|||
|
|
# 目标价格计算(基于PE和支撑阻力)
|
|||
|
|
target_price_pe = current_price * (self.PE_THRESHOLDS['fair'] / max(fundamental.pe_ttm, 1))
|
|||
|
|
target_price_resistance = technical.resistance_level * 1.1
|
|||
|
|
target_price = round((target_price_pe + target_price_resistance) / 2, 2)
|
|||
|
|
|
|||
|
|
# 投资建议
|
|||
|
|
key_factors = []
|
|||
|
|
|
|||
|
|
if score >= 6:
|
|||
|
|
rating = InvestmentRating.BUY
|
|||
|
|
recommendation_text = f"综合基本面和技术面分析,该股投资价值较高。目标价{target_price}元,建议逢低买入。"
|
|||
|
|
key_factors.append("盈利能力强劲,ROE表现优异")
|
|||
|
|
elif score >= 4:
|
|||
|
|
rating = InvestmentRating.ACCUMULATE
|
|||
|
|
recommendation_text = f"公司基本面良好,建议关注回调机会逐步建仓。目标价{target_price}元。"
|
|||
|
|
key_factors.append("基本面稳健,估值合理")
|
|||
|
|
elif score >= 2:
|
|||
|
|
rating = InvestmentRating.HOLD
|
|||
|
|
recommendation_text = "公司基本面一般,当前估值合理,建议持有观望。"
|
|||
|
|
key_factors.append("基本面中性,估值合理")
|
|||
|
|
else:
|
|||
|
|
rating = InvestmentRating.REDUCE
|
|||
|
|
recommendation_text = "公司基本面较弱或估值偏高,建议谨慎或减仓。"
|
|||
|
|
key_factors.append("基本面一般或估值偏高")
|
|||
|
|
|
|||
|
|
if technical.trend in ["强势上涨", "震荡上行"]:
|
|||
|
|
key_factors.append("技术面呈上升趋势")
|
|||
|
|
elif technical.trend in ["强势下跌", "震荡下行"]:
|
|||
|
|
key_factors.append("技术面呈下降趋势")
|
|||
|
|
|
|||
|
|
if fundamental.pe_ttm <= self.PE_THRESHOLDS['undervalued']:
|
|||
|
|
key_factors.append("估值处于历史低位")
|
|||
|
|
|
|||
|
|
return InvestmentRecommendation(
|
|||
|
|
rating=rating,
|
|||
|
|
target_price=target_price,
|
|||
|
|
risk_level=risk_level,
|
|||
|
|
recommendation_text=recommendation_text,
|
|||
|
|
key_factors=key_factors
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def generate_core_summary(self,
|
|||
|
|
fundamental: FundamentalAnalysis = None,
|
|||
|
|
technical: TechnicalAnalysis = None,
|
|||
|
|
recommendation: InvestmentRecommendation = None) -> List[str]:
|
|||
|
|
"""
|
|||
|
|
生成核心提要要点
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
要点列表
|
|||
|
|
"""
|
|||
|
|
if fundamental is None:
|
|||
|
|
fundamental = self.analyze_fundamental()
|
|||
|
|
if technical is None:
|
|||
|
|
technical = self.analyze_technical()
|
|||
|
|
if recommendation is None:
|
|||
|
|
# 获取最新价格
|
|||
|
|
latest_price = self.price_df['close'].iloc[-1] if 'close' in self.price_df.columns else 0
|
|||
|
|
recommendation = self.generate_recommendation(latest_price, fundamental, technical)
|
|||
|
|
|
|||
|
|
summary_points = []
|
|||
|
|
|
|||
|
|
# ROE要点
|
|||
|
|
if fundamental.roe > 0:
|
|||
|
|
summary_points.append(
|
|||
|
|
f"该公司最新净资产收益率(ROE)为{fundamental.roe}%,{fundamental.roe_comment}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 估值要点
|
|||
|
|
if fundamental.pe_ttm > 0:
|
|||
|
|
summary_points.append(
|
|||
|
|
f"当前市盈率(TTM)为{fundamental.pe_ttm}倍,{fundamental.valuation_comment}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 技术面要点
|
|||
|
|
summary_points.append(
|
|||
|
|
f"技术面显示股价呈现{technical.trend}趋势,{technical.technical_summary}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 投资建议要点
|
|||
|
|
summary_points.append(
|
|||
|
|
f"给予\"{recommendation.rating.value}\"评级,目标价格{recommendation.target_price}元,风险等级为{recommendation.risk_level.value}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return summary_points
|
|||
|
|
|
|||
|
|
def get_price_statistics(self) -> Dict:
|
|||
|
|
"""
|
|||
|
|
获取价格统计数据
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
统计数据字典
|
|||
|
|
"""
|
|||
|
|
df = self.price_df
|
|||
|
|
|
|||
|
|
if len(df) == 0:
|
|||
|
|
return {}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
'latest_price': round(df['close'].iloc[-1], 2),
|
|||
|
|
'highest_price': round(df['high'].max(), 2),
|
|||
|
|
'lowest_price': round(df['low'].min(), 2),
|
|||
|
|
'avg_price': round(df['close'].mean(), 2),
|
|||
|
|
'total_change': round(((df['close'].iloc[-1] / df['close'].iloc[0]) - 1) * 100, 2),
|
|||
|
|
'max_daily_gain': round(df['pct_chg'].max(), 2) if 'pct_chg' in df.columns else 0,
|
|||
|
|
'max_daily_loss': round(df['pct_chg'].min(), 2) if 'pct_chg' in df.columns else 0,
|
|||
|
|
'volatility': round(df['close'].pct_change().std() * np.sqrt(52) * 100, 2)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def perform_comprehensive_analysis(price_df: pd.DataFrame,
|
|||
|
|
financial_data: Dict,
|
|||
|
|
current_price: float = None) -> Dict:
|
|||
|
|
"""
|
|||
|
|
执行综合分析并返回所有结果
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
price_df: 价格数据
|
|||
|
|
financial_data: 财务数据
|
|||
|
|
current_price: 当前价格(可选)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
包含所有分析结果的字典
|
|||
|
|
"""
|
|||
|
|
engine = AnalysisEngine(price_df, financial_data)
|
|||
|
|
|
|||
|
|
if current_price is None:
|
|||
|
|
current_price = price_df['close'].iloc[-1]
|
|||
|
|
|
|||
|
|
technical = engine.analyze_technical()
|
|||
|
|
fundamental = engine.analyze_fundamental()
|
|||
|
|
recommendation = engine.generate_recommendation(current_price, fundamental, technical)
|
|||
|
|
summary_points = engine.generate_core_summary(fundamental, technical, recommendation)
|
|||
|
|
price_stats = engine.get_price_statistics()
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
'technical': technical,
|
|||
|
|
'fundamental': fundamental,
|
|||
|
|
'recommendation': recommendation,
|
|||
|
|
'summary_points': summary_points,
|
|||
|
|
'price_stats': price_stats
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
# 测试代码
|
|||
|
|
import numpy as np
|
|||
|
|
|
|||
|
|
# 创建模拟数据
|
|||
|
|
dates = pd.date_range(start='2025-01-01', periods=60, freq='W')
|
|||
|
|
np.random.seed(42)
|
|||
|
|
prices = 30 + np.cumsum(np.random.randn(60) * 0.5)
|
|||
|
|
|
|||
|
|
price_df = pd.DataFrame({
|
|||
|
|
'date': dates,
|
|||
|
|
'open': prices * 0.98,
|
|||
|
|
'high': prices * 1.03,
|
|||
|
|
'low': prices * 0.97,
|
|||
|
|
'close': prices,
|
|||
|
|
'volume': np.random.randint(1000000, 5000000, 60),
|
|||
|
|
'pe_ttm': 25 + np.random.randn(60) * 2,
|
|||
|
|
'pb_lf': 3 + np.random.randn(60) * 0.2
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
financial_data = {
|
|||
|
|
'roe_wgt': 16.5,
|
|||
|
|
'pe_ttm': 25.3,
|
|||
|
|
'pb_lf': 3.1,
|
|||
|
|
'sec_name': '测试公司'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 执行分析
|
|||
|
|
results = perform_comprehensive_analysis(price_df, financial_data)
|
|||
|
|
|
|||
|
|
print("=== 技术分析 ===")
|
|||
|
|
print(f"趋势: {results['technical'].trend}")
|
|||
|
|
print(f"支撑位: {results['technical'].support_level}")
|
|||
|
|
print(f"阻力位: {results['technical'].resistance_level}")
|
|||
|
|
|
|||
|
|
print("\n=== 基本面分析 ===")
|
|||
|
|
print(f"ROE: {results['fundamental'].roe}%")
|
|||
|
|
print(f"PE TTM: {results['fundamental'].pe_ttm}")
|
|||
|
|
print(f"PB LF: {results['fundamental'].pb_lf}")
|
|||
|
|
|
|||
|
|
print("\n=== 投资建议 ===")
|
|||
|
|
print(f"评级: {results['recommendation'].rating.value}")
|
|||
|
|
print(f"目标价: {results['recommendation'].target_price}元")
|
|||
|
|
print(f"风险等级: {results['recommendation'].risk_level.value}")
|
|||
|
|
|
|||
|
|
print("\n=== 核心提要 ===")
|
|||
|
|
for i, point in enumerate(results['summary_points'], 1):
|
|||
|
|
print(f"{i}. {point}")
|