diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 1acbca565..14823722e 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -20,7 +20,9 @@ All profit calculations of Freqtrade include fees. For Backtesting / Hyperopt / ## Bot execution logic Starting freqtrade in dry-run or live mode (using `freqtrade trade`) will start the bot and start the bot iteration loop. -By default, loop runs every few seconds (`internals.process_throttle_secs`) and does roughly the following in the following sequence: +This will also run the `bot_start()` callback. + +By default, the bot loop runs every few seconds (`internals.process_throttle_secs`) and performs the following actions: * Fetch open trades from persistence. * Calculate current list of tradable pairs. @@ -54,6 +56,7 @@ This loop will be repeated again and again until the bot is stopped. [backtesting](backtesting.md) or [hyperopt](hyperopt.md) do only part of the above logic, since most of the trading operations are fully simulated. * Load historic data for configured pairlist. +* Calls `bot_start()` once. * Calls `bot_loop_start()` once. * Calculate indicators (calls `populate_indicators()` once per pair). * Calculate entry / exit signals (calls `populate_entry_trend()` and `populate_exit_trend()` once per pair). diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ee535e9c3..b30c9b965 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -959,6 +959,29 @@ class FreqtradeBot(LoggingMixin): logger.debug(f'Found no {exit_signal_type} signal for %s.', trade) return False + def _check_and_execute_exit(self, trade: Trade, exit_rate: float, + enter: bool, exit_: bool, exit_tag: Optional[str]) -> bool: + """ + Check and execute trade exit + """ + exits: List[ExitCheckTuple] = self.strategy.should_exit( + trade, + exit_rate, + datetime.now(timezone.utc), + enter=enter, + exit_=exit_, + force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 + ) + for should_exit in exits: + if should_exit.exit_flag: + exit_tag1 = exit_tag if should_exit.exit_type == ExitType.EXIT_SIGNAL else None + logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.exit_type}' + f'{f" Tag: {exit_tag1}" if exit_tag1 is not None else ""}') + exited = self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag1) + if exited: + return True + return False + def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool: """ Abstracts creating stoploss orders from the logic. @@ -1110,28 +1133,6 @@ class FreqtradeBot(LoggingMixin): logger.warning(f"Could not create trailing stoploss order " f"for pair {trade.pair}.") - def _check_and_execute_exit(self, trade: Trade, exit_rate: float, - enter: bool, exit_: bool, exit_tag: Optional[str]) -> bool: - """ - Check and execute trade exit - """ - exits: List[ExitCheckTuple] = self.strategy.should_exit( - trade, - exit_rate, - datetime.now(timezone.utc), - enter=enter, - exit_=exit_, - force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 - ) - for should_exit in exits: - if should_exit.exit_flag: - logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.exit_type}' - f'{f" Tag: {exit_tag}" if exit_tag is not None else ""}') - exited = self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag) - if exited: - return True - return False - def manage_open_orders(self) -> None: """ Management of open orders on exchange. Unfilled orders might be cancelled if timeout diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index cacb87745..030d7bdf0 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -189,6 +189,7 @@ class Backtesting: self.strategy.order_types['stoploss_on_exchange'] = False self.strategy.ft_bot_start() + strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)() def _load_protections(self, strategy: IStrategy): if self.config.get('enable_protections', False): @@ -1140,8 +1141,6 @@ class Backtesting: backtest_start_time = datetime.now(timezone.utc) self._set_strategy(strat) - strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)() - # Use max_open_trades in backtesting, except --disable-max-market-positions is set if self.config.get('use_max_market_positions', True): # Must come from strategy config, as the strategy may modify this setting. diff --git a/requirements.txt b/requirements.txt index 178d852b0..2ccadea30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,17 +2,17 @@ numpy==1.23.0 pandas==1.4.3 pandas-ta==0.3.14b -ccxt==1.89.14 +ccxt==1.89.96 # Pin cryptography for now due to rust build errors with piwheels cryptography==37.0.2 aiohttp==3.8.1 SQLAlchemy==1.4.39 -python-telegram-bot==13.12 +python-telegram-bot==13.13 arrow==1.2.2 cachetools==4.2.2 -requests==2.28.0 +requests==2.28.1 urllib3==1.26.9 -jsonschema==4.6.0 +jsonschema==4.6.1 TA-Lib==0.4.24 technical==1.3.0 tabulate==0.8.10 @@ -28,14 +28,14 @@ py_find_1st==1.1.5 # Load ticker files 30% faster python-rapidjson==1.6 # Properly format api responses -orjson==3.7.3 +orjson==3.7.6 # Notify systemd sdnotify==0.3.2 # API Server fastapi==0.78.0 -uvicorn==0.17.6 +uvicorn==0.18.2 pyjwt==2.4.0 aiofiles==0.8.0 psutil==5.9.1 @@ -44,7 +44,7 @@ psutil==5.9.1 colorama==0.4.5 # Building config files interactively questionary==1.10.0 -prompt-toolkit==3.0.29 +prompt-toolkit==3.0.30 # Extensions to datetime library python-dateutil==2.8.2 diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 9f3c5845f..1ad8b33cf 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -861,6 +861,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0) assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) + assert hyperopt.backtesting.strategy.bot_loop_started is True assert hyperopt.backtesting.strategy.buy_rsi.in_space is True assert hyperopt.backtesting.strategy.buy_rsi.value == 35 diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index 28ecf617a..876b31b14 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -44,6 +44,11 @@ class HyperoptableStrategy(StrategyTestV2): }) return prot + bot_loop_started = False + + def bot_loop_start(self): + self.bot_loop_started = True + def bot_start(self, **kwargs) -> None: """ Parameters can also be defined here ... diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 4f3d5f667..4963e2b0a 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3951,9 +3951,9 @@ def test_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limit_order_ # Test if entry-signal is absent (should sell due to roi = true) if is_short: - patch_get_signal(freqtrade, enter_long=False, exit_short=False) + patch_get_signal(freqtrade, enter_long=False, exit_short=False, exit_tag='something') else: - patch_get_signal(freqtrade, enter_long=False, exit_long=False) + patch_get_signal(freqtrade, enter_long=False, exit_long=False, exit_tag='something') assert freqtrade.handle_trade(trade) is True assert trade.exit_reason == ExitType.ROI.value