From 75eccea88df2ad8183b2859cb8d48d278a07db74 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Nov 2021 19:43:43 +0100 Subject: [PATCH] Improve futures detection, add ccxt-compat test --- freqtrade/exchange/binance.py | 6 +++++- freqtrade/exchange/exchange.py | 2 +- freqtrade/exchange/ftx.py | 4 ++++ tests/conftest.py | 2 ++ tests/exchange/test_ccxt_compat.py | 23 ++++++++++++++++++++++- tests/exchange/test_exchange.py | 5 +++++ 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 1cbbffc51..232e2cb55 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -3,7 +3,7 @@ import json import logging from datetime import datetime from pathlib import Path -from typing import Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import arrow import ccxt @@ -119,6 +119,10 @@ class Binance(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e + def market_is_future(self, market: Dict[str, Any]) -> bool: + # TODO-lev: This should be unified in ccxt to "swap"... + return market.get('future', False) is True + @retrier def fill_leverage_brackets(self): """ diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 0b47c98b8..0acd10900 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -340,7 +340,7 @@ class Exchange: return self.markets.get(pair, {}).get('base', '') def market_is_future(self, market: Dict[str, Any]) -> bool: - return market.get('future', False) is True or market.get('futures') is True + return market.get('swap', False) is True def market_is_spot(self, market: Dict[str, Any]) -> bool: return market.get('spot', False) is True diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 8347993ee..e798f2c29 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -159,3 +159,7 @@ class Ftx(Exchange): if order['type'] == 'stop': return safe_value_fallback2(order, order, 'id_stop', 'id') return order['id'] + + def market_is_future(self, market: Dict[str, Any]) -> bool: + # TODO-lev: This should be unified in ccxt to "swap"... + return market.get('future', False) is True diff --git a/tests/conftest.py b/tests/conftest.py index 74dd6f360..7e5dd47e7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -778,6 +778,7 @@ def get_markets(): 'quote': 'USDT', 'spot': True, 'future': True, + 'swap': True, 'margin': True, 'type': 'spot', 'precision': { @@ -805,6 +806,7 @@ def get_markets(): 'active': False, 'spot': True, 'future': True, + 'swap': True, 'margin': True, 'type': 'spot', 'precision': { diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index b14df070c..59f144bab 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -26,6 +26,7 @@ EXCHANGES = { 'pair': 'BTC/USDT', 'hasQuoteVolume': True, 'timeframe': '5m', + 'futures': True, }, 'kraken': { 'pair': 'BTC/USDT', @@ -82,13 +83,19 @@ def exchange(request, exchange_conf): @pytest.fixture(params=EXCHANGES, scope="class") -def exchange_futures(request, exchange_conf): +def exchange_futures(request, exchange_conf, class_mocker): if not EXCHANGES[request.param].get('futures') is True: yield None, request.param else: exchange_conf['exchange']['name'] = request.param exchange_conf['trading_mode'] = 'futures' exchange_conf['collateral'] = 'cross' + # TODO-lev This mock should no longer be necessary once futures are enabled. + class_mocker.patch( + 'freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_collateral') + class_mocker.patch( + 'freqtrade.exchange.binance.Binance.fill_leverage_brackets') + exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True) yield exchange, request.param @@ -103,6 +110,20 @@ class TestCCXTExchange(): markets = exchange.markets assert pair in markets assert isinstance(markets[pair], dict) + assert exchange.market_is_spot(markets[pair]) + + def test_load_markets_futures(self, exchange_futures): + exchange, exchangename = exchange_futures + if not exchange: + # exchange_futures only returns values for supported exchanges + return + pair = EXCHANGES[exchangename]['pair'] + pair = EXCHANGES[exchangename].get('futures_pair', pair) + markets = exchange.markets + assert pair in markets + assert isinstance(markets[pair], dict) + + assert exchange.market_is_future(markets[pair]) def test_ccxt_fetch_tickers(self, exchange): exchange, exchangename = exchange diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 12c20a7ca..e974cbd43 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2985,6 +2985,10 @@ def test_timeframe_to_next_date(): ("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'spot', {}, False), ("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'margin', {}, False), ("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'futures', {}, True), + + ("BTC/USDT:USDT", 'BTC', 'USD', "okex", False, False, True, 'spot', {}, False), + ("BTC/USDT:USDT", 'BTC', 'USD', "okex", False, False, True, 'margin', {}, False), + ("BTC/USDT:USDT", 'BTC', 'USD', "okex", False, False, True, 'futures', {}, True), ]) def test_market_is_tradable( mocker, default_conf, market_symbol, base, @@ -2999,6 +3003,7 @@ def test_market_is_tradable( 'quote': quote, 'spot': spot, 'future': futures, + 'swap': futures, 'margin': margin, **(add_dict), }