"""LRU cache wrapper with per-ticker invalidation. Provides an in-memory cache (max 1000 entries) keyed on (ticker, start_date, end_date, indicator_type). Supports selective invalidation of all entries for a given ticker — needed when new OHLCV data is ingested. """ from __future__ import annotations from collections import OrderedDict from typing import Any, Hashable CacheKey = tuple[str, Any, Any, str] # (ticker, start_date, end_date, indicator_type) _DEFAULT_MAX_SIZE = 1000 class LRUCache: """Simple LRU cache backed by an ``OrderedDict``. Parameters ---------- max_size: Maximum number of entries. When exceeded the least-recently-used entry is evicted. Defaults to 1000. """ def __init__(self, max_size: int = _DEFAULT_MAX_SIZE) -> None: self._max_size = max_size self._store: OrderedDict[Hashable, Any] = OrderedDict() # ------------------------------------------------------------------ # Public API # ------------------------------------------------------------------ def get(self, key: CacheKey) -> Any | None: """Return cached value or ``None`` on miss. Accessing an entry promotes it to most-recently-used. """ if key not in self._store: return None self._store.move_to_end(key) return self._store[key] def set(self, key: CacheKey, value: Any) -> None: """Insert or update *key* with *value*. If the cache is full the least-recently-used entry is evicted. """ if key in self._store: self._store.move_to_end(key) self._store[key] = value return if len(self._store) >= self._max_size: self._store.popitem(last=False) # evict LRU self._store[key] = value def invalidate_ticker(self, ticker: str) -> int: """Remove all entries whose first key element matches *ticker*. Returns the number of evicted entries. """ keys_to_remove = [k for k in self._store if k[0] == ticker] for k in keys_to_remove: del self._store[k] return len(keys_to_remove) def clear(self) -> None: """Remove all entries.""" self._store.clear() @property def size(self) -> int: """Current number of cached entries.""" return len(self._store) @property def max_size(self) -> int: """Maximum capacity.""" return self._max_size # Module-level singleton used by the indicator service. indicator_cache = LRUCache()