kubectl --context=gke_vaulted-gift-406223_europe-west1-b_private-cluster-3 -n bot-ssc-03 exec -it pod/freqtrade-bot-ssc-03-6cdcbd68f6-fw59q -c freqtrade -- cat /extra_strategies/DWT_Predict2Hedged.py #pragma pylint: disable=W0105, C0103, C0301, W1203 from datetime import datetime from functools import reduce # import timeit import requests # from freqtrade.rpc import RPCManager # from freqtrade.rpc.external_message_consumer import ExternalMessageConsumer # from freqtrade.rpc.rpc_types import (ProfitLossStr, RPCCancelMsg, RPCEntryMsg, RPCExitCancelMsg, # RPCExitMsg, RPCProtectionMsg) # from freqtrade import rpc_singleton import numpy as np # Get rid of pandas warnings during backtesting import pandas as pd from pandas import DataFrame, Series import scipy from datetime import datetime from typing import Dict, List, Optional, Tuple, Union # from sklearn.ensemble import GradientBoostingRegressor from sklearn.preprocessing import RobustScaler from xgboost import XGBRegressor from lightgbm import LGBMRegressor from utils.FuturesPositionsFetcher import FuturesPositionFetcher from typing import Dict, List, Optional, Tuple, Union # import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.strategy import (IStrategy, DecimalParameter, CategoricalParameter) from freqtrade.persistence import Trade pd.options.mode.chained_assignment = None # default='warn' # Strategy specific imports, files must reside in same folder as strategy import sys from pathlib import Path sys.path.append(str(Path(__file__).parent)) import logging import warnings log = logging.getLogger(__name__) # log.setLevel(logging.DEBUG) warnings.simplefilter(action='ignore', category=pd.errors.PerformanceWarning) from utils.DataframeUtils import DataframeUtils, ScalerType import pywt import talib.abstract as ta from freqtrade.rpc import RPCManager from freqtrade.rpc.external_message_consumer import ExternalMessageConsumer from freqtrade.rpc.rpc_types import (ProfitLossStr, RPCCancelMsg, RPCEntryMsg, RPCExitCancelMsg, RPCExitMsg, RPCProtectionMsg, RPCMessageType) from utils.dsHedging import dsHedging ''' #################################################################################### DWT_Predict2 - use a Discreet Wavelet Transform to model the price, and a regression algorithm trained on the DWT coefficients, which is then used to predict future prices. Unfortunately, this must all be done in a rolling fashion to avoid lookahead bias - so it is pretty slow This variant uses the just the DWT coefficients, ot the whole dataframe #################################################################################### ''' class DWT_Predict2Hedged(IStrategy): # Do *not* hyperopt for the roi and stoploss spaces rpc: RPCManager = None # DerSalvador Hedging hedging_url = "" hedging_leverage = 1 hedging_stake_amount = 0 hedging_apikey = "" hedging_apisecret = "" existing_position_on_exchange = None # ROI table: minimal_roi = { "0": 0.04, "100": 0.02 } # Stoploss: stoploss = -0.10 # Trailing stop: trailing_stop = False trailing_stop_positive = None trailing_stop_positive_offset = 0.0 trailing_only_offset_is_reached = False timeframe = '5m' inf_timeframe = '15m' use_custom_stoploss = True # Recommended use_exit_signal = True exit_profit_only = False ignore_roi_if_entry_signal = True # Required startup_candle_count = 128 # must be power of 2 win_size = 14 process_only_new_candles = True custom_trade_info = {} ################################### # Strategy Specific Variable Storage ## Hyperopt Variables model_window = startup_candle_count lookahead = 6 # df_coeffs: DataFrame = None coeff_array = None coeff_model = None dataframeUtils = None scaler = RobustScaler() # DWT hyperparams # NOTE: this strategy does not hyperopt well, no idea why. Note that some vars are turned off (optimize=False) # the defaults are set for fairly frequent trades, and get out quickly # if you want bigger trades, then increase entry_model_diff, decrese exit_model_diff and adjust profit_threshold and # loss_threshold accordingly. # Note that there is also a corellation to self.lookahead, but that cannot be a hyperopt parameter (because it is # used in populate_indicators). Larger lookahead implies bigger differences between the model and actual price entry_model_diff = DecimalParameter(0.5, 3.0, decimals=1, default=1.0, space='buy', load=True, optimize=True) exit_model_diff = DecimalParameter(-5.0, 0.0, decimals=1, default=-1.0, space='sell', load=True, optimize=True) # trailing stoploss tstop_start = DecimalParameter(0.0, 0.06, default=0.019, decimals=3, space='sell', load=True, optimize=True) tstop_ratio = DecimalParameter(0.7, 0.99, default=0.8, decimals=3, space='sell', load=True, optimize=True) # profit threshold exit profit_threshold = DecimalParameter(0.005, 0.065, default=0.06, decimals=3, space='sell', load=True, optimize=True) use_profit_threshold = CategoricalParameter([True, False], default=False, space='sell', load=True, optimize=True) # loss threshold exit loss_threshold = DecimalParameter(-0.065, -0.005, default=-0.046, decimals=3, space='sell', load=True, optimize=True) use_loss_threshold = CategoricalParameter([True, False], default=True, space='sell', load=True, optimize=True) # use exit signal? enable_exit_signal = CategoricalParameter([True, False], default=True, space='sell', load=True, optimize=True) # enable entry/exit guards (safer vs profit) enable_guards = CategoricalParameter([True, False], default=True, space='sell', load=True, optimize=False) plot_config = { 'main_plot': { 'close': {'color': 'cornflowerblue'}, # 'model_model': {'color': 'lightsalmon'}, 'model_predict': {'color': 'mediumaquamarine'}, }, 'subplots': { "Diff": { 'model_diff': {'color': 'brown'}, }, } } def hedge(self, pair, direction): dsHedging.hedge(self, pair, direction) # log.info("Entering Hedge Modus") # current_timestamp = datetime.now() # tag = f'ForcedHedging {pair} at {current_timestamp} with leverage {self.hedging_leverage}' # tag += f'amount {self.hedging_stake_amount}' # tag += f'direction {direction} on bot {self.hedging_url}' # log.info(f"Entering hedging: {tag}") # payload = { # "pair": pair, # "side": direction, # "ordertype": "market", # "stakeamount": self.hedging_stake_amount, # "entry_tag": tag, # "leverage": self.hedging_leverage # } # headers = { # "accept": "application/json", # "Content-Type": "application/json" # } # username = self.config['api_server']['username'] # password = self.config['api_server']['password'] # auth = (username, password) # try: # response = requests.post(self.hedging_url, json=payload, headers=headers, auth=auth) # response.raise_for_status() # Check for HTTP errors # if response.status_code == 200: # log.info("Hedging successful. Response:") # log.info(response.json()) # else: # log.info(f"Request returned status code: {response.status_code}") # except requests.RequestException as e: # log.error(f"An error occurred during hedging: {e}") # log.info("Leaving Hedge Modus") @staticmethod def setRPCManager(rpc: RPCManager): DWT_Predict2Hedged.rpc = rpc ############################################ def hedging_config(self, config) -> None: self.hedging_url = config['dersalvador']['hedging']['hedge_bot_api'] self.hedging_leverage = config['dersalvador']['hedging']['leverage'] self.hedging_stake_amount = config['dersalvador']['hedging']['stake_amount'] self.hedging_apikey = config['dersalvador']['hedging']['apikey'] self.hedging_apisecret = config['dersalvador']['hedging']['apisecret'] # ################################### # def bot_loop_start(self, current_time: datetime, **kwargs) -> None: # log.info("Bot loop start ") # return @staticmethod def sendMessageToTelegram(msg: str): msg = { 'type': RPCMessageType.STARTUP, 'status': f"{msg}" } if DWT_Predict2Hedged.rpc is not None: DWT_Predict2Hedged.rpc.send_msg(msg) else: log.warning("RPC Telegram object not initialized in Strategy") def bot_start(self, **kwargs) -> None: if self.dataframeUtils is None: self.dataframeUtils = DataframeUtils() self.dataframeUtils.set_scaler_type(ScalerType.Robust) if self.coeff_model is None: self.create_model() if self.config['dersalvador']['hedging'] is not None: self.hedging_config(self.config) msg=f'*Found Hedging section in config*\n' msg+=f'*API:* {self.hedging_url}\n' msg+=f'*Amount:* {self.hedging_stake_amount}\n' msg+=f'*Leverage:* {self.hedging_leverage}\n' log.info(msg) DWT_Predict2Hedged.sendMessageToTelegram(msg) else: msg="No Hedging section found in config file" log.info(msg) DWT_Predict2Hedged.sendMessageToTelegram(msg) return ################################### ''' Informative Pair Definitions ''' def informative_pairs(self): return [] ################################### ''' Indicator Definitions ''' def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # Base pair dataframe timeframe indicators curr_pair = metadata['pair'] log.info(dir(self.config)) # print("") # print(curr_pair) # print("") # # build the DWT # print(" Building DWT...") # dataframe['model_model'] = dataframe['close'].rolling(window=self.model_window).apply(self.model) # RSI dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.win_size) # Williams %R dataframe['wr'] = 0.02 * (self.williams_r(dataframe, period=self.win_size) + 50.0) # Fisher RSI rsi = 0.1 * (dataframe['rsi'] - 50) dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) # Combined Fisher RSI and Williams %R dataframe['fisher_wr'] = (dataframe['wr'] + dataframe['fisher_rsi']) / 2.0 # add predictions dataframe = self.add_predictions(dataframe) # calculation % diff between model and actual closing price dataframe['model_diff'] = 100.0 * (dataframe['model_predict'] - dataframe['close']) / dataframe['close'] return dataframe ################################### # Williams %R def williams_r(self, dataframe: DataFrame, period: int = 14) -> Series: ''' Williams %R, or just %R, is a technical analysis oscillator showing the current closing price in relation to the high and low of the past N days (for a given N). It was developed by a publisher and promoter of trading materials, Larry Williams. Its purpose is to tell whether a stock or commodity market is trading near the high or the low, or somewhere in between, of its recent trading range. The oscillator is on a negative scale, from −100 (lowest) up to 0 (highest). ''' highest_high = dataframe["high"].rolling(center=False, window=period).max() lowest_low = dataframe["low"].rolling(center=False, window=period).min() WR = Series( (highest_high - dataframe["close"]) / (highest_high - lowest_low), name=f"{period} Williams %R", ) return WR * -100 ################################### def madev(self, d, axis=None): ''' Mean absolute deviation of a signal''' return np.mean(np.absolute(d - np.mean(d, axis)), axis) ################################### # function to get dwt coefficients def get_coeffs(self, data: np.array) -> np.array: # print(pywt.wavelist(kind='discrete')) x = data # data must be of even length, so trim if necessary if (len(x) % 2) != 0: x = x[1:] # get the DWT coefficients wavelet = 'haar' # wavelet = 'db12' # wavelet = 'db4' coeffs = pywt.wavedec(x, wavelet, mode='smooth') # # remove higher harmonics # level = 2 # length = len(data) # sigma = (1 / 0.6745) * self.madev(coeffs[-level]) # uthresh = sigma * np.sqrt(2 * np.log(length)) # coeffs[1:] = (pywt.threshold(i, value=uthresh, mode='hard') for i in coeffs[1:]) # flatten the coefficient arrays features = np.concatenate(np.array(coeffs, dtype=object)) # # trim down to max 128 entries # if len(features) > 128: # features = features[:128] return features # builds a numpy array of coefficients def add_coefficients(self, dataframe) -> DataFrame: # normalise the dataframe df_norm = self.convert_dataframe(dataframe) # copy the close data into an np.array (faster) close_data = np.array(df_norm['close']) init_done = False # roll through the close data and create DWT coefficients for each step nrows = np.shape(close_data)[0] start = 0 end = start + self.model_window dest = end # print(f"nrows:{nrows} start:{start} end:{end} dest:{dest} nbuffs:{nbuffs}") self.coeff_array = None num_coeffs = 0 while end < nrows: dslice = close_data[start:end] # print(f"start:{start} end:{end} dest:{dest} len:{len(dslice)}") features = self.get_coeffs(dslice) # initialise the np.array (need features first to know size) if not init_done: init_done = True num_coeffs = len(features) self.coeff_array = np.zeros((nrows, num_coeffs), dtype=float) # print(f"coeff_array:{np.shape(self.coeff_array)}") # copy the features to the appropriate row of the coefficient array (offset due to startup window) self.coeff_array[dest] = features start = start + 1 dest = dest + 1 end = end + 1 # # normalise the coefficients # self.scaler.fit(self.coeff_array) # self.coeff_array = self.scaler.transform(self.coeff_array) return dataframe # builds a numpy array of coefficients def get_coefficient_table(self, data: np.array): init_done = False # roll through the data and create DWT coefficients for each step nrows = np.shape(data)[0] # print(f'nrows:{nrows}') start = 0 end = start + self.model_window - 1 dest = end # print(f"nrows:{nrows} start:{start} end:{end} dest:{dest} nbuffs:{nbuffs}") coeff_table = None num_coeffs = 0 while end < nrows: dslice = data[start:end] # print(f"start:{start} end:{end} dest:{dest} len:{len(dslice)}") features = self.get_coeffs(dslice) # initialise the np.array (need features first to know size) if not init_done: init_done = True num_coeffs = len(features) coeff_table = np.zeros((nrows, num_coeffs), dtype=float) # print(f"coeff_table:{np.shape(coeff_table)}") # copy the features to the appropriate row of the coefficient array (offset due to startup window) coeff_table[dest] = features start = start + 1 dest = dest + 1 end = end + 1 return coeff_table #------------- def convert_dataframe(self, dataframe: DataFrame) -> DataFrame: df = dataframe.copy() # convert date column so that it can be scaled. if 'date' in df.columns: dates = pd.to_datetime(df['date'], utc=True) df['date'] = dates.astype('int64') df.fillna(0.0, inplace=True) df.set_index('date') df.reindex() # scale the dataframe self.scaler.fit(df) df = pd.DataFrame(self.scaler.transform(df), columns=df.columns) return df #------------- def create_model(self): # self.coeff_model = SVR(kernel='rbf', C=1.0, epsilon=0.1) # params = {'n_estimators': 100, 'max_depth': 4, 'min_samples_split': 2, # 'learning_rate': 0.1, 'loss': 'squared_error'} # self.coeff_model = GradientBoostingRegressor(**params) params = {'n_estimators': 100, 'max_depth': 4, 'learning_rate': 0.1} # MSSM: See comment below # self.coeff_model = XGBRegressor(**params) # LGBMRegressor gives better/faster results, but has issues on some MacOS platforms. Hence, not using it any more self.coeff_model = LGBMRegressor(**params) return #------------- def train_model(self, data: np.array, coeff_table: np.array): #TODO: do grid search for best parameters first time through (or just during debug) # print(f"data: {np.shape(data)} coeff_table:{np.shape(coeff_table)}") # need to exclude the startup period at the front, and the lookahead period at the end # x = coeff_table[self.startup_candle_count:-self.lookahead] # y = data[self.startup_candle_count+self.lookahead:] x = coeff_table[:-self.lookahead] y = data[self.lookahead:] # print(f"df: {np.shape(x)} y:{np.shape(y)}") self.coeff_model.fit(x, y) return #------------- def get_modal_value(self, data): # round to 6 decimal places (otherwise there is unlikely to be a modal value) rnd = np.round(data, decimals=6) # get the mode mode = scipy.stats.mode(rnd, keepdims=False).mode return mode # get predictions based on array of close data def get_predictions(self, data: np.array): coeff_table = self.get_coefficient_table(data) self.train_model(data, coeff_table) predictions = self.coeff_model.predict(coeff_table) return predictions # add predictions in a non-rolling fashion. Use this when future data is *not* present (e.g. dry run) def add_predictions(self, dataframe: DataFrame) -> DataFrame: dataframe['model_predict'] = dataframe['close'] # normalise the dataframe # df_norm = self.convert_dataframe(dataframe) close_data = np.array(dataframe['close']) # for hyperopt, dryrun and plot modes, we need to add data using a rolling window if self.dp.runmode.value in ('hyperopt' 'backtest' 'plot'): # dataframe['model_predict'] = self.add_rolling_predictions(close_data) dataframe['model_predict'] = self.add_jumping_predictions(close_data) else: # for other modes, we can just directly process the data (because it is not forward looking) dataframe['model_predict'] = self.get_predictions(close_data) return dataframe # add predictions in a rolling fashion. Use this when future data is present (e.g. backtest) def add_rolling_predictions(self, data: np.array) -> DataFrame: use_expanding_window = False # add data in a rolling fashion. # Note that this is not quite the same as the usual (pandas) rolling window, since we can use an expanding window # that includes any data that lies in the 'past'. # Also, I restrict the window size to 974, which is the size of the data buffer in dry run and live run modes live_buffer_size = 974 # roll through the close data and predict for each step nrows = np.shape(data)[0] # initialise the prediction array, using the close data pred_array = data.copy() # build the coefficient table (outside the main loop) coeff_table = self.get_coefficient_table(data) # win_size = self.model_window # if (nrows > live_buffer_size): # max_win_size = live_buffer_size # else: # max_win_size = self.model_window # if training withion loop, we need to use a buffer size at least 2x the DWT/DWT window size + lookahead win_size = live_buffer_size max_win_size = live_buffer_size start = 0 end = start + win_size first_time = True while end < nrows: start = end - win_size dslice = data[start:end] cslice = coeff_table[start:end] # print(f"start:{start} end:{end} win_size:{win_size} dslice:{np.shape(dslice)}") self.train_model(dslice, cslice) preds = self.coeff_model.predict(cslice) if first_time: pred_array[:win_size] = preds first_time = False else: pred_array[end] = preds[-1] end = end + 1 if use_expanding_window: win_size = min(win_size+1, max_win_size) return pred_array # add predictions in a jumping fashion. This is a compromise - the rolling version is very slow # Note: you probably need to manually tune the parameters, since there is some limited lookahead here def add_jumping_predictions(self, data: np.array) -> DataFrame: use_expanding_window = False # roll through the close data and predict for each step nrows = np.shape(data)[0] # initialise the prediction array, using the close data pred_array = data.copy() # build the coefficient table (outside the main loop) coeff_table = self.get_coefficient_table(data) # if training within loop, we need to use a buffer size at least 2x the DWT/DWT window size + lookahead # here, just using same size as the live run buffer win_size = 974 # loop until we get to/past the end of the buffer start = 0 end = start + win_size while end < nrows: # extract the data and coefficients from the current window start = end - win_size dslice = data[start:end] cslice = coeff_table[start:end] # print(f"start:{start} end:{end} win_size:{win_size} dslice:{np.shape(dslice)}") # (re-)train the model and get predictions self.train_model(dslice, cslice) preds = self.coeff_model.predict(cslice) # copy the predictions for this window into the main predictions array pred_array[start:end] = preds # move the window to the next segment end = end + win_size - 1 # make sure the last section gets processed (the loop above may not exactly fit the data) dslice = data[-win_size:] cslice = coeff_table[-win_size:] self.train_model(dslice, cslice) preds = self.coeff_model.predict(cslice) pred_array[-win_size:] = preds return pred_array ################################### ''' entry Signal ''' def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] dataframe.loc[:, 'enter_tag'] = '' # some trading volume conditions.append(dataframe['volume'] > 0) if self.enable_guards.value: # Fisher/Williams in buy region conditions.append(dataframe['fisher_wr'] <= -0.5) else: conditions.append(dataframe['fisher_wr'] < 0.0) # very loose guard # Model triggers model_cond = ( # model predicts a rise above the entry threshold (dataframe['model_diff'] >= self.entry_model_diff.value) & # model prediction is going up (dataframe['model_predict'] >= dataframe['model_predict'].shift()) ) conditions.append(model_cond) # set entry tags dataframe.loc[model_cond, 'enter_tag'] += 'model_entry ' if conditions: dataframe.loc[reduce(lambda x, y: x & y, conditions), 'enter_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: str, side: str, **kwargs) -> bool: dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) last_candle = dataframe.iloc[-1].squeeze() # don't buy if the purchase price is above the current prediction (both can change) pred = round(last_candle['model_predict'], 4) price = round(rate, 4) if pred > price: if self.dp.runmode.value not in ('backtest', 'plot', 'hyperopt'): print(f'Entry: {pair}, rate: {price}') result = True else: if self.dp.runmode.value not in ('hyperopt'): print(f"Entry rejected: {pair}. Prediction:{pred:.4f} <= rate:{price:.4f}") result = False # don't buy if sell signal active (it can happen) if last_candle['exit_long'] > 0: if self.dp.runmode.value not in ('hyperopt'): print(f"Entry rejected: sell active") result = False return result ################################### ''' exit Signal ''' def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] dataframe.loc[:, 'exit_tag'] = '' if not self.enable_exit_signal.value: dataframe['exit_long'] = 0 return dataframe if self.enable_guards.value: # some volume conditions.append(dataframe['volume'] > 0) # Fisher/Williams in sell region conditions.append(dataframe['fisher_wr'] >= 0.5) else: conditions.append(dataframe['fisher_wr'] > 0.0) # very loose guard # Model triggers model_cond = ( (dataframe['model_diff'] <= self.exit_model_diff.value) ) conditions.append(model_cond) # set exit tags dataframe.loc[model_cond, 'exit_tag'] += 'model_exit ' if conditions: dataframe.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 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: if self.dp.runmode.value not in ('backtest', 'plot', 'hyperopt'): print(f'Trade Exit: {pair}, rate: {round(rate, 4)}') return True ################################### ''' Custom Stoploss ''' # simplified version of custom trailing stoploss def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: # if current profit is above start value, then set stoploss at fraction of current profit if current_profit > self.tstop_start.value: return current_profit * self.tstop_ratio.value # return min(-0.001, max(stoploss_from_open(0.05, current_profit), -0.99)) return self.stoploss ################################### ''' Custom Exit (Note that this runs even if use_custom_stoploss is False) ''' # simplified version of custom exit # is called every 5 seconds, https://www.freqtrade.io/en/stable/strategy-callbacks/#custom-exit-signal def custom_exit(self, pair: str, trade: Trade, current_time: 'datetime', current_rate: float, current_profit: float, **kwargs): # Keep for testing Strategy Improvements, is called every 5 seconds, add code and breakpoint here log.info(f"Entering custom_exit for {pair}, current_profit: {current_profit}, current_rate: {current_rate}, timestamp: {current_time}") positionFetcher = FuturesPositionFetcher(self.hedging_apikey, self.hedging_apisecret) symbol=pair.split('/')[0]+"USDT" DWT_Predict2Hedged.existing_position_on_exchange = positionFetcher.get_futures_position_information(symbol) # dsHedging.hedge_me(self, trade, pair) # dsHedging.hedge_me(self, trade, pair, DWT_Predict2Hedged.existing_position_on_exchange) if not self.use_custom_stoploss: return None # New logic: Evaluate indicators for hedging when profit trend is negative if current_profit < 0: # Example indicator: if RSI is below 30, indicating oversold, consider hedging if last_candle['rsi'] < 30: log.info(f"Hedging: RSI oversold") dsHedging.hedge_me(self, trade, pair, DWT_Predict2Hedged.existing_position_on_exchange) log.info(f"Leaving custom_exit for {pair}, current_profit: {current_profit}, current_rate: {current_rate}, timestamp: {current_time}") return 'rsi_oversold' # Example indicator: if MACD is below signal line, indicating bearish trend if last_candle['macd'] < last_candle['macdsignal']: log.info(f"Hedging: MACD bearish") dsHedging.hedge_me(self, trade, pair, DWT_Predict2Hedged.existing_position_on_exchange) log.info(f"Leaving custom_exit for {pair}, current_profit: {current_profit}, current_rate: {current_rate}, timestamp: {current_time}") return 'macd_bearish' dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) last_candle = dataframe.iloc[-1].squeeze() # strong sell signal, in profit if (current_profit > 0) and (last_candle['fisher_wr'] > 0.95): log.info(f"Hedging: fwr_overbought") log.info(f"Leaving custom_exit for {pair}, current_profit: {current_profit}, current_rate: {current_rate}, timestamp: {current_time}") return 'fwr_overbought' # Above 1%, sell if Fisher/Williams in sell range if current_profit > 0.01: if last_candle['fisher_wr'] > 1.8: return 'take_profit' # if model prediction is above threshold, don't exit if last_candle['model_diff'] >= self.entry_model_diff.value: return None # check profit against ROI target. This sort of emulates the freqtrade roi approach, but is much simpler if self.use_profit_threshold.value: if (current_profit >= self.profit_threshold.value): return 'profit_threshold' # check loss against threshold. This sort of emulates the freqtrade stoploss approach, but is much simpler if self.use_loss_threshold.value: if (current_profit <= self.loss_threshold.value): dsHedging.hedge_me(self, trade, pair, DWT_Predict2Hedged.existing_position_on_exchange) log.info(f"Leaving custom_exit for {pair}, current_profit: {current_profit}, current_rate: {current_rate}, timestamp: {current_time}") return 'loss_threshold' # Sell any positions at a loss if they are held for more than 'N' days. if (current_time - trade.open_date_utc).days >= 7: log.info(f"Leaving custom_exit for {pair}, current_profit: {current_profit}, current_rate: {current_rate}, timestamp: {current_time}") dsHedging.hedge_me(self, trade, pair, DWT_Predict2Hedged.existing_position_on_exchange) return 'unclog' # big drop predicted. Should also trigger an exit signal, but this might be quicker (and will likely be 'market' sell) if last_candle['model_diff'] <= min(-1.0, self.exit_model_diff.value): log.info(f"Leaving custom_exit for {pair}, current_profit: {current_profit}, current_rate: {current_rate}, timestamp: {current_time}") dsHedging.hedge_me(self, trade, pair, DWT_Predict2Hedged.existing_position_on_exchange) return 'predict_drop' # if in profit and exit signal is set, sell (even if exit signals are disabled) if (current_profit > 0) and (last_candle['exit_long'] > 0): log.info(f"Leaving custom_exit for {pair}, current_profit: {current_profit}, current_rate: {current_rate}, timestamp: {current_time}") return 'exit_signal' else: dsHedging.hedge_me(self, trade, pair, DWT_Predict2Hedged.existing_position_on_exchange) log.info(f"Leaving custom_exit for {pair}, current_profit: {current_profit}, current_rate: {current_rate}, timestamp: {current_time}") return None def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, **kwargs) -> float: return 3