# source: https://raw.githubusercontent.com/remiotore/freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/vin.py
from pandas.core.series import Series
from freqtrade.strategy import IStrategy, informative
from freqtrade.exchange import timeframe_to_prev_date
from freqtrade.persistence import Trade
import logging
import numpy as np
from pandas import DataFrame
from functools import reduce
from datetime import datetime
import locale
locale.setlocale(category=locale.LC_ALL, locale='')
log = logging.getLogger(__name__)

class Github_remiotore_freqtrade__vin__20260111_210550(IStrategy):
    INTERFACE_VERSION = 2

    def version(self) -> str:
        return 'v2.0.1'

    min_day_listed: int = 5
    pct_lb = range(21, 29)
    lc2_lb = range(15, 21)
    custom_buy_info = {}
    max_concurrent_buy_signals_check = True

    minimal_roi = {"0": 100}
    stoploss = -1
    stoploss_on_exchange = False
    trailing_stop = False
    use_custom_stoploss = False
    timeframe = '5m'
    process_only_new_candles = True
    use_sell_signal = True
    sell_profit_only = False
    startup_candle_count: int = 36

    @property
    def protections(self):
        return [
            {
                "method": "CooldownPeriod",
                "stop_duration_candles": 36
            }
        ]

    @informative('1d')
    def populate_indicators_1h(self, df: DataFrame, metadata: dict) -> DataFrame:
        i = self.min_day_listed
        df['candle_count'] = df['volume'].rolling(window=i, min_periods=i).count()
        return df

    def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame:
        green = (df['close'] - df['open']).ge(0)
        bodysize = (df['close'] / df['open']).where(green, df['open'] / df['close'])
        hi_adj = df['close'].where(green, df['open']) + (df['high'] - df['close']).where(green, (df['high'] - df['open'])) / bodysize.pow(0.25)
        lo_adj = df['open'].where(green, df['close']) - (df['open'] - df['low']).where(green, (df['close'] - df['low'])) / bodysize.pow(0.25)
        df['hlc3_adj'] = (hi_adj + lo_adj + df['close']) / 3
        df['lc2_adj'] = (lo_adj + df['close']) / 2
        df['hc2_adj'] = (hi_adj + df['close']) / 2
        df['ho2_adj'] = (hi_adj + df['open']) / 2
        df_closechange = df['close'] - df['close'].shift(1)
        s = (1, 2, 3)
        for i in s:
            df['updown'] = np.where(df_closechange.rolling(window=i, min_periods=i).sum().gt(0), 1, np.where(df_closechange.rolling(window=i, min_periods=i).sum().lt(0), -1, 0))
            df[f"streak_{i}"] = df['updown'].groupby((df['updown'].ne(df['updown'].shift(1))).cumsum()).cumsum()
        df['streak_s_min'] = df[[f"streak_{i}" for i in s]].min(axis=1)
        df['streak_s_min_change'] = df['close'] / df['close'].to_numpy()[df.index.to_numpy() - df['streak_s_min'].abs().to_numpy()]
        df['streak_s_max'] = df[[f"streak_{i}" for i in s]].max(axis=1)
        df.drop(columns=[f"streak_{i}" for i in s], inplace=True)
        i = 12
        df['updown'] = np.where(df_closechange.rolling(window=i, min_periods=i).sum().gt(0), 1, np.where(df_closechange.rolling(window=i, min_periods=i).sum().lt(0), -1, 0))
        df['streak_h'] = df['updown'].groupby((df['updown'].ne(df['updown'].shift(1))).cumsum()).cumsum()

        df.drop(columns=['updown'], inplace=True)
        for i in self.pct_lb:
            df[f"pctchange_{i}"] = df['close'].pct_change(periods=i)
            pctchange_mean = df[f"pctchange_{i}"].rolling(window=i, min_periods=i).mean()
            pctchange_std = df[f"pctchange_{i}"].rolling(window=i, min_periods=i).std()
            df[f"bb_pctchange_{i}_up"] = pctchange_mean + 2 * pctchange_std
            df[f"bb_pctchange_{i}_lo"] = pctchange_mean - 2 * pctchange_std
            df[f"volume_mean_{i}"] = df['volume'].rolling(window=i, min_periods=i).mean()
        for i in self.lc2_lb:
            df[f"lc2_low_{i}"] = df['lc2_adj'].rolling(window=i, min_periods=i).min()
            df[f"volume_mean_{i}"] = df['volume'].rolling(window=i, min_periods=i).mean()
        return df

    def fill_custom_buy_info(self, df:DataFrame, metadata: dict):
        df_buy: Series = df.loc[df['buy'], 'date']
        for index, buy_date in df_buy.iteritems():
            if buy_date not in self.custom_buy_info:
                self.custom_buy_info[buy_date] = {}
                self.custom_buy_info[buy_date]['buy_signals'] = 1
            else:
                self.custom_buy_info[buy_date]['buy_signals'] += 1
        return None

    def populate_buy_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        b = __name__.lower()
        if b in ('Github_remiotore_freqtrade__vin__20260111_210550', 'Github_remiotore_freqtrade__vin__20260111_210550pct'):
            df.loc[:, 'buy_tag_pct'] = ''
            for i in self.pct_lb:
                buy_conditions = [
                    df['candle_count_1d'].ge(self.min_day_listed),
                    df['volume'].gt(df[f"volume_mean_{i}"].shift(1)),
                    df['streak_s_min'].le(-1),
                    df['streak_s_max'].between(-5, 0),
                    df['streak_h'].ge(-20),
                    df['streak_s_min'].ge(df['streak_h']),
                    df['streak_s_min_change'].le(0.97),
                    (df[f"pctchange_{i}"] / df[f"bb_pctchange_{i}_lo"]).between(1.01, 1.39),
                    (df[f"bb_pctchange_{i}_up"] - df[f"bb_pctchange_{i}_lo"]).ge(0.02),
                    (df['lc2_adj'] / df['close']).between(0.945, 0.995)
                ]
                buy = reduce(lambda x, y: x & y, buy_conditions)
                df.loc[buy, 'buy_tag_pct'] += f"{i} "
            df.loc[:, 'buy'] = df['buy_tag_pct'].ne('')
            df.loc[df['buy'], 'buy_tag'] = 'pct ' + df['buy_tag_pct'].str.strip()
        if b in ('Github_remiotore_freqtrade__vin__20260111_210550', 'Github_remiotore_freqtrade__vin__20260111_210550lc2'):
            df.loc[:, 'buy_tag_lc2'] = ''
            for i in self.lc2_lb:
                buy_conditions = [
                    df['candle_count_1d'].ge(self.min_day_listed),
                    df['volume'].gt(df[f"volume_mean_{i}"].shift(1)),
                    df['streak_s_max'].between(-5, 0),
                    df['streak_h'].ge(-20),
                    (df['lc2_adj'] / df[f"lc2_low_{i}"].shift(1)).le(0.97),
                    (df['lc2_adj'] / df['close']).between(0.945, 0.995)
                ]
                buy = reduce(lambda x, y: x & y, buy_conditions)
                df.loc[buy, 'buy_tag_lc2'] += f"{i} "
            df.loc[:, 'buy'] = df['buy_tag_lc2'].eq('15 16 17 18 19 20 ')
            df.loc[df['buy'], 'buy_tag'] = 'lc2 ' + df['buy_tag_lc2'].str.strip()
        if b == 'Github_remiotore_freqtrade__vin__20260111_210550':
            df.loc[:, 'buy'] = df['buy_tag_pct'].ne('') | df['buy_tag_lc2'].eq('15 16 17 18 19 20 ')
            df.loc[df['buy'], 'buy_tag'] = (('pct ' + df['buy_tag_pct']).where(df['buy_tag_pct'].ne(''), '') + ('lc2 ' + df['buy_tag_lc2']).where(df['buy_tag_lc2'].eq('15 16 17 18 19 20 '), '')).str.strip()
        self.fill_custom_buy_info(df, metadata)
        return df

    def populate_sell_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        df.loc[:, 'sell'] = False
        return df

    def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
                    current_profit: float, **kwargs):
        df: DataFrame = self.dp.get_analyzed_dataframe(pair, self.timeframe)[0]
        trade_open_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
        df_trade = df.loc[df['date'].ge(trade_open_date)]
        trade_len = len(df_trade)
        candle_1 = df_trade.iloc[-1]
        trade_recent_buys = df_trade['buy'].tail(min(trade_len, 6)).sum()
        d = candle_1['date'].strftime('%Y-%m-%d %H:%M')
        if trade_len <= 2 or trade_recent_buys >= 1 or candle_1['streak_s_min'] >= -1 or candle_1['streak_s_max'] >= 1:
            return None
        candle_2 = df_trade.iloc[-2]
        candle_min = df_trade.loc[df_trade['hlc3_adj'] <= 1.001 * df_trade['hlc3_adj'].min()].iloc[-1]
        candle_max = df_trade.loc[df_trade['hlc3_adj'] >= 0.999 * df_trade['hlc3_adj'].max()].iloc[-1]
        rise = candle_max['hlc3_adj'] / candle_min['hlc3_adj'] if candle_max['date'] > candle_min['date'] else 1
        fall = candle_max['hlc3_adj'] / candle_1['hlc3_adj']
        cp = (candle_1['close'] - trade.open_rate) / trade.open_rate
        u: str = trade.buy_tag[:3]
        volume_mean = df_trade['volume'].mean()
        if fall > max(1.15 - trade_len / 5000, pow(rise, 0.5)):
            log.info(f"{d} custom_sell: fall sell {cp:.2f} for pair {pair} with trade len {trade_len}.")
            return f"fall {u}"
        if candle_1['volume'] >= volume_mean:
            if candle_1['hc2_adj'] / candle_1['close'] >= 1.015 and candle_1['volume'] / candle_2['volume'] <= 0.8:
                log.info(f"{d} custom_sell: hc2c sell {cp:.2f} for pair {pair} with trade len {trade_len}.")
                return f"hc2c {u}"
            if candle_1['ho2_adj'] / candle_1['open'] >= 1.005 and candle_1['volume'] / candle_2['volume'] <= 0.7:
                log.info(f"{d} custom_sell: ho2o sell {cp:.2f}for pair {pair} with trade len {trade_len}.")
                return f"ho2o {u}"
        j = int(36 * rise * fall)
        if trade_len > j:
            if df_trade['hlc3_adj'].tail(j).max() / df_trade['hlc3_adj'].tail(j).min() <= 1.03 and candle_1['streak_s_max'] < 1 and candle_1['streak_s_min'] < 0:
                log.info(f"{d} custom_sell: side sell {cp:.2f} for pair {pair} with trade len {trade_len}.")
                return f"side {u}"
        return None

    def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
                            time_in_force: str, current_time: datetime, **kwargs) -> bool:
        if self.max_concurrent_buy_signals_check:
            df: DataFrame = self.dp.get_analyzed_dataframe(pair, self.timeframe)[0]
            buy_candle_date = df['date'].iloc[-1]
            d = buy_candle_date.strftime('%Y-%m-%d %H:%M')
            try:
                buy_info = self.custom_buy_info[buy_candle_date]

                buy_signal_count = buy_info['buy_signals']
            except:
                log.warning(f"{d} confirm_trade_entry: No buy info for pair {pair}.")
                return False
            else:
                pairs = len(self.dp.current_whitelist())
                max_concurrent_buy_signals = max(int(pairs * 0.08), 2)
                if buy_signal_count > max_concurrent_buy_signals:
                    log.info(f"{d} confirm_trade_entry: Cancel buy for pair {pair}. There are {buy_signal_count} concurrent buy signals (max = {max_concurrent_buy_signals}).")
                    return False
                log.info(f"{d} confirm_trade_entry: Buy for pair {pair} with {buy_signal_count} concurrent buy signals.")
        return True

    def confirm_trade_exit(self, pair: str, trade: "Trade", order_type: str, amount: float,
                           rate: float, time_in_force: str, sell_reason: str, **kwargs) -> bool:
        if self.max_concurrent_buy_signals_check and sell_reason[:4] == 'fall':
            df: DataFrame = self.dp.get_analyzed_dataframe(pair, self.timeframe)[0]
            pairs = len(self.dp.current_whitelist())
            max_concurrent_buy_signals = max(int(pairs * 0.04), 1)
            for i in (-1, -2):
                try:
                    buy_candle_date = df['date'].iloc[i]
                    d = buy_candle_date.strftime('%Y-%m-%d %H:%M')
                    buy_info = self.custom_buy_info[buy_candle_date]
                    buy_signal_count = buy_info['buy_signals']
                except:
                    return True
                else:
                    if buy_signal_count > max_concurrent_buy_signals:
                        log.info(f"{d} confirm_trade_exit: Cancel sell {sell_reason} for pair {pair}. There are {buy_signal_count} concurrent buy signals in candle {i} (max = {max_concurrent_buy_signals}).")
                        return False                    
        return True

class ViNPct(Github_remiotore_freqtrade__vin__20260111_210550):
    pass

class ViNLc2(Github_remiotore_freqtrade__vin__20260111_210550):
    pass

class ViNHlc3(Github_remiotore_freqtrade__vin__20260111_210550):
    hlc3_lb = range(16, 30)
    def populate_indicators_buy(self, df: DataFrame, metadata: dict) -> DataFrame:
        for i in self.hlc3_lb:
            df[f"hlc3_volatility_{i}"] = df['hlc3_adj'].rolling(window=i, min_periods=i).max() / df['hlc3_adj'].rolling(window=i, min_periods=i).min()
            df[f"volume_mean_{i}"] = df['volume'].rolling(window=i, min_periods=i).mean()
        return df

    def populate_buy_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        df.loc[:, 'buy_tag'] = ''
        for i in self.hlc3_lb:
            buy_conditions = [
                df['candle_count_1d'].ge(self.min_day_listed),
                df[f"hlc3_volatility_{i}"].shift(2).le(1.02),
                (df['volume'].shift(1) / df[f"volume_mean_{i}"].shift(2)).gt(1.01),
                (df['volume'] / df['volume'].shift(1)).gt(1.02),
                (df['hlc3_adj'] / df['hlc3_adj'].shift(1)).gt(1.02)

            ]
            buy = reduce(lambda x, y: x & y, buy_conditions)
            df.loc[buy, 'buy_tag'] += f"{i} "
        df.loc[:, 'buy'] = df['buy_tag'].ne('')
        df.loc[df['buy'], 'buy_tag'] = 'hlc3 ' + df['buy_tag'].str.strip()

        self.fill_custom_buy_info(df, metadata)
        return df