Давай сделаем обновление функционала - добавим под кнопкой мониторинга еще кнопку "Monitor ALL USDT" - при нажатии на которую наша программа запросит весь список монет на фьючерсах с парой USDT, затем запросит весь список монет на споте с парой USDT, если у спотовой пары нету аналога на фьючерсах, то ее мы исключаем из мониторинга. Также может быть такой момент что некоторые пару на фьючерсах/споте имеют приставку 1000 а на споте/фьючерсах ее не имеют, нужно тчобы наша программа понимала это. Например на фьючерсах есть монета 1000SHIB, а на споте ее аналог без приставки 1000, просто SHIB


Здравствуйте!

Понимаю, что вы хотите расширить функционал бота, добавив кнопку "Monitor ALL USDT", которая будет автоматически добавлять все пары USDT, имеющиеся как на спотовом, так и на фьючерсном рынках Binance, в список мониторинга. Также нужно учесть особенности символов с префиксом "1000" на фьючерсах и без него на споте.

Я внесу необходимые изменения в файлы index.html и binance_bot.py, чтобы реализовать этот функционал.

Изменения в index.html
Добавим новую кнопку "Monitor ALL USDT" под формой мониторинга.

Обновлённый файл index.html

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Binance Trading Bot</title>
    <style>
        /* Стили */
        body {
            font-family: Arial, sans-serif;
            background-color: #121212;
            color: #ffffff;
            margin: 0;
            padding: 20px;
        }
        h1, h2 {
            text-align: center;
        }
        .controls {
            text-align: center;
            margin-bottom: 20px;
        }
        .controls form, .controls a, .controls button {
            display: inline-block;
            margin: 0 10px;
        }
        table {
            width: 95%;
            margin: 0 auto 20px;
            border-collapse: collapse;
            color: #ffffff;
        }
        th, td {
            border: 1px solid #555;
            padding: 8px;
            text-align: center;
        }
        th {
            background-color: #333333;
        }
        .status {
            font-weight: bold;
        }
        .button {
            padding: 5px 10px;
            font-size: 14px;
            text-decoration: none;
            border: none;
            border-radius: 3px;
            cursor: pointer;
        }
        .green {
            background-color: #4CAF50;
            color: white;
        }
        .red {
            background-color: #f44336;
            color: white;
        }
        .yellow {
            background-color: #ff9800;
            color: white;
        }
        .form-input {
            padding: 5px;
            font-size: 14px;
            width: 150px;
            border: 1px solid #ccc;
            border-radius: 3px;
        }
        .black-text {
            color: black !important;
        }
        .spinner {
            display: inline-block;
            width: 20px;
            height: 20px;
            border: 3px solid rgba(255, 255, 255, 0.3);
            border-radius: 50%;
            border-top-color: #ffffff;
            animation: spin 1s ease-in-out infinite;
            vertical-align: middle;
        }
        @keyframes spin {
            to { transform: rotate(360deg); }
        }
        .status-text {
            display: inline-block;
            vertical-align: middle;
            margin-left: 5px;
        }
        /* Дополнительные стили для окрашивания ячеек */
        .value-yellow {
            background-color: yellow;
            color: black;
        }
        .value-orange {
            background-color: orange;
            color: black;
        }
        .value-violet {
            background-color: #EE82EE; /* light violet */
            color: black;
        }
        .value-pink {
            background-color: pink;
            color: black;
        }
        .positive {
            background-color: green;
            color: white;
        }
        .negative {
            background-color: red;
            color: white;
        }
    </style>
</head>
<body>
    <h1>Binance Trading Bot</h1>
    <div class="controls">
        <a class="button green" href="/start">СТАРТ</a>
        <a class="button red" href="/stop">СТОП</a>
        <p class="status">Статус торговли: {{ 'Разрешена' if trading_allowed else 'Запрещена' }}</p>
    </div>
    <div class="controls">
        <form action="/set_position_size" method="post">
            Размер позиции (USDT):
            <input class="form-input" type="number" name="position_size" value="{{ position_size }}" min="1" step="any">
            <button class="button yellow" type="submit">SET</button>
        </form>
        <form action="/set_max_positions" method="post">
            Количество позиций:
            <input class="form-input" type="number" name="max_positions" value="{{ max_open_positions }}" min="1">
            <button class="button yellow" type="submit">SET</button>
        </form>
    </div>

    <!-- Форма для установки параметров плотности -->
    <div class="controls">
        <form action="/set_density_params" method="post">
            Объём плотности ($/USDT):
            <input class="form-input" type="number" name="min_density_volume" value="{{ min_density_volume }}" min="0" step="any">
            Время разъедания (мин.):
            <input class="form-input" type="number" name="min_consumption_time" value="{{ min_consumption_time }}" min="0" step="any">
            <button class="button yellow" type="submit">Установить</button>
        </form>
    </div>

    <!-- Форма для ввода символов и кнопка СТОП МОНИТОРИНГ -->
    <div class="controls">
        <form id="monitoring-form" action="/set_symbols" method="post" style="display: inline-block;">
            Монеты для мониторинга (через запятую):
            <input class="form-input" type="text" name="symbols" placeholder="BTC, ETH, DOGE" value="{{ monitored_symbols }}">
            <button class="button yellow" type="submit">MONITORING</button>
        </form>
        <button class="button yellow" onclick="location.href='/monitor_all_usdt'">Monitor ALL USDT</button>
        <a class="button red" href="/stop_monitoring">СТОП МОНИТОРИНГ</a>
        <span id="monitoring-status" style="margin-left: 10px;">
            {% if monitoring_active %}
                <span class="spinner"></span>
                <span class="status-text" style="color: green;">Мониторинг активен</span>
            {% else %}
                <span class="status-text" style="color: red;">Мониторинг не запущен</span>
            {% endif %}
        </span>
    </div>

    <h2>Плотности</h2>
    <table id="densities-table">
        <thead>
            <tr>
                <th>Символ</th>
                <th>Цена</th>
                <th>Количество</th>
                <th>Общая сумма</th>
                <th>Расстояние до цены (%)</th>
                <th>Время разъедания (мин)</th>
            </tr>
        </thead>
        <tbody>
            <!-- Данные будут загружаться динамически через JavaScript -->
        </tbody>
    </table>

    <h2>Открытые позиции</h2>
    <table id="positions-table">
        <thead>
            <tr>
                <th>Символ</th>
                <th>Сторона</th>
                <th>Количество</th>
                <th>Цена входа</th>
                <th>Текущая цена</th>
                <th>PnL (%)</th>
            </tr>
        </thead>
        <tbody>
            <!-- Данные будут загружаться динамически через JavaScript -->
        </tbody>
    </table>

    <script>
        function fetchDensities() {
            fetch('/get_densities')
                .then(response => response.json())
                .then(data => {
                    const tbody = document.querySelector('#densities-table tbody');
                    tbody.innerHTML = '';
                    data.forEach(density => {
                        const row = document.createElement('tr');

                        // Окрашивание ячейки "Общая сумма" в зависимости от значения
                        let totalValueCellClass = '';
                        if (density.total_value > 3000000) {
                            totalValueCellClass = 'value-pink';
                        } else if (density.total_value > 1000000) {
                            totalValueCellClass = 'value-violet';
                        } else if (density.total_value > 500000) {
                            totalValueCellClass = 'value-orange';
                        } else if (density.total_value > 250000) {
                            totalValueCellClass = 'value-yellow';
                        }

                        // Окрашивание ячейки "Расстояние до цены (%)"
                        let distanceCellClass = '';
                        if (density.distance > 0) {
                            distanceCellClass = 'positive';
                        } else if (density.distance < 0) {
                            distanceCellClass = 'negative';
                        }

                        // Форматирование отображения общей суммы с процентом изменения
                        const totalValueFormatted = `${Math.round(density.total_value)}$ (${density.percentage_change >= 0 ? '+' : ''}${density.percentage_change.toFixed(2)}%)`;

                        // Округляем время в минутах до 1 знака после запятой
                        const consumptionTimeFormatted = density.consumption_time.toFixed(1);

                        // Убираем лишние нули в цене
                        const priceFormatted = parseFloat(density.price).toString();

                        // Форматирование символа с учётом плеча для фьючерсов
                        let symbolDisplay = '';
                        if (density.market_type === 'spot') {
                            symbolDisplay = `${density.symbol} (спот)`;
                        } else {
                            symbolDisplay = `${density.symbol} (фьючерс Х${density.leverage})`;
                        }

                        row.innerHTML = `
                            <td>${symbolDisplay}</td>
                            <td>${priceFormatted}</td>
                            <td>${density.qty.toFixed(1)}</td>
                            <td class="${totalValueCellClass}">${totalValueFormatted}</td>
                            <td class="${distanceCellClass}">${density.distance.toFixed(4)}</td>
                            <td>${consumptionTimeFormatted}</td>
                        `;
                        tbody.appendChild(row);
                    });
                })
                .catch(error => {
                    console.error('Ошибка при получении плотностей:', error);
                });
        }

        function fetchPositions() {
            fetch('/get_open_positions')
                .then(response => response.json())
                .then(data => {
                    const tbody = document.querySelector('#positions-table tbody');
                    tbody.innerHTML = '';
                    data.forEach(position => {
                        const row = document.createElement('tr');
                        row.innerHTML = `
                            <td>${position.symbol}</td>
                            <td>${position.side}</td>
                            <td>${position.quantity.toFixed(8)}</td>
                            <td>${position.entry_price.toFixed(8)}</td>
                            <td>${position.current_price.toFixed(8)}</td>
                            <td>${position.pnl_percent.toFixed(2)}</td>
                        `;
                        tbody.appendChild(row);
                    });
                })
                .catch(error => {
                    console.error('Ошибка при получении позиций:', error);
                });
        }

        // Запускаем функции сразу после загрузки страницы
        fetchDensities();
        fetchPositions();

        // Обновляем данные каждые 5 секунд
        setInterval(fetchDensities, 5000);
        setInterval(fetchPositions, 5000);
    </script>
</body>
</html>
Объяснение изменений в index.html:
Добавлена новая кнопка "Monitor ALL USDT" под формой мониторинга:

<button class="button yellow" onclick="location.href='/monitor_all_usdt'">Monitor ALL USDT</button>
Кнопка отправляет запрос на новый маршрут /monitor_all_usdt, который будет обрабатывать этот функционал в binance_bot.py.
Изменения в binance_bot.py
Добавим новый маршрут /monitor_all_usdt, который будет получать все общие символы USDT с фьючерсного и спотового рынков и запускать мониторинг по ним.

Обновлённый файл binance_bot.py

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

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

# Функция для загрузки 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-ключами.")
        exit(1)
    except Exception as e:
        print(f"Ошибка при чтении binance_api.txt: {e}")
        exit(1)
    if not api_key or not api_secret:
        print("API_KEY или API_SECRET не найдены в binance_api.txt.")
        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__)

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

# Функция получения общих символов USDT для спота и фьючерсов
def get_common_symbols():
    spot_exchange_info = client.get_exchange_info()
    futures_exchange_info = client.futures_exchange_info()

    spot_symbols = {symbol['symbol'] for symbol in spot_exchange_info['symbols']
                    if symbol['quoteAsset'] == 'USDT' and symbol['status'] == 'TRADING'}
    futures_symbols = {symbol['symbol'] 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']
        futures_symbol = pair['futures']
        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(pair)

    # Получаем плечи для фьючерсных символов
    futures_symbols = [pair['futures'] for pair in pairs_to_monitor]
    try:
        positions = client.futures_account()['positions']
        for pos in positions:
            symbol = pos['symbol']
            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

# Остальной код без изменений...

# Добавляем новый маршрут для "Monitor ALL USDT"
@app.route('/monitor_all_usdt')
def monitor_all_usdt():
    global selected_symbols, symbols_to_monitor, monitoring_active, density_history
    # Останавливаем текущий мониторинг
    stop_asyncio_tasks()

    # Обновляем символы для мониторинга: все общие символы 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 = {}

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

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

    return redirect(url_for('index'))

# Остальной код остаётся без изменений...
Объяснение изменений в binance_bot.py:
Обновлена функция get_common_symbols():
Улучшена логика сопоставления символов между спотом и фьючерсами с учётом префикса "1000".
Теперь функция корректно обрабатывает случаи, когда на одном рынке символ имеет префикс "1000", а на другом нет.
Добавлен новый маршрут /monitor_all_usdt:

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

    # Обновляем символы для мониторинга: все общие символы 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 = {}

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

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

    return redirect(url_for('index'))
При нажатии на кнопку "Monitor ALL USDT" этот маршрут:
Останавливает текущий мониторинг.
Получает все общие символы USDT между спотом и фьючерсами.
Обновляет список selected_symbols базовыми активами.
Обновляет символы для мониторинга, вызывая get_symbols_to_monitor().
Очищает кеш density_history.
Устанавливает флаг monitoring_active = True.
Перезапускает асинхронные задачи для сбора данных.
Перенаправляет пользователя на главную страницу.
В функции get_symbols_to_monitor() и get_common_symbols() обеспечено корректное сопоставление символов с префиксом "1000" между спотом и фьючерсами.
Инструкции по обновлению и запуску:
Обновите файл index.html:
Замените ваш текущий файл index.html на предоставленный выше обновлённый код.
Обновите файл binance_bot.py:
Замените ваш текущий файл binance_bot.py на предоставленный выше обновлённый код.
Убедитесь, что все зависимости установлены и актуальны.
Запустите приложение:
Остановите текущий запуск бота из терминала, если он запущен.
Запустите скрипт:

python binance_bot.py
Откройте веб-браузер и перейдите по адресу:

http://localhost:4000
Проверьте функциональность:
Убедитесь, что интерфейс отображается корректно и новая кнопка "Monitor ALL USDT" присутствует.
Нажмите на кнопку "Monitor ALL USDT".
Подождите несколько секунд, чтобы данные загрузились.
Проверьте, что в таблице плотностей отображаются данные для всех символов USDT, доступных как на споте, так и на фьючерсах.
Проверьте консоль на предмет ошибок или предупреждений. Если они есть, сообщите о них.
Дополнительные рекомендации:
Следите за лимитами API Binance:
При мониторинге большого количества символов может увеличиться число запросов к API Binance.
Убедитесь, что ваш API-ключ имеет достаточные лимиты и не превышает ограничения.
Оптимизация производительности:
При большом количестве символов возможно увеличение нагрузки на систему.
Если столкнётесь с проблемами производительности, рассмотрите возможность оптимизации кода или уменьшения числа символов.
Обработка исключений:
В коде добавлены блоки try-except для обработки возможных ошибок при получении данных от API.
Следите за консолью для выявления и устранения возможных проблем.
Если у вас возникнут дополнительные вопросы или потребуется помощь с настройкой или отладкой кода, пожалуйста, дайте мне знать. Я всегда готов помочь!

Желаю успешной работы вашего бота!


в прошлом сообщении ты забыл написать полный код файла python


Здравствуйте!

Извиняюсь за то, что в предыдущем сообщении не предоставил полный код файла binance_bot.py, как вы просили. Ниже я предоставляю полный обновлённый код файла binance_bot.py с учётом всех внесённых изменений и добавлением функционала кнопки "Monitor ALL USDT".

Полный обновлённый код файла binance_bot.py

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

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

# Функция для загрузки 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-ключами.")
        exit(1)
    except Exception as e:
        print(f"Ошибка при чтении binance_api.txt: {e}")
        exit(1)
    if not api_key or not api_secret:
        print("API_KEY или API_SECRET не найдены в binance_api.txt.")
        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__)

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

# Функция получения общих символов USDT для спота и фьючерсов
def get_common_symbols():
    spot_exchange_info = client.get_exchange_info()
    futures_exchange_info = client.futures_exchange_info()

    spot_symbols = {symbol['symbol'] for symbol in spot_exchange_info['symbols']
                    if symbol['quoteAsset'] == 'USDT' and symbol['status'] == 'TRADING'}
    futures_symbols = {symbol['symbol'] 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']
        futures_symbol = pair['futures']
        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(pair)

    # Получаем плечи для фьючерсных символов
    futures_symbols = [pair['futures'] for pair in pairs_to_monitor]
    try:
        positions = client.futures_account()['positions']
        for pos in positions:
            symbol = pos['symbol']
            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() > 300:
        # Обновляем данные
        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]  # kline[7] - объём quote asset
            volume_cache[key] = {
                'volumes': volumes,
                'last_update': now
            }
        except Exception as e:
            print(f"Ошибка при получении исторических данных для {symbol}: {e}")
            return None

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

    return volumes

# Функция обработки сообщений глубины рынка
def process_depth_message(msg, market_type):
    if 'e' in msg and msg['e'] == 'depthUpdate':
        symbol = msg['s']
        bids = [(float(b[0]), float(b[1])) for b in msg['b']]
        asks = [(float(a[0]), float(a[1])) for a in msg['a']]

        # Проверяем, покрывают ли bids и asks диапазон 5% от текущей цены
        try:
            if market_type == 'spot':
                current_price = float(client.get_symbol_ticker(symbol=symbol)['price'])
            else:
                current_price = float(client.futures_mark_price(symbol=symbol)['markPrice'])
        except Exception as e:
            return

        min_price = current_price * 0.95
        max_price = current_price * 1.05

        # Определяем минимальные и максимальные цены в ордерах
        bid_prices = [price for price, qty in bids]
        ask_prices = [price for price, qty in asks]

        bids_ok = bid_prices and min(bid_prices) <= min_price
        asks_ok = ask_prices and max(ask_prices) >= max_price

        if not bids_ok or not asks_ok:
            # Необходимо запросить дополнительную глубину через REST API
            try:
                if market_type == 'spot':
                    depth = client.get_order_book(symbol=symbol, limit=1000)
                else:
                    depth = client.futures_order_book(symbol=symbol, limit=1000)
                bids = [(float(b[0]), float(b[1])) for b in depth['bids']]
                asks = [(float(a[0]), float(a[1])) for a in depth['asks']]
            except Exception as e:
                print(f"Ошибка при запросе глубины рынка для {symbol}: {e}")
                return

        if market_type == 'futures':
            depth_data[symbol] = {
                'bids': bids,
                'asks': asks
            }
        elif market_type == 'spot':
            spot_depth_data[symbol] = {
                'bids': bids,
                'asks': asks
            }

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

        for market_type in ['spot', 'futures']:
            if market_type == 'spot':
                symbol = spot_symbol
                data = spot_depth_data.get(symbol, None)
            else:
                symbol = futures_symbol
                data = depth_data.get(symbol, None)

            if not data:
                continue

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

            try:
                # Получаем текущую цену
                if market_type == 'spot':
                    current_price = float(client.get_symbol_ticker(symbol=symbol)['price'])
                else:
                    current_price = float(client.futures_mark_price(symbol=symbol)['markPrice'])
            except Exception as e:
                print(f"Ошибка при получении текущей цены для {symbol} ({market_type}): {e}")
                continue

            # Получаем кэшированные 5-минутные объёмы за последние 24 часа
            volumes = get_5m_volumes(symbol, market_type)
            if not volumes:
                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 = (symbol, market_type, price)
                    # Получаем initial_total_value из кеша или устанавливаем текущий total_value
                    if key not in density_history:
                        initial_total_value = total_value
                        density_history[key] = {
                            'initial_total_value': initial_total_value,
                            'last_total_value': total_value
                        }
                    else:
                        initial_total_value = density_history[key]['initial_total_value']
                        density_history[key]['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,  # Используем базовый символ (например, "BTC")
                        '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'])

            # Добавляем только 1 плотность выше и 1 ниже на каждом рынке для каждой монеты
            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 update_positions():
    # Здесь вы можете добавить код для обновления позиций (если необходимо)
    pass

# Функция мониторинга и торговли
def monitor_and_trade():
    # Задержка перед началом мониторинга, чтобы данные успели загрузиться
    time.sleep(5)  # Задержка в 5 секунд

    while True:
        if TRADING_ALLOWED and monitoring_active:
            densities = find_top_densities()
            for density in densities:
                symbol = density['symbol']
                if symbol in open_positions:
                    continue
                if check_entry_conditions(density):
                    open_position(density)
        time.sleep(1)
        update_positions()

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

    # Составляем списки потоков для фьючерсов и спота
    futures_streams = [f"{pair['futures'].lower()}@depth@100ms" for pair in symbols_to_monitor]
    spot_streams = [f"{pair['spot'].lower()}@depth@100ms" for pair in symbols_to_monitor]

    # Разбиваем на чанки по 100 потоков
    chunk_size = 100
    futures_chunks = [futures_streams[i:i + chunk_size] for i in range(0, len(futures_streams), chunk_size)]
    spot_chunks = [spot_streams[i:i + chunk_size] for i in range(0, len(spot_streams), chunk_size)]

    async def start_futures_socket(stream_list):
        ts = bm.futures_multiplex_socket(stream_list)
        async with ts as tscm:
            while monitoring_active:
                try:
                    res = await tscm.recv()
                    if 'data' in res:
                        msg = res['data']
                        process_depth_message(msg, 'futures')
                except asyncio.CancelledError:
                    print("WebSocket tasks cancelled (futures)")
                    break
                except Exception as e:
                    print(f"Ошибка в WebSocket (futures): {e}")
                    await asyncio.sleep(1)

    async def start_spot_socket(stream_list):
        ts = bm.multiplex_socket(stream_list)
        async with ts as tscm:
            while monitoring_active:
                try:
                    res = await tscm.recv()
                    if 'data' in res:
                        msg = res['data']
                        process_depth_message(msg, 'spot')
                except asyncio.CancelledError:
                    print("WebSocket tasks cancelled (spot)")
                    break
                except Exception as e:
                    print(f"Ошибка в WebSocket (spot): {e}")
                    await asyncio.sleep(1)

    tasks = []
    for chunk in futures_chunks:
        tasks.append(asyncio.create_task(start_futures_socket(chunk)))
    for chunk in spot_chunks:
        tasks.append(asyncio.create_task(start_spot_socket(chunk)))

    try:
        await asyncio.gather(*tasks)
    except asyncio.CancelledError:
        print("Все WebSocket задачи отменены")
    finally:
        await async_client.close_connection()

# Запуск асинхронных задач
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 asyncio.CancelledError:
        pass

# Функция остановки асинхронных задач
def stop_asyncio_tasks():
    global asyncio_loop, asyncio_tasks, monitoring_active
    print("Остановка асинхронных задач")
    monitoring_active = False  # Сбрасываем флаг активности мониторинга
    # Отменяем текущие задачи
    if asyncio_tasks:
        for task in asyncio_tasks:
            task.cancel()
    # Останавливаем цикл событий
    if asyncio_loop:
        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
    symbols_input = request.form.get('symbols', '')
    selected_symbols = [s.strip().upper() for s in symbols_input.split(',') if s.strip()]

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

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

    # Очищаем кеш плотностей при перезапуске сканирования
    density_history = {}

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

    # Перезапускаем асинхронные задачи с новыми символами
    threading.Thread(target=run_asyncio_tasks).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
    # Останавливаем текущий мониторинг
    stop_asyncio_tasks()

    # Обновляем символы для мониторинга: все общие символы 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 = {}

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

    # Перезапускаем асинхронные задачи с новыми символами
    threading.Thread(target=run_asyncio_tasks).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__':
    # Устанавливаем флаг активности мониторинга
    monitoring_active = True

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

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

    # Запуск Flask-приложения в отдельном потоке
    app_thread = threading.Thread(target=lambda: app.run(host='0.0.0.0', port=4000))
    app_thread.start()

    # Ждём завершения потока Flask-приложения
    app_thread.join()
Объяснение изменений:
Добавлен новый маршрут /monitor_all_usdt:
При нажатии на кнопку "Monitor ALL USDT" вызывается функция monitor_all_usdt(), которая:
Останавливает текущий мониторинг.
Получает все общие символы USDT между спотом и фьючерсами, используя get_common_symbols().
Обновляет список selected_symbols базовыми активами.
Обновляет символы для мониторинга, вызывая get_symbols_to_monitor().
Очищает кеш density_history.
Устанавливает флаг monitoring_active = True.
Перезапускает асинхронные задачи для сбора данных.
Перенаправляет пользователя на главную страницу.
Обновлена функция get_common_symbols():
Улучшена логика сопоставления символов между спотом и фьючерсами с учётом префикса "1000".
Обновлена функция get_symbols_to_monitor():
Теперь корректно обрабатывает selected_symbols, полученные из monitor_all_usdt().
Инструкции по обновлению и запуску:
Обновите файл binance_bot.py:
Замените ваш текущий файл binance_bot.py на предоставленный выше обновлённый код.
Обновите файл index.html:
Убедитесь, что вы также обновили index.html с учётом предыдущих изменений, включая добавление кнопки "Monitor ALL USDT".
Запустите приложение:
Остановите текущий запуск бота из терминала, если он запущен.
Запустите скрипт:

python binance_bot.py
Откройте веб-браузер и перейдите по адресу:

http://localhost:4000
Проверьте функциональность:
Убедитесь, что интерфейс отображается корректно и новая кнопка "Monitor ALL USDT" присутствует.
Нажмите на кнопку "Monitor ALL USDT".
Подождите несколько секунд, чтобы данные загрузились.
Проверьте, что в таблице плотностей отображаются данные для всех символов USDT, доступных как на споте, так и на фьючерсах.
Проверьте консоль на предмет ошибок или предупреждений. Если они есть, сообщите о них.
Если у вас возникнут дополнительные вопросы или потребуется помощь с настройкой или отладкой кода, пожалуйста, дайте мне знать. Я всегда готов помочь!

Желаю успешной работы вашего бота!


после запуска мониторинга всех монет, плотности начинают появлятся в таблице, но в консоли идут какие то предупреждуния/ошибки:
Запуск асинхронных задач
192.168.100.140 - - [03/Oct/2024 18:18:00] "GET /monitor_all_usdt HTTP/1.1" 302 -
192.168.100.140 - - [03/Oct/2024 18:18:00] "GET / HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:00] "GET /get_open_positions HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:04] "GET /get_densities HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:04] "GET /get_densities HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:04] "GET /get_densities HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:05] "GET /get_densities HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:05] "GET /get_open_positions HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:06] "GET /get_densities HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:10] "GET /get_open_positions HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:11] "GET /get_densities HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:15] "GET /get_open_positions HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:18:20] "GET /get_open_positions HTTP/1.1" 200 -
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7d1179d44500>
Task was destroyed but it is pending!
task: <Task cancelling name='Task-6' coro=<start_socket() running at /home/user/trade_bot_test/trade_bot_test.py:421> wait_for=<_GatheringFuture pending cb=[Task.task_wakeup()]> cb=[gather.._done_callback() at /usr/lib/python3.12/asyncio/tasks.py:767]>
Exception ignored in: <coroutine object start_socket at 0x7d117b389580>
Traceback (most recent call last):
File "/home/user/trade_bot_test/trade_bot_test.py", line 216, in process_depth_message
asks = [(float(a[0]), float(a[1])) for a in depth['asks']]
^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: coroutine ignored GeneratorExit
Task was destroyed but it is pending!
task: <Task cancelling name='Task-12' coro=<start_socket..start_futures_socket() running at /home/user/trade_bot_test/trade_bot_test.py:384> wait_for=<Task cancelling name='Task-17' coro=<WebSocketCommonProtocol.transfer_data() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:964> wait_for= cb=[Task.task_wakeup(), <1 more>, Task.task_wakeup()]> cb=[gather.._done_callback() at /usr/lib/python3.12/asyncio/tasks.py:767]>
Task was destroyed but it is pending!
task: <Task cancelling name='Task-17' coro=<WebSocketCommonProtocol.transfer_data() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:964> wait_for= cb=[Task.task_wakeup(), <1 more>, Task.task_wakeup()]>
Task was destroyed but it is pending!
task: <Task pending name='Task-18' coro=<WebSocketCommonProtocol.keepalive_ping() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1242> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-19' coro=<WebSocketCommonProtocol.close_connection() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1287> wait_for=<Task cancelling name='Task-17' coro=<WebSocketCommonProtocol.transfer_data() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:964> wait_for= cb=[Task.task_wakeup(), <1 more>, Task.task_wakeup()]>>
Task was destroyed but it is pending!
task: <Task cancelling name='Task-16' coro=<start_socket..start_spot_socket() running at /home/user/trade_bot_test/trade_bot_test.py:400> wait_for=<Task cancelled name='Task-27' coro=<WebSocketCommonProtocol.transfer_data() done, defined at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:944>> cb=[gather.._done_callback() at /usr/lib/python3.12/asyncio/tasks.py:767]>
Task was destroyed but it is pending!
task: <Task cancelling name='Task-15' coro=<start_socket..start_spot_socket() running at /home/user/trade_bot_test/trade_bot_test.py:400> wait_for=<Task cancelled name='Task-30' coro=<WebSocketCommonProtocol.transfer_data() done, defined at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:944>> cb=[gather.._done_callback() at /usr/lib/python3.12/asyncio/tasks.py:767]>
Task was destroyed but it is pending!
task: <Task cancelling name='Task-14' coro=<start_socket..start_spot_socket() running at /home/user/trade_bot_test/trade_bot_test.py:400> wait_for=<Task cancelled name='Task-33' coro=<WebSocketCommonProtocol.transfer_data() done, defined at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:944>> cb=[gather.._done_callback() at /usr/lib/python3.12/asyncio/tasks.py:767]>
Task was destroyed but it is pending!
task: <Task cancelling name='Task-13' coro=<start_socket..start_futures_socket() running at /home/user/trade_bot_test/trade_bot_test.py:384> wait_for=<Task cancelled name='Task-24' coro=<WebSocketCommonProtocol.transfer_data() done, defined at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:944>> cb=[gather.._done_callback() at /usr/lib/python3.12/asyncio/tasks.py:767]>
Task was destroyed but it is pending!
task: <Task cancelling name='Task-11' coro=<start_socket..start_futures_socket() running at /home/user/trade_bot_test/trade_bot_test.py:384> wait_for=<Task cancelled name='Task-21' coro=<WebSocketCommonProtocol.transfer_data() done, defined at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:944>> cb=[gather.._done_callback() at /usr/lib/python3.12/asyncio/tasks.py:767]>
Exception ignored in: <coroutine object WebSocketCommonProtocol.close_connection at 0x7d1170a8c520>
Traceback (most recent call last):
File "/home/user/trade_bot_test/trade_bot_test.py", line 216, in process_depth_message
asks = [(float(a[0]), float(a[1])) for a in depth['asks']]
^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: coroutine ignored GeneratorExit
Task was destroyed but it is pending!
task: <Task pending name='Task-20' coro=<ReconnectingWebsocket._read_loop() running at /home/user/trade_bot_test/lib/python3.12/site-packages/binance/streams.py:140> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-22' coro=<WebSocketCommonProtocol.keepalive_ping() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1242> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-23' coro=<WebSocketCommonProtocol.close_connection() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1287> wait_for=<Task cancelled name='Task-21' coro=<WebSocketCommonProtocol.transfer_data() done, defined at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:944>>>
Task was destroyed but it is pending!
task: <Task pending name='Task-25' coro=<WebSocketCommonProtocol.keepalive_ping() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1242> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-26' coro=<WebSocketCommonProtocol.close_connection() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1287> wait_for=<Task cancelled name='Task-24' coro=<WebSocketCommonProtocol.transfer_data() done, defined at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:944>>>
Task was destroyed but it is pending!
task: <Task pending name='Task-28' coro=<WebSocketCommonProtocol.keepalive_ping() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1242> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-29' coro=<WebSocketCommonProtocol.close_connection() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1287> wait_for=<Task cancelled name='Task-27' coro=<WebSocketCommonProtocol.transfer_data() done, defined at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:944>>>
Task was destroyed but it is pending!
task: <Task pending name='Task-31' coro=<WebSocketCommonProtocol.keepalive_ping() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1242> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-32' coro=<WebSocketCommonProtocol.close_connection() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1287> wait_for=<Task cancelled name='Task-30' coro=<WebSocketCommonProtocol.transfer_data() done, defined at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:944>>>
Task was destroyed but it is pending!
task: <Task pending name='Task-34' coro=<WebSocketCommonProtocol.keepalive_ping() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1242> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-35' coro=<WebSocketCommonProtocol.close_connection() running at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:1287> wait_for=<Task cancelled name='Task-33' coro=<WebSocketCommonProtocol.transfer_data() done, defined at /home/user/trade_bot_test/lib/python3.12/site-packages/websockets/legacy/protocol.py:944>>>
Exception ignored in: <coroutine object WebSocketCommonProtocol.close_connection at 0x7d117b2260c0>
Traceback (most recent call last):
File "/home/user/trade_bot_test/trade_bot_test.py", line 216, in process_depth_message
asks = [(float(a[0]), float(a[1])) for a in depth['asks']]
^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: coroutine ignored GeneratorExit
Exception ignored in: <coroutine object WebSocketCommonProtocol.close_connection at 0x7d117b226190>
Traceback (most recent call last):
File "/home/user/trade_bot_test/trade_bot_test.py", line 216, in process_depth_message
asks = [(float(a[0]), float(a[1])) for a in depth['asks']]
^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: coroutine ignored GeneratorExit
Exception ignored in: <coroutine object WebSocketCommonProtocol.close_connection at 0x7d117b225a40>
Traceback (most recent call last):
File "/home/user/trade_bot_test/trade_bot_test.py", line 216, in process_depth_message
asks = [(float(a[0]), float(a[1])) for a in depth['asks']]
^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: coroutine ignored GeneratorExit
Exception ignored in: <coroutine object WebSocketCommonProtocol.close_connection at 0x7d1170a8d2f0>
Traceback (most recent call last):
File "/home/user/trade_bot_test/trade_bot_test.py", line 216, in process_depth_message
asks = [(float(a[0]), float(a[1])) for a in depth['asks']]
^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: coroutine ignored GeneratorExit
Exception ignored in: <coroutine object WebSocketCommonProtocol.close_connection at 0x7d1170a8d630>
Traceback (most recent call last):
File "/home/user/trade_bot_test/trade_bot_test.py", line 216, in process_depth_message
asks = [(float(a[0]), float(a[1])) for a in depth['asks']]
^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: coroutine ignored GeneratorExit
Task was destroyed but it is pending!
task: <Task pending name='Task-36' coro=<ReconnectingWebsocket._read_loop() running at /home/user/trade_bot_test/lib/python3.12/site-packages/binance/streams.py:140> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-37' coro=<ReconnectingWebsocket._read_loop() running at /home/user/trade_bot_test/lib/python3.12/site-packages/binance/streams.py:140> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-38' coro=<ReconnectingWebsocket._read_loop() running at /home/user/trade_bot_test/lib/python3.12/site-packages/binance/streams.py:140> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-39' coro=<ReconnectingWebsocket._read_loop() running at /home/user/trade_bot_test/lib/python3.12/site-packages/binance/streams.py:140> wait_for=>
Task was destroyed but it is pending!
task: <Task pending name='Task-40' coro=<ReconnectingWebsocket._read_loop() running at /home/user/trade_bot_test/lib/python3.12/site-packages/binance/streams.py:140> wait_for=>


Здравствуйте!

Спасибо, что предоставили детали ошибок. Судя по предоставленным сообщениями из консоли, вы столкнулись с предупреждениями и ошибками, связанными с асинхронными задачами и их завершением. Давайте разберёмся, в чём причина этих предупреждений, и как мы можем их исправить.

Анализ ошибок
Unclosed client session

Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x...>
Это предупреждение означает, что объект ClientSession из библиотеки aiohttp не был корректно закрыт. Это может привести к утечкам ресурсов.
Task was destroyed but it is pending!

Task was destroyed but it is pending!
task: <Task cancelling name='Task-6' coro=<start_socket() running at /home/user/trade_bot_test/trade_bot_test.py:421> wait_for=<_GatheringFuture pending cb=[Task.task_wakeup()]> cb=[gather.<locals>._done_callback() at /usr/lib/python3.12/asyncio/tasks.py:767]>
Это означает, что асинхронная задача была уничтожена (например, при завершении программы), но она всё ещё находится в состоянии ожидания. Это может происходить, если задачи не были корректно отменены и завершены.
RuntimeError: coroutine ignored GeneratorExit

Exception ignored in: <coroutine object start_socket at 0x...>
Traceback (most recent call last):
  File "/home/user/trade_bot_test/trade_bot_test.py", line 216, in process_depth_message
    asks = [(float(a[0]), float(a[1])) for a in depth['asks']]
            ^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: coroutine ignored GeneratorExit
Это происходит, когда корутина игнорирует исключение GeneratorExit, которое вызывается при завершении генератора или корутины. Это может быть связано с тем, что корутина была отменена, но не корректно обработала это состояние.
Причина возникновения ошибок
Проблемы возникают из-за некорректного завершения асинхронных задач и не закрытия клиентских сессий при остановке мониторинга или завершении программы. При мониторинге большого количества символов эти проблемы могут проявляться чаще.

Решение проблемы
Нам необходимо:

Обеспечить корректное завершение асинхронных задач и закрытие клиентских сессий при остановке мониторинга.
Исправить код для корректной обработки отмены корутин и задач.
Шаги для исправления
1. Закрытие клиентской сессии async_client
В вашем коде async_client создаётся в функции start_socket(), но при завершении мониторинга или программы сессия не закрывается. Нам нужно убедиться, что сессия async_client корректно закрывается.

2. Обработка отмены задач и закрытие сокетов
При отмене асинхронных задач нам нужно убедиться, что все корутины корректно завершаются и ожидают завершения всех операций.

3. Корректное использование цикла событий asyncio_loop
Нам нужно убедиться, что цикл событий корректно останавливается и закрывается при завершении программы.

Обновление кода
Я внесу следующие изменения в ваш файл binance_bot.py:

Добавим функцию для корректного закрытия async_client

async def close_async_client():
    if async_client:
        await async_client.close_connection()
Обновим функцию stop_asyncio_tasks()
Обеспечим корректное завершение всех асинхронных задач.
Закроем async_client при остановке задач.
Закроем цикл событий asyncio_loop.
Исправим функцию start_socket()
Убедимся, что все исключения корректно обрабатываются.
Полный обновлённый код файла binance_bot.py

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

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

# Функция для загрузки 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-ключами.")
        exit(1)
    except Exception as e:
        print(f"Ошибка при чтении binance_api.txt: {e}")
        exit(1)
    if not api_key or not api_secret:
        print("API_KEY или API_SECRET не найдены в binance_api.txt.")
        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__)

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

# Функция получения общих символов USDT для спота и фьючерсов
def get_common_symbols():
    spot_exchange_info = client.get_exchange_info()
    futures_exchange_info = client.futures_exchange_info()

    spot_symbols = {symbol['symbol'] for symbol in spot_exchange_info['symbols']
                    if symbol['quoteAsset'] == 'USDT' and symbol['status'] == 'TRADING'}
    futures_symbols = {symbol['symbol'] 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']
        futures_symbol = pair['futures']
        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(pair)

    # Получаем плечи для фьючерсных символов
    futures_symbols = [pair['futures'] for pair in pairs_to_monitor]
    try:
        positions = client.futures_account()['positions']
        for pos in positions:
            symbol = pos['symbol']
            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() > 300:
        # Обновляем данные
        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]  # kline[7] - объём quote asset
            volume_cache[key] = {
                'volumes': volumes,
                'last_update': now
            }
        except Exception as e:
            print(f"Ошибка при получении исторических данных для {symbol}: {e}")
            return None

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

    return volumes

# Функция обработки сообщений глубины рынка
def process_depth_message(msg, market_type):
    if 'e' in msg and msg['e'] == 'depthUpdate':
        symbol = msg['s']
        bids = [(float(b[0]), float(b[1])) for b in msg['b']]
        asks = [(float(a[0]), float(a[1])) for a in msg['a']]

        # Проверяем, покрывают ли bids и asks диапазон 5% от текущей цены
        try:
            if market_type == 'spot':
                current_price = float(client.get_symbol_ticker(symbol=symbol)['price'])
            else:
                current_price = float(client.futures_mark_price(symbol=symbol)['markPrice'])
        except Exception as e:
            return

        min_price = current_price * 0.95
        max_price = current_price * 1.05

        # Определяем минимальные и максимальные цены в ордерах
        bid_prices = [price for price, qty in bids]
        ask_prices = [price for price, qty in asks]

        bids_ok = bid_prices and min(bid_prices) <= min_price
        asks_ok = ask_prices and max(ask_prices) >= max_price

        if not bids_ok or not asks_ok:
            # Необходимо запросить дополнительную глубину через REST API
            try:
                if market_type == 'spot':
                    depth = client.get_order_book(symbol=symbol, limit=1000)
                else:
                    depth = client.futures_order_book(symbol=symbol, limit=1000)
                bids = [(float(b[0]), float(b[1])) for b in depth['bids']]
                asks = [(float(a[0]), float(a[1])) for a in depth['asks']]
            except Exception as e:
                print(f"Ошибка при запросе глубины рынка для {symbol}: {e}")
                return

        if market_type == 'futures':
            depth_data[symbol] = {
                'bids': bids,
                'asks': asks
            }
        elif market_type == 'spot':
            spot_depth_data[symbol] = {
                'bids': bids,
                'asks': asks
            }

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

        for market_type in ['spot', 'futures']:
            if market_type == 'spot':
                symbol = spot_symbol
                data = spot_depth_data.get(symbol, None)
            else:
                symbol = futures_symbol
                data = depth_data.get(symbol, None)

            if not data:
                continue

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

            try:
                # Получаем текущую цену
                if market_type == 'spot':
                    current_price = float(client.get_symbol_ticker(symbol=symbol)['price'])
                else:
                    current_price = float(client.futures_mark_price(symbol=symbol)['markPrice'])
            except Exception as e:
                print(f"Ошибка при получении текущей цены для {symbol} ({market_type}): {e}")
                continue

            # Получаем кэшированные 5-минутные объёмы за последние 24 часа
            volumes = get_5m_volumes(symbol, market_type)
            if not volumes:
                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 = (symbol, market_type, price)
                    # Получаем initial_total_value из кеша или устанавливаем текущий total_value
                    if key not in density_history:
                        initial_total_value = total_value
                        density_history[key] = {
                            'initial_total_value': initial_total_value,
                            'last_total_value': total_value
                        }
                    else:
                        initial_total_value = density_history[key]['initial_total_value']
                        density_history[key]['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,  # Используем базовый символ (например, "BTC")
                        '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'])

            # Добавляем только 1 плотность выше и 1 ниже на каждом рынке для каждой монеты
            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 update_positions():
    # Здесь вы можете добавить код для обновления позиций (если необходимо)
    pass

# Функция мониторинга и торговли
def monitor_and_trade():
    # Задержка перед началом мониторинга, чтобы данные успели загрузиться
    time.sleep(5)  # Задержка в 5 секунд

    while True:
        if TRADING_ALLOWED and monitoring_active:
            densities = find_top_densities()
            for density in densities:
                symbol = density['symbol']
                if symbol in open_positions:
                    continue
                if check_entry_conditions(density):
                    open_position(density)
        time.sleep(1)
        update_positions()

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

    # Составляем списки потоков для фьючерсов и спота
    futures_streams = [f"{pair['futures'].lower()}@depth@100ms" for pair in symbols_to_monitor]
    spot_streams = [f"{pair['spot'].lower()}@depth@100ms" for pair in symbols_to_monitor]

    # Разбиваем на чанки по 100 потоков
    chunk_size = 100
    futures_chunks = [futures_streams[i:i + chunk_size] for i in range(0, len(futures_streams), chunk_size)]
    spot_chunks = [spot_streams[i:i + chunk_size] for i in range(0, len(spot_streams), chunk_size)]

    tasks = []

    async def start_futures_socket(stream_list):
        try:
            ts = bm.futures_multiplex_socket(stream_list)
            async with ts as tscm:
                while monitoring_active:
                    try:
                        res = await tscm.recv()
                        if 'data' in res:
                            msg = res['data']
                            process_depth_message(msg, 'futures')
                    except asyncio.CancelledError:
                        print("WebSocket tasks cancelled (futures)")
                        break
                    except Exception as e:
                        print(f"Ошибка в WebSocket (futures): {e}")
                        await asyncio.sleep(1)
        except Exception as e:
            print(f"Ошибка при запуске WebSocket (futures): {e}")

    async def start_spot_socket(stream_list):
        try:
            ts = bm.multiplex_socket(stream_list)
            async with ts as tscm:
                while monitoring_active:
                    try:
                        res = await tscm.recv()
                        if 'data' in res:
                            msg = res['data']
                            process_depth_message(msg, 'spot')
                    except asyncio.CancelledError:
                        print("WebSocket tasks cancelled (spot)")
                        break
                    except Exception as e:
                        print(f"Ошибка в WebSocket (spot): {e}")
                        await asyncio.sleep(1)
        except Exception as e:
            print(f"Ошибка при запуске WebSocket (spot): {e}")

    for chunk in futures_chunks:
        tasks.append(asyncio.create_task(start_futures_socket(chunk)))
    for chunk in spot_chunks:
        tasks.append(asyncio.create_task(start_spot_socket(chunk)))

    try:
        await asyncio.gather(*tasks)
    except asyncio.CancelledError:
        print("Все WebSocket задачи отменены")
    finally:
        # Закрываем BinanceSocketManager и клиента
        await async_client.close_connection()
        await async_client.session.close()
        await bm.close()
        print("WebSocket соединения закрыты")

# Функция для закрытия async_client
async def close_async_client():
    global async_client
    if async_client:
        await async_client.close_connection()
        await async_client.session.close()
        async_client = None
        print("async_client закрыт")

# Запуск асинхронных задач
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 asyncio.CancelledError:
        pass

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

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

    # Останавливаем цикл событий
    if asyncio_loop and asyncio_loop.is_running():
        asyncio_loop.call_soon_threadsafe(asyncio_loop.stop)
        asyncio_loop.close()
        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
    symbols_input = request.form.get('symbols', '')
    selected_symbols = [s.strip().upper() for s in symbols_input.split(',') if s.strip()]

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

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

    # Очищаем кеш плотностей при перезапуске сканирования
    density_history = {}

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

    # Перезапускаем асинхронные задачи с новыми символами
    threading.Thread(target=run_asyncio_tasks).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
    # Останавливаем текущий мониторинг
    stop_asyncio_tasks()

    # Обновляем символы для мониторинга: все общие символы 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 = {}

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

    # Перезапускаем асинхронные задачи с новыми символами
    threading.Thread(target=run_asyncio_tasks).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__':
    # Устанавливаем флаг активности мониторинга
    monitoring_active = True

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

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

finally:
    # Закрываем BinanceSocketManager и клиента
    await async_client.close_connection()
    await async_client.session.close()
    await bm.close()
    print("WebSocket соединения закрыты")
Убедились, что сессии и подключения корректно закрываются при завершении асинхронных задач.
Корректная отмена асинхронных задач
В функции stop_asyncio_tasks() убедились, что задачи корректно отменяются, а цикл событий закрывается:

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

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

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

    asyncio_tasks = []
Исправление запуска приложений
Убрали запуск Flask-приложения в отдельном потоке, так как Flask может работать в основном потоке, а остальные задачи могут быть запущены в других потоках.
Запустили app.run() в основном потоке, а поток monitor_and_trade в отдельном потоке.
Что делать дальше
Запустите обновлённый код и проверьте, исчезли ли предупреждения и ошибки.
Если ошибки всё ещё возникают, попробуйте уменьшить количество символов для мониторинга, чтобы проверить, влияет ли это на возникновение ошибок.
Следите за консолью при запуске и остановке мониторинга, чтобы убедиться, что сообщения об ошибках отсутствуют.
Заключение
Предоставленные изменения должны помочь в устранении ошибок и предупреждений, связанных с некорректным завершением асинхронных задач и не закрытыми клиентскими сессиями. Если у вас возникнут дополнительные вопросы или потребуется помощь с настройкой или отладкой кода, пожалуйста, дайте мне знать.

Желаю успешной работы вашего бота!


примерно через 1 минуту после начала мониторинга (при нажатии кнопки "Monitor ALL USDT") в консоли пошли ошибки о лимитах API:
192.168.100.140 - - [03/Oct/2024 18:32:45] "GET /get_densities HTTP/1.1" 200 -
Ошибка при получении текущей цены для 1MBABYDOGEUSDT (futures): APIError(code=-1003): Too many requests; current limit of IP(37.45.24.125) is 2400 requests per minute. Please use the websocket for live updates to avoid polling the API.
Ошибка при запросе глубины рынка для UNFIUSDT: APIError(code=-1003): Too many requests; current limit of IP(37.45.24.125) is 2400 requests per minute. Please use the websocket for live updates to avoid polling the API.
Ошибка при получении текущей цены для 1MBABYDOGEUSDT (futures): APIError(code=-1003): Too many requests; current limit of IP(37.45.24.125) is 2400 requests per minute. Please use the websocket for live updates to avoid polling the API.
Ошибка при получении текущей цены для 1MBABYDOGEUSDT (futures): APIError(code=-1003): Too many requests; current limit of IP(37.45.24.125) is 2400 requests per minute. Please use the websocket for live updates to avoid polling the API.


Здравствуйте!

Понимаю, что столкновение с ограничениями API может быть неприятным, особенно при мониторинге большого количества символов после нажатия кнопки "Monitor ALL USDT". Давайте разберёмся в причине возникновения ошибок и найдём решение.

Анализ проблемы
Ошибка:


APIError(code=-1003): Too many requests; current limit of IP(37.45.24.125) is 2400 requests per minute. Please use the websocket for live updates to avoid polling the API.
означает, что ваш бот превышает лимит запросов REST API Binance. Это происходит из-за большого количества запросов, которые ваш бот отправляет при мониторинге всех символов USDT. Binance рекомендует использовать WebSocket для получения данных в реальном времени и минимизировать использование REST API.

Причины возникновения ошибки в коде
Частые REST API запросы при обработке каждого сообщения WebSocket:
В функции process_depth_message, если данные глубины рынка неполные, выполняется дополнительный запрос к REST API для получения полной глубины рынка.
REST API запросы для получения текущей цены в find_top_densities:
В функции find_top_densities для каждого символа выполняется запрос к REST API для получения текущей цены.
Решение проблемы
Мы можем решить проблему, уменьшив количество REST API запросов:

Использовать данные WebSocket для получения текущей цены:
Вместо получения текущей цены через REST API, будем использовать среднюю цену между лучшим бидом и лучшим аском из данных глубины рынка, полученных по WebSocket.
Избежать дополнительных REST API запросов в process_depth_message:
Если данные глубины рынка неполные, мы можем игнорировать такие обновления или работать с теми данными, которые у нас есть, без дополнительных запросов.
Ограничить частоту обновления данных, которые необходимо получать через REST API:
Например, обновлять 5-минутные объёмы не чаще, чем раз в 15 минут.
Обновлённый код binance_bot.py
Ниже предоставляю полный обновлённый код binance_bot.py с внесёнными изменениями:


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

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

# Функция для загрузки 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-ключами.")
        exit(1)
    except Exception as e:
        print(f"Ошибка при чтении binance_api.txt: {e}")
        exit(1)
    if not api_key or not api_secret:
        print("API_KEY или API_SECRET не найдены в binance_api.txt.")
        exit(1)
    return api_key, api_secret

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

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

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

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

# Функция получения общих символов USDT для спота и фьючерсов
def get_common_symbols():
    spot_exchange_info = client.get_exchange_info()
    futures_exchange_info = client.futures_exchange_info()

    spot_symbols = {symbol['symbol'] for symbol in spot_exchange_info['symbols']
                    if symbol['quoteAsset'] == 'USDT' and symbol['status'] == 'TRADING'}
    futures_symbols = {symbol['symbol'] 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']
        futures_symbol = pair['futures']
        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(pair)

    # Получаем плечи для фьючерсных символов
    futures_symbols = [pair['futures'] for pair in pairs_to_monitor]
    try:
        positions = client.futures_account()['positions']
        for pos in positions:
            symbol = pos['symbol']
            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:  # Обновляем каждые 15 минут
        # Обновляем данные
        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]  # kline[7] - объём quote asset
            volume_cache[key] = {
                'volumes': volumes,
                'last_update': now
            }
        except Exception as e:
            print(f"Ошибка при получении исторических данных для {symbol}: {e}")
            return None

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

    return volumes

# Функция обработки сообщений глубины рынка
def process_depth_message(msg, market_type):
    if 'e' in msg and msg['e'] == 'depthUpdate':
        symbol = msg['s']
        bids = [(float(b[0]), float(b[1])) for b in msg['b']]
        asks = [(float(a[0]), float(a[1])) for a in msg['a']]

        # Обновляем текущую цену на основе последних данных
        if bids and asks:
            best_bid = bids[0][0]
            best_ask = asks[0][0]
            current_price = (best_bid + best_ask) / 2
        elif bids:
            current_price = bids[0][0]
        elif asks:
            current_price = asks[0][0]
        else:
            # Если нет данных, пропускаем
            return

        # Сохраняем текущую цену и глубину рынка для символа
        if market_type == 'futures':
            depth_data[symbol] = {
                'bids': bids,
                'asks': asks,
                'current_price': current_price
            }
        elif market_type == 'spot':
            spot_depth_data[symbol] = {
                'bids': bids,
                'asks': asks,
                'current_price': current_price
            }

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

        for market_type in ['spot', 'futures']:
            if market_type == 'spot':
                symbol = spot_symbol
                data = spot_depth_data.get(symbol, None)
            else:
                symbol = futures_symbol
                data = depth_data.get(symbol, None)

            if not data:
                continue

            bids = data['bids']
            asks = data['asks']
            current_price = data.get('current_price')
            if not current_price:
                continue

            # Получаем кэшированные 5-минутные объёмы за последние 24 часа
            volumes = get_5m_volumes(symbol, market_type)
            if not volumes:
                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 = (symbol, market_type, price)
                    # Получаем initial_total_value из кеша или устанавливаем текущий total_value
                    if key not in density_history:
                        initial_total_value = total_value
                        density_history[key] = {
                            'initial_total_value': initial_total_value,
                            'last_total_value': total_value
                        }
                    else:
                        initial_total_value = density_history[key]['initial_total_value']
                        density_history[key]['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,  # Используем базовый символ (например, "BTC")
                        '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'])

            # Добавляем только 1 плотность выше и 1 ниже на каждом рынке для каждой монеты
            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 update_positions():
    # Здесь вы можете добавить код для обновления позиций (если необходимо)
    pass

# Функция мониторинга и торговли
def monitor_and_trade():
    # Задержка перед началом мониторинга, чтобы данные успели загрузиться
    time.sleep(5)  # Задержка в 5 секунд

    while True:
        if TRADING_ALLOWED and monitoring_active:
            densities = find_top_densities()
            for density in densities:
                symbol = density['symbol']
                if symbol in open_positions:
                    continue
                if check_entry_conditions(density):
                    open_position(density)
        time.sleep(1)
        update_positions()

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

    # Составляем списки потоков для фьючерсов и спота
    futures_streams = [f"{pair['futures'].lower()}@depth@100ms" for pair in symbols_to_monitor]
    spot_streams = [f"{pair['spot'].lower()}@depth@100ms" for pair in symbols_to_monitor]

    # Разбиваем на чанки по 50 потоков (чтобы избежать перегрузки)
    chunk_size = 50
    futures_chunks = [futures_streams[i:i + chunk_size] for i in range(0, len(futures_streams), chunk_size)]
    spot_chunks = [spot_streams[i:i + chunk_size] for i in range(0, len(spot_streams), chunk_size)]

    tasks = []

    async def start_futures_socket(stream_list):
        try:
            ts = bm.futures_multiplex_socket(stream_list)
            async with ts as tscm:
                while monitoring_active:
                    try:
                        res = await tscm.recv()
                        if 'data' in res:
                            msg = res['data']
                            process_depth_message(msg, 'futures')
                    except asyncio.CancelledError:
                        print("WebSocket tasks cancelled (futures)")
                        break
                    except Exception as e:
                        print(f"Ошибка в WebSocket (futures): {e}")
                        await asyncio.sleep(1)
        except Exception as e:
            print(f"Ошибка при запуске WebSocket (futures): {e}")

    async def start_spot_socket(stream_list):
        try:
            ts = bm.multiplex_socket(stream_list)
            async with ts as tscm:
                while monitoring_active:
                    try:
                        res = await tscm.recv()
                        if 'data' in res:
                            msg = res['data']
                            process_depth_message(msg, 'spot')
                    except asyncio.CancelledError:
                        print("WebSocket tasks cancelled (spot)")
                        break
                    except Exception as e:
                        print(f"Ошибка в WebSocket (spot): {e}")
                        await asyncio.sleep(1)
        except Exception as e:
            print(f"Ошибка при запуске WebSocket (spot): {e}")

    for chunk in futures_chunks:
        tasks.append(asyncio.create_task(start_futures_socket(chunk)))
    for chunk in spot_chunks:
        tasks.append(asyncio.create_task(start_spot_socket(chunk)))

    try:
        await asyncio.gather(*tasks)
    except asyncio.CancelledError:
        print("Все WebSocket задачи отменены")
    finally:
        if async_client:
            await async_client.close_connection()
        print("WebSocket соединения закрыты")

# Запуск асинхронных задач
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 asyncio.CancelledError:
        pass

# Функция остановки асинхронных задач
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
    symbols_input = request.form.get('symbols', '')
    selected_symbols = [s.strip().upper() for s in symbols_input.split(',') if s.strip()]

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

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

    # Очищаем кеш плотностей при перезапуске сканирования
    density_history = {}

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

    # Перезапускаем асинхронные задачи с новыми символами
    threading.Thread(target=run_asyncio_tasks).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
    # Останавливаем текущий мониторинг
    stop_asyncio_tasks()

    # Обновляем символы для мониторинга: все общие символы 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 = {}

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

    # Перезапускаем асинхронные задачи с новыми символами
    threading.Thread(target=run_asyncio_tasks).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__':
    # Запуск мониторинга и торговли в отдельном потоке
    threading.Thread(target=monitor_and_trade).start()

    # Запуск Flask-приложения в основном потоке
    app.run(host='0.0.0.0', port=4000)
Что изменилось
Убраны вызовы REST API в process_depth_message:
Мы больше не делаем дополнительные запросы к API для получения глубины рынка.
Используем текущую цену из данных WebSocket:
В process_depth_message вычисляем current_price на основе данных bids и asks.
В find_top_densities используем current_price из сохранённых данных:
Больше не вызываем REST API для получения текущей цены.
Увеличен интервал обновления объёмов до 15 минут:
В функции get_5m_volumes обновляем данные каждые 900 секунд (15 минут).
Изменён размер чанка для WebSocket потоков:
Уменьшили chunk_size до 50, чтобы уменьшить нагрузку на WebSocket соединения.
Обеспечили корректное закрытие асинхронных задач и сессий:
В функциях start_socket и stop_asyncio_tasks добавили необходимый код для корректного завершения.
Что сделать дальше
Запустите бота и проверьте, уменьшается ли количество ошибок об ограничениях API.
Следите за консолью на предмет других ошибок или предупреждений.
Если продолжаете получать ошибки, попробуйте уменьшить количество символов для мониторинга или дополнительно оптимизировать код.
Дополнительные рекомендации
Используйте WebSocket для получения всех необходимых данных.
Избегайте вызовов REST API в циклах или частых операциях.
Если необходимо получать какие-либо данные через REST API, кэшируйте их и обновляйте не чаще определённого интервала.
Следите за документацией Binance API по ограничениям и рекомендациям.
Если у вас возникнут дополнительные вопросы или потребуется помощь с настройкой или отладкой кода, пожалуйста, дайте мне знать. Я всегда готов помочь!

Желаю успешной работы вашего бота!


После последнего измнений кода, программа за 5 минут не смогла найти ни одной плотности, я попробовал изменить фильтр на более лояльный, но программа так и не нашла плотности. В консоли ошибок нету:
192.168.100.140 - - [03/Oct/2024 18:50:49] "GET /get_open_positions HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:50:54] "GET /get_open_positions HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:50:59] "GET /get_open_positions HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:51:04] "GET /get_open_positions HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:51:09] "GET /get_open_positions HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 18:51:14] "GET /get_open_positions HTTP/1.1" 200 -


Здравствуйте!

Приношу извинения за возникшую проблему. Похоже, что последние изменения в коде, направленные на уменьшение запросов к REST API, привели к тому, что плотности перестали находиться. Давайте вместе разберёмся, в чём причина, и найдём решение.

Анализ проблемы
После исключения запросов к REST API для получения текущей цены и глубины рынка, мы стали использовать текущую цену, рассчитанную на основе данных, полученных из WebSocket. Однако, есть несколько причин, по которым плотности могут не находиться:

Недостаточные данные из WebSocket для расчёта текущей цены:
Если в данных, полученных из WebSocket, отсутствуют биды или аски (например, при низкой ликвидности), то вычисление current_price может быть некорректным или вовсе пропускаться.
Отсутствие полной глубины рынка:
WebSocket может не предоставлять достаточную глубину рынка для обнаружения плотностей, особенно если мы не запрашиваем полную глубину через REST API.
Логические ошибки в коде после изменений:
Возможно, некоторые участки кода неверно обрабатывают данные после последних изменений.
Предлагаемое решение
Чтобы исправить ситуацию и вернуть функциональность поиска плотностей, предлагаю:

Вернуть запросы к REST API для получения текущей цены с кэшированием и ограничением частоты запросов:
Использовать REST API для получения текущей цены, но кэшировать её и обновлять, например, раз в 60 секунд, чтобы не превышать лимиты API.
Вернуть запросы к REST API для получения полной глубины рынка при необходимости:
Если данные из WebSocket недостаточны для вычисления требуемых плотностей, использовать REST API запросы, но с ограничением частоты.
Добавить отладочные сообщения:
Это поможет понять, на каком этапе происходит проблема и почему плотности не вычисляются.
Обновлённый код binance_bot.py
Ниже предоставляю полный обновлённый код файла binance_bot.py, включающий предложенные изменения.


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

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

# Функция для загрузки 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-ключами.")
        exit(1)
    except Exception as e:
        print(f"Ошибка при чтении binance_api.txt: {e}")
        exit(1)
    if not api_key or not api_secret:
        print("API_KEY или API_SECRET не найдены в binance_api.txt.")
        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__)

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

# Функция получения общих символов USDT для спота и фьючерсов
def get_common_symbols():
    spot_exchange_info = client.get_exchange_info()
    futures_exchange_info = client.futures_exchange_info()

    spot_symbols = {symbol['symbol'] for symbol in spot_exchange_info['symbols']
                    if symbol['quoteAsset'] == 'USDT' and symbol['status'] == 'TRADING'}
    futures_symbols = {symbol['symbol'] 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']
        futures_symbol = pair['futures']
        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(pair)

    # Получаем плечи для фьючерсных символов
    futures_symbols = [pair['futures'] for pair in pairs_to_monitor]
    try:
        positions = client.futures_account()['positions']
        for pos in positions:
            symbol = pos['symbol']
            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

# Функция получения текущей цены с кэшированием
def get_current_price(symbol, market_type):
    key = f"{market_type}_{symbol}"
    now = datetime.now(timezone.utc)
    if key not in current_price_cache or (now - current_price_cache[key]['last_update']).total_seconds() > 30:
        try:
            if market_type == 'spot':
                price = float(client.get_symbol_ticker(symbol=symbol)['price'])
            else:
                price = float(client.futures_mark_price(symbol=symbol)['markPrice'])
            current_price_cache[key] = {
                'price': price,
                'last_update': now
            }
        except Exception as e:
            print(f"Ошибка при получении текущей цены для {symbol} ({market_type}): {e}")
            return None
    else:
        price = current_price_cache[key]['price']
    return price

# Функция получения 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:  # Обновляем каждые 15 минут
        # Обновляем данные
        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]  # kline[7] - объём quote asset
            volume_cache[key] = {
                'volumes': volumes,
                'last_update': now
            }
        except Exception as e:
            print(f"Ошибка при получении исторических данных для {symbol}: {e}")
            return None

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

    return volumes

# Функция обработки сообщений глубины рынка
def process_depth_message(msg, market_type):
    if 'e' in msg and msg['e'] == 'depthUpdate':
        symbol = msg['s']
        bids = [(float(b[0]), float(b[1])) for b in msg['b']]
        asks = [(float(a[0]), float(a[1])) for a in msg['a']]

        # Сохраняем глубину рынка
        if market_type == 'futures':
            depth_data[symbol] = {
                'bids': bids,
                'asks': asks
            }
        elif market_type == 'spot':
            spot_depth_data[symbol] = {
                'bids': bids,
                'asks': asks
            }

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

        for market_type in ['spot', 'futures']:
            if market_type == 'spot':
                symbol = spot_symbol
                data = spot_depth_data.get(symbol, None)
            else:
                symbol = futures_symbol
                data = depth_data.get(symbol, None)

            if not data:
                continue

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

            # Получаем текущую цену
            current_price = get_current_price(symbol, market_type)
            if not current_price:
                continue

            # Получаем кэшированные 5-минутные объёмы за последние 24 часа
            volumes = get_5m_volumes(symbol, market_type)
            if not volumes:
                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 = (symbol, market_type, price)
                    # Получаем initial_total_value из кеша или устанавливаем текущий total_value
                    if key not in density_history:
                        initial_total_value = total_value
                        density_history[key] = {
                            'initial_total_value': initial_total_value,
                            'last_total_value': total_value
                        }
                    else:
                        initial_total_value = density_history[key]['initial_total_value']
                        density_history[key]['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,  # Используем базовый символ (например, "BTC")
                        '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'])

            # Добавляем только 1 плотность выше и 1 ниже на каждом рынке для каждой монеты
            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 update_positions():
    # Здесь вы можете добавить код для обновления позиций (если необходимо)
    pass

# Функция мониторинга и торговли
def monitor_and_trade():
    # Задержка перед началом мониторинга, чтобы данные успели загрузиться
    time.sleep(5)  # Задержка в 5 секунд

    while True:
        if TRADING_ALLOWED and monitoring_active:
            densities = find_top_densities()
            for density in densities:
                symbol = density['symbol']
                if symbol in open_positions:
                    continue
                if check_entry_conditions(density):
                    open_position(density)
        time.sleep(1)
        update_positions()

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

    # Составляем списки потоков для фьючерсов и спота
    futures_streams = [f"{pair['futures'].lower()}@depth20@100ms" for pair in symbols_to_monitor]
    spot_streams = [f"{pair['spot'].lower()}@depth20@100ms" for pair in symbols_to_monitor]

    # Разбиваем на чанки по 50 потоков (чтобы избежать перегрузки)
    chunk_size = 50
    futures_chunks = [futures_streams[i:i + chunk_size] for i in range(0, len(futures_streams), chunk_size)]
    spot_chunks = [spot_streams[i:i + chunk_size] for i in range(0, len(spot_streams), chunk_size)]

    tasks = []

    async def start_futures_socket(stream_list):
        try:
            ts = bm.futures_multiplex_socket(stream_list)
            async with ts as tscm:
                while monitoring_active:
                    try:
                        res = await tscm.recv()
                        if 'data' in res:
                            msg = res['data']
                            process_depth_message(msg, 'futures')
                    except asyncio.CancelledError:
                        print("WebSocket tasks cancelled (futures)")
                        break
                    except Exception as e:
                        print(f"Ошибка в WebSocket (futures): {e}")
                        await asyncio.sleep(1)
        except Exception as e:
            print(f"Ошибка при запуске WebSocket (futures): {e}")

    async def start_spot_socket(stream_list):
        try:
            ts = bm.multiplex_socket(stream_list)
            async with ts as tscm:
                while monitoring_active:
                    try:
                        res = await tscm.recv()
                        if 'data' in res:
                            msg = res['data']
                            process_depth_message(msg, 'spot')
                    except asyncio.CancelledError:
                        print("WebSocket tasks cancelled (spot)")
                        break
                    except Exception as e:
                        print(f"Ошибка в WebSocket (spot): {e}")
                        await asyncio.sleep(1)
        except Exception as e:
            print(f"Ошибка при запуске WebSocket (spot): {e}")

    for chunk in futures_chunks:
        tasks.append(asyncio.create_task(start_futures_socket(chunk)))
    for chunk in spot_chunks:
        tasks.append(asyncio.create_task(start_spot_socket(chunk)))

    try:
        await asyncio.gather(*tasks)
    except asyncio.CancelledError:
        print("Все WebSocket задачи отменены")
    finally:
        if async_client:
            await async_client.close_connection()
        print("WebSocket соединения закрыты")

# Запуск асинхронных задач
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 asyncio.CancelledError:
        pass

# Функция остановки асинхронных задач
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()

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

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

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

    # Перезапускаем асинхронные задачи с новыми символами
    threading.Thread(target=run_asyncio_tasks).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()

    # Обновляем символы для мониторинга: все общие символы 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 = {}

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

    # Перезапускаем асинхронные задачи с новыми символами
    threading.Thread(target=run_asyncio_tasks).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__':
    # Запуск мониторинга и торговли в отдельном потоке
    threading.Thread(target=monitor_and_trade).start()

    # Запуск Flask-приложения в основном потоке
    app.run(host='0.0.0.0', port=4000)
Объяснение изменений
Добавлена функция get_current_price() с кэшированием:
Эта функция получает текущую цену для символа с заданным типом рынка (spot или futures) через REST API и кэширует её на 30 секунд. Это позволяет минимизировать количество запросов к API и избежать превышения лимитов.
В функции find_top_densities() используется get_current_price():
Теперь мы гарантированно получаем текущую цену для расчётов, что повышает точность обнаружения плотностей.
В функции process_depth_message() убран расчёт current_price:
Мы больше не пытаемся вычислять текущую цену на основе данных WebSocket, так как это могло приводить к ошибкам. Вместо этого, мы используем кэшированную цену из get_current_price().
Добавлены отладочные сообщения:
Добавлены print() в ключевых местах для отслеживания процесса и выявления возможных проблем.
Очищение кэша текущих цен при перезапуске мониторинга:
При перезапуске мониторинга мы очищаем current_price_cache, чтобы избежать использования устаревших данных.
Улучшена обработка асинхронных задач:
Обеспечено корректное завершение асинхронных задач и закрытие соединений с WebSocket и API.
Рекомендации по дальнейшим действиям
Проверьте обновлённый код:
Запустите бота с новым кодом и проверьте, появляются ли плотности в таблице.
Следите за консолью:
Обратите внимание на отладочные сообщения и возможные ошибки. Если они появляются, сообщите мне детали, и мы сможем их разобрать.
Настройте параметры фильтрации:
Попробуйте установить более мягкие значения параметров "Объём плотности ($/USDT)" и "Время разъедания (мин.)", чтобы убедиться, что плотности находятся.
Оцените производительность и нагрузку:
Если вы всё ещё сталкиваетесь с проблемами лимитов API, подумайте о снижении частоты запросов или уменьшении количества символов для мониторинга.
Заключение
Я надеюсь, что эти изменения помогут решить проблему и ваш бот снова начнёт находить плотности. Если у вас возникнут дополнительные вопросы или потребуется помощь с отладкой, пожалуйста, дайте мне знать. Я всегда готов помочь!

Желаю успешной работы вашего бота!