refactor(tushare): 合并ETF复权方法,消除冗余设计
- 合并 fetch_etf_adj 和 _fetch_etf_adj 为单一方法 - 删除 _fetch_etf_qfq 转发方法 - 减少~26行代码,优化代码结构(从3个方法→1个方法) - 保持公共接口签名不变,完全向后兼容 - 全面测试通过:raw/qfq/hfq三种模式数据正确 - 更新 VALID_ADJ_BY_TYPE 配置,ETF支持前复权/后复权
This commit is contained in:
@@ -121,23 +121,23 @@ class TushareSource:
|
|||||||
code: ETF代码,如 '159915.SZ', '518880.SH'
|
code: ETF代码,如 '159915.SZ', '518880.SH'
|
||||||
start_date: 开始日期 'YYYY-MM-DD'
|
start_date: 开始日期 'YYYY-MM-DD'
|
||||||
end_date: 结束日期 'YYYY-MM-DD'
|
end_date: 结束日期 'YYYY-MM-DD'
|
||||||
adj: 复权类型 'raw'(原始) / 'hfq'(后复权),默认 'raw'
|
adj: 复权类型 'raw'(原始) / 'qfq'(前复权) / 'hfq'(后复权),默认 'raw'
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
DataFrame with columns: date, open, high, low, close, volume
|
DataFrame with columns: date, open, high, low, close, volume
|
||||||
adj='hfq' 时额外返回 adj_factor, close_hfq
|
adj='qfq' 或 'hfq' 时额外返回复权价格
|
||||||
|
|
||||||
DataFrame.attrs 附加元数据:
|
DataFrame.attrs 附加元数据:
|
||||||
- attrs['nav']: 净值 DataFrame
|
- attrs['nav']: 净值 DataFrame
|
||||||
- attrs['premium']: 溢价率 Series(始终基于原始价格计算)
|
- attrs['premium']: 溢价率 Series(始终基于原始价格计算)
|
||||||
"""
|
"""
|
||||||
# 校验 adj 参数
|
# 校验 adj 参数
|
||||||
if adj not in ['raw', 'hfq']:
|
if adj not in ['raw', 'qfq', 'hfq']:
|
||||||
raise ValueError(f"ETF 仅支持 adj='raw' 或 'hfq',当前: {adj}")
|
raise ValueError(f"ETF 仅支持 adj='raw', 'qfq' 或 'hfq',当前: {adj}")
|
||||||
|
|
||||||
# 1. 获取价格数据
|
# 1. 获取价格数据
|
||||||
if adj == 'hfq':
|
if adj in ['qfq', 'hfq']:
|
||||||
price_df = self._fetch_etf_hfq(code, start_date, end_date)
|
price_df = self.fetch_etf_adj(code, start_date, end_date, adj)
|
||||||
else:
|
else:
|
||||||
price_df = self._fetch_etf_raw(code, start_date, end_date)
|
price_df = self._fetch_etf_raw(code, start_date, end_date)
|
||||||
|
|
||||||
@@ -150,8 +150,12 @@ class TushareSource:
|
|||||||
|
|
||||||
# 3. 计算溢价率(始终使用原始价格)
|
# 3. 计算溢价率(始终使用原始价格)
|
||||||
if nav_df is not None and len(nav_df) > 0:
|
if nav_df is not None and len(nav_df) > 0:
|
||||||
# hfq 时需要获取原始价格来计算溢价率
|
# qfq/hfq 时需要获取原始价格来计算溢价率
|
||||||
price_for_premium = price_df if adj == 'raw' else self._fetch_etf_raw(code, start_date, end_date)
|
if adj == 'raw':
|
||||||
|
price_for_premium = price_df
|
||||||
|
else:
|
||||||
|
price_for_premium = self._fetch_etf_raw(code, start_date, end_date)
|
||||||
|
|
||||||
if price_for_premium is not None:
|
if price_for_premium is not None:
|
||||||
premium_series = self._calculate_premium_series(price_for_premium, nav_df)
|
premium_series = self._calculate_premium_series(price_for_premium, nav_df)
|
||||||
price_df.attrs['premium'] = premium_series
|
price_df.attrs['premium'] = premium_series
|
||||||
@@ -390,7 +394,7 @@ class TushareSource:
|
|||||||
|
|
||||||
def fetch_etf_adj(self, code: str, start_date: str, end_date: str, adj: str = 'hfq') -> Optional[pd.DataFrame]:
|
def fetch_etf_adj(self, code: str, start_date: str, end_date: str, adj: str = 'hfq') -> Optional[pd.DataFrame]:
|
||||||
"""
|
"""
|
||||||
获取 ETF 复权价格数据(公共接口)
|
获取 ETF 复权价格数据
|
||||||
|
|
||||||
自己实现复权计算(不使用 pro_bar,避免 pandas 兼容性问题):
|
自己实现复权计算(不使用 pro_bar,避免 pandas 兼容性问题):
|
||||||
1. 使用 fund_daily() 获取原始价格
|
1. 使用 fund_daily() 获取原始价格
|
||||||
@@ -401,6 +405,8 @@ class TushareSource:
|
|||||||
- 后复权 (hfq): close_hfq = close × adj_factor
|
- 后复权 (hfq): close_hfq = close × adj_factor
|
||||||
- 前复权 (qfq): close_qfq = close × adj_factor / latest_factor
|
- 前复权 (qfq): close_qfq = close × adj_factor / latest_factor
|
||||||
|
|
||||||
|
fund_adj 单次限 2000 条,按 5 年分段请求再拼接。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
code: ETF代码,如 '159915.SZ', '518880.SH'
|
code: ETF代码,如 '159915.SZ', '518880.SH'
|
||||||
start_date: 开始日期 'YYYY-MM-DD'
|
start_date: 开始日期 'YYYY-MM-DD'
|
||||||
@@ -413,27 +419,6 @@ class TushareSource:
|
|||||||
if adj not in ['qfq', 'hfq']:
|
if adj not in ['qfq', 'hfq']:
|
||||||
raise ValueError(f"ETF adj 参数必须是 'qfq' 或 'hfq',当前: {adj}")
|
raise ValueError(f"ETF adj 参数必须是 'qfq' 或 'hfq',当前: {adj}")
|
||||||
|
|
||||||
return self._fetch_etf_adj(code, start_date, end_date, adj)
|
|
||||||
|
|
||||||
def _fetch_etf_adj(self, code: str, start_date: str, end_date: str, adj: str = 'hfq') -> Optional[pd.DataFrame]:
|
|
||||||
"""
|
|
||||||
获取 ETF 复权价格数据(内部方法)
|
|
||||||
|
|
||||||
自己实现复权计算(不使用 pro_bar):
|
|
||||||
1. 使用 fund_daily() 获取原始价格
|
|
||||||
2. 使用 fund_adj() 获取复权因子
|
|
||||||
3. 根据 adj 参数计算复权价格
|
|
||||||
|
|
||||||
fund_adj 单次限 2000 条,按 5 年分段请求再拼接。
|
|
||||||
|
|
||||||
Args:
|
|
||||||
code: ETF代码,如 '159915.SZ', '518880.SH'
|
|
||||||
start_date: 开始日期 'YYYY-MM-DD'
|
|
||||||
end_date: 结束日期 'YYYY-MM-DD'
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
DataFrame with columns: date, code, open, high, low, close, volume, adj_factor
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
pro = self._get_pro_api()
|
pro = self._get_pro_api()
|
||||||
ts_code = code.replace('.SS', '.SH')
|
ts_code = code.replace('.SS', '.SH')
|
||||||
|
|||||||
Reference in New Issue
Block a user