from typing import Dict, List from datetime import datetime from functools import reduce from skopt.space import Dimension, Integer import numpy as np from freqtrade.persistence import Trade from freqtrade.strategy import IntParameter, DecimalParameter, CategoricalParameter, informative from freqtrade.strategy.interface import IStrategy from pandas import DataFrame, Series import talib.abstract as ta import numpy # noqa 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 zema(dataframe, period, field='close'): """ Source: https://github.com/freqtrade/technical/blob/master/technical/indicators/overlap_studies.py#L79 Modified slightly to use ta.EMA instead of technical ema """ df = dataframe.copy() df['ema1'] = ta.EMA(df[field], timeperiod=period) df['ema2'] = ta.EMA(df['ema1'], timeperiod=period) df['d'] = df['ema1'] - df['ema2'] df['zema'] = df['ema1'] + df['d'] return df['zema'] def same_length(bigger, shorter): return np.concatenate((np.full((bigger.shape[0] - shorter.shape[0]), np.nan), shorter)) def mastreak(dataframe: DataFrame, period: int = 4, field='close') -> Series: """ MA Streak Port of: https://www.tradingview.com/script/Yq1z7cIv-MA-Streak-Can-Show-When-a-Run-Is-Getting-Long-in-the-Tooth/ """ df = dataframe.copy() avgval = zema(df, period, field) arr = np.diff(avgval) pos = np.clip(arr, 0, 1).astype(bool).cumsum() neg = np.clip(arr, -1, 0).astype(bool).cumsum() streak = np.where(arr >= 0, pos - np.maximum.accumulate(np.where(arr <= 0, pos, 0)), -neg + np.maximum.accumulate(np.where(arr >= 0, neg, 0))) res = same_length(df['close'], streak) return res def linear_growth(start: float, end: float, start_time: int, end_time: int, trade_time: int) -> float: """ Simple linear growth function. Grows from start to end after end_time minutes (starts after start_time minutes) """ time = max(0, trade_time - start_time) rate = (end - start) / (end_time - start_time) return min(end, start + (rate * time)) def pcc(dataframe: DataFrame, period: int = 20, mult: int = 2): """ Percent Change Channel PCC is like KC unless it uses percentage changes in price to set channel distance. https://www.tradingview.com/script/6wwAWXA1-MA-Streak-Change-Channel/ """ df = dataframe.copy() df['previous_close'] = df['close'].shift() df['close_change'] = (df['close'] - df['previous_close']) / df['previous_close'] * 100 df['high_change'] = (df['high'] - df['close']) / df['close'] * 100 df['low_change'] = (df['low'] - df['close']) / df['close'] * 100 df['delta'] = df['high_change'] - df['low_change'] mid = zema(df, period, 'close_change') rangema = zema(df, period, 'delta') upper = mid + rangema * mult lower = mid - rangema * mult return upper, rangema, lower 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 linear_decay(start: float, end: float, start_time: int, end_time: int, trade_time: int) -> float: """ Simple linear decay function. Decays from start to end after end_time minutes (starts after start_time minutes) """ time = max(0, trade_time - start_time) rate = (start - end) / (end_time - start_time) return max(end, start - (rate * time)) class BinHV27_short_10(IStrategy): """ strategy sponsored by user BinH from slack """ minimal_roi = { "0": 100 } stoploss = -0.1 timeframe = '5m' process_only_new_candles = False startup_candle_count = 240 use_custom_stoploss = False custom_trade_info = {} can_short = True 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 } buy_optimize = False buy_adx1 = IntParameter(low=10, high=100, default=25, space='buy', optimize=buy_optimize) buy_emarsi1 = IntParameter(low=10, high=100, default=20, space='buy', optimize=buy_optimize) buy_adx2 = IntParameter(low=20, high=100, default=30, space='buy', optimize=buy_optimize) buy_emarsi2 = IntParameter(low=20, high=100, default=20, space='buy', optimize=buy_optimize) buy_adx3 = IntParameter(low=10, high=100, default=35, space='buy', optimize=buy_optimize) buy_emarsi3 = IntParameter(low=10, high=100, default=20, space='buy', optimize=buy_optimize) buy_adx4 = IntParameter(low=20, high=100, default=30, space='buy', optimize=buy_optimize) buy_emarsi4 = IntParameter(low=20, high=100, default=25, space='buy', optimize=buy_optimize) leverage_optimize = True leverage_num = IntParameter(low=1, high=10, default=1, space='buy', optimize=leverage_optimize) ce_op = True csell_pullback_amount = DecimalParameter(0.005, 0.15, default=0.01, space='sell', load=True, optimize=ce_op) csell_roi_type = CategoricalParameter(['static', 'decay', 'step'], default='step', space='sell', load=True, optimize=ce_op) csell_roi_start = DecimalParameter(0.01, 0.15, default=0.01, space='sell', load=True, optimize=ce_op) csell_roi_end = DecimalParameter(0.0, 0.01, default=0, space='sell', load=True, optimize=ce_op) csell_roi_time = IntParameter(720, 1440, default=720, space='sell', load=True, optimize=ce_op) csell_trend_type = CategoricalParameter(['rmi', 'ssl', 'candle', 'any', 'none'], default='any', space='sell', load=True, optimize=ce_op) csell_pullback = CategoricalParameter([True, False], default=True, space='sell', load=True, optimize=ce_op) csell_pullback_respect_roi = CategoricalParameter([True, False], default=False, space='sell', load=True, optimize=ce_op) csell_endtrend_respect_roi = CategoricalParameter([True, False], default=False, space='sell', load=True, optimize=ce_op) class HyperOpt: @staticmethod def generate_roi_table(params: Dict) -> Dict[int, float]: roi_table = {params['roi_t1']: 0} return roi_table @staticmethod def roi_space() -> List[Dimension]: roi_min_time = 10 roi_max_time = 720 roi_limits = { 'roi_t1_min': int(roi_min_time), 'roi_t1_max': int(roi_max_time) } return [ Integer(roi_limits['roi_t1_min'], roi_limits['roi_t1_max'], name='roi_t1') ] @informative('1d', 'BTC/USDT') def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['ema90'] = ta.EMA(dataframe, timeperiod=90) return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if not metadata['pair'] in self.custom_trade_info: self.custom_trade_info[metadata['pair']] = {} if 'had-trend' not in self.custom_trade_info[metadata["pair"]]: self.custom_trade_info[metadata['pair']]['had-trend'] = False dataframe['rsi'] = numpy.nan_to_num(ta.RSI(dataframe, timeperiod=5)) rsiframe = DataFrame(dataframe['rsi']).rename(columns={'rsi': 'close'}) dataframe['emarsi'] = numpy.nan_to_num(ta.EMA(rsiframe, timeperiod=5)) dataframe['adx'] = numpy.nan_to_num(ta.ADX(dataframe)) dataframe['minusdi'] = numpy.nan_to_num(ta.MINUS_DI(dataframe)) minusdiframe = DataFrame(dataframe['minusdi']).rename(columns={'minusdi': 'close'}) dataframe['minusdiema'] = numpy.nan_to_num(ta.EMA(minusdiframe, timeperiod=25)) dataframe['plusdi'] = numpy.nan_to_num(ta.PLUS_DI(dataframe)) plusdiframe = DataFrame(dataframe['plusdi']).rename(columns={'plusdi': 'close'}) dataframe['plusdiema'] = numpy.nan_to_num(ta.EMA(plusdiframe, timeperiod=5)) dataframe['lowsma'] = numpy.nan_to_num(ta.EMA(dataframe, timeperiod=60)) dataframe['highsma'] = numpy.nan_to_num(ta.EMA(dataframe, timeperiod=120)) dataframe['fastsma'] = numpy.nan_to_num(ta.SMA(dataframe, timeperiod=120)) dataframe['slowsma'] = numpy.nan_to_num(ta.SMA(dataframe, timeperiod=240)) dataframe['bigup'] = dataframe['fastsma'].gt(dataframe['slowsma']) & ( (dataframe['fastsma'] - dataframe['slowsma']) > dataframe['close'] / 300) dataframe['bigdown'] = ~dataframe['bigup'] dataframe['trend'] = dataframe['fastsma'] - dataframe['slowsma'] dataframe['preparechangetrend'] = dataframe['trend'].gt(dataframe['trend'].shift()) dataframe['preparechangetrendconfirm'] = dataframe['preparechangetrend'] & dataframe['trend'].shift().gt( dataframe['trend'].shift(2)) dataframe['continueup'] = dataframe['slowsma'].gt(dataframe['slowsma'].shift()) & dataframe[ 'slowsma'].shift().gt(dataframe['slowsma'].shift(2)) dataframe['delta'] = dataframe['fastsma'] - dataframe['fastsma'].shift() dataframe['slowingdown'] = dataframe['delta'].lt(dataframe['delta'].shift()) dataframe['rmi'] = RMI(dataframe, length=24, mom=5) dataframe['rmi-up'] = np.where(dataframe['rmi'] >= dataframe['rmi'].shift(), 1, 0) dataframe['rmi-down-trend'] = np.where(dataframe['rmi-up'].rolling(5).sum() <= 2, 1, 0) ssldown, sslup = SSLChannels_ATR(dataframe, length=21) dataframe['ssl-dir'] = np.where(sslup > ssldown, 'up', 'down') dataframe['candle-up'] = np.where(dataframe['close'] >= dataframe['open'], 1, 0) dataframe['candle-down-trend'] = np.where(dataframe['candle-up'].rolling(5).sum() <= 2, 1, 0) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] dataframe.loc[:, 'enter_tag'] = '' buy_1 = ( dataframe['btc_usdt_close_1d'].lt(dataframe['btc_usdt_ema90_1d']) & dataframe['slowsma'].gt(0) & dataframe['close'].lt(dataframe['highsma']) & dataframe['close'].lt(dataframe['lowsma']) & dataframe['minusdi'].gt(dataframe['minusdiema']) & dataframe['rsi'].ge(dataframe['rsi'].shift()) & ~dataframe['preparechangetrend'] & ~dataframe['continueup'] & dataframe['adx'].gt(self.buy_adx1.value) & dataframe['bigdown'] & dataframe['emarsi'].le(self.buy_emarsi1.value) ) buy_2 = ( dataframe['btc_usdt_close_1d'].lt(dataframe['btc_usdt_ema90_1d']) & dataframe['slowsma'].gt(0) & dataframe['close'].lt(dataframe['highsma']) & dataframe['close'].lt(dataframe['lowsma']) & dataframe['minusdi'].gt(dataframe['minusdiema']) & dataframe['rsi'].ge(dataframe['rsi'].shift()) & ~dataframe['preparechangetrend'] & dataframe['continueup'] & dataframe['adx'].gt(self.buy_adx2.value) & dataframe['bigdown'] & dataframe['emarsi'].le(self.buy_emarsi2.value) ) buy_3 = ( dataframe['btc_usdt_close_1d'].lt(dataframe['btc_usdt_ema90_1d']) & dataframe['slowsma'].gt(0) & dataframe['close'].lt(dataframe['highsma']) & dataframe['close'].lt(dataframe['lowsma']) & dataframe['minusdi'].gt(dataframe['minusdiema']) & dataframe['rsi'].ge(dataframe['rsi'].shift()) & ~dataframe['continueup'] & dataframe['adx'].gt(self.buy_adx3.value) & dataframe['bigup'] & dataframe['emarsi'].le(self.buy_emarsi3.value) ) buy_4 = ( dataframe['btc_usdt_close_1d'].lt(dataframe['btc_usdt_ema90_1d']) & dataframe['slowsma'].gt(0) & dataframe['close'].lt(dataframe['highsma']) & dataframe['close'].lt(dataframe['lowsma']) & dataframe['minusdi'].gt(dataframe['minusdiema']) & dataframe['rsi'].ge(dataframe['rsi'].shift()) & dataframe['continueup'] & dataframe['adx'].gt(self.buy_adx4.value) & dataframe['bigup'] & dataframe['emarsi'].le(self.buy_emarsi4.value) ) conditions.append(buy_1) dataframe.loc[buy_1, 'enter_tag'] += 'buy_1' conditions.append(buy_2) dataframe.loc[buy_2, 'enter_tag'] += 'buy_2' conditions.append(buy_3) dataframe.loc[buy_3, 'enter_tag'] += 'buy_3' conditions.append(buy_4) dataframe.loc[buy_4, 'enter_tag'] += 'buy_4' if conditions: dataframe.loc[ reduce(lambda x, y: x | y, conditions), 'enter_short'] = 1 dataframe.loc[(), ['enter_long', 'enter_tag']] = (0, 'long_in') return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[(), ['exit_short', 'exit_tag']] = (0, 'short_out') dataframe.loc[(), ['exit_long', 'exit_tag']] = (0, 'long_out') return dataframe def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, side: str, **kwargs) -> float: return self.leverage_num.value def custom_exit(self, pair: str, trade: Trade, current_time: 'datetime', current_rate: float, current_profit: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) last_candle = dataframe.iloc[-1].squeeze() trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60) max_profit = max(0, trade.calc_profit_ratio(trade.max_rate)) pullback_value = max(0, (max_profit - self.csell_pullback_amount.value)) in_trend = False if self.csell_roi_type.value == 'static': min_roi = self.csell_roi_start.value elif self.csell_roi_type.value == 'decay': min_roi = linear_decay(self.csell_roi_start.value, self.csell_roi_end.value, 0, self.csell_roi_time.value, trade_dur) elif self.csell_roi_type.value == 'step': if trade_dur < self.csell_roi_time.value: min_roi = self.csell_roi_start.value else: min_roi = self.csell_roi_end.value if self.csell_trend_type.value == 'rmi' or self.csell_trend_type.value == 'any': if last_candle['rmi-down-trend'] == 1: in_trend = True if self.csell_trend_type.value == 'ssl' or self.csell_trend_type.value == 'any': if last_candle['ssl-dir'] == 'down': in_trend = True if self.csell_trend_type.value == 'candle' or self.csell_trend_type.value == 'any': if last_candle['candle-down-trend'] == 1: in_trend = True if in_trend and current_profit > 0: self.custom_trade_info[trade.pair]['had-trend'] = True if self.csell_pullback.value and (current_profit <= pullback_value): if self.csell_pullback_respect_roi.value and current_profit > min_roi: return 'intrend_pullback_roi' elif not self.csell_pullback_respect_roi.value: if current_profit > min_roi: return 'intrend_pullback_roi' else: return 'intrend_pullback_noroi' return None elif not in_trend: if self.custom_trade_info[trade.pair]['had-trend']: if current_profit > min_roi: self.custom_trade_info[trade.pair]['had-trend'] = False return 'trend_roi' elif not self.csell_endtrend_respect_roi.value: self.custom_trade_info[trade.pair]['had-trend'] = False return 'trend_noroi' elif current_profit > min_roi: return 'notrend_roi' else: return None