import json
import logging
import threading
from binance.client import Client
from binance.exceptions import BinanceAPIException
import pandas as pd
from ta import trend, momentum, volatility
import os
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from telegram import Bot
import requests  # Для синхронной отправки сообщений

# ======================== Конфигурация ========================

def load_config(config_file='config.json'):
    """
    Загрузка конфигурации из файла config.json
    """
    if not os.path.exists(config_file):
        raise FileNotFoundError(f"{config_file} не найден.")
    with open(config_file, 'r') as f:
        return json.load(f)

# ======================== Логирование ========================

def setup_logger(config):
    """
    Настройка логирования
    """
    logger = logging.getLogger('crypto_analyzer')
    log_level = getattr(logging, config.get('logging_level', 'INFO').upper(), logging.INFO)
    logger.setLevel(log_level)

    # Создание файлового обработчика
    fh = logging.FileHandler('crypto_analyzer.log')
    fh.setLevel(log_level)

    # Создание консольного обработчика
    ch = logging.StreamHandler()
    ch.setLevel(log_level)

    # Форматирование логов
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)

    # Добавление обработчиков к логгеру
    if not logger.handlers:
        logger.addHandler(fh)
        logger.addHandler(ch)

    return logger

# ======================== Telegram Уведомления ========================

class TelegramNotifier:
    """
    Класс для отправки уведомлений в Telegram
    """
    def __init__(self, token, chat_id, logger):
        self.token = token
        self.chat_id = chat_id
        self.logger = logger
        self.api_url = f"https://api.telegram.org/bot{self.token}/sendMessage"

    def send_message(self, message, parse_mode='HTML'):
        try:
            params = {
                'chat_id': self.chat_id,
                'text': message,
                'parse_mode': parse_mode
            }
            response = requests.get(self.api_url, params=params)
            if response.status_code == 200:
                self.logger.info("Уведомление отправлено в Telegram.")
            else:
                self.logger.error(f"Не удалось отправить уведомление в Telegram: {response.status_code} {response.text}")
        except Exception as e:
            self.logger.error(f"Ошибка при отправке сообщения в Telegram: {e}")

# ======================== Binance API Модуль ========================

class BinanceAPI:
    """
    Класс для взаимодействия с Binance API
    """
    def __init__(self, api_key, api_secret, logger):
        self.client = Client(api_key, api_secret, requests_params={'timeout': 30})  # Увеличен timeout до 30 секунд
        self.logger = logger

    def get_futures_symbols(self):
        """
        Получение списка всех perpetual futures символов, котируемых в USDT,
        исключая стабильные монеты.
        """
        try:
            info = self.client.futures_exchange_info()
            excluded_assets = config.get('excluded_base_assets', [])
            symbols = [
                s['symbol'] for s in info['symbols']
                if s['contractType'] == 'PERPETUAL'
                and s['quoteAsset'] == 'USDT'
                and s['baseAsset'] not in excluded_assets
            ]
            self.logger.info(f"Получено {len(symbols)} perpetual futures символов, котируемых в USDT (исключая стабильные монеты).")
            self.logger.debug(f"Отфильтрованные символы (без стабильных монет): {symbols}")
            return symbols
        except BinanceAPIException as e:
            self.logger.error(f"Ошибка при получении символов фьючерсов: {e}")
            return []
        except Exception as e:
            self.logger.error(f"Неизвестная ошибка при получении символов фьючерсов: {e}")
            return []

    def get_historical_klines(self, symbol, interval, limit=1000, retry_attempts=5, retry_delay=5):
        """
        Получение исторических свечей для символа с повторными попытками при ошибках
        """
        attempts = 0
        while attempts < retry_attempts:
            try:
                klines = self.client.futures_klines(symbol=symbol, interval=interval, limit=limit)
                df = pd.DataFrame(klines, 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.set_index('open_time', inplace=True)
                df = df.astype(float)
                return df
            except BinanceAPIException as e:
                self.logger.error(f"Ошибка при получении свечей для {symbol}: {e}")
            except requests.exceptions.Timeout as e:
                self.logger.error(f"Таймаут при получении свечей для {symbol}: {e}")
            except Exception as e:
                self.logger.error(f"Неизвестная ошибка при получении свечей для {symbol}: {e}")
            
            attempts += 1
            sleep_time = retry_delay * (2 ** attempts)  # Экспоненциальная задержка
            self.logger.info(f"Повторная попытка {attempts}/{retry_attempts} через {sleep_time} секунд...")
            time.sleep(sleep_time)
        
        self.logger.warning(f"Не удалось получить данные для символа: {symbol} после {retry_attempts} попыток.")
        return pd.DataFrame()

# ======================== Индикаторы Модуль ========================

def add_indicators(df, logger):
    """
    Добавление технических индикаторов в DataFrame
    """
    # RSI
    df['rsi'] = momentum.RSIIndicator(close=df['close'], window=14).rsi()
    logger.debug(f"{df.name} - RSI рассчитан.")

    # MACD
    macd = trend.MACD(close=df['close'])
    df['macd'] = macd.macd()
    df['macd_signal'] = macd.macd_signal()
    df['macd_diff'] = macd.macd_diff()
    logger.debug(f"{df.name} - MACD рассчитан.")

    # Bollinger Bands
    bollinger = volatility.BollingerBands(close=df['close'], window=20, window_dev=2)
    df['bb_hband'] = bollinger.bollinger_hband()
    df['bb_lband'] = bollinger.bollinger_lband()
    df['bb_middle'] = bollinger.bollinger_mavg()
    logger.debug(f"{df.name} - Bollinger Bands рассчитаны.")

    # ATR
    df['atr'] = volatility.AverageTrueRange(high=df['high'], low=df['low'], close=df['close'], window=14).average_true_range()
    logger.debug(f"{df.name} - ATR рассчитан.")

    # Объём скользящая средняя
    df['vol_ma'] = df['volume'].rolling(window=20).mean()
    logger.debug(f"{df.name} - Volume MA рассчитана.")

    return df

# ======================== Анализ Модуль ========================

def calculate_support_resistance(df, window=50, logger=None):
    """
    Расчёт уровней поддержки и сопротивления
    """
    support = df['low'].rolling(window=window).min()
    resistance = df['high'].rolling(window=window).max()
    df['support'] = support
    df['resistance'] = resistance
    if logger:
        logger.debug(f"{df.name} - Support и Resistance рассчитаны.")
    return df

def signal_generator(df, logger, window=50, config=None):
    """
    Генерация торговых сигналов на основе индикаторов.
    Сигнал считается сильным, если большинство индикаторов подтверждают направление.
    """
    if df.empty:
        return None

    latest = df.iloc[-1]

    buy_signals = 0
    sell_signals = 0

    logger.debug(f"Проверка сигналов для {df.name}")

    # Пример сигнала на основе RSI
    if latest['rsi'] < 30:
        buy_signals += 1
        logger.debug("RSI: Buy сигнал")
    elif latest['rsi'] > 70:
        sell_signals += 1
        logger.debug("RSI: Sell сигнал")

    # Пример сигнала на основе MACD
    if latest['macd'] > latest['macd_signal']:
        buy_signals += 1
        logger.debug("MACD: Buy сигнал")
    elif latest['macd'] < latest['macd_signal']:
        sell_signals += 1
        logger.debug("MACD: Sell сигнал")

    # Пример сигнала на основе Bollinger Bands
    if latest['close'] < latest['bb_lband']:
        buy_signals += 1
        logger.debug("Bollinger Bands: Buy сигнал")
    elif latest['close'] > latest['bb_hband']:
        sell_signals += 1
        logger.debug("Bollinger Bands: Sell сигнал")

    # Дополнительные сигналы на основе ATR и объёма
    atr_mean = df['atr'].rolling(window=window).mean().iloc[-1]
    if latest['atr'] < atr_mean:
        buy_signals += 1
        logger.debug("ATR: Buy сигнал")
    else:
        sell_signals += 1
        logger.debug("ATR: Sell сигнал")

    if latest['volume'] > latest['vol_ma']:
        buy_signals += 1
        logger.debug("Volume MA: Buy сигнал")
    else:
        sell_signals += 1
        logger.debug("Volume MA: Sell сигнал")

    # Логирование количества сигналов
    logger.debug(f"Сигналы для {df.name}: Buy={buy_signals}, Sell={sell_signals}")

    # Определение порога для сильных сигналов (например, 4 из 5)
    threshold = config.get('threshold', 4) if config else 4

    if buy_signals >= threshold:
        logger.debug(f"Сигнал для {df.name}: BUY")
        return 'buy'
    elif sell_signals >= threshold:
        logger.debug(f"Сигнал для {df.name}: SELL")
        return 'sell'
    else:
        logger.debug(f"Сигнал для {df.name}: Нет сильного сигнала")
        return None

# ======================== Отчёты Модуль ========================

def generate_report(promising_coins, logger, notifier):
    """
    Генерация отчёта по наиболее перспективным монетам и отправка его в Telegram.
    В отчёте содержатся уровни поддержки и сопротивления, точка входа, тейк-профит и стоп-лосс.
    """
    try:
        if not promising_coins:
            message = "⚠️ Анализ завершён. Нет перспективных монет для отчёта."
            logger.info(message)
            if notifier:
                notifier.send_message(message)
            return

        report = "<b>📈 Торговые Сигналы по Binance Futures (USDT) 📉</b>\n\n"

        for coin in promising_coins:
            symbol = coin.get('symbol')
            signal = coin.get('signal', '').capitalize()
            close = coin.get('close', 0)
            support = coin.get('support', 0)
            resistance = coin.get('resistance', 0)
            entry = coin.get('entry', 'Не определена')
            take_profit = coin.get('take_profit', 'Не определен')
            stop_loss = coin.get('stop_loss', 'Не определен')

            # Проверка разницы между поддержкой и сопротивлением
            if abs(support - resistance) < 0.01:  # Настройте порог по необходимости
                logger.warning(f"Отфильтрован символ {symbol} из-за слишком малой разницы между support и resistance.")
                continue

            report += f"🔹 <b>{symbol}</b>\n"
            report += f"➤ <b>Направление:</b> {signal}\n"
            report += f"➤ <b>Цена закрытия:</b> {close:.2f} USDT\n"
            report += f"➤ <b>Уровни:</b> Support: {support:.2f}, Resistance: {resistance:.2f}\n"
            report += f"➤ <b>Точка входа:</b> {entry:.2f} USDT\n"
            report += f"➤ <b>Тейк-Профит:</b> {take_profit:.2f} USDT\n"
            report += f"➤ <b>Стоп-Лосс:</b> {stop_loss:.2f} USDT\n\n"

        # Ограничение длины сообщения Telegram (4096 символов)
        if len(report) > 4000:
            report = report[:4000] + "\n... Продолжение следует."

        logger.info("Отчёт сформирован для отправки в Telegram.")
        if notifier:
            notifier.send_message(report)
    except Exception as e:
        logger.error(f"Ошибка при генерации отчёта: {e}")
        if notifier:
            notifier.send_message(f"Ошибка при генерации отчёта: {e}")

# ======================== Основная Функция ========================

def process_symbol(symbol, api, logger, results, config):
    """
    Обработка одного символа: получение данных, расчёт индикаторов, генерация сигнала.
    """
    try:
        logger.info(f"Обработка символа: {symbol}")
        df = api.get_historical_klines(
            symbol, config['timeframe'], limit=config['lookback'],
            retry_attempts=config.get('retry_attempts', 5),
            retry_delay=config.get('retry_delay', 5)
        )
        if df.empty:
            logger.warning(f"Нет данных для символа: {symbol}")
            return

        # Добавление имени символа в DataFrame для логирования
        df.name = symbol

        df = add_indicators(df, logger)
        df = calculate_support_resistance(df, window=50, logger=logger)

        signal = signal_generator(df, logger, window=50, config=config)  # Передача config

        if signal:
            last_close = df['close'].iloc[-1]
            support = df['support'].iloc[-1]
            resistance = df['resistance'].iloc[-1]
            atr = df['atr'].iloc[-1]
            entry = support if signal == 'buy' else resistance  # Точка входа

            k = config.get('atr_multiplier', 1.5)  # Коэффициент для TP и SL

            if signal == 'buy':
                take_profit = entry + (atr * k)
                stop_loss = entry - (atr * k) / 3  # SL = TP / 3 ниже Entry
            else:
                take_profit = entry - (atr * k)
                stop_loss = entry + (atr * k) / 3  # SL = TP / 3 выше Entry

            results.append({
                'symbol': symbol,
                'signal': signal,
                'close': last_close,
                'support': support,
                'resistance': resistance,
                'entry': entry,
                'take_profit': take_profit,  # Изменение
                'stop_loss': stop_loss       # Изменение
            })
            logger.info(f"Сигнал для {symbol}: {signal} (Точка входа: {entry:.2f}, TP: {take_profit:.2f}, SL: {stop_loss:.2f})")
    except Exception as e:
        logger.error(f"Ошибка при обработке {symbol}: {e}")

def main():
    # Загрузка конфигурации
    try:
        global config
        config = load_config()
    except Exception as e:
        print(f"Не удалось загрузить конфигурацию: {e}")
        return

    # Настройка логирования
    logger = setup_logger(config)
    logger.info("Старт анализа фьючерсов Binance")

    # Инициализация Telegram уведомлений
    telegram_token = config.get('telegram_token')
    telegram_chat_id = config.get('telegram_chat_id')
    if telegram_token and telegram_chat_id:
        notifier = TelegramNotifier(telegram_token, telegram_chat_id, logger)
    else:
        notifier = None
        logger.warning("Telegram уведомления не настроены. Укажите telegram_token и telegram_chat_id в config.json.")

    # Инициализация Binance API
    api = BinanceAPI(config['api_key'], config['api_secret'], logger)

    # Получение списка символов фьючерсов, котируемых в USDT
    symbols = api.get_futures_symbols()
    if not symbols:
        logger.error("Нет доступных символов для анализа.")
        if notifier:
            notifier.send_message("❌ Нет доступных символов для анализа.")
        return

    results = []

    max_threads = config.get('max_threads', 5)  # Уменьшен до 5 для стабильности

    # Использование ThreadPoolExecutor для обработки символов с ограничением количества потоков
    with ThreadPoolExecutor(max_workers=max_threads) as executor:
        future_to_symbol = {executor.submit(process_symbol, symbol, api, logger, results, config): symbol for symbol in symbols}
        for future in as_completed(future_to_symbol):
            symbol = future_to_symbol[future]
            try:
                future.result()
            except Exception as e:
                logger.error(f"Ошибка при запуске обработки символа {symbol}: {e}")

    # Фильтрация перспективных монет (например, сигналы buy и sell)
    promising_coins = [x for x in results if x['signal'] in ['buy', 'sell']]

    # Генерация отчёта и отправка в Telegram
    generate_report(promising_coins, logger, notifier)

    logger.info("Анализ завершён.")

if __name__ == "__main__":
    main() скрипт отбирает  монеты с одинаковыми данными или пишет их сам , сейчас пришлю пример и config тоже