# source: https://raw.githubusercontent.com/remiotore/freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/JT_V04.py




import numpy as np
import pandas as pd
from pandas import DataFrame
from datetime import datetime
from typing import Optional, Union
from datetime import datetime, timedelta
from pandas import DataFrame, Series
from freqtrade.persistence import Trade

from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
                                IntParameter, RealParameter, IStrategy, merge_informative_pair)


import talib.abstract as ta
import pandas_ta as pta
from technical import qtpylib


def williams_r(dataframe: DataFrame, period: int = 14) -> pd.Series:
    """Williams %R, or just %R, is a technical analysis oscillator showing the current closing price in relation to the high and low
        of the past N days (for a given N). It was developed by a publisher and promoter of trading materials, Larry Williams.
        Its purpose is to tell whether a stock or commodity market is trading near the high or the low, or somewhere in between,
        of its recent trading range.
        The oscillator is on a negative scale, from −100 (lowest) up to 0 (highest).
    """

    highest_high = dataframe["high"].rolling(center=False, window=period).max()
    lowest_low = dataframe["low"].rolling(center=False, window=period).min()

    WR = pd.Series(
        (highest_high - dataframe["close"]) / (highest_high - lowest_low),
        name=f"{period} Williams %R",
        )

    return WR * -100


class Github_remiotore_freqtrade__JT_V04__20260111_210550(IStrategy):


    INTERFACE_VERSION = 3

    timeframe = '5m'

    can_short: bool = False


    minimal_roi = {
        "0": 0.15,
        "5": 0.10,
        "10": 0.08,
        "15": 0.06,
        "30": 0.04,
        "40": 0.03,
        "60": 0.02,
        "120": 0.005
    }







    stoploss = -0.99
    use_custom_stoploss = False






    process_only_new_candles = True

    use_exit_signal = True



    startup_candle_count: int = 60

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

    plot_config = {
        'main_plot': {
            'tema': {},
            'sar': {'color': 'white'},
        },
        'subplots': {
            "MACD": {
                'macd': {'color': 'blue'},
                'macdsignal': {'color': 'orange'},
            },
            "RSI": {
                'rsi': {'color': 'red'},
            }
        }
    }
    buy_params = { 
        "bbdelta_close": 0.00082,
        "bbdelta_tail": 0.85788,
        "close_bblower": 0.00128,
        "closedelta_close": 0.00987,
        "low_offset": 0.991,
        "rocr1_1h": 0.9346,
        "rocr_1h": 0.65666,                       
        "base_nb_candles_buy": 12,  # value loaded from strategy
        "buy_bb_delta": 0.025,  # value loaded from strategy
        "buy_bb_factor": 0.995,  # value loaded from strategy
        "buy_bb_width": 0.095,  # value loaded from strategy   
        "buy_bb_width_1h": 1.074,  # value loaded from strategy        
        "buy_cci": -116,  # value loaded from strategy                                                                 
        "buy_cci_length": 25,  # value loaded from strategy 
        "buy_closedelta": 15.0,  # value loaded from strategy                                                                                                                                                                                 
        "buy_clucha_bbdelta_close": 0.049,  # value loaded from strategy
        "buy_clucha_bbdelta_tail": 1.146,  # value loaded from strategy
        "buy_clucha_close_bblower": 0.018,  # value loaded from strategy
        "buy_clucha_closedelta_close": 0.017,  # value loaded from strategy
        "buy_clucha_rocr_1h": 0.526,  # value loaded from strategy
        "buy_ema_diff": 0.025,  # value loaded from strategy
        "buy_rmi": 49,  # value loaded from strategy
        "buy_rmi_length": 17,  # value loaded from strategy 
        "buy_roc_1h": 10,  # value loaded from strategy
        "buy_srsi_fk": 32,  # value loaded from strategy
    }


    sell_params = {
        "high_offset": 1.012,
        "high_offset_2": 1.016,
        "sell_deadfish_bb_factor": 1.089,
        "sell_deadfish_bb_width": 0.11,
        "sell_deadfish_profit": -0.107,
        "sell_deadfish_volume_factor": 1.761,
        "base_nb_candles_sell": 22,  # value loaded from strategy
        "pHSL": -0.397,  # value loaded from strategy
        "pPF_1": 0.012,  # value loaded from strategy
        "pPF_2": 0.07,  # value loaded from strategy
        "pSL_1": 0.015,  # value loaded from strategy
        "pSL_2": 0.068,  # value loaded from strategy
        "sell_bbmiddle_close": 1.09092,  # value loaded from strategy
        "sell_fisher": 0.46406,  # value loaded from strategy
        "sell_trail_down_1": 0.03,  # value loaded from strategy
        "sell_trail_down_2": 0.015,  # value loaded from strategy
        "sell_trail_profit_max_1": 0.4,  # value loaded from strategy
        "sell_trail_profit_max_2": 0.11,  # value loaded from strategy
        "sell_trail_profit_min_1": 0.1,  # value loaded from strategy
        "sell_trail_profit_min_2": 0.04,  # value loaded from strategy
    }

    buy_rsi = IntParameter(10, 40, default=30, space="buy")
    sell_rsi = IntParameter(60, 90, default=70, space="sell")

    sell_fisher = RealParameter(0.1, 0.5, default=0.38414, space='sell', optimize=False)
    sell_bbmiddle_close = RealParameter(0.97, 1.1, default=1.07634, space='sell', optimize=False)

    is_optimize_deadfish = True
    sell_deadfish_bb_width = DecimalParameter(0.03, 0.75, default=0.05 , space='sell', optimize = is_optimize_deadfish)
    sell_deadfish_profit = DecimalParameter(-0.15, -0.05, default=-0.08 , space='sell', optimize = is_optimize_deadfish)
    sell_deadfish_bb_factor = DecimalParameter(0.90, 1.20, default=1.0 , space='sell', optimize = is_optimize_deadfish)
    sell_deadfish_volume_factor = DecimalParameter(1, 2.5, default=1.5 ,space='sell', optimize = is_optimize_deadfish)

    base_nb_candles_buy = IntParameter(8, 20, default=buy_params['base_nb_candles_buy'], space='buy', optimize=False)
    base_nb_candles_sell = IntParameter(8, 20, default=sell_params['base_nb_candles_sell'], space='sell', optimize=False)
    low_offset = DecimalParameter(0.985, 0.995, default=buy_params['low_offset'], space='buy', optimize=True)
    high_offset = DecimalParameter(1.005, 1.015, default=sell_params['high_offset'], space='sell', optimize=True)
    high_offset_2 = DecimalParameter(1.010, 1.020, default=sell_params['high_offset_2'], space='sell', optimize=True)
    
    sell_trail_profit_min_1 = DecimalParameter(0.1, 0.25, default=0.1, space='sell', decimals=3, optimize=False, load=True)
    sell_trail_profit_max_1 = DecimalParameter(0.3, 0.5, default=0.4, space='sell', decimals=2, optimize=False, load=True)
    sell_trail_down_1 = DecimalParameter(0.04, 0.1, default=0.03, space='sell', decimals=3, optimize=False, load=True)

    sell_trail_profit_min_2 = DecimalParameter(0.04, 0.1, default=0.04, space='sell', decimals=3, optimize=False, load=True)
    sell_trail_profit_max_2 = DecimalParameter(0.08, 0.25, default=0.11, space='sell', decimals=2, optimize=False, load=True)
    sell_trail_down_2 = DecimalParameter(0.04, 0.2, default=0.015, space='sell', decimals=3, optimize=False, load=True)

    
    

    def informative_pairs(self):
        """
        Define additional, informative pair/interval combinations to be cached from the exchange.
        These pair/interval combinations are non-tradeable, unless they are part
        of the whitelist as well.
        For more information, please consult the documentation
        :return: List of tuples in the format (pair, interval)
            Sample: return [("ETH/USDT", "5m"),
                            ("BTC/USDT", "15m"),
                            ]
        """
        return []

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        
        
        dataframe['avg_volume'] = dataframe['volume'].rolling(window=10).mean()

        heikinashi = qtpylib.heikinashi(dataframe)
        dataframe['ha_open'] = heikinashi['open']
        dataframe['ha_close'] = heikinashi['close']
        dataframe['ha_high'] = heikinashi['high']
        dataframe['ha_low'] = heikinashi['low']



        upperband, middleband, lowerband = ta.BBANDS(dataframe['ha_close'], timeperiod=40, nbdevup=2.0, nbdevdn=2.0, matype=0)

        dataframe['lower'] = lowerband
        dataframe['mid'] = middleband



        dataframe['bb_upperband'] = upperband
        dataframe['bb_lowerband'] = dataframe['lower']
        dataframe['bb_middleband'] = dataframe['mid']
        dataframe['ema_fast'] = ta.EMA(dataframe['ha_close'], timeperiod=3)
        dataframe['ema_slow'] = ta.EMA(dataframe['ha_close'], timeperiod=50)
        dataframe['ema_200'] = ta.EMA(dataframe['ha_close'], timeperiod=200)
        dataframe['volume_mean_slow'] = dataframe['volume'].rolling(window=30).mean()
        dataframe['rocr'] = ta.ROCR(dataframe['ha_close'], timeperiod=28)  # 能找出低点，但是相对有一些局限性。比较延迟
        
        rsi = ta.RSI(dataframe, timeperiod=14)
        dataframe["rsi"] = rsi



        dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=4)
        dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=20)
        dataframe['hma_50'] = qtpylib.hull_moving_average(dataframe['close'], window=50)
        dataframe['ma_sell_22'] = ta.EMA(dataframe, timeperiod=22)

        bollinger2 = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
        dataframe['bb_lowerband2'] = bollinger2['lower']
        dataframe['bb_middleband2'] = bollinger2['mid']
        dataframe['bb_upperband2'] = bollinger2['upper']
        dataframe['bb_width'] = ((dataframe['bb_upperband2'] - dataframe['bb_lowerband2']) / dataframe['bb_middleband2'])

        dataframe['volume_mean_12'] = dataframe['volume'].rolling(12).mean().shift(1)
        dataframe['volume_mean_24'] = dataframe['volume'].rolling(24).mean().shift(1)

        dataframe['cmf'] = chaikin_money_flow(dataframe, 20)

        stoch_fast = ta.STOCHF(dataframe, 5, 3, 0, 3, 0)
        dataframe['fastd'] = stoch_fast['fastd']
        dataframe['fastk'] = stoch_fast['fastk']

        dataframe['vwma'] = pta.vwma(dataframe["ha_close"], dataframe["volume"], 28*0.98)


        dataframe['close_delta'] = (dataframe['close'] - 0.990 * dataframe['bb_lowerband']) / dataframe['close']

        return dataframe

    
    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
                        current_profit: float, **kwargs) -> float:

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        current_candle = dataframe.iloc[-1].squeeze()

        if current_time - timedelta(minutes=60) > trade.open_date_utc:
            if (current_candle["fastk"] > 75) and (current_profit > -0.01):
                return -0.001

        if current_time - timedelta(days=1) > trade.open_date_utc:
            if (current_candle["fastk"] > 75) and (current_profit > -0.05):
                return -0.001

        enter_tag = ''
        if hasattr(trade, 'enter_tag') and trade.enter_tag is not None:
            enter_tag = trade.enter_tag
        enter_tags = enter_tag.split()

        if current_profit >= 0.05:
            return -0.005

        if current_profit > 0:
            if current_candle["fastk"] > 75:
                return -0.001

        return self.stoploss

    def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
                    current_profit: float, **kwargs):
        
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        filled_buys = trade.select_filled_orders('buy')
        count_of_buys = len(filled_buys)
        buy_tag = 'empty'
        if hasattr(trade, 'buy_tag') and trade.buy_tag is not None:
            buy_tag = trade.buy_tag
        buy_tags = buy_tag.split()

        
        if last_candle is not None and "rocr" in buy_tags:
            if current_profit >= 0.022:
                return 'sell dazhuan'
            
            if (current_profit > 0.1) & (current_profit < 0.4) & (((trade.max_rate - trade.open_rate) / 100) > (current_profit + 0.03)):
                return 'trail_target_1'
            elif (current_profit > 0.04) & (current_profit < 0.11) & (((trade.max_rate - trade.open_rate) / 100) > (current_profit + 0.015)):
                return 'trail_target_2'
            elif (current_profit > 0.03) & (last_candle['rsi'] > 85):
                 return 'RSI-85 target'

            if (current_profit > 0) & (last_candle['close'] > last_candle['hma_50']) & (last_candle['close'] > (last_candle['ma_sell_22'] * 1.012)) &  (last_candle['volume'] > 0) & (last_candle['rsi_fast'] > last_candle['rsi_slow']):
                return 'sell signal2' 
            
            if ((current_profit < self.sell_deadfish_profit.value)
                and (last_candle['close'] < last_candle['ema_200'])
                and (last_candle['bb_width'] < self.sell_deadfish_bb_width.value)
                and (last_candle['close'] > last_candle['bb_middleband2'] * self.sell_deadfish_bb_factor.value)
                and (last_candle['volume_mean_12'] < last_candle['volume_mean_24'] * self.sell_deadfish_volume_factor.value)
                and (last_candle['cmf'] < 0.0)
            ):
                return "sell_stoploss_deadfish"

        if "close_delta" in buy_tags and last_candle is not None:



            if (current_time - timedelta(minutes=60) > trade.open_date_utc) and last_candle['enter_tag'] == "close_delta":
                return "sell chuanxi"



            if current_profit >= 0.0065:
                return 'sell xiaozhuan'








    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        
        dataframe.loc[
        (       
                (dataframe['rocr'] < 0.95) &
                (dataframe['ema_fast'] <= 0.988*dataframe['bb_lowerband'])
        ),
        ['enter_long', 'enter_tag']
        ] = (1, "rocr")

        dataframe.loc[
        (       
            (dataframe['rocr'] < 0.965) &   # 0.985
            (dataframe['ha_high'] < dataframe['vwma']) &
            (dataframe['close_delta'] >= 0) &
            (dataframe['close_delta'] <= 0.0035)


        ),
        ['enter_long', 'enter_tag']
        ] = (1, "close_delta")
        
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (dataframe["rocr"] > 1.02) &
            ((dataframe['ema_fast']) >= dataframe['bb_upperband']) &
            (dataframe['volume'] > 0),
            ['exit_long', 'exit_tag']
        ] = (1, "tag1")

        dataframe.loc[
            (dataframe["rocr"] > 1.06) &
            (dataframe["low"] > dataframe['vwma'])&
            (dataframe['volume'] > 0),
            ['exit_long', 'exit_tag']
        ] = (1, 'tag2')
        return dataframe

    def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float,
                           entry_tag: Optional[str], side: str, **kwargs) -> float:
        if pair == "BTC/USDT" or pair == "ETH/USDT":
            return proposed_rate
        return proposed_rate*0.99

    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: Optional[float], max_stake: float,
                            leverage: float, entry_tag: Optional[str], side: str,
                            **kwargs) -> float:
        if entry_tag == "rocr":
            return self.wallets.get_total_stake_amount() * 0.75
        else:
            return self.wallets.get_total_stake_amount() / self.config['max_open_trades']


def chaikin_money_flow(dataframe, n=20, fillna=False):
    mfv = ((dataframe['close'] - dataframe['low']) - (dataframe['high'] - dataframe['close'])) / (dataframe['high'] - dataframe['low'])
    mfv = mfv.fillna(0.0)  # float division by zero
    mfv *= dataframe['volume']
    cmf = (mfv.rolling(n, min_periods=0).sum()
           / dataframe['volume'].rolling(n, min_periods=0).sum())
    if fillna:
        cmf = cmf.replace([np.inf, -np.inf], np.nan).fillna(0)
    return Series(cmf, name='cmf')
    
