# source: https://raw.githubusercontent.com/aminak58/pragma-trading-bot/e3301da3a6c43d83fed77fe22c910c8492e71e9a/user_data/strategies/ImprovedTrendPhaseStrategy.py
"""
Improved Trend Phase Strategy - Phase 1 Optimization
- Enhanced entry logic with better filtering
- Improved risk management
- Target: WinRate > 30%, Sharpe > 0.5, MDD < 2%
"""
from pathlib import Path
import sys

# Add project root and src to path
root_path = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(root_path))
sys.path.insert(0, str(root_path / 'src'))

from datetime import datetime
from typing import Optional
import numpy as np
import pandas as pd
from pandas import DataFrame
from freqtrade.strategy import IStrategy
import talib.abstract as ta

from src.regime.hmm_detector import RegimeDetector


class Github_aminak58_pragma_trading_bot__ImprovedTrendPhaseStrategy__20251020_190231(IStrategy):
    """
    Improved Trend Phase Strategy - Phase 1 Optimization
    - Enhanced entry logic with better filtering
    - Improved risk management
    - Target: WinRate > 30%, Sharpe > 0.5, MDD < 2%
    """
    INTERFACE_VERSION = 3
    timeframe = '5m'
    process_only_new_candles = True
    startup_candle_count = 1000
    can_short = False

    # More conservative ROI
    minimal_roi = {"0": 0.03, "45": 0.02, "90": 0.015, "180": 0.01}
    
    # Tighter stoploss
    stoploss = -0.05
    
    # Position sizing
    position_adjustment_enable = True
    max_entry_position_adjustment = 2

    def __init__(self, config: dict) -> None:
        super().__init__(config)
        self.detector = RegimeDetector()
        self.trained = False

    def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame:
        # Basic technical indicators
        df['ema20'] = ta.EMA(df, timeperiod=20)
        df['ema50'] = ta.EMA(df, timeperiod=50)
        df['ema100'] = ta.EMA(df, timeperiod=100)
        df['rsi'] = ta.RSI(df, timeperiod=14)
        df['adx'] = ta.ADX(df, timeperiod=14)
        df['atr'] = ta.ATR(df, timeperiod=14)
        
        # Bollinger Bands for volatility
        bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        df['bb_upper'] = bb['upperband']
        df['bb_middle'] = bb['middleband']
        df['bb_lower'] = bb['lowerband']
        df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_middle']
        
        # MACD for momentum
        macd = ta.MACD(df)
        df['macd'] = macd['macd']
        df['macd_signal'] = macd['macdsignal']
        df['macd_hist'] = macd['macdhist']
        
        # Volume indicators
        df['volume_sma'] = df['volume'].rolling(20).mean()
        df['volume_ratio'] = df['volume'] / df['volume_sma']
        
        # Price momentum
        df['price_change'] = df['close'].pct_change(5)
        df['price_momentum'] = df['close'] / df['close'].shift(10) - 1

        # Train HMM once sufficient candles
        if not self.trained and len(df) >= 1000:
            try:
                self.detector.train(df.tail(1000))
                self.trained = True
            except Exception as e:
                self.trained = False

        # Predict regimes/conf if trained
        if self.trained:
            regimes, conf = self.detector.predict_regime_sequence(df, smooth_window=5)
            # align
            if len(regimes) != len(df):
                n = min(len(regimes), len(df))
                regimes = regimes[-n:]
                conf = conf[-n:]
                df = df.tail(n).copy()
            df['regime'] = regimes
            df['regime_conf'] = conf
        else:
            df['regime'] = 'unknown'
            df['regime_conf'] = 0.0

        # Compute Trend Phase Score
        try:
            score = self.detector.compute_trend_phase_score(df)
            df['trend_phase_score'] = score
        except Exception as e:
            df['trend_phase_score'] = 0.0

        # Enhanced percentile-based labels with confidence gating
        score_series = df['trend_phase_score'].dropna()
        if not score_series.empty:
            p30 = score_series.quantile(0.30)
            p50 = score_series.quantile(0.50)
            p70 = score_series.quantile(0.70)
            p80 = score_series.quantile(0.80)
            
            df['phase_label'] = 'Neutral'
            # More selective thresholds
            df.loc[df['trend_phase_score'] >= p80, 'phase_label'] = 'Uptrend_Early'
            df.loc[(df['trend_phase_score'] >= p70) & (df['trend_phase_score'] < p80), 'phase_label'] = 'Uptrend_Late'
            df.loc[df['trend_phase_score'] <= p30, 'phase_label'] = 'Downtrend_Early'
            df.loc[(df['trend_phase_score'] > p30) & (df['trend_phase_score'] < p50), 'phase_label'] = 'Downtrend_Late'
        else:
            df['phase_label'] = 'Neutral'

        return df

    def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        df['enter_long'] = False
        
        # Enhanced entry logic with multiple confirmations
        entry_mask = (
            # Core trend phase condition
            (df['phase_label'] == 'Uptrend_Early') &
            
            # Regime filtering with confidence
            (df['regime'].isin(['trending', 'high_volatility'])) &
            (df['regime_conf'] > 0.6) &
            
            # Technical confirmations
            (df['rsi'] > 40) &  # Not oversold
            (df['rsi'] < 75) &  # Not overbought
            (df['close'] > df['ema20']) &  # Above short EMA
            (df['ema20'] > df['ema50']) &  # EMA alignment
            (df['macd'] > df['macd_signal']) &  # MACD bullish
            (df['macd_hist'] > df['macd_hist'].shift(1)) &  # MACD improving
            
            # Volume confirmation
            (df['volume_ratio'] > 1.2) &  # Above average volume
            
            # Price momentum
            (df['price_change'] > 0) &  # Positive short-term momentum
            (df['price_momentum'] > -0.02) &  # Not in severe decline
            
            # Volatility check
            (df['bb_width'] > 0.02) &  # Sufficient volatility
            (df['bb_width'] < 0.08)  # Not extreme volatility
        )
        
        df['enter_long'] = entry_mask
        
        # Enter tags for analysis
        df['enter_tag'] = None
        df.loc[entry_mask & (df['regime'] == 'trending'), 'enter_tag'] = 'UpEarly_trending'
        df.loc[entry_mask & (df['regime'] == 'high_volatility'), 'enter_tag'] = 'UpEarly_highvol'
        
        return df

    def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        df['exit_long'] = False
        
        # Enhanced exit logic
        exit_mask = (
            # Phase change exits
            (df['phase_label'].isin(['Uptrend_Late', 'Downtrend_Early', 'Downtrend_Late'])) |
            
            # Technical exits
            (df['rsi'] > 80) |  # Severely overbought
            (df['close'] < df['ema20']) |  # Below short EMA
            (df['ema20'] < df['ema50']) |  # EMA bearish cross
            (df['macd'] < df['macd_signal']) |  # MACD bearish
            (df['macd_hist'] < df['macd_hist'].shift(1)) |  # MACD deteriorating
            
            # Volume exits
            (df['volume_ratio'] < 0.8) |  # Low volume
            
            # Momentum exits
            (df['price_change'] < -0.01) |  # Negative momentum
            (df['price_momentum'] < -0.03) |  # Severe decline
            
            # Volatility exits
            (df['bb_width'] > 0.10) |  # Extreme volatility
            (df['regime'] == 'high_volatility') & (df['bb_width'] > 0.06)  # High vol + wide BB
        )
        
        df['exit_long'] = exit_mask
        return df

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                       current_rate: float, current_profit: float, **kwargs) -> float:
        """
        Enhanced dynamic stoploss
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe.empty:
            return self.stoploss
            
        last_candle = dataframe.iloc[-1]
        regime = last_candle.get('regime', 'unknown')
        regime_conf = last_candle.get('regime_conf', 0.0)
        atr_percent = last_candle.get('atr', 0) / current_rate
        
        # Base stoploss by regime
        if regime == 'trending':
            base_stoploss = -0.04  # Tighter for trending
        elif regime == 'low_volatility':
            base_stoploss = -0.06  # Wider for low vol
        elif regime == 'high_volatility':
            base_stoploss = -0.08  # Much wider for high vol
        else:
            base_stoploss = self.stoploss
        
        # Adjust based on confidence
        if regime_conf > 0.8:
            base_stoploss *= 0.9  # Tighter for high confidence
        elif regime_conf < 0.6:
            base_stoploss *= 1.1  # Wider for low confidence
            
        # Adjust based on ATR
        if atr_percent > 0.015:  # High volatility
            base_stoploss *= 1.2
        elif atr_percent < 0.005:  # Low volatility
            base_stoploss *= 0.95
            
        # Adjust based on profit
        if current_profit > 0.015:  # In profit
            base_stoploss = max(base_stoploss, -0.025)  # Trail stop
        elif current_profit < -0.015:  # In loss
            base_stoploss *= 1.05  # Slightly wider
            
        return max(base_stoploss, -0.15)  # Cap at -15%

    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                           proposed_stake: float, min_stake: float, max_stake: float,
                           leverage: float, entry_tag: Optional[str], side: str,
                           **kwargs) -> float:
        """
        Enhanced dynamic position sizing
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe.empty:
            return proposed_stake
            
        last_candle = dataframe.iloc[-1]
        regime = last_candle.get('regime', 'unknown')
        regime_conf = last_candle.get('regime_conf', 0.0)
        
        # Base position size by regime
        if regime == 'trending':
            base_multiplier = 0.8  # Reduced from 1.0
        elif regime == 'low_volatility':
            base_multiplier = 0.6  # Reduced from 0.8
        elif regime == 'high_volatility':
            base_multiplier = 0.4  # Much smaller for high vol
        else:
            base_multiplier = 0.5
            
        # Adjust based on confidence
        if regime_conf > 0.8:
            base_multiplier *= 1.1  # Increase for high confidence
        elif regime_conf < 0.6:
            base_multiplier *= 0.8  # Reduce for low confidence
            
        # Adjust based on entry tag
        if entry_tag == 'UpEarly_trending':
            base_multiplier *= 1.0  # No extra boost
        elif entry_tag == 'UpEarly_highvol':
            base_multiplier *= 0.8  # Smaller for high vol regime
            
        adjusted_stake = proposed_stake * base_multiplier
        
        # Ensure within bounds
        return max(min_stake, min(adjusted_stake, max_stake))

    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
                side: str, **kwargs) -> float:
        """
        Conservative leverage
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe.empty:
            return 1.0
            
        last_candle = dataframe.iloc[-1]
        regime = last_candle.get('regime', 'unknown')
        
        # Conservative leverage by regime
        if regime == 'trending':
            return min(1.5, max_leverage)
        elif regime == 'low_volatility':
            return min(1.2, max_leverage)
        elif regime == 'high_volatility':
            return 1.0  # No leverage for high vol
        else:
            return 1.0
