From 35a388bf9ae2038efbb7f011e375b9f17cfe24d7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 May 2023 19:39:00 +0200 Subject: [PATCH 01/26] Don't force min_roi to have content --- freqtrade/constants.py | 1 - freqtrade/strategy/interface.py | 2 +- tests/optimize/test_backtest_detail.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 3802ec3ad..7012acb7c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -148,7 +148,6 @@ CONF_SCHEMA = { 'patternProperties': { '^[0-9.]+$': {'type': 'number'} }, - 'minProperties': 1 }, 'amount_reserve_percent': {'type': 'number', 'minimum': 0.0, 'maximum': 0.5}, 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True, 'minimum': -1}, diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 382f38c9a..ae8070b2d 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -48,7 +48,7 @@ class IStrategy(ABC, HyperStrategyMixin): _ft_params_from_file: Dict # associated minimal roi - minimal_roi: Dict = {"0": 10.0} + minimal_roi: Dict = {} # associated stoploss stoploss: float diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 158dd04dc..82c036e07 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -820,7 +820,7 @@ tc52 = BTContainer(data=[ [2, 4900, 5250, 4500, 5100, 6172, 0, 0], # Order readjust [3, 5100, 5100, 4650, 4750, 6172, 0, 0], # stoploss hit? [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], - stop_loss=-0.03, roi={"0": 0.10}, profit_perc=-0.03, + stop_loss=-0.03, roi={}, profit_perc=-0.03, use_exit_signal=True, timeout=1000, custom_entry_price=4200, adjust_entry_price=5200, trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2, is_short=False)] From 36c82ad67ca81bbb463b1ea2dd0ee80e089681ad Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 May 2023 19:40:02 +0200 Subject: [PATCH 02/26] Update documentation for min_roi --- docs/strategy-customization.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 8b6654c6c..8913d787b 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -342,16 +342,12 @@ The above configuration would therefore mean: The calculation does include fees. -To disable ROI completely, set it to an insanely high number: +To disable ROI completely, set it to an empty dictionary: ```python -minimal_roi = { - "0": 100 -} +minimal_roi = {} ``` -While technically not completely disabled, this would exit once the trade reaches 10000% Profit. - To use times based on candle duration (timeframe), the following snippet can be handy. This will allow you to change the timeframe for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...) From 5649d1d4da6f140f3db99e6e4a902c0fcd6bbc1f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 May 2023 19:57:12 +0200 Subject: [PATCH 03/26] Convert minimal_roi to list comprehension --- freqtrade/strategy/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index ae8070b2d..80093fed8 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -1265,7 +1265,7 @@ class IStrategy(ABC, HyperStrategyMixin): :return: minimal ROI entry value or None if none proper ROI entry was found. """ # Get highest entry in ROI dict where key <= trade-duration - roi_list = list(filter(lambda x: x <= trade_dur, self.minimal_roi.keys())) + roi_list = [x for x in self.minimal_roi.keys() if x <= trade_dur] if not roi_list: return None, None roi_entry = max(roi_list) From 51ba4d14e945dc84ab265383d6d4799bf5eb10e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 03:56:46 +0000 Subject: [PATCH 04/26] Bump pydantic from 1.10.7 to 1.10.8 Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.7 to 1.10.8. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/v1.10.8/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v1.10.7...v1.10.8) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cff54ca1c..e8ba3238d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ sdnotify==0.3.2 # API Server fastapi==0.95.2 -pydantic==1.10.7 +pydantic==1.10.8 uvicorn==0.22.0 pyjwt==2.7.0 aiofiles==23.1.0 From 12af6ea766a7b34a31a07d267787ae83f371bcdf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 03:56:50 +0000 Subject: [PATCH 05/26] Bump pytest-cov from 4.0.0 to 4.1.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 4.0.0 to 4.1.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v4.1.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cc3463174..5423d35be 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,7 +12,7 @@ mypy==1.3.0 pre-commit==3.3.2 pytest==7.3.1 pytest-asyncio==0.21.0 -pytest-cov==4.0.0 +pytest-cov==4.1.0 pytest-mock==3.10.0 pytest-random-order==1.1.0 isort==5.12.0 From 3bc390cb2e9c6a736c36e7f0d38f4a0376cb8120 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 03:56:53 +0000 Subject: [PATCH 06/26] Bump types-requests from 2.30.0.0 to 2.31.0.0 Bumps [types-requests](https://github.com/python/typeshed) from 2.30.0.0 to 2.31.0.0. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cc3463174..d5b7ca577 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -25,6 +25,6 @@ nbconvert==7.4.0 # mypy types types-cachetools==5.3.0.5 types-filelock==3.2.7 -types-requests==2.30.0.0 +types-requests==2.31.0.0 types-tabulate==0.9.0.2 types-python-dateutil==2.8.19.13 From 4e9a43ebf602b57cb9cead2bda2a348e4e759313 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 03:56:56 +0000 Subject: [PATCH 07/26] Bump cachetools from 5.3.0 to 5.3.1 Bumps [cachetools](https://github.com/tkem/cachetools) from 5.3.0 to 5.3.1. - [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tkem/cachetools/compare/v5.3.0...v5.3.1) --- updated-dependencies: - dependency-name: cachetools dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cff54ca1c..b12e2d859 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ python-telegram-bot==20.3 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.23.3 arrow==1.2.3 -cachetools==5.3.0 +cachetools==5.3.1 requests==2.31.0 urllib3==2.0.2 jsonschema==4.17.3 From 8fdec5f3a6e17faf28fc980f6a94b30c0ad75127 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 03:57:05 +0000 Subject: [PATCH 08/26] Bump ccxt from 3.1.5 to 3.1.13 Bumps [ccxt](https://github.com/ccxt/ccxt) from 3.1.5 to 3.1.13. - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/3.1.5...3.1.13) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cff54ca1c..a0e6c0387 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.24.3 pandas==2.0.1 pandas-ta==0.3.14b -ccxt==3.1.5 +ccxt==3.1.13 cryptography==40.0.2; platform_machine != 'armv7l' cryptography==40.0.1; platform_machine == 'armv7l' aiohttp==3.8.4 From 90808683e2c33ec86bcc374c8b7804e21dbcd009 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 03:57:13 +0000 Subject: [PATCH 09/26] Bump ruff from 0.0.269 to 0.0.270 Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.269 to 0.0.270. - [Release notes](https://github.com/charliermarsh/ruff/releases) - [Changelog](https://github.com/charliermarsh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.269...v0.0.270) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cc3463174..1a68daa54 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==3.3.1 -ruff==0.0.269 +ruff==0.0.270 mypy==1.3.0 pre-commit==3.3.2 pytest==7.3.1 From cb7a0f9bff3d4825cca56a589f12399f5d7a686d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 03:57:23 +0000 Subject: [PATCH 10/26] Bump orjson from 3.8.12 to 3.8.14 Bumps [orjson](https://github.com/ijl/orjson) from 3.8.12 to 3.8.14. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.8.12...3.8.14) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cff54ca1c..f8c34100a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,7 +32,7 @@ py_find_1st==1.1.5 # Load ticker files 30% faster python-rapidjson==1.10 # Properly format api responses -orjson==3.8.12 +orjson==3.8.14 # Notify systemd sdnotify==0.3.2 From 35836479de24b98f1c375587949c99d251f807ae Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 May 2023 08:09:56 +0200 Subject: [PATCH 11/26] Bump requests pre-commit dependency --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4be298d7b..8a39a7773 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: additional_dependencies: - types-cachetools==5.3.0.5 - types-filelock==3.2.7 - - types-requests==2.30.0.0 + - types-requests==2.31.0.0 - types-tabulate==0.9.0.2 - types-python-dateutil==2.8.19.13 - SQLAlchemy==2.0.15 From 9f1bdc19aab3b2162a4df6cfff01aef53ceaf017 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 May 2023 08:10:29 +0200 Subject: [PATCH 12/26] Bump ruff pre-commit version --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4be298d7b..4724db36f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.0.263' + rev: 'v0.0.270' hooks: - id: ruff From f074383d6a91933d224266189c54d7e46a936170 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 May 2023 17:24:04 +0200 Subject: [PATCH 13/26] Extract orderbook logic into separate method --- freqtrade/exchange/exchange.py | 36 ++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 3b1466c69..ac7306736 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1669,19 +1669,8 @@ class Exchange: order_book_top = conf_strategy.get('order_book_top', 1) if order_book is None: order_book = self.fetch_l2_order_book(pair, order_book_top) - logger.debug('order_book %s', order_book) - # top 1 = index 0 - try: - obside: OBLiteral = 'bids' if price_side == 'bid' else 'asks' - rate = order_book[obside][order_book_top - 1][0] - except (IndexError, KeyError) as e: - logger.warning( - f"{pair} - {name} Price at location {order_book_top} from orderbook " - f"could not be determined. Orderbook: {order_book}" - ) - raise PricingError from e - logger.debug(f"{pair} - {name} price from orderbook {price_side_word}" - f"side - top {order_book_top} order book {side} rate {rate:.8f}") + rate = self.get_rate_from_ob(pair, side, order_book, name, price_side, + order_book_top) else: logger.debug(f"Using Last {price_side_word} / Last Price") if ticker is None: @@ -1703,6 +1692,27 @@ class Exchange: return rate + def get_rate_from_ob(self, pair: str, side: EntryExit, order_book: OrderBook, name: str, + price_side: BidAsk, order_book_top: int) -> float: + """ + Get rate from orderbook + :raises: PricingError if rate could not be determined. + """ + logger.debug('order_book %s', order_book) + # top 1 = index 0 + try: + obside: OBLiteral = 'bids' if price_side == 'bid' else 'asks' + rate = order_book[obside][order_book_top - 1][0] + except (IndexError, KeyError) as e: + logger.warning( + f"{pair} - {name} Price at location {order_book_top} from orderbook " + f"could not be determined. Orderbook: {order_book}" + ) + raise PricingError from e + logger.debug(f"{pair} - {name} price from orderbook {price_side.capitalize()}" + f"side - top {order_book_top} order book {side} rate {rate:.8f}") + return rate + def get_rates(self, pair: str, refresh: bool, is_short: bool) -> Tuple[float, float]: entry_rate = None exit_rate = None From af1dbf7dffd6effc7746233fbfdb1f8664fa02ba Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 May 2023 17:27:11 +0200 Subject: [PATCH 14/26] Extract get_rate_from_ticker from get_rate method --- freqtrade/exchange/exchange.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index ac7306736..0406146cd 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1669,21 +1669,13 @@ class Exchange: order_book_top = conf_strategy.get('order_book_top', 1) if order_book is None: order_book = self.fetch_l2_order_book(pair, order_book_top) - rate = self.get_rate_from_ob(pair, side, order_book, name, price_side, - order_book_top) + rate = self._get_rate_from_ob(pair, side, order_book, name, price_side, + order_book_top) else: logger.debug(f"Using Last {price_side_word} / Last Price") if ticker is None: ticker = self.fetch_ticker(pair) - ticker_rate = ticker[price_side] - if ticker['last'] and ticker_rate: - if side == 'entry' and ticker_rate > ticker['last']: - balance = conf_strategy.get('price_last_balance', 0.0) - ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate) - elif side == 'exit' and ticker_rate < ticker['last']: - balance = conf_strategy.get('price_last_balance', 0.0) - ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last']) - rate = ticker_rate + rate = self._get_rate_from_ticker(side, ticker, conf_strategy, price_side) if rate is None: raise PricingError(f"{name}-Rate for {pair} was empty.") @@ -1692,8 +1684,24 @@ class Exchange: return rate - def get_rate_from_ob(self, pair: str, side: EntryExit, order_book: OrderBook, name: str, - price_side: BidAsk, order_book_top: int) -> float: + def _get_rate_from_ticker(self, side: EntryExit, ticker: Ticker, conf_strategy: Dict[str, Any], + price_side: BidAsk) -> Optional[float]: + """ + Get rate from ticker. + """ + ticker_rate = ticker[price_side] + if ticker['last'] and ticker_rate: + if side == 'entry' and ticker_rate > ticker['last']: + balance = conf_strategy.get('price_last_balance', 0.0) + ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate) + elif side == 'exit' and ticker_rate < ticker['last']: + balance = conf_strategy.get('price_last_balance', 0.0) + ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last']) + rate = ticker_rate + return rate + + def _get_rate_from_ob(self, pair: str, side: EntryExit, order_book: OrderBook, name: str, + price_side: BidAsk, order_book_top: int) -> float: """ Get rate from orderbook :raises: PricingError if rate could not be determined. From b666c418bbb7d9903ca20676f12f39574753062a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 May 2023 17:33:11 +0200 Subject: [PATCH 15/26] Don't use variables for simple debug values --- freqtrade/exchange/exchange.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 0406146cd..6edaf2661 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1662,8 +1662,6 @@ class Exchange: price_side = self._get_price_side(side, is_short, conf_strategy) - price_side_word = price_side.capitalize() - if conf_strategy.get('use_order_book', False): order_book_top = conf_strategy.get('order_book_top', 1) @@ -1672,7 +1670,7 @@ class Exchange: rate = self._get_rate_from_ob(pair, side, order_book, name, price_side, order_book_top) else: - logger.debug(f"Using Last {price_side_word} / Last Price") + logger.debug(f"Using Last {price_side.capitalize()} / Last Price") if ticker is None: ticker = self.fetch_ticker(pair) rate = self._get_rate_from_ticker(side, ticker, conf_strategy, price_side) From 1f543666f427d664098e39a4605df5174fbd2345 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 31 May 2023 11:46:31 +0200 Subject: [PATCH 16/26] Improve test for reload-markets timings, fix bug closes #8714 --- freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 6edaf2661..88022e19c 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -191,7 +191,7 @@ class Exchange: # Converts the interval provided in minutes in config to seconds self.markets_refresh_interval: int = exchange_conf.get( - "markets_refresh_interval", 60) * 60 + "markets_refresh_interval", 60) * 60 * 1000 if self.trading_mode != TradingMode.SPOT and load_leverage_tiers: self.fill_leverage_tiers() diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index ef70c8ba1..f022a0905 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -633,21 +633,23 @@ def test__load_markets(default_conf, mocker, caplog): assert ex.markets == expected_return -def test_reload_markets(default_conf, mocker, caplog): +def test_reload_markets(default_conf, mocker, caplog, time_machine): caplog.set_level(logging.DEBUG) initial_markets = {'ETH/BTC': {}} updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}} - + start_dt = dt_now() + time_machine.move_to(start_dt, tick=False) api_mock = MagicMock() api_mock.load_markets = MagicMock(return_value=initial_markets) default_conf['exchange']['markets_refresh_interval'] = 10 exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance", mock_markets=False) exchange._load_async_markets = MagicMock() - exchange._last_markets_refresh = dt_ts() + assert exchange._last_markets_refresh == dt_ts() assert exchange.markets == initial_markets + time_machine.move_to(start_dt + timedelta(minutes=8), tick=False) # less than 10 minutes have passed, no reload exchange.reload_markets() assert exchange.markets == initial_markets @@ -655,12 +657,18 @@ def test_reload_markets(default_conf, mocker, caplog): api_mock.load_markets = MagicMock(return_value=updated_markets) # more than 10 minutes have passed, reload is executed - exchange._last_markets_refresh = dt_ts(dt_now() - timedelta(minutes=15)) + time_machine.move_to(start_dt + timedelta(minutes=11), tick=False) exchange.reload_markets() assert exchange.markets == updated_markets assert exchange._load_async_markets.call_count == 1 assert log_has('Performing scheduled market reload..', caplog) + # Not called again + exchange._load_async_markets.reset_mock() + + exchange.reload_markets() + assert exchange._load_async_markets.call_count == 0 + def test_reload_markets_exception(default_conf, mocker, caplog): caplog.set_level(logging.DEBUG) From 193d88c9c85190a0cc6a501bd12dec578d226060 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 31 May 2023 14:12:03 +0200 Subject: [PATCH 17/26] Double-check cancelling stop order didn't close the trade --- freqtrade/freqtradebot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index fc4c65caf..42ac85fdb 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1302,6 +1302,10 @@ class FreqtradeBot(LoggingMixin): f"(orderid:{order['id']}) in order to add another one ...") self.cancel_stoploss_on_exchange(trade) + if not trade.is_open: + logger.warning( + f"Trade {trade} is closed, not creating trailing stoploss order.") + return # Create new stoploss order if not self.create_stoploss_order(trade=trade, stop_price=stoploss_norm): From 5311614d54dadb38747f74c605f5040d2a4cd438 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 31 May 2023 14:31:45 +0200 Subject: [PATCH 18/26] Update force exit wording --- freqtrade/rpc/rpc.py | 6 +++--- tests/rpc/test_rpc.py | 10 +++++----- tests/rpc/test_rpc_apiserver.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index dedb35503..6e1cc2b76 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -802,12 +802,12 @@ class RPC: with self._freqtrade._exit_lock: if trade_id == 'all': - # Execute sell for all open orders + # Execute exit for all open orders for trade in Trade.get_open_trades(): self.__exec_force_exit(trade, ordertype) Trade.commit() self._freqtrade.wallets.update() - return {'result': 'Created sell orders for all open trades.'} + return {'result': 'Created exit orders for all open trades.'} # Query for trade trade = Trade.get_trades( @@ -820,7 +820,7 @@ class RPC: self.__exec_force_exit(trade, ordertype, amount) Trade.commit() self._freqtrade.wallets.update() - return {'result': f'Created sell order for trade {trade_id}.'} + return {'result': f'Created exit order for trade {trade_id}.'} def _force_entry_validations(self, pair: str, order_side: SignalDirection): if not self._freqtrade.config.get('force_entry_enable', False): diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 405727d8c..da5882cf1 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -703,15 +703,15 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: rpc._rpc_force_exit(None) msg = rpc._rpc_force_exit('all') - assert msg == {'result': 'Created sell orders for all open trades.'} + assert msg == {'result': 'Created exit orders for all open trades.'} freqtradebot.enter_positions() msg = rpc._rpc_force_exit('all') - assert msg == {'result': 'Created sell orders for all open trades.'} + assert msg == {'result': 'Created exit orders for all open trades.'} freqtradebot.enter_positions() msg = rpc._rpc_force_exit('2') - assert msg == {'result': 'Created sell order for trade 2.'} + assert msg == {'result': 'Created exit order for trade 2.'} freqtradebot.state = State.STOPPED with pytest.raises(RPCException, match=r'.*trader is not running*'): @@ -775,7 +775,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: ) # check that the trade is called, which is done by ensuring exchange.cancel_order is called msg = rpc._rpc_force_exit('4') - assert msg == {'result': 'Created sell order for trade 4.'} + assert msg == {'result': 'Created exit order for trade 4.'} assert cancel_order_mock.call_count == 2 assert trade.amount == amount @@ -795,7 +795,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: } ) msg = rpc._rpc_force_exit('3') - assert msg == {'result': 'Created sell order for trade 3.'} + assert msg == {'result': 'Created exit order for trade 3.'} # status quo, no exchange calls assert cancel_order_mock.call_count == 3 diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 842981ad0..5ba42f6ea 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1333,7 +1333,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): rc = client_post(client, f"{BASE_URI}/forceexit", data={"tradeid": "5", "ordertype": "market", "amount": 23}) assert_response(rc) - assert rc.json() == {'result': 'Created sell order for trade 5.'} + assert rc.json() == {'result': 'Created exit order for trade 5.'} Trade.rollback() trade = Trade.get_trades([Trade.id == 5]).first() @@ -1343,7 +1343,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): rc = client_post(client, f"{BASE_URI}/forceexit", data={"tradeid": "5"}) assert_response(rc) - assert rc.json() == {'result': 'Created sell order for trade 5.'} + assert rc.json() == {'result': 'Created exit order for trade 5.'} Trade.rollback() trade = Trade.get_trades([Trade.id == 5]).first() From 08d040db14c8cb196c63e361076179c24fbb4501 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 31 May 2023 14:59:41 +0200 Subject: [PATCH 19/26] Slightly update force_exit test --- tests/rpc/test_rpc.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index da5882cf1..ef5f0f78e 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -761,6 +761,8 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: freqtradebot.config['max_open_trades'] = 3 freqtradebot.enter_positions() + + cancel_order_mock.reset_mock() trade = Trade.session.scalars(select(Trade).filter(Trade.id == '2')).first() amount = trade.amount # make an limit-buy open trade, if there is no 'filled', don't sell it @@ -776,11 +778,12 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: # check that the trade is called, which is done by ensuring exchange.cancel_order is called msg = rpc._rpc_force_exit('4') assert msg == {'result': 'Created exit order for trade 4.'} - assert cancel_order_mock.call_count == 2 + assert cancel_order_mock.call_count == 1 assert trade.amount == amount + cancel_order_mock.reset_mock() trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first() - + amount = trade.amount # make an limit-sell open trade mocker.patch( f'{EXMS}.fetch_order', @@ -797,7 +800,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: msg = rpc._rpc_force_exit('3') assert msg == {'result': 'Created exit order for trade 3.'} # status quo, no exchange calls - assert cancel_order_mock.call_count == 3 + assert cancel_order_mock.call_count == 1 def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: From 430cd24bbcb0e9ad9188a97c1b46bb5a279dd0f6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 31 May 2023 15:00:09 +0200 Subject: [PATCH 20/26] Invert order (exit trade 3 before trade 4) --- tests/rpc/test_rpc.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index ef5f0f78e..5a9e94e8d 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -762,25 +762,6 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: freqtradebot.config['max_open_trades'] = 3 freqtradebot.enter_positions() - cancel_order_mock.reset_mock() - trade = Trade.session.scalars(select(Trade).filter(Trade.id == '2')).first() - amount = trade.amount - # make an limit-buy open trade, if there is no 'filled', don't sell it - mocker.patch( - f'{EXMS}.fetch_order', - return_value={ - 'status': 'open', - 'type': 'limit', - 'side': 'buy', - 'filled': None - } - ) - # check that the trade is called, which is done by ensuring exchange.cancel_order is called - msg = rpc._rpc_force_exit('4') - assert msg == {'result': 'Created exit order for trade 4.'} - assert cancel_order_mock.call_count == 1 - assert trade.amount == amount - cancel_order_mock.reset_mock() trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first() amount = trade.amount @@ -802,6 +783,25 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: # status quo, no exchange calls assert cancel_order_mock.call_count == 1 + cancel_order_mock.reset_mock() + trade = Trade.session.scalars(select(Trade).filter(Trade.id == '2')).first() + amount = trade.amount + # make an limit-buy open trade, if there is no 'filled', don't sell it + mocker.patch( + f'{EXMS}.fetch_order', + return_value={ + 'status': 'open', + 'type': 'limit', + 'side': 'buy', + 'filled': None + } + ) + # check that the trade is called, which is done by ensuring exchange.cancel_order is called + msg = rpc._rpc_force_exit('4') + assert msg == {'result': 'Created exit order for trade 4.'} + assert cancel_order_mock.call_count == 1 + assert trade.amount == amount + def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) From 5fc8426b9bedc9e583b7cc5e876a530168136242 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 31 May 2023 17:06:51 +0200 Subject: [PATCH 21/26] Improve handling of order cancelation failures with force_exit closes #8708 --- freqtrade/rpc/rpc.py | 12 ++++++++++-- tests/rpc/test_rpc.py | 33 +++++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 6e1cc2b76..c3759e03a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -755,7 +755,7 @@ class RPC: return {'status': 'Reloaded from orders from exchange'} def __exec_force_exit(self, trade: Trade, ordertype: Optional[str], - amount: Optional[float] = None) -> None: + amount: Optional[float] = None) -> bool: # Check if there is there is an open order fully_canceled = False if trade.open_order_id: @@ -770,6 +770,9 @@ class RPC: self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_EXIT']) if not fully_canceled: + if trade.open_order_id is not None: + # Order cancellation failed, so we can't exit. + return False # Get current rate and execute sell current_rate = self._freqtrade.exchange.get_rate( trade.pair, side='exit', is_short=trade.is_short, refresh=True) @@ -790,6 +793,9 @@ class RPC: trade, current_rate, exit_check, ordertype=order_type, sub_trade_amt=sub_amount) + return True + return False + def _rpc_force_exit(self, trade_id: str, ordertype: Optional[str] = None, *, amount: Optional[float] = None) -> Dict[str, str]: """ @@ -817,9 +823,11 @@ class RPC: logger.warning('force_exit: Invalid argument received') raise RPCException('invalid argument') - self.__exec_force_exit(trade, ordertype, amount) + result = self.__exec_force_exit(trade, ordertype, amount) Trade.commit() self._freqtrade.wallets.update() + if not result: + raise RPCException('Failed to exit trade.') return {'result': f'Created exit order for trade {trade_id}.'} def _force_entry_validations(self, pair: str, order_side: SignalDirection): diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 5a9e94e8d..31d7ae37b 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -765,7 +765,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: cancel_order_mock.reset_mock() trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first() amount = trade.amount - # make an limit-sell open trade + # make an limit-sell open order trade mocker.patch( f'{EXMS}.fetch_order', return_value={ @@ -778,12 +778,24 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: 'id': trade.orders[0].order_id, } ) + cancel_order_3 = mocker.patch( + f'{EXMS}.cancel_order_with_result', + return_value={ + 'status': 'canceled', + 'type': 'limit', + 'side': 'sell', + 'amount': amount, + 'remaining': amount, + 'filled': 0.0, + 'id': trade.orders[0].order_id, + } + ) msg = rpc._rpc_force_exit('3') assert msg == {'result': 'Created exit order for trade 3.'} # status quo, no exchange calls - assert cancel_order_mock.call_count == 1 + assert cancel_order_3.call_count == 1 + assert cancel_order_mock.call_count == 0 - cancel_order_mock.reset_mock() trade = Trade.session.scalars(select(Trade).filter(Trade.id == '2')).first() amount = trade.amount # make an limit-buy open trade, if there is no 'filled', don't sell it @@ -796,10 +808,23 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: 'filled': None } ) + cancel_order_4 = mocker.patch( + f'{EXMS}.cancel_order_with_result', + return_value={ + 'status': 'canceled', + 'type': 'limit', + 'side': 'sell', + 'amount': amount, + 'remaining': 0.0, + 'filled': amount, + 'id': trade.orders[0].order_id, + } + ) # check that the trade is called, which is done by ensuring exchange.cancel_order is called msg = rpc._rpc_force_exit('4') assert msg == {'result': 'Created exit order for trade 4.'} - assert cancel_order_mock.call_count == 1 + assert cancel_order_4.call_count == 1 + assert cancel_order_mock.call_count == 0 assert trade.amount == amount From 9c6fee384143683ae564ae39421532f8258ca9fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 31 May 2023 17:14:22 +0200 Subject: [PATCH 22/26] Enable gate futures for spread-filter again closes #8687 --- freqtrade/exchange/gate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/exchange/gate.py b/freqtrade/exchange/gate.py index 2ac135fc1..eceab4ec1 100644 --- a/freqtrade/exchange/gate.py +++ b/freqtrade/exchange/gate.py @@ -33,7 +33,6 @@ class Gate(Exchange): _ft_has_futures: Dict = { "needs_trading_fees": True, "marketOrderRequiresPrice": False, - "tickers_have_bid_ask": False, "fee_cost_in_contracts": False, # Set explicitly to false for clarity "order_props_in_contracts": ['amount', 'filled', 'remaining'], "stop_price_type_field": "price_type", From e0d9603e997a8386e3c46f61354c37a660786db3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Jun 2023 07:03:07 +0200 Subject: [PATCH 23/26] Raise correct httperrorcode for webserver-only endpoitns --- freqtrade/rpc/api_server/deps.py | 5 +++-- tests/rpc/test_rpc_apiserver.py | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/api_server/deps.py b/freqtrade/rpc/api_server/deps.py index 8fd105d3e..4c118d274 100644 --- a/freqtrade/rpc/api_server/deps.py +++ b/freqtrade/rpc/api_server/deps.py @@ -1,7 +1,7 @@ from typing import Any, AsyncIterator, Dict, Optional from uuid import uuid4 -from fastapi import Depends +from fastapi import Depends, HTTPException from freqtrade.enums import RunMode from freqtrade.persistence import Trade @@ -57,5 +57,6 @@ def get_message_stream(): def is_webserver_mode(config=Depends(get_config)): if config['runmode'] != RunMode.WEBSERVER: - raise RPCException('Bot is not in the correct state') + raise HTTPException(status_code=503, + detail='Bot is not in the correct state.') return None diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 5ba42f6ea..cdf620b90 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1673,7 +1673,8 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): rc = client_get(client, f"{BASE_URI}/backtest") # Backtest prevented in default mode - assert_response(rc, 502) + assert_response(rc, 503) + assert rc.json()['detail'] == 'Bot is not in the correct state.' ftbot.config['runmode'] = RunMode.WEBSERVER # Backtesting not started yet @@ -1812,7 +1813,9 @@ def test_api_backtest_history(botclient, mocker, testdatadir): ]) rc = client_get(client, f"{BASE_URI}/backtest/history") - assert_response(rc, 502) + assert_response(rc, 503) + assert rc.json()['detail'] == 'Bot is not in the correct state.' + ftbot.config['user_data_dir'] = testdatadir ftbot.config['runmode'] = RunMode.WEBSERVER From 8aee368f60a4629dd50e1e1479aa91ae394d4f0f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Jun 2023 07:07:02 +0200 Subject: [PATCH 24/26] auto-inject webserver mode dependency --- freqtrade/rpc/api_server/api_backtest.py | 17 ++++++++--------- freqtrade/rpc/api_server/webserver.py | 4 +++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 8fa1a87b8..411ba4978 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -16,14 +16,14 @@ from freqtrade.exchange.common import remove_exchange_credentials from freqtrade.misc import deep_merge_dicts from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest, BacktestResponse) -from freqtrade.rpc.api_server.deps import get_config, is_webserver_mode +from freqtrade.rpc.api_server.deps import get_config from freqtrade.rpc.api_server.webserver_bgwork import ApiBG from freqtrade.rpc.rpc import RPCException logger = logging.getLogger(__name__) -# Private API, protected by authentication +# Private API, protected by authentication and webserver_mode dependency router = APIRouter() @@ -102,7 +102,7 @@ def __run_backtest_bg(btconfig: Config): @router.post('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) async def api_start_backtest( bt_settings: BacktestRequest, background_tasks: BackgroundTasks, - config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): + config=Depends(get_config)): ApiBG.bt['bt_error'] = None """Start backtesting if not done so already""" if ApiBG.bgtask_running: @@ -143,7 +143,7 @@ async def api_start_backtest( @router.get('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) -def api_get_backtest(ws_mode=Depends(is_webserver_mode)): +def api_get_backtest(): """ Get backtesting result. Returns Result after backtesting has been ran. @@ -188,7 +188,7 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): @router.delete('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) -def api_delete_backtest(ws_mode=Depends(is_webserver_mode)): +def api_delete_backtest(): """Reset backtesting""" if ApiBG.bgtask_running: return { @@ -215,7 +215,7 @@ def api_delete_backtest(ws_mode=Depends(is_webserver_mode)): @router.get('/backtest/abort', response_model=BacktestResponse, tags=['webserver', 'backtest']) -def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): +def api_backtest_abort(): if not ApiBG.bgtask_running: return { "status": "not_running", @@ -236,15 +236,14 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): @router.get('/backtest/history', response_model=List[BacktestHistoryEntry], tags=['webserver', 'backtest']) -def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): +def api_backtest_history(config=Depends(get_config)): # Get backtest result history, read from metadata files return get_backtest_resultlist(config['user_data_dir'] / 'backtest_results') @router.get('/backtest/history/result', response_model=BacktestResponse, tags=['webserver', 'backtest']) -def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), - ws_mode=Depends(is_webserver_mode)): +def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config)): # Get backtest result history, read from metadata files fn = config['user_data_dir'] / 'backtest_results' / filename results: Dict[str, Any] = { diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index 165849a7f..ea623e0ed 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -118,6 +118,7 @@ class ApiServer(RPCHandler): from freqtrade.rpc.api_server.api_v1 import router as api_v1 from freqtrade.rpc.api_server.api_v1 import router_public as api_v1_public from freqtrade.rpc.api_server.api_ws import router as ws_router + from freqtrade.rpc.api_server.deps import is_webserver_mode from freqtrade.rpc.api_server.web_ui import router_ui app.include_router(api_v1_public, prefix="/api/v1") @@ -126,7 +127,8 @@ class ApiServer(RPCHandler): dependencies=[Depends(http_basic_or_jwt_token)], ) app.include_router(api_backtest, prefix="/api/v1", - dependencies=[Depends(http_basic_or_jwt_token)], + dependencies=[Depends(http_basic_or_jwt_token), + Depends(is_webserver_mode)], ) app.include_router(ws_router, prefix="/api/v1") app.include_router(router_login, prefix="/api/v1", tags=["auth"]) From d0f900f567fe7a7269de51e641aed2d09460a281 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Jun 2023 20:09:59 +0200 Subject: [PATCH 25/26] set HTTPX level to warning closes #8717 --- freqtrade/loggers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 58f207608..dc01e4e5b 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -32,7 +32,7 @@ def _set_loggers(verbosity: int = 0, api_verbosity: str = 'info') -> None: logging.INFO if verbosity <= 2 else logging.DEBUG ) logging.getLogger('telegram').setLevel(logging.INFO) - logging.getLogger('httpx').setLevel(logging.INFO) + logging.getLogger('httpx').setLevel(logging.WARNING) logging.getLogger('werkzeug').setLevel( logging.ERROR if api_verbosity == 'error' else logging.INFO From 565c0496d9c6cbf0cf81998f7037e2e2d2392000 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Jun 2023 20:10:57 +0200 Subject: [PATCH 26/26] Bump httpx min requirement --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1cd4dce1f..d04774361 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ aiohttp==3.8.4 SQLAlchemy==2.0.15 python-telegram-bot==20.3 # can't be hard-pinned due to telegram-bot pinning httpx with ~ -httpx>=0.23.3 +httpx>=0.24.1 arrow==1.2.3 cachetools==5.3.1 requests==2.31.0 diff --git a/setup.py b/setup.py index f8b8b515c..106b5b6d3 100644 --- a/setup.py +++ b/setup.py @@ -107,7 +107,7 @@ setup( 'ast-comments', 'aiohttp', 'cryptography', - 'httpx', + 'httpx>=0.24.1', 'python-dateutil', 'packaging', ],