# source: https://raw.githubusercontent.com/mohsenjfar/trade/17ea10e916b730006e142258d67c307b102faca1/user_data/Archive/cluster_strategy_v2_1.py
# directory_url: https://github.com/mohsenjfar/trade/blob/main/user_data/Archive/
# User: mohsenjfar
# Repository: trade
# --------------------from pandas import DataFrame
from freqtrade.persistence import Trade
from sklearn.cluster import KMeans
from datetime import datetime
from typing import Optional
from freqtrade.persistence import Trade

from freqtrade.strategy import (
    IStrategy,
    stoploss_from_absolute
)

class Github_mohsenjfar_trade__cluster_strategy_v2_1__20260816_043218(IStrategy):

    ''' Specs:
    - Use 4h timeframe as cluster timeframe
    - Use 1m timeframe as main timeframe
    - Split cluster timeframe to 6 clusters
    - When price crosses above one cluster border open long position and put stop second border below
    - When price crosses below one cluster border open short position and put stop second border above
    - Trail after each border
    '''

    INTERFACE_VERSION = 3

    can_short: bool = True

    stoploss = -0.01

    timeframe = '1m'

    total_risk = 0.005

    process_only_new_candles = False

    use_exit_signal = False

    exit_profit_only = False

    ignore_roi_if_entry_signal = False

    use_custom_stoploss = True

    position_adjustment_enable = False

    startup_candle_count: int = 240

    order_types = {
        'entry': 'limit',
        'exit': 'limit',
        'stoploss': 'limit',
        'stoploss_on_exchange': True
    }

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

    @property
    def protections(self):
        return [
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 30,
                "trade_limit": 2,
                "stop_duration_candles": 15,
                "required_profit": 0.0,
                "only_per_pair": True,
                "only_per_side": False
            }
        ]

    def cluster_borders(self):
        dataframe = self.dp.get_pair_dataframe(pair="WIF/USDT:USDT", timeframe="15m")
        dataframe = dataframe[-16:]
        X = dataframe.close.values.reshape(-1,1)
        kmeans = KMeans(n_clusters=6, random_state=42).fit(X)
        dataframe['cluster'] = kmeans.predict(X)
        return dataframe.groupby(['cluster']).min().close.sort_values().values

    def informative_pairs(self):
        return [
            ("WIF/USDT:USDT", "15m", "futures"),
        ]

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

        borders = self.cluster_borders()

        for c, border in enumerate(borders):
            dataframe.loc[(dataframe.close >= border),'cluster'] = c

        return dataframe

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

        dataframe.loc[
            (
                (dataframe.cluster.shift(1) < dataframe.cluster)
            ),
            'enter_long'
        ] = 1

        dataframe.loc[
            (
                (dataframe.cluster.shift(1) > dataframe.cluster)
            ),
            'enter_short'
        ] = 1

        return dataframe

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

        return dataframe

    def position_size(self, total_asset, risk):

        size = total_asset * self.total_risk / risk if risk > self.total_risk else total_asset

        return size

    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:

        borders = self.cluster_borders()
        
        if side == 'long':
            risk = 1 - borders[borders < current_rate][-1] / current_rate
        else:
            risk = borders[borders > current_rate][0] / current_rate - 1
        
        stake = self.position_size(max_stake, risk)

        return stake

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

        borders = self.cluster_borders()
        stop = trade.get_custom_data(key='stop')
        adjusted_stop = trade.get_custom_data(key='adjusted_stop')

        if (current_rate - trade.open_rate) * 3 >= stop:
            return stoploss_from_absolute(
                stop * 2,
                current_rate,
                is_short=trade.is_short,
                leverage=trade.leverage
            )

        if adjusted_stop:
            return None

        if current_profit > 0 and ((current_rate - trade.open_rate) / 2 >= stop):
            adjusted_stop = (current_rate - trade.open_rate) / 2
            trade.set_custom_data(key='adjusted_stop', value = adjusted_stop)
            return stoploss_from_absolute(
                adjusted_stop,
                current_rate,
                is_short=trade.is_short,
                leverage=trade.leverage
            )

        if trade.is_short:
            return stoploss_from_absolute(
                borders[borders > current_rate][0],
                current_rate,
                is_short=trade.is_short,
                leverage=trade.leverage
            )
        return stoploss_from_absolute(
                borders[borders < current_rate][-1],
                current_rate,
                is_short=trade.is_short,
                leverage=trade.leverage
            )
    
    def order_filled(self, pair: str, trade: Trade, order: 'Order', current_time: datetime, **kwargs) -> None:

        borders = self.cluster_borders()

        if trade.nr_of_successful_entries == 1:
            if trade.is_short:
                trade.set_custom_data(key='stop', value=borders[borders > trade.open_rate][0])
            else:
                trade.set_custom_data(key='stop', value=borders[borders < trade.open_rate][-1])
            trade.set_custom_data(key='OB', value=self.dp.orderbook(trade.pair, maximum=200))

        return None

    # def bot_loop_start(self, current_time: datetime, **kwargs) -> None:

    #     pairs = self.dp.current_whitelist()
    #     for pair in pairs:
    #         if self.is_pair_locked(pair):
    #             self.unlock_pair(pair)