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
bquant/core/config.py ADDED
@@ -0,0 +1,328 @@
1
+ """
2
+ Configuration settings for the BQuant Project.
3
+
4
+ Universal configuration supporting multiple instruments, indicators, and analysis methods.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Dict, Any, Optional
10
+
11
+ # ============================================================================
12
+ # PROJECT STRUCTURE
13
+ # ============================================================================
14
+
15
+ PROJECT_ROOT = Path(__file__).parent.parent.parent
16
+ DATA_DIR = PROJECT_ROOT / "data"
17
+ SCRIPTS_DIR = PROJECT_ROOT / "scripts"
18
+ NOTEBOOKS_DIR = PROJECT_ROOT / "research" / "notebooks"
19
+ ALLDATA_DIR = DATA_DIR / "alldata"
20
+ PROCESSED_DATA_DIR = DATA_DIR / "processed"
21
+ RESULTS_DIR = PROJECT_ROOT / "results"
22
+
23
+ # Создаем директории если их нет
24
+ for directory in [PROCESSED_DATA_DIR, RESULTS_DIR, PROJECT_ROOT / "logs"]:
25
+ directory.mkdir(exist_ok=True, parents=True)
26
+
27
+ # ============================================================================
28
+ # UNIVERSAL DATA CONFIGURATION
29
+ # ============================================================================
30
+
31
+ # ============================================================================
32
+ # TIMEFRAME MAPPING CONFIGURATION
33
+ # ============================================================================
34
+
35
+ # Маппинг таймфреймов для разных поставщиков данных
36
+ TIMEFRAME_MAPPING = {
37
+ 'tradingview': {
38
+ # Внутричасовые таймфреймы (минуты)
39
+ '1m': '1', '2m': '2', '3m': '3', '4m': '4', '5m': '5', '6m': '6', '10m': '10',
40
+ '12m': '12', '15m': '15', '20m': '20', '30m': '30', '45m': '45',
41
+ # Часовые таймфреймы
42
+ '1h': '60', '2h': '120', '3h': '180', '4h': '240', '6h': '360', '8h': '480', '12h': '720',
43
+ # Дневные и выше
44
+ '1d': '1D', '1D': '1D', '1w': '1W', '1W': '1W', '1M': '1M', '3M': '3M', '6M': '6M', '12M': '12M'
45
+ },
46
+ 'metatrader': {
47
+ # Внутричасовые таймфреймы (минуты)
48
+ '1m': 'M1', '2m': 'M2', '3m': 'M3', '4m': 'M4', '5m': 'M5', '6m': 'M6', '10m': 'M10',
49
+ '12m': 'M12', '15m': 'M15', '20m': 'M20', '30m': 'M30', '45m': 'M45',
50
+ # Часовые таймфреймы
51
+ '1h': 'H1', '2h': 'H2', '3h': 'H3', '4h': 'H4', '6h': 'H6', '8h': 'H8', '12h': 'H12',
52
+ # Дневные и выше
53
+ '1d': 'Daily', '1D': 'Daily', '1w': 'Weekly', '1W': 'Weekly', '1M': 'Monthly', '3M': 'Monthly', '6M': 'Monthly', '12M': 'Monthly'
54
+ }
55
+ }
56
+
57
+ # Универсальные паттерны файлов для разных поставщиков
58
+ DATA_FILE_PATTERNS = {
59
+ 'tradingview': {
60
+ 'oanda': "OANDA_{symbol}, {timeframe}.csv",
61
+ 'forexcom': "FOREXCOM_{symbol}, {timeframe}.csv",
62
+ 'icmarkets': "ICMARKETS_{symbol}, {timeframe}.csv",
63
+ 'default': "OANDA_{symbol}, {timeframe}.csv" # По умолчанию OANDA
64
+ },
65
+ 'metatrader': {
66
+ 'default': "{symbol}{timeframe}.csv"
67
+ },
68
+ 'generic': {
69
+ 'default': "{symbol}_{timeframe}.csv"
70
+ },
71
+ 'custom': {
72
+ 'default': "{symbol}_{timeframe}_{source}.csv"
73
+ }
74
+ }
75
+
76
+ # Поддерживаемые таймфреймы (универсальные)
77
+ SUPPORTED_TIMEFRAMES = {
78
+ # Внутричасовые таймфреймы (минуты)
79
+ '1m': '1 minute', '2m': '2 minutes', '3m': '3 minutes', '4m': '4 minutes',
80
+ '5m': '5 minutes', '6m': '6 minutes', '10m': '10 minutes', '12m': '12 minutes',
81
+ '15m': '15 minutes', '20m': '20 minutes', '30m': '30 minutes', '45m': '45 minutes',
82
+ # Часовые таймфреймы
83
+ '1h': '1 hour', '2h': '2 hours', '3h': '3 hours', '4h': '4 hours',
84
+ '6h': '6 hours', '8h': '8 hours', '12h': '12 hours',
85
+ # Дневные и выше
86
+ '1d': '1 day', '1D': '1 day', '1w': '1 week', '1W': '1 week',
87
+ '1M': '1 month', '3M': '3 months', '6M': '6 months', '12M': '12 months'
88
+ }
89
+
90
+ # Настройки валидации данных
91
+ DATA_VALIDATION = {
92
+ 'required_columns': ['open', 'high', 'low', 'close'],
93
+ 'optional_columns': ['volume'],
94
+ 'min_records': 100,
95
+ 'max_missing_ratio': 0.1,
96
+ 'outlier_threshold': 3.0
97
+ }
98
+
99
+ # Настройки кэширования
100
+ CACHE_CONFIG = {
101
+ 'enable_memory_cache': True,
102
+ 'enable_disk_cache': True,
103
+ 'memory_size': 100, # Количество записей в памяти
104
+ 'default_ttl': 3600, # Время жизни по умолчанию (секунды)
105
+ 'cache_dir': None, # None = используется ~/.cache/bquant
106
+ 'auto_cleanup': True, # Автоматическая очистка истекших записей
107
+ 'cleanup_interval': 300 # Интервал очистки в секундах
108
+ }
109
+
110
+ # ============================================================================
111
+ # UNIVERSAL INDICATOR CONFIGURATION
112
+ # ============================================================================
113
+
114
+ # Универсальные параметры индикаторов
115
+ DEFAULT_INDICATORS = {
116
+ 'macd': {
117
+ 'fast': 12,
118
+ 'slow': 26,
119
+ 'signal': 9
120
+ },
121
+ 'rsi': {
122
+ 'length': 14
123
+ },
124
+ 'bollinger_bands': {
125
+ 'length': 20,
126
+ 'std': 2
127
+ },
128
+ 'atr': {
129
+ 'length': 14
130
+ },
131
+ 'sma': {
132
+ 'length': 20
133
+ },
134
+ 'ema': {
135
+ 'length': 20
136
+ },
137
+ 'stochastic': {
138
+ 'k_length': 14,
139
+ 'd_length': 3
140
+ },
141
+ 'williams_r': {
142
+ 'length': 14
143
+ }
144
+ }
145
+
146
+ # ============================================================================
147
+ # UNIVERSAL ANALYSIS CONFIGURATION
148
+ # ============================================================================
149
+
150
+ # Универсальные параметры анализа
151
+ ANALYSIS_CONFIG = {
152
+ 'zone_analysis': {
153
+ 'min_duration': 2,
154
+ 'min_amplitude': 0.001,
155
+ 'normalization_method': 'atr', # 'atr', 'price', 'none'
156
+ 'detection_method': 'sign_change' # 'sign_change', 'threshold', 'custom'
157
+ },
158
+ 'pattern_analysis': {
159
+ 'min_pattern_length': 3,
160
+ 'max_pattern_length': 50,
161
+ 'similarity_threshold': 0.8
162
+ },
163
+ 'statistical_analysis': {
164
+ 'confidence_level': 0.95,
165
+ 'significance_level': 0.05,
166
+ 'bootstrap_samples': 1000,
167
+ 'random_state': 42
168
+ }
169
+ }
170
+
171
+ # ============================================================================
172
+ # MACHINE LEARNING CONFIGURATION
173
+ # ============================================================================
174
+
175
+ ML_CONFIG = {
176
+ 'random_state': 42,
177
+ 'test_size': 0.3,
178
+ 'validation_size': 0.2,
179
+ 'cross_validation_folds': 5,
180
+ 'feature_selection_method': 'mutual_info',
181
+ 'hyperparameter_tuning': True
182
+ }
183
+
184
+ # ============================================================================
185
+ # VISUALIZATION CONFIGURATION
186
+ # ============================================================================
187
+
188
+ VISUALIZATION = {
189
+ 'figure_size': (12, 8),
190
+ 'dpi': 100,
191
+ 'style': 'seaborn-v0_8',
192
+ 'color_palette': 'viridis',
193
+ 'save_format': 'png',
194
+ 'save_dpi': 300
195
+ }
196
+
197
+ # ============================================================================
198
+ # LOGGING CONFIGURATION
199
+ # ============================================================================
200
+
201
+ LOGGING = {
202
+ 'level': "INFO",
203
+ 'format': "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
204
+ 'file_logging': True,
205
+ 'log_file': PROJECT_ROOT / "logs" / "bquant.log"
206
+ }
207
+
208
+ # ============================================================================
209
+ # EXPERIMENT CONFIGURATION
210
+ # ============================================================================
211
+
212
+ EXPERIMENT_CONFIG = {
213
+ 'results_dir': RESULTS_DIR,
214
+ 'save_intermediate': True,
215
+ 'experiment_naming': 'timestamp', # 'timestamp', 'custom', 'auto'
216
+ 'version_control': True
217
+ }
218
+
219
+ # ============================================================================
220
+ # CONFIGURATION MANAGEMENT FUNCTIONS
221
+ # ============================================================================
222
+
223
+ def get_data_path(symbol: str, timeframe: str, data_source: str = 'tradingview', quote_provider: str = 'default') -> Path:
224
+ """
225
+ Generate data file path based on symbol and timeframe for different data providers.
226
+
227
+ Args:
228
+ symbol: Trading symbol (e.g., 'XAUUSD', 'EURUSD')
229
+ timeframe: Timeframe (e.g., '1h', '1d', '5m')
230
+ data_source: Data source ('tradingview', 'metatrader', 'generic', 'custom')
231
+ quote_provider: Quote provider for the data source (e.g., 'oanda', 'forexcom', 'icmarkets')
232
+
233
+ Returns:
234
+ Path to data file
235
+ """
236
+ # Validate timeframe first
237
+ timeframe = validate_timeframe(timeframe)
238
+
239
+ # Get mapped timeframe for the specific data source
240
+ if data_source in TIMEFRAME_MAPPING:
241
+ mapped_timeframe = TIMEFRAME_MAPPING[data_source].get(timeframe, timeframe)
242
+ else:
243
+ mapped_timeframe = timeframe
244
+
245
+ # Get file pattern for the data source and quote provider
246
+ source_patterns = DATA_FILE_PATTERNS.get(data_source, DATA_FILE_PATTERNS['generic'])
247
+
248
+ if isinstance(source_patterns, dict):
249
+ # New format with quote providers
250
+ pattern = source_patterns.get(quote_provider, source_patterns.get('default', source_patterns))
251
+ else:
252
+ # Legacy format (backward compatibility)
253
+ pattern = source_patterns
254
+
255
+ # Generate filename
256
+ filename = pattern.format(symbol=symbol, timeframe=mapped_timeframe)
257
+ return DATA_DIR / filename
258
+
259
+ def get_indicator_params(indicator: str, **overrides) -> Dict[str, Any]:
260
+ """
261
+ Get indicator parameters with optional overrides.
262
+
263
+ Args:
264
+ indicator: Indicator name ('macd', 'rsi', etc.)
265
+ **overrides: Parameter overrides
266
+
267
+ Returns:
268
+ Dictionary with indicator parameters
269
+ """
270
+ params = DEFAULT_INDICATORS.get(indicator, {}).copy()
271
+ params.update(overrides)
272
+ return params
273
+
274
+ def get_analysis_params(analysis_type: str, **overrides) -> Dict[str, Any]:
275
+ """
276
+ Get analysis parameters with optional overrides.
277
+
278
+ Args:
279
+ analysis_type: Analysis type ('zone_analysis', 'pattern_analysis', etc.)
280
+ **overrides: Parameter overrides
281
+
282
+ Returns:
283
+ Dictionary with analysis parameters
284
+ """
285
+ params = ANALYSIS_CONFIG.get(analysis_type, {}).copy()
286
+ params.update(overrides)
287
+ return params
288
+
289
+ def validate_timeframe(timeframe: str) -> str:
290
+ """
291
+ Validate and normalize timeframe.
292
+
293
+ Args:
294
+ timeframe: Timeframe string
295
+
296
+ Returns:
297
+ Validated timeframe
298
+
299
+ Raises:
300
+ ValueError: If timeframe is not supported
301
+ """
302
+ if timeframe not in SUPPORTED_TIMEFRAMES:
303
+ raise ValueError(f"Unsupported timeframe: {timeframe}. Supported: {list(SUPPORTED_TIMEFRAMES.keys())}")
304
+ return timeframe
305
+
306
+ def get_results_path(experiment_name: str, file_type: str = 'csv') -> Path:
307
+ """
308
+ Generate results file path.
309
+
310
+ Args:
311
+ experiment_name: Name of the experiment
312
+ file_type: File extension
313
+
314
+ Returns:
315
+ Path to results file
316
+ """
317
+ filename = f"{experiment_name}.{file_type}"
318
+ return RESULTS_DIR / filename
319
+
320
+
321
+ def get_cache_config() -> Dict[str, Any]:
322
+ """
323
+ Получить конфигурацию кэширования.
324
+
325
+ Returns:
326
+ Словарь с настройками кэширования
327
+ """
328
+ return CACHE_CONFIG.copy()
@@ -0,0 +1,351 @@
1
+ """
2
+ Исключения системы BQuant
3
+
4
+ Определение кастомных исключений для различных компонентов системы.
5
+ """
6
+
7
+ from typing import Optional, Any, Dict
8
+
9
+
10
+ class BQuantError(Exception):
11
+ """
12
+ Базовое исключение BQuant
13
+
14
+ Все исключения BQuant должны наследоваться от этого класса.
15
+ """
16
+
17
+ def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
18
+ """
19
+ Args:
20
+ message: Сообщение об ошибке
21
+ details: Дополнительные детали ошибки
22
+ """
23
+ super().__init__(message)
24
+ self.message = message
25
+ self.details = details or {}
26
+
27
+ def __str__(self) -> str:
28
+ if self.details:
29
+ details_str = ', '.join(f"{k}={v}" for k, v in self.details.items())
30
+ return f"{self.message} (Details: {details_str})"
31
+ return self.message
32
+
33
+
34
+ class DataError(BQuantError):
35
+ """
36
+ Ошибка данных
37
+
38
+ Возникает при проблемах с загрузкой, обработкой или валидацией данных.
39
+ """
40
+ pass
41
+
42
+
43
+ class DataValidationError(DataError):
44
+ """
45
+ Ошибка валидации данных
46
+
47
+ Возникает когда данные не соответствуют ожидаемому формату или содержат ошибки.
48
+ """
49
+ pass
50
+
51
+
52
+ class DataLoadingError(DataError):
53
+ """
54
+ Ошибка загрузки данных
55
+
56
+ Возникает при невозможности загрузить данные из источника.
57
+ """
58
+ pass
59
+
60
+
61
+ class DataProcessingError(DataError):
62
+ """
63
+ Ошибка обработки данных
64
+
65
+ Возникает при ошибках во время обработки или трансформации данных.
66
+ """
67
+ pass
68
+
69
+
70
+ class ConfigurationError(BQuantError):
71
+ """
72
+ Ошибка конфигурации
73
+
74
+ Возникает при проблемах с конфигурационными параметрами.
75
+ """
76
+ pass
77
+
78
+
79
+ class InvalidTimeframeError(ConfigurationError):
80
+ """
81
+ Ошибка неверного таймфрейма
82
+
83
+ Возникает при использовании неподдерживаемого таймфрейма.
84
+ """
85
+ pass
86
+
87
+
88
+ class InvalidIndicatorParametersError(ConfigurationError):
89
+ """
90
+ Ошибка неверных параметров индикатора
91
+
92
+ Возникает при передаче некорректных параметров для расчета индикатора.
93
+ """
94
+ pass
95
+
96
+
97
+ class AnalysisError(BQuantError):
98
+ """
99
+ Ошибка анализа
100
+
101
+ Возникает при проблемах в аналитических модулях.
102
+ """
103
+ pass
104
+
105
+
106
+ class IndicatorCalculationError(AnalysisError):
107
+ """
108
+ Ошибка расчета индикатора
109
+
110
+ Возникает при невозможности рассчитать технический индикатор.
111
+ """
112
+ pass
113
+
114
+
115
+ class ZoneAnalysisError(AnalysisError):
116
+ """
117
+ Ошибка анализа зон
118
+
119
+ Возникает при проблемах в процессе анализа зон MACD или других индикаторов.
120
+ """
121
+ pass
122
+
123
+
124
+ class StatisticalAnalysisError(AnalysisError):
125
+ """
126
+ Ошибка статистического анализа
127
+
128
+ Возникает при проблемах в статистических тестах или расчетах.
129
+ """
130
+ pass
131
+
132
+
133
+ class VisualizationError(BQuantError):
134
+ """
135
+ Ошибка визуализации
136
+
137
+ Возникает при проблемах с созданием графиков или визуализаций.
138
+ """
139
+ pass
140
+
141
+
142
+ class MLError(BQuantError):
143
+ """
144
+ Ошибка машинного обучения
145
+
146
+ Возникает при проблемах в ML модулях (пока в разработке).
147
+ """
148
+ pass
149
+
150
+
151
+ class FeatureExtractionError(MLError):
152
+ """
153
+ Ошибка извлечения признаков
154
+
155
+ Возникает при проблемах с извлечением признаков для ML.
156
+ """
157
+ pass
158
+
159
+
160
+ class ModelTrainingError(MLError):
161
+ """
162
+ Ошибка обучения модели
163
+
164
+ Возникает при проблемах в процессе обучения ML модели.
165
+ """
166
+ pass
167
+
168
+
169
+ class FileOperationError(BQuantError):
170
+ """
171
+ Ошибка файловых операций
172
+
173
+ Возникает при проблемах с чтением/записью файлов.
174
+ """
175
+ pass
176
+
177
+
178
+ class NotImplementedError(BQuantError):
179
+ """
180
+ Ошибка незавершенной функциональности
181
+
182
+ Возникает при обращении к функциональности, которая еще не реализована.
183
+ """
184
+ pass
185
+
186
+
187
+ # Вспомогательные функции для создания исключений
188
+
189
+ def create_data_validation_error(
190
+ message: str,
191
+ column: Optional[str] = None,
192
+ expected_type: Optional[str] = None,
193
+ actual_type: Optional[str] = None,
194
+ expected_shape: Optional[tuple] = None,
195
+ actual_shape: Optional[tuple] = None
196
+ ) -> DataValidationError:
197
+ """
198
+ Создать исключение валидации данных с деталями
199
+ """
200
+ details = {}
201
+ if column:
202
+ details['column'] = column
203
+ if expected_type:
204
+ details['expected_type'] = expected_type
205
+ if actual_type:
206
+ details['actual_type'] = actual_type
207
+ if expected_shape:
208
+ details['expected_shape'] = expected_shape
209
+ if actual_shape:
210
+ details['actual_shape'] = actual_shape
211
+
212
+ return DataValidationError(message, details)
213
+
214
+
215
+ def create_indicator_calculation_error(
216
+ indicator_name: str,
217
+ message: str,
218
+ parameters: Optional[Dict[str, Any]] = None,
219
+ data_shape: Optional[tuple] = None
220
+ ) -> IndicatorCalculationError:
221
+ """
222
+ Создать исключение расчета индикатора с деталями
223
+ """
224
+ details = {'indicator': indicator_name}
225
+ if parameters:
226
+ details['parameters'] = parameters
227
+ if data_shape:
228
+ details['data_shape'] = data_shape
229
+
230
+ return IndicatorCalculationError(message, details)
231
+
232
+
233
+ def create_configuration_error(
234
+ parameter_name: str,
235
+ message: str,
236
+ expected_values: Optional[list] = None,
237
+ actual_value: Optional[Any] = None
238
+ ) -> ConfigurationError:
239
+ """
240
+ Создать исключение конфигурации с деталями
241
+ """
242
+ details = {'parameter': parameter_name}
243
+ if expected_values:
244
+ details['expected_values'] = expected_values
245
+ if actual_value is not None:
246
+ details['actual_value'] = actual_value
247
+
248
+ return ConfigurationError(message, details)
249
+
250
+
251
+ # Контекстные менеджеры для обработки исключений
252
+
253
+ class BQuantErrorContext:
254
+ """
255
+ Контекстный менеджер для обработки исключений BQuant
256
+ """
257
+
258
+ def __init__(self, operation: str, logger=None):
259
+ """
260
+ Args:
261
+ operation: Описание операции
262
+ logger: Логгер для записи ошибок
263
+ """
264
+ self.operation = operation
265
+ self.logger = logger
266
+
267
+ def __enter__(self):
268
+ return self
269
+
270
+ def __exit__(self, exc_type, exc_val, exc_tb):
271
+ if exc_type is not None:
272
+ if self.logger:
273
+ self.logger.error(f"Ошибка в операции '{self.operation}': {exc_val}")
274
+
275
+ # Если это не наше исключение, оборачиваем его
276
+ if not isinstance(exc_val, BQuantError):
277
+ if exc_type in (ValueError, TypeError):
278
+ # Конвертируем стандартные исключения в наши
279
+ raise ConfigurationError(
280
+ f"Ошибка в операции '{self.operation}': {exc_val}"
281
+ ) from exc_val
282
+ else:
283
+ # Оборачиваем все остальные в базовое исключение
284
+ raise BQuantError(
285
+ f"Неожиданная ошибка в операции '{self.operation}': {exc_val}"
286
+ ) from exc_val
287
+
288
+ return False # Не подавляем исключение
289
+
290
+
291
+ # Валидаторы с исключениями
292
+
293
+ def validate_timeframe(timeframe: str, supported_timeframes: list):
294
+ """
295
+ Валидировать таймфрейм с выбросом исключения
296
+ """
297
+ if timeframe not in supported_timeframes:
298
+ raise InvalidTimeframeError(
299
+ f"Неподдерживаемый таймфрейм: {timeframe}",
300
+ {
301
+ 'timeframe': timeframe,
302
+ 'supported_timeframes': supported_timeframes
303
+ }
304
+ )
305
+
306
+
307
+ def validate_indicator_parameters(indicator: str, parameters: Dict[str, Any], required_params: list):
308
+ """
309
+ Валидировать параметры индикатора с выбросом исключения
310
+ """
311
+ missing_params = [param for param in required_params if param not in parameters]
312
+ if missing_params:
313
+ raise InvalidIndicatorParametersError(
314
+ f"Отсутствуют обязательные параметры для индикатора {indicator}: {missing_params}",
315
+ {
316
+ 'indicator': indicator,
317
+ 'missing_parameters': missing_params,
318
+ 'provided_parameters': list(parameters.keys())
319
+ }
320
+ )
321
+
322
+
323
+ def validate_ohlcv_data(data, required_columns: list = None):
324
+ """
325
+ Валидировать OHLCV данные с выбросом исключения
326
+ """
327
+ import pandas as pd
328
+
329
+ if not isinstance(data, pd.DataFrame):
330
+ raise create_data_validation_error(
331
+ "Данные должны быть pandas DataFrame",
332
+ expected_type="pandas.DataFrame",
333
+ actual_type=type(data).__name__
334
+ )
335
+
336
+ required_columns = required_columns or ['open', 'high', 'low', 'close']
337
+ missing_columns = [col for col in required_columns if col not in data.columns]
338
+
339
+ if missing_columns:
340
+ raise create_data_validation_error(
341
+ f"Отсутствуют обязательные колонки: {missing_columns}",
342
+ expected_type="OHLCV columns",
343
+ actual_type=list(data.columns)
344
+ )
345
+
346
+ if len(data) == 0:
347
+ raise create_data_validation_error(
348
+ "DataFrame не содержит данных",
349
+ expected_shape="(n_rows > 0, n_cols)",
350
+ actual_shape=data.shape
351
+ )