# source: https://raw.githubusercontent.com/remiotore/freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/volatim_improved.py
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
import numpy as np
import pandas as pd
from freqtrade.persistence import Trade
from datetime import datetime
import freqtrade.vendor.qtpylib.indicators as qtpylib
import talib.abstract as ta

class Github_remiotore_freqtrade__volatim_improved__20260111_210550(IStrategy):
    # Strategy parameters
    stoploss = -0.08  # Improved from -0.1 to -0.08 for better risk management
    can_short = True
    trailing_stop = True
    trailing_stop_positive = 0.015  # 1.5%
    trailing_stop_positive_offset = 0.025  # 2.5%
    
    timeframe = '5m'  # Same as original
    
    # ROI table for systematic profit taking
    minimal_roi = {
        "0": 0.12,   # 12% initial target
        "15": 0.08,  # 8% after 15 minutes
        "30": 0.05,  # 5% after 30 minutes
        "60": 0.03,  # 3% after 1 hour
        "120": 0.015 # 1.5% after 2 hours
    }
    
    # Strategy-specific parameters
    volatility_window = 20
    returns_window = 10
    rsi_period = 14
    volume_window = 20

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Enhanced volatility-based indicators with improved signal generation
        """
        
        # Basic price action
        dataframe['returns'] = dataframe['close'].pct_change()
        
        # Volatility calculations (core of Volatim strategy)
        dataframe['returns_roll_mean'] = dataframe['returns'].rolling(
            window=self.returns_window
        ).mean()
        
        dataframe['returns_roll_std'] = dataframe['returns'].rolling(
            window=self.returns_window
        ).std()
        
        # Volatility bands (similar to Bollinger Bands but for returns)
        dataframe['returns_roll_mean_cumsum'] = dataframe['returns_roll_mean'].cumsum()
        
        # Upper and lower volatility thresholds
        volatility_multiplier = 2.0
        dataframe['returns_roll_mean_cumsum_upper'] = (
            dataframe['returns_roll_mean_cumsum'] + 
            (volatility_multiplier * dataframe['returns_roll_std'])
        )
        dataframe['returns_roll_mean_cumsum_lower'] = (
            dataframe['returns_roll_mean_cumsum'] - 
            (volatility_multiplier * dataframe['returns_roll_std'])
        )
        
        # RSI for momentum
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.rsi_period)
        
        # Rolling min/max for volatility extremes
        dataframe['rolling_min'] = dataframe['low'].rolling(
            window=self.volatility_window
        ).min()
        dataframe['rolling_max'] = dataframe['high'].rolling(
            window=self.volatility_window
        ).max()
        
        # Local extremes detection
        dataframe['local_min'] = (
            dataframe['low'] == dataframe['rolling_min']
        ).astype(int)
        dataframe['local_max'] = (
            dataframe['high'] == dataframe['rolling_max']
        ).astype(int)
        
        # Bitcoin correlation (if trading altcoins)
        # Note: This would need actual BTC data feed in practice
        # For now, we'll simulate market correlation
        dataframe['btc_returns'] = dataframe['returns']  # Placeholder
        dataframe['btc_returns_roll_mean'] = dataframe['btc_returns'].rolling(
            window=self.returns_window
        ).mean()
        dataframe['btc_returns_roll_mean_cumsum'] = dataframe['btc_returns_roll_mean'].cumsum()
        
        # Enhanced volatility indicators
        # ATR for dynamic volatility measurement
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        dataframe['atr_pct'] = dataframe['atr'] / dataframe['close'] * 100
        
        # Volatility ratio (current vs historical average)
        dataframe['volatility_ratio'] = (
            dataframe['atr_pct'] / dataframe['atr_pct'].rolling(50).mean()
        )
        
        # Bollinger Bands for additional volatility context
        bb = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2, nbdevdn=2)
        dataframe['bb_upper'] = bb['upperband']
        dataframe['bb_lower'] = bb['lowerband']
        dataframe['bb_middle'] = bb['middleband']
        dataframe['bb_width'] = (dataframe['bb_upper'] - dataframe['bb_lower']) / dataframe['bb_middle']
        
        # Volume analysis
        if 'volume' in dataframe.columns:
            dataframe['volume_sma'] = dataframe['volume'].rolling(self.volume_window).mean()
            dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma']
        else:
            dataframe['volume_ratio'] = 1
        
        # Market regime detection
        dataframe['trend_strength'] = abs(
            dataframe['close'].rolling(20).mean() - dataframe['close'].rolling(50).mean()
        ) / dataframe['atr']
        
        # Volatility breakout signals
        dataframe['vol_breakout_up'] = (
            (dataframe['volatility_ratio'] > 1.2) & 
            (dataframe['close'] > dataframe['bb_middle']) &
            (dataframe['returns'] > dataframe['returns_roll_mean_cumsum_upper'])
        ).astype(int)
        
        dataframe['vol_breakout_down'] = (
            (dataframe['volatility_ratio'] > 1.2) & 
            (dataframe['close'] < dataframe['bb_middle']) &
            (dataframe['returns'] < dataframe['returns_roll_mean_cumsum_lower'])
        ).astype(int)
        
        # Mean reversion signals
        dataframe['mean_reversion_up'] = (
            (dataframe['close'] <= dataframe['bb_lower']) &
            (dataframe['rsi'] < 30) &
            (dataframe['local_min'] == 1)
        ).astype(int)
        
        dataframe['mean_reversion_down'] = (
            (dataframe['close'] >= dataframe['bb_upper']) &
            (dataframe['rsi'] > 70) &
            (dataframe['local_max'] == 1)
        ).astype(int)
        
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['enter_long'] = 0
        dataframe['enter_short'] = 0
        
        # Long Entry: Volatility breakout + momentum
        long_conditions = [
            # Primary volatility signal
            (dataframe['vol_breakout_up'] == 1) |
            (dataframe['mean_reversion_up'] == 1),
            
            # Momentum confirmation
            dataframe['rsi'] > 25,  # Not oversold
            dataframe['rsi'] < 75,  # Not overbought
            
            # Volatility environment
            dataframe['volatility_ratio'] > 0.8,  # Some volatility present
            dataframe['volatility_ratio'] < 3.0,  # Not extremely volatile
            
            # Volume confirmation
            dataframe['volume_ratio'] > 0.7,
            
            # Returns momentum
            dataframe['returns_roll_mean'] > dataframe['returns_roll_mean'].shift(1),
            
            # Market structure
            dataframe['close'] > dataframe['rolling_min'],
        ]
        
        dataframe.loc[
            np.logical_and.reduce(long_conditions),
            'enter_long'
        ] = 1
        
        # Short Entry: Volatility breakout down + momentum
        short_conditions = [
            # Primary volatility signal
            (dataframe['vol_breakout_down'] == 1) |
            (dataframe['mean_reversion_down'] == 1),
            
            # Momentum confirmation
            dataframe['rsi'] > 25,  # Not oversold
            dataframe['rsi'] < 75,  # Not overbought
            
            # Volatility environment
            dataframe['volatility_ratio'] > 0.8,  # Some volatility present
            dataframe['volatility_ratio'] < 3.0,  # Not extremely volatile
            
            # Volume confirmation
            dataframe['volume_ratio'] > 0.7,
            
            # Returns momentum
            dataframe['returns_roll_mean'] < dataframe['returns_roll_mean'].shift(1),
            
            # Market structure
            dataframe['close'] < dataframe['rolling_max'],
        ]
        
        dataframe.loc[
            np.logical_and.reduce(short_conditions),
            'enter_short'
        ] = 1
        
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['exit_long'] = 0
        dataframe['exit_short'] = 0
        
        # Long Exit: Volatility collapse or momentum reversal
        long_exit_conditions = [
            # Volatility collapse
            (dataframe['volatility_ratio'] < 0.6) |
            
            # RSI overbought
            (dataframe['rsi'] > 80) |
            
            # Returns momentum reversal
            (dataframe['returns_roll_mean'] < dataframe['returns_roll_mean'].shift(2)) |
            
            # Price at resistance
            (dataframe['close'] >= dataframe['rolling_max']) |
            
            # Bollinger Band squeeze
            (dataframe['bb_width'] < dataframe['bb_width'].rolling(10).mean() * 0.5)
        ]
        
        dataframe.loc[
            np.logical_or.reduce(long_exit_conditions),
            'exit_long'
        ] = 1
        
        # Short Exit: Volatility collapse or momentum reversal
        short_exit_conditions = [
            # Volatility collapse
            (dataframe['volatility_ratio'] < 0.6) |
            
            # RSI oversold
            (dataframe['rsi'] < 20) |
            
            # Returns momentum reversal
            (dataframe['returns_roll_mean'] > dataframe['returns_roll_mean'].shift(2)) |
            
            # Price at support
            (dataframe['close'] <= dataframe['rolling_min']) |
            
            # Bollinger Band squeeze
            (dataframe['bb_width'] < dataframe['bb_width'].rolling(10).mean() * 0.5)
        ]
        
        dataframe.loc[
            np.logical_or.reduce(short_exit_conditions),
            'exit_short'
        ] = 1
        
        return dataframe
    
    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
                       current_rate: float, current_profit: float, **kwargs) -> float:
        """
        Dynamic stoploss based on volatility
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        
        if len(dataframe) == 0:
            return self.stoploss
        
        current_candle = dataframe.iloc[-1]
        
        # Volatility-adjusted stoploss
        volatility_multiplier = max(1.0, current_candle['volatility_ratio'])
        base_stoploss = self.stoploss
        
        # Tighter stops in high volatility
        if current_candle['volatility_ratio'] > 2.0:
            adjusted_stoploss = base_stoploss * 0.7  # Tighter stop
        elif current_candle['volatility_ratio'] > 1.5:
            adjusted_stoploss = base_stoploss * 0.85
        else:
            adjusted_stoploss = base_stoploss
        
        return max(adjusted_stoploss, -0.12)  # Maximum 12% loss
    
    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, side: str,
                 **kwargs) -> float:
        """
        Dynamic leverage based on volatility
        """
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            
            if len(dataframe) == 0:
                return 3  # Conservative default
            
            current_candle = dataframe.iloc[-1]
            volatility_ratio = current_candle.get('volatility_ratio', 1.0)
            
            # Lower leverage in high volatility
            if volatility_ratio > 2.0:
                return 2  # Very conservative in high vol
            elif volatility_ratio > 1.5:
                return 3  # Moderate leverage
            elif volatility_ratio > 1.0:
                return 5  # Standard leverage
            else:
                return 7  # Higher leverage in low volatility
                
        except Exception:
            return 3  # Safe default
    
    def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
                   current_rate: float, current_profit: float, **kwargs):
        """
        Enhanced exit logic for volatility-based trading
        """
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            
            if len(dataframe) == 0:
                return None
                
            current_candle = dataframe.iloc[-1]
            
            # Exit on volatility collapse (major signal deterioration)
            if current_candle['volatility_ratio'] < 0.4:
                return "volatility_collapse"
            
            # Exit on extreme RSI with profit
            if current_profit > 0.02:  # At least 2% profit
                if trade.is_short and current_candle['rsi'] < 15:
                    return "rsi_extreme_short_exit"
                elif not trade.is_short and current_candle['rsi'] > 85:
                    return "rsi_extreme_long_exit"
            
            # Take profits on strong volatility spikes
            if current_profit > 0.05 and current_candle['volatility_ratio'] > 3.0:
                return "high_volatility_profit_take"
            
            return None
            
        except Exception:
            return None