# source: https://raw.githubusercontent.com/gilaigenttech/freqtrade-user_data/55fc4091f9f3acd4d4c049014dec19b5380bcc16/strategies/PerpSpotBasisStrategy_Working.py
#!/usr/bin/env python3
"""
Working PerpSpotBasisStrategy - Ready to use!
"""
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_Working__20251008_112727(IStrategy):
    timeframe = "5m"
    can_short = False
    startup_candle_count = 200

    use_exit_signal = False

    # Reasonable ROI targets
    minimal_roi = {
        "0": 0.06,
        "60": 0.03,
        "120": 0.015,
        "240": 0
    }
    stoploss = -0.02

    process_only_new_candles = True

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

        # Load futures data for basis calculations
        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)

        # Volume 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
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        dataframe['macdhist'] = macd['macdhist']

        # Bollinger Bands
        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"""
        try:
            possible_paths = [
                f'/home/gil/freqtrade/user_data/data/binance/futures/{pair.replace("/", "_")}-5m-futures.feather',
                f'/home/gil/freqtrade/user_data/data/binance/futures/{pair.replace("/", "_")}_USDT-5m-futures.feather',
                f'/home/gil/freqtrade/user_data/data/binance/{pair.replace("/", "_")}_USDT-5m.feather'
            ]
            
            for futures_path in possible_paths:
                if os.path.exists(futures_path):
                    df = pd.read_feather(futures_path)
                    return df
            return None
            
        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 'close_perp' not in dataframe.columns or dataframe['close_perp'].isna().all():
            return dataframe

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

        # Basis moving averages
        dataframe['basis_ma_10'] = ta.SMA(dataframe['basis'], timeperiod=10)
        dataframe['basis_ma_30'] = ta.SMA(dataframe['basis'], timeperiod=30)

        # Basis rate of change
        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

        # Volume ratio
        dataframe['volume_ratio'] = dataframe['volume_perp'] / dataframe['volume']

        # Basis momentum
        dataframe['basis_momentum'] = dataframe['basis'] - dataframe['basis_ma_30']

        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_ma'] = 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 available)
        if ('&-enter_long' in dataframe.columns and 
            not dataframe['&-enter_long'].isna().all()):
            freqai_condition = dataframe['&-enter_long'] > self.freqai_prediction_threshold.value
            dataframe.loc[freqai_condition, 'enter_long'] = 1
        else:
            # Fallback strategy using basis + technical analysis
            
            # Basic technical conditions
            oversold = dataframe['rsi'] < 35
            uptrend = dataframe['ema_20'] > dataframe['ema_50']
            macd_bullish = dataframe['macd'] > dataframe['macdsignal']
            price_near_bb_lower = dataframe['close'] <= dataframe['bb_lower'] * 1.01
            volume_above_average = dataframe['volume_ratio_ma'] > 1.2
            
            # Basis trading signals (if available)
            basis_entry = False
            if 'basis_zscore' in dataframe.columns and not dataframe['basis_zscore'].isna().all():
                # Enter when basis is extremely negative (futures trading at discount)
                extreme_negative_basis = dataframe['basis_zscore'] < -2
                basis_recovering = dataframe['basis_roc'] > 0  # Basis starting to recover
                basis_entry = extreme_negative_basis & basis_recovering
            
            # Combine all conditions - need multiple confirmations
            technical_entry = oversold & uptrend & macd_bullish & price_near_bb_lower & volume_above_average
            
            # Enter if either strong technical setup OR basis opportunity
            entry_condition = technical_entry | basis_entry
            
            dataframe.loc[entry_condition, 'enter_long'] = 1

        return dataframe

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

        # Technical exits
        overbought = dataframe['rsi'] > 70
        price_at_bb_upper = dataframe['close'] >= dataframe['bb_upper'] * 0.99
        macd_bearish = dataframe['macd'] < dataframe['macdsignal']
        
        # Basis exits (if available)
        basis_exit = False
        if 'basis_zscore' in dataframe.columns and not dataframe['basis_zscore'].isna().all():
            # Exit when basis normalizes or becomes extremely positive
            basis_normalized = dataframe['basis_zscore'] > 1
            basis_exit = basis_normalized

        exit_condition = overbought | price_at_bb_upper | (macd_bearish & overbought) | basis_exit
        dataframe.loc[exit_condition, 'exit_long'] = 1

        return dataframe

    # FreqAI configuration (optional - strategy works without it)
    freqai_info = {
        "identifier": "SKLearnRandomForestClassifier",
        "features": [
            "rsi",
            "ema_20",
            "ema_50",
            "adx",
            "macd",
            "macdsignal",
            "macdhist",
            "volume_ratio_ma",
            "obv",
            "vwap",
            "bb_upper",
            "bb_middle",
            "bb_lower",
            # Basis features (will be ignored if not available)
            "basis_ma_10",
            "basis_ma_30",
            "basis_zscore",
            "basis_roc",
            "basis_momentum",
        ],
        "label_period_candles": 12,
        "data_split_parameters": {"split_train": 0.85, "split_test": 0.15},
        "model_training_parameters": {
            "n_estimators": 100,
            "max_depth": 8,
            "min_samples_split": 10,
            "min_samples_leaf": 5,
            "random_state": 42,
            "n_jobs": -1
        }
    }