diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 204ed971e..f6db04da6 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -1,2 +1,3 @@ from freqtrade.exchange.exchange import Exchange # noqa: F401 from freqtrade.exchange.kraken import Kraken # noqa: F401 +from freqtrade.exchange.binance import Binance # noqa: F401 diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py new file mode 100644 index 000000000..60d35f343 --- /dev/null +++ b/freqtrade/exchange/binance.py @@ -0,0 +1,26 @@ +""" Binance exchange subclass """ +import logging +from typing import Dict + +from freqtrade.exchange import Exchange + +logger = logging.getLogger(__name__) + + +class Binance(Exchange): + + _ft_has: Dict = { + "stoploss_on_exchange": True, + } + + def get_order_book(self, pair: str, limit: int = 100) -> dict: + """ + get order book level 2 from exchange + + 20180619: binance support limits but only on specific range + """ + limit_range = [5, 10, 20, 50, 100, 500, 1000] + # get next-higher step in the limit_range list + limit = min(list(filter(lambda x: limit <= x, limit_range))) + + return super(Binance, self).get_order_book(pair, limit) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 23926d00f..42ef46eb8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -24,7 +24,7 @@ API_RETRY_COUNT = 4 # Urls to exchange markets, insert quote and base with .format() _EXCHANGE_URLS = { ccxt.bittrex.__name__: '/Market/Index?MarketName={quote}-{base}', - ccxt.binance.__name__: '/tradeDetail.html?symbol={base}_{quote}' + ccxt.binance.__name__: '/tradeDetail.html?symbol={base}_{quote}', } @@ -69,11 +69,17 @@ class Exchange(object): _conf: Dict = {} _params: Dict = {} + # Dict to specify which options each exchange implements + # TODO: this should be merged with attributes from subclasses + # To avoid having to copy/paste this to all subclasses. + _ft_has = { + "stoploss_on_exchange": False, + } + def __init__(self, config: dict) -> None: """ Initializes this module with the given config, - it does basic validation whether the specified - exchange and pairs are valid. + it does basic validation whether the specified exchange and pairs are valid. :return: None """ self._conf.update(config) @@ -236,11 +242,11 @@ class Exchange(object): raise OperationalException( f'Exchange {self.name} does not support market orders.') - if order_types.get('stoploss_on_exchange'): - if self.name != 'Binance': - raise OperationalException( - 'On exchange stoploss is not supported for %s.' % self.name - ) + if (order_types.get("stoploss_on_exchange") + and not self._ft_has.get("stoploss_on_exchange", False)): + raise OperationalException( + 'On exchange stoploss is not supported for %s.' % self.name + ) def validate_order_time_in_force(self, order_time_in_force: Dict) -> None: """ @@ -368,6 +374,7 @@ class Exchange(object): """ creates a stoploss limit order. NOTICE: it is not supported by all exchanges. only binance is tested for now. + TODO: implementation maybe needs to be moved to the binance subclass """ ordertype = "stop_loss_limit" @@ -614,18 +621,8 @@ class Exchange(object): Notes: 20180619: bittrex doesnt support limits -.- - 20180619: binance support limits but only on specific range """ try: - if self._api.name == 'Binance': - limit_range = [5, 10, 20, 50, 100, 500, 1000] - # get next-higher step in the limit_range list - limit = min(list(filter(lambda x: limit <= x, limit_range))) - # above script works like loop below (but with slightly better performance): - # for limitx in limit_range: - # if limit <= limitx: - # limit = limitx - # break return self._api.fetch_l2_order_book(pair, limit) except ccxt.NotSupported as e: diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 153be89c1..d904eb2c5 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -12,12 +12,16 @@ import pytest from pandas import DataFrame from freqtrade import DependencyException, OperationalException, TemporaryError -from freqtrade.exchange import Exchange, Kraken +from freqtrade.exchange import Exchange, Kraken, Binance from freqtrade.exchange.exchange import API_RETRY_COUNT from freqtrade.tests.conftest import get_patched_exchange, log_has, log_has_re from freqtrade.resolvers.exchange_resolver import ExchangeResolver +# Make sure to always keep one exchange here which is NOT subclassed!! +EXCHANGES = ['bittrex', 'binance', 'kraken', ] + + # Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines def get_mock_coro(return_value): async def mock_coro(*args, **kwargs): @@ -26,16 +30,17 @@ def get_mock_coro(return_value): return Mock(wraps=mock_coro) -def ccxt_exceptionhandlers(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs): +def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, + fun, mock_ccxt_fun, **kwargs): with pytest.raises(TemporaryError): api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) getattr(exchange, fun)(**kwargs) assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1 with pytest.raises(OperationalException): api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) getattr(exchange, fun)(**kwargs) assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 @@ -113,7 +118,7 @@ def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - exchange = ExchangeResolver('Binance', default_conf).exchange + exchange = ExchangeResolver('Bittrex', default_conf).exchange assert isinstance(exchange, Exchange) assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog.record_tuples) @@ -122,6 +127,15 @@ def test_exchange_resolver(default_conf, mocker, caplog): exchange = ExchangeResolver('Kraken', default_conf).exchange assert isinstance(exchange, Exchange) assert isinstance(exchange, Kraken) + assert not isinstance(exchange, Binance) + assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", + caplog.record_tuples) + + exchange = ExchangeResolver('Binance', default_conf).exchange + assert isinstance(exchange, Exchange) + assert isinstance(exchange, Binance) + assert not isinstance(exchange, Kraken) + assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog.record_tuples) @@ -447,9 +461,10 @@ def test_exchange_has(default_conf, mocker): ("buy"), ("sell") ]) -def test_dry_run_order(default_conf, mocker, side): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_dry_run_order(default_conf, mocker, side, exchange_name): default_conf['dry_run'] = True - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) order = exchange.dry_run_order( pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200) @@ -466,7 +481,8 @@ def test_dry_run_order(default_conf, mocker, side): ("limit", 200), ("stop_loss_limit", 200) ]) -def test_create_order(default_conf, mocker, side, ordertype, rate): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_create_order(default_conf, mocker, side, ordertype, rate, exchange_name): api_mock = MagicMock() order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6)) api_mock.create_order = MagicMock(return_value={ @@ -478,7 +494,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate): default_conf['dry_run'] = False mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order( pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=200) @@ -503,7 +519,8 @@ def test_buy_dry_run(default_conf, mocker): assert 'dry_run_buy_' in order['id'] -def test_buy_prod(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_buy_prod(default_conf, mocker, exchange_name): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_type = 'market' @@ -517,7 +534,7 @@ def test_buy_prod(default_conf, mocker): default_conf['dry_run'] = False mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) @@ -548,25 +565,25 @@ def test_buy_prod(default_conf, mocker): # test exception handling with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) @@ -671,7 +688,8 @@ def test_sell_dry_run(default_conf, mocker): assert 'dry_run_sell_' in order['id'] -def test_sell_prod(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_sell_prod(default_conf, mocker, exchange_name): api_mock = MagicMock() order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6)) order_type = 'market' @@ -685,7 +703,7 @@ def test_sell_prod(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) @@ -710,22 +728,22 @@ def test_sell_prod(default_conf, mocker): # test exception handling with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) @@ -736,23 +754,24 @@ def test_get_balance_dry_run(default_conf, mocker): assert exchange.get_balance(currency='BTC') == 999.9 -def test_get_balance_prod(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_balance_prod(default_conf, mocker, exchange_name): api_mock = MagicMock() api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4}}) default_conf['dry_run'] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.get_balance(currency='BTC') == 123.4 with pytest.raises(OperationalException): api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_balance(currency='BTC') with pytest.raises(TemporaryError, match=r'.*balance due to malformed exchange response:.*'): - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) mocker.patch('freqtrade.exchange.Exchange.get_balances', MagicMock(return_value={})) exchange.get_balance(currency='BTC') @@ -763,7 +782,8 @@ def test_get_balances_dry_run(default_conf, mocker): assert exchange.get_balances() == {} -def test_get_balances_prod(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_balances_prod(default_conf, mocker, exchange_name): balance_item = { 'free': 10.0, 'total': 10.0, @@ -777,17 +797,18 @@ def test_get_balances_prod(default_conf, mocker): '3ST': balance_item }) default_conf['dry_run'] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert len(exchange.get_balances()) == 3 assert exchange.get_balances()['1ST']['free'] == 10.0 assert exchange.get_balances()['1ST']['total'] == 10.0 assert exchange.get_balances()['1ST']['used'] == 0.0 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "get_balances", "fetch_balance") -def test_get_tickers(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_tickers(default_conf, mocker, exchange_name): api_mock = MagicMock() tick = {'ETH/BTC': { 'symbol': 'ETH/BTC', @@ -802,7 +823,7 @@ def test_get_tickers(default_conf, mocker): } } api_mock.fetch_tickers = MagicMock(return_value=tick) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # retrieve original ticker tickers = exchange.get_tickers() @@ -813,20 +834,21 @@ def test_get_tickers(default_conf, mocker): assert tickers['BCH/BTC']['bid'] == 0.6 assert tickers['BCH/BTC']['ask'] == 0.5 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "get_tickers", "fetch_tickers") with pytest.raises(OperationalException): api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_tickers() api_mock.fetch_tickers = MagicMock(return_value={}) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_tickers() -def test_get_ticker(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_ticker(default_conf, mocker, exchange_name): api_mock = MagicMock() tick = { 'symbol': 'ETH/BTC', @@ -836,7 +858,7 @@ def test_get_ticker(default_conf, mocker): } api_mock.fetch_ticker = MagicMock(return_value=tick) api_mock.markets = {'ETH/BTC': {}} - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # retrieve original ticker ticker = exchange.get_ticker(pair='ETH/BTC') @@ -851,7 +873,7 @@ def test_get_ticker(default_conf, mocker): 'last': 42, } api_mock.fetch_ticker = MagicMock(return_value=tick) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # if not caching the result we should get the same ticker # if not fetching a new result we should get the cached ticker @@ -870,20 +892,21 @@ def test_get_ticker(default_conf, mocker): exchange.get_ticker(pair='ETH/BTC', refresh=False) assert api_mock.fetch_ticker.call_count == 0 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "get_ticker", "fetch_ticker", pair='ETH/BTC', refresh=True) api_mock.fetch_ticker = MagicMock(return_value={}) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_ticker(pair='ETH/BTC', refresh=True) with pytest.raises(DependencyException, match=r'Pair XRP/ETH not available'): exchange.get_ticker(pair='XRP/ETH', refresh=True) -def test_get_history(default_conf, mocker, caplog): - exchange = get_patched_exchange(mocker, default_conf) +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_history(default_conf, mocker, caplog, exchange_name): + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) tick = [ [ arrow.utcnow().timestamp * 1000, # unix timestamp ms @@ -962,7 +985,8 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: @pytest.mark.asyncio -async def test__async_get_candle_history(default_conf, mocker, caplog): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name): tick = [ [ arrow.utcnow().timestamp * 1000, # unix timestamp ms @@ -975,11 +999,10 @@ async def test__async_get_candle_history(default_conf, mocker, caplog): ] caplog.set_level(logging.DEBUG) - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) # Monkey-patch async function exchange._api_async.fetch_ohlcv = get_mock_coro(tick) - exchange = Exchange(default_conf) pair = 'ETH/BTC' res = await exchange._async_get_candle_history(pair, "5m") assert type(res) is tuple @@ -998,7 +1021,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog): api_mock = MagicMock() with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) await exchange._async_get_candle_history(pair, "5m", (arrow.utcnow().timestamp - 2000) * 1000) @@ -1051,12 +1074,13 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog): assert log_has("Async code raised an exception: TypeError", caplog.record_tuples) -def test_get_order_book(default_conf, mocker, order_book_l2): - default_conf['exchange']['name'] = 'binance' +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_order_book(default_conf, mocker, order_book_l2, exchange_name): + default_conf['exchange']['name'] = exchange_name api_mock = MagicMock() api_mock.fetch_l2_order_book = order_book_l2 - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order_book = exchange.get_order_book(pair='ETH/BTC', limit=10) assert 'bids' in order_book assert 'asks' in order_book @@ -1064,19 +1088,20 @@ def test_get_order_book(default_conf, mocker, order_book_l2): assert len(order_book['asks']) == 10 -def test_get_order_book_exception(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_order_book_exception(default_conf, mocker, exchange_name): api_mock = MagicMock() with pytest.raises(OperationalException): api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_order_book(pair='ETH/BTC', limit=50) with pytest.raises(TemporaryError): api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_order_book(pair='ETH/BTC', limit=50) with pytest.raises(OperationalException): api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_order_book(pair='ETH/BTC', limit=50) @@ -1089,8 +1114,9 @@ def make_fetch_ohlcv_mock(data): return fetch_ohlcv_mock +@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.asyncio -async def test___async_get_candle_history_sort(default_conf, mocker): +async def test___async_get_candle_history_sort(default_conf, mocker, exchange_name): def sort_data(data, key): return sorted(data, key=key) @@ -1108,7 +1134,7 @@ async def test___async_get_candle_history_sort(default_conf, mocker): [1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687], [1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867] ] - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange._api_async.fetch_ohlcv = get_mock_coro(tick) sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data)) # Test the ticker history sort @@ -1170,36 +1196,39 @@ async def test___async_get_candle_history_sort(default_conf, mocker): assert ticks[9][5] == 2.31452783 -def test_cancel_order_dry_run(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_cancel_order_dry_run(default_conf, mocker, exchange_name): default_conf['dry_run'] = True - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) assert exchange.cancel_order(order_id='123', pair='TKN/BTC') is None # Ensure that if not dry_run, we should call API -def test_cancel_order(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_cancel_order(default_conf, mocker, exchange_name): default_conf['dry_run'] = False api_mock = MagicMock() api_mock.cancel_order = MagicMock(return_value=123) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123 with pytest.raises(DependencyException): api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.cancel_order(order_id='_', pair='TKN/BTC') assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "cancel_order", "cancel_order", order_id='_', pair='TKN/BTC') -def test_get_order(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_order(default_conf, mocker, exchange_name): default_conf['dry_run'] = True order = MagicMock() order.myid = 123 - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange._dry_run_open_orders['X'] = order print(exchange.get_order('X', 'TKN/BTC')) assert exchange.get_order('X', 'TKN/BTC').myid == 123 @@ -1207,33 +1236,28 @@ def test_get_order(default_conf, mocker): default_conf['dry_run'] = False api_mock = MagicMock() api_mock.fetch_order = MagicMock(return_value=456) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.get_order('X', 'TKN/BTC') == 456 with pytest.raises(DependencyException): api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_order(order_id='_', pair='TKN/BTC') assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_order', 'fetch_order', order_id='_', pair='TKN/BTC') -def test_name(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_name(default_conf, mocker, exchange_name): mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - default_conf['exchange']['name'] = 'binance' + default_conf['exchange']['name'] = exchange_name exchange = Exchange(default_conf) - assert exchange.name == 'Binance' - - -def test_id(default_conf, mocker): - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - default_conf['exchange']['name'] = 'binance' - exchange = Exchange(default_conf) - assert exchange.id == 'binance' + assert exchange.name == exchange_name.title() + assert exchange.id == exchange_name def test_get_pair_detail_url(default_conf, mocker, caplog): @@ -1267,7 +1291,8 @@ def test_get_pair_detail_url(default_conf, mocker, caplog): assert log_has('Could not get exchange url for Poloniex', caplog.record_tuples) -def test_get_trades_for_order(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_trades_for_order(default_conf, mocker, exchange_name): order_id = 'ABCD-ABCD' since = datetime(2018, 5, 5) default_conf["dry_run"] = False @@ -1294,13 +1319,13 @@ def test_get_trades_for_order(default_conf, mocker): 'amount': 0.2340606, 'fee': {'cost': 0.06179, 'currency': 'BTC'} }]) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since) assert len(orders) == 1 assert orders[0]['price'] == 165 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_trades_for_order', 'fetch_my_trades', order_id=order_id, pair='LTC/BTC', since=since) @@ -1308,10 +1333,11 @@ def test_get_trades_for_order(default_conf, mocker): assert exchange.get_trades_for_order(order_id, 'LTC/BTC', since) == [] -def test_get_markets(default_conf, mocker, markets): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_markets(default_conf, mocker, markets, exchange_name): api_mock = MagicMock() api_mock.fetch_markets = markets - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) ret = exchange.get_markets() assert isinstance(ret, list) assert len(ret) == 6 @@ -1319,11 +1345,12 @@ def test_get_markets(default_conf, mocker, markets): assert ret[0]["id"] == "ethbtc" assert ret[0]["symbol"] == "ETH/BTC" - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_markets', 'fetch_markets') -def test_get_fee(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_fee(default_conf, mocker, exchange_name): api_mock = MagicMock() api_mock.calculate_fee = MagicMock(return_value={ 'type': 'taker', @@ -1331,11 +1358,11 @@ def test_get_fee(default_conf, mocker): 'rate': 0.025, 'cost': 0.05 }) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.get_fee() == 0.025 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_fee', 'calculate_fee')