#technical_analysis.py
import asyncio
import logging
import time
from typing import Dict, List, Tuple

import pandas as pd
from ta.momentum import RSIIndicator, StochasticOscillator, AroonOscillator
from ta.trend import ADXIndicator, CCIIndicator, MACD, SMAIndicator, IchimokuCloud
from ta.volatility import BollingerBands

# Django setup
import os
import django

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'binancebot.settings')
django.setup()

from .models import Kline, Signal, TradingPair

# Logger setup
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)

class CryptoBot:
    def __init__(self, timeframes: List[str] = ["5m", "15m", "1h"], num_candles: int = 200):
        self.timeframes = timeframes
        self.num_candles = num_candles
        logger.info("CryptoBot initialized with timeframes: %s and num_candles: %d", timeframes, num_candles)

    async def get_candles_df(self, pair: str, timeframe: str) -> pd.DataFrame:
        """
        Fetches the most recent `num_candles` records for the specified trading pair and timeframe.
        """
        klines = Kline.objects.filter(symbol=pair, timeframe=timeframe).order_by("-timestamp")[:self.num_candles]
        df = pd.DataFrame.from_records(klines.values())
        df.set_index("timestamp", inplace=True)
        df.sort_index(inplace=True)  # Ensure chronological order for analysis
        return df

    async def calculate_technical_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
        # Technical indicators calculation
        df["sma_fast"] = SMAIndicator(close=df["close"], window=14).sma_indicator()
        df["sma_slow"] = SMAIndicator(close=df["close"], window=28).sma_indicator()
        df["rsi"] = RSIIndicator(close=df["close"], window=14).rsi()
        stoch = StochasticOscillator(close=df["close"], high=df["high"], low=df["low"], window=14, smooth_window=3)
        df["stoch_k"] = stoch.stoch()
        df["stoch_d"] = stoch.stoch_signal()

        macd = MACD(close=df["close"], window_slow=26, window_fast=12, window_sign=9)
        df["macd"] = macd.macd()
        df["macd_signal"] = macd.macd_signal()
        df["adx"] = ADXIndicator(high=df["high"], low=df["low"], close=df["close"], window=14).adx()
        df["cci"] = CCIIndicator(high=df["high"], low=df["low"], close=df["close"], window=20).cci()

        bollinger = BollingerBands(close=df["close"], window=20, window_dev=2)
        df["bollinger_mavg"] = bollinger.bollinger_mavg()
        df["bollinger_hband"] = bollinger.bollinger_hband()
        df["bollinger_lband"] = bollinger.bollinger_lband()

        ichimoku = IchimokuCloud(high=df["high"], low=df["low"], window1=9, window2=26, window3=52)
        df["ichimoku_a"] = ichimoku.ichimoku_a()
        df["ichimoku_b"] = ichimoku.ichimoku_b()

        aroon = AroonOscillator(high=df["high"], low=df["low"], window=14)
        df["aroon_up"] = aroon.aroon_up()
        df["aroon_down"] = aroon.aroon_down()

        return df

    async def generate_signal(self, pair: str, timeframe: str) -> Tuple[str, Dict[str, float]]:
        try:
            df = await self.get_candles_df(pair, timeframe)
            df = await self.calculate_technical_indicators(df)

            current_price = df["close"].iloc[-1]
            signal = "Neutral"

            # Example trading strategy (including multiple checks)
            if (
                df["sma_fast"].iloc[-2] < df["sma_slow"].iloc[-2] and df["sma_fast"].iloc[-1] > df["sma_slow"].iloc[-1]
                and df["rsi"].iloc[-1] < 30
                and df["stoch_k"].iloc[-1] < 20
                and df["stoch_d"].iloc[-1] < 20
            ):
                signal = "Strong Buy"
            elif df["sma_fast"].iloc[-2] < df["sma_slow"].iloc[-2] and df["sma_fast"].iloc[-1] > df["sma_slow"].iloc[-1]:
                signal = "Buy"
            elif (
                df["sma_fast"].iloc[-2] > df["sma_slow"].iloc[-2] and df["sma_fast"].iloc[-1] < df["sma_slow"].iloc[-1]
                and df["rsi"].iloc[-1] > 70
                and df["stoch_k"].iloc[-1] > 80
                and df["stoch_d"].iloc[-1] > 80
            ):
                signal = "Strong Sell"
            elif df["sma_fast"].iloc[-2] > df["sma_slow"].iloc[-2] and df["sma_fast"].iloc[-1] < df["sma_slow"].iloc[-1]:
                signal = "Sell"

            # MACD and ADX signals
            if df["macd"].iloc[-2] < df["macd_signal"].iloc[-2] and df["macd"].iloc[-1] > df["macd_signal"].iloc[-1] and df["adx"].iloc[-1] > 25 and df["cci"].iloc[-1] > 100:
                signal = "Buy"
            elif df["macd"].iloc[-2] > df["macd_signal"].iloc[-2] and df["macd"].iloc[-1] < df["macd_signal"].iloc[-1] and df["adx"].iloc[-1] > 25 and df["cci"].iloc[-1] < -100:
                signal = "Sell"

            # Bollinger Bands and Ichimoku Cloud signals
            if (
                df["bollinger_hband"].iloc[-1] > df["close"].iloc[-1] > df["bollinger_mavg"].iloc[-1]
                and df["aroon_up"].iloc[-1] > 70
                and df["aroon_down"].iloc[-1] < 30
                and df["ichimoku_a"].iloc[-1] > df["ichimoku_b"].iloc[-1]
            ):
                signal = "Buy"
            elif (
                df["bollinger_lband"].iloc[-1] < df["close"].iloc[-1] < df["bollinger_mavg"].iloc[-1]
                and df["aroon_up"].iloc[-1] < 30
                and df["aroon_down"].iloc[-1] > 70
                and df["ichimoku_a"].iloc[-1] < df["ichimoku_b"].iloc[-1]
            ):
                signal = "Sell"

            support_level, resistance_level = self.calculate_support_and_resistance(df)

            # Risk Management
            stop_loss, take_profit = self.calculate_stop_loss_take_profit(signal, current_price, support_level, resistance_level)
            
            metrics = {
                "price": current_price,
                "stop_loss": stop_loss,
                "take_profit": take_profit,
                "support": support_level,
                "resistance": resistance_level
            }

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

    def calculate_support_and_resistance(self, df: pd.DataFrame) -> Tuple[float, float]:
        try:
            # Calculate support and resistance levels as the lowest and highest of the last `num_candles`.
            support_level = df["low"].tail(self.num_candles).min()
            resistance_level = df["high"].tail(self.num_candles).max()
            return support_level, resistance_level
        except Exception as e:
            logger.error(f"Error calculating support and resistance: {str(e)}")
            return df["low"].iloc[-1], df["high"].iloc[-1]  # Fall back to last row values in case of error.

    def calculate_stop_loss_take_profit(self, signal: str, current_price: float, support: float, resistance: float) -> Tuple[float, float]:
        if signal in ["Buy", "Strong Buy"]:
            stop_loss = support
            take_profit = resistance
        elif signal in ["Sell", "Strong Sell"]:
            stop_loss = resistance
            take_profit = support
        else:
            stop_loss = support
            take_profit = resistance

        return stop_loss, take_profit

    async def save_signal(self, pair: str, timeframe: str, signal: str, metrics: Dict[str, float]):
        try:
            Signal.objects.create(
                symbol=pair,
                timestamp=pd.Timestamp.now(),
                timeframe=timeframe,
                signal_type=signal,
                price=metrics.get("price"),
                stop_loss=metrics.get("stop_loss"),
                take_profit=metrics.get("take_profit"),
            )
            logger.info(f"Signal for {pair} {timeframe} saved to the database.")
        except Exception as e:
            logger.error(f"Error saving signal for {pair} {timeframe}: {e}")

    async def run_bot(self):
        while True:
            logger.info("Starting bot cycle...")
            start_time = time.time()

            tasks = []
            symbols = TradingPair.objects.values_list("symbol", flat=True)

            for symbol in symbols:
                for timeframe in self.timeframes:
                    task = self.process_symbol(symbol, timeframe)
                    tasks.append(task)

            await asyncio.gather(*tasks)

            end_time = time.time()
            execution_time = end_time - start_time
            sleep_time = max(60 - execution_time, 0)
            logger.info(f"Bot cycle complete. Sleeping for {sleep_time} seconds.")
            await asyncio.sleep(sleep_time)

    async def process_symbol(self, symbol: str, timeframe: str):
        signal, metrics = await self.generate_signal(symbol, timeframe)
        await self.save_signal(symbol, timeframe, signal, metrics)

if __name__ == "__main__":
    bot = CryptoBot()
    try:
        asyncio.run(bot.run_bot())
    except KeyboardInterrupt:
        logger.info("Stopping bot...")

Analyze the code and tell me what we can improve to increase the accuracy of signals generated.