Merge branch 'freqtrade:develop' into develop

This commit is contained in:
Simon Waiblinger 2024-05-24 20:47:41 +02:00 committed by GitHub
commit 2a82e00857
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 286 additions and 64 deletions

View File

@ -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. 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. 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 The `fiat_display_currency` configuration parameter sets the base currency to use for the
conversion from coin to fiat in the bot Telegram reports. conversion from coin to fiat in the bot Telegram reports.
@ -587,7 +594,25 @@ The valid values are:
"BTC", "ETH", "XRP", "LTC", "BCH", "BNB" "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 ## Using Dry-run mode

View File

@ -100,7 +100,10 @@ def ask_user_config() -> Dict[str, Any]:
{ {
"type": "text", "type": "text",
"name": "fiat_display_currency", "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", "default": "USD",
}, },
{ {

View File

@ -156,6 +156,7 @@ SUPPORTED_FIAT = [
"LTC", "LTC",
"BCH", "BCH",
"BNB", "BNB",
"", # Allow empty field in config.
] ]
MINIMAL_CONFIG = { MINIMAL_CONFIG = {
@ -322,6 +323,14 @@ CONF_SCHEMA = {
}, },
"required": REQUIRED_ORDERTIF, "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"}, "exchange": {"$ref": "#/definitions/exchange"},
"edge": {"$ref": "#/definitions/edge"}, "edge": {"$ref": "#/definitions/edge"},
"freqai": {"$ref": "#/definitions/freqai"}, "freqai": {"$ref": "#/definitions/freqai"},

View File

@ -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": [ "NTRN/USDT:USDT": [
{ {
"tier": 1.0, "tier": 1.0,

View File

@ -8,12 +8,12 @@ import logging
from typing import Any, Dict, List from typing import Any, Dict, List
from cachetools import TTLCache from cachetools import TTLCache
from pycoingecko import CoinGeckoAPI
from freqtrade.constants import Config from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange.types import Tickers from freqtrade.exchange.types import Tickers
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
from freqtrade.util.coin_gecko import FtCoinGeckoApi
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -44,7 +44,13 @@ class MarketCapPairList(IPairList):
self._refresh_period = self._pairlistconfig.get("refresh_period", 86400) self._refresh_period = self._pairlistconfig.get("refresh_period", 86400)
self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
self._def_candletype = self._config["candle_type_def"] 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: if self._max_rank > 250:
raise OperationalException("This filter only support marketcap rank up to 250.") raise OperationalException("This filter only support marketcap rank up to 250.")

View File

@ -5,14 +5,14 @@ e.g BTC to USD
import logging import logging
from datetime import datetime from datetime import datetime
from typing import Dict, List from typing import Any, Dict, List
from cachetools import TTLCache from cachetools import TTLCache
from pycoingecko import CoinGeckoAPI
from requests.exceptions import RequestException 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.mixins.logging_mixin import LoggingMixin
from freqtrade.util.coin_gecko import FtCoinGeckoApi
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -40,28 +40,28 @@ class CryptoToFiatConverter(LoggingMixin):
""" """
__instance = None __instance = None
_coingecko: CoinGeckoAPI = None
_coinlistings: List[Dict] = [] _coinlistings: List[Dict] = []
_backoff: float = 0.0 _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: if not cls.__instance:
CryptoToFiatConverter.__instance = object.__new__(cls) cls.__instance = super().__new__(cls)
try: return cls.__instance
# 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
def __init__(self) -> None: def __init__(self, config: Config) -> None:
# Timeout: 6h # Timeout: 6h
self._pair_price: TTLCache = TTLCache(maxsize=500, ttl=6 * 60 * 60) 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) LoggingMixin.__init__(self, logger, 3600)
self._load_cryptomap() self._load_cryptomap()

View File

@ -107,7 +107,7 @@ class RPC:
self._freqtrade = freqtrade self._freqtrade = freqtrade
self._config: Config = freqtrade.config self._config: Config = freqtrade.config
if self._config.get("fiat_display_currency"): if self._config.get("fiat_display_currency"):
self._fiat_converter = CryptoToFiatConverter() self._fiat_converter = CryptoToFiatConverter(self._config)
@staticmethod @staticmethod
def _rpc_show_config( def _rpc_show_config(

View File

@ -10,7 +10,8 @@
"stake_currency": "{{ stake_currency }}", "stake_currency": "{{ stake_currency }}",
"stake_amount": {{ stake_amount }}, "stake_amount": {{ stake_amount }},
"tradable_balance_ratio": 0.99, "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": {{ dry_run | lower }},
"dry_run_wallet": 1000, "dry_run_wallet": 1000,
"cancel_open_orders_on_exit": false, "cancel_open_orders_on_exit": false,

View File

@ -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})

View File

@ -1,3 +1,3 @@
# Requirements for freqtrade client library # Requirements for freqtrade client library
requests==2.31.0 requests==2.32.2
python-rapidjson==1.17 python-rapidjson==1.17

View File

@ -11,7 +11,7 @@ python-telegram-bot==21.1.1
httpx>=0.24.1 httpx>=0.24.1
humanize==4.9.0 humanize==4.9.0
cachetools==5.3.3 cachetools==5.3.3
requests==2.31.0 requests==2.32.2
urllib3==2.2.1 urllib3==2.2.1
jsonschema==4.22.0 jsonschema==4.22.0
TA-Lib==0.4.28 TA-Lib==0.4.28

View File

@ -539,7 +539,7 @@ def patch_coingecko(mocker) -> None:
] ]
) )
mocker.patch.multiple( mocker.patch.multiple(
"freqtrade.rpc.fiat_convert.CoinGeckoAPI", "freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
get_price=tickermock, get_price=tickermock,
get_coins_list=listmock, get_coins_list=listmock,
) )

View File

@ -2306,7 +2306,7 @@ def test_MarketCapPairList_filter(
) )
mocker.patch( mocker.patch(
"freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets", "freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets",
return_value=test_value, return_value=test_value,
) )
@ -2344,7 +2344,7 @@ def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machi
) )
mocker.patch( mocker.patch(
"freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets", "freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets",
return_value=test_value, return_value=test_value,
) )

View File

@ -8,11 +8,20 @@ import pytest
from requests.exceptions import RequestException from requests.exceptions import RequestException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.util.coin_gecko import FtCoinGeckoApi
from tests.conftest import log_has, log_has_re from tests.conftest import log_has, log_has_re
def test_fiat_convert_is_supported(mocker): def test_fiat_convert_is_singleton():
fiat_convert = CryptoToFiatConverter() 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="usd") is True assert fiat_convert._is_supported_fiat(fiat="usd") is True
assert fiat_convert._is_supported_fiat(fiat="abc") is False 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): def test_fiat_convert_find_price(mocker):
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter({})
fiat_convert._coinlistings = {} fiat_convert._coinlistings = {}
fiat_convert._backoff = 0 fiat_convert._backoff = 0
@ -48,7 +57,7 @@ def test_fiat_convert_find_price(mocker):
def test_fiat_convert_unsupported_crypto(mocker, caplog): def test_fiat_convert_unsupported_crypto(mocker, caplog):
mocker.patch("freqtrade.rpc.fiat_convert.CryptoToFiatConverter._coinlistings", return_value=[]) 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 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) 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 "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."): with pytest.raises(ValueError, match=r"The fiat us dollar is not supported."):
fiat_convert.get_price(crypto_symbol="btc", fiat_symbol="US Dollar") 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 assert find_price.call_count == 1
def test_fiat_convert_same_currencies(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 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() fiat_convert = CryptoToFiatConverter({})
assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="EUR") == 0.0 assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="EUR") == 0.0
def test_loadcryptomap(mocker): def test_loadcryptomap():
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter({})
assert len(fiat_convert._coinlistings) == 2 assert len(fiat_convert._coinlistings) == 2
assert fiat_convert._get_gecko_id("btc") == "bitcoin" 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 # Because CryptoToFiatConverter is a Singleton we reset the listings
listmock = MagicMock(side_effect=RequestException) listmock = MagicMock(side_effect=RequestException)
mocker.patch.multiple( mocker.patch.multiple(
"freqtrade.rpc.fiat_convert.CoinGeckoAPI", "freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
get_coins_list=listmock, get_coins_list=listmock,
) )
# with pytest.raises(RequestEsxception): # with pytest.raises(RequestEsxception):
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter({})
fiat_convert._coinlistings = {} fiat_convert._coinlistings = {}
fiat_convert._load_cryptomap() fiat_convert._load_cryptomap()
assert len(fiat_convert._coinlistings) == 0 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 # Because CryptoToFiatConverter is a Singleton we reset the value of _coingecko
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter({})
cmc_temp = CryptoToFiatConverter._coingecko cmc_temp = fiat_convert._coingecko
CryptoToFiatConverter._coingecko = None fiat_convert._coingecko = None
assert fiat_convert._coingecko is None assert fiat_convert._coingecko is None
assert fiat_convert._find_price(crypto_symbol="btc", fiat_symbol="usd") == 0.0 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): 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" req_exception = "429 Too Many Requests"
listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception)) listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception))
mocker.patch.multiple( mocker.patch.multiple(
"freqtrade.rpc.fiat_convert.CoinGeckoAPI", "freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
get_coins_list=listmock, get_coins_list=listmock,
) )
# with pytest.raises(RequestEsxception): # with pytest.raises(RequestEsxception):
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter({})
fiat_convert._coinlistings = {} fiat_convert._coinlistings = {}
fiat_convert._load_cryptomap() fiat_convert._load_cryptomap()
@ -144,8 +153,8 @@ 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 = CryptoToFiatConverter({})
fiat_convert._coinlistings = [ fiat_convert._coinlistings = [
{"id": "helium", "symbol": "hnt", "name": "Helium"}, {"id": "helium", "symbol": "hnt", "name": "Helium"},
{"id": "hymnode", "symbol": "hnt", "name": "Hymnode"}, {"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 # Because CryptoToFiatConverter is a Singleton we reset the listings
listmock = MagicMock(return_value=None) listmock = MagicMock(return_value=None)
mocker.patch.multiple( mocker.patch.multiple(
"freqtrade.rpc.fiat_convert.CoinGeckoAPI", "freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
get_coins_list=listmock, get_coins_list=listmock,
) )
# with pytest.raises(RequestEsxception): # with pytest.raises(RequestEsxception):
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter({})
fiat_convert._coinlistings = [] fiat_convert._coinlistings = []
fiat_convert._load_cryptomap() fiat_convert._load_cryptomap()
@ -182,7 +191,7 @@ def test_fiat_invalid_response(mocker, caplog):
def test_convert_amount(mocker): def test_convert_amount(mocker):
mocker.patch("freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price", return_value=12345.0) 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") result = fiat_convert.convert_amount(crypto_amount=1.23, crypto_symbol="BTC", fiat_symbol="USD")
assert result == 15184.35 assert result == 15184.35
@ -193,3 +202,18 @@ def test_convert_amount(mocker):
crypto_amount="1.23", crypto_symbol="BTC", fiat_symbol="BTC" crypto_amount="1.23", crypto_symbol="BTC", fiat_symbol="BTC"
) )
assert result == 1.23 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/"

View File

@ -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: def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
"freqtrade.rpc.fiat_convert.CoinGeckoAPI", "freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}), get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}),
) )
mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=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}" assert "-0.00" == f"{fiat_profit_sum:.2f}"
# Test with fiat convert # 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") result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD")
assert "Since" in headers assert "Since" in headers
assert "Pair" in headers assert "Pair" in headers
@ -312,7 +312,7 @@ def test__rpc_timeunit_profit(
fiat_display_currency = default_conf_usdt["fiat_display_currency"] fiat_display_currency = default_conf_usdt["fiat_display_currency"]
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter({})
# Try valid data # Try valid data
days = rpc._rpc_timeunit_profit(7, stake_currency, fiat_display_currency) 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) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
create_mock_trades(fee, is_short) create_mock_trades(fee, is_short)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter({})
trades = rpc._rpc_trade_history(2) trades = rpc._rpc_trade_history(2)
assert len(trades["trades"]) == 2 assert len(trades["trades"]) == 2
assert trades["trades_count"] == 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"] fiat_display_currency = default_conf_usdt["fiat_display_currency"]
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter({})
res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert res["trade_count"] == 0 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 # ETH will be skipped due to mocked Error below
mocker.patch.multiple( mocker.patch.multiple(
"freqtrade.rpc.fiat_convert.CoinGeckoAPI", "freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}), get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}),
) )
mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=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) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter({})
with pytest.raises(RPCException, match="Error getting current tickers."): with pytest.raises(RPCException, match="Error getting current tickers."):
rpc._rpc_balance(default_conf["stake_currency"], default_conf["fiat_display_currency"]) 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( mocker.patch.multiple(
"freqtrade.rpc.fiat_convert.CoinGeckoAPI", "freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
get_price=MagicMock(return_value={"bitcoin": {"usd": 1.2}}), get_price=MagicMock(return_value={"bitcoin": {"usd": 1.2}}),
) )
mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=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) freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter({})
result = rpc._rpc_balance( result = rpc._rpc_balance(
default_conf_usdt["stake_currency"], default_conf_usdt["fiat_display_currency"] default_conf_usdt["stake_currency"], default_conf_usdt["fiat_display_currency"]

View File

@ -2564,10 +2564,14 @@ def test_send_msg_buy_notification_no_fiat(
("Short", "short_signal_01", 2.0), ("Short", "short_signal_01", 2.0),
], ],
) )
@pytest.mark.parametrize("fiat", ["", None])
def test_send_msg_exit_notification_no_fiat( 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: ) -> None:
if fiat is None:
del default_conf["fiat_display_currency"] 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) time_machine.move_to("2022-05-02 00:00:00 +00:00", tick=False)
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)

View File

@ -659,6 +659,16 @@ def test_validate_default_conf(default_conf) -> None:
validate_config_schema(default_conf) 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): def test_validate_max_open_trades(default_conf):
default_conf["max_open_trades"] = float("inf") default_conf["max_open_trades"] = float("inf")
default_conf["stake_amount"] = "unlimited" default_conf["stake_amount"] = "unlimited"