import logging from datetime import datetime, timezone from functools import reduce from typing import Dict, List import freqtrade.vendor.qtpylib.indicators as qtpylib import numpy as np import pandas_ta as pta import talib.abstract as ta import technical.indicators as ftt from freqtrade.persistence import Trade from freqtrade.strategy import ( BooleanParameter, DecimalParameter, IntParameter, informative, stoploss_from_open, ) from freqtrade.strategy.interface import IStrategy from pandas import DataFrame, Series from skopt.space import Dimension, Integer, Real logger = logging.getLogger(__name__) def bollinger_bands(stock_price, window_size, num_of_std): rolling_mean = stock_price.rolling(window=window_size).mean() rolling_std = stock_price.rolling(window=window_size).std() lower_band = rolling_mean - (rolling_std * num_of_std) return np.nan_to_num(rolling_mean), np.nan_to_num(lower_band) def ha_typical_price(bars): res = (bars["ha_high"] + bars["ha_low"] + bars["ha_close"]) / 3.0 return Series(index=bars.index, data=res) class red(IStrategy): """ VERSION MODIFIED BY REUNIWARE (InvestDataSystems@Yahoo.Com / 2021) THIS VERSION CONTAINS HYPEROPT SETTINGS FROM E0V1E (cf. https://discord.gg/Ayvcvs6N ) """ class HyperOpt: @staticmethod def generate_roi_table(params: Dict) -> Dict[int, float]: roi_table = {} roi_table[0] = ( params["roi_p1"] + params["roi_p2"] + params["roi_p3"] + params["roi_p4"] + params["roi_p5"] + params["roi_p6"] ) roi_table[params["roi_t6"]] = ( params["roi_p1"] + params["roi_p2"] + params["roi_p3"] + params["roi_p4"] + params["roi_p5"] ) roi_table[params["roi_t6"] + params["roi_t5"]] = ( params["roi_p1"] + params["roi_p2"] + params["roi_p3"] + params["roi_p4"] ) roi_table[params["roi_t6"] + params["roi_t5"] + params["roi_t4"]] = ( params["roi_p1"] + params["roi_p2"] + params["roi_p3"] ) roi_table[ params["roi_t6"] + params["roi_t5"] + params["roi_t4"] + params["roi_t3"] ] = (params["roi_p1"] + params["roi_p2"]) roi_table[ params["roi_t6"] + params["roi_t5"] + params["roi_t4"] + params["roi_t3"] + params["roi_t2"] ] = params["roi_p1"] roi_table[ params["roi_t6"] + params["roi_t5"] + params["roi_t4"] + params["roi_t3"] + params["roi_t2"] + params["roi_t1"] ] = 0 return roi_table @staticmethod def roi_space() -> List[Dimension]: return [ Integer(1, 15, name="roi_t6"), Integer(1, 45, name="roi_t5"), Integer(1, 90, name="roi_t4"), Integer(45, 120, name="roi_t3"), Integer(45, 180, name="roi_t2"), Integer(90, 300, name="roi_t1"), Real(0.005, 0.10, name="roi_p6"), Real(0.005, 0.07, name="roi_p5"), Real(0.005, 0.05, name="roi_p4"), Real(0.005, 0.025, name="roi_p3"), Real(0.005, 0.01, name="roi_p2"), Real(0.003, 0.007, name="roi_p1"), ] buy_params = { "cci_buy": -175, "cci_candles_buy": 72, "rsi_buy": 90, "rsi_candles_buy": 36, "antipump_threshold": 0.133, "buy_btc_safe_1d": -0.311, "clucha_bbdelta_close": 0.04796, "clucha_bbdelta_tail": 0.93112, "clucha_close_bblower": 0.01645, "clucha_closedelta_close": 0.00931, "clucha_enabled": False, "clucha_rocr_1h": 0.41663, "cofi_adx": 8, "cofi_ema": 0.639, "cofi_enabled": False, "cofi_ewo_high": 5.6, "cofi_fastd": 40, "cofi_fastk": 13, "ewo_1_enabled": False, "ewo_1_rsi_14": 45, "ewo_1_rsi_4": 7, "ewo_candles_buy": 13, "ewo_candles_sell": 19, "ewo_high": 5.249, "ewo_high_offset": 1.04116, "ewo_low": -11.424, "ewo_low_enabled": True, "ewo_low_offset": 0.97463, "ewo_low_rsi_4": 35, "lambo1_ema_14_factor": 1.054, "lambo1_enabled": False, "lambo1_rsi_14_limit": 26, "lambo1_rsi_4_limit": 18, "lambo2_ema_14_factor": 0.981, "lambo2_enabled": True, "lambo2_rsi_14_limit": 39, "lambo2_rsi_4_limit": 44, "local_trend_bb_factor": 0.823, "local_trend_closedelta": 19.253, "local_trend_ema_diff": 0.125, "local_trend_enabled": True, "nfi32_cti_limit": -1.09639, "nfi32_enabled": True, "nfi32_rsi_14": 15, "nfi32_rsi_4": 49, "nfi32_sma_factor": 0.93391, } sell_params = { "pHSL": -0.134, "pPF_1": 0.02, "pPF_2": 0.047, "pSL_1": 0.02, "pSL_2": 0.046, "sell-fisher": 0.38414, "sell-bbmiddle-close": 1.07634, } minimal_roi = { "0": 0.10347601757573865, "3": 0.050495605759981035, "5": 0.03350898081823659, "61": 0.0275218557571848, "125": 0.011112591523667215, "292": 0.005185372158403069, "399": 0, } stoploss = -0.99 # use custom stoploss trailing_stop = False trailing_stop_positive = 0.3207 trailing_stop_positive_offset = 0.3849 trailing_only_offset_is_reached = False """ END HYPEROPT """ timeframe = "5m" use_exit_signal = True exit_profit_only = False ignore_roi_if_entry_signal = False use_custom_stoploss = True process_only_new_candles = True startup_candle_count = 200 order_types = { "entry": "market", "exit": "market", "emergency_exit": "market", "force_entry": "market", "force_exit": "market", "stoploss": "market", "stoploss_on_exchange": False, "stoploss_on_exchange_interval": 60, "stoploss_on_exchange_limit_ratio": 0.99, } """ STOPLOSS PARAMETERS """ pHSL = DecimalParameter( -0.200, -0.040, default=-0.08, decimals=3, space="sell", load=True ) pPF_1 = DecimalParameter( 0.008, 0.020, default=0.016, decimals=3, space="sell", load=True ) pSL_1 = DecimalParameter( 0.008, 0.020, default=0.011, decimals=3, space="sell", load=True ) pPF_2 = DecimalParameter( 0.040, 0.100, default=0.080, decimals=3, space="sell", load=True ) pSL_2 = DecimalParameter( 0.020, 0.070, default=0.040, decimals=3, space="sell", load=True ) """ BUY PARAMETERS """ cci_buy = IntParameter(-200, 200, default=-100, space="buy") cci_candles_buy = IntParameter(10, 80, default=60, space="buy") rsi_buy = IntParameter(10, 90, default=30, space="buy") rsi_candles_buy = IntParameter(10, 80, default=26, space="buy") clucha_bbdelta_close = DecimalParameter( 0.01, 0.05, default=buy_params["clucha_bbdelta_close"], decimals=5, space="buy", ) clucha_bbdelta_tail = DecimalParameter( 0.7, 1.2, default=buy_params["clucha_bbdelta_tail"], decimals=5, space="buy", ) clucha_close_bblower = DecimalParameter( 0.001, 0.05, default=buy_params["clucha_close_bblower"], decimals=5, space="buy", ) clucha_closedelta_close = DecimalParameter( 0.001, 0.05, default=buy_params["clucha_closedelta_close"], decimals=5, space="buy", ) clucha_rocr_1h = DecimalParameter( 0.1, 1.0, default=buy_params["clucha_rocr_1h"], decimals=5, space="buy", ) lambo1_ema_14_factor = DecimalParameter( 0.8, 1.2, decimals=3, default=buy_params["lambo1_ema_14_factor"], space="buy", ) lambo1_rsi_4_limit = IntParameter( 5, 60, default=buy_params["lambo1_rsi_4_limit"], space="buy", ) lambo1_rsi_14_limit = IntParameter( 5, 60, default=buy_params["lambo1_rsi_14_limit"], space="buy", ) lambo2_ema_14_factor = DecimalParameter( 0.8, 1.2, decimals=3, default=buy_params["lambo2_ema_14_factor"], space="buy", ) lambo2_rsi_4_limit = IntParameter( 5, 60, default=buy_params["lambo2_rsi_4_limit"], space="buy", ) lambo2_rsi_14_limit = IntParameter( 5, 60, default=buy_params["lambo2_rsi_14_limit"], space="buy", ) local_trend_ema_diff = DecimalParameter( 0, 0.2, default=buy_params["local_trend_ema_diff"], space="buy", ) local_trend_bb_factor = DecimalParameter( 0.8, 1.2, default=buy_params["local_trend_bb_factor"], space="buy", ) local_trend_closedelta = DecimalParameter( 5.0, 30.0, default=buy_params["local_trend_closedelta"], space="buy", ) ewo_candles_buy = IntParameter( 2, 30, default=buy_params["ewo_candles_buy"], space="buy", ) ewo_candles_sell = IntParameter( 2, 35, default=buy_params["ewo_candles_sell"], space="buy", ) ewo_low_offset = DecimalParameter( 0.7, 1.2, default=buy_params["ewo_low_offset"], decimals=5, space="buy", ) ewo_high_offset = DecimalParameter( 0.75, 1.5, default=buy_params["ewo_high_offset"], decimals=5, space="buy", ) ewo_high = DecimalParameter( 2.0, 15.0, default=buy_params["ewo_high"], space="buy", ) ewo_1_rsi_14 = IntParameter( 10, 100, default=buy_params["ewo_1_rsi_14"], space="buy", ) ewo_1_rsi_4 = IntParameter( 1, 50, default=buy_params["ewo_1_rsi_4"], space="buy", ) ewo_low_rsi_4 = IntParameter( 1, 50, default=buy_params["ewo_low_rsi_4"], space="buy", ) ewo_low = DecimalParameter( -20.0, -8.0, default=buy_params["ewo_low"], space="buy", ) cofi_ema = DecimalParameter( 0.6, 1.4, default=buy_params["cofi_ema"], space="buy", ) cofi_fastk = IntParameter( 1, 100, default=buy_params["cofi_fastk"], space="buy", ) cofi_fastd = IntParameter( 1, 100, default=buy_params["cofi_fastd"], space="buy", ) cofi_adx = IntParameter( 1, 100, default=buy_params["cofi_adx"], space="buy", ) cofi_ewo_high = DecimalParameter( 1.0, 15.0, default=buy_params["cofi_ewo_high"], space="buy", ) nfi32_rsi_4 = IntParameter( 1, 100, default=buy_params["nfi32_rsi_4"], space="buy", ) nfi32_rsi_14 = IntParameter( 1, 100, default=buy_params["nfi32_rsi_4"], space="buy", ) nfi32_sma_factor = DecimalParameter( 0.7, 1.2, default=buy_params["nfi32_sma_factor"], decimals=5, space="buy", ) nfi32_cti_limit = DecimalParameter( -1.2, 0, default=buy_params["nfi32_cti_limit"], decimals=5, space="buy", ) buy_btc_safe_1d = DecimalParameter( -0.5, -0.015, default=buy_params["buy_btc_safe_1d"], ) antipump_threshold = DecimalParameter( 0, 0.4, default=buy_params["antipump_threshold"], space="buy", ) ewo_1_enabled = BooleanParameter( default=buy_params["ewo_1_enabled"], space="buy", ) ewo_low_enabled = BooleanParameter( default=buy_params["ewo_low_enabled"], space="buy", ) cofi_enabled = BooleanParameter( default=buy_params["cofi_enabled"], space="buy", ) lambo1_enabled = BooleanParameter( default=buy_params["lambo1_enabled"], space="buy", ) lambo2_enabled = BooleanParameter( default=buy_params["lambo2_enabled"], space="buy", ) local_trend_enabled = BooleanParameter( default=buy_params["local_trend_enabled"], space="buy", ) nfi32_enabled = BooleanParameter( default=buy_params["nfi32_enabled"], space="buy", ) clucha_enabled = BooleanParameter( default=buy_params["clucha_enabled"], space="buy", ) def custom_stoploss( self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs, ) -> float: HSL = self.pHSL.value PF_1 = self.pPF_1.value SL_1 = self.pSL_1.value PF_2 = self.pPF_2.value SL_2 = self.pSL_2.value if current_profit > PF_2: sl_profit = SL_2 + (current_profit - PF_2) elif current_profit > PF_1: sl_profit = SL_1 + ((current_profit - PF_1) * (SL_2 - SL_1) / (PF_2 - PF_1)) else: sl_profit = HSL if sl_profit >= current_profit: return -0.99 return stoploss_from_open(sl_profit, current_profit) @informative("1h") def populate_1h_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: heikinashi = qtpylib.heikinashi(dataframe) dataframe["ha_close"] = heikinashi["close"] dataframe["rocr"] = ta.ROCR(dataframe["ha_close"], timeperiod=168) return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: heikinashi = qtpylib.heikinashi(dataframe) dataframe["ha_open"] = heikinashi["open"] dataframe["ha_close"] = heikinashi["close"] dataframe["ha_high"] = heikinashi["high"] dataframe["ha_low"] = heikinashi["low"] dataframe["ema_8"] = ta.EMA(dataframe, timeperiod=8) dataframe["ema_14"] = ta.EMA(dataframe, timeperiod=14) dataframe["ema_26"] = ta.EMA(dataframe, timeperiod=26) dataframe["sma_15"] = ta.SMA(dataframe, timeperiod=15) dataframe["rsi_4"] = ta.RSI(dataframe, timeperiod=4) dataframe["rsi_14"] = ta.RSI(dataframe, timeperiod=14) dataframe["rsi_20"] = ta.RSI(dataframe, timeperiod=20) dataframe["cti"] = pta.cti(dataframe["close"], length=20) stoch_fast = ta.STOCHF(dataframe, 5, 3, 0, 3, 0) dataframe["fastd"] = stoch_fast["fastd"] dataframe["fastk"] = stoch_fast["fastk"] dataframe["adx"] = ta.ADX(dataframe) mid, lower = bollinger_bands( ha_typical_price(dataframe), window_size=40, num_of_std=2 ) dataframe["lower"] = lower dataframe["mid"] = mid bollinger2 = qtpylib.bollinger_bands( qtpylib.typical_price(dataframe), window=20, stds=2 ) dataframe["bb_lowerband2"] = bollinger2["lower"] dataframe["bb_middleband2"] = bollinger2["mid"] dataframe["bb_upperband2"] = bollinger2["upper"] dataframe["closedelta"] = ( dataframe["close"] - dataframe["close"].shift() ).abs() dataframe["bbdelta"] = (mid - dataframe["lower"]).abs() dataframe["ha_closedelta"] = ( dataframe["ha_close"] - dataframe["ha_close"].shift() ).abs() dataframe["tail"] = (dataframe["ha_close"] - dataframe["ha_low"]).abs() dataframe["bb_lowerband"] = dataframe["lower"] dataframe["bb_middleband"] = dataframe["mid"] dataframe["ema_fast"] = ta.EMA(dataframe["ha_close"], timeperiod=3) dataframe["ema_slow"] = ta.EMA(dataframe["ha_close"], timeperiod=50) dataframe["rocr"] = ta.ROCR(dataframe["ha_close"], timeperiod=28) dataframe["EWO"] = EWO(dataframe, 50, 200) rsi = ta.RSI(dataframe) dataframe["rsi"] = rsi rsi = 0.1 * (rsi - 50) dataframe["fisher"] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) dataframe["zema_30"] = ftt.zema(dataframe, period=30) dataframe["zema_200"] = ftt.zema(dataframe, period=200) dataframe["pump_strength"] = ( dataframe["zema_30"] - dataframe["zema_200"] ) / dataframe["zema_30"] dataframe["perc"] = ( (dataframe["high"] - dataframe["low"]) / dataframe["low"] * 100 ) dataframe["avg3_perc"] = ta.EMA(dataframe["perc"], 3) dataframe["norm_perc"] = ( dataframe["perc"] - dataframe["perc"].rolling(50).min() ) / (dataframe["perc"].rolling(50).max() - dataframe["perc"].rolling(50).min()) return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] dataframe.loc[:, "buy_tag"] = "" dataframe[f"ma_buy_{self.ewo_candles_buy.value}"] = ta.EMA( dataframe, timeperiod=int(self.ewo_candles_buy.value) ) dataframe[f"ma_sell_{self.ewo_candles_sell.value}"] = ta.EMA( dataframe, timeperiod=int(self.ewo_candles_sell.value) ) dataframe[f"cci_buy_{self.cci_candles_buy.value}"] = ta.CCI( dataframe, timeperiod=self.cci_candles_buy.value ) dataframe[f"rsi_buy_{self.rsi_candles_buy.value}"] = ta.RSI( dataframe, timeperiod=self.rsi_candles_buy.value ) rsiCCI = dataframe[ f"rsi_buy_{self.rsi_candles_buy.value}" ] < self.rsi_buy.value & ( dataframe[f"cci_buy_{self.cci_candles_buy.value}"] < self.cci_buy.value ) dataframe.loc[rsiCCI, "buy_tag"] += "rsi+cci_" conditions.append(rsiCCI) lambo1 = ( bool(self.lambo1_enabled.value) & ( dataframe["close"] < (dataframe["ema_14"] * self.lambo1_ema_14_factor.value) ) & (dataframe["rsi_4"] < int(self.lambo1_rsi_4_limit.value)) & (dataframe["rsi_14"] < int(self.lambo1_rsi_14_limit.value)) ) dataframe.loc[lambo1, "buy_tag"] += "lambo1_" conditions.append(lambo1) lambo2 = ( bool(self.lambo2_enabled.value) & ( dataframe["close"] < (dataframe["ema_14"] * self.lambo2_ema_14_factor.value) ) & (dataframe["rsi_4"] < int(self.lambo2_rsi_4_limit.value)) & (dataframe["rsi_14"] < int(self.lambo2_rsi_14_limit.value)) ) dataframe.loc[lambo2, "buy_tag"] += "lambo2_" conditions.append(lambo2) local_uptrend = ( bool(self.local_trend_enabled.value) & (dataframe["ema_26"] > dataframe["ema_14"]) & ( dataframe["ema_26"] - dataframe["ema_14"] > dataframe["open"] * self.local_trend_ema_diff.value ) & ( dataframe["ema_26"].shift() - dataframe["ema_14"].shift() > dataframe["open"] / 100 ) & ( dataframe["close"] < dataframe["bb_lowerband2"] * self.local_trend_bb_factor.value ) & ( dataframe["closedelta"] > dataframe["close"] * self.local_trend_closedelta.value / 1000 ) ) dataframe.loc[local_uptrend, "buy_tag"] += "local_uptrend_" conditions.append(local_uptrend) nfi_32 = ( bool(self.nfi32_enabled.value) & (dataframe["rsi_20"] < dataframe["rsi_20"].shift(1)) & (dataframe["rsi_4"] < self.nfi32_rsi_4.value) & (dataframe["rsi_14"] > self.nfi32_rsi_14.value) & (dataframe["close"] < dataframe["sma_15"] * self.nfi32_sma_factor.value) & (dataframe["cti"] < self.nfi32_cti_limit.value) ) dataframe.loc[nfi_32, "buy_tag"] += "nfi_32_" conditions.append(nfi_32) ewo_1 = ( bool(self.ewo_1_enabled.value) & (dataframe["rsi_4"] < self.ewo_1_rsi_4.value) & ( dataframe["close"] < ( dataframe[f"ma_buy_{self.ewo_candles_buy.value}"] * self.ewo_low_offset.value ) ) & (dataframe["EWO"] > self.ewo_high.value) & (dataframe["rsi_14"] < self.ewo_1_rsi_14.value) & ( dataframe["close"] < ( dataframe[f"ma_sell_{self.ewo_candles_sell.value}"] * self.ewo_high_offset.value ) ) ) dataframe.loc[ewo_1, "buy_tag"] += "ewo1_" conditions.append(ewo_1) ewo_low = ( bool(self.ewo_low_enabled.value) & (dataframe["rsi_4"] < self.ewo_low_rsi_4.value) & ( dataframe["close"] < ( dataframe[f"ma_buy_{self.ewo_candles_buy.value}"] * self.ewo_low_offset.value ) ) & (dataframe["EWO"] < self.ewo_low.value) & ( dataframe["close"] < ( dataframe[f"ma_sell_{self.ewo_candles_sell.value}"] * self.ewo_high_offset.value ) ) ) dataframe.loc[ewo_low, "buy_tag"] += "ewo_low_" conditions.append(ewo_low) cofi = ( bool(self.cofi_enabled.value) & (dataframe["open"] < dataframe["ema_8"] * self.cofi_ema.value) & (qtpylib.crossed_above(dataframe["fastk"], dataframe["fastd"])) & (dataframe["fastk"] < self.cofi_fastk.value) & (dataframe["fastd"] < self.cofi_fastd.value) & (dataframe["adx"] > self.cofi_adx.value) & (dataframe["EWO"] > self.cofi_ewo_high.value) ) dataframe.loc[cofi, "buy_tag"] += "cofi_" conditions.append(cofi) clucHA = ( bool(self.clucha_enabled.value) & (dataframe["rocr_1h"].gt(self.clucha_rocr_1h.value)) & ( ( (dataframe["lower"].shift().gt(0)) & ( dataframe["bbdelta"].gt( dataframe["ha_close"] * self.clucha_bbdelta_close.value ) ) & ( dataframe["ha_closedelta"].gt( dataframe["ha_close"] * self.clucha_closedelta_close.value ) ) & ( dataframe["tail"].lt( dataframe["bbdelta"] * self.clucha_bbdelta_tail.value ) ) & (dataframe["ha_close"].lt(dataframe["lower"].shift())) & (dataframe["ha_close"].le(dataframe["ha_close"].shift())) ) | ( (dataframe["ha_close"] < dataframe["ema_slow"]) & ( dataframe["ha_close"] < self.clucha_close_bblower.value * dataframe["bb_lowerband"] ) ) ) ) dataframe.loc[clucHA, "buy_tag"] += "clucHA_" conditions.append(clucHA) dataframe.loc[ reduce(lambda x, y: x | y, conditions), "buy", ] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: params = self.sell_params dataframe.loc[ (dataframe["fisher"] > params["sell-fisher"]) & (dataframe["ha_high"].le(dataframe["ha_high"].shift(1))) & (dataframe["ha_high"].shift(1).le(dataframe["ha_high"].shift(2))) & (dataframe["ha_close"].le(dataframe["ha_close"].shift(1))) & (dataframe["ema_fast"] > dataframe["ha_close"]) & ( (dataframe["ha_close"] * params["sell-bbmiddle-close"]) > dataframe["bb_middleband"] ) & (dataframe["volume"] > 0), "sell", ] = 1 return dataframe def confirm_trade_exit( self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, current_time: datetime, **kwargs, ) -> bool: trade.exit_reason = exit_reason + "_" + trade.buy_tag return True def pct_change(a, b): return (b - a) / a def EWO(dataframe, ema_length=5, ema2_length=35): df = dataframe.copy() ema1 = ta.EMA(df, timeperiod=ema_length) ema2 = ta.EMA(df, timeperiod=ema2_length) emadif = (ema1 - ema2) / df["low"] * 100 return emadif class RED_DYNAMIC_TB(red): process_only_new_candles = True custom_info_trail_buy: Dict = dict() trailing_buy_order_enabled = True trailing_expire_seconds = 1800 trailing_buy_uptrend_enabled = True trailing_expire_seconds_uptrend = 90 min_uptrend_trailing_profit = 0.02 trailing_buy_max_stop = 0.01 # stop trailing buy if current_price > starting_price * (1+trailing_buy_max_stop) trailing_buy_max_buy = 0.002 # 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): if pair not in self.custom_info_trail_buy: self.custom_info_trail_buy[pair] = dict() if reinit or "trailing_buy" not 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 = datetime.now(timezone.utc) 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): current_trailing_profit_ratio = self.current_trailing_profit_ratio( pair, current_price ) last_candle = dataframe.iloc[-1] adapt = abs((last_candle["norm_perc"])) default_offset = 0.003 * (1 + adapt) # NOTE: default_offset 0.003 <--> 0.006 trailing_buy = self.trailing_buy(pair) if not trailing_buy["trailing_buy_order_started"]: return default_offset 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): return "forcebuy" else: 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) ) ): return "forcebuy" if current_trailing_profit_ratio < 0: 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 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 ): 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": 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: 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"] ): 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) ): 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) ): 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: 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 is 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!!!" ) 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"] is 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"] return dataframe