import numpy as np
import asyncio
from .models import Kline
from scipy.stats import linregress
from collections import deque
import logging
import pandas as pd
from datetime import timedelta
import json
from decimal import Decimal
from .models import Signal

# Django setup
import django
from django.utils import timezone

# 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 MarketStructure:
    def __init__(self, df):
        self.df = df
        self.swings = []
        self.current_trend = None
        self.key_levels = []
        
    def identify_swing_points(self, window=5):
        """Identifies swing highs and lows using a rolling window"""
        highs = self.df['high'].rolling(window=window, center=True).apply(
            lambda x: 1 if x.index[len(x)//2] == x.idxmax() else 0
        )
        lows = self.df['low'].rolling(window=window, center=True).apply(
            lambda x: -1 if x.index[len(x)//2] == x.idxmin() else 0
        )
        self.swings = highs + lows
        return self.swings
    
    def analyze_trend_structure(self):
        """Analyzes market structure based on swing points"""
        if len(self.swings) < 4:
            return None
            
        recent_swings = [x for x in self.swings[-4:] if x != 0]
        
        # Check for uptrend structure
        if all(recent_swings[i] > recent_swings[i-1] for i in range(1, len(recent_swings))):
            return "UPTREND"
            
        # Check for downtrend structure    
        elif all(recent_swings[i] < recent_swings[i-1] for i in range(1, len(recent_swings))):
            return "DOWNTREND"
            
        return "SIDEWAYS"

    def find_key_levels(self, sensitivity=0.02):
        """Identifies key support and resistance levels"""
        price_clusters = []
        current_cluster = []
        
        for price in self.df['close']:
            if not current_cluster or abs(price - np.mean(current_cluster)) / price < sensitivity:
                current_cluster.append(price)
            else:
                if len(current_cluster) > 3:  # Consider it a key level if price visited multiple times
                    price_clusters.append(np.mean(current_cluster))
                current_cluster = [price]
                
        self.key_levels = sorted(price_clusters)
        return self.key_levels

class PriceActionSignalGenerator:
    def __init__(self, df, symbol, interval):
        self.df = df
        self.symbol = symbol
        self.interval = interval
        self.market_structure = MarketStructure(df)
        self.signals = []
        
    async def analyze_price_action(self):
        """Comprehensive price action analysis"""
        try:
            # 1. Market Structure Analysis
            swing_points = self.market_structure.identify_swing_points()
            trend = self.market_structure.analyze_trend_structure()
            key_levels = self.market_structure.find_key_levels()
            
            # 2. Volatility Analysis
            recent_volatility = self.calculate_volatility()
            
            # 3. Volume Analysis
            volume_signal = self.analyze_volume_quality()
            
            # 4. Pattern Recognition
            chart_pattern = self.detect_chart_patterns()
            candlestick_signal = self.analyze_candlestick_patterns()
            
            # 5. Price Momentum
            momentum = self.analyze_price_momentum()
            
            # Combine all analyses for final signal
            signal_data = self.generate_trading_signal(
                trend=trend,
                key_levels=key_levels,
                volatility=recent_volatility,
                volume_signal=volume_signal,
                chart_pattern=chart_pattern,
                candlestick_signal=candlestick_signal,
                momentum=momentum
            )
            
            return signal_data
            
        except Exception as e:
            logging.error(f"Error in price action analysis: {str(e)}")
            return None
            
    def calculate_volatility(self, window=20):
        """Calculates recent price volatility"""
        returns = self.df['close'].pct_change()
        volatility = returns.rolling(window=window).std()
        return volatility.iloc[-1]
        
    def analyze_volume_quality(self):
        """Analyzes volume characteristics"""
        avg_volume = self.df['volume'].rolling(window=20).mean()
        current_volume = self.df['volume'].iloc[-1]
        
        volume_trend = 'INCREASING' if current_volume > avg_volume.iloc[-1] else 'DECREASING'
        
        # Volume climax detection
        if current_volume > avg_volume.iloc[-1] * 3:
            return 'CLIMAX'
        
        return volume_trend
        
    def detect_chart_patterns(self):
        """Detects various chart patterns"""
        patterns = []
        
        # Double Top Pattern
        if self.check_double_top():
            patterns.append('DOUBLE_TOP')
            
        # Double Bottom Pattern    
        if self.check_double_bottom():
            patterns.append('DOUBLE_BOTTOM')
            
        # Triangle Patterns
        triangle = self.check_triangle_patterns()
        if triangle:
            patterns.append(triangle)
            
        # Channel Pattern
        if self.check_channel_pattern():
            patterns.append('CHANNEL')
            
        return patterns
        
    def check_double_top(self, tolerance=0.02):
        """Checks for double top pattern"""
        recent_highs = self.df['high'].rolling(window=5).max()
        tops = [i for i in range(len(recent_highs)-20, len(recent_highs)) 
               if abs(recent_highs.iloc[i] - recent_highs.iloc[i-1]) / recent_highs.iloc[i] < tolerance]
               
        return len(tops) >= 2
        
    def check_double_bottom(self, tolerance=0.02):
        """Checks for double bottom pattern"""
        recent_lows = self.df['low'].rolling(window=5).min()
        bottoms = [i for i in range(len(recent_lows)-20, len(recent_lows))
                  if abs(recent_lows.iloc[i] - recent_lows.iloc[i-1]) / recent_lows.iloc[i] < tolerance]
                  
        return len(bottoms) >= 2
        
    def check_triangle_patterns(self):
        """Identifies triangle patterns"""
        recent_highs = self.df['high'].rolling(window=5).max().dropna()
        recent_lows = self.df['low'].rolling(window=5).min().dropna()
        
        high_slope = linregress(range(len(recent_highs)), recent_highs)[0]
        low_slope = linregress(range(len(recent_lows)), recent_lows)[0]
        
        if abs(high_slope) < 0.001 and abs(low_slope) < 0.001:
            return 'RECTANGLE'
        elif high_slope < -0.001 and low_slope > 0.001:
            return 'CONVERGING_TRIANGLE'
        elif high_slope > 0.001 and low_slope < -0.001:
            return 'EXPANDING_TRIANGLE'
            
        return None
        
    def check_channel_pattern(self):
        """Detects price channel formation"""
        recent_prices = self.df['close'].tail(20)
        slope, _, r_value, _, _ = linregress(range(len(recent_prices)), recent_prices)
        
        return abs(r_value) > 0.85  # Strong linear relationship indicates channel
        
    def analyze_candlestick_patterns(self):
        """Advanced candlestick pattern analysis"""
        patterns = []
        
        # Engulfing Pattern
        if self.check_engulfing():
            patterns.append('ENGULFING')
            
        # Pin Bar Formation    
        if self.check_pin_bar():
            patterns.append('PIN_BAR')
            
        # Inside Bar Pattern
        if self.check_inside_bar():
            patterns.append('INSIDE_BAR')
            
        return patterns
        
    def check_engulfing(self):
        """Checks for bullish/bearish engulfing patterns"""
        current = self.df.iloc[-1]
        previous = self.df.iloc[-2]
        
        bullish_engulfing = (previous['close'] < previous['open'] and
                            current['close'] > current['open'] and
                            current['open'] < previous['close'] and
                            current['close'] > previous['open'])
                            
        bearish_engulfing = (previous['close'] > previous['open'] and
                            current['close'] < current['open'] and
                            current['open'] > previous['close'] and
                            current['close'] < previous['open'])
                            
        return 'BULLISH_ENGULFING' if bullish_engulfing else 'BEARISH_ENGULFING' if bearish_engulfing else None
        
    def check_pin_bar(self, min_ratio=2):
        """Identifies pin bar (hammer/shooting star) formations"""
        current = self.df.iloc[-1]
        
        body = abs(current['close'] - current['open'])
        upper_wick = current['high'] - max(current['open'], current['close'])
        lower_wick = min(current['open'], current['close']) - current['low']
        
        if upper_wick > body * min_ratio and lower_wick < body:
            return 'SHOOTING_STAR'
        elif lower_wick > body * min_ratio and upper_wick < body:
            return 'HAMMER'
            
        return None
        
    def check_inside_bar(self):
        """Detects inside bar formation"""
        current = self.df.iloc[-1]
        previous = self.df.iloc[-2]
        
        inside_bar = (current['high'] < previous['high'] and
                     current['low'] > previous['low'])
                     
        return inside_bar
        
    def analyze_price_momentum(self):
        """Analyzes recent price momentum"""
        recent_closes = self.df['close'].tail(5)
        momentum = (recent_closes.iloc[-1] - recent_closes.iloc[0]) / recent_closes.iloc[0]
        
        if momentum > 0.02:
            return 'STRONG_BULLISH'
        elif momentum < -0.02:
            return 'STRONG_BEARISH'
        elif momentum > 0:
            return 'WEAK_BULLISH'
        else:
            return 'WEAK_BEARISH'
            
    def generate_trading_signal(self, trend, key_levels, volatility, volume_signal,
                              chart_pattern, candlestick_signal, momentum):
        """Generates final trading signal with supporting evidence"""
        
        current_price = self.df['close'].iloc[-1]
        nearest_support = min((level for level in key_levels if level < current_price), default=None)
        nearest_resistance = min((level for level in key_levels if level > current_price), default=None)
        
        signal_strength = 0
        signal_direction = None
        supporting_factors = []
        
        # Trend Analysis
        if trend == "UPTREND":
            signal_strength += 2
            signal_direction = "BUY"
            supporting_factors.append("Strong uptrend structure")
        elif trend == "DOWNTREND":
            signal_strength += 2
            signal_direction = "SELL"
            supporting_factors.append("Strong downtrend structure")
            
        # Pattern Analysis    
        for pattern in chart_pattern:
            if pattern in ['DOUBLE_BOTTOM', 'CONVERGING_TRIANGLE'] and signal_direction == "BUY":
                signal_strength += 1
                supporting_factors.append(f"Bullish {pattern} pattern")
            elif pattern in ['DOUBLE_TOP', 'EXPANDING_TRIANGLE'] and signal_direction == "SELL":
                signal_strength += 1
                supporting_factors.append(f"Bearish {pattern} pattern")
                
        # Candlestick Analysis
        for pattern in candlestick_signal:
            if pattern in ['BULLISH_ENGULFING', 'HAMMER'] and signal_direction == "BUY":
                signal_strength += 1
                supporting_factors.append(f"Bullish {pattern} candlestick")
            elif pattern in ['BEARISH_ENGULFING', 'SHOOTING_STAR'] and signal_direction == "SELL":
                signal_strength += 1
                supporting_factors.append(f"Bearish {pattern} candlestick")
                
        # Volume Confirmation        
        if volume_signal == 'INCREASING' and signal_direction:
            signal_strength += 1
            supporting_factors.append("Strong volume confirmation")
            
        # Momentum Alignment    
        if momentum.startswith('STRONG') and (
            (momentum.endswith('BULLISH') and signal_direction == "BUY") or
            (momentum.endswith('BEARISH') and signal_direction == "SELL")
        ):
            signal_strength += 1
            supporting_factors.append("Strong momentum alignment")
            
        # Generate final signal
        if signal_strength >= 4:
            final_signal = f"STRONG_{signal_direction}"
            success_rate = 80
        elif signal_strength >= 2:
            final_signal = signal_direction
            success_rate = 65
        elif trend == "SIDEWAYS" and self.check_channel_pattern():
            final_signal = "NEUTRAL"
            success_rate = 50
            supporting_factors.append("Clear trading channel identified")
        else:
            final_signal = "NO_SIGNAL"
            success_rate = 0
            
        # Calculate stop loss and take profit
        if final_signal.endswith('BUY'):
            stop_loss = nearest_support * 0.99 if nearest_support else current_price * 0.98
            take_profit = nearest_resistance * 1.01 if nearest_resistance else current_price * 1.03
        elif final_signal.endswith('SELL'):
            stop_loss = nearest_resistance * 1.01 if nearest_resistance else current_price * 1.02
            take_profit = nearest_support * 0.99 if nearest_support else current_price * 0.97
        else:
            stop_loss = take_profit = None
            
        return {
            'timestamp': self.df.index[-1],
            'current_time': timezone.now(),
            'symbol': self.symbol,
            'interval': self.interval,
            'price': current_price,
            'signal': final_signal,
            'stop_loss': stop_loss,
            'take_profit': take_profit,
            'success_rate': success_rate,
            'supporting_factors': supporting_factors,
            'trend': trend,
            'volatility': volatility,
            'volume_signal': volume_signal,
            'chart_patterns': chart_pattern,
            'candlestick_patterns': candlestick_signal,
            'momentum': momentum
        }
class EnhancedPriceActionBot:
    def __init__(self, intervals, lookback, db_config):
        self.intervals = intervals
        self.lookback = lookback
        self.db_config = db_config
        self.signal_generators = {}
        self.signals_cache = {}
        
    async def initialize(self):
        """Initialize signal generators for all symbols"""
        try:
            from django.db import connection
            async with connection.cursor() as cursor:
                await cursor.execute("SELECT DISTINCT symbol FROM klines")
                symbols = await cursor.fetchall()
                
            for symbol in symbols:
                self.signal_generators[symbol] = {}
                for interval in self.intervals:
                    df = await self.fetch_historical_data(symbol, interval)
                    self.signal_generators[symbol][interval] = PriceActionSignalGenerator(
                        df, symbol, interval
                    )
        except Exception as e:
            logging.error(f"Initialization error: {str(e)}")
            
    async def fetch_historical_data(self, symbol, interval):
        """Fetch historical data from Django ORM"""
        try:
            from django.db.models import Q
            lookback_time = timezone.now() - timedelta(days=self.lookback)
            
            queryset = Kline.objects.filter(
                Q(symbol=symbol) & 
                Q(interval=interval) &
                Q(timestamp__gte=lookback_time)
            ).order_by('timestamp')
            
            data = list(queryset.values())
            df = pd.DataFrame(data)
            df.set_index('timestamp', inplace=True)
            return df
            
        except Exception as e:
            logging.error(f"Data fetch error: {str(e)}")
            return None
            
    async def analyze_all_timeframes(self, symbol):
        """Analyze price action across all timeframes"""
        try:
            signals = {}
            for interval in self.intervals:
                if symbol in self.signal_generators and interval in self.signal_generators[symbol]:
                    signal = await self.signal_generators[symbol][interval].analyze_price_action()
                    signals[interval] = signal
                    
            # Combine signals across timeframes
            combined_signal = self.combine_timeframe_signals(signals)
            return combined_signal
            
        except Exception as e:
            logging.error(f"Timeframe analysis error: {str(e)}")
            return None
            
    def combine_timeframe_signals(self, signals):
        """Combine signals from multiple timeframes"""
        signal_scores = {
            'BUY': 0,
            'SELL': 0,
            'NEUTRAL': 0
        }
        
        # Weight factors for different timeframes
        weights = {
            '1h': 0.4,
            '15m': 0.3,
            '5m': 0.2,
            '1m': 0.1
        }
        
        for interval, signal in signals.items():
            if not signal:
                continue
                
            base_signal = signal['signal'].replace('STRONG_', '')
            weight = weights.get(interval, 0.1)
            
            if 'STRONG' in signal['signal']:
                weight *= 1.5
                
            signal_scores[base_signal] += weight
            
        # Determine final signal
        max_score = max(signal_scores.values())
        if max_score == 0:
            return None
            
        strongest_signal = max(signal_scores.items(), key=lambda x: x[1])[0]
        confirmation_level = max_score / sum(weights.values())
        
        # Combine supporting factors from all timeframes
        all_factors = []
        for interval, signal in signals.items():
            if signal and signal['supporting_factors']:
                all_factors.extend([f"{interval}: {factor}" for factor in signal['supporting_factors']])
                
        # Get reference signal for price levels
        reference_signal = signals.get('1h') or signals.get('15m')
        if not reference_signal:
            return None
            
        return {
            'timestamp': reference_signal['timestamp'],
            'current_time': timezone.now(),
            'symbol': reference_signal['symbol'],
            'price': reference_signal['price'],
            'signal': f"STRONG_{strongest_signal}" if confirmation_level > 0.7 else strongest_signal,
            'stop_loss': reference_signal['stop_loss'],
            'take_profit': reference_signal['take_profit'],
            'success_rate': min(95, int(confirmation_level * 100)),
            'supporting_factors': all_factors,
            'timeframe_signals': signals
        }
        
    async def monitor_signals(self):
        """Monitor and update signal results"""
        while True:
            try:
                for symbol, intervals in self.signals_cache.items():
                    for interval, signals in intervals.items():
                        for signal in signals:
                            if signal['result'] == 'PENDING':
                                current_price = await self.get_current_price(symbol)
                                
                                if current_price <= signal['stop_loss']:
                                    signal['result'] = 'LOST'
                                elif current_price >= signal['take_profit']:
                                    signal['result'] = 'WON'
                                    
                await asyncio.sleep(60)  # Check every minute
                
            except Exception as e:
                logging.error(f"Signal monitoring error: {str(e)}")
                await asyncio.sleep(60)
                
    async def get_current_price(self, symbol):
        """Get current price from database"""
        try:
            latest_kline = await Kline.objects.filter(
                symbol=symbol,
                interval='1m'
            ).order_by('-timestamp').first()
            
            return latest_kline.close if latest_kline else None
            
        except Exception as e:
            logging.error(f"Price fetch error: {str(e)}")
            return None
            
    async def run(self):
        """Main bot execution loop"""
        try:
            await self.initialize()
            
            # Start signal monitoring in background
            asyncio.create_task(self.monitor_signals())
            
            while True:
                for symbol in self.signal_generators.keys():
                    signal = await self.analyze_all_timeframes(symbol)
                    
                    if signal:
                        # Cache signal for monitoring
                        if symbol not in self.signals_cache:
                            self.signals_cache[symbol] = {}
                        if signal['interval'] not in self.signals_cache[symbol]:
                            self.signals_cache[symbol][signal['interval']] = []
                            
                        signal['result'] = 'PENDING'
                        self.signals_cache[symbol][signal['interval']].append(signal)
                        
                        # Save signal to database
                        await self.save_signal(signal)
                        
                await asyncio.sleep(60)  # Run analysis every minute
                
        except Exception as e:
            logging.error(f"Bot execution error: {str(e)}")
            
    async def save_signal(self, signal):
        """Save generated signal to database with updated model fields"""
        try:
        # Calculate risk/reward ratio
            if signal['take_profit'] and signal['stop_loss']:
                risk = abs(signal['price'] - signal['stop_loss'])
                reward = abs(signal['take_profit'] - signal['price'])
                risk_reward_ratio = reward / risk if risk != 0 else None
            else:
                risk_reward_ratio = None
            
        # Get 24h price change
            yesterday_price = await self.get_24h_ago_price(signal['symbol'])
            price_change_24h = ((signal['price'] - yesterday_price) / yesterday_price * 100) if yesterday_price else 0.0
        
        # Determine market regime based on trend analysis
            if 'timeframe_signals' in signal and '1h' in signal['timeframe_signals']:
                market_regime = signal['timeframe_signals']['1h'].get('trend', 'UNKNOWN')
            else:
                market_regime = 'UNKNOWN'
            
        # Prepare additional info
            additional_info = {
                'supporting_factors': signal['supporting_factors'],
                'timeframe_analysis': {
                    tf: {
                        'trend': s.get('trend'),
                        'volume_signal': s.get('volume_signal'),
                        'chart_patterns': s.get('chart_patterns'),
                        'candlestick_patterns': s.get('candlestick_patterns'),
                        'momentum': s.get('momentum')
                    } for tf, s in signal['timeframe_signals'].items() if s
                }
            }

            await Signal.objects.create(
                symbol=signal['symbol'],
                timestamp=signal['timestamp'],
                timeframe=signal.get('interval', '1h'),  # Default to 1h if not specified
                signal_type=signal['signal'],
                price=Decimal(str(signal['price'])),
                stop_loss=Decimal(str(signal['stop_loss'])) if signal['stop_loss'] else Decimal('0'),
                take_profit=Decimal(str(signal['take_profit'])) if signal['take_profit'] else Decimal('0'),
                result='PENDING',
                win_probability=signal['success_rate'] / 100,  # Convert percentage to decimal
                success_rate=signal['success_rate'],
                price_change_24h=price_change_24h,
                risk_reward_ratio=risk_reward_ratio,
                market_regime=market_regime,
                run_date=signal['current_time'],
                additional_info=additional_info
            )
        except Exception as e:
            logging.error(f"Signal save error: {str(e)}")

    async def get_24h_ago_price(self, symbol):
        """Get price from 24 hours ago"""
        try:
            yesterday = timezone.now() - timedelta(days=1)
            kline = await Kline.objects.filter(
                symbol=symbol,
                interval='1h',
                timestamp__lte=yesterday
            ).order_by('-timestamp').first()
        
            return float(kline.close) if kline else None
        
        except Exception as e:
            logging.error(f"24h price fetch error: {str(e)}")
            return None

Based on features of my bot, what would be a rough estimate of the signals accuracy in percentage. And is it production ready or does it lack critical features?