ATR Initial Stops with Risk-Reward and Break-Even Adjustments
Summary
This strategy example shows how to set an initial stop from the entry candle’s close minus twice the ATR, then use the distance from entry to that stop as the unit of risk. A configurable risk-reward multiple defines a price threshold for moving the stop to the target level. A separate profit threshold moves the stop to the entry price adjusted for opening and closing fees, aiming to protect capital once the trade has gained enough.
The document supplies implementation code but no backtest results or evidence of performance. Its entry and exit signal functions are explicitly placeholders: the example enters long on every signal evaluation and never generates a regular exit signal. The stop logic also relies on stored candle data and trade timing, so users would need to check those details in their execution environment. The example illustrates stop management rather than a complete, validated trading strategy.
Key ideas
- The initial stop is set two ATRs below the candle close used to calculate it.
- The entry-to-stop distance defines risk, which is multiplied to derive the take-profit threshold.
- After a configurable profit threshold, the stop moves to entry adjusted for trading fees.
- The entry and exit signal functions are placeholders, so the code does not provide a tested entry strategy.
- The document provides no performance results or backtest evidence.
Tags
Full text
# FixedRiskRewardLoss
# FixedRiskRewardLoss
This strategy uses custom_stoploss() to enforce a fixed risk/reward ratio
by first calculating a dynamic initial stoploss via ATR - last negative peak
After that, we caculate that initial risk and multiply it with an risk_reward_ratio
Once this is reached, stoploss is set to it and sell signal is enabled
Also there is a break even ratio. Once this is reached, the stoploss is adjusted to minimize
losses by setting it to the buy rate + fees.
## Source (GPL-3.0)
```python
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# isort: skip_file
# --- Do not remove these libs ---
import numpy as np # noqa
import pandas as pd # noqa
from pandas import DataFrame
from freqtrade.strategy import IStrategy
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from datetime import datetime
from freqtrade.persistence import Trade
import logging
logger = logging.getLogger(__name__)
class FixedRiskRewardLoss(IStrategy):
"""
This strategy uses custom_stoploss() to enforce a fixed risk/reward ratio
by first calculating a dynamic initial stoploss via ATR - last negative peak
After that, we caculate that initial risk and multiply it with an risk_reward_ratio
Once this is reached, stoploss is set to it and sell signal is enabled
Also there is a break even ratio. Once this is reached, the stoploss is adjusted to minimize
losses by setting it to the buy rate + fees.
"""
INTERFACE_VERSION: int = 3
custom_info = {
'risk_reward_ratio': 3.5,
'set_to_break_even_at_profit': 1,
}
use_custom_stoploss = True
stoploss = -0.9
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
"""
custom_stoploss using a risk/reward ratio
"""
result = break_even_sl = takeprofit_sl = -1
custom_info_pair = self.custom_info.get(pair)
if custom_info_pair is not None:
# using current_time/open_date directly via custom_info_pair[trade.open_daten]
# would only work in backtesting/hyperopt.
# in live/dry-run, we have to search for nearest row before it
open_date_mask = custom_info_pair.index.unique().get_loc(trade.open_date_utc, method='ffill')
open_df = custom_info_pair.iloc[open_date_mask]
# trade might be open too long for us to find opening candle
if(len(open_df) != 1):
return -1 # won't update current stoploss
initial_sl_abs = open_df['stoploss_rate']
# calculate initial stoploss at open_date
initial_sl = initial_sl_abs/current_rate-1
# calculate take profit treshold
# by using the initial risk and multiplying it
risk_distance = trade.open_rate-initial_sl_abs
reward_distance = risk_distance*self.custom_info['risk_reward_ratio']
# take_profit tries to lock in profit once price gets over
# risk/reward ratio treshold
take_profit_price_abs = trade.open_rate+reward_distance
# take_profit gets triggerd at this profit
take_profit_pct = take_profit_price_abs/trade.open_rate-1
# break_even tries to set sl at open_rate+fees (0 loss)
break_even_profit_distance = risk_distance*self.custom_info['set_to_break_even_at_profit']
# break_even gets triggerd at this profit
break_even_profit_pct = (break_even_profit_distance+current_rate)/current_rate-1
result = initial_sl
if(current_profit >= break_even_profit_pct):
break_even_sl = (trade.open_rate*(1+trade.fee_open+trade.fee_close) / current_rate)-1
result = break_even_sl
if(current_profit >= take_profit_pct):
takeprofit_sl = take_profit_price_abs/current_rate-1
result = takeprofit_sl
return result
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['atr'] = ta.ATR(dataframe)
dataframe['stoploss_rate'] = dataframe['close']-(dataframe['atr']*2)
self.custom_info[metadata['pair']] = dataframe[['date', 'stoploss_rate']].copy().set_index('date')
# all "normal" indicators:
# e.g.
# dataframe['rsi'] = ta.RSI(dataframe)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Placeholder Strategy: buys when SAR is smaller then candle before
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
# Always buys
dataframe.loc[:, 'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Placeholder Strategy: does nothing
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
# Never sells
dataframe.loc[:, 'exit_long'] = 0
return dataframe
```Shown in full with attribution under the source's licence. Licence: GPL-3.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.