mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-13 03:33:55 +00:00
Merge branch 'freqtrade:develop' into feature/setup-win
This commit is contained in:
commit
9c816045f1
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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"},
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
26
freqtrade/util/coin_gecko.py
Normal file
26
freqtrade/util/coin_gecko.py
Normal 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})
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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/"
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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:
|
||||||
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)
|
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)
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user