Привет в прошлом диалоге мы с тобой писали программу на Python с веб-интерфейсом управления для мониторинга криптовалют и их плотностей на Binance и торговли. Сейчас я предоставлю тебе код файлов py и html которые мы сделали и напишу на чем остановились.
ФАЙЛ Python:
import os
import sys
import asyncio
import threading
import time
import traceback
import math
import json
import logging
from datetime import datetime, timezone, timedelta
from binance import AsyncClient, BinanceSocketManager, Client
from binance.exceptions import BinanceAPIException
from flask import Flask, render_template, request, redirect, url_for, jsonify
import schedule

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

# Параметры бота (без изменений)
MAX_OPEN_POSITIONS = 5
POSITION_SIZE_USDT = 10
TRADING_ALLOWED = False
monitoring_active = False
min_density_volume = 1000
min_consumption_time = 0.1

# Инициализация глобальных переменных (без изменений)
selected_symbols = []
excluded_symbols = []
symbols_to_monitor = []
order_book_data = {}
open_positions = {}
density_history = {}
current_price_cache = {}
volume_cache = {}
symbol_leverages = {}
limit_per_symbol = {}
asyncio_loop = None
asyncio_tasks = []

# Функция для загрузки API-ключей из файла (без изменений)
def load_api_keys():
    # ... (ваш код)
    pass

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

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

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

# Остальной код функций (без изменений)
# ...

# Маршруты 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,
                           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, limit_per_symbol

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

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

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

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

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

    # Инициализируем лимиты
    initialize_limits()

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

    # Даем время на получение начальных данных
    time.sleep(10)  # Ждем 10 секунд (можно настроить)

    # Рассчитываем необходимые лимиты на основе полученных данных
    for pair in symbols_to_monitor:
        for market_type in ['spot', 'futures']:
            if market_type == 'spot':
                symbol = pair['spot']
            else:
                symbol = pair['futures']
            key = f"{market_type}_{symbol}"
            limit = calculate_required_limit(symbol, market_type)
            limit_per_symbol[key] = limit
            print(f"Установлен limit {limit} для {symbol} ({market_type})")

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

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

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

    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_asyncio_tasks()
    monitoring_active = False

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

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

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

    # Инициализируем лимиты
    initialize_limits()

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

    # Даем время на получение начальных данных
    time.sleep(10)  # Ждем 10 секунд (можно настроить)

    # Рассчитываем необходимые лимиты на основе полученных данных
    for pair in symbols_to_monitor:
        for market_type in ['spot', 'futures']:
            if market_type == 'spot':
                symbol = pair['spot']
            else:
                symbol = pair['futures']
            key = f"{market_type}_{symbol}"
            limit = calculate_required_limit(symbol, market_type)
            limit_per_symbol[key] = limit
            print(f"Установлен limit {limit} для {symbol} ({market_type})")

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

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

    return redirect(url_for('index'))

@app.route('/monitor_all_usdt')
def monitor_all_usdt():
    global selected_symbols, symbols_to_monitor, monitoring_active, density_history, current_price_cache, limit_per_symbol

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

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

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

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

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

    # Инициализируем лимиты
    initialize_limits()

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

    # Даем время на получение начальных данных
    time.sleep(10)  # Ждем 10 секунд (можно настроить)

    # Рассчитываем необходимые лимиты на основе полученных данных
    for pair in symbols_to_monitor:
        for market_type in ['spot', 'futures']:
            if market_type == 'spot':
                symbol = pair['spot']
            else:
                symbol = pair['futures']
            key = f"{market_type}_{symbol}"
            limit = calculate_required_limit(symbol, market_type)
            limit_per_symbol[key] = limit
            print(f"Установлен limit {limit} для {symbol} ({market_type})")

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

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

    return redirect(url_for('index'))

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

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

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

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

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

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

ФАЙЛ 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" -->
        <form action="/monitor_all_usdt" method="post" style="display: inline-block;">
            <button class="button yellow" type="submit">Monitor ALL USDT</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>
    <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>