import pandas as pd import numpy as np try: from .config import TS_TOKEN, START_DATE, END_DATE, PRECISION_CONFIG from .stock_utils import * except (ImportError, SystemError): from config import TS_TOKEN, START_DATE, END_DATE, PRECISION_CONFIG from stock_utils import * from .data_source import get_tushare_pro class FinanceData: def __init__(self, token=None): """ 初始化 Tushare 接口 :param token: 可选,不再使用(保留兼容) """ self.pro = get_tushare_pro() self.ts_code = None self.fin_date = None self.unit_factor = 100000000 # 单位换算因子 (元 -> 亿) def get_finance_data(self): """ 获取完整的财务数据并计算指标 :param ts_code: 股票代码 (如 '002273.SZ') :param fin_date: 财报日期 (如 '20220331') :return: 包含财务指标的字典 """ # 获取各类财务数据 (返回 DataFrame) balance = self.get_balance() pre_balance = self.get_pre_balance() income = self.get_income() cash = self.get_cashflow() balance = balance.where(balance.notna(), 0) # NaN替换为0 pre_balance = pre_balance.where(pre_balance.notna(), 0) # NaN替换为0 income = income.where(income.notna(), 0) # NaN替换为0 cash = cash.where(cash.notna(), 0) # NaN替换为0 # 初始化结果字典 data = { 'ts_code': self.ts_code, 'period': self.fin_date, '--运营数据--': '' } # 运营数据计算 cash_equ = cash['c_cash_equ_end_period'].iloc[0] inventories = balance['inventories'].iloc[0] total_assets = balance['total_assets'].iloc[0] # 应收账票 = 应收账款 + 应收票据 accounts_receiv = float(balance['accounts_receiv'].iloc[0]) + float(balance['notes_receiv'].iloc[0]) prepayment = float(balance['prepayment'].iloc[0]) data.update({ '现金额-亿': cash_equ / self.unit_factor, '现金占比率': cash_equ / total_assets, '存货-亿': inventories / self.unit_factor, '存货占比率': inventories / total_assets, '应收账票-亿': accounts_receiv / self.unit_factor, '应收账票占比率': accounts_receiv / total_assets, '预付款-亿': prepayment / self.unit_factor, '预付款占比率': prepayment / total_assets, '运营占比率': (cash_equ + inventories + accounts_receiv + prepayment) / total_assets }) # 资产分布计算 fix_assets = float(balance['fix_assets'].iloc[0]) if 'fix_assets' in balance else 0 intan_assets = float(balance['intan_assets'].iloc[0]) if 'intan_assets' in balance else 0 lt_eqt_invest = float(balance['lt_eqt_invest'].iloc[0]) if 'lt_eqt_invest' in balance else 0 data.update({ '--资产分布--': '', '固定资产-亿': fix_assets / self.unit_factor, '固定资产占比率': fix_assets / total_assets, '无形资产-亿': intan_assets / self.unit_factor, '无形资产占率': intan_assets / total_assets, '股权投资-亿': lt_eqt_invest / self.unit_factor, '股权投资占比率': lt_eqt_invest / total_assets, '投资占比率': (fix_assets + intan_assets + lt_eqt_invest) / total_assets }) # 负债分布计算 acct_payable = float(balance['acct_payable'].iloc[0]) if 'acct_payable' in balance else 0 notes_payable = float(balance['notes_payable'].iloc[0]) if 'notes_payable' in balance else 0 adv_receipts = float(balance['adv_receipts'].iloc[0]) if 'adv_receipts' in balance else 0 st_borr = float(balance['st_borr'].iloc[0]) if 'st_borr' in balance else 0 lt_borr = float(balance['lt_borr'].iloc[0]) if 'lt_borr' in balance else 0 bond_payable = float(balance['bond_payable'].iloc[0]) if 'bond_payable' in balance else 0 biz_liab = acct_payable + notes_payable + adv_receipts fin_liab = st_borr + lt_borr + bond_payable zcfz = (float(balance['total_cur_liab'].iloc[0]) + float(balance['total_ncl'].iloc[0])) / total_assets data.update({ '--负债分布--': '', '经营负债-亿': biz_liab / self.unit_factor, '经营负债占比率': biz_liab / total_assets, '金融负债-亿': fin_liab / self.unit_factor, '金融负债占比率': fin_liab / total_assets, '资产负债率': zcfz }) # 运营能力计算 total_days = self.get_total_days() oper_cost = income['oper_cost'].iloc[0] revenue = income['revenue'].iloc[0] # 存货周转天数 (防除零) #avg_inventories = (float(pre_balance.get('inventories', 0)) + inventories) / 2 avg_inventories = (float(pre_balance['inventories'].iloc[0]) + inventories) / 2 days_1 = total_days / (oper_cost / avg_inventories) if avg_inventories > 0 else 0 # 应收周转天数 (防除零) avg_receiv = (float(pre_balance['accounts_receiv'].iloc[0]) + accounts_receiv) / 2 days_2 = total_days / (revenue / avg_receiv) if avg_receiv > 0 else 0 data.update({ '--运营能力--': '', '存货周转天数': days_1, '应收周转天数': days_2, '营业周期': days_1 + days_2 }) # 管理费分布计算 gross_profit = revenue - oper_cost gross_margin = gross_profit / revenue if revenue > 0 else 0 data.update({ '--管理费分布--': '', '毛利额': gross_profit / self.unit_factor, '毛利率': gross_margin, '营业税金率': float(income['biz_tax_surchg'].iloc[0]) / float(revenue) if revenue > 0 else 0, '销售费用率': float(income['sell_exp'].iloc[0]) / float(revenue) if revenue > 0 else 0, '研发费用率': float(income['rd_exp'].iloc[0]) / float(revenue) if revenue > 0 else 0, '管理费用率': float(income['admin_exp'].iloc[0]) / float(revenue) if revenue > 0 else 0, '净利润': float(income['n_income'].iloc[0]) / self.unit_factor, '净利润率': float(income['n_income'].iloc[0]) / float(revenue) if revenue > 0 else 0 }) # 权益及回报率计算 data.update({ '--权益及回报率--': '', '总资产-亿': total_assets / self.unit_factor, '销售收入-亿': revenue / self.unit_factor, '总资产周转率': revenue / total_assets if total_assets > 0 else 0, '总资产回报率': float(income['n_income'].iloc[0]) / total_assets if total_assets > 0 else 0, '权益乘数': 1 / (1 - zcfz) if zcfz < 1 else 0, }) # 计算ROE (净资产回报率) roa = data['总资产回报率'] data['净资产回报率'] = roa * data['权益乘数'] # 格式化比率数据 for key in list(data.keys()): if isinstance(data[key], float): if key.endswith('率'): data[key] = f"{data[key] * 100:.2f}%" else: data[key] = round(data[key], 2) return pd.Series(data) def get_balance(self): """获取资产负债表数据""" fields = [ 'ts_code', 'end_date', 'total_assets', 'fix_assets', 'intan_assets', 'lt_eqt_invest', 'inventories', 'accounts_receiv', 'notes_receiv', 'prepayment', 'acct_payable', 'notes_payable', 'adv_receipts', 'st_borr', 'lt_borr', 'bond_payable', 'total_cur_liab', 'total_ncl' ] return self.pro.balancesheet( ts_code=self.ts_code, period=self.fin_date, fields=fields ) def get_pre_balance(self): """获取上年度资产负债表数据""" pre_date = f"{int(self.fin_date[:4]) - 1}1231" # 上年末日期 fields = ['inventories', 'accounts_receiv', 'notes_receiv'] return self.pro.balancesheet( ts_code=self.ts_code, period=pre_date, fields=fields ) # 直接返回Series def get_income(self): """获取利润表数据""" fields = [ 'revenue', 'oper_cost', 'biz_tax_surchg', 'sell_exp', 'fin_exp', 'admin_exp', 'n_income', 'rd_exp' ] return self.pro.income( ts_code=self.ts_code, period=self.fin_date, fields=fields ) def get_cashflow(self): """获取现金流量表数据""" #c_cash_equ_end_period 期末现金及现金等价物余额 #end_bal_cash 现金的期末余额 fields = ['c_cash_equ_end_period', 'end_bal_cash'] return self.pro.cashflow( ts_code=self.ts_code, period=self.fin_date, fields=fields ) def get_total_days(self): """根据财报类型返回计算周转率的天数""" quarter = self.fin_date[4:6] return { '03': 90, # Q1 '06': 180, # H1 '09': 270, # Q1-Q3 '12': 360 # 全年 }.get(quarter, 360) #循环调用类 def get_finance_data_range( ts_code, start_date=START_DATE, end_date=END_DATE,TS_TOKEN=TS_TOKEN): """ 获取指定日期范围内的所有财报数据 :param ts_code: 股票代码 :param start_date: 开始日期 (yyyyMMdd) :param end_date: 结束日期 (yyyyMMdd) :return: 合并后的财报数据列表 """ results = pd.DataFrame() # 生成所有可能的财报日期 (季度末) years = range(int(start_date[:4]), int(end_date[:4]) + 1) report_dates = [] for year in years: report_dates.extend([ f"{year}0331", # Q1 f"{year}0630", # Q2 f"{year}0930", # Q3 f"{year}1231" # Q4 ]) # 筛选在指定日期范围内的财报日期 report_dates = [ date for date in report_dates if start_date <= date <= end_date ] report_dates=get_released_report_dates(start_date,end_date) # 按日期顺序获取财报数据 analyzer = FinanceData(TS_TOKEN) analyzer.ts_code=tscodeCheck(ts_code) for fin_date in sorted(report_dates): analyzer.fin_date = fin_date try: data = analyzer.get_finance_data() results = pd.concat([pd.DataFrame(results), data.to_frame().T], ignore_index=True) if len(results) > 0 else data.to_frame().T except Exception as e: print(f"获取 {ts_code} {fin_date} 财报数据失败: {str(e)}") continue return results # 使用示例 if __name__ == "__main__": # 导入当站目录的config文件 try: # 尝试相对导入(作为包的一部分) from .config import TS_TOKEN, START_DATE, END_DATE, PRECISION_CONFIG except (ImportError, SystemError): # 失败则使用绝对导入(直接运行脚本) from config import TS_TOKEN, START_DATE, END_DATE, PRECISION_CONFIG #token = "your_tushare_token" # 替换为实际token #analyzer = FinanceData(TS_TOKEN) # 获取002273.SZ在2022Q1的财务数据 #analyzer.ts_code = '300316.SZ' #analyzer.fin_date = '20240630' #result = analyzer.get_finance_data() result=get_finance_data_range("300316",start_date='20200101',end_date='2025060') print(result) #print(pd.Series(result))