Как создать криптовалютный дашборд с помощью Plotly и API Binance

Упрощаем отслеживание активов

Как установить API Binance

Binance предоставляет два типа доступа к API: реальный и тестовый.

Настраиваем реальный API Binance

Реальный API обеспечивает прямой доступ к аккаунту. В нем будут отражены любые сделки, совершенные через этот API. Поэтому использовать его следует с осторожностью.

Для начала нужно зарегистрироваться на Binance.

После регистрации вам будет предложено установить двухфакторную аутентификацию (2FA). Вы также можете подключить ее самостоятельно в настройках безопасности.

Затем переходим во вкладку API Management в настройках. Вам будет предложено установить метки для ключей API. Эта функция пригодится тем, у кого есть несколько ключей, связанных с одним аккаунтом.

Указываем метки и нажимаем Create API. После этого нужно пройти аутентификацию еще раз. Затем отобразятся API Key и Secret Key. Скопируйте их в безопасное место. По умолчанию получить доступ к ключам можно следующим способом, который можно изменить:

Мы будем использовать ключи как реального, так и тестового API, чтобы понять, как с ними работать. С целью не засорять реальный аккаунт установим доступ только для чтения для ключей реального API:

Сохранять ключи реального API мы будем в файл secret.cfg, как показано ниже. Не забывайте, что нельзя ни с кем делиться этим файлом.

[BINANCE]
ACTUAL_API_KEY = <your-actual-api-key>
ACTUAL_SECRET_KEY = <your-actual-secret-key>

Настраиваем тестовый API Binance

Тестовый API Binance полностью имитирует взаимодействие с реальным API. Рекомендуем для начала повзаимодействовать с ним, чтобы убедиться в корректности работы приложения.

Для начала необходимо войти в систему: https://testnet.binance.vision/ (на данный момент вход поддерживается только с GitHub).

Затем нажмите на Generate HMAC_SHA256 Key (Сгенерировать ключ HMAC_SHA256) и снова укажите метки для ключей. Отобразившиеся после создания ключи также скопируйте в безопасное место. Все подробности о тестовом API можно прочитать на главной странице.

Теперь добавим ключи тестового API в файл secret.cfg, как показано ниже:

[BINANCE]
ACTUAL_API_KEY = <your-actual-api-key>
ACTUAL_SECRET_KEY = <your-actual-secret-key>
TEST_API_KEY = <your-test-api-key>
TEST_SECRET_KEY = <your-test-secret-key>

Мы успешно настроили ключи реального и тестового API и сохранили их в файле secret.cfg. Теперь можно переходить к получению данных.

Как получить данные с помощью API Binance

Устанавливаем библиотеку python-binance

Binance не предоставляет библиотеку Python для взаимодействия с API. Поэтому мы воспользуемся популярным сторонним инструментом под названием python-binance.

Устанавливаем python-binance с помощью следующей команды:

$ pip install python-binance

Получаем информацию об аккаунте

В этом разделе мы воспользуемся тестовым аккаунтом. По умолчанию в нем будет отображаться баланс различных криптовалют. У python-binance нет доступа к тестовому API, поэтому мы поменяем URL-адрес конечной точки.

Код ниже предоставляет информацию о тестовом аккаунте:

# Импорт библиотек 
from binance.client import Client
import configparser

# Загрузка ключей из файла config
config = configparser.ConfigParser()
config.read_file(open('<path-to-your-config-file>'))
test_api_key = config.get('BINANCE', 'TEST_API_KEY')
test_secret_key = config.get('BINANCE', 'TEST_SECRET_KEY')

client = Client(test_api_key, test_secret_key)

client.API_URL = 'https://testnet.binance.vision/api'  # Это нужно, чтобы изменить URL-адрес конечной точки тестового аккаунта  

info = client.get_account()  # Получение информации об аккаунте

print(info)

Мы получаем такие важные данные, как тип аккаунта (accountType), баланс, разрешение и прочие.

Теперь получим баланс ETH:

balance = client.get_asset_balance(asset='ETH')

print(balance)

Библиотека python-binance предоставляет много возможностей. Подробную информацию о ней можно найти в документации.

Получаем архивные данные

Тестовый API выдает фиктивные архивные данные. Поэтому мы воспользуемся реальным API и его ключами.

Ниже показано, как получить стоимость ETH на Binance с самой ранней даты до текущего дня:

# Импорт библиотек 
from binance.client import Client
import configparser
import pandas as pd

# Загрузка ключей из файла config
config = configparser.ConfigParser()
config.read_file(open('<path-to-your-config-file>'))
actual_api_key = config.get('BINANCE', 'ACTUAL_API_KEY')
actual_secret_key = config.get('BINANCE', 'ACTUAL_SECRET_KEY')

client = Client(actual_api_key, actual_secret_key)

# Получение самой ранней доступной даты на Binance
earliest_timestamp = client._get_earliest_valid_timestamp('ETHUSDT', '1d')  # Здесь "ETHUSDT" - валютная пара, а "1d" - временной интервал
print(earliest_timestamp)

# Получение архивных данных (Candle data или Kline)
candle = client.get_historical_klines("ETHUSDT", "1d", earliest_timestamp, limit=1000)

print(candle[1])

Вывод выше представляет следующие параметры, упомянутые в документации Binance API:

Преобразовываем полученные данные в датафрейм и сохраняем его как файл CSV:

eth_df = pd.DataFrame(candle, columns=['dateTime', 'open', 'high', 'low', 'close', 'volume', 'closeTime', 'quoteAssetVolume', 'numberOfTrades', 'takerBuyBaseVol', 'takerBuyQuoteVol', 'ignore'])
eth_df.dateTime = pd.to_datetime(eth_df.dateTime, unit='ms')
eth_df.closeTime = pd.to_datetime(eth_df.closeTime, unit='ms')
eth_df.set_index('dateTime', inplace=True)
eth_df.to_csv('eth_candle.csv')

print(eth_df.tail(10))

Получаем данные в реальном времени

Чтобы передавать данные в реальном времени, можно воспользоваться WebSocket Binance. Вот как это сделать:

# Импорт библиотек 
from binance.client import Client
import configparser
from binance.websockets import BinanceSocketManager
from twisted.internet import reactor 

# Загрузка ключей из файла config
config = configparser.ConfigParser()
config.read_file(open('<path-to-your-config-file>'))
actual_api_key = config.get('BINANCE', 'ACTUAL_API_KEY')
actual_secret_key = config.get('BINANCE', 'ACTUAL_SECRET_KEY')

client = Client(actual_api_key, actual_secret_key)


def streaming_data_process(msg):
    """
    Function to process the received messages
    param msg: input message
    """
    print(f"message type: {msg['e']}")
    print(f"close price: {msg['c']}")
    print(f"best ask price: {msg['a']}")
    print(f"best bid price: {msg['b']}")
    print("---------------------------")
    
# Запуск WebSocket
bm = BinanceSocketManager(client)
conn_key = bm.start_symbol_ticker_socket('ETHUSDT', streaming_data_process)
bm.start()

Ниже показано, как остановить потоковую передачу данных и закрыть WebSocket:

# Остановка websocket
bm.stop_socket(conn_key)

# Websocket использует цикл reactor из библиотеки Twisted. Метод выше
# остановит соединение с веб-сокетом, но не цикл reactor, поэтому код может 
# выйти не сразу.

# Поэтому нужно указать
reactor.stop()

Итак, мы научились получать данные несколькими способами. Теперь можно переходить к созданию дашборда Plotly.

Как создать дашборд с помощью Plotly

В этом разделе мы создадим дашборд с помощью Plotly, который будет отслеживать криптовалютный портфель тестового аккаунта в реальном времени и изменять его общую стоимость в зависимости от получаемых данных.

Вот как будет выглядеть финальная версия дашборда. Детали внешнего вида можно изменить позднее:

В дашборд включены следующие функции:

  • Индикатор: общая стоимость портфеля в USDT.
  • Индикатор: общая стоимость портфеля в BTC.
  • Индикатор: конвертация BNB/USDT.
  • Круговая диаграмма: распределение портфеля (в USDT).
  • Столбчатая диаграмма: распределение токенов.

Теперь рассмотрим код.

  1. Импортируем все необходимые библиотеки:
# Импорт библиотек
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
from binance.client import Client
import configparser
from binance.websockets import BinanceSocketManager
import time

2. Прочитываем все ключи, устанавливаем соединение и получаем информацию об аккаунте:

# Загрузка ключей из файла config
config = configparser.ConfigParser()
config.read_file(open(
    '/home/venom/GitHub/blog_code/building_cryptocurrency_dashboard_plotly_binanceAPI/secret.cfg'))
test_api_key = config.get('BINANCE', 'TEST_API_KEY')
test_secret_key = config.get('BINANCE', 'TEST_SECRET_KEY')

# Установка соединения
client = Client(test_api_key, test_secret_key)
client.API_URL = 'https://testnet.binance.vision/api'  # Это нужно, чтобы изменить URL-адрес конечной точки тестового аккаунт

# Получение информации об аккаунте для проверки баланса
info = client.get_account()  # Получение информации об аккаунте

# Сохранение различных токенов и соответствующих величин в списках
assets = []
values = []
for index in range(len(info['balances'])):
    for key in info['balances'][index]:
        if key == 'asset':
            assets.append(info['balances'][index][key])
        if key == 'free':
            values.append(info['balances'][index][key])


token_usdt = {}  # Словарь для хранения стоимости валютной пары в USDT
token_pairs = []  # Список для хранения различных пар токенов

# Создание пар токенов и сохранение их в список
for token in assets:
    if token != 'USDT':
        token_pairs.append(token + 'USDT')

3. Определяем функции, которые будут обрабатывать потоковые данные и рассчитывать показатели на их основе:

def streaming_data_process(msg):
    """
    Function to process the received messages and add latest token pair price
    into the token_usdt dictionary
    :param msg: input message
    """
    global token_usdt
    token_usdt[msg['s']] = msg['c']


def total_amount_usdt(assets, values, token_usdt):
    """
    Function to calculate total portfolio value in USDT
    :param assets: Assets list
    :param values: Assets quantity
    :param token_usdt: Token pair price dict
    :return: total value in USDT
    """
    total_amount = 0
    for i, token in enumerate(assets):
        if token != 'USDT':
            total_amount += float(values[i]) * float(
                token_usdt[token + 'USDT'])
        else:
            total_amount += float(values[i]) * 1
    return total_amount


def total_amount_btc(assets, values, token_usdt):
    """
    Function to calculate total portfolio value in BTC
    :param assets: Assets list
    :param values: Assets quantity
    :param token_usdt: Token pair price dict
    :return: total value in BTC
    """
    total_amount = 0
    for i, token in enumerate(assets):
        if token != 'BTC' and token != 'USDT':
            total_amount += float(values[i]) \
                            * float(token_usdt[token + 'USDT']) \
                            / float(token_usdt['BTCUSDT'])
        if token == 'BTC':
            total_amount += float(values[i]) * 1
        else:
            total_amount += float(values[i]) \
                            / float(token_usdt['BTCUSDT'])
    return total_amount


def assets_usdt(assets, values, token_usdt):
    """
    Function to convert all assets into equivalent USDT value
    :param assets: Assets list
    :param values: Assets quantity
    :param token_usdt: Token pair price dict
    :return: list of asset values in USDT
    """
    assets_in_usdt = []
    for i, token in enumerate(assets):
        if token != 'USDT':
            assets_in_usdt.append(
                float(values[i]) * float(token_usdt[token + 'USDT'])
            )
        else:
            assets_in_usdt.append(float(values[i]) * 1)
    return assets_in_usdt

4. Начинаем передавать данные в реальном времени:

# Потоковая передача данных для токенов в портфолио
bm = BinanceSocketManager(client)
for tokenpair in token_pairs:
    conn_key = bm.start_symbol_ticker_socket(tokenpair, streaming_data_process)
bm.start()
time.sleep(5)  # Это дает всем парам токенов достаточно времени, чтобы установить соединение

5. Определяем макет, графики и хостинг:

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.FLATLY])
server = app.server

app.layout = html.Div([
    html.Div([
        html.Div([
            dcc.Graph(
                id='figure-1',
                figure={
                    'data': [
                        go.Indicator(
                            mode="number",
                            value=total_amount_usdt(assets, values, token_usdt),
                        )
                    ],
                    'layout':
                        go.Layout(
                            title="Portfolio Value (USDT)"
                        )
                }
            )], style={'width': '30%', 'height': '300px',
                       'display': 'inline-block'}),
        html.Div([
            dcc.Graph(
                id='figure-2',
                figure={
                    'data': [
                        go.Indicator(
                            mode="number",
                            value=total_amount_btc(assets, values, token_usdt),
                            number={'valueformat': 'g'}
                        )
                    ],
                    'layout':
                        go.Layout(
                            title="Portfolio Value (BTC)"
                        )
                }
            )], style={'width': '30%', 'height': '300px',
                       'display': 'inline-block'}),
        html.Div([
            dcc.Graph(
                id='figure-3',
                figure={
                    'data': [
                        go.Indicator(
                            mode="number",
                            value=float(token_usdt['BNBUSDT']),
                            number={'valueformat': 'g'}
                        )
                    ],
                    'layout':
                        go.Layout(
                            title="BNB/USDT"
                        )
                }
            )],
            style={'width': '30%', 'height': '300px', 'display': 'inline-block'})
    ]),
    html.Div([
        html.Div([
            dcc.Graph(
                id='figure-4',
                figure={
                    'data': [
                        go.Pie(
                            labels=assets,
                            values=assets_usdt(assets, values, token_usdt),
                            hoverinfo="label+percent"
                        )
                    ],
                    'layout':
                        go.Layout(
                            title="Portfolio Distribution (in USDT)"
                        )
                }
            )], style={'width': '50%', 'display': 'inline-block'}),
        html.Div([
            dcc.Graph(
                id='figure-5',
                figure={
                    'data': [
                        go.Bar(
                            x=assets,
                            y=values,
                            name="Token Quantities For Different Assets",
                        )
                    ],
                    'layout':
                        go.Layout(
                            showlegend=False,
                            title="Tokens distribution"
                        )
                }
            )], style={'width': '50%', 'display': 'inline-block'}),
        dcc.Interval(
            id='1-second-interval',
            interval=1000,  # 1000 milliseconds
            n_intervals=0
        )
    ]),
])


@app.callback(Output('figure-1', 'figure'),
              Output('figure-2', 'figure'),
              Output('figure-3', 'figure'),
              Output('figure-4', 'figure'),
              Output('figure-5', 'figure'),
              Input('1-second-interval', 'n_intervals'))
def update_layout(n):
    figure1 = {
        'data': [
            go.Indicator(
                mode="number",
                value=total_amount_usdt(assets, values, token_usdt),
            )
        ],
        'layout':
            go.Layout(
                title="Portfolio Value (USDT)"
            )
    }
    figure2 = {
        'data': [
            go.Indicator(
                mode="number",
                value=total_amount_btc(assets, values, token_usdt),
                number={'valueformat': 'g'}
            )
        ],
        'layout':
            go.Layout(
                title="Portfolio Value (BTC)"
            )
    }
    figure3 = {
        'data': [
            go.Indicator(
                mode="number",
                value=float(token_usdt['BNBUSDT']),
                number={'valueformat': 'g'}
            )
        ],
        'layout':
            go.Layout(
                title="BNB/USDT"
            )
    }
    figure4 = {
        'data': [
            go.Pie(
                labels=assets,
                values=assets_usdt(assets, values, token_usdt),
                hoverinfo="label+percent"
            )
        ],
        'layout':
            go.Layout(
                title="Portfolio Distribution (in USDT)"
            )
    }
    figure5 = {
        'data': [
            go.Bar(
                x=assets,
                y=values,
                name="Token Quantities For Different Assets",
            )
        ],
        'layout':
            go.Layout(
                showlegend=False,
                title="Tokens distribution"
            )
    }

    return figure1, figure2, figure3, figure4, figure5


if __name__ == '__main__':
    app.run_server(host='127.0.0.1', port='8050', debug=True)

Вот и все! Этот код позволяет отслеживать криптовалютный портфель тестового аккаунта. Его можно с легкостью настроить и для реального аккаунта без изменения URL-адреса конечной точки.

Весь код можно найти в репозитории на GitHub.

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Mayank Vadsola: Building a cryptocurrency dashboard using Plotly and Binance API

Предыдущая статьяКак инструменты дизайна интерфейса и визуализации способствуют развитию Machine Teaching?
Следующая статья3 совета, как стать мастером Йода по JavaScript