diff --git a/datasource/tushare_source.py b/datasource/tushare_source.py index 556f503..ea78cb0 100644 --- a/datasource/tushare_source.py +++ b/datasource/tushare_source.py @@ -399,29 +399,30 @@ class TushareSource: 复权公式: - 后复权 (hfq): close_hfq = close × adj_factor + - 前复权 (qfq): close_qfq = close × adj_factor / latest_factor Args: code: ETF代码,如 '159915.SZ', '518880.SH' start_date: 开始日期 'YYYY-MM-DD' end_date: 结束日期 'YYYY-MM-DD' - adj: 复权类型,ETF 仅支持 'hfq'(后复权) + adj: 复权类型,支持 'hfq'(后复权)或 'qfq'(前复权) Returns: DataFrame with columns: date, code, open, high, low, close, volume, adj_factor """ - if adj != 'hfq': - raise ValueError(f"ETF 仅支持 adj='hfq'(后复权),当前: {adj}") + if adj not in ['qfq', 'hfq']: + raise ValueError(f"ETF adj 参数必须是 'qfq' 或 'hfq',当前: {adj}") - return self._fetch_etf_hfq(code, start_date, end_date) + return self._fetch_etf_adj(code, start_date, end_date, adj) - def _fetch_etf_hfq(self, code: str, start_date: str, end_date: str) -> 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): 1. 使用 fund_daily() 获取原始价格 2. 使用 fund_adj() 获取复权因子 - 3. 计算后复权价格:close_hfq = close × adj_factor + 3. 根据 adj 参数计算复权价格 fund_adj 单次限 2000 条,按 5 年分段请求再拼接。 @@ -496,14 +497,32 @@ class TushareSource: df_adj_aligned = df_adj.reindex(df_daily.index) df_adj_aligned['adj_factor'] = df_adj_aligned['adj_factor'].ffill().fillna(1.0) - # 步骤 5: 计算后复权价格 + # 步骤 5: 计算复权价格 df = df_daily.copy() df['adj_factor'] = df_adj_aligned['adj_factor'] - df['close_hfq'] = (df['close'] * df['adj_factor']).round(4) - df['open'] = (df['open'] * df['adj_factor']).round(4) - df['high'] = (df['high'] * df['adj_factor']).round(4) - df['low'] = (df['low'] * df['adj_factor']).round(4) - df['close'] = df['close_hfq'] # close 列设为后复权价格 + + if adj == 'hfq': + # 后复权: close_hfq = close × adj_factor + df['close_hfq'] = (df['close'] * df['adj_factor']).round(4) + df['open'] = (df['open'] * df['adj_factor']).round(4) + df['high'] = (df['high'] * df['adj_factor']).round(4) + df['low'] = (df['low'] * df['adj_factor']).round(4) + df['close'] = df['close_hfq'] # close 列设为后复权价格 + elif adj == 'qfq': + # 前复权: close_qfq = close × adj_factor / latest_factor + # 获取全量最新复权因子 + latest_factor = df_adj['adj_factor'].iloc[-1] + if latest_factor and latest_factor > 0: + adj_ratio = df['adj_factor'] / latest_factor + df['close_qfq'] = (df['close'] * adj_ratio).round(4) + df['open'] = (df['open'] * adj_ratio).round(4) + df['high'] = (df['high'] * adj_ratio).round(4) + df['low'] = (df['low'] * adj_ratio).round(4) + df['close'] = df['close_qfq'] # close 列设为前复权价格 + else: + # 无有效复权因子,返回原始价格 + df['close'] = df['close'] + df['code'] = code return df[['code', 'open', 'high', 'low', 'close', 'volume', 'adj_factor']] diff --git a/datasource/universal_fetcher.py b/datasource/universal_fetcher.py index 0b9a375..476dade 100644 --- a/datasource/universal_fetcher.py +++ b/datasource/universal_fetcher.py @@ -171,7 +171,7 @@ class UniversalDataFetcher: # 各资产类型支持的 adj 参数 VALID_ADJ_BY_TYPE = { AssetType.CHINA_INDEX: ['raw'], # 指数无复权 - AssetType.CHINA_ETF: ['raw', 'hfq'], # ETF 仅支持后复权 + AssetType.CHINA_ETF: ['raw', 'qfq', 'hfq'], # ETF 支持前复权/后复权 AssetType.CHINA_STOCK: ['raw', 'qfq', 'hfq'], AssetType.US_INDEX: ['raw'], # 指数无复权 AssetType.US_STOCK: ['raw', 'qfq', 'hfq'],