# source: https://raw.githubusercontent.com/mohsenjfar/trade/17ea10e916b730006e142258d67c307b102faca1/user_data/Archive/strategy_.py
# directory_url: https://github.com/mohsenjfar/trade/blob/main/user_data/Archive/
# User: mohsenjfar
# Repository: trade
# --------------------# importing freqtrade modules
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, stoploss_from_absolute, stoploss_from_open, merge_informative_pair

# importing calculation modules
import pandas as pd
from pandas import DataFrame
import numpy as np
from technical import qtpylib
import talib.abstract as ta
from scipy.signal import argrelextrema

# importing other modules
from datetime import datetime, timezone
from typing import Optional
import logging
from collections import defaultdict

logger = logging.getLogger(__name__)

class Github_mohsenjfar_trade__strategy___20260816_043218(IStrategy):

    INTERFACE_VERSION = 3

    can_short: bool = True

    stoploss = -0.01

    timeframe = '1m'
    informative_timeframes = ['15m','4h']
    order = 16

    use_exit_signal = True

    use_custom_stoploss = True

    startup_candle_count: int = 48

    process_only_new_candles = True

    position_adjustment_enable = True

    notifications = defaultdict(None)

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

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

    def informative_pairs(self):
        pairs = self.dp.current_whitelist()
        return [(pair, informative) for pair in pairs for informative in self.informative_timeframes]
    

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

        informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='15m')
        min_peaks = argrelextrema(informative["low"].values, np.less_equal, order=self.order)
        max_peaks = argrelextrema(informative["high"].values, np.greater_equal, order=self.order)
        informative.loc[(informative.index.isin(min_peaks[0])),'extrema'] = informative.low
        informative.loc[(informative.index.isin(max_peaks[0])),'extrema'] = informative.high
        bins = informative.extrema.dropna().drop_duplicates().values[-3:-1]
        bins = np.sort(np.append(bins, [-np.inf,np.inf]))
        informative['atr'] = ta.ATR(informative, timeperiod=3)
        dataframe = merge_informative_pair(dataframe, informative, self.timeframe, '15m', ffill=True)
        
        dataframe['boundaries'] = pd.cut(dataframe.close, bins=bins, precision=4)
    
        dataframe['long_stop'] = dataframe.boundaries.apply(lambda x: x.left).astype(float)
        dataframe['long_trigger'] = dataframe['long_stop'] + (dataframe['atr_15m'] * 2)
        dataframe['long_distance'] = dataframe['long_trigger'] - dataframe['long_stop']
        dataframe['long_risk'] = dataframe['long_distance'] / dataframe['long_stop']

        dataframe['short_stop'] = dataframe.boundaries.apply(lambda x: x.right).astype(float)
        dataframe['short_trigger'] = dataframe['short_stop'] - (dataframe['atr_15m'] * 2)
        dataframe['short_distance'] = dataframe['short_stop'] - dataframe['short_trigger']
        dataframe['short_risk'] = dataframe['short_distance'] / dataframe['short_stop']

        return dataframe


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

        dataframe.loc[
            (
                (qtpylib.crossed_above(dataframe['close'], dataframe['long_trigger']))
            ),
            'enter_long'
        ] = 1

        dataframe.loc[
            (
                (qtpylib.crossed_below(dataframe['close'], dataframe['short_trigger']))
            ),
            'enter_short'
        ] = 1

        dataframe.to_csv('user_data/notebooks/out.csv', index=False)
                
        return dataframe


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

        return dataframe


    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:
        
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
            last_candle = dataframe.iloc[-1].squeeze()
            risk = last_candle.short_risk if side == 'short' else last_candle.long_risk
            today = datetime.now(timezone.utc).date()
            closed_trades = Trade.get_trades_proxy(close_date=today)
            today_loss = sum(trade.close_profit_abs for trade in closed_trades if trade.close_profit_abs < 0)
            stake_in_use = Trade.total_open_trades_stakes()
            total_stake = stake_in_use + max_stake
            today_loss_ratio = today_loss / total_stake

            if today_loss_ratio < self.stoploss:
                logger.info(f"Max day loss ({today_loss_ratio * 100:.2f}%), stop entering {side} position for {pair}")
                return None
            
            return min((proposed_stake * abs(self.stoploss) / 2) / (risk * leverage), proposed_stake)
        
        except Exception as e:
            logger.warning(e)
            return None
    

    def order_filled(self, pair: str, trade: Trade, order, current_time: datetime, **kwargs) -> None:

        if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side):
            dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
            last_candle = dataframe.iloc[-1].squeeze()
            risk = last_candle.short_risk if trade.is_short else last_candle.long_risk
            trade.set_custom_data(key='risk', value=risk)
            
            trade.set_custom_data(key='OB', value=self.dp.orderbook(pair, maximum=200))
    

    def adjust_trade_position(self, trade: Trade, current_time: datetime,
                              current_rate: float, current_profit: float,
                              min_stake: float | None, max_stake: float,
                              current_entry_rate: float, current_exit_rate: float,
                              current_entry_profit: float, current_exit_profit: float,
                              **kwargs
                              ) -> float | None | tuple[float | None, str | None]:

        risk = trade.get_custom_data(key='risk')
        if (current_profit > risk) and (trade.nr_of_successful_exits == 0):
            return - trade.stake_amount / 2


    def custom_exit_price(self, pair: str, trade: Trade,
                          current_time: datetime, proposed_rate: float,
                          current_profit: float, exit_tag: str | None, **kwargs) -> float:

        risk = trade.get_custom_data(key='risk')
        if (current_profit > risk) and (trade.nr_of_successful_exits == 0):
            side = -1 if trade.is_short else 1
            return trade.open_rate * (1 + side * risk)


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

        dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        stop = last_candle.short_stop if trade.is_short else last_candle.long_stop
        trigger = last_candle.short_trigger if trade.is_short else last_candle.long_trigger
        risk = trade.get_custom_data(key='risk')

        if current_rate > trigger:
            reward = abs(1 - trade.open_rate / stop)
            if reward > 3 * risk:
                return stoploss_from_absolute(
                        stop,
                        current_rate,
                        is_short=trade.is_short,
                        leverage=trade.leverage
                    )
        
        return stoploss_from_open(
            -risk, 
            current_profit, 
            is_short=trade.is_short, 
            leverage=trade.leverage
        )