# source: https://raw.githubusercontent.com/zmomz/freq_strategies/f25f8493ebab589e327cd011b829d1c64cafae84/strategies/compactedFUOPT.py
import numpy as np
import pandas as pd
from pandas import DataFrame
from typing import Optional, Union
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
                                IntParameter, IStrategy, informative, merge_informative_pair)

from datetime import datetime
import talib.abstract as ta
import pandas_ta as pta
from technical import qtpylib

class Github_zmomz_freq_strategies__compactedFUOPT__20240729_180907(IStrategy):
    INTERFACE_VERSION = 3
    can_short: bool = True
    minimal_roi = {"0": 1}     # 100% profit after 0 minutes (virtually impossible)
    use_stop_on_candle_close = True
    use_custom_stoploss = False
    

    process_only_new_candles = True
    use_exit_signal = True
    exit_profit_only = True
    ignore_roi_if_entry_signal = True
    startup_candle_count: int = 30

    # factors to optimize
    timeframe = '1h'
    stoploss = -0.333
    trailing_stop = True
    trailing_stop_positive = 0.013
    trailing_stop_positive_offset = 0.048
    trailing_only_offset_is_reached = True
    long_multiplier = DecimalParameter(1.01, 1.10, default=1.02, space='buy')
    short_multiplier = DecimalParameter(0.90, 0.99, default=0.936, space='buy')
    xlevrage = IntParameter(1, 20, default=5, space='buy')

    order_types = {
        'entry': 'market',
        'exit': 'market',
        'stoploss': 'market',
        'stoploss_on_exchange': False
    }

    order_time_in_force = {
        'entry': 'GTC',
        'exit': 'GTC'
    }

    @property
    def plot_config(self):
        return {
            'main_plot': {
                'high_of_current_high': {
                    'color': 'green',
                    'type': 'scatter',
                    'plotly': {}
                },
                'low_of_current_low': {
                    'color': 'red',
                    'type': 'scatter',
                    'plotly': {}
                },
                'high_of_current_swing_high': {
                    'color': 'blue',
                    'type': 'scatter',
                    'plotly': {}
                },
                'low_of_current_swing_low': {
                    'color': 'purple',
                    'type': 'scatter',
                    'plotly': {}
                }
            }
        }
    
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if 'looking_for' not in dataframe.columns:
            dataframe['looking_for'] = 'high'
            columns_to_initialize = [
                '_of_potential','_of_current_high','_of_previous_high','_of_current_low','_of_previous_low',
                '_of_current_swing_high','_of_previous_swing_high','_of_current_swing_low','_of_previous_swing_low'
            ]
            for column in columns_to_initialize:
                dataframe[f'high{column}'] = dataframe['high']
                dataframe[f'low{column}'] = dataframe['low']
                dataframe[f'close{column}'] = dataframe['close']
            dataframe['swing_date'] = dataframe['date']

        for i in range(1, len(dataframe)):
            if dataframe['looking_for'].iloc[i - 1] == 'high':
                for col in ['high_of_current_low', 'low_of_current_low', 'close_of_current_low', 
                            'high_of_previous_low', 'low_of_previous_low', 'close_of_previous_low']:
                    dataframe.at[i, col] = dataframe[col].iloc[i - 1]

                if dataframe['close'].iloc[i] < dataframe['low_of_potential'].iloc[i - 1]:
                    for col in ['high', 'low', 'close']:
                        dataframe.at[i, f'{col}_of_current_high'] = dataframe[f'{col}_of_potential'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_potential'] = dataframe[col].iloc[i]
                        dataframe.at[i, f'{col}_of_previous_high'] = dataframe[f'{col}_of_current_high'].iloc[i - 1]
                    dataframe.at[i, 'looking_for'] = 'low'
                    if dataframe['close_of_potential'].iloc[i - 1] > dataframe['high_of_current_swing_high'].iloc[i - 1]:
                        for col in ['high', 'low', 'close']:
                            dataframe.at[i, f'{col}_of_previous_swing_high'] = dataframe[f'{col}_of_current_swing_high'].iloc[i - 1]
                            dataframe.at[i, f'{col}_of_current_swing_high'] = dataframe[f'{col}_of_potential'].iloc[i - 1]
                        range_of_current_wave = dataframe.loc[dataframe['swing_date'] == dataframe['swing_date'].iloc[i - 1]]
                        lowest_current_low = range_of_current_wave['low_of_current_low'].min()
                        dataframe.at[i, 'low_of_current_swing_low'] = lowest_current_low
                        if not range_of_current_wave.loc[range_of_current_wave['low_of_current_low'] == lowest_current_low].empty:
                            for col in ['high', 'low', 'close']:
                                dataframe.at[i, f'{col}_of_current_swing_low'] = range_of_current_wave.loc[
                                    range_of_current_wave['low_of_current_low'] == lowest_current_low][f'{col}_of_current_low'].values[0]
                        for col in ['high', 'low', 'close']:
                            dataframe.at[i, f'{col}_of_previous_swing_low'] = dataframe[f'{col}_of_current_swing_low'].iloc[i - 1]
                        dataframe.at[i, 'swing_date'] = dataframe['date'].iloc[i]
                    else:
                        for col in ['high', 'low', 'close']:
                            dataframe.at[i, f'{col}_of_current_swing_high'] = dataframe[f'{col}_of_current_swing_high'].iloc[i - 1]
                            dataframe.at[i, f'{col}_of_previous_swing_high'] = dataframe[f'{col}_of_previous_swing_high'].iloc[i - 1]
                            dataframe.at[i, f'{col}_of_current_swing_low'] = dataframe[f'{col}_of_current_swing_low'].iloc[i - 1]
                            dataframe.at[i, f'{col}_of_previous_swing_low'] = dataframe[f'{col}_of_previous_swing_low'].iloc[i - 1]
                        dataframe.at[i, 'swing_date'] = dataframe['swing_date'].iloc[i - 1]
                elif dataframe['high'].iloc[i] > dataframe['high_of_potential'].iloc[i - 1]:
                    for col in ['high', 'low', 'close']:
                        dataframe.at[i, f'{col}_of_potential'] = dataframe[col].iloc[i]
                    for col in ['high', 'low', 'close']:
                        dataframe.at[i, f'{col}_of_current_high'] = dataframe[f'{col}_of_current_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_high'] = dataframe[f'{col}_of_previous_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_current_swing_high'] = dataframe[f'{col}_of_current_swing_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_swing_high'] = dataframe[f'{col}_of_previous_swing_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_current_swing_low'] = dataframe[f'{col}_of_current_swing_low'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_swing_low'] = dataframe[f'{col}_of_previous_swing_low'].iloc[i - 1]
                        dataframe.at[i, 'swing_date'] = dataframe['swing_date'].iloc[i - 1]
                    dataframe.at[i, 'looking_for'] = 'high'
                else:
                    for col in ['high', 'low', 'close']:
                        dataframe.at[i, f'{col}_of_current_high'] = dataframe[f'{col}_of_current_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_potential'] = dataframe[f'{col}_of_potential'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_high'] = dataframe[f'{col}_of_previous_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_current_swing_high'] = dataframe[f'{col}_of_current_swing_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_swing_high'] = dataframe[f'{col}_of_previous_swing_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_current_swing_low'] = dataframe[f'{col}_of_current_swing_low'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_swing_low'] = dataframe[f'{col}_of_previous_swing_low'].iloc[i - 1]
                        dataframe.at[i, 'swing_date'] = dataframe['swing_date'].iloc[i - 1]
                    dataframe.at[i, 'looking_for'] = 'high'
            else:
                for col in ['high_of_current_high', 'low_of_current_high', 'close_of_current_high',
                            'high_of_previous_high', 'low_of_previous_high', 'close_of_previous_high']:
                    dataframe.at[i, col] = dataframe[col].iloc[i - 1]

                if dataframe['close'].iloc[i] > dataframe['high_of_potential'].iloc[i - 1]:
                    for col in ['high', 'low', 'close']:
                        dataframe.at[i, f'{col}_of_current_low'] = dataframe[f'{col}_of_potential'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_potential'] = dataframe[col].iloc[i]
                        dataframe.at[i, f'{col}_of_previous_low'] = dataframe[f'{col}_of_current_low'].iloc[i - 1]
                    dataframe.at[i, 'looking_for'] = 'high'
                    if dataframe['close_of_potential'].iloc[i - 1] < dataframe['low_of_current_swing_low'].iloc[i - 1]:
                        for col in ['high', 'low', 'close']:
                            dataframe.at[i, f'{col}_of_previous_swing_low'] = dataframe[f'{col}_of_current_swing_low'].iloc[i - 1]
                            dataframe.at[i, f'{col}_of_current_swing_low'] = dataframe[f'{col}_of_potential'].iloc[i - 1]
                        range_of_current_wave = dataframe.loc[dataframe['swing_date'] == dataframe['swing_date'].iloc[i - 1]]
                        highest_current_high = range_of_current_wave['high_of_current_high'].max()
                        dataframe.at[i, 'high_of_current_swing_high'] = highest_current_high
                        if not range_of_current_wave.loc[range_of_current_wave['high_of_current_high'] == highest_current_high].empty:
                            for col in ['high', 'low', 'close']:
                                dataframe.at[i, f'{col}_of_current_swing_high'] = range_of_current_wave.loc[
                                    range_of_current_wave['high_of_current_high'] == highest_current_high][f'{col}_of_current_high'].values[0]
                        for col in ['high', 'low', 'close']:
                            dataframe.at[i, f'{col}_of_previous_swing_high'] = dataframe[f'{col}_of_current_swing_high'].iloc[i - 1]
                        dataframe.at[i, 'swing_date'] = dataframe['date'].iloc[i]
                    else:
                        for col in ['high', 'low', 'close']:
                            dataframe.at[i, f'{col}_of_current_swing_high'] = dataframe[f'{col}_of_current_swing_high'].iloc[i - 1]
                            dataframe.at[i, f'{col}_of_previous_swing_high'] = dataframe[f'{col}_of_previous_swing_high'].iloc[i - 1]
                            dataframe.at[i, f'{col}_of_current_swing_low'] = dataframe[f'{col}_of_current_swing_low'].iloc[i - 1]
                            dataframe.at[i, f'{col}_of_previous_swing_low'] = dataframe[f'{col}_of_previous_swing_low'].iloc[i - 1]
                        dataframe.at[i, 'swing_date'] = dataframe['swing_date'].iloc[i - 1]
                elif dataframe['low'].iloc[i] < dataframe['low_of_potential'].iloc[i - 1]:
                    for col in ['high', 'low', 'close']:
                        dataframe.at[i, f'{col}_of_potential'] = dataframe[col].iloc[i]
                    for col in ['high', 'low', 'close']:
                        dataframe.at[i, f'{col}_of_current_low'] = dataframe[f'{col}_of_current_low'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_low'] = dataframe[f'{col}_of_previous_low'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_current_swing_high'] = dataframe[f'{col}_of_current_swing_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_swing_high'] = dataframe[f'{col}_of_previous_swing_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_current_swing_low'] = dataframe[f'{col}_of_current_swing_low'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_swing_low'] = dataframe[f'{col}_of_previous_swing_low'].iloc[i - 1]
                        dataframe.at[i, 'swing_date'] = dataframe['swing_date'].iloc[i - 1]
                    dataframe.at[i, 'looking_for'] = 'low'
                else:
                    for col in ['high', 'low', 'close']:
                        dataframe.at[i, f'{col}_of_current_low'] = dataframe[f'{col}_of_current_low'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_potential'] = dataframe[f'{col}_of_potential'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_low'] = dataframe[f'{col}_of_previous_low'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_current_swing_high'] = dataframe[f'{col}_of_current_swing_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_swing_high'] = dataframe[f'{col}_of_previous_swing_high'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_current_swing_low'] = dataframe[f'{col}_of_current_swing_low'].iloc[i - 1]
                        dataframe.at[i, f'{col}_of_previous_swing_low'] = dataframe[f'{col}_of_previous_swing_low'].iloc[i - 1]
                        dataframe.at[i, 'swing_date'] = dataframe['swing_date'].iloc[i - 1]
                    dataframe.at[i, 'looking_for'] = 'low'
        
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe['close_of_current_high'] > dataframe['high_of_previous_high'] * self.long_multiplier.value) 
                &(dataframe['close_of_current_low'] > dataframe['low_of_previous_low']) 
            ),
            'enter_long'] = 1

        dataframe.loc[
            (
                (dataframe['close_of_current_low'] < dataframe['low_of_previous_low'] * self.short_multiplier.value) 
                &(dataframe['close_of_current_high'] < dataframe['high_of_previous_high']) 
            ),
            'enter_short'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (dataframe['close'] > dataframe['high_of_current_high']),
            'exit_long'] = 1

        dataframe.loc[
            (dataframe['close'] < dataframe['low_of_current_low']),
            'exit_short'] = 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:
                 
        entry_tag = ''
        max_leverage = self.xlevrage.value

        return max_leverage
