# source: https://raw.githubusercontent.com/gksdud6330/crytpo-auto-trading-bot/e754e3c873b4845c56f7fb752748330c48cd6369/ml_trading/user_data/strategies/MLStrategy.py
"""
ML Signal Strategy for Freqtrade
Uses ML model signals to trade crypto futures
"""

import logging
from datetime import datetime
from typing import Dict, List, Optional
import pandas as pd
import joblib
import os

from freqtrade.strategy import IStrategy
from freqtrade.strategy.parameters import CategoricalParameter, DecimalParameter, IntParameter

logger = logging.getLogger(__name__)


class Github_gksdud6330_crytpo_auto_trading_bot__MLStrategy__20260118_144545(IStrategy):
    """
    ML-based trading strategy with 1h timeframe for faster signals.
    Uses XGBoost/LightGBM/RandomForest signals.
    """

    # Strategy parameters
    minimal_roi = {
        "0": 0.03,      # 3% profit
        "15": 0.02,     # 2% profit after 15 min
        "30": 0.015,    # 1.5% profit after 30 min
        "60": 0.01,     # 1% profit after 1 hour
    }

    stoploss = -0.03  # 3% stoploss
    trailing_stop = True
    trailing_stop_positive = 0.015
    trailing_stop_positive_offset = 0.02
    trailing_only_offset_is_reached = True

    timeframe = '1h'

    process_only_new_candles = True
    startup_candle_count = 60

    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    order_types = {
        'entry': 'limit',
        'exit': 'limit',
        'stoploss': 'market',
        'stoploss_on_exchange': False,
        'exit_timeout': 15,
        'entry_timeout': 15,
    }

    order_time_in_force = {
        'entry': 'GTC',
        'exit': 'GTC',
    }

    # ML model settings
    model_dir = '/Users/hy/Desktop/Coding/stock-market/ml_trading/models'
    min_confidence = 0.55

    # Trading pairs
    INTERFACE_EMPTY = []
    whitelist = [
        "BTC/USDT",
        "ETH/USDT",
        "SOL/USDT",
        "BNB/USDT",
        "XRP/USDT",
        "ADA/USDT",
        "DOGE/USDT",
        "MATIC/USDT",
        "DOT/USDT",
        "LTC/USDT"
    ]
    blacklist = []

    # Cache for ML signals
    _ml_signals_cache = {}

    def __init__(self, config: dict = None):
        super().__init__(config)
        self._load_ml_models()

    def _load_ml_models(self):
        """Load ML models if available"""
        self.models = {}
        self.scalers = {}
        self.feature_cols = []

        model_path = f"{self.model_dir}/xgboost_optimized_4h.joblib"
        scaler_path = f"{self.model_dir}/scaler_4h.joblib"
        feature_path = f"{self.model_dir}/features_4h.txt"

        if os.path.exists(model_path):
            try:
                self.models['xgboost'] = joblib.load(model_path)
                self.scalers['xgboost'] = joblib.load(scaler_path)
                with open(feature_path, 'r') as f:
                    self.feature_cols = [line.strip() for line in f]
                logger.info("ML models loaded successfully")
            except Exception as e:
                logger.warning(f"Failed to load ML models: {e}")
                self.models = {}

    def get_ml_signal(self, pair: str) -> Dict:
        """
        Get ML prediction for a trading pair.
        Returns: {'signal': 'buy'/'sell'/'hold', 'confidence': 0.0-1.0}
        """
        # Check cache
        cache_key = f"{pair}_{datetime.now().strftime('%Y%m%d%H')}"
        if cache_key in self._ml_signals_cache:
            return self._ml_signals_cache[cache_key]

        # Generate ML signal (simulated if no model loaded)
        if not self.models:
            # Simulated signal for demo
            import random
            signals = ['hold', 'hold', 'hold', 'buy', 'sell']
            weights = [0.4, 0.4, 0.1, 0.05, 0.05]
            signal = random.choices(signals, weights=weights)[0]
            confidence = random.uniform(0.50, 0.75)
        else:
            # Real ML prediction would go here
            signal = 'hold'
            confidence = 0.50

        result = {'signal': signal, 'confidence': confidence}
        self._ml_signals_cache[cache_key] = result
        return result

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Add technical indicators for ML features.
        1시간마다 분석 + 5분봉으로 세밀한 진입
        """
        # RSI (14)
        delta = dataframe['close'].diff()
        gain = delta.where(delta > 0, 0).rolling(window=14).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
        rs = gain / loss
        dataframe['rsi'] = 100 - (100 / (1 + rs))

        # MACD (12, 26, 9)
        ema12 = dataframe['close'].ewm(span=12).mean()
        ema26 = dataframe['close'].ewm(span=26).mean()
        dataframe['macd'] = ema12 - ema26
        dataframe['macd_signal'] = dataframe['macd'].ewm(span=9).mean()
        dataframe['macd_hist'] = dataframe['macd'] - dataframe['macd_signal']

        # Bollinger Bands (20)
        bb_period = 20
        bb_std = dataframe['close'].rolling(window=bb_period).std()
        bb_sma = dataframe['close'].rolling(window=bb_period).mean()
        dataframe['bb_upper'] = bb_sma + (bb_std * 2)
        dataframe['bb_lower'] = bb_sma - (bb_std * 2)
        dataframe['bb_width'] = (dataframe['bb_upper'] - dataframe['bb_lower']) / bb_sma
        dataframe['bb_position'] = (dataframe['close'] - dataframe['bb_lower']) / (dataframe['bb_upper'] - dataframe['bb_lower'])

        # Volume + EMA
        dataframe['volume'] = dataframe['volume'].fillna(0)
        dataframe['volume_ema'] = dataframe['volume'].ewm(span=20).mean()
        dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_ema']

        # Price momentum
        dataframe['momentum_1h'] = dataframe['close'].pct_change(1)
        dataframe['momentum_4h'] = dataframe['close'].pct_change(4)
        dataframe['momentum_12h'] = dataframe['close'].pct_change(12)

        # EMA cross
        dataframe['ema_9'] = dataframe['close'].ewm(span=9).mean()
        dataframe['ema_21'] = dataframe['close'].ewm(span=21).mean()
        dataframe['ema_cross'] = (dataframe['ema_9'] - dataframe['ema_21']) / dataframe['ema_21']

        # ATR for volatility
        high_low = dataframe['high'] - dataframe['low']
        high_close = abs(dataframe['high'] - dataframe['close'].shift())
        low_close = abs(dataframe['low'] - dataframe['close'].shift())
        tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
        dataframe['atr'] = tr.rolling(window=14).mean()
        dataframe['atr_percent'] = dataframe['atr'] / dataframe['close']

        return dataframe

    def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Define entry signals based on ML predictions.
        """
        pair = metadata['pair']
        ml_signal = self.get_ml_signal(pair)

        # Entry signal: ML confidence > threshold and signal is 'buy'
        dataframe.loc[
            (ml_signal['signal'] == 'buy') &
            (ml_signal['confidence'] >= self.min_confidence),
            'enter_long'
        ] = 1

        dataframe.loc[
            (ml_signal['signal'] == 'sell') &
            (ml_signal['confidence'] >= self.min_confidence),
            'enter_short'
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Define exit signals based on ML predictions and technical indicators.
        """
        pair = metadata['pair']
        ml_signal = self.get_ml_signal(pair)

        # Exit signals
        dataframe.loc[
            (ml_signal['signal'] == 'sell') &
            (ml_signal['confidence'] >= self.min_confidence),
            'exit_long'
        ] = 1

        dataframe.loc[
            (ml_signal['signal'] == 'buy') &
            (ml_signal['confidence'] >= self.min_confidence),
            'exit_short'
        ] = 1

        # Also exit on technical signals
        dataframe.loc[
            dataframe['rsi'] > 80,
            'exit_long'
        ] = 1

        dataframe.loc[
            dataframe['rsi'] < 20,
            'exit_long'
        ] = 1

        return dataframe

    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, min_leverage: float, max_leverage: float) -> float:
        """
        Set leverage for futures trading.
        """
        return 2.0  # 2x leverage

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs) -> float:
        """
        Custom stoploss based on volatility.
        """
        return self.stoploss

    def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float,
                           proposed_entry: float, side: str) -> float:
        """
        Get custom entry price.
        """
        return proposed_entry

    def custom_exit_price(self, pair: str, trade: 'Trade', current_time: datetime,
                          proposed_rate: float, proposed_exit: float, side: str) -> float:
        """
        Get custom exit price.
        """
        return proposed_exit

    def bot_loop_start(self, current_time: datetime, current_rate: float,
                       **kwargs) -> None:
        """
        Called at the start of each bot iteration.
        Refresh ML signals cache daily.
        """
        pass

    def confirm_trade_entry(self, pair: str, order_type: str, amount: float,
                            rate: float, time_in_force: str, current_time: datetime,
                            entry_rate: float, entry_price: float, side: str,
                            **kwargs) -> bool:
        """
        Confirm trade entry.
        """
        ml_signal = self.get_ml_signal(pair)
        logger.info(f"ML Signal for {pair}: {ml_signal}")
        return True

    def confirm_trade_exit(self, trade: 'Trade', order_type: str, amount: float,
                           rate: float, time_in_force: str, exit_reason: str,
                           current_time: datetime, **kwargs) -> bool:
        """
        Confirm trade exit.
        """
        return True

    def adjust_trade_position(self, trade: 'Trade', current_time: datetime,
                              current_rate: float, current_profit: float,
                              min_stake: float, max_stake: float,
                              current_stake_amount: float, current_entry_price: float,
                              current_exit_price: float, current_order_type: str,
                              **kwargs) -> Optional[float]:
        """
        Adjust trade position (add to or reduce position).
        """
        return None

    def get_signal(self, pair: str, timeframe: str,
                   metadata: dict) -> tuple[bool, bool]:
        """
        Get buy/sell signal for a pair.
        Returns (buy_signal, sell_signal)
        """
        ml_signal = self.get_ml_signal(pair)
        buy = ml_signal['signal'] == 'buy' and ml_signal['confidence'] >= self.min_confidence
        sell = ml_signal['signal'] == 'sell' and ml_signal['confidence'] >= self.min_confidence
        return buy, sell
