# source: https://raw.githubusercontent.com/DerSalvador/freqtrade-helm-chart/a669dc11b640b0eb63aa8f8b51e9f181fd7ee43c/chart/deployed_strategies/binance-michael-k8s-namespace/EnsembleStrategy.py
from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter, stoploss_from_open
import logging
from pandas import DataFrame
from freqtrade.resolvers import StrategyResolver
from itertools import combinations
from functools import reduce
from freqtrade.persistence import Trade
from datetime import datetime
logger = logging.getLogger(__name__)
# "AwesomeMacd",
STRATEGIES = ['CombinedBinHAndCluc', 'CombinedBinHAndClucV2', 'CombinedBinHAndClucV5', 'CombinedBinHAndClucV7', 'CombinedBinHAndClucV8', 'SMAOffset', 'SMAOffsetV2', 'SMAOffsetProtectOptV0', 'SMAOffsetProtectOptV1', 'NostalgiaForInfinityV1', 'NostalgiaForInfinityV2', 'NostalgiaForInfinityV3', 'NostalgiaForInfinityV4', 'NostalgiaForInfinityV5', 'NostalgiaForInfinityV7', 'Obelisk_Ichimoku_ZEMA_v1']
STRAT_COMBINATIONS = reduce(lambda x, y: list(combinations(STRATEGIES, y)) + x, range(len(STRATEGIES) + 1), [])
MAX_COMBINATIONS = len(STRAT_COMBINATIONS) - 2

class Github_DerSalvador_freqtrade_helm_chart__EnsembleStrategy__20260115_122204(IStrategy):
    INTERFACE_VERSION = 3
    loaded_strategies = {}
    informative_timeframe = '1h'
    entry_action_diff_threshold = DecimalParameter(0, 1, default=0, decimals=2, optimize=True, load=True)
    entry_strategies = IntParameter(0, MAX_COMBINATIONS, default=0, optimize=True, load=True)
    # trailing stoploss hyperopt parameters
    # hard stoploss profit
    exit_HSL = DecimalParameter(-0.2, -0.04, default=-0.08, decimals=3, optimize=True, load=True)
    # profit threshold 1, trigger point, SL_1 is used
    exit_PF_1 = DecimalParameter(0.008, 0.02, default=0.016, decimals=3, optimize=True, load=True)
    exit_SL_1 = DecimalParameter(0.008, 0.02, default=0.011, decimals=3, optimize=True, load=True)
    # profit threshold 2, SL_2 is used
    exit_PF_2 = DecimalParameter(0.04, 0.1, default=0.08, decimals=3, optimize=True, load=True)
    exit_SL_2 = DecimalParameter(0.02, 0.07, default=0.04, decimals=3, optimize=True, load=True)
    stoploss = -0.99  # effectively disabled.
    exit_profit_offset = 0.001  # it doesn't meant anything, just to guarantee there is a minimal profit.
    use_exit_signal = False
    ignore_roi_if_entry_signal = False
    exit_profit_only = False
    # Trailing stoploss
    trailing_stop = False
    trailing_only_offset_is_reached = False
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.025
    # Custom stoploss
    use_custom_stoploss = True
    # Run "populate_indicators()" only for new candle.
    process_only_new_candles = True
    # Number of candles the strategy requires before producing valid signals
    startup_candle_count: int = 200
    minimal_roi = {'0': 100.0}
    entry_params = {}
    exit_params = {}
    protections = [{'method': 'CooldownPeriod', 'stop_duration_candles': 2}, {'method': 'StoplossGuard', 'lookback_period_candles': 100, 'trade_limit': 4, 'stop_duration_candles': 10, 'only_per_pair': True}]

    def __init__(self, config: dict) -> None:
        super().__init__(config)
        logger.info(f'Buy stratrategies: {STRAT_COMBINATIONS[self.entry_strategies.value]}')

    def informative_pairs(self):
        pairs = self.dp.current_whitelist()
        informative_pairs = [(pair, self.informative_timeframe) for pair in pairs]
        return informative_pairs

    def get_strategy(self, strategy_name):
        strategy = self.loaded_strategies.get(strategy_name)
        if not strategy:
            config = self.config
            config['strategy'] = strategy_name
            strategy = StrategyResolver.load_strategy(config)
        strategy.dp = self.dp
        strategy.wallets = self.wallets
        self.loaded_strategies[strategy_name] = strategy
        return strategy

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        strategies = STRAT_COMBINATIONS[self.entry_strategies.value]
        for strategy_name in strategies:
            strategy = self.get_strategy(strategy_name)
            strategy_indicators = strategy.advise_indicators(dataframe, metadata)
            dataframe[f'strat_entry_signal_{strategy_name}'] = strategy.advise_entry(strategy_indicators, metadata)['enter_long']
        dataframe['enter_long'] = (dataframe.filter(like='strat_entry_signal_').mean(axis=1) > self.entry_action_diff_threshold.value).astype(int)
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['exit_long'] = 0
        return dataframe

    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float:
        """
        new custom stoploss, both hard and trailing functions. Trailing stoploss first rises at a slower
        rate than the current rate until a profit threshold is reached, after which it rises at a constant
        percentage as per a normal trailing stoploss. This allows more margin for pull-backs during a rise.
        """
        # hard stoploss profit
        HSL = self.exit_HSL.value
        PF_1 = self.exit_PF_1.value
        SL_1 = self.exit_SL_1.value
        PF_2 = self.exit_PF_2.value
        SL_2 = self.exit_SL_2.value
        # For profits between PF_1 and PF_2 the stoploss (sl_profit) used is linearly interpolated
        # between the values of SL_1 and SL_2. For all profits above PL_2 the sl_profit value
        # rises linearly with current profit, for profits below PF_1 the hard stoploss profit is used.
        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 current_profit > PF_1:
            stoploss = stoploss_from_open(sl_profit, current_profit)
        else:
            stoploss = stoploss_from_open(HSL, current_profit)
        return stoploss or stoploss_from_open(HSL, current_profit) or 1