547 lines
23 KiB
Python
547 lines
23 KiB
Python
"""
|
||
财务数据处理服务
|
||
专门处理财务数据的获取、格式化、分析和展示
|
||
"""
|
||
|
||
from typing import Dict, Any, List, Optional, Tuple
|
||
import asyncio
|
||
import logging
|
||
from datetime import datetime, timedelta
|
||
|
||
from ..schemas.data import FinancialDataResponse, MarketDataResponse
|
||
from ..core.exceptions import DataSourceError, ValidationError
|
||
from .data_fetcher import DataFetcher
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class FinancialDataProcessor:
|
||
"""财务数据处理器"""
|
||
|
||
def __init__(self, data_fetcher: DataFetcher):
|
||
self.data_fetcher = data_fetcher
|
||
self.retry_count = 3
|
||
self.retry_delay = 2 # 秒
|
||
|
||
async def process_financial_data(self, symbol: str, market: str) -> Dict[str, Any]:
|
||
"""处理财务数据的主入口"""
|
||
try:
|
||
# 并行获取财务数据和市场数据
|
||
financial_data, market_data = await asyncio.gather(
|
||
self._fetch_financial_data_with_retry(symbol, market),
|
||
self._fetch_market_data_with_retry(symbol, market),
|
||
return_exceptions=True
|
||
)
|
||
|
||
# 检查是否有异常
|
||
if isinstance(financial_data, Exception):
|
||
logger.error(f"获取财务数据失败: {str(financial_data)}")
|
||
raise financial_data
|
||
|
||
if isinstance(market_data, Exception):
|
||
logger.error(f"获取市场数据失败: {str(market_data)}")
|
||
# 市场数据失败不影响财务数据处理,使用空数据
|
||
market_data = self._create_empty_market_data(symbol, market)
|
||
|
||
# 验证数据完整性
|
||
self._validate_financial_data(financial_data)
|
||
|
||
# 格式化数据
|
||
formatted_data = self._format_financial_data(financial_data, market_data)
|
||
|
||
# 计算财务比率和指标
|
||
calculated_metrics = self._calculate_financial_metrics(financial_data, market_data)
|
||
|
||
# 生成数据质量报告
|
||
quality_report = self._generate_quality_report(financial_data, market_data)
|
||
|
||
return {
|
||
"raw_data": {
|
||
"financial_data": financial_data.dict(),
|
||
"market_data": market_data.dict()
|
||
},
|
||
"formatted_tables": formatted_data,
|
||
"calculated_metrics": calculated_metrics,
|
||
"quality_report": quality_report,
|
||
"processing_timestamp": datetime.utcnow().isoformat()
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"财务数据处理失败: {symbol} ({market}) - {str(e)}")
|
||
raise DataSourceError(f"财务数据处理失败: {str(e)}")
|
||
|
||
async def _fetch_financial_data_with_retry(self, symbol: str, market: str) -> FinancialDataResponse:
|
||
"""带重试机制的财务数据获取"""
|
||
last_exception = None
|
||
|
||
for attempt in range(self.retry_count):
|
||
try:
|
||
logger.info(f"获取财务数据 (尝试 {attempt + 1}/{self.retry_count}): {symbol} ({market})")
|
||
return await self.data_fetcher.fetch_financial_data(symbol, market)
|
||
|
||
except Exception as e:
|
||
last_exception = e
|
||
logger.warning(f"财务数据获取失败 (尝试 {attempt + 1}): {str(e)}")
|
||
|
||
if attempt < self.retry_count - 1:
|
||
await asyncio.sleep(self.retry_delay * (attempt + 1)) # 递增延迟
|
||
continue
|
||
else:
|
||
break
|
||
|
||
raise DataSourceError(f"财务数据获取失败,已重试 {self.retry_count} 次: {str(last_exception)}")
|
||
|
||
async def _fetch_market_data_with_retry(self, symbol: str, market: str) -> MarketDataResponse:
|
||
"""带重试机制的市场数据获取"""
|
||
last_exception = None
|
||
|
||
for attempt in range(self.retry_count):
|
||
try:
|
||
logger.info(f"获取市场数据 (尝试 {attempt + 1}/{self.retry_count}): {symbol} ({market})")
|
||
return await self.data_fetcher.fetch_market_data(symbol, market)
|
||
|
||
except Exception as e:
|
||
last_exception = e
|
||
logger.warning(f"市场数据获取失败 (尝试 {attempt + 1}): {str(e)}")
|
||
|
||
if attempt < self.retry_count - 1:
|
||
await asyncio.sleep(self.retry_delay * (attempt + 1))
|
||
continue
|
||
else:
|
||
break
|
||
|
||
raise DataSourceError(f"市场数据获取失败,已重试 {self.retry_count} 次: {str(last_exception)}")
|
||
|
||
def _create_empty_market_data(self, symbol: str, market: str) -> MarketDataResponse:
|
||
"""创建空的市场数据响应"""
|
||
return MarketDataResponse(
|
||
symbol=symbol,
|
||
market=market,
|
||
data_source="fallback",
|
||
last_updated=datetime.utcnow(),
|
||
price_data={},
|
||
volume_data={},
|
||
technical_indicators={}
|
||
)
|
||
|
||
def _validate_financial_data(self, financial_data: FinancialDataResponse):
|
||
"""验证财务数据完整性"""
|
||
if not financial_data:
|
||
raise ValidationError("财务数据为空")
|
||
|
||
# 检查必要的数据字段
|
||
required_fields = ["balance_sheet", "income_statement", "cash_flow"]
|
||
missing_fields = []
|
||
|
||
for field in required_fields:
|
||
data = getattr(financial_data, field, None)
|
||
if not data or not isinstance(data, dict) or not data:
|
||
missing_fields.append(field)
|
||
|
||
if missing_fields:
|
||
logger.warning(f"缺少财务数据字段: {missing_fields}")
|
||
# 不抛出异常,只记录警告,允许部分数据处理
|
||
|
||
def _format_financial_data(self, financial_data: FinancialDataResponse, market_data: MarketDataResponse) -> List[Dict[str, Any]]:
|
||
"""格式化财务数据为前端表格格式"""
|
||
tables = []
|
||
|
||
try:
|
||
# 资产负债表
|
||
if financial_data.balance_sheet:
|
||
balance_sheet_table = self._create_balance_sheet_table(financial_data.balance_sheet)
|
||
tables.append(balance_sheet_table)
|
||
|
||
# 利润表
|
||
if financial_data.income_statement:
|
||
income_statement_table = self._create_income_statement_table(financial_data.income_statement)
|
||
tables.append(income_statement_table)
|
||
|
||
# 现金流量表
|
||
if financial_data.cash_flow:
|
||
cash_flow_table = self._create_cash_flow_table(financial_data.cash_flow)
|
||
tables.append(cash_flow_table)
|
||
|
||
# 关键指标表
|
||
if financial_data.key_metrics or market_data.price_data:
|
||
key_metrics_table = self._create_key_metrics_table(
|
||
financial_data.key_metrics or {},
|
||
market_data.price_data or {}
|
||
)
|
||
tables.append(key_metrics_table)
|
||
|
||
except Exception as e:
|
||
logger.error(f"财务数据格式化失败: {str(e)}")
|
||
# 返回空表格而不是抛出异常
|
||
|
||
return tables
|
||
|
||
def _create_balance_sheet_table(self, balance_sheet_data: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""创建资产负债表"""
|
||
total_assets = balance_sheet_data.get("total_assets", 0)
|
||
|
||
return {
|
||
"title": "资产负债表",
|
||
"headers": ["项目", "金额(万元)", "占总资产比例"],
|
||
"rows": [
|
||
[
|
||
{"label": "总资产", "value": "总资产"},
|
||
{"label": "", "value": self._format_currency(total_assets)},
|
||
{"label": "", "value": "100.00%"}
|
||
],
|
||
[
|
||
{"label": "货币资金", "value": "货币资金"},
|
||
{"label": "", "value": self._format_currency(balance_sheet_data.get("monetary_cap", 0))},
|
||
{"label": "", "value": self._calculate_percentage(
|
||
balance_sheet_data.get("monetary_cap", 0), total_assets
|
||
)}
|
||
],
|
||
[
|
||
{"label": "应收账款", "value": "应收账款"},
|
||
{"label": "", "value": self._format_currency(balance_sheet_data.get("accounts_receiv", 0))},
|
||
{"label": "", "value": self._calculate_percentage(
|
||
balance_sheet_data.get("accounts_receiv", 0), total_assets
|
||
)}
|
||
],
|
||
[
|
||
{"label": "存货", "value": "存货"},
|
||
{"label": "", "value": self._format_currency(balance_sheet_data.get("inventories", 0))},
|
||
{"label": "", "value": self._calculate_percentage(
|
||
balance_sheet_data.get("inventories", 0), total_assets
|
||
)}
|
||
],
|
||
[
|
||
{"label": "总负债", "value": "总负债"},
|
||
{"label": "", "value": self._format_currency(balance_sheet_data.get("total_liab", 0))},
|
||
{"label": "", "value": self._calculate_percentage(
|
||
balance_sheet_data.get("total_liab", 0), total_assets
|
||
)}
|
||
],
|
||
[
|
||
{"label": "股东权益", "value": "股东权益"},
|
||
{"label": "", "value": self._format_currency(balance_sheet_data.get("total_hldr_eqy_exc_min_int", 0))},
|
||
{"label": "", "value": self._calculate_percentage(
|
||
balance_sheet_data.get("total_hldr_eqy_exc_min_int", 0), total_assets
|
||
)}
|
||
]
|
||
]
|
||
}
|
||
|
||
def _create_income_statement_table(self, income_data: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""创建利润表"""
|
||
revenue = income_data.get("revenue", 0)
|
||
|
||
return {
|
||
"title": "利润表",
|
||
"headers": ["项目", "金额(万元)", "占营收比例"],
|
||
"rows": [
|
||
[
|
||
{"label": "营业收入", "value": "营业收入"},
|
||
{"label": "", "value": self._format_currency(revenue)},
|
||
{"label": "", "value": "100.00%"}
|
||
],
|
||
[
|
||
{"label": "营业利润", "value": "营业利润"},
|
||
{"label": "", "value": self._format_currency(income_data.get("operate_profit", 0))},
|
||
{"label": "", "value": self._calculate_percentage(
|
||
income_data.get("operate_profit", 0), revenue
|
||
)}
|
||
],
|
||
[
|
||
{"label": "利润总额", "value": "利润总额"},
|
||
{"label": "", "value": self._format_currency(income_data.get("total_profit", 0))},
|
||
{"label": "", "value": self._calculate_percentage(
|
||
income_data.get("total_profit", 0), revenue
|
||
)}
|
||
],
|
||
[
|
||
{"label": "净利润", "value": "净利润"},
|
||
{"label": "", "value": self._format_currency(income_data.get("n_income", 0))},
|
||
{"label": "", "value": self._calculate_percentage(
|
||
income_data.get("n_income", 0), revenue
|
||
)}
|
||
],
|
||
[
|
||
{"label": "归母净利润", "value": "归母净利润"},
|
||
{"label": "", "value": self._format_currency(income_data.get("n_income_attr_p", 0))},
|
||
{"label": "", "value": self._calculate_percentage(
|
||
income_data.get("n_income_attr_p", 0), revenue
|
||
)}
|
||
],
|
||
[
|
||
{"label": "基本每股收益", "value": "基本每股收益"},
|
||
{"label": "", "value": f"{income_data.get('basic_eps', 0):.3f}元"},
|
||
{"label": "", "value": "-"}
|
||
]
|
||
]
|
||
}
|
||
|
||
def _create_cash_flow_table(self, cash_flow_data: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""创建现金流量表"""
|
||
return {
|
||
"title": "现金流量表",
|
||
"headers": ["项目", "金额(万元)", "现金流状况"],
|
||
"rows": [
|
||
[
|
||
{"label": "经营活动现金流", "value": "经营活动现金流"},
|
||
{"label": "", "value": self._format_currency(cash_flow_data.get("n_cashflow_act", 0))},
|
||
{"label": "", "value": self._evaluate_cash_flow(cash_flow_data.get("n_cashflow_act", 0))}
|
||
],
|
||
[
|
||
{"label": "投资活动现金流", "value": "投资活动现金流"},
|
||
{"label": "", "value": self._format_currency(cash_flow_data.get("n_cashflow_inv_act", 0))},
|
||
{"label": "", "value": self._evaluate_cash_flow(cash_flow_data.get("n_cashflow_inv_act", 0))}
|
||
],
|
||
[
|
||
{"label": "筹资活动现金流", "value": "筹资活动现金流"},
|
||
{"label": "", "value": self._format_currency(cash_flow_data.get("n_cashflow_fin_act", 0))},
|
||
{"label": "", "value": self._evaluate_cash_flow(cash_flow_data.get("n_cashflow_fin_act", 0))}
|
||
],
|
||
[
|
||
{"label": "期末现金余额", "value": "期末现金余额"},
|
||
{"label": "", "value": self._format_currency(cash_flow_data.get("c_cash_equ_end_period", 0))},
|
||
{"label": "", "value": "-"}
|
||
]
|
||
]
|
||
}
|
||
|
||
def _create_key_metrics_table(self, key_metrics: Dict[str, Any], price_data: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""创建关键财务指标表"""
|
||
return {
|
||
"title": "关键财务指标",
|
||
"headers": ["指标", "数值", "评价"],
|
||
"rows": [
|
||
[
|
||
{"label": "市盈率(PE)", "value": "市盈率(PE)"},
|
||
{"label": "", "value": f"{key_metrics.get('pe', 0):.2f}"},
|
||
{"label": "", "value": self._evaluate_pe_ratio(key_metrics.get('pe', 0))}
|
||
],
|
||
[
|
||
{"label": "市净率(PB)", "value": "市净率(PB)"},
|
||
{"label": "", "value": f"{key_metrics.get('pb', 0):.2f}"},
|
||
{"label": "", "value": self._evaluate_pb_ratio(key_metrics.get('pb', 0))}
|
||
],
|
||
[
|
||
{"label": "净资产收益率(ROE)", "value": "净资产收益率(ROE)"},
|
||
{"label": "", "value": f"{key_metrics.get('roe', 0):.2f}%"},
|
||
{"label": "", "value": self._evaluate_roe(key_metrics.get('roe', 0))}
|
||
],
|
||
[
|
||
{"label": "总资产收益率(ROA)", "value": "总资产收益率(ROA)"},
|
||
{"label": "", "value": f"{key_metrics.get('roa', 0):.2f}%"},
|
||
{"label": "", "value": self._evaluate_roa(key_metrics.get('roa', 0))}
|
||
],
|
||
[
|
||
{"label": "毛利率", "value": "毛利率"},
|
||
{"label": "", "value": f"{key_metrics.get('gross_margin', 0):.2f}%"},
|
||
{"label": "", "value": self._evaluate_gross_margin(key_metrics.get('gross_margin', 0))}
|
||
],
|
||
[
|
||
{"label": "资产负债率", "value": "资产负债率"},
|
||
{"label": "", "value": f"{key_metrics.get('debt_to_assets', 0):.2f}%"},
|
||
{"label": "", "value": self._evaluate_debt_ratio(key_metrics.get('debt_to_assets', 0))}
|
||
]
|
||
]
|
||
}
|
||
|
||
def _calculate_financial_metrics(self, financial_data: FinancialDataResponse, market_data: MarketDataResponse) -> Dict[str, Any]:
|
||
"""计算额外的财务指标"""
|
||
try:
|
||
balance_sheet = financial_data.balance_sheet or {}
|
||
income_statement = financial_data.income_statement or {}
|
||
cash_flow = financial_data.cash_flow or {}
|
||
price_data = market_data.price_data or {}
|
||
|
||
# 计算流动比率
|
||
current_assets = balance_sheet.get("monetary_cap", 0) + balance_sheet.get("accounts_receiv", 0) + balance_sheet.get("inventories", 0)
|
||
current_liabilities = balance_sheet.get("total_liab", 1) * 0.6 # 估算流动负债为总负债的60%
|
||
current_ratio = current_assets / current_liabilities if current_liabilities > 0 else 0
|
||
|
||
# 计算净利润率
|
||
revenue = income_statement.get("revenue", 1)
|
||
net_income = income_statement.get("n_income_attr_p", 0)
|
||
net_margin = (net_income / revenue * 100) if revenue > 0 else 0
|
||
|
||
# 计算现金流覆盖率
|
||
operating_cash_flow = cash_flow.get("n_cashflow_act", 0)
|
||
cash_coverage_ratio = (operating_cash_flow / net_income) if net_income > 0 else 0
|
||
|
||
return {
|
||
"liquidity_ratios": {
|
||
"current_ratio": round(current_ratio, 2),
|
||
"quick_ratio": round(current_ratio * 0.8, 2) # 简化计算
|
||
},
|
||
"profitability_ratios": {
|
||
"net_margin": round(net_margin, 2),
|
||
"operating_margin": round((income_statement.get("operate_profit", 0) / revenue * 100) if revenue > 0 else 0, 2)
|
||
},
|
||
"efficiency_ratios": {
|
||
"asset_turnover": round((revenue / balance_sheet.get("total_assets", 1)) if balance_sheet.get("total_assets", 0) > 0 else 0, 2),
|
||
"inventory_turnover": round((revenue / balance_sheet.get("inventories", 1)) if balance_sheet.get("inventories", 0) > 0 else 0, 2)
|
||
},
|
||
"cash_flow_ratios": {
|
||
"cash_coverage_ratio": round(cash_coverage_ratio, 2),
|
||
"free_cash_flow": operating_cash_flow + cash_flow.get("n_cashflow_inv_act", 0)
|
||
}
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"财务指标计算失败: {str(e)}")
|
||
return {}
|
||
|
||
def _generate_quality_report(self, financial_data: FinancialDataResponse, market_data: MarketDataResponse) -> Dict[str, Any]:
|
||
"""生成数据质量报告"""
|
||
quality_checks = []
|
||
overall_score = 0
|
||
total_checks = 0
|
||
|
||
# 检查财务数据完整性
|
||
data_completeness = {
|
||
"balance_sheet": bool(financial_data.balance_sheet),
|
||
"income_statement": bool(financial_data.income_statement),
|
||
"cash_flow": bool(financial_data.cash_flow),
|
||
"key_metrics": bool(financial_data.key_metrics)
|
||
}
|
||
|
||
for check_name, is_complete in data_completeness.items():
|
||
quality_checks.append({
|
||
"check_name": check_name,
|
||
"status": "pass" if is_complete else "fail",
|
||
"message": "数据完整" if is_complete else "数据缺失"
|
||
})
|
||
if is_complete:
|
||
overall_score += 1
|
||
total_checks += 1
|
||
|
||
# 检查市场数据
|
||
market_data_complete = bool(market_data.price_data)
|
||
quality_checks.append({
|
||
"check_name": "market_data",
|
||
"status": "pass" if market_data_complete else "fail",
|
||
"message": "市场数据完整" if market_data_complete else "市场数据缺失"
|
||
})
|
||
if market_data_complete:
|
||
overall_score += 1
|
||
total_checks += 1
|
||
|
||
# 计算质量等级
|
||
quality_ratio = overall_score / total_checks if total_checks > 0 else 0
|
||
|
||
if quality_ratio >= 0.9:
|
||
quality_grade = "优秀"
|
||
elif quality_ratio >= 0.7:
|
||
quality_grade = "良好"
|
||
elif quality_ratio >= 0.5:
|
||
quality_grade = "一般"
|
||
else:
|
||
quality_grade = "较差"
|
||
|
||
return {
|
||
"overall_score": overall_score,
|
||
"total_checks": total_checks,
|
||
"quality_ratio": round(quality_ratio, 2),
|
||
"quality_grade": quality_grade,
|
||
"checks": quality_checks,
|
||
"data_source": financial_data.data_source,
|
||
"last_updated": financial_data.last_updated.isoformat()
|
||
}
|
||
|
||
def _format_currency(self, value: float) -> str:
|
||
"""格式化货币数值"""
|
||
if value == 0:
|
||
return "0.00"
|
||
|
||
# 转换为万元
|
||
value_wan = value / 10000
|
||
|
||
if abs(value_wan) >= 10000:
|
||
# 大于1亿,显示为亿元
|
||
return f"{value_wan / 10000:.2f}亿"
|
||
elif abs(value_wan) >= 1:
|
||
return f"{value_wan:.2f}万"
|
||
else:
|
||
return f"{value:.2f}"
|
||
|
||
def _calculate_percentage(self, numerator: float, denominator: float) -> str:
|
||
"""计算百分比"""
|
||
if denominator == 0:
|
||
return "0.00%"
|
||
|
||
percentage = (numerator / denominator) * 100
|
||
return f"{percentage:.2f}%"
|
||
|
||
def _evaluate_cash_flow(self, cash_flow: float) -> str:
|
||
"""评估现金流状况"""
|
||
if cash_flow > 0:
|
||
return "流入"
|
||
elif cash_flow < 0:
|
||
return "流出"
|
||
else:
|
||
return "平衡"
|
||
|
||
def _evaluate_pe_ratio(self, pe: float) -> str:
|
||
"""评估市盈率"""
|
||
if pe <= 0:
|
||
return "亏损"
|
||
elif pe < 15:
|
||
return "低估"
|
||
elif pe < 25:
|
||
return "合理"
|
||
elif pe < 40:
|
||
return "偏高"
|
||
else:
|
||
return "高估"
|
||
|
||
def _evaluate_pb_ratio(self, pb: float) -> str:
|
||
"""评估市净率"""
|
||
if pb < 1:
|
||
return "破净"
|
||
elif pb < 2:
|
||
return "低估"
|
||
elif pb < 3:
|
||
return "合理"
|
||
else:
|
||
return "偏高"
|
||
|
||
def _evaluate_roe(self, roe: float) -> str:
|
||
"""评估净资产收益率"""
|
||
if roe < 5:
|
||
return "较低"
|
||
elif roe < 15:
|
||
return "一般"
|
||
elif roe < 25:
|
||
return "良好"
|
||
else:
|
||
return "优秀"
|
||
|
||
def _evaluate_roa(self, roa: float) -> str:
|
||
"""评估总资产收益率"""
|
||
if roa < 3:
|
||
return "较低"
|
||
elif roa < 8:
|
||
return "一般"
|
||
elif roa < 15:
|
||
return "良好"
|
||
else:
|
||
return "优秀"
|
||
|
||
def _evaluate_gross_margin(self, margin: float) -> str:
|
||
"""评估毛利率"""
|
||
if margin < 10:
|
||
return "较低"
|
||
elif margin < 30:
|
||
return "一般"
|
||
elif margin < 50:
|
||
return "良好"
|
||
else:
|
||
return "优秀"
|
||
|
||
def _evaluate_debt_ratio(self, debt_ratio: float) -> str:
|
||
"""评估资产负债率"""
|
||
if debt_ratio < 30:
|
||
return "保守"
|
||
elif debt_ratio < 50:
|
||
return "合理"
|
||
elif debt_ratio < 70:
|
||
return "偏高"
|
||
else:
|
||
return "风险" |