feat: fetch_etf_with_nav 返回历史溢价率序列
修改内容:
1. universal_fetcher.py
- fetch_etf_with_nav 返回三值:(price_df, nav_df, premium_series)
- 新增 _calculate_premium_series 方法:计算每一天的溢价率
- 溢价率 = (ETF收盘价 - ETF净值) / ETF净值
- 净值用ffill对齐价格日期(处理T+1延迟)
2. flask_server.py
- /api/v1/etf/nav 端点返回历史溢价率序列
- 添加 premium_series 字段:[{date, premium}]
- 添加 latest_premium: 最新溢价率
- 添加 premium_stats: 统计数据(mean/std/min/max/median)
测试结果(513100.SH 纳指100 ETF):
- 价格数据: 8条
- 净值数据: 8条
- 溢价率序列: 8条
- 最新溢价率: 0.1500%
- 溢价率均值: 1.1433%
- 溢价率范围: 0.15% ~ 1.69%
This commit is contained in:
@@ -484,11 +484,11 @@ def get_etf_nav():
|
|||||||
"hint": "Only A股ETF (codes starting with 51/52/15/16) supported",
|
"hint": "Only A股ETF (codes starting with 51/52/15/16) supported",
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# 获取净值
|
# 获取净值和溢价率
|
||||||
f = get_fetcher()
|
f = get_fetcher()
|
||||||
try:
|
try:
|
||||||
with f:
|
with f:
|
||||||
price_df, nav_df = f.fetch_etf_with_nav(code, start, end)
|
price_df, nav_df, premium_series = f.fetch_etf_with_nav(code, start, end)
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"code": code,
|
"code": code,
|
||||||
@@ -496,14 +496,30 @@ def get_etf_nav():
|
|||||||
"nav": dataframe_to_json(nav_df) if nav_df else {"data": [], "count": 0},
|
"nav": dataframe_to_json(nav_df) if nav_df else {"data": [], "count": 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
# 计算最新溢价率
|
# 添加历史溢价率序列
|
||||||
if nav_df is not None and len(nav_df) > 0 and price_df is not None and len(price_df) > 0:
|
if premium_series is not None and len(premium_series) > 0:
|
||||||
latest_nav = nav_df['nav'].iloc[-1]
|
# 转换为日期-溢价率列表
|
||||||
latest_price = price_df['close'].iloc[-1]
|
premium_data = [
|
||||||
if latest_nav > 0:
|
{"date": date.strftime('%Y-%m-%d'), "premium": round(premium, 6)}
|
||||||
premium = (latest_price - latest_nav) / latest_nav
|
for date, premium in premium_series.items()
|
||||||
result['premium_rate'] = premium
|
]
|
||||||
result['premium_date'] = nav_df.index[-1].strftime('%Y-%m-%d')
|
result['premium_series'] = premium_data
|
||||||
|
|
||||||
|
# 最新溢价率
|
||||||
|
latest_premium = premium_series.iloc[-1]
|
||||||
|
latest_date = premium_series.index[-1].strftime('%Y-%m-%d')
|
||||||
|
result['latest_premium'] = round(latest_premium, 6)
|
||||||
|
result['premium_date'] = latest_date
|
||||||
|
|
||||||
|
# 溢价率统计
|
||||||
|
result['premium_stats'] = {
|
||||||
|
"mean": round(premium_series.mean(), 6),
|
||||||
|
"std": round(premium_series.std(), 6),
|
||||||
|
"min": round(premium_series.min(), 6),
|
||||||
|
"max": round(premium_series.max(), 6),
|
||||||
|
"median": round(premium_series.median(), 6),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|||||||
@@ -189,21 +189,64 @@ class UniversalDataFetcher:
|
|||||||
code: str,
|
code: str,
|
||||||
start_date: str,
|
start_date: str,
|
||||||
end_date: str
|
end_date: str
|
||||||
) -> Tuple[Optional[pd.DataFrame], Optional[pd.DataFrame]]:
|
) -> Tuple[Optional[pd.DataFrame], Optional[pd.DataFrame], Optional[pd.Series]]:
|
||||||
"""
|
"""
|
||||||
获取ETF价格 + 净值
|
获取ETF价格 + 净值 + 溢价率序列
|
||||||
|
|
||||||
用于计算溢价率
|
计算每一天的溢价率,用于分析溢价率走势
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
code: ETF代码
|
code: ETF代码
|
||||||
|
start_date: 开始日期
|
||||||
|
end_date: 结束日期
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(price_df, nav_df)
|
(price_df, nav_df, premium_series)
|
||||||
|
- price_df: ETF价格数据 (OHLCV)
|
||||||
|
- nav_df: ETF净值数据
|
||||||
|
- premium_series: 溢价率序列 (每天计算)
|
||||||
"""
|
"""
|
||||||
price_df = self._tushare.fetch_etf(code, start_date, end_date)
|
price_df = self._tushare.fetch_etf(code, start_date, end_date)
|
||||||
nav_df = self._tushare.fetch_etf_nav(code, start_date, end_date)
|
nav_df = self._tushare.fetch_etf_nav(code, start_date, end_date)
|
||||||
return price_df, nav_df
|
|
||||||
|
# 计算历史溢价率序列
|
||||||
|
premium_series = None
|
||||||
|
if price_df is not None and nav_df is not None and len(nav_df) > 0:
|
||||||
|
premium_series = self._calculate_premium_series(price_df, nav_df)
|
||||||
|
|
||||||
|
return price_df, nav_df, premium_series
|
||||||
|
|
||||||
|
def _calculate_premium_series(
|
||||||
|
self,
|
||||||
|
price_df: pd.DataFrame,
|
||||||
|
nav_df: pd.DataFrame
|
||||||
|
) -> Optional[pd.Series]:
|
||||||
|
"""
|
||||||
|
计算历史溢价率序列
|
||||||
|
|
||||||
|
溢价率 = (ETF收盘价 - ETF净值) / ETF净值
|
||||||
|
|
||||||
|
注意:净值数据通常T+1公布,需要处理日期对齐问题
|
||||||
|
|
||||||
|
Args:
|
||||||
|
price_df: ETF价格数据(索引为日期)
|
||||||
|
nav_df: ETF净值数据(索引为日期)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
溢价率Series(索引为日期,值为溢价率)
|
||||||
|
"""
|
||||||
|
# 对齐日期:净值用ffill填充(因为T+1公布)
|
||||||
|
# 价格日期可能比净值日期多一天
|
||||||
|
aligned_nav = nav_df['nav'].reindex(price_df.index, method='ffill')
|
||||||
|
|
||||||
|
# 计算溢价率
|
||||||
|
close_prices = price_df['close']
|
||||||
|
premium = (close_prices - aligned_nav) / aligned_nav
|
||||||
|
|
||||||
|
# 过滤掉无效值(净值缺失的日期)
|
||||||
|
premium = premium.dropna()
|
||||||
|
|
||||||
|
return premium
|
||||||
|
|
||||||
def _fetch_us_index(
|
def _fetch_us_index(
|
||||||
self,
|
self,
|
||||||
|
|||||||
Reference in New Issue
Block a user