C:\Users\Сергей\AppData\Local\Programs\Python\Python312\python.exe C:\Users\Сергей\Desktop\стратегия_new\main.py 
  File "C:\Users\Сергей\Desktop\стратегия_new\main.py", line 385
    def plot_candlestick(df, symbol, entry=None, take_profit=None):
SyntaxError: expected 'except' or 'finally' block

Process finished with exit code 1 исправь ошибку import logging
import aiohttp
import pandas as pd
import numpy as np
import mplfinance as mpf
from datetime import datetime, timedelta, timezone
import telegram
from telegram import InputMediaPhoto
import asyncio
import config
from io import BytesIO
from ta.trend import (
    MACD,
    SMAIndicator,
    EMAIndicator,
    IchimokuIndicator,
    ADXIndicator,
    CCIIndicator
)
from ta.momentum import RSIIndicator, StochasticOscillator
from ta.volatility import BollingerBands, AverageTrueRange
from ta.volume import OnBalanceVolumeIndicator, MoneyFlowIndex
import os
import glob
from PIL import Image, __version__ as PILLOW_VERSION
from packaging import version
from telegram.error import TelegramError, RetryAfter, TimedOut
from concurrent.futures import ThreadPoolExecutor
from matplotlib import pyplot as plt

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("bot.log"),
        logging.StreamHandler()
    ]
)

# Проверка версии Pillow и логирование
try:
    pillow_version = version.parse(PILLOW_VERSION)
    if pillow_version < version.parse("10.0.0"):
        logging.warning(f"Используется устаревшая версия Pillow ({PILLOW_VERSION}). Рекомендуется обновиться до версии 10.0.0 или выше.")
    else:
        logging.info(f"Используется Pillow версии {PILLOW_VERSION}.")
except Exception as e:
    logging.error(f"Не удалось определить версию Pillow: {e}")

MAX_TG_MESSAGE_LENGTH = 4000
MEDIA_GROUP_LIMIT_PER_SECOND = 20

# Директория для сохранения графиков
GRAPHS_DIR = 'graphs'

# Создание директории для графиков, если она не существует
if not os.path.exists(GRAPHS_DIR):
    os.makedirs(GRAPHS_DIR)

def clean_previous_graphs():
    try:
        files = glob.glob(os.path.join(GRAPHS_DIR, '*_72h.png'))
        for file in files:
            os.remove(file)
        logging.info(f"Удалено {len(files)} предыдущих графиков.")
    except Exception as e:
        logging.error(f"Ошибка при удалении предыдущих графиков: {e}")

def is_volume_sufficient(df, threshold=1000):
    average_volume = df['Volume'].mean()
    return average_volume >= threshold

def has_sufficient_price_variation(df, min_variation=0.5):
    price_diff = df['High'].max() - df['Low'].min()
    if df['Low'].min() == 0:
        return False
    percentage_variation = (price_diff / df['Low'].min()) * 100
    return percentage_variation >= min_variation

def is_image_blank(image_path, variance_threshold=100.0):
    try:
        with Image.open(image_path) as img:
            grayscale = img.convert('L')
            np_img = np.array(grayscale)
            variance = np.var(np_img)
            logging.debug(f"Variance for {image_path}: {variance}")
            return variance < variance_threshold
    except Exception as e:
        logging.error(f"Ошибка при проверке изображения {image_path}: {e}")
        return False

def compress_image(image_path, max_size=(800, 600), quality=70):
    try:
        with Image.open(image_path) as img:
            if version.parse(PILLOW_VERSION) >= version.parse("10.0.0"):
                resample_method = Image.Resampling.LANCZOS
            else:
                resample_method = Image.ANTIALIAS

            img.thumbnail(max_size, resample_method)
            img.save(image_path, optimize=True, quality=quality)
        logging.info(f"Изображение сжато и сохранено: {image_path}")

        if is_image_blank(image_path):
            logging.warning(f"Изображение {image_path} оказалось пустым после сжатия.")
            os.remove(image_path)
            return False

        return True
    except Exception as e:
        logging.error(f"Ошибка при сжатии изображения {image_path}: {e}")
        return False

async def get_future_symbols(session):
    try:
        logging.info("Запрос списка фьючерсных пар USDT от Binance.")
        url = 'https://fapi.binance.com/fapi/v1/exchangeInfo'
        async with session.get(url) as response:
            response.raise_for_status()
            data = await response.json()
            symbols = [s['symbol'] for s in data['symbols']
                       if s['quoteAsset'] == 'USDT' and s['contractType'] == 'PERPETUAL']
            logging.info(f"Найдено {len(symbols)} фьючерсных пар USDT.")
            return symbols
    except aiohttp.ClientError as e:
        logging.error(f"Ошибка при запросе списка символов: {e}")
        raise
    except Exception as e:
        logging.error(f"Неизвестная ошибка при обработке списка символов: {e}")
        raise

async def get_historical_klines(session, symbol, interval, hours):
    try:
        logging.info(f"Запрос данных для {symbol} с интервалом {interval} за последние {hours} часов.")
        end_time = datetime.now(timezone.utc)
        start_time = end_time - timedelta(hours=hours)
        start_timestamp = int(start_time.timestamp() * 1000)
        end_timestamp = int(end_time.timestamp() * 1000)
        url = 'https://fapi.binance.com/fapi/v1/klines'
        params = {
            'symbol': symbol,
            'interval': interval,
            'startTime': start_timestamp,
            'endTime': end_timestamp,
            'limit': 1000
        }
        async with session.get(url, params=params) as response:
            response.raise_for_status()
            data = await response.json()
            if not data:
                logging.warning(f"Нет данных для символа {symbol}.")
                return None
            df = pd.DataFrame(data, columns=[
                'Open time', 'Open', 'High', 'Low', 'Close', 'Volume',
                'Close time', 'Quote asset volume', 'Number of trades',
                'Taker buy base asset volume', 'Taker buy quote asset volume', 'Ignore'
            ])
            df['Open time'] = pd.to_datetime(df['Open time'], unit='ms')
            df['Close time'] = pd.to_datetime(df['Close time'], unit='ms')
            df[['Open', 'High', 'Low', 'Close', 'Volume']] = df[['Open', 'High', 'Low', 'Close', 'Volume']].astype(float)
            df.sort_values('Open time', inplace=True)
            logging.info(f"Данные успешно получены для {symbol} (строк: {len(df)}).")
            return df[['Open time', 'Open', 'High', 'Low', 'Close', 'Volume']]
    except aiohttp.ClientError as e:
        logging.error(f"Ошибка при запросе данных для {symbol}: {e}")
        return None
    except Exception as e:
        logging.error(f"Неизвестная ошибка при обработке данных для {symbol}: {e}")
        return None

def compute_indicators(df):
    try:
        logging.info("Вычисление технических индикаторов.")

        # RSI
        rsi = RSIIndicator(close=df['Close'], window=14)
        df['RSI'] = rsi.rsi()

        # MACD
        macd = MACD(close=df['Close'])
        df['MACD'] = macd.macd()
        df['MACD_Signal'] = macd.macd_signal()
        df['MACD_Diff'] = macd.macd_diff()

        # Скользящие средние (SMA50 и EMA20)
        sma50 = SMAIndicator(close=df['Close'], window=50)
        df['SMA50'] = sma50.sma_indicator()

        ema20 = EMAIndicator(close=df['Close'], window=20)
        df['EMA20'] = ema20.ema_indicator()

        # Полосы Боллинджера
        bb = BollingerBands(close=df['Close'], window=20, window_dev=2)
        df['BB_High'] = bb.bollinger_hband()
        df['BB_Low'] = bb.bollinger_lband()
        df['BB_Width'] = bb.bollinger_wband()

        # Стохастический осциллятор
        stochastic = StochasticOscillator(high=df['High'], low=df['Low'], close=df['Close'], window=14, smooth_window=3)
        df['Stoch_%K'] = stochastic.stoch()
        df['Stoch_%D'] = stochastic.stoch_signal()

        # Ichimoku Cloud
        ichimoku = IchimokuIndicator(high=df['High'], low=df['Low'], window1=9, window2=26, window3=52)
        df['Ichimoku_A'] = ichimoku.ichimoku_a()
        df['Ichimoku_B'] = ichimoku.ichimoku_b()

        # ATR
        atr = AverageTrueRange(high=df['High'], low=df['Low'], close=df['Close'], window=14)
        df['ATR'] = atr.average_true_range()

        # VWAP
        df['VWAP'] = (df['Volume'] * (df['High'] + df['Low'] + df['Close']) / 3).cumsum() / df['Volume'].cumsum()

        # ADX
        adx = ADXIndicator(high=df['High'], low=df['Low'], close=df['Close'], window=14)
        df['ADX'] = adx.adx()
        df['ADX_Pos'] = adx.adx_pos()
        df['ADX_Neg'] = adx.adx_neg()

        # CCI (Commodity Channel Index)
        cci = CCIIndicator(high=df['High'], low=df['Low'], close=df['Close'], window=20)
        df['CCI'] = cci.cci()

        # MFI (Money Flow Index)
        mfi = MoneyFlowIndex(high=df['High'], low=df['Low'], close=df['Close'], volume=df['Volume'], window=14)
        df['MFI'] = mfi.money_flow_index()

        # On-Balance Volume (OBV)
        obv = OnBalanceVolumeIndicator(close=df['Close'], volume=df['Volume'])
        df['OBV'] = obv.on_balance_volume()

        logging.info("Технические индикаторы успешно вычислены.")
        return df
    except Exception as e:
        logging.error(f"Ошибка при вычислении индикаторов: {e}")
        raise

def analyze_indicators(df):
    try:
        logging.info("Анализ индикаторов для определения фазы рынка и расчета тренда.")
        latest = df.iloc[-1]

        signals = {
            'RSI': None,
            'MACD': None,
            'EMA20': None,
            'Bollinger Bands': None,
            'Stochastic Oscillator': None,
            'Ichimoku': None,
            'VWAP': None,
            'ADX': None,
            'CCI': None,
            'MFI': None,
            'OBV': None
        }

        # RSI
        if latest['RSI'] > 70:
            signals['RSI'] = 'Overbought'
        elif latest['RSI'] < 30:
            signals['RSI'] = 'Oversold'

        # MACD
        if latest['MACD'] > latest['MACD_Signal']:
            signals['MACD'] = 'Bullish Crossover'
        elif latest['MACD'] < latest['MACD_Signal']:
            signals['MACD'] = 'Bearish Crossover'

        # EMA20 vs. Close Price
        if latest['Close'] > latest['EMA20']:
            signals['EMA20'] = 'Price Above EMA20'
        else:
            signals['EMA20'] = 'Price Below EMA20'

        # Bollinger Bands
        if latest['Close'] > latest['BB_High']:
            signals['Bollinger Bands'] = 'Price Breaks Upper Band'
        elif latest['Close'] < latest['BB_Low']:
            signals['Bollinger Bands'] = 'Price Breaks Lower Band'

        # Stochastic Oscillator
        if latest['Stoch_%K'] > latest['Stoch_%D']:
            signals['Stochastic Oscillator'] = 'Bullish Signal'
        elif latest['Stoch_%K'] < latest['Stoch_%D']:
            signals['Stochastic Oscillator'] = 'Bearish Signal'

        # Ichimoku Cloud
        if latest['Close'] > latest['Ichimoku_A'] and latest['Close'] > latest['Ichimoku_B']:
            signals['Ichimoku'] = 'Above Cloud - Bullish'
        elif latest['Close'] < latest['Ichimoku_A'] and latest['Close'] < latest['Ichimoku_B']:
            signals['Ichimoku'] = 'Below Cloud - Bearish'
        else:
            signals['Ichimoku'] = 'Within Cloud - Neutral'

        # VWAP
        if latest['Close'] > latest['VWAP']:
            signals['VWAP'] = 'Price Above VWAP'
        else:
            signals['VWAP'] = 'Price Below VWAP'

        # ADX для определения фазы рынка
        if latest['ADX'] > 25:
            signals['ADX'] = 'Strong Trend'
        elif latest['ADX'] < 20:
            signals['ADX'] = 'Balance Phase'
        else:
            signals['ADX'] = 'Low Trend Strength'

        # CCI
        if latest['CCI'] > 100:
            signals['CCI'] = 'CCI Overbought'
        elif latest['CCI'] < -100:
            signals['CCI'] = 'CCI Oversold'

        # On-Balance Volume (OBV)
        if latest['OBV'] > df['OBV'].iloc[-2]:
            signals['OBV'] = 'OBV Increasing'
        else:
            signals['OBV'] = 'OBV Decreasing'

        # Определение фазы рынка
        if signals['ADX'] == 'Balance Phase':
            market_phase = 'Balance'
        else:
            market_phase = 'Impulse'

        # Подсчет сигналов
        bullish_signals = [
            'Bullish Crossover', 'Price Above EMA20',
            'Price Breaks Upper Band', 'Bullish Signal',
            'Above Cloud - Bullish', 'Price Above VWAP',
            'CCI Overbought', 'MFI Overbought',
            'OBV Increasing'
        ]
        bearish_signals = [
            'Bearish Crossover', 'Price Below EMA20',
            'Price Breaks Lower Band', 'Bearish Signal',
            'Below Cloud - Bearish', 'Price Below VWAP',
            'CCI Oversold', 'MFI Oversold',
            'OBV Decreasing'
        ]

        bullish = sum(1 for signal in signals.values() if signal in bullish_signals)
        bearish = sum(1 for signal in signals.values() if signal in bearish_signals)

        total = bullish + bearish

        if total == 0:
            direction = "Нейтрально"
            probability = "N/A"
        else:
            if bullish > bearish:
                direction = "Бычий тренд"
                probability = f"{(bullish / total) * 100:.2f}% вероятность"
            elif bearish > bullish:
                direction = "Медвежий тренд"
                probability = f"{(bearish / total) * 100:.2f}% вероятность"
            else:
                direction = "Нейтрально"
                probability = "50% вероятность"

        # Определение точки входа и тейк-профита на основе фазы рынка
        entry_price = None
        take_profit = None

        if market_phase == "Balance":
            lookback = 20
            recent_high = df['High'].tail(lookback).max()
            recent_low = df['Low'].tail(lookback).min()

            if direction == "Бычий тренд":
                entry_price = recent_low
                take_profit = recent_high
            elif direction == "Медвежий тренд":
                entry_price = recent_high
                take_profit = recent_low

        logging.info(f"Определенная фаза рынка: {market_phase}, направление: {direction} с вероятностью {probability}.")
        logging.info(f"Точка входа: {entry_price}, Тейк-профит: {take_profit}")

        return market_phase, direction, probability, entry_price, take_profit

def plot_candlestick(df, symbol, entry=None, take_profit=None):
    try:
        logging.info(f"Построение графика свечей для {symbol}.")

        if df.empty:
            logging.warning(f"Пустой DataFrame для {symbol}. Пропуск построения графика.")
            return None

        if df.shape[0] < 10:
            logging.warning(f"Недостаточно данных для построения графика {symbol}. (строк: {df.shape[0]})")
            return None

        if df['Close'].nunique() == 1:
            logging.warning(f"Все значения цены закрытия одинаковы для {symbol}. Пропуск построения графика.")
            return None

        logging.info(f"Данные для построения графика {symbol}:\n{df.tail()}")

        df = df.copy()
        df.set_index('Open time', inplace=True)
        mc = mpf.make_marketcolors(up='g', down='r', inherit=True)
        s = mpf.make_mpf_style(marketcolors=mc)

        fig, axlist = mpf.plot(
            df,
            type='candle',
            style=s,
            title=f'{symbol} - Последние 72 часа',
            ylabel='Цена (USDT)',
            volume=True,
            addplot=[],
            returnfig=True,
            figsize=(12, 8)
        )

        if entry is not None and take_profit is not None:
            ax = axlist[0]
            ax.axhline(entry, linestyle='--', color='blue', linewidth=1)
            ax.axhline(take_profit, linestyle='--', color='orange', linewidth=1)

            ax.annotate('Entry', xy=(df.index[-1], entry), xytext=(0, 10),
                        textcoords="offset points", arrowprops=dict(arrowstyle='->', color='blue'),
                        fontsize=8, color='blue', ha='right')
            ax.annotate('Take Profit', xy=(df.index[-1], take_profit), xytext=(0, -15),
                        textcoords="offset points", arrowprops=dict(arrowstyle='->', color='orange'),
                        fontsize=8, color='orange', ha='right')

        buffer = BytesIO()
        fig.savefig(buffer, format='png', dpi=100, bbox_inches='tight')
        plt.close(fig)
        buffer.seek(0)
        img = Image.open(buffer)

        grayscale = img.convert('L')
        np_img = np.array(grayscale)
        variance = np.var(np_img)
        logging.debug(f"Variance for {symbol}: {variance}")
        if variance < 100.0:
            logging.warning(f"График для {symbol} является пустым (variance={variance}). Пропуск.")
            return None

        filename = os.path.join(GRAPHS_DIR, f"{symbol}_72h.png")
        img.save(filename)

        compression_success = compress_image(filename)
        if not compression_success:
            logging.warning(f"Изображение {filename} не прошло проверку на пустоту после сжатия.")
            return None

        logging.info(f"График сохранен как {filename}.")
        return filename
    except Exception as e:
        logging.error(f"Ошибка при построении графика для {symbol}: {e}")
        filename = os.path.join(GRAPHS_DIR, f"{symbol}_72h.png")
        if os.path.exists(filename):
            try:
                os.remove(filename)
                logging.info(f"Удален некорректный файл графика: {filename}")
            except Exception as remove_e:
                logging.error(f"Ошибка при удалении файла {filename}: {remove_e}")
        return None

async def send_media_group_with_retry(bot, chat_id, symbols, retries=3, delay=5):
    media_group = []
    for symbol_info in symbols:
        symbol = symbol_info['symbol']
        direction = symbol_info['direction']
        probability = symbol_info['probability']
        image_path = symbol_info['image']
        entry = symbol_info.get('entry')
        take_profit = symbol_info.get('take_profit')

        if image_path is None:
            logging.warning(f"Нет данных изображения для {symbol}. Пропуск.")
            continue

        if direction == "Бычий тренд":
            emoji = "📈"
            profit_percent = ((take_profit - entry) / entry) * 100
        elif direction == "Медвежий тренд":
            emoji = "📉"
            profit_percent = ((entry - take_profit) / entry) * 100
        else:
            emoji = "🔍"
            profit_percent = 0

        if entry is not None and take_profit is not None:
            caption = (
                f"{emoji} Монета: {symbol}\n"
                f"🔺 Направление: {direction}\n"
                f"🔢 Вероятность: {probability}\n"
                f"📍 Точка входа: {entry:.4f} USDT\n"
                f"🎯 Тейк-профит: {take_profit:.4f} USDT\n"
                f"💹 Потенциальная прибыльность: {profit_percent:.2f}%"
            )
        else:
            caption = (
                f"{emoji} Монета: {symbol}\n"
                f"🔺 Направление: {direction}\n"
                f"🔢 Вероятность: {probability}"
            )

        try:
            with open(image_path, 'rb') as photo_file:
                media_group.append(InputMediaPhoto(photo_file, caption=caption))
        except Exception as e:
            logging.error(f"Ошибка при открытии изображения {image_path}: {e}")
            continue

        if len(media_group) == 10:
            logging.info(f"Отправка группы из {len(media_group)} графиков в Telegram.")
            await send_media_group_with_retry_helper(bot, chat_id, media_group, retries, delay)
            media_group = []
            await asyncio.sleep(2)

    if media_group:
        logging.info(f"Отправка последней группы из {len(media_group)} графиков в Telegram.")
        await send_media_group_with_retry_helper(bot, chat_id, media_group, retries, delay)
        media_group = []
        await asyncio.sleep(2)

async def send_media_group_with_retry_helper(bot, chat_id, media_group, retries, delay):
    attempt = 0
    while attempt < retries:
        try:
            await bot.send_media_group(chat_id=chat_id, media=media_group)
            logging.info(f"Группа медиа успешно отправлена (Попытка {attempt + 1}).")
            return
        except RetryAfter as e:
            logging.warning(f"Превышен лимит отправки. Попытка {attempt + 1} через {e.retry_after} секунд.")
            await asyncio.sleep(e.retry_after)
        except TimedOut:
            logging.warning(f"Таймаут при отправке группы медиа. Попытка {attempt + 1} через {delay} секунд.")
            await asyncio.sleep(delay)
        except TelegramError as e:
            logging.error(f"TelegramError при отправке группы медиа: {e}. Попытка {attempt + 1} через {delay} секунд.")
            await asyncio.sleep(delay)
        except Exception as e:
            logging.error(f"Неизвестная ошибка при отправке группы медиа: {e}. Попытка {attempt + 1} через {delay} секунд.")
            await asyncio.sleep(delay)
        attempt += 1

    logging.error("Не удалось отправить группу медиа после нескольких попыток.")

async def send_telegram_report(token, chat_id, bullish_symbols, bearish_symbols):
    bot = telegram.Bot(token=token)
    try:
        if bullish_symbols:
            message_growth = "📈 **Список монет в фазе Баланса с высокой вероятностью тренда:**"
            logging.info("Отправка сообщения о монетах для торговли в фазе баланса.")
            await bot.send_message(chat_id=chat_id, text=message_growth, parse_mode='Markdown')
            logging.info(f"Отправка группы из {len(bullish_symbols)} графиков в Telegram для бычьего тренда.")
            await send_media_group_with_retry(bot, chat_id, bullish_symbols)
        else:
            logging.info("Нет монет в фазе Баланса с бычьим трендом для отправки.")

        if bearish_symbols:
            message_decline = "📉 **Список монет в фазе Баланса с высокой вероятностью тренда:**"
            logging.info("Отправка сообщения о монетах для торговли в фазе падения тренда.")
            await bot.send_message(chat_id=chat_id, text=message_decline, parse_mode='Markdown')
            logging.info(f"Отправка группы из {len(bearish_symbols)} графиков в Telegram для медвежьего тренда.")
            await send_media_group_with_retry(bot, chat_id, bearish_symbols)
        else:
            logging.info("Нет монет в фазе Баланса с медвежьим трендом для отправки.")

        try:
            logging.info("Удаление отправленных графиков.")
            files = glob.glob(os.path.join(GRAPHS_DIR, '*_72h.png'))
            for file in files:
                os.remove(file)
            logging.info(f"Удалено {len(files)} графиков.")
        except Exception as e:
            logging.error(f"Ошибка при удалении графиков: {e}")

        logging.info("Отчет успешно отправлен в Telegram.")
    except Exception as e:
        logging.error(f"Ошибка при отправке отчета в Telegram: {e}")

async def process_symbol(session, symbol, semaphore, executor):
    async with semaphore:
        try:
            df = await get_historical_klines(session, symbol, '1h', 72)
            if df is None or len(df) < 50:
                logging.warning(f"Недостаточно данных для анализа {symbol}. Пропуск.")
                return None

            if not is_volume_sufficient(df):
                logging.info(f"Объем торгов для {symbol} ниже порога. Пропуск.")
                return None

            if not has_sufficient_price_variation(df):
                logging.info(f"Вариация цен для {symbol} ниже порога. Пропуск.")
                return None

            loop = asyncio.get_event_loop()
            df = await loop.run_in_executor(executor, compute_indicators, df)

            market_phase, direction, probability, entry_price, take_profit = await loop.run_in_executor(executor, analyze_indicators, df)

            if entry_price is not None:
                entry_price = round(entry_price, 4)
            if take_profit is not None:
                take_profit = round(take_profit, 4)

            if market_phase == "Balance" and probability != "N/A":
                try:
                    prob_value = float(probability.split('%')[0])
                except ValueError:
                    logging.warning(f"Некорректное значение вероятности для {symbol}: {probability}. Пропуск.")
                    return None

                if prob_value == 100.0:
                    logging.info(f"Монета {symbol} соответствует критерию (prob={probability}, фаза: {market_phase}).")

                    image_path = await loop.run_in_executor(executor, plot_candlestick, df, symbol, entry_price, take_profit)
                    if image_path is None:
                        logging.warning(f"График для {symbol} не создан. Пропуск.")
                        return None

                    return {
                        'symbol': symbol,
                        'direction': direction,
                        'probability': probability,
                        'image': image_path,
                        'entry': entry_price,
                        'take_profit': take_profit
                    }

            return None

        except Exception as e:
            logging.error(f"Ошибка при обработке символа {symbol}: {e}")
            return None

async def main():
    try:
        clean_previous_graphs()

        connector = aiohttp.TCPConnector(limit=100)
        async with aiohttp.ClientSession(connector=connector) as session:
            symbols = await get_future_symbols(session)
            bullish_symbols = []
            bearish_symbols = []

            semaphore = asyncio.Semaphore(20)
            executor = ThreadPoolExecutor(max_workers=10)

            tasks = [
                process_symbol(session, symbol, semaphore, executor) for symbol in symbols
            ]

            results = await asyncio.gather(*tasks)

            for result in results:
                if result is None:
                    continue
                if result['direction'] == "Бычий тренд":
                    bullish_symbols.append(result)
                elif result['direction'] == "Медвежий тренд":
                    bearish_symbols.append(result)

            if bullish_symbols or bearish_symbols:
                logging.info(f"Отправка отчета с {len(bullish_symbols)} монетами для роста и {len(bearish_symbols)} монетами для падения.")
                await send_telegram_report(config.TELEGRAM_TOKEN, config.CHAT_ID, bullish_symbols, bearish_symbols)
            else:
                logging.info("Нет монет, соответствующих критериям для отчета.")

            logging.info("Процесс завершен успешно.")
    except Exception as e:
        logging.error(f"Процесс завершился с ошибкой: {e}")

if __name__ == '__main__':
    asyncio.run(main())