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,599 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Модуль тем оформления BQuant
|
|
3
|
+
|
|
4
|
+
Предоставляет различные темы оформления для графиков:
|
|
5
|
+
- Светлые и темные темы
|
|
6
|
+
- Финансовые цветовые схемы
|
|
7
|
+
- Настраиваемые стили
|
|
8
|
+
- Консистентность оформления
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Dict, Any, List, Optional, Union
|
|
12
|
+
import warnings
|
|
13
|
+
|
|
14
|
+
from ..core.logging_config import get_logger
|
|
15
|
+
|
|
16
|
+
# Получаем логгер для модуля
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
# Проверка доступности библиотек
|
|
20
|
+
try:
|
|
21
|
+
import plotly.graph_objects as go
|
|
22
|
+
import plotly.io as pio
|
|
23
|
+
PLOTLY_AVAILABLE = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
PLOTLY_AVAILABLE = False
|
|
26
|
+
logger.warning("Plotly not available - themes functionality will be limited")
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
import matplotlib.pyplot as plt
|
|
30
|
+
import matplotlib.style as mplstyle
|
|
31
|
+
import seaborn as sns
|
|
32
|
+
MATPLOTLIB_AVAILABLE = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
MATPLOTLIB_AVAILABLE = False
|
|
35
|
+
logger.warning("Matplotlib not available - themes functionality will be limited")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ChartThemes:
|
|
39
|
+
"""
|
|
40
|
+
Класс для управления темами оформления графиков.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self):
|
|
44
|
+
"""Инициализация менеджера тем."""
|
|
45
|
+
self.logger = get_logger(f"{__name__}.ChartThemes")
|
|
46
|
+
|
|
47
|
+
# Определяем доступные темы
|
|
48
|
+
self._themes = {
|
|
49
|
+
'bquant_light': self._get_bquant_light_theme(),
|
|
50
|
+
'bquant_dark': self._get_bquant_dark_theme(),
|
|
51
|
+
'financial': self._get_financial_theme(),
|
|
52
|
+
'minimal': self._get_minimal_theme(),
|
|
53
|
+
'professional': self._get_professional_theme()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Текущая тема по умолчанию
|
|
57
|
+
self._current_theme = 'bquant_light'
|
|
58
|
+
|
|
59
|
+
self.logger.info(f"Chart themes initialized with {len(self._themes)} available themes")
|
|
60
|
+
|
|
61
|
+
def get_available_themes(self) -> List[str]:
|
|
62
|
+
"""
|
|
63
|
+
Получить список доступных тем.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Список названий тем
|
|
67
|
+
"""
|
|
68
|
+
return list(self._themes.keys())
|
|
69
|
+
|
|
70
|
+
def get_theme(self, theme_name: str) -> Dict[str, Any]:
|
|
71
|
+
"""
|
|
72
|
+
Получить конфигурацию темы.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
theme_name: Название темы
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Словарь с конфигурацией темы
|
|
79
|
+
"""
|
|
80
|
+
if theme_name not in self._themes:
|
|
81
|
+
self.logger.warning(f"Theme '{theme_name}' not found, using default")
|
|
82
|
+
theme_name = self._current_theme
|
|
83
|
+
|
|
84
|
+
return self._themes[theme_name].copy()
|
|
85
|
+
|
|
86
|
+
def set_default_theme(self, theme_name: str) -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Установить тему по умолчанию.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
theme_name: Название темы
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
True если тема успешно установлена
|
|
95
|
+
"""
|
|
96
|
+
if theme_name not in self._themes:
|
|
97
|
+
self.logger.error(f"Theme '{theme_name}' not found")
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
self._current_theme = theme_name
|
|
101
|
+
self.logger.info(f"Default theme set to: {theme_name}")
|
|
102
|
+
|
|
103
|
+
# Применяем тему к библиотекам
|
|
104
|
+
self._apply_theme_to_libraries(theme_name)
|
|
105
|
+
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
def apply_theme_to_figure(self, fig: Union[go.Figure, plt.Figure],
|
|
109
|
+
theme_name: str = None) -> Union[go.Figure, plt.Figure]:
|
|
110
|
+
"""
|
|
111
|
+
Применить тему к конкретному графику.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
fig: Объект графика
|
|
115
|
+
theme_name: Название темы (если None, используется текущая)
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Обновленный объект графика
|
|
119
|
+
"""
|
|
120
|
+
if theme_name is None:
|
|
121
|
+
theme_name = self._current_theme
|
|
122
|
+
|
|
123
|
+
theme_config = self.get_theme(theme_name)
|
|
124
|
+
|
|
125
|
+
if PLOTLY_AVAILABLE and isinstance(fig, go.Figure):
|
|
126
|
+
return self._apply_plotly_theme(fig, theme_config)
|
|
127
|
+
elif MATPLOTLIB_AVAILABLE and hasattr(fig, 'patch'):
|
|
128
|
+
return self._apply_matplotlib_theme(fig, theme_config)
|
|
129
|
+
else:
|
|
130
|
+
self.logger.warning("Unsupported figure type for theme application")
|
|
131
|
+
return fig
|
|
132
|
+
|
|
133
|
+
def _apply_theme_to_libraries(self, theme_name: str):
|
|
134
|
+
"""Применить тему к библиотекам визуализации."""
|
|
135
|
+
theme_config = self.get_theme(theme_name)
|
|
136
|
+
|
|
137
|
+
# Plotly
|
|
138
|
+
if PLOTLY_AVAILABLE:
|
|
139
|
+
try:
|
|
140
|
+
plotly_template = self._create_plotly_template(theme_config)
|
|
141
|
+
pio.templates['bquant_custom'] = plotly_template
|
|
142
|
+
pio.templates.default = 'bquant_custom'
|
|
143
|
+
self.logger.debug(f"Applied {theme_name} theme to Plotly")
|
|
144
|
+
except Exception as e:
|
|
145
|
+
self.logger.warning(f"Failed to apply theme to Plotly: {e}")
|
|
146
|
+
|
|
147
|
+
# Matplotlib
|
|
148
|
+
if MATPLOTLIB_AVAILABLE:
|
|
149
|
+
try:
|
|
150
|
+
self._apply_matplotlib_rcparams(theme_config)
|
|
151
|
+
self.logger.debug(f"Applied {theme_name} theme to Matplotlib")
|
|
152
|
+
except Exception as e:
|
|
153
|
+
self.logger.warning(f"Failed to apply theme to Matplotlib: {e}")
|
|
154
|
+
|
|
155
|
+
def _apply_plotly_theme(self, fig: go.Figure, theme_config: Dict[str, Any]) -> go.Figure:
|
|
156
|
+
"""Применить тему к Plotly графику."""
|
|
157
|
+
colors = theme_config.get('colors', {})
|
|
158
|
+
layout = theme_config.get('layout', {})
|
|
159
|
+
|
|
160
|
+
# Обновляем layout
|
|
161
|
+
fig.update_layout(
|
|
162
|
+
plot_bgcolor=colors.get('background', '#ffffff'),
|
|
163
|
+
paper_bgcolor=colors.get('paper', '#ffffff'),
|
|
164
|
+
font=dict(
|
|
165
|
+
family=layout.get('font_family', 'Arial'),
|
|
166
|
+
size=layout.get('font_size', 12),
|
|
167
|
+
color=colors.get('text', '#000000')
|
|
168
|
+
),
|
|
169
|
+
title_font=dict(
|
|
170
|
+
size=layout.get('title_font_size', 16),
|
|
171
|
+
color=colors.get('text', '#000000')
|
|
172
|
+
),
|
|
173
|
+
showlegend=layout.get('show_legend', True),
|
|
174
|
+
legend=dict(
|
|
175
|
+
bgcolor=colors.get('legend_bg', 'rgba(255,255,255,0.8)'),
|
|
176
|
+
bordercolor=colors.get('legend_border', '#cccccc'),
|
|
177
|
+
borderwidth=1
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Обновляем оси
|
|
182
|
+
axis_config = dict(
|
|
183
|
+
gridcolor=colors.get('grid', '#eeeeee'),
|
|
184
|
+
linecolor=colors.get('axis_line', '#cccccc'),
|
|
185
|
+
tickcolor=colors.get('tick', '#cccccc'),
|
|
186
|
+
titlefont=dict(color=colors.get('text', '#000000')),
|
|
187
|
+
tickfont=dict(color=colors.get('text', '#000000'))
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
fig.update_xaxes(**axis_config)
|
|
191
|
+
fig.update_yaxes(**axis_config)
|
|
192
|
+
|
|
193
|
+
return fig
|
|
194
|
+
|
|
195
|
+
def _apply_matplotlib_theme(self, fig: plt.Figure, theme_config: Dict[str, Any]) -> plt.Figure:
|
|
196
|
+
"""Применить тему к Matplotlib графику."""
|
|
197
|
+
colors = theme_config.get('colors', {})
|
|
198
|
+
layout = theme_config.get('layout', {})
|
|
199
|
+
|
|
200
|
+
# Применяем настройки к figure
|
|
201
|
+
fig.patch.set_facecolor(colors.get('paper', '#ffffff'))
|
|
202
|
+
|
|
203
|
+
# Применяем к осям
|
|
204
|
+
for ax in fig.get_axes():
|
|
205
|
+
ax.set_facecolor(colors.get('background', '#ffffff'))
|
|
206
|
+
|
|
207
|
+
# Цвета осей и сетки
|
|
208
|
+
ax.tick_params(colors=colors.get('text', '#000000'))
|
|
209
|
+
ax.grid(True, color=colors.get('grid', '#eeeeee'), alpha=0.7)
|
|
210
|
+
ax.spines['bottom'].set_color(colors.get('axis_line', '#cccccc'))
|
|
211
|
+
ax.spines['top'].set_color(colors.get('axis_line', '#cccccc'))
|
|
212
|
+
ax.spines['right'].set_color(colors.get('axis_line', '#cccccc'))
|
|
213
|
+
ax.spines['left'].set_color(colors.get('axis_line', '#cccccc'))
|
|
214
|
+
|
|
215
|
+
# Заголовки
|
|
216
|
+
ax.title.set_color(colors.get('text', '#000000'))
|
|
217
|
+
ax.xaxis.label.set_color(colors.get('text', '#000000'))
|
|
218
|
+
ax.yaxis.label.set_color(colors.get('text', '#000000'))
|
|
219
|
+
|
|
220
|
+
return fig
|
|
221
|
+
|
|
222
|
+
def _create_plotly_template(self, theme_config: Dict[str, Any]) -> go.layout.Template:
|
|
223
|
+
"""Создать Plotly template из конфигурации темы."""
|
|
224
|
+
colors = theme_config.get('colors', {})
|
|
225
|
+
layout = theme_config.get('layout', {})
|
|
226
|
+
|
|
227
|
+
template = go.layout.Template()
|
|
228
|
+
|
|
229
|
+
# Layout настройки
|
|
230
|
+
template.layout = go.Layout(
|
|
231
|
+
font=dict(
|
|
232
|
+
family=layout.get('font_family', 'Arial'),
|
|
233
|
+
size=layout.get('font_size', 12),
|
|
234
|
+
color=colors.get('text', '#000000')
|
|
235
|
+
),
|
|
236
|
+
title_font=dict(
|
|
237
|
+
size=layout.get('title_font_size', 16)
|
|
238
|
+
),
|
|
239
|
+
plot_bgcolor=colors.get('background', '#ffffff'),
|
|
240
|
+
paper_bgcolor=colors.get('paper', '#ffffff'),
|
|
241
|
+
colorway=theme_config.get('color_sequence', [
|
|
242
|
+
'#1f77b4', '#ff7f0e', '#2ca02c', '#d62728',
|
|
243
|
+
'#9467bd', '#8c564b', '#e377c2', '#7f7f7f'
|
|
244
|
+
])
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Оси
|
|
248
|
+
axis_template = dict(
|
|
249
|
+
gridcolor=colors.get('grid', '#eeeeee'),
|
|
250
|
+
linecolor=colors.get('axis_line', '#cccccc'),
|
|
251
|
+
tickcolor=colors.get('tick', '#cccccc'),
|
|
252
|
+
zerolinecolor=colors.get('zero_line', '#cccccc')
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
template.layout.xaxis = axis_template
|
|
256
|
+
template.layout.yaxis = axis_template
|
|
257
|
+
|
|
258
|
+
return template
|
|
259
|
+
|
|
260
|
+
def _apply_matplotlib_rcparams(self, theme_config: Dict[str, Any]):
|
|
261
|
+
"""Применить настройки темы к matplotlib rcParams."""
|
|
262
|
+
colors = theme_config.get('colors', {})
|
|
263
|
+
layout = theme_config.get('layout', {})
|
|
264
|
+
|
|
265
|
+
# Обновляем rcParams
|
|
266
|
+
plt.rcParams.update({
|
|
267
|
+
'figure.facecolor': colors.get('paper', '#ffffff'),
|
|
268
|
+
'axes.facecolor': colors.get('background', '#ffffff'),
|
|
269
|
+
'axes.edgecolor': colors.get('axis_line', '#cccccc'),
|
|
270
|
+
'axes.labelcolor': colors.get('text', '#000000'),
|
|
271
|
+
'xtick.color': colors.get('text', '#000000'),
|
|
272
|
+
'ytick.color': colors.get('text', '#000000'),
|
|
273
|
+
'text.color': colors.get('text', '#000000'),
|
|
274
|
+
'grid.color': colors.get('grid', '#eeeeee'),
|
|
275
|
+
'font.family': layout.get('font_family', 'sans-serif'),
|
|
276
|
+
'font.size': layout.get('font_size', 12),
|
|
277
|
+
'axes.titlesize': layout.get('title_font_size', 16),
|
|
278
|
+
'figure.titlesize': layout.get('title_font_size', 16)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
# Определения тем
|
|
282
|
+
def _get_bquant_light_theme(self) -> Dict[str, Any]:
|
|
283
|
+
"""Светлая тема BQuant."""
|
|
284
|
+
return {
|
|
285
|
+
'name': 'BQuant Light',
|
|
286
|
+
'colors': {
|
|
287
|
+
'background': '#ffffff',
|
|
288
|
+
'paper': '#fafafa',
|
|
289
|
+
'text': '#2c3e50',
|
|
290
|
+
'grid': '#ecf0f1',
|
|
291
|
+
'axis_line': '#bdc3c7',
|
|
292
|
+
'tick': '#7f8c8d',
|
|
293
|
+
'zero_line': '#95a5a6',
|
|
294
|
+
'legend_bg': 'rgba(255,255,255,0.9)',
|
|
295
|
+
'legend_border': '#bdc3c7',
|
|
296
|
+
'bullish': '#27ae60',
|
|
297
|
+
'bearish': '#e74c3c',
|
|
298
|
+
'neutral': '#3498db',
|
|
299
|
+
'volume': '#95a5a6'
|
|
300
|
+
},
|
|
301
|
+
'layout': {
|
|
302
|
+
'font_family': 'Segoe UI, Arial, sans-serif',
|
|
303
|
+
'font_size': 12,
|
|
304
|
+
'title_font_size': 16,
|
|
305
|
+
'show_legend': True,
|
|
306
|
+
'grid_alpha': 0.3
|
|
307
|
+
},
|
|
308
|
+
'color_sequence': [
|
|
309
|
+
'#3498db', '#e74c3c', '#27ae60', '#f39c12',
|
|
310
|
+
'#9b59b6', '#1abc9c', '#34495e', '#e67e22'
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
def _get_bquant_dark_theme(self) -> Dict[str, Any]:
|
|
315
|
+
"""Темная тема BQuant."""
|
|
316
|
+
return {
|
|
317
|
+
'name': 'BQuant Dark',
|
|
318
|
+
'colors': {
|
|
319
|
+
'background': '#2c3e50',
|
|
320
|
+
'paper': '#34495e',
|
|
321
|
+
'text': '#ecf0f1',
|
|
322
|
+
'grid': '#4a5f7a',
|
|
323
|
+
'axis_line': '#7f8c8d',
|
|
324
|
+
'tick': '#bdc3c7',
|
|
325
|
+
'zero_line': '#95a5a6',
|
|
326
|
+
'legend_bg': 'rgba(52,73,94,0.9)',
|
|
327
|
+
'legend_border': '#7f8c8d',
|
|
328
|
+
'bullish': '#2ecc71',
|
|
329
|
+
'bearish': '#e74c3c',
|
|
330
|
+
'neutral': '#3498db',
|
|
331
|
+
'volume': '#95a5a6'
|
|
332
|
+
},
|
|
333
|
+
'layout': {
|
|
334
|
+
'font_family': 'Segoe UI, Arial, sans-serif',
|
|
335
|
+
'font_size': 12,
|
|
336
|
+
'title_font_size': 16,
|
|
337
|
+
'show_legend': True,
|
|
338
|
+
'grid_alpha': 0.4
|
|
339
|
+
},
|
|
340
|
+
'color_sequence': [
|
|
341
|
+
'#3498db', '#e74c3c', '#2ecc71', '#f39c12',
|
|
342
|
+
'#9b59b6', '#1abc9c', '#ecf0f1', '#e67e22'
|
|
343
|
+
]
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
def _get_financial_theme(self) -> Dict[str, Any]:
|
|
347
|
+
"""Финансовая тема."""
|
|
348
|
+
return {
|
|
349
|
+
'name': 'Financial',
|
|
350
|
+
'colors': {
|
|
351
|
+
'background': '#ffffff',
|
|
352
|
+
'paper': '#f8f9fa',
|
|
353
|
+
'text': '#212529',
|
|
354
|
+
'grid': '#dee2e6',
|
|
355
|
+
'axis_line': '#6c757d',
|
|
356
|
+
'tick': '#495057',
|
|
357
|
+
'zero_line': '#6c757d',
|
|
358
|
+
'legend_bg': 'rgba(248,249,250,0.95)',
|
|
359
|
+
'legend_border': '#dee2e6',
|
|
360
|
+
'bullish': '#198754',
|
|
361
|
+
'bearish': '#dc3545',
|
|
362
|
+
'neutral': '#0d6efd',
|
|
363
|
+
'volume': '#6c757d'
|
|
364
|
+
},
|
|
365
|
+
'layout': {
|
|
366
|
+
'font_family': 'Roboto, Helvetica, Arial, sans-serif',
|
|
367
|
+
'font_size': 11,
|
|
368
|
+
'title_font_size': 14,
|
|
369
|
+
'show_legend': True,
|
|
370
|
+
'grid_alpha': 0.5
|
|
371
|
+
},
|
|
372
|
+
'color_sequence': [
|
|
373
|
+
'#0d6efd', '#dc3545', '#198754', '#fd7e14',
|
|
374
|
+
'#6f42c1', '#20c997', '#212529', '#f77b72'
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
def _get_minimal_theme(self) -> Dict[str, Any]:
|
|
379
|
+
"""Минималистичная тема."""
|
|
380
|
+
return {
|
|
381
|
+
'name': 'Minimal',
|
|
382
|
+
'colors': {
|
|
383
|
+
'background': '#ffffff',
|
|
384
|
+
'paper': '#ffffff',
|
|
385
|
+
'text': '#333333',
|
|
386
|
+
'grid': '#f0f0f0',
|
|
387
|
+
'axis_line': '#cccccc',
|
|
388
|
+
'tick': '#999999',
|
|
389
|
+
'zero_line': '#cccccc',
|
|
390
|
+
'legend_bg': 'rgba(255,255,255,0.8)',
|
|
391
|
+
'legend_border': '#e0e0e0',
|
|
392
|
+
'bullish': '#4caf50',
|
|
393
|
+
'bearish': '#f44336',
|
|
394
|
+
'neutral': '#2196f3',
|
|
395
|
+
'volume': '#9e9e9e'
|
|
396
|
+
},
|
|
397
|
+
'layout': {
|
|
398
|
+
'font_family': 'Helvetica Neue, Arial, sans-serif',
|
|
399
|
+
'font_size': 11,
|
|
400
|
+
'title_font_size': 14,
|
|
401
|
+
'show_legend': False,
|
|
402
|
+
'grid_alpha': 0.2
|
|
403
|
+
},
|
|
404
|
+
'color_sequence': [
|
|
405
|
+
'#2196f3', '#f44336', '#4caf50', '#ff9800',
|
|
406
|
+
'#9c27b0', '#00bcd4', '#607d8b', '#795548'
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
def _get_professional_theme(self) -> Dict[str, Any]:
|
|
411
|
+
"""Профессиональная тема."""
|
|
412
|
+
return {
|
|
413
|
+
'name': 'Professional',
|
|
414
|
+
'colors': {
|
|
415
|
+
'background': '#fefefe',
|
|
416
|
+
'paper': '#f5f5f5',
|
|
417
|
+
'text': '#1a1a1a',
|
|
418
|
+
'grid': '#e8e8e8',
|
|
419
|
+
'axis_line': '#666666',
|
|
420
|
+
'tick': '#4a4a4a',
|
|
421
|
+
'zero_line': '#666666',
|
|
422
|
+
'legend_bg': 'rgba(245,245,245,0.95)',
|
|
423
|
+
'legend_border': '#cccccc',
|
|
424
|
+
'bullish': '#2e7d32',
|
|
425
|
+
'bearish': '#c62828',
|
|
426
|
+
'neutral': '#1565c0',
|
|
427
|
+
'volume': '#757575'
|
|
428
|
+
},
|
|
429
|
+
'layout': {
|
|
430
|
+
'font_family': 'Times New Roman, serif',
|
|
431
|
+
'font_size': 12,
|
|
432
|
+
'title_font_size': 16,
|
|
433
|
+
'show_legend': True,
|
|
434
|
+
'grid_alpha': 0.4
|
|
435
|
+
},
|
|
436
|
+
'color_sequence': [
|
|
437
|
+
'#1565c0', '#c62828', '#2e7d32', '#ef6c00',
|
|
438
|
+
'#5e35b1', '#00838f', '#424242', '#d84315'
|
|
439
|
+
]
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
# Глобальный экземпляр менеджера тем
|
|
444
|
+
_theme_manager = ChartThemes()
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
# Удобные функции
|
|
448
|
+
def get_available_themes() -> List[str]:
|
|
449
|
+
"""
|
|
450
|
+
Получить список доступных тем.
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
Список названий тем
|
|
454
|
+
"""
|
|
455
|
+
return _theme_manager.get_available_themes()
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def get_theme(theme_name: str) -> Dict[str, Any]:
|
|
459
|
+
"""
|
|
460
|
+
Получить конфигурацию темы.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
theme_name: Название темы
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
Словарь с конфигурацией темы
|
|
467
|
+
"""
|
|
468
|
+
return _theme_manager.get_theme(theme_name)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def apply_theme(theme_name: str) -> bool:
|
|
472
|
+
"""
|
|
473
|
+
Применить тему ко всем последующим графикам.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
theme_name: Название темы
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
True если тема успешно применена
|
|
480
|
+
"""
|
|
481
|
+
return _theme_manager.set_default_theme(theme_name)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def apply_theme_to_figure(fig: Union[go.Figure, plt.Figure],
|
|
485
|
+
theme_name: str = None) -> Union[go.Figure, plt.Figure]:
|
|
486
|
+
"""
|
|
487
|
+
Применить тему к конкретному графику.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
fig: Объект графика
|
|
491
|
+
theme_name: Название темы
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
Обновленный объект графика
|
|
495
|
+
"""
|
|
496
|
+
return _theme_manager.apply_theme_to_figure(fig, theme_name)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def get_theme_colors(theme_name: str = None) -> Dict[str, str]:
|
|
500
|
+
"""
|
|
501
|
+
Получить цветовую схему темы.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
theme_name: Название темы (если None, используется текущая)
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
Словарь с цветами темы
|
|
508
|
+
"""
|
|
509
|
+
if theme_name is None:
|
|
510
|
+
theme_name = _theme_manager._current_theme
|
|
511
|
+
|
|
512
|
+
theme_config = _theme_manager.get_theme(theme_name)
|
|
513
|
+
return theme_config.get('colors', {})
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def reset_theme():
|
|
517
|
+
"""Сбросить тему к значениям по умолчанию."""
|
|
518
|
+
_theme_manager.set_default_theme('bquant_light')
|
|
519
|
+
|
|
520
|
+
# Сброс matplotlib к defaults
|
|
521
|
+
if MATPLOTLIB_AVAILABLE:
|
|
522
|
+
plt.rcdefaults()
|
|
523
|
+
|
|
524
|
+
logger.info("Theme reset to default")
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def create_custom_theme(name: str,
|
|
528
|
+
colors: Dict[str, str],
|
|
529
|
+
layout: Dict[str, Any] = None) -> bool:
|
|
530
|
+
"""
|
|
531
|
+
Создать пользовательскую тему.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
name: Название темы
|
|
535
|
+
colors: Словарь с цветами
|
|
536
|
+
layout: Настройки макета (опционально)
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
True если тема успешно создана
|
|
540
|
+
"""
|
|
541
|
+
if layout is None:
|
|
542
|
+
layout = {
|
|
543
|
+
'font_family': 'Arial, sans-serif',
|
|
544
|
+
'font_size': 12,
|
|
545
|
+
'title_font_size': 16,
|
|
546
|
+
'show_legend': True,
|
|
547
|
+
'grid_alpha': 0.3
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
# Дополняем цвета значениями по умолчанию
|
|
551
|
+
default_colors = _theme_manager._get_bquant_light_theme()['colors']
|
|
552
|
+
full_colors = {**default_colors, **colors}
|
|
553
|
+
|
|
554
|
+
custom_theme = {
|
|
555
|
+
'name': name,
|
|
556
|
+
'colors': full_colors,
|
|
557
|
+
'layout': layout,
|
|
558
|
+
'color_sequence': colors.get('color_sequence', default_colors.get('color_sequence', []))
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
_theme_manager._themes[name] = custom_theme
|
|
562
|
+
logger.info(f"Custom theme '{name}' created")
|
|
563
|
+
|
|
564
|
+
return True
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def list_theme_info():
|
|
568
|
+
"""Вывести информацию о всех доступных темах."""
|
|
569
|
+
themes = _theme_manager.get_available_themes()
|
|
570
|
+
current = _theme_manager._current_theme
|
|
571
|
+
|
|
572
|
+
print("Available BQuant Themes:")
|
|
573
|
+
print("=" * 30)
|
|
574
|
+
|
|
575
|
+
for theme_name in themes:
|
|
576
|
+
theme_config = _theme_manager.get_theme(theme_name)
|
|
577
|
+
status = " (current)" if theme_name == current else ""
|
|
578
|
+
print(f" • {theme_config['name']}{status}")
|
|
579
|
+
print(f" ID: {theme_name}")
|
|
580
|
+
|
|
581
|
+
colors = theme_config.get('colors', {})
|
|
582
|
+
if 'bullish' in colors and 'bearish' in colors:
|
|
583
|
+
print(f" Colors: Bullish({colors['bullish']}), Bearish({colors['bearish']})")
|
|
584
|
+
|
|
585
|
+
print()
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
# Экспорт
|
|
589
|
+
__all__ = [
|
|
590
|
+
'ChartThemes',
|
|
591
|
+
'get_available_themes',
|
|
592
|
+
'get_theme',
|
|
593
|
+
'apply_theme',
|
|
594
|
+
'apply_theme_to_figure',
|
|
595
|
+
'get_theme_colors',
|
|
596
|
+
'reset_theme',
|
|
597
|
+
'create_custom_theme',
|
|
598
|
+
'list_theme_info'
|
|
599
|
+
]
|