Files
simonandClaude Opus 4.7 271a9343a5 Initial commit: cc-cursor 全链路量化研究平台
7 Sprints 全部完成:
  Sprint 0: 基础设施 (DataManager + MariaDB)
  Sprint 1: 因子引擎 (34因子/12分类)
  Sprint 2: VectorBT 回测 (5策略+截面)
  Sprint 3: Optuna 优化 (+Walk-Forward)
  Sprint 4: ML 模型 (LightGBM+CatBoost)
  Sprint 5: Qwen 情绪因子 (三源新闻+日期对齐)
  Sprint 6: Agent 系统 (4Agent+日报.md/.html)

生产加固 (15项): Tushare双源fallback, SSH自动恢复, pool_pre_ping,
  save_daily先删后插, load_dotenv绝对路径, 日报5d/20d修复,
  RiskAgent改上证指数, 昨日对比+数据截止, mac_report utf8mb4,
  CLAUDE-*.md 9条已知Bug, demo全参数化, djapi数据源归一化,
  indexDatas API修正

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 15:59:05 +08:00

288 lines
12 KiB
Python

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))