diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 7f23c2031..e721ee2c9 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -8,16 +8,19 @@ suitable to run with freqtrade. from copy import deepcopy from datetime import datetime, timedelta, timezone from pathlib import Path +from typing import Tuple import pytest from freqtrade.enums import CandleType from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date -from freqtrade.exchange.exchange import timeframe_to_msecs +from freqtrade.exchange.exchange import Exchange, timeframe_to_msecs from freqtrade.resolvers.exchange_resolver import ExchangeResolver from tests.conftest import get_default_conf_usdt +EXCHANGE_FIXTURE_TYPE = Tuple[Exchange, str] + # Exchanges that should be tested EXCHANGES = { 'bittrex': { @@ -141,19 +144,19 @@ def exchange_futures(request, exchange_conf, class_mocker): @pytest.mark.longrun class TestCCXTExchange(): - def test_load_markets(self, exchange): - exchange, exchangename = exchange + def test_load_markets(self, exchange: EXCHANGE_FIXTURE_TYPE): + exch, exchangename = exchange pair = EXCHANGES[exchangename]['pair'] - markets = exchange.markets + markets = exch.markets assert pair in markets assert isinstance(markets[pair], dict) - assert exchange.market_is_spot(markets[pair]) + assert exch.market_is_spot(markets[pair]) - def test_has_validations(self, exchange): + def test_has_validations(self, exchange: EXCHANGE_FIXTURE_TYPE): - exchange, exchangename = exchange + exch, exchangename = exchange - exchange.validate_ordertypes({ + exch.validate_ordertypes({ 'entry': 'limit', 'exit': 'limit', 'stoploss': 'limit', @@ -162,13 +165,13 @@ class TestCCXTExchange(): if exchangename == 'gateio': # gateio doesn't have market orders on spot return - exchange.validate_ordertypes({ + exch.validate_ordertypes({ 'entry': 'market', 'exit': 'market', 'stoploss': 'market', }) - def test_load_markets_futures(self, exchange_futures): + def test_load_markets_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): exchange, exchangename = exchange_futures if not exchange: # exchange_futures only returns values for supported exchanges @@ -181,11 +184,11 @@ class TestCCXTExchange(): assert exchange.market_is_future(markets[pair]) - def test_ccxt_fetch_tickers(self, exchange): - exchange, exchangename = exchange + def test_ccxt_fetch_tickers(self, exchange: EXCHANGE_FIXTURE_TYPE): + exch, exchangename = exchange pair = EXCHANGES[exchangename]['pair'] - tickers = exchange.get_tickers() + tickers = exch.get_tickers() assert pair in tickers assert 'ask' in tickers[pair] assert tickers[pair]['ask'] is not None @@ -195,11 +198,11 @@ class TestCCXTExchange(): if EXCHANGES[exchangename].get('hasQuoteVolume'): assert tickers[pair]['quoteVolume'] is not None - def test_ccxt_fetch_ticker(self, exchange): - exchange, exchangename = exchange + def test_ccxt_fetch_ticker(self, exchange: EXCHANGE_FIXTURE_TYPE): + exch, exchangename = exchange pair = EXCHANGES[exchangename]['pair'] - ticker = exchange.fetch_ticker(pair) + ticker = exch.fetch_ticker(pair) assert 'ask' in ticker assert ticker['ask'] is not None assert 'bid' in ticker @@ -208,21 +211,21 @@ class TestCCXTExchange(): if EXCHANGES[exchangename].get('hasQuoteVolume'): assert ticker['quoteVolume'] is not None - def test_ccxt_fetch_l2_orderbook(self, exchange): - exchange, exchangename = exchange + def test_ccxt_fetch_l2_orderbook(self, exchange: EXCHANGE_FIXTURE_TYPE): + exch, exchangename = exchange pair = EXCHANGES[exchangename]['pair'] - l2 = exchange.fetch_l2_order_book(pair) + l2 = exch.fetch_l2_order_book(pair) assert 'asks' in l2 assert 'bids' in l2 assert len(l2['asks']) >= 1 assert len(l2['bids']) >= 1 - l2_limit_range = exchange._ft_has['l2_limit_range'] - l2_limit_range_required = exchange._ft_has['l2_limit_range_required'] + l2_limit_range = exch._ft_has['l2_limit_range'] + l2_limit_range_required = exch._ft_has['l2_limit_range_required'] if exchangename == 'gateio': # TODO: Gateio is unstable here at the moment, ignoring the limit partially. return for val in [1, 2, 5, 25, 100]: - l2 = exchange.fetch_l2_order_book(pair, val) + l2 = exch.fetch_l2_order_book(pair, val) if not l2_limit_range or val in l2_limit_range: if val > 50: # Orderbooks are not always this deep. @@ -232,7 +235,7 @@ class TestCCXTExchange(): assert len(l2['asks']) == val assert len(l2['bids']) == val else: - next_limit = exchange.get_next_limit_in_list( + next_limit = exch.get_next_limit_in_list( val, l2_limit_range, l2_limit_range_required) if next_limit is None: assert len(l2['asks']) > 100 @@ -245,23 +248,23 @@ class TestCCXTExchange(): assert len(l2['asks']) == next_limit assert len(l2['asks']) == next_limit - def test_ccxt_fetch_ohlcv(self, exchange): - exchange, exchangename = exchange + def test_ccxt_fetch_ohlcv(self, exchange: EXCHANGE_FIXTURE_TYPE): + exch, exchangename = exchange pair = EXCHANGES[exchangename]['pair'] timeframe = EXCHANGES[exchangename]['timeframe'] pair_tf = (pair, timeframe, CandleType.SPOT) - ohlcv = exchange.refresh_latest_ohlcv([pair_tf]) + ohlcv = exch.refresh_latest_ohlcv([pair_tf]) assert isinstance(ohlcv, dict) - assert len(ohlcv[pair_tf]) == len(exchange.klines(pair_tf)) - # assert len(exchange.klines(pair_tf)) > 200 + assert len(ohlcv[pair_tf]) == len(exch.klines(pair_tf)) + # assert len(exch.klines(pair_tf)) > 200 # Assume 90% uptime ... - assert len(exchange.klines(pair_tf)) > exchange.ohlcv_candle_limit( + assert len(exch.klines(pair_tf)) > exch.ohlcv_candle_limit( timeframe, CandleType.SPOT) * 0.90 # Check if last-timeframe is within the last 2 intervals now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2)) - assert exchange.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now) + assert exch.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now) def ccxt__async_get_candle_history(self, exchange, exchangename, pair, timeframe, candle_type): @@ -289,17 +292,17 @@ class TestCCXTExchange(): assert len(candles) >= min(candle_count, candle_count1) assert candles[0][0] == since_ms or (since_ms + timeframe_ms) - def test_ccxt__async_get_candle_history(self, exchange): - exchange, exchangename = exchange + def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE): + exc, exchangename = exchange # For some weired reason, this test returns random lengths for bittrex. - if not exchange._ft_has['ohlcv_has_history'] or exchangename in ('bittrex'): + if not exc._ft_has['ohlcv_has_history'] or exchangename in ('bittrex'): return pair = EXCHANGES[exchangename]['pair'] timeframe = EXCHANGES[exchangename]['timeframe'] self.ccxt__async_get_candle_history( - exchange, exchangename, pair, timeframe, CandleType.SPOT) + exc, exchangename, pair, timeframe, CandleType.SPOT) - def test_ccxt__async_get_candle_history_futures(self, exchange_futures): + def test_ccxt__async_get_candle_history_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): exchange, exchangename = exchange_futures if not exchange: # exchange_futures only returns values for supported exchanges @@ -309,7 +312,7 @@ class TestCCXTExchange(): self.ccxt__async_get_candle_history( exchange, exchangename, pair, timeframe, CandleType.FUTURES) - def test_ccxt_fetch_funding_rate_history(self, exchange_futures): + def test_ccxt_fetch_funding_rate_history(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): exchange, exchangename = exchange_futures if not exchange: # exchange_futures only returns values for supported exchanges @@ -347,7 +350,7 @@ class TestCCXTExchange(): (rate['open'].min() != rate['open'].max()) ) - def test_ccxt_fetch_mark_price_history(self, exchange_futures): + def test_ccxt_fetch_mark_price_history(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): exchange, exchangename = exchange_futures if not exchange: # exchange_futures only returns values for supported exchanges @@ -371,7 +374,7 @@ class TestCCXTExchange(): assert mark_candles[mark_candles['date'] == prev_hour].iloc[0]['open'] != 0.0 assert mark_candles[mark_candles['date'] == this_hour].iloc[0]['open'] != 0.0 - def test_ccxt__calculate_funding_fees(self, exchange_futures): + def test_ccxt__calculate_funding_fees(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): exchange, exchangename = exchange_futures if not exchange: # exchange_futures only returns values for supported exchanges @@ -387,16 +390,16 @@ class TestCCXTExchange(): # TODO: tests fetch_trades (?) - def test_ccxt_get_fee(self, exchange): - exchange, exchangename = exchange + def test_ccxt_get_fee(self, exchange: EXCHANGE_FIXTURE_TYPE): + exch, exchangename = exchange pair = EXCHANGES[exchangename]['pair'] threshold = 0.01 - assert 0 < exchange.get_fee(pair, 'limit', 'buy') < threshold - assert 0 < exchange.get_fee(pair, 'limit', 'sell') < threshold - assert 0 < exchange.get_fee(pair, 'market', 'buy') < threshold - assert 0 < exchange.get_fee(pair, 'market', 'sell') < threshold + assert 0 < exch.get_fee(pair, 'limit', 'buy') < threshold + assert 0 < exch.get_fee(pair, 'limit', 'sell') < threshold + assert 0 < exch.get_fee(pair, 'market', 'buy') < threshold + assert 0 < exch.get_fee(pair, 'market', 'sell') < threshold - def test_ccxt_get_max_leverage_spot(self, exchange): + def test_ccxt_get_max_leverage_spot(self, exchange: EXCHANGE_FIXTURE_TYPE): spot, spot_name = exchange if spot: leverage_in_market_spot = EXCHANGES[spot_name].get('leverage_in_spot_market') @@ -406,7 +409,7 @@ class TestCCXTExchange(): assert (isinstance(spot_leverage, float) or isinstance(spot_leverage, int)) assert spot_leverage >= 1.0 - def test_ccxt_get_max_leverage_futures(self, exchange_futures): + def test_ccxt_get_max_leverage_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): futures, futures_name = exchange_futures if futures: leverage_tiers_public = EXCHANGES[futures_name].get('leverage_tiers_public') @@ -419,7 +422,7 @@ class TestCCXTExchange(): assert (isinstance(futures_leverage, float) or isinstance(futures_leverage, int)) assert futures_leverage >= 1.0 - def test_ccxt_get_contract_size(self, exchange_futures): + def test_ccxt_get_contract_size(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): futures, futures_name = exchange_futures if futures: futures_pair = EXCHANGES[futures_name].get( @@ -430,7 +433,7 @@ class TestCCXTExchange(): assert (isinstance(contract_size, float) or isinstance(contract_size, int)) assert contract_size >= 0.0 - def test_ccxt_load_leverage_tiers(self, exchange_futures): + def test_ccxt_load_leverage_tiers(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): futures, futures_name = exchange_futures if futures and EXCHANGES[futures_name].get('leverage_tiers_public'): leverage_tiers = futures.load_leverage_tiers() @@ -463,7 +466,7 @@ class TestCCXTExchange(): oldminNotional = tier['minNotional'] oldmaxNotional = tier['maxNotional'] - def test_ccxt_dry_run_liquidation_price(self, exchange_futures): + def test_ccxt_dry_run_liquidation_price(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): futures, futures_name = exchange_futures if futures and EXCHANGES[futures_name].get('leverage_tiers_public'): @@ -494,7 +497,7 @@ class TestCCXTExchange(): assert (isinstance(liquidation_price, float)) assert liquidation_price >= 0.0 - def test_ccxt_get_max_pair_stake_amount(self, exchange_futures): + def test_ccxt_get_max_pair_stake_amount(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): futures, futures_name = exchange_futures if futures: futures_pair = EXCHANGES[futures_name].get(