# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from typing import Dict, List
from functools import reduce
from pandas import DataFrame
# --------------------------------
import math
import logging
from datetime import datetime, timedelta, timezone
import time
from typing import Dict, List, Optional

import talib.abstract as ta
import numpy as np
import freqtrade.vendor.qtpylib.indicators as qtpylib
import datetime
from technical.util import resample_to_interval, resampled_merge
from freqtrade.persistence import Trade
from freqtrade.strategy import stoploss_from_open, merge_informative_pair, DecimalParameter, IntParameter, CategoricalParameter
import technical.indicators as ftt

######################################## Warning ########################################
# You won't get a lot of benefits by simply changing to this strategy                   #
# with the HyperOpt values changed.                                                     #
#                                                                                       #
# You should test it closely, trying backtesting and dry running, and we recommend      #
# customizing the terms of sale and purchase as well.                                   #
#                                                                                       #
# You should always be careful in real trading!                                         #
#########################################################################################



def EWO(dataframe, ema_length=5, ema2_length=35):
    #df = dataframe.copy()
    ema1 = ta.EMA(dataframe, timeperiod=ema_length)
    ema2 = ta.EMA(dataframe, timeperiod=ema2_length)
    emadif = (ema1 - ema2) / dataframe['close'] * 100
    return emadif

class SMAOffsetProtectOptV1_kkeue_20210619_dca_5xleverage(IStrategy):
    position_adjustment_enable = True
    lev = 5
    initial_safety_order_trigger = -0.02 * lev
    max_so_multiplier = 3
    safety_order_step_scale = 2
    safety_order_volume_scale = 1.8
    
    max_so_multiplier = (1 + max_so_multiplier)
    if(max_so_multiplier > 0):
        if(safety_order_volume_scale > 1):
            max_so_multiplier = (2 + (safety_order_volume_scale * (math.pow(safety_order_volume_scale,(max_so_multiplier - 1)) - 1) / (safety_order_volume_scale - 1)))
        elif(safety_order_volume_scale < 1):
            max_so_multiplier = (2 + (safety_order_volume_scale * (1 - math.pow(safety_order_volume_scale,(max_so_multiplier - 1))) / (1 - safety_order_volume_scale)))

    # Since stoploss can only go up and can't go down, if you set your stoploss here, your lowest stoploss will always be tied to the first entry rate
    # So disable the hard stoploss here, and use custom_exit or custom_stoploss to handle the stoploss trigger
    stoploss = -1

    def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
                    current_profit: float, **kwargs):

        tag = super().custom_exit(pair, trade, current_time, current_rate, current_profit, **kwargs)
        if tag:
            return tag
            
        entry_tag = 'empty'
        if hasattr(trade, 'entry_tag') and trade.entry_tag is not None:
            entry_tag = trade.entry_tag
        entry_tags = entry_tag.split()

        if current_profit <= -0.35 * self.lev:
            return f'stop_loss ({enter_tag})'

        return None

    # Let unlimited stakes leave funds open for DCA orders
    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: float, max_stake: float,
                            **kwargs) -> float:
                            
        return proposed_stake / self.max_so_multiplier

    def adjust_trade_position(self, trade: Trade, current_time: datetime,
                              current_rate: float, current_profit: float, min_stake: float,
                              max_stake: float, **kwargs) -> Optional[float]:
        if current_profit > self.initial_safety_order_trigger:
            return None

        filled_entrys = trade.select_filled_orders('entry')
        count_of_entrys = len(filled_entrys)

        if 1 <= count_of_entrys <= self.max_so_multiplier:
            safety_order_trigger = (abs(self.initial_safety_order_trigger) * count_of_entrys)
            if (self.safety_order_step_scale > 1):
                safety_order_trigger = abs(self.initial_safety_order_trigger) + (abs(self.initial_safety_order_trigger) * self.safety_order_step_scale * (math.pow(self.safety_order_step_scale,(count_of_entrys - 1)) - 1) / (self.safety_order_step_scale - 1))
            elif (self.safety_order_step_scale < 1):
                safety_order_trigger = abs(self.initial_safety_order_trigger) + (abs(self.initial_safety_order_trigger) * self.safety_order_step_scale * (1 - math.pow(self.safety_order_step_scale,(count_of_entrys - 1))) / (1 - self.safety_order_step_scale))

            if current_profit <= (-1 * abs(safety_order_trigger)):
                try:
                    # This returns first order stake size
                    stake_amount = filled_entrys[0].cost
                    # This then calculates current safety order size
                    stake_amount = stake_amount * math.pow(self.safety_order_volume_scale,(count_of_entrys - 1))
                    amount = stake_amount / current_rate
                    logger.info(f"Initiating safety order entry #{count_of_entrys} for {trade.pair} with stake amount of {stake_amount} which equals {amount}")
                    return stake_amount
                except Exception as exception:
                    logger.info(f'Error occured while trying to get stake amount for {trade.pair}: {str(exception)}') 
                    return None

        return None

    # Modified entry / exit params - 20210619
    # entry hyperspace params:
    entry_params = {
        "base_nb_candles_entry": 16,
        "ewo_high": 5.672,
        "ewo_low": -19.931,
        "low_offset": 0.973,
        "rsi_entry": 59,
    }

    # exit hyperspace params:
    exit_params = {
        "base_nb_candles_exit": 20,
        "high_offset": 1.010,
    }
    INTERFACE_VERSION = 2

    # Modified ROI - 20210620
    # ROI table:
    minimal_roi = {
        "0": 0.028 * lev,
        "10": 0.018 * lev,
        "30": 0.010 * lev,
        "40": 0.005 * lev
    }

    # Stoploss:
    stoploss = -0.5

    # SMAOffset
    base_nb_candles_entry = IntParameter(
        5, 80, default=entry_params['base_nb_candles_entry'], space='entry', optimize=True)
    base_nb_candles_exit = IntParameter(
        5, 80, default=exit_params['base_nb_candles_exit'], space='exit', optimize=True)
    low_offset = DecimalParameter(
        0.9, 0.99, default=entry_params['low_offset'], space='entry', optimize=True)
    high_offset = DecimalParameter(
        0.99, 1.1, default=exit_params['high_offset'], space='exit', optimize=True)

    # Protection
    fast_ewo = 50
    slow_ewo = 200
    ewo_low = DecimalParameter(-20.0, -8.0,
                               default=entry_params['ewo_low'], space='entry', optimize=True)
    ewo_high = DecimalParameter(
        2.0, 12.0, default=entry_params['ewo_high'], space='entry', optimize=True)
    rsi_entry = IntParameter(30, 70, default=entry_params['rsi_entry'], space='entry', optimize=True)


    # Trailing stop:
    trailing_stop = False
    trailing_stop_positive = 0.001 * lev
    trailing_stop_positive_offset = 0.01 * lev
    trailing_only_offset_is_reached = True

    # exit signal
    use_exit_signal = True
    exit_profit_only = False
    exit_profit_offset = 0.01 * lev
    ignore_roi_if_entry_signal = False

    # Optimal timeframe for the strategy
    timeframe = '5m'
    informative_timeframe = '1h'

    process_only_new_candles = True
    startup_candle_count: int = 30

    plot_config = {
        'main_plot': {
            'ma_entry': {'color': 'orange'},
            'ma_exit': {'color': 'orange'},
        },
    }

    use_custom_stoploss = False

    def informative_pairs(self):

        pairs = self.dp.current_whitelist()
        informative_pairs = [(pair, self.informative_timeframe) for pair in pairs]

        return informative_pairs

    def get_informative_indicators(self, metadata: dict):

        dataframe = self.dp.get_pair_dataframe(
            pair=metadata['pair'], timeframe=self.informative_timeframe)

        return dataframe

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

        # Calculate all ma_entry values
        for val in self.base_nb_candles_entry.range:
            dataframe[f'ma_entry_{val}'] = ta.EMA(dataframe, timeperiod=val)

        # Calculate all ma_exit values
        for val in self.base_nb_candles_exit.range:
            dataframe[f'ma_exit_{val}'] = ta.EMA(dataframe, timeperiod=val)

        # Elliot
        dataframe['EWO'] = EWO(dataframe, self.fast_ewo, self.slow_ewo)
        
        # RSI
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        conditions.append(
            (
                (dataframe['close'] < (dataframe[f'ma_entry_{self.base_nb_candles_entry.value}'] * self.low_offset.value)) &
                (dataframe['EWO'] > self.ewo_high.value) &
                (dataframe['rsi'] < self.rsi_entry.value) &
                (dataframe['volume'] > 0)
            )
        )

        conditions.append(
            (
                (dataframe['close'] < (dataframe[f'ma_entry_{self.base_nb_candles_entry.value}'] * self.low_offset.value)) &
                (dataframe['EWO'] < self.ewo_low.value) &
                (dataframe['volume'] > 0)
            )
        )

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions),
                'enter_long'
            ]=1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        conditions.append(
            (
                (dataframe['close'] > (dataframe[f'ma_exit_{self.base_nb_candles_exit.value}'] * self.high_offset.value)) &
                (dataframe['volume'] > 0)
            )
        )

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions),
                'exit_long'
            ]=1

        return dataframe

    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
                 **kwargs) -> float:
        """
        Customize leverage for each new trade. This method is only called in futures mode.

        :param pair: Pair that's currently analyzed
        :param current_time: datetime object, containing the current datetime
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
        :param proposed_leverage: A leverage proposed by the bot.
        :param max_leverage: Max leverage allowed on this pair
        :param entry_tag: Optional entry_tag (enter_tag) if provided with the entry signal.
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
        :return: A leverage amount, which is between 1.0 and max_leverage.
        """
        return self.lev
        
class SMAOffsetProtectOptV1_1(SMAOffsetProtectOptV1_kkeue_20210619):
    #Epoch details:

    #271/512:    468 trades. 453/0/15 Wins/Draws/Losses. Avg profit   1.11%. Median profit   1.14%. Total profit  129.62363315 BUSD ( 103.70%). Avg duration 2:07:00 min. Objective: -50137.47104


    # entry hyperspace params:
    entry_params = {
        "base_nb_candles_entry": 5,
        "ewo_high": 3.944,
        "ewo_low": -12.07,
        "low_offset": 0.987,
        "rsi_entry": 69,
    }

    # exit hyperspace params:
    exit_params = {
        "base_nb_candles_exit": 53,
        "high_offset": 1.044,
    }

    # ROI table:  # value loaded from strategy
    minimal_roi = {
        "0": 0.028,
        "10": 0.018,
        "30": 0.01,
        "40": 0.005
    }

    # Stoploss:
    stoploss = -0.5  # value loaded from strategy

    # Trailing stop:
    trailing_stop = False  # value loaded from strategy
    trailing_stop_positive = 0.001  # value loaded from strategy
    trailing_stop_positive_offset = 0.01  # value loaded from strategy
    trailing_only_offset_is_reached = True  # value loaded from strategy


