refactor(datasource): 分层接口设计,移除HybridDataSource
架构改动: - 移除 HybridDataSource(功能被 UniversalDataFetcher 覆盖) - 新增分层接口设计:基础层 + 扩展层 基础层(统一接口): - fetch(): 统一 OHLCV 接口,自动识别资产类型 - fetch_batch(): 批量获取 扩展层(资产类型特有): - fetch_etf_adj(): A股 ETF 后复权价格 - fetch_us_adj(): 美股复权价格 - fetch_etf_with_nav(): ETF 价格 + 净值 + 溢价率 其他修改: - YFinanceSource: 新增 fetch_adj() 方法 - strategy.py: 改用 UniversalDataFetcher 替代 HybridDataSource - __init__.py: 移除 HybridDataSource 导出
This commit is contained in:
@@ -114,6 +114,70 @@ class YFinanceSource:
|
||||
print(f"YFinance下载 {code} ({yf_code}) 失败: {e}")
|
||||
return None
|
||||
|
||||
def fetch_adj(self, code: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
获取复权价格数据
|
||||
|
||||
使用 auto_adjust=True 获取复权后的价格
|
||||
- 消除拆分(split)和分红(dividend)对价格的影响
|
||||
- 适用于美股股票/ETF
|
||||
|
||||
Args:
|
||||
code: 代码(如 'AAPL', 'TSLA', 'QQQ')
|
||||
start_date: 开始日期 'YYYY-MM-DD'
|
||||
end_date: 结束日期 'YYYY-MM-DD'
|
||||
|
||||
Returns:
|
||||
DataFrame with columns: date, open, high, low, close, volume (复权后)
|
||||
"""
|
||||
import yfinance as yf
|
||||
|
||||
# 添加延迟避免限流
|
||||
time.sleep(self._delay)
|
||||
|
||||
# 转换代码格式
|
||||
yf_code = self.CODE_MAP.get(code, code)
|
||||
|
||||
try:
|
||||
ticker = yf.Ticker(yf_code)
|
||||
|
||||
# end_date 需要加一天(yfinance的end是排他的)
|
||||
end_dt = datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)
|
||||
|
||||
# auto_adjust=True 获取复权价格
|
||||
df = ticker.history(
|
||||
start=start_date,
|
||||
end=end_dt.strftime("%Y-%m-%d"),
|
||||
auto_adjust=True
|
||||
)
|
||||
|
||||
if df is None or len(df) == 0:
|
||||
return None
|
||||
|
||||
# 标准化列名
|
||||
df = df.rename(columns={
|
||||
"Open": "open",
|
||||
"High": "high",
|
||||
"Low": "low",
|
||||
"Close": "close",
|
||||
"Volume": "volume",
|
||||
})
|
||||
|
||||
# 确保索引是日期格式
|
||||
df.index = pd.to_datetime(df.index, utc=True).tz_localize(None).normalize()
|
||||
df.index.name = "date"
|
||||
|
||||
# 添加代码列和标记
|
||||
df["code"] = code
|
||||
df.attrs['code'] = code
|
||||
df.attrs['adjusted'] = True
|
||||
|
||||
return df[['code', 'open', 'high', 'low', 'close', 'volume']]
|
||||
|
||||
except Exception as e:
|
||||
print(f"YFinance下载复权数据 {code} ({yf_code}) 失败: {e}")
|
||||
return None
|
||||
|
||||
def is_yfinance_code(self, code: str) -> bool:
|
||||
"""判断是否需要YFinance获取"""
|
||||
# 非A股代码
|
||||
|
||||
Reference in New Issue
Block a user