Team Ai
Apppublic

javitechjkd/backtestingv2

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
cache.py123 linesDownload Raw Back to utils
1"""
2Data caching utilities for backtesting application.
3Uses diskcache for persistent caching with automatic expiration.
4"""
5import hashlib
6import json
7from typing import Any, Optional, Callable
8from functools import wraps
9from datetime import timedelta
10import diskcache
11from config import config
12from utils.logger import setup_logger
13
14logger = setup_logger(__name__)
15
16# Initialize disk cache
17cache = diskcache.Cache(str(config.CACHE_DIR))
18
19
20def generate_cache_key(*args, **kwargs) -> str:
21    """
22    Generate a unique cache key from function arguments.
23    
24    Args:
25        *args: Positional arguments
26        **kwargs: Keyword arguments
27    
28    Returns:
29        MD5 hash of the serialized arguments
30    """
31    key_data = {
32        'args': args,
33        'kwargs': kwargs
34    }
35    key_string = json.dumps(key_data, sort_keys=True, default=str)
36    return hashlib.md5(key_string.encode()).hexdigest()
37
38
39def cached(expire_hours: Optional[int] = None):
40    """
41    Decorator to cache function results on disk.
42    
43    Args:
44        expire_hours: Hours until cache expires (uses config default if None)
45    
46    Example:
47        @cached(expire_hours=24)
48        def fetch_data(symbol: str, period: str):
49            # Expensive operation
50            return data
51    """
52    def decorator(func: Callable) -> Callable:
53        @wraps(func)
54        def wrapper(*args, **kwargs) -> Any:
55            if not config.ENABLE_CACHE:
56                return func(*args, **kwargs)
57            
58            # Generate cache key
59            func_name = f"{func.__module__}.{func.__name__}"
60            cache_key = f"{func_name}:{generate_cache_key(*args, **kwargs)}"
61            
62            # Try to get from cache
63            cached_value = cache.get(cache_key)
64            if cached_value is not None:
65                logger.debug(f"Cache HIT for {func_name}")
66                return cached_value
67            
68            # Cache miss - compute value
69            logger.debug(f"Cache MISS for {func_name}")
70            result = func(*args, **kwargs)
71            
72            # Store in cache with expiration
73            expiry = expire_hours if expire_hours is not None else config.CACHE_EXPIRY_HOURS
74            expire_seconds = expiry * 3600
75            cache.set(cache_key, result, expire=expire_seconds)
76            
77            return result
78        
79        return wrapper
80    return decorator
81
82
83def clear_cache():
84    """Clear all cached data."""
85    cache.clear()
86    logger.info("Cache cleared")
87
88
89def get_cache_stats() -> dict:
90    """
91    Get cache statistics.
92    
93    Returns:
94        Dictionary with cache stats (size, count, hits, misses)
95    """
96    return {
97        'size_mb': cache.volume() / (1024 * 1024),
98        'count': len(cache),
99        'hits': cache.stats(enable=True)[0],
100        'misses': cache.stats(enable=True)[1]
101    }
102
103
104# Example usage for yfinance data fetching
105def get_cached_market_data(symbol: str, period: str, interval: str):
106    """
107    Fetch market data with caching.
108    This is a helper function that can be used in data loaders.
109    """
110    from datetime import datetime
111    import yfinance as yf
112    
113    @cached(expire_hours=config.CACHE_EXPIRY_HOURS)
114    def _fetch_data(symbol: str, period: str, interval: str, cache_bust: str):
115        logger.info(f"Fetching data for {symbol} ({period}, {interval})")
116        ticker = yf.Ticker(symbol)
117        data = ticker.history(period=period, interval=interval)
118        return data
119    
120    # Cache bust daily to ensure fresh data
121    cache_bust = datetime.now().strftime('%Y-%m-%d')
122    return _fetch_data(symbol, period, interval, cache_bust)
123