From 0c16a45999688b39feb0c55be17b15f02dcf3d24 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 13:56:28 +0200 Subject: [PATCH 01/21] Fix odd bug related to singleton usage --- tests/rpc/test_fiat_convert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 049716414..dbc69a9fd 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -116,12 +116,12 @@ def test_fiat_convert_without_network(mocker): fiat_convert = CryptoToFiatConverter() - cmc_temp = CryptoToFiatConverter._coingecko - CryptoToFiatConverter._coingecko = None + cmc_temp = fiat_convert._coingecko + fiat_convert._coingecko = None assert fiat_convert._coingecko is None assert fiat_convert._find_price(crypto_symbol="btc", fiat_symbol="usd") == 0.0 - CryptoToFiatConverter._coingecko = cmc_temp + fiat_convert._coingecko = cmc_temp def test_fiat_too_many_requests_response(mocker, caplog): From 95077f4752a643382c5201797e8a48411e129bac Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 13:57:06 +0200 Subject: [PATCH 02/21] Remove unused "mocker" fixtures in fiat_convert --- tests/rpc/test_fiat_convert.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index dbc69a9fd..91543f01b 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -11,7 +11,7 @@ from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from tests.conftest import log_has, log_has_re -def test_fiat_convert_is_supported(mocker): +def test_fiat_convert_is_supported(): fiat_convert = CryptoToFiatConverter() assert fiat_convert._is_supported_fiat(fiat="USD") is True assert fiat_convert._is_supported_fiat(fiat="usd") is True @@ -77,19 +77,19 @@ def test_fiat_convert_get_price(mocker): assert find_price.call_count == 1 -def test_fiat_convert_same_currencies(mocker): +def test_fiat_convert_same_currencies(): fiat_convert = CryptoToFiatConverter() assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="USD") == 1.0 -def test_fiat_convert_two_FIAT(mocker): +def test_fiat_convert_two_FIAT(): fiat_convert = CryptoToFiatConverter() assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="EUR") == 0.0 -def test_loadcryptomap(mocker): +def test_loadcryptomap(): fiat_convert = CryptoToFiatConverter() assert len(fiat_convert._coinlistings) == 2 @@ -111,7 +111,7 @@ def test_fiat_init_network_exception(mocker): assert len(fiat_convert._coinlistings) == 0 -def test_fiat_convert_without_network(mocker): +def test_fiat_convert_without_network(): # Because CryptoToFiatConverter is a Singleton we reset the value of _coingecko fiat_convert = CryptoToFiatConverter() @@ -144,7 +144,7 @@ def test_fiat_too_many_requests_response(mocker, caplog): ) -def test_fiat_multiple_coins(mocker, caplog): +def test_fiat_multiple_coins(caplog): fiat_convert = CryptoToFiatConverter() fiat_convert._coinlistings = [ {"id": "helium", "symbol": "hnt", "name": "Helium"}, From 7a309d69279642cf1222762dccb4f48af670ceeb Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 13:58:15 +0200 Subject: [PATCH 03/21] Add explicit "fiat convert singleton" code --- tests/rpc/test_fiat_convert.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 91543f01b..d72d99abf 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -11,6 +11,14 @@ from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from tests.conftest import log_has, log_has_re +def test_fiat_convert_is_singleton(): + fiat_convert = CryptoToFiatConverter() + fiat_convert2 = CryptoToFiatConverter() + + assert fiat_convert is fiat_convert2 + assert id(fiat_convert) == id(fiat_convert2) + + def test_fiat_convert_is_supported(): fiat_convert = CryptoToFiatConverter() assert fiat_convert._is_supported_fiat(fiat="USD") is True From c1f780794affb4a44dee806087ece267931632b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 14:02:09 +0200 Subject: [PATCH 04/21] Add CoinGeckoApi Wrapper --- freqtrade/util/CoinGecko.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 freqtrade/util/CoinGecko.py diff --git a/freqtrade/util/CoinGecko.py b/freqtrade/util/CoinGecko.py new file mode 100644 index 000000000..24f0b8664 --- /dev/null +++ b/freqtrade/util/CoinGecko.py @@ -0,0 +1,24 @@ +from pycoingecko import CoinGeckoAPI + + +class FtCoinGeckoApi(CoinGeckoAPI): + """ + Simple wrapper around pycoingecko's api to support Demo API keys. + + """ + + __API_URL_BASE = "https://api.coingecko.com/api/v3/" + __PRO_API_URL_BASE = "https://pro-api.coingecko.com/api/v3/" + _api_key: str = "" + + def __init__(self, api_key: str = "", *, is_demo=True, retries=5): + super().__init__(retries=retries) + # Doint' pass api_key to parent, instead set the header on the session directly + self._api_key = api_key + + if api_key and not is_demo: + self.api_base_url = self.__PRO_API_URL_BASE + self.session.headers.update({"x-cg-pro-api-key": api_key}) + else: + self.api_base_url = self.__API_URL_BASE + self.session.headers.update({"x-cg-demo-api-key": api_key}) From cb1600d7b098efc974ce25e168fd01e8ea7403fb Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 14:08:44 +0200 Subject: [PATCH 05/21] Update fiat_convert to use FtCoinGeckoApi --- freqtrade/rpc/fiat_convert.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index 96758d296..5cfc675a8 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -8,11 +8,11 @@ from datetime import datetime from typing import Dict, List from cachetools import TTLCache -from pycoingecko import CoinGeckoAPI from requests.exceptions import RequestException from freqtrade.constants import SUPPORTED_FIAT from freqtrade.mixins.logging_mixin import LoggingMixin +from freqtrade.util.CoinGecko import FtCoinGeckoApi logger = logging.getLogger(__name__) @@ -40,11 +40,11 @@ class CryptoToFiatConverter(LoggingMixin): """ __instance = None - _coingecko: CoinGeckoAPI = None + _coingecko: FtCoinGeckoApi = None _coinlistings: List[Dict] = [] _backoff: float = 0.0 - def __new__(cls): + def __new__(cls, *args, **kwargs): """ This class is a singleton - cannot be instantiated twice. """ @@ -53,7 +53,7 @@ class CryptoToFiatConverter(LoggingMixin): try: # Limit retires to 1 (0 and 1) # otherwise we risk bot impact if coingecko is down. - CryptoToFiatConverter._coingecko = CoinGeckoAPI(retries=1) + CryptoToFiatConverter._coingecko = FtCoinGeckoApi(retries=1) except BaseException: CryptoToFiatConverter._coingecko = None return CryptoToFiatConverter.__instance From 62166e23f67434b40c47a480cb3045790510b812 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 14:09:19 +0200 Subject: [PATCH 06/21] Improve singleton pattern --- freqtrade/rpc/fiat_convert.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index 5cfc675a8..53440eb15 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -5,7 +5,7 @@ e.g BTC to USD import logging from datetime import datetime -from typing import Dict, List +from typing import Any, Dict, List from cachetools import TTLCache from requests.exceptions import RequestException @@ -44,19 +44,19 @@ class CryptoToFiatConverter(LoggingMixin): _coinlistings: List[Dict] = [] _backoff: float = 0.0 - def __new__(cls, *args, **kwargs): + def __new__(cls, *args: Any, **kwargs: Any) -> Any: """ - This class is a singleton - cannot be instantiated twice. + Singleton pattern to ensure only one instance is created. """ - if CryptoToFiatConverter.__instance is None: - CryptoToFiatConverter.__instance = object.__new__(cls) + if not cls.__instance: + cls.__instance = super().__new__(cls) try: # Limit retires to 1 (0 and 1) # otherwise we risk bot impact if coingecko is down. - CryptoToFiatConverter._coingecko = FtCoinGeckoApi(retries=1) + cls._coingecko = FtCoinGeckoApi(retries=1) except BaseException: - CryptoToFiatConverter._coingecko = None - return CryptoToFiatConverter.__instance + cls._coingecko = None + return cls.__instance def __init__(self) -> None: # Timeout: 6h From 773940e05c7fb603a66efdd749f4ca84b8680107 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 14:15:53 +0200 Subject: [PATCH 07/21] Update mocks to FtCoinGeckoApi --- tests/conftest.py | 2 +- tests/rpc/test_fiat_convert.py | 8 ++++---- tests/rpc/test_rpc.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 944f78e22..3686a548a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -539,7 +539,7 @@ def patch_coingecko(mocker) -> None: ] ) mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_price=tickermock, get_coins_list=listmock, ) diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index d72d99abf..4d502b32b 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -12,7 +12,7 @@ from tests.conftest import log_has, log_has_re def test_fiat_convert_is_singleton(): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({"a": 22}) fiat_convert2 = CryptoToFiatConverter() assert fiat_convert is fiat_convert2 @@ -108,7 +108,7 @@ def test_fiat_init_network_exception(mocker): # Because CryptoToFiatConverter is a Singleton we reset the listings listmock = MagicMock(side_effect=RequestException) mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): @@ -137,7 +137,7 @@ def test_fiat_too_many_requests_response(mocker, caplog): req_exception = "429 Too Many Requests" listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception)) mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): @@ -173,7 +173,7 @@ def test_fiat_invalid_response(mocker, caplog): # Because CryptoToFiatConverter is a Singleton we reset the listings listmock = MagicMock(return_value=None) mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index d421ba556..e91aa85c6 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -225,7 +225,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}), ) mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) @@ -495,7 +495,7 @@ def test_rpc_balance_handle_error(default_conf, mocker): # ETH will be skipped due to mocked Error below mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}), ) mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) @@ -558,7 +558,7 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): ] mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_price=MagicMock(return_value={"bitcoin": {"usd": 1.2}}), ) mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.2) From 1ff162cf1740b202e2049f2e66201042b8c27c48 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 14:32:08 +0200 Subject: [PATCH 08/21] Use coingecko api keys --- freqtrade/rpc/fiat_convert.py | 18 +++++++++--------- freqtrade/rpc/rpc.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index 53440eb15..4e404ed8d 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -10,7 +10,7 @@ from typing import Any, Dict, List from cachetools import TTLCache from requests.exceptions import RequestException -from freqtrade.constants import SUPPORTED_FIAT +from freqtrade.constants import SUPPORTED_FIAT, Config from freqtrade.mixins.logging_mixin import LoggingMixin from freqtrade.util.CoinGecko import FtCoinGeckoApi @@ -40,7 +40,7 @@ class CryptoToFiatConverter(LoggingMixin): """ __instance = None - _coingecko: FtCoinGeckoApi = None + _coinlistings: List[Dict] = [] _backoff: float = 0.0 @@ -50,18 +50,18 @@ class CryptoToFiatConverter(LoggingMixin): """ if not cls.__instance: cls.__instance = super().__new__(cls) - try: - # Limit retires to 1 (0 and 1) - # otherwise we risk bot impact if coingecko is down. - cls._coingecko = FtCoinGeckoApi(retries=1) - except BaseException: - cls._coingecko = None return cls.__instance - def __init__(self) -> None: + def __init__(self, config: Config) -> None: # Timeout: 6h self._pair_price: TTLCache = TTLCache(maxsize=500, ttl=6 * 60 * 60) + coingecko_config = config.get("coingecko", {}) + self._coingecko = FtCoinGeckoApi( + api_key=coingecko_config.get("api_key", ""), + is_demo=coingecko_config.get("is_demo", True), + retries=1, + ) LoggingMixin.__init__(self, logger, 3600) self._load_cryptomap() diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index fd75da917..4cafa12ad 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -107,7 +107,7 @@ class RPC: self._freqtrade = freqtrade self._config: Config = freqtrade.config if self._config.get("fiat_display_currency"): - self._fiat_converter = CryptoToFiatConverter() + self._fiat_converter = CryptoToFiatConverter(self._config) @staticmethod def _rpc_show_config( From 3729daf082efe345e92c1ee7b147d6f8718452d6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 14:34:18 +0200 Subject: [PATCH 09/21] Add type check for coingecko settings --- freqtrade/constants.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 2b8ed7965..c6a46dae3 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -322,6 +322,14 @@ CONF_SCHEMA = { }, "required": REQUIRED_ORDERTIF, }, + "coingecko": { + "type": "object", + "properties": { + "is_demo": {"type": "boolean", "default": True}, + "api_key": {"type": "string"}, + }, + "required": ["is_demo", "api_key"], + }, "exchange": {"$ref": "#/definitions/exchange"}, "edge": {"$ref": "#/definitions/edge"}, "freqai": {"$ref": "#/definitions/freqai"}, From 5fd76a79fef28fc838aa316191724eb245032981 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 14:39:57 +0200 Subject: [PATCH 10/21] Add coingecko API documentation --- docs/configuration.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 6e89348e4..e2501cf48 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -568,7 +568,14 @@ The possible values are: `GTC` (default), `FOK` or `IOC`. This is ongoing work. For now, it is supported only for binance, gate and kucoin. Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange. -### What values can be used for fiat_display_currency? +### Fiat conversion + +Freqtrade uses the Coingecko API to convert the coin value to it's corresponding fiat value for the Telegram reports. +The FIAT currency can be set in the configuration file as `fiat_display_currency`. + +Removing `fiat_display_currency` completely from the configuration will skip initializing coingecko, and will not show any FIAT currency conversion. This has no importance for the correct functioning of the bot. + +#### What values can be used for fiat_display_currency? The `fiat_display_currency` configuration parameter sets the base currency to use for the conversion from coin to fiat in the bot Telegram reports. @@ -587,7 +594,25 @@ The valid values are: "BTC", "ETH", "XRP", "LTC", "BCH", "BNB" ``` -Removing `fiat_display_currency` completely from the configuration will skip initializing coingecko, and will not show any FIAT currency conversion. This has no importance for the correct functioning of the bot. +#### Coingecko Rate limit problems + +On some IP ranges, coingecko is heavily rate-limiting. +In such cases, you may want to add your coingecko API key to the configuration. + +``` json +{ + "fiat_display_currency": "USD", + "coingecko": { + "api_key": "your-api", + "is_demo": true + } +} +``` + +Freqtrade supports both Demo and Pro coingecko API keys. + +The Coingecko API key is NOT required for the bot to function correctly. +It is only used for the conversion of coin to fiat in the Telegram reports, which usually also work without API key. ## Using Dry-run mode From 8d1285bb21c8aedac829156315f0e486b12d1eeb Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 14:44:25 +0200 Subject: [PATCH 11/21] Set session params instead of headers --- freqtrade/util/CoinGecko.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/freqtrade/util/CoinGecko.py b/freqtrade/util/CoinGecko.py index 24f0b8664..c39721c00 100644 --- a/freqtrade/util/CoinGecko.py +++ b/freqtrade/util/CoinGecko.py @@ -16,9 +16,10 @@ class FtCoinGeckoApi(CoinGeckoAPI): # Doint' pass api_key to parent, instead set the header on the session directly self._api_key = api_key - if api_key and not is_demo: - self.api_base_url = self.__PRO_API_URL_BASE - self.session.headers.update({"x-cg-pro-api-key": api_key}) - else: + if api_key and is_demo: + # Use demo api key self.api_base_url = self.__API_URL_BASE - self.session.headers.update({"x-cg-demo-api-key": api_key}) + self.session.params.update({"x_cg_demo_api_key": api_key}) + else: + self.api_base_url = self.__PRO_API_URL_BASE + self.session.params.update({"x_cg_pro_api_key": api_key}) From 9e0ccb1cf405f1920d8e930775fcbd39344fa041 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 15:11:43 +0200 Subject: [PATCH 12/21] Rename coingecko wrapper file --- freqtrade/rpc/fiat_convert.py | 2 +- freqtrade/util/{CoinGecko.py => coin_gecko.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename freqtrade/util/{CoinGecko.py => coin_gecko.py} (100%) diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index 4e404ed8d..f671c1522 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -12,7 +12,7 @@ from requests.exceptions import RequestException from freqtrade.constants import SUPPORTED_FIAT, Config from freqtrade.mixins.logging_mixin import LoggingMixin -from freqtrade.util.CoinGecko import FtCoinGeckoApi +from freqtrade.util.coin_gecko import FtCoinGeckoApi logger = logging.getLogger(__name__) diff --git a/freqtrade/util/CoinGecko.py b/freqtrade/util/coin_gecko.py similarity index 100% rename from freqtrade/util/CoinGecko.py rename to freqtrade/util/coin_gecko.py From 94e0a808b711eb2160b29f89aaa0c111d00a0272 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 15:14:15 +0200 Subject: [PATCH 13/21] Add test, invert logic --- freqtrade/util/coin_gecko.py | 11 ++++++----- tests/rpc/test_fiat_convert.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/freqtrade/util/coin_gecko.py b/freqtrade/util/coin_gecko.py index c39721c00..47b80875b 100644 --- a/freqtrade/util/coin_gecko.py +++ b/freqtrade/util/coin_gecko.py @@ -16,10 +16,11 @@ class FtCoinGeckoApi(CoinGeckoAPI): # Doint' pass api_key to parent, instead set the header on the session directly self._api_key = api_key - if api_key and is_demo: - # Use demo api key - self.api_base_url = self.__API_URL_BASE - self.session.params.update({"x_cg_demo_api_key": api_key}) - else: + if api_key and not is_demo: self.api_base_url = self.__PRO_API_URL_BASE self.session.params.update({"x_cg_pro_api_key": api_key}) + else: + # Use demo api key + self.api_base_url = self.__API_URL_BASE + if api_key: + self.session.params.update({"x_cg_demo_api_key": api_key}) diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 4d502b32b..75b6fd0b4 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -8,6 +8,7 @@ import pytest from requests.exceptions import RequestException from freqtrade.rpc.fiat_convert import CryptoToFiatConverter +from freqtrade.util.coin_gecko import FtCoinGeckoApi from tests.conftest import log_has, log_has_re @@ -201,3 +202,18 @@ def test_convert_amount(mocker): crypto_amount="1.23", crypto_symbol="BTC", fiat_symbol="BTC" ) assert result == 1.23 + + +def test_FtCoinGeckoApi(): + ftc = FtCoinGeckoApi() + assert ftc._api_key == "" + assert ftc.api_base_url == "https://api.coingecko.com/api/v3/" + + # defaults to demo + ftc = FtCoinGeckoApi(api_key="123456") + assert ftc._api_key == "123456" + assert ftc.api_base_url == "https://api.coingecko.com/api/v3/" + + ftc = FtCoinGeckoApi(api_key="123456", is_demo=False) + assert ftc._api_key == "123456" + assert ftc.api_base_url == "https://pro-api.coingecko.com/api/v3/" From 2cd3089b3a38f24c82e2c1412ac82361249d6d1f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 15:16:12 +0200 Subject: [PATCH 14/21] Update fiat-convert test cases --- tests/rpc/test_fiat_convert.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 75b6fd0b4..061df2e53 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -14,14 +14,14 @@ from tests.conftest import log_has, log_has_re def test_fiat_convert_is_singleton(): fiat_convert = CryptoToFiatConverter({"a": 22}) - fiat_convert2 = CryptoToFiatConverter() + fiat_convert2 = CryptoToFiatConverter({}) assert fiat_convert is fiat_convert2 assert id(fiat_convert) == id(fiat_convert2) def test_fiat_convert_is_supported(): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) assert fiat_convert._is_supported_fiat(fiat="USD") is True assert fiat_convert._is_supported_fiat(fiat="usd") is True assert fiat_convert._is_supported_fiat(fiat="abc") is False @@ -29,7 +29,7 @@ def test_fiat_convert_is_supported(): def test_fiat_convert_find_price(mocker): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = {} fiat_convert._backoff = 0 @@ -57,7 +57,7 @@ def test_fiat_convert_find_price(mocker): def test_fiat_convert_unsupported_crypto(mocker, caplog): mocker.patch("freqtrade.rpc.fiat_convert.CryptoToFiatConverter._coinlistings", return_value=[]) - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) assert fiat_convert._find_price(crypto_symbol="CRYPTO_123", fiat_symbol="EUR") == 0.0 assert log_has("unsupported crypto-symbol CRYPTO_123 - returning 0.0", caplog) @@ -67,7 +67,7 @@ def test_fiat_convert_get_price(mocker): "freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price", return_value=28000.0 ) - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) with pytest.raises(ValueError, match=r"The fiat us dollar is not supported."): fiat_convert.get_price(crypto_symbol="btc", fiat_symbol="US Dollar") @@ -87,19 +87,19 @@ def test_fiat_convert_get_price(mocker): def test_fiat_convert_same_currencies(): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="USD") == 1.0 def test_fiat_convert_two_FIAT(): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="EUR") == 0.0 def test_loadcryptomap(): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) assert len(fiat_convert._coinlistings) == 2 assert fiat_convert._get_gecko_id("btc") == "bitcoin" @@ -113,7 +113,7 @@ def test_fiat_init_network_exception(mocker): get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = {} fiat_convert._load_cryptomap() @@ -123,7 +123,7 @@ def test_fiat_init_network_exception(mocker): def test_fiat_convert_without_network(): # Because CryptoToFiatConverter is a Singleton we reset the value of _coingecko - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) cmc_temp = fiat_convert._coingecko fiat_convert._coingecko = None @@ -142,7 +142,7 @@ def test_fiat_too_many_requests_response(mocker, caplog): get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = {} fiat_convert._load_cryptomap() @@ -154,7 +154,7 @@ def test_fiat_too_many_requests_response(mocker, caplog): def test_fiat_multiple_coins(caplog): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = [ {"id": "helium", "symbol": "hnt", "name": "Helium"}, {"id": "hymnode", "symbol": "hnt", "name": "Hymnode"}, @@ -178,7 +178,7 @@ def test_fiat_invalid_response(mocker, caplog): get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = [] fiat_convert._load_cryptomap() @@ -191,7 +191,7 @@ def test_fiat_invalid_response(mocker, caplog): def test_convert_amount(mocker): mocker.patch("freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price", return_value=12345.0) - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) result = fiat_convert.convert_amount(crypto_amount=1.23, crypto_symbol="BTC", fiat_symbol="USD") assert result == 15184.35 From 468d0f8cf0cd484daa313ed897bc849cc01eec8b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 15:22:13 +0200 Subject: [PATCH 15/21] use FtCoinGeckoApi for marketCapPairlist, too --- freqtrade/plugins/pairlist/MarketCapPairList.py | 10 ++++++++-- freqtrade/rpc/fiat_convert.py | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 0ea351634..709b6100b 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -8,12 +8,12 @@ import logging from typing import Any, Dict, List from cachetools import TTLCache -from pycoingecko import CoinGeckoAPI from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.util.coin_gecko import FtCoinGeckoApi logger = logging.getLogger(__name__) @@ -44,7 +44,13 @@ class MarketCapPairList(IPairList): self._refresh_period = self._pairlistconfig.get("refresh_period", 86400) self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) self._def_candletype = self._config["candle_type_def"] - self._coingecko: CoinGeckoAPI = CoinGeckoAPI() + + _coingecko_config = config.get("coingecko", {}) + + self._coingecko: FtCoinGeckoApi = FtCoinGeckoApi( + api_key=_coingecko_config.get("api_key", ""), + is_demo=_coingecko_config.get("is_demo", True), + ) if self._max_rank > 250: raise OperationalException("This filter only support marketcap rank up to 250.") diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index f671c1522..20f8df468 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -55,11 +55,11 @@ class CryptoToFiatConverter(LoggingMixin): def __init__(self, config: Config) -> None: # Timeout: 6h self._pair_price: TTLCache = TTLCache(maxsize=500, ttl=6 * 60 * 60) - coingecko_config = config.get("coingecko", {}) + _coingecko_config = config.get("coingecko", {}) self._coingecko = FtCoinGeckoApi( - api_key=coingecko_config.get("api_key", ""), - is_demo=coingecko_config.get("is_demo", True), + api_key=_coingecko_config.get("api_key", ""), + is_demo=_coingecko_config.get("is_demo", True), retries=1, ) LoggingMixin.__init__(self, logger, 3600) From 1588a4253d90ac82f242da8c02d1ddbcedc613f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 15:29:22 +0200 Subject: [PATCH 16/21] Update tests for coinGecko updates --- tests/plugins/test_pairlist.py | 4 ++-- tests/rpc/test_rpc.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index df48d4b98..b1fed192b 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -2306,7 +2306,7 @@ def test_MarketCapPairList_filter( ) mocker.patch( - "freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets", + "freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets", return_value=test_value, ) @@ -2344,7 +2344,7 @@ def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machi ) mocker.patch( - "freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets", + "freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets", return_value=test_value, ) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e91aa85c6..a0c235cd5 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -266,7 +266,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert "-0.00" == f"{fiat_profit_sum:.2f}" # Test with fiat convert - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD") assert "Since" in headers assert "Pair" in headers @@ -312,7 +312,7 @@ def test__rpc_timeunit_profit( fiat_display_currency = default_conf_usdt["fiat_display_currency"] rpc = RPC(freqtradebot) - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) # Try valid data days = rpc._rpc_timeunit_profit(7, stake_currency, fiat_display_currency) @@ -344,7 +344,7 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short): freqtradebot = get_patched_freqtradebot(mocker, default_conf) create_mock_trades(fee, is_short) rpc = RPC(freqtradebot) - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) trades = rpc._rpc_trade_history(2) assert len(trades["trades"]) == 2 assert trades["trades_count"] == 2 @@ -434,7 +434,7 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: fiat_display_currency = default_conf_usdt["fiat_display_currency"] rpc = RPC(freqtradebot) - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) assert res["trade_count"] == 0 @@ -509,7 +509,7 @@ def test_rpc_balance_handle_error(default_conf, mocker): freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) with pytest.raises(RPCException, match="Error getting current tickers."): rpc._rpc_balance(default_conf["stake_currency"], default_conf["fiat_display_currency"]) @@ -578,7 +578,7 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) result = rpc._rpc_balance( default_conf_usdt["stake_currency"], default_conf_usdt["fiat_display_currency"] From 531843ebcb0fd2a9623dbcb0c088d56539b32293 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 May 2024 17:02:00 +0200 Subject: [PATCH 17/21] Improve message for fiat_display_currency allow leaving empty for new-config --- freqtrade/commands/build_config_commands.py | 5 ++++- freqtrade/templates/base_config.json.j2 | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 82ff76061..76f3bb2af 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -100,7 +100,10 @@ def ask_user_config() -> Dict[str, Any]: { "type": "text", "name": "fiat_display_currency", - "message": "Please insert your display Currency (for reporting):", + "message": ( + "Please insert your display Currency for reporting " + "(leave empty to disable FIAT conversion):" + ), "default": "USD", }, { diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index caa27a69e..4956cf056 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -10,7 +10,8 @@ "stake_currency": "{{ stake_currency }}", "stake_amount": {{ stake_amount }}, "tradable_balance_ratio": 0.99, - "fiat_display_currency": "{{ fiat_display_currency }}",{{ ('\n "timeframe": "' + timeframe + '",') if timeframe else '' }} +{{- ('\n "fiat_display_currency": "' + fiat_display_currency + '",') if fiat_display_currency else ''}} +{{- ('\n "timeframe": "' + timeframe + '",') if timeframe else '' }} "dry_run": {{ dry_run | lower }}, "dry_run_wallet": 1000, "cancel_open_orders_on_exit": false, From d52431c581f81975bbab0ff8887552616cc1eb55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 21:58:49 +0000 Subject: [PATCH 18/21] --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- ft_client/requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ft_client/requirements.txt b/ft_client/requirements.txt index a1eb259aa..8a88ae2ab 100644 --- a/ft_client/requirements.txt +++ b/ft_client/requirements.txt @@ -1,3 +1,3 @@ # Requirements for freqtrade client library -requests==2.31.0 +requests==2.32.0 python-rapidjson==1.17 diff --git a/requirements.txt b/requirements.txt index 71af75e1a..100d7e85d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ python-telegram-bot==21.1.1 httpx>=0.24.1 humanize==4.9.0 cachetools==5.3.3 -requests==2.31.0 +requests==2.32.0 urllib3==2.2.1 jsonschema==4.22.0 TA-Lib==0.4.28 From 23aef6e0545becde370d586d90e0baf55c90dcba Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 May 2024 20:16:04 +0200 Subject: [PATCH 19/21] Bump requests to 2.32.2 2.32.0 was yanked. --- ft_client/requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ft_client/requirements.txt b/ft_client/requirements.txt index 8a88ae2ab..36cd79635 100644 --- a/ft_client/requirements.txt +++ b/ft_client/requirements.txt @@ -1,3 +1,3 @@ # Requirements for freqtrade client library -requests==2.32.0 +requests==2.32.2 python-rapidjson==1.17 diff --git a/requirements.txt b/requirements.txt index 100d7e85d..12c02b0b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ python-telegram-bot==21.1.1 httpx>=0.24.1 humanize==4.9.0 cachetools==5.3.3 -requests==2.32.0 +requests==2.32.2 urllib3==2.2.1 jsonschema==4.22.0 TA-Lib==0.4.28 From c3fa8a4c45b8f2381fe8fe40aeb5d0743181ab63 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 May 2024 20:30:35 +0200 Subject: [PATCH 20/21] feat: Allow empty fiat_display_currency (instead of completely deleting that key) --- freqtrade/constants.py | 1 + tests/rpc/test_rpc_telegram.py | 8 ++++++-- tests/test_configuration.py | 10 ++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index c6a46dae3..f8f1ac7ee 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -156,6 +156,7 @@ SUPPORTED_FIAT = [ "LTC", "BCH", "BNB", + "", # Allow empty field in config. ] MINIMAL_CONFIG = { diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 3063e644b..6e4aa3384 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -2564,10 +2564,14 @@ def test_send_msg_buy_notification_no_fiat( ("Short", "short_signal_01", 2.0), ], ) +@pytest.mark.parametrize("fiat", ["", None]) def test_send_msg_exit_notification_no_fiat( - default_conf, mocker, direction, enter_signal, leverage, time_machine + default_conf, mocker, direction, enter_signal, leverage, time_machine, fiat ) -> None: - del default_conf["fiat_display_currency"] + if fiat is None: + del default_conf["fiat_display_currency"] + else: + default_conf["fiat_display_currency"] = fiat time_machine.move_to("2022-05-02 00:00:00 +00:00", tick=False) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 7faa35c4a..d0e0aa2c6 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -659,6 +659,16 @@ def test_validate_default_conf(default_conf) -> None: validate_config_schema(default_conf) +@pytest.mark.parametrize("fiat", ["EUR", "USD", "", None]) +def test_validate_fiat_currency_options(default_conf, fiat) -> None: + # Validate via our validator - we allow setting defaults! + if fiat is not None: + default_conf["fiat_display_currency"] = fiat + else: + del default_conf["fiat_display_currency"] + validate_config_schema(default_conf) + + def test_validate_max_open_trades(default_conf): default_conf["max_open_trades"] = float("inf") default_conf["stake_amount"] = "unlimited" From d59422159adf6bcc832f5db36d7e3dce253eca13 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Thu, 23 May 2024 03:02:28 +0000 Subject: [PATCH 21/21] chore: update pre-commit hooks --- .../exchange/binance_leverage_tiers.json | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index e2defc7d2..56cf9e482 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -24569,6 +24569,120 @@ } } ], + "NOT/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "NTRN/USDT:USDT": [ { "tier": 1.0,