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 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/constants.py b/freqtrade/constants.py index 2b8ed7965..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 = { @@ -322,6 +323,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"}, 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, 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 96758d296..20f8df468 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -5,14 +5,14 @@ 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 pycoingecko import CoinGeckoAPI 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.coin_gecko import FtCoinGeckoApi logger = logging.getLogger(__name__) @@ -40,28 +40,28 @@ class CryptoToFiatConverter(LoggingMixin): """ __instance = None - _coingecko: CoinGeckoAPI = None + _coinlistings: List[Dict] = [] _backoff: float = 0.0 - def __new__(cls): + 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) - try: - # Limit retires to 1 (0 and 1) - # otherwise we risk bot impact if coingecko is down. - CryptoToFiatConverter._coingecko = CoinGeckoAPI(retries=1) - except BaseException: - CryptoToFiatConverter._coingecko = None - return CryptoToFiatConverter.__instance + if not cls.__instance: + cls.__instance = super().__new__(cls) + 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( 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, diff --git a/freqtrade/util/coin_gecko.py b/freqtrade/util/coin_gecko.py new file mode 100644 index 000000000..47b80875b --- /dev/null +++ b/freqtrade/util/coin_gecko.py @@ -0,0 +1,26 @@ +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.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/ft_client/requirements.txt b/ft_client/requirements.txt index a1eb259aa..36cd79635 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.2 python-rapidjson==1.17 diff --git a/requirements.txt b/requirements.txt index 71af75e1a..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.31.0 +requests==2.32.2 urllib3==2.2.1 jsonschema==4.22.0 TA-Lib==0.4.28 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/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_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 049716414..061df2e53 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -8,11 +8,20 @@ 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 -def test_fiat_convert_is_supported(mocker): - fiat_convert = CryptoToFiatConverter() +def test_fiat_convert_is_singleton(): + fiat_convert = CryptoToFiatConverter({"a": 22}) + 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 assert fiat_convert._is_supported_fiat(fiat="usd") is True assert fiat_convert._is_supported_fiat(fiat="abc") is False @@ -20,7 +29,7 @@ def test_fiat_convert_is_supported(mocker): def test_fiat_convert_find_price(mocker): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = {} fiat_convert._backoff = 0 @@ -48,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) @@ -58,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") @@ -77,20 +86,20 @@ def test_fiat_convert_get_price(mocker): assert find_price.call_count == 1 -def test_fiat_convert_same_currencies(mocker): - fiat_convert = CryptoToFiatConverter() +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): - fiat_convert = CryptoToFiatConverter() +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): - fiat_convert = CryptoToFiatConverter() +def test_loadcryptomap(): + fiat_convert = CryptoToFiatConverter({}) assert len(fiat_convert._coinlistings) == 2 assert fiat_convert._get_gecko_id("btc") == "bitcoin" @@ -100,28 +109,28 @@ 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): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = {} fiat_convert._load_cryptomap() 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() + 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): @@ -129,11 +138,11 @@ 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): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = {} fiat_convert._load_cryptomap() @@ -144,8 +153,8 @@ def test_fiat_too_many_requests_response(mocker, caplog): ) -def test_fiat_multiple_coins(mocker, caplog): - fiat_convert = CryptoToFiatConverter() +def test_fiat_multiple_coins(caplog): + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = [ {"id": "helium", "symbol": "hnt", "name": "Helium"}, {"id": "hymnode", "symbol": "hnt", "name": "Hymnode"}, @@ -165,11 +174,11 @@ 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): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = [] fiat_convert._load_cryptomap() @@ -182,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 @@ -193,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/" diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index d421ba556..a0c235cd5 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) @@ -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 @@ -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) @@ -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"]) @@ -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) @@ -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"] 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"