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,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
+ ]