diff --git a/docs/configuration.md b/docs/configuration.md index 3a9c19161..1d695db3e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -135,6 +135,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.*
**Datatype:** Positive Integer | `exchange.skip_pair_validation` | Skip pairlist validation on startup.
*Defaults to `false`
**Datatype:** Boolean | `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.
*Defaults to `false`
**Datatype:** Boolean +| `exchange.unknown_fee_rate` | Fallback value to use when calculating trading fees. This can be useful for exchanges which have fees in non-tradable currencies. The value provided here will be multiplied with the "fee cost".
*Defaults to `None`
**Datatype:** float | `exchange.log_responses` | Log relevant exchange responses. For debug mode only - use with care.
*Defaults to `false`
**Datatype:** Boolean | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation. | `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.*
**Datatype:** Boolean diff --git a/docs/exchanges.md b/docs/exchanges.md index 3883e0b1d..374a6b8cc 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -199,6 +199,11 @@ OKEX requires a passphrase for each api key, you will therefore need to add this !!! Warning OKEX only provides 100 candles per api call. Therefore, the strategy will only have a pretty low amount of data available in backtesting mode. +## Gate.io + +Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0). +The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value. + ## All exchanges Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index e074718ca..f15759ea5 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -395,6 +395,7 @@ CONF_SCHEMA = { }, 'uniqueItems': True }, + 'unknown_fee_rate': {'type': 'number'}, 'outdated_offset': {'type': 'integer', 'minimum': 1}, 'markets_refresh_interval': {'type': 'integer'}, 'ccxt_config': {'type': 'object'}, diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 22041ddef..66d6b3b55 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1194,9 +1194,11 @@ class Exchange: tick = self.fetch_ticker(comb) fee_to_quote_rate = safe_value_fallback2(tick, tick, 'last', 'ask') - return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8) except ExchangeError: - return None + fee_to_quote_rate = self._config['exchange'].get('unknown_fee_rate', None) + if not fee_to_quote_rate: + return None + return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8) def extract_cost_curr_rate(self, order: Dict) -> Tuple[float, str, Optional[float]]: """ diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index d669600a2..10f181bb6 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -160,7 +160,7 @@ class ShowConfig(BaseModel): trailing_stop_positive_offset: Optional[float] trailing_only_offset_is_reached: Optional[bool] unfilledtimeout: UnfilledTimeout - order_types: OrderTypes + order_types: Optional[OrderTypes] use_custom_stoploss: Optional[bool] timeframe: Optional[str] timeframe_ms: int diff --git a/requirements.txt b/requirements.txt index 675d91a87..89abe2a14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ ccxt==1.63.55 cryptography==36.0.0 aiohttp==3.8.1 SQLAlchemy==1.4.28 -python-telegram-bot==13.8.1 +python-telegram-bot==13.9 arrow==1.2.1 cachetools==4.2.2 requests==2.26.0 @@ -32,7 +32,7 @@ sdnotify==0.3.2 # API Server fastapi==0.70.0 -uvicorn==0.15.0 +uvicorn==0.16.0 pyjwt==2.3.0 aiofiles==0.8.0 psutil==5.8.0 diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b33e0cbb7..a4b151742 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2948,39 +2948,49 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None: assert ex.extract_cost_curr_rate(order) == expected -@pytest.mark.parametrize("order,expected", [ +@pytest.mark.parametrize("order,unknown_fee_rate,expected", [ # Using base-currency ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.1), + 'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, None, 0.1), ({'symbol': 'ETH/BTC', 'amount': 0.05, 'cost': 0.05, - 'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.08), + 'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, None, 0.08), # Using quote currency ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'BTC', 'cost': 0.005}}, 0.1), + 'fee': {'currency': 'BTC', 'cost': 0.005}}, None, 0.1), ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, 0.04), + 'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, None, 0.04), # Using foreign currency ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'NEO', 'cost': 0.0012}}, 0.001944), + 'fee': {'currency': 'NEO', 'cost': 0.0012}}, None, 0.001944), ({'symbol': 'ETH/BTC', 'amount': 2.21, 'cost': 0.02992561, - 'fee': {'currency': 'NEO', 'cost': 0.00027452}}, 0.00074305), + 'fee': {'currency': 'NEO', 'cost': 0.00027452}}, None, 0.00074305), # Rate included in return - return as is ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}}, 0.01), + 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}}, None, 0.01), ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, 0.005), + 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, None, 0.005), # 0.1% filled - no costs (kraken - #3431) ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, - 'fee': {'currency': 'BTC', 'cost': 0.0, 'rate': None}}, None), + 'fee': {'currency': 'BTC', 'cost': 0.0, 'rate': None}}, None, None), ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, - 'fee': {'currency': 'ETH', 'cost': 0.0, 'rate': None}}, 0.0), + 'fee': {'currency': 'ETH', 'cost': 0.0, 'rate': None}}, None, 0.0), ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, - 'fee': {'currency': 'NEO', 'cost': 0.0, 'rate': None}}, None), + 'fee': {'currency': 'NEO', 'cost': 0.0, 'rate': None}}, None, None), + # Invalid pair combination - POINT/BTC is not a pair + ({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5, + 'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, None, None), + ({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5, + 'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 1, 4.0), + ({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5, + 'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 2, 8.0), ]) -def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None: +def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None: mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081}) + if unknown_fee_rate: + default_conf['exchange']['unknown_fee_rate'] = unknown_fee_rate ex = get_patched_exchange(mocker, default_conf) + assert ex.calculate_fee_rate(order) == expected