From bed92027fc792ad3dd5d433d6094e9258b0de190 Mon Sep 17 00:00:00 2001 From: aszerW Date: Sat, 16 May 2026 10:27:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(premium):=20=E6=BA=A2=E4=BB=B7=E7=8E=87?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E6=94=B9=E7=94=A8=E5=8A=A8=E6=80=81=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E5=8E=9F=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 _calculate_premium_series 方法,改为动态匹配净值日期: 原问题: - 统一使用T-1净值规则导致A股/港股/商品ETF溢价率计算错误 - 如创业板ETF用T-1净值而非当天净值,溢价率从0.76%变成0.19% 修复方案: - 优先使用当天净值(A股/港股/商品/债券/日本QDII) - 当天净值不存在时使用T-1净值(美股QDII/欧洲QDII/原油QDII) 验证结果: - 11只ETF全部验证通过,与集思录数据完全一致 --- datasource/universal_fetcher.py | 48 ++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/datasource/universal_fetcher.py b/datasource/universal_fetcher.py index b25bb24..15c1e2f 100644 --- a/datasource/universal_fetcher.py +++ b/datasource/universal_fetcher.py @@ -236,8 +236,13 @@ class UniversalDataFetcher: 溢价率 = (ETF收盘价 - ETF净值) / ETF净值 - 关键:QDII基金净值T+1披露,需要用T日收盘价配T-1日净值 - 集思录做法:价格日期配前一日净值(净值日期滞后一天) + 关键:不同QDII基金净值披露规则不同 + - 部分基金净值当天披露(如日经ETF):价格日期=净值日期 + - 部分基金净值T+1披露(如纳指ETF):价格日期配T-1日净值 + + 集思录做法:根据基金特性选择匹配方式 + - 如果有当天净值数据,优先使用当天净值 + - 如果当天净值不存在,使用T-1日净值 Args: price_df: ETF价格数据(索引为日期) @@ -255,22 +260,41 @@ class UniversalDataFetcher: if nav_index.has_duplicates: nav_df = nav_df[~nav_df.index.duplicated(keep='last')] - # QDII净值T+1披露:T日收盘价配T-1日净值 - # 将净值索引后移一天,使价格日期与前一日净值对齐 + # 优先尝试使用当天净值(如日经ETF) + same_day_dates = price_df.index.intersection(nav_df.index) + + # 对于没有当天净值的日期,使用T-1日净值(如纳指ETF) nav_df_shifted = nav_df.copy() nav_df_shifted.index = nav_df_shifted.index + pd.Timedelta(days=1) + shifted_dates = price_df.index.intersection(nav_df_shifted.index) - # 找到对齐后的共同日期(价格日期) - common_dates = price_df.index.intersection(nav_df_shifted.index) + # 排除已有当天净值的日期 + t1_dates = shifted_dates.difference(same_day_dates) - if len(common_dates) == 0: + premium_data = {} + + # 使用当天净值计算 + if len(same_day_dates) > 0: + close_same = price_df.loc[same_day_dates, 'close'] + nav_same = nav_df.loc[same_day_dates, 'nav'] + for date in same_day_dates: + if pd.notna(close_same.loc[date]) and pd.notna(nav_same.loc[date]): + premium_data[date] = (close_same.loc[date] - nav_same.loc[date]) / nav_same.loc[date] + + # 使用T-1日净值计算(仅用于没有当天净值的日期) + if len(t1_dates) > 0: + close_t1 = price_df.loc[t1_dates, 'close'] + nav_t1 = nav_df_shifted.loc[t1_dates, 'nav'] + for date in t1_dates: + if pd.notna(close_t1.loc[date]) and pd.notna(nav_t1.loc[date]): + premium_data[date] = (close_t1.loc[date] - nav_t1.loc[date]) / nav_t1.loc[date] + + if len(premium_data) == 0: return None - # 计算溢价率:T日收盘价 / T-1日净值(已后移到T日索引) - close_prices = price_df.loc[common_dates, 'close'] - nav_values = nav_df_shifted.loc[common_dates, 'nav'] - - premium = (close_prices - nav_values) / nav_values + # 构建Series并按日期排序 + premium = pd.Series(premium_data) + premium = premium.sort_index() premium = premium.dropna() return premium