Мы с тобой в прошлом диалоге писали программу на python с веб-интерфейсом для мониторинга плотностей по криптовалютам на binance и торговли через плотности. Я сейчас напишу тебе наши последние сообщения, твои обычно начинаются со слов "Здравствуйте!" :
в консоли есть предупреждения:
Обновлена текущая цена для SAGAUSDT (spot): 2.8505
Обновлена текущая цена для TAOUSDT (spot): 615.5
Обновлена текущая цена для NEIROUSDT (spot): 0.00164831
Обновлена текущая цена для HMSTRUSDT (spot): 0.004821
Ошибка при обновлении книги ордеров для OPUSDT (futures): APIError(code=-1003): Way too many requests; IP(178.120.153.216) banned until 1728329639057. Please use the websocket for live updates to avoid bans.
Traceback (most recent call last):
File "/home/user/trade_bot_test/trade_bot_test.py", line 205, in update_order_book
depth = client.futures_order_book(symbol=symbol, limit=1000)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/trade_bot_test/lib/python3.12/site-packages/binance/client.py", line 6169, in futures_order_book
return self._request_futures_api('get', 'depth', data=params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/trade_bot_test/lib/python3.12/site-packages/binance/client.py", line 383, in _request_futures_api
return self._request(method, uri, signed, True, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/trade_bot_test/lib/python3.12/site-packages/binance/client.py", line 359, in _request
return self._handle_response(self.response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/trade_bot_test/lib/python3.12/site-packages/binance/client.py", line 368, in _handle_response
raise BinanceAPIException(response, response.status_code, response.text)
binance.exceptions.BinanceAPIException: APIError(code=-1003): Way too many requests; IP(178.120.153.216) banned until 1728329639057. Please use the websocket for live updates to avoid bans.
Обновлена книга ордеров для VANRYUSDT (spot)
Найдено 0 плотностей для ENAUSDT (spot)
Найдено 0 плотностей для ENAUSDT (futures)


Понимаю, вы столкнулись с проблемой, связанной с превышением лимита запросов к API Binance. Ошибка указывает на то, что ваш IP-адрес временно заблокирован из-за слишком большого количества запросов:


APIError(code=-1003): Way too many requests; IP(178.120.153.216) banned until 1728329639057. Please use the websocket for live updates to avoid bans.
Причина проблемы:

Превышение лимитов запросов API Binance. При мониторинге всех USDT-пар вы выполняете большое количество запросов к API Binance для получения книги ордеров (get_order_book и futures_order_book) с высокой глубиной (limit=1000) для каждого символа.
Binance устанавливает ограничения на количество запросов и вес каждого запроса. При превышении этих ограничений IP-адрес временно блокируется. Подробнее о лимитах можно узнать в официальной документации Binance.
Решение проблемы:

Использование WebSocket для обновления книги ордеров. Binance рекомендует использовать WebSocket для получения обновлений в реальном времени, особенно для данных, которые часто меняются, таких как книга ордеров. Это позволит получать обновления без необходимости постоянно делать запросы к REST API.
Снижение частоты запросов и уменьшение глубины книги ордеров. Если по какой-то причине использование WebSocket невозможно, можно уменьшить количество запросов, снизив частоту обновления и глубину книги ордеров (limit). Однако это лишь временное решение и не оптимально для получения актуальных данных.
Детальное решение:

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

Шаги для реализации:
Использовать WebSocket для подписки на поток данных книги ордеров. Binance предоставляет возможность подписываться на потоки Depth Streams, которые передают обновления книги ордеров в реальном времени.
Подписаться на несколько символов одновременно. Для этого используется мультиплексирование WebSocket-соединений. Вы можете подписаться на несколько потоков в одном соединении.
Обновить функцию update_order_book() для работы с WebSocket. Вместо того, чтобы запрашивать данные через REST API, вы будете обрабатывать входящие сообщения от WebSocket и обновлять данные книги ордеров в вашем приложении.
Обновление кода:
1. Обновить функцию start_socket() для подписки на потоки книги ордеров:


async def start_socket():
    global async_client
    print("Запуск асинхронных задач для обновления цен и книги ордеров")
    async_client = await AsyncClient.create(API_KEY, API_SECRET)
    bm = BinanceSocketManager(async_client)

    tasks = []

    # Создаем список потоков для подписки
    streams = []

    # Подготовка списка потоков книги ордеров для всех символов
    for pair in symbols_to_monitor:
        spot_symbol = pair['spot'].lower()
        futures_symbol = pair['futures'].lower()

        # Поток книги ордеров для спота
        streams.append(f"{spot_symbol}@depth5@100ms")

        # Поток книги ордеров для фьючерсов
        streams.append(f"{futures_symbol}@depth5@100ms")

    # Если список потоков слишком большой, нужно разделить его на части
    max_streams_per_connection = 1024  # Лимит Binance на количество потоков в одном соединении
    stream_chunks = [streams[i:i + max_streams_per_connection] for i in range(0, len(streams), max_streams_per_connection)]

    for chunk in stream_chunks:
        # Создаем задачу для каждой группы потоков
        tasks.append(asyncio.create_task(process_order_book_streams(bm, chunk)))

    # Добавляем задачи для мини-текеров, если нужно
    tasks.append(asyncio.create_task(start_miniticker_sockets(bm)))

    try:
        await asyncio.gather(*tasks)
    except Exception as e:
        print(f"Ошибка в задачах WebSocket: {e}")
        traceback.print_exc()
    finally:
        if async_client:
            await async_client.close_connection()
        print("WebSocket connections closed")
2. Добавить функцию для обработки потоков книги ордеров:


async def process_order_book_streams(bm, streams):
    try:
        print(f"Подписка на потоки: {streams}")
        ts = bm.multiplex_socket(streams)
        async with ts as tscm:
            while monitoring_active:
                try:
                    res = await tscm.recv()
                    msg = res.get('data', {})
                    if msg:
                        symbol = msg['s'].upper()
                        # Определяем тип рынка (spot или futures)
                        market_type = 'spot' if symbol in [pair['spot'].upper() for pair in symbols_to_monitor] else 'futures'
                        process_order_book_message(msg, market_type)
                except Exception as e:
                    print(f"Ошибка в потоке книги ордеров: {e}")
                    traceback.print_exc()
                    await asyncio.sleep(1)
    except Exception as e:
        print(f"Ошибка при подписке на потоки книги ордеров: {e}")
        traceback.print_exc()
3. Добавить функцию обработки сообщений книги ордеров:


def process_order_book_message(msg, market_type):
    try:
        symbol = msg['s'].upper()
        # Получаем новые уровни
        bids = [(float(b[0]), float(b[1])) for b in msg.get('b', [])]
        asks = [(float(a[0]), float(a[1])) for a in msg.get('a', [])]
        key = f"{market_type}_{symbol}"
        # Обновляем данные книги ордеров
        order_book_data[key] = {
            'bids': bids,
            'asks': asks,
            'last_update': datetime.now()
        }
        print(f"Обновлена книга ордеров для {symbol} ({market_type}) через WebSocket")
    except Exception as e:
        print(f"Ошибка в process_order_book_message для {market_type}: {e}")
        traceback.print_exc()
4. Обновить функцию start_miniticker_sockets() для передачи bm:


async def start_miniticker_sockets(bm):
    miniticker_tasks = []

    # Фьючерсы MiniTicker
    miniticker_tasks.append(asyncio.create_task(futures_miniticker(bm)))
    # Спот MiniTicker
    miniticker_tasks.append(asyncio.create_task(spot_miniticker(bm)))

    await asyncio.gather(*miniticker_tasks)
5. Обновить функции spot_miniticker() и futures_miniticker() для принятия bm как параметра:


async def spot_miniticker(bm):
    # Ваш код здесь

async def futures_miniticker(bm):
    # Ваш код здесь
6. Исключить использование REST API для получения книги ордеров.

Удалите или закомментируйте вызов update_order_book() в start_scheduler(), так как теперь мы получаем данные через WebSocket.

7. Обновить функцию start_scheduler() или удалить ее, если она больше не нужна.


def start_scheduler():
    # Если нет других задач планировщика, можно удалить эту функцию
    pass
Дополнительные рекомендации:
Убедитесь, что обрабатываете исключения в асинхронных функциях, чтобы избежать прерывания работы приложения.
Проверяйте лимиты Binance на количество потоков WebSocket. Максимальное количество потоков в одном соединении ограничено, поэтому, если вы подписываетесь на очень большое количество символов, разбейте их на несколько соединений.
Оптимизируйте список символов. Если вы не можете обрабатывать данные от всех символов, рассмотрите возможность мониторинга наиболее важных или ликвидных пар.
Пример полного кода с внесенными изменениями:
Для вашего удобства предоставляю обновленный код с учетом всех изменений:

[КОД МОЖНО ПРЕДОСТАВИТЬ В ОТДЕЛЬНОМ ФАЙЛЕ ИЛИ ПРИЛОЖИТЬ К ПОСЛАНИЮ]

Заключение:

Использование WebSocket для получения обновлений книги ордеров позволит:

Избежать превышения лимитов запросов и временных блокировок.
Получать актуальные данные в реальном времени без задержек.
Снизить нагрузку на API Binance и на ваше приложение.
Пожалуйста, попробуйте внести эти изменения в ваш код и сообщите, если у вас возникнут дополнительные вопросы или сложности. Я готов помочь вам с дальнейшей настройкой и оптимизацией вашего приложения.


Но разве переход на WebSocket позволит получить такое огромное кол-во (около 650 пар(спот+фьючерсы)) данные? Нужно значит чтобы программа после формирования общего списка пар, сама настроила нужное количество потоков через мультиплексирование WebSocket-соединений.


Вы правы, при переходе на WebSocket мы можем получать данные для большого количества пар, в вашем случае около 650 пар (спот + фьючерсы). Это возможно и реализуемо благодаря возможностям Binance WebSocket и правильной организации подключения.

Как программа обрабатывает большое количество пар:
Мультиплексирование WebSocket-соединений: Binance позволяет нам использовать мультиплексированные WebSocket-соединения, что означает, что мы можем подписаться на множество потоков (стримов) в рамках одного соединения. Однако есть ограничение: максимальное количество потоков в одном WebSocket-соединении составляет 1024.
Автоматическая разбивка потоков на несколько соединений: В нашем обновленном коде предусмотрена логика, которая автоматически разбивает общий список потоков на группы по 1024 потока (или меньше, если потоков меньше):

# Максимальное количество потоков в одном подключении
max_streams_per_connection = 1024

# Разбиваем список потоков на группы
stream_chunks = [streams[i:i + max_streams_per_connection] for i in range(0, len(streams), max_streams_per_connection)]
Затем для каждой группы потоков мы создаем отдельное WebSocket-соединение:

for chunk in stream_chunks:
    # Создаем задачу для каждой группы потоков
    tasks.append(asyncio.create_task(process_order_book_streams(bm, chunk)))
Обработка потоков книги ордеров через WebSocket: Функция process_order_book_streams обрабатывает входящие данные от всех подписанных потоков в группе:

async def process_order_book_streams(bm, streams):
    try:
        print(f"Подписка на потоки: {streams}")
        ts = bm.multiplex_socket(streams)
        async with ts as tscm:
            while monitoring_active:
                try:
                    res = await tscm.recv()
                    msg = res.get('data', {})
                    if msg:
                        symbol = msg['s'].upper()
                        # Определяем тип рынка (spot или futures)
                        # Здесь можно улучшить логику определения рынка
                        process_order_book_message(msg, 'spot' if '@depth@' in res.get('stream', '') else 'futures')
                except Exception as e:
                    print(f"Ошибка в потоке книги ордеров: {e}")
                    traceback.print_exc()
                    await asyncio.sleep(1)
    except Exception as e:
        print(f"Ошибка при подписке на потоки книги ордеров: {e}")
        traceback.print_exc()
Автоматизация процесса: Таким образом, после формирования общего списка пар, программа автоматически:
Формирует список потоков для подписки (по одному на каждую пару для спота и фьючерсов).
Разбивает список на группы по 1024 потока.
Создает необходимое количество WebSocket-соединений для этих групп.
Обрабатывает данные с каждого потока в асинхронном режиме.
Дополнительные соображения:
Ресурсозатратность: Обработка данных от 650 пар с двух рынков (спот + фьючерсы), то есть всего 1300 потоков, может быть достаточно ресурсозатратной задачей.
Память и процессор: Убедитесь, что сервер или компьютер, на котором работает бот, имеет достаточно ресурсов (RAM, CPU), чтобы обрабатывать такое количество данных в реальном времени.
Сетевой трафик: Проверьте пропускную способность сети, так как объем входящего трафика будет значительным.
Оптимизация: Возможно, вам стоит рассмотреть оптимизацию списка символов:
Фильтрация по ликвидности: Мониторьте только те пары, которые имеют достаточную ликвидность или объем торгов.
Фокусирование на определенных парах: Если вы знаете, какие пары наиболее важны для вашей стратегии, сконцентрируйтесь на них.
Динамическая фильтрация: Реализуйте механизм, который будет автоматически добавлять или исключать пары из мониторинга на основе определенных критериев.
Ограничения Binance:
Количество WebSocket-соединений: Binance ограничивает общее количество WebSocket-соединений до 5 на IP для неавторизованных соединений и до 30 для авторизованных (с API-ключом). Убедитесь, что вы не превышаете эти лимиты. В текущем коде вы используете авторизованные соединения с использованием AsyncClient.create(API_KEY, API_SECRET), поэтому лимит составляет 30 соединений.
Обновление книги ордеров: Мы подписываемся на @depth5@100ms потоки, которые предоставляют обновления книги ордеров с глубиной 5 и частотой 100 мс. Возможно, вы можете использовать меньшую частоту (например, 1000 мс), если вам не нужны настолько частые обновления, что снизит нагрузку.
Обработка данных:
Асинхронность и многопоточность: Убедитесь, что асинхронная обработка данных реализована эффективно, чтобы не было блокирующих операций, которые могут замедлить обработку входящих сообщений.
Очистка старых данных: Реализуйте механизм очистки или ограничения размера данных, чтобы кэш не рос бесконечно.
Заключение:
Да, переход на WebSocket позволит вам получать данные для большого количества пар, и ваш код уже автоматически настраивает нужное количество потоков через мультиплексирование WebSocket-соединений, соблюдая ограничения Binance.

Ваш текущий код уже учитывает:

Ограничение на количество потоков в одном соединении (1024)
Автоматическую разбивку потоков на группы и создание нескольких WebSocket-соединений
Тем не менее, важно следить за производительностью вашего приложения и ресурсоемкостью, а также соблюдать ограничения Binance, чтобы избежать блокировок или других проблем.

Рекомендации по дальнейшим действиям:
Тестирование:
Запустите ваше приложение и внимательно следите за загрузкой CPU, памяти и сетевого трафика.
Убедитесь, что данные обрабатываются корректно и без задержек.
Логирование:
Добавьте подробные логи для мониторинга работы программы.
Логи помогут вам выявить узкие места или ошибки в работе.
Мониторинг ограничений:
Следите за количеством открытых WebSocket-соединений.
Если приближаетесь к лимитам Binance, рассмотрите возможность уменьшения количества соединений или оптимизации списка символов.
Оптимизация:
Если заметите, что приложение потребляет слишком много ресурсов, рассмотрите возможность оптимизации, как описано выше.
Например, уменьшите частоту обновления книг ордеров или сократите список мониторимых пар до наиболее важных для вашей стратегии.
Обновление библиотеки python-binance:
Убедитесь, что вы используете последнюю версию библиотеки python-binance, так как она может содержать оптимизации и исправления, связанные с работой WebSocket.

pip install --upgrade python-binance
Итог:
Ваше приложение настроено корректно для обработки большого количества пар через WebSocket, и вы можете продолжать использовать его в текущем виде. Главное — следить за производительностью и, при необходимости, вносить оптимизации.

Если у вас возникнут дополнительные вопросы или потребуется помощь в внесении изменений или оптимизации приложения, пожалуйста, обращайтесь. Я всегда готов помочь!


Ты говорил про обновление книги ордеров в 1000 мс, но мне не нужно раз 1 сек обновлять книгу ордеров, только рыночную цены монет. Книгу ордеров будет достаточно обновлять раз в 1 минуту, по всем монетам на глубину 5% от рыночной цены вверх и вниз. И напиши полный код файла


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

Однако есть важный момент, о котором стоит упомянуть:

Проблема с лимитами запросов при использовании REST API
Binance устанавливает ограничения на количество запросов к API и вес каждого запроса. При получении книги ордеров через REST API с большой глубиной и для большого количества символов вы рискуете превысить лимиты и получить блокировку IP-адреса.

Вес запросов к книге ордеров
Согласно официальной документации Binance:

GET /api/v3/depth (для спотового рынка) и GET /fapi/v1/depth (для фьючерсного рынка) имеют различный вес, зависящий от параметра limit:
limit=5, 10, 20, 50, 100 → Вес: 1
limit=500 → Вес: 5
limit=1000 → Вес: 10
Важно: При использовании limit=1000 каждый запрос имеет вес 10.

Расчет общего веса запросов
Если вы запрашиваете книгу ордеров для 650 пар раз в минуту с limit=1000 для спота и фьючерсов, это означает:

1300 запросов в минуту (650 спотовых символов + 650 фьючерсных).
Общий вес запросов: 1300 запросов * 10 (вес) = 13,000.
Это значительно превышает лимит Binance в 1200 весовых единиц в минуту для REST API (точный лимит может меняться, но порядок величин такой).

Решение проблемы
Чтобы избежать превышения лимитов и блокировки, предлагаю следующие шаги:

Снизить limit в запросах на получение книги ордеров. Используя limit=100 или меньше, каждый запрос будет иметь вес 1. Например:
limit=100 → Вес: 1
limit=5 → Вес: 1
Сократить количество символов для мониторинга. Возможно, имеет смысл мониторить не все 650 пар, а выбрать наиболее значимые по объему торгов или ликвидности.
Использовать WebSocket для получения книги ордеров. Несмотря на ваши опасения, использование WebSocket для получения книги ордеров является оптимальным решением. Вы можете подписаться только на необходимые вам уровни (например, глубина 5 или 10) и получать обновления в реальном времени без риска превысить лимиты.
Ваш запрос
Однако, поскольку вы предпочитаете обновлять книгу ордеров через REST API раз в минуту, предлагаю следующий подход:

Снизить limit до 100 или 50. Это значительно снизит вес запросов.
Оптимизировать частоту запросов. Вместо того чтобы обновлять все символы каждую минуту, можно распределить запросы так, чтобы не превысить лимиты.
Распараллелить запросы с учетом ограничений. Можно использовать асинхронные запросы с ограничением количества одновременных запросов.
Обновленный код
Ниже приведен полный обновленный код вашего скрипта с учетом вышеизложенного. Я внес следующие изменения:

В функции update_order_book() уменьшил limit до 100.
Добавил управление количеством одновременных запросов к API.
Реализовал асинхронные запросы для обновления книги ордеров с использованием asyncio и aiohttp.
Добавил расчет времени ожидания между запросами, чтобы не превышать лимиты.
Полный код:


import os
import sys
import asyncio
import threading
import time
import traceback
from datetime import datetime, timezone, timedelta
from binance import AsyncClient, BinanceSocketManager, Client
from binance.exceptions import BinanceAPIException
from flask import Flask, render_template, request, redirect, url_for, jsonify
import schedule
import aiohttp  # Добавляем aiohttp для асинхронных запросов
import math

# Параметры бота
MAX_OPEN_POSITIONS = 5       # Максимальное количество открытых позиций
POSITION_SIZE_USDT = 10      # Размер позиции в USDT
TRADING_ALLOWED = False      # Разрешена ли торговля (по умолчанию False)
monitoring_active = False    # Флаг активности мониторинга
min_density_volume = 1000    # Минимальный объём плотности в USDT
min_consumption_time = 0.1   # Минимальное время разъедания в минутах

# Инициализация глобальных переменных
selected_symbols = []        # Список выбранных пользователем символов
symbols_to_monitor = []
order_book_data = {}         # Данные книги ордеров
open_positions = {}          # Открытые позиции
density_history = {}         # История плотностей
current_price_cache = {}     # Кэш для текущих цен из WebSocket
volume_cache = {}            # Кэш для хранения объёмов
symbol_leverages = {}        # Словарь плеч для фьючерсных символов
asyncio_loop = None          # Цикл событий asyncio
asyncio_tasks = []           # Список асинхронных задач

# Добавляем ограничение на количество одновременных запросов
MAX_CONCURRENT_REQUESTS = 50  # Настройте это значение по потребности

# Функция для загрузки API-ключей из файла
def load_api_keys():
    api_key = None
    api_secret = None
    try:
        with open('binance_api.txt', 'r') as f:
            for line in f:
                if 'API_KEY' in line:
                    api_key = line.strip().split('=')[1].strip().strip("'").strip('"')
                elif 'API_SECRET' in line:
                    api_secret = line.strip().split('=')[1].strip().strip("'").strip('"')
    except FileNotFoundError:
        print("Файл binance_api.txt не найден. Пожалуйста, создайте файл с вашими API-ключами.")
        sys.exit(1)
    except Exception as e:
        print(f"Ошибка при чтении binance_api.txt: {e}")
        sys.exit(1)
    if not api_key or not api_secret:
        print("API_KEY или API_SECRET не найдены в binance_api.txt.")
        sys.exit(1)
    return api_key, api_secret

# Загрузка API-ключей
API_KEY, API_SECRET = load_api_keys()

# Инициализация клиента Binance
client = Client(API_KEY, API_SECRET)
async_client = None   # Для асинхронной работы

# Создание приложения Flask
app = Flask(__name__)

# Функция получения общих символов USDT для спота и фьючерсов
def get_common_symbols():
    try:
        spot_exchange_info = client.get_exchange_info()
        futures_exchange_info = client.futures_exchange_info()
    except Exception as e:
        print(f"Ошибка при получении информации о бирже: {e}")
        return []

    spot_symbols = {symbol['symbol'].upper() for symbol in spot_exchange_info['symbols']
                    if symbol['quoteAsset'] == 'USDT' and symbol['status'] == 'TRADING'}
    futures_symbols = {symbol['symbol'].upper() for symbol in futures_exchange_info['symbols']
                       if symbol['quoteAsset'] == 'USDT' and symbol['status'] == 'TRADING'}

    combined_symbols = []
    for spot_symbol in spot_symbols:
        base_asset = spot_symbol.replace('USDT', '')
        # Ищем соответствующий фьючерсный символ
        futures_symbol = None
        if spot_symbol in futures_symbols:
            futures_symbol = spot_symbol
        elif '1000' + base_asset + 'USDT' in futures_symbols:
            futures_symbol = '1000' + base_asset + 'USDT'
        elif base_asset.startswith('1000') and base_asset[4:] + 'USDT' in futures_symbols:
            futures_symbol = base_asset[4:] + 'USDT'
        elif '1000' + spot_symbol in futures_symbols:
            futures_symbol = '1000' + spot_symbol
        elif spot_symbol.startswith('1000') and spot_symbol[4:] in futures_symbols:
            futures_symbol = spot_symbol[4:]
        # Если нашли соответствующий фьючерсный символ, добавляем пару
        if futures_symbol:
            combined_symbols.append({'spot': spot_symbol, 'futures': futures_symbol})

    return combined_symbols

# Функция получения символов для мониторинга
def get_symbols_to_monitor():
    global selected_symbols, symbol_leverages
    symbol_leverages = {}  # Сбросим словарь плеч
    if not selected_symbols:
        return []

    pairs_to_monitor = []

    # Получаем общие символы спота и фьючерсов
    common_symbols = get_common_symbols()

    # Приводим введённые символы к верхнему регистру и удаляем пробелы
    input_symbols = [s.strip().upper() for s in selected_symbols]

    # Создаём списки базовых активов и полных пар из введённых символов
    input_bases = [s.replace('USDT', '') for s in input_symbols]
    input_full_symbols = [s if s.endswith('USDT') else s + 'USDT' for s in input_symbols]

    for pair in common_symbols:
        spot_symbol = pair['spot'].upper()
        futures_symbol = pair['futures'].upper()
        base_asset = spot_symbol.replace('USDT', '')

        # Проверяем, есть ли базовый актив или полный символ в введённых данных
        if base_asset in input_bases or spot_symbol in input_full_symbols or futures_symbol in input_full_symbols:
            pairs_to_monitor.append({'spot': spot_symbol, 'futures': futures_symbol})

    # Получаем плечи для фьючерсных символов
    futures_symbols = [pair['futures'] for pair in pairs_to_monitor]
    try:
        positions = client.futures_account()['positions']
        for pos in positions:
            symbol = pos['symbol'].upper()
            if symbol in futures_symbols:
                leverage = int(pos['leverage'])
                symbol_leverages[symbol] = leverage
    except Exception as e:
        print(f"Ошибка при получении плеча: {e}")

    # Устанавливаем плечо 1 по умолчанию для символов без позиций
    for symbol in futures_symbols:
        if symbol not in symbol_leverages:
            symbol_leverages[symbol] = 1  # Плечо по умолчанию

    print(f"Пары для мониторинга: {pairs_to_monitor}")
    return pairs_to_monitor

# Функция получения 5-минутных объёмов за последние 24 часа
def get_5m_volumes(symbol, market_type):
    key = f"{market_type}_{symbol}"
    now = datetime.now(timezone.utc)
    if key not in volume_cache or (now - volume_cache[key]['last_update']).total_seconds() > 900:
        # Обновляем данные
        end_time = int(now.timestamp() * 1000)
        start_time = end_time - 24 * 60 * 60 * 1000  # За последние 24 часа
        try:
            if market_type == 'spot':
                klines = client.get_historical_klines(
                    symbol,
                    Client.KLINE_INTERVAL_5MINUTE,
                    start_str=start_time,
                    end_str=end_time,
                    limit=288
                )
            else:
                klines = client.futures_historical_klines(
                    symbol,
                    Client.KLINE_INTERVAL_5MINUTE,
                    start_str=start_time,
                    end_str=end_time,
                    limit=288
                )
            volumes = [float(kline[7]) for kline in klines]
            volume_cache[key] = {
                'volumes': volumes,
                'last_update': now
            }
            # Отладочное сообщение
            print(f"Обновлены объёмы для {symbol} ({market_type})")
        except Exception as e:
            print(f"Ошибка при получении исторических данных для {symbol}: {e}")
            return None

    else:
        volumes = volume_cache[key]['volumes']

    return volumes

# Асинхронная функция для обновления книги ордеров
async def update_order_book():
    global order_book_data
    print("Асинхронное обновление книги ордеров...")

    limit = 100  # Устанавливаем limit=100 для снижения веса запросов

    semaphore = asyncio.Semaphore(MAX_CONCURRENT_REQUESTS)
    tasks = []

    async with aiohttp.ClientSession() as session:
        for pair in symbols_to_monitor:
            for market_type in ['spot', 'futures']:
                if market_type == 'spot':
                    symbol = pair['spot']
                    url = f"https://api.binance.com/api/v3/depth"
                else:
                    symbol = pair['futures']
                    url = f"https://fapi.binance.com/fapi/v1/depth"

                symbol = symbol.upper()

                params = {
                    'symbol': symbol,
                    'limit': limit
                }

                task = asyncio.create_task(fetch_order_book(session, semaphore, url, params, market_type, symbol))
                tasks.append(task)

        await asyncio.gather(*tasks)

async def fetch_order_book(session, semaphore, url, params, market_type, symbol):
    async with semaphore:
        try:
            async with session.get(url, params=params, timeout=10) as response:
                if response.status == 200:
                    data = await response.json()
                    bids = [(float(b[0]), float(b[1])) for b in data['bids']]
                    asks = [(float(a[0]), float(a[1])) for a in data['asks']]
                    order_book_data[f"{market_type}_{symbol}"] = {
                        'bids': bids,
                        'asks': asks,
                        'last_update': datetime.now()
                    }
                    print(f"Обновлена книга ордеров для {symbol} ({market_type})")
                else:
                    print(f"Ошибка при получении книги ордеров для {symbol} ({market_type}): HTTP {response.status}")
        except Exception as e:
            print(f"Ошибка при обновлении книги ордеров для {symbol} ({market_type}): {e}")
            traceback.print_exc()

# Планировщик для запуска асинхронного обновления книги ордеров раз в минуту
def start_scheduler():
    schedule.every(1).minutes.do(schedule_update_order_book)
    while monitoring_active:
        schedule.run_pending()
        time.sleep(1)

def schedule_update_order_book():
    asyncio.run(update_order_book())

# Функция обработки сообщений мини-текера (не меняется)
def process_miniticker_message(msg, market_type):
    try:
        if isinstance(msg, list):
            # Сообщение является списком мини-текеров
            for item in msg:
                symbol = item['s'].upper()
                if symbol in [pair[market_type].upper() for pair in symbols_to_monitor]:
                    price = float(item['c'])
                    key = f"{market_type}_{symbol}"
                    current_price_cache[key] = {
                        'price': price,
                        'last_update': datetime.now(timezone.utc)
                    }
                    # Отладочное сообщение
                    # print(f"Обновлена текущая цена для {symbol} ({market_type}): {price}")
        elif isinstance(msg, dict):
            # Сообщение является одним мини-текером
            symbol = msg['s'].upper()
            if symbol in [pair[market_type].upper() for pair in symbols_to_monitor]:
                price = float(msg['c'])
                key = f"{market_type}_{symbol}"
                current_price_cache[key] = {
                    'price': price,
                    'last_update': datetime.now(timezone.utc)
                }
                # Отладочное сообщение
                # print(f"Обновлена текущая цена для {symbol} ({market_type}): {price}")
        else:
            print(f"Неизвестный формат сообщения: {msg}")
    except Exception as e:
        print(f"Ошибка в process_miniticker_message для {market_type}: {e}")
        traceback.print_exc()

# Функция получения текущей цены из кэша
def get_current_price(symbol, market_type):
    key = f"{market_type}_{symbol}"
    if key in current_price_cache:
        price = current_price_cache[key]['price']
        return price
    else:
        # Текущая цена отсутствует в кэше
        return None

# Функция поиска плотностей
def find_top_densities():
    densities = []
    for pair in symbols_to_monitor:
        spot_symbol = pair['spot'].upper()
        futures_symbol = pair['futures'].upper()
        base_asset = spot_symbol.replace('USDT', '')

        for market_type in ['spot', 'futures']:
            if market_type == 'spot':
                symbol = spot_symbol
            else:
                symbol = futures_symbol

            data = order_book_data.get(f"{market_type}_{symbol}")
            if not data:
                print(f"Нет данных книги ордеров для {symbol} ({market_type})")
                continue

            bids = data['bids']
            asks = data['asks']

            # Получаем текущую цену
            current_price = get_current_price(symbol, market_type)
            if not current_price:
                print(f"Нет текущей цены для {symbol} ({market_type})")
                continue

            # Получаем объёмы
            volumes = get_5m_volumes(symbol, market_type)
            if not volumes:
                print(f"Нет объёмов для {symbol} ({market_type})")
                continue

            avg_5m_volume = sum(volumes) / len(volumes)

            # Фильтруем ордера в пределах 5% от текущей цены
            min_price = current_price * 0.95
            max_price = current_price * 1.05

            densities_found = []

            for side, orders in [('bid', bids), ('ask', asks)]:
                for price, qty in orders:
                    if price < min_price or price > max_price:
                        continue

                    total_value = price * qty
                    if total_value < min_density_volume:
                        continue

                    # Рассчитываем время разъедания
                    consumption_time = (total_value / avg_5m_volume) * 5  # В минутах

                    # Проверяем условие на минимальное время разъедания
                    if consumption_time < min_consumption_time:
                        continue

                    distance = (price - current_price) / current_price * 100  # В процентах

                    key_density = (symbol, market_type, price)
                    if key_density not in density_history:
                        initial_total_value = total_value
                        density_history[key_density] = {
                            'initial_total_value': initial_total_value,
                            'last_total_value': total_value
                        }
                    else:
                        initial_total_value = density_history[key_density]['initial_total_value']
                        density_history[key_density]['last_total_value'] = total_value

                    percentage_change = ((total_value - initial_total_value) / initial_total_value) * 100

                    # Получаем плечо для фьючерсного рынка
                    leverage = 1
                    if market_type == 'futures':
                        leverage = symbol_leverages.get(symbol, 1)

                    densities_found.append({
                        'symbol': base_asset,
                        'market_type': market_type,
                        'leverage': leverage,
                        'price': price,
                        'qty': qty,
                        'total_value': total_value,
                        'initial_total_value': initial_total_value,
                        'percentage_change': percentage_change,
                        'distance': distance,
                        'consumption_time': consumption_time
                    })

            # print(f"Найдено {len(densities_found)} плотностей для {symbol} ({market_type})")

            # Оставляем только 2 плотности на пару (1 выше и 1 ниже текущей цены)
            densities_above = [d for d in densities_found if d['price'] > current_price]
            densities_below = [d for d in densities_found if d['price'] < current_price]

            densities_above.sort(key=lambda x: x['price'])
            densities_below.sort(key=lambda x: -x['price'])

            if densities_above:
                densities.append(densities_above[0])
            if densities_below:
                densities.append(densities_below[0])

    densities.sort(key=lambda x: (x['symbol'], x['market_type']))
    return densities

# Функция мониторинга и торговли (обновлена)
def monitor_and_trade():
    while monitoring_active:
        time.sleep(1)

# Асинхронная функция для работы с WebSocket (только для обновления текущих цен)
async def start_socket():
    global async_client
    print("Запуск асинхронных задач для обновления цен")
    async_client = await AsyncClient.create(API_KEY, API_SECRET)
    bm = BinanceSocketManager(async_client)

    tasks = []

    # Функция для запуска мини-текера
    async def start_miniticker_sockets():
        miniticker_tasks = []

        # Фьючерсы MiniTicker
        async def futures_miniticker():
            try:
                print("Запуск фьючерсного MiniTicker сокета")
                ts = bm.futures_multiplex_socket(['!miniTicker@arr'])
                async with ts as tscm:
                    while monitoring_active:
                        try:
                            res = await tscm.recv()
                            msg = res.get('data', [])
                            if msg:
                                process_miniticker_message(msg, 'futures')
                        except Exception as e:
                            print(f"Ошибка в MiniTicker WebSocket (futures): {e}")
                            traceback.print_exc()
                            await asyncio.sleep(1)
            except Exception as e:
                print(f"Ошибка при запуске MiniTicker WebSocket (futures): {e}")
                traceback.print_exc()

        # Спот MiniTicker
        async def spot_miniticker():
            try:
                print("Запуск спотового MiniTicker сокета")
                ts = bm.multiplex_socket(['!miniTicker@arr'])
                async with ts as tscm:
                    while monitoring_active:
                        try:
                            res = await tscm.recv()
                            msg = res.get('data', [])
                            if msg:
                                process_miniticker_message(msg, 'spot')
                        except Exception as e:
                            print(f"Ошибка в MiniTicker WebSocket (spot): {e}")
                            traceback.print_exc()
                            await asyncio.sleep(1)
            except Exception as e:
                print(f"Ошибка при запуске MiniTicker WebSocket (spot): {e}")
                traceback.print_exc()

        miniticker_tasks.append(asyncio.create_task(futures_miniticker()))
        miniticker_tasks.append(asyncio.create_task(spot_miniticker()))

        await asyncio.gather(*miniticker_tasks)

    tasks.append(asyncio.create_task(start_miniticker_sockets()))

    try:
        await asyncio.gather(*tasks)
    except Exception as e:
        print(f"Ошибка в задачах WebSocket: {e}")
        traceback.print_exc()
    finally:
        if async_client:
            await async_client.close_connection()
        print("WebSocket connections closed")

# Запуск асинхронных задач
def run_asyncio_tasks():
    global asyncio_loop, asyncio_tasks
    asyncio_loop = asyncio.new_event_loop()
    asyncio.set_event_loop(asyncio_loop)
    asyncio_tasks = [
        asyncio.ensure_future(start_socket()),
    ]
    try:
        asyncio_loop.run_until_complete(asyncio.gather(*asyncio_tasks))
    except Exception as e:
        print(f"Ошибка в основном цикле событий asyncio: {e}")
        traceback.print_exc()

# Функция остановки асинхронных задач
def stop_asyncio_tasks():
    global asyncio_loop, asyncio_tasks, monitoring_active
    print("Остановка асинхронных задач")
    monitoring_active = False  # Сбрасываем флаг активности мониторинга

    # Отменяем текущие задачи
    for task in asyncio_tasks:
        if not task.done():
            task.cancel()

    # Останавливаем цикл событий
    if asyncio_loop and asyncio_loop.is_running():
        asyncio_loop.call_soon_threadsafe(asyncio_loop.stop)
        asyncio_loop = None

    asyncio_tasks = []

# Функции открытия и закрытия позиции
def open_position(density):
    # Здесь вы можете реализовать логику открытия позиции
    print(f"Открытие позиции по {density['symbol']} ({density['market_type']})")

def close_position(symbol):
    # Здесь вы можете реализовать логику закрытия позиции
    print(f"Закрытие позиции по {symbol}")

def check_entry_conditions(density):
    # Реализуйте ваши условия для входа в позицию
    return True  # Пока всегда возвращает True

# Маршруты Flask

@app.route('/')
def index():
    return render_template('index.html',
                           densities=[],
                           open_positions=open_positions,
                           trading_allowed=TRADING_ALLOWED,
                           position_size=POSITION_SIZE_USDT,
                           max_open_positions=MAX_OPEN_POSITIONS,
                           monitored_symbols=", ".join(selected_symbols),
                           monitoring_active=monitoring_active,
                           min_density_volume=min_density_volume,
                           min_consumption_time=min_consumption_time)

@app.route('/start')
def start_trading():
    global TRADING_ALLOWED
    TRADING_ALLOWED = True
    return redirect(url_for('index'))

@app.route('/stop')
def stop_trading():
    global TRADING_ALLOWED
    TRADING_ALLOWED = False
    return redirect(url_for('index'))

@app.route('/set_position_size', methods=['POST'])
def set_position_size():
    global POSITION_SIZE_USDT
    POSITION_SIZE_USDT = float(request.form['position_size'])
    return redirect(url_for('index'))

@app.route('/set_max_positions', methods=['POST'])
def set_max_positions():
    global MAX_OPEN_POSITIONS
    MAX_OPEN_POSITIONS = int(request.form['max_positions'])
    return redirect(url_for('index'))

# Маршрут для установки параметров плотности
@app.route('/set_density_params', methods=['POST'])
def set_density_params():
    global min_density_volume, min_consumption_time
    min_density_volume = float(request.form['min_density_volume'])
    min_consumption_time = float(request.form['min_consumption_time'])
    return redirect(url_for('index'))

@app.route('/set_symbols', methods=['POST'])
def set_symbols():
    global selected_symbols, symbols_to_monitor, monitoring_active, density_history, current_price_cache
    symbols_input = request.form.get('symbols', '')
    selected_symbols = [s.strip().upper() for s in symbols_input.split(',') if s.strip()]

    # Останавливаем текущий мониторинг
    stop_asyncio_tasks()
    monitoring_active = False

    # Обновляем символы для мониторинга
    symbols_to_monitor = get_symbols_to_monitor()
    print(f"Список символов для мониторинга обновлён: {selected_symbols}")

    # Очищаем кеш плотностей и цен при перезапуске сканирования
    density_history = {}
    current_price_cache = {}
    order_book_data.clear()

    # Устанавливаем флаг активности мониторинга
    monitoring_active = True

    # Запускаем планировщик для обновления книги ордеров
    threading.Thread(target=start_scheduler).start()

    # Запускаем асинхронные задачи для обновления цен
    threading.Thread(target=run_asyncio_tasks).start()

    # Запускаем мониторинг и торговлю
    threading.Thread(target=monitor_and_trade).start()

    return redirect(url_for('index'))

@app.route('/monitor_all_usdt')
def monitor_all_usdt():
    global selected_symbols, symbols_to_monitor, monitoring_active, density_history, current_price_cache
    # Останавливаем текущий мониторинг
    stop_asyncio_tasks()
    monitoring_active = False

    # Обновляем символы для мониторинга: все общие символы USDT
    common_symbols = get_common_symbols()
    # Получаем список базовых активов
    selected_symbols = [pair['spot'].replace('USDT', '') for pair in common_symbols]

    # Обновляем символы для мониторинга
    symbols_to_monitor = get_symbols_to_monitor()
    print(f"Список символов для мониторинга обновлён: {selected_symbols}")

    # Очищаем кеш плотностей и цен при перезапуске сканирования
    density_history = {}
    current_price_cache = {}
    order_book_data.clear()

    # Устанавливаем флаг активности мониторинга
    monitoring_active = True

    # Запускаем планировщик для обновления книги ордеров
    threading.Thread(target=start_scheduler).start()

    # Запускаем асинхронные задачи для обновления цен
    threading.Thread(target=run_asyncio_tasks).start()

    # Запускаем мониторинг и торговлю
    threading.Thread(target=monitor_and_trade).start()

    # Перенаправляем на главную страницу
    return redirect(url_for('index'))

@app.route('/stop_monitoring')
def stop_monitoring():
    global monitoring_active
    stop_asyncio_tasks()
    monitoring_active = False
    return redirect(url_for('index'))

@app.route('/get_densities')
def get_densities_route():
    densities = find_top_densities()
    # Преобразуем числовые данные в float для корректной сериализации
    for d in densities:
        d['price'] = float(d['price'])
        d['qty'] = float(d['qty'])
        d['total_value'] = float(d['total_value'])
        d['distance'] = float(d['distance'])
        d['consumption_time'] = float(d['consumption_time'])
        d['initial_total_value'] = float(d['initial_total_value'])
        d['percentage_change'] = float(d['percentage_change'])
        d['leverage'] = int(d['leverage'])
    return jsonify(densities)

@app.route('/get_open_positions')
def get_open_positions_route():
    positions = []
    for symbol, position in open_positions.items():
        positions.append({
            'symbol': symbol,
            'side': position['side'],
            'quantity': float(position['quantity']),
            'entry_price': float(position['entry_price']),
            'current_price': float(position['current_price']),
            'pnl_percent': float(position['pnl_percent'])
        })
    return jsonify(positions)

@app.route('/open_position', methods=['POST'])
def handle_open_position():
    symbol = request.form['symbol']
    density = next((d for d in find_top_densities() if d['symbol'] == symbol), None)
    if density and check_entry_conditions(density):
        open_position(density)
    return redirect(url_for('index'))

@app.route('/close_position', methods=['POST'])
def handle_close_position():
    symbol = request.form['symbol']
    close_position(symbol)
    return redirect(url_for('index'))

# Запуск приложения
if __name__ == '__main__':
    # Запуск Flask-приложения
    app.run(host='0.0.0.0', port=4000)
Объяснение изменений:
Снижение limit в запросах на получение книги ордеров:
Установил limit = 100 в функции update_order_book(). Это уменьшило вес каждого запроса до 1.
Асинхронное выполнение запросов:
Использовал asyncio и aiohttp для асинхронного выполнения запросов к API.
Добавил ограничение MAX_CONCURRENT_REQUESTS для контроля количества одновременных запросов.
Планирование обновления книг ордеров:
Обновление книг ордеров происходит раз в минуту с использованием планировщика schedule.
Используется asyncio.run(update_order_book()) для запуска асинхронной функции из синхронного контекста.
Управление лимитами:
Сниженный limit и контроль одновременных запросов помогают избежать превышения лимитов Binance API.
Сохранение функциональности обновления текущих цен через WebSocket:
Функции для работы с WebSocket и обновления текущих цен остались без изменений.
Убрали использование WebSocket для обновления книги ордеров:
Теперь книги ордеров обновляются через REST API раз в минуту.
Дополнительные рекомендации:
Мониторинг лимитов:
Следите за логами приложения, чтобы убедиться, что вы не получаете ошибок, связанных с превышением лимитов.
Оптимизация списка символов:
Если вы все еще сталкиваетесь с проблемами, рассмотрите возможность сокращения списка символов для мониторинга.
Регулировка параметров:
При необходимости ajustируйте MAX_CONCURRENT_REQUESTS и limit в запросах для достижения оптимальной работы.
Заключение
С данным кодом вы сможете обновлять книгу ордеров раз в минуту через REST API, избегая превышения лимитов и получая необходимые данные для мониторинга плотностей. Текущие рыночные цены продолжают обновляться ежесекундно через WebSocket.

Пожалуйста, внедрите этот код и протестируйте его. Если у вас возникнут дополнительные вопросы или потребуется помощь, не стесняйтесь обращаться.


Ты меня неправильно понял, я непротив использования WebSocket для обновления книги ордеров. Но позволит ли он получить требуемую глубину (5% от рыночной цены в обе стороны) по всем монетам раз в 1 минуту?


Вы правы, прошу прощения за недопонимание. Я предложил решение с REST API, но вы не против использовать WebSocket для обновления книги ордеров, и хотите знать, можно ли получить требуемую глубину (5% от рыночной цены вверх и вниз) по всем монетам раз в 1 минуту через WebSocket.

Ответ на ваш вопрос:
Да, использование WebSocket для обновления книги ордеров позволит вам получать необходимые данные с требуемой глубиной и частотой.

Объяснение:
Binance предоставляет Level 2 Depth Streams, которые позволяют получать информацию о книге ордеров с заданной глубиной. Вы можете подписаться на поток с определенной глубиной.

Форматы потоков:
Depth Streams с глубиной 5, 10 или 20.
Пример для глубины 20 и частотой обновления 1000ms:

wss://stream.binance.com:9443/ws/bnbusdt@depth20@1000ms
Обратите внимание, что глубина (20 в примере) указывает на количество наилучших ценовых уровней в книге ордеров.
Однако, если вам нужна книга ордеров с глубиной 5% от текущей цены, то при использовании WebSocket вам потребуется получать полную книгу ордеров и самостоятельно отфильтровывать ордера, входящие в диапазон ±5% от текущей цены.

К сожалению, WebSocket-потоки Binance не предоставляют возможность запрашивать книгу ордеров в процентах от текущей цены напрямую. Но мы можем использовать чтение полного стакана и самостоятельно фильтровать данные.

Решение:

Подписаться на Depth Streams с глубиной 1000.
Это даст вам полную книгу ордеров для каждого символа.
В обработчике сообщений WebSocket:
Обновлять локальную копию книги ордеров для каждого символа.
Фильтровать ордера, которые находятся в диапазоне ±5% от текущей цены.
Использовать локальный буфер стаканов:
Binance рекомендует поддерживать локальную копию книги ордеров, используя механизм "Распределение и Слияние" (Diff. Depth Stream).
Но в нашем случае мы можем обновлять книгу ордеров при получении сообщений с глубиной 1000 и обновлять ее полностью.
Реализация:
Подписываемся на потоки книги ордеров для каждого символа.
Используем метод bm.depth_socket() для каждого символа с глубиной 1000 и частотой обновления 1000ms.
Обрабатываем сообщения от WebSocket.
Обновляем книгу ордеров в order_book_data.
Фильтруем данные при необходимости.
Обновленный код:
Я перепишу ваш код, чтобы использовать WebSocket для обновления книги ордеров с глубиной 1000 и частотой 1000ms для всех символов.

Полный обновленный код:


import os
import sys
import asyncio
import threading
import time
import traceback
from datetime import datetime, timezone, timedelta
from binance import AsyncClient, BinanceSocketManager, Client
from binance.exceptions import BinanceAPIException
from flask import Flask, render_template, request, redirect, url_for, jsonify
import schedule
import math

# Параметры бота
MAX_OPEN_POSITIONS = 5       # Максимальное количество открытых позиций
POSITION_SIZE_USDT = 10      # Размер позиции в USDT
TRADING_ALLOWED = False      # Разрешена ли торговля (по умолчанию False)
monitoring_active = False    # Флаг активности мониторинга
min_density_volume = 1000    # Минимальный объём плотности в USDT
min_consumption_time = 0.1   # Минимальное время разъедания в минутах

# Инициализация глобальных переменных
selected_symbols = []        # Список выбранных пользователем символов
symbols_to_monitor = []
order_book_data = {}         # Данные книги ордеров
open_positions = {}          # Открытые позиции
density_history = {}         # История плотностей
current_price_cache = {}     # Кэш для текущих цен из WebSocket
volume_cache = {}            # Кэш для хранения объёмов
symbol_leverages = {}        # Словарь плеч для фьючерсных символов
asyncio_loop = None          # Цикл событий asyncio
asyncio_tasks = []           # Список асинхронных задач

# Функция для загрузки API-ключей из файла
def load_api_keys():
    api_key = None
    api_secret = None
    try:
        with open('binance_api.txt', 'r') as f:
            for line in f:
                if 'API_KEY' in line:
                    api_key = line.strip().split('=')[1].strip().strip("'").strip('"')
                elif 'API_SECRET' in line:
                    api_secret = line.strip().split('=')[1].strip().strip("'").strip('"')
    except FileNotFoundError:
        print("Файл binance_api.txt не найден. Пожалуйста, создайте файл с вашими API-ключами.")
        sys.exit(1)
    except Exception as e:
        print(f"Ошибка при чтении binance_api.txt: {e}")
        sys.exit(1)
    if not api_key or not api_secret:
        print("API_KEY или API_SECRET не найдены в binance_api.txt.")
        sys.exit(1)
    return api_key, api_secret

# Загрузка API-ключей
API_KEY, API_SECRET = load_api_keys()

# Инициализация клиента Binance
client = Client(API_KEY, API_SECRET)
async_client = None   # Для асинхронной работы

# Создание приложения Flask
app = Flask(__name__)

# Функция получения общих символов USDT для спота и фьючерсов
def get_common_symbols():
    try:
        spot_exchange_info = client.get_exchange_info()
        futures_exchange_info = client.futures_exchange_info()
    except Exception as e:
        print(f"Ошибка при получении информации о бирже: {e}")
        return []

    spot_symbols = {symbol['symbol'].upper() for symbol in spot_exchange_info['symbols']
                    if symbol['quoteAsset'] == 'USDT' and symbol['status'] == 'TRADING'}
    futures_symbols = {symbol['symbol'].upper() for symbol in futures_exchange_info['symbols']
                       if symbol['quoteAsset'] == 'USDT' and symbol['status'] == 'TRADING'}

    combined_symbols = []
    for spot_symbol in spot_symbols:
        base_asset = spot_symbol.replace('USDT', '')
        # Ищем соответствующий фьючерсный символ
        futures_symbol = None
        if spot_symbol in futures_symbols:
            futures_symbol = spot_symbol
        elif '1000' + base_asset + 'USDT' in futures_symbols:
            futures_symbol = '1000' + base_asset + 'USDT'
        elif base_asset.startswith('1000') and base_asset[4:] + 'USDT' in futures_symbols:
            futures_symbol = base_asset[4:] + 'USDT'
        elif '1000' + spot_symbol in futures_symbols:
            futures_symbol = '1000' + spot_symbol
        elif spot_symbol.startswith('1000') and spot_symbol[4:] in futures_symbols:
            futures_symbol = spot_symbol[4:]
        # Если нашли соответствующий фьючерсный символ, добавляем пару
        if futures_symbol:
            combined_symbols.append({'spot': spot_symbol, 'futures': futures_symbol})

    return combined_symbols

# Функция получения символов для мониторинга
def get_symbols_to_monitor():
    global selected_symbols, symbol_leverages
    symbol_leverages = {}  # Сбросим словарь плеч
    if not selected_symbols:
        return []

    pairs_to_monitor = []

    # Получаем общие символы спота и фьючерсов
    common_symbols = get_common_symbols()

    # Приводим введённые символы к верхнему регистру и удаляем пробелы
    input_symbols = [s.strip().upper() for s in selected_symbols]

    # Создаём списки базовых активов и полных пар из введённых символов
    input_bases = [s.replace('USDT', '') for s in input_symbols]
    input_full_symbols = [s if s.endswith('USDT') else s + 'USDT' for s in input_symbols]

    for pair in common_symbols:
        spot_symbol = pair['spot'].upper()
        futures_symbol = pair['futures'].upper()
        base_asset = spot_symbol.replace('USDT', '')

        # Проверяем, есть ли базовый актив или полный символ в введённых данных
        if base_asset in input_bases or spot_symbol in input_full_symbols or futures_symbol in input_full_symbols:
            pairs_to_monitor.append({'spot': spot_symbol, 'futures': futures_symbol})

    # Получаем плечи для фьючерсных символов
    futures_symbols = [pair['futures'] for pair in pairs_to_monitor]
    try:
        positions = client.futures_account()['positions']
        for pos in positions:
            symbol = pos['symbol'].upper()
            if symbol in futures_symbols:
                leverage = int(pos['leverage'])
                symbol_leverages[symbol] = leverage
    except Exception as e:
        print(f"Ошибка при получении плеча: {e}")

    # Устанавливаем плечо 1 по умолчанию для символов без позиций
    for symbol in futures_symbols:
        if symbol not in symbol_leverages:
            symbol_leverages[symbol] = 1  # Плечо по умолчанию

    print(f"Пары для мониторинга: {pairs_to_monitor}")
    return pairs_to_monitor

# Функция получения 5-минутных объёмов за последние 24 часа
def get_5m_volumes(symbol, market_type):
    key = f"{market_type}_{symbol}"
    now = datetime.now(timezone.utc)
    if key not in volume_cache or (now - volume_cache[key]['last_update']).total_seconds() > 900:
        # Обновляем данные
        end_time = int(now.timestamp() * 1000)
        start_time = end_time - 24 * 60 * 60 * 1000  # За последние 24 часа
        try:
            if market_type == 'spot':
                klines = client.get_historical_klines(
                    symbol,
                    Client.KLINE_INTERVAL_5MINUTE,
                    start_str=start_time,
                    end_str=end_time,
                    limit=288
                )
            else:
                klines = client.futures_historical_klines(
                    symbol,
                    Client.KLINE_INTERVAL_5MINUTE,
                    start_str=start_time,
                    end_str=end_time,
                    limit=288
                )
            volumes = [float(kline[7]) for kline in klines]
            volume_cache[key] = {
                'volumes': volumes,
                'last_update': now
            }
            # Отладочное сообщение
            # print(f"Обновлены объёмы для {symbol} ({market_type})")
        except Exception as e:
            print(f"Ошибка при получении исторических данных для {symbol}: {e}")
            return None

    else:
        volumes = volume_cache[key]['volumes']

    return volumes

# Функция обработки сообщений книги ордеров
def process_order_book_message(msg, market_type):
    try:
        symbol = msg['s'].upper()
        bids = [(float(b[0]), float(b[1])) for b in msg.get('b', [])]
        asks = [(float(a[0]), float(a[1])) for a in msg.get('a', [])]
        key = f"{market_type}_{symbol}"
        order_book_data[key] = {
            'bids': bids,
            'asks': asks,
            'last_update': datetime.now()
        }
        # print(f"Обновлена книга ордеров для {symbol} ({market_type}) через WebSocket")
    except Exception as e:
        print(f"Ошибка в process_order_book_message для {market_type}: {e}")
        traceback.print_exc()

# Функция получения текущей цены из кэша
def get_current_price(symbol, market_type):
    key = f"{market_type}_{symbol}"
    if key in current_price_cache:
        price = current_price_cache[key]['price']
        return price
    else:
        # Текущая цена отсутствует в кэше
        return None

# Функция поиска плотностей
def find_top_densities():
    densities = []
    for pair in symbols_to_monitor:
        spot_symbol = pair['spot'].upper()
        futures_symbol = pair['futures'].upper()
        base_asset = spot_symbol.replace('USDT', '')

        for market_type in ['spot', 'futures']:
            if market_type == 'spot':
                symbol = spot_symbol
            else:
                symbol = futures_symbol

            data = order_book_data.get(f"{market_type}_{symbol}")
            if not data:
                print(f"Нет данных книги ордеров для {symbol} ({market_type})")
                continue

            bids = data['bids']
            asks = data['asks']

            # Получаем текущую цену
            current_price = get_current_price(symbol, market_type)
            if not current_price:
                print(f"Нет текущей цены для {symbol} ({market_type})")
                continue

            # Получаем объёмы
            volumes = get_5m_volumes(symbol, market_type)
            if not volumes:
                print(f"Нет объёмов для {symbol} ({market_type})")
                continue

            avg_5m_volume = sum(volumes) / len(volumes)

            # Фильтруем ордера в пределах 5% от текущей цены
            min_price = current_price * 0.95
            max_price = current_price * 1.05

            densities_found = []

            for side, orders in [('bid', bids), ('ask', asks)]:
                for price, qty in orders:
                    if price < min_price or price > max_price:
                        continue

                    total_value = price * qty
                    if total_value < min_density_volume:
                        continue

                    # Рассчитываем время разъедания
                    consumption_time = (total_value / avg_5m_volume) * 5  # В минутах

                    # Проверяем условие на минимальное время разъедания
                    if consumption_time < min_consumption_time:
                        continue

                    distance = (price - current_price) / current_price * 100  # В процентах

                    key_density = (symbol, market_type, price)
                    if key_density not in density_history:
                        initial_total_value = total_value
                        density_history[key_density] = {
                            'initial_total_value': initial_total_value,
                            'last_total_value': total_value
                        }
                    else:
                        initial_total_value = density_history[key_density]['initial_total_value']
                        density_history[key_density]['last_total_value'] = total_value

                    percentage_change = ((total_value - initial_total_value) / initial_total_value) * 100

                    # Получаем плечо для фьючерсного рынка
                    leverage = 1
                    if market_type == 'futures':
                        leverage = symbol_leverages.get(symbol, 1)

                    densities_found.append({
                        'symbol': base_asset,
                        'market_type': market_type,
                        'leverage': leverage,
                        'price': price,
                        'qty': qty,
                        'total_value': total_value,
                        'initial_total_value': initial_total_value,
                        'percentage_change': percentage_change,
                        'distance': distance,
                        'consumption_time': consumption_time
                    })

            # Оставляем только 2 плотности на пару (1 выше и 1 ниже текущей цены)
            densities_above = [d for d in densities_found if d['price'] > current_price]
            densities_below = [d for d in densities_found if d['price'] < current_price]

            densities_above.sort(key=lambda x: x['price'])
            densities_below.sort(key=lambda x: -x['price'])

            if densities_above:
                densities.append(densities_above[0])
            if densities_below:
                densities.append(densities_below[0])

    densities.sort(key=lambda x: (x['symbol'], x['market_type']))
    return densities

# Функция мониторинга и торговли (обновлена)
def monitor_and_trade():
    while monitoring_active:
        time.sleep(1)

# Асинхронная функция для работы с WebSocket
async def start_socket():
    global async_client
    print("Запуск асинхронных задач для обновления цен и книги ордеров")
    async_client = await AsyncClient.create(API_KEY, API_SECRET)
    bm = BinanceSocketManager(async_client)

    tasks = []

    # Создаем список потоков для подписки
    streams = []

    # Подготовка списка потоков книги ордеров для всех символов
    for pair in symbols_to_monitor:
        spot_symbol = pair['spot'].lower()
        futures_symbol = pair['futures'].lower()

        # Поток книги ордеров для спота
        streams.append(f"{spot_symbol}@depth1000@1000ms")

        # Поток книги ордеров для фьючерсов
        streams.append(f"{futures_symbol}@depth@1000ms")  # Фьючерсы поддерживают только полную глубину

    # Если список потоков слишком большой, нужно разделить его на части
    max_streams_per_connection = 100  # Ограничим до 100 потоков на соединение
    stream_chunks = [streams[i:i + max_streams_per_connection] for i in range(0, len(streams), max_streams_per_connection)]

    for chunk in stream_chunks:
        # Создаем задачу для каждой группы потоков
        tasks.append(asyncio.create_task(process_order_book_streams(bm, chunk)))

    # Добавляем задачи для мини-текеров
    tasks.append(asyncio.create_task(start_miniticker_sockets(bm)))

    try:
        await asyncio.gather(*tasks)
    except Exception as e:
        print(f"Ошибка в задачах WebSocket: {e}")
        traceback.print_exc()
    finally:
        if async_client:
            await async_client.close_connection()
        print("WebSocket connections closed")

# Функция обработки потоков книги ордеров
async def process_order_book_streams(bm, streams):
    try:
        print(f"Подписка на потоки: {streams}")
        ts = bm.multiplex_socket(streams)
        async with ts as tscm:
            while monitoring_active:
                try:
                    res = await tscm.recv()
                    msg = res.get('data', {})
                    if msg:
                        stream = res.get('stream', '')
                        symbol = msg['s'].upper()
                        # Определяем тип рынка на основе имени потока
                        if 'depth@' in stream or 'depth1000@' in stream:
                            market_type = 'spot'
                        else:
                            market_type = 'futures'
                        process_order_book_message(msg, market_type)
                except Exception as e:
                    print(f"Ошибка в потоке книги ордеров: {e}")
                    traceback.print_exc()
                    await asyncio.sleep(1)
    except Exception as e:
        print(f"Ошибка при подписке на потоки книги ордеров: {e}")
        traceback.print_exc()

# Функция для запуска мини-текеров
async def start_miniticker_sockets(bm):
    miniticker_tasks = []

    # Фьючерсы MiniTicker
    miniticker_tasks.append(asyncio.create_task(futures_miniticker(bm)))
    # Спот MiniTicker
    miniticker_tasks.append(asyncio.create_task(spot_miniticker(bm)))

    await asyncio.gather(*miniticker_tasks)

async def spot_miniticker(bm):
    try:
        print("Запуск спотового MiniTicker сокета")
        ts = bm.multiplex_socket(['!miniTicker@arr'])
        async with ts as tscm:
            while monitoring_active:
                try:
                    res = await tscm.recv()
                    msg = res.get('data', [])
                    if msg:
                        process_miniticker_message(msg, 'spot')
                except Exception as e:
                    print(f"Ошибка в MiniTicker WebSocket (spot): {e}")
                    traceback.print_exc()
                    await asyncio.sleep(1)
    except Exception as e:
        print(f"Ошибка при запуске MiniTicker WebSocket (spot): {e}")
        traceback.print_exc()

async def futures_miniticker(bm):
    try:
        print("Запуск фьючерсного MiniTicker сокета")
        ts = bm.futures_multiplex_socket(['!miniTicker@arr'])
        async with ts as tscm:
            while monitoring_active:
                try:
                    res = await tscm.recv()
                    msg = res.get('data', [])
                    if msg:
                        process_miniticker_message(msg, 'futures')
                except Exception as e:
                    print(f"Ошибка в MiniTicker WebSocket (futures): {e}")
                    traceback.print_exc()
                    await asyncio.sleep(1)
    except Exception as e:
        print(f"Ошибка при запуске MiniTicker WebSocket (futures): {e}")
        traceback.print_exc()

# Запуск асинхронных задач
def run_asyncio_tasks():
    global asyncio_loop, asyncio_tasks
    asyncio_loop = asyncio.new_event_loop()
    asyncio.set_event_loop(asyncio_loop)
    asyncio_tasks = [
        asyncio.ensure_future(start_socket()),
    ]
    try:
        asyncio_loop.run_until_complete(asyncio.gather(*asyncio_tasks))
    except Exception as e:
        print(f"Ошибка в основном цикле событий asyncio: {e}")
        traceback.print_exc()

# Функция остановки асинхронных задач
def stop_asyncio_tasks():
    global asyncio_loop, asyncio_tasks, monitoring_active
    print("Остановка асинхронных задач")
    monitoring_active = False  # Сбрасываем флаг активности мониторинга

    # Отменяем текущие задачи
    for task in asyncio_tasks:
        if not task.done():
            task.cancel()

    # Останавливаем цикл событий
    if asyncio_loop and asyncio_loop.is_running():
        asyncio_loop.call_soon_threadsafe(asyncio_loop.stop)
        asyncio_loop = None

    asyncio_tasks = []

# Функции открытия и закрытия позиции
def open_position(density):
    # Здесь вы можете реализовать логику открытия позиции
    print(f"Открытие позиции по {density['symbol']} ({density['market_type']})")

def close_position(symbol):
    # Здесь вы можете реализовать логику закрытия позиции
    print(f"Закрытие позиции по {symbol}")

def check_entry_conditions(density):
    # Реализуйте ваши условия для входа в позицию
    return True  # Пока всегда возвращает True

# Маршруты Flask

@app.route('/')
def index():
    return render_template('index.html',
                           densities=[],
                           open_positions=open_positions,
                           trading_allowed=TRADING_ALLOWED,
                           position_size=POSITION_SIZE_USDT,
                           max_open_positions=MAX_OPEN_POSITIONS,
                           monitored_symbols=", ".join(selected_symbols),
                           monitoring_active=monitoring_active,
                           min_density_volume=min_density_volume,
                           min_consumption_time=min_consumption_time)

@app.route('/start')
def start_trading():
    global TRADING_ALLOWED
    TRADING_ALLOWED = True
    return redirect(url_for('index'))

@app.route('/stop')
def stop_trading():
    global TRADING_ALLOWED
    TRADING_ALLOWED = False
    return redirect(url_for('index'))

@app.route('/set_position_size', methods=['POST'])
def set_position_size():
    global POSITION_SIZE_USDT
    POSITION_SIZE_USDT = float(request.form['position_size'])
    return redirect(url_for('index'))

@app.route('/set_max_positions', methods=['POST'])
def set_max_positions():
    global MAX_OPEN_POSITIONS
    MAX_OPEN_POSITIONS = int(request.form['max_positions'])
    return redirect(url_for('index'))

# Маршрут для установки параметров плотности
@app.route('/set_density_params', methods=['POST'])
def set_density_params():
    global min_density_volume, min_consumption_time
    min_density_volume = float(request.form['min_density_volume'])
    min_consumption_time = float(request.form['min_consumption_time'])
    return redirect(url_for('index'))

@app.route('/set_symbols', methods=['POST'])
def set_symbols():
    global selected_symbols, symbols_to_monitor, monitoring_active, density_history, current_price_cache
    symbols_input = request.form.get('symbols', '')
    selected_symbols = [s.strip().upper() for s in symbols_input.split(',') if s.strip()]

    # Останавливаем текущий мониторинг
    stop_asyncio_tasks()
    monitoring_active = False

    # Обновляем символы для мониторинга
    symbols_to_monitor = get_symbols_to_monitor()
    print(f"Список символов для мониторинга обновлён: {selected_symbols}")

    # Очищаем кеш плотностей и цен при перезапуске сканирования
    density_history = {}
    current_price_cache = {}
    order_book_data.clear()

    # Устанавливаем флаг активности мониторинга
    monitoring_active = True

    # Запускаем асинхронные задачи для обновления цен и книги ордеров
    threading.Thread(target=run_asyncio_tasks).start()

    # Запускаем мониторинг и торговлю
    threading.Thread(target=monitor_and_trade).start()

    return redirect(url_for('index'))

@app.route('/monitor_all_usdt')
def monitor_all_usdt():
    global selected_symbols, symbols_to_monitor, monitoring_active, density_history, current_price_cache
    # Останавливаем текущий мониторинг
    stop_asyncio_tasks()
    monitoring_active = False

    # Обновляем символы для мониторинга: все общие символы USDT
    common_symbols = get_common_symbols()
    # Получаем список базовых активов
    selected_symbols = [pair['spot'].replace('USDT', '') for pair in common_symbols]

    # Обновляем символы для мониторинга
    symbols_to_monitor = get_symbols_to_monitor()
    print(f"Список символов для мониторинга обновлён: {selected_symbols}")

    # Очищаем кеш плотностей и цен при перезапуске сканирования
    density_history = {}
    current_price_cache = {}
    order_book_data.clear()

    # Устанавливаем флаг активности мониторинга
    monitoring_active = True

    # Запускаем асинхронные задачи для обновления цен и книги ордеров
    threading.Thread(target=run_asyncio_tasks).start()

    # Запускаем мониторинг и торговлю
    threading.Thread(target=monitor_and_trade).start()

    # Перенаправляем на главную страницу
    return redirect(url_for('index'))

@app.route('/stop_monitoring')
def stop_monitoring():
    global monitoring_active
    stop_asyncio_tasks()
    monitoring_active = False
    return redirect(url_for('index'))

@app.route('/get_densities')
def get_densities_route():
    densities = find_top_densities()
    # Преобразуем числовые данные в float для корректной сериализации
    for d in densities:
        d['price'] = float(d['price'])
        d['qty'] = float(d['qty'])
        d['total_value'] = float(d['total_value'])
        d['distance'] = float(d['distance'])
        d['consumption_time'] = float(d['consumption_time'])
        d['initial_total_value'] = float(d['initial_total_value'])
        d['percentage_change'] = float(d['percentage_change'])
        d['leverage'] = int(d['leverage'])
    return jsonify(densities)

@app.route('/get_open_positions')
def get_open_positions_route():
    positions = []
    for symbol, position in open_positions.items():
        positions.append({
            'symbol': symbol,
            'side': position['side'],
            'quantity': float(position['quantity']),
            'entry_price': float(position['entry_price']),
            'current_price': float(position['current_price']),
            'pnl_percent': float(position['pnl_percent'])
        })
    return jsonify(positions)

@app.route('/open_position', methods=['POST'])
def handle_open_position():
    symbol = request.form['symbol']
    density = next((d for d in find_top_densities() if d['symbol'] == symbol), None)
    if density and check_entry_conditions(density):
        open_position(density)
    return redirect(url_for('index'))

@app.route('/close_position', methods=['POST'])
def handle_close_position():
    symbol = request.form['symbol']
    close_position(symbol)
    return redirect(url_for('index'))

# Запуск приложения
if __name__ == '__main__':
    # Запуск Flask-приложения
    app.run(host='0.0.0.0', port=4000)
Объяснение изменений:
Использование WebSocket для книги ордеров:
В функции start_socket() мы создаем подписки на потоки книги ордеров для каждого символа:

streams.append(f"{spot_symbol}@depth1000@1000ms")
streams.append(f"{futures_symbol}@depth@1000ms")  # Для фьючерсов
Для спотового рынка мы используем @depth1000@1000ms, что означает глубина 1000 и частота обновления 1000ms.
Для фьючерсного рынка используется @depth@1000ms. Binance предоставляет потоки с полной глубиной для фьючерсов без указания конкретной глубины.
Обработка сообщений книги ордеров:
Функция process_order_book_message() обрабатывает входящие сообщения и обновляет order_book_data.
Увеличение максимального количества потоков:
В целях производительности мы ограничиваем количество потоков на одно соединение:

max_streams_per_connection = 100
Это необходимо, чтобы предотвратить перегрузку WebSocket-соединений.
Удаление REST API запросов книги ордеров:
Мы полностью убрали использование REST API для обновления книги ордеров, поскольку теперь эта информация поступает через WebSocket.
Обновление monitor_and_trade() и других функций:
Функция monitor_and_trade() осталась без изменений.
Важные замечания:
Ограничения Binance:
Binance ограничивает количество WebSocket-соединений. Убедитесь, что общее количество соединений не превышает лимиты.
Производительность:
Подписка на большое количество потоков может быть ресурсоемкой.
Рекомендуется мониторить производительность вашего приложения и при необходимости оптимизировать список символов.
Отладка и тестирование:
Обязательно проведите тщательное тестирование, чтобы убедиться, что данные корректно обрабатываются и обновляются.
Заключение:
Использование WebSocket для обновления книги ордеров позволит вам получать необходимые данные с требуемой глубиной в реальном времени, без риска получить блокировку из-за превышения лимитов API.

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