diff --git a/docs/advanced-hyperopt.md b/docs/advanced-hyperopt.md index 7f1bd0fed..8a1ebaff3 100644 --- a/docs/advanced-hyperopt.md +++ b/docs/advanced-hyperopt.md @@ -98,6 +98,23 @@ class MyAwesomeStrategy(IStrategy): !!! Note All overrides are optional and can be mixed/matched as necessary. +### Dynamic parameters + +Parameters can also be defined dynamically, but must be available to the instance once the * [`bot_start()` callback](strategy-callbacks.md#bot-start) has been called. + +``` python + +class MyAwesomeStrategy(IStrategy): + + def bot_start(self, **kwargs) -> None: + self.buy_adx = IntParameter(20, 30, default=30, optimize=True) + + # ... +``` + +!!! Warning + Parameters created this way will not show up in the `list-strategies` parameter count. + ### Overriding Base estimator You can define your own estimator for Hyperopt by implementing `generate_estimator()` in the Hyperopt subclass. diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index e1e57a1f3..656f206a4 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -549,10 +549,11 @@ class AwesomeStrategy(IStrategy): :param pair: Pair that's about to be bought/shorted. :param order_type: Order type (as configured in order_types). usually limit or market. - :param amount: Amount in target (quote) currency that's going to be traded. + :param amount: Amount in target (base) currency that's going to be traded. :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 current_time: datetime object, containing the current datetime + :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param side: 'long' or 'short' - indicating the direction of the proposed trade :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the buy-order is placed on the exchange. @@ -586,7 +587,7 @@ class AwesomeStrategy(IStrategy): rate: float, time_in_force: str, exit_reason: str, current_time: datetime, **kwargs) -> bool: """ - Called right before placing a regular sell order. + Called right before placing a regular exit order. Timing for this function is critical, so avoid doing heavy computations or network requests in this method. @@ -594,9 +595,10 @@ class AwesomeStrategy(IStrategy): 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 order_type: Order type (as configured in order_types). usually limit or market. - :param amount: Amount in quote currency. + :param amount: Amount in base currency. :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 exit_reason: Exit reason. @@ -604,7 +606,7 @@ class AwesomeStrategy(IStrategy): 'exit_signal', 'force_exit', 'emergency_exit'] :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the exit-order is placed on the exchange. + :return bool: When True, then the exit-order is placed on the exchange. False aborts the process """ if exit_reason == 'force_exit' and trade.calc_profit_ratio(rate) < 0: @@ -802,17 +804,18 @@ For markets / exchanges that don't support leverage, this method is ignored. ``` python class AwesomeStrategy(IStrategy): - def leverage(self, pair: str, current_time: 'datetime', current_rate: float, - proposed_leverage: float, max_leverage: float, side: str, + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ - Customize leverage for each new trade. + Customize leverage for each new trade. This method is only called in futures mode. :param pair: Pair that's currently analyzed :param current_time: datetime object, containing the current datetime :param current_rate: Rate, calculated based on pricing settings in exit_pricing. :param proposed_leverage: A leverage proposed by the bot. :param max_leverage: Max leverage allowed on this pair + :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param side: 'long' or 'short' - indicating the direction of the proposed trade :return: A leverage amount, which is between 1.0 and max_leverage. """ diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a2a12a03a..95eb911cf 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -781,7 +781,7 @@ class FreqtradeBot(LoggingMixin): current_rate=enter_limit_requested, proposed_leverage=1.0, max_leverage=max_leverage, - side=trade_side, + side=trade_side, entry_tag=entry_tag, ) if self.trading_mode != TradingMode.SPOT else 1.0 # Cap leverage between 1.0 and max_leverage. leverage = min(max(leverage, 1.0), max_leverage) @@ -1203,15 +1203,15 @@ class FreqtradeBot(LoggingMixin): current_order_rate=order_obj.price, entry_tag=trade.enter_tag, side=trade.entry_side) - full_cancel = False + replacing = True cancel_reason = constants.CANCEL_REASON['REPLACE'] if not adjusted_entry_price: - full_cancel = True if trade.nr_of_successful_entries == 0 else False + replacing = False cancel_reason = constants.CANCEL_REASON['USER_CANCEL'] if order_obj.price != adjusted_entry_price: # cancel existing order if new price is supplied or None self.handle_cancel_enter(trade, order, cancel_reason, - allow_full_cancel=full_cancel) + replacing=replacing) if adjusted_entry_price: # place new order only if new price is supplied self.execute_entry( @@ -1245,10 +1245,11 @@ class FreqtradeBot(LoggingMixin): def handle_cancel_enter( self, trade: Trade, order: Dict, reason: str, - allow_full_cancel: Optional[bool] = True + replacing: Optional[bool] = False ) -> bool: """ Buy cancel - cancel order + :param replacing: Replacing order - prevent trade deletion. :return: True if order was fully cancelled """ was_trade_fully_canceled = False @@ -1286,7 +1287,7 @@ class FreqtradeBot(LoggingMixin): if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC): # if trade is not partially completed and it's the only order, just delete the trade open_order_count = len([order for order in trade.orders if order.status == 'open']) - if open_order_count <= 1 and allow_full_cancel: + if open_order_count <= 1 and trade.nr_of_successful_entries == 0 and not replacing: logger.info(f'{side} order fully cancelled. Removing {trade} from database.') trade.delete() was_trade_fully_canceled = True @@ -1295,7 +1296,7 @@ class FreqtradeBot(LoggingMixin): # FIXME TODO: This could possibly reworked to not duplicate the code 15 lines below. self.update_trade_state(trade, trade.open_order_id, corder) trade.open_order_id = None - logger.info(f'Partial {side} order timeout for {trade}.') + logger.info(f'{side} Order timeout for {trade}.') else: # if trade is partially complete, edit the stake details for the trade # and close the order diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index a7fdef25c..6c387a431 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -188,9 +188,7 @@ class Backtesting: # since a "perfect" stoploss-exit is assumed anyway # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False - if self.dataprovider.runmode == RunMode.BACKTEST: - # in hyperopt mode - don't re-init params - self.strategy.ft_load_hyper_params(False) + self.strategy.ft_bot_start() def _load_protections(self, strategy: IStrategy): @@ -709,7 +707,7 @@ class Backtesting: current_rate=row[OPEN_IDX], proposed_leverage=1.0, max_leverage=max_leverage, - side=direction, + side=direction, entry_tag=entry_tag, ) if self._can_short else 1.0 # Cap leverage between 1.0 and max_leverage. leverage = min(max(leverage, 1.0), max_leverage) @@ -900,26 +898,30 @@ class Backtesting: self.protections.stop_per_pair(pair, current_time, side) self.protections.global_stop(current_time, side) - def manage_open_orders(self, trade: LocalTrade, current_time, row: Tuple) -> bool: + def manage_open_orders(self, trade: LocalTrade, current_time: datetime, row: Tuple) -> bool: """ Check if any open order needs to be cancelled or replaced. Returns True if the trade should be deleted. """ for order in [o for o in trade.orders if o.ft_is_open]: - if self.check_order_cancel(trade, order, current_time): + oc = self.check_order_cancel(trade, order, current_time) + if oc: # delete trade due to order timeout return True - elif self.check_order_replace(trade, order, current_time, row): + elif oc is None and self.check_order_replace(trade, order, current_time, row): # delete trade due to user request self.canceled_trade_entries += 1 return True # default maintain trade return False - def check_order_cancel(self, trade: LocalTrade, order: Order, current_time) -> bool: + def check_order_cancel( + self, trade: LocalTrade, order: Order, current_time: datetime) -> Optional[bool]: """ Check if current analyzed order has to be canceled. - Returns True if the trade should be Deleted (initial order was canceled). + Returns True if the trade should be Deleted (initial order was canceled), + False if it's Canceled + None if the order is still active. """ timedout = self.strategy.ft_check_timed_out( trade, # type: ignore[arg-type] @@ -933,12 +935,15 @@ class Backtesting: else: # Close additional entry order del trade.orders[trade.orders.index(order)] + trade.open_order_id = None + return False if order.side == trade.exit_side: self.timedout_exit_orders += 1 # Close exit order and retry exiting on next signal. del trade.orders[trade.orders.index(order)] - - return False + trade.open_order_id = None + return False + return None def check_order_replace(self, trade: LocalTrade, order: Order, current_time, row: Tuple) -> bool: diff --git a/freqtrade/strategy/hyper.py b/freqtrade/strategy/hyper.py index ee62b5516..cdcfc969e 100644 --- a/freqtrade/strategy/hyper.py +++ b/freqtrade/strategy/hyper.py @@ -4,9 +4,8 @@ This module defines a base class for auto-hyperoptable strategies. """ import logging from pathlib import Path -from typing import Any, Dict, Iterator, List, Tuple +from typing import Any, Dict, Iterator, List, Tuple, Type, Union -from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.misc import deep_merge_dicts, json_load from freqtrade.optimize.hyperopt_tools import HyperoptTools @@ -34,9 +33,7 @@ class HyperStrategyMixin: params = self.load_params_from_file() params = params.get('params', {}) self._ft_params_from_file = params - - if config.get('runmode') != RunMode.BACKTEST: - self.ft_load_hyper_params(config.get('runmode') == RunMode.HYPEROPT) + # Init/loading of parameters is done as part of ft_bot_start(). def enumerate_parameters(self, category: str = None) -> Iterator[Tuple[str, BaseParameter]]: """ @@ -56,28 +53,13 @@ class HyperStrategyMixin: for par in params: yield par.name, par - @classmethod - def detect_parameters(cls, category: str) -> Iterator[Tuple[str, BaseParameter]]: - """ Detect all parameters for 'category' """ - for attr_name in dir(cls): - if not attr_name.startswith('__'): # Ignore internals, not strictly necessary. - attr = getattr(cls, attr_name) - if issubclass(attr.__class__, BaseParameter): - if (attr_name.startswith(category + '_') - and attr.category is not None and attr.category != category): - raise OperationalException( - f'Inconclusive parameter name {attr_name}, category: {attr.category}.') - if (category == attr.category or - (attr_name.startswith(category + '_') and attr.category is None)): - yield attr_name, attr - @classmethod def detect_all_parameters(cls) -> Dict: """ Detect all parameters and return them as a list""" params: Dict[str, Any] = { - 'buy': list(cls.detect_parameters('buy')), - 'sell': list(cls.detect_parameters('sell')), - 'protection': list(cls.detect_parameters('protection')), + 'buy': list(detect_parameters(cls, 'buy')), + 'sell': list(detect_parameters(cls, 'sell')), + 'protection': list(detect_parameters(cls, 'protection')), } params.update({ 'count': len(params['buy'] + params['sell'] + params['protection']) @@ -159,7 +141,7 @@ class HyperStrategyMixin: logger.info(f"No params for {space} found, using default values.") param_container: List[BaseParameter] = getattr(self, f"ft_{space}_params") - for attr_name, attr in self.detect_parameters(space): + for attr_name, attr in detect_parameters(self, space): attr.name = attr_name attr.in_space = hyperopt and HyperoptTools.has_space(self.config, space) if not attr.category: @@ -190,3 +172,25 @@ class HyperStrategyMixin: if not p.optimize or not p.in_space: params[p.category][name] = p.value return params + + +def detect_parameters( + obj: Union[HyperStrategyMixin, Type[HyperStrategyMixin]], + category: str + ) -> Iterator[Tuple[str, BaseParameter]]: + """ + Detect all parameters for 'category' for "obj" + :param obj: Strategy object or class + :param category: category - usually `'buy', 'sell', 'protection',... + """ + for attr_name in dir(obj): + if not attr_name.startswith('__'): # Ignore internals, not strictly necessary. + attr = getattr(obj, attr_name) + if issubclass(attr.__class__, BaseParameter): + if (attr_name.startswith(category + '_') + and attr.category is not None and attr.category != category): + raise OperationalException( + f'Inconclusive parameter name {attr_name}, category: {attr.category}.') + if (category == attr.category or + (attr_name.startswith(category + '_') and attr.category is None)): + yield attr_name, attr diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index c521943b1..3b3d326ff 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -14,6 +14,7 @@ from freqtrade.constants import ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, SignalTagType, SignalType, TradingMode) +from freqtrade.enums.runmode import RunMode from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds from freqtrade.persistence import Order, PairLocks, Trade @@ -151,6 +152,8 @@ class IStrategy(ABC, HyperStrategyMixin): """ strategy_safe_wrapper(self.bot_start)() + self.ft_load_hyper_params(self.config.get('runmode') == RunMode.HYPEROPT) + @abstractmethod def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ @@ -284,7 +287,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param pair: Pair that's about to be bought/shorted. :param order_type: Order type (as configured in order_types). usually limit or market. - :param amount: Amount in target (quote) currency that's going to be traded. + :param amount: Amount in target (base) currency that's going to be traded. :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 current_time: datetime object, containing the current datetime @@ -311,7 +314,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param pair: Pair for trade that's about to be exited. :param trade: trade object. :param order_type: Order type (as configured in order_types). usually limit or market. - :param amount: Amount in quote currency. + :param amount: Amount in base currency. :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 exit_reason: Exit reason. @@ -506,8 +509,8 @@ class IStrategy(ABC, HyperStrategyMixin): return current_order_rate def leverage(self, pair: str, current_time: datetime, current_rate: float, - proposed_leverage: float, max_leverage: float, side: str, - **kwargs) -> float: + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], + side: str, **kwargs) -> float: """ Customize leverage for each new trade. This method is only called in futures mode. @@ -516,6 +519,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param current_rate: Rate, calculated based on pricing settings in exit_pricing. :param proposed_leverage: A leverage proposed by the bot. :param max_leverage: Max leverage allowed on this pair + :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param side: 'long' or 'short' - indicating the direction of the proposed trade :return: A leverage amount, which is between 1.0 and max_leverage. """ diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index 3854efd85..acefd0363 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -159,7 +159,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f :param pair: Pair that's about to be bought/shorted. :param order_type: Order type (as configured in order_types). usually limit or market. - :param amount: Amount in target (quote) currency that's going to be traded. + :param amount: Amount in target (base) currency that's going to be traded. :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 current_time: datetime object, containing the current datetime @@ -175,7 +175,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: rate: float, time_in_force: str, exit_reason: str, current_time: 'datetime', **kwargs) -> bool: """ - Called right before placing a regular sell order. + Called right before placing a regular exit order. Timing for this function is critical, so avoid doing heavy computations or network requests in this method. @@ -183,10 +183,10 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: When not implemented by a strategy, returns True (always confirming). - :param pair: Pair that's currently analyzed + :param pair: Pair for trade that's about to be exited. :param trade: trade object. :param order_type: Order type (as configured in order_types). usually limit or market. - :param amount: Amount in quote currency. + :param amount: Amount in base currency. :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 exit_reason: Exit reason. @@ -194,7 +194,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: 'exit_signal', 'force_exit', 'emergency_exit'] :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the exit-order is placed on the exchange. + :return bool: When True, then the exit-order is placed on the exchange. False aborts the process """ return True @@ -267,8 +267,8 @@ def adjust_trade_position(self, trade: 'Trade', current_time: 'datetime', return None def leverage(self, pair: str, current_time: datetime, current_rate: float, - proposed_leverage: float, max_leverage: float, side: str, - **kwargs) -> float: + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], + side: str, **kwargs) -> float: """ Customize leverage for each new trade. This method is only called in futures mode. @@ -277,6 +277,7 @@ def leverage(self, pair: str, current_time: datetime, current_rate: float, :param current_rate: Rate, calculated based on pricing settings in exit_pricing. :param proposed_leverage: A leverage proposed by the bot. :param max_leverage: Max leverage allowed on this pair + :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param side: 'long' or 'short' - indicating the direction of the proposed trade :return: A leverage amount, which is between 1.0 and max_leverage. """ diff --git a/scripts/rest_client.py b/scripts/rest_client.py index ecbb65253..e5d358c98 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -261,7 +261,7 @@ class FtRestClient(): } return self._post("forcebuy", data=data) - def force_enter(self, pair, side, price=None): + def forceenter(self, pair, side, price=None): """Force entering a trade :param pair: Pair to buy (ETH/BTC) @@ -273,7 +273,7 @@ class FtRestClient(): "side": side, "price": price, } - return self._post("force_enter", data=data) + return self._post("forceenter", data=data) def forceexit(self, tradeid): """Force-exit a trade. diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 8522894f7..9f3c5845f 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -509,7 +509,6 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: hyperopt.min_date = Arrow(2017, 12, 10) hyperopt.max_date = Arrow(2017, 12, 13) hyperopt.init_spaces() - hyperopt.dimensions = hyperopt.dimensions generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values())) assert generate_optimizer_value == response_expected diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index f4dcf1a05..28ecf617a 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -27,7 +27,6 @@ class HyperoptableStrategy(StrategyTestV2): 'sell_minusdi': 0.4 } - buy_rsi = IntParameter([0, 50], default=30, space='buy') buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy') sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell', @@ -45,6 +44,12 @@ class HyperoptableStrategy(StrategyTestV2): }) return prot + def bot_start(self, **kwargs) -> None: + """ + Parameters can also be defined here ... + """ + self.buy_rsi = IntParameter([0, 50], default=30, space='buy') + def informative_pairs(self): """ Define additional, informative pair/interval combinations to be cached from the exchange. diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index 340001ef2..2c7ccbdf2 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -178,8 +178,8 @@ class StrategyTestV3(IStrategy): return dataframe def leverage(self, pair: str, current_time: datetime, current_rate: float, - proposed_leverage: float, max_leverage: float, side: str, - **kwargs) -> float: + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], + side: str, **kwargs) -> float: # Return 3.0 in all cases. # Bot-logic must make sure it's an allowed leverage and eventually adjust accordingly. diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 12cbf5370..b7b73bdcf 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -16,6 +16,7 @@ from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.optimize.space import SKDecimal from freqtrade.persistence import PairLocks, Trade from freqtrade.resolvers import StrategyResolver +from freqtrade.strategy.hyper import detect_parameters from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter, DecimalParameter, IntParameter, RealParameter) from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper @@ -614,6 +615,7 @@ def test_leverage_callback(default_conf, side) -> None: proposed_leverage=1.0, max_leverage=5.0, side=side, + entry_tag=None, ) == 1 default_conf['strategy'] = CURRENT_TEST_STRATEGY @@ -625,6 +627,7 @@ def test_leverage_callback(default_conf, side) -> None: proposed_leverage=1.0, max_leverage=5.0, side=side, + entry_tag='entry_tag_test', ) == 3 @@ -893,7 +896,7 @@ def test_auto_hyperopt_interface(default_conf): default_conf.update({'strategy': 'HyperoptableStrategy'}) PairLocks.timeframe = default_conf['timeframe'] strategy = StrategyResolver.load_strategy(default_conf) - + strategy.ft_bot_start() with pytest.raises(OperationalException): next(strategy.enumerate_parameters('deadBeef')) @@ -908,15 +911,18 @@ def test_auto_hyperopt_interface(default_conf): assert strategy.sell_minusdi.value == 0.5 all_params = strategy.detect_all_parameters() assert isinstance(all_params, dict) - assert len(all_params['buy']) == 2 + # Only one buy param at class level + assert len(all_params['buy']) == 1 + # Running detect params at instance level reveals both parameters. + assert len(list(detect_parameters(strategy, 'buy'))) == 2 assert len(all_params['sell']) == 2 # Number of Hyperoptable parameters - assert all_params['count'] == 6 + assert all_params['count'] == 5 strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy') with pytest.raises(OperationalException, match=r"Inconclusive parameter.*"): - [x for x in strategy.detect_parameters('sell')] + [x for x in detect_parameters(strategy, 'sell')] def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog): diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 5a5467370..0e4f9db99 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2572,6 +2572,7 @@ def test_check_handle_cancelled_buy( get_fee=fee ) freqtrade = FreqtradeBot(default_conf_usdt) + open_trade.orders = [] open_trade.is_short = is_short Trade.query.session.add(open_trade) @@ -2954,6 +2955,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_ freqtrade = FreqtradeBot(default_conf_usdt) freqtrade._notify_enter_cancel = MagicMock() + # TODO: Convert to real trade trade = MagicMock() trade.pair = 'LTC/USDT' trade.open_rate = 200 @@ -2961,6 +2963,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_ trade.entry_side = "buy" l_order['filled'] = 0.0 l_order['status'] = 'open' + trade.nr_of_successful_entries = 0 reason = CANCEL_REASON['TIMEOUT'] assert freqtrade.handle_cancel_enter(trade, l_order, reason) assert cancel_order_mock.call_count == 1 @@ -3003,7 +3006,9 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho freqtrade = FreqtradeBot(default_conf_usdt) reason = CANCEL_REASON['TIMEOUT'] + # TODO: Convert to real trade trade = MagicMock() + trade.nr_of_successful_entries = 0 trade.pair = 'LTC/ETH' trade.entry_side = "sell" if is_short else "buy" assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason) @@ -3036,13 +3041,14 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order freqtrade = FreqtradeBot(default_conf_usdt) freqtrade._notify_enter_cancel = MagicMock() - + # TODO: Convert to real trade trade = MagicMock() trade.pair = 'LTC/USDT' trade.entry_side = "buy" trade.open_rate = 200 trade.entry_side = "buy" trade.open_order_id = "open_order_noop" + trade.nr_of_successful_entries = 0 l_order['filled'] = 0.0 l_order['status'] = 'open' reason = CANCEL_REASON['TIMEOUT']