Name changes for strategy

This commit is contained in:
Sam Germain 2021-08-18 06:03:44 -06:00
parent 98fe3e73de
commit e2d5299116
11 changed files with 174 additions and 133 deletions

View File

@ -232,7 +232,12 @@ class Backtesting:
pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist
df_analyzed = self.strategy.advise_sell( df_analyzed = self.strategy.advise_sell(
self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy() self.strategy.advise_buy(
pair_data,
{'pair': pair}
),
{'pair': pair}
).copy()
# Trim startup period from analyzed dataframe # Trim startup period from analyzed dataframe
df_analyzed = trim_dataframe(df_analyzed, self.timerange, df_analyzed = trim_dataframe(df_analyzed, self.timerange,
startup_candles=self.required_startup) startup_candles=self.required_startup)

View File

@ -285,11 +285,13 @@ class Hyperopt:
# Apply parameters # Apply parameters
if HyperoptTools.has_space(self.config, 'buy'): if HyperoptTools.has_space(self.config, 'buy'):
self.backtesting.strategy.advise_buy = ( # type: ignore self.backtesting.strategy.advise_buy = ( # type: ignore
self.custom_hyperopt.buy_strategy_generator(params_dict)) self.custom_hyperopt.buy_strategy_generator(params_dict)
)
if HyperoptTools.has_space(self.config, 'sell'): if HyperoptTools.has_space(self.config, 'sell'):
self.backtesting.strategy.advise_sell = ( # type: ignore self.backtesting.strategy.advise_sell = ( # type: ignore
self.custom_hyperopt.sell_strategy_generator(params_dict)) self.custom_hyperopt.sell_strategy_generator(params_dict)
)
if HyperoptTools.has_space(self.config, 'protection'): if HyperoptTools.has_space(self.config, 'protection'):
for attr_name, attr in self.backtesting.strategy.enumerate_parameters('protection'): for attr_name, attr in self.backtesting.strategy.enumerate_parameters('protection'):

View File

@ -193,18 +193,22 @@ class StrategyResolver(IResolver):
# register temp path with the bot # register temp path with the bot
abs_paths.insert(0, temp.resolve()) abs_paths.insert(0, temp.resolve())
strategy = StrategyResolver._load_object(paths=abs_paths, strategy = StrategyResolver._load_object(
object_name=strategy_name, paths=abs_paths,
add_source=True, object_name=strategy_name,
kwargs={'config': config}, add_source=True,
) kwargs={'config': config},
)
if strategy: if strategy:
strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args)
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args) strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
if any(x == 2 for x in [strategy._populate_fun_len, if any(x == 2 for x in [
strategy._buy_fun_len, strategy._populate_fun_len,
strategy._sell_fun_len]): strategy._buy_fun_len,
strategy._sell_fun_len
]):
strategy.INTERFACE_VERSION = 1 strategy.INTERFACE_VERSION = 1
return strategy return strategy

View File

@ -242,13 +242,13 @@ class IStrategy(ABC, HyperStrategyMixin):
When not implemented by a strategy, returns True (always confirming). When not implemented by a strategy, returns True (always confirming).
:param pair: Pair that's about to be sold. :param pair: Pair for trade that's about to be exited.
:param trade: trade object. :param trade: trade object.
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in quote currency. :param amount: Amount in quote currency.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param sell_reason: Sell reason. :param sell_reason: Exit reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell'] 'sell_signal', 'force_sell', 'emergency_sell']
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
@ -283,15 +283,15 @@ class IStrategy(ABC, HyperStrategyMixin):
def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs) -> Optional[Union[str, bool]]: current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
""" """
Custom sell signal logic indicating that specified position should be sold. Returning a Custom exit signal logic indicating that specified position should be sold. Returning a
string or True from this method is equal to setting sell signal on a candle at specified string or True from this method is equal to setting exit signal on a candle at specified
time. This method is not called when sell signal is set. time. This method is not called when exit signal is set.
This method should be overridden to create sell signals that depend on trade parameters. For This method should be overridden to create exit signals that depend on trade parameters. For
example you could implement a sell relative to the candle when the trade was opened, example you could implement an exit relative to the candle when the trade was opened,
or a custom 1:2 risk-reward ROI. or a custom 1:2 risk-reward ROI.
Custom sell reason max length is 64. Exceeding characters will be removed. Custom exit reason max length is 64. Exceeding characters will be removed.
:param pair: Pair that's currently analyzed :param pair: Pair that's currently analyzed
:param trade: trade object. :param trade: trade object.
@ -299,7 +299,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param current_rate: Rate, calculated based on pricing settings in ask_strategy. :param current_rate: Rate, calculated based on pricing settings in ask_strategy.
:param current_profit: Current profit (as ratio), calculated based on current_rate. :param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return: To execute sell, return a string with custom sell reason or True. Otherwise return :return: To execute exit, return a string with custom sell reason or True. Otherwise return
None or False. None or False.
""" """
return None return None
@ -528,27 +528,34 @@ class IStrategy(ABC, HyperStrategyMixin):
) )
return False, False, None return False, False, None
buy = latest[SignalType.BUY.value] == 1 enter = latest[SignalType.BUY.value] == 1
sell = False exit = False
if SignalType.SELL.value in latest: if SignalType.SELL.value in latest:
sell = latest[SignalType.SELL.value] == 1 exit = latest[SignalType.SELL.value] == 1
buy_tag = latest.get(SignalTagType.BUY_TAG.value, None) buy_tag = latest.get(SignalTagType.BUY_TAG.value, None)
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', logger.debug('trigger: %s (pair=%s) buy=%s sell=%s',
latest['date'], pair, str(buy), str(sell)) latest['date'], pair, str(enter), str(exit))
timeframe_seconds = timeframe_to_seconds(timeframe) timeframe_seconds = timeframe_to_seconds(timeframe)
if self.ignore_expired_candle(latest_date=latest_date, if self.ignore_expired_candle(
current_time=datetime.now(timezone.utc), latest_date=latest_date,
timeframe_seconds=timeframe_seconds, current_time=datetime.now(timezone.utc),
buy=buy): timeframe_seconds=timeframe_seconds,
return False, sell, buy_tag enter=enter
return buy, sell, buy_tag ):
return False, exit, buy_tag
return enter, exit, buy_tag
def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, def ignore_expired_candle(
timeframe_seconds: int, buy: bool): self,
if self.ignore_buying_expired_candle_after and buy: latest_date: datetime,
current_time: datetime,
timeframe_seconds: int,
enter: bool
):
if self.ignore_buying_expired_candle_after and enter:
time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds)) time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds))
return time_delta.total_seconds() > self.ignore_buying_expired_candle_after return time_delta.total_seconds() > self.ignore_buying_expired_candle_after
else: else:
@ -559,7 +566,7 @@ class IStrategy(ABC, HyperStrategyMixin):
force_stoploss: float = 0) -> SellCheckTuple: force_stoploss: float = 0) -> SellCheckTuple:
""" """
This function evaluates if one of the conditions required to trigger a sell This function evaluates if one of the conditions required to trigger a sell
has been reached, which can either be a stop-loss, ROI or sell-signal. has been reached, which can either be a stop-loss, ROI or exit-signal.
:param low: Only used during backtesting to simulate stoploss :param low: Only used during backtesting to simulate stoploss
:param high: Only used during backtesting, to simulate ROI :param high: Only used during backtesting, to simulate ROI
:param force_stoploss: Externally provided stoploss :param force_stoploss: Externally provided stoploss
@ -578,7 +585,7 @@ class IStrategy(ABC, HyperStrategyMixin):
current_rate = high or rate current_rate = high or rate
current_profit = trade.calc_profit_ratio(current_rate) current_profit = trade.calc_profit_ratio(current_rate)
# if buy signal and ignore_roi is set, we don't need to evaluate min_roi. # if enter signal and ignore_roi is set, we don't need to evaluate min_roi.
roi_reached = (not (buy and self.ignore_roi_if_buy_signal) roi_reached = (not (buy and self.ignore_roi_if_buy_signal)
and self.min_roi_reached(trade=trade, current_profit=current_profit, and self.min_roi_reached(trade=trade, current_profit=current_profit,
current_time=date)) current_time=date))
@ -609,12 +616,12 @@ class IStrategy(ABC, HyperStrategyMixin):
custom_reason = custom_reason[:CUSTOM_SELL_MAX_LENGTH] custom_reason = custom_reason[:CUSTOM_SELL_MAX_LENGTH]
else: else:
custom_reason = None custom_reason = None
# TODO: return here if sell-signal should be favored over ROI # TODO: return here if exit-signal should be favored over ROI
# Start evaluations # Start evaluations
# Sequence: # Sequence:
# ROI (if not stoploss) # ROI (if not stoploss)
# Sell-signal # Exit-signal
# Stoploss # Stoploss
if roi_reached and stoplossflag.sell_type != SellType.STOP_LOSS: if roi_reached and stoplossflag.sell_type != SellType.STOP_LOSS:
logger.debug(f"{trade.pair} - Required profit reached. sell_type=SellType.ROI") logger.debug(f"{trade.pair} - Required profit reached. sell_type=SellType.ROI")
@ -632,7 +639,7 @@ class IStrategy(ABC, HyperStrategyMixin):
return stoplossflag return stoplossflag
# This one is noisy, commented out... # This one is noisy, commented out...
# logger.debug(f"{trade.pair} - No sell signal.") # logger.debug(f"{trade.pair} - No exit signal.")
return SellCheckTuple(sell_type=SellType.NONE) return SellCheckTuple(sell_type=SellType.NONE)
def stop_loss_reached(self, current_rate: float, trade: Trade, def stop_loss_reached(self, current_rate: float, trade: Trade,
@ -769,7 +776,8 @@ class IStrategy(ABC, HyperStrategyMixin):
currently traded pair currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
logger.debug(f"Populating buy signals for pair {metadata.get('pair')}.")
logger.debug(f"Populating enter signals for pair {metadata.get('pair')}.")
if self._buy_fun_len == 2: if self._buy_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see " warnings.warn("deprecated - check out the Sample strategy to see "
@ -787,7 +795,8 @@ class IStrategy(ABC, HyperStrategyMixin):
currently traded pair currently traded pair
:return: DataFrame with sell column :return: DataFrame with sell column
""" """
logger.debug(f"Populating sell signals for pair {metadata.get('pair')}.")
logger.debug(f"Populating exit signals for pair {metadata.get('pair')}.")
if self._sell_fun_len == 2: if self._sell_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see " warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning) "the current function headers!", DeprecationWarning)

View File

@ -58,7 +58,10 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
return dataframe return dataframe
def stoploss_from_open(open_relative_stop: float, current_profit: float) -> float: def stoploss_from_open(
open_relative_stop: float,
current_profit: float
) -> float:
""" """
Given the current profit, and a desired stop loss value relative to the open price, Given the current profit, and a desired stop loss value relative to the open price,
@ -72,7 +75,7 @@ def stoploss_from_open(open_relative_stop: float, current_profit: float) -> floa
:param open_relative_stop: Desired stop loss percentage relative to open price :param open_relative_stop: Desired stop loss percentage relative to open price
:param current_profit: The current profit percentage :param current_profit: The current profit percentage
:return: Positive stop loss value relative to current price :return: Stop loss value relative to current price
""" """
# formula is undefined for current_profit -1, return maximum value # formula is undefined for current_profit -1, return maximum value

View File

@ -46,7 +46,7 @@ class SampleHyperOpt(IHyperOpt):
""" """
@staticmethod @staticmethod
def indicator_space() -> List[Dimension]: def buy_indicator_space() -> List[Dimension]:
""" """
Define your Hyperopt space for searching buy strategy parameters. Define your Hyperopt space for searching buy strategy parameters.
""" """
@ -59,7 +59,7 @@ class SampleHyperOpt(IHyperOpt):
Categorical([True, False], name='fastd-enabled'), Categorical([True, False], name='fastd-enabled'),
Categorical([True, False], name='adx-enabled'), Categorical([True, False], name='adx-enabled'),
Categorical([True, False], name='rsi-enabled'), Categorical([True, False], name='rsi-enabled'),
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') Categorical(['boll', 'macd_cross_signal', 'sar_reversal'], name='trigger')
] ]
@staticmethod @staticmethod
@ -71,37 +71,39 @@ class SampleHyperOpt(IHyperOpt):
""" """
Buy strategy Hyperopt will build and use. Buy strategy Hyperopt will build and use.
""" """
conditions = [] long_conditions = []
# GUARDS AND TRENDS # GUARDS AND TRENDS
if 'mfi-enabled' in params and params['mfi-enabled']: if 'mfi-enabled' in params and params['mfi-enabled']:
conditions.append(dataframe['mfi'] < params['mfi-value']) long_conditions.append(dataframe['mfi'] < params['mfi-value'])
if 'fastd-enabled' in params and params['fastd-enabled']: if 'fastd-enabled' in params and params['fastd-enabled']:
conditions.append(dataframe['fastd'] < params['fastd-value']) long_conditions.append(dataframe['fastd'] < params['fastd-value'])
if 'adx-enabled' in params and params['adx-enabled']: if 'adx-enabled' in params and params['adx-enabled']:
conditions.append(dataframe['adx'] > params['adx-value']) long_conditions.append(dataframe['adx'] > params['adx-value'])
if 'rsi-enabled' in params and params['rsi-enabled']: if 'rsi-enabled' in params and params['rsi-enabled']:
conditions.append(dataframe['rsi'] < params['rsi-value']) long_conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS # TRIGGERS
if 'trigger' in params: if 'trigger' in params:
if params['trigger'] == 'bb_lower': if params['trigger'] == 'boll':
conditions.append(dataframe['close'] < dataframe['bb_lowerband']) long_conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal': if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above( long_conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal'] dataframe['macd'],
dataframe['macdsignal']
)) ))
if params['trigger'] == 'sar_reversal': if params['trigger'] == 'sar_reversal':
conditions.append(qtpylib.crossed_above( long_conditions.append(qtpylib.crossed_above(
dataframe['close'], dataframe['sar'] dataframe['close'],
dataframe['sar']
)) ))
# Check that volume is not 0 # Check that volume is not 0
conditions.append(dataframe['volume'] > 0) long_conditions.append(dataframe['volume'] > 0)
if conditions: if long_conditions:
dataframe.loc[ dataframe.loc[
reduce(lambda x, y: x & y, conditions), reduce(lambda x, y: x & y, long_conditions),
'buy'] = 1 'buy'] = 1
return dataframe return dataframe
@ -122,9 +124,11 @@ class SampleHyperOpt(IHyperOpt):
Categorical([True, False], name='sell-fastd-enabled'), Categorical([True, False], name='sell-fastd-enabled'),
Categorical([True, False], name='sell-adx-enabled'), Categorical([True, False], name='sell-adx-enabled'),
Categorical([True, False], name='sell-rsi-enabled'), Categorical([True, False], name='sell-rsi-enabled'),
Categorical(['sell-bb_upper', Categorical(['sell-boll',
'sell-macd_cross_signal', 'sell-macd_cross_signal',
'sell-sar_reversal'], name='sell-trigger') 'sell-sar_reversal'],
name='sell-trigger'
)
] ]
@staticmethod @staticmethod
@ -136,37 +140,39 @@ class SampleHyperOpt(IHyperOpt):
""" """
Sell strategy Hyperopt will build and use. Sell strategy Hyperopt will build and use.
""" """
conditions = [] exit_long_conditions = []
# GUARDS AND TRENDS # GUARDS AND TRENDS
if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']: if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']:
conditions.append(dataframe['mfi'] > params['sell-mfi-value']) exit_long_conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']: if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']:
conditions.append(dataframe['fastd'] > params['sell-fastd-value']) exit_long_conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
if 'sell-adx-enabled' in params and params['sell-adx-enabled']: if 'sell-adx-enabled' in params and params['sell-adx-enabled']:
conditions.append(dataframe['adx'] < params['sell-adx-value']) exit_long_conditions.append(dataframe['adx'] < params['sell-adx-value'])
if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']: if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']:
conditions.append(dataframe['rsi'] > params['sell-rsi-value']) exit_long_conditions.append(dataframe['rsi'] > params['sell-rsi-value'])
# TRIGGERS # TRIGGERS
if 'sell-trigger' in params: if 'sell-trigger' in params:
if params['sell-trigger'] == 'sell-bb_upper': if params['sell-trigger'] == 'sell-boll':
conditions.append(dataframe['close'] > dataframe['bb_upperband']) exit_long_conditions.append(dataframe['close'] > dataframe['bb_upperband'])
if params['sell-trigger'] == 'sell-macd_cross_signal': if params['sell-trigger'] == 'sell-macd_cross_signal':
conditions.append(qtpylib.crossed_above( exit_long_conditions.append(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd'] dataframe['macdsignal'],
dataframe['macd']
)) ))
if params['sell-trigger'] == 'sell-sar_reversal': if params['sell-trigger'] == 'sell-sar_reversal':
conditions.append(qtpylib.crossed_above( exit_long_conditions.append(qtpylib.crossed_above(
dataframe['sar'], dataframe['close'] dataframe['sar'],
dataframe['close']
)) ))
# Check that volume is not 0 # Check that volume is not 0
conditions.append(dataframe['volume'] > 0) exit_long_conditions.append(dataframe['volume'] > 0)
if conditions: if exit_long_conditions:
dataframe.loc[ dataframe.loc[
reduce(lambda x, y: x & y, conditions), reduce(lambda x, y: x & y, exit_long_conditions),
'sell'] = 1 'sell'] = 1
return dataframe return dataframe

View File

@ -74,7 +74,7 @@ class AdvancedSampleHyperOpt(IHyperOpt):
Categorical([True, False], name='fastd-enabled'), Categorical([True, False], name='fastd-enabled'),
Categorical([True, False], name='adx-enabled'), Categorical([True, False], name='adx-enabled'),
Categorical([True, False], name='rsi-enabled'), Categorical([True, False], name='rsi-enabled'),
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') Categorical(['boll', 'macd_cross_signal', 'sar_reversal'], name='trigger')
] ]
@staticmethod @staticmethod
@ -86,36 +86,36 @@ class AdvancedSampleHyperOpt(IHyperOpt):
""" """
Buy strategy Hyperopt will build and use Buy strategy Hyperopt will build and use
""" """
conditions = [] long_conditions = []
# GUARDS AND TRENDS # GUARDS AND TRENDS
if 'mfi-enabled' in params and params['mfi-enabled']: if 'mfi-enabled' in params and params['mfi-enabled']:
conditions.append(dataframe['mfi'] < params['mfi-value']) long_conditions.append(dataframe['mfi'] < params['mfi-value'])
if 'fastd-enabled' in params and params['fastd-enabled']: if 'fastd-enabled' in params and params['fastd-enabled']:
conditions.append(dataframe['fastd'] < params['fastd-value']) long_conditions.append(dataframe['fastd'] < params['fastd-value'])
if 'adx-enabled' in params and params['adx-enabled']: if 'adx-enabled' in params and params['adx-enabled']:
conditions.append(dataframe['adx'] > params['adx-value']) long_conditions.append(dataframe['adx'] > params['adx-value'])
if 'rsi-enabled' in params and params['rsi-enabled']: if 'rsi-enabled' in params and params['rsi-enabled']:
conditions.append(dataframe['rsi'] < params['rsi-value']) long_conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS # TRIGGERS
if 'trigger' in params: if 'trigger' in params:
if params['trigger'] == 'bb_lower': if params['trigger'] == 'boll':
conditions.append(dataframe['close'] < dataframe['bb_lowerband']) long_conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal': if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above( long_conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal'] dataframe['macd'], dataframe['macdsignal']
)) ))
if params['trigger'] == 'sar_reversal': if params['trigger'] == 'sar_reversal':
conditions.append(qtpylib.crossed_above( long_conditions.append(qtpylib.crossed_above(
dataframe['close'], dataframe['sar'] dataframe['close'], dataframe['sar']
)) ))
# Check that volume is not 0 # Check that volume is not 0
conditions.append(dataframe['volume'] > 0) long_conditions.append(dataframe['volume'] > 0)
if conditions: if long_conditions:
dataframe.loc[ dataframe.loc[
reduce(lambda x, y: x & y, conditions), reduce(lambda x, y: x & y, long_conditions),
'buy'] = 1 'buy'] = 1
return dataframe return dataframe
@ -136,9 +136,10 @@ class AdvancedSampleHyperOpt(IHyperOpt):
Categorical([True, False], name='sell-fastd-enabled'), Categorical([True, False], name='sell-fastd-enabled'),
Categorical([True, False], name='sell-adx-enabled'), Categorical([True, False], name='sell-adx-enabled'),
Categorical([True, False], name='sell-rsi-enabled'), Categorical([True, False], name='sell-rsi-enabled'),
Categorical(['sell-bb_upper', Categorical(['sell-boll',
'sell-macd_cross_signal', 'sell-macd_cross_signal',
'sell-sar_reversal'], name='sell-trigger') 'sell-sar_reversal'],
name='sell-trigger')
] ]
@staticmethod @staticmethod
@ -151,36 +152,38 @@ class AdvancedSampleHyperOpt(IHyperOpt):
Sell strategy Hyperopt will build and use Sell strategy Hyperopt will build and use
""" """
# print(params) # print(params)
conditions = [] exit_long_conditions = []
# GUARDS AND TRENDS # GUARDS AND TRENDS
if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']: if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']:
conditions.append(dataframe['mfi'] > params['sell-mfi-value']) exit_long_conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']: if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']:
conditions.append(dataframe['fastd'] > params['sell-fastd-value']) exit_long_conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
if 'sell-adx-enabled' in params and params['sell-adx-enabled']: if 'sell-adx-enabled' in params and params['sell-adx-enabled']:
conditions.append(dataframe['adx'] < params['sell-adx-value']) exit_long_conditions.append(dataframe['adx'] < params['sell-adx-value'])
if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']: if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']:
conditions.append(dataframe['rsi'] > params['sell-rsi-value']) exit_long_conditions.append(dataframe['rsi'] > params['sell-rsi-value'])
# TRIGGERS # TRIGGERS
if 'sell-trigger' in params: if 'sell-trigger' in params:
if params['sell-trigger'] == 'sell-bb_upper': if params['sell-trigger'] == 'sell-boll':
conditions.append(dataframe['close'] > dataframe['bb_upperband']) exit_long_conditions.append(dataframe['close'] > dataframe['bb_upperband'])
if params['sell-trigger'] == 'sell-macd_cross_signal': if params['sell-trigger'] == 'sell-macd_cross_signal':
conditions.append(qtpylib.crossed_above( exit_long_conditions.append(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd'] dataframe['macdsignal'],
dataframe['macd']
)) ))
if params['sell-trigger'] == 'sell-sar_reversal': if params['sell-trigger'] == 'sell-sar_reversal':
conditions.append(qtpylib.crossed_above( exit_long_conditions.append(qtpylib.crossed_above(
dataframe['sar'], dataframe['close'] dataframe['sar'],
dataframe['close']
)) ))
# Check that volume is not 0 # Check that volume is not 0
conditions.append(dataframe['volume'] > 0) exit_long_conditions.append(dataframe['volume'] > 0)
if conditions: if exit_long_conditions:
dataframe.loc[ dataframe.loc[
reduce(lambda x, y: x & y, conditions), reduce(lambda x, y: x & y, exit_long_conditions),
'sell'] = 1 'sell'] = 1
return dataframe return dataframe

View File

@ -68,15 +68,17 @@ class DefaultHyperOpt(IHyperOpt):
# TRIGGERS # TRIGGERS
if 'trigger' in params: if 'trigger' in params:
if params['trigger'] == 'bb_lower': if params['trigger'] == 'boll':
conditions.append(dataframe['close'] < dataframe['bb_lowerband']) conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal': if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above( conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal'] dataframe['macd'],
dataframe['macdsignal']
)) ))
if params['trigger'] == 'sar_reversal': if params['trigger'] == 'sar_reversal':
conditions.append(qtpylib.crossed_above( conditions.append(qtpylib.crossed_above(
dataframe['close'], dataframe['sar'] dataframe['close'],
dataframe['sar']
)) ))
if conditions: if conditions:
@ -102,7 +104,7 @@ class DefaultHyperOpt(IHyperOpt):
Categorical([True, False], name='fastd-enabled'), Categorical([True, False], name='fastd-enabled'),
Categorical([True, False], name='adx-enabled'), Categorical([True, False], name='adx-enabled'),
Categorical([True, False], name='rsi-enabled'), Categorical([True, False], name='rsi-enabled'),
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') Categorical(['boll', 'macd_cross_signal', 'sar_reversal'], name='trigger')
] ]
@staticmethod @staticmethod
@ -128,15 +130,17 @@ class DefaultHyperOpt(IHyperOpt):
# TRIGGERS # TRIGGERS
if 'sell-trigger' in params: if 'sell-trigger' in params:
if params['sell-trigger'] == 'sell-bb_upper': if params['sell-trigger'] == 'sell-boll':
conditions.append(dataframe['close'] > dataframe['bb_upperband']) conditions.append(dataframe['close'] > dataframe['bb_upperband'])
if params['sell-trigger'] == 'sell-macd_cross_signal': if params['sell-trigger'] == 'sell-macd_cross_signal':
conditions.append(qtpylib.crossed_above( conditions.append(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd'] dataframe['macdsignal'],
dataframe['macd']
)) ))
if params['sell-trigger'] == 'sell-sar_reversal': if params['sell-trigger'] == 'sell-sar_reversal':
conditions.append(qtpylib.crossed_above( conditions.append(qtpylib.crossed_above(
dataframe['sar'], dataframe['close'] dataframe['sar'],
dataframe['close']
)) ))
if conditions: if conditions:
@ -162,9 +166,10 @@ class DefaultHyperOpt(IHyperOpt):
Categorical([True, False], name='sell-fastd-enabled'), Categorical([True, False], name='sell-fastd-enabled'),
Categorical([True, False], name='sell-adx-enabled'), Categorical([True, False], name='sell-adx-enabled'),
Categorical([True, False], name='sell-rsi-enabled'), Categorical([True, False], name='sell-rsi-enabled'),
Categorical(['sell-bb_upper', Categorical(['sell-boll',
'sell-macd_cross_signal', 'sell-macd_cross_signal',
'sell-sar_reversal'], name='sell-trigger') 'sell-sar_reversal'],
name='sell-trigger')
] ]
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

View File

@ -167,7 +167,7 @@ class HyperoptableStrategy(IStrategy):
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair :param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with sell column
""" """
dataframe.loc[ dataframe.loc[
( (

View File

@ -156,17 +156,21 @@ def test_ignore_expired_candle(default_conf):
# Add 1 candle length as the "latest date" defines candle open. # Add 1 candle length as the "latest date" defines candle open.
current_time = latest_date + timedelta(seconds=80 + 300) current_time = latest_date + timedelta(seconds=80 + 300)
assert strategy.ignore_expired_candle(latest_date=latest_date, assert strategy.ignore_expired_candle(
current_time=current_time, latest_date=latest_date,
timeframe_seconds=300, current_time=current_time,
buy=True) is True timeframe_seconds=300,
enter=True
) is True
current_time = latest_date + timedelta(seconds=30 + 300) current_time = latest_date + timedelta(seconds=30 + 300)
assert not strategy.ignore_expired_candle(latest_date=latest_date, assert not strategy.ignore_expired_candle(
current_time=current_time, latest_date=latest_date,
timeframe_seconds=300, current_time=current_time,
buy=True) is True timeframe_seconds=300,
enter=True
) is True
def test_assert_df_raise(mocker, caplog, ohlcv_history): def test_assert_df_raise(mocker, caplog, ohlcv_history):

View File

@ -382,13 +382,13 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
assert isinstance(indicator_df, DataFrame) assert isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns assert 'adx' in indicator_df.columns
buydf = strategy.advise_buy(result, metadata=metadata) enterdf = strategy.advise_buy(result, metadata=metadata)
assert isinstance(buydf, DataFrame) assert isinstance(enterdf, DataFrame)
assert 'buy' in buydf.columns assert 'buy' in enterdf.columns
selldf = strategy.advise_sell(result, metadata=metadata) exitdf = strategy.advise_sell(result, metadata=metadata)
assert isinstance(selldf, DataFrame) assert isinstance(exitdf, DataFrame)
assert 'sell' in selldf assert 'sell' in exitdf
assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.", assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.",
caplog) caplog)
@ -409,10 +409,10 @@ def test_strategy_interface_versioning(result, monkeypatch, default_conf):
assert isinstance(indicator_df, DataFrame) assert isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns assert 'adx' in indicator_df.columns
buydf = strategy.advise_buy(result, metadata=metadata) enterdf = strategy.advise_buy(result, metadata=metadata)
assert isinstance(buydf, DataFrame) assert isinstance(enterdf, DataFrame)
assert 'buy' in buydf.columns assert 'buy' in enterdf.columns
selldf = strategy.advise_sell(result, metadata=metadata) exitdf = strategy.advise_sell(result, metadata=metadata)
assert isinstance(selldf, DataFrame) assert isinstance(exitdf, DataFrame)
assert 'sell' in selldf assert 'sell' in exitdf