import numpy as np # noqa import pandas as pd # noqa from pandas import DataFrame from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, IStrategy, IntParameter) import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.strategy import merge_informative_pair from freqtrade.persistence import Trade from datetime import datetime, timedelta from typing import Dict, List, Optional, Tuple from cachetools import TTLCache import technical.indicators as ftt import time class LongShortRangeTradingMachetesV1_1(IStrategy): INTERFACE_VERSION = 2 TF_STAND_BY_WAITING_FOR_MARKET_CONDITION_STATE = 0 TF_ENTERED_MARKET_CONDITION_WAITING_FOR_CONFIRMATION_STATE = 1 TF_MARKET_CONDITION_CONFIRMED_WAITING_FOR_ENTRY_SIGNAL_STATE = 2 TF_ENTRY_SIGNAL_FOUND_STATE = 3 TF_ENTRY_SIGNAL_FOUND_WAITING_FOR_EXIT_SIGNAL_STATE = 4 custom_trade_flow_info = {} custom_trade_info = {} custom_current_price_cache = TTLCache(maxsize=100, ttl=300) minimal_roi = { "0": 1 } stoploss = -0.1 trailing_stop = False timeframe = '1m' timeframe_medium = '15m' timeframe_long = '5m' candels_per_timeframe_medium = 4 candels_per_timeframe_long = 16 process_only_new_candles = False use_sell_signal = True sell_profit_only = False ignore_roi_if_buy_signal = False startup_candle_count: int = 500 use_dynamic_roi = True use_custom_stoploss = True order_types = { 'buy': 'limit', 'sell': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False } order_time_in_force = { 'buy': 'gtc', 'sell': 'gtc' } plot_config = { 'main_plot': { f'base_open_{timeframe}': {}, f'base_high_{timeframe}': {}, f'base_low_{timeframe}': {}, f'base_close_{timeframe}': {}, f'base_overbought_price_{timeframe}': {}, f'base_oversold_price_{timeframe}': {}, f'base_overbought_price_{timeframe_medium}': {}, f'base_oversold_price_{timeframe_medium}': {}, f'base_overbought_price_{timeframe_long}': {}, f'base_oversold_price_{timeframe_long}': {}, }, 'subplots': { "cci": { f'base_cci_{timeframe}': {'color': 'yellow'}, f'base_cci_{timeframe_medium}': {'color': 'yellow'}, f'base_cci_{timeframe_long}': {'color': 'yellow'}, f'base_cci_overbought_value_{timeframe}': { 'color': 'rgba(35, 138, 29, 0.75)', 'fill_to': f'base_cci_oversold_value_{timeframe}', 'fill_label': 'cci', 'fill_color': 'rgba(51, 255, 117, 0.2)', } }, "signals": { 'has_entered_market_condition': {'color': 'red'}, 'is_in_market_condition': {'color': 'red'}, 'has_confirmation': {'color': 'yellow'}, 'has_entry_signal': {'color': 'green'}, 'entry_signal': {'color': 'blue'}, 'has_exit_signal': {'color': 'green'}, 'exit_signal': {'color': 'blue'} } } } droi_trend_type = CategoricalParameter(['rmi', 'ssl', 'candle', 'any'], default='any', space='sell', optimize=True) droi_pullback = CategoricalParameter([True, False], default=True, space='sell', optimize=True) droi_pullback_amount = DecimalParameter(0.005, 0.02, default=0.005, space='sell', optimize=True) droi_pullback_respect_table = CategoricalParameter([True, False], default=False, space='sell', optimize=True) cstp_threshold = DecimalParameter(-0.05, 0, default=-0.03, space='sell', optimize=True) cstp_bail_how = CategoricalParameter(['roc', 'time', 'any'], default='roc', space='sell', optimize=True) cstp_bail_roc = DecimalParameter(-0.05, -0.01, default=-0.03, space='sell', optimize=True) cstp_bail_time = IntParameter(720, 1440, default=720, space='sell', optimize=True) cstp_trailing_stop_positive_offset = DecimalParameter(0.005, 0.06,default=0.01,space='sell', optimize=True) cstp_trailing_stop_profit_devider = IntParameter(2, 4,default=2,space='sell', optimize=True) cstp_trailing_max_stoploss = DecimalParameter(0.02, 0.08,default=0.02,space='sell', optimize=True) cstp_trailing_enabled = CategoricalParameter([True, False], default=True, space='sell', optimize=True) def on_populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: base_pair_name = self.get_base_pair_name(metadata['pair']) dataframe = self.get_base_pair_dataframe(dataframe, base_pair_name, self.timeframe_long) dataframe = self.get_base_pair_dataframe(dataframe, base_pair_name, self.timeframe) dataframe = self.get_indicators_custom_stoploss(dataframe) self.setup_custom_trade_info(dataframe, metadata) return dataframe def on_populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if "UP" in metadata['pair'] or "DOWN" in metadata['pair']: if "UP" in metadata['pair']: dataframe = self.calc_indicator_signals_long(dataframe, metadata) elif "DOWN" in metadata['pair']: dataframe = self.calc_indicator_signals_short(dataframe, metadata) dataframe = self.calc_trade_flow(dataframe, metadata) else: dataframe['entry_signal'] = 0 return dataframe def on_populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if "UP" in metadata['pair'] or "DOWN" in metadata['pair']: if "UP" in metadata['pair']: dataframe = self.calc_indicator_signals_long(dataframe, metadata) elif "DOWN" in metadata['pair']: dataframe = self.calc_indicator_signals_short(dataframe, metadata) dataframe = self.calc_trade_flow(dataframe, metadata) else: dataframe['exit_signal'] = 0 return dataframe def informative_pairs(self): pairs = self.dp.current_whitelist() informative_pairs = [] informative_pairs += [(pair, self.timeframe) for pair in pairs] informative_pairs += [(pair, self.timeframe_medium) for pair in pairs] informative_pairs += [(pair, self.timeframe_long) for pair in pairs] return informative_pairs def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if not self.dp: return dataframe dataframe = self.on_populate_indicators(dataframe, metadata) return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe = self.on_populate_buy_trend(dataframe, metadata) dataframe.loc[ ( (dataframe['entry_signal'] == 1) ), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe = self.on_populate_sell_trend(dataframe, metadata) dataframe.loc[ ( (dataframe['exit_signal'] == 1) ), 'sell'] = 1 return dataframe def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, current_profit: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() return False def get_indicators_custom_stoploss(self, dataframe): def RMI(dataframe, *, length=20, mom=5): """ Source: https://github.com/freqtrade/technical/blob/master/technical/indicators/indicators.py#L912 """ df = dataframe.copy() df['maxup'] = (df['close'] - df['close'].shift(mom)).clip(lower=0) df['maxdown'] = (df['close'].shift(mom) - df['close']).clip(lower=0) df.fillna(0, inplace=True) df["emaInc"] = ta.EMA(df, price='maxup', timeperiod=length) df["emaDec"] = ta.EMA(df, price='maxdown', timeperiod=length) df['RMI'] = np.where(df['emaDec'] == 0, 0, 100 - 100 / (1 + df["emaInc"] / df["emaDec"])) return df["RMI"] def SSLChannels_ATR(dataframe, length=7): """ SSL Channels with ATR: https://www.tradingview.com/script/SKHqWzql-SSL-ATR-channel/ Credit to @JimmyNixx for python """ df = dataframe.copy() df['ATR'] = ta.ATR(df, timeperiod=14) df['smaHigh'] = df['high'].rolling(length).mean() + df['ATR'] df['smaLow'] = df['low'].rolling(length).mean() - df['ATR'] df['hlv'] = np.where(df['close'] > df['smaHigh'], 1, np.where(df['close'] < df['smaLow'], -1, np.NAN)) df['hlv'] = df['hlv'].ffill() df['sslDown'] = np.where(df['hlv'] < 0, df['smaHigh'], df['smaLow']) df['sslUp'] = np.where(df['hlv'] < 0, df['smaLow'], df['smaHigh']) return df['sslDown'], df['sslUp'] def SROC(dataframe, roclen=21, emalen=13, smooth=21): df = dataframe.copy() roc = ta.ROC(df, timeperiod=roclen) ema = ta.EMA(df, timeperiod=emalen) sroc = ta.ROC(ema, timeperiod=smooth) return sroc dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) dataframe['roc'] = ta.ROC(dataframe, timeperiod=9) dataframe['rmi'] = RMI(dataframe, length=24, mom=5) ssldown, sslup = SSLChannels_ATR(dataframe, length=21) dataframe['sroc'] = SROC(dataframe, roclen=21, emalen=13, smooth=21) dataframe['ssl-dir'] = np.where(sslup > ssldown,'up','down') dataframe['rmi-up'] = np.where(dataframe['rmi'] >= dataframe['rmi'].shift(),1,0) dataframe['rmi-up-trend'] = np.where(dataframe['rmi-up'].rolling(5).sum() >= 3,1,0) dataframe['candle-up'] = np.where(dataframe['close'] >= dataframe['close'].shift(),1,0) dataframe['candle-up-trend'] = np.where(dataframe['candle-up'].rolling(5).sum() >= 3,1,0) return dataframe def get_indicators(self, dataframe): dataframe['cci'] = ta.CCI(dataframe) dataframe['cci_overbought_value'] = 100 dataframe['cci_oversold_value'] = -100 dataframe['overbought_price'] = ( (qtpylib.crossed_below(dataframe['cci'], 100)) ).fillna(0).astype('int') * dataframe['high'] dataframe['overbought_price'] = dataframe['overbought_price'].replace(to_replace=0, method='ffill') dataframe['oversold_price'] = ( (qtpylib.crossed_above(dataframe['cci'], -100)) ).fillna(0).astype('int') * dataframe['low'] dataframe['oversold_price'] = dataframe['oversold_price'].replace(to_replace=0, method='ffill') return dataframe def get_base_pair_name(self, pair_name): pair_name_parts = pair_name.split('/') target_pair_name = pair_name_parts[0] target_pair_suffix = 'DOWN' if ("DOWN" in pair_name) else 'UP' target_base_name = target_pair_name.replace(target_pair_suffix, "") base_pair_name = target_base_name + '/USDT' return base_pair_name def get_base_pair_dataframe(self,dataframe, base_pair_name, timeframe_str, smooth_list = None, candels_per_timeframe = 2): base_pair_dataframe = self.dp.get_pair_dataframe(base_pair_name, timeframe_str) base_pair_dataframe = self.get_indicators(base_pair_dataframe) ignore_columns = ['date'] base_pair_dataframe.rename(columns=lambda s: "base_" + s if (not s in ignore_columns) else s, inplace=True) dataframe = merge_informative_pair(dataframe, base_pair_dataframe, self.timeframe, timeframe_str, ffill=True) if smooth_list != None: for indicator_key in smooth_list: dataframe[f'base_{indicator_key}_{timeframe_str}'] = ta.SMA(dataframe[f'base_{indicator_key}_{timeframe_str}'], timeperiod=candels_per_timeframe) return dataframe def calc_indicator_signals_long(self, dataframe, metadata): dataframe['has_entered_market_condition'] = ( (qtpylib.crossed_above(dataframe[f'base_close_{self.timeframe}'], dataframe[f'base_oversold_price_{self.timeframe_long}'])) ).fillna(0).astype('int') dataframe['is_in_market_condition'] = ( (dataframe[f'base_close_{self.timeframe}'] > dataframe[f'base_oversold_price_{self.timeframe_long}']) ).fillna(0).astype('int') dataframe['has_confirmation'] = ( (qtpylib.crossed_above(dataframe[f'base_cci_{self.timeframe_long}'], 0)) ).fillna(0).astype('int') dataframe['has_entry_signal'] = ( (dataframe[f'base_cci_{self.timeframe_long}'] >= 0) ).fillna(0).astype('int') dataframe['has_exit_signal'] = ( (qtpylib.crossed_above(dataframe[f'base_close_{self.timeframe}'], dataframe[f'base_overbought_price_{self.timeframe_long}'])) ).fillna(0).astype('int') return dataframe def calc_indicator_signals_short(self, dataframe, metadata): dataframe['has_entered_market_condition'] = ( (qtpylib.crossed_below(dataframe[f'base_close_{self.timeframe}'], dataframe[f'base_overbought_price_{self.timeframe_long}'])) ).fillna(0).astype('int') dataframe['is_in_market_condition'] = ( (dataframe[f'base_close_{self.timeframe}'] < dataframe[f'base_overbought_price_{self.timeframe_long}']) ).fillna(0).astype('int') dataframe['has_confirmation'] = ( (qtpylib.crossed_below(dataframe[f'base_cci_{self.timeframe_long}'], 0)) ).fillna(0).astype('int') dataframe['has_entry_signal'] = ( (dataframe[f'base_cci_{self.timeframe_long}'] <= 0) ).fillna(0).astype('int') dataframe['has_exit_signal'] = ( (qtpylib.crossed_below(dataframe[f'base_close_{self.timeframe}'], dataframe[f'base_oversold_price_{self.timeframe_long}'])) ).fillna(0).astype('int') return dataframe def init_trade_flow_info(self, pair): if not pair in self.custom_trade_flow_info: self.custom_trade_flow_info[pair] = {} self.custom_trade_flow_info[pair]['trade_flow'] = None self.set_trade_flow_state(self.TF_STAND_BY_WAITING_FOR_MARKET_CONDITION_STATE, pair) def is_trade_flow_state(self, trade_flow_state, pair): return self.custom_trade_flow_info[pair]['trade_flow'] == trade_flow_state def set_trade_flow_state(self, trade_flow_state, pair): self.custom_trade_flow_info[pair]['trade_flow'] = trade_flow_state def calc_trade_flow(self, dataframe, metadata): dataframe['entry_signal'] = 0 dataframe['exit_signal'] = 0 pair = metadata['pair'] start_time = time.time() self.init_trade_flow_info(pair) for row_df in zip(dataframe['has_entered_market_condition'],dataframe['is_in_market_condition'],dataframe['has_confirmation'],dataframe['has_entry_signal'],dataframe['has_exit_signal'],dataframe['date']): if self.is_trade_flow_state(self.TF_STAND_BY_WAITING_FOR_MARKET_CONDITION_STATE, pair): if row_df[0] == 1: self.set_trade_flow_state(self.TF_ENTERED_MARKET_CONDITION_WAITING_FOR_CONFIRMATION_STATE, pair) else: continue if self.is_trade_flow_state(self.TF_ENTERED_MARKET_CONDITION_WAITING_FOR_CONFIRMATION_STATE, pair): if row_df[1] == 1: if row_df[2] == 1: self.set_trade_flow_state(self.TF_MARKET_CONDITION_CONFIRMED_WAITING_FOR_ENTRY_SIGNAL_STATE, pair) else: continue else: self.set_trade_flow_state(self.TF_STAND_BY_WAITING_FOR_MARKET_CONDITION_STATE, pair) continue if self.is_trade_flow_state(self.TF_MARKET_CONDITION_CONFIRMED_WAITING_FOR_ENTRY_SIGNAL_STATE, pair): if row_df[1] == 1: if row_df[2] == 1: if row_df[3] == 1: self.set_trade_flow_state(self.TF_ENTRY_SIGNAL_FOUND_STATE, pair) else: continue else: self.set_trade_flow_state(self.TF_ENTERED_MARKET_CONDITION_WAITING_FOR_CONFIRMATION_STATE, pair) continue else: self.set_trade_flow_state(self.TF_STAND_BY_WAITING_FOR_MARKET_CONDITION_STATE, pair) continue if self.is_trade_flow_state(self.TF_ENTRY_SIGNAL_FOUND_STATE, pair): dataframe.at[dataframe['date'] == row_df[5], 'entry_signal'] = 1 self.set_trade_flow_state(self.TF_ENTRY_SIGNAL_FOUND_WAITING_FOR_EXIT_SIGNAL_STATE, pair) if self.is_trade_flow_state(self.TF_ENTRY_SIGNAL_FOUND_WAITING_FOR_EXIT_SIGNAL_STATE, pair): if row_df[4] == 1: self.set_trade_flow_state(self.TF_STAND_BY_WAITING_FOR_MARKET_CONDITION_STATE, pair) dataframe.at[dataframe['date'] == row_df[5], 'exit_signal'] = 1 else: continue print(pair, 'calc_trade_flow', time.time() - start_time) return dataframe def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60) if self.config['runmode'].value in ('live', 'dry_run'): dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) sroc = dataframe['sroc'].iat[-1] else: sroc = self.custom_trade_info[trade.pair]['sroc'].loc[current_time]['sroc'] new_stoploss = 1 if current_profit < self.cstp_threshold.value: if self.cstp_bail_how.value == 'roc' or self.cstp_bail_how.value == 'any': if (sroc/100) <= self.cstp_bail_roc.value: new_stoploss = 0.001 if self.cstp_bail_how.value == 'time' or self.cstp_bail_how.value == 'any': if trade_dur > self.cstp_bail_time.value: new_stoploss = 0.001 else: if self.cstp_trailing_enabled.value == True and current_profit >= self.cstp_trailing_stop_positive_offset.value: desired_stoploss = current_profit / self.cstp_trailing_stop_profit_devider.value new_stoploss = max(min(desired_stoploss, self.cstp_trailing_max_stoploss.value), 0.025) return new_stoploss def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60) if self.use_dynamic_roi: _, roi = self.min_roi_reached_dynamic(trade, current_profit, current_time, trade_dur) else: _, roi = self.min_roi_reached_entry(trade_dur) if roi is None: return False else: return current_profit > roi def min_roi_reached_dynamic(self, trade: Trade, current_profit: float, current_time: datetime, trade_dur: int) -> Tuple[Optional[int], Optional[float]]: minimal_roi = self.minimal_roi _, table_roi = self.min_roi_reached_entry(trade_dur) if self.custom_trade_info and trade and trade.pair in self.custom_trade_info: if self.config['runmode'].value in ('live', 'dry_run'): dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=trade.pair, timeframe=self.timeframe) rmi_trend = dataframe['rmi-up-trend'].iat[-1] candle_trend = dataframe['candle-up-trend'].iat[-1] ssl_dir = dataframe['ssl-dir'].iat[-1] else: rmi_trend = self.custom_trade_info[trade.pair]['rmi-up-trend'].loc[current_time]['rmi-up-trend'] candle_trend = self.custom_trade_info[trade.pair]['candle-up-trend'].loc[current_time]['candle-up-trend'] ssl_dir = self.custom_trade_info[trade.pair]['ssl-dir'].loc[current_time]['ssl-dir'] min_roi = table_roi max_profit = trade.calc_profit_ratio(trade.max_rate) pullback_value = (max_profit - self.droi_pullback_amount.value) in_trend = False if self.droi_trend_type.value == 'rmi' or self.droi_trend_type.value == 'any': if rmi_trend == 1: in_trend = True if self.droi_trend_type.value == 'ssl' or self.droi_trend_type.value == 'any': if ssl_dir == 'up': in_trend = True if self.droi_trend_type.value == 'candle' or self.droi_trend_type.value == 'any': if candle_trend == 1: in_trend = True if (in_trend == True): min_roi = 100 if self.droi_pullback.value == True and (current_profit < pullback_value): if self.droi_pullback_respect_table.value == True: min_roi = table_roi else: min_roi = current_profit / 2 else: min_roi = table_roi return trade_dur, min_roi def get_current_price(self, pair: str, refresh: bool) -> float: if not refresh: rate = self.custom_current_price_cache.get(pair) if rate: return rate ask_strategy = self.config.get('ask_strategy', {}) if ask_strategy.get('use_order_book', False): ob = self.dp.orderbook(pair, 1) rate = ob[f"{ask_strategy['price_side']}s"][0][0] else: ticker = self.dp.ticker(pair) rate = ticker['last'] self.custom_current_price_cache[pair] = rate return rate def populate_trades(self, pair: str) -> dict: if not pair in self.custom_trade_info: self.custom_trade_info[pair] = {} trade_data = {} trade_data['active_trade'] = False if self.config['runmode'].value in ('live', 'dry_run'): active_trade = Trade.get_trades([Trade.pair == pair, Trade.is_open.is_(True),]).all() if active_trade: current_rate = self.get_current_price(pair, True) """ freqtrade | Traceback (most recent call last): freqtrade | File "/freqtrade/freqtrade/strategy/strategy_wrapper.py", line 17, in wrapper freqtrade | return f(*args, **kwargs) freqtrade | File "/freqtrade/freqtrade/strategy/interface.py", line 417, in _analyze_ticker_internal freqtrade | dataframe = self.analyze_ticker(dataframe, metadata) freqtrade | File "/freqtrade/freqtrade/strategy/interface.py", line 396, in analyze_ticker freqtrade | dataframe = self.advise_indicators(dataframe, metadata) freqtrade | File "/freqtrade/freqtrade/strategy/interface.py", line 763, in advise_indicators freqtrade | return self.populate_indicators(dataframe, metadata) freqtrade | File "/freqtrade/user_data/strategies/LongAndShortMachetes.py", line 258, in populate_indicators freqtrade | dataframe = self.on_populate_indicators(dataframe, metadata) freqtrade | File "/freqtrade/user_data/strategies/LongAndShortMachetes.py", line 142, in on_populate_indicators freqtrade | self.setup_custom_trade_info(dataframe, metadata) freqtrade | File "/freqtrade/user_data/strategies/LongAndShortMachetes.py", line 636, in setup_custom_trade_info freqtrade | self.custom_trade_info[metadata['pair']] = self.populate_trades(metadata['pair']) freqtrade | File "/freqtrade/user_data/strategies/LongAndShortMachetes.py", line 629, in populate_trades freqtrade | active_trade[0].adjust_min_max_rates(current_rate) freqtrade | TypeError: adjust_min_max_rates() missing 1 required positional argument: 'current_price_low' freqtrade | 2021-09-18 11:34:11,851 - freqtrade.strategy.interface - WARNING - Unable to analyze candle (OHLCV) data for pair DOTDOWN/USDT: adjust_min_max_rates() missing 1 required positional argument: 'current_price_low' from interface:should_sell This function evaluates if one of the conditions required to trigger a sell has been reached, which can either be a stop-loss, ROI or sell-signal. :param low: Only used during backtesting to simulate stoploss :param high: Only used during backtesting, to simulate ROI :param force_stoploss: Externally provided stoploss :return: True if trade should be sold, False otherwise """ active_trade[0].adjust_min_max_rates(current_rate, current_rate) return trade_data def setup_custom_trade_info(self, dataframe, metadata): self.custom_trade_info[metadata['pair']] = self.populate_trades(metadata['pair']) if self.dp.runmode.value in ('backtest', 'hyperopt'): self.custom_trade_info[metadata['pair']]['roc'] = dataframe[['date', 'roc']].copy().set_index('date') self.custom_trade_info[metadata['pair']]['atr'] = dataframe[['date', 'atr']].copy().set_index('date') self.custom_trade_info[metadata['pair']]['sroc'] = dataframe[['date', 'sroc']].copy().set_index('date') self.custom_trade_info[metadata['pair']]['ssl-dir'] = dataframe[['date', 'ssl-dir']].copy().set_index('date') self.custom_trade_info[metadata['pair']]['rmi-up-trend'] = dataframe[['date', 'rmi-up-trend']].copy().set_index('date') self.custom_trade_info[metadata['pair']]['candle-up-trend'] = dataframe[['date', 'candle-up-trend']].copy().set_index('date')