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,547 @@
1
+ """
2
+ Модуль анализа зон BQuant
3
+
4
+ Предоставляет функции для анализа различных типов зон в финансовых данных:
5
+ - MACD зоны
6
+ - Price action зоны
7
+ - Поддержка и сопротивление
8
+ - Волновой анализ
9
+ - Зоны накопления/распределения
10
+ """
11
+
12
+ from typing import Dict, List, Any, Optional, Tuple
13
+ import pandas as pd
14
+ import numpy as np
15
+ from datetime import datetime, timedelta
16
+ from dataclasses import dataclass
17
+
18
+ from ...core.logging_config import get_logger
19
+ from .. import BaseAnalyzer, AnalysisResult
20
+
21
+ logger = get_logger(__name__)
22
+
23
+ # Версия модуля анализа зон
24
+ __version__ = "0.0.0"
25
+
26
+
27
+ @dataclass
28
+ class Zone:
29
+ """
30
+ Базовый класс для представления зоны.
31
+
32
+ Attributes:
33
+ zone_id: Уникальный идентификатор зоны
34
+ zone_type: Тип зоны (support, resistance, accumulation, etc.)
35
+ start_time: Время начала зоны
36
+ end_time: Время окончания зоны
37
+ start_price: Цена начала зоны
38
+ end_price: Цена окончания зоны
39
+ strength: Сила зоны (0-1)
40
+ confidence: Уверенность в зоне (0-1)
41
+ metadata: Дополнительные метаданные
42
+ """
43
+ zone_id: str
44
+ zone_type: str
45
+ start_time: datetime
46
+ end_time: datetime
47
+ start_price: float
48
+ end_price: float
49
+ strength: float = 0.0
50
+ confidence: float = 0.0
51
+ metadata: Dict[str, Any] = None
52
+
53
+ def __post_init__(self):
54
+ if self.metadata is None:
55
+ self.metadata = {}
56
+
57
+ @property
58
+ def duration(self) -> timedelta:
59
+ """Длительность зоны."""
60
+ return self.end_time - self.start_time
61
+
62
+ @property
63
+ def price_range(self) -> float:
64
+ """Ценовой диапазон зоны."""
65
+ return abs(self.end_price - self.start_price)
66
+
67
+ @property
68
+ def mid_price(self) -> float:
69
+ """Средняя цена зоны."""
70
+ return (self.start_price + self.end_price) / 2
71
+
72
+ def to_dict(self) -> Dict[str, Any]:
73
+ """Конвертация в словарь."""
74
+ return {
75
+ 'zone_id': self.zone_id,
76
+ 'zone_type': self.zone_type,
77
+ 'start_time': self.start_time.isoformat(),
78
+ 'end_time': self.end_time.isoformat(),
79
+ 'start_price': self.start_price,
80
+ 'end_price': self.end_price,
81
+ 'duration_hours': self.duration.total_seconds() / 3600,
82
+ 'price_range': self.price_range,
83
+ 'mid_price': self.mid_price,
84
+ 'strength': self.strength,
85
+ 'confidence': self.confidence,
86
+ 'metadata': self.metadata
87
+ }
88
+
89
+
90
+ class ZoneAnalyzer(BaseAnalyzer):
91
+ """
92
+ Базовый класс для анализа зон.
93
+ """
94
+
95
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
96
+ """
97
+ Инициализация анализатора зон.
98
+
99
+ Args:
100
+ config: Конфигурация анализатора
101
+ """
102
+ super().__init__("ZoneAnalyzer", config)
103
+
104
+ # Параметры по умолчанию
105
+ self.min_zone_duration = self.config.get('min_zone_duration', 5)
106
+ self.min_strength_threshold = self.config.get('min_strength_threshold', 0.3)
107
+ self.min_confidence_threshold = self.config.get('min_confidence_threshold', 0.5)
108
+
109
+ def identify_support_resistance(self, data: pd.DataFrame,
110
+ window: int = 20,
111
+ min_touches: int = 2) -> List[Zone]:
112
+ """
113
+ Определение зон поддержки и сопротивления.
114
+
115
+ Args:
116
+ data: DataFrame с OHLCV данными
117
+ window: Окно для поиска локальных экстремумов
118
+ min_touches: Минимальное количество касаний для подтверждения зоны
119
+
120
+ Returns:
121
+ Список зон поддержки и сопротивления
122
+ """
123
+ zones = []
124
+
125
+ if len(data) < window * 2:
126
+ self.logger.warning(f"Insufficient data for support/resistance analysis: {len(data)} < {window * 2}")
127
+ return zones
128
+
129
+ # Ищем локальные минимумы (поддержка)
130
+ support_levels = self._find_local_extrema(data['low'], window, 'min')
131
+ support_zones = self._validate_zones(data, support_levels, 'support', min_touches)
132
+ zones.extend(support_zones)
133
+
134
+ # Ищем локальные максимумы (сопротивление)
135
+ resistance_levels = self._find_local_extrema(data['high'], window, 'max')
136
+ resistance_zones = self._validate_zones(data, resistance_levels, 'resistance', min_touches)
137
+ zones.extend(resistance_zones)
138
+
139
+ self.logger.debug(f"Identified {len(zones)} support/resistance zones")
140
+ return zones
141
+
142
+ def _find_local_extrema(self, series: pd.Series, window: int,
143
+ extrema_type: str) -> List[Tuple[int, float]]:
144
+ """
145
+ Поиск локальных экстремумов.
146
+
147
+ Args:
148
+ series: Временной ряд
149
+ window: Размер окна
150
+ extrema_type: Тип экстремума ('min' или 'max')
151
+
152
+ Returns:
153
+ Список (индекс, значение) экстремумов
154
+ """
155
+ extrema = []
156
+
157
+ for i in range(window, len(series) - window):
158
+ window_data = series.iloc[i-window:i+window+1]
159
+ center_value = series.iloc[i]
160
+
161
+ if extrema_type == 'min':
162
+ if center_value == window_data.min():
163
+ extrema.append((i, center_value))
164
+ elif extrema_type == 'max':
165
+ if center_value == window_data.max():
166
+ extrema.append((i, center_value))
167
+
168
+ return extrema
169
+
170
+ def _validate_zones(self, data: pd.DataFrame, extrema: List[Tuple[int, float]],
171
+ zone_type: str, min_touches: int) -> List[Zone]:
172
+ """
173
+ Валидация и создание зон на основе экстремумов.
174
+
175
+ Args:
176
+ data: DataFrame с данными
177
+ extrema: Список экстремумов
178
+ zone_type: Тип зоны
179
+ min_touches: Минимальное количество касаний
180
+
181
+ Returns:
182
+ Список валидных зон
183
+ """
184
+ zones = []
185
+
186
+ if len(extrema) < min_touches:
187
+ return zones
188
+
189
+ # Группируем близкие экстремумы
190
+ tolerance = data['close'].std() * 0.1 # 10% от стандартного отклонения
191
+
192
+ grouped_extrema = self._group_extrema(extrema, tolerance)
193
+
194
+ for group in grouped_extrema:
195
+ if len(group) >= min_touches:
196
+ zone = self._create_zone_from_group(data, group, zone_type)
197
+ if zone:
198
+ zones.append(zone)
199
+
200
+ return zones
201
+
202
+ def _group_extrema(self, extrema: List[Tuple[int, float]],
203
+ tolerance: float) -> List[List[Tuple[int, float]]]:
204
+ """
205
+ Группировка близких экстремумов.
206
+
207
+ Args:
208
+ extrema: Список экстремумов
209
+ tolerance: Допустимое отклонение для группировки
210
+
211
+ Returns:
212
+ Список групп экстремумов
213
+ """
214
+ if not extrema:
215
+ return []
216
+
217
+ # Сортируем по цене
218
+ sorted_extrema = sorted(extrema, key=lambda x: x[1])
219
+
220
+ groups = []
221
+ current_group = [sorted_extrema[0]]
222
+
223
+ for i in range(1, len(sorted_extrema)):
224
+ curr_price = sorted_extrema[i][1]
225
+ prev_price = sorted_extrema[i-1][1]
226
+
227
+ if abs(curr_price - prev_price) <= tolerance:
228
+ current_group.append(sorted_extrema[i])
229
+ else:
230
+ if len(current_group) > 1:
231
+ groups.append(current_group)
232
+ current_group = [sorted_extrema[i]]
233
+
234
+ # Добавляем последнюю группу
235
+ if len(current_group) > 1:
236
+ groups.append(current_group)
237
+
238
+ return groups
239
+
240
+ def _create_zone_from_group(self, data: pd.DataFrame,
241
+ group: List[Tuple[int, float]],
242
+ zone_type: str) -> Optional[Zone]:
243
+ """
244
+ Создание зоны из группы экстремумов.
245
+
246
+ Args:
247
+ data: DataFrame с данными
248
+ group: Группа экстремумов
249
+ zone_type: Тип зоны
250
+
251
+ Returns:
252
+ Объект Zone или None
253
+ """
254
+ if not group:
255
+ return None
256
+
257
+ # Вычисляем параметры зоны
258
+ indices = [idx for idx, _ in group]
259
+ prices = [price for _, price in group]
260
+
261
+ start_idx = min(indices)
262
+ end_idx = max(indices)
263
+
264
+ start_time = data.index[start_idx]
265
+ end_time = data.index[end_idx]
266
+
267
+ min_price = min(prices)
268
+ max_price = max(prices)
269
+
270
+ # Вычисляем силу зоны (на основе количества касаний)
271
+ strength = min(len(group) / 5.0, 1.0) # Нормализуем к 1.0
272
+
273
+ # Вычисляем уверенность (на основе плотности касаний)
274
+ time_span = (end_time - start_time).total_seconds() / 3600 # в часах
275
+ if time_span > 0:
276
+ touch_density = len(group) / time_span
277
+ confidence = min(touch_density / 2.0, 1.0) # Нормализуем к 1.0
278
+ else:
279
+ confidence = 1.0
280
+
281
+ zone_id = f"{zone_type}_{start_time.strftime('%Y%m%d_%H%M')}_{end_time.strftime('%Y%m%d_%H%M')}"
282
+
283
+ zone = Zone(
284
+ zone_id=zone_id,
285
+ zone_type=zone_type,
286
+ start_time=start_time,
287
+ end_time=end_time,
288
+ start_price=min_price,
289
+ end_price=max_price,
290
+ strength=strength,
291
+ confidence=confidence,
292
+ metadata={
293
+ 'touch_count': len(group),
294
+ 'price_std': np.std(prices),
295
+ 'indices': indices
296
+ }
297
+ )
298
+
299
+ return zone
300
+
301
+ def analyze_zone_breaks(self, data: pd.DataFrame, zones: List[Zone]) -> Dict[str, Any]:
302
+ """
303
+ Анализ пробоев зон.
304
+
305
+ Args:
306
+ data: DataFrame с данными
307
+ zones: Список зон для анализа
308
+
309
+ Returns:
310
+ Результаты анализа пробоев
311
+ """
312
+ break_analysis = {
313
+ 'total_zones': len(zones),
314
+ 'broken_zones': 0,
315
+ 'false_breaks': 0,
316
+ 'clean_breaks': 0,
317
+ 'break_details': []
318
+ }
319
+
320
+ for zone in zones:
321
+ # Найдем данные после окончания зоны
322
+ zone_end_idx = data.index.get_loc(zone.end_time)
323
+ if zone_end_idx >= len(data) - 1:
324
+ continue
325
+
326
+ post_zone_data = data.iloc[zone_end_idx + 1:]
327
+
328
+ # Определяем пробой
329
+ if zone.zone_type == 'support':
330
+ # Пробой поддержки - цена идет ниже
331
+ break_condition = post_zone_data['low'] < zone.start_price
332
+ else: # resistance
333
+ # Пробой сопротивления - цена идет выше
334
+ break_condition = post_zone_data['high'] > zone.end_price
335
+
336
+ if break_condition.any():
337
+ break_analysis['broken_zones'] += 1
338
+
339
+ # Анализируем тип пробоя
340
+ break_idx = break_condition.idxmax()
341
+ break_details = {
342
+ 'zone_id': zone.zone_id,
343
+ 'zone_type': zone.zone_type,
344
+ 'break_time': break_idx,
345
+ 'break_price': post_zone_data.loc[break_idx, 'close'],
346
+ 'zone_strength': zone.strength,
347
+ 'zone_confidence': zone.confidence
348
+ }
349
+
350
+ break_analysis['break_details'].append(break_details)
351
+
352
+ self.logger.debug(f"Analyzed breaks for {len(zones)} zones")
353
+ return break_analysis
354
+
355
+ def analyze(self, data: pd.DataFrame, **kwargs) -> AnalysisResult:
356
+ """
357
+ Выполнение комплексного анализа зон.
358
+
359
+ Args:
360
+ data: DataFrame с OHLCV данными
361
+ **kwargs: Дополнительные параметры
362
+
363
+ Returns:
364
+ AnalysisResult с результатами анализа зон
365
+ """
366
+ if not self.validate_data(data):
367
+ raise ValueError("Data validation failed")
368
+
369
+ # Проверяем наличие необходимых колонок
370
+ required_columns = ['open', 'high', 'low', 'close']
371
+ missing_columns = [col for col in required_columns if col not in data.columns]
372
+ if missing_columns:
373
+ raise ValueError(f"Missing required columns: {missing_columns}")
374
+
375
+ results = {}
376
+
377
+ # Анализ зон поддержки и сопротивления
378
+ window = kwargs.get('window', 20)
379
+ min_touches = kwargs.get('min_touches', 2)
380
+
381
+ zones = self.identify_support_resistance(data, window, min_touches)
382
+
383
+ # Фильтруем зоны по силе и уверенности
384
+ strong_zones = [z for z in zones if z.strength >= self.min_strength_threshold]
385
+ confident_zones = [z for z in zones if z.confidence >= self.min_confidence_threshold]
386
+
387
+ results['zones'] = {
388
+ 'all_zones': [zone.to_dict() for zone in zones],
389
+ 'strong_zones': [zone.to_dict() for zone in strong_zones],
390
+ 'confident_zones': [zone.to_dict() for zone in confident_zones],
391
+ 'zone_count': len(zones),
392
+ 'strong_zone_count': len(strong_zones),
393
+ 'confident_zone_count': len(confident_zones)
394
+ }
395
+
396
+ # Анализ пробоев зон
397
+ if zones:
398
+ break_analysis = self.analyze_zone_breaks(data, zones)
399
+ results['break_analysis'] = break_analysis
400
+
401
+ # Статистика зон
402
+ if zones:
403
+ zone_stats = self._calculate_zone_statistics(zones)
404
+ results['zone_statistics'] = zone_stats
405
+
406
+ metadata = {
407
+ 'analyzer': 'ZoneAnalyzer',
408
+ 'window': window,
409
+ 'min_touches': min_touches,
410
+ 'min_strength_threshold': self.min_strength_threshold,
411
+ 'min_confidence_threshold': self.min_confidence_threshold,
412
+ 'config': self.config
413
+ }
414
+
415
+ return AnalysisResult(
416
+ analysis_type='zones',
417
+ results=results,
418
+ data_size=len(data),
419
+ metadata=metadata
420
+ )
421
+
422
+ def _calculate_zone_statistics(self, zones: List[Zone]) -> Dict[str, Any]:
423
+ """
424
+ Вычисление статистик по зонам.
425
+
426
+ Args:
427
+ zones: Список зон
428
+
429
+ Returns:
430
+ Статистики зон
431
+ """
432
+ if not zones:
433
+ return {}
434
+
435
+ # Группируем по типам
436
+ support_zones = [z for z in zones if z.zone_type == 'support']
437
+ resistance_zones = [z for z in zones if z.zone_type == 'resistance']
438
+
439
+ # Общие статистики
440
+ strengths = [z.strength for z in zones]
441
+ confidences = [z.confidence for z in zones]
442
+ durations = [z.duration.total_seconds() / 3600 for z in zones] # в часах
443
+
444
+ stats = {
445
+ 'total_zones': len(zones),
446
+ 'support_zones': len(support_zones),
447
+ 'resistance_zones': len(resistance_zones),
448
+ 'avg_strength': np.mean(strengths),
449
+ 'avg_confidence': np.mean(confidences),
450
+ 'avg_duration_hours': np.mean(durations),
451
+ 'max_strength': max(strengths),
452
+ 'max_confidence': max(confidences),
453
+ 'max_duration_hours': max(durations)
454
+ }
455
+
456
+ return stats
457
+
458
+
459
+ def get_zone_analyzers() -> Dict[str, str]:
460
+ """
461
+ Получить список доступных анализаторов зон.
462
+
463
+ Returns:
464
+ Словарь {анализатор: описание}
465
+ """
466
+ return {
467
+ 'zone': 'Комплексный анализ зон поддержки/сопротивления',
468
+ 'support_resistance': 'Анализ уровней поддержки и сопротивления',
469
+ 'macd_zones': 'Анализ MACD зон',
470
+ 'price_action': 'Анализ зон price action'
471
+ }
472
+
473
+
474
+ # Удобные функции
475
+ def find_support_resistance(data: pd.DataFrame, window: int = 20,
476
+ min_touches: int = 2) -> List[Zone]:
477
+ """
478
+ Быстрый поиск уровней поддержки и сопротивления.
479
+
480
+ Args:
481
+ data: DataFrame с OHLCV данными
482
+ window: Окно для поиска экстремумов
483
+ min_touches: Минимальное количество касаний
484
+
485
+ Returns:
486
+ Список зон
487
+ """
488
+ analyzer = ZoneAnalyzer()
489
+ return analyzer.identify_support_resistance(data, window, min_touches)
490
+
491
+
492
+ # Импорт расширенных модулей анализа зон
493
+ try:
494
+ from .zone_features import (
495
+ ZoneFeatures,
496
+ ZoneFeaturesAnalyzer,
497
+ analyze_zones_distribution,
498
+ extract_zone_features
499
+ )
500
+ _zone_features_available = True
501
+ logger.info("Zone features module loaded successfully")
502
+ except ImportError as e:
503
+ logger.warning(f"Zone features module not available: {e}")
504
+ _zone_features_available = False
505
+
506
+ try:
507
+ from .sequence_analysis import (
508
+ TransitionAnalysis,
509
+ ClusterAnalysis,
510
+ ZoneSequenceAnalyzer,
511
+ create_zone_sequence_analysis,
512
+ cluster_zone_shapes
513
+ )
514
+ _sequence_analysis_available = True
515
+ logger.info("Sequence analysis module loaded successfully")
516
+ except ImportError as e:
517
+ logger.warning(f"Sequence analysis module not available: {e}")
518
+ _sequence_analysis_available = False
519
+
520
+
521
+ # Экспорт базового функционала
522
+ __all__ = [
523
+ 'Zone',
524
+ 'ZoneAnalyzer',
525
+ 'get_zone_analyzers',
526
+ 'find_support_resistance',
527
+ '__version__'
528
+ ]
529
+
530
+ # Добавляем zone features если доступен
531
+ if _zone_features_available:
532
+ __all__.extend([
533
+ 'ZoneFeatures',
534
+ 'ZoneFeaturesAnalyzer',
535
+ 'analyze_zones_distribution',
536
+ 'extract_zone_features'
537
+ ])
538
+
539
+ # Добавляем sequence analysis если доступен
540
+ if _sequence_analysis_available:
541
+ __all__.extend([
542
+ 'TransitionAnalysis',
543
+ 'ClusterAnalysis',
544
+ 'ZoneSequenceAnalyzer',
545
+ 'create_zone_sequence_analysis',
546
+ 'cluster_zone_shapes'
547
+ ])