feat(data): 新增财务指标和涨跌停数据接口
- 财务指标: fina_indicator_vip 封装,166 字段,季度同步 - 涨跌停价格: stk_limit 封装,日频数据同步 - 配套单元测试和调度中心集成
This commit is contained in:
@@ -12,11 +12,13 @@ Available APIs:
|
||||
- api_namechange: Stock name change history (股票曾用名)
|
||||
- api_bak_basic: Stock historical list (股票历史列表)
|
||||
- api_stock_st: ST stock list (ST股票列表)
|
||||
- api_stk_limit: Stock limit price (每日涨跌停价格)
|
||||
|
||||
Example:
|
||||
>>> from src.data.api_wrappers import get_daily, get_stock_basic, get_trade_cal, get_bak_basic
|
||||
>>> from src.data.api_wrappers import get_pro_bar, sync_pro_bar, get_daily_basic, sync_daily_basic
|
||||
>>> from src.data.api_wrappers import get_stock_st, sync_stock_st
|
||||
>>> from src.data.api_wrappers import get_stk_limit, sync_stk_limit
|
||||
>>> data = get_daily('000001.SZ', start_date='20240101', end_date='20240131')
|
||||
>>> pro_data = get_pro_bar('000001.SZ', start_date='20240101', end_date='20240131')
|
||||
>>> daily_basic = get_daily_basic(trade_date='20240101')
|
||||
@@ -24,6 +26,7 @@ Example:
|
||||
>>> calendar = get_trade_cal('20240101', '20240131')
|
||||
>>> bak_basic = get_bak_basic(trade_date='20240101')
|
||||
>>> stock_st = get_stock_st(trade_date='20240101')
|
||||
>>> stk_limit = get_stk_limit(trade_date='20240101')
|
||||
"""
|
||||
|
||||
from src.data.api_wrappers.api_daily import (
|
||||
@@ -58,6 +61,12 @@ from src.data.api_wrappers.api_stock_st import (
|
||||
sync_stock_st,
|
||||
StockSTSync,
|
||||
)
|
||||
from src.data.api_wrappers.api_stk_limit import (
|
||||
get_stk_limit,
|
||||
sync_stk_limit,
|
||||
preview_stk_limit_sync,
|
||||
StkLimitSync,
|
||||
)
|
||||
from src.data.api_wrappers.api_trade_cal import (
|
||||
get_trade_cal,
|
||||
get_trading_days,
|
||||
@@ -107,6 +116,11 @@ __all__ = [
|
||||
"get_stock_st",
|
||||
"sync_stock_st",
|
||||
"StockSTSync",
|
||||
# Stock limit price
|
||||
"get_stk_limit",
|
||||
"sync_stk_limit",
|
||||
"preview_stk_limit_sync",
|
||||
"StkLimitSync",
|
||||
]
|
||||
|
||||
# =============================================================================
|
||||
@@ -179,6 +193,17 @@ try:
|
||||
order=40,
|
||||
)
|
||||
|
||||
# 7. Stock Limit Price - 每日涨跌停价格
|
||||
from src.data.api_wrappers.api_stk_limit import StkLimitSync
|
||||
|
||||
sync_registry.register_class(
|
||||
name="stk_limit",
|
||||
sync_class=StkLimitSync,
|
||||
display_name="每日涨跌停价格",
|
||||
description="股票每日涨跌停价格(涨停价、跌停价)",
|
||||
order=50,
|
||||
)
|
||||
|
||||
except ImportError:
|
||||
# sync_registry 可能不存在(首次导入),忽略
|
||||
pass
|
||||
|
||||
224
src/data/api_wrappers/api_stk_limit.py
Normal file
224
src/data/api_wrappers/api_stk_limit.py
Normal file
@@ -0,0 +1,224 @@
|
||||
"""Stock Limit Price (涨跌停价格) interface.
|
||||
|
||||
Fetch daily limit up/down prices for all stocks from Tushare.
|
||||
This interface retrieves the upper and lower limit prices for stocks,
|
||||
which are typically available around 8:40 AM each trading day.
|
||||
"""
|
||||
|
||||
from typing import override
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from src.data.client import TushareClient
|
||||
from src.data.api_wrappers.base_sync import DateBasedSync
|
||||
|
||||
|
||||
def get_stk_limit(
|
||||
trade_date: str | None = None,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
ts_code: str | None = None,
|
||||
client: TushareClient | None = None,
|
||||
) -> pd.DataFrame:
|
||||
"""Fetch stock limit prices from Tushare.
|
||||
|
||||
This interface retrieves daily limit up/down prices for stocks.
|
||||
Each trading day, limit prices are available around 8:40 AM.
|
||||
Supports fetching all stocks for a single date (preferred for efficiency)
|
||||
or date range data for specific stocks.
|
||||
|
||||
Args:
|
||||
trade_date: Specific trade date (YYYYMMDD format).
|
||||
If provided, fetches all stocks for this date (most efficient).
|
||||
start_date: Start date (YYYYMMDD format).
|
||||
Used with end_date for date range queries.
|
||||
end_date: End date (YYYYMMDD format).
|
||||
Used with start_date for date range queries.
|
||||
ts_code: Stock code filter (optional).
|
||||
e.g., '000001.SZ', '600000.SH'
|
||||
client: Optional TushareClient instance for shared rate limiting.
|
||||
If None, creates a new client. For concurrent sync operations,
|
||||
pass a shared client to ensure proper rate limiting.
|
||||
|
||||
Returns:
|
||||
pd.DataFrame with columns:
|
||||
- trade_date: Trade date (YYYYMMDD)
|
||||
- ts_code: Stock code
|
||||
- pre_close: Previous closing price
|
||||
- up_limit: Upper limit price (涨停价)
|
||||
- down_limit: Lower limit price (跌停价)
|
||||
|
||||
Example:
|
||||
>>> # Get all stocks limit prices for a single date (most efficient)
|
||||
>>> data = get_stk_limit(trade_date='20240625')
|
||||
>>>
|
||||
>>> # Get date range data
|
||||
>>> data = get_stk_limit(start_date='20240101', end_date='20240131')
|
||||
>>>
|
||||
>>> # Get specific stock data
|
||||
>>> data = get_stk_limit(ts_code='000001.SZ', start_date='20240101', end_date='20240131')
|
||||
"""
|
||||
client = client or TushareClient()
|
||||
|
||||
# Build parameters
|
||||
params = {}
|
||||
if trade_date:
|
||||
params["trade_date"] = trade_date
|
||||
if start_date:
|
||||
params["start_date"] = start_date
|
||||
if end_date:
|
||||
params["end_date"] = end_date
|
||||
if ts_code:
|
||||
params["ts_code"] = ts_code
|
||||
|
||||
# Fetch data
|
||||
data = client.query("stk_limit", **params) # type: ignore
|
||||
|
||||
# Rename date column if needed
|
||||
if "date" in data.columns:
|
||||
data = data.rename(columns={"date": "trade_date"})
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class StkLimitSync(DateBasedSync):
|
||||
"""Stock Limit Price data batch sync manager.
|
||||
|
||||
Inherits from DateBasedSync, fetches data by date for all stocks.
|
||||
Each API call retrieves limit prices for all stocks on a specific date.
|
||||
|
||||
Example:
|
||||
>>> sync = StkLimitSync()
|
||||
>>> results = sync.sync_all() # Incremental sync
|
||||
>>> results = sync.sync_all(force_full=True) # Full reload
|
||||
>>> preview = sync.preview_sync() # Preview
|
||||
"""
|
||||
|
||||
table_name: str = "stk_limit"
|
||||
default_start_date: str = "20180101"
|
||||
|
||||
# Table schema definition
|
||||
TABLE_SCHEMA: dict[str, str] = {
|
||||
"ts_code": "VARCHAR(16) NOT NULL",
|
||||
"trade_date": "DATE NOT NULL",
|
||||
"pre_close": "DOUBLE",
|
||||
"up_limit": "DOUBLE",
|
||||
"down_limit": "DOUBLE",
|
||||
}
|
||||
|
||||
# Index definitions
|
||||
TABLE_INDEXES: list[tuple[str, list[str]]] = [
|
||||
("idx_stk_limit_date_code", ["trade_date", "ts_code"]),
|
||||
]
|
||||
|
||||
# Primary key definition
|
||||
PRIMARY_KEY: tuple[str, str] = ("ts_code", "trade_date")
|
||||
|
||||
@override
|
||||
def fetch_single_date(self, trade_date: str) -> pd.DataFrame:
|
||||
"""Fetch limit prices for all stocks on a specific date.
|
||||
|
||||
Args:
|
||||
trade_date: Trading date (YYYYMMDD)
|
||||
|
||||
Returns:
|
||||
DataFrame with limit prices for all stocks on the date
|
||||
"""
|
||||
# Use get_stk_limit to fetch all stocks for a single date
|
||||
data = get_stk_limit(
|
||||
trade_date=trade_date,
|
||||
client=self.client, # Pass shared client for rate limiting
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
def sync_stk_limit(
|
||||
force_full: bool = False,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
dry_run: bool = False,
|
||||
) -> pd.DataFrame:
|
||||
"""Sync stock limit prices to local DuckDB storage.
|
||||
|
||||
This is the main entry point for stock limit price data synchronization.
|
||||
|
||||
Args:
|
||||
force_full: If True, force full reload from default_start_date
|
||||
start_date: Manual start date override (YYYYMMDD)
|
||||
end_date: Manual end date override (defaults to today)
|
||||
dry_run: If True, only preview what would be synced without writing
|
||||
|
||||
Returns:
|
||||
DataFrame with synced data
|
||||
|
||||
Example:
|
||||
>>> # First sync (full load from default_start_date)
|
||||
>>> result = sync_stk_limit()
|
||||
>>>
|
||||
>>> # Subsequent syncs (incremental - only new data)
|
||||
>>> result = sync_stk_limit()
|
||||
>>>
|
||||
>>> # Force full reload
|
||||
>>> result = sync_stk_limit(force_full=True)
|
||||
>>>
|
||||
>>> # Manual date range
|
||||
>>> result = sync_stk_limit(start_date='20240101', end_date='20240131')
|
||||
>>>
|
||||
>>> # Dry run (preview only)
|
||||
>>> result = sync_stk_limit(dry_run=True)
|
||||
"""
|
||||
sync_manager = StkLimitSync()
|
||||
return sync_manager.sync_all(
|
||||
force_full=force_full,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
|
||||
|
||||
def preview_stk_limit_sync(
|
||||
force_full: bool = False,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
sample_size: int = 3,
|
||||
) -> dict[str, object]:
|
||||
"""Preview stock limit price sync data volume and samples.
|
||||
|
||||
This is the recommended way to check what would be synced before
|
||||
actually performing the synchronization.
|
||||
|
||||
Args:
|
||||
force_full: If True, preview full sync from default_start_date
|
||||
start_date: Manual start date override
|
||||
end_date: Manual end date override (defaults to today)
|
||||
sample_size: Number of sample days to fetch for preview (default: 3)
|
||||
|
||||
Returns:
|
||||
Dictionary with preview information:
|
||||
{
|
||||
'sync_needed': bool,
|
||||
'date_count': int,
|
||||
'start_date': str,
|
||||
'end_date': str,
|
||||
'estimated_records': int,
|
||||
'sample_data': pd.DataFrame,
|
||||
'mode': str, # 'full', 'incremental', or 'none'
|
||||
}
|
||||
|
||||
Example:
|
||||
>>> # Preview what would be synced
|
||||
>>> preview = preview_stk_limit_sync()
|
||||
>>>
|
||||
>>> # Preview full sync
|
||||
>>> preview = preview_stk_limit_sync(force_full=True)
|
||||
>>>
|
||||
>>> # Preview with more samples
|
||||
>>> preview = preview_stk_limit_sync(sample_size=5)
|
||||
"""
|
||||
sync_manager = StkLimitSync()
|
||||
return sync_manager.preview_sync(
|
||||
force_full=force_full,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
sample_size=sample_size,
|
||||
)
|
||||
@@ -1324,52 +1324,6 @@ class DateBasedSync(BaseDataSync):
|
||||
probe_desc = f"date={probe_date}, all stocks"
|
||||
self._probe_table_and_cleanup(probe_data, probe_desc)
|
||||
|
||||
# 执行同步
|
||||
combined = self._run_date_range_sync(sync_start, sync_end, dry_run)
|
||||
if self._should_probe_table():
|
||||
print(f"[{class_name}] Table '{self.table_name}' is empty, probing...")
|
||||
# 使用最近一个交易日的完整数据进行探测
|
||||
probe_date = get_last_trading_day(sync_start, sync_end)
|
||||
if probe_date:
|
||||
probe_data = self.fetch_single_date(probe_date)
|
||||
probe_desc = f"date={probe_date}, all stocks"
|
||||
self._probe_table_and_cleanup(probe_data, probe_desc)
|
||||
|
||||
# 执行同步
|
||||
if self._should_probe_table():
|
||||
print(f"[{class_name}] Table '{self.table_name}' is empty, probing...")
|
||||
# 使用最近一个交易日的完整数据进行探测
|
||||
probe_date = get_last_trading_day(sync_start, sync_end)
|
||||
if probe_date:
|
||||
probe_data = self.fetch_single_date(probe_date)
|
||||
probe_desc = f"date={probe_date}, all stocks"
|
||||
self._probe_table_and_cleanup(probe_data, probe_desc)
|
||||
if self._should_probe_table():
|
||||
print(f"[{class_name}] Table '{self.table_name}' is empty, probing...")
|
||||
# 使用最近一个交易日的完整数据进行探测
|
||||
probe_date = get_last_trading_day(sync_start, sync_end)
|
||||
if probe_date:
|
||||
probe_data = self.fetch_single_date(probe_date)
|
||||
probe_desc = f"date={probe_date}, all stocks"
|
||||
self._probe_table_and_cleanup(probe_data, probe_desc)
|
||||
|
||||
# 执行同步
|
||||
storage = Storage()
|
||||
if not storage.exists(self.table_name):
|
||||
print(
|
||||
f"[{class_name}] Table '{self.table_name}' doesn't exist, creating..."
|
||||
)
|
||||
# 获取样本数据以推断 schema
|
||||
sample = self.fetch_single_date(sync_end)
|
||||
if sample.empty:
|
||||
# 尝试另一个日期
|
||||
sample = self.fetch_single_date("20240102")
|
||||
if not sample.empty:
|
||||
self._ensure_table_schema(sample)
|
||||
else:
|
||||
print(f"[{class_name}] Cannot create table: no sample data available")
|
||||
return pd.DataFrame()
|
||||
|
||||
# 执行同步
|
||||
combined = self._run_date_range_sync(sync_start, sync_end, dry_run)
|
||||
|
||||
|
||||
394
src/data/api_wrappers/financial_data/api_fina_indicator.py
Normal file
394
src/data/api_wrappers/financial_data/api_fina_indicator.py
Normal file
@@ -0,0 +1,394 @@
|
||||
"""财务指标数据接口 (VIP 版本)
|
||||
|
||||
使用 Tushare VIP 接口 (fina_indicator_vip) 获取财务指标数据。
|
||||
按季度同步,一次请求获取一个季度的全部上市公司数据。
|
||||
|
||||
接口说明:
|
||||
- fina_indicator_vip: 获取某一季度全部上市公司财务指标数据
|
||||
- 需要 5000 积分才能调用
|
||||
- period 参数为报告期(季度最后一天,如 20231231)
|
||||
- 每次请求最多返回 100 条记录(需多次请求获取更多数据)
|
||||
|
||||
使用方式:
|
||||
# 同步财务指标数据
|
||||
from src.data.api_wrappers.financial_data.api_fina_indicator import (
|
||||
FinaIndicatorQuarterSync,
|
||||
sync_fina_indicator
|
||||
)
|
||||
|
||||
# 方式1: 使用类
|
||||
syncer = FinaIndicatorQuarterSync()
|
||||
syncer.sync_incremental() # 增量同步
|
||||
syncer.sync_full() # 全量同步
|
||||
|
||||
# 方式2: 使用便捷函数
|
||||
sync_fina_indicator() # 增量同步
|
||||
sync_fina_indicator(force_full=True) # 全量同步
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
import pandas as pd
|
||||
|
||||
from src.data.client import TushareClient
|
||||
from src.data.api_wrappers.base_financial_sync import (
|
||||
QuarterBasedSync,
|
||||
sync_financial_data,
|
||||
preview_financial_sync,
|
||||
)
|
||||
|
||||
|
||||
class FinaIndicatorQuarterSync(QuarterBasedSync):
|
||||
"""财务指标季度同步实现。
|
||||
|
||||
使用 fina_indicator_vip 接口按季度获取全部上市公司财务指标数据。
|
||||
|
||||
表结构: financial_fina_indicator
|
||||
主键: (ts_code, end_date)
|
||||
"""
|
||||
|
||||
table_name = "financial_fina_indicator"
|
||||
api_name = "fina_indicator_vip"
|
||||
|
||||
# 目标报表类型:默认只同步合并报表(财务指标接口无需过滤 report_type)
|
||||
TARGET_REPORT_TYPE = None
|
||||
|
||||
# 表结构定义 - 完整的财务指标字段
|
||||
TABLE_SCHEMA = {
|
||||
# 基础字段
|
||||
"ts_code": "VARCHAR(16) NOT NULL",
|
||||
"ann_date": "DATE",
|
||||
"end_date": "DATE NOT NULL",
|
||||
# 每股收益指标
|
||||
"eps": "DOUBLE",
|
||||
"dt_eps": "DOUBLE",
|
||||
"total_revenue_ps": "DOUBLE",
|
||||
"revenue_ps": "DOUBLE",
|
||||
"capital_rese_ps": "DOUBLE",
|
||||
"surplus_rese_ps": "DOUBLE",
|
||||
"undist_profit_ps": "DOUBLE",
|
||||
"extra_item": "DOUBLE",
|
||||
"profit_dedt": "DOUBLE",
|
||||
"gross_margin": "DOUBLE",
|
||||
# 偿债能力指标
|
||||
"current_ratio": "DOUBLE",
|
||||
"quick_ratio": "DOUBLE",
|
||||
"cash_ratio": "DOUBLE",
|
||||
# 营运能力指标
|
||||
"invturn_days": "DOUBLE",
|
||||
"arturn_days": "DOUBLE",
|
||||
"inv_turn": "DOUBLE",
|
||||
"ar_turn": "DOUBLE",
|
||||
"ca_turn": "DOUBLE",
|
||||
"fa_turn": "DOUBLE",
|
||||
"assets_turn": "DOUBLE",
|
||||
# 盈利能力指标
|
||||
"op_income": "DOUBLE",
|
||||
"valuechange_income": "DOUBLE",
|
||||
"interst_income": "DOUBLE",
|
||||
"daa": "DOUBLE",
|
||||
"ebit": "DOUBLE",
|
||||
"ebitda": "DOUBLE",
|
||||
"fcff": "DOUBLE",
|
||||
"fcfe": "DOUBLE",
|
||||
# 资本结构指标
|
||||
"current_exint": "DOUBLE",
|
||||
"noncurrent_exint": "DOUBLE",
|
||||
"interestdebt": "DOUBLE",
|
||||
"netdebt": "DOUBLE",
|
||||
"tangible_asset": "DOUBLE",
|
||||
"working_capital": "DOUBLE",
|
||||
"networking_capital": "DOUBLE",
|
||||
"invest_capital": "DOUBLE",
|
||||
"retained_earnings": "DOUBLE",
|
||||
# 每股指标
|
||||
"diluted2_eps": "DOUBLE",
|
||||
"bps": "DOUBLE",
|
||||
"ocfps": "DOUBLE",
|
||||
"retainedps": "DOUBLE",
|
||||
"cfps": "DOUBLE",
|
||||
"ebit_ps": "DOUBLE",
|
||||
"fcff_ps": "DOUBLE",
|
||||
"fcfe_ps": "DOUBLE",
|
||||
# 销售能力指标
|
||||
"netprofit_margin": "DOUBLE",
|
||||
"grossprofit_margin": "DOUBLE",
|
||||
"cogs_of_sales": "DOUBLE",
|
||||
"expense_of_sales": "DOUBLE",
|
||||
"profit_to_gr": "DOUBLE",
|
||||
"saleexp_to_gr": "DOUBLE",
|
||||
"adminexp_of_gr": "DOUBLE",
|
||||
"finaexp_of_gr": "DOUBLE",
|
||||
"impai_ttm": "DOUBLE",
|
||||
"gc_of_gr": "DOUBLE",
|
||||
"op_of_gr": "DOUBLE",
|
||||
"ebit_of_gr": "DOUBLE",
|
||||
# 投资回报率指标
|
||||
"roe": "DOUBLE",
|
||||
"roe_waa": "DOUBLE",
|
||||
"roe_dt": "DOUBLE",
|
||||
"roa": "DOUBLE",
|
||||
"npta": "DOUBLE",
|
||||
"roic": "DOUBLE",
|
||||
"roe_yearly": "DOUBLE",
|
||||
"roa2_yearly": "DOUBLE",
|
||||
"roe_avg": "DOUBLE",
|
||||
# 利润结构指标
|
||||
"opincome_of_ebt": "DOUBLE",
|
||||
"investincome_of_ebt": "DOUBLE",
|
||||
"n_op_profit_of_ebt": "DOUBLE",
|
||||
"tax_to_ebt": "DOUBLE",
|
||||
"dtprofit_to_profit": "DOUBLE",
|
||||
# 现金流量指标
|
||||
"salescash_to_or": "DOUBLE",
|
||||
"ocf_to_or": "DOUBLE",
|
||||
"ocf_to_opincome": "DOUBLE",
|
||||
# 资本支出指标
|
||||
"capitalized_to_da": "DOUBLE",
|
||||
# 杠杆与偿债能力指标
|
||||
"debt_to_assets": "DOUBLE",
|
||||
"assets_to_eqt": "DOUBLE",
|
||||
"dp_assets_to_eqt": "DOUBLE",
|
||||
"ca_to_assets": "DOUBLE",
|
||||
"nca_to_assets": "DOUBLE",
|
||||
"tbassets_to_totalassets": "DOUBLE",
|
||||
"int_to_talcap": "DOUBLE",
|
||||
"eqt_to_talcapital": "DOUBLE",
|
||||
"currentdebt_to_debt": "DOUBLE",
|
||||
"longdeb_to_debt": "DOUBLE",
|
||||
"ocf_to_shortdebt": "DOUBLE",
|
||||
"debt_to_eqt": "DOUBLE",
|
||||
"eqt_to_debt": "DOUBLE",
|
||||
"eqt_to_interestdebt": "DOUBLE",
|
||||
"tangibleasset_to_debt": "DOUBLE",
|
||||
"tangasset_to_intdebt": "DOUBLE",
|
||||
"tangibleasset_to_netdebt": "DOUBLE",
|
||||
"ocf_to_debt": "DOUBLE",
|
||||
"ocf_to_interestdebt": "DOUBLE",
|
||||
"ocf_to_netdebt": "DOUBLE",
|
||||
"ebit_to_interest": "DOUBLE",
|
||||
"longdebt_to_workingcapital": "DOUBLE",
|
||||
"ebitda_to_debt": "DOUBLE",
|
||||
# 营运周期指标
|
||||
"turn_days": "DOUBLE",
|
||||
"roa_yearly": "DOUBLE",
|
||||
"roa_dp": "DOUBLE",
|
||||
"fixed_assets": "DOUBLE",
|
||||
# 利润质量指标
|
||||
"profit_prefin_exp": "DOUBLE",
|
||||
"non_op_profit": "DOUBLE",
|
||||
"op_to_ebt": "DOUBLE",
|
||||
"nop_to_ebt": "DOUBLE",
|
||||
"ocf_to_profit": "DOUBLE",
|
||||
# 流动性指标
|
||||
"cash_to_liqdebt": "DOUBLE",
|
||||
"cash_to_liqdebt_withinterest": "DOUBLE",
|
||||
"op_to_liqdebt": "DOUBLE",
|
||||
"op_to_debt": "DOUBLE",
|
||||
"roic_yearly": "DOUBLE",
|
||||
"total_fa_trun": "DOUBLE",
|
||||
"profit_to_op": "DOUBLE",
|
||||
# 单季度指标 (q_*)
|
||||
"q_opincome": "DOUBLE",
|
||||
"q_investincome": "DOUBLE",
|
||||
"q_dtprofit": "DOUBLE",
|
||||
"q_eps": "DOUBLE",
|
||||
"q_netprofit_margin": "DOUBLE",
|
||||
"q_gsprofit_margin": "DOUBLE",
|
||||
"q_exp_to_sales": "DOUBLE",
|
||||
"q_profit_to_gr": "DOUBLE",
|
||||
"q_saleexp_to_gr": "DOUBLE",
|
||||
"q_adminexp_to_gr": "DOUBLE",
|
||||
"q_finaexp_to_gr": "DOUBLE",
|
||||
"q_impair_to_gr_ttm": "DOUBLE",
|
||||
"q_gc_to_gr": "DOUBLE",
|
||||
"q_op_to_gr": "DOUBLE",
|
||||
"q_roe": "DOUBLE",
|
||||
"q_dt_roe": "DOUBLE",
|
||||
"q_npta": "DOUBLE",
|
||||
"q_opincome_to_ebt": "DOUBLE",
|
||||
"q_investincome_to_ebt": "DOUBLE",
|
||||
"q_dtprofit_to_profit": "DOUBLE",
|
||||
"q_salescash_to_or": "DOUBLE",
|
||||
"q_ocf_to_sales": "DOUBLE",
|
||||
"q_ocf_to_or": "DOUBLE",
|
||||
# 同比增长率指标 (*_yoy)
|
||||
"basic_eps_yoy": "DOUBLE",
|
||||
"dt_eps_yoy": "DOUBLE",
|
||||
"cfps_yoy": "DOUBLE",
|
||||
"op_yoy": "DOUBLE",
|
||||
"ebt_yoy": "DOUBLE",
|
||||
"netprofit_yoy": "DOUBLE",
|
||||
"dt_netprofit_yoy": "DOUBLE",
|
||||
"ocf_yoy": "DOUBLE",
|
||||
"roe_yoy": "DOUBLE",
|
||||
"bps_yoy": "DOUBLE",
|
||||
"assets_yoy": "DOUBLE",
|
||||
"eqt_yoy": "DOUBLE",
|
||||
"tr_yoy": "DOUBLE",
|
||||
"or_yoy": "DOUBLE",
|
||||
# 单季度增长指标 (q_*_yoy, q_*_qoq)
|
||||
"q_gr_yoy": "DOUBLE",
|
||||
"q_gr_qoq": "DOUBLE",
|
||||
"q_sales_yoy": "DOUBLE",
|
||||
"q_sales_qoq": "DOUBLE",
|
||||
"q_op_yoy": "DOUBLE",
|
||||
"q_op_qoq": "DOUBLE",
|
||||
"q_profit_yoy": "DOUBLE",
|
||||
"q_profit_qoq": "DOUBLE",
|
||||
"q_netprofit_yoy": "DOUBLE",
|
||||
"q_netprofit_qoq": "DOUBLE",
|
||||
# 其他指标
|
||||
"equity_yoy": "DOUBLE",
|
||||
"rd_exp": "DOUBLE",
|
||||
"update_flag": "VARCHAR(1)",
|
||||
}
|
||||
|
||||
# 索引定义(不要创建唯一索引)
|
||||
# 注意:财务数据可能发生多次修正,不设置主键和唯一索引
|
||||
TABLE_INDEXES = [
|
||||
("idx_financial_fina_indicator_ts_code", ["ts_code"]),
|
||||
("idx_financial_fina_indicator_end_date", ["end_date"]),
|
||||
("idx_financial_fina_indicator_ts_period", ["ts_code", "end_date"]),
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
"""初始化财务指标同步器。"""
|
||||
super().__init__()
|
||||
self._fields = None # 默认返回全部字段
|
||||
|
||||
def fetch_single_quarter(self, period: str) -> pd.DataFrame:
|
||||
"""获取单季度的全部上市公司财务指标数据。
|
||||
|
||||
注意:fina_indicator_vip 接口每次请求最多返回 100 条记录,
|
||||
需要通过 offset 参数循环获取该季度的全部数据。
|
||||
|
||||
Args:
|
||||
period: 报告期,季度最后一天日期(如 '20231231')
|
||||
|
||||
Returns:
|
||||
包含该季度全部上市公司财务指标数据的 DataFrame
|
||||
"""
|
||||
all_data = []
|
||||
offset = 0
|
||||
limit = 100 # API 限制每次最多返回 100 条
|
||||
|
||||
while True:
|
||||
params = {
|
||||
"period": period,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}
|
||||
|
||||
if self._fields:
|
||||
params["fields"] = self._fields
|
||||
|
||||
df = self.client.query(self.api_name, **params)
|
||||
|
||||
if df.empty:
|
||||
break
|
||||
|
||||
all_data.append(df)
|
||||
|
||||
# 如果返回的数据少于 limit,说明已经取完
|
||||
if len(df) < limit:
|
||||
break
|
||||
|
||||
offset += limit
|
||||
|
||||
if not all_data:
|
||||
return pd.DataFrame()
|
||||
|
||||
return pd.concat(all_data, ignore_index=True)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 便捷函数
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def sync_fina_indicator(
|
||||
force_full: bool = False,
|
||||
dry_run: bool = False,
|
||||
) -> list:
|
||||
"""同步财务指标数据(便捷函数)。
|
||||
|
||||
Args:
|
||||
force_full: 若为 True,强制全量同步
|
||||
dry_run: 若为 True,仅预览不写入
|
||||
|
||||
Returns:
|
||||
同步结果列表
|
||||
|
||||
Example:
|
||||
>>> # 增量同步
|
||||
>>> sync_fina_indicator()
|
||||
>>>
|
||||
>>> # 全量同步
|
||||
>>> sync_fina_indicator(force_full=True)
|
||||
>>>
|
||||
>>> # 预览
|
||||
>>> sync_fina_indicator(dry_run=True)
|
||||
"""
|
||||
return sync_financial_data(FinaIndicatorQuarterSync, force_full, dry_run)
|
||||
|
||||
|
||||
def preview_fina_indicator_sync() -> dict:
|
||||
"""预览财务指标同步信息。
|
||||
|
||||
Returns:
|
||||
预览信息字典
|
||||
"""
|
||||
return preview_financial_sync(FinaIndicatorQuarterSync)
|
||||
|
||||
|
||||
def get_fina_indicator(period: str, fields: Optional[str] = None) -> pd.DataFrame:
|
||||
"""获取财务指标数据(原始接口,单季度)。
|
||||
|
||||
用于直接获取某个季度的数据,不进行同步管理。
|
||||
注意:该接口每次最多返回 100 条记录,如需获取全部数据请使用同步功能。
|
||||
|
||||
Args:
|
||||
period: 报告期,季度最后一天日期(如 '20231231')
|
||||
fields: 指定返回字段,默认返回全部字段
|
||||
|
||||
Returns:
|
||||
包含财务指标数据的 DataFrame
|
||||
"""
|
||||
client = TushareClient()
|
||||
|
||||
if fields is None:
|
||||
fields = (
|
||||
"ts_code,ann_date,end_date,eps,dt_eps,total_revenue_ps,revenue_ps,"
|
||||
"capital_rese_ps,surplus_rese_ps,undist_profit_ps,extra_item,profit_dedt,"
|
||||
"gross_margin,current_ratio,quick_ratio,cash_ratio,invturn_days,arturn_days,"
|
||||
"inv_turn,ar_turn,ca_turn,fa_turn,assets_turn,op_income,valuechange_income,"
|
||||
"interst_income,daa,ebit,ebitda,fcff,fcfe,current_exint,noncurrent_exint,"
|
||||
"interestdebt,netdebt,tangible_asset,working_capital,networking_capital,"
|
||||
"invest_capital,retained_earnings,diluted2_eps,bps,ocfps,retainedps,cfps,"
|
||||
"ebit_ps,fcff_ps,fcfe_ps,netprofit_margin,grossprofit_margin,cogs_of_sales,"
|
||||
"expense_of_sales,profit_to_gr,saleexp_to_gr,adminexp_of_gr,finaexp_of_gr,"
|
||||
"impai_ttm,gc_of_gr,op_of_gr,ebit_of_gr,roe,roe_waa,roe_dt,roa,npta,roic,"
|
||||
"roe_yearly,roa2_yearly,roe_avg,opincome_of_ebt,investincome_of_ebt,"
|
||||
"n_op_profit_of_ebt,tax_to_ebt,dtprofit_to_profit,salescash_to_or,"
|
||||
"ocf_to_or,ocf_to_opincome,capitalized_to_da,debt_to_assets,assets_to_eqt,"
|
||||
"dp_assets_to_eqt,ca_to_assets,nca_to_assets,tbassets_to_totalassets,"
|
||||
"int_to_talcap,eqt_to_talcapital,currentdebt_to_debt,longdeb_to_debt,"
|
||||
"ocf_to_shortdebt,debt_to_eqt,eqt_to_debt,eqt_to_interestdebt,"
|
||||
"tangibleasset_to_debt,tangasset_to_intdebt,tangibleasset_to_netdebt,"
|
||||
"ocf_to_debt,ocf_to_interestdebt,ocf_to_netdebt,ebit_to_interest,"
|
||||
"longdebt_to_workingcapital,ebitda_to_debt,turn_days,roa_yearly,roa_dp,"
|
||||
"fixed_assets,profit_prefin_exp,non_op_profit,op_to_ebt,nop_to_ebt,"
|
||||
"ocf_to_profit,cash_to_liqdebt,cash_to_liqdebt_withinterest,op_to_liqdebt,"
|
||||
"op_to_debt,roic_yearly,total_fa_trun,profit_to_op,q_opincome,"
|
||||
"q_investincome,q_dtprofit,q_eps,q_netprofit_margin,q_gsprofit_margin,"
|
||||
"q_exp_to_sales,q_profit_to_gr,q_saleexp_to_gr,q_adminexp_to_gr,"
|
||||
"q_finaexp_to_gr,q_impair_to_gr_ttm,q_gc_to_gr,q_op_to_gr,q_roe,q_dt_roe,"
|
||||
"q_npta,q_opincome_to_ebt,q_investincome_to_ebt,q_dtprofit_to_profit,"
|
||||
"q_salescash_to_or,q_ocf_to_sales,q_ocf_to_or,basic_eps_yoy,dt_eps_yoy,"
|
||||
"cfps_yoy,op_yoy,ebt_yoy,netprofit_yoy,dt_netprofit_yoy,ocf_yoy,roe_yoy,"
|
||||
"bps_yoy,assets_yoy,eqt_yoy,tr_yoy,or_yoy,q_gr_yoy,q_gr_qoq,q_sales_yoy,"
|
||||
"q_sales_qoq,q_op_yoy,q_op_qoq,q_profit_yoy,q_profit_qoq,q_netprofit_yoy,"
|
||||
"q_netprofit_qoq,equity_yoy,rd_exp,update_flag"
|
||||
)
|
||||
|
||||
return client.query("fina_indicator_vip", period=period, fields=fields)
|
||||
@@ -7,6 +7,7 @@
|
||||
- income: 利润表 (已实现)
|
||||
- balance: 资产负债表 (已实现)
|
||||
- cashflow: 现金流量表 (已实现)
|
||||
- fina_indicator: 财务指标 (已实现)
|
||||
|
||||
使用方式:
|
||||
# 同步所有财务数据(增量)
|
||||
@@ -25,12 +26,15 @@
|
||||
# 只同步现金流量表
|
||||
sync_financial(data_types=["cashflow"])
|
||||
|
||||
# 只同步财务指标
|
||||
sync_financial(data_types=["fina_indicator"])
|
||||
|
||||
# 预览同步
|
||||
from src.data.api_wrappers.financial_data.api_financial_sync import preview_sync
|
||||
preview = preview_sync()
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from src.data.api_wrappers.financial_data.api_income import (
|
||||
IncomeQuarterSync,
|
||||
@@ -47,6 +51,11 @@ from src.data.api_wrappers.financial_data.api_cashflow import (
|
||||
sync_cashflow,
|
||||
preview_cashflow_sync,
|
||||
)
|
||||
from src.data.api_wrappers.financial_data.api_fina_indicator import (
|
||||
FinaIndicatorQuarterSync,
|
||||
sync_fina_indicator,
|
||||
preview_fina_indicator_sync,
|
||||
)
|
||||
|
||||
|
||||
# 支持的财务数据类型映射
|
||||
@@ -69,6 +78,12 @@ FINANCIAL_SYNCERS = {
|
||||
"preview_func": preview_cashflow_sync,
|
||||
"display_name": "现金流量表",
|
||||
},
|
||||
"fina_indicator": {
|
||||
"syncer_class": FinaIndicatorQuarterSync,
|
||||
"sync_func": sync_fina_indicator,
|
||||
"preview_func": preview_fina_indicator_sync,
|
||||
"display_name": "财务指标",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +91,7 @@ def sync_financial(
|
||||
data_types: Optional[List[str]] = None,
|
||||
force_full: bool = False,
|
||||
dry_run: bool = False,
|
||||
) -> Dict[str, List]:
|
||||
) -> dict[str, list]:
|
||||
"""同步财务数据(调度函数)。
|
||||
|
||||
根据指定的数据类型,调度对应的同步器执行同步。
|
||||
@@ -157,7 +172,7 @@ def sync_financial(
|
||||
return results
|
||||
|
||||
|
||||
def preview_sync(data_types: Optional[List[str]] = None) -> Dict[str, Dict]:
|
||||
def preview_sync(data_types: Optional[List[str]] = None) -> dict[str, dict]:
|
||||
"""预览财务数据同步信息。
|
||||
|
||||
Args:
|
||||
@@ -189,7 +204,7 @@ def preview_sync(data_types: Optional[List[str]] = None) -> Dict[str, Dict]:
|
||||
return previews
|
||||
|
||||
|
||||
def list_financial_types() -> List[Dict]:
|
||||
def list_financial_types() -> list[dict]:
|
||||
"""列出所有支持的财务数据类型。
|
||||
|
||||
Returns:
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user