# source: https://raw.githubusercontent.com/DerSalvador/freqtrade-helm-chart/f80d4d8b77c53435e9c0a9045636f1bfb2b8c539/chart/deployed_strategies/TrailingBuyStrat.py
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from typing import Dict, List
from functools import reduce
from pandas import DataFrame, Series
# --------------------------------
import logging
import pandas as pd
import numpy as np
import datetime
from freqtrade.persistence import Trade
logger = logging.getLogger(__name__)

class Github_DerSalvador_freqtrade_helm_chart__TrailingBuyStrat__20260416_224245(IStrategy):
    INTERFACE_VERSION = 3
    # replace this by your strategy
    pass

class TrailingBuyStrat(Github_DerSalvador_freqtrade_helm_chart__TrailingBuyStrat__20260416_224245):
    # This class is designed to heritate from yours and starts trailing entry with your entry signals
    # Trailing entry starts at any entry signal
    # Trailing entry stops  with BUY if : price decreases and rises again more than trailing_entry_offset
    # Trailing entry stops with NO BUY : current price is > intial price * (1 +  trailing_entry_max) OR custom_exit tag
    # IT IS NOT COMPATIBLE WITH BACKTEST/HYPEROPT
    #
    # if process_only_new_candles = True, then you need to use 1m timeframe (and normal strat timeframe as informative)
    # if process_only_new_candles = False, it will use ticker data and you won't need to change anything
    trailing_entry_order_enabled = True
    trailing_entry_offset = 0.005  # rebound limit before a entry in % of initial price
    # (example with 0.5%. initial price : 100 (uplimit is 100.5), 2nd price : 99 (no entry, uplimit updated to 99.5), 3price 98 (no entry uplimit updated to 98.5), 4th price 99 -> BUY
    trailing_entry_max = 0.1  # stop trailing entry if current_price > starting_price * (1+trailing_entry_max)
    process_only_new_candles = False
    custom_info = dict()
    init_trailing_dict = {'trailing_entry_order_started': False, 'trailing_entry_order_uplimit': 0, 'start_trailing_price': 0, 'enter_tag': None}

    def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs):
        tag = super().custom_exit(pair, trade, current_time, current_rate, current_profit, **kwargs)
        if tag:
            self.custom_info[pair]['trailing_entry'] = self.init_trailing_dict
            logger.info(f'STOP trailing entry for {pair} because of {tag}')
        return tag

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = super().populate_indicators(dataframe, metadata)
        if not metadata['pair'] in self.custom_info:
            self.custom_info[metadata['pair']] = dict()
        if not 'trailing_entry' in self.custom_info[metadata['pair']]:
            self.custom_info[metadata['pair']]['trailing_entry'] = self.init_trailing_dict
        return dataframe

    def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, **kwargs) -> bool:
        val = super().confirm_trade_exit(pair, trade, order_type, amount, rate, time_in_force, exit_reason, **kwargs)
        self.custom_info[pair]['trailing_entry'] = self.init_trailing_dict
        return val

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        def get_local_min(x):
            win = dataframe.loc[:, 'barssince_last_entry'].iloc[x.shape[0] - 1].astype('int')
            win = max(win, 0)
            return pd.Series(x).rolling(window=win).min().iloc[-1]
        dataframe = super().populate_entry_trend(dataframe, metadata)
        dataframe = dataframe.rename(columns={'entry': 'pre_entry'})
        if self.trailing_entry_order_enabled and self.config['runmode'].value in ('live', 'dry_run'):  # trailing live dry ticker, 1m
            last_candle = dataframe.iloc[-1].squeeze()
            if not self.process_only_new_candles:
                current_price = self.get_current_price(metadata['pair'])
            else:
                current_price = last_candle['close']
            dataframe['enter_long'] = 0
            if not self.custom_info[metadata['pair']]['trailing_entry']['trailing_entry_order_started'] and last_candle['pre_entry'] == 1:
                self.custom_info[metadata['pair']]['trailing_entry'] = {'trailing_entry_order_started': True, 'trailing_entry_order_uplimit': last_candle['close'], 'start_trailing_price': last_candle['close'], 'enter_tag': last_candle['enter_tag'] if 'enter_tag' in last_candle else 'entry signal'}
                logger.info(f"start trailing entry for {metadata['pair']} at {last_candle['close']}")
            elif self.custom_info[metadata['pair']]['trailing_entry']['trailing_entry_order_started']:
                if current_price < self.custom_info[metadata['pair']]['trailing_entry']['trailing_entry_order_uplimit']:
                    # update uplimit
                    self.custom_info[metadata['pair']]['trailing_entry']['trailing_entry_order_uplimit'] = min(current_price * (1 + self.trailing_entry_offset), self.custom_info[metadata['pair']]['trailing_entry']['trailing_entry_order_uplimit'])
                    logger.info(f"update trailing entry for {metadata['pair']} at {self.custom_info[metadata['pair']]['trailing_entry']['trailing_entry_order_uplimit']}")
                elif current_price < self.custom_info[metadata['pair']]['trailing_entry']['start_trailing_price']:
                    # entry ! current price > uplimit but lower thant starting price
                    dataframe.iloc[-1, dataframe.columns.get_loc('enter_long')] = 1
                    ratio = '%.2f' % ((1 - current_price / self.custom_info[metadata['pair']]['trailing_entry']['start_trailing_price']) * 100)
                    if 'enter_tag' in dataframe.columns:
                        dataframe.iloc[-1, dataframe.columns.get_loc('enter_tag')] = f"{self.custom_info[metadata['pair']]['trailing_entry']['enter_tag']} ({ratio} %)"
                    # stop trailing when entry signal ! prevent from entrying much higher price when slot is free
                    self.custom_info[metadata['pair']]['trailing_entry'] = self.init_trailing_dict
                    logger.info(f"STOP trailing entry for {metadata['pair']} because I entry it {ratio}")
                elif current_price > self.custom_info[metadata['pair']]['trailing_entry']['start_trailing_price'] * (1 + self.trailing_entry_max):
                    self.custom_info[metadata['pair']]['trailing_entry'] = self.init_trailing_dict
                    logger.info(f"STOP trailing entry for {metadata['pair']} because of the price is higher than starting prix * {1 + self.trailing_entry_max}")
                else:
                    logger.info(f"price to high for {metadata['pair']} at {current_price} vs {self.custom_info[metadata['pair']]['trailing_entry']['trailing_entry_order_uplimit']}")
        elif self.trailing_entry_order_enabled:
            # FOR BACKTEST
            # NOT WORKING
            dataframe.loc[(dataframe['pre_entry'] == 1) & (dataframe['pre_entry'].shift() == 0), 'pre_entry_switch'] = 1
            dataframe['pre_entry_switch'] = dataframe['pre_entry_switch'].fillna(0)
            dataframe['barssince_last_entry'] = dataframe['pre_entry_switch'].groupby(dataframe['pre_entry_switch'].cumsum()).cumcount()
            # Create integer positions of each row
            idx_positions = np.arange(len(dataframe))
            # "shift" those integer positions by the amount in shift col
            shifted_idx_positions = idx_positions - dataframe['barssince_last_entry']
            # get the label based index from our DatetimeIndex
            shifted_loc_index = dataframe.index[shifted_idx_positions]
            # Retrieve the "shifted" values and assign them as a new column
            dataframe['close_5m_last_entry'] = dataframe.loc[shifted_loc_index, 'close_5m'].values
            dataframe.loc[:, 'close_lower'] = dataframe.loc[:, 'close'].expanding().apply(get_local_min)
            dataframe['close_lower'] = np.where(dataframe['close_lower'].isna() == True, dataframe['close'], dataframe['close_lower'])
            dataframe['close_lower_offset'] = dataframe['close_lower'] * (1 + self.trailing_entry_offset)
            dataframe['trailing_entry_order_uplimit'] = np.where(dataframe['barssince_last_entry'] < 20, pd.DataFrame([dataframe['close_5m_last_entry'], dataframe['close_lower_offset']]).min(), np.nan)  # must entry within last 20 candles after signal
            dataframe.loc[(dataframe['barssince_last_entry'] < 20) & (dataframe['close'] > dataframe['trailing_entry_order_uplimit']), 'trailing_entry'] = 1
            dataframe['trailing_entry_count'] = dataframe['trailing_entry'].rolling(20).sum()
            dataframe.log[(dataframe['trailing_entry'] == 1) & (dataframe['trailing_entry_count'] == 1), 'enter_long'] = 1
        else:  # No entry trailing
            dataframe.loc[dataframe['pre_entry'] == 1, 'enter_long'] = 1
        return dataframe

    def get_current_price(self, pair: str) -> float:
        ticker = self.dp.ticker(pair)
        current_price = ticker['last']
        return current_price