From 0bdd330cd9a95b0d69624c232f7b537beacbf7d4 Mon Sep 17 00:00:00 2001 From: jilv220 Date: Mon, 22 Nov 2021 12:35:14 -0500 Subject: [PATCH] add trailing buy --- BB_RPB_TSL.py | 284 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 268 insertions(+), 16 deletions(-) diff --git a/BB_RPB_TSL.py b/BB_RPB_TSL.py index 2ef452d..463fe18 100644 --- a/BB_RPB_TSL.py +++ b/BB_RPB_TSL.py @@ -1,13 +1,14 @@ # --- Do not remove these libs --- import freqtrade.vendor.qtpylib.indicators as qtpylib import numpy as np +import logging import talib.abstract as ta import pandas_ta as pta from freqtrade.persistence import Trade from freqtrade.strategy.interface import IStrategy from pandas import DataFrame, Series, DatetimeIndex, merge -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from freqtrade.strategy import merge_informative_pair, CategoricalParameter, DecimalParameter, IntParameter, stoploss_from_open from freqtrade.exchange import timeframe_to_prev_date from functools import reduce @@ -293,7 +294,7 @@ class BB_RPB_TSL(IStrategy): buy_gumbo_cti = DecimalParameter(-0.9, -0.0, default=-0.5 , optimize = is_optimize_gumbo_protection) buy_gumbo_r14 = DecimalParameter(-100, -44, default=-60 , optimize = is_optimize_gumbo_protection) - is_optimize_sqzmom_protection = True + is_optimize_sqzmom_protection = False buy_sqzmom_ema = DecimalParameter(0.9, 1.2, default=0.97 , optimize = is_optimize_sqzmom_protection) buy_sqzmom_ewo = DecimalParameter(-12 , 12, default= 0 , optimize = is_optimize_sqzmom_protection) buy_sqzmom_r14 = DecimalParameter(-100, -22, default=-50 , optimize = is_optimize_sqzmom_protection) @@ -331,9 +332,14 @@ class BB_RPB_TSL(IStrategy): sell_deadfish_bb_factor = DecimalParameter(0.90, 1.20, default=1.0 , optimize = is_optimize_deadfish) sell_deadfish_volume_factor = DecimalParameter(1, 2.5, default=1.0 , optimize = is_optimize_deadfish) + is_optimize_bleeding = False + sell_bleeding_cti = DecimalParameter(-0.9, -0.0, default=-0.5 , optimize = is_optimize_bleeding) + sell_bleeding_r14 = DecimalParameter(-100, -44, default=-60 , optimize = is_optimize_bleeding) + sell_bleeding_volume_factor = DecimalParameter(1, 2.5, default=1.0 , optimize = is_optimize_bleeding) + is_optimize_cti_r = False - sell_cti_r_cti = DecimalParameter(0.5, 1, default=0.5 , optimize = is_optimize_cti_r) - sell_cti_r_r = DecimalParameter(-20, 0, default=-20 , optimize = is_optimize_cti_r) + sell_cti_r_cti = DecimalParameter(0.55, 1, default=0.5 , optimize = is_optimize_cti_r) + sell_cti_r_r = DecimalParameter(-15, 0, default=-20 , optimize = is_optimize_cti_r) ############################################################################ @@ -358,6 +364,7 @@ def informative_1h_indicators(self, dataframe: DataFrame, metadata: dict) -> Dat # CTI informative_1h['cti'] = pta.cti(informative_1h["close"], length=20) + informative_1h['cti_40'] = pta.cti(informative_1h["close"], length=40) # CRSI (3, 2, 100) crsi_closechange = informative_1h['close'] / informative_1h['close'].shift(1) @@ -365,6 +372,7 @@ def informative_1h_indicators(self, dataframe: DataFrame, metadata: dict) -> Dat informative_1h['crsi'] = (ta.RSI(informative_1h['close'], timeperiod=3) + ta.RSI(crsi_updown, timeperiod=2) + ta.ROC(informative_1h['close'], 100)) / 3 # Williams %R + informative_1h['r_96'] = williams_r(informative_1h, period=96) informative_1h['r_480'] = williams_r(informative_1h, period=480) # Bollinger bands @@ -476,12 +484,14 @@ def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', curre if (last_candle['cti'] > self.sell_cti_r_cti.value) and (last_candle['r_14'] > self.sell_cti_r_r.value): return f"sell_profit_t_cti_r_0_1( {buy_tag})" - if (last_candle['momdiv_sell_1h'] == True) and (current_profit > 0.02): - return f"signal_profit_q_momdiv_1h( {buy_tag})" - if (last_candle['momdiv_sell'] == True) and (current_profit > 0.02): - return f"signal_profit_q_momdiv( {buy_tag})" - if (last_candle['momdiv_coh'] == True) and (current_profit > 0.02): - return f"signal_profit_q_momdiv_coh( {buy_tag})" + # main sell + if current_profit > 0.02: + if (last_candle['momdiv_sell_1h'] == True): + return f"signal_profit_q_momdiv_1h( {buy_tag})" + if (last_candle['momdiv_sell'] == True): + return f"signal_profit_q_momdiv( {buy_tag})" + if (last_candle['momdiv_coh'] == True): + return f"signal_profit_q_momdiv_coh( {buy_tag})" # sell bear if last_candle['close'] < last_candle['ema_200']: @@ -510,7 +520,7 @@ def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', curre and (((last_candle['ema_200'] - last_candle['close']) / last_candle['close']) < 0.022) and last_candle['rsi'] > previous_candle_1['rsi'] and (last_candle['rsi'] > (last_candle['rsi_1h'] + 10.0)) - ): + ): return f"sell_stoploss_u_e_1( {buy_tag})" # stoploss - deadfish @@ -522,6 +532,15 @@ def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', curre ): return f"sell_stoploss_deadfish( {buy_tag})" + # stoploss - bleeding + #if ( (current_profit < -0.05) + #and (last_candle['close'] < last_candle['ema_200']) + #and (last_candle['cti_mean_24'] < self.sell_bleeding_cti.value) + #and (last_candle['r_14_mean_24'] < self.sell_bleeding_r14.value) + #and (last_candle['volume_mean_12'] < last_candle['volume_mean_24'] * self.sell_bleeding_volume_factor.value) + #): + #return f"sell_stoploss_bleeding( {buy_tag})" + return None ## Confirm Entry @@ -733,7 +752,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: (dataframe['close'] < dataframe['bb_lowerband3'] * self.buy_bb_factor.value) ) - is_local_uptrend = ( # from NFI next gen + is_local_uptrend = ( # from NFI next gen, credit goes to @iterativ (dataframe['ema_26'] > dataframe['ema_12']) & (dataframe['ema_26'] - dataframe['ema_12'] > dataframe['open'] * self.buy_ema_diff.value) & (dataframe['ema_26'].shift() - dataframe['ema_12'].shift() > dataframe['open'] / 100) & @@ -797,7 +816,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: ) ) - is_cofi = ( + is_cofi = ( # Modified from cofi, credit goes to original author "slack user CofiBit" (dataframe['open'] < dataframe['ema_8'] * self.buy_ema_cofi.value) & (qtpylib.crossed_above(dataframe['fastk'], dataframe['fastd'])) & (dataframe['fastk'] < self.buy_fastk.value) & @@ -808,7 +827,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: (dataframe['r_14'] < self.buy_cofi_r14.value) ) - is_gumbo = ( # modified from gumbo1 + is_gumbo = ( # Modified from gumbo1, creadit goes to original author @raph92 (dataframe['EWO'] < self.buy_gumbo_ewo_low.value) & (dataframe['bb_middleband2_1h'] >= dataframe['T3_1h']) & (dataframe['T3'] <= dataframe['ema_8'] * self.buy_gumbo_ema.value) & @@ -816,7 +835,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: (dataframe['r_14'] < self.buy_gumbo_r14.value) ) - is_sqzmom = ( + is_sqzmom = ( # Modified from squeezeMomentum, credit goes to original author @LazyBear of TradingView (is_sqzOff) & (dataframe['linreg_val_20'].shift(2) > dataframe['linreg_val_20'].shift(1)) & (dataframe['linreg_val_20'].shift(1) < dataframe['linreg_val_20']) & @@ -826,7 +845,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: (dataframe['r_14'] < self.buy_sqzmom_r14.value) ) - # NFI quick mode + # NFI quick mode, credit goes to @iterativ is_nfi_13 = ( (dataframe['ema_50_1h'] > dataframe['ema_100_1h']) & (dataframe['close'] < dataframe['sma_30'] * 0.99) & @@ -1088,3 +1107,236 @@ def T3(dataframe, length=5): df['T3Average'] = c1 * df['xe6'] + c2 * df['xe5'] + c3 * df['xe4'] + c4 * df['xe3'] return df['T3Average'] + +logger = logging.getLogger(__name__) + +class BB_RPB_TSL_Trailing(BB_RPB_TSL): + # Original idea by @MukavaValkku, code by @tirail and @stash86 + # + # This class is designed to inherit from yours and starts trailing buy with your buy signals + # Trailing buy starts at any buy signal and will move to next candles if the trailing still active + # Trailing buy stops with BUY if : price decreases and rises again more than trailing_buy_offset + # Trailing buy stops with NO BUY : current price is > initial price * (1 + trailing_buy_max) OR custom_sell tag + # IT IS NOT COMPATIBLE WITH BACKTEST/HYPEROPT + # + + process_only_new_candles = True + + custom_info_trail_buy = dict() + + # Trailing buy parameters + trailing_buy_order_enabled = True + trailing_expire_seconds = 1800 + + # If the current candle goes above min_uptrend_trailing_profit % before trailing_expire_seconds_uptrend seconds, buy the coin + trailing_buy_uptrend_enabled = False + trailing_expire_seconds_uptrend = 90 + min_uptrend_trailing_profit = 0.02 + + debug_mode = True + trailing_buy_max_stop = 0.02 # stop trailing buy if current_price > starting_price * (1+trailing_buy_max_stop) + trailing_buy_max_buy = 0.000 # buy if price between uplimit (=min of serie (current_price * (1 + trailing_buy_offset())) and (start_price * 1+trailing_buy_max_buy)) + + init_trailing_dict = { + 'trailing_buy_order_started': False, + 'trailing_buy_order_uplimit': 0, + 'start_trailing_price': 0, + 'buy_tag': None, + 'start_trailing_time': None, + 'offset': 0, + 'allow_trailing': False, + } + + def trailing_buy(self, pair, reinit=False): + # returns trailing buy info for pair (init if necessary) + if not pair in self.custom_info_trail_buy: + self.custom_info_trail_buy[pair] = dict() + if (reinit or not 'trailing_buy' in self.custom_info_trail_buy[pair]): + self.custom_info_trail_buy[pair]['trailing_buy'] = self.init_trailing_dict.copy() + return self.custom_info_trail_buy[pair]['trailing_buy'] + + def trailing_buy_info(self, pair: str, current_price: float): + # current_time live, dry run + current_time = datetime.now(timezone.utc) + if not self.debug_mode: + return + trailing_buy = self.trailing_buy(pair) + + duration = 0 + try: + duration = (current_time - trailing_buy['start_trailing_time']) + except TypeError: + duration = 0 + finally: + logger.info( + f"pair: {pair} : " + f"start: {trailing_buy['start_trailing_price']:.4f}, " + f"duration: {duration}, " + f"current: {current_price:.4f}, " + f"uplimit: {trailing_buy['trailing_buy_order_uplimit']:.4f}, " + f"profit: {self.current_trailing_profit_ratio(pair, current_price)*100:.2f}%, " + f"offset: {trailing_buy['offset']}") + + def current_trailing_profit_ratio(self, pair: str, current_price: float) -> float: + trailing_buy = self.trailing_buy(pair) + if trailing_buy['trailing_buy_order_started']: + return (trailing_buy['start_trailing_price'] - current_price) / trailing_buy['start_trailing_price'] + else: + return 0 + + def trailing_buy_offset(self, dataframe, pair: str, current_price: float): + # return rebound limit before a buy in % of initial price, function of current price + # return None to stop trailing buy (will start again at next buy signal) + # return 'forcebuy' to force immediate buy + # (example with 0.5%. initial price : 100 (uplimit is 100.5), 2nd price : 99 (no buy, uplimit updated to 99.5), 3price 98 (no buy uplimit updated to 98.5), 4th price 99 -> BUY + current_trailing_profit_ratio = self.current_trailing_profit_ratio(pair, current_price) + default_offset = 0.005 + + trailing_buy = self.trailing_buy(pair) + if not trailing_buy['trailing_buy_order_started']: + return default_offset + + # example with duration and indicators + # dry run, live only + last_candle = dataframe.iloc[-1] + current_time = datetime.now(timezone.utc) + trailing_duration = current_time - trailing_buy['start_trailing_time'] + if trailing_duration.total_seconds() > self.trailing_expire_seconds: + if ((current_trailing_profit_ratio > 0) and (last_candle['buy'] == 1)): + # more than 1h, price under first signal, buy signal still active -> buy + return 'forcebuy' + else: + # wait for next signal + return None + elif (self.trailing_buy_uptrend_enabled and (trailing_duration.total_seconds() < self.trailing_expire_seconds_uptrend) and (current_trailing_profit_ratio < (-1 * self.min_uptrend_trailing_profit))): + # less than 90s and price is rising, buy + return 'forcebuy' + + if current_trailing_profit_ratio < 0: + # current price is higher than initial price + return default_offset + + trailing_buy_offset = { + 0.06: 0.02, + 0.03: 0.01, + 0: default_offset, + } + + for key in trailing_buy_offset: + if current_trailing_profit_ratio > key: + return trailing_buy_offset[key] + + return default_offset + + # end of trailing buy parameters + # ----------------------------------------------------- + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe = super().populate_indicators(dataframe, metadata) + self.trailing_buy(metadata['pair']) + return dataframe + + def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, **kwargs) -> bool: + val = super().confirm_trade_entry(pair, order_type, amount, rate, time_in_force, **kwargs) + + if val: + if self.trailing_buy_order_enabled and self.config['runmode'].value in ('live', 'dry_run'): + val = False + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + if(len(dataframe) >= 1): + last_candle = dataframe.iloc[-1].squeeze() + current_price = rate + trailing_buy = self.trailing_buy(pair) + trailing_buy_offset = self.trailing_buy_offset(dataframe, pair, current_price) + + if trailing_buy['allow_trailing']: + if (not trailing_buy['trailing_buy_order_started'] and (last_candle['buy'] == 1)): + # start trailing buy + + # self.custom_info_trail_buy[pair]['trailing_buy']['trailing_buy_order_started'] = True + # self.custom_info_trail_buy[pair]['trailing_buy']['trailing_buy_order_uplimit'] = last_candle['close'] + # self.custom_info_trail_buy[pair]['trailing_buy']['start_trailing_price'] = last_candle['close'] + # self.custom_info_trail_buy[pair]['trailing_buy']['buy_tag'] = f"initial_buy_tag (strat trail price {last_candle['close']})" + # self.custom_info_trail_buy[pair]['trailing_buy']['start_trailing_time'] = datetime.now(timezone.utc) + # self.custom_info_trail_buy[pair]['trailing_buy']['offset'] = 0 + + trailing_buy['trailing_buy_order_started'] = True + trailing_buy['trailing_buy_order_uplimit'] = last_candle['close'] + trailing_buy['start_trailing_price'] = last_candle['close'] + trailing_buy['buy_tag'] = last_candle['buy_tag'] + trailing_buy['start_trailing_time'] = datetime.now(timezone.utc) + trailing_buy['offset'] = 0 + + self.trailing_buy_info(pair, current_price) + logger.info(f'start trailing buy for {pair} at {last_candle["close"]}') + + elif trailing_buy['trailing_buy_order_started']: + if trailing_buy_offset == 'forcebuy': + # buy in custom conditions + val = True + ratio = "%.2f" % ((self.current_trailing_profit_ratio(pair, current_price)) * 100) + self.trailing_buy_info(pair, current_price) + logger.info(f"price OK for {pair} ({ratio} %, {current_price}), order may not be triggered if all slots are full") + + elif trailing_buy_offset is None: + # stop trailing buy custom conditions + self.trailing_buy(pair, reinit=True) + logger.info(f'STOP trailing buy for {pair} because "trailing buy offset" returned None') + + elif current_price < trailing_buy['trailing_buy_order_uplimit']: + # update uplimit + old_uplimit = trailing_buy["trailing_buy_order_uplimit"] + self.custom_info_trail_buy[pair]['trailing_buy']['trailing_buy_order_uplimit'] = min(current_price * (1 + trailing_buy_offset), self.custom_info_trail_buy[pair]['trailing_buy']['trailing_buy_order_uplimit']) + self.custom_info_trail_buy[pair]['trailing_buy']['offset'] = trailing_buy_offset + self.trailing_buy_info(pair, current_price) + logger.info(f'update trailing buy for {pair} at {old_uplimit} -> {self.custom_info_trail_buy[pair]["trailing_buy"]["trailing_buy_order_uplimit"]}') + elif current_price < (trailing_buy['start_trailing_price'] * (1 + self.trailing_buy_max_buy)): + # buy ! current price > uplimit && lower thant starting price + val = True + ratio = "%.2f" % ((self.current_trailing_profit_ratio(pair, current_price)) * 100) + self.trailing_buy_info(pair, current_price) + logger.info(f"current price ({current_price}) > uplimit ({trailing_buy['trailing_buy_order_uplimit']}) and lower than starting price price ({(trailing_buy['start_trailing_price'] * (1 + self.trailing_buy_max_buy))}). OK for {pair} ({ratio} %), order may not be triggered if all slots are full") + + elif current_price > (trailing_buy['start_trailing_price'] * (1 + self.trailing_buy_max_stop)): + # stop trailing buy because price is too high + self.trailing_buy(pair, reinit=True) + self.trailing_buy_info(pair, current_price) + logger.info(f'STOP trailing buy for {pair} because of the price is higher than starting price * {1 + self.trailing_buy_max_stop}') + else: + # uplimit > current_price > max_price, continue trailing and wait for the price to go down + self.trailing_buy_info(pair, current_price) + logger.info(f'price too high for {pair} !') + + else: + logger.info(f"Wait for next buy signal for {pair}") + + if (val == True): + self.trailing_buy_info(pair, rate) + self.trailing_buy(pair, reinit=True) + logger.info(f'STOP trailing buy for {pair} because I buy it') + + return val + + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe = super().populate_buy_trend(dataframe, metadata) + + if self.trailing_buy_order_enabled and self.config['runmode'].value in ('live', 'dry_run'): + last_candle = dataframe.iloc[-1].squeeze() + trailing_buy = self.trailing_buy(metadata['pair']) + if (last_candle['buy'] == 1): + if not trailing_buy['trailing_buy_order_started']: + open_trades = Trade.get_trades([Trade.pair == metadata['pair'], Trade.is_open.is_(True), ]).all() + if not open_trades: + logger.info(f"Set 'allow_trailing' to True for {metadata['pair']} to start trailing!!!") + # self.custom_info_trail_buy[metadata['pair']]['trailing_buy']['allow_trailing'] = True + trailing_buy['allow_trailing'] = True + initial_buy_tag = last_candle['buy_tag'] if 'buy_tag' in last_candle else 'buy signal' + dataframe.loc[:, 'buy_tag'] = f"{initial_buy_tag} (start trail price {last_candle['close']})" + else: + if (trailing_buy['trailing_buy_order_started'] == True): + logger.info(f"Continue trailing for {metadata['pair']}. Manually trigger buy signal!!") + dataframe.loc[:,'buy'] = 1 + dataframe.loc[:, 'buy_tag'] = trailing_buy['buy_tag'] + # dataframe['buy'] = 1 + + return dataframe