# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # flake8: noqa: F401 # isort: skip_file # --- Do not remove these libs --- import logging import numpy as np import pandas as pd from pandas import DataFrame from datetime import datetime, timedelta from typing import Optional, Union, Tuple from freqtrade.exchange import timeframe_to_prev_date from freqtrade.persistence import Trade from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, stoploss_from_open, IntParameter, IStrategy, merge_informative_pair, informative, stoploss_from_absolute) # -------------------------------- # Add your lib to import here import talib.abstract as ta import pandas_ta as pta from technical import qtpylib class FractalAtr2(IStrategy): # Strategy interface version - allow new iterations of the strategy interface. # Check the documentation or the Sample strategy to get the latest version. INTERFACE_VERSION = 3 # Optimal timeframe for the strategy. timeframe = '1m' # Can this strategy go short? can_short: bool = False # Minimal ROI designed for the strategy. # This attribute will be overridden if the config file contains "minimal_roi". minimal_roi = { "0": 0.5 } # Optimal stoploss designed for the strategy. # This attribute will be overridden if the config file contains "stoploss". stoploss = -1 position_adjustment_enable = True use_custom_stoploss = True # Trailing stoploss trailing_stop = False # Run "populate_indicators()" only for new candle. process_only_new_candles = True # These values can be overridden in the config. use_exit_signal = True exit_profit_only = False ignore_roi_if_entry_signal = False # Number of candles the strategy requires before producing valid signals startup_candle_count: int = 30 # Strategy parameters buy_rsi = IntParameter(10, 40, default=30, space="buy") sell_rsi = IntParameter(60, 90, default=70, space="sell") # trailing stoploss hyperopt parameters # hard stoploss profit pHSL = DecimalParameter(-0.200, -0.040, default=-0.99, decimals=3, space='sell', optimize=False, load=True) # profit threshold 1, trigger point, SL_1 is used pPF_1 = DecimalParameter(0.008, 0.020, default=0.022, decimals=3, space='sell', optimize=True, load=True) pSL_1 = DecimalParameter(0.008, 0.020, default=0.021, decimals=3, space='sell', optimize=True, load=True) # profit threshold 2, SL_2 is used pPF_2 = DecimalParameter(0.040, 0.100, default=0.080, decimals=3, space='sell', optimize=True, load=True) pSL_2 = DecimalParameter(0.020, 0.070, default=0.040, decimals=3, space='sell', optimize=True, load=True) # Optional order type mapping. order_types = { 'entry': 'limit', 'exit': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False } # Optional order time in force. order_time_in_force = { 'entry': 'GTC', 'exit': 'GTC' } locked_pairs = {} max_profit_pairs = {} def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: float, max_stake: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) if True | (len(dataframe) < 1): return None last_candle = dataframe.iloc[-1].squeeze() filled_buys = trade.select_filled_orders('buy') count_of_buys = len(filled_buys) condition = (last_candle['enter_long'] == 1) # & (last_candle['close'] <= last_candle['close_1h']) # self.protection_nb_buy_lost.value if (0 < count_of_buys <= 2) & (current_profit < - 0.1) & (condition): try: stake_amount = self.config['stake_amount'] print("Adjust " + trade.pair + " " + str(current_time) + " " + str(current_profit) + " " + str(count_of_buys) + " " + str(stake_amount)) return stake_amount except Exception as exception: print(exception) return None return None def calculateScore(self, last_candle): score = 1 #- (last_candle['percent10'] / 0.01 return score @informative('1d') def populate_indicators_1d(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['rsi'] = ta.RSI(dataframe) dataframe['atr'] = ta.ATR(dataframe) dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5) # Bollinger Bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] dataframe["bb_percent"] = ( (dataframe["close"] - dataframe["bb_lowerband"]) / (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) ) dataframe["bb_ecart"] = ((dataframe["bb_upperband"] - dataframe["bb_lowerband"])) dataframe["bb_width"] = ( (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["bb_middleband"] ) return dataframe @informative('1h') def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: self.will_frac(dataframe) dataframe['bear'] = dataframe['high'].where(dataframe['fractal_sup'] == True, 0) dataframe['bear'] = dataframe['bear'].replace(to_replace=0, method="ffill") dataframe['bull'] = dataframe['low'].where(dataframe['fractal_inf'] == True, 0) dataframe['bull'] = dataframe['bull'].replace(to_replace=0, method="ffill") dataframe['max72'] = ta.MAX(dataframe['close'], timeperiod=72) dataframe['min72'] = ta.MIN(dataframe['close'], timeperiod=72) dataframe['mid72'] = (dataframe['min72'] + (dataframe['max72'] - dataframe['min72']) / 2) dataframe['width_72'] = (dataframe['max72'] - dataframe['min72']) / dataframe['max72'] dataframe['percent'] = dataframe['close'].pct_change() dataframe['percent5'] = dataframe['close'].pct_change(5) return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: self.will_frac(dataframe) dataframe['rsi'] = ta.RSI(dataframe) dataframe['atr'] = ta.ATR(dataframe) dataframe['bear'] = dataframe['high'].where(dataframe['fractal_sup'] == True, 0) dataframe['bear'] = dataframe['bear'].replace(to_replace=0, method="ffill") dataframe['bull'] = dataframe['low'].where(dataframe['fractal_inf'] == True, 0) dataframe['bull'] = dataframe['bull'].replace(to_replace=0, method="ffill") dataframe["percent"] = dataframe["close"].pct_change() dataframe["percent3"] = dataframe["close"].pct_change(3) dataframe["percent10"] = dataframe["close"].pct_change(10) dataframe["diff_close1"] = dataframe["close"] - dataframe["close"].shift(1) dataframe["diff_close3"] = dataframe["close"] - dataframe["close"].shift(3) dataframe["diff_close5"] = dataframe["close"] - dataframe["close"].shift(5) dataframe["diff_close10"] = dataframe["close"] - dataframe["close"].shift(10) dataframe['min50'] = ta.MIN(dataframe['close'], timeperiod=50) dataframe['max50'] = ta.MAX(dataframe['close'], timeperiod=50) dataframe['min200'] = ta.MIN(dataframe['close'], timeperiod=200) dataframe['min200_3'] = dataframe['min200'].rolling(3).mean() dataframe['max200'] = ta.MAX(dataframe['close'], timeperiod=200) dataframe['atr_relatif'] = ta.ATR(dataframe) / dataframe["min200"] dataframe["close50"] = dataframe["close"].rolling(50).mean() dataframe["close3"] = dataframe["close"].rolling(3).mean() dataframe['rsi_mean'] = dataframe['rsi'].rolling(3).mean() dataframe['atr_relatif_mean'] = dataframe['atr_relatif'].rolling(3).mean() dataframe['width_200'] = (dataframe['max200'] - dataframe['min200']) / dataframe['max200'] dataframe['width_close'] = (dataframe['close'] - dataframe['min200']) / dataframe['close'] dataframe['200_close'] = dataframe['width_close'] / dataframe['width_200'] * 100 dataframe['72h_close'] = (dataframe['close'] - dataframe['min72_1h']) / dataframe['min72_1h'] * 100 # Bollinger Bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] dataframe["bb_percent"] = ( (dataframe["close"] - dataframe["bb_lowerband"]) / (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) ) dataframe["bb_ecart"] = ((dataframe["bb_upperband"] - dataframe["bb_lowerband"])) dataframe["bb_width"] = ( (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["bb_middleband"] ) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (qtpylib.crossed_above(dataframe['close'], dataframe['bull']) | qtpylib.crossed_above(dataframe['close'].shift(1), dataframe['bull'].shift(1)) | qtpylib.crossed_above(dataframe['close'].shift(2), dataframe['bull'].shift(2)) | (dataframe['low'] <= dataframe['min200']) | (dataframe['low'] <= dataframe['bb_lowerband_1d']) ) & # ((dataframe['rsi'] < 60) | (dataframe['atr_relatif'] > 0.008)) & # ((dataframe['200_close'] < 25) | (dataframe['width_200'] < 0.10)) & (dataframe['open'] < dataframe['bb_upperband_1d']) & (dataframe['bb_width'] >= 0.015) & (dataframe['width_close'] <= 0.015) & (dataframe['open'] < dataframe['close3']) & (dataframe['open'] < dataframe['close50']) & (dataframe['fractal_inf_1h'] == 1) & #((dataframe['fractal_inf_1h'] == 1) | (dataframe['low'] <= dataframe['bb_lowerband_1d'])) & # (dataframe['percent10'] < -0.005) & (dataframe['volume'] > 0) # Make sure Volume is not 0 ), ['buy', 'enter_tag']] = (1, 'buy_fractal') # dataframe.loc[ # ( # # ((dataframe['200_close'] < 25) | (dataframe['width_200'] < 0.10)) & # # (dataframe['close3'] <= dataframe['width_200']) & # (dataframe['min200'].shift(5) == dataframe['min200']) & # (dataframe['min72_1h'] > dataframe['min200']) & # (dataframe['width_200'] > 0.035) & # (dataframe['width_close'] == 0) & # (dataframe['volume'] > 0) # Make sure Volume is not 0 # # ), ['buy', 'enter_tag']] = (1, 'buy_min72') return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( ((dataframe['200_close'].shift(3) > 95) | (dataframe['200_close'].shift(2) > 95) | (dataframe['200_close'].shift(1) > 95) | (dataframe['200_close'] > 95) | (dataframe['percent'] < -0.005) # | (dataframe['high'] >= dataframe['max50']) # | ((dataframe['percent3'] < 0) & (dataframe['close3'].shift(1) >= dataframe['close3'])) ) & (dataframe['percent'] < 0) & (dataframe['volume'] > 0) # Make sure Volume is not 0 ), 'exit_long'] = 1 return dataframe def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, entry_tag: Optional[str], **kwargs) -> bool: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() last_candle_12 = dataframe.iloc[-13].squeeze() allow_to_buy = True #(not self.stop_all) #& (not self.all_down) if pair in self.locked_pairs: trade = self.locked_pairs[pair] if (last_candle['close3'] >= last_candle['mid72_1h']): print(pair + ' is locked ' + str(current_time) + " rate=" + str(rate) + " locked_rate=" + str( trade.open_rate) + ' close3=' + str(last_candle['close3']) + ' mid72=' + str(last_candle['mid72_1h']) + str(trade)) allow_to_buy = False # else: # print(pair + ' unlocked ' + str(current_time) + " rate=" + str(rate) + " locked_rate=" + str( # self.locked_pairs[pair])) # del self.locked_pairs[pair] if allow_to_buy: print('Buy ' + entry_tag + ' ' + str(current_time) + ' ' + pair + " dispo=" + str( round(self.wallets.get_available_stake_amount())) + " score=" + str(self.calculateScore(last_candle))) return allow_to_buy 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, **kwargs, ) -> bool: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() days = (current_time - trade.open_date_utc).days hours = (current_time - trade.open_date_utc).seconds / 3600 minutes = (current_time - trade.open_date_utc).seconds / 60 allow_to_sell = False #(minutes > 30) profit = trade.calc_profit(rate, amount) if not (pair in self.max_profit_pairs): self.max_profit_pairs[pair] = profit self.max_profit_pairs[pair] = max(self.max_profit_pairs[pair], profit) if (self.max_profit_pairs[pair] > 0.5): allow_decrease = 0.2 * self.max_profit_pairs[pair] print("Max profit=" + str(self.max_profit_pairs[pair]) + ' ' + str(profit) + ' ' + str(allow_decrease)) if self.max_profit_pairs[pair] - profit < allow_decrease: allow_to_sell = False else: allow_to_sell = True # if last_candle['percent'] <= if (profit < - 1.5): print("Stop loss profit=" + str(self.max_profit_pairs[pair]) + ' ' + str(profit)) allow_to_sell = True string = "" buy_signal = self.getTradeCandle(dataframe, trade) if not buy_signal.empty: buy_signal_candle = buy_signal.iloc[-1] string = str(buy_signal_candle['date']) + ' open=' + str(buy_signal_candle['open']) \ + " score=" + str(self.calculateScore(last_candle)) if allow_to_sell: print('Sell trade ' + exit_reason + ' ' + str(current_time) + ' ' + pair + " dispo=" + str( round(self.wallets.get_available_stake_amount())) #"+ str(amount) + ' ' + str(rate) + " open_rate=" + str(trade.open_rate) + " rate=" + str(rate) + " profit=" + str(trade.calc_profit(rate, amount)) + " " + string) del self.max_profit_pairs[pair] else: print('Cancel Sell trade ' + exit_reason + ' ' + str(current_time) + ' ' + pair) return allow_to_sell def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]': dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() if (last_candle['percent'] > 0) | (last_candle['percent3'] > -0.002): return None days = (current_time - trade.open_date_utc).days hours = (current_time - trade.open_date_utc).seconds / 3600 minutes = (current_time - trade.open_date_utc).seconds / 60 if not (pair in self.max_profit_pairs): self.max_profit_pairs[pair] = current_profit self.max_profit_pairs[pair] = max(self.max_profit_pairs[pair], current_profit) if (hours >= 2) & (self.max_profit_pairs[pair] < 0.002) & (self.max_profit_pairs[pair] > - 0.002): return "no_change" # if (days >= 1) & (current_profit < -0.02): # return 'too_old' self.testLockedTrade(trade, current_profit, current_rate, current_time, pair) # if pair in self.locked_pairs: # print(pair + " stoploss") # return "force_stoploss" factor = 10 exit_atr = (trade.open_rate + (last_candle['atr'] * factor)) if (current_rate > exit_atr): return "exit_atr" def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() if (last_candle['percent'] > 0) | (last_candle['percent3'] > -0.002): return -1 # print(pair + " " + str(current_time) + " rate=" + str(current_profit)) self.testLockedTrade(trade, current_profit, current_rate, current_time, pair) # hard stoploss profit 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 # For profits between PF_1 and PF_2 the stoploss (sl_profit) used is linearly interpolated # between the values of SL_1 and SL_2. For all profits above PL_2 the sl_profit value # rises linearly with current profit, for profits below PF_1 the hard stoploss profit is used. 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 return stoploss_from_open(sl_profit, current_profit) # def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, # current_rate: float, current_profit: float, **kwargs) -> float: # dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) # candle = dataframe.iloc[-1].squeeze() # factor = 10 # return stoploss_from_absolute(current_rate - (candle['atr'] * factor), current_rate, is_short=trade.is_short) def getTradeCandle(self, dataframe, trade: 'Trade'): trade_open_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc) buy_signal = dataframe.loc[dataframe['date'] <= trade_open_date] return buy_signal def testLockedTrade(self, trade: 'Trade', current_profit, current_rate, current_time, pair): return "" if current_profit <= -0.1: if pair in self.locked_pairs: trade = self.locked_pairs[pair] if current_rate > trade.open_rate: print(pair + ' unlocked ' + str(current_time) + " rate=" + str(current_rate) + " locked_rate=" + str( trade.open_rate) + ' profit=' + str(current_profit)) del self.locked_pairs[pair] # else: # self.locked_pairs[pair] = current_rate else: self.locked_pairs[pair] = trade # self.lock_pair(pair, until=current_time + timedelta(hours=24)) print(pair + " locked at " + str(current_time) + " rate=" + str(current_rate) + " locked_rate=" + str( trade.open_rate) + ' profit=' + str(current_profit)) def will_frac(self, df: pd.DataFrame, period: int = 2) -> Tuple[pd.Series, pd.Series]: """ Indicate bearish and bullish fractal patterns using shifted Series. :param df: OHLC data :param period: number of lower (or higher) points on each side of a high (or low) :return: tuple of boolean Series (bearish, bullish) where True marks a fractal pattern """ periods = [p for p in range(-period, period + 1) if p != 0] # default [-2, -1, 1, 2] highs = [df['high'] > df['high'].shift(p) for p in periods] bears = pd.Series(np.logical_and.reduce(highs), index=df.index) lows = [df['low'] < df['low'].shift(p) for p in periods] bulls = pd.Series(np.logical_and.reduce(lows), index=df.index) df['fractal_sup'] = bears df['fractal_inf'] = bulls return df