Бот на языке Python с управлением через веб-интерфейсом для мониторинга плотностей криптовалют на Binance и торговли по стратегии "пробоя" и "отскока" от плотностей. Функции:
1) Автоматическое формирование списка криптовалютных пар по кнопке - бот запрашивает список криптовалют на фьючерсном и спотовом рынке с USDT. Те пары на спотовом рынке которые не имеют аналогов на фьючерсном рынке удаляются из списка для мониторинга. Стоит учитывать префиксы 1000 - например на спотовым рынке есть монета SHIBUSDT, а ее аналог на фьючерсном рынке 1000SHIBUSDT (в котором курс уже не за 1 монету а за 1000). 
2) Список исключение - поле в котором можно задать список монет которые будут удалены из списка мониторинга даже если были отобраны в автоматическом режиме - здесь вписывается не полная пара с USDT, а только монеты через запятую. (Пример: BTC, ETH, BNB (будут исключены все эти пары с USDT из мониторинга на спотовом и фьючерсном рынках)).
3) Ручной список монет - поле в котором можно задать список монет только которые будут мониториться - здесь вписывается не полная пара с USDT, а только монеты через запятую. (Пример: BTC, ETH, BNB (будут мониториться все эти пары с USDT на спотовом и фьючерсном рынках)). При включении ручного мониторинга во время работы автоматического мониторинга делается остановка мониторинга на 5 секунда с очисткой кеша, и запуск мониторинга по монетам введенным с поле с ручным списком.
4) Должна быть кнопка остановки мониторинга вручную.
5) Программа берет API ключ из файла binance_api.txt в той же папке с исполняемым файлом py, ключ будет в формате: 
API_KEY = 'ключ'
API_SECRET = 'секрет'
6) После запуска мониторинга через WebSocket ежесекундно получаем рыночные цены всех монет которые мониторятся в данный момент (они будут нужны для вычисления значения столбца 6 "До уровня %" нашей таблицы).
7) После запуска формирования автоматического списка или вручную списка криптовалют с USDT - программа через WebSocket получает начальную книгу ордеров по всем монетам на спотовом и фьючерсном рынке с максимальной глубиной для WebSocket (кажется 20). После получения первой начальной книги ордеров по всем монетам программа анализирует сполько для каждой монеты нужно шагов цены чтобы покрыть глубину ордеров 3% от текущей цены и последующие запросы выполняет через REST API на требуюмую глубину для каждой монеты чтобы покрыть 3%. Следует так же сформировать очередь запросов так как существует лимит запросов по спотовому и фьючерсному рынку через REST API в минуту, чтобы не превысить лимит. Я понимаю что получение книг ордеров по всем монетам через REST API с глубиной 3% не превышая лимит, займет несколько минут. Меня устроит полный период обновления по всем монетам раз в 5 минут.
8) После запуска мониторинга мы через WebSocket получаем 12 последних 5-минутных объемов торгов по каждый монете, вычисляем самый крупный объем из этих 12 5-ти минуток, 11 последних 5-ти минуток по каждой монете помещаем в кеш, а последнюю постоянно обновляем каждую 1 минуту, пока последняя 5-ти минутка не закроется (закрываются всегда в 00, 05, 10, 15 и тп минут по мировому времени) и не попадет в кеш и затем будет обновляться новая 5-ти минутка. Самый крупный объем из 5-ти минуток необходим для вычисления времени разъедания по конкретной монете, пример по монете BTC, самый крупный 5-ти минутный объем мы находим 10BTC, то есть если будет требоваться время разъедания не менее 10 минут, то это не менее 20BTC (10BTC/5минут*10минут).
8) На нашей веб форме будет темный фоновый стиль страницы, сверху будут кнопки управления торговлей и мониторингом, а ниже таблица состоящая из 6 стобцов, которая будет сдублирована (слева и справа - поровну по количеству плотностей в ней). Стобцы будут следующие: 1-й стобец - "Монета" (здесь будет монета по которой имеется плотность и название рынка, а также в скобках плечо (только для фьючерсного рынка), например: BTC(spot) или ETH(futures Х50), 2-й стобец "Объем" (здесь указывается объем найденной и отобранной плотностей в USDT ($) с округлением по тысяч $ (то есть не 256423, а 256k), а также % от ее начального объема при котором она была найденая впервые и помещена в кеш. То есть если плотность изначально была 200000$(100%), то это 100%, а потом плотность стала 2200000$(110%), так же в зависимости от объема окрашивать данную ячейку в цвет : более 100000$ - серый, более 250000$ - коричневый, более 500000$ - желтый, более 1000000$ - фиолетовый, более 3000000$ - бирюзовый), 3-й столбец "Цена" (здесь будет указана цена по которой стоит эта плотность в стакане заявок книги ордеров), 4-й стобец "Время разъедания" (здесь будет указано вычисленное время разъедания нашей плотности исходя из данных по максимальному объему 12 последних 5-ти минутных объемов торгов (указывал выше алгоритм вычисления), округленное до целых минут, 5-й столбец "Монет в уровне" (здесь будет указано количество монет в плотности, с округлением по такому правилу чтобы не было более 4-х цифр, то есть если число монет более 4-х цифр, то есть 5 и более то кругляется с приставой (K -тысячи, M-миллионы) (пример  монет 15443 - 15.44K, монет 3543523 - 3.543M.), 6-й стобец "До уровня %" (здесь расчитывается как далеко наша плотность находится от текущей рыночной цены (которая обновляется раз в секунду и соответственно значение этого столбца должно обновляться то же раз в 1 секунду с обновлением рыночной цены монеты) в процентах и если она находится ниже рыночной цены то процент будет со знакомы минус, если выше то просто число процента (пример: -1.54% или 2.45%), значение округлять до 2-х знаков после запятой, а так же если процент является отрицательным то ячейку "Монета" и данную "До уровня %" данной строки окрашивать в красный, если процент положительных то в зеленый.
9) Все значения в ячейках таблицы (цифры и названия монет - черным цветом).
10) По каждой монете выводить в таблицу не более 2-х плотностей (одна выше рыночной цены и одна ниже), для спотовой монеты и фьючерсной отдельные лимиты (например могут быть выведены 2 плотности по BTCUSDT(spot) и 2 плотности BTCUSDT(futures X50)), следует выводить плотности которые прошли отбор и наиболее близки к рыночной цене.
11) Сортировать данные в таблице по алфавитному порядку названия монеты
12) Элемент торговли пока отключен, мы его реализуем в будущем.
13) При написании кода ты должен обновить свои данные об актуальных методах, лимитах и остальной информации по использованию REST API Binance и WebSocket Binance
14) Мы ранее с тобой писали код, могу предоставить ниже код для файлов python и html которые мы сделали

Вот что у нас получилось, код файла python:
import os
import sys
import threading
import time
import logging
from datetime import datetime, timedelta
from collections import deque
from binance.client import Client
from binance.websocket.spot.websocket_client import SpotWebsocketClient
from binance.websocket.futures.websocket_client import FuturesWebsocketClient
from binance.exceptions import BinanceAPIException
from flask import Flask, render_template, request, redirect, url_for, jsonify
import queue

# Отключение логгера Flask для предотвращения вывода сообщений о запросах
log = logging.getLogger('werkzeug')
log.disabled = True

# Параметры бота
MAX_OPEN_POSITIONS = 5
POSITION_SIZE_USDT = 10.0
TRADING_ALLOWED = False
monitoring_active = False
min_density_volume = 100000.0  # Объем плотности в $
min_consumption_time = 10.0    # Время разъедания в минутах

# Инициализация глобальных переменных
selected_symbols = []
excluded_symbols = []
symbols_to_monitor = []
order_book_data = {}
open_positions = {}
density_history = {}
current_price_cache = {}
symbol_leverages = {}
max_5min_volumes = {}
five_min_volumes = {}
spot_ws_client = None  # Клиент WebSocket для спота
futures_ws_client = None  # Клиент WebSocket для фьючерсов
order_book_update_queue_spot = queue.Queue()
order_book_update_queue_futures = queue.Queue()

# Лимиты запросов для спота и фьючерсов
SPOT_REQUEST_LIMIT = 1200  # Запросов в минуту
FUTURES_REQUEST_LIMIT = 2400  # Запросов в минуту

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

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

# Инициализация клиента Binance
client = Client(API_KEY, API_SECRET)

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

# Функция для остановки задач
def stop_tasks():
    global monitoring_active, spot_ws_client, futures_ws_client
    monitoring_active = False
    if spot_ws_client:
        spot_ws_client.stop()
    if futures_ws_client:
        futures_ws_client.stop()
    spot_ws_client = None
    futures_ws_client = None
    print("Мониторинг остановлен")

# Функция для запуска задач
def run_tasks():
    threading.Thread(target=start_ws_clients).start()
    threading.Thread(target=monitor_and_trade).start()
    threading.Thread(target=process_order_book_updates_spot).start()
    threading.Thread(target=process_order_book_updates_futures).start()

def start_ws_clients():
    global monitoring_active, spot_ws_client, futures_ws_client
    monitoring_active = True
    spot_ws_client = SpotWebsocketClient(stream_url="wss://stream.binance.com:9443")
    spot_ws_client.start()
    futures_ws_client = FuturesWebsocketClient(stream_url="wss://fstream.binance.com")
    futures_ws_client.start()
    subscribe_to_streams()

def subscribe_to_streams():
    # Подписываемся на потоки цен и книги ордеров через WebSocket
    for pair in symbols_to_monitor:
        symbol_spot = pair['spot']
        symbol_futures = pair['futures']

        # Подписываемся на поток цены для спота
        spot_ws_client.instant_subscribe(
            stream=f"{symbol_spot.lower()}@ticker",
            callback=handle_price_update_spot
        )

        # Подписываемся на поток цены для фьючерсов
        futures_ws_client.instant_subscribe(
            stream=f"{symbol_futures.lower()}@ticker",
            callback=handle_price_update_futures
        )

        # Подписываемся на свечи 5m для спота
        spot_ws_client.instant_subscribe(
            stream=f"{symbol_spot.lower()}@kline_5m",
            callback=handle_kline
        )

        # Подписываемся на свечи 5m для фьючерсов
        futures_ws_client.instant_subscribe(
            stream=f"{symbol_futures.lower()}@kline_5m",
            callback=handle_kline
        )

        # Подписываемся на поток книги ордеров с глубиной 20 для спота
        spot_ws_client.instant_subscribe(
            stream=f"{symbol_spot.lower()}@depth20",
            callback=handle_initial_depth_stream_spot
        )

        # Подписываемся на поток книги ордеров с глубиной 20 для фьючерсов
        futures_ws_client.instant_subscribe(
            stream=f"{symbol_futures.lower()}@depth20",
            callback=handle_initial_depth_stream_futures
        )

def handle_price_update_spot(msg):
    # Обработка обновления текущей цены для спота
    try:
        data = msg.get('data', {})
        if not data:
            return
        symbol = data['s']
        price = float(data['c'])
        current_price_cache[symbol] = price
    except Exception as e:
        print(f"Ошибка в handle_price_update_spot: {e}")

def handle_price_update_futures(msg):
    # Обработка обновления текущей цены для фьючерсов
    try:
        data = msg.get('data', {})
        if not data:
            return
        symbol = data['s']
        price = float(data['c'])
        current_price_cache[symbol] = price
    except Exception as e:
        print(f"Ошибка в handle_price_update_futures: {e}")

def handle_initial_depth_stream_spot(msg):
    # Обработка начальных книг ордеров для спота
    try:
        data = msg.get('data', {})
        if not data:
            return
        symbol = data['s']
        bids = data['b']
        asks = data['a']
        order_book_data[symbol] = {'bids': bids, 'asks': asks}
        # Добавляем в очередь на обновление книги ордеров через REST API
        order_book_update_queue_spot.put(symbol)
    except Exception as e:
        print(f"Ошибка в handle_initial_depth_stream_spot: {e}")

def handle_initial_depth_stream_futures(msg):
    # Обработка начальных книг ордеров для фьючерсов
    try:
        data = msg.get('data', {})
        if not data:
            return
        symbol = data['s']
        bids = data['b']
        asks = data['a']
        order_book_data[symbol] = {'bids': bids, 'asks': asks}
        # Добавляем в очередь на обновление книги ордеров через REST API
        order_book_update_queue_futures.put(symbol)
    except Exception as e:
        print(f"Ошибка в handle_initial_depth_stream_futures: {e}")

def handle_kline(msg):
    try:
        data = msg.get('data', {})
        if not data:
            return
        symbol = data['s']
        is_candle_closed = data['k']['x']
        volume = float(data['k']['q'])  # Объем в quoteAsset (USDT)
        if symbol not in five_min_volumes:
            five_min_volumes[symbol] = deque(maxlen=12)
        if is_candle_closed:
            five_min_volumes[symbol].append(volume)
            max_5min_volumes[symbol] = max(five_min_volumes[symbol])
        else:
            # Обновляем текущую свечу
            if len(five_min_volumes[symbol]) == 0:
                five_min_volumes[symbol].append(volume)
            else:
                five_min_volumes[symbol][-1] = volume
            max_5min_volumes[symbol] = max(five_min_volumes[symbol])
    except Exception as e:
        print(f"Ошибка в handle_kline: {e}")

def process_order_book_updates_spot():
    # Обрабатываем очередь обновления книг ордеров для спота
    while monitoring_active:
        if not order_book_update_queue_spot.empty():
            symbol = order_book_update_queue_spot.get()
            update_order_book(symbol, 'spot')
            time.sleep(60.0 / SPOT_REQUEST_LIMIT)  # Ждем, чтобы не превысить лимит
        else:
            time.sleep(1)

def process_order_book_updates_futures():
    # Обрабатываем очередь обновления книг ордеров для фьючерсов
    while monitoring_active:
        if not order_book_update_queue_futures.empty():
            symbol = order_book_update_queue_futures.get()
            update_order_book(symbol, 'futures')
            time.sleep(60.0 / FUTURES_REQUEST_LIMIT)  # Ждем, чтобы не превысить лимит
        else:
            time.sleep(1)

def update_order_book(symbol, market_type):
    try:
        # Получаем текущую цену
        current_price = current_price_cache.get(symbol)
        if not current_price:
            print(f"Текущая цена для {symbol} не найдена")
            return

        # Рассчитываем цену на 3% от текущей
        price_threshold_high = current_price * 1.03
        price_threshold_low = current_price * 0.97

        # Максимальный допустимый limit
        max_limit = 5000 if market_type == 'spot' else 1000

        # Инициализируем depth
        depth = None

        levels = 20  # Начальное значение

        while levels <= max_limit:
            # Получаем книгу ордеров с заданным уровнем глубины
            if market_type == 'spot':
                depth = client.get_order_book(symbol=symbol, limit=levels)
            else:
                depth = client.futures_order_book(symbol=symbol, limit=levels)

            # Проверяем, покрывает ли книга ордеров 3% диапазон
            bids = depth['bids']
            asks = depth['asks']

            if not bids or not asks:
                break

            lowest_bid = float(bids[-1][0])
            highest_ask = float(asks[-1][0])

            if lowest_bid <= price_threshold_low and highest_ask >= price_threshold_high:
                break  # Достигли необходимой глубины

            levels *= 2  # Увеличиваем уровни

        # Сохраняем обновленную книгу ордеров
        order_book_data[symbol] = depth

        print(f"Обновлена книга ордеров для {symbol} ({market_type}) с глубиной {levels}")

    except Exception as e:
        print(f"Ошибка при обновлении книги ордеров для {symbol} ({market_type}): {e}")

def get_symbols_to_monitor():
    symbols = []
    for symbol in selected_symbols:
        if symbol not in excluded_symbols:
            symbols.append({
                'spot': f"{symbol}USDT",
                'futures': f"{symbol}USDT"
            })
    return symbols

def monitor_and_trade():
    global monitoring_active
    while monitoring_active:
        densities = find_top_densities()
        # Обновляем density_history для отображения на веб-интерфейсе
        for d in densities:
            density_history[d['symbol']] = d
        # Трейдинговая логика
        if TRADING_ALLOWED:
            for density in densities:
                if check_entry_conditions(density):
                    if len(open_positions) < MAX_OPEN_POSITIONS:
                        open_position(density)
        time.sleep(1)

def find_top_densities():
    densities = []
    for symbol, depth in order_book_data.items():
        bids = depth['bids']
        asks = depth['asks']
        if not bids or not asks:
            continue
        current_price = current_price_cache.get(symbol)
        if not current_price:
            continue
        # Рассчитываем объемы на разных уровнях
        cumulative_volume = 0
        for bid in bids:
            price = float(bid[0])
            qty = float(bid[1])
            cumulative_volume += price * qty
            percentage_diff = ((current_price - price) / current_price) * 100
            if percentage_diff >= 3:
                break
            if cumulative_volume >= min_density_volume:
                # Рассчитываем время разъедания
                max_volume = max_5min_volumes.get(symbol, 0)
                if max_volume == 0:
                    continue
                consumption_time = cumulative_volume / max_volume * 5  # В минутах
                if consumption_time >= min_consumption_time:
                    densities.append({
                        'symbol': symbol,
                        'price': price,
                        'qty': qty,
                        'total_value': cumulative_volume,
                        'distance': percentage_diff,
                        'consumption_time': consumption_time,
                        'market_type': 'spot' if symbol in [p['spot'] for p in symbols_to_monitor] else 'futures',
                        'leverage': symbol_leverages.get(symbol, 1)
                    })
                break
    return densities

def check_entry_conditions(density):
    if density['total_value'] >= min_density_volume and density['consumption_time'] >= min_consumption_time:
        return True
    return False

def open_position(density):
    symbol = density['symbol']
    market_type = density['market_type']
    side = 'BUY' if density['distance'] > 0 else 'SELL'
    quantity = POSITION_SIZE_USDT / density['price']

    try:
        if market_type == 'spot':
            if side == 'BUY':
                order = client.order_market_buy(symbol=symbol, quantity=quantity)
            else:
                order = client.order_market_sell(symbol=symbol, quantity=quantity)
        else:
            if side == 'BUY':
                order = client.futures_create_order(
                    symbol=symbol,
                    side='BUY',
                    type='MARKET',
                    quantity=quantity
                )
            else:
                order = client.futures_create_order(
                    symbol=symbol,
                    side='SELL',
                    type='MARKET',
                    quantity=quantity
                )
        open_positions[symbol] = {
            'symbol': symbol,
            'side': side,
            'quantity': quantity,
            'entry_price': density['price'],
            'current_price': density['price'],
            'pnl_percent': 0.0,
            'market_type': market_type
        }
        print(f"Открыта позиция по {symbol} на {market_type} в сторону {side}")
    except Exception as e:
        print(f"Ошибка при открытии позиции по {symbol}: {e}")

def close_position(symbol):
    position = open_positions.get(symbol)
    if position:
        market_type = position.get('market_type', 'spot')
        side = 'SELL' if position['side'] == 'BUY' else 'BUY'
        quantity = position['quantity']

        try:
            if market_type == 'spot':
                if side == 'BUY':
                    order = client.order_market_buy(symbol=symbol, quantity=quantity)
                else:
                    order = client.order_market_sell(symbol=symbol, quantity=quantity)
            else:
                if side == 'BUY':
                    order = client.futures_create_order(
                        symbol=symbol,
                        side='BUY',
                        type='MARKET',
                        quantity=quantity
                    )
                else:
                    order = client.futures_create_order(
                        symbol=symbol,
                        side='SELL',
                        type='MARKET',
                        quantity=quantity
                    )
            del open_positions[symbol]
            print(f"Закрыта позиция по {symbol} на {market_type}")
        except Exception as e:
            print(f"Ошибка при закрытии позиции по {symbol}: {e}")

def get_common_symbols():
    spot_symbols = set()
    futures_symbols = dict()
    prefixes = ['1000', '100']  # Возможные префиксы на фьючерсах

    # Получаем все спотовые символы, заканчивающиеся на USDT
    spot_info = client.get_exchange_info()
    for s in spot_info['symbols']:
        if s['quoteAsset'] == 'USDT' and s['status'] == 'TRADING':
            spot_symbols.add(s['symbol'])

    # Получаем все фьючерсные символы, заканчивающиеся на USDT
    futures_info = client.futures_exchange_info()
    for s in futures_info['symbols']:
        if s['quoteAsset'] == 'USDT' and s['status'] == 'TRADING':
            futures_symbol = s['symbol']
            base_asset = s['baseAsset']
            # Удаляем возможные префиксы
            for prefix in prefixes:
                if base_asset.startswith(prefix):
                    base_asset = base_asset[len(prefix):]
                    break
            futures_symbols[base_asset] = futures_symbol

    # Сопоставляем символы
    common_symbols = []
    for spot_symbol in spot_symbols:
        if spot_symbol.endswith('USDT'):
            base_asset = spot_symbol.replace('USDT', '')
            # Проверяем наличие в фьючерсах
            if base_asset in futures_symbols:
                futures_symbol = futures_symbols[base_asset]
                common_symbols.append({
                    'spot': spot_symbol,
                    'futures': futures_symbol
                })

    return common_symbols

# Маршруты Flask

@app.route('/')
def index():
    return render_template('index.html',
                           densities=list(density_history.values()),
                           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,
                           excluded_symbols=", ".join(excluded_symbols))

@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, five_min_volumes, max_5min_volumes

    symbols_input = request.form.get('symbols', '')
    selected_symbols = [s.strip().upper() for s in symbols_input.split(',') if s.strip()]

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

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

    # Очищаем кеши
    density_history.clear()
    current_price_cache.clear()
    order_book_data.clear()
    five_min_volumes.clear()
    max_5min_volumes.clear()

    # Запускаем задачи
    run_tasks()

    return redirect(url_for('index'))

@app.route('/set_excluded_symbols', methods=['POST'])
def set_excluded_symbols():
    global excluded_symbols, symbols_to_monitor, monitoring_active, density_history, current_price_cache, five_min_volumes, max_5min_volumes

    excluded_input = request.form.get('excluded_symbols', '')
    excluded_symbols = [s.strip().upper() for s in excluded_input.split(',') if s.strip()]
    print(f"Символы, исключенные из мониторинга: {excluded_symbols}")

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

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

    # Очищаем кеши
    density_history.clear()
    current_price_cache.clear()
    order_book_data.clear()
    five_min_volumes.clear()
    max_5min_volumes.clear()

    # Запускаем задачи
    run_tasks()

    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, five_min_volumes, max_5min_volumes

    print("Функция monitor_all_usdt вызвана")

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

    # Получаем все общие символы USDT
    common_symbols = get_common_symbols()
    selected_symbols = [pair['spot'].replace('USDT', '') for pair in common_symbols]

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

    # Очищаем кеши
    density_history.clear()
    current_price_cache.clear()
    order_book_data.clear()
    five_min_volumes.clear()
    max_5min_volumes.clear()

    # Запускаем задачи
    run_tasks()

    return redirect(url_for('index'))

@app.route('/stop_monitoring')
def stop_monitoring():
    stop_tasks()
    return redirect(url_for('index'))

@app.route('/get_densities')
def get_densities_route():
    densities = list(density_history.values())
    # Преобразуем числовые данные в 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['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 = density_history.get(symbol)
    if density and check_entry_conditions(density):
        open_position(density)
    return redirect(url_for('index'))

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

# Запуск приложения
if __name__ == '__main__':
    # Запуск Flask-приложения
    app.run(host='0.0.0.0', port=4000, debug=True)

Код файла 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;
        }
        /* Новый стиль для размещения таблиц бок о бок */
        .table-container {
            display: flex;
            justify-content: space-between;
            width: 95%;
            margin: 0 auto;
        }
        .table-container table {
            width: 48%;
        }
    </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 action="/set_excluded_symbols" method="post">
            Символы для исключения (через запятую):
            <input class="form-input" type="text" name="excluded_symbols" placeholder="USDC, BUSD" value="{{ excluded_symbols }}">
            <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>
        <!-- Обновленная кнопка "Monitor ALL USDT" -->
        <a class="button yellow" href="/monitor_all_usdt">Monitor ALL USDT</a>
        <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>
    <div class="table-container">
        <table id="densities-table-left">
            <thead>
                <tr>
                    <th>Символ</th>
                    <th>Цена</th>
                    <th>Количество</th>
                    <th>Общая сумма</th>
                    <th>Расстояние до цены (%)</th>
                    <th>Время разъедания (мин)</th>
                </tr>
            </thead>
            <tbody>
                <!-- Данные будут загружаться динамически через JavaScript -->
            </tbody>
        </table>

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

    <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 tableLeftBody = document.querySelector('#densities-table-left tbody');
                    const tableRightBody = document.querySelector('#densities-table-right tbody');
                    tableLeftBody.innerHTML = '';
                    tableRightBody.innerHTML = '';

                    // Разбиваем данные на две части
                    const middleIndex = Math.ceil(data.length / 2);
                    const leftData = data.slice(0, middleIndex);
                    const rightData = data.slice(middleIndex);

                    // Функция для создания строк таблицы
                    function createRows(densities, tbody) {
                        densities.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)}%)`;

                            const consumptionTimeFormatted = Math.round(density.consumption_time);

                            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>${Math.round(density.qty)}</td>
                                <td class="${totalValueCellClass}">${totalValueFormatted}</td>
                                <td class="${distanceCellClass}">${density.distance.toFixed(2)}</td>
                                <td>${consumptionTimeFormatted}</td>
                            `;
                            tbody.appendChild(row);
                        });
                    }

                    // Заполняем левую и правую таблицы
                    createRows(leftData, tableLeftBody);
                    createRows(rightData, tableRightBody);
                })
                .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();

        // Обновляем данные каждые 1 секунду
        setInterval(fetchDensities, 1000);
        setInterval(fetchPositions, 1000);
    </script>
</body>
</html>