diff --git a/tests/exchange_online/conftest.py b/tests/exchange_online/conftest.py new file mode 100644 index 000000000..f4b50778d --- /dev/null +++ b/tests/exchange_online/conftest.py @@ -0,0 +1,329 @@ +from copy import deepcopy +from pathlib import Path +from typing import Tuple + +import pytest + +from freqtrade.constants import Config +from freqtrade.exchange.exchange import Exchange +from freqtrade.resolvers.exchange_resolver import ExchangeResolver +from tests.conftest import EXMS, get_default_conf_usdt + + +EXCHANGE_FIXTURE_TYPE = Tuple[Exchange, str] + +# Exchanges that should be tested online +EXCHANGES = { + 'bittrex': { + 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', + 'hasQuoteVolume': False, + 'timeframe': '1h', + 'leverage_tiers_public': False, + 'leverage_in_spot_market': False, + }, + 'binance': { + 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', + 'use_ci_proxy': True, + 'hasQuoteVolume': True, + 'timeframe': '1h', + 'futures': True, + 'futures_pair': 'BTC/USDT:USDT', + 'hasQuoteVolumeFutures': True, + 'leverage_tiers_public': False, + 'leverage_in_spot_market': False, + 'trades_lookback_hours': 4, + 'private_methods': [ + 'fapiPrivateGetPositionSideDual', + 'fapiPrivateGetMultiAssetsMargin' + ], + 'sample_order': [{ + "symbol": "SOLUSDT", + "orderId": 3551312894, + "orderListId": -1, + "clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba", + "transactTime": 1674493798550, + "price": "15.50000000", + "origQty": "1.10000000", + "executedQty": "0.00000000", + "cummulativeQuoteQty": "0.00000000", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "workingTime": 1674493798550, + "fills": [], + "selfTradePreventionMode": "NONE", + }] + }, + 'binanceus': { + 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', + 'hasQuoteVolume': True, + 'timeframe': '1h', + 'futures': False, + 'sample_order': [{ + "symbol": "SOLUSDT", + "orderId": 3551312894, + "orderListId": -1, + "clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba", + "transactTime": 1674493798550, + "price": "15.50000000", + "origQty": "1.10000000", + "executedQty": "0.00000000", + "cummulativeQuoteQty": "0.00000000", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "workingTime": 1674493798550, + "fills": [], + "selfTradePreventionMode": "NONE", + }] + }, + 'kraken': { + 'pair': 'BTC/USD', + 'stake_currency': 'USD', + 'hasQuoteVolume': True, + 'timeframe': '1h', + 'leverage_tiers_public': False, + 'leverage_in_spot_market': True, + 'trades_lookback_hours': 12, + }, + 'kucoin': { + 'pair': 'XRP/USDT', + 'stake_currency': 'USDT', + 'hasQuoteVolume': True, + 'timeframe': '1h', + 'leverage_tiers_public': False, + 'leverage_in_spot_market': True, + 'sample_order': [ + {'id': '63d6742d0adc5570001d2bbf7'}, # create order + { + 'id': '63d6742d0adc5570001d2bbf7', + 'symbol': 'SOL-USDT', + 'opType': 'DEAL', + 'type': 'limit', + 'side': 'buy', + 'price': '15.5', + 'size': '1.1', + 'funds': '0', + 'dealFunds': '17.05', + 'dealSize': '1.1', + 'fee': '0.000065252', + 'feeCurrency': 'USDT', + 'stp': '', + 'stop': '', + 'stopTriggered': False, + 'stopPrice': '0', + 'timeInForce': 'GTC', + 'postOnly': False, + 'hidden': False, + 'iceberg': False, + 'visibleSize': '0', + 'cancelAfter': 0, + 'channel': 'API', + 'clientOid': '0a053870-11bf-41e5-be61-b272a4cb62e1', + 'remark': None, + 'tags': 'partner:ccxt', + 'isActive': False, + 'cancelExist': False, + 'createdAt': 1674493798550, + 'tradeType': 'TRADE' + }], + }, + 'gate': { + 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', + 'hasQuoteVolume': True, + 'timeframe': '1h', + 'futures': True, + 'futures_pair': 'BTC/USDT:USDT', + 'hasQuoteVolumeFutures': True, + 'leverage_tiers_public': True, + 'leverage_in_spot_market': True, + 'sample_order': [ + { + "id": "276266139423", + "text": "apiv4", + "create_time": "1674493798", + "update_time": "1674493798", + "create_time_ms": "1674493798550", + "update_time_ms": "1674493798550", + "status": "closed", + "currency_pair": "SOL_USDT", + "type": "limit", + "account": "spot", + "side": "buy", + "amount": "1.1", + "price": "15.5", + "time_in_force": "gtc", + "iceberg": "0", + "left": "0", + "fill_price": "17.05", + "filled_total": "17.05", + "avg_deal_price": "15.5", + "fee": "0.0000018", + "fee_currency": "SOL", + "point_fee": "0", + "gt_fee": "0", + "gt_maker_fee": "0", + "gt_taker_fee": "0.0015", + "gt_discount": True, + "rebated_fee": "0", + "rebated_fee_currency": "USDT" + }, + { + # market order + 'id': '276401180529', + 'text': 'apiv4', + 'create_time': '1674493798', + 'update_time': '1674493798', + 'create_time_ms': '1674493798550', + 'update_time_ms': '1674493798550', + 'status': 'cancelled', + 'currency_pair': 'SOL_USDT', + 'type': 'market', + 'account': 'spot', + 'side': 'buy', + 'amount': '17.05', + 'price': '0', + 'time_in_force': 'ioc', + 'iceberg': '0', + 'left': '0.0000000016228', + 'fill_price': '17.05', + 'filled_total': '17.05', + 'avg_deal_price': '15.5', + 'fee': '0', + 'fee_currency': 'SOL', + 'point_fee': '0.0199999999967544', + 'gt_fee': '0', + 'gt_maker_fee': '0', + 'gt_taker_fee': '0', + 'gt_discount': False, + 'rebated_fee': '0', + 'rebated_fee_currency': 'USDT' + } + ], + }, + 'okx': { + 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', + 'hasQuoteVolume': True, + 'timeframe': '1h', + 'futures': True, + 'futures_pair': 'BTC/USDT:USDT', + 'hasQuoteVolumeFutures': False, + 'leverage_tiers_public': True, + 'leverage_in_spot_market': True, + 'private_methods': ['fetch_accounts'], + }, + 'bybit': { + 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', + 'hasQuoteVolume': True, + 'use_ci_proxy': True, + 'timeframe': '1h', + 'futures_pair': 'BTC/USDT:USDT', + 'futures': True, + 'leverage_tiers_public': True, + 'leverage_in_spot_market': True, + 'sample_order': [ + { + "orderId": "1274754916287346280", + "orderLinkId": "1666798627015730", + "symbol": "SOLUSDT", + "createTime": "1674493798550", + "orderPrice": "15.5", + "orderQty": "1.1", + "orderType": "LIMIT", + "side": "BUY", + "status": "NEW", + "timeInForce": "GTC", + "accountId": "5555555", + "execQty": "0", + "orderCategory": "0" + } + ] + }, + 'huobi': { + 'pair': 'ETH/BTC', + 'stake_currency': 'BTC', + 'hasQuoteVolume': True, + 'timeframe': '1h', + 'futures': False, + }, + 'bitvavo': { + 'pair': 'BTC/EUR', + 'stake_currency': 'EUR', + 'hasQuoteVolume': True, + 'timeframe': '1h', + 'leverage_tiers_public': False, + 'leverage_in_spot_market': False, + }, +} + + +@pytest.fixture(scope="class") +def exchange_conf(): + config = get_default_conf_usdt((Path(__file__).parent / "testdata").resolve()) + config['exchange']['pair_whitelist'] = [] + config['exchange']['key'] = '' + config['exchange']['secret'] = '' + config['dry_run'] = False + config['entry_pricing']['use_order_book'] = True + config['exit_pricing']['use_order_book'] = True + return config + + +def set_test_proxy(config: Config, use_proxy: bool) -> Config: + # Set proxy to test in CI. + import os + if use_proxy and (proxy := os.environ.get('CI_WEB_PROXY')): + config1 = deepcopy(config) + config1['exchange']['ccxt_config'] = { + "httpsProxy": proxy, + } + return config1 + + return config + + +@pytest.fixture(params=EXCHANGES, scope="class") +def exchange(request, exchange_conf): + exchange_conf = set_test_proxy( + exchange_conf, EXCHANGES[request.param].get('use_ci_proxy', False)) + exchange_conf['exchange']['name'] = request.param + exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency'] + exchange = ExchangeResolver.load_exchange(exchange_conf, validate=True) + + yield exchange, request.param + + +@pytest.fixture(params=EXCHANGES, scope="class") +def exchange_futures(request, exchange_conf, class_mocker): + if EXCHANGES[request.param].get('futures') is not True: + pytest.skip(f"Exchange {request.param} does not support futures.") + else: + exchange_conf = set_test_proxy( + exchange_conf, EXCHANGES[request.param].get('use_ci_proxy', False)) + exchange_conf = deepcopy(exchange_conf) + exchange_conf['exchange']['name'] = request.param + exchange_conf['trading_mode'] = 'futures' + exchange_conf['margin_mode'] = 'isolated' + exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency'] + + class_mocker.patch( + 'freqtrade.exchange.binance.Binance.fill_leverage_tiers') + class_mocker.patch(f'{EXMS}.fetch_trading_fees') + class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init') + class_mocker.patch('freqtrade.exchange.binance.Binance.additional_exchange_init') + class_mocker.patch('freqtrade.exchange.bybit.Bybit.additional_exchange_init') + class_mocker.patch(f'{EXMS}.load_cached_leverage_tiers', return_value=None) + class_mocker.patch(f'{EXMS}.cache_leverage_tiers') + + exchange = ExchangeResolver.load_exchange( + exchange_conf, validate=True, load_leverage_tiers=True) + + yield exchange, request.param diff --git a/tests/exchange_online/test_ccxt_compat.py b/tests/exchange_online/test_ccxt_compat.py index 0aa59db2a..e33875416 100644 --- a/tests/exchange_online/test_ccxt_compat.py +++ b/tests/exchange_online/test_ccxt_compat.py @@ -5,338 +5,14 @@ However, these tests should give a good idea to determine if a new exchange is 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.constants import Config from freqtrade.enums import CandleType from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date -from freqtrade.exchange.exchange import Exchange, timeframe_to_msecs -from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import EXMS, get_default_conf_usdt - - -EXCHANGE_FIXTURE_TYPE = Tuple[Exchange, str] - -# Exchanges that should be tested -EXCHANGES = { - 'bittrex': { - 'pair': 'BTC/USDT', - 'stake_currency': 'USDT', - 'hasQuoteVolume': False, - 'timeframe': '1h', - 'leverage_tiers_public': False, - 'leverage_in_spot_market': False, - }, - 'binance': { - 'pair': 'BTC/USDT', - 'stake_currency': 'USDT', - 'use_ci_proxy': True, - 'hasQuoteVolume': True, - 'timeframe': '1h', - 'futures': True, - 'futures_pair': 'BTC/USDT:USDT', - 'hasQuoteVolumeFutures': True, - 'leverage_tiers_public': False, - 'leverage_in_spot_market': False, - 'trades_lookback_hours': 4, - 'private_methods': [ - 'fapiPrivateGetPositionSideDual', - 'fapiPrivateGetMultiAssetsMargin' - ], - 'sample_order': [{ - "symbol": "SOLUSDT", - "orderId": 3551312894, - "orderListId": -1, - "clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba", - "transactTime": 1674493798550, - "price": "15.50000000", - "origQty": "1.10000000", - "executedQty": "0.00000000", - "cummulativeQuoteQty": "0.00000000", - "status": "NEW", - "timeInForce": "GTC", - "type": "LIMIT", - "side": "BUY", - "workingTime": 1674493798550, - "fills": [], - "selfTradePreventionMode": "NONE", - }] - }, - 'binanceus': { - 'pair': 'BTC/USDT', - 'stake_currency': 'USDT', - 'hasQuoteVolume': True, - 'timeframe': '1h', - 'futures': False, - 'sample_order': [{ - "symbol": "SOLUSDT", - "orderId": 3551312894, - "orderListId": -1, - "clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba", - "transactTime": 1674493798550, - "price": "15.50000000", - "origQty": "1.10000000", - "executedQty": "0.00000000", - "cummulativeQuoteQty": "0.00000000", - "status": "NEW", - "timeInForce": "GTC", - "type": "LIMIT", - "side": "BUY", - "workingTime": 1674493798550, - "fills": [], - "selfTradePreventionMode": "NONE", - }] - }, - 'kraken': { - 'pair': 'BTC/USD', - 'stake_currency': 'USD', - 'hasQuoteVolume': True, - 'timeframe': '1h', - 'leverage_tiers_public': False, - 'leverage_in_spot_market': True, - 'trades_lookback_hours': 12, - }, - 'kucoin': { - 'pair': 'XRP/USDT', - 'stake_currency': 'USDT', - 'hasQuoteVolume': True, - 'timeframe': '1h', - 'leverage_tiers_public': False, - 'leverage_in_spot_market': True, - 'sample_order': [ - {'id': '63d6742d0adc5570001d2bbf7'}, # create order - { - 'id': '63d6742d0adc5570001d2bbf7', - 'symbol': 'SOL-USDT', - 'opType': 'DEAL', - 'type': 'limit', - 'side': 'buy', - 'price': '15.5', - 'size': '1.1', - 'funds': '0', - 'dealFunds': '17.05', - 'dealSize': '1.1', - 'fee': '0.000065252', - 'feeCurrency': 'USDT', - 'stp': '', - 'stop': '', - 'stopTriggered': False, - 'stopPrice': '0', - 'timeInForce': 'GTC', - 'postOnly': False, - 'hidden': False, - 'iceberg': False, - 'visibleSize': '0', - 'cancelAfter': 0, - 'channel': 'API', - 'clientOid': '0a053870-11bf-41e5-be61-b272a4cb62e1', - 'remark': None, - 'tags': 'partner:ccxt', - 'isActive': False, - 'cancelExist': False, - 'createdAt': 1674493798550, - 'tradeType': 'TRADE' - }], - }, - 'gate': { - 'pair': 'BTC/USDT', - 'stake_currency': 'USDT', - 'hasQuoteVolume': True, - 'timeframe': '1h', - 'futures': True, - 'futures_pair': 'BTC/USDT:USDT', - 'hasQuoteVolumeFutures': True, - 'leverage_tiers_public': True, - 'leverage_in_spot_market': True, - 'sample_order': [ - { - "id": "276266139423", - "text": "apiv4", - "create_time": "1674493798", - "update_time": "1674493798", - "create_time_ms": "1674493798550", - "update_time_ms": "1674493798550", - "status": "closed", - "currency_pair": "SOL_USDT", - "type": "limit", - "account": "spot", - "side": "buy", - "amount": "1.1", - "price": "15.5", - "time_in_force": "gtc", - "iceberg": "0", - "left": "0", - "fill_price": "17.05", - "filled_total": "17.05", - "avg_deal_price": "15.5", - "fee": "0.0000018", - "fee_currency": "SOL", - "point_fee": "0", - "gt_fee": "0", - "gt_maker_fee": "0", - "gt_taker_fee": "0.0015", - "gt_discount": True, - "rebated_fee": "0", - "rebated_fee_currency": "USDT" - }, - { - # market order - 'id': '276401180529', - 'text': 'apiv4', - 'create_time': '1674493798', - 'update_time': '1674493798', - 'create_time_ms': '1674493798550', - 'update_time_ms': '1674493798550', - 'status': 'cancelled', - 'currency_pair': 'SOL_USDT', - 'type': 'market', - 'account': 'spot', - 'side': 'buy', - 'amount': '17.05', - 'price': '0', - 'time_in_force': 'ioc', - 'iceberg': '0', - 'left': '0.0000000016228', - 'fill_price': '17.05', - 'filled_total': '17.05', - 'avg_deal_price': '15.5', - 'fee': '0', - 'fee_currency': 'SOL', - 'point_fee': '0.0199999999967544', - 'gt_fee': '0', - 'gt_maker_fee': '0', - 'gt_taker_fee': '0', - 'gt_discount': False, - 'rebated_fee': '0', - 'rebated_fee_currency': 'USDT' - } - ], - }, - 'okx': { - 'pair': 'BTC/USDT', - 'stake_currency': 'USDT', - 'hasQuoteVolume': True, - 'timeframe': '1h', - 'futures': True, - 'futures_pair': 'BTC/USDT:USDT', - 'hasQuoteVolumeFutures': False, - 'leverage_tiers_public': True, - 'leverage_in_spot_market': True, - 'private_methods': ['fetch_accounts'], - }, - 'bybit': { - 'pair': 'BTC/USDT', - 'stake_currency': 'USDT', - 'hasQuoteVolume': True, - 'use_ci_proxy': True, - 'timeframe': '1h', - 'futures_pair': 'BTC/USDT:USDT', - 'futures': True, - 'leverage_tiers_public': True, - 'leverage_in_spot_market': True, - 'sample_order': [ - { - "orderId": "1274754916287346280", - "orderLinkId": "1666798627015730", - "symbol": "SOLUSDT", - "createTime": "1674493798550", - "orderPrice": "15.5", - "orderQty": "1.1", - "orderType": "LIMIT", - "side": "BUY", - "status": "NEW", - "timeInForce": "GTC", - "accountId": "5555555", - "execQty": "0", - "orderCategory": "0" - } - ] - }, - 'huobi': { - 'pair': 'ETH/BTC', - 'stake_currency': 'BTC', - 'hasQuoteVolume': True, - 'timeframe': '1h', - 'futures': False, - }, - 'bitvavo': { - 'pair': 'BTC/EUR', - 'stake_currency': 'EUR', - 'hasQuoteVolume': True, - 'timeframe': '1h', - 'leverage_tiers_public': False, - 'leverage_in_spot_market': False, - }, -} - - -@pytest.fixture(scope="class") -def exchange_conf(): - config = get_default_conf_usdt((Path(__file__).parent / "testdata").resolve()) - config['exchange']['pair_whitelist'] = [] - config['exchange']['key'] = '' - config['exchange']['secret'] = '' - config['dry_run'] = False - config['entry_pricing']['use_order_book'] = True - config['exit_pricing']['use_order_book'] = True - return config - - -def set_test_proxy(config: Config, use_proxy: bool) -> Config: - # Set proxy to test in CI. - import os - if use_proxy and (proxy := os.environ.get('CI_WEB_PROXY')): - config1 = deepcopy(config) - config1['exchange']['ccxt_config'] = { - "httpsProxy": proxy, - } - return config1 - - return config - - -@pytest.fixture(params=EXCHANGES, scope="class") -def exchange(request, exchange_conf): - exchange_conf = set_test_proxy( - exchange_conf, EXCHANGES[request.param].get('use_ci_proxy', False)) - exchange_conf['exchange']['name'] = request.param - exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency'] - exchange = ExchangeResolver.load_exchange(exchange_conf, validate=True) - - yield exchange, request.param - - -@pytest.fixture(params=EXCHANGES, scope="class") -def exchange_futures(request, exchange_conf, class_mocker): - if EXCHANGES[request.param].get('futures') is not True: - pytest.skip(f"Exchange {request.param} does not support futures.") - else: - exchange_conf = set_test_proxy( - exchange_conf, EXCHANGES[request.param].get('use_ci_proxy', False)) - exchange_conf = deepcopy(exchange_conf) - exchange_conf['exchange']['name'] = request.param - exchange_conf['trading_mode'] = 'futures' - exchange_conf['margin_mode'] = 'isolated' - exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency'] - - class_mocker.patch( - 'freqtrade.exchange.binance.Binance.fill_leverage_tiers') - class_mocker.patch(f'{EXMS}.fetch_trading_fees') - class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init') - class_mocker.patch('freqtrade.exchange.binance.Binance.additional_exchange_init') - class_mocker.patch('freqtrade.exchange.bybit.Bybit.additional_exchange_init') - class_mocker.patch(f'{EXMS}.load_cached_leverage_tiers', return_value=None) - class_mocker.patch(f'{EXMS}.cache_leverage_tiers') - - exchange = ExchangeResolver.load_exchange( - exchange_conf, validate=True, load_leverage_tiers=True) - - yield exchange, request.param +from freqtrade.exchange.exchange import timeframe_to_msecs +from tests.exchange_online.conftest import EXCHANGE_FIXTURE_TYPE, EXCHANGES @pytest.mark.longrun