# source: https://raw.githubusercontent.com/project237/my_freqtrade_scripts/bf56425d9e420e99463f88a3a594d9f4e5062e03/cindicatorAB.py
# testing new repo | 2022-06-28 16:37

"""
ok, so this will be the strategy file where we are gonna be running for each single iteration of 
the backtest loop. 

1- Inside populate_indicators, we simply use M_dt column to the find the correct 15 min candle and insert the columns "buy", 
price1-3 and indicator
as new column to the backtest dataframe. All but first columns will have the same value for each row (TODO - workaround) and 
the column "buy" will insert a 1 for an otherwise 0 on whichever row matches the the M_dt   

2- Inside populate_buy_trend we just instruct it to buy when the column "buy" is 1. 

3- Inside populate_sell_trend we instruct it to sell whenever either price2 or price3 is within the candle range. 
"""


## --- Do not remove these libs ---
from ctypes.wintypes import BOOL
from freqtrade.strategy import IStrategy, merge_informative_pair
from typing import Dict, List
from functools import reduce

# my imports / un-imports here
import pandas as pd
import arrow
# from pandas import pd.DataFrame
## --------------------------------

class github_project237_my_freqtrade_scripts__cindicatorAB__20220818_141153(IStrategy):
    # So all the methods below are called only once per backtest and each time during the bot loop for 
    # live trading and dry run modes
    # keep this in mind..
    """
    Sample strategy implementing Informative Pairs - compares stake_currency with USDT.
    Not performing very well - but should serve as an example how to use a referential pair against USDT.
    author@: xmatthias
    github@: https://github.com/freqtrade/freqtrade-strategies

    How to use it?
    > python3 freqtrade -s github_project237_my_freqtrade_scripts__cindicatorAB__20220818_141153
    """

    # An informative dictionary mapping ticker df column to their meanings - 2022-06-30 21:07
    ticker_df_column_ref = {
        "timestamp": 0,
        "open"     : 1,
        "high"     : 2,
        "low"      : 3,
        "close"    : 4,
        "volume"   : 5
    }

    # we import our cindictor AB sorted df here for use in populate_indicators
    signals = pd.read_json("CND_AB_parsed_fix1.json")

    # parameters that will go to hyperopt
    my_params = {
        "buy_buffer" : 1.01,
        "sell_buffer": 1.01
        # these two will be directly multiplied with the raw signal price
     }

    def first_is_recent_or_eq(self, ts1, ts2, use_arrow=True):
        """
        compares two unix timestamps, if the timetamps might be of different units, 
        keep use_arrow unchanged
        """
        if use_arrow:
            return arrow.get(ts1) >= arrow.get(ts2)
        return ts1 >= ts2

    # |*|
    def return_current_signal_as_dict(self, candle_T, signals_df):
        """
        signals_df  - dataframe that contains parsed signal values indexed by datetime 
        candle_T    - unix timestamp of the current candle start time 
        !!WARNING   - assumes candle_T is the STARTING timestamp of a given candle  
        TODO - CHECK ASSUMPTION ABOVE
        """
        # METHOD 1
        # here we want to pick the most recent signal that is at or before the candle timestamp
        # the best way of doing this without doing redundant computation would be to
        # 1- start with from candle 1, go down until a signal with later ts is found, 
        # 2- Pick the latest one, assign its values to the columns  

        cols = ['base', 'above', 'below',"indicator", "M_dt"]
        # dict_signals = {"price1":None, "price2":None, "price3":None, "indicator": None}

        # define the filter that selects all rows before the candle_T
        at_or_beforeF = signals_df["M_dt"].apply(lambda x: self.first_is_recent_or_eq(candle_T, x, use_arrow=False))

        # return the selected columns, this is a series object
        at_or_before = signals_df.loc[at_or_beforeF, cols]

        # pick the last one and write the signals into a dict
        last_dict = at_or_before.iloc[-1].to_dict()

        return last_dict
    
    ## 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
    }

    ## Optimal stoploss designed for the strategy
    ## This attribute will be overridden if the config file contains "stoploss"
    # !! leaving this ratio well below what the exit signal should be doing,
    # !! in effect, we're not using this
    stoploss = -0.25

    ## Optimal timeframe for the strategy
    timeframe = '1h'

    ## trailing stoploss
    trailing_stop                 = False
    trailing_stop_positive        = 0.02
    trailing_stop_positive_offset = 0.04

    ## run "populate_indicators" only for new candle
    ta_on_candle = False

    ## Experimental settings (configuration will overide these if set)
    use_exit_signal            = True
    ignore_roi_if_entry_signal = True


    ## Optional order type mapping
    # !! leave these as is since these don't affect the bt
    order_types = {
        'buy'                 : 'limit',
        'sell'                : 'limit',
        'stoploss'            : 'market',
        'stoploss_on_exchange': False
    }

    # |*|
    def informative_pairs(self):
        """
        Define additional, informative pair/interval combinations to be cached from the exchange.
        These pair/interval combinations are non-tradeable, unless they are part
        of the whitelist as well.
        For more information, please consult the documentation
        :return: List of tuples in the format (pair, interval)
            Sample: return [("ETH/USDT", "5m"),
                            ("BTC/USDT", "15m"),
                            ]
        """
        return []

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Adds several different TA indicators to the given pd.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.
        !! - when binance data is converted from json to df, row.loc[0] inside lambda expression should
        !! refer to the candle timestamp
        """

        # We have two apporaches here so comment them as you need
        # !! signals exists as a global variable here CONSIDER PASSING IT AS ARGUMENT IF THIS VERSION FAILS

        ## dataframe['ema20'] = ta.EMA(dataframe, timeperiod=20)
        ## dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
        ## dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)

        # APPROACH 1 new columns at once
        # new_cols = ["base","above","below","indicator", "M_dt"]

        # # inserts new empty columns to the price dataframe with NaN values
        # dataframe = dataframe.reindex(columns = dataframe.columns.tolist() + new_cols)

        # # populate empty columns from signals dataframe using insert_signal_cols()
        # dataframe = dataframe.apply(lambda row: return_current_signal_as_dict(row.loc[0], self.signals), axis='columns', result_type='expand')

        # approach 2 - get the dict and populate / expand one by one 
        dataframe["signal"]    = dataframe.apply(lambda row: self.return_current_signal_as_dict(row.loc[0], self.signals), axis='columns')
        dataframe["base"]      = dataframe.apply(lambda row: row.signal["base"], axis="columns")
        dataframe["above"]     = dataframe.apply(lambda row: row.signal["above"], axis="columns")
        dataframe["below"]     = dataframe.apply(lambda row: row.signal["below"], axis="columns")
        dataframe["indicator"] = dataframe.apply(lambda row: row.signal["indicator"], axis="columns")
        dataframe["M_dt"]      = dataframe.apply(lambda row: row.signal["M_dt"], axis="columns")

        return dataframe

    # |*|
    def populate_buy_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Based on TA indicators, populates the buy signal for the given dataframe
        :param dataframe: pd.DataFrame
        :return: pd.DataFrame with buy column
        """
        
        # we esentially buy whenever the (base price + buy_buffer) is within low price and high price 
        # (this will go to hyperopt later) 
        dataframe.loc[
            (
                # (dataframe['low_15m'] <= (dataframe['base'] * self.my_params["buy_buffer"])  <= dataframe['high_15m'])

                # in case reference needed, check deprecated line above. 
                (dataframe.loc[:,3] <= (dataframe['base'] * self.my_params["buy_buffer"])  <= dataframe.loc[:,2])
            ),
            'buy'] = 1

        return dataframe

    # |*|
    def populate_sell_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Based on TA indicators, populates the sell signal for the given dataframe
        :param dataframe: pd.DataFrame
        :return: pd.DataFrame with buy column
        """

        # we sell whenever the either above or below price is within the low and high of the candle
        dataframe.loc[
            (
                # (dataframe['low_15m'] <= (dataframe['above'] * self.my_params["sell_buffer"])  <= dataframe['high_15m']) |
                # (dataframe['low_15m'] <= (dataframe['below'] * self.my_params["sell_buffer"])  <= dataframe['high_15m'])

                # in case reference needed, check deprecated line above. 
                (dataframe.loc[:,3] <= (dataframe['above'] * self.my_params["sell_buffer"])  <= dataframe.loc[:,2]) |
                (dataframe.loc[:,3] <= (dataframe['below'] * self.my_params["sell_buffer"])  <= dataframe.loc[:,2])
            ),
            'sell'] = 1
        return dataframe
