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,657 @@
1
+ """
2
+ Модуль тестирования гипотез для торговых стратегий BQuant
3
+
4
+ Адаптировано из scripts/research/hypothesis_testing.py с улучшениями для новой архитектуры.
5
+ Предоставляет комплексные статистические тесты для анализа зон и торговых паттернов.
6
+ """
7
+
8
+ import pandas as pd
9
+ import numpy as np
10
+ from scipy import stats
11
+ from scipy.stats import ttest_ind, mannwhitneyu, chi2_contingency
12
+ from typing import Dict, Any, Optional, List, Union
13
+ import warnings
14
+ from dataclasses import dataclass
15
+ from datetime import datetime
16
+
17
+ from ...core.logging_config import get_logger
18
+ from ...core.exceptions import StatisticalAnalysisError
19
+ from .. import AnalysisResult
20
+
21
+ # Получаем логгер для модуля
22
+ logger = get_logger(__name__)
23
+
24
+ warnings.filterwarnings('ignore')
25
+
26
+
27
+ @dataclass
28
+ class HypothesisTestResult:
29
+ """
30
+ Результат статистического теста гипотезы.
31
+
32
+ Attributes:
33
+ hypothesis: Описание тестируемой гипотезы
34
+ test_type: Тип статистического теста
35
+ statistic: Значение тестовой статистики
36
+ p_value: p-значение теста
37
+ significant: Является ли результат значимым (p < alpha)
38
+ alpha: Уровень значимости
39
+ effect_size: Размер эффекта (если применимо)
40
+ confidence_interval: Доверительный интервал (если применимо)
41
+ sample_size: Размер выборки
42
+ metadata: Дополнительные метаданные теста
43
+ """
44
+ hypothesis: str
45
+ test_type: str
46
+ statistic: float
47
+ p_value: float
48
+ significant: bool
49
+ alpha: float = 0.05
50
+ effect_size: Optional[float] = None
51
+ confidence_interval: Optional[tuple] = None
52
+ sample_size: Optional[int] = None
53
+ metadata: Dict[str, Any] = None
54
+
55
+ def __post_init__(self):
56
+ if self.metadata is None:
57
+ self.metadata = {}
58
+
59
+ def to_dict(self) -> Dict[str, Any]:
60
+ """Конвертация результата в словарь."""
61
+ return {
62
+ 'hypothesis': self.hypothesis,
63
+ 'test_type': self.test_type,
64
+ 'statistic': self.statistic,
65
+ 'p_value': self.p_value,
66
+ 'significant': self.significant,
67
+ 'alpha': self.alpha,
68
+ 'effect_size': self.effect_size,
69
+ 'confidence_interval': self.confidence_interval,
70
+ 'sample_size': self.sample_size,
71
+ 'metadata': self.metadata
72
+ }
73
+
74
+
75
+ class HypothesisTestSuite:
76
+ """
77
+ Набор статистических тестов для анализа торговых зон и паттернов.
78
+ """
79
+
80
+ def __init__(self, alpha: float = 0.05):
81
+ """
82
+ Инициализация набора тестов.
83
+
84
+ Args:
85
+ alpha: Уровень значимости для всех тестов
86
+ """
87
+ self.alpha = alpha
88
+ self.logger = get_logger(f"{__name__}.HypothesisTestSuite")
89
+
90
+ self.logger.info(f"Initialized hypothesis test suite with alpha={alpha}")
91
+
92
+ def test_zone_duration_hypothesis(self, zones_features: List[Dict[str, Any]]) -> HypothesisTestResult:
93
+ """
94
+ Тест гипотезы о влиянии длительности зон на последующее движение цены.
95
+
96
+ H0: Длительность зоны не влияет на доходность
97
+ H1: Длительные зоны дают другую доходность чем короткие
98
+
99
+ Args:
100
+ zones_features: Список словарей с характеристиками зон
101
+
102
+ Returns:
103
+ HypothesisTestResult с результатами теста
104
+ """
105
+ self.logger.info("Testing zone duration hypothesis")
106
+
107
+ try:
108
+ df_features = pd.DataFrame(zones_features)
109
+
110
+ if 'duration' not in df_features.columns or 'price_return' not in df_features.columns:
111
+ raise StatisticalAnalysisError("Missing required columns: 'duration' or 'price_return'")
112
+
113
+ # Определяем длинные и короткие зоны (верхние и нижние 20%)
114
+ long_threshold = df_features['duration'].quantile(0.8)
115
+ short_threshold = df_features['duration'].quantile(0.2)
116
+
117
+ long_zones = df_features[df_features['duration'] >= long_threshold]
118
+ short_zones = df_features[df_features['duration'] <= short_threshold]
119
+
120
+ if len(long_zones) == 0 or len(short_zones) == 0:
121
+ raise StatisticalAnalysisError("Insufficient data: need both long and short zones")
122
+
123
+ # Выполняем t-тест
124
+ t_stat, p_value = stats.ttest_ind(
125
+ long_zones['price_return'].dropna(),
126
+ short_zones['price_return'].dropna()
127
+ )
128
+
129
+ # Вычисляем размер эффекта (Cohen's d)
130
+ pooled_std = np.sqrt(
131
+ ((len(long_zones) - 1) * long_zones['price_return'].var() +
132
+ (len(short_zones) - 1) * short_zones['price_return'].var()) /
133
+ (len(long_zones) + len(short_zones) - 2)
134
+ )
135
+
136
+ effect_size = (long_zones['price_return'].mean() - short_zones['price_return'].mean()) / pooled_std
137
+
138
+ # Метаданные теста
139
+ metadata = {
140
+ 'long_zones_count': len(long_zones),
141
+ 'short_zones_count': len(short_zones),
142
+ 'long_zones_mean_return': long_zones['price_return'].mean(),
143
+ 'short_zones_mean_return': short_zones['price_return'].mean(),
144
+ 'long_threshold': long_threshold,
145
+ 'short_threshold': short_threshold,
146
+ 'long_zones_std': long_zones['price_return'].std(),
147
+ 'short_zones_std': short_zones['price_return'].std()
148
+ }
149
+
150
+ return HypothesisTestResult(
151
+ hypothesis="Zone duration affects price returns",
152
+ test_type="Independent t-test",
153
+ statistic=t_stat,
154
+ p_value=p_value,
155
+ significant=p_value < self.alpha,
156
+ alpha=self.alpha,
157
+ effect_size=effect_size,
158
+ sample_size=len(long_zones) + len(short_zones),
159
+ metadata=metadata
160
+ )
161
+
162
+ except Exception as e:
163
+ self.logger.error(f"Zone duration hypothesis test failed: {e}")
164
+ raise StatisticalAnalysisError(f"Zone duration test failed: {e}")
165
+
166
+ def test_histogram_slope_hypothesis(self, zones_features: List[Dict[str, Any]]) -> HypothesisTestResult:
167
+ """
168
+ Тест гипотезы о корреляции между наклоном гистограммы MACD и длительностью зоны.
169
+
170
+ H0: Наклон гистограммы не коррелирует с длительностью зоны
171
+ H1: Существует значимая корреляция
172
+
173
+ Args:
174
+ zones_features: Список словарей с характеристиками зон
175
+
176
+ Returns:
177
+ HypothesisTestResult с результатами теста
178
+ """
179
+ self.logger.info("Testing histogram slope hypothesis")
180
+
181
+ try:
182
+ df_features = pd.DataFrame(zones_features)
183
+
184
+ required_cols = ['hist_slope', 'duration']
185
+ missing_cols = [col for col in required_cols if col not in df_features.columns]
186
+ if missing_cols:
187
+ raise StatisticalAnalysisError(f"Missing required columns: {missing_cols}")
188
+
189
+ # Убираем NaN значения
190
+ clean_data = df_features[['hist_slope', 'duration']].dropna()
191
+
192
+ if len(clean_data) < 3:
193
+ raise StatisticalAnalysisError("Insufficient data for correlation test (need at least 3 points)")
194
+
195
+ # Вычисляем корреляцию Пирсона
196
+ correlation, p_value = stats.pearsonr(clean_data['hist_slope'], clean_data['duration'])
197
+
198
+ # Вычисляем t-статистику для корреляции
199
+ n = len(clean_data)
200
+ t_stat = correlation * np.sqrt((n - 2) / (1 - correlation**2))
201
+
202
+ # Доверительный интервал для корреляции (Fisher's z-transform)
203
+ z = np.arctanh(correlation)
204
+ se = 1 / np.sqrt(n - 3)
205
+ z_ci = stats.norm.interval(1 - self.alpha, loc=z, scale=se)
206
+ ci = (np.tanh(z_ci[0]), np.tanh(z_ci[1]))
207
+
208
+ metadata = {
209
+ 'correlation': correlation,
210
+ 'sample_size': n,
211
+ 'degrees_of_freedom': n - 2,
212
+ 'hist_slope_mean': clean_data['hist_slope'].mean(),
213
+ 'hist_slope_std': clean_data['hist_slope'].std(),
214
+ 'duration_mean': clean_data['duration'].mean(),
215
+ 'duration_std': clean_data['duration'].std()
216
+ }
217
+
218
+ return HypothesisTestResult(
219
+ hypothesis="Histogram slope correlates with zone duration",
220
+ test_type="Pearson correlation test",
221
+ statistic=t_stat,
222
+ p_value=p_value,
223
+ significant=p_value < self.alpha,
224
+ alpha=self.alpha,
225
+ effect_size=correlation, # Корреляция как размер эффекта
226
+ confidence_interval=ci,
227
+ sample_size=n,
228
+ metadata=metadata
229
+ )
230
+
231
+ except Exception as e:
232
+ self.logger.error(f"Histogram slope hypothesis test failed: {e}")
233
+ raise StatisticalAnalysisError(f"Histogram slope test failed: {e}")
234
+
235
+ def test_bull_bear_asymmetry_hypothesis(self, zones_features: List[Dict[str, Any]]) -> HypothesisTestResult:
236
+ """
237
+ Тест гипотезы об асимметрии между бычьими и медвежьими зонами.
238
+
239
+ H0: Нет различий между бычьими и медвежьими зонами
240
+ H1: Существуют значимые различия
241
+
242
+ Args:
243
+ zones_features: Список словарей с характеристиками зон
244
+
245
+ Returns:
246
+ HypothesisTestResult с результатами теста
247
+ """
248
+ self.logger.info("Testing bull-bear asymmetry hypothesis")
249
+
250
+ try:
251
+ df_features = pd.DataFrame(zones_features)
252
+
253
+ required_cols = ['type', 'duration', 'price_return']
254
+ missing_cols = [col for col in required_cols if col not in df_features.columns]
255
+ if missing_cols:
256
+ raise StatisticalAnalysisError(f"Missing required columns: {missing_cols}")
257
+
258
+ bull_zones = df_features[df_features['type'] == 'bull']
259
+ bear_zones = df_features[df_features['type'] == 'bear']
260
+
261
+ if len(bull_zones) == 0 or len(bear_zones) == 0:
262
+ raise StatisticalAnalysisError("Insufficient data: need both bull and bear zones")
263
+
264
+ # Тест асимметрии по длительности
265
+ duration_stat, duration_p = stats.ttest_ind(
266
+ bull_zones['duration'].dropna(),
267
+ bear_zones['duration'].dropna()
268
+ )
269
+
270
+ # Тест асимметрии по доходности
271
+ return_stat, return_p = stats.ttest_ind(
272
+ bull_zones['price_return'].dropna(),
273
+ bear_zones['price_return'].dropna()
274
+ )
275
+
276
+ # Комбинированный p-value (метод Бонферрони)
277
+ combined_p = min(duration_p * 2, return_p * 2, 1.0)
278
+
279
+ # Размер эффекта для длительности (Cohen's d)
280
+ duration_pooled_std = np.sqrt(
281
+ ((len(bull_zones) - 1) * bull_zones['duration'].var() +
282
+ (len(bear_zones) - 1) * bear_zones['duration'].var()) /
283
+ (len(bull_zones) + len(bear_zones) - 2)
284
+ )
285
+ duration_effect = (bull_zones['duration'].mean() - bear_zones['duration'].mean()) / duration_pooled_std
286
+
287
+ metadata = {
288
+ 'duration_test': {
289
+ 't_statistic': duration_stat,
290
+ 'p_value': duration_p,
291
+ 'significant': duration_p < self.alpha,
292
+ 'bull_mean': bull_zones['duration'].mean(),
293
+ 'bear_mean': bear_zones['duration'].mean(),
294
+ 'effect_size': duration_effect
295
+ },
296
+ 'return_test': {
297
+ 't_statistic': return_stat,
298
+ 'p_value': return_p,
299
+ 'significant': return_p < self.alpha,
300
+ 'bull_mean': bull_zones['price_return'].mean(),
301
+ 'bear_mean': bear_zones['price_return'].mean()
302
+ },
303
+ 'bull_zones_count': len(bull_zones),
304
+ 'bear_zones_count': len(bear_zones)
305
+ }
306
+
307
+ return HypothesisTestResult(
308
+ hypothesis="Bullish and bearish zones are asymmetric",
309
+ test_type="Multiple t-tests with Bonferroni correction",
310
+ statistic=max(abs(duration_stat), abs(return_stat)),
311
+ p_value=combined_p,
312
+ significant=combined_p < self.alpha,
313
+ alpha=self.alpha,
314
+ effect_size=duration_effect,
315
+ sample_size=len(bull_zones) + len(bear_zones),
316
+ metadata=metadata
317
+ )
318
+
319
+ except Exception as e:
320
+ self.logger.error(f"Bull-bear asymmetry hypothesis test failed: {e}")
321
+ raise StatisticalAnalysisError(f"Bull-bear asymmetry test failed: {e}")
322
+
323
+ def test_sequence_hypothesis(self, zones_features: List[Dict[str, Any]]) -> HypothesisTestResult:
324
+ """
325
+ Тест гипотезы о неслучайности последовательностей зон.
326
+
327
+ H0: Последовательности зон случайны
328
+ H1: Последовательности следуют неслучайным паттернам
329
+
330
+ Args:
331
+ zones_features: Список словарей с характеристиками зон
332
+
333
+ Returns:
334
+ HypothesisTestResult с результатами теста
335
+ """
336
+ self.logger.info("Testing sequence hypothesis")
337
+
338
+ try:
339
+ if 'type' not in zones_features[0]:
340
+ raise StatisticalAnalysisError("Missing 'type' field in zone features")
341
+
342
+ # Создаем последовательность типов зон
343
+ zone_types = [zone['type'] for zone in zones_features]
344
+
345
+ if len(zone_types) < 3:
346
+ raise StatisticalAnalysisError("Need at least 3 zones for sequence analysis")
347
+
348
+ # Подсчитываем переходы
349
+ transitions = {}
350
+ for i in range(len(zone_types) - 1):
351
+ transition = f"{zone_types[i]}_to_{zone_types[i+1]}"
352
+ transitions[transition] = transitions.get(transition, 0) + 1
353
+
354
+ # Тест хи-квадрат на равномерность переходов
355
+ observed_freq = list(transitions.values())
356
+
357
+ if len(observed_freq) < 2:
358
+ raise StatisticalAnalysisError("Need at least 2 different transition types")
359
+
360
+ # Ожидаемая частота при равномерном распределении
361
+ total_transitions = sum(observed_freq)
362
+ expected_freq = total_transitions / len(observed_freq)
363
+
364
+ chi2_stat, chi2_p = stats.chisquare(observed_freq, [expected_freq] * len(observed_freq))
365
+
366
+ # Дополнительный тест: runs test для проверки случайности
367
+ # Преобразуем в бинарную последовательность
368
+ binary_sequence = [1 if zone_type == 'bull' else 0 for zone_type in zone_types]
369
+ runs_stat, runs_p = self._runs_test(binary_sequence)
370
+
371
+ # Комбинированный p-value
372
+ combined_p = min(chi2_p * 2, runs_p * 2, 1.0)
373
+
374
+ metadata = {
375
+ 'transitions': transitions,
376
+ 'total_transitions': total_transitions,
377
+ 'chi2_statistic': chi2_stat,
378
+ 'chi2_p_value': chi2_p,
379
+ 'runs_statistic': runs_stat,
380
+ 'runs_p_value': runs_p,
381
+ 'sequence_length': len(zone_types),
382
+ 'unique_transitions': len(transitions)
383
+ }
384
+
385
+ return HypothesisTestResult(
386
+ hypothesis="Zone sequences follow non-random patterns",
387
+ test_type="Chi-square and runs tests",
388
+ statistic=chi2_stat,
389
+ p_value=combined_p,
390
+ significant=combined_p < self.alpha,
391
+ alpha=self.alpha,
392
+ sample_size=len(zone_types),
393
+ metadata=metadata
394
+ )
395
+
396
+ except Exception as e:
397
+ self.logger.error(f"Sequence hypothesis test failed: {e}")
398
+ raise StatisticalAnalysisError(f"Sequence test failed: {e}")
399
+
400
+ def test_volatility_hypothesis(self, zones_features: List[Dict[str, Any]]) -> HypothesisTestResult:
401
+ """
402
+ Тест гипотезы о влиянии волатильности на характеристики зон.
403
+
404
+ H0: Волатильность не влияет на характеристики зон
405
+ H1: Существует значимая связь
406
+
407
+ Args:
408
+ zones_features: Список словарей с характеристиками зон
409
+
410
+ Returns:
411
+ HypothesisTestResult с результатами теста
412
+ """
413
+ self.logger.info("Testing volatility hypothesis")
414
+
415
+ try:
416
+ df_features = pd.DataFrame(zones_features)
417
+
418
+ # Определяем прокси волатильности
419
+ if 'price_return_atr' in df_features.columns:
420
+ volatility_proxy = df_features['price_return_atr']
421
+ vol_column = 'price_return_atr'
422
+ elif 'atr' in df_features.columns:
423
+ volatility_proxy = df_features['atr']
424
+ vol_column = 'atr'
425
+ else:
426
+ volatility_proxy = df_features['price_return'].abs()
427
+ vol_column = 'abs_price_return'
428
+
429
+ correlations = {}
430
+
431
+ # Тестируем корреляции с различными характеристиками зон
432
+ test_columns = ['duration', 'macd_amplitude', 'price_return']
433
+ available_columns = [col for col in test_columns if col in df_features.columns]
434
+
435
+ if not available_columns:
436
+ raise StatisticalAnalysisError("No suitable columns for volatility correlation test")
437
+
438
+ significant_correlations = 0
439
+ p_values = []
440
+
441
+ for col in available_columns:
442
+ clean_data = df_features[[vol_column, col]].dropna()
443
+
444
+ if len(clean_data) >= 3:
445
+ corr, p_val = stats.pearsonr(volatility_proxy.dropna(), clean_data[col])
446
+ correlations[f'volatility_{col}_correlation'] = {
447
+ 'correlation': corr,
448
+ 'p_value': p_val,
449
+ 'significant': p_val < self.alpha,
450
+ 'sample_size': len(clean_data)
451
+ }
452
+
453
+ if p_val < self.alpha:
454
+ significant_correlations += 1
455
+ p_values.append(p_val)
456
+
457
+ # Объединенный тест: корректировка для множественных сравнений (Holm-Bonferroni)
458
+ if p_values:
459
+ from statsmodels.stats.multitest import multipletests
460
+ corrected_p = multipletests(p_values, alpha=self.alpha, method='holm')[1]
461
+ combined_p = min(corrected_p) if corrected_p.size > 0 else 1.0
462
+ else:
463
+ combined_p = 1.0
464
+
465
+ # Суммарная статистика
466
+ avg_correlation = np.mean([corr_data['correlation'] for corr_data in correlations.values()])
467
+
468
+ metadata = {
469
+ 'volatility_proxy': vol_column,
470
+ 'correlations': correlations,
471
+ 'significant_correlations': significant_correlations,
472
+ 'total_correlations_tested': len(available_columns),
473
+ 'volatility_mean': volatility_proxy.mean(),
474
+ 'volatility_std': volatility_proxy.std(),
475
+ 'individual_p_values': p_values
476
+ }
477
+
478
+ return HypothesisTestResult(
479
+ hypothesis="Volatility affects zone characteristics",
480
+ test_type="Multiple correlation tests with Holm-Bonferroni correction",
481
+ statistic=avg_correlation,
482
+ p_value=combined_p,
483
+ significant=combined_p < self.alpha,
484
+ alpha=self.alpha,
485
+ effect_size=avg_correlation,
486
+ sample_size=len(df_features),
487
+ metadata=metadata
488
+ )
489
+
490
+ except Exception as e:
491
+ self.logger.error(f"Volatility hypothesis test failed: {e}")
492
+ raise StatisticalAnalysisError(f"Volatility test failed: {e}")
493
+
494
+ def _runs_test(self, binary_sequence: List[int]) -> tuple:
495
+ """
496
+ Runs test для проверки случайности бинарной последовательности.
497
+
498
+ Args:
499
+ binary_sequence: Последовательность из 0 и 1
500
+
501
+ Returns:
502
+ Tuple (z_statistic, p_value)
503
+ """
504
+ n = len(binary_sequence)
505
+ n1 = sum(binary_sequence)
506
+ n0 = n - n1
507
+
508
+ if n1 == 0 or n0 == 0:
509
+ return 0.0, 1.0
510
+
511
+ # Подсчет runs (серий)
512
+ runs = 1
513
+ for i in range(1, n):
514
+ if binary_sequence[i] != binary_sequence[i-1]:
515
+ runs += 1
516
+
517
+ # Ожидаемое количество runs
518
+ expected_runs = (2 * n1 * n0) / n + 1
519
+
520
+ # Дисперсия
521
+ variance = (2 * n1 * n0 * (2 * n1 * n0 - n)) / (n**2 * (n - 1))
522
+
523
+ if variance <= 0:
524
+ return 0.0, 1.0
525
+
526
+ # Z-статистика
527
+ z = (runs - expected_runs) / np.sqrt(variance)
528
+
529
+ # p-value (двусторонний тест)
530
+ p_value = 2 * (1 - stats.norm.cdf(abs(z)))
531
+
532
+ return z, p_value
533
+
534
+ def run_all_tests(self, zones_features: List[Dict[str, Any]]) -> AnalysisResult:
535
+ """
536
+ Выполнить все тесты гипотез.
537
+
538
+ Args:
539
+ zones_features: Список словарей с характеристиками зон
540
+
541
+ Returns:
542
+ AnalysisResult с результатами всех тестов
543
+ """
544
+ self.logger.info("Running all hypothesis tests")
545
+
546
+ if not zones_features:
547
+ raise StatisticalAnalysisError("No zone features provided")
548
+
549
+ tests = {}
550
+
551
+ # Выполняем все тесты
552
+ test_methods = [
553
+ ('zone_duration', self.test_zone_duration_hypothesis),
554
+ ('histogram_slope', self.test_histogram_slope_hypothesis),
555
+ ('bull_bear_asymmetry', self.test_bull_bear_asymmetry_hypothesis),
556
+ ('sequence_patterns', self.test_sequence_hypothesis),
557
+ ('volatility_effects', self.test_volatility_hypothesis)
558
+ ]
559
+
560
+ for test_name, test_method in test_methods:
561
+ try:
562
+ result = test_method(zones_features)
563
+ tests[test_name] = result.to_dict()
564
+ except Exception as e:
565
+ self.logger.warning(f"Test {test_name} failed: {e}")
566
+ tests[test_name] = {
567
+ 'error': str(e),
568
+ 'test_type': test_name,
569
+ 'significant': False
570
+ }
571
+
572
+ # Подсчет значимых результатов
573
+ significant_count = sum(1 for test_result in tests.values()
574
+ if test_result.get('significant', False))
575
+
576
+ # Сводка
577
+ summary = {
578
+ 'total_tests': len(tests),
579
+ 'significant_tests': significant_count,
580
+ 'significance_rate': significant_count / len(tests) if tests else 0,
581
+ 'alpha_level': self.alpha,
582
+ 'total_zones': len(zones_features)
583
+ }
584
+
585
+ results = {
586
+ 'tests': tests,
587
+ 'summary': summary
588
+ }
589
+
590
+ metadata = {
591
+ 'analyzer': 'HypothesisTestSuite',
592
+ 'alpha': self.alpha,
593
+ 'timestamp': datetime.now().isoformat()
594
+ }
595
+
596
+ return AnalysisResult(
597
+ analysis_type='hypothesis_testing',
598
+ results=results,
599
+ data_size=len(zones_features),
600
+ metadata=metadata
601
+ )
602
+
603
+
604
+ # Удобные функции для быстрого использования
605
+ def run_all_hypothesis_tests(zones_features: List[Dict[str, Any]], alpha: float = 0.05) -> Dict[str, Any]:
606
+ """
607
+ Выполнить все тесты гипотез (совместимость с оригинальным API).
608
+
609
+ Args:
610
+ zones_features: Список словарей с характеристиками зон
611
+ alpha: Уровень значимости
612
+
613
+ Returns:
614
+ Словарь с результатами всех тестов
615
+ """
616
+ test_suite = HypothesisTestSuite(alpha=alpha)
617
+ analysis_result = test_suite.run_all_tests(zones_features)
618
+ return analysis_result.results
619
+
620
+
621
+ def test_single_hypothesis(zones_features: List[Dict[str, Any]],
622
+ test_type: str,
623
+ alpha: float = 0.05) -> HypothesisTestResult:
624
+ """
625
+ Выполнить один конкретный тест гипотезы.
626
+
627
+ Args:
628
+ zones_features: Список словарей с характеристиками зон
629
+ test_type: Тип теста ('duration', 'slope', 'asymmetry', 'sequence', 'volatility')
630
+ alpha: Уровень значимости
631
+
632
+ Returns:
633
+ HypothesisTestResult с результатами теста
634
+ """
635
+ test_suite = HypothesisTestSuite(alpha=alpha)
636
+
637
+ test_mapping = {
638
+ 'duration': test_suite.test_zone_duration_hypothesis,
639
+ 'slope': test_suite.test_histogram_slope_hypothesis,
640
+ 'asymmetry': test_suite.test_bull_bear_asymmetry_hypothesis,
641
+ 'sequence': test_suite.test_sequence_hypothesis,
642
+ 'volatility': test_suite.test_volatility_hypothesis
643
+ }
644
+
645
+ if test_type not in test_mapping:
646
+ raise ValueError(f"Unknown test type: {test_type}. Available: {list(test_mapping.keys())}")
647
+
648
+ return test_mapping[test_type](zones_features)
649
+
650
+
651
+ # Экспорт
652
+ __all__ = [
653
+ 'HypothesisTestResult',
654
+ 'HypothesisTestSuite',
655
+ 'run_all_hypothesis_tests',
656
+ 'test_single_hypothesis'
657
+ ]