Как собрать свой дашборд на Python с несколькими инструментами
Привет! Меня зовут Григорий Соколов, я CTO Trade2Good. В этой статье я расскажу, как с помощью Python, а также библиотек Dash и Plotly, создать интерактивный дашборд для анализа финансовых данных. Мы будем использовать OHLC-данные (открытие, максимум, минимум, закрытие) — они часто применяются для анализа акций и других финансовых инструментов.
Создание дашбордов — это мощный инструмент для анализа данных и принятия решений. Вот ключевые причины, почему дашборды так важны:
Визуализация сложных данных
- Графики вместо чисел. Большие таблицы сложно читать. А дашборд помогает быстро увидеть тренды и аномалии.
- Пример: cвечной график OHLC сразу показывает, как колебалась цена акции — это не так очевидно в обычной таблице Excel.
Интерактивное исследование
- Глубокая аналитика без написания кода каждым сотрудником.
- «Живые» фильтры вместо статичных отчетовCopyDownload. Пользователи могут:
dcc.DatePickerRange() # Выбор периода
dcc.Dropdown() # Сравнение метрик
dcc.Checklist() # Включение индикаторов
3. Оперативное принятие решений
- Мониторинг в реальном времени. Дашборды могут обновляться через API (например, для трейдинга):
# Псевдокод для подключения биржевых данных
import yfinance as yf
data =yf.download('AAPL', period='1d', interval='1m')
4. Автоматизация отчетности
- Замена рутинных отчетов. Вместо еженедельных Excel-сводок — всегда актуальные данные.
- Экономия времени. Один дашборд заменяет ежедневные отчеты, презентации для руководства и ручные выгрузки для коллег.
5. Обнаружение аномалий
Визуальные триггеры для быстрого реагирования. Пример кода для выделения аномалий:
fig.add_shape( # Красная линия при падении цены >5%
type="line",
x0=df['Date'].min(), y0=alert_level,
x1=df['Date'].max(), y1=alert_level,
line=dict(color="red", width=2, dash="dot")
)
6. Универсальный инструмент
Применение в разных сферах с минимальными изменениями: финансы (анализ акций — как в нашем примере, маркетинг: конверсии и ROI кампаний, продажи (динамика выручки), логистика (мониторинг поставок).
Совместная работа
- Общее пространство для команды — все видят одни данные без искажений.
Масштабируемость
Базовый вариант:
# Только цена и объем
dcc.Graph(id='price-chart'),
dcc.Graph(id='volume-chart')
Продвинутая версия:
# Добавляем:
dcc.Graph(id='correlation-matrix'), # Корреляции активов
dcc.Graph(id='news-sentiment') # Анализ новостей
9. Интеграция с современным стеком
Дашборды легко встраиваются в веб-приложения (Flask/Django), BI-системы (Tableau/Power BI через iframe) и мобильные решения (с адаптивным дизайном).
Стек технологий
Dash — фреймворк для создания веб-дашбордов, Plotly — библиотека для интерактивной визуализации, Pandas — для обработки и анализа данных.
Создаем дашбоард
Шаг 1: Импорт необходимых библиотек
# Импорт библиотек для работы с данными
import pandas as pd # Для работы с табличными данными
import numpy as np # Для математических операций
# Импорт компонентов для работы с датами
from datetime import datetime, timedelta # Для работы с датами
# Импорт компонентов Dash для создания веб-интерфейса
from dash import Dash, dcc, html, Input, Output
# Dash - основной класс приложения
# dcc - компоненты графиков и элементов управления
# html - HTML-компоненты
# Input, Output - для создания интерактивности
# Импорт библиотек для визуализации
import plotly.graph_objects as go # Для создания сложных графиков
import plotly.express as px # Для создания простых графиков
Шаг 2: Генерация синтетических OHLC-данных
def generate_ohlc_data(days=30):
"""
Генерирует синтетические OHLC данные для демонстрации работы дашборда
Параметры:
days (int): количество дней данных для генерации
Возвращает:
pd.DataFrame: DataFrame с колонками Date, Open, High, Low, Close, Volume
"""
# Устанавливаем seed для воспроизводимости результатов
np.random.seed(42)
# Создаем список дат, начиная с текущей даты минус указанное количество дней
start_date = datetime.now() - timedelta(days=days)
dates = [start_date + timedelta(days=i) for i in range(days)]
# Генерируем базовую цену с небольшим случайным отклонением
base_price = 100 + np.random.normal(0, 10)
# Инициализируем списки для хранения данных
opens = [] # Цены открытия
highs = [] # Максимальные цены
lows = [] # Минимальные цены
closes = [] # Цены закрытия
volumes = [] # Объемы торгов
# Генерируем данные для каждого дня
for i in range(days):
# Цена открытия равна цене закрытия предыдущего дня с небольшим изменением
# Для первого дня используем базовую цену
open_price = base_price if i == 0 else closes[i-1] * (1 + np.random.normal(0, 0.02))
# Цена закрытия отличается от цены открытия
close_price = open_price * (1 + np.random.normal(0, 0.03))
# Максимальная цена - максимальная из open/close плюс небольшой шум
high_price = max(open_price, close_price) * (1 + abs(np.random.normal(0, 0.01)))
# Минимальная цена - минимальная из open/close минус небольшой шум
low_price = min(open_price, close_price) * (1 - abs(np.random.normal(0, 0.01)))
# Объем торгов - случайное значение вокруг 100000
volume = int(np.random.normal(100000, 20000))
# Добавляем сгенерированные значения в списки
opens.append(round(open_price, 2)) # Округляем до 2 знаков после запятой
closes.append(round(close_price, 2))
highs.append(round(high_price, 2))
lows.append(round(low_price, 2))
volumes.append(volume)
# Создаем DataFrame из сгенерированных данных
return pd.DataFrame({
'Date': dates, # Колонка с датами
'Open': opens, # Цены открытия
'High': highs, # Максимальные цены
'Low': lows, # Минимальные цены
'Close': closes, # Цены закрытия
'Volume': volumes # Объемы торгов
})
# Генерируем 60 дней данных и сохраняем в переменную ohlc_data
ohlc_data = generate_ohlc_data(60)
Шаг 3: Инициализация приложения Dash
# Создаем экземпляр приложения Dash
# __name__ помогает Dash находить статические файлы
app = Dash(__name__)
Шаг 4: Создание макета дашборда
# Определяем структуру дашборда с помощью компонентов Dash
app.layout = html.Div([
# Заголовок дашборда
html.H1("Финансовый аналитический дашборд",
style={'textAlign': 'center'}), # Центрируем заголовок
# Панель управления - левая часть дашборда
html.Div([
# Блок выбора типа графика
html.Div([
html.Label("Тип графика:"), # Метка для dropdown
dcc.Dropdown(
id='chart-type', # Идентификатор для callback
options=[ # Варианты выбора
{'label': 'Линейный', 'value': 'line'},
{'label': 'Свечной', 'value': 'candle'},
{'label': 'OHLC', 'value': 'ohlc'}
],
value='candle', # Значение по умолчанию
clearable=False # Запрещаем очистку выбора
),
], style={'padding': '10px'}), # Отступы для блока
# Блок выбора диапазона дат
html.Div([
html.Label("Диапазон дат:"),
dcc.DatePickerRange(
id='date-range',
min_date_allowed=ohlc_data['Date'].min(), # Минимальная доступная дата
max_date_allowed=ohlc_data['Date'].max(), # Максимальная доступная дата
start_date=ohlc_data['Date'].min(), # Начальная дата по умолчанию
end_date=ohlc_data['Date'].max() # Конечная дата по умолчанию
),
], style={'padding': '10px'}),
# Блок выбора скользящих средних
html.Div([
html.Label("Скользящие средние:"),
dcc.Checklist(
id='ma-toggle',
options=[ # Варианты скользящих средних
{'label': 'MA 7', 'value': 7},
{'label': 'MA 14', 'value': 14},
{'label': 'MA 30', 'value': 30}
],
value=[] # По умолчанию ничего не выбрано
),
], style={'padding': '10px'}),
# Блок выбора технических индикаторов
html.Div([
html.Label("Индикаторы:"),
dcc.Checklist(
id='rsi-toggle',
options=[
{'label': 'RSI (14)', 'value': 14} # Индекс относительной силы
],
value=[]
),
], style={'padding': '10px'}),
], style={
'width': '20%', # Ширина панели управления
'display': 'inline-block', # Расположение в строке
'vertical-align': 'top', # Выравнивание по верху
'border-right': '1px solid #ddd', # Граница справа
'padding': '10px' # Внутренние отступы
}),
# Основная область с графиками - правая часть дашборда
html.Div([
# График цен
dcc.Graph(id='price-chart'),
# График объемов
dcc.Graph(id='volume-chart'),
# График RSI (изначально скрыт)
dcc.Graph(id='rsi-chart', style={'display': 'none'})
], style={
'width': '78%', # Ширина области с графиками
'display': 'inline-block', # Расположение в строке
'padding': '10px' # Внутренние отступы
})
])
Шаг 5: Настройка интерактивности (callback)
# Декоратор callback определяет, какие компоненты будут обновляться
# при изменении входных параметров
@app.callback(
# Выходные параметры (что обновляем):
[Output('price-chart', 'figure'), # График цен
Output('volume-chart', 'figure'), # График объемов
Output('rsi-chart', 'figure'), # График RSI
Output('rsi-chart', 'style')], # Стиль графика RSI (видимость)
# Входные параметры (что отслеживаем):
[Input('chart-type', 'value'), # Тип графика
Input('date-range', 'start_date'), # Начальная дата
Input('date-range', 'end_date'), # Конечная дата
Input('ma-toggle', 'value'), # Выбранные скользящие средние
Input('rsi-toggle', 'value')] # Показать RSI
)
def update_charts(chart_type, start_date, end_date, ma_values, rsi_values):
"""
Обновляет все графики на основе выбранных пользователем параметров
Параметры:
chart_type (str): тип графика (line/candle/ohlc)
start_date (str): начальная дата в формате YYYY-MM-DD
end_date (str): конечная дата в формате YYYY-MM-DD
ma_values (list): список периодов для скользящих средних
rsi_values (list): список периодов для RSI
Возвращает:
tuple: (fig_price, fig_volume, fig_rsi, rsi_style)
"""
# 1. Фильтрация данных по выбранному диапазону дат
filtered_data = ohlc_data[
(ohlc_data['Date'] >= pd.to_datetime(start_date)) &
(ohlc_data['Date'] <= pd.to_datetime(end_date))
].copy() # Создаем копию, чтобы не изменять исходные данные
# 2. Создание ценового графика в зависимости от выбранного типа
if chart_type == 'line':
# Линейный график цен закрытия
price_fig = px.line(filtered_data,
x='Date',
y='Close',
title='Цена закрытия')
elif chart_type == 'candle':
# Свечной график
price_fig = go.Figure(data=[go.Candlestick(
x=filtered_data['Date'],
open=filtered_data['Open'],
high=filtered_data['High'],
low=filtered_data['Low'],
close=filtered_data['Close'],
name='Цена' # Название для легенды
)])
price_fig.update_layout(title='Свечной график')
else:
# OHLC график
price_fig = go.Figure(data=[go.Ohlc(
x=filtered_data['Date'],
open=filtered_data['Open'],
high=filtered_data['High'],
low=filtered_data['Low'],
close=filtered_data['Close'],
name='Цена'
)])
price_fig.update_layout(title='OHLC график')
# 3. Добавление скользящих средних, если они выбраны
for window in ma_values:
# Создаем колонку с MA для текущего окна
ma_col = f'MA_{window}'
filtered_data[ma_col] = filtered_data['Close'].rolling(window=window).mean()
# Добавляем линию MA на график
price_fig.add_trace(go.Scatter(
x=filtered_data['Date'],
y=filtered_data[ma_col],
name=f'MA {window}', # Название для легенды
line=dict(width=2) # Толщина линии
))
# 4. Создание графика объемов
volume_fig = px.bar(filtered_data,
x='Date',
y='Volume',
title='Объем торгов')
# 5. Подготовка графика RSI
rsi_fig = go.Figure() # Пустой график по умолчанию
rsi_style = {'display': 'none'} # Стиль - скрыт
# Если RSI выбран
if rsi_values:
window = rsi_values[0] # Получаем период RSI
# Рассчитываем RSI:
# 1. Разница между текущей и предыдущей ценой закрытия
delta = filtered_data['Close'].diff()
# 2. Отделяем рост и падение
gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
# 3. Рассчитываем относительную силу
rs = gain / loss
# 4. Рассчитываем RSI
rsi = 100 - (100 / (1 + rs))
# Создаем график RSI
rsi_fig.add_trace(go.Scatter(
x=filtered_data['Date'],
y=rsi,
name='RSI',
line=dict(color='purple', width=2)
))
# Добавляем горизонтальные линии уровней 30 и 70
rsi_fig.add_hline(y=30, line_dash="dash", line_color="red")
rsi_fig.add_hline(y=70, line_dash="dash", line_color="red")
# Настраиваем заголовок и размер
rsi_fig.update_layout(title=f'RSI ({window} дней)', height=300)
# Делаем график видимым
rsi_style = {'display': 'block'}
# 6. Общие настройки для всех графиков
for fig in [price_fig, volume_fig, rsi_fig]:
fig.update_layout(
hovermode='x unified', # Общий режим hover для всех графиков
margin=dict(l=20, r=20, t=40, b=20), # Отступы
xaxis=dict(rangeslider=dict(visible=False)) # Скрываем rangeslider
# Дополнительные настройки для графиков
price_fig.update_layout(height=500) # Высота ценового графика
volume_fig.update_layout(height=250) # Высота графика объемов
# Возвращаем все графики и стиль RSI
return price_fig, volume_fig, rsi_fig, rsi_style
Шаг 6: Запуск приложения
# Запускаем приложение, если скрипт выполняется напрямую
if __name__ == '__main__':
# app.run - основной метод для запуска приложения Dash
# debug=True - включение режима отладки
# host='0.0.0.0' - делает приложение доступным в локальной сети
# port=8050 - порт для доступа к приложению
app.run(debug=True, host='0.0.0.0', port=8050)

Как работает дашборд
- Генерация данных: Создаются синтетические OHLC-данные за 60 дней с случайными колебаниями цен.
- Инициализация Dash: Создается веб-приложение с помощью Dash.
- Макет интерфейса:
- Левая панель с элементами управления (выбор типа графика, диапазона дат, индикаторов)
- Правая область с тремя графиками (цен, объемов и RSI)
- Интерактивность:
- При изменении любого параметра срабатывает callback-функция
- Данные фильтруются по выбранному диапазону дат
- Строятся соответствующие графики с учетом всех выбранных параметров
- Технические индикаторы:
- Скользящие средние добавляются как дополнительные линии на ценовой график
- RSI отображается на отдельном графике, когда выбран
- Запуск: Приложение становится доступно по адресу http://localhost:8050
Этот дашборд можно легко расширить, добавив:
- Загрузку реальных данных через API (например, yfinance)
- Дополнительные технические индикаторы
- Возможность сравнения нескольких активов
- Экспорт данных и графиков
Дашборды превращают сырые данные в стратегические инсайты. Наш пример на Python с Plotly Dash — это отправная точка для создания профессиональных аналитических инструментов. С их помощью вы не просто смотрите на цифры, а видите историю, скрытую в данных, и можете действовать на опережение. код как всегда в нашем репозитории.