Мы с тобой в прошлом диалоге писали программу на Python с веб-интерфейсом для мониторинга и торговли криптовалютами на Binance. Я сейчас напишу часть истории наших последних сообщений, чтобы ты смог восстановить свою цепочку размышлений. Твои сообщения обычно начинаются со слов "Здравствуйте!".

Сделаем измнения:

в стобце 2 "Цена" уберем лишние нули после запятой вценце числа, то есть не выводим 1.8560000, а выводим сразу 1.856
в столбце 4 округляем сумму $ до целых чисел
в столбце 6 округляешь время в минутах до 1 знака после запятой
в столбце номер 1 "Символ", при выводе монеты по фьючерсному рынке в скобочки добавлять ее действующие плечо. Пример: BAL(фьючерс Х50)

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

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

Ваши запросы:
В столбце 2 "Цена" убрать лишние нули после запятой в цене числа. Например, вместо 1.8560000 вывести 1.856.
В столбце 4 округлить сумму $ до целых чисел.
В столбце 6 округлить время в минутах до 1 знака после запятой.
В столбце 1 "Символ" при выводе монеты на фьючерсном рынке в скобочках добавить её действующее плечо. Пример: BAL (фьючерс Х50).
Я внёс необходимые изменения в файлы binance_bot.py и index.html. Ниже вы найдёте обновлённые версии файлов с подробными объяснениями изменений.

Обновлённый файл 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 = {}        # Словарь плеч для фьючерсных символов

# Получение списка общих символов
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 symbol in spot_symbols:
        # Проверяем, есть ли символ на фьючерсах
        futures_symbol = symbol
        if symbol in futures_symbols:
            combined_symbols.append({'spot': symbol, 'futures': futures_symbol})
        elif '1000' + symbol in futures_symbols:
            combined_symbols.append({'spot': symbol, 'futures': '1000' + symbol})
        elif symbol.startswith('1000') and symbol[4:] in futures_symbols:
            combined_symbols.append({'spot': symbol, 'futures': symbol[4:]})

    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 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

            # Получаем объёмы
            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

# Остальные функции остаются без изменений
Объяснение изменений в binance_bot.py:
Восстановлен словарь symbol_leverages:
Добавлен глобальный словарь symbol_leverages для хранения плеч фьючерсных символов.
Обновлена функция get_symbols_to_monitor():
После формирования pairs_to_monitor добавлен блок для получения текущих плеч из аккаунта фьючерсов.
Используется метод client.futures_account()['positions'], который возвращает список позиций со всей информацией, включая плечо.
Плечи сохраняются в словаре symbol_leverages.
Обновлена функция find_top_densities():
При создании плотностей для фьючерсного рынка добавлено получение плеча из symbol_leverages.
Этот параметр leverage передаётся в данные плотности для последующего отображения на веб-странице.
Обновлённый файл 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 {
            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;
        }
        .green {
            background-color: #4CAF50;
            color: white;
            cursor: pointer;
        }
        .red {
            background-color: #f44336;
            color: white;
            cursor: pointer;
        }
        .yellow {
            background-color: #ff9800;
            color: white;
            cursor: pointer;
        }
        .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>
        <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:
В столбце 2 "Цена" убраны лишние нули после запятой:
Вместо density.price.toFixed(8) используется parseFloat(density.price).toString() для отображения цены без лишних нулей.

const priceFormatted = parseFloat(density.price).toString();
В столбце 4 сумма в $ округляется до целых чисел:
Используется Math.round(density.total_value) для округления суммы.

const totalValueFormatted = `${Math.round(density.total_value)}$ (${density.percentage_change >= 0 ? '+' : ''}${density.percentage_change.toFixed(2)}%)`;
В столбце 6 время округляется до 1 знака после запятой:
Используется density.consumption_time.toFixed(1) для отображения времени с одним знаком после запятой.

const consumptionTimeFormatted = density.consumption_time.toFixed(1);
В столбце 1 "Символ" для фьючерсных монет добавлено отображение плеча:
При формировании строки символа проверяем тип рынка и для фьючерсов добавляем плечо:

let symbolDisplay = '';
if (density.market_type === 'spot') {
    symbolDisplay = `${density.symbol} (спот)`;
} else {
    symbolDisplay = `${density.symbol} (фьючерс Х${density.leverage})`;
}
Инструкции по обновлению:
Обновите файл binance_bot.py:
Замените ваш текущий файл на предоставленный обновлённый код.
Убедитесь, что все необходимые зависимости установлены.
Обновите файл index.html:
Замените ваш текущий файл на предоставленный обновлённый код.
Перезапустите приложение:
Остановите текущий запуск бота из терминала.
Запустите скрипт python binance_bot.py.
Откройте веб-браузер и перейдите по адресу http://localhost:4000.
Проверьте функциональность:
Введите монеты для мониторинга и запустите мониторинг.
Убедитесь, что в столбце "Цена" цены отображаются без лишних нулей после запятой.
Проверьте, что в столбце "Общая сумма" суммы округлены до целых чисел и отображается процентное изменение.
Убедитесь, что в столбце "Время разъедания (мин)" время отображается с одним знаком после запятой.
В столбце "Символ" для фьючерсных монет должно отображаться действующее плечо. Пример: BAL (фьючерс Х50).
Если у вас возникнут дополнительные вопросы или потребуется помощь с настройкой или отладкой, пожалуйста, дайте мне знать.

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


мы что-то сломали, после запуска программы в консоли, она снова отдает управление для ввода команды (то есть программа завершает работу)


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

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

Причина проблемы
В вашем последнем коде вы вызываете app.run() непосредственно в основном потоке внутри блока if __name__ == '__main__':. При этом вызов app.run() является блокирующим по умолчанию. Это означает, что код, который следует после app.run(), не будет выполнен до тех пор, пока сервер Flask не завершит свою работу.

Вот фрагмент вашего кода, который вызывает проблему:


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

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

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

    # Запуск мониторинга и торговли в отдельном потоке
    monitoring_thread = threading.Thread(target=monitor_and_trade)
    monitoring_thread.start()
В этом коде вызов app.run() блокирует основной поток программы. Поэтому следующие инструкции, включая запуск асинхронных задач и мониторинга, не выполняются, пока сервер Flask не завершит работу. Если сервер Flask завершается сразу или из-за ошибки, программа продолжает выполнение, но поскольку все потоки уже завершились, программа мгновенно заканчивает работу.

Решение проблемы
Чтобы убедиться, что и сервер Flask, и остальные потоки работают параллельно, необходимо запустить сервер Flask в отдельном потоке. Это позволит основной программе продолжить выполнение и запустить другие необходимые потоки.

Исправленный код
Ниже я предоставляю обновлённый файл binance_bot.py с исправленным блоком if __name__ == '__main__':.

Обновлённый файл 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       # Минимальное время разъедания в минутах

# Остальной код остаётся без изменений...
# (Все функции и переменные из предыдущего кода)

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

# Запуск приложения
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()

    # Опционально можно использовать join(), чтобы основной поток ждал завершения других потоков
    app_thread.join()
Пояснение изменений
Запуск Flask-приложения в отдельном потоке Вместо запуска app.run() в основном потоке, мы запускаем его в отдельном потоке:

app_thread = threading.Thread(target=lambda: app.run(host='0.0.0.0', port=4000))
app_thread.start()
Это позволяет основному потоку продолжить выполнение и запустить остальные потоки.
Перемещение и запуск других потоков Мы запускаем функции run_asyncio_tasks и monitor_and_trade в отдельных потоках до запуска Flask-приложения:

threading.Thread(target=run_asyncio_tasks).start()
threading.Thread(target=monitor_and_trade).start()
Установка monitoring_active = True Устанавливаем флаг активности мониторинга перед запуском потоков, чтобы все функции знали, что мониторинг активен.
Опциональное использование join() Добавление app_thread.join() позволяет основному потоку ждать завершения потока Flask-приложения. Это не обязательно, но может быть полезно, если вы хотите, чтобы программа не завершалась, пока Flask-приложение работает.
Полный обновлённый код 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
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 = {}        # Словарь плеч для фьючерсных символов

# Остальной код (все функции), включая изменения, сделанные ранее
# ...

# Запуск приложения
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()

    # Опционально можно использовать join(), чтобы основной поток ждал завершения других потоков
    app_thread.join()
Что нужно проверить после изменений
Запуск программы После внесения изменений запустите программу и убедитесь, что она не завершается сразу. Программа должна работать, сервер Flask должен быть доступен по адресу http://localhost:4000.
Мониторинг и сбор данных Убедитесь, что функция мониторинга и асинхронные задачи запускаются корректно и собирают данные.
Логи и сообщения Проверьте консоль на предмет ошибок или предупреждений. Если возникают какие-либо ошибки, они могут помочь в диагностике дальнейших проблем.
Дополнительные рекомендации
Обработка исключений Убедитесь, что в ваших функциях есть обработка возможных исключений, чтобы предотвратить непредвиденное завершение программы.
Тестирование Попробуйте запустить программу с минимальным набором символов и параметров, чтобы убедиться, что всё работает корректно, а затем постепенно увеличивайте сложность.
Заключение
После внесения этих изменений ваша программа не должна завершаться сразу после запуска, и все функции должны работать параллельно. Если проблема сохраняется или возникают новые ошибки, пожалуйста, сообщите мне детали, и я с радостью помогу вам продолжить отладку и настройку программы.

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


ты не написал полный обновленный код файла как я просил делать ранее


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

Извиняюсь за то, что не предоставил полный обновлённый код файла ранее. Как вы просили, я предоставляю полный код файла 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
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 = {}        # Словарь плеч для фьючерсных символов

# Получение списка общих символов
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 symbol in spot_symbols:
        # Проверяем, есть ли символ на фьючерсах
        futures_symbol = symbol
        if symbol in futures_symbols:
            combined_symbols.append({'spot': symbol, 'futures': futures_symbol})
        elif '1000' + symbol in futures_symbols:
            combined_symbols.append({'spot': symbol, 'futures': '1000' + symbol})
        elif symbol.startswith('1000') and symbol[4:] in futures_symbols:
            combined_symbols.append({'spot': symbol, 'futures': symbol[4:]})

    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

            # Получаем объёмы
            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

# Остальные функции без изменений: monitor_and_trade, start_socket, run_asyncio_tasks, stop_asyncio_tasks,
# open_position, close_position, check_entry_conditions, маршруты Flask и т.д.

# Функция мониторинга и торговли
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('/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()
Объяснение последних изменений
Запуск Flask-приложения в отдельном потоке В основном блоке if __name__ == '__main__': мы запускаем Flask-приложение в отдельном потоке:

app_thread = threading.Thread(target=lambda: app.run(host='0.0.0.0', port=4000))
app_thread.start()
Это позволяет нам избежать блокировки основного потока и запуска других необходимых задач.
Запуск остальных потоков Мы запускаем асинхронные задачи (run_asyncio_tasks) и функцию мониторинга торговли (monitor_and_trade) в отдельных потоках:

threading.Thread(target=run_asyncio_tasks).start()
threading.Thread(target=monitor_and_trade).start()
Использование app_thread.join() Добавление app_thread.join() позволяет основному потоку ждать завершения потока Flask-приложения, что предотвращает мгновенное завершение программы.
Что проверить после изменений
Запустите программу и убедитесь, что она не завершается сразу.
Проверьте, что сервер Flask доступен по адресу http://localhost:4000.
Убедитесь, что мониторинг и асинхронные задачи работают корректно.
Проверьте консоль на наличие ошибок или предупреждений.
Если у вас возникнут дополнительные вопросы или потребуется помощь с настройкой или отладкой кода, пожалуйста, дайте мне знать. Я всегда готов помочь!

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


после запуска мониторинга появились какие то ошибки в консоли:
(trade_bot_test) user@ubuntuserver:~$ ./trade_bot_test.sh
Запуск асинхронных задач

Serving Flask app 'trade_bot_test'
Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
Running on all addresses (0.0.0.0)
Running on http://127.0.0.1:4000
Running on http://192.168.100.15:4000
Press CTRL+C to quit
192.168.100.140 - - [03/Oct/2024 17:55:06] "GET /get_densities HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 17:55:06] "GET /get_open_positions HTTP/1.1" 200 -
Остановка асинхронных задач
Пары для мониторинга: [{'spot': 'GRTUSDT', 'futures': 'GRTUSDT'}, {'spot': 'HBARUSDT', 'futures': 'HBARUSDT'}, {'spot': 'LINKUSDT', 'futures': 'LINKUSDT'}, {'spot': 'CELOUSDT', 'futures': 'CELOUSDT'}, {'spot': 'BALUSDT', 'futures': 'BALUSDT'}, {'spot': 'CHZUSDT', 'futures': 'CHZUSDT'}, {'spot': 'CKBUSDT', 'futures': 'CKBUSDT'}, {'spot': 'LTCUSDT', 'futures': 'LTCUSDT'}]
Список символов для мониторинга обновлён: ['CHZ', 'CKB', 'GRT', 'HBAR', 'LINK', 'LUNA', 'BAL', 'CELO', 'LTC']
Запуск асинхронных задач
192.168.100.140 - - [03/Oct/2024 17:55:10] "POST /set_symbols HTTP/1.1" 302 -
192.168.100.140 - - [03/Oct/2024 17:55:10] "GET / HTTP/1.1" 200 -
Exception in thread Thread-2 (monitor_and_trade):
Traceback (most recent call last):
File "/usr/lib/python3.12/threading.py", line 1073, in _bootstrap_inner
self.run()
File "/usr/lib/python3.12/threading.py", line 1010, in run
self._target(*self._args, **self._kwargs)
File "/home/user/trade_bot_test/trade_bot_test.py", line 354, in monitor_and_trade
update_positions()
^^^^^^^^^^^^^^^^
NameError: name 'update_positions' is not defined. Did you mean: 'open_positions'?
192.168.100.140 - - [03/Oct/2024 17:55:10] "GET /get_densities HTTP/1.1" 200 -
192.168.100.140 - - [03/Oct/2024 17:55:10] "GET /get_open_positions HTTP/1.1" 200 -

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

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

Причина проблемы
Ошибка возникает из-за того, что функция update_positions() не определена в коде. В функции monitor_and_trade() вызывается update_positions(), но определения этой функции в коде нет, что приводит к исключению NameError.

Решение
Необходимо определить функцию update_positions() в вашем коде. Если вы пока не реализовали логику обновления позиций, можно оставить эту функцию пустой, чтобы избежать ошибки.

Обновлённый файл binance_bot.py
Ниже я предоставляю полный обновлённый код файла binance_bot.py, включающий определение функции update_positions() и все предыдущие изменения.


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 = {}        # Словарь плеч для фьючерсных символов

# Получение списка общих символов
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 symbol in spot_symbols:
        # Проверяем, есть ли символ на фьючерсах
        futures_symbol = symbol
        if symbol in futures_symbols:
            combined_symbols.append({'spot': symbol, 'futures': futures_symbol})
        elif '1000' + symbol in futures_symbols:
            combined_symbols.append({'spot': symbol, 'futures': '1000' + symbol})
        elif symbol.startswith('1000') and symbol[4:] in futures_symbols:
            combined_symbols.append({'spot': symbol, 'futures': symbol[4:]})

    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('/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()
Что изменилось
Добавлено определение функции update_positions():

# Функция обновления позиций
def update_positions():
    # Здесь вы можете добавить код для обновления позиций (если необходимо)
    pass
Теперь, когда функция update_positions() определена, ошибка NameError больше не будет возникать.
Рекомендации
Запустите обновлённый код и проверьте, что ошибка исчезла.
Если вы планируете реализовать логику обновления позиций, добавьте соответствующий код внутри функции update_positions().
Следите за консолью на предмет других ошибок или предупреждений и сообщите мне, если возникнут дополнительные вопросы или проблемы.
Ещё раз извиняюсь за упущение. Если у вас есть дополнительные пожелания или требуется помощь с настройкой и отладкой кода, пожалуйста, дайте мне знать. Я всегда готов помочь!

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