Timeframe
5m
Direction
Long Only
Stoploss
-99.0%
Trailing Stop
Yes
ROI
0m: 14.0%, 37m: 7.8%, 97m: 2.3%, 160m: 0.0%
Interface Version
3
Startup Candles
N/A
Indicators
4
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from pandas import DataFrame
from freqtrade.strategy import IStrategy
from typing import Optional
from datetime import datetime
from freqtrade.persistence import Trade
from datetime import timedelta
from freqtrade.strategy import stoploss_from_open, DecimalParameter, IntParameter
import talib.abstract as ta
import numpy as np
import pandas_ta as pta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import logging
import math
log = logging.getLogger(__name__)
# This strategy leverages the high volatility in the lower timeframes for tokens with high volatility
# ---
# Designed and written by @hextropian (Twitter), a.k.a. as DrWho?#8511 (Discord)
# Use at your own risk - no warranties of success whatsoever.
# Hyperopted parameters for 5m
class VolatilityCatcher(IStrategy):
INTERFACE_VERSION = 3
minimal_roi = {
"0": 0.14,
"37": 0.078,
"97": 0.023,
"160": 0
}
stoploss = -0.99 # Disabled, using custom
use_custom_stoploss = True
# Trailing stop:
trailing_stop = True
trailing_stop_positive = 0.347
trailing_stop_positive_offset = 0.358
trailing_only_offset_is_reached = True
# Optimal timeframe for the strategy.
timeframe = '5m'
# Run "populate_indicators()" only for new candle.
process_only_new_candles = True
# These values can be overridden in the "ask_strategy" section in the config.
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = True
# Number of candles the strategy requires before producing valid signals
startup_candle_count: int = 60
rsi_threshold = IntParameter(low=3, high=60, default=50, space='buy', optimize=True, load=True)
rsi_overbought = IntParameter(low=50, high=98, default=75, space='sell', optimize=True, load=True)
reversal_threshold = DecimalParameter(0.01, 0.5, default=0.1, decimals=2, space='buy', optimize=True, load=True)
# trailing stoploss hyperopt parameters
opt_HSL = False # Optimize?
# hard stoploss profit
pHSL = DecimalParameter(-0.200, -0.040, default=-0.08, decimals=3, space='sell', optimize=opt_HSL, load=True)
# # profit threshold 1, trigger point, SL_1 is used
pPF_1 = DecimalParameter(0.008, 0.020, default=0.016, decimals=3, space='sell', optimize=opt_HSL, load=True)
pSL_1 = DecimalParameter(0.008, 0.020, default=0.011, decimals=3, space='sell', optimize=opt_HSL, load=True)
# profit threshold 2, SL_2 is used
pPF_2 = DecimalParameter(0.040, 0.100, default=0.080, decimals=3, space='sell', optimize=opt_HSL, load=True)
pSL_2 = DecimalParameter(0.020, 0.070, default=0.040, decimals=3, space='sell', optimize=opt_HSL, load=True)
buy_params = {
"reversal_threshold": 0.36,
"rsi_threshold": 10,
}
sell_params = {
"pHSL": -0.189,
"pPF_1": 0.009,
"pPF_2": 0.065,
"pSL_1": 0.009,
"pSL_2": 0.057,
"rsi_overbought": 89,
}
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
# hard stoploss profit
HSL = self.pHSL.value
PF_1 = self.pPF_1.value
SL_1 = self.pSL_1.value
PF_2 = self.pPF_2.value
SL_2 = self.pSL_2.value
# For profits between PF_1 and PF_2 the stoploss (sl_profit) used is linearly interpolated
# between the values of SL_1 and SL_2. For all profits above PL_2 the sl_profit value
# rises linearly with current profit, for profits below PF_1 the hard stoploss profit is used.
if (current_profit > PF_2):
sl_profit = SL_2 + (current_profit - PF_2)
elif (current_profit > PF_1):
sl_profit = SL_1 + ((current_profit - PF_1)*(SL_2 - SL_1)/(PF_2 - PF_1))
else:
sl_profit = HSL
return stoploss_from_open(sl_profit, current_profit)
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
"""
From NostalgiaForInfinityX by iterativ
https://github.com/iterativv/NostalgiaForInfinity
allow force entries and protects against slippage.
"""
if (entry_tag == 'force_entry'):
return True
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if(len(dataframe) < 1):
return True
dataframe = dataframe.iloc[-1].squeeze()
if ((rate > dataframe['close'])):
slippage = ((rate / dataframe['close']) - 1.0)
if slippage < 0.044:
return True
else:
log.warning(
"Cancelling buy for %s due to slippage %s",
pair, slippage
)
return False
return True
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['volma'] = ta.SMA(dataframe['volume'], timeperiod=60)
dataframe['ema5c'] = ta.EMA(dataframe['close'], timeperiod=5)
dataframe['ema4o'] = ta.EMA(dataframe['open'], timeperiod=4)
return dataframe
#################################################################################
## ##
## BUY (Enter) conditions ##
## ##
#################################################################################
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(
(dataframe['close'] > dataframe['open']) & # Current candle is green
(dataframe['close'] < dataframe['bb_middleband']) # and closes below the middle BB
)
&
(
(dataframe['close'].shift(1) < dataframe['open'].shift(1)) # Previous candle is red
&(dataframe['close'].shift(1) <= dataframe['bb_lowerband'].shift(1)) # and closes below the BB
)
&
( # Volume is above its MA for any of the last 3 candles
(dataframe['volume'] >= dataframe['volma'])
| (dataframe['volume'].shift(1) >= dataframe['volma'].shift(1))
| (dataframe['volume'].shift(2) >= dataframe['volma'].shift(2))
)
&
( # Candle marks a reversal on [virtually] the same place
(dataframe['open'] - dataframe['close'].shift(1)).abs() <= self.reversal_threshold.value
)
&
( # Oversold RSI on previous candle
(dataframe['rsi'].shift(1) <= self.rsi_threshold.value)
)
& (dataframe['volume'] > 0)
),
'enter_long'] = 1
return dataframe
#################################################################################
## ##
## SELL (Exit) conditions ##
## ##
#################################################################################
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe['close'].shift(1) > dataframe['open'].shift(1)) & # Previous candle is green
(dataframe['close'].shift(1) >= dataframe['bb_upperband']) & # Above its upperband
(dataframe['rsi'].shift(1) >= self.rsi_overbought.value) &
# Now we look at the current candle:
(dataframe['ema5c'] <= dataframe['ema4o']) & # Lost momentum
(dataframe['volume'] > 0)
),
'exit_long'] = 1
return dataframe