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
[…] Use ATR for exits (better version) […]
[…] If you are on latest freqtrade, please use this method instead. It stores the info into database, so you have no risk of losing the info in case of bot […]