Per-timeframe cache TTLs — D1 cached 4h, H1 30m, M15 5m
A single 5-minute refresh_interval applied to every timeframe wasted API credits: a daily candle was being refetched every 5 minutes even though it only changes once per day. Now each TF caches for roughly its bar interval, so repeat scans within an hour skip D1 entirely (saves ~60s of throttle wait) and within 30 min also skip H1. config.py: new cache_ttl_per_tf dict on PlatformConfig with sensible defaults for every timeframe; refresh_interval kept as fallback. data_provider.py: _cache_ttl() helper used by both fetch_ohlcv and prefetch_batch instead of the global refresh_interval. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -196,9 +196,25 @@ class PlatformConfig:
|
|||||||
# Twelve Data API key (overridden by TWELVE_DATA_API_KEY env var)
|
# Twelve Data API key (overridden by TWELVE_DATA_API_KEY env var)
|
||||||
twelve_data_api_key: str = "f563b22ffdfd4924955decbe1a8c8701"
|
twelve_data_api_key: str = "f563b22ffdfd4924955decbe1a8c8701"
|
||||||
|
|
||||||
# Dashboard refresh interval (seconds)
|
# Dashboard refresh interval (seconds) — used as a fallback when a
|
||||||
|
# timeframe isn't covered by cache_ttl_per_tf below.
|
||||||
refresh_interval: int = 300 # 5 minutes
|
refresh_interval: int = 300 # 5 minutes
|
||||||
|
|
||||||
|
# Per-timeframe cache TTL (seconds). A 1-week candle changes less
|
||||||
|
# often than a 15-minute candle, so caching them all for the same
|
||||||
|
# 5 minutes wastes API credits. Tuned to roughly match each TF's
|
||||||
|
# bar interval so we re-fetch about once per new bar.
|
||||||
|
cache_ttl_per_tf: Dict[Timeframe, int] = field(default_factory=lambda: {
|
||||||
|
Timeframe.M1: 60, # 1 minute
|
||||||
|
Timeframe.M5: 150, # 2.5 minutes
|
||||||
|
Timeframe.M15: 300, # 5 minutes
|
||||||
|
Timeframe.M30: 600, # 10 minutes
|
||||||
|
Timeframe.H1: 1800, # 30 minutes
|
||||||
|
Timeframe.H4: 7200, # 2 hours
|
||||||
|
Timeframe.D1: 14400, # 4 hours
|
||||||
|
Timeframe.W1: 86400, # 24 hours
|
||||||
|
})
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
log_level: str = "INFO"
|
log_level: str = "INFO"
|
||||||
log_signals_to_file: bool = True
|
log_signals_to_file: bool = True
|
||||||
|
|||||||
+9
-3
@@ -125,6 +125,10 @@ class ForexDataProvider:
|
|||||||
clean = symbol.replace("=X", "")
|
clean = symbol.replace("=X", "")
|
||||||
return f"{clean[:3]}/{clean[3:]}"
|
return f"{clean[:3]}/{clean[3:]}"
|
||||||
|
|
||||||
|
def _cache_ttl(self, timeframe: Timeframe) -> int:
|
||||||
|
"""Cache TTL for a given timeframe — falls back to refresh_interval."""
|
||||||
|
return self.config.cache_ttl_per_tf.get(timeframe, self.config.refresh_interval)
|
||||||
|
|
||||||
def fetch_ohlcv(
|
def fetch_ohlcv(
|
||||||
self,
|
self,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
@@ -142,10 +146,11 @@ class ForexDataProvider:
|
|||||||
|
|
||||||
cache_key = f"{symbol}_{timeframe.value}"
|
cache_key = f"{symbol}_{timeframe.value}"
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
ttl = self._cache_ttl(timeframe)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
cache_key in self._cache_timestamps
|
cache_key in self._cache_timestamps
|
||||||
and (now - self._cache_timestamps[cache_key]) < self.config.refresh_interval
|
and (now - self._cache_timestamps[cache_key]) < ttl
|
||||||
and cache_key in self._cache
|
and cache_key in self._cache
|
||||||
):
|
):
|
||||||
cached = self._cache.get(cache_key)
|
cached = self._cache.get(cache_key)
|
||||||
@@ -178,14 +183,15 @@ class ForexDataProvider:
|
|||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
for tf in timeframes:
|
for tf in timeframes:
|
||||||
|
ttl = self._cache_ttl(tf)
|
||||||
cached = all(
|
cached = all(
|
||||||
f"{s}_{tf.value}" in self._cache_timestamps
|
f"{s}_{tf.value}" in self._cache_timestamps
|
||||||
and (now - self._cache_timestamps[f"{s}_{tf.value}"]) < self.config.refresh_interval
|
and (now - self._cache_timestamps[f"{s}_{tf.value}"]) < ttl
|
||||||
and not self._cache.get(f"{s}_{tf.value}", pd.DataFrame()).empty
|
and not self._cache.get(f"{s}_{tf.value}", pd.DataFrame()).empty
|
||||||
for s in symbols
|
for s in symbols
|
||||||
)
|
)
|
||||||
if cached:
|
if cached:
|
||||||
logger.debug(f"Skipping prefetch for {tf.value} — all {len(symbols)} symbols cached")
|
logger.debug(f"Skipping prefetch for {tf.value} — all {len(symbols)} symbols cached (TTL {ttl}s)")
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
self._batch_fetch_timeframe(symbols, tf, lookback_days)
|
self._batch_fetch_timeframe(symbols, tf, lookback_days)
|
||||||
|
|||||||
Reference in New Issue
Block a user