# source: https://raw.githubusercontent.com/hankniel/freqtrade/83ba4265808f598d5e1f810daa10ebea1726a645/user_data/strategies/technical/ChopFlowLong.py
"""
ChopFlow ATR Scalp Strategy
Source: https://www.tradingview.com/script/TgcbEl6W-ChopFlow-ATR-Scalp-Strategy/
"""
# --- Do not remove these libs ---
from freqtrade.strategy import IStrategy, stoploss_from_absolute, Trade
from pandas import DataFrame
from datetime import datetime
import numpy as np
# --------------------------------
from user_data.utils.indicators.indicator_helpers import true_range
from technical.indicators import chopiness

import talib.abstract as ta

class Github_hankniel_freqtrade__ChopFlowLong__20251014_102948(IStrategy):
    INTERFACE_VERSION: int = 3
    
    # Minimal ROI designed for the strategy.
    # This attribute will be overridden if the config file contains "minimal_roi"
    # minimal_roi = {
    #     "60":  0.01,
    #     "30":  0.03,
    #     "20":  0.04,
    #     "0":  0.05
    # }
    
    use_custom_roi = True
    use_custom_stoploss = True
    
    # This attribute will be overridden if the config file contains "stoploss"
    stoploss = -0.99
    
    # Optimal timeframe for the strategy
    timeframe = '15m'
    
    # trailing stoploss
    trailing_stop = False
    
    # run "populate_indicators" only for new candle
    process_only_new_candles = True
    
    # Experimental settings (configuration will overide these if set)
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False
    
    # Short
    can_short = True
    
    # Optional order type mapping
    order_types = {
        'entry': 'limit',
        'exit': 'limit',
        'stoploss': 'market',
        'stoploss_on_exchange': True
    }
    
    # Strategy configs
    atr_period: int = 14
    atr_multiplier: float = 1.5
    chop_period: int = 14
    chop_threshold: float = 60.0
    obv_ema_len: int = 10
    
    custom_leverage: int = 1
    
    # #* For production:
    # with open('configs/technical_configs/chopflow-long.json', 'r') as f:
    #     chopflow_long_configs = json.load(f)
    
    # Required startup_candles
    startup_candle_count: int = max(atr_period * 2, chop_period * 2, obv_ema_len * 2)
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Adds several different TA indicators to the given DataFrame
        
        Performance Note: For the best performance be frugal on the number of indicators
        you are using. Let uncomment only the indicator you are using in your strategies
        or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
        """
        atr_period = self.atr_period
        chop_period = self.chop_period
        obv_ema_len = self.obv_ema_len
        
        # #* For production
        # pair = metadata['pair']
        # if self.chopflow_long_configs:
        #     atr_period = self.chopflow_long_configs.get(pair, {}).get('atr_period', 20)
        #     chop_period = self.chopflow_long_configs.get(pair, {}).get('chop_period', 14)
        #     obv_ema_len = self.chopflow_long_configs.get(pair, {}).get('obv_ema_len', 10)
        
        # ATR
        dataframe['atr'] = ta.ATR(dataframe, period=atr_period)
        tr = ta.TRANGE(dataframe)
        
        # Chopiness index
        sum_tr = tr.rolling(chop_period).sum().values
        range_ = dataframe['high'].rolling(chop_period).max() - dataframe['low'].rolling(chop_period).min()
        dataframe['chop'] = 100 * np.log10(sum_tr / range_) / np.log10(chop_period)
        
        # On-balance volume
        # price_change = np.diff(dataframe['close'], prepend=dataframe['close'].iloc[0])
        # dataframe['obv'] = np.cumsum(np.sign(price_change) * dataframe['volume'])
        dataframe['obv'] = ta.OBV(dataframe)
        dataframe['obv_ema'] = ta.EMA(dataframe['obv'], timeperiod=obv_ema_len)
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the buy signal for the given dataframe
        :param dataframe: DataFrame
        :return: DataFrame with buy column
        """
        chop_threshold = self.chop_threshold
        
        # #* For production
        # pair = metadata['pair']
        # if self.chopflow_long_configs:
        #     chop_threshold = self.chopflow_long_configs.get(pair, {}).get('chop_threshold', 20)
        
        # Enter long condition
        conditions = (
            (dataframe['chop'] < chop_threshold) & 
            (dataframe['obv'] > dataframe['obv_ema'])
        )
        dataframe.loc[conditions, ['enter_long', 'enter_tag']] = (1, 'chop_flow')
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the sell signal for the given dataframe
        :param dataframe: DataFrame
        :return: DataFrame with buy column
        """
        
        return dataframe
    
    def custom_roi(
        self, pair: str, trade: Trade, current_time: datetime, trade_duration: int,
        entry_tag: str | None, side: str, **kwargs
    ) -> float | None:
        """
        Custom ROI logic, returns a new minimum ROI threshold (as a ratio, e.g., 0.05 for +5%).
        Only called when use_custom_roi is set to True.
        
        If used at the same time as minimal_roi, an exit will be triggered when the lower
        threshold is reached. Example: If minimal_roi = {"0": 0.01} and custom_roi returns 0.05,
        an exit will be triggered if profit reaches 5%.
        
        :param pair: Pair that's currently analyzed.
        :param trade: trade object.
        :param current_time: datetime object, containing the current datetime.
        :param trade_duration: Current trade duration in minutes.
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
        :param side: 'long' or 'short' - indicating the direction of the current trade.
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
        :return float: New ROI value as a ratio, or None to fall back to minimal_roi logic.
        """
        atr_multiplier = self.atr_multiplier
        
        # #* For production
        # pair = metadata['pair']
        # if self.chopflow_long_configs:
        #     atr_multiplier = self.chopflow_long_configs.get(pair, {}).get('atr_multiplier', 20)
        
        # Get last candle
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        
        # ATR based roi
        atr = last_candle['atr']
        last_close = last_candle['close']
        
        roi = ((atr * atr_multiplier) / last_close) * trade.leverage
        
        return roi
    
    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
                        current_rate: float, current_profit: float, after_fill: bool, 
                        **kwargs) -> float | None:
        """
        Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
        e.g. returning -0.05 would create a stoploss 5% below current_rate.
        The custom stoploss can never be below self.stoploss, which serves as a hard maximum loss.
        
        For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
        
        When not implemented by a strategy, returns the initial stoploss value.
        Only called when use_custom_stoploss is set to True.
        
        :param pair: Pair that's currently analyzed
        :param trade: trade object.
        :param current_time: datetime object, containing the current datetime
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
        :param after_fill: True if the stoploss is called after the order was filled.
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
        :return float: New stoploss value, relative to the current_rate
        """
        atr_multiplier = self.atr_multiplier
        
        # #* For production
        # pair = metadata['pair']
        # if self.chopflow_long_configs:
        #     atr_multiplier = self.chopflow_long_configs.get(pair, {}).get('atr_multiplier', 20)
        
        # Get last candle
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        
        # ATR based stoploss
        close = last_candle['close']
        atr = last_candle['atr']
        
        long = not trade.is_short
        
        if long:
            stoploss_price = close - (atr * atr_multiplier)
            stoploss_value = stoploss_from_absolute(
                stoploss_price, 
                current_rate=current_rate,
                is_short=trade.is_short,
                leverage=trade.leverage
            )
        else:
            stoploss_price = close + (atr * atr_multiplier)
            stoploss_value = stoploss_from_absolute(
                stoploss_price, 
                current_rate=current_rate,
                is_short=trade.is_short,
                leverage=trade.leverage
            )
        
        return stoploss_value
    
    def leverage(
        self, pair: str, current_time: datetime, current_rate: float,
        proposed_leverage: float, max_leverage: float, entry_tag: str | None, side: str,
        **kwargs
    ) -> float:
        leverage = self.custom_leverage
        
        return leverage