fix(rotation): 溢价率缓存增加增量更新逻辑

- preload_premium: 检查缓存日期范围,不足时增量拉取
- 新增 _fetch_premium_api: 拉取并合并新溢价率数据
- 调用时传入 end_date 触发增量检查

修复前: premium CSV存在即返回旧数据,明天9点运行时拿不到最新
修复后: 检测 latest_cached < end_date 时自动拉取增量
This commit is contained in:
2026-06-01 23:56:18 +08:00
parent 19f1c63981
commit 5e11b6b690

View File

@@ -169,22 +169,40 @@ class DataCache:
except Exception:
pass
def preload_premium(self, code: str, start_date: str = '2000-01-01', end_date: str = None) -> Optional[Dict[str, float]]:
"""Load premium data for an ETF code from cache, or fetch from API if not available"""
def preload_premium(self, code: str, end_date: str = None) -> Optional[Dict[str, float]]:
"""Load premium data for an ETF code from cache, with incremental update.
If cache exists but doesn't cover end_date, fetches the gap."""
if code in self.premium_data:
return self.premium_data[code]
# Already in memory - check if up-to-date
if end_date:
dates = sorted(self.premium_data[code].keys())
if dates and dates[-1] >= end_date:
return self.premium_data[code]
else:
return self.premium_data[code]
cache_path = self._premium_cache_path(code)
if cache_path.exists():
try:
df = pd.read_csv(cache_path)
if len(df) > 0 and 'date' in df.columns and 'premium' in df.columns:
self.premium_data[code] = dict(zip(df['date'].astype(str), df['premium']))
# Check if cache covers end_date
if end_date:
latest_cached = max(self.premium_data[code].keys())
if latest_cached >= end_date:
return self.premium_data[code]
# Cache is stale - fetch gap from latest_cached+1 to end_date
fetch_start = (pd.Timestamp(latest_cached) + timedelta(days=1)).strftime('%Y-%m-%d')
self._fetch_premium_api(code, fetch_start, end_date)
return self.premium_data[code]
except Exception:
pass
# No cache: fetch premium_series directly from API (returns full history)
if end_date is None:
end_date = datetime.now().strftime('%Y-%m-%d')
# No cache: fetch full history from API
self._fetch_premium_api(code, '2000-01-01', end_date or datetime.now().strftime('%Y-%m-%d'))
return self.premium_data.get(code)
def _fetch_premium_api(self, code: str, start_date: str, end_date: str):
"""Fetch premium_series from API and merge into cache"""
url = f"{self.base_url}{self.api_path}"
params = {'code': code, 'start': start_date, 'end': end_date, 'adj': 'raw'}
for attempt in range(3):
@@ -194,25 +212,25 @@ class DataCache:
if attempt < 2:
time.sleep(1)
continue
return None
return
data = resp.json()
if 'error' in data:
return None
return
premium_series = data.get('premium_series', [])
if premium_series:
premium_dict = {item['date']: item['premium'] for item in premium_series}
self.premium_data[code] = premium_dict
self._save_premium_cache(code, premium_dict)
print(f" + premium {code}: {len(premium_dict)} days")
return premium_dict
return None
new_data = {item['date']: item['premium'] for item in premium_series}
if code not in self.premium_data:
self.premium_data[code] = {}
self.premium_data[code].update(new_data)
self._save_premium_cache(code, self.premium_data[code])
print(f" + premium {code}: +{len(new_data)} days (total {len(self.premium_data[code])})")
return
except requests.exceptions.Timeout:
if attempt < 2:
continue
return None
return
except Exception:
return None
return None
return
def get_trading_calendar(self, market: str, start_date: str, end_date: str) -> Optional[pd.DatetimeIndex]:
"""Fetch trading calendar from API"""
@@ -330,7 +348,7 @@ class SimpleRotationStrategy:
self.etf_data[code] = df
# Load premium data cache for all ETF trade codes
for code in trade_codes:
self.data_cache.preload_premium(code)
self.data_cache.preload_premium(code, end_date=end_date)
print(f"\n Trade: {len(self.etf_data)}/{len(trade_codes)} OK, premium: {len(self.data_cache.premium_data)} loaded")
def _compute_momentum(self, signal_code: str, date: pd.Timestamp) -> Optional[float]: