# source: https://raw.githubusercontent.com/gilaigenttech/freqtrade-user_data/d16b7f5db829fb674151ed0894a239fe9ee6c3bd/strategies/PerpSpotBasisStrategy_Fixed.py
#!/usr/bin/env python3
"""
Fixed version of PerpSpotBasisStrategy
"""
import pandas as pd
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import DecimalParameter
from pandas import DataFrame
import talib.abstract as ta
import numpy as np
import os

class Github_gilaigenttech_freqtrade_user_data__PerpSpotBasisStrategy_Fixed__20250926_111255(IStrategy):
    timeframe = "5m"
    can_short = False
    startup_candle_count = 200

    use_exit_signal = False

    # More reasonable ROI
    minimal_roi = {
        "0": 0.08,
        "60": 0.04,
        "120": 0.02,
        "240": 0
    }
    stoploss = -0.02  # Using config stoploss

    process_only_new_candles = True

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        pair = metadata['pair']

        # Try to load futures data, but don't fail if it's not available
        futures_data = self.load_futures_data(pair)
        if futures_data is not None:
            dataframe = self.merge_futures_data(dataframe, futures_data)
            dataframe = self.calculate_basis_features(dataframe)

        # Always calculate these basic features
        dataframe = self.calculate_volume_features(dataframe)

        # Technical indicators
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["ema_20"] = ta.EMA(dataframe, timeperiod=20)
        dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        dataframe['macdhist'] = macd['macdhist']

        # Bollinger Bands for additional signals
        bb = ta.BBANDS(dataframe)
        dataframe['bb_upper'] = bb['upperband']
        dataframe['bb_middle'] = bb['middleband']
        dataframe['bb_lower'] = bb['lowerband']

        return dataframe

    def load_futures_data(self, pair):
        """Load corresponding futures data if available"""
        try:
            futures_pair = pair.replace('/', '_') + '_USDT-5m-futures.feather'
            futures_path = f'/home/gil/freqtrade/user_data/data/binance/futures/{futures_pair}'

            if not os.path.exists(futures_path):
                return None

            df = pd.read_feather(futures_path)
            return df
        except Exception:
            return None

    def merge_futures_data(self, spot_df, futures_df):
        """Merge spot and futures data"""
        try:
            merged = pd.merge(spot_df, futures_df[['date', 'close', 'volume']],
                            left_on='date', right_on='date', how='left',
                            suffixes=('', '_perp'))

            merged[['close_perp', 'volume_perp']] = merged[['close_perp', 'volume_perp']].ffill()
            return merged
        except Exception:
            return spot_df

    def calculate_basis_features(self, dataframe: DataFrame) -> DataFrame:
        """Calculate perp-spot basis features if perp data is available"""
        if 'close_perp' not in dataframe.columns or dataframe['close_perp'].isna().all():
            return dataframe

        # Basic basis calculation
        dataframe['basis'] = (dataframe['close_perp'] - dataframe['close']) / dataframe['close'] * 100

        # Only calculate if we have valid data
        if not dataframe['basis'].isna().all():
            dataframe['basis_ma_10'] = ta.SMA(dataframe['basis'], timeperiod=10)
            dataframe['basis_ma_30'] = ta.SMA(dataframe['basis'], timeperiod=30)
            dataframe['basis_roc'] = ta.ROC(dataframe['basis'], timeperiod=5)

            # Z-score of basis
            basis_mean = dataframe['basis'].rolling(window=50).mean()
            basis_std = dataframe['basis'].rolling(window=50).std()
            dataframe['basis_zscore'] = (dataframe['basis'] - basis_mean) / basis_std

        return dataframe

    def calculate_volume_features(self, dataframe):
        """Calculate volume features"""
        try:
            dataframe['volume_ma_20'] = ta.SMA(dataframe['volume'], timeperiod=20)
            dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_ma_20']
            dataframe['obv'] = ta.OBV(dataframe['close'], dataframe['volume'])

            # VWAP
            vwap_period = 20
            cumulative_price_volume = (dataframe['close'] * dataframe['volume']).rolling(vwap_period).sum()
            cumulative_volume = dataframe['volume'].rolling(vwap_period).sum()
            dataframe['vwap'] = cumulative_price_volume / cumulative_volume

            return dataframe
        except Exception:
            return dataframe

    freqai_prediction_threshold = DecimalParameter(0.6, 0.8, default=0.7, space='buy', optimize=True, load=True)

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[:, 'enter_long'] = 0

        # Primary: FreqAI signal
        if '&-enter_long' in dataframe.columns:
            freqai_condition = dataframe['&-enter_long'] > self.freqai_prediction_threshold.value
            dataframe.loc[freqai_condition, 'enter_long'] = 1
        else:
            # Fallback: Conservative technical analysis
            oversold = dataframe['rsi'] < 30
            uptrend = dataframe['ema_20'] > dataframe['ema_50']
            macd_bullish = dataframe['macd'] > dataframe['macdsignal']
            near_bb_lower = dataframe['close'] <= dataframe['bb_lower'] * 1.02  # Within 2% of BB lower
            volume_confirmation = dataframe['volume_ratio'] > 1.1
            
            # Require multiple confirmations
            entry_condition = oversold & uptrend & macd_bullish & near_bb_lower & volume_confirmation
            
            # Additional filter: only enter if price is above VWAP (trend confirmation)
            price_above_vwap = dataframe['close'] > dataframe['vwap']
            entry_condition = entry_condition & price_above_vwap
            
            dataframe.loc[entry_condition, 'enter_long'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[:, 'exit_long'] = 0

        # Exit conditions
        overbought = dataframe['rsi'] > 75
        near_bb_upper = dataframe['close'] >= dataframe['bb_upper'] * 0.98
        
        # Exit if basis normalizes (only if we have basis data)
        basis_exit = False
        if 'basis_zscore' in dataframe.columns and not dataframe['basis_zscore'].isna().all():
            basis_exit = dataframe['basis_zscore'] > 1.5

        exit_condition = overbought | near_bb_upper | basis_exit
        dataframe.loc[exit_condition, 'exit_long'] = 1

        return dataframe

    # FreqAI configuration - robust feature set
    freqai_info = {
        "identifier": "SKLearnRandomForestClassifier",
        "features": [
            "rsi",
            "ema_20",
            "ema_50", 
            "adx",
            "macd",
            "macdsignal",
            "macdhist",
            "volume_ratio",
            "obv",
            "vwap",
            "bb_upper",
            "bb_middle",
            "bb_lower",
            # Include basis features if available, FreqAI will handle missing values
            "basis_ma_10",
            "basis_ma_30",
            "basis_zscore",
        ],
        "label_period_candles": 24,  # Predict 2 hours ahead
        "data_split_parameters": {"split_train": 0.8, "split_test": 0.2},
        "model_training_parameters": {
            "n_estimators": 100,
            "max_depth": 8,
            "min_samples_split": 10,
            "min_samples_leaf": 5,
            "random_state": 42,
            "n_jobs": -1
        }
    }