def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: cs = self.get_pair_params(pair, 'custom_stop') trade_dur = int( (current_time.timestamp() - trade.open_date_utc.timestamp()) // 60) min_profit = trade.calc_profit_ratio(trade.min_rate) max_profit = trade.calc_profit_ratio(trade.max_rate) profit_diff = current_profit - min_profit decay_stoploss = cta.linear_growth(cs['decay-start'], cs['decay-end'], cs['decay-delay'], cs['decay-time'], trade_dur) # enable stoploss in positive profits after threshold to trail as specifed distance if cs['pos-trail'] == True: if current_profit > cs['pos-threshold']: return current_profit - cs['pos-trail-dist'] if self.config['runmode'].value in ('live', 'dry_run'): dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=pair, timeframe=self.timeframe) roc = dataframe['roc'].iat[-1] atr = dataframe['atr'].iat[-1] rmi_slow = dataframe['rmi-slow'].iat[-1] consensus_buy = dataframe['consensus-buy'].iat[-1] consensus_sell = dataframe['consensus-sell'].iat[-1] # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!) else: roc = self.custom_trade_info[ trade.pair]['roc'].loc[current_time]['roc'] atr = self.custom_trade_info[ trade.pair]['atr'].loc[current_time]['atr'] rmi_slow = self.custom_trade_info[ trade.pair]['rmi-slow'].loc[current_time]['rmi-slow'] consensus_buy = self.custom_trade_info[ trade.pair]['consensus-buy'].loc[current_time]['consensus-buy'] consensus_sell = self.custom_trade_info[trade.pair][ 'consensus-sell'].loc[current_time]['consensus-sell'] if current_profit < cs['cur-threshold']: # Dynamic bailout based on rate of change if (roc / 100) <= cs['roc-bail'] or cs['con-bail'] <= consensus_sell: if cs['bail-how'] == 'atr': return ((current_rate - atr) / current_rate) - 1 elif cs['bail-how'] == 'immediate': return current_rate else: return decay_stoploss # if we might be on a rebound, move the stoploss to the low point or keep it where it was if (current_profit > min_profit) or roc > 0 or rmi_slow >= cs[ 'rmi-trend'] or consensus_buy >= cs['con-trend']: if profit_diff > cs['cur-min-diff']: return min_profit return -1 return decay_stoploss
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: start = self.custom_stop['decay-start'] end = self.custom_stop['decay-end'] time = self.custom_stop['decay-time'] delay = self.custom_stop['decay-delay'] pos_trail = self.custom_stop['pos-trail'] pos_threshold = self.custom_stop['pos-threshold'] pos_trail_dist = self.custom_stop['pos-trail-dist'] open_minutes: int = (current_time - trade.open_date).total_seconds() // 60 decay_stoploss = cta.linear_growth(start, end, delay, time, open_minutes) min_profit = trade.calc_profit_ratio(trade.min_rate) max_profit = trade.calc_profit_ratio(trade.max_rate) profit_diff = current_profit - min_profit if pos_trail == True: # if trade is positive above threshold, immediately move the stoploss up to x% behind it if current_profit > pos_threshold: return current_profit - pos_trail_dist # if we might be on a rebound, move the stoploss to the low point or keep it where it was if current_profit > min_profit: if profit_diff > 0.015: return min_profit return -1 # otherwise return the time decaying stoploss return decay_stoploss
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: params = self.get_pair_params(metadata['pair'], 'sell') trade_data = self.custom_trade_info[metadata['pair']] conditions = [] # If we are in an active trade (which is the only way a sell can occur...) # This is needed to block backtest/hyperopt from hitting the profit stuff and erroring out. if trade_data['active_trade']: # Decay a loss cutoff point where we allow a sell to occur idea is this allows # a trade to go into the negative a little bit before we react to sell. loss_cutoff = cta.linear_growth(-0.03, 0, 0, 240, trade_data['open_minutes']) conditions.append((trade_data['current_profit'] < loss_cutoff)) # Examine the state of our other trades and free_slots to inform the decision if trade_data['other_trades']: if trade_data['free_slots'] > 0: # If the average of all our other trades is below a certain threshold based # on free slots available, hold and wait for market recovery. hold_pct = (1 / trade_data['free_slots']) * -0.04 conditions.append( trade_data['avg_other_profit'] >= hold_pct) else: # If we are out of free slots disregard the above and allow the biggest loser to sell. conditions.append(trade_data['biggest_loser'] == True) conditions.append( dataframe['consensus_sell'] > params['consensus-sell']) if conditions: dataframe.loc[reduce(lambda x, y: x & y, conditions), 'sell'] = 1 return dataframe
def min_roi_reached_dynamic( self, trade: Trade, current_profit: float, current_time: datetime, trade_dur: int) -> Tuple[Optional[int], Optional[float]]: dynamic_roi = self.dynamic_roi minimal_roi = self.minimal_roi print(f"In the ROI place for pair: {trade.pair}") if not dynamic_roi or not minimal_roi: return None, None if self.custom_trade_info and trade and trade.pair in self.custom_trade_info: # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!) if self.dp and self.dp.runmode.value in ('backtest', 'hyperopt'): roc = self.custom_trade_info[ trade.pair]['roc'].loc[current_time]['roc'] atr = self.custom_trade_info[ trade.pair]['atr'].loc[current_time]['atr'] rmi_slow = self.custom_trade_info[ trade.pair]['rmi-slow'].loc[current_time]['rmi-slow'] rmi_trend = self.custom_trade_info[trade.pair][ 'rmi-up-trend'].loc[current_time]['rmi-up-trend'] # Otherwise get it out of the dataframe else: dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=trade.pair, timeframe=self.timeframe) roc = dataframe['roc'].iat[-1] atr = dataframe['atr'].iat[-1] rmi_slow = dataframe['rmi-slow'].iat[-1] rmi_trend = dataframe['rmi-up-trend'].iat[-1] d = dynamic_roi profit_factor = (1 - (rmi_slow / d['profit-factor'])) rmi_grow = cta.linear_growth(d['rmi-start'], d['rmi-end'], d['grow-delay'], d['grow-time'], trade_dur) max_profit = trade.calc_profit_ratio(trade.max_rate) open_rate = trade.open_rate atr_roi = max(0, ((open_rate + atr) / open_rate) - 1) roc_roi = max(0, (roc / 100)) # If we observe a strong upward trend and our current profit is above the max (multiplied by a factor), hold if (current_profit > (max_profit * profit_factor)) and ( rmi_trend == 1) and (rmi_slow > rmi_grow): min_roi = 100 # Otherwise fallback something else as specified else: if d['fallback'] == 'atr': min_roi = atr_roi if d['fallback'] == 'roc': min_roi = roc_roi else: _, min_roi = self.min_roi_reached_entry(trade_dur) else: return self.min_roi_reached_entry(trade_dur) return trade_dur, min_roi
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: params = self.get_pair_params(metadata['pair'], 'sell') at = self.custom_active_trade trade_data = self.custom_trade_info[metadata['pair']] conditions = [] """ Previous Schism sell implementation has been *partially* replaced by custom_stoploss. Doing it there makes it able to be backtested more realistically but some of the functionality such as the ability to use other trades or the # of slots in our decision need to remain here. The current sell is meant to be a pre-stoploss bailout but most sells at a loss should happen due to the stoploss in live/dry. ** Only part of this signal functions in backtest/hyperopt. Can only be optimized in live/dry. ** Results in backtest/hyperopts will sell far more readily than in live due to the profit guards. """ # If we are in an active trade (which is the only way a sell can occur...) # This is needed to block backtest/hyperopt from hitting the profit stuff and erroring out. if trade_data['active_trade']: # Decay a loss cutoff point where we allow a sell to occur idea is this allows # a trade to go into the negative a little bit before we react to sell. loss_cutoff = cta.linear_growth(at['loss-start'], at['loss-end'], at['loss-delay'], at['loss-time'], trade_data['open_minutes']) conditions.append((trade_data['current_profit'] < loss_cutoff)) # Examine the state of our other trades and free_slots to inform the decision if trade_data['other_trades']: if trade_data['free_slots'] > 0: # If the average of all our other trades is below a certain threshold based # on free slots available, hold and wait for market recovery. hold_pct = (1/trade_data['free_slots']) * at['sell-mkt-down'] conditions.append(trade_data['avg_other_profit'] >= hold_pct) else: # If we are out of free slots disregard the above and allow the biggest loser to sell. conditions.append(trade_data['biggest_loser'] == True) # Primary sell trigger rmi_drop = dataframe['rmi-max'] - (dataframe['rmi-max'] * params['sell-rmi-drop']) conditions.append( (dataframe['rmi-dn-trend'] == 1) & (qtpylib.crossed_below(dataframe['rmi-slow'], rmi_drop)) & (dataframe['volume'].gt(0)) ) # TODO: Evaluate PMAX in dry/live if params['sell-pmax-enable'] == True: conditions.append((dataframe['pmax-trend'] == 'down')) if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), 'sell'] = 1 return dataframe
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: cs = self.custom_stop trade_dur: int = (current_time - trade.open_date).total_seconds() // 60 min_profit = trade.calc_profit_ratio(trade.min_rate) max_profit = trade.calc_profit_ratio(trade.max_rate) profit_diff = current_profit - min_profit decay_stoploss = cta.linear_growth(cs['decay-start'], cs['decay-end'], cs['decay-delay'], cs['decay-time'], trade_dur) # enable stoploss in positive profits after threshold to trail as specifed distance if cs['pos-trail'] == True: if current_profit > cs['pos-threshold']: return current_profit - cs['pos-trail-dist'] # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!) if self.dp and self.dp.runmode.value in ('backtest', 'hyperopt'): roc = self.custom_trade_info[ trade.pair]['roc'].loc[current_time]['roc'] atr = self.custom_trade_info[ trade.pair]['atr'].loc[current_time]['atr'] rmi_slow = self.custom_trade_info[ trade.pair]['rmi-slow'].loc[current_time]['rmi-slow'] rmi_trend = self.custom_trade_info[ trade.pair]['rmi-up-trend'].loc[current_time]['rmi-up-trend'] # Otherwise get it out of the dataframe else: dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=pair, timeframe=self.timeframe) roc = dataframe['roc'].iat[-1] atr = dataframe['atr'].iat[-1] rmi_slow = dataframe['rmi-slow'].iat[-1] rmi_trend = dataframe['rmi-up-trend'].iat[-1] if current_profit < cs['cur-threshold']: # Dynamic bailout based on rate of change if (roc / 100) < cs['cur-roc']: stoploss = (current_rate - atr) / current_rate return stoploss - 1 # if we might be on a rebound, move the stoploss to the low point or keep it where it was if (current_profit > min_profit) or roc > 0: if profit_diff > cs['cur-min-diff']: return min_profit return -1 return decay_stoploss
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: cs = self.custom_stop open_minutes: int = (current_time - trade.open_date).total_seconds() // 60 min_profit = trade.calc_profit_ratio(trade.min_rate) max_profit = trade.calc_profit_ratio(trade.max_rate) profit_diff = current_profit - min_profit # enable stoploss in positive profits after threshold to trail as specifed distance if cs['pos-trail'] == True: if current_profit > cs['pos-threshold']: return current_profit - cs['pos-trail-dist'] # decay-only is literally just the decay, decay uses the decay unless profit is increasing if cs['mode'] == 'decay' or cs['mode'] == 'decay-only': decay_stoploss = cta.linear_growth(cs['decay-start'], cs['decay-end'], cs['decay-delay'], cs['decay-time'], open_minutes) if cs['mode'] == 'decay-only': return decay_stoploss # if we might be on a rebound, move the stoploss to the low point or keep it where it was if (current_profit > min_profit) and current_profit < cs['cur-threshold']: if profit_diff > cs['cur-min-diff']: return min_profit return -1 return decay_stoploss if cs['mode'] == 'dynamic': if not pair in cs: cs[pair] = {} cs[pair]['current_profit'] cs[pair]['min_profit'] cs[pair]['max_profit'] # if all else fails, keep the stoploss where it is return -1
def min_roi_reached_dynamic( self, trade: Trade, current_profit: float, current_time: datetime, trade_dur: int) -> Tuple[Optional[int], Optional[float]]: dynamic_roi = self.get_pair_params(trade.pair, 'dynamic_roi') minimal_roi = self.get_pair_params(trade.pair, 'minimal_roi') if not dynamic_roi or not minimal_roi: return None, None _, table_roi = self.min_roi_reached_entry(trade_dur, trade.pair) # see if we have the data we need to do this, otherwise fall back to the standard table if self.custom_trade_info and trade and trade.pair in self.custom_trade_info: if self.config['runmode'].value in ('live', 'dry_run'): dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=trade.pair, timeframe=self.timeframe) roc = dataframe['roc'].iat[-1] atr = dataframe['atr'].iat[-1] rmi_slow = dataframe['rmi-slow'].iat[-1] rmi_trend = dataframe['rmi-up-trend'].iat[-1] # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!) else: roc = self.custom_trade_info[ trade.pair]['roc'].loc[current_time]['roc'] atr = self.custom_trade_info[ trade.pair]['atr'].loc[current_time]['atr'] rmi_slow = self.custom_trade_info[ trade.pair]['rmi-slow'].loc[current_time]['rmi-slow'] rmi_trend = self.custom_trade_info[trade.pair][ 'rmi-up-trend'].loc[current_time]['rmi-up-trend'] d = dynamic_roi open_rate = trade.open_rate max_profit = trade.calc_profit_ratio(trade.max_rate) atr_roi = max(d['min-roc-atr'], ((open_rate + atr) / open_rate) - 1) roc_roi = max(d['min-roc-atr'], (roc / 100)) # atr as the fallback (if > min-roc-atr) if d['fallback'] == 'atr': min_roi = atr_roi # roc as the fallback (if > min-roc-atr) elif d['fallback'] == 'roc': min_roi = roc_roi # atr or table as the fallback (whichever is larger) elif d['fallback'] == 'atr-table': min_roi = max(table_roi, atr_roi) # roc or table as the fallback (whichever is larger) elif d['fallback'] == 'roc-table': min_roi = max(table_roi, roc_roi) # default to table else: min_roi = table_roi rmi_grow = cta.linear_growth(d['rmi-start'], d['rmi-end'], d['grow-delay'], d['grow-time'], trade_dur) profit_factor = (1 - (rmi_slow / d['profit-factor'])) pullback_buffer = (max_profit * profit_factor) # If we observe a strong upward trend and our current profit has not retreated from the peak by much, hold if (rmi_trend == 1) and (rmi_slow > rmi_grow): if (current_profit < pullback_buffer) and max_profit > min_roi: # allow immediate bailout if we were above the ROI point and retreated below it min_roi = current_profit / 2 else: min_roi = 100 else: min_roi = table_roi return trade_dur, min_roi
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: params = self.get_pair_params(metadata['pair'], 'buy') at = self.custom_active_trade trade_data = self.custom_trade_info[metadata['pair']] conditions = [] """ The primary "secret sauce" of Solipsis is to take advantage of the ignore_roi_if_buy_signal setting. Ideally, the ROI table is very tight and aggressive allowing for quick exits on ROI without reliance on a sell signal. However, if certain criteria are met for an open trade, we stimulate a sticking buy signal on purpose to prevent the bot from selling to the ROI in the midst of an upward trend. This is not currently able to be backtested or hyperopted so all settings here must be tested and validated in dry-run or live. It is safe to assume that when backtesting, all sells for reason roi will have likely been for higher profits in live trading than in backtest. """ # If active trade, look at trend to persist a buy signal for ignore_roi_if_buy_signal if trade_data['active_trade']: profit_factor = (1 - (dataframe['rmi-slow'].iloc[-1] / at['roi-pft-factor'])) rmi_grow = cta.linear_growth(at['roi-rmi-start'], at['roi-rmi-end'], at['roi-delay'], at['roi-time'], trade_data['open_minutes']) conditions.append(trade_data['current_profit'] > (trade_data['peak_profit'] * profit_factor)) # TODO: Experiment with replacing or augmenting this with PMAX conditions.append( ( (params['base-pmax-enable'] == True) & (dataframe['pmax-trend'] == 'up') ) | ( (dataframe['rmi-up-trend'] == 1) & (dataframe['rmi-slow'] >= rmi_grow) ) ) # Standard signals for entering new trades else: # Guards on informative timeframe if params['inf-guard'] == 'upper' or params['inf-guard'] == 'both': conditions.append( (dataframe['close'] <= dataframe[f"3d_low_{self.inf_timeframe}"] + (params['inf-pct-adr-top'] * dataframe[f"adr_{self.inf_timeframe}"])) ) if params['inf-guard'] == 'lower' or params['inf-guard'] == 'both': conditions.append( (dataframe['close'] >= dataframe[f"3d_low_{self.inf_timeframe}"] + (params['inf-pct-adr-bot'] * dataframe[f"adr_{self.inf_timeframe}"])) ) # Informative Timeframe conditions.append( (dataframe[f"rsi_{self.inf_timeframe}"] >= params['inf-rsi']) & (dataframe[f"fib-ret_{self.inf_timeframe}"] >= params['inf-fib-level']) ) if params['base-pmax-enable'] == True: conditions.append((dataframe['pmax-trend'] == params['base-pmax'])) # # Base Timeframe # Look for bottom of downward trend and expect bounce if params['base-trend'] == 'down': conditions.append( (dataframe['fib-ret'] <= params['base-fib-level']) & (dataframe['rmi-dn-trend'] == 1) & (dataframe['rmi-slow'] >= params['base-rmi-slow']) & (dataframe['rmi-fast'] <= params['base-rmi-fast']) & (dataframe['mp'] <= params['base-mp']) ) # Look for upward trend elif params['base-trend'] == 'up': conditions.append( (dataframe['fib-ret'] >= params['base-fib-level']) & (dataframe['rmi-up-trend'] == 1) & (dataframe['rmi-slow'] <= params['base-rmi-slow']) & (dataframe['rmi-fast'] >= params['base-rmi-fast']) & (dataframe['mp'] >= params['base-mp']) ) # Extra conditions for BTC/ETH stakes on additional informative pairs if self.config['stake_currency'] in ('BTC', 'ETH'): conditions.append( (dataframe[f"{self.config['stake_currency']}_rsi"] < params['xtra-base-stake-rsi']) | (dataframe[f"{self.custom_fiat}_rsi"] > params['xtra-base-fiat-rsi']) ) conditions.append(dataframe[f"{self.config['stake_currency']}_rmi_{self.inf_timeframe}"] < params['xtra-inf-stake-rmi']) # Applies to active or new trades conditions.append(dataframe['volume'].gt(0)) if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), 'buy'] = 1 return dataframe
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: cs = self.custom_stop # Attempting to populate some of the trade data used elsewhere in the strategy with the trade object from here in backtest/hyperopt if self.dp and self.dp.runmode.value in ('backtest', 'hyperopt'): self.custom_trade_info[pair] = self.populate_trades( pair, trade=trade, time=current_time, rate=current_rate, profit=current_profit) trade_data = self.custom_trade_info[pair] print(f"Trade Data from Populate Trades: {trade_data}") open_minutes: int = (current_time - trade.open_date).total_seconds() // 60 min_profit = trade.calc_profit_ratio( trade.min_rate) # min array no object? max_profit = trade.calc_profit_ratio( trade.max_rate) # max array no object? profit_diff = current_profit - min_profit print(f"Trade object in stoploss {trade}") print(f"Open Minutes: {open_minutes}") print(f"Current Rate: {current_rate}") print(f"Current Profit: {current_profit}") print(f"Min Profit: {min_profit}") print(f"Max Profit: {max_profit}") # enable stoploss in positive profits after threshold to trail as specifed distance if cs['pos-trail'] == True: if current_profit > cs['pos-threshold']: return current_profit - cs['pos-trail-dist'] # decay-only is literally just the decay, decay uses the decay unless profit is increasing if cs['mode'] == 'decay' or cs['mode'] == 'decay-only': decay_stoploss = cta.linear_growth(cs['decay-start'], cs['decay-end'], cs['decay-delay'], cs['decay-time'], open_minutes) if cs['mode'] == 'decay-only': return decay_stoploss # if we might be on a rebound, move the stoploss to the low point or keep it where it was if (current_profit > min_profit) and current_profit < cs['cur-threshold']: if profit_diff > cs['cur-min-diff']: return min_profit return -1 return decay_stoploss if cs['mode'] == 'dynamic': if not pair in cs: cs[pair] = {} cs[pair]['current_profit'] cs[pair]['min_profit'] cs[pair]['max_profit'] # if all else fails, keep the stoploss where it is return -1
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: params = self.get_pair_params(metadata['pair'], 'buy') trade_data = self.custom_trade_info[metadata['pair']] conditions = [] # If active trade, look at trend to persist a buy signal for ignore_roi_if_buy_signal if trade_data['active_trade']: profit_factor = (1 - (dataframe['rmi-slow'].iloc[-1] / 400)) rmi_grow = cta.linear_growth(30, 70, 180, 720, trade_data['open_minutes']) conditions.append( trade_data['current_profit'] > (trade_data['peak_profit'] * profit_factor)) conditions.append(dataframe['rmi-up-trend'] == 1) conditions.append(dataframe['rmi-slow'] >= rmi_grow) # Standard signals for entering new trades else: # Primary guards on informative timeframe to make sure we don't trade when market is peaked or bottomed out if params['inf-guard'] == 'upper' or params['inf-guard'] == 'both': conditions.append((dataframe['close'] <= dataframe[f"3d_low_{self.inf_timeframe}"] + (params['inf-pct-adr-top'] * dataframe[f"adr_{self.inf_timeframe}"]))) if params['inf-guard'] == 'lower' or params['inf-guard'] == 'both': conditions.append((dataframe['close'] >= dataframe[f"3d_low_{self.inf_timeframe}"] + (params['inf-pct-adr-bot'] * dataframe[f"adr_{self.inf_timeframe}"]))) # Base Timeframe conditions.append( (dataframe['rmi-dn-trend'] == 1) & (dataframe['rmi-slow'] >= params['base-rmi-slow']) & (dataframe['rmi-fast'] <= params['base-rmi-fast']) & (dataframe['mp'] <= params['base-mp'])) # Extra conditions for */BTC and */ETH stakes on additional informative pairs if self.config['stake_currency'] in ('BTC', 'ETH'): conditions.append(( dataframe[f"{self.config['stake_currency']}_rmi"] < params['xtra-base-stake-rmi']) | (dataframe[f"{self.custom_fiat}_rmi"] > params['xtra-base-fiat-rmi'])) conditions.append(dataframe[ f"{self.config['stake_currency']}_rmi_{self.inf_timeframe}"] > params['xtra-inf-stake-rmi']) # Extra conditions for BTC/STAKE if not in whitelist else: pairs = self.dp.current_whitelist() btc_stake = f"BTC/{self.config['stake_currency']}" if not btc_stake in pairs: conditions.append( (dataframe['BTC_rmi'] < params['xbtc-base-rmi']) & (dataframe[f"BTC_rmi_{self.inf_timeframe}"] > params['xbtc-inf-rmi'])) # Applies to active or new trades conditions.append(dataframe['volume'].gt(0)) if conditions: dataframe.loc[reduce(lambda x, y: x & y, conditions), 'buy'] = 1 return dataframe
def min_roi_reached_dynamic( self, trade: Trade, current_profit: float, current_time: datetime, trade_dur: int) -> Tuple[Optional[int], Optional[float]]: dynamic_roi = self.get_pair_params(trade.pair, 'dynamic_roi') minimal_roi = self.get_pair_params(trade.pair, 'minimal_roi') if not dynamic_roi or not minimal_roi: return None, None _, table_roi = self.min_roi_reached_entry(trade_dur, trade.pair) # see if we have the data we need to do this, otherwise fall back to the standard table if self.custom_trade_info and trade and trade.pair in self.custom_trade_info: if self.config['runmode'].value in ('live', 'dry_run'): dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=trade.pair, timeframe=self.timeframe) roc = dataframe['roc'].iat[-1] atr = dataframe['atr'].iat[-1] rmi_slow = dataframe['rmi-slow'].iat[-1] rmi_trend = dataframe['rmi-up-trend'].iat[-1] # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!) else: roc = self.custom_trade_info[ trade.pair]['roc'].loc[current_time]['roc'] atr = self.custom_trade_info[ trade.pair]['atr'].loc[current_time]['atr'] rmi_slow = self.custom_trade_info[ trade.pair]['rmi-slow'].loc[current_time]['rmi-slow'] rmi_trend = self.custom_trade_info[trade.pair][ 'rmi-up-trend'].loc[current_time]['rmi-up-trend'] d = dynamic_roi profit_factor = (1 - (rmi_slow / d['profit-factor'])) rmi_grow = cta.linear_growth(d['rmi-start'], d['rmi-end'], d['grow-delay'], d['grow-time'], trade_dur) max_profit = trade.calc_profit_ratio(trade.max_rate) open_rate = trade.open_rate atr_roi = max(d['min-roc-atr'], ((open_rate + atr) / open_rate) - 1) roc_roi = max(d['min-roc-atr'], (roc / 100)) # atr as the fallback (if > min-roc-atr) if d['fallback'] == 'atr': min_roi = atr_roi # roc as the fallback (if > min-roc-atr) elif d['fallback'] == 'roc': min_roi = roc_roi # atr or table as the fallback (whichever is larger) elif d['fallback'] == 'atr-table': min_roi = max(table_roi, atr_roi) # roc or table as the fallback (whichever is larger) elif d['fallback'] == 'roc-table': min_roi = max(table_roi, roc_roi) # default to table else: min_roi = table_roi # If we observe a strong upward trend and our current profit has not retreated from the peak by much, hold if (rmi_trend == 1) and (rmi_slow > rmi_grow): if current_profit > min_roi and (current_profit < (max_profit * profit_factor)): min_roi = min_roi else: min_roi = 100 else: min_roi = table_roi """ # Attempting to wedge the dynamic roi value into a thing so we can trick backtesting... if self.config['runmode'].value not in ('live', 'dry_run'): # Theoretically, if backtesting uses this value, ROI was triggered so we need to trick it with a sell # rate other than what is on the standard ROI table... self.custom_trade_info['backtest']['roi'] = max(min_roi, current_profit) """ return trade_dur, min_roi