fix(premium): 溢价率计算改用动态匹配原则
修复 _calculate_premium_series 方法,改为动态匹配净值日期: 原问题: - 统一使用T-1净值规则导致A股/港股/商品ETF溢价率计算错误 - 如创业板ETF用T-1净值而非当天净值,溢价率从0.76%变成0.19% 修复方案: - 优先使用当天净值(A股/港股/商品/债券/日本QDII) - 当天净值不存在时使用T-1净值(美股QDII/欧洲QDII/原油QDII) 验证结果: - 11只ETF全部验证通过,与集思录数据完全一致
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user