FSD – Better way of using ATR for exits

This is an improved version of this method, but it requires Freqtrade version 2024.3 or later.

This method is simpler than the old one, and have no weakness. Now the atr roi and stoploss will survive any bot restart. You also don’t need to write confirm_trade_entry and confirm_trade_exit functions

def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> Optional[Union[str, bool]]:

		entry_time = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
		dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)

		atr_roi = trade.get_custom_data(key='atr_roi', default=0)
		atr_sl = trade.get_custom_data(key='atr_sl', default=0)
		if (atr_roi == 0):
			# need to set the roi and sl
			signal_time = entry_time - timedelta(minutes=int(self.timeframe_minutes))
			signal_candle = dataframe.loc[dataframe['date'] == signal_time]
			if not signal_candle.empty:
				# make sure we take only a row
				signal_candle = signal_candle.iloc[-1].squeeze()
				
				atr_roi = (signal_candle['close'] + (self.atr_distance * self.risk_reward_ratio * signal_candle['atr']))
				atr_sl = (signal_candle['close'] - (self.atr_distance * signal_candle['atr']))

				trade.set_custom_data(key='atr_roi', value=atr_roi)
				trade.set_custom_data(key='atr_sl', value=atr_sl)

		if (current_time - timedelta(minutes=int(self.timeframe_minutes)) >= entry_time):
			
			current_candle = dataframe.iloc[-1].squeeze()

			if (atr_roi > 0):			
				if (current_candle['close'] >= atr_roi):
					return "atr_roi"

				if (current_candle['close'] <= atr_sl):
					return "atr_sl"

			else:
				# Signal candle not found, use percentage as exits
				current_profit = trade.calc_profit_ratio(current_candle['close'])
				if (current_profit > 0.01):
					return "emergency_roi"
				elif (current_profit < -0.04):
					return "emergency_sl"

		return None

If you are on Futures market, then the snippet should be modified a little bit like this

lev = 10

# ROI before leverage
roi = 0.0025

# Stoploss before leverage
stoploss = -0.001

risk_reward_ratio = 1
atr_distance = 2

timeframe = '15m'
can_short = True
timeframe_minutes = timeframe_to_minutes(timeframe)

# Disable ROI
minimal_roi = {
	"0": 1000
}


def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> Optional[Union[str, bool]]:

	entry_time = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
	cur_time = timeframe_to_prev_date(self.timeframe, current_time)
	dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)

	atr_roi = trade.get_custom_data(key='atr_roi', default=None)
	atr_sl = trade.get_custom_data(key='atr_sl', default=None)

	if (atr_roi is not None):
		signal_time = entry_time - timedelta(minutes=int(self.timeframe_minutes))
		signal_candle = dataframe.loc[dataframe['date'] == signal_time]
		if not signal_candle.empty:
			signal_candle = signal_candle.iloc[-1].squeeze()
			if trade.is_short:
				trade.set_custom_data(key='atr_roi', value=(signal_candle['close'] - (self.atr_distance * self.risk_reward_ratio * signal_candle['atr'])))
				trade.set_custom_data(key='atr_sl', value=(signal_candle['close'] + (self.atr_distance * signal_candle['atr'])))
			else:
				trade.set_custom_data(key='atr_roi', value=(signal_candle['close'] + (self.atr_distance * self.risk_reward_ratio * signal_candle['atr'])))
				trade.set_custom_data(key='atr_sl', value=(signal_candle['close'] - (self.atr_distance * signal_candle['atr'])))
		
		atr_roi = trade.get_custom_data(key='atr_roi', default=None)
		atr_sl = trade.get_custom_data(key='atr_sl', default=None)

	if (cur_time > entry_time):
		current_candle = dataframe.iloc[-1].squeeze()
		
		# use ATR
		if atr_roi:
			if (current_candle['close'] >= atr_roi):
				return "atr_roi"

			if (current_candle['close'] <= atr_sl):
				return "atr_sl"
		# Use simple % roi/SL
		else:
			current_profit = trade.calc_profit_ratio(current_candle['close'])
			if current_profit >= (self.roi * self.lev):
				return "emergency roi"
			if current_profit <= -(self.stoploss * self.lev):
				return "emergency sl"
	return None

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *