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,520 @@
1
+ """
2
+ Модуль анализа характеристик зон BQuant
3
+
4
+ Адаптировано из scripts/research/macd_analysis.py с улучшениями для новой архитектуры.
5
+ Предоставляет функции для анализа признаков и распределения торговых зон.
6
+ """
7
+
8
+ import pandas as pd
9
+ import numpy as np
10
+ from scipy import stats
11
+ from scipy.signal import find_peaks
12
+ from typing import Dict, Any, List, Optional, Union
13
+ from datetime import datetime
14
+ from dataclasses import dataclass
15
+
16
+ from ...core.logging_config import get_logger
17
+ from ...core.exceptions import AnalysisError
18
+ from .. import AnalysisResult, BaseAnalyzer
19
+
20
+ # Получаем логгер для модуля
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ @dataclass
25
+ class ZoneFeatures:
26
+ """
27
+ Характеристики торговой зоны.
28
+
29
+ Attributes:
30
+ zone_id: Уникальный идентификатор зоны
31
+ zone_type: Тип зоны ('bull' или 'bear')
32
+ duration: Длительность зоны в периодах
33
+ start_price: Цена в начале зоны
34
+ end_price: Цена в конце зоны
35
+ price_return: Доходность за зону
36
+ macd_amplitude: Амплитуда MACD в зоне
37
+ hist_amplitude: Амплитуда гистограммы MACD
38
+ price_range_pct: Ценовой диапазон в процентах
39
+ atr_normalized_return: Доходность, нормализованная на ATR
40
+ correlation_price_hist: Корреляция между ценой и гистограммой
41
+ num_peaks: Количество пиков в зоне
42
+ num_troughs: Количество впадин в зоне
43
+ drawdown_from_peak: Просадка от пика (для бычьих зон)
44
+ rally_from_trough: Отскок от минимума (для медвежьих зон)
45
+ metadata: Дополнительные метаданные
46
+ """
47
+ zone_id: str
48
+ zone_type: str
49
+ duration: int
50
+ start_price: float
51
+ end_price: float
52
+ price_return: float
53
+ macd_amplitude: float
54
+ hist_amplitude: float
55
+ price_range_pct: float
56
+ atr_normalized_return: Optional[float] = None
57
+ correlation_price_hist: Optional[float] = None
58
+ num_peaks: Optional[int] = None
59
+ num_troughs: Optional[int] = None
60
+ drawdown_from_peak: Optional[float] = None
61
+ rally_from_trough: Optional[float] = None
62
+ metadata: Dict[str, Any] = None
63
+
64
+ def __post_init__(self):
65
+ if self.metadata is None:
66
+ self.metadata = {}
67
+
68
+ def to_dict(self) -> Dict[str, Any]:
69
+ """Конвертация в словарь."""
70
+ result = {}
71
+ for field, value in self.__dict__.items():
72
+ if isinstance(value, (int, float, str, bool)) or value is None:
73
+ result[field] = value
74
+ elif isinstance(value, dict):
75
+ result[field] = value
76
+ return result
77
+
78
+
79
+ class ZoneFeaturesAnalyzer(BaseAnalyzer):
80
+ """
81
+ Анализатор характеристик торговых зон.
82
+
83
+ Предоставляет методы для:
84
+ - Извлечения признаков зон
85
+ - Анализа распределения характеристик
86
+ - Статистического анализа зон
87
+ - Сравнительного анализа типов зон
88
+ """
89
+
90
+ def __init__(self, min_duration: int = 2, min_amplitude: float = 0.001):
91
+ """
92
+ Инициализация анализатора.
93
+
94
+ Args:
95
+ min_duration: Минимальная длительность зоны
96
+ min_amplitude: Минимальная амплитуда для значимой зоны
97
+ """
98
+ super().__init__("ZoneFeaturesAnalyzer")
99
+ self.min_duration = min_duration
100
+ self.min_amplitude = min_amplitude
101
+ self.logger = get_logger(f"{__name__}.ZoneFeaturesAnalyzer")
102
+
103
+ self.logger.info(f"Initialized zone features analyzer with min_duration={min_duration}, min_amplitude={min_amplitude}")
104
+
105
+ def extract_zone_features(self, zone_info: Dict[str, Any]) -> ZoneFeatures:
106
+ """
107
+ Извлечение признаков из информации о зоне.
108
+
109
+ Args:
110
+ zone_info: Словарь с информацией о зоне
111
+ - zone_id: ID зоны
112
+ - type: Тип зоны ('bull'/'bear')
113
+ - duration: Длительность
114
+ - data: DataFrame с OHLCV + MACD + ATR
115
+
116
+ Returns:
117
+ ZoneFeatures: Объект с характеристиками зоны
118
+ """
119
+ try:
120
+ data = zone_info['data']
121
+ zone_type = zone_info['type']
122
+ zone_id = zone_info.get('zone_id', f"{zone_type}_{len(data)}")
123
+
124
+ if len(data) < self.min_duration:
125
+ raise AnalysisError(f"Zone duration {len(data)} is less than minimum {self.min_duration}")
126
+
127
+ # Базовые характеристики
128
+ start_price = float(data['close'].iloc[0])
129
+ end_price = float(data['close'].iloc[-1])
130
+ price_return = (end_price / start_price) - 1
131
+
132
+ # MACD характеристики
133
+ max_macd = float(data['macd'].max())
134
+ min_macd = float(data['macd'].min())
135
+ macd_amplitude = max_macd - min_macd
136
+
137
+ # Гистограмма MACD
138
+ max_hist = float(data['macd_hist'].max())
139
+ min_hist = float(data['macd_hist'].min())
140
+ hist_amplitude = max_hist - min_hist
141
+
142
+ # Ценовые характеристики
143
+ max_price = float(data['high'].max())
144
+ min_price = float(data['low'].min())
145
+ price_range_pct = (max_price / min_price) - 1
146
+
147
+ # ATR нормализация
148
+ atr_normalized_return = None
149
+ if 'atr' in data.columns and data['atr'].iloc[0] > 0:
150
+ atr_normalized_return = price_return / float(data['atr'].iloc[0])
151
+
152
+ # Корреляция цены и гистограммы
153
+ correlation_price_hist = None
154
+ if len(data) >= 3:
155
+ try:
156
+ correlation_price_hist = float(data['close'].corr(data['macd_hist']))
157
+ except:
158
+ correlation_price_hist = None
159
+
160
+ # Анализ пиков и впадин
161
+ num_peaks = None
162
+ num_troughs = None
163
+ try:
164
+ peaks, _ = find_peaks(data['high'].values, height=data['high'].mean())
165
+ troughs, _ = find_peaks(-data['low'].values, height=-data['low'].mean())
166
+ num_peaks = len(peaks)
167
+ num_troughs = len(troughs)
168
+ except:
169
+ pass
170
+
171
+ # Специфичные для типа зоны метрики
172
+ drawdown_from_peak = None
173
+ rally_from_trough = None
174
+
175
+ if zone_type == 'bull':
176
+ # Просадка от пика
177
+ drawdown_from_peak = (end_price / max_price) - 1
178
+ elif zone_type == 'bear':
179
+ # Отскок от минимума
180
+ rally_from_trough = (end_price / min_price) - 1
181
+
182
+ # Метаданные
183
+ metadata = {
184
+ 'data_points': len(data),
185
+ 'start_timestamp': str(data.index[0]) if hasattr(data.index[0], '__str__') else None,
186
+ 'end_timestamp': str(data.index[-1]) if hasattr(data.index[-1], '__str__') else None,
187
+ 'max_macd': max_macd,
188
+ 'min_macd': min_macd,
189
+ 'avg_macd': float(data['macd'].mean()),
190
+ 'macd_std': float(data['macd'].std()),
191
+ 'max_hist': max_hist,
192
+ 'min_hist': min_hist,
193
+ 'avg_hist': float(data['macd_hist'].mean()),
194
+ 'hist_std': float(data['macd_hist'].std()),
195
+ 'max_price': max_price,
196
+ 'min_price': min_price,
197
+ 'price_range': max_price - min_price
198
+ }
199
+
200
+ if 'atr' in data.columns:
201
+ metadata.update({
202
+ 'atr_start': float(data['atr'].iloc[0]),
203
+ 'atr_end': float(data['atr'].iloc[-1]),
204
+ 'avg_atr': float(data['atr'].mean())
205
+ })
206
+
207
+ return ZoneFeatures(
208
+ zone_id=zone_id,
209
+ zone_type=zone_type,
210
+ duration=len(data),
211
+ start_price=start_price,
212
+ end_price=end_price,
213
+ price_return=price_return,
214
+ macd_amplitude=macd_amplitude,
215
+ hist_amplitude=hist_amplitude,
216
+ price_range_pct=price_range_pct,
217
+ atr_normalized_return=atr_normalized_return,
218
+ correlation_price_hist=correlation_price_hist,
219
+ num_peaks=num_peaks,
220
+ num_troughs=num_troughs,
221
+ drawdown_from_peak=drawdown_from_peak,
222
+ rally_from_trough=rally_from_trough,
223
+ metadata=metadata
224
+ )
225
+
226
+ except Exception as e:
227
+ self.logger.error(f"Failed to extract zone features: {e}")
228
+ raise AnalysisError(f"Failed to extract zone features: {e}")
229
+
230
+ def analyze_zones_distribution(self, zones_features: List[Union[ZoneFeatures, Dict[str, Any]]]) -> AnalysisResult:
231
+ """
232
+ Анализ распределения характеристик зон.
233
+
234
+ Args:
235
+ zones_features: Список объектов ZoneFeatures или словарей
236
+
237
+ Returns:
238
+ AnalysisResult с анализом распределения
239
+ """
240
+ try:
241
+ self.logger.info(f"Analyzing distribution of {len(zones_features)} zones")
242
+
243
+ if not zones_features:
244
+ raise AnalysisError("No zones features provided")
245
+
246
+ # Конвертируем в DataFrame
247
+ features_dicts = []
248
+ for zone in zones_features:
249
+ if isinstance(zone, ZoneFeatures):
250
+ features_dicts.append(zone.to_dict())
251
+ elif isinstance(zone, dict):
252
+ features_dicts.append(zone)
253
+ else:
254
+ raise AnalysisError(f"Invalid zone features type: {type(zone)}")
255
+
256
+ df_features = pd.DataFrame(features_dicts)
257
+
258
+ # Разделяем по типу зон
259
+ bull_zones = df_features[df_features['zone_type'] == 'bull']
260
+ bear_zones = df_features[df_features['zone_type'] == 'bear']
261
+
262
+ # Общая статистика
263
+ total_stats = {
264
+ 'total_zones': len(df_features),
265
+ 'bull_zones_count': len(bull_zones),
266
+ 'bear_zones_count': len(bear_zones),
267
+ 'bull_ratio': len(bull_zones) / len(df_features) if len(df_features) > 0 else 0,
268
+ 'bear_ratio': len(bear_zones) / len(df_features) if len(df_features) > 0 else 0
269
+ }
270
+
271
+ # Статистика по длительности
272
+ duration_stats = self._calculate_distribution_stats(df_features, bull_zones, bear_zones, 'duration')
273
+
274
+ # Статистика по доходности
275
+ return_stats = self._calculate_distribution_stats(df_features, bull_zones, bear_zones, 'price_return')
276
+
277
+ # Статистика по амплитуде MACD
278
+ macd_amplitude_stats = self._calculate_distribution_stats(df_features, bull_zones, bear_zones, 'macd_amplitude')
279
+
280
+ # Статистика по амплитуде гистограммы
281
+ hist_amplitude_stats = self._calculate_distribution_stats(df_features, bull_zones, bear_zones, 'hist_amplitude')
282
+
283
+ # Дополнительные метрики
284
+ additional_stats = {}
285
+
286
+ # Корреляции
287
+ if 'correlation_price_hist' in df_features.columns:
288
+ correlation_data = df_features['correlation_price_hist'].dropna()
289
+ if len(correlation_data) > 0:
290
+ additional_stats['price_hist_correlation'] = {
291
+ 'mean': float(correlation_data.mean()),
292
+ 'std': float(correlation_data.std()),
293
+ 'positive_correlations': len(correlation_data[correlation_data > 0]),
294
+ 'negative_correlations': len(correlation_data[correlation_data < 0]),
295
+ 'strong_correlations': len(correlation_data[abs(correlation_data) > 0.7])
296
+ }
297
+
298
+ # Пики и впадины
299
+ if 'num_peaks' in df_features.columns and 'num_troughs' in df_features.columns:
300
+ peaks_data = df_features['num_peaks'].dropna()
301
+ troughs_data = df_features['num_troughs'].dropna()
302
+
303
+ if len(peaks_data) > 0 and len(troughs_data) > 0:
304
+ additional_stats['peaks_troughs'] = {
305
+ 'avg_peaks_per_zone': float(peaks_data.mean()),
306
+ 'avg_troughs_per_zone': float(troughs_data.mean()),
307
+ 'zones_with_peaks': len(peaks_data[peaks_data > 0]),
308
+ 'zones_with_troughs': len(troughs_data[troughs_data > 0])
309
+ }
310
+
311
+ # Объединяем все результаты
312
+ results = {
313
+ 'total_statistics': total_stats,
314
+ 'duration_distribution': duration_stats,
315
+ 'return_distribution': return_stats,
316
+ 'macd_amplitude_distribution': macd_amplitude_stats,
317
+ 'hist_amplitude_distribution': hist_amplitude_stats,
318
+ 'additional_metrics': additional_stats
319
+ }
320
+
321
+ metadata = {
322
+ 'analyzer': 'ZoneFeaturesAnalyzer',
323
+ 'analysis_method': 'zones_distribution',
324
+ 'min_duration': self.min_duration,
325
+ 'min_amplitude': self.min_amplitude,
326
+ 'timestamp': datetime.now().isoformat()
327
+ }
328
+
329
+ return AnalysisResult(
330
+ analysis_type='zones_distribution',
331
+ results=results,
332
+ data_size=len(zones_features),
333
+ metadata=metadata
334
+ )
335
+
336
+ except Exception as e:
337
+ self.logger.error(f"Zones distribution analysis failed: {e}")
338
+ raise AnalysisError(f"Zones distribution analysis failed: {e}")
339
+
340
+ def _calculate_distribution_stats(self, df_all: pd.DataFrame,
341
+ df_bull: pd.DataFrame,
342
+ df_bear: pd.DataFrame,
343
+ column: str) -> Dict[str, Any]:
344
+ """
345
+ Вычисление статистики распределения для конкретной характеристики.
346
+
347
+ Args:
348
+ df_all: Все зоны
349
+ df_bull: Бычьи зоны
350
+ df_bear: Медвежьи зоны
351
+ column: Название колонки для анализа
352
+
353
+ Returns:
354
+ Словарь со статистикой распределения
355
+ """
356
+ stats_dict = {}
357
+
358
+ # Общая статистика
359
+ if column in df_all.columns:
360
+ all_data = df_all[column].dropna()
361
+ if len(all_data) > 0:
362
+ stats_dict['overall'] = {
363
+ 'mean': float(all_data.mean()),
364
+ 'median': float(all_data.median()),
365
+ 'std': float(all_data.std()),
366
+ 'min': float(all_data.min()),
367
+ 'max': float(all_data.max()),
368
+ 'q25': float(all_data.quantile(0.25)),
369
+ 'q75': float(all_data.quantile(0.75)),
370
+ 'skewness': float(all_data.skew()),
371
+ 'kurtosis': float(all_data.kurtosis())
372
+ }
373
+
374
+ # Статистика для бычьих зон
375
+ if column in df_bull.columns and len(df_bull) > 0:
376
+ bull_data = df_bull[column].dropna()
377
+ if len(bull_data) > 0:
378
+ stats_dict['bull'] = {
379
+ 'mean': float(bull_data.mean()),
380
+ 'median': float(bull_data.median()),
381
+ 'std': float(bull_data.std()),
382
+ 'min': float(bull_data.min()),
383
+ 'max': float(bull_data.max()),
384
+ 'count': len(bull_data)
385
+ }
386
+
387
+ # Статистика для медвежьих зон
388
+ if column in df_bear.columns and len(df_bear) > 0:
389
+ bear_data = df_bear[column].dropna()
390
+ if len(bear_data) > 0:
391
+ stats_dict['bear'] = {
392
+ 'mean': float(bear_data.mean()),
393
+ 'median': float(bear_data.median()),
394
+ 'std': float(bear_data.std()),
395
+ 'min': float(bear_data.min()),
396
+ 'max': float(bear_data.max()),
397
+ 'count': len(bear_data)
398
+ }
399
+
400
+ # Сравнительная статистика
401
+ if ('bull' in stats_dict and 'bear' in stats_dict and
402
+ column in df_bull.columns and column in df_bear.columns):
403
+
404
+ bull_data = df_bull[column].dropna()
405
+ bear_data = df_bear[column].dropna()
406
+
407
+ if len(bull_data) > 1 and len(bear_data) > 1:
408
+ try:
409
+ # t-тест для сравнения средних
410
+ t_stat, p_value = stats.ttest_ind(bull_data, bear_data)
411
+
412
+ stats_dict['comparison'] = {
413
+ 't_statistic': float(t_stat),
414
+ 'p_value': float(p_value),
415
+ 'significant_difference': p_value < 0.05,
416
+ 'bull_vs_bear_ratio': stats_dict['bull']['mean'] / stats_dict['bear']['mean'] if stats_dict['bear']['mean'] != 0 else None
417
+ }
418
+ except Exception as e:
419
+ self.logger.warning(f"Failed to calculate comparison stats for {column}: {e}")
420
+
421
+ return stats_dict
422
+
423
+ def get_zone_features_summary(self, zones_features: List[Union[ZoneFeatures, Dict[str, Any]]]) -> Dict[str, Any]:
424
+ """
425
+ Получение краткой сводки по характеристикам зон.
426
+
427
+ Args:
428
+ zones_features: Список объектов ZoneFeatures или словарей
429
+
430
+ Returns:
431
+ Словарь с краткой сводкой
432
+ """
433
+ try:
434
+ if not zones_features:
435
+ return {'error': 'No zones features provided'}
436
+
437
+ # Конвертируем в DataFrame
438
+ features_dicts = []
439
+ for zone in zones_features:
440
+ if isinstance(zone, ZoneFeatures):
441
+ features_dicts.append(zone.to_dict())
442
+ elif isinstance(zone, dict):
443
+ features_dicts.append(zone)
444
+
445
+ df_features = pd.DataFrame(features_dicts)
446
+
447
+ bull_zones = df_features[df_features['zone_type'] == 'bull']
448
+ bear_zones = df_features[df_features['zone_type'] == 'bear']
449
+
450
+ summary = {
451
+ 'total_zones': len(df_features),
452
+ 'bull_zones': len(bull_zones),
453
+ 'bear_zones': len(bear_zones),
454
+ 'avg_duration': float(df_features['duration'].mean()) if 'duration' in df_features.columns else None,
455
+ 'avg_return': float(df_features['price_return'].mean()) if 'price_return' in df_features.columns else None,
456
+ 'positive_returns': len(df_features[df_features['price_return'] > 0]) if 'price_return' in df_features.columns else None,
457
+ 'negative_returns': len(df_features[df_features['price_return'] < 0]) if 'price_return' in df_features.columns else None
458
+ }
459
+
460
+ if len(bull_zones) > 0:
461
+ summary['bull_avg_duration'] = float(bull_zones['duration'].mean()) if 'duration' in bull_zones.columns else None
462
+ summary['bull_avg_return'] = float(bull_zones['price_return'].mean()) if 'price_return' in bull_zones.columns else None
463
+
464
+ if len(bear_zones) > 0:
465
+ summary['bear_avg_duration'] = float(bear_zones['duration'].mean()) if 'duration' in bear_zones.columns else None
466
+ summary['bear_avg_return'] = float(bear_zones['price_return'].mean()) if 'price_return' in bear_zones.columns else None
467
+
468
+ return summary
469
+
470
+ except Exception as e:
471
+ self.logger.error(f"Failed to get zone features summary: {e}")
472
+ return {'error': str(e)}
473
+
474
+
475
+ # Удобные функции для быстрого использования
476
+ def analyze_zones_distribution(zones_features: List[Union[ZoneFeatures, Dict[str, Any]]],
477
+ min_duration: int = 2,
478
+ min_amplitude: float = 0.001) -> Dict[str, Any]:
479
+ """
480
+ Анализ распределения зон (совместимость с оригинальным API).
481
+
482
+ Args:
483
+ zones_features: Список характеристик зон
484
+ min_duration: Минимальная длительность зоны
485
+ min_amplitude: Минимальная амплитуда
486
+
487
+ Returns:
488
+ Словарь с результатами анализа
489
+ """
490
+ analyzer = ZoneFeaturesAnalyzer(min_duration=min_duration, min_amplitude=min_amplitude)
491
+ analysis_result = analyzer.analyze_zones_distribution(zones_features)
492
+ return analysis_result.results
493
+
494
+
495
+ def extract_zone_features(zone_info: Dict[str, Any],
496
+ min_duration: int = 2,
497
+ min_amplitude: float = 0.001) -> Dict[str, Any]:
498
+ """
499
+ Извлечение признаков зоны (совместимость с оригинальным API).
500
+
501
+ Args:
502
+ zone_info: Информация о зоне
503
+ min_duration: Минимальная длительность зоны
504
+ min_amplitude: Минимальная амплитуда
505
+
506
+ Returns:
507
+ Словарь с характеристиками зоны
508
+ """
509
+ analyzer = ZoneFeaturesAnalyzer(min_duration=min_duration, min_amplitude=min_amplitude)
510
+ zone_features = analyzer.extract_zone_features(zone_info)
511
+ return zone_features.to_dict()
512
+
513
+
514
+ # Экспорт
515
+ __all__ = [
516
+ 'ZoneFeatures',
517
+ 'ZoneFeaturesAnalyzer',
518
+ 'analyze_zones_distribution',
519
+ 'extract_zone_features'
520
+ ]
bquant/cli.py ADDED
@@ -0,0 +1,160 @@
1
+ """
2
+ BQuant CLI Module
3
+
4
+ Простой CLI интерфейс для основных функций BQuant.
5
+ Работает "из коробки" без внешних зависимостей.
6
+ """
7
+
8
+ import argparse
9
+ import sys
10
+ from typing import Optional
11
+
12
+ from .core.logging_config import get_logger
13
+ from .data.samples import get_sample_data, list_datasets, print_sample_data_status
14
+ from .indicators import MACD
15
+ from .visualization import charts
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ def analyze_macd(dataset_name: str = 'tv_xauusd_1h', output_file: Optional[str] = None):
21
+ """
22
+ Простой анализ MACD для sample данных.
23
+
24
+ Args:
25
+ dataset_name: Название датасета для анализа
26
+ output_file: Путь для сохранения графика (опционально)
27
+ """
28
+ try:
29
+ print(f"🔍 Анализ MACD для датасета: {dataset_name}")
30
+
31
+ # Загружаем данные
32
+ print("📊 Загрузка данных...")
33
+ data = get_sample_data(dataset_name)
34
+ print(f" Загружено {len(data)} записей")
35
+
36
+ # Рассчитываем MACD
37
+ print("📈 Расчет MACD...")
38
+ macd = MACD()
39
+ result = macd.calculate(data)
40
+ print(" MACD рассчитан успешно")
41
+
42
+ # Создаем график
43
+ print("📊 Создание графика...")
44
+ try:
45
+ # Создаем простой график цен с MACD
46
+ fig = charts.create_price_chart(data, chart_type='line', title=f"Price Chart - {dataset_name}")
47
+
48
+ if output_file:
49
+ fig.write_html(output_file)
50
+ print(f" График сохранен в: {output_file}")
51
+ else:
52
+ fig.show()
53
+ print(" График отображен в браузере")
54
+ except Exception as e:
55
+ print(f" ⚠️ Не удалось создать график: {e}")
56
+ print(" ✅ Анализ MACD завершен успешно (без графика)")
57
+
58
+ print("✅ Анализ завершен успешно!")
59
+
60
+ except Exception as e:
61
+ print(f"❌ Ошибка при анализе: {e}")
62
+ logger.error(f"MACD analysis failed: {e}")
63
+ sys.exit(1)
64
+
65
+
66
+ def list_available_data():
67
+ """Показать список доступных sample данных."""
68
+ try:
69
+ print("📋 Доступные sample данные:")
70
+ print("=" * 50)
71
+
72
+ datasets = list_datasets()
73
+ for i, dataset in enumerate(datasets, 1):
74
+ print(f"{i}. {dataset['title']} ({dataset['name']})")
75
+ print(f" Символ: {dataset['symbol']} | Таймфрейм: {dataset['timeframe']}")
76
+ print(f" Записей: {dataset['rows']:,} | Размер: {dataset['size_kb']} KB")
77
+ print()
78
+
79
+ print(f"Всего доступно датасетов: {len(datasets)}")
80
+
81
+ except Exception as e:
82
+ print(f"❌ Ошибка при получении списка данных: {e}")
83
+ logger.error(f"Failed to list datasets: {e}")
84
+ sys.exit(1)
85
+
86
+
87
+ def show_data_status():
88
+ """Показать статус всех sample данных."""
89
+ try:
90
+ print_sample_data_status()
91
+ except Exception as e:
92
+ print(f"❌ Ошибка при получении статуса: {e}")
93
+ logger.error(f"Failed to show data status: {e}")
94
+ sys.exit(1)
95
+
96
+
97
+ def main():
98
+ """Основная функция CLI."""
99
+ parser = argparse.ArgumentParser(
100
+ description="BQuant - Quantitative Research Toolkit",
101
+ formatter_class=argparse.RawDescriptionHelpFormatter,
102
+ epilog="""
103
+ Примеры использования:
104
+ bquant analyze # Анализ MACD с данными по умолчанию
105
+ bquant analyze mt_xauusd_m15 # Анализ MACD с конкретным датасетом
106
+ bquant analyze --output chart.html # Сохранение графика в файл
107
+ bquant list # Список доступных данных
108
+ bquant status # Статус всех данных
109
+ """
110
+ )
111
+
112
+ subparsers = parser.add_subparsers(dest='command', help='Доступные команды')
113
+
114
+ # Команда analyze
115
+ analyze_parser = subparsers.add_parser('analyze', help='Анализ MACD')
116
+ analyze_parser.add_argument(
117
+ 'dataset',
118
+ nargs='?',
119
+ default='tv_xauusd_1h',
120
+ help='Название датасета (по умолчанию: tv_xauusd_1h)'
121
+ )
122
+ analyze_parser.add_argument(
123
+ '--output', '-o',
124
+ help='Путь для сохранения графика (HTML файл)'
125
+ )
126
+
127
+ # Команда list
128
+ subparsers.add_parser('list', help='Список доступных данных')
129
+
130
+ # Команда status
131
+ subparsers.add_parser('status', help='Статус всех данных')
132
+
133
+ args = parser.parse_args()
134
+
135
+ if not args.command:
136
+ parser.print_help()
137
+ return
138
+
139
+ try:
140
+ if args.command == 'analyze':
141
+ analyze_macd(args.dataset, args.output)
142
+ elif args.command == 'list':
143
+ list_available_data()
144
+ elif args.command == 'status':
145
+ show_data_status()
146
+ else:
147
+ print(f"❌ Неизвестная команда: {args.command}")
148
+ sys.exit(1)
149
+
150
+ except KeyboardInterrupt:
151
+ print("\n⚠️ Операция прервана пользователем")
152
+ sys.exit(0)
153
+ except Exception as e:
154
+ print(f"❌ Неожиданная ошибка: {e}")
155
+ logger.error(f"Unexpected error in CLI: {e}")
156
+ sys.exit(1)
157
+
158
+
159
+ if __name__ == '__main__':
160
+ main()