import numpy as np
from scipy import stats
import pandas as pd
from collections import deque
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from typing import Dict, List, Tuple
import logging
from binance.client import Client
from binance.exceptions import BinanceAPIException
import time

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class MultiCryptoSignalBot:
    def __init__(self, short_period: int = 100, long_period: int = 500, volume_threshold: float = 2, ob_imbalance_threshold: float = 0.7):
        self.short_period = short_period
        self.long_period = long_period
        self.volume_threshold = volume_threshold
        self.ob_imbalance_threshold = ob_imbalance_threshold
        self.pair_data: Dict[str, Dict] = {}
        self.client = Client()

    def initialize_pair(self, pair: str):
        self.pair_data[pair] = {
            'volume_history': deque(maxlen=self.long_period),
            'ob_imbalance_history': deque(maxlen=self.long_period),
            'price_history': deque(maxlen=self.long_period),
            'volatility_history': deque(maxlen=self.long_period)
        }

    def calculate_volume_zscore(self, pair: str, current_volume: float) -> float:
        self.pair_data[pair]['volume_history'].append(current_volume)
        if len(self.pair_data[pair]['volume_history']) < self.short_period:
            return 0
        return stats.zscore(list(self.pair_data[pair]['volume_history']))[-1]

    def calculate_ob_imbalance(self, bids: List[Tuple[float, float]], asks: List[Tuple[float, float]]) -> float:
        total_bid_volume = sum(bid[1] for bid in bids)
        total_ask_volume = sum(ask[1] for ask in asks)
        return total_bid_volume / (total_bid_volume + total_ask_volume)

    def calculate_ob_imbalance_zscore(self, pair: str, current_imbalance: float) -> float:
        self.pair_data[pair]['ob_imbalance_history'].append(current_imbalance)
        if len(self.pair_data[pair]['ob_imbalance_history']) < self.short_period:
            return 0
        return stats.zscore(list(self.pair_data[pair]['ob_imbalance_history']))[-1]

    def calculate_volume_weighted_price(self, trades: List[Dict[str, float]]) -> float:
        return sum(trade['price'] * trade['volume'] for trade in trades) / sum(trade['volume'] for trade in trades)

    def calculate_ob_depth(self, order_book: Dict[str, List[Tuple[float, float]]], levels: int = 10) -> Tuple[float, float]:
        bid_depth = sum(bid[1] for bid in order_book['bids'][:levels])
        ask_depth = sum(ask[1] for ask in order_book['asks'][:levels])
        return bid_depth, ask_depth

    def detect_spoofing(self, order_book: Dict[str, List[Tuple[float, float]]], trades: List[Dict[str, float]], time_window: int = 60) -> bool:
        large_orders = [order for order in order_book['bids'] + order_book['asks'] if order[1] > self.volume_threshold * np.mean([trade['volume'] for trade in trades])]
        return any(order[0] not in [trade['price'] for trade in trades] for order in large_orders)

    def calculate_volatility(self, pair: str, current_price: float) -> float:
        self.pair_data[pair]['price_history'].append(current_price)
        if len(self.pair_data[pair]['price_history']) < self.short_period:
            return 0
        returns = np.diff(list(self.pair_data[pair]['price_history'])) / list(self.pair_data[pair]['price_history'])[:-1]
        volatility = np.std(returns) * np.sqrt(len(returns))
        self.pair_data[pair]['volatility_history'].append(volatility)
        return volatility

    def detect_market_regime(self, pair: str) -> str:
        if len(self.pair_data[pair]['volatility_history']) < self.short_period:
            return 'unknown'
        recent_volatility = list(self.pair_data[pair]['volatility_history'])[-self.short_period:]
        
        # Feature engineering: Include price momentum and volume
        price_momentum = (self.pair_data[pair]['price_history'][-1] / self.pair_data[pair]['price_history'][-self.short_period]) - 1
        volume_change = (self.pair_data[pair]['volume_history'][-1] / np.mean(self.pair_data[pair]['volume_history'][-self.short_period:])) - 1
        
        features = np.array([[v, price_momentum, volume_change] for v in recent_volatility])
        
        # Normalize features
        scaler = StandardScaler()
        normalized_features = scaler.fit_transform(features)
        
        # Use silhouette score to determine optimal number of clusters
        best_n_clusters = 2
        best_silhouette = -1
        for n_clusters in range(2, 6):
            kmeans = KMeans(n_clusters=n_clusters, random_state=0, n_init=10)
            cluster_labels = kmeans.fit_predict(normalized_features)
            silhouette = silhouette_score(normalized_features, cluster_labels)
            if silhouette > best_silhouette:
                best_silhouette = silhouette
                best_n_clusters = n_clusters
        
        kmeans = KMeans(n_clusters=best_n_clusters, random_state=0, n_init=10).fit(normalized_features)
        current_regime = kmeans.predict(normalized_features[-1].reshape(1, -1))[0]
        
        regimes = ['low_volatility', 'medium_volatility', 'high_volatility', 'extreme_volatility', 'unknown']
        return regimes[min(current_regime, len(regimes) - 1)]

    def adaptive_thresholds(self, market_regime: str) -> Tuple[float, float]:
        if market_regime == 'high_volatility':
            return self.volume_threshold * 1.5, self.ob_imbalance_threshold * 1.2
        elif market_regime == 'low_volatility':
            return self.volume_threshold * 0.75, self.ob_imbalance_threshold * 0.9
        elif market_regime == 'extreme_volatility':
            return self.volume_threshold * 2, self.ob_imbalance_threshold * 1.5
        else:
            return self.volume_threshold, self.ob_imbalance_threshold

    def calculate_stop_loss_take_profit(self, signal: str, current_price: float, volatility: float) -> Tuple[float, float]:
        atr_multiplier = 2  # Adjust this value based on risk tolerance
        stop_loss = current_price * (1 - atr_multiplier * volatility) if signal == "Buy" else current_price * (1 + atr_multiplier * volatility)
        take_profit = current_price * (1 + 2 * atr_multiplier * volatility) if signal == "Buy" else current_price * (1 - 2 * atr_multiplier * volatility)
        return stop_loss, take_profit

    def generate_signal(self, pair: str, current_volume: float, bids: List[Tuple[float, float]], asks: List[Tuple[float, float]], 
                        trades: List[Dict[str, float]], order_book: Dict[str, List[Tuple[float, float]]], current_price: float) -> Tuple[str, Dict]:
        try:
            if pair not in self.pair_data:
                self.initialize_pair(pair)

            volume_zscore = self.calculate_volume_zscore(pair, current_volume)
            ob_imbalance = self.calculate_ob_imbalance(bids, asks)
            ob_imbalance_zscore = self.calculate_ob_imbalance_zscore(pair, ob_imbalance)
            vwap = self.calculate_volume_weighted_price(trades)
            bid_depth, ask_depth = self.calculate_ob_depth(order_book)
            spoofing_detected = self.detect_spoofing(order_book, trades)
            volatility = self.calculate_volatility(pair, current_price)
            market_regime = self.detect_market_regime(pair)
            
            adaptive_volume_threshold, adaptive_ob_threshold = self.adaptive_thresholds(market_regime)

            if volume_zscore > adaptive_volume_threshold and ob_imbalance > adaptive_ob_threshold:
                if ob_imbalance_zscore > 0 and bid_depth > ask_depth:
                    signal = "Buy"
                elif ob_imbalance_zscore < 0 and ask_depth > bid_depth:
                    signal = "Sell"
                else:
                    signal = "Neutral"
            else:
                signal = "Neutral"

            if spoofing_detected:
                signal = "Neutral"

            signal_strength = abs(volume_zscore * ob_imbalance_zscore)
            stop_loss, take_profit = self.calculate_stop_loss_take_profit(signal, current_price, volatility)

            metrics = {
                'volume_zscore': volume_zscore,
                'ob_imbalance': ob_imbalance,
                'ob_imbalance_zscore': ob_imbalance_zscore,
                'vwap': vwap,
                'bid_depth': bid_depth,
                'ask_depth': ask_depth,
                'spoofing_detected': spoofing_detected,
                'volatility': volatility,
                'market_regime': market_regime,
                'signal_strength': signal_strength,
                'stop_loss': stop_loss,
                'take_profit': take_profit
            }

            logger.info(f"Generated signal for {pair}: {signal}")
            return signal, metrics
        except Exception as e:
            logger.error(f"Error generating signal for {pair}: {str(e)}")
            return "Neutral", {}

    def get_binance_futures_usdt_pairs(self):
        try:
            exchange_info = self.client.futures_exchange_info()
            return [symbol['symbol'] for symbol in exchange_info['symbols'] if symbol['quoteAsset'] == 'USDT']
        except BinanceAPIException as e:
            logger.error(f"Error fetching USDT pairs from Binance: {str(e)}")
            return []

    def fetch_market_data(self, pair: str):
        try:
            depth = self.client.futures_order_book(symbol=pair)
            trades = self.client.futures_recent_trades(symbol=pair)
            ticker = self.client.futures_ticker(symbol=pair)

            bids = [(float(bid[0]), float(bid[1])) for bid in depth['bids']]
            asks = [(float(ask[0]), float(ask[1])) for ask in depth['asks']]
            recent_trades = [{'price': float(trade['price']), 'volume': float(trade['qty'])} for trade in trades]
            current_price = float(ticker['lastPrice'])
            current_volume = float(ticker['volume'])

            return current_volume, bids, asks, recent_trades, {'bids': bids, 'asks': asks}, current_price
        except BinanceAPIException as e:
            logger.error(f"Error fetching market data for {pair}: {str(e)}")
            return None

    def run(self):
        pairs = self.get_binance_futures_usdt_pairs()
        while True:
            for pair in pairs:
                market_data = self.fetch_market_data(pair)
                if market_data:
                    current_volume, bids, asks, trades, order_book, current_price = market_data
                    signal, metrics = self.generate_signal(pair, current_volume, bids, asks, trades, order_book, current_price)
                    logger.info(f"Pair: {pair}, Signal: {signal}, Metrics: {metrics}")
                time.sleep(1)  # Add delay to avoid API rate limits
            time.sleep(60)  # Wait for 1 minute before next iteration

if __name__ == "__main__":
    bot = MultiCryptoSignalBot()
    bot.run()

    I want you to modify my code with new features below without removing any of the existin features, make it robust and sophisticated;
    Fixed parameter initialization: The initial parameters are still hardcoded and not dynamically optimized.
    No performance metrics: The bot doesn't track or report on its performance over time.
    Put/Call Ratio: This measures the trading volume of put options relative to call options. A high put/call ratio suggests bearish sentiment, while a low ratio suggests bullish sentiment.
    Funding Rates: For perpetual futures contracts, funding rates reflect the difference in pricing between the underlying asset and the futures contract. Positive funding rates indicate bullish sentiment, while negative rates suggest bearish sentiment.
    Whale Activity: Monitoring large transactions or "whale" movements on the blockchain can provide clues about sentiment. Large transfers to exchanges might suggest selling intent, whereas large off-exchange transfers could indicate holding sentiment.
    Do not include Social Media Sentiment Analysis and News Sentiment Analysis as this is performed in a seperate script file.
    Exchange Data Analysis: Monitoring data from crypto exchanges, such as trading volume, open interest in futures, and order book depth, can provide insights into market sentiment. For example, a large increase in buying volume or unusually high open interest could reflect bullish sentiment.
    Use On-Chain Analysis: Utilize blockchain data, such as whale activity, transaction volumes, and active addresses. This can be particularly insightful for crypto markets.
    High-Frequency Data: Use the most precise and up-to-date data feeds available to minimize delays.
    Continuous Learning: Implement a feedback system that learns from past trades and updates models or strategies accordingly.
Do not remove any function, just advance or add to make it robust. Generate signals for different timeframes that is 1 minute, 5minute, 15 minute, and 1 hour