# --- Do not remove these libs --- from freqtrade.strategy.interface import IStrategy from pandas import DataFrame import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib # -------------------------------- import pandas as pd # noqa pd.options.mode.chained_assignment = None # default='warn' from technical.util import resample_to_interval, resampled_merge from functools import reduce from datetime import datetime, timedelta # ObeliskRSI v6.1 - 2021-03-06 # # by Obelisk # https://twitter.com/brookmiles # # Run at your own risk. # I don't know what I'm doing. # Backtests quite a bit better than it actually works. # Let me know if you manage to improve it! # # Buys when RSI crosses the low threshold # Doesn't wait for reversal, trying to anticipate the "bottom" of the dip. # # Sells on ROI (often), Stoploss (sometimes), or RSI high (rarely) # # Custom Stoploss starts wide and then narrows over time # Combined with ROI, every trade will be forced to close in custom_stop_ramp_minutes, win or lose # # Bull/Bear trends # # Uses two different sets of entry/exit thresholds, which are used depending on the longer RSI trend # Enter earlier and leave later when bullish; enter later and leave earlier when bearish # # Strengths # # Choppy markets - requires strong enough dips to trigger a entry, then we want a bounce back up # # Weaknesses # # Steady bull runs - no dips to entry, missed opportunity # Sudden ongoing bear runs - if the market keeps dipping and dropping repeatedly that's bad, # so use protections, eg. # # "protections": [ # { # "method": "StoplossGuard", # "lookback_period": 720, # "trade_limit": 2, # "stop_duration": 720, # "only_per_pair": true # }, # ], # # Works best on high volume pairs with decent volatility eg. # # "SXP/USD", # "MATIC/USD", # "SUSHI/USD", # "CHZ/USD", # # and not ETH/USD or BTC/USD def easeInCubic(t): return t * t * t def clamp(num, min_value, max_value): return max(min(num, max_value), min_value) def clamp01(num): return clamp(num, 0, 1) class ObeliskRSI_v6_1(IStrategy): INTERFACE_VERSION = 3 # Optimal timeframe for the strategy timeframe = '5m' startup_candle_count = 240 process_only_new_candles = True # ROI table: minimal_roi = {'0': 0.15, '35': 0.04, '65': 0.01, '115': 0} # Buy hyperspace params: entry_params = {'bear-entry-rsi-value': 21, 'bull-entry-rsi-value': 35} # Sell hyperspace params: exit_params = {'bear-exit-rsi-value': 55, 'bull-exit-rsi-value': 69} # Stoploss: stoploss = -0.3 use_custom_stoploss = True custom_stop_ramp_minutes = 110 def get_ticker_indicator(self): return int(self.timeframe[:-1]) def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # longer RSI used for determining trend resample_rsi_indicator = self.get_ticker_indicator() * 12 resample_rsi_key = 'resample_{}_rsi'.format(resample_rsi_indicator) dataframe_long = resample_to_interval(dataframe, resample_rsi_indicator) dataframe_long['rsi'] = ta.RSI(dataframe_long, timeperiod=14) dataframe = resampled_merge(dataframe, dataframe_long) dataframe[resample_rsi_key].fillna(method='ffill', inplace=True) # bull used to select between two different sets of entry/exit threshold values # based on long RSI dataframe['bull'] = dataframe[resample_rsi_key].gt(60).astype('int') # normal rsi acts mainly as the entry trigger # used for exit as well, but more likely to ROI or stop out dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) return dataframe # entry low def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: params = self.entry_params conditions = [] conditions.append(dataframe['volume'] > 0) conditions.append((dataframe['bull'] > 0) & qtpylib.crossed_below(dataframe['rsi'], params['bull-entry-rsi-value']) | ~(dataframe['bull'] > 0) & qtpylib.crossed_below(dataframe['rsi'], params['bear-entry-rsi-value'])) dataframe.loc[reduce(lambda x, y: x & y, conditions), 'enter_long'] = 1 return dataframe # exit high def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: params = self.exit_params conditions = [] conditions.append(dataframe['volume'] > 0) conditions.append((dataframe['bull'] > 0) & (dataframe['rsi'] > params['bull-exit-rsi-value']) | ~(dataframe['bull'] > 0) & (dataframe['rsi'] > params['bear-exit-rsi-value'])) dataframe.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1 return dataframe # Custom stoploss starts at the basic stoploss, and ramps towards zero in a curve, # narrowing the trailing stoploss until forcing the trade to stop out after custom_stop_ramp_minutes def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: since_open = current_time - trade.open_date sl_pct = 1 - easeInCubic(clamp01(since_open / timedelta(minutes=self.custom_stop_ramp_minutes))) sl_ramp = abs(self.stoploss) * sl_pct return sl_ramp + 0.001 # we can't go all the way to zero