# price_action_bot.py

import asyncio
import logging
import pandas as pd
import numpy as np
from datetime import timedelta
from decimal import Decimal
import json
from django.utils import timezone
from django.db import connection
from collections import deque
import time

# Django setup
import os
import django

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

from .models import Kline, Signal, TradingPair  # Adjust the import according to your project structure

# Enhanced logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('trading_signals.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


class PriceActionBot:
    def __init__(self, timeframes=['1m', '5m', '15m', '1h'], num_candles=500):
        self.timeframes = timeframes
        self.num_candles = num_candles
        self.symbols = []
        self.performance_metrics = {}
        self.parameters = {
            'swing_window': 5,
            'volatility_window': 20,
            'momentum_window': 5,
            'pattern_tolerance': 0.02,
            'trend_strength_threshold': 4,
            'sideways_trend_threshold': 2,
            'range_bound_tolerance': 0.015,  # For neutral signals in range-bound markets
        }

    async def fetch_symbols(self):
        """Fetch the list of trading pairs from the database."""
        try:
            self.symbols = list(TradingPair.objects.values_list('symbol', flat=True))
            logger.info(f"Fetched symbols: {self.symbols}")
        except Exception as e:
            logger.error(f"Error fetching symbols: {str(e)}")

    async def fetch_candles(self, symbol, timeframe):
        """Fetch historical price data from the database."""
        try:
            klines = Kline.objects.filter(
                symbol=symbol,
                timeframe=timeframe
            ).order_by('-timestamp')[:self.num_candles]

            df = pd.DataFrame(list(klines.values()))
            if df.empty:
                logger.warning(f"No data for {symbol} {timeframe}")
                return None

            df.set_index('timestamp', inplace=True)
            df.sort_index(inplace=True)

            # Data preprocessing
            df.replace([np.inf, -np.inf], np.nan, inplace=True)
            df.dropna(inplace=True)

            return df
        except Exception as e:
            logger.error(f"Error fetching candles for {symbol} {timeframe}: {str(e)}")
            return None

    async def process_symbol(self, symbol):
        """Process all timeframes for a single symbol."""
        tasks = []
        for timeframe in self.timeframes:
            tasks.append(self.analyze_timeframe(symbol, timeframe))
        await asyncio.gather(*tasks)

    async def analyze_timeframe(self, symbol, timeframe):
        """Analyze a single symbol and timeframe."""
        try:
            df = await self.fetch_candles(symbol, timeframe)
            if df is None or df.empty:
                return

            # Advanced price action analysis
            signal_data = self.generate_signal(df, symbol, timeframe)

            if signal_data['signal'] != 'NO_SIGNAL':
                # Save the signal to the database
                await self.save_signal(signal_data)
            else:
                logger.info(f"No significant signal for {symbol} ({timeframe}). Signal: {signal_data['signal']}")

        except Exception as e:
            logger.error(f"Error analyzing {symbol} {timeframe}: {str(e)}")

    def generate_signal(self, df, symbol, timeframe):
        """Generates a trading signal based on advanced price action analysis."""
        try:
            # Start time for performance measurement
            analysis_start = time.time()

            current_price = df['close'].iloc[-1]
            current_time = timezone.now()
            timestamp = df.index[-1]

            # Calculate 24h price change
            change_24h = self.calculate_24h_change(df)

            # Market structure analysis
            market_structure = self.analyze_market_structure(df)

            # Volatility analysis
            volatility = self.calculate_volatility(df)

            # Volume analysis
            volume_signal = self.analyze_volume(df)

            # Advanced pattern recognition
            patterns = self.detect_patterns(df)

            # Momentum analysis
            momentum = self.analyze_momentum(df)

            # Order book analysis (placeholder, since order book data is not available)
            # In a real implementation, you would fetch order book data and analyze it
            # order_book_signal = self.analyze_order_book(symbol)

            # Signal determination
            signal_strength, supporting_factors = self.determine_signal(
                market_structure, volatility, volume_signal, patterns, momentum, current_price
            )

            # Determine signal type based on strength
            signal = self.get_signal_type(signal_strength)

            # Calculate stop-loss and take-profit levels
            stop_loss, take_profit = self.calculate_trade_levels(
                df, signal, current_price, market_structure
            )

            # Success rate estimation based on signal strength
            success_rate = min(50 + signal_strength * 10, 95) if signal_strength > 0 else 0

            # Result is pending since we need future price data
            result = 'Pending'

            # Compile additional info
            additional_info = {
                'supporting_factors': supporting_factors,
                'volatility': volatility,
                'volume_signal': volume_signal,
                'patterns': patterns,
                'momentum': momentum,
                'market_structure': market_structure,
            }

            # End time for performance measurement
            analysis_end = time.time()
            analysis_time = analysis_end - analysis_start

            logger.info(f"Analysis time for {symbol} {timeframe}: {analysis_time:.2f}s")

            # Create the signal data dictionary
            signal_data = {
                'time_generated': current_time,
                'timestamp': timestamp,
                'symbol': symbol,
                'price': current_price,
                'price_change_24h': change_24h,
                'signal': signal,
                'stop_loss': stop_loss,
                'take_profit': take_profit,
                'success_rate': success_rate,
                'result': result,
                'additional_info': additional_info,
                'timeframe': timeframe,
                'win_probability': success_rate
            }

            return signal_data

        except Exception as e:
            logger.error(f"Error generating signal for {symbol} {timeframe}: {str(e)}")
            return {'signal': 'NO_SIGNAL'}

    def calculate_24h_change(self, df):
        """Calculates the 24-hour price change percentage."""
        try:
            current_price = df['close'].iloc[-1]
            past_time = df.index[-1] - timedelta(hours=24)
            past_prices = df[df.index >= past_time]['close']
            if not past_prices.empty:
                past_price = past_prices.iloc[0]
                change = ((current_price - past_price) / past_price) * 100
            else:
                change = 0.0
            return round(change, 2)
        except Exception as e:
            logger.error(f"Error calculating 24h change: {str(e)}")
            return 0.0

    def analyze_market_structure(self, df):
        """Analyzes the market structure to identify trends and key levels."""
        try:
            swing_window = self.parameters['swing_window']
            highs = df['high'].rolling(window=swing_window, center=True).max()
            lows = df['low'].rolling(window=swing_window, center=True).min()

            swing_highs = df['high'][(df['high'] == highs) & (highs.shift() != highs)]
            swing_lows = df['low'][(df['low'] == lows) & (lows.shift() != lows)]

            # Identify trend based on swing points
            swings = pd.concat([swing_highs, swing_lows]).sort_index()
            trend = self.identify_trend(swings)

            # Identify key levels
            key_levels = self.identify_key_levels(df)

            return {
                'trend': trend,
                'swings': swings,
                'key_levels': key_levels
            }
        except Exception as e:
            logger.error(f"Error analyzing market structure: {str(e)}")
            return {'trend': 'UNKNOWN', 'swings': None, 'key_levels': []}

    def identify_trend(self, swings):
        """Determines the current market trend based on swing highs and lows."""
        try:
            if swings.empty:
                return 'UNKNOWN'

            # Consider last few swing points
            last_swings = swings.iloc[-4:]

            if len(last_swings) < 4:
                return 'UNKNOWN'

            swing_prices = last_swings.values
            swing_directions = ['high' if price in swings['high'].values else 'low' for price in swing_prices]

            # Analyze swing patterns
            trend = 'SIDEWAYS'
            if all(direction == 'high' for direction in swing_directions[::2]) and all(direction == 'low' for direction in swing_directions[1::2]):
                if all(x < y for x, y in zip(swing_prices[::2], swing_prices[2::2])) and \
                   all(x > y for x, y in zip(swing_prices[1::2], swing_prices[3::2])):
                    trend = 'UPTREND'
            elif all(direction == 'low' for direction in swing_directions[::2]) and all(direction == 'high' for direction in swing_directions[1::2]):
                if all(x > y for x, y in zip(swing_prices[::2], swing_prices[2::2])) and \
                   all(x < y for x, y in zip(swing_prices[1::2], swing_prices[3::2])):
                    trend = 'DOWNTREND'

            return trend
        except Exception as e:
            logger.error(f"Error identifying trend: {str(e)}")
            return 'UNKNOWN'

    def identify_key_levels(self, df):
        """Identifies key support and resistance levels."""
        try:
            tolerance = self.parameters['pattern_tolerance']
            price_levels = []

            # Find levels where price touches multiple times
            for price in df['close']:
                nearby_levels = [level for level in price_levels if abs(level - price) / price < tolerance]
                if not nearby_levels:
                    price_levels.append(price)
                else:
                    # Adjust existing level to average
                    idx = price_levels.index(nearby_levels[0])
                    price_levels[idx] = (price_levels[idx] + price) / 2

            # Filter levels with sufficient touches
            key_levels = [level for level in price_levels if (df['close'] - level).abs().lt(tolerance * df['close']).sum() >= 3]

            key_levels = sorted(key_levels)
            return key_levels
        except Exception as e:
            logger.error(f"Error identifying key levels: {str(e)}")
            return []

    def calculate_volatility(self, df):
        """Calculates recent price volatility."""
        try:
            window = self.parameters['volatility_window']
            returns = df['close'].pct_change()
            volatility = returns.rolling(window=window).std().iloc[-1]
            return volatility
        except Exception as e:
            logger.error(f"Error calculating volatility: {str(e)}")
            return 0.0

    def analyze_volume(self, df):
        """Analyzes volume trends."""
        try:
            avg_volume = df['volume'].rolling(window=20).mean().iloc[-1]
            current_volume = df['volume'].iloc[-1]

            if current_volume > avg_volume * 1.5:
                return 'HIGH_VOLUME'
            elif current_volume < avg_volume * 0.5:
                return 'LOW_VOLUME'
            else:
                return 'AVERAGE_VOLUME'
        except Exception as e:
            logger.error(f"Error analyzing volume: {str(e)}")
            return 'UNKNOWN'

    def detect_patterns(self, df):
        """Detects advanced price patterns."""
        try:
            patterns = []
            tolerance = self.parameters['pattern_tolerance']

            # Head and Shoulders pattern detection
            if self.detect_head_and_shoulders(df, tolerance):
                patterns.append('HEAD_AND_SHOULDERS')

            # Double Top and Double Bottom
            if self.detect_double_top(df, tolerance):
                patterns.append('DOUBLE_TOP')
            elif self.detect_double_bottom(df, tolerance):
                patterns.append('DOUBLE_BOTTOM')

            # Flag and Pennant patterns
            if self.detect_flag_pennant(df):
                patterns.append('FLAG_OR_PENNANT')

            # Triangles and wedges can be added here similarly

            return patterns
        except Exception as e:
            logger.error(f"Error detecting patterns: {str(e)}")
            return []

    def detect_head_and_shoulders(self, df, tolerance):
        """Detects Head and Shoulders pattern."""
        # Implement pattern recognition logic
        return False  # Placeholder for actual implementation

    def detect_double_top(self, df, tolerance):
        """Detects Double Top pattern."""
        recent_highs = df['high'].rolling(window=5).max().dropna()
        peaks = recent_highs[(recent_highs.shift(1) < recent_highs) & (recent_highs.shift(-1) < recent_highs)]
        if len(peaks) >= 2:
            first_peak = peaks.iloc[-2]
            second_peak = peaks.iloc[-1]
            if abs(first_peak - second_peak) / first_peak < tolerance:
                return True
        return False

    def detect_double_bottom(self, df, tolerance):
        """Detects Double Bottom pattern."""
        recent_lows = df['low'].rolling(window=5).min().dropna()
        troughs = recent_lows[(recent_lows.shift(1) > recent_lows) & (recent_lows.shift(-1) > recent_lows)]
        if len(troughs) >= 2:
            first_trough = troughs.iloc[-2]
            second_trough = troughs.iloc[-1]
            if abs(first_trough - second_trough) / first_trough < tolerance:
                return True
        return False

    def detect_flag_pennant(self, df):
        """Detects Flag or Pennant pattern."""
        # Implement pattern recognition logic
        return False  # Placeholder for actual implementation

    def analyze_momentum(self, df):
        """Analyzes price momentum."""
        try:
            momentum_window = self.parameters['momentum_window']
            df['momentum'] = df['close'] - df['close'].shift(momentum_window)
            current_momentum = df['momentum'].iloc[-1]
            if current_momentum > 0:
                return 'BULLISH_MOMENTUM'
            elif current_momentum < 0:
                return 'BEARISH_MOMENTUM'
            else:
                return 'NEUTRAL_MOMENTUM'
        except Exception as e:
            logger.error(f"Error analyzing momentum: {str(e)}")
            return 'UNKNOWN'

    def analyze_order_book(self, symbol):
        """Analyzes the order book for buy/sell pressure."""
        # Placeholder function since order book data is not accessible
        return 'NEUTRAL'

    def determine_signal(self, market_structure, volatility, volume_signal, patterns, momentum, current_price):
        """Determines the trading signal based on analyzed factors."""
        signal_strength = 0
        supporting_factors = []

        # Trend analysis
        trend = market_structure['trend']
        if trend == 'UPTREND':
            signal_strength += 2
            supporting_factors.append('Strong uptrend identified.')
        elif trend == 'DOWNTREND':
            signal_strength -= 2
            supporting_factors.append('Strong downtrend identified.')

        # Volume analysis
        if volume_signal == 'HIGH_VOLUME':
            signal_strength += 1
            supporting_factors.append('High trading volume detected.')
        elif volume_signal == 'LOW_VOLUME':
            signal_strength -= 1
            supporting_factors.append('Low trading volume detected.')

        # Volatility consideration
        if volatility < 0.01:
            signal_strength -= 1
            supporting_factors.append('Low volatility, possible sideways movement.')
        elif volatility > 0.05:
            signal_strength -= 1
            supporting_factors.append('High volatility, increased risk.')

        # Pattern recognition
        for pattern in patterns:
            if pattern == 'DOUBLE_BOTTOM':
                signal_strength += 2
                supporting_factors.append('Bullish Double Bottom pattern detected.')
            elif pattern == 'DOUBLE_TOP':
                signal_strength -= 2
                supporting_factors.append('Bearish Double Top pattern detected.')
            elif pattern == 'HEAD_AND_SHOULDERS':
                signal_strength -= 2
                supporting_factors.append('Bearish Head and Shoulders pattern detected.')
            elif pattern == 'FLAG_OR_PENNANT':
                signal_strength += 1
                supporting_factors.append('Continuation pattern detected.')

        # Momentum
        if momentum == 'BULLISH_MOMENTUM':
            signal_strength += 1
            supporting_factors.append('Positive price momentum.')
        elif momentum == 'BEARISH_MOMENTUM':
            signal_strength -= 1
            supporting_factors.append('Negative price momentum.')

        # Order book analysis (placeholder)
        order_book_signal = 'NEUTRAL'  # Replace with actual analysis if available

        if order_book_signal == 'BUY_PRESSURE':
            signal_strength += 1
            supporting_factors.append('Order book shows buying pressure.')
        elif order_book_signal == 'SELL_PRESSURE':
            signal_strength -= 1
            supporting_factors.append('Order book shows selling pressure.')

        # Sideways trend handling
        if abs(signal_strength) <= self.parameters['sideways_trend_threshold']:
            # Check if price is within a range-bound market
            if self.is_range_bound_market(market_structure['key_levels'], current_price):
                signal_strength = 0
                supporting_factors.append('Range-bound market detected.')

        return signal_strength, supporting_factors

    def is_range_bound_market(self, key_levels, current_price):
        """Determines if the market is range-bound."""
        try:
            if not key_levels or len(key_levels) < 2:
                return False

            lower_bound = min(key_levels)
            upper_bound = max(key_levels)
            price_range = upper_bound - lower_bound
            tolerance = self.parameters['range_bound_tolerance'] * current_price

            return abs(current_price - lower_bound) <= tolerance or abs(current_price - upper_bound) <= tolerance
        except Exception as e:
            logger.error(f"Error determining range-bound market: {str(e)}")
            return False

    def get_signal_type(self, signal_strength):
        """Determines the signal type based on the signal strength."""
        if signal_strength >= self.parameters['trend_strength_threshold']:
            return 'STRONG_BUY'
        elif signal_strength >= 2:
            return 'BUY'
        elif signal_strength <= -self.parameters['trend_strength_threshold']:
            return 'STRONG_SELL'
        elif signal_strength <= -2:
            return 'SELL'
        elif signal_strength == 0:
            return 'NEUTRAL'
        else:
            return 'NO_SIGNAL'

    def calculate_trade_levels(self, df, signal, current_price, market_structure):
        """Calculates stop-loss and take-profit levels."""
        try:
            key_levels = market_structure['key_levels']
            if signal in ['BUY', 'STRONG_BUY']:
                # Stop-loss below the nearest support
                support_levels = [level for level in key_levels if level < current_price]
                if support_levels:
                    stop_loss = min(support_levels)
                else:
                    stop_loss = current_price * 0.98  # Default to 2% below current price

                # Take-profit at the nearest resistance
                resistance_levels = [level for level in key_levels if level > current_price]
                if resistance_levels:
                    take_profit = max(resistance_levels)
                else:
                    take_profit = current_price * 1.02  # Default to 2% above current price
            elif signal in ['SELL', 'STRONG_SELL']:
                # Stop-loss above the nearest resistance
                resistance_levels = [level for level in key_levels if level > current_price]
                if resistance_levels:
                    stop_loss = max(resistance_levels)
                else:
                    stop_loss = current_price * 1.02  # Default to 2% above current price

                # Take-profit at the nearest support
                support_levels = [level for level in key_levels if level < current_price]
                if support_levels:
                    take_profit = min(support_levels)
                else:
                    take_profit = current_price * 0.98  # Default to 2% below current price
            else:
                stop_loss = None
                take_profit = None

            return round(stop_loss, 8) if stop_loss else None, round(take_profit, 8) if take_profit else None
        except Exception as e:
            logger.error(f"Error calculating trade levels: {str(e)}")
            return None, None

    async def save_signal(self, signal_data):
        """Saves the generated signal to the database."""
        try:
            Signal.objects.create(
                symbol=signal_data['symbol'],
                timestamp=signal_data['timestamp'],
                timeframe=signal_data['timeframe'],
                signal_type=signal_data['signal'],
                price=Decimal(str(signal_data['price'])),
                stop_loss=Decimal(str(signal_data['stop_loss'])) if signal_data['stop_loss'] else None,
                take_profit=Decimal(str(signal_data['take_profit'])) if signal_data['take_profit'] else None,
                result=signal_data['result'],
                win_probability=signal_data['win_probability'],
                success_rate=signal_data['success_rate'],
                price_change_24h=signal_data['price_change_24h'],
                run_date=signal_data['time_generated'],
                additional_info=signal_data['additional_info']
            )
            logger.info(f"Saved signal for {signal_data['symbol']} ({signal_data['timeframe']}): {signal_data['signal']}")
        except Exception as e:
            logger.error(f"Error saving signal for {signal_data['symbol']} ({signal_data['timeframe']}): {str(e)}")

    async def run_bot(self):
        """Main execution loop of the bot."""
        try:
            await self.fetch_symbols()
            while True:
                start_time = time.time()
                tasks = []

                for symbol in self.symbols:
                    tasks.append(self.process_symbol(symbol))

                await asyncio.gather(*tasks)

                end_time = time.time()
                cycle_time = end_time - start_time
                sleep_time = max(60 - cycle_time, 0)
                logger.info(f"Cycle completed in {cycle_time:.2f}s, sleeping for {sleep_time:.2f}s")
                await asyncio.sleep(sleep_time)
        except Exception as e:
            logger.error(f"Error in run_bot: {str(e)}")

    # Performance Optimization and Parameter Tuning

    def optimize_parameters(self):
        """Performs parameter optimization using grid search."""
        # Placeholder for actual implementation
        pass

    def cross_validate_strategy(self):
        """Implements cross-validation to assess strategy robustness."""
        # Placeholder for actual implementation
        pass

    def walk_forward_optimization(self):
        """Implements walk-forward optimization."""
        # Placeholder for actual implementation
        pass

    async def monitor_performance(self):
        """Monitors and updates signal results."""
        while True:
            try:
                # Fetch pending signals
                pending_signals = Signal.objects.filter(result='Pending')

                for signal in pending_signals:
                    current_price = await self.get_current_price(signal.symbol)
                    if current_price is None:
                        continue

                    if signal.stop_loss and current_price <= signal.stop_loss:
                        signal.result = 'Lost'
                        signal.save()
                        logger.info(f"Signal for {signal.symbol} ({signal.timeframe}) has Lost.")
                    elif signal.take_profit and current_price >= signal.take_profit:
                        signal.result = 'Won'
                        signal.save()
                        logger.info(f"Signal for {signal.symbol} ({signal.timeframe}) has Won.")

                await asyncio.sleep(60)  # Check every minute

            except Exception as e:
                logger.error(f"Error in monitor_performance: {str(e)}")
                await asyncio.sleep(60)

    async def get_current_price(self, symbol):
        """Gets the current price for a symbol."""
        try:
            latest_kline = Kline.objects.filter(
                symbol=symbol,
                timeframe='1m'
            ).order_by('-timestamp').first()
            if latest_kline:
                return latest_kline.close
            else:
                return None
        except Exception as e:
            logger.error(f"Error fetching current price for {symbol}: {str(e)}")
            return None

if __name__ == '__main__':
    bot = PriceActionBot()
    try:
        loop = asyncio.get_event_loop()
        loop.create_task(bot.run_bot())
        loop.create_task(bot.monitor_performance())
        loop.run_forever()
    except KeyboardInterrupt:
        logger.info("Stopping bot...")

Fill in the placeholders for optimize_parameters, cross_validate_strategy, and walk_forward_optimization with actual implementations. Implement detailed logic for detecting advanced patterns like Head and Shoulders, Flags, Pennants, and Triangles and add more chart patterns and more candlestick patterns. Consider using statistical methods to validate patterns and reduce false signals. 
Rewrite the whole code with all features including the changes and send me complete code