From c0195c5bca9e1dd6c8b1b8975d24be0c50bb15a4 Mon Sep 17 00:00:00 2001 From: aszerW Date: Mon, 25 May 2026 19:59:49 +0800 Subject: [PATCH] =?UTF-8?q?refactor(tushare):=20=E5=90=88=E5=B9=B6ETF?= =?UTF-8?q?=E5=A4=8D=E6=9D=83=E6=96=B9=E6=B3=95=EF=BC=8C=E6=B6=88=E9=99=A4?= =?UTF-8?q?=E5=86=97=E4=BD=99=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 合并 fetch_etf_adj 和 _fetch_etf_adj 为单一方法 - 删除 _fetch_etf_qfq 转发方法 - 减少~26行代码,优化代码结构(从3个方法→1个方法) - 保持公共接口签名不变,完全向后兼容 - 全面测试通过:raw/qfq/hfq三种模式数据正确 - 更新 VALID_ADJ_BY_TYPE 配置,ETF支持前复权/后复权 --- datasource/tushare_source.py | 45 ++++++++++++------------------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/datasource/tushare_source.py b/datasource/tushare_source.py index ea78cb0..20f5b0d 100644 --- a/datasource/tushare_source.py +++ b/datasource/tushare_source.py @@ -121,23 +121,23 @@ class TushareSource: code: ETF代码,如 '159915.SZ', '518880.SH' start_date: 开始日期 'YYYY-MM-DD' end_date: 结束日期 'YYYY-MM-DD' - adj: 复权类型 'raw'(原始) / 'hfq'(后复权),默认 'raw' + adj: 复权类型 'raw'(原始) / 'qfq'(前复权) / 'hfq'(后复权),默认 'raw' Returns: DataFrame with columns: date, open, high, low, close, volume - adj='hfq' 时额外返回 adj_factor, close_hfq + adj='qfq' 或 'hfq' 时额外返回复权价格 DataFrame.attrs 附加元数据: - attrs['nav']: 净值 DataFrame - attrs['premium']: 溢价率 Series(始终基于原始价格计算) """ # 校验 adj 参数 - if adj not in ['raw', 'hfq']: - raise ValueError(f"ETF 仅支持 adj='raw' 或 'hfq',当前: {adj}") + if adj not in ['raw', 'qfq', 'hfq']: + raise ValueError(f"ETF 仅支持 adj='raw', 'qfq' 或 'hfq',当前: {adj}") # 1. 获取价格数据 - if adj == 'hfq': - price_df = self._fetch_etf_hfq(code, start_date, end_date) + if adj in ['qfq', 'hfq']: + price_df = self.fetch_etf_adj(code, start_date, end_date, adj) else: price_df = self._fetch_etf_raw(code, start_date, end_date) @@ -150,8 +150,12 @@ class TushareSource: # 3. 计算溢价率(始终使用原始价格) if nav_df is not None and len(nav_df) > 0: - # hfq 时需要获取原始价格来计算溢价率 - price_for_premium = price_df if adj == 'raw' else self._fetch_etf_raw(code, start_date, end_date) + # qfq/hfq 时需要获取原始价格来计算溢价率 + 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: premium_series = self._calculate_premium_series(price_for_premium, nav_df) 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]: """ - 获取 ETF 复权价格数据(公共接口) + 获取 ETF 复权价格数据 自己实现复权计算(不使用 pro_bar,避免 pandas 兼容性问题): 1. 使用 fund_daily() 获取原始价格 @@ -401,6 +405,8 @@ class TushareSource: - 后复权 (hfq): close_hfq = close × adj_factor - 前复权 (qfq): close_qfq = close × adj_factor / latest_factor + fund_adj 单次限 2000 条,按 5 年分段请求再拼接。 + Args: code: ETF代码,如 '159915.SZ', '518880.SH' start_date: 开始日期 'YYYY-MM-DD' @@ -413,27 +419,6 @@ class TushareSource: if adj not in ['qfq', 'hfq']: 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: pro = self._get_pro_api() ts_code = code.replace('.SS', '.SH')