From 168593d7af3d4ccfcbbd1462ca2cf2b5a03f3e3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 03:13:34 +0000 Subject: [PATCH 1/5] chore(deps): bump ccxt from 4.4.82 to 4.4.85 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.82 to 4.4.85. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/v4.4.82...v4.4.85) --- updated-dependencies: - dependency-name: ccxt dependency-version: 4.4.85 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 9eea5f738..45edac9fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.2 numexpr==2.10.2 pandas-ta==0.3.14b -ccxt==4.4.82 +ccxt==4.4.85 cryptography==45.0.2 aiohttp==3.9.5 SQLAlchemy==2.0.41 From 43fea433631df852bb630ce544c304c09b668fc8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 26 May 2025 07:15:35 +0200 Subject: [PATCH 2/5] chore: bump aiohttp to 3.11.18 Aligns dependency with ccxt requirements. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 45edac9fe..b7765159c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ pandas-ta==0.3.14b ccxt==4.4.85 cryptography==45.0.2 -aiohttp==3.9.5 +aiohttp==3.11.18 SQLAlchemy==2.0.41 python-telegram-bot==22.1 # can't be hard-pinned due to telegram-bot pinning httpx with ~ From 33b548206518fe79f60129c4a570adb3452873e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 May 2025 07:06:57 +0200 Subject: [PATCH 3/5] fix: memory leak on binance in combination with aiohttp>3.10 apparently, returning big data through run_until_complete can cause this (or closes #11317 --- freqtrade/exchange/exchange.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 88e392fc8..820b0e99c 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -637,9 +637,9 @@ class Exchange: if self._exchange_ws: self._exchange_ws.reset_connections() - async def _api_reload_markets(self, reload: bool = False) -> dict[str, Any]: + async def _api_reload_markets(self, reload: bool = False) -> None: try: - return await self._api_async.load_markets(reload=reload, params={}) + await self._api_async.load_markets(reload=reload, params={}) except ccxt.DDoSProtection as e: raise DDosProtection(e) from e except (ccxt.OperationFailed, ccxt.ExchangeError) as e: @@ -649,14 +649,14 @@ class Exchange: except ccxt.BaseError as e: raise TemporaryError(e) from e - def _load_async_markets(self, reload: bool = False) -> dict[str, Any]: + def _load_async_markets(self, reload: bool = False) -> None: try: with self._loop_lock: markets = self.loop.run_until_complete(self._api_reload_markets(reload=reload)) if isinstance(markets, Exception): raise markets - return markets + return None except asyncio.TimeoutError as e: logger.warning("Could not load markets. Reason: %s", e) raise TemporaryError from e @@ -679,7 +679,8 @@ class Exchange: # on initial load, we retry 3 times to ensure we get the markets retries: int = 3 if force else 0 # Reload async markets, then assign them to sync api - self._markets = retrier(self._load_async_markets, retries=retries)(reload=True) + retrier(self._load_async_markets, retries=retries)(reload=True) + self._markets = self._api_async.markets self._api.set_markets(self._api_async.markets, self._api_async.currencies) # Assign options array, as it contains some temporary information from the exchange. self._api.options = self._api_async.options From b8b94d58c49a8e07e3c49938b9dd331489c27be0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 May 2025 20:12:59 +0200 Subject: [PATCH 4/5] test: update tests for new reload_markets behavior --- tests/exchange/test_exchange.py | 42 +++++++++++++++++---------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index bb0c82b8a..ab4ea8dce 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -590,7 +590,8 @@ def test__load_markets(default_conf, mocker, caplog): expected_return = {"ETH/BTC": "available"} api_mock = MagicMock() - api_mock.load_markets = get_mock_coro(return_value=expected_return) + api_mock.load_markets = get_mock_coro() + api_mock.markets = expected_return mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) default_conf["exchange"]["pair_whitelist"] = ["ETH/BTC"] ex = Exchange(default_conf) @@ -606,6 +607,7 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine): time_machine.move_to(start_dt, tick=False) api_mock = MagicMock() api_mock.load_markets = get_mock_coro(return_value=initial_markets) + api_mock.markets = initial_markets default_conf["exchange"]["markets_refresh_interval"] = 10 exchange = get_patched_exchange( mocker, default_conf, api_mock, exchange="binance", mock_markets=False @@ -624,6 +626,7 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine): api_mock.load_markets = get_mock_coro(return_value=updated_markets) # more than 10 minutes have passed, reload is executed time_machine.move_to(start_dt + timedelta(minutes=11), tick=False) + api_mock.markets = updated_markets exchange.reload_markets() assert exchange.markets == updated_markets assert lam_spy.call_count == 1 @@ -669,34 +672,33 @@ def test_reload_markets_exception(default_conf, mocker, caplog): @pytest.mark.parametrize("stake_currency", ["ETH", "BTC", "USDT"]) -def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): +def test_validate_stakecurrency(default_conf, stake_currency, mocker): default_conf["stake_currency"] = stake_currency api_mock = MagicMock() - type(api_mock).load_markets = get_mock_coro( - return_value={ - "ETH/BTC": {"quote": "BTC"}, - "LTC/BTC": {"quote": "BTC"}, - "XRP/ETH": {"quote": "ETH"}, - "NEO/USDT": {"quote": "USDT"}, - } - ) + api_mock.load_markets = get_mock_coro() + api_mock.markets = { + "ETH/BTC": {"quote": "BTC"}, + "LTC/BTC": {"quote": "BTC"}, + "XRP/ETH": {"quote": "ETH"}, + "NEO/USDT": {"quote": "USDT"}, + } mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) mocker.patch(f"{EXMS}.validate_timeframes") mocker.patch(f"{EXMS}.validate_pricing") Exchange(default_conf) -def test_validate_stakecurrency_error(default_conf, mocker, caplog): +def test_validate_stakecurrency_error(default_conf, mocker): default_conf["stake_currency"] = "XRP" api_mock = MagicMock() - type(api_mock).load_markets = get_mock_coro( - return_value={ - "ETH/BTC": {"quote": "BTC"}, - "LTC/BTC": {"quote": "BTC"}, - "XRP/ETH": {"quote": "ETH"}, - "NEO/USDT": {"quote": "USDT"}, - } - ) + api_mock.load_markets = get_mock_coro() + api_mock.markets = { + "ETH/BTC": {"quote": "BTC"}, + "LTC/BTC": {"quote": "BTC"}, + "XRP/ETH": {"quote": "ETH"}, + "NEO/USDT": {"quote": "USDT"}, + } + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) mocker.patch(f"{EXMS}.validate_timeframes") with pytest.raises( @@ -705,7 +707,7 @@ def test_validate_stakecurrency_error(default_conf, mocker, caplog): ): Exchange(default_conf) - type(api_mock).load_markets = get_mock_coro(side_effect=ccxt.NetworkError("No connection.")) + api_mock.load_markets = get_mock_coro(side_effect=ccxt.NetworkError("No connection.")) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) with pytest.raises( From 77dcc5354cc581f4247c757ece92b0fbb039098a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 May 2025 07:08:58 +0200 Subject: [PATCH 5/5] test: update hyperliquid test for new load_markets behavior --- tests/exchange/test_hyperliquid.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/exchange/test_hyperliquid.py b/tests/exchange/test_hyperliquid.py index 808bef802..a169a9a73 100644 --- a/tests/exchange/test_hyperliquid.py +++ b/tests/exchange/test_hyperliquid.py @@ -283,7 +283,8 @@ def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker): default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" default_conf["stake_currency"] = "USDC" - api_mock.load_markets = get_mock_coro(return_value=markets) + api_mock.load_markets = get_mock_coro() + api_mock.markets = markets exchange = get_patched_exchange( mocker, default_conf, api_mock, exchange="hyperliquid", mock_markets=False )