bquant 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. bquant/__init__.py +31 -0
  2. bquant/analysis/__init__.py +297 -0
  3. bquant/analysis/candlestick/__init__.py +100 -0
  4. bquant/analysis/chart/__init__.py +98 -0
  5. bquant/analysis/statistical/__init__.py +414 -0
  6. bquant/analysis/statistical/hypothesis_testing.py +657 -0
  7. bquant/analysis/technical/__init__.py +98 -0
  8. bquant/analysis/timeseries/__init__.py +102 -0
  9. bquant/analysis/zones/__init__.py +547 -0
  10. bquant/analysis/zones/sequence_analysis.py +661 -0
  11. bquant/analysis/zones/zone_features.py +520 -0
  12. bquant/cli.py +160 -0
  13. bquant/core/__init__.py +112 -0
  14. bquant/core/cache.py +534 -0
  15. bquant/core/config.py +328 -0
  16. bquant/core/exceptions.py +351 -0
  17. bquant/core/logging_config.py +368 -0
  18. bquant/core/numpy_fix.py +99 -0
  19. bquant/core/performance.py +554 -0
  20. bquant/core/utils.py +327 -0
  21. bquant/data/__init__.py +85 -0
  22. bquant/data/loader.py +436 -0
  23. bquant/data/processor.py +644 -0
  24. bquant/data/samples/__init__.py +442 -0
  25. bquant/data/samples/datasets.py +246 -0
  26. bquant/data/samples/embedded/__init__.py +16 -0
  27. bquant/data/samples/embedded/mt_xauusd_m15.py +9018 -0
  28. bquant/data/samples/embedded/tv_xauusd_1h.py +17018 -0
  29. bquant/data/samples/utils.py +393 -0
  30. bquant/data/schemas.py +285 -0
  31. bquant/data/validator.py +498 -0
  32. bquant/indicators/__init__.py +115 -0
  33. bquant/indicators/base.py +487 -0
  34. bquant/indicators/calculators.py +404 -0
  35. bquant/indicators/library.py +514 -0
  36. bquant/indicators/loaders.py +413 -0
  37. bquant/indicators/macd.py +754 -0
  38. bquant/ml/__init__.py +19 -0
  39. bquant/visualization/__init__.py +316 -0
  40. bquant/visualization/charts.py +616 -0
  41. bquant/visualization/statistical.py +812 -0
  42. bquant/visualization/themes.py +599 -0
  43. bquant/visualization/zones.py +775 -0
  44. bquant-0.0.0.dist-info/METADATA +167 -0
  45. bquant-0.0.0.dist-info/RECORD +49 -0
  46. bquant-0.0.0.dist-info/WHEEL +5 -0
  47. bquant-0.0.0.dist-info/entry_points.txt +2 -0
  48. bquant-0.0.0.dist-info/licenses/LICENSE +21 -0
  49. bquant-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,112 @@
1
+ """
2
+ BQuant Core Module
3
+
4
+ Core functionality including configuration, utilities, and base classes.
5
+ """
6
+
7
+ from .config import (
8
+ get_data_path,
9
+ get_indicator_params,
10
+ validate_timeframe,
11
+ PROJECT_ROOT,
12
+ DATA_DIR,
13
+ DEFAULT_INDICATORS,
14
+ TIMEFRAME_MAPPING
15
+ )
16
+
17
+ from .utils import (
18
+ setup_project_logging,
19
+ calculate_returns,
20
+ normalize_data,
21
+ save_results,
22
+ validate_ohlcv_columns,
23
+ create_timestamp,
24
+ memory_usage_info,
25
+ ensure_directory
26
+ )
27
+
28
+ from .exceptions import (
29
+ BQuantError,
30
+ DataError,
31
+ DataValidationError,
32
+ DataLoadingError,
33
+ DataProcessingError,
34
+ ConfigurationError,
35
+ InvalidTimeframeError,
36
+ InvalidIndicatorParametersError,
37
+ AnalysisError,
38
+ IndicatorCalculationError,
39
+ ZoneAnalysisError,
40
+ StatisticalAnalysisError,
41
+ VisualizationError,
42
+ MLError,
43
+ FileOperationError
44
+ )
45
+
46
+ from .logging_config import (
47
+ setup_logging,
48
+ get_logger,
49
+ log_function_call,
50
+ log_performance,
51
+ LoggingContext
52
+ )
53
+
54
+ from .numpy_fix import (
55
+ apply_numpy_fixes,
56
+ check_numpy_compatibility,
57
+ ensure_numpy_compatibility,
58
+ NaN,
59
+ nan
60
+ )
61
+
62
+ __all__ = [
63
+ # Config
64
+ "get_data_path",
65
+ "get_indicator_params",
66
+ "validate_timeframe",
67
+ "PROJECT_ROOT",
68
+ "DATA_DIR",
69
+ "DEFAULT_INDICATORS",
70
+ "TIMEFRAME_MAPPING",
71
+
72
+ # Utils
73
+ "setup_project_logging",
74
+ "calculate_returns",
75
+ "normalize_data",
76
+ "save_results",
77
+ "validate_ohlcv_columns",
78
+ "create_timestamp",
79
+ "memory_usage_info",
80
+ "ensure_directory",
81
+
82
+ # Exceptions
83
+ "BQuantError",
84
+ "DataError",
85
+ "DataValidationError",
86
+ "DataLoadingError",
87
+ "DataProcessingError",
88
+ "ConfigurationError",
89
+ "InvalidTimeframeError",
90
+ "InvalidIndicatorParametersError",
91
+ "AnalysisError",
92
+ "IndicatorCalculationError",
93
+ "ZoneAnalysisError",
94
+ "StatisticalAnalysisError",
95
+ "VisualizationError",
96
+ "MLError",
97
+ "FileOperationError",
98
+
99
+ # Logging
100
+ "setup_logging",
101
+ "get_logger",
102
+ "log_function_call",
103
+ "log_performance",
104
+ "LoggingContext",
105
+
106
+ # Numpy fix
107
+ "apply_numpy_fixes",
108
+ "check_numpy_compatibility",
109
+ "ensure_numpy_compatibility",
110
+ "NaN",
111
+ "nan"
112
+ ]
bquant/core/cache.py ADDED
@@ -0,0 +1,534 @@
1
+ """
2
+ Система кэширования для BQuant
3
+
4
+ Модуль предоставляет различные стратегии кэширования для оптимизации производительности
5
+ расчетов индикаторов и анализа данных.
6
+ """
7
+
8
+ import hashlib
9
+ import pickle
10
+ import time
11
+ from typing import Any, Dict, Optional, Callable, Union, Tuple
12
+ from functools import wraps
13
+ from pathlib import Path
14
+ import pandas as pd
15
+ import numpy as np
16
+ from dataclasses import dataclass
17
+ from datetime import datetime, timedelta
18
+
19
+ from .logging_config import get_logger
20
+ from .config import get_cache_config
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ @dataclass
26
+ class CacheEntry:
27
+ """
28
+ Запись в кэше.
29
+
30
+ Attributes:
31
+ data: Кэшированные данные
32
+ timestamp: Время создания записи
33
+ hits: Количество обращений к записи
34
+ expiry: Время истечения записи (None = не истекает)
35
+ size_bytes: Размер данных в байтах
36
+ metadata: Дополнительные метаданные
37
+ """
38
+ data: Any
39
+ timestamp: datetime
40
+ hits: int = 0
41
+ expiry: Optional[datetime] = None
42
+ size_bytes: int = 0
43
+ metadata: Dict[str, Any] = None
44
+
45
+ def __post_init__(self):
46
+ if self.metadata is None:
47
+ self.metadata = {}
48
+
49
+ # Приблизительный расчет размера
50
+ if self.size_bytes == 0:
51
+ self.size_bytes = self._estimate_size()
52
+
53
+ def _estimate_size(self) -> int:
54
+ """Приблизительная оценка размера данных."""
55
+ try:
56
+ if isinstance(self.data, pd.DataFrame):
57
+ return self.data.memory_usage(deep=True).sum()
58
+ elif isinstance(self.data, pd.Series):
59
+ return self.data.memory_usage(deep=True)
60
+ elif isinstance(self.data, np.ndarray):
61
+ return self.data.nbytes
62
+ else:
63
+ return len(pickle.dumps(self.data))
64
+ except Exception:
65
+ return 1024 # Fallback размер
66
+
67
+ def is_expired(self) -> bool:
68
+ """Проверяет, истекла ли запись."""
69
+ if self.expiry is None:
70
+ return False
71
+ return datetime.now() > self.expiry
72
+
73
+ def touch(self):
74
+ """Обновляет счетчик обращений."""
75
+ self.hits += 1
76
+
77
+
78
+ class MemoryCache:
79
+ """
80
+ Кэш в памяти с поддержкой TTL и LRU эвикции.
81
+ """
82
+
83
+ def __init__(self, max_size: int = 100, default_ttl: int = 3600):
84
+ """
85
+ Инициализация кэша.
86
+
87
+ Args:
88
+ max_size: Максимальное количество записей
89
+ default_ttl: Время жизни записи по умолчанию (секунды)
90
+ """
91
+ self.max_size = max_size
92
+ self.default_ttl = default_ttl
93
+ self._cache: Dict[str, CacheEntry] = {}
94
+ self._access_order: List[str] = []
95
+ self.logger = get_logger(f"{__name__}.MemoryCache")
96
+
97
+ # Статистика
98
+ self._hits = 0
99
+ self._misses = 0
100
+ self._evictions = 0
101
+
102
+ def _generate_key(self, func_name: str, args: tuple, kwargs: dict) -> str:
103
+ """Генерирует ключ кэша на основе функции и аргументов."""
104
+ # Создаем строку для хэширования
105
+ key_parts = [func_name]
106
+
107
+ # Добавляем args
108
+ for arg in args:
109
+ if isinstance(arg, pd.DataFrame):
110
+ # Для DataFrame используем хэш данных
111
+ key_parts.append(pd.util.hash_pandas_object(arg).sum())
112
+ elif isinstance(arg, (pd.Series, np.ndarray)):
113
+ key_parts.append(str(hash(arg.tobytes() if hasattr(arg, 'tobytes') else str(arg))))
114
+ else:
115
+ key_parts.append(str(hash(str(arg))))
116
+
117
+ # Добавляем kwargs
118
+ sorted_kwargs = sorted(kwargs.items())
119
+ for k, v in sorted_kwargs:
120
+ key_parts.append(f"{k}={hash(str(v))}")
121
+
122
+ # Создаем хэш
123
+ key_string = "|".join(str(part) for part in key_parts)
124
+ return hashlib.md5(key_string.encode()).hexdigest()
125
+
126
+ def get(self, key: str) -> Optional[Any]:
127
+ """Получить значение из кэша."""
128
+ if key not in self._cache:
129
+ self._misses += 1
130
+ return None
131
+
132
+ entry = self._cache[key]
133
+
134
+ # Проверяем истечение
135
+ if entry.is_expired():
136
+ self.logger.debug(f"Cache entry expired: {key}")
137
+ del self._cache[key]
138
+ if key in self._access_order:
139
+ self._access_order.remove(key)
140
+ self._misses += 1
141
+ return None
142
+
143
+ # Обновляем статистику и порядок доступа
144
+ entry.touch()
145
+ self._hits += 1
146
+
147
+ # Перемещаем в конец списка (LRU)
148
+ if key in self._access_order:
149
+ self._access_order.remove(key)
150
+ self._access_order.append(key)
151
+
152
+ self.logger.debug(f"Cache hit: {key}")
153
+ return entry.data
154
+
155
+ def put(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
156
+ """Сохранить значение в кэше."""
157
+ # Определяем время истечения
158
+ expiry = None
159
+ if ttl is not None or self.default_ttl > 0:
160
+ ttl_seconds = ttl if ttl is not None else self.default_ttl
161
+ expiry = datetime.now() + timedelta(seconds=ttl_seconds)
162
+
163
+ # Создаем запись
164
+ entry = CacheEntry(
165
+ data=value,
166
+ timestamp=datetime.now(),
167
+ expiry=expiry
168
+ )
169
+
170
+ # Проверяем, нужна ли эвикция
171
+ if len(self._cache) >= self.max_size and key not in self._cache:
172
+ self._evict_lru()
173
+
174
+ # Сохраняем запись
175
+ self._cache[key] = entry
176
+
177
+ # Обновляем порядок доступа
178
+ if key in self._access_order:
179
+ self._access_order.remove(key)
180
+ self._access_order.append(key)
181
+
182
+ self.logger.debug(f"Cache put: {key}, expires: {expiry}")
183
+
184
+ def _evict_lru(self) -> None:
185
+ """Удаляет наименее недавно использованную запись."""
186
+ if not self._access_order:
187
+ return
188
+
189
+ lru_key = self._access_order[0]
190
+ del self._cache[lru_key]
191
+ self._access_order.remove(lru_key)
192
+ self._evictions += 1
193
+
194
+ self.logger.debug(f"Evicted LRU entry: {lru_key}")
195
+
196
+ def invalidate(self, key: str) -> bool:
197
+ """Удалить запись из кэша."""
198
+ if key in self._cache:
199
+ del self._cache[key]
200
+ if key in self._access_order:
201
+ self._access_order.remove(key)
202
+ self.logger.debug(f"Invalidated cache entry: {key}")
203
+ return True
204
+ return False
205
+
206
+ def clear(self) -> None:
207
+ """Очистить весь кэш."""
208
+ count = len(self._cache)
209
+ self._cache.clear()
210
+ self._access_order.clear()
211
+ self.logger.info(f"Cleared cache: {count} entries removed")
212
+
213
+ def cleanup_expired(self) -> int:
214
+ """Удалить истекшие записи."""
215
+ expired_keys = []
216
+ for key, entry in self._cache.items():
217
+ if entry.is_expired():
218
+ expired_keys.append(key)
219
+
220
+ for key in expired_keys:
221
+ self.invalidate(key)
222
+
223
+ if expired_keys:
224
+ self.logger.info(f"Cleaned up {len(expired_keys)} expired entries")
225
+
226
+ return len(expired_keys)
227
+
228
+ def stats(self) -> Dict[str, Any]:
229
+ """Получить статистику кэша."""
230
+ total_requests = self._hits + self._misses
231
+ hit_rate = (self._hits / total_requests * 100) if total_requests > 0 else 0
232
+
233
+ total_size = sum(entry.size_bytes for entry in self._cache.values())
234
+
235
+ return {
236
+ 'entries': len(self._cache),
237
+ 'max_size': self.max_size,
238
+ 'hits': self._hits,
239
+ 'misses': self._misses,
240
+ 'hit_rate': hit_rate,
241
+ 'evictions': self._evictions,
242
+ 'total_size_bytes': total_size,
243
+ 'avg_size_bytes': total_size / len(self._cache) if self._cache else 0
244
+ }
245
+
246
+
247
+ class DiskCache:
248
+ """
249
+ Кэш на диске для долгосрочного хранения результатов.
250
+ """
251
+
252
+ def __init__(self, cache_dir: Union[str, Path] = None):
253
+ """
254
+ Инициализация дискового кэша.
255
+
256
+ Args:
257
+ cache_dir: Директория для кэша (по умолчанию .cache/bquant)
258
+ """
259
+ if cache_dir is None:
260
+ cache_dir = Path.home() / ".cache" / "bquant"
261
+
262
+ self.cache_dir = Path(cache_dir)
263
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
264
+
265
+ self.logger = get_logger(f"{__name__}.DiskCache")
266
+ self.logger.info(f"Disk cache initialized: {self.cache_dir}")
267
+
268
+ def _get_file_path(self, key: str) -> Path:
269
+ """Получить путь к файлу кэша."""
270
+ return self.cache_dir / f"{key}.pkl"
271
+
272
+ def get(self, key: str) -> Optional[Any]:
273
+ """Получить значение из дискового кэша."""
274
+ file_path = self._get_file_path(key)
275
+
276
+ if not file_path.exists():
277
+ return None
278
+
279
+ try:
280
+ with open(file_path, 'rb') as f:
281
+ entry = pickle.load(f)
282
+
283
+ if entry.is_expired():
284
+ file_path.unlink()
285
+ return None
286
+
287
+ entry.touch()
288
+ return entry.data
289
+
290
+ except Exception as e:
291
+ self.logger.warning(f"Failed to load from disk cache {key}: {e}")
292
+ # Удаляем поврежденный файл
293
+ if file_path.exists():
294
+ file_path.unlink()
295
+ return None
296
+
297
+ def put(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
298
+ """Сохранить значение в дисковый кэш."""
299
+ file_path = self._get_file_path(key)
300
+
301
+ # Определяем время истечения
302
+ expiry = None
303
+ if ttl is not None:
304
+ expiry = datetime.now() + timedelta(seconds=ttl)
305
+
306
+ # Создаем запись
307
+ entry = CacheEntry(
308
+ data=value,
309
+ timestamp=datetime.now(),
310
+ expiry=expiry
311
+ )
312
+
313
+ try:
314
+ with open(file_path, 'wb') as f:
315
+ pickle.dump(entry, f)
316
+
317
+ self.logger.debug(f"Saved to disk cache: {key}")
318
+ return True
319
+
320
+ except Exception as e:
321
+ self.logger.error(f"Failed to save to disk cache {key}: {e}")
322
+ return False
323
+
324
+ def invalidate(self, key: str) -> bool:
325
+ """Удалить файл из дискового кэша."""
326
+ file_path = self._get_file_path(key)
327
+
328
+ if file_path.exists():
329
+ file_path.unlink()
330
+ self.logger.debug(f"Removed from disk cache: {key}")
331
+ return True
332
+ return False
333
+
334
+ def clear(self) -> int:
335
+ """Очистить весь дисковый кэш."""
336
+ count = 0
337
+ for file_path in self.cache_dir.glob("*.pkl"):
338
+ file_path.unlink()
339
+ count += 1
340
+
341
+ self.logger.info(f"Cleared disk cache: {count} files removed")
342
+ return count
343
+
344
+ def cleanup_expired(self) -> int:
345
+ """Удалить истекшие файлы."""
346
+ expired_count = 0
347
+
348
+ for file_path in self.cache_dir.glob("*.pkl"):
349
+ try:
350
+ with open(file_path, 'rb') as f:
351
+ entry = pickle.load(f)
352
+
353
+ if entry.is_expired():
354
+ file_path.unlink()
355
+ expired_count += 1
356
+
357
+ except Exception:
358
+ # Удаляем поврежденные файлы
359
+ file_path.unlink()
360
+ expired_count += 1
361
+
362
+ if expired_count > 0:
363
+ self.logger.info(f"Cleaned up {expired_count} expired disk cache entries")
364
+
365
+ return expired_count
366
+
367
+
368
+ class CacheManager:
369
+ """
370
+ Менеджер кэширования, объединяющий память и диск.
371
+ """
372
+
373
+ def __init__(self, memory_size: int = 100, disk_cache: bool = True):
374
+ """
375
+ Инициализация менеджера кэша.
376
+
377
+ Args:
378
+ memory_size: Размер кэша в памяти
379
+ disk_cache: Использовать ли дисковый кэш
380
+ """
381
+ self.memory_cache = MemoryCache(max_size=memory_size)
382
+ self.disk_cache = DiskCache() if disk_cache else None
383
+ self.logger = get_logger(f"{__name__}.CacheManager")
384
+
385
+ def get(self, key: str) -> Optional[Any]:
386
+ """Получить значение из кэша (сначала память, потом диск)."""
387
+ # Сначала проверяем память
388
+ result = self.memory_cache.get(key)
389
+ if result is not None:
390
+ return result
391
+
392
+ # Потом диск
393
+ if self.disk_cache:
394
+ result = self.disk_cache.get(key)
395
+ if result is not None:
396
+ # Сохраняем в память для быстрого доступа
397
+ self.memory_cache.put(key, result)
398
+ return result
399
+
400
+ return None
401
+
402
+ def put(self, key: str, value: Any, ttl: Optional[int] = None, disk: bool = True) -> None:
403
+ """Сохранить значение в кэш."""
404
+ # Всегда сохраняем в память
405
+ self.memory_cache.put(key, value, ttl)
406
+
407
+ # Сохраняем на диск если разрешено
408
+ if disk and self.disk_cache:
409
+ self.disk_cache.put(key, value, ttl)
410
+
411
+ def invalidate(self, key: str) -> None:
412
+ """Удалить из всех кэшей."""
413
+ self.memory_cache.invalidate(key)
414
+ if self.disk_cache:
415
+ self.disk_cache.invalidate(key)
416
+
417
+ def clear(self) -> None:
418
+ """Очистить все кэши."""
419
+ self.memory_cache.clear()
420
+ if self.disk_cache:
421
+ self.disk_cache.clear()
422
+
423
+ def cleanup(self) -> Dict[str, int]:
424
+ """Очистить истекшие записи."""
425
+ memory_cleaned = self.memory_cache.cleanup_expired()
426
+ disk_cleaned = self.disk_cache.cleanup_expired() if self.disk_cache else 0
427
+
428
+ return {
429
+ 'memory_cleaned': memory_cleaned,
430
+ 'disk_cleaned': disk_cleaned
431
+ }
432
+
433
+ def stats(self) -> Dict[str, Any]:
434
+ """Получить общую статистику."""
435
+ stats = {
436
+ 'memory': self.memory_cache.stats()
437
+ }
438
+
439
+ if self.disk_cache:
440
+ disk_files = len(list(self.disk_cache.cache_dir.glob("*.pkl")))
441
+ stats['disk'] = {
442
+ 'entries': disk_files,
443
+ 'cache_dir': str(self.disk_cache.cache_dir)
444
+ }
445
+
446
+ return stats
447
+
448
+
449
+ # Глобальный менеджер кэша
450
+ _global_cache_manager: Optional[CacheManager] = None
451
+
452
+
453
+ def get_cache_manager() -> CacheManager:
454
+ """Получить глобальный менеджер кэша."""
455
+ global _global_cache_manager
456
+
457
+ if _global_cache_manager is None:
458
+ cache_config = get_cache_config()
459
+ _global_cache_manager = CacheManager(
460
+ memory_size=cache_config.get('memory_size', 100),
461
+ disk_cache=cache_config.get('enable_disk_cache', True)
462
+ )
463
+
464
+ return _global_cache_manager
465
+
466
+
467
+ def cached(ttl: Optional[int] = None, disk: bool = True, key_prefix: str = ""):
468
+ """
469
+ Декоратор для кэширования результатов функций.
470
+
471
+ Args:
472
+ ttl: Время жизни кэша в секундах
473
+ disk: Сохранять ли на диск
474
+ key_prefix: Префикс для ключа кэша
475
+ """
476
+ def decorator(func: Callable) -> Callable:
477
+ @wraps(func)
478
+ def wrapper(*args, **kwargs):
479
+ cache_manager = get_cache_manager()
480
+
481
+ # Генерируем ключ
482
+ func_name = f"{key_prefix}{func.__module__}.{func.__name__}"
483
+ key = cache_manager.memory_cache._generate_key(func_name, args, kwargs)
484
+
485
+ # Проверяем кэш
486
+ result = cache_manager.get(key)
487
+ if result is not None:
488
+ logger.debug(f"Cache hit for {func_name}")
489
+ return result
490
+
491
+ # Вычисляем результат
492
+ logger.debug(f"Cache miss for {func_name}, calculating...")
493
+ result = func(*args, **kwargs)
494
+
495
+ # Сохраняем в кэш
496
+ cache_manager.put(key, result, ttl, disk)
497
+
498
+ return result
499
+
500
+ return wrapper
501
+ return decorator
502
+
503
+
504
+ def cache_key(*args, **kwargs) -> str:
505
+ """Генерирует ключ кэша для заданных аргументов."""
506
+ cache_manager = get_cache_manager()
507
+ return cache_manager.memory_cache._generate_key("manual_key", args, kwargs)
508
+
509
+
510
+ def clear_cache():
511
+ """Очистить глобальный кэш."""
512
+ global _global_cache_manager
513
+ if _global_cache_manager:
514
+ _global_cache_manager.clear()
515
+
516
+
517
+ def cache_stats() -> Dict[str, Any]:
518
+ """Получить статистику глобального кэша."""
519
+ cache_manager = get_cache_manager()
520
+ return cache_manager.stats()
521
+
522
+
523
+ # Экспорт
524
+ __all__ = [
525
+ 'CacheEntry',
526
+ 'MemoryCache',
527
+ 'DiskCache',
528
+ 'CacheManager',
529
+ 'get_cache_manager',
530
+ 'cached',
531
+ 'cache_key',
532
+ 'clear_cache',
533
+ 'cache_stats'
534
+ ]