BB_RPB_TSL @author jilv220 Simple bollinger brand strategy inspired by this blog ( https://hacks-for-life.blogspot.com/2020/12/freqtrade-notes.html ) RPB, which stands for Real Pull Back, taken from ( https://github.com/GeorgeMurAlkh/freqtrade-stuff/blob/main/user_data/strategies/TheRealPullbackV2.py ) The trailing custom stoploss taken from BigZ04_TSL from Perkmeister ( modded by ilya ) I modified it to better suit my taste and added Hyperopt for this strategy.
Timeframe
5m
Direction
Long Only
Stoploss
-99.0%
Trailing Stop
No
ROI
0m: 10.0%
Interface Version
N/A
Startup Candles
N/A
Indicators
10
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np
import talib.abstract as ta
import pandas_ta as pta
from typing import Dict, List
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame, Series, DatetimeIndex, merge
from datetime import datetime, timedelta
from freqtrade.strategy import merge_informative_pair, CategoricalParameter, DecimalParameter, IntParameter, stoploss_from_open
from functools import reduce
from technical.indicators import RMI, zema
def EWO(dataframe, ema_length=5, ema2_length=35):
df = dataframe.copy()
ema1 = ta.EMA(df, timeperiod=ema_length)
ema2 = ta.EMA(df, timeperiod=ema2_length)
emadif = (ema1 - ema2) / df['low'] * 100
return emadif
def williams_r(dataframe: DataFrame, period: int = 14) -> Series:
"""Williams %R, or just %R, is a technical analysis oscillator showing the current closing price in relation to the high and low
of the past N days (for a given N). It was developed by a publisher and promoter of trading materials, Larry Williams.
Its purpose is to tell whether a stock or commodity market is trading near the high or the low, or somewhere in between,
of its recent trading range.
The oscillator is on a negative scale, from −100 (lowest) up to 0 (highest).
"""
highest_high = dataframe["high"].rolling(center=False, window=period).max()
lowest_low = dataframe["low"].rolling(center=False, window=period).min()
WR = Series(
(highest_high - dataframe["close"]) / (highest_high - lowest_low),
name=f"{period} Williams %R",
)
return WR * -100
class BB_RPB_TSL_RNG_TBS(IStrategy):
'''
BB_RPB_TSL
@author jilv220
Simple bollinger brand strategy inspired by this blog ( https://hacks-for-life.blogspot.com/2020/12/freqtrade-notes.html )
RPB, which stands for Real Pull Back, taken from ( https://github.com/GeorgeMurAlkh/freqtrade-stuff/blob/main/user_data/strategies/TheRealPullbackV2.py )
The trailing custom stoploss taken from BigZ04_TSL from Perkmeister ( modded by ilya )
I modified it to better suit my taste and added Hyperopt for this strategy.
'''
buy_params = {
"buy_btc_safe": -289,
"buy_btc_safe_1d": -0.05,
"buy_threshold": 0.003,
"buy_bb_factor": 0.999,
"buy_bb_delta": 0.025,
"buy_bb_width": 0.095,
"buy_cci": -116,
"buy_cci_length": 25,
"buy_rmi": 49,
"buy_rmi_length": 17,
"buy_srsi_fk": 32,
"buy_closedelta": 12.148,
"buy_ema_diff": 0.022,
"buy_adx": 20,
"buy_fastd": 20,
"buy_fastk": 22,
"buy_ema_cofi": 0.98,
"buy_ewo_high": 4.179,
"buy_ema_high_2": 1.087,
"buy_ema_low_2": 0.970,
}
sell_params = {
"pHSL": -0.178,
"pPF_1": 0.019,
"pPF_2": 0.065,
"pSL_1": 0.019,
"pSL_2": 0.062,
"sell_btc_safe": -389,
"base_nb_candles_sell": 24,
"high_offset": 0.991,
"high_offset_2": 0.997
}
minimal_roi = {
"0": 0.10,
}
timeframe = '5m'
inf_1h = '1h'
stoploss = -0.99
use_custom_stoploss = True
use_sell_signal = True
process_only_new_candles = True
is_optimize_dip = False
buy_rmi = IntParameter(30, 50, default=35, optimize= is_optimize_dip)
buy_cci = IntParameter(-135, -90, default=-133, optimize= is_optimize_dip)
buy_srsi_fk = IntParameter(30, 50, default=25, optimize= is_optimize_dip)
buy_cci_length = IntParameter(25, 45, default=25, optimize = is_optimize_dip)
buy_rmi_length = IntParameter(8, 20, default=8, optimize = is_optimize_dip)
is_optimize_break = False
buy_bb_width = DecimalParameter(0.05, 0.2, default=0.15, optimize = is_optimize_break)
buy_bb_delta = DecimalParameter(0.025, 0.08, default=0.04, optimize = is_optimize_break)
is_optimize_local_dip = False
buy_ema_diff = DecimalParameter(0.022, 0.027, default=0.025, optimize = is_optimize_local_dip)
buy_bb_factor = DecimalParameter(0.990, 0.999, default=0.995, optimize = False)
buy_closedelta = DecimalParameter(12.0, 18.0, default=15.0, optimize = is_optimize_local_dip)
is_optimize_ewo = False
buy_rsi_fast = IntParameter(35, 50, default=45, optimize = False)
buy_rsi = IntParameter(15, 30, default=35, optimize = False)
buy_ewo = DecimalParameter(-6.0, 5, default=-5.585, optimize = is_optimize_ewo)
buy_ema_low = DecimalParameter(0.9, 0.99, default=0.942 , optimize = is_optimize_ewo)
buy_ema_high = DecimalParameter(0.95, 1.2, default=1.084 , optimize = is_optimize_ewo)
is_optimize_ewo_2 = False
buy_ema_low_2 = DecimalParameter(0.96, 0.978, default=0.96 , optimize = is_optimize_ewo_2)
buy_ema_high_2 = DecimalParameter(1.05, 1.2, default=1.09 , optimize = is_optimize_ewo_2)
is_optimize_cofi = False
buy_ema_cofi = DecimalParameter(0.96, 0.98, default=0.97 , optimize = is_optimize_cofi)
buy_fastk = IntParameter(20, 30, default=20, optimize = is_optimize_cofi)
buy_fastd = IntParameter(20, 30, default=20, optimize = is_optimize_cofi)
buy_adx = IntParameter(20, 30, default=30, optimize = is_optimize_cofi)
buy_ewo_high = DecimalParameter(2, 12, default=3.553, optimize = is_optimize_cofi)
is_optimize_btc_safe = False
buy_btc_safe = IntParameter(-300, 50, default=-200, optimize = is_optimize_btc_safe)
buy_btc_safe_1d = DecimalParameter(-0.075, -0.025, default=-0.05, optimize = is_optimize_btc_safe)
buy_threshold = DecimalParameter(0.003, 0.012, default=0.008, optimize = is_optimize_btc_safe)
buy_is_dip_enabled = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True)
buy_is_break_enabled = CategoricalParameter([True, False], default=True, space='buy', optimize=False, load=True)
sell_btc_safe = IntParameter(-400, -300, default=-365, optimize = True)
base_nb_candles_sell = IntParameter(5, 80, default=sell_params['base_nb_candles_sell'], space='sell', optimize=True)
high_offset = DecimalParameter(0.95, 1.1, default=sell_params['high_offset'], space='sell', optimize=True)
high_offset_2 = DecimalParameter(0.99, 1.5, default=sell_params['high_offset_2'], space='sell', optimize=True)
pHSL = DecimalParameter(-0.200, -0.040, default=-0.08, decimals=3, space='sell', load=True)
pPF_1 = DecimalParameter(0.008, 0.020, default=0.016, decimals=3, space='sell', load=True)
pSL_1 = DecimalParameter(0.008, 0.020, default=0.011, decimals=3, space='sell', load=True)
pPF_2 = DecimalParameter(0.040, 0.100, default=0.080, decimals=3, space='sell', load=True)
pSL_2 = DecimalParameter(0.020, 0.070, default=0.040, decimals=3, space='sell', load=True)
def informative_pairs(self):
pairs = self.dp.current_whitelist()
informative_pairs = [(pair, self.timeframe) for pair in pairs]
if self.config['stake_currency'] in ['USDT','BUSD','USDC','DAI','TUSD','PAX','USD','EUR','GBP']:
btc_info_pair = f"BTC/{self.config['stake_currency']}"
else:
btc_info_pair = "BTC/USDT"
informative_pairs.append((btc_info_pair, self.timeframe))
return informative_pairs
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
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
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
if (sl_profit >= current_profit):
return -0.99
return stoploss_from_open(sl_profit, current_profit)
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
assert self.dp, "DataProvider is required for multiple timeframes."
bollinger2 = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband2'] = bollinger2['lower']
dataframe['bb_middleband2'] = bollinger2['mid']
dataframe['bb_upperband2'] = bollinger2['upper']
bollinger3 = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=3)
dataframe['bb_lowerband3'] = bollinger3['lower']
dataframe['bb_middleband3'] = bollinger3['mid']
dataframe['bb_upperband3'] = bollinger3['upper']
if self.config['stake_currency'] in ['USDT','BUSD','USDC','DAI','TUSD','PAX','USD','EUR','GBP']:
btc_info_pair = f"BTC/{self.config['stake_currency']}"
else:
btc_info_pair = "BTC/USDT"
inf_tf = '5m'
informative = self.dp.get_pair_dataframe(btc_info_pair, timeframe=inf_tf)
informative_past = informative.copy().shift(1) # Get recent BTC info
informative_past_source = (informative_past['open'] + informative_past['close'] + informative_past['high'] + informative_past['low']) / 4 # Get BTC price
informative_threshold = informative_past_source * self.buy_threshold.value # BTC dump n% in 5 min
informative_past_delta = informative_past['close'].shift(1) - informative_past['close'] # should be positive if dump
informative_diff = informative_threshold - informative_past_delta # Need be larger than 0
dataframe['btc_threshold'] = informative_threshold
dataframe['btc_diff'] = informative_diff
informative_past_1d = informative.copy().shift(288)
informative_past_source_1d = (informative_past_1d['open'] + informative_past_1d['close'] + informative_past_1d['high'] + informative_past_1d['low']) / 4
dataframe['btc_5m'] = informative_past_source
dataframe['btc_1d'] = informative_past_source_1d
dataframe['bb_width'] = ((dataframe['bb_upperband2'] - dataframe['bb_lowerband2']) / dataframe['bb_middleband2'])
dataframe['bb_delta'] = ((dataframe['bb_lowerband2'] - dataframe['bb_lowerband3']) / dataframe['bb_lowerband2'])
dataframe['bb_bottom_cross'] = qtpylib.crossed_below(dataframe['close'], dataframe['bb_lowerband3']).astype('int')
for val in self.buy_cci_length.range:
dataframe[f'cci_length_{val}'] = ta.CCI(dataframe, val)
dataframe['cci'] = ta.CCI(dataframe, 26)
dataframe['cci_long'] = ta.CCI(dataframe, 170)
for val in self.buy_rmi_length.range:
dataframe[f'rmi_length_{val}'] = RMI(dataframe, length=val, mom=4)
stoch = ta.STOCHRSI(dataframe, 15, 20, 2, 2)
dataframe['srsi_fk'] = stoch['fastk']
dataframe['srsi_fd'] = stoch['fastd']
dataframe['closedelta'] = (dataframe['close'] - dataframe['close'].shift()).abs()
dataframe['sma_15'] = ta.SMA(dataframe, timeperiod=15)
dataframe['sma_30'] = ta.SMA(dataframe, timeperiod=30)
dataframe['cti'] = pta.cti(dataframe["close"], length=20)
dataframe['ema_8'] = ta.EMA(dataframe, timeperiod=8)
dataframe['ema_12'] = ta.EMA(dataframe, timeperiod=12)
dataframe['ema_13'] = ta.EMA(dataframe, timeperiod=13)
dataframe['ema_16'] = ta.EMA(dataframe, timeperiod=16)
dataframe['ema_26'] = ta.EMA(dataframe, timeperiod=26)
dataframe['hma_50'] = qtpylib.hull_moving_average(dataframe['close'], window=50)
dataframe['ema_100'] = ta.EMA(dataframe, timeperiod=100)
dataframe['sma_9'] = ta.SMA(dataframe, timeperiod=9)
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=4)
dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=20)
dataframe['EWO'] = EWO(dataframe, 50, 200)
stoch_fast = ta.STOCHF(dataframe, 5, 3, 0, 3, 0)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
dataframe['adx'] = ta.ADX(dataframe)
dataframe['r_14'] = williams_r(dataframe, period=14)
dataframe['volume_mean_4'] = dataframe['volume'].rolling(4).mean().shift(1)
for val in self.base_nb_candles_sell.range:
dataframe[f'ma_sell_{val}'] = ta.EMA(dataframe, timeperiod=val)
return dataframe
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions = []
dataframe.loc[:, 'buy_tag'] = ''
if self.buy_is_dip_enabled.value:
is_dip = (
(dataframe[f'rmi_length_{self.buy_rmi_length.value}'] < self.buy_rmi.value) &
(dataframe[f'cci_length_{self.buy_cci_length.value}'] <= self.buy_cci.value) &
(dataframe['srsi_fk'] < self.buy_srsi_fk.value)
)
if self.buy_is_break_enabled.value:
is_break = (
( (dataframe['bb_delta'] > self.buy_bb_delta.value) #"buy_bb_delta": 0.025 0.036
& #"buy_bb_width": 0.095 0.133
(dataframe['bb_width'] > self.buy_bb_width.value)
)
&
(dataframe['closedelta'] > dataframe['close'] * self.buy_closedelta.value / 1000 ) & # from BinH
(dataframe['close'] < dataframe['bb_lowerband3'] * self.buy_bb_factor.value)
)
is_local_uptrend = ( # from NFI next gen
(dataframe['ema_26'] > dataframe['ema_12']) &
(dataframe['ema_26'] - dataframe['ema_12'] > dataframe['open'] * self.buy_ema_diff.value) &
(dataframe['ema_26'].shift() - dataframe['ema_12'].shift() > dataframe['open'] / 100) &
(dataframe['close'] < dataframe['bb_lowerband2'] * self.buy_bb_factor.value) &
(dataframe['closedelta'] > dataframe['close'] * self.buy_closedelta.value / 1000 )
)
is_ewo = ( # from SMA offset
(dataframe['rsi_fast'] < self.buy_rsi_fast.value) &
(dataframe['close'] < dataframe['ema_8'] * self.buy_ema_low.value) &
(dataframe['EWO'] > self.buy_ewo.value) &
(dataframe['close'] < dataframe['ema_16'] * self.buy_ema_high.value) &
(dataframe['rsi'] < self.buy_rsi.value)
)
is_ewo_2 = (
(dataframe['rsi_fast'] < self.buy_rsi_fast.value) &
(dataframe['close'] < dataframe['ema_8'] * self.buy_ema_low_2.value) &
(dataframe['EWO'] > self.buy_ewo_high.value) &
(dataframe['close'] < dataframe['ema_16'] * self.buy_ema_high_2.value) &
(dataframe['rsi'] < self.buy_rsi.value)
)
is_cofi = (
(dataframe['open'] < dataframe['ema_8'] * self.buy_ema_cofi.value) &
(qtpylib.crossed_above(dataframe['fastk'], dataframe['fastd'])) &
(dataframe['fastk'] < self.buy_fastk.value) &
(dataframe['fastd'] < self.buy_fastd.value) &
(dataframe['adx'] > self.buy_adx.value) &
(dataframe['EWO'] > self.buy_ewo_high.value)
)
is_nfi_32 = (
(dataframe['rsi_slow'] < dataframe['rsi_slow'].shift(1)) &
(dataframe['rsi_fast'] < 46) &
(dataframe['rsi'] > 19) &
(dataframe['close'] < dataframe['sma_15'] * 0.942) &
(dataframe['cti'] < -0.86)
)
is_nfi_33 = (
(dataframe['close'] < (dataframe['ema_13'] * 0.978)) &
(dataframe['EWO'] > 8) &
(dataframe['cti'] < -0.88) &
(dataframe['rsi'] < 32) &
(dataframe['r_14'] < -98.0) &
(dataframe['volume'] < (dataframe['volume_mean_4'] * 2.5))
)
is_BB_checked = is_dip & is_break
conditions.append(is_BB_checked) # ~1.7 89%
dataframe.loc[is_BB_checked, 'buy_tag'] += 'bb '
conditions.append(is_local_uptrend) # ~3.84 90.2%
dataframe.loc[is_local_uptrend, 'buy_tag'] += 'local uptrend '
conditions.append(is_ewo) # ~2.26 93.5%
dataframe.loc[is_ewo, 'buy_tag'] += 'ewo '
conditions.append(is_ewo_2) # ~3.68 90.3%
dataframe.loc[is_ewo_2, 'buy_tag'] += 'ewo2 '
conditions.append(is_cofi) # ~3.21 90.8%
dataframe.loc[is_cofi, 'buy_tag'] += 'cofi '
conditions.append(is_nfi_32) # ~2.43 91.3%
dataframe.loc[is_nfi_32, 'buy_tag'] += 'nfi 32 '
conditions.append(is_nfi_33) # ~0.11 100%
dataframe.loc[is_nfi_33, 'buy_tag'] += 'nfi 33 '
if conditions:
dataframe.loc[reduce(lambda x, y: x | y, conditions), 'buy' ] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions = []
conditions.append(
(
(dataframe['close'] > dataframe['sma_9'])&
(dataframe['close'] > (dataframe[f'ma_sell_{self.base_nb_candles_sell.value}'] * self.high_offset_2.value)) &
(dataframe['rsi']>50)&
(dataframe['volume'] > 0)&
(dataframe['rsi_fast'] > dataframe['rsi_slow'])
)
|
(
(dataframe['sma_9'] > (dataframe['sma_9'].shift(1) + dataframe['sma_9'].shift(1)*0.005 )) &
(dataframe['close'] < dataframe['hma_50'])&
(dataframe['close'] > (dataframe[f'ma_sell_{self.base_nb_candles_sell.value}'] * self.high_offset.value)) &
(dataframe['volume'] > 0)&
(dataframe['rsi_fast']>dataframe['rsi_slow'])
)
)
if conditions:
dataframe.loc[
reduce(lambda x, y: x | y, conditions),
'sell'
]=1
return dataframe
class TrailingBuyStrat2(BB_RPB_TSL_RNG_TBS):
process_only_new_candles = True
custom_info_trail_buy = dict()
trailing_buy_order_enabled = True
trailing_expire_seconds = 1800
trailing_buy_uptrend_enabled = False
trailing_expire_seconds_uptrend = 90
min_uptrend_trailing_profit = 0.02
debug_mode = True
trailing_buy_max_stop = 0.02 # stop trailing buy if current_price > starting_price * (1+trailing_buy_max_stop)
trailing_buy_max_buy = 0.000 # buy if price between uplimit (=min of serie (current_price * (1 + trailing_buy_offset())) and (start_price * 1+trailing_buy_max_buy))
init_trailing_dict = {
'trailing_buy_order_started': False,
'trailing_buy_order_uplimit': 0,
'start_trailing_price': 0,
'buy_tag': None,
'start_trailing_time': None,
'offset': 0,
'allow_trailing': False,
}
def trailing_buy(self, pair, reinit=False):
if not pair in self.custom_info_trail_buy:
self.custom_info_trail_buy[pair] = dict()
if (reinit or not 'trailing_buy' in self.custom_info_trail_buy[pair]):
self.custom_info_trail_buy[pair]['trailing_buy'] = self.init_trailing_dict.copy()
return self.custom_info_trail_buy[pair]['trailing_buy']
def trailing_buy_info(self, pair: str, current_price: float):
current_time = datetime.now(timezone.utc)
if not self.debug_mode:
return
trailing_buy = self.trailing_buy(pair)
duration = 0
try:
duration = (current_time - trailing_buy['start_trailing_time'])
except TypeError:
duration = 0
finally:
logger.info(
f"pair: {pair} : "
f"start: {trailing_buy['start_trailing_price']:.4f}, "
f"duration: {duration}, "
f"current: {current_price:.4f}, "
f"uplimit: {trailing_buy['trailing_buy_order_uplimit']:.4f}, "
f"profit: {self.current_trailing_profit_ratio(pair, current_price)*100:.2f}%, "
f"offset: {trailing_buy['offset']}")
def current_trailing_profit_ratio(self, pair: str, current_price: float) -> float:
trailing_buy = self.trailing_buy(pair)
if trailing_buy['trailing_buy_order_started']:
return (trailing_buy['start_trailing_price'] - current_price) / trailing_buy['start_trailing_price']
else:
return 0
def trailing_buy_offset(self, dataframe, pair: str, current_price: float):
current_trailing_profit_ratio = self.current_trailing_profit_ratio(pair, current_price)
default_offset = 0.005
trailing_buy = self.trailing_buy(pair)
if not trailing_buy['trailing_buy_order_started']:
return default_offset
last_candle = dataframe.iloc[-1]
current_time = datetime.now(timezone.utc)
trailing_duration = current_time - trailing_buy['start_trailing_time']
if trailing_duration.total_seconds() > self.trailing_expire_seconds:
if ((current_trailing_profit_ratio > 0) and (last_candle['buy'] == 1)):
return 'forcebuy'
else:
return None
elif (self.trailing_buy_uptrend_enabled and (trailing_duration.total_seconds() < self.trailing_expire_seconds_uptrend) and (current_trailing_profit_ratio < (-1 * self.min_uptrend_trailing_profit))):
return 'forcebuy'
if current_trailing_profit_ratio < 0:
return default_offset
trailing_buy_offset = {
0.06: 0.02,
0.03: 0.01,
0: default_offset,
}
for key in trailing_buy_offset:
if current_trailing_profit_ratio > key:
return trailing_buy_offset[key]
return default_offset
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe = super().populate_indicators(dataframe, metadata)
self.trailing_buy(metadata['pair'])
return dataframe
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, **kwargs) -> bool:
val = super().confirm_trade_entry(pair, order_type, amount, rate, time_in_force, **kwargs)
if val:
if self.trailing_buy_order_enabled and self.config['runmode'].value in ('live', 'dry_run'):
val = False
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if(len(dataframe) >= 1):
last_candle = dataframe.iloc[-1].squeeze()
current_price = rate
trailing_buy = self.trailing_buy(pair)
trailing_buy_offset = self.trailing_buy_offset(dataframe, pair, current_price)
if trailing_buy['allow_trailing']:
if (not trailing_buy['trailing_buy_order_started'] and (last_candle['buy'] == 1)):
trailing_buy['trailing_buy_order_started'] = True
trailing_buy['trailing_buy_order_uplimit'] = last_candle['close']
trailing_buy['start_trailing_price'] = last_candle['close']
trailing_buy['buy_tag'] = last_candle['buy_tag']
trailing_buy['start_trailing_time'] = datetime.now(timezone.utc)
trailing_buy['offset'] = 0
self.trailing_buy_info(pair, current_price)
logger.info(f'start trailing buy for {pair} at {last_candle["close"]}')
elif trailing_buy['trailing_buy_order_started']:
if trailing_buy_offset == 'forcebuy':
val = True
ratio = "%.2f" % ((self.current_trailing_profit_ratio(pair, current_price)) * 100)
self.trailing_buy_info(pair, current_price)
logger.info(f"price OK for {pair} ({ratio} %, {current_price}), order may not be triggered if all slots are full")
elif trailing_buy_offset is None:
self.trailing_buy(pair, reinit=True)
logger.info(f'STOP trailing buy for {pair} because "trailing buy offset" returned None')
elif current_price < trailing_buy['trailing_buy_order_uplimit']:
old_uplimit = trailing_buy["trailing_buy_order_uplimit"]
self.custom_info_trail_buy[pair]['trailing_buy']['trailing_buy_order_uplimit'] = min(current_price * (1 + trailing_buy_offset), self.custom_info_trail_buy[pair]['trailing_buy']['trailing_buy_order_uplimit'])
self.custom_info_trail_buy[pair]['trailing_buy']['offset'] = trailing_buy_offset
self.trailing_buy_info(pair, current_price)
logger.info(f'update trailing buy for {pair} at {old_uplimit} -> {self.custom_info_trail_buy[pair]["trailing_buy"]["trailing_buy_order_uplimit"]}')
elif current_price < (trailing_buy['start_trailing_price'] * (1 + self.trailing_buy_max_buy)):
val = True
ratio = "%.2f" % ((self.current_trailing_profit_ratio(pair, current_price)) * 100)
self.trailing_buy_info(pair, current_price)
logger.info(f"current price ({current_price}) > uplimit ({trailing_buy['trailing_buy_order_uplimit']}) and lower than starting price price ({(trailing_buy['start_trailing_price'] * (1 + self.trailing_buy_max_buy))}). OK for {pair} ({ratio} %), order may not be triggered if all slots are full")
elif current_price > (trailing_buy['start_trailing_price'] * (1 + self.trailing_buy_max_stop)):
self.trailing_buy(pair, reinit=True)
self.trailing_buy_info(pair, current_price)
logger.info(f'STOP trailing buy for {pair} because of the price is higher than starting price * {1 + self.trailing_buy_max_stop}')
else:
self.trailing_buy_info(pair, current_price)
logger.info(f'price too high for {pair} !')
else:
logger.info(f"Wait for next buy signal for {pair}")
if (val == True):
self.trailing_buy_info(pair, rate)
self.trailing_buy(pair, reinit=True)
logger.info(f'STOP trailing buy for {pair} because I buy it')
return val
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe = super().populate_buy_trend(dataframe, metadata)
if self.trailing_buy_order_enabled and self.config['runmode'].value in ('live', 'dry_run'):
last_candle = dataframe.iloc[-1].squeeze()
trailing_buy = self.trailing_buy(metadata['pair'])
if (last_candle['buy'] == 1):
if not trailing_buy['trailing_buy_order_started']:
open_trades = Trade.get_trades([Trade.pair == metadata['pair'], Trade.is_open.is_(True), ]).all()
if not open_trades:
logger.info(f"Set 'allow_trailing' to True for {metadata['pair']} to start trailing!!!")
trailing_buy['allow_trailing'] = True
initial_buy_tag = last_candle['buy_tag'] if 'buy_tag' in last_candle else 'buy signal'
dataframe.loc[:, 'buy_tag'] = f"{initial_buy_tag} (start trail price {last_candle['close']})"
else:
if (trailing_buy['trailing_buy_order_started'] == True):
logger.info(f"Continue trailing for {metadata['pair']}. Manually trigger buy signal!!")
dataframe.loc[:,'buy'] = 1
dataframe.loc[:, 'buy_tag'] = trailing_buy['buy_tag']
return dataframe