strategy sponsored by user BinH from slack
Timeframe
5m
Direction
Long & Short
Stoploss
-99.0%
Trailing Stop
No
ROI
0m: 10000.0%
Interface Version
N/A
Startup Candles
240
Indicators
6
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
import logging
from datetime import datetime
from functools import reduce
import numpy as np
from freqtrade.strategy import IntParameter, DecimalParameter, CategoricalParameter, informative
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame, Series
import talib.abstract as ta
import numpy # noqa
logger = logging.getLogger(__name__)
# custom indicators
# ##################################################################################################
def RMI(dataframe, *, length=20, mom=5):
"""
Source: https://github.com/freqtrade/technical/blob/master/technical/indicators/indicators.py#L912
"""
df = dataframe.copy()
df['maxup'] = (df['close'] - df['close'].shift(mom)).clip(lower=0)
df['maxdown'] = (df['close'].shift(mom) - df['close']).clip(lower=0)
df.fillna(0, inplace=True)
df["emaInc"] = ta.EMA(df, price='maxup', timeperiod=length)
df["emaDec"] = ta.EMA(df, price='maxdown', timeperiod=length)
df['RMI'] = np.where(df['emaDec'] == 0, 0, 100 - 100 / (1 + df["emaInc"] / df["emaDec"]))
return df["RMI"]
def zema(dataframe, period, field='close'):
"""
Source: https://github.com/freqtrade/technical/blob/master/technical/indicators/overlap_studies.py#L79
Modified slightly to use ta.EMA instead of technical ema
"""
df = dataframe.copy()
df['ema1'] = ta.EMA(df[field], timeperiod=period)
df['ema2'] = ta.EMA(df['ema1'], timeperiod=period)
df['d'] = df['ema1'] - df['ema2']
df['zema'] = df['ema1'] + df['d']
return df['zema']
def same_length(bigger, shorter):
return np.concatenate((np.full((bigger.shape[0] - shorter.shape[0]), np.nan), shorter))
def mastreak(dataframe: DataFrame, period: int = 4, field='close') -> Series:
"""
MA Streak
Port of: https://www.tradingview.com/script/Yq1z7cIv-MA-Streak-Can-Show-When-a-Run-Is-Getting-Long-in-the-Tooth/
"""
df = dataframe.copy()
avgval = zema(df, period, field)
arr = np.diff(avgval)
pos = np.clip(arr, 0, 1).astype(bool).cumsum()
neg = np.clip(arr, -1, 0).astype(bool).cumsum()
streak = np.where(arr >= 0, pos - np.maximum.accumulate(np.where(arr <= 0, pos, 0)),
-neg + np.maximum.accumulate(np.where(arr >= 0, neg, 0)))
res = same_length(df['close'], streak)
return res
def linear_growth(start: float, end: float, start_time: int, end_time: int, trade_time: int) -> float:
"""
Simple linear growth function. Grows from start to end after end_time minutes (starts after start_time minutes)
"""
time = max(0, trade_time - start_time)
rate = (end - start) / (end_time - start_time)
return min(end, start + (rate * time))
def pcc(dataframe: DataFrame, period: int = 20, mult: int = 2):
"""
Percent Change Channel
PCC is like KC unless it uses percentage changes in price to set channel distance.
https://www.tradingview.com/script/6wwAWXA1-MA-Streak-Change-Channel/
"""
df = dataframe.copy()
df['previous_close'] = df['close'].shift()
df['close_change'] = (df['close'] - df['previous_close']) / df['previous_close'] * 100
df['high_change'] = (df['high'] - df['close']) / df['close'] * 100
df['low_change'] = (df['low'] - df['close']) / df['close'] * 100
df['delta'] = df['high_change'] - df['low_change']
mid = zema(df, period, 'close_change')
rangema = zema(df, period, 'delta')
upper = mid + rangema * mult
lower = mid - rangema * mult
return upper, rangema, lower
def SSLChannels_ATR(dataframe, length=7):
"""
SSL Channels with ATR: https://www.tradingview.com/script/SKHqWzql-SSL-ATR-channel/
Credit to @JimmyNixx for python
"""
df = dataframe.copy()
df['ATR'] = ta.ATR(df, timeperiod=14)
df['smaHigh'] = df['high'].rolling(length).mean() + df['ATR']
df['smaLow'] = df['low'].rolling(length).mean() - df['ATR']
df['hlv'] = np.where(df['close'] > df['smaHigh'], 1, np.where(df['close'] < df['smaLow'], -1, np.NAN))
df['hlv'] = df['hlv'].ffill()
df['sslDown'] = np.where(df['hlv'] < 0, df['smaHigh'], df['smaLow'])
df['sslUp'] = np.where(df['hlv'] < 0, df['smaLow'], df['smaHigh'])
return df['sslDown'], df['sslUp']
def SROC(dataframe, roclen=21, emalen=13, smooth=21):
df = dataframe.copy()
roc = ta.ROC(df, timeperiod=roclen)
ema = ta.EMA(df, timeperiod=emalen)
sroc = ta.ROC(ema, timeperiod=smooth)
return sroc
def linear_decay(start: float, end: float, start_time: int, end_time: int, trade_time: int) -> float:
"""
Simple linear decay function. Decays from start to end after end_time minutes (starts after start_time minutes)
"""
time = max(0, trade_time - start_time)
rate = (start - end) / (end_time - start_time)
return max(end, start - (rate * time))
# #####################################################################################################
class BinHV27_short(IStrategy):
"""
strategy sponsored by user BinH from slack
"""
minimal_roi = {
"0": 100
}
stoploss = -0.99
timeframe = '5m'
process_only_new_candles = False
startup_candle_count = 240
# default False
use_custom_stoploss = True
custom_trade_info = {}
can_short = True
order_types = {
'entry': 'market',
'exit': 'market',
'emergency_exit': 'market',
'force_entry': 'market',
'force_exit': "market",
'stoploss': 'market',
'stoploss_on_exchange': False,
'stoploss_on_exchange_interval': 60,
'stoploss_on_exchange_limit_ratio': 0.99
}
# buy params
buy_optimize = True
buy_adx1 = IntParameter(low=10, high=100, default=25, space='buy', optimize=buy_optimize)
buy_emarsi1 = IntParameter(low=10, high=100, default=20, space='buy', optimize=buy_optimize)
buy_adx2 = IntParameter(low=20, high=100, default=30, space='buy', optimize=buy_optimize)
buy_emarsi2 = IntParameter(low=20, high=100, default=20, space='buy', optimize=buy_optimize)
buy_adx3 = IntParameter(low=10, high=100, default=35, space='buy', optimize=buy_optimize)
buy_emarsi3 = IntParameter(low=10, high=100, default=20, space='buy', optimize=buy_optimize)
buy_adx4 = IntParameter(low=20, high=100, default=30, space='buy', optimize=buy_optimize)
buy_emarsi4 = IntParameter(low=20, high=100, default=25, space='buy', optimize=buy_optimize)
leverage_optimize = True
leverage_num = IntParameter(low=1, high=10, default=1, space='buy', optimize=leverage_optimize)
protect_optimize = True
cooldown_lookback = IntParameter(1, 240, default=5, space="protection", optimize=protect_optimize)
max_drawdown_lookback = IntParameter(1, 288, default=12, space="protection", optimize=protect_optimize)
max_drawdown_trade_limit = IntParameter(1, 20, default=5, space="protection", optimize=protect_optimize)
max_drawdown_stop_duration = IntParameter(1, 288, default=12, space="protection", optimize=protect_optimize)
max_allowed_drawdown = DecimalParameter(0.10, 0.50, default=0.20, decimals=2, space="protection",
optimize=protect_optimize)
stoploss_guard_lookback = IntParameter(1, 288, default=12, space="protection", optimize=protect_optimize)
stoploss_guard_trade_limit = IntParameter(1, 20, default=3, space="protection", optimize=protect_optimize)
stoploss_guard_stop_duration = IntParameter(1, 288, default=12, space="protection", optimize=protect_optimize)
# custom exit
ce_op = True
csell_pullback_amount = DecimalParameter(0.005, 0.15, default=0.01, space='sell', load=True, optimize=ce_op)
csell_roi_type = CategoricalParameter(['static', 'decay', 'step'], default='step', space='sell', load=True,
optimize=ce_op)
csell_roi_start = DecimalParameter(0.01, 0.15, default=0.01, space='sell', load=True, optimize=ce_op)
csell_roi_end = DecimalParameter(0.0, 0.01, default=0, space='sell', load=True, optimize=ce_op)
csell_roi_time = IntParameter(720, 1440, default=720, space='sell', load=True, optimize=ce_op)
csell_trend_type = CategoricalParameter(['rmi', 'ssl', 'candle', 'any', 'none'], default='any', space='sell',
load=True, optimize=ce_op)
csell_pullback = CategoricalParameter([True, False], default=True, space='sell', load=True, optimize=ce_op)
csell_pullback_respect_roi = CategoricalParameter([True, False], default=False, space='sell', load=True,
optimize=ce_op)
csell_endtrend_respect_roi = CategoricalParameter([True, False], default=False, space='sell', load=True,
optimize=ce_op)
# Custom Stoploss
cs_op = True
cstop_loss_threshold = DecimalParameter(-0.35, -0.01, default=-0.03, space='sell', load=True, optimize=cs_op)
cstop_bail_how = CategoricalParameter(['roc', 'time', 'any', 'none'], default='none', space='sell', load=True,
optimize=cs_op)
cstop_bail_roc = DecimalParameter(-5.0, -1.0, default=-3.0, space='sell', load=True, optimize=cs_op)
cstop_bail_time = IntParameter(60, 1440, default=720, space='sell', load=True, optimize=cs_op)
cstop_bail_time_trend = CategoricalParameter([True, False], default=True, space='sell', load=True, optimize=cs_op)
cstop_max_stoploss = DecimalParameter(-0.30, -0.01, default=-0.10, space='sell', load=True, optimize=cs_op)
# # Protection hyperspace params:
# protection_params = {
# "cooldown_lookback": 5,
# "max_drawdown_lookback": 12,
# "max_drawdown_trade_limit": 5,
# "max_drawdown_stop_duration": 12,
# "max_allowed_drawdown": 0.2,
# "stoploss_guard_lookback": 12,
# "stoploss_guard_trade_limit": 3,
# "stoploss_guard_stop_duration": 12
# }
#
# @property
# def protections(self):
# return [
# {
# "method": "CooldownPeriod",
# "stop_duration_candles": self.cooldown_lookback.value
# },
# {
# "method": "MaxDrawdown",
# "lookback_period_candles": self.max_drawdown_lookback.value,
# "trade_limit": self.max_drawdown_trade_limit.value,
# "stop_duration_candles": self.max_drawdown_stop_duration.value,
# "max_allowed_drawdown": self.max_allowed_drawdown.value
# },
# {
# "method": "StoplossGuard",
# "lookback_period_candles": self.stoploss_guard_lookback.value,
# "trade_limit": self.stoploss_guard_trade_limit.value,
# "stop_duration_candles": self.stoploss_guard_stop_duration.value,
# "only_per_pair": False
# }
# ]
@informative('1d', 'BTC/USDT')
def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['ema90'] = ta.EMA(dataframe, timeperiod=90)
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
if not metadata['pair'] in self.custom_trade_info:
self.custom_trade_info[metadata['pair']] = {}
if 'had-trend' not in self.custom_trade_info[metadata["pair"]]:
self.custom_trade_info[metadata['pair']]['had-trend'] = False
dataframe['rsi'] = numpy.nan_to_num(ta.RSI(dataframe, timeperiod=5))
rsiframe = DataFrame(dataframe['rsi']).rename(columns={'rsi': 'close'})
dataframe['emarsi'] = numpy.nan_to_num(ta.EMA(rsiframe, timeperiod=5))
dataframe['adx'] = numpy.nan_to_num(ta.ADX(dataframe))
dataframe['minusdi'] = numpy.nan_to_num(ta.MINUS_DI(dataframe))
minusdiframe = DataFrame(dataframe['minusdi']).rename(columns={'minusdi': 'close'})
dataframe['minusdiema'] = numpy.nan_to_num(ta.EMA(minusdiframe, timeperiod=25))
dataframe['plusdi'] = numpy.nan_to_num(ta.PLUS_DI(dataframe))
plusdiframe = DataFrame(dataframe['plusdi']).rename(columns={'plusdi': 'close'})
dataframe['plusdiema'] = numpy.nan_to_num(ta.EMA(plusdiframe, timeperiod=5))
dataframe['lowsma'] = numpy.nan_to_num(ta.EMA(dataframe, timeperiod=60))
dataframe['highsma'] = numpy.nan_to_num(ta.EMA(dataframe, timeperiod=120))
dataframe['fastsma'] = numpy.nan_to_num(ta.SMA(dataframe, timeperiod=120))
dataframe['slowsma'] = numpy.nan_to_num(ta.SMA(dataframe, timeperiod=240))
dataframe['bigup'] = dataframe['fastsma'].gt(dataframe['slowsma']) & (
(dataframe['fastsma'] - dataframe['slowsma']) > dataframe['close'] / 300)
dataframe['bigdown'] = ~dataframe['bigup']
dataframe['trend'] = dataframe['fastsma'] - dataframe['slowsma']
dataframe['preparechangetrend'] = dataframe['trend'].gt(dataframe['trend'].shift())
dataframe['preparechangetrendconfirm'] = dataframe['preparechangetrend'] & dataframe['trend'].shift().gt(
dataframe['trend'].shift(2))
dataframe['continueup'] = dataframe['slowsma'].gt(dataframe['slowsma'].shift()) & dataframe[
'slowsma'].shift().gt(dataframe['slowsma'].shift(2))
dataframe['delta'] = dataframe['fastsma'] - dataframe['fastsma'].shift()
dataframe['slowingdown'] = dataframe['delta'].lt(dataframe['delta'].shift())
dataframe['rmi'] = RMI(dataframe, length=24, mom=5)
dataframe['rmi-up'] = np.where(dataframe['rmi'] >= dataframe['rmi'].shift(), 1, 0)
dataframe['rmi-down-trend'] = np.where(dataframe['rmi-up'].rolling(5).sum() <= 2, 1, 0)
# Indicators used only for ROI and Custom Stoploss
ssldown, sslup = SSLChannels_ATR(dataframe, length=21)
dataframe['sroc'] = SROC(dataframe, roclen=21, emalen=13, smooth=21)
dataframe['ssl-dir'] = np.where(sslup > ssldown, 'up', 'down')
# Trends, Peaks and Crosses
dataframe['candle-up'] = np.where(dataframe['close'] >= dataframe['open'], 1, 0)
dataframe['candle-down-trend'] = np.where(dataframe['candle-up'].rolling(5).sum() <= 2, 1, 0)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions = []
dataframe.loc[:, 'enter_tag'] = ''
buy_1 = (
dataframe['btc_usdt_close_1d'].lt(dataframe['btc_usdt_ema90_1d']) &
dataframe['slowsma'].gt(0) &
dataframe['close'].lt(dataframe['highsma']) &
dataframe['close'].lt(dataframe['lowsma']) &
dataframe['minusdi'].gt(dataframe['minusdiema']) &
dataframe['rsi'].ge(dataframe['rsi'].shift()) &
~dataframe['preparechangetrend'] &
~dataframe['continueup'] &
dataframe['adx'].gt(self.buy_adx1.value) &
dataframe['bigdown'] &
dataframe['emarsi'].le(self.buy_emarsi1.value)
)
buy_2 = (
dataframe['btc_usdt_close_1d'].lt(dataframe['btc_usdt_ema90_1d']) &
dataframe['slowsma'].gt(0) &
dataframe['close'].lt(dataframe['highsma']) &
dataframe['close'].lt(dataframe['lowsma']) &
dataframe['minusdi'].gt(dataframe['minusdiema']) &
dataframe['rsi'].ge(dataframe['rsi'].shift()) &
~dataframe['preparechangetrend'] &
dataframe['continueup'] &
dataframe['adx'].gt(self.buy_adx2.value) &
dataframe['bigdown'] &
dataframe['emarsi'].le(self.buy_emarsi2.value)
)
buy_3 = (
dataframe['btc_usdt_close_1d'].lt(dataframe['btc_usdt_ema90_1d']) &
dataframe['slowsma'].gt(0) &
dataframe['close'].lt(dataframe['highsma']) &
dataframe['close'].lt(dataframe['lowsma']) &
dataframe['minusdi'].gt(dataframe['minusdiema']) &
dataframe['rsi'].ge(dataframe['rsi'].shift()) &
~dataframe['continueup'] &
dataframe['adx'].gt(self.buy_adx3.value) &
dataframe['bigup'] &
dataframe['emarsi'].le(self.buy_emarsi3.value)
)
buy_4 = (
dataframe['btc_usdt_close_1d'].lt(dataframe['btc_usdt_ema90_1d']) &
dataframe['slowsma'].gt(0) &
dataframe['close'].lt(dataframe['highsma']) &
dataframe['close'].lt(dataframe['lowsma']) &
dataframe['minusdi'].gt(dataframe['minusdiema']) &
dataframe['rsi'].ge(dataframe['rsi'].shift()) &
dataframe['continueup'] &
dataframe['adx'].gt(self.buy_adx4.value) &
dataframe['bigup'] &
dataframe['emarsi'].le(self.buy_emarsi4.value)
)
conditions.append(buy_1)
dataframe.loc[buy_1, 'enter_tag'] += 'buy_1'
conditions.append(buy_2)
dataframe.loc[buy_2, 'enter_tag'] += 'buy_2'
conditions.append(buy_3)
dataframe.loc[buy_3, 'enter_tag'] += 'buy_3'
conditions.append(buy_4)
dataframe.loc[buy_4, 'enter_tag'] += 'buy_4'
if conditions:
dataframe.loc[
reduce(lambda x, y: x | y, conditions),
'enter_short'] = 1
dataframe.loc[(), ['enter_long', 'enter_tag']] = (0, 'long_in')
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[(), ['exit_short', 'exit_tag']] = (0, 'short_out')
dataframe.loc[(), ['exit_long', 'exit_tag']] = (0, 'long_out')
return dataframe
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, side: str,
**kwargs) -> float:
return self.leverage_num.value
"""
Custom Stoploss
"""
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float,
current_profit: float, **kwargs) -> float:
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60)
in_trend = self.custom_trade_info[trade.pair]['had-trend']
if current_profit < self.cstop_max_stoploss.value:
return 0.01
# Determine how we sell when we are in a loss
if current_profit < self.cstop_loss_threshold.value:
if self.cstop_bail_how.value == 'roc' or self.cstop_bail_how.value == 'any':
# Dynamic bailout based on rate of change
if last_candle['sroc'] <= self.cstop_bail_roc.value:
return 0.01
if self.cstop_bail_how.value == 'time' or self.cstop_bail_how.value == 'any':
# Dynamic bailout based on time, unless time_trend is true and there is a potential reversal
if trade_dur > self.cstop_bail_time.value:
if self.cstop_bail_time_trend.value and in_trend:
return 1
else:
return 0.01
return 1
"""
Custom Sell
"""
def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
current_profit: float, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60)
max_profit = max(0, trade.calc_profit_ratio(trade.max_rate))
pullback_value = max(0, (max_profit - self.csell_pullback_amount.value))
in_trend = False
# Determine our current ROI point based on the defined type
if self.csell_roi_type.value == 'static':
min_roi = self.csell_roi_start.value
elif self.csell_roi_type.value == 'decay':
min_roi = linear_decay(self.csell_roi_start.value, self.csell_roi_end.value, 0, self.csell_roi_time.value,
trade_dur)
elif self.csell_roi_type.value == 'step':
if trade_dur < self.csell_roi_time.value:
min_roi = self.csell_roi_start.value
else:
min_roi = self.csell_roi_end.value
# Determine if there is a trend
if self.csell_trend_type.value == 'rmi' or self.csell_trend_type.value == 'any':
if last_candle['rmi-down-trend'] == 1:
in_trend = True
if self.csell_trend_type.value == 'ssl' or self.csell_trend_type.value == 'any':
if last_candle['ssl-dir'] == 'down':
in_trend = True
if self.csell_trend_type.value == 'candle' or self.csell_trend_type.value == 'any':
if last_candle['candle-down-trend'] == 1:
in_trend = True
# Don't sell if we are in a trend unless the pullback threshold is met
if in_trend and current_profit > 0:
# Record that we were in a trend for this trade/pair for a more useful sell message later
self.custom_trade_info[trade.pair]['had-trend'] = True
# If pullback is enabled and profit has pulled back allow a sell, maybe
if self.csell_pullback.value and (current_profit <= pullback_value):
if self.csell_pullback_respect_roi.value and current_profit > min_roi:
return 'intrend_pullback_roi'
elif not self.csell_pullback_respect_roi.value:
if current_profit > min_roi:
return 'intrend_pullback_roi'
else:
return 'intrend_pullback_noroi'
# We are in a trend and pullback is disabled or has not happened or various criteria were not met, hold
return None
# If we are not in a trend, just use the roi value
elif not in_trend:
if self.custom_trade_info[trade.pair]['had-trend']:
if current_profit > min_roi:
self.custom_trade_info[trade.pair]['had-trend'] = False
return 'trend_roi'
elif not self.csell_endtrend_respect_roi.value:
self.custom_trade_info[trade.pair]['had-trend'] = False
return 'trend_noroi'
elif current_profit > min_roi:
return 'notrend_roi'
else:
return None