Files
aiagents-stock/quarterly_report_data.py
songzhuoyuan befdc32aea tushare
2026-08-11 20:41:31 +08:00

587 lines
24 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
季报数据获取模块
使用akshare获取个股最近8期季度财务报告
"""
import pandas as pd
import sys
import io
import warnings
from datetime import datetime
import akshare as ak
from http_timeout import call_with_timeout
from data_source_manager import data_source_manager
warnings.filterwarnings('ignore')
# 设置标准输出编码为UTF-8(仅在命令行环境,避免streamlit冲突)
def _setup_stdout_encoding():
"""仅在命令行环境设置标准输出编码"""
if sys.platform == 'win32' and not hasattr(sys.stdout, '_original_stream'):
try:
# 检测是否在streamlit环境中
import streamlit
# 在streamlit中不修改stdout
return
except ImportError:
# 不在streamlit环境,可以安全修改
try:
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='ignore')
except:
pass
_setup_stdout_encoding()
class QuarterlyReportDataFetcher:
"""季报数据获取类(使用akshare数据源)"""
def __init__(self):
self.periods = 8 # 获取最近8期季报
self.available = True
print("✓ 季报数据获取器初始化成功(akshare数据源)")
def get_quarterly_reports(self, symbol):
"""
获取股票的季报数据
Args:
symbol: 股票代码(6位数字)
Returns:
dict: 包含季报数据的字典
"""
data = {
"symbol": symbol,
"income_statement": None, # 利润表
"balance_sheet": None, # 资产负债表
"cash_flow": None, # 现金流量表
"financial_indicators": None, # 财务指标
"data_success": False,
"source": "akshare"
}
# 只支持中国股票
if not self._is_chinese_stock(symbol):
data["error"] = "季报数据仅支持中国A股股票"
return data
try:
print(f"📊 正在获取 {symbol} 的季报数据...")
# 获取利润表(优先tushare,失败时回退akshare
income_data = self._get_income_statement_from_tushare(symbol)
if income_data is None:
income_data = self._get_income_statement(symbol)
if income_data:
data["income_statement"] = income_data
print(f" ✓ 成功获取 {len(income_data.get('data', []))} 期利润表数据")
# 获取资产负债表(优先tushare,失败时回退akshare
balance_data = self._get_balance_sheet_from_tushare(symbol)
if balance_data is None:
balance_data = self._get_balance_sheet(symbol)
if balance_data:
data["balance_sheet"] = balance_data
print(f" ✓ 成功获取 {len(balance_data.get('data', []))} 期资产负债表数据")
# 获取现金流量表(优先tushare,失败时回退akshare
cash_flow_data = self._get_cash_flow_from_tushare(symbol)
if cash_flow_data is None:
cash_flow_data = self._get_cash_flow(symbol)
if cash_flow_data:
data["cash_flow"] = cash_flow_data
print(f" ✓ 成功获取 {len(cash_flow_data.get('data', []))} 期现金流量表数据")
# 获取财务指标(优先tushare,失败时回退akshare
indicators_data = self._get_financial_indicators_from_tushare(symbol)
if indicators_data is None:
indicators_data = self._get_financial_indicators(symbol)
if indicators_data:
data["financial_indicators"] = indicators_data
print(f" ✓ 成功获取 {len(indicators_data.get('data', []))} 期财务指标数据")
# 如果至少有一个成功,则标记为成功
if income_data or balance_data or cash_flow_data or indicators_data:
data["data_success"] = True
print("✅ 季报数据获取完成")
else:
print("⚠️ 未能获取到季报数据")
except Exception as e:
print(f"❌ 获取季报数据失败: {e}")
data["error"] = str(e)
return data
def _is_chinese_stock(self, symbol):
"""判断是否为中国股票"""
return symbol.isdigit() and len(symbol) == 6
def _convert_ts_records(self, df, field_map, periods):
"""将tushare返回的财务表转换为统一的记录结构"""
try:
data_list = []
for _, row in df.head(periods).iterrows():
item = {}
for ch_name, ts_name in field_map.items():
if ts_name in df.columns:
value = row.get(ts_name)
if value is None or (isinstance(value, float) and pd.isna(value)):
continue
try:
item[ch_name] = str(value)
except:
item[ch_name] = "N/A"
if item:
data_list.append(item)
return {
"data": data_list,
"periods": len(data_list),
"columns": list(field_map.keys()),
"query_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
except Exception as e:
print(f" 转换tushare财务数据异常: {e}")
return None
def _get_income_statement_from_tushare(self, symbol):
"""从tushare获取利润表(优先数据源)"""
try:
if not data_source_manager.tushare_available:
return None
ts_code = data_source_manager._convert_to_ts_code(symbol)
df = data_source_manager.tushare_api.income(ts_code=ts_code)
if df is None or df.empty:
return None
df = df.drop_duplicates(subset=['end_date']).sort_values('end_date', ascending=False)
field_map = {
'报告期': 'end_date',
'营业总收入': 'total_revenue',
'营业收入': 'revenue',
'营业总成本': 'total_operate_cost',
'营业利润': 'operate_profit',
'利润总额': 'total_profit',
'净利润': 'n_income',
'归属于母公司所有者的净利润': 'n_income_attr_p',
'基本每股收益': 'basic_eps',
'稀释每股收益': 'diluted_eps',
'销售费用': 'sell_exp',
'管理费用': 'admin_exp',
'财务费用': 'fin_exp',
'研发费用': 'rd_exp',
}
result = self._convert_ts_records(df, field_map, self.periods)
if result and result.get('periods'):
print(f" ✓ tushare成功获取 {result['periods']} 期利润表数据")
return result
except Exception as e:
print(f" tushare获取利润表异常: {e}")
return None
def _get_balance_sheet_from_tushare(self, symbol):
"""从tushare获取资产负债表(优先数据源)"""
try:
if not data_source_manager.tushare_available:
return None
ts_code = data_source_manager._convert_to_ts_code(symbol)
df = data_source_manager.tushare_api.balancesheet(ts_code=ts_code)
if df is None or df.empty:
return None
df = df.drop_duplicates(subset=['end_date']).sort_values('end_date', ascending=False)
field_map = {
'报告期': 'end_date',
'资产总计': 'total_assets',
'流动资产合计': 'total_cur_assets',
'非流动资产合计': 'total_ncur_assets',
'负债合计': 'total_liab',
'流动负债合计': 'total_cur_liab',
'非流动负债合计': 'total_ncur_liab',
'所有者权益合计': 'total_hldr_eqy_inc_min_int',
'归属于母公司股东权益合计': 'total_hldr_eqy_exc_min_int',
}
result = self._convert_ts_records(df, field_map, self.periods)
if result and result.get('periods'):
print(f" ✓ tushare成功获取 {result['periods']} 期资产负债表数据")
return result
except Exception as e:
print(f" tushare获取资产负债表异常: {e}")
return None
def _get_cash_flow_from_tushare(self, symbol):
"""从tushare获取现金流量表(优先数据源)"""
try:
if not data_source_manager.tushare_available:
return None
ts_code = data_source_manager._convert_to_ts_code(symbol)
df = data_source_manager.tushare_api.cashflow(ts_code=ts_code)
if df is None or df.empty:
return None
df = df.drop_duplicates(subset=['end_date']).sort_values('end_date', ascending=False)
field_map = {
'报告期': 'end_date',
'经营活动产生的现金流量净额': 'n_cashflow_act',
'投资活动产生的现金流量净额': 'n_cashflow_inv_act',
'筹资活动产生的现金流量净额': 'n_cash_flows_fnc_act',
}
result = self._convert_ts_records(df, field_map, self.periods)
if result and result.get('periods'):
print(f" ✓ tushare成功获取 {result['periods']} 期现金流量表数据")
return result
except Exception as e:
print(f" tushare获取现金流量表异常: {e}")
return None
def _get_financial_indicators_from_tushare(self, symbol):
"""从tushare获取财务指标(优先数据源)"""
try:
if not data_source_manager.tushare_available:
return None
ts_code = data_source_manager._convert_to_ts_code(symbol)
df = data_source_manager.tushare_api.fina_indicator(ts_code=ts_code)
if df is None or df.empty:
return None
df = df.drop_duplicates(subset=['end_date']).sort_values('end_date', ascending=False)
field_map = {
'报告期': 'end_date',
'净资产收益率': 'roe',
'总资产净利率': 'roa',
'销售净利率': 'netprofit_margin',
'销售毛利率': 'grossprofit_margin',
'资产负债率': 'debt_to_assets',
'流动比率': 'current_ratio',
'速动比率': 'quick_ratio',
'应收账款周转率': 'ar_turnover',
'存货周转率': 'inventory_turnover',
'总资产周转率': 'assets_turnover',
'每股收益': 'eps',
'每股净资产': 'bps',
'每股经营现金流': 'cfps',
}
result = self._convert_ts_records(df, field_map, self.periods)
if result and result.get('periods'):
print(f" ✓ tushare成功获取 {result['periods']} 期财务指标数据")
return result
except Exception as e:
print(f" tushare获取财务指标异常: {e}")
return None
def _get_income_statement(self, symbol):
"""获取利润表数据"""
try:
# stock_financial_report_sina - 新浪财经季度利润表
df = call_with_timeout(ak.stock_financial_report_sina, timeout=25,
stock=symbol, symbol="利润表")
if df is None or df.empty:
print(f" 未找到利润表数据")
return None
# 获取最近8期
df = df.head(self.periods)
# 转换为字典列表
data_list = []
for idx, row in df.iterrows():
item = {}
for col in df.columns:
value = row.get(col)
if value is None or (isinstance(value, float) and pd.isna(value)):
continue
try:
item[col] = str(value)
except:
item[col] = "N/A"
if item:
data_list.append(item)
return {
"data": data_list,
"periods": len(data_list),
"columns": df.columns.tolist(),
"query_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
except Exception as e:
print(f" 获取利润表异常: {e}")
return None
def _get_balance_sheet(self, symbol):
"""获取资产负债表数据"""
try:
# stock_financial_report_sina - 新浪财经季度资产负债表
df = call_with_timeout(ak.stock_financial_report_sina, timeout=25,
stock=symbol, symbol="资产负债表")
if df is None or df.empty:
print(f" 未找到资产负债表数据")
return None
# 获取最近8期
df = df.head(self.periods)
# 转换为字典列表
data_list = []
for idx, row in df.iterrows():
item = {}
for col in df.columns:
value = row.get(col)
if value is None or (isinstance(value, float) and pd.isna(value)):
continue
try:
item[col] = str(value)
except:
item[col] = "N/A"
if item:
data_list.append(item)
return {
"data": data_list,
"periods": len(data_list),
"columns": df.columns.tolist(),
"query_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
except Exception as e:
print(f" 获取资产负债表异常: {e}")
return None
def _get_cash_flow(self, symbol):
"""获取现金流量表数据"""
try:
# stock_financial_report_sina - 新浪财经季度现金流量表
df = call_with_timeout(ak.stock_financial_report_sina, timeout=25,
stock=symbol, symbol="现金流量表")
if df is None or df.empty:
print(f" 未找到现金流量表数据")
return None
# 获取最近8期
df = df.head(self.periods)
# 转换为字典列表
data_list = []
for idx, row in df.iterrows():
item = {}
for col in df.columns:
value = row.get(col)
if value is None or (isinstance(value, float) and pd.isna(value)):
continue
try:
item[col] = str(value)
except:
item[col] = "N/A"
if item:
data_list.append(item)
return {
"data": data_list,
"periods": len(data_list),
"columns": df.columns.tolist(),
"query_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
except Exception as e:
print(f" 获取现金流量表异常: {e}")
return None
def _get_financial_indicators(self, symbol):
"""获取财务指标数据"""
try:
# 使用stock_financial_abstract替代已失效的stock_financial_analysis_indicator
df = call_with_timeout(ak.stock_financial_abstract, timeout=25, symbol=symbol)
if df is None or df.empty:
print(f" 未找到财务指标数据")
return None
# 获取最近8期
df = df.head(self.periods * 2) # 取更多数据以确保有足够的季度数据
# 提取关键财务指标
key_indicators = [
'净资产收益率(ROE)', '总资产报酬率(ROA)', '销售净利率', '销售毛利率',
'资产负债率', '流动比率', '速动比率', '应收账款周转率', '存货周转率',
'总资产周转率', '基本每股收益', '每股净资产', '每股现金流'
]
# 筛选出包含关键指标的行
indicator_rows = df[df['指标'].isin(key_indicators)]
if indicator_rows.empty:
print(f" 未找到关键财务指标数据")
return None
# 获取日期列(排除'选项'和'指标'列)
date_columns = [col for col in df.columns if col not in ['选项', '指标']]
# 转换为字典列表,每个字典代表一个时期的财务指标
data_list = []
for date_col in date_columns[:self.periods]: # 只取最近的periods期
item = {'报告期': date_col}
for _, row in indicator_rows.iterrows():
indicator_name = row['指标']
value = row.get(date_col)
if value is not None and not (isinstance(value, float) and pd.isna(value)):
try:
# 尝试转换为字符串
item[indicator_name] = str(value)
except:
item[indicator_name] = "N/A"
else:
item[indicator_name] = "N/A"
data_list.append(item)
return {
"data": data_list,
"periods": len(data_list),
"columns": ['报告期'] + key_indicators,
"query_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
except Exception as e:
print(f" 获取财务指标异常: {e}")
return None
def format_quarterly_reports_for_ai(self, data):
"""
将季报数据格式化为适合AI阅读的文本
"""
if not data or not data.get("data_success"):
return "未能获取季报数据"
text_parts = []
text_parts.append(f"""
【季度财务报告数据 - tushare/akshare自动切换】
股票代码:{data.get('symbol', 'N/A')}
数据期数:最近{self.periods}期季报
""")
# 利润表数据
if data.get("income_statement"):
income_data = data["income_statement"]
text_parts.append(f"""
═══════════════════════════════════════
📊 利润表(最近{income_data.get('periods', 0)}期)
═══════════════════════════════════════
""")
# 提取关键指标
key_fields = ['报告期', '营业总收入', '营业收入', '营业总成本', '营业利润',
'利润总额', '净利润', '归属于母公司所有者的净利润',
'基本每股收益', '稀释每股收益']
for idx, item in enumerate(income_data.get('data', []), 1):
text_parts.append(f"\n{idx} 期:")
for field in key_fields:
if field in item:
text_parts.append(f" {field}: {item[field]}")
# 显示其他重要字段(如果有)
other_fields = ['销售费用', '管理费用', '财务费用', '研发费用']
for field in other_fields:
if field in item:
text_parts.append(f" {field}: {item[field]}")
# 资产负债表数据
if data.get("balance_sheet"):
balance_data = data["balance_sheet"]
text_parts.append(f"""
═══════════════════════════════════════
📊 资产负债表(最近{balance_data.get('periods', 0)}期)
═══════════════════════════════════════
""")
# 提取关键指标
key_fields = ['报告期', '资产总计', '流动资产合计', '非流动资产合计',
'负债合计', '流动负债合计', '非流动负债合计',
'所有者权益合计', '归属于母公司股东权益合计']
for idx, item in enumerate(balance_data.get('data', []), 1):
text_parts.append(f"\n{idx} 期:")
for field in key_fields:
if field in item:
text_parts.append(f" {field}: {item[field]}")
# 现金流量表数据
if data.get("cash_flow"):
cash_flow_data = data["cash_flow"]
text_parts.append(f"""
═══════════════════════════════════════
📊 现金流量表(最近{cash_flow_data.get('periods', 0)}期)
═══════════════════════════════════════
""")
# 提取关键指标
key_fields = ['报告期', '经营活动产生的现金流量净额',
'投资活动产生的现金流量净额', '筹资活动产生的现金流量净额',
'现金及现金等价物净增加额', '期末现金及现金等价物余额']
for idx, item in enumerate(cash_flow_data.get('data', []), 1):
text_parts.append(f"\n{idx} 期:")
for field in key_fields:
if field in item:
text_parts.append(f" {field}: {item[field]}")
# 财务指标数据
if data.get("financial_indicators"):
indicators_data = data["financial_indicators"]
text_parts.append(f"""
═══════════════════════════════════════
📊 关键财务指标(最近{indicators_data.get('periods', 0)}期)
═══════════════════════════════════════
""")
# 提取关键指标
key_fields = ['报告期', '净资产收益率', '总资产净利率', '销售净利率',
'销售毛利率', '资产负债率', '流动比率', '速动比率',
'应收账款周转率', '存货周转率', '总资产周转率',
'每股收益', '每股净资产', '每股经营现金流']
for idx, item in enumerate(indicators_data.get('data', []), 1):
text_parts.append(f"\n{idx} 期:")
for field in key_fields:
if field in item:
text_parts.append(f" {field}: {item[field]}")
return "\n".join(text_parts)
# 测试函数
if __name__ == "__main__":
print("测试季报数据获取(akshare数据源)...")
print("="*60)
fetcher = QuarterlyReportDataFetcher()
if not fetcher.available:
print("❌ 季报数据获取器不可用")
sys.exit(1)
# 测试股票
test_symbols = ["000001", "600519"] # 平安银行、贵州茅台
for symbol in test_symbols:
print(f"\n{'='*60}")
print(f"正在测试股票: {symbol}")
print(f"{'='*60}\n")
data = fetcher.get_quarterly_reports(symbol)
if data.get("data_success"):
print("\n" + "="*60)
print("季报数据获取成功!")
print("="*60)
formatted_text = fetcher.format_quarterly_reports_for_ai(data)
print(formatted_text)
else:
print(f"\n获取失败: {data.get('error', '未知错误')}")
print("\n")