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 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 trade.is_short:
if (current_candle['close'] <= atr_roi):
return "atr_roi"
if (current_candle['close'] >= atr_sl):
return "atr_sl"
else:
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 […]
Hey, I think there is a typo in the futures code:
>> if (atr_roi is not None):
atr_roi never actually gets calculated, I think it should be
>> if (atr_roi is None):
Plus with the line:
>> if current_profit <= -(self.stoploss * self.lev):
afaik self.stoploss is always negative, so doing another negation will make the bot exit on potentially positive profit
nice catch
ALSO, another suggestion for the futures code:
if atr_roi:
if (current_candle[‘close’] >= atr_roi):
return “atr_roi”
if (current_candle[‘close’] = atr_roi):
return “atr_roi”
if (current_candle[‘close’] <= atr_sl):
return "atr_sl"
if atr_roi and trade.is_short:
if (current_candle['close'] = atr_sl):
return “atr_sl”
to take into account the direction of the trade.
Sorry for spamming, just going along with the article and giving feedback
Oops, code got mangled, I meant that atr_roi/atr_sl returning logic should probably look something like this in order to account for short trades:
if atr_roi and not trade.is_short:
if (current_candle[‘close’] >= atr_roi):
return “atr_roi”
if (current_candle[‘close’] <= atr_sl):
return "atr_sl"
if atr_roi and trade.is_short:
if (current_candle['close'] = atr_sl):
return “atr_sl”
line 57 error here
should be called by line 51