Files
signal-platform/app/cache.py
Dennis Thiessen 61ab24490d
Some checks failed
Deploy / lint (push) Failing after 7s
Deploy / test (push) Has been skipped
Deploy / deploy (push) Has been skipped
first commit
2026-02-20 17:31:01 +01:00

87 lines
2.5 KiB
Python

"""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()