# source: https://raw.githubusercontent.com/rmallarapu-bc/brahma/9287745fc036c2f4c00c586e598290885c2883b9/archive/Jafar4.py
from datetime import datetime  # noqa
from typing import Optional, Union  # noqa
import pandas_ta as pta
from pandas import DataFrame  # noqa

import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np  # noqa
import pandas as pd  # noqa
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
from freqtrade.strategy import (
    IStrategy,
)


class Github_rmallarapu_bc_brahma__Jafar4__20240229_213751(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = '15m'
    # timeframe = '1d'
    # Can this strategy go short?
    can_short: bool = True

    minimal_roi = {
        "0": 0.1,
    }

    # Trailing stoploss
    trailing_stop = True
    trailing_stop_positive = 0.002
    trailing_stop_positive_offset = 0.005
    trailing_only_offset_is_reached = True

    stoploss = -0.72
    process_only_new_candles = True
    use_exit_signal = False
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    # Number of candles the strategy requires before producing valid signals
    startup_candle_count: int = 15

    # Optional order type mapping.
    order_types = {
        'entry': 'limit',
        'exit': 'limit',
        'stoploss': 'market',
        'stoploss_on_exchange': False
    }

    # Optional order time in force.
    order_time_in_force = {
        'entry': 'gtc',
        'exit': 'gtc'
    }

    @property
    def protections(self):
        return [
            {
                "method": "CooldownPeriod",
                "stop_duration_candles": 2
            },
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 24,
                "trade_limit": 4,
                "stop_duration_candles": 2,
                "only_per_pair": False
            },

        ]

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Crypto trading bot strategy indicators

        dataframe["rsi"] = ta.RSI(dataframe)

        # Variables
        TS = 9
        KS = 26
        SS = 52
        CS = 26
        OS = 0

        # Ichimoku indicator
        dataframe['tenkan'] = \
            pta.ichimoku(high=dataframe['high'], low=dataframe['low'], close=dataframe['close'], tenkan=TS, kijun=KS,
                         senkou=SS, offset=OS)[0][f'ITS_{TS}']
        dataframe['kijun'] = \
            pta.ichimoku(high=dataframe['high'], low=dataframe['low'], close=dataframe['close'], tenkan=TS, kijun=KS,
                         senkou=SS, offset=OS)[0][f'IKS_{KS}']
        dataframe['senkou_a'] = \
            pta.ichimoku(high=dataframe['high'], low=dataframe['low'], close=dataframe['close'], tenkan=TS, kijun=KS,
                         senkou=SS, offset=OS)[0][f'ISA_{TS}']
        dataframe['senkou_b'] = \
            pta.ichimoku(high=dataframe['high'], low=dataframe['low'], close=dataframe['close'], tenkan=TS, kijun=KS,
                         senkou=SS, offset=OS)[0][f'ISB_{KS}']
        dataframe['chikou'] = \
            pta.ichimoku(high=dataframe['high'], low=dataframe['low'], close=dataframe['close'], tenkan=TS, kijun=KS,
                         senkou=SS, offset=OS)[0][f'ICS_{KS}']

        # No LONG entries when price is below Senkou A, Senkou B and Kijun sen
        dataframe['long_signal'] = (dataframe['close'] > dataframe['senkou_a']) & (
                dataframe['close'] > dataframe['senkou_b']) & (dataframe['close'] > dataframe['kijun'])

        # No SHORT entries when price is above Senkou A, Senkou B and Kijun sen
        dataframe['short_signal'] = (dataframe['close'] < dataframe['senkou_a']) & (
                dataframe['close'] < dataframe['senkou_b']) & (dataframe['close'] < dataframe['kijun'])

        # Exit indicators
        dataframe['long_exit'] = (dataframe['close'] < dataframe['kijun']) & (dataframe['long_signal'] == True)
        dataframe['short_exit'] = (dataframe['close'] > dataframe['kijun']) & (dataframe['short_signal'] == True)

        # Uncomment this if you use this strategy for real/dummy trading
        # #print(dataframe)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                    (dataframe['long_signal'] == True)
                    & (qtpylib.crossed_below(dataframe["rsi"], 40))
            ),
            ['enter_long', 'enter_tag']] = (1, 'kumo_breakout_long')
        dataframe.loc[
            (
                    (dataframe['short_signal'] == True)
                    & (qtpylib.crossed_above(dataframe["rsi"], 60))
            ),
            ['enter_short', 'enter_tag']] = (1, 'kumo_breakout_short')

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe['long_exit'] == True)
            ),
            ['exit_long', 'exit_tag']] = (1, 'closeprice_below_ks')
        dataframe.loc[
            (
                (dataframe['short_exit'] == True)
            ),
            ['exit_short', 'exit_tag']] = (1, 'closeprice_above_ks')
        return dataframe

    from freqtrade.persistence import Trade
    from datetime import timedelta, datetime
    from typing import Optional

    # DCA options
    position_adjustment_enable = True
    max_entry_position_adjustment = 2
    initial_order_size = 1.0  # 20% of total capital
    incremental_order_size = 0.5  # increase capital by 40% of initial order for every {initial_profit} loss
    initial_profit = 0.01
    recurring_loss = -0.20
    loss_upper_limit = -0.20

    # leverage
    leverage_num = 1

    # This is called when placing the initial order (opening trade)
    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:

        # We need to leave most of the funds for possible further DCA orders
        # This also applies to fixed stakes
        return proposed_stake * self.initial_order_size

    def adjust_trade_position(self, trade: Trade, current_time: datetime,
                              current_rate: float, current_profit: float,
                              min_stake: Optional[float], max_stake: float,
                              current_entry_rate: float, current_exit_rate: float,
                              current_entry_profit: float, current_exit_profit: float,
                              **kwargs) -> Optional[float]:
        """
        Custom trade adjustment logic, returning the stake amount that a trade should be
        increased or decreased.
        This means extra buy or sell orders with additional fees.
        Only called when `position_adjustment_enable` is set to True.

        For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/

        When not implemented by a strategy, returns None

        :param trade: trade object.
        :param current_time: datetime object, containing the current datetime
        :param current_rate: Current buy rate.
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
        :param min_stake: Minimal stake size allowed by exchange (for both entries and exits)
        :param max_stake: Maximum stake allowed (either through balance, or by exchange limits).
        :param current_entry_rate: Current rate using entry pricing.
        :param current_exit_rate: Current rate using exit pricing.
        :param current_entry_profit: Current profit using entry pricing.
        :param current_exit_profit: Current profit using exit pricing.
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
        :return float: Stake amount to adjust your trade,
                       Positive values to increase position, Negative values to decrease position.
                       Return None for no action.
        """

        filled_entries = trade.select_filled_orders(trade.entry_side)
        count_of_entries = trade.nr_of_successful_entries
        count_of_exits = trade.nr_of_successful_exits
        stake_amount = filled_entries[0].cost

        if current_profit > self.initial_profit and count_of_exits == 0:
            return -(trade.stake_amount / 2)

        if current_profit < self.loss_upper_limit:
            return None  # if the losses are greater than 10%, stop

        # Only buy when prices are not actively falling.
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
            last_candle = dataframe.iloc[-1].squeeze()
            previous_candle = dataframe.iloc[-2].squeeze()
            if last_candle['close'] < previous_candle['close']:
                return None
        except:
            pass

        # determine the stake amount now
        if current_profit < self.recurring_loss and count_of_entries <= self.max_entry_position_adjustment:
            return stake_amount * self.incremental_order_size
        else:
            return None

        return None

    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, side: str,
                 **kwargs) -> float:
        return self.leverage_num

    def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
                           rate: float, time_in_force: str, exit_reason: str,
                           current_time: datetime, **kwargs) -> bool:

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if exit_reason != 'force_exit' and trade.calc_profit_ratio(rate) < 0:
            # Reject selling with negative profit - exit manually
            return False
        return True