idea: Relative Average Distance: Calculate distance of price from its simple moving average. Measure mean reversion by applying RSI formula to price differences. Go long when RAD < 25 short when > 75
Timeframe
30m
Direction
Long Only
Stoploss
-50.0%
Trailing Stop
No
ROI
0m: 39.7%, 90m: 7.6%, 258m: 5.5%, 791m: 10.0%
Interface Version
N/A
Startup Candles
35
Indicators
4
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
import numpy as np
# --------------------------------
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
# need these to block pairs for x time
from freqtrade.persistence import Trade
from datetime import timedelta, datetime, timezone, time
class rad_testing(IStrategy):
"""
idea:
Relative Average Distance: Calculate distance of price from its simple moving average.
Measure mean reversion by applying RSI formula to price differences.
Go long when RAD < 25 short when > 75
Base on article from Sofien Kaabar:
https://kaabar-sofien.medium.com/the-relative-average-distance-a-new-mean-reversion-trading-indicator-c21f8f1986e3
"""
# Optimal timeframe for the strategy.
timeframe = '30m'
# Create custom dictionary
custom_info = {}
# ROI table:
minimal_roi = {
"0": 0.39724,
"90": 0.07604,
"258": 0.05495,
"791": 0.1
}
# Stoploss:
stoploss = -0.50
# Trailing stop:
trailing_stop = False
process_only_new_candles = True
use_custom_stoploss = True
sell_profit_only = False
startup_candle_count = 35
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float:
if self.custom_info and pair in self.custom_info and trade:
# using current_time directly (like below) will only work in backtesting.
# so check "runmode" to make sure that it's only used in backtesting/hyperopt
if self.dp and self.dp.runmode.value in ('backtest', 'hyperopt'):
relative_sl = self.custom_info[pair].loc[current_time]['atr_ema']
# in live / dry-run, it'll be really the current time
else:
# but we can just use the last entry from an already analyzed dataframe instead
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
# WARNING
# only use .iat[-1] in live mode, not in backtesting/hyperopt
# otherwise you will look into the future
# see: https://www.freqtrade.io/en/latest/strategy-customization/#common-mistakes-when-developing-strategies
relative_sl = dataframe['atr_ema'].iat[-1]
# set stoploss using atr based on current profit levels
# tighten stop as profit increases to lock in gains
if current_profit >= 0.07:
return ((current_rate - relative_sl *1) / current_rate) - 1
if current_profit >= 0.03:
return ((current_rate - relative_sl *2) / current_rate) - 1
if current_profit >= 0.01:
return ((current_rate - relative_sl *3) / current_rate) - 1
return ((current_rate - relative_sl *5) / current_rate) - 1
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# lock pairs if they lost in the last x days
if self.config['runmode'].value in ('live', 'dry_run'):
# fetch closed trades
trades = Trade.get_trades([Trade.pair == metadata['pair'],
Trade.open_date > datetime.utcnow() - timedelta(days=1),
Trade.is_open.is_(False),
]).all()
# Analyze the conditions you'd like to lock the pair ....
sumprofit = sum(trade.close_profit for trade in trades)
if sumprofit < 0:
# Lock pair for
self.lock_pair(metadata['pair'], until=datetime.now(timezone.utc) + timedelta(hours=6))
# main trigger - get relative average distance
dataframe['sma'] = ta.SMA(dataframe, timeperiod=8)
dataframe['price_dif'] = dataframe['close'] - dataframe['sma']
dataframe['rad'] = ta.RSI(dataframe['price_dif'], timeperiod=5)
# atr for stoploss calculations
dataframe['atr'] = ta.ATR(dataframe, timeperiod=8)
dataframe['atr_ema'] = ta.EMA(dataframe['atr'], timeperiod=5)
# confirm signal with sma of slower version of rad?
# using cross for entry gives less profits
dataframe['sma_slow'] = ta.SMA(dataframe, timeperiod=13)
dataframe['slow_dif'] = dataframe['close'] - dataframe['sma_slow']
dataframe['slow_rad'] = ta.SMA(ta.RSI(dataframe['slow_dif'], timeperiod=5),timeperiod=8)
#if self.dp.runmode.value in ('backtest', 'hyperopt'):
# add indicator mapped to correct DatetimeIndex to custom_info
self.custom_info[metadata['pair']] = dataframe[['date', 'atr','atr_ema']].copy().set_index('date')
return dataframe
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
# (
# (qtpylib.crossed_above(dataframe['rad'], dataframe['slow_rad'])) &
# (dataframe['rad'].shift(1) < 30) )
# |
# (
# (qtpylib.crossed_above(dataframe['rad'], dataframe['slow_rad'])) &
# (dataframe['rad'].shift(2) < 30)
# )
# |
(
(qtpylib.crossed_below(dataframe['rad'], 23)) &
(dataframe['rad'].shift(1) > 23) &
(dataframe['rad'].shift(2) > 23) &
(dataframe['sma'] > dataframe['close'])
)
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
(
(qtpylib.crossed_below(dataframe['rad'], dataframe['slow_rad'])) &
(dataframe['rad'].shift(1) > 60)
)
# |
# (
# (qtpylib.crossed_above(dataframe['rad'], 80)) &
# (dataframe['rad'].shift(1) < 80)
# )
# | # trying closing out through midnight est? avoid overnight selloffs?
# (
# (dataframe['date'].dt.time >= time(2,0)) &
# (dataframe['date'].dt.time <= time(5,0)) &
# (dataframe['close'] < dataframe['close'].shift(1)) &
# (dataframe['close'].shift(1) < dataframe['close'].shift(2))
# )
),
'sell'] = 1
return dataframe