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.
- bquant/__init__.py +31 -0
- bquant/analysis/__init__.py +297 -0
- bquant/analysis/candlestick/__init__.py +100 -0
- bquant/analysis/chart/__init__.py +98 -0
- bquant/analysis/statistical/__init__.py +414 -0
- bquant/analysis/statistical/hypothesis_testing.py +657 -0
- bquant/analysis/technical/__init__.py +98 -0
- bquant/analysis/timeseries/__init__.py +102 -0
- bquant/analysis/zones/__init__.py +547 -0
- bquant/analysis/zones/sequence_analysis.py +661 -0
- bquant/analysis/zones/zone_features.py +520 -0
- bquant/cli.py +160 -0
- bquant/core/__init__.py +112 -0
- bquant/core/cache.py +534 -0
- bquant/core/config.py +328 -0
- bquant/core/exceptions.py +351 -0
- bquant/core/logging_config.py +368 -0
- bquant/core/numpy_fix.py +99 -0
- bquant/core/performance.py +554 -0
- bquant/core/utils.py +327 -0
- bquant/data/__init__.py +85 -0
- bquant/data/loader.py +436 -0
- bquant/data/processor.py +644 -0
- bquant/data/samples/__init__.py +442 -0
- bquant/data/samples/datasets.py +246 -0
- bquant/data/samples/embedded/__init__.py +16 -0
- bquant/data/samples/embedded/mt_xauusd_m15.py +9018 -0
- bquant/data/samples/embedded/tv_xauusd_1h.py +17018 -0
- bquant/data/samples/utils.py +393 -0
- bquant/data/schemas.py +285 -0
- bquant/data/validator.py +498 -0
- bquant/indicators/__init__.py +115 -0
- bquant/indicators/base.py +487 -0
- bquant/indicators/calculators.py +404 -0
- bquant/indicators/library.py +514 -0
- bquant/indicators/loaders.py +413 -0
- bquant/indicators/macd.py +754 -0
- bquant/ml/__init__.py +19 -0
- bquant/visualization/__init__.py +316 -0
- bquant/visualization/charts.py +616 -0
- bquant/visualization/statistical.py +812 -0
- bquant/visualization/themes.py +599 -0
- bquant/visualization/zones.py +775 -0
- bquant-0.0.0.dist-info/METADATA +167 -0
- bquant-0.0.0.dist-info/RECORD +49 -0
- bquant-0.0.0.dist-info/WHEEL +5 -0
- bquant-0.0.0.dist-info/entry_points.txt +2 -0
- bquant-0.0.0.dist-info/licenses/LICENSE +21 -0
- bquant-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Модуль визуализации зон BQuant
|
|
3
|
+
|
|
4
|
+
Предоставляет инструменты для визуализации торговых зон:
|
|
5
|
+
- Отображение MACD зон на графиках
|
|
6
|
+
- Визуализация характеристик зон
|
|
7
|
+
- Интерактивные графики анализа зон
|
|
8
|
+
- Статистические диаграммы зон
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
import numpy as np
|
|
13
|
+
from typing import Dict, Any, List, Optional, Union, Tuple
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
import warnings
|
|
16
|
+
|
|
17
|
+
from ..core.logging_config import get_logger
|
|
18
|
+
from ..core.exceptions import AnalysisError
|
|
19
|
+
|
|
20
|
+
# Получаем логгер для модуля
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
# Проверка доступности библиотек визуализации
|
|
24
|
+
try:
|
|
25
|
+
import plotly.graph_objects as go
|
|
26
|
+
import plotly.subplots as sp
|
|
27
|
+
from plotly.subplots import make_subplots
|
|
28
|
+
import plotly.express as px
|
|
29
|
+
PLOTLY_AVAILABLE = True
|
|
30
|
+
except ImportError:
|
|
31
|
+
PLOTLY_AVAILABLE = False
|
|
32
|
+
logger.warning("Plotly not available - zones visualization will be limited")
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
import matplotlib.pyplot as plt
|
|
36
|
+
import matplotlib.patches as patches
|
|
37
|
+
import seaborn as sns
|
|
38
|
+
MATPLOTLIB_AVAILABLE = True
|
|
39
|
+
except ImportError:
|
|
40
|
+
MATPLOTLIB_AVAILABLE = False
|
|
41
|
+
logger.warning("Matplotlib not available - zones visualization will be limited")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ZoneChartBuilder:
|
|
45
|
+
"""
|
|
46
|
+
Базовый класс для построения графиков зон.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, backend: str = 'plotly'):
|
|
50
|
+
"""
|
|
51
|
+
Инициализация построителя графиков зон.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
backend: Библиотека для построения ('plotly' или 'matplotlib')
|
|
55
|
+
"""
|
|
56
|
+
self.backend = backend
|
|
57
|
+
self.logger = get_logger(f"{__name__}.ZoneChartBuilder")
|
|
58
|
+
|
|
59
|
+
# Проверяем доступность выбранной библиотеки
|
|
60
|
+
if backend == 'plotly' and not PLOTLY_AVAILABLE:
|
|
61
|
+
if MATPLOTLIB_AVAILABLE:
|
|
62
|
+
self.backend = 'matplotlib'
|
|
63
|
+
self.logger.warning("Plotly not available, switching to matplotlib")
|
|
64
|
+
else:
|
|
65
|
+
raise AnalysisError("No visualization libraries available")
|
|
66
|
+
|
|
67
|
+
elif backend == 'matplotlib' and not MATPLOTLIB_AVAILABLE:
|
|
68
|
+
if PLOTLY_AVAILABLE:
|
|
69
|
+
self.backend = 'plotly'
|
|
70
|
+
self.logger.warning("Matplotlib not available, switching to plotly")
|
|
71
|
+
else:
|
|
72
|
+
raise AnalysisError("No visualization libraries available")
|
|
73
|
+
|
|
74
|
+
# Цветовая схема для зон
|
|
75
|
+
self.zone_colors = {
|
|
76
|
+
'bull': {'fill': 'rgba(0, 255, 136, 0.3)', 'line': '#00ff88'},
|
|
77
|
+
'bear': {'fill': 'rgba(255, 68, 68, 0.3)', 'line': '#ff4444'},
|
|
78
|
+
'support': {'fill': 'rgba(0, 136, 255, 0.3)', 'line': '#0088ff'},
|
|
79
|
+
'resistance': {'fill': 'rgba(255, 136, 0, 0.3)', 'line': '#ff8800'}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
self.logger.info(f"Zone chart builder initialized with {self.backend} backend")
|
|
83
|
+
|
|
84
|
+
def _prepare_zone_data(self, zones_data: Union[List[Dict], pd.DataFrame]) -> List[Dict]:
|
|
85
|
+
"""
|
|
86
|
+
Подготовка данных зон для визуализации.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
zones_data: Данные зон
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Список словарей с данными зон
|
|
93
|
+
"""
|
|
94
|
+
if isinstance(zones_data, pd.DataFrame):
|
|
95
|
+
return zones_data.to_dict('records')
|
|
96
|
+
elif isinstance(zones_data, list):
|
|
97
|
+
return zones_data
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError("zones_data must be DataFrame or list of dicts")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ZoneVisualizer(ZoneChartBuilder):
|
|
103
|
+
"""
|
|
104
|
+
Класс для визуализации торговых зон.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, backend: str = 'plotly', **kwargs):
|
|
108
|
+
"""
|
|
109
|
+
Инициализация визуализатора зон.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
backend: Библиотека для построения
|
|
113
|
+
**kwargs: Дополнительные параметры
|
|
114
|
+
"""
|
|
115
|
+
super().__init__(backend)
|
|
116
|
+
|
|
117
|
+
# Настройки по умолчанию
|
|
118
|
+
self.default_config = {
|
|
119
|
+
'width': kwargs.get('width', 1200),
|
|
120
|
+
'height': kwargs.get('height', 800),
|
|
121
|
+
'show_zone_labels': kwargs.get('show_zone_labels', True),
|
|
122
|
+
'show_zone_stats': kwargs.get('show_zone_stats', True),
|
|
123
|
+
'opacity': kwargs.get('opacity', 0.3)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
def plot_zones_on_price_chart(self, price_data: pd.DataFrame,
|
|
127
|
+
zones_data: Union[List[Dict], pd.DataFrame],
|
|
128
|
+
title: str = "Price Chart with Zones",
|
|
129
|
+
**kwargs) -> Union[go.Figure, plt.Figure]:
|
|
130
|
+
"""
|
|
131
|
+
Отображение зон на графике цен.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
price_data: DataFrame с данными цен (OHLCV)
|
|
135
|
+
zones_data: Данные зон
|
|
136
|
+
title: Заголовок графика
|
|
137
|
+
**kwargs: Дополнительные параметры
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Объект графика
|
|
141
|
+
"""
|
|
142
|
+
zones = self._prepare_zone_data(zones_data)
|
|
143
|
+
|
|
144
|
+
if self.backend == 'plotly':
|
|
145
|
+
return self._create_plotly_zones_on_price(price_data, zones, title, **kwargs)
|
|
146
|
+
else:
|
|
147
|
+
return self._create_matplotlib_zones_on_price(price_data, zones, title, **kwargs)
|
|
148
|
+
|
|
149
|
+
def plot_macd_zones(self, macd_data: pd.DataFrame,
|
|
150
|
+
zones_data: Union[List[Dict], pd.DataFrame],
|
|
151
|
+
title: str = "MACD with Zones",
|
|
152
|
+
**kwargs) -> Union[go.Figure, plt.Figure]:
|
|
153
|
+
"""
|
|
154
|
+
Отображение зон на графике MACD.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
macd_data: DataFrame с данными MACD
|
|
158
|
+
zones_data: Данные зон
|
|
159
|
+
title: Заголовок графика
|
|
160
|
+
**kwargs: Дополнительные параметры
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Объект графика
|
|
164
|
+
"""
|
|
165
|
+
zones = self._prepare_zone_data(zones_data)
|
|
166
|
+
|
|
167
|
+
if self.backend == 'plotly':
|
|
168
|
+
return self._create_plotly_macd_zones(macd_data, zones, title, **kwargs)
|
|
169
|
+
else:
|
|
170
|
+
return self._create_matplotlib_macd_zones(macd_data, zones, title, **kwargs)
|
|
171
|
+
|
|
172
|
+
def plot_zones_analysis(self, zones_data: Union[List[Dict], pd.DataFrame],
|
|
173
|
+
analysis_data: Dict[str, Any] = None,
|
|
174
|
+
title: str = "Zones Analysis",
|
|
175
|
+
**kwargs) -> Union[go.Figure, plt.Figure]:
|
|
176
|
+
"""
|
|
177
|
+
Визуализация анализа зон.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
zones_data: Данные зон
|
|
181
|
+
analysis_data: Результаты анализа зон (опционально)
|
|
182
|
+
title: Заголовок графика
|
|
183
|
+
**kwargs: Дополнительные параметры
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Объект графика
|
|
187
|
+
"""
|
|
188
|
+
zones = self._prepare_zone_data(zones_data)
|
|
189
|
+
|
|
190
|
+
if self.backend == 'plotly':
|
|
191
|
+
return self._create_plotly_zones_analysis(zones, analysis_data, title, **kwargs)
|
|
192
|
+
else:
|
|
193
|
+
return self._create_matplotlib_zones_analysis(zones, analysis_data, title, **kwargs)
|
|
194
|
+
|
|
195
|
+
def plot_zones_distribution(self, zones_data: Union[List[Dict], pd.DataFrame],
|
|
196
|
+
feature: str = 'duration',
|
|
197
|
+
title: str = "Zones Distribution",
|
|
198
|
+
**kwargs) -> Union[go.Figure, plt.Figure]:
|
|
199
|
+
"""
|
|
200
|
+
Визуализация распределения характеристик зон.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
zones_data: Данные зон
|
|
204
|
+
feature: Характеристика для анализа
|
|
205
|
+
title: Заголовок графика
|
|
206
|
+
**kwargs: Дополнительные параметры
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Объект графика
|
|
210
|
+
"""
|
|
211
|
+
zones = self._prepare_zone_data(zones_data)
|
|
212
|
+
|
|
213
|
+
if self.backend == 'plotly':
|
|
214
|
+
return self._create_plotly_zones_distribution(zones, feature, title, **kwargs)
|
|
215
|
+
else:
|
|
216
|
+
return self._create_matplotlib_zones_distribution(zones, feature, title, **kwargs)
|
|
217
|
+
|
|
218
|
+
def plot_zones_correlation(self, zones_data: Union[List[Dict], pd.DataFrame],
|
|
219
|
+
title: str = "Zones Characteristics Correlation",
|
|
220
|
+
**kwargs) -> Union[go.Figure, plt.Figure]:
|
|
221
|
+
"""
|
|
222
|
+
Визуализация корреляций характеристик зон.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
zones_data: Данные зон
|
|
226
|
+
title: Заголовок графика
|
|
227
|
+
**kwargs: Дополнительные параметры
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Объект графика
|
|
231
|
+
"""
|
|
232
|
+
zones_df = pd.DataFrame(self._prepare_zone_data(zones_data))
|
|
233
|
+
|
|
234
|
+
if self.backend == 'plotly':
|
|
235
|
+
return self._create_plotly_zones_correlation(zones_df, title, **kwargs)
|
|
236
|
+
else:
|
|
237
|
+
return self._create_matplotlib_zones_correlation(zones_df, title, **kwargs)
|
|
238
|
+
|
|
239
|
+
# Plotly реализации
|
|
240
|
+
def _create_plotly_zones_on_price(self, price_data: pd.DataFrame,
|
|
241
|
+
zones: List[Dict], title: str,
|
|
242
|
+
**kwargs) -> go.Figure:
|
|
243
|
+
"""Создание графика цен с зонами с помощью Plotly."""
|
|
244
|
+
# Основной график свечей
|
|
245
|
+
fig = go.Figure()
|
|
246
|
+
|
|
247
|
+
# Добавляем свечной график
|
|
248
|
+
fig.add_trace(go.Candlestick(
|
|
249
|
+
x=price_data.index,
|
|
250
|
+
open=price_data['open'],
|
|
251
|
+
high=price_data['high'],
|
|
252
|
+
low=price_data['low'],
|
|
253
|
+
close=price_data['close'],
|
|
254
|
+
name='Price',
|
|
255
|
+
increasing_line_color='#00ff88',
|
|
256
|
+
decreasing_line_color='#ff4444'
|
|
257
|
+
))
|
|
258
|
+
|
|
259
|
+
# Добавляем зоны
|
|
260
|
+
for i, zone in enumerate(zones):
|
|
261
|
+
if 'start_time' in zone and 'end_time' in zone:
|
|
262
|
+
zone_type = zone.get('type', 'bull')
|
|
263
|
+
color_config = self.zone_colors.get(zone_type, self.zone_colors['bull'])
|
|
264
|
+
|
|
265
|
+
# Определяем границы зоны по цене
|
|
266
|
+
if 'start_price' in zone and 'end_price' in zone:
|
|
267
|
+
y0 = min(zone['start_price'], zone['end_price'])
|
|
268
|
+
y1 = max(zone['start_price'], zone['end_price'])
|
|
269
|
+
else:
|
|
270
|
+
# Если нет ценовых границ, используем весь диапазон
|
|
271
|
+
y0 = price_data['low'].min()
|
|
272
|
+
y1 = price_data['high'].max()
|
|
273
|
+
|
|
274
|
+
# Добавляем прямоугольник зоны
|
|
275
|
+
fig.add_shape(
|
|
276
|
+
type="rect",
|
|
277
|
+
x0=zone['start_time'],
|
|
278
|
+
y0=y0,
|
|
279
|
+
x1=zone['end_time'],
|
|
280
|
+
y1=y1,
|
|
281
|
+
fillcolor=color_config['fill'],
|
|
282
|
+
line=dict(color=color_config['line'], width=1),
|
|
283
|
+
layer="below"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Добавляем подпись зоны
|
|
287
|
+
if self.default_config['show_zone_labels']:
|
|
288
|
+
fig.add_annotation(
|
|
289
|
+
x=zone['start_time'],
|
|
290
|
+
y=y1,
|
|
291
|
+
text=f"{zone_type.title()} Zone {i+1}",
|
|
292
|
+
showarrow=False,
|
|
293
|
+
font=dict(size=10),
|
|
294
|
+
bgcolor="white",
|
|
295
|
+
opacity=0.8
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
fig.update_layout(
|
|
299
|
+
title=title,
|
|
300
|
+
width=self.default_config['width'],
|
|
301
|
+
height=self.default_config['height'],
|
|
302
|
+
xaxis_rangeslider_visible=False,
|
|
303
|
+
template='plotly_white'
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
return fig
|
|
307
|
+
|
|
308
|
+
def _create_plotly_macd_zones(self, macd_data: pd.DataFrame,
|
|
309
|
+
zones: List[Dict], title: str,
|
|
310
|
+
**kwargs) -> go.Figure:
|
|
311
|
+
"""Создание графика MACD с зонами с помощью Plotly."""
|
|
312
|
+
fig = make_subplots(
|
|
313
|
+
rows=2, cols=1,
|
|
314
|
+
shared_xaxes=True,
|
|
315
|
+
vertical_spacing=0.05,
|
|
316
|
+
row_heights=[0.6, 0.4],
|
|
317
|
+
subplot_titles=['MACD with Zones', 'Histogram']
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# MACD линии
|
|
321
|
+
fig.add_trace(go.Scatter(
|
|
322
|
+
x=macd_data.index,
|
|
323
|
+
y=macd_data['macd'],
|
|
324
|
+
mode='lines',
|
|
325
|
+
name='MACD',
|
|
326
|
+
line=dict(color='blue', width=2)
|
|
327
|
+
), row=1, col=1)
|
|
328
|
+
|
|
329
|
+
fig.add_trace(go.Scatter(
|
|
330
|
+
x=macd_data.index,
|
|
331
|
+
y=macd_data['macd_signal'],
|
|
332
|
+
mode='lines',
|
|
333
|
+
name='Signal',
|
|
334
|
+
line=dict(color='red', width=2)
|
|
335
|
+
), row=1, col=1)
|
|
336
|
+
|
|
337
|
+
# Гистограмма
|
|
338
|
+
colors = ['green' if val >= 0 else 'red' for val in macd_data['macd_hist']]
|
|
339
|
+
fig.add_trace(go.Bar(
|
|
340
|
+
x=macd_data.index,
|
|
341
|
+
y=macd_data['macd_hist'],
|
|
342
|
+
name='Histogram',
|
|
343
|
+
marker_color=colors,
|
|
344
|
+
opacity=0.7
|
|
345
|
+
), row=2, col=1)
|
|
346
|
+
|
|
347
|
+
# Добавляем зоны
|
|
348
|
+
for i, zone in enumerate(zones):
|
|
349
|
+
if 'start_time' in zone and 'end_time' in zone:
|
|
350
|
+
zone_type = zone.get('type', 'bull')
|
|
351
|
+
color = 'lightblue' if zone_type == 'bull' else 'lightpink'
|
|
352
|
+
|
|
353
|
+
# Добавляем зону на график MACD
|
|
354
|
+
fig.add_vrect(
|
|
355
|
+
x0=zone['start_time'],
|
|
356
|
+
x1=zone['end_time'],
|
|
357
|
+
fillcolor=color,
|
|
358
|
+
opacity=self.default_config['opacity'],
|
|
359
|
+
layer="below",
|
|
360
|
+
line_width=0,
|
|
361
|
+
row=1, col=1
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Добавляем зону на гистограмму
|
|
365
|
+
fig.add_vrect(
|
|
366
|
+
x0=zone['start_time'],
|
|
367
|
+
x1=zone['end_time'],
|
|
368
|
+
fillcolor=color,
|
|
369
|
+
opacity=self.default_config['opacity'],
|
|
370
|
+
layer="below",
|
|
371
|
+
line_width=0,
|
|
372
|
+
row=2, col=1
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
fig.update_layout(
|
|
376
|
+
title=title,
|
|
377
|
+
width=self.default_config['width'],
|
|
378
|
+
height=self.default_config['height'],
|
|
379
|
+
template='plotly_white',
|
|
380
|
+
showlegend=True
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return fig
|
|
384
|
+
|
|
385
|
+
def _create_plotly_zones_analysis(self, zones: List[Dict],
|
|
386
|
+
analysis_data: Dict[str, Any],
|
|
387
|
+
title: str, **kwargs) -> go.Figure:
|
|
388
|
+
"""Создание графика анализа зон с помощью Plotly."""
|
|
389
|
+
# Создаем DataFrame из зон
|
|
390
|
+
zones_df = pd.DataFrame(zones)
|
|
391
|
+
|
|
392
|
+
if zones_df.empty:
|
|
393
|
+
# Создаем пустой график с сообщением
|
|
394
|
+
fig = go.Figure()
|
|
395
|
+
fig.add_annotation(
|
|
396
|
+
text="No zones data available",
|
|
397
|
+
xref="paper", yref="paper",
|
|
398
|
+
x=0.5, y=0.5, xanchor='center', yanchor='middle'
|
|
399
|
+
)
|
|
400
|
+
fig.update_layout(title=title)
|
|
401
|
+
return fig
|
|
402
|
+
|
|
403
|
+
# Создаем подграфики для различных аспектов анализа
|
|
404
|
+
fig = make_subplots(
|
|
405
|
+
rows=2, cols=2,
|
|
406
|
+
subplot_titles=['Zones by Type', 'Duration Distribution',
|
|
407
|
+
'Return Distribution', 'Zone Timeline'],
|
|
408
|
+
specs=[[{"type": "pie"}, {"type": "histogram"}],
|
|
409
|
+
[{"type": "histogram"}, {"type": "scatter"}]]
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# 1. Распределение по типам зон
|
|
413
|
+
if 'zone_type' in zones_df.columns:
|
|
414
|
+
type_counts = zones_df['zone_type'].value_counts()
|
|
415
|
+
fig.add_trace(go.Pie(
|
|
416
|
+
labels=type_counts.index,
|
|
417
|
+
values=type_counts.values,
|
|
418
|
+
name="Zone Types"
|
|
419
|
+
), row=1, col=1)
|
|
420
|
+
|
|
421
|
+
# 2. Распределение длительности
|
|
422
|
+
if 'duration' in zones_df.columns:
|
|
423
|
+
fig.add_trace(go.Histogram(
|
|
424
|
+
x=zones_df['duration'],
|
|
425
|
+
name="Duration",
|
|
426
|
+
nbinsx=20
|
|
427
|
+
), row=1, col=2)
|
|
428
|
+
|
|
429
|
+
# 3. Распределение доходности
|
|
430
|
+
if 'price_return' in zones_df.columns:
|
|
431
|
+
fig.add_trace(go.Histogram(
|
|
432
|
+
x=zones_df['price_return'],
|
|
433
|
+
name="Returns",
|
|
434
|
+
nbinsx=20
|
|
435
|
+
), row=2, col=1)
|
|
436
|
+
|
|
437
|
+
# 4. Временная линия зон
|
|
438
|
+
if 'start_time' in zones_df.columns and 'duration' in zones_df.columns:
|
|
439
|
+
fig.add_trace(go.Scatter(
|
|
440
|
+
x=zones_df['start_time'] if 'start_time' in zones_df.columns else range(len(zones_df)),
|
|
441
|
+
y=zones_df['duration'],
|
|
442
|
+
mode='markers',
|
|
443
|
+
name="Zone Timeline",
|
|
444
|
+
marker=dict(
|
|
445
|
+
size=8,
|
|
446
|
+
color=zones_df['zone_type'].map({'bull': 'blue', 'bear': 'red'}) if 'zone_type' in zones_df.columns else 'blue'
|
|
447
|
+
)
|
|
448
|
+
), row=2, col=2)
|
|
449
|
+
|
|
450
|
+
fig.update_layout(
|
|
451
|
+
title=title,
|
|
452
|
+
width=self.default_config['width'],
|
|
453
|
+
height=self.default_config['height'],
|
|
454
|
+
template='plotly_white'
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
return fig
|
|
458
|
+
|
|
459
|
+
def _create_plotly_zones_distribution(self, zones: List[Dict],
|
|
460
|
+
feature: str, title: str,
|
|
461
|
+
**kwargs) -> go.Figure:
|
|
462
|
+
"""Создание графика распределения характеристик зон с помощью Plotly."""
|
|
463
|
+
zones_df = pd.DataFrame(zones)
|
|
464
|
+
|
|
465
|
+
if zones_df.empty or feature not in zones_df.columns:
|
|
466
|
+
fig = go.Figure()
|
|
467
|
+
fig.add_annotation(
|
|
468
|
+
text=f"No data available for feature: {feature}",
|
|
469
|
+
xref="paper", yref="paper",
|
|
470
|
+
x=0.5, y=0.5, xanchor='center', yanchor='middle'
|
|
471
|
+
)
|
|
472
|
+
fig.update_layout(title=title)
|
|
473
|
+
return fig
|
|
474
|
+
|
|
475
|
+
# Создаем подграфики
|
|
476
|
+
fig = make_subplots(
|
|
477
|
+
rows=1, cols=2,
|
|
478
|
+
subplot_titles=[f'{feature.title()} Distribution', f'{feature.title()} by Zone Type']
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
# Общее распределение
|
|
482
|
+
fig.add_trace(go.Histogram(
|
|
483
|
+
x=zones_df[feature],
|
|
484
|
+
name=f"All {feature}",
|
|
485
|
+
nbinsx=20,
|
|
486
|
+
opacity=0.7
|
|
487
|
+
), row=1, col=1)
|
|
488
|
+
|
|
489
|
+
# Распределение по типам зон
|
|
490
|
+
if 'zone_type' in zones_df.columns:
|
|
491
|
+
for zone_type in zones_df['zone_type'].unique():
|
|
492
|
+
type_data = zones_df[zones_df['zone_type'] == zone_type][feature]
|
|
493
|
+
fig.add_trace(go.Histogram(
|
|
494
|
+
x=type_data,
|
|
495
|
+
name=f"{zone_type.title()} {feature}",
|
|
496
|
+
nbinsx=15,
|
|
497
|
+
opacity=0.7
|
|
498
|
+
), row=1, col=2)
|
|
499
|
+
|
|
500
|
+
fig.update_layout(
|
|
501
|
+
title=title,
|
|
502
|
+
width=self.default_config['width'],
|
|
503
|
+
height=self.default_config['height'],
|
|
504
|
+
template='plotly_white',
|
|
505
|
+
barmode='overlay'
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
return fig
|
|
509
|
+
|
|
510
|
+
def _create_plotly_zones_correlation(self, zones_df: pd.DataFrame,
|
|
511
|
+
title: str, **kwargs) -> go.Figure:
|
|
512
|
+
"""Создание матрицы корреляций характеристик зон с помощью Plotly."""
|
|
513
|
+
# Выбираем только числовые колонки
|
|
514
|
+
numeric_columns = zones_df.select_dtypes(include=[np.number]).columns
|
|
515
|
+
|
|
516
|
+
if len(numeric_columns) < 2:
|
|
517
|
+
fig = go.Figure()
|
|
518
|
+
fig.add_annotation(
|
|
519
|
+
text="Insufficient numeric data for correlation analysis",
|
|
520
|
+
xref="paper", yref="paper",
|
|
521
|
+
x=0.5, y=0.5, xanchor='center', yanchor='middle'
|
|
522
|
+
)
|
|
523
|
+
fig.update_layout(title=title)
|
|
524
|
+
return fig
|
|
525
|
+
|
|
526
|
+
# Вычисляем корреляции
|
|
527
|
+
corr_matrix = zones_df[numeric_columns].corr()
|
|
528
|
+
|
|
529
|
+
# Создаем heatmap
|
|
530
|
+
fig = go.Figure(data=go.Heatmap(
|
|
531
|
+
z=corr_matrix.values,
|
|
532
|
+
x=corr_matrix.columns,
|
|
533
|
+
y=corr_matrix.columns,
|
|
534
|
+
colorscale='RdBu',
|
|
535
|
+
zmin=-1, zmax=1,
|
|
536
|
+
text=np.round(corr_matrix.values, 2),
|
|
537
|
+
texttemplate="%{text}",
|
|
538
|
+
textfont={"size": 10},
|
|
539
|
+
hoverongaps=False
|
|
540
|
+
))
|
|
541
|
+
|
|
542
|
+
fig.update_layout(
|
|
543
|
+
title=title,
|
|
544
|
+
width=self.default_config['width'],
|
|
545
|
+
height=self.default_config['height'],
|
|
546
|
+
template='plotly_white'
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
return fig
|
|
550
|
+
|
|
551
|
+
# Matplotlib реализации (упрощенные)
|
|
552
|
+
def _create_matplotlib_zones_on_price(self, price_data: pd.DataFrame,
|
|
553
|
+
zones: List[Dict], title: str,
|
|
554
|
+
**kwargs) -> plt.Figure:
|
|
555
|
+
"""Создание графика цен с зонами с помощью Matplotlib."""
|
|
556
|
+
fig, ax = plt.subplots(figsize=(12, 6))
|
|
557
|
+
|
|
558
|
+
# Простой линейный график цен
|
|
559
|
+
ax.plot(price_data.index, price_data['close'], label='Close Price', linewidth=1)
|
|
560
|
+
|
|
561
|
+
# Добавляем зоны как вертикальные полосы
|
|
562
|
+
for i, zone in enumerate(zones):
|
|
563
|
+
if 'start_time' in zone and 'end_time' in zone:
|
|
564
|
+
zone_type = zone.get('type', 'bull')
|
|
565
|
+
color = 'lightblue' if zone_type == 'bull' else 'lightpink'
|
|
566
|
+
|
|
567
|
+
ax.axvspan(zone['start_time'], zone['end_time'],
|
|
568
|
+
alpha=self.default_config['opacity'],
|
|
569
|
+
color=color,
|
|
570
|
+
label=f"{zone_type.title()} Zone" if i == 0 else "")
|
|
571
|
+
|
|
572
|
+
ax.set_title(title)
|
|
573
|
+
ax.legend()
|
|
574
|
+
ax.grid(True, alpha=0.3)
|
|
575
|
+
|
|
576
|
+
return fig
|
|
577
|
+
|
|
578
|
+
def _create_matplotlib_macd_zones(self, macd_data: pd.DataFrame,
|
|
579
|
+
zones: List[Dict], title: str,
|
|
580
|
+
**kwargs) -> plt.Figure:
|
|
581
|
+
"""Создание графика MACD с зонами с помощью Matplotlib."""
|
|
582
|
+
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
|
|
583
|
+
|
|
584
|
+
# MACD
|
|
585
|
+
ax1.plot(macd_data.index, macd_data['macd'], label='MACD', color='blue')
|
|
586
|
+
ax1.plot(macd_data.index, macd_data['macd_signal'], label='Signal', color='red')
|
|
587
|
+
ax1.set_title('MACD with Zones')
|
|
588
|
+
ax1.legend()
|
|
589
|
+
ax1.grid(True, alpha=0.3)
|
|
590
|
+
|
|
591
|
+
# Гистограмма
|
|
592
|
+
colors = ['green' if val >= 0 else 'red' for val in macd_data['macd_hist']]
|
|
593
|
+
ax2.bar(macd_data.index, macd_data['macd_hist'], color=colors, alpha=0.7)
|
|
594
|
+
ax2.set_title('Histogram')
|
|
595
|
+
ax2.grid(True, alpha=0.3)
|
|
596
|
+
|
|
597
|
+
# Добавляем зоны
|
|
598
|
+
for zone in zones:
|
|
599
|
+
if 'start_time' in zone and 'end_time' in zone:
|
|
600
|
+
zone_type = zone.get('type', 'bull')
|
|
601
|
+
color = 'lightblue' if zone_type == 'bull' else 'lightpink'
|
|
602
|
+
|
|
603
|
+
ax1.axvspan(zone['start_time'], zone['end_time'],
|
|
604
|
+
alpha=self.default_config['opacity'], color=color)
|
|
605
|
+
ax2.axvspan(zone['start_time'], zone['end_time'],
|
|
606
|
+
alpha=self.default_config['opacity'], color=color)
|
|
607
|
+
|
|
608
|
+
plt.tight_layout()
|
|
609
|
+
return fig
|
|
610
|
+
|
|
611
|
+
def _create_matplotlib_zones_analysis(self, zones: List[Dict],
|
|
612
|
+
analysis_data: Dict[str, Any],
|
|
613
|
+
title: str, **kwargs) -> plt.Figure:
|
|
614
|
+
"""Создание графика анализа зон с помощью Matplotlib."""
|
|
615
|
+
zones_df = pd.DataFrame(zones)
|
|
616
|
+
|
|
617
|
+
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 8))
|
|
618
|
+
|
|
619
|
+
# 1. Распределение по типам
|
|
620
|
+
if 'zone_type' in zones_df.columns:
|
|
621
|
+
type_counts = zones_df['zone_type'].value_counts()
|
|
622
|
+
ax1.pie(type_counts.values, labels=type_counts.index, autopct='%1.1f%%')
|
|
623
|
+
ax1.set_title('Zones by Type')
|
|
624
|
+
|
|
625
|
+
# 2. Распределение длительности
|
|
626
|
+
if 'duration' in zones_df.columns:
|
|
627
|
+
ax2.hist(zones_df['duration'], bins=20, alpha=0.7)
|
|
628
|
+
ax2.set_title('Duration Distribution')
|
|
629
|
+
ax2.set_xlabel('Duration')
|
|
630
|
+
|
|
631
|
+
# 3. Распределение доходности
|
|
632
|
+
if 'price_return' in zones_df.columns:
|
|
633
|
+
ax3.hist(zones_df['price_return'], bins=20, alpha=0.7)
|
|
634
|
+
ax3.set_title('Return Distribution')
|
|
635
|
+
ax3.set_xlabel('Return')
|
|
636
|
+
|
|
637
|
+
# 4. Scatter plot длительность vs доходность
|
|
638
|
+
if 'duration' in zones_df.columns and 'price_return' in zones_df.columns:
|
|
639
|
+
if 'zone_type' in zones_df.columns:
|
|
640
|
+
for zone_type in zones_df['zone_type'].unique():
|
|
641
|
+
type_data = zones_df[zones_df['zone_type'] == zone_type]
|
|
642
|
+
ax4.scatter(type_data['duration'], type_data['price_return'],
|
|
643
|
+
label=zone_type.title(), alpha=0.7)
|
|
644
|
+
ax4.legend()
|
|
645
|
+
else:
|
|
646
|
+
ax4.scatter(zones_df['duration'], zones_df['price_return'], alpha=0.7)
|
|
647
|
+
ax4.set_xlabel('Duration')
|
|
648
|
+
ax4.set_ylabel('Return')
|
|
649
|
+
ax4.set_title('Duration vs Return')
|
|
650
|
+
|
|
651
|
+
plt.suptitle(title)
|
|
652
|
+
plt.tight_layout()
|
|
653
|
+
return fig
|
|
654
|
+
|
|
655
|
+
def _create_matplotlib_zones_distribution(self, zones: List[Dict],
|
|
656
|
+
feature: str, title: str,
|
|
657
|
+
**kwargs) -> plt.Figure:
|
|
658
|
+
"""Создание графика распределения с помощью Matplotlib."""
|
|
659
|
+
zones_df = pd.DataFrame(zones)
|
|
660
|
+
|
|
661
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
|
|
662
|
+
|
|
663
|
+
# Общее распределение
|
|
664
|
+
if feature in zones_df.columns:
|
|
665
|
+
ax1.hist(zones_df[feature], bins=20, alpha=0.7)
|
|
666
|
+
ax1.set_title(f'{feature.title()} Distribution')
|
|
667
|
+
ax1.set_xlabel(feature.title())
|
|
668
|
+
|
|
669
|
+
# По типам зон
|
|
670
|
+
if 'zone_type' in zones_df.columns and feature in zones_df.columns:
|
|
671
|
+
for zone_type in zones_df['zone_type'].unique():
|
|
672
|
+
type_data = zones_df[zones_df['zone_type'] == zone_type][feature]
|
|
673
|
+
ax2.hist(type_data, bins=15, alpha=0.7, label=zone_type.title())
|
|
674
|
+
ax2.set_title(f'{feature.title()} by Zone Type')
|
|
675
|
+
ax2.set_xlabel(feature.title())
|
|
676
|
+
ax2.legend()
|
|
677
|
+
|
|
678
|
+
plt.suptitle(title)
|
|
679
|
+
plt.tight_layout()
|
|
680
|
+
return fig
|
|
681
|
+
|
|
682
|
+
def _create_matplotlib_zones_correlation(self, zones_df: pd.DataFrame,
|
|
683
|
+
title: str, **kwargs) -> plt.Figure:
|
|
684
|
+
"""Создание матрицы корреляций с помощью Matplotlib."""
|
|
685
|
+
# Выбираем только числовые колонки
|
|
686
|
+
numeric_columns = zones_df.select_dtypes(include=[np.number]).columns
|
|
687
|
+
|
|
688
|
+
if len(numeric_columns) < 2:
|
|
689
|
+
fig, ax = plt.subplots(figsize=(8, 6))
|
|
690
|
+
ax.text(0.5, 0.5, 'Insufficient numeric data for correlation analysis',
|
|
691
|
+
ha='center', va='center')
|
|
692
|
+
ax.set_title(title)
|
|
693
|
+
return fig
|
|
694
|
+
|
|
695
|
+
# Вычисляем корреляции
|
|
696
|
+
corr_matrix = zones_df[numeric_columns].corr()
|
|
697
|
+
|
|
698
|
+
# Создаем heatmap
|
|
699
|
+
fig, ax = plt.subplots(figsize=(10, 8))
|
|
700
|
+
im = ax.imshow(corr_matrix.values, cmap='RdBu', vmin=-1, vmax=1)
|
|
701
|
+
|
|
702
|
+
# Настраиваем оси
|
|
703
|
+
ax.set_xticks(range(len(corr_matrix.columns)))
|
|
704
|
+
ax.set_yticks(range(len(corr_matrix.columns)))
|
|
705
|
+
ax.set_xticklabels(corr_matrix.columns, rotation=45)
|
|
706
|
+
ax.set_yticklabels(corr_matrix.columns)
|
|
707
|
+
|
|
708
|
+
# Добавляем значения
|
|
709
|
+
for i in range(len(corr_matrix.columns)):
|
|
710
|
+
for j in range(len(corr_matrix.columns)):
|
|
711
|
+
text = ax.text(j, i, f'{corr_matrix.iloc[i, j]:.2f}',
|
|
712
|
+
ha="center", va="center", color="black")
|
|
713
|
+
|
|
714
|
+
fig.colorbar(im)
|
|
715
|
+
ax.set_title(title)
|
|
716
|
+
plt.tight_layout()
|
|
717
|
+
return fig
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
# Удобные функции
|
|
721
|
+
def plot_zones_on_chart(price_data: pd.DataFrame, zones_data, **kwargs):
|
|
722
|
+
"""
|
|
723
|
+
Быстрое отображение зон на графике цен.
|
|
724
|
+
|
|
725
|
+
Args:
|
|
726
|
+
price_data: DataFrame с данными цен
|
|
727
|
+
zones_data: Данные зон
|
|
728
|
+
**kwargs: Дополнительные параметры
|
|
729
|
+
|
|
730
|
+
Returns:
|
|
731
|
+
Объект графика
|
|
732
|
+
"""
|
|
733
|
+
visualizer = ZoneVisualizer()
|
|
734
|
+
return visualizer.plot_zones_on_price_chart(price_data, zones_data, **kwargs)
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def plot_macd_zones_chart(macd_data: pd.DataFrame, zones_data, **kwargs):
|
|
738
|
+
"""
|
|
739
|
+
Быстрое отображение зон на графике MACD.
|
|
740
|
+
|
|
741
|
+
Args:
|
|
742
|
+
macd_data: DataFrame с данными MACD
|
|
743
|
+
zones_data: Данные зон
|
|
744
|
+
**kwargs: Дополнительные параметры
|
|
745
|
+
|
|
746
|
+
Returns:
|
|
747
|
+
Объект графика
|
|
748
|
+
"""
|
|
749
|
+
visualizer = ZoneVisualizer()
|
|
750
|
+
return visualizer.plot_macd_zones(macd_data, zones_data, **kwargs)
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def analyze_zones_visually(zones_data, **kwargs):
|
|
754
|
+
"""
|
|
755
|
+
Быстрый визуальный анализ зон.
|
|
756
|
+
|
|
757
|
+
Args:
|
|
758
|
+
zones_data: Данные зон
|
|
759
|
+
**kwargs: Дополнительные параметры
|
|
760
|
+
|
|
761
|
+
Returns:
|
|
762
|
+
Объект графика
|
|
763
|
+
"""
|
|
764
|
+
visualizer = ZoneVisualizer()
|
|
765
|
+
return visualizer.plot_zones_analysis(zones_data, **kwargs)
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
# Экспорт
|
|
769
|
+
__all__ = [
|
|
770
|
+
'ZoneChartBuilder',
|
|
771
|
+
'ZoneVisualizer',
|
|
772
|
+
'plot_zones_on_chart',
|
|
773
|
+
'plot_macd_zones_chart',
|
|
774
|
+
'analyze_zones_visually'
|
|
775
|
+
]
|