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

from freqtrade.strategy.interface import IStrategy
import freqtrade.vendor.qtpylib.indicators as qtpylib
import pandas_ta as pta
import talib.abstract as ta
import numpy as np

from pandas import DataFrame, Series, DatetimeIndex, merge
from datetime import datetime, timedelta
from freqtrade.persistence import Trade
from freqtrade.strategy import merge_informative_pair, CategoricalParameter, DecimalParameter, IntParameter, stoploss_from_open

import math
import logging

logger = logging.getLogger(__name__)

def top_percent_change(dataframe: DataFrame, length: int) -> float:
        """
        Percentage change of the current close from the range maximum Open price

        :param dataframe: DataFrame The original OHLC dataframe
        :param length: int The length to look back
        """
        if length == 0:
            return (dataframe['open'] - dataframe['close']) / dataframe['close']
        else:
            return (dataframe['open'].rolling(length).max() - dataframe['close']) / dataframe['close']

def williams_r(dataframe: DataFrame, period: int = 14) -> 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 = Series(
        (highest_high - dataframe["close"]) / (highest_high - lowest_low),
        name=f"{period} Williams %R",
        )

    return WR * -100

class Github_remiotore_freqtrade__McQueen1__20260111_210550(IStrategy):
    """

    Designed to use with Github_remiotore_freqtrade__McQueen1__20260111_210550 DCA settings

    TTP: 1.1%(0.2%), BO: 38.0 USDT, SO: 38.0 USDT, OS: 1.2, SS: 1.13, MAD: 2, SOS: 1.6, MSTC: 11


    2021/12 Crash
        ========================================================== BUY TAG STATS ===========================================================
    |   TAG |   Buys |   Avg Profit % |   Cum Profit % |   Tot Profit USDT |   Tot Profit % |   Avg Duration |   Win  Draw  Loss  Win% |
    |-------+--------+----------------+----------------+-------------------+----------------+----------------+-------------------------|
    | TOTAL |    412 |           1.14 |         469.43 |          1157.492 |           0.45 |        5:04:00 |   412     0     0   100 |

    2021/05 Crash
        ========================================================== BUY TAG STATS ===========================================================
    |   TAG |   Buys |   Avg Profit % |   Cum Profit % |   Tot Profit USDT |   Tot Profit % |   Avg Duration |   Win  Draw  Loss  Win% |
    |-------+--------+----------------+----------------+-------------------+----------------+----------------+-------------------------|
    | TOTAL |    197 |           1.25 |         245.79 |           631.840 |           0.25 |        4:22:00 |   197     0     0   100 |

    2021/09 - 2021/11 Bull
        ========================================================== BUY TAG STATS ===========================================================
    |   TAG |   Buys |   Avg Profit % |   Cum Profit % |   Tot Profit USDT |   Tot Profit % |   Avg Duration |   Win  Draw  Loss  Win% |
    |-------+--------+----------------+----------------+-------------------+----------------+----------------+-------------------------|
    | TOTAL |    327 |           1.30 |         424.98 |           961.187 |           0.37 |        3:26:00 |   326     0     1  99.7 |

    """

    minimal_roi = {
        "0": 0.1,
    }

    sell_params = {
        "pHSL": -0.998,

        "pPF_1": 0.011,
        "pPF_2": 0.065,
        "pSL_1": 0.011,
        "pSL_2": 0.062,
    }

    trailing_stop = True
    trailing_stop_positive = 0.002
    trailing_stop_positive_offset = 0.075
    trailing_only_offset_is_reached = True

    stoploss = -0.998

    use_custom_stoploss = True

    process_only_new_candles = True
    startup_candle_count = 168

    timeframe = '5m'

    pHSL = DecimalParameter(-0.500, -0.040, default=-0.08, decimals=3, space='sell', load=True)

    pPF_1 = DecimalParameter(0.008, 0.020, default=0.016, decimals=3, space='sell', load=True)
    pSL_1 = DecimalParameter(0.008, 0.020, default=0.011, decimals=3, space='sell', load=True)

    pPF_2 = DecimalParameter(0.040, 0.100, default=0.080, decimals=3, space='sell', load=True)
    pSL_2 = DecimalParameter(0.020, 0.070, default=0.040, decimals=3, space='sell', load=True)

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

        HSL = self.pHSL.value
        PF_1 = self.pPF_1.value
        SL_1 = self.pSL_1.value
        PF_2 = self.pPF_2.value
        SL_2 = self.pSL_2.value




        if current_profit > PF_2:
            sl_profit = SL_2 + (current_profit - PF_2)
        elif current_profit > PF_1:
            sl_profit = SL_1 + ((current_profit - PF_1) * (SL_2 - SL_1) / (PF_2 - PF_1))
        else:
            sl_profit = HSL

        if sl_profit >= current_profit:
            return -0.99

        return stoploss_from_open(sl_profit, current_profit)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['rsi_84'] = ta.RSI(dataframe, timeperiod=84)
        dataframe['rsi_112'] = ta.RSI(dataframe, timeperiod=112)

        bollinger1 = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=17, stds=1)
        dataframe['bb_lowerband'] = bollinger1['lower']
        dataframe['bb_middleband'] = bollinger1['mid']
        dataframe['bb_upperband'] = bollinger1['upper']

        dataframe['closedelta'] = (dataframe['close'] - dataframe['close'].shift()).abs()

        dataframe['tpct_change_0']   = top_percent_change(dataframe, 0)
        dataframe['tpct_change_1']   = top_percent_change(dataframe, 1)
        dataframe['tpct_change_2']   = top_percent_change(dataframe, 2)
        dataframe['tpct_change_4']   = top_percent_change(dataframe, 4)
        dataframe['tpct_change_5']   = top_percent_change(dataframe, 5)
        dataframe['tpct_change_9']   = top_percent_change(dataframe, 9)

        dataframe['sma_50'] = ta.SMA(dataframe['close'], timeperiod=50)
        dataframe['sma_200'] = ta.SMA(dataframe['close'], timeperiod=200)

        dataframe['cti'] = pta.cti(dataframe["close"], length=20)

        dataframe['adx'] = ta.ADX(dataframe)

        dataframe['r_14'] = williams_r(dataframe, period=14)
        dataframe['r_96'] = williams_r(dataframe, period=96)

        dataframe['hl2'] = (dataframe['high'] + dataframe['low']) / 2
        dataframe['mama'], dataframe['fama'] = ta.MAMA(dataframe['hl2'], 0.5, 0.05)
        dataframe['mama_diff'] = ( ( dataframe['mama'] - dataframe['fama'] ) / dataframe['hl2'] )

        crsi_closechange = dataframe['close'] / dataframe['close'].shift(1)
        crsi_updown = np.where(crsi_closechange.gt(1), 1.0, np.where(crsi_closechange.lt(1), -1.0, 0.0))
        dataframe['crsi'] =  (ta.RSI(dataframe['close'], timeperiod=3) + ta.RSI(crsi_updown, timeperiod=2) + ta.ROC(dataframe['close'], 100)) / 3

        inf_tf = '1h'

        informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)

        informative['cti_40'] = pta.cti(informative["close"], length=40)

        informative['r_96'] = williams_r(informative, period=96)
        informative['r_480'] = williams_r(informative, period=480)

        informative['hl2'] = (informative['high'] + informative['low']) / 2
        informative['mama'], informative['fama'] = ta.MAMA(informative['hl2'], 0.5, 0.05)
        informative['mama_diff'] = ( ( informative['mama'] - informative['fama'] ) / informative['hl2'] )

        dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)

        return dataframe

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        is_crash_1 = (
                (dataframe['tpct_change_1'] < 0.08) &
                (dataframe['tpct_change_2'] < 0.08) &
                (dataframe['tpct_change_4'] < 0.10)
            )

        dataframe.loc[
            (
                (

                    (dataframe['close'] < dataframe['mama']) &
                    (dataframe['r_14'] < -30) &
                    (dataframe['cti'] < 3.0) &
                    (dataframe['adx'] > 26) &
                    (dataframe['mama_diff_1h'] > 0.003) &

                    (dataframe['mama'] > dataframe['fama']) &
                    (dataframe['sma_50'] > dataframe['sma_200'] * 1.01) &
                    (dataframe['mama_1h'] > dataframe['fama_1h'] * 1.01) &

                    (dataframe['rsi_84'] < 55) &
                    (dataframe['rsi_112'] < 55) &
                    (dataframe['cti_40_1h'] < 0.73) &
                    (dataframe['r_96_1h'] < -6) &
                    (dataframe['mama_diff_1h'] < 0.027) &
                    (dataframe['close'].rolling(288).max() >= (dataframe['close'] * 1.03 ))
                )
            ),
            'buy'] = 1
        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe['close'] > dataframe['bb_upperband'] * 0.999) &
                (dataframe['rsi'] > 76)
            ),
            'sell'] = 0
        return dataframe

class Github_remiotore_freqtrade__McQueen1__20260111_210550(Github_remiotore_freqtrade__McQueen1__20260111_210550):

    position_adjustment_enable = True

    initial_safety_order_trigger = -0.016
    max_safety_orders = 5
    safety_order_step_scale = 1.13         #SS
    safety_order_volume_scale = 1.2        #OS

    max_dca_multiplier = (1 + max_safety_orders)
    if (max_safety_orders > 0):
        if (safety_order_volume_scale > 1):
            max_dca_multiplier = (2 + (safety_order_volume_scale * (math.pow(safety_order_volume_scale, (max_safety_orders - 1)) - 1) / (safety_order_volume_scale - 1)))
        elif (safety_order_volume_scale < 1):
            max_dca_multiplier = (2 + (safety_order_volume_scale * (1 - math.pow(safety_order_volume_scale, (max_safety_orders - 1))) / (1 - safety_order_volume_scale)))

    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: float, max_stake: float,
                            **kwargs) -> float:

        if self.config['stake_amount'] == 'unlimited':
            return proposed_stake / self.max_dca_multiplier

        return proposed_stake

    def adjust_trade_position(self, trade: Trade, current_time: datetime,
                              current_rate: float, current_profit: float, min_stake: float,
                              max_stake: float, **kwargs):

        if current_profit > self.initial_safety_order_trigger:
            return None

        count_of_buys = trade.nr_of_successful_buys

        if 1 <= count_of_buys <= self.max_safety_orders:
            safety_order_trigger = (abs(self.initial_safety_order_trigger) * count_of_buys)
            if (self.safety_order_step_scale > 1):
                safety_order_trigger = abs(self.initial_safety_order_trigger) + (abs(self.initial_safety_order_trigger) * self.safety_order_step_scale * (math.pow(self.safety_order_step_scale,(count_of_buys - 1)) - 1) / (self.safety_order_step_scale - 1))
            elif (self.safety_order_step_scale < 1):
                safety_order_trigger = abs(self.initial_safety_order_trigger) + (abs(self.initial_safety_order_trigger) * self.safety_order_step_scale * (1 - math.pow(self.safety_order_step_scale,(count_of_buys - 1))) / (1 - self.safety_order_step_scale))

            if current_profit <= (-1 * abs(safety_order_trigger)):
                try:
                    stake_amount = self.wallets.get_trade_stake_amount(trade.pair, None)

                    stake_amount = stake_amount / self.max_dca_multiplier

                    stake_amount = stake_amount * math.pow(self.safety_order_volume_scale, (count_of_buys - 1))
                    amount = stake_amount / current_rate
                    logger.info(f"Initiating safety order buy #{count_of_buys} for {trade.pair} with stake amount of {stake_amount} which equals {amount}")
                    return stake_amount
                except Exception as exception:
                    logger.info(f'Error occured while trying to get stake amount for {trade.pair}: {str(exception)}')
                    return None

        return None

