stable/tests/exchange/test_exchange.py

2352 lines
99 KiB
Python
Raw Normal View History

2018-01-28 07:38:41 +00:00
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access
import copy
import logging
2019-08-06 18:23:32 +00:00
from datetime import datetime, timezone
2018-07-04 07:31:35 +00:00
from random import randint
2020-06-28 09:17:06 +00:00
from unittest.mock import MagicMock, Mock, PropertyMock, patch
2018-03-17 21:44:47 +00:00
import arrow
import ccxt
import pytest
2018-12-11 18:48:36 +00:00
from pandas import DataFrame
from freqtrade.exceptions import (DDosProtection, DependencyException,
InvalidOrderException, OperationalException,
TemporaryError)
from freqtrade.exchange import Binance, Exchange, Kraken
2020-06-28 14:18:39 +00:00
from freqtrade.exchange.common import API_RETRY_COUNT, calculate_backoff
2020-06-02 18:30:31 +00:00
from freqtrade.exchange.exchange import (market_is_active,
timeframe_to_minutes,
2019-08-12 13:43:10 +00:00
timeframe_to_msecs,
2019-08-12 14:11:43 +00:00
timeframe_to_next_date,
timeframe_to_prev_date,
timeframe_to_seconds)
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
2019-09-08 07:54:15 +00:00
from tests.conftest import get_patched_exchange, log_has, log_has_re
# Make sure to always keep one exchange here which is NOT subclassed!!
2020-03-25 14:50:33 +00:00
EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx']
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
def get_mock_coro(return_value):
async def mock_coro(*args, **kwargs):
return return_value
return Mock(wraps=mock_coro)
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
fun, mock_ccxt_fun, retries=API_RETRY_COUNT + 1, **kwargs):
2020-06-28 09:17:06 +00:00
with patch('freqtrade.exchange.common.time.sleep'):
with pytest.raises(DDosProtection):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DDos"))
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 == retries
2020-06-28 09:17:06 +00:00
2018-06-28 19:22:43 +00:00
with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeaDBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-28 19:22:43 +00:00
getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
2018-06-28 19:22:43 +00:00
with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-28 19:22:43 +00:00
getattr(exchange, fun)(**kwargs)
2018-07-01 17:37:55 +00:00
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
2018-06-28 19:22:43 +00:00
async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun,
retries=API_RETRY_COUNT + 1, **kwargs):
2020-06-28 09:17:06 +00:00
with patch('freqtrade.exchange.common.asyncio.sleep', get_mock_coro(None)):
2020-06-28 09:17:06 +00:00
with pytest.raises(DDosProtection):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("Dooh"))
2020-06-28 09:17:06 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
2020-06-28 09:17:06 +00:00
2018-08-01 19:19:49 +00:00
with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
2018-08-01 19:19:49 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
2018-08-01 19:19:49 +00:00
with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
2018-08-01 19:19:49 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
def test_init(default_conf, mocker, caplog):
2018-01-31 17:37:38 +00:00
caplog.set_level(logging.INFO)
2018-06-17 18:13:39 +00:00
get_patched_exchange(mocker, default_conf)
2019-08-11 18:17:39 +00:00
assert log_has('Instance is running with dry_run enabled', caplog)
2019-09-10 21:18:07 +00:00
def test_init_ccxt_kwargs(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
caplog.set_level(logging.INFO)
conf = copy.deepcopy(default_conf)
conf['exchange']['ccxt_async_config'] = {'aiohttp_trust_env': True, 'asyncio_loop': True}
ex = Exchange(conf)
assert log_has(
"Applying additional ccxt config: {'aiohttp_trust_env': True, 'asyncio_loop': True}",
caplog)
assert ex._api_async.aiohttp_trust_env
assert not ex._api.aiohttp_trust_env
# Reset logging and config
caplog.clear()
conf = copy.deepcopy(default_conf)
conf['exchange']['ccxt_config'] = {'TestKWARG': 11}
conf['exchange']['ccxt_sync_config'] = {'TestKWARG44': 11}
conf['exchange']['ccxt_async_config'] = {'asyncio_loop': True}
asynclogmsg = "Applying additional ccxt config: {'TestKWARG': 11, 'asyncio_loop': True}"
ex = Exchange(conf)
assert not ex._api_async.aiohttp_trust_env
assert hasattr(ex._api, 'TestKWARG')
assert ex._api.TestKWARG == 11
# ccxt_config is assigned to both sync and async
assert not hasattr(ex._api_async, 'TestKWARG44')
assert hasattr(ex._api_async, 'TestKWARG')
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
assert log_has(asynclogmsg, caplog)
def test_destroy(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG)
get_patched_exchange(mocker, default_conf)
2019-08-11 18:17:39 +00:00
assert log_has('Exchange object destroyed, closing async loop', caplog)
2018-06-28 20:32:28 +00:00
def test_init_exception(default_conf, mocker):
default_conf['exchange']['name'] = 'wrong_exchange_name'
2019-08-25 08:13:35 +00:00
with pytest.raises(OperationalException,
match=f"Exchange {default_conf['exchange']['name']} is not supported"):
2018-06-17 18:13:39 +00:00
Exchange(default_conf)
2018-06-28 20:32:28 +00:00
default_conf['exchange']['name'] = 'binance'
2019-08-25 08:13:35 +00:00
with pytest.raises(OperationalException,
match=f"Exchange {default_conf['exchange']['name']} is not supported"):
2018-06-28 20:32:28 +00:00
mocker.patch("ccxt.binance", MagicMock(side_effect=AttributeError))
Exchange(default_conf)
2019-08-25 08:13:35 +00:00
with pytest.raises(OperationalException,
match=r"Initialization of ccxt failed. Reason: DeadBeef"):
mocker.patch("ccxt.binance", MagicMock(side_effect=ccxt.BaseError("DeadBeef")))
Exchange(default_conf)
2018-07-29 08:10:55 +00:00
def test_exchange_resolver(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock()))
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
exchange = ExchangeResolver.load_exchange('Bittrex', default_conf)
assert isinstance(exchange, Exchange)
2019-08-11 18:17:39 +00:00
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog)
caplog.clear()
exchange = ExchangeResolver.load_exchange('kraken', default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Kraken)
2019-02-24 19:08:27 +00:00
assert not isinstance(exchange, Binance)
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
2019-08-11 18:17:39 +00:00
caplog)
2019-02-24 19:08:27 +00:00
exchange = ExchangeResolver.load_exchange('binance', default_conf)
2019-02-24 19:08:27 +00:00
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.",
2019-08-11 18:17:39 +00:00
caplog)
# Test mapping
exchange = ExchangeResolver.load_exchange('binanceus', default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Binance)
assert not isinstance(exchange, Kraken)
def test_validate_order_time_in_force(default_conf, mocker, caplog):
caplog.set_level(logging.INFO)
# explicitly test bittrex, exchanges implementing other policies need seperate tests
ex = get_patched_exchange(mocker, default_conf, id="bittrex")
tif = {
"buy": "gtc",
"sell": "gtc",
}
ex.validate_order_time_in_force(tif)
tif2 = {
"buy": "fok",
"sell": "ioc",
}
with pytest.raises(OperationalException, match=r"Time in force.*not supported for .*"):
ex.validate_order_time_in_force(tif2)
# Patch to see if this will pass if the values are in the ft dict
ex._ft_has.update({"order_time_in_force": ["gtc", "fok", "ioc"]})
ex.validate_order_time_in_force(tif2)
@pytest.mark.parametrize("amount,precision_mode,precision,expected", [
(2.34559, 2, 4, 2.3455),
(2.34559, 2, 5, 2.34559),
(2.34559, 2, 3, 2.345),
(2.9999, 2, 3, 2.999),
(2.9909, 2, 3, 2.990),
# Tests for Tick-size
(2.34559, 4, 0.0001, 2.3455),
(2.34559, 4, 0.00001, 2.34559),
(2.34559, 4, 0.001, 2.345),
(2.9999, 4, 0.001, 2.999),
(2.9909, 4, 0.001, 2.990),
(2.9909, 4, 0.005, 2.990),
(2.9999, 4, 0.005, 2.995),
])
def test_amount_to_precision(default_conf, mocker, amount, precision_mode, precision, expected):
'''
Test rounds down
'''
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': precision}}})
2019-10-26 08:08:23 +00:00
exchange = get_patched_exchange(mocker, default_conf, id="binance")
# digits counting mode
# DECIMAL_PLACES = 2
# SIGNIFICANT_DIGITS = 3
# TICK_SIZE = 4
mocker.patch('freqtrade.exchange.Exchange.precisionMode',
PropertyMock(return_value=precision_mode))
2019-10-26 08:08:23 +00:00
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
pair = 'ETH/BTC'
assert exchange.amount_to_precision(pair, amount) == expected
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
(2.34559, 2, 4, 2.3456),
(2.34559, 2, 5, 2.34559),
(2.34559, 2, 3, 2.346),
(2.9999, 2, 3, 3.000),
(2.9909, 2, 3, 2.991),
# Tests for Tick_size
(2.34559, 4, 0.0001, 2.3456),
(2.34559, 4, 0.00001, 2.34559),
(2.34559, 4, 0.001, 2.346),
(2.9999, 4, 0.001, 3.000),
(2.9909, 4, 0.001, 2.991),
2020-01-14 19:16:47 +00:00
(2.9909, 4, 0.005, 2.995),
(2.9973, 4, 0.005, 3.0),
(2.9977, 4, 0.005, 3.0),
2020-01-14 19:16:47 +00:00
(234.43, 4, 0.5, 234.5),
(234.53, 4, 0.5, 235.0),
(0.891534, 4, 0.0001, 0.8916),
])
def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected):
'''
Test price to precision
'''
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}})
2019-10-26 08:08:23 +00:00
exchange = get_patched_exchange(mocker, default_conf, id="binance")
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
# digits counting mode
# DECIMAL_PLACES = 2
# SIGNIFICANT_DIGITS = 3
# TICK_SIZE = 4
mocker.patch('freqtrade.exchange.Exchange.precisionMode',
PropertyMock(return_value=precision_mode))
pair = 'ETH/BTC'
2020-01-14 19:16:47 +00:00
assert pytest.approx(exchange.price_to_precision(pair, price)) == expected
2020-04-15 05:53:31 +00:00
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
(2.34559, 2, 4, 0.0001),
(2.34559, 2, 5, 0.00001),
(2.34559, 2, 3, 0.001),
(2.9999, 2, 3, 0.001),
(200.0511, 2, 3, 0.001),
# Tests for Tick_size
(2.34559, 4, 0.0001, 0.0001),
(2.34559, 4, 0.00001, 0.00001),
(2.34559, 4, 0.0025, 0.0025),
(2.9909, 4, 0.0025, 0.0025),
(234.43, 4, 0.5, 0.5),
(234.43, 4, 0.0025, 0.0025),
(234.43, 4, 0.00013, 0.00013),
])
def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precision, expected):
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}})
exchange = get_patched_exchange(mocker, default_conf, id="binance")
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
mocker.patch('freqtrade.exchange.Exchange.precisionMode',
PropertyMock(return_value=precision_mode))
pair = 'ETH/BTC'
assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected
def test_set_sandbox(default_conf, mocker):
"""
Test working scenario
"""
api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
})
2018-07-29 08:10:55 +00:00
url_mock = PropertyMock(return_value={'test': "api-public.sandbox.gdax.com",
'api': 'https://api.gdax.com'})
type(api_mock).urls = url_mock
2019-03-05 18:46:03 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock)
liveurl = exchange._api.urls['api']
default_conf['exchange']['sandbox'] = True
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
assert exchange._api.urls['api'] != liveurl
2018-07-29 08:10:55 +00:00
def test_set_sandbox_exception(default_conf, mocker):
"""
Test Fail scenario
"""
api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
})
url_mock = PropertyMock(return_value={'api': 'https://api.gdax.com'})
type(api_mock).urls = url_mock
with pytest.raises(OperationalException, match=r'does not provide a sandbox api'):
2019-03-05 18:46:03 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock)
default_conf['exchange']['sandbox'] = True
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
2018-08-14 18:42:13 +00:00
def test__load_async_markets(default_conf, mocker, caplog):
2020-06-17 06:33:53 +00:00
mocker.patch('freqtrade.exchange.Exchange._init_ccxt')
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
exchange = Exchange(default_conf)
2018-08-14 18:42:13 +00:00
exchange._api_async.load_markets = get_mock_coro(None)
exchange._load_async_markets()
assert exchange._api_async.load_markets.call_count == 1
caplog.set_level(logging.DEBUG)
exchange._api_async.load_markets = Mock(side_effect=ccxt.BaseError("deadbeef"))
exchange._load_async_markets()
2019-08-11 18:17:39 +00:00
assert log_has('Could not load async markets. Reason: deadbeef', caplog)
2018-08-14 18:42:13 +00:00
2018-09-11 17:46:47 +00:00
def test__load_markets(default_conf, mocker, caplog):
caplog.set_level(logging.INFO)
api_mock = MagicMock()
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError("SomeError"))
2019-03-05 18:46:03 +00:00
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
2019-03-05 18:46:03 +00:00
Exchange(default_conf)
2019-08-11 18:17:39 +00:00
assert log_has('Unable to initialize markets. Reason: SomeError', caplog)
2018-09-11 17:46:47 +00:00
expected_return = {'ETH/BTC': 'available'}
2019-03-05 18:46:03 +00:00
api_mock = MagicMock()
2018-09-11 17:46:47 +00:00
api_mock.load_markets = MagicMock(return_value=expected_return)
2019-03-05 18:46:03 +00:00
type(api_mock).markets = expected_return
2018-09-11 17:46:47 +00:00
default_conf['exchange']['pair_whitelist'] = ['ETH/BTC']
2019-10-26 08:08:23 +00:00
ex = get_patched_exchange(mocker, default_conf, api_mock, id="binance", mock_markets=False)
2018-09-11 17:46:47 +00:00
assert ex.markets == expected_return
2020-06-09 22:39:23 +00:00
def test_reload_markets(default_conf, mocker, caplog):
2019-03-10 15:36:25 +00:00
caplog.set_level(logging.DEBUG)
initial_markets = {'ETH/BTC': {}}
2019-03-12 16:54:16 +00:00
def load_markets(*args, **kwargs):
exchange._api.markets = updated_markets
2019-03-10 16:40:54 +00:00
api_mock = MagicMock()
2019-03-12 16:54:16 +00:00
api_mock.load_markets = load_markets
2019-03-10 15:36:25 +00:00
type(api_mock).markets = initial_markets
default_conf['exchange']['markets_refresh_interval'] = 10
2019-10-26 08:08:23 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance",
mock_markets=False)
exchange._load_async_markets = MagicMock()
2019-03-10 15:36:25 +00:00
exchange._last_markets_refresh = arrow.utcnow().timestamp
2019-03-10 16:40:54 +00:00
updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}}
2019-03-10 15:36:25 +00:00
assert exchange.markets == initial_markets
# less than 10 minutes have passed, no reload
2020-06-09 22:39:23 +00:00
exchange.reload_markets()
2019-03-10 15:36:25 +00:00
assert exchange.markets == initial_markets
assert exchange._load_async_markets.call_count == 0
2019-03-10 15:36:25 +00:00
# more than 10 minutes have passed, reload is executed
exchange._last_markets_refresh = arrow.utcnow().timestamp - 15 * 60
2020-06-09 22:39:23 +00:00
exchange.reload_markets()
2019-03-10 15:36:25 +00:00
assert exchange.markets == updated_markets
assert exchange._load_async_markets.call_count == 1
2019-08-11 18:17:39 +00:00
assert log_has('Performing scheduled market reload..', caplog)
2019-03-10 15:36:25 +00:00
2020-06-09 22:39:23 +00:00
def test_reload_markets_exception(default_conf, mocker, caplog):
2019-04-24 19:56:24 +00:00
caplog.set_level(logging.DEBUG)
api_mock = MagicMock()
api_mock.load_markets = MagicMock(side_effect=ccxt.NetworkError("LoadError"))
2019-04-24 19:56:24 +00:00
default_conf['exchange']['markets_refresh_interval'] = 10
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
# less than 10 minutes have passed, no reload
2020-06-09 22:39:23 +00:00
exchange.reload_markets()
2019-04-24 19:56:24 +00:00
assert exchange._last_markets_refresh == 0
2019-08-11 18:17:39 +00:00
assert log_has_re(r"Could not reload markets.*", caplog)
2019-04-24 19:56:24 +00:00
2020-01-11 11:01:34 +00:00
@pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT'])
def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog):
default_conf['stake_currency'] = stake_currency
api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'},
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
Exchange(default_conf)
2020-01-11 12:14:19 +00:00
def test_validate_stake_currency_error(default_conf, mocker, caplog):
default_conf['stake_currency'] = 'XRP'
api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'},
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
with pytest.raises(OperationalException,
match=r'XRP is not available as stake on .*'
'Available currencies are: BTC, ETH, USDT'):
Exchange(default_conf)
2020-01-11 11:01:34 +00:00
def test_get_quote_currencies(default_conf, mocker):
ex = get_patched_exchange(mocker, default_conf)
assert set(ex.get_quote_currencies()) == set(['USD', 'ETH', 'BTC', 'USDT'])
2020-01-11 11:01:34 +00:00
2020-02-25 06:01:23 +00:00
@pytest.mark.parametrize('pair,expected', [
('XRP/BTC', 'BTC'),
('LTC/USD', 'USD'),
('ETH/USDT', 'USDT'),
('XLTCUSDT', 'USDT'),
('XRP/NOCURRENCY', ''),
2020-02-25 06:01:23 +00:00
])
def test_get_pair_quote_currency(default_conf, mocker, pair, expected):
ex = get_patched_exchange(mocker, default_conf)
assert ex.get_pair_quote_currency(pair) == expected
@pytest.mark.parametrize('pair,expected', [
('XRP/BTC', 'XRP'),
('LTC/USD', 'LTC'),
('ETH/USDT', 'ETH'),
('XLTCUSDT', 'LTC'),
('XRP/NOCURRENCY', ''),
2020-02-25 06:01:23 +00:00
])
def test_get_pair_base_currency(default_conf, mocker, pair, expected):
ex = get_patched_exchange(mocker, default_conf)
assert ex.get_pair_base_currency(pair) == expected
2020-01-11 11:01:34 +00:00
2019-03-05 18:46:03 +00:00
def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly
api_mock = MagicMock()
2019-03-05 18:46:03 +00:00
type(api_mock).markets = PropertyMock(return_value={
'ETH/BTC': {'quote': 'BTC'},
'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC'},
'NEO/BTC': {'quote': 'BTC'},
})
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
2018-06-17 18:13:39 +00:00
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
2018-06-17 18:13:39 +00:00
Exchange(default_conf)
def test_validate_pairs_not_available(default_conf, mocker):
api_mock = MagicMock()
2019-03-05 18:46:03 +00:00
type(api_mock).markets = PropertyMock(return_value={
2019-08-03 11:18:37 +00:00
'XRP/BTC': {'inactive': True}
2019-03-05 18:46:03 +00:00
})
2018-06-17 18:13:39 +00:00
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
2018-06-17 18:13:39 +00:00
with pytest.raises(OperationalException, match=r'not available'):
2018-06-17 18:13:39 +00:00
Exchange(default_conf)
def test_validate_pairs_exception(default_conf, mocker, caplog):
2018-01-31 17:37:38 +00:00
caplog.set_level(logging.INFO)
api_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance'))
2018-03-30 20:52:25 +00:00
2019-03-05 18:46:03 +00:00
type(api_mock).markets = PropertyMock(return_value={})
2018-06-17 18:35:52 +00:00
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
2018-06-17 18:35:52 +00:00
2019-03-05 18:46:03 +00:00
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'):
2018-06-17 18:35:52 +00:00
Exchange(default_conf)
2019-03-05 18:46:03 +00:00
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}))
2018-06-17 18:35:52 +00:00
Exchange(default_conf)
2019-08-11 18:17:39 +00:00
assert log_has('Unable to validate pairs (assuming they are correct).', caplog)
def test_validate_pairs_restricted(default_conf, mocker, caplog):
api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC', 'info': {'IsRestricted': True}},
'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ...
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
assert log_has("Pair XRP/BTC is restricted for some users on this exchange."
"Please check if you are impacted by this restriction "
"on the exchange and eventually remove XRP/BTC from your whitelist.", caplog)
def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog):
api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'},
'HELLO-WORLD': {'quote': 'BTC'},
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
2020-02-29 13:56:04 +00:00
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, caplog):
api_mock = MagicMock()
default_conf['stake_currency'] = ''
type(api_mock).markets = PropertyMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'},
'HELLO-WORLD': {'quote': 'BTC'},
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog):
default_conf['exchange']['pair_whitelist'].append('HELLO-WORLD')
api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'},
'HELLO-WORLD': {'quote': 'USDT'},
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"):
Exchange(default_conf)
2020-01-11 10:36:28 +00:00
@pytest.mark.parametrize("timeframe", [
('5m'), ("1m"), ("15m"), ("1h")
])
def test_validate_timeframes(default_conf, mocker, timeframe):
2020-06-01 18:39:01 +00:00
default_conf["timeframe"] = timeframe
2018-07-07 12:42:53 +00:00
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
timeframes = PropertyMock(return_value={'1m': '1m',
'5m': '5m',
'15m': '15m',
'1h': '1h'})
type(api_mock).timeframes = timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
2018-09-11 17:46:47 +00:00
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
2018-07-07 12:42:53 +00:00
Exchange(default_conf)
def test_validate_timeframes_failed(default_conf, mocker):
2020-06-01 18:39:01 +00:00
default_conf["timeframe"] = "3m"
2018-07-07 12:42:53 +00:00
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
2020-01-11 10:36:28 +00:00
timeframes = PropertyMock(return_value={'15s': '15s',
'1m': '1m',
2018-07-07 12:42:53 +00:00
'5m': '5m',
'15m': '15m',
'1h': '1h'})
type(api_mock).timeframes = timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
2018-09-11 17:46:47 +00:00
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
2019-03-05 18:46:03 +00:00
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
with pytest.raises(OperationalException,
match=r"Invalid timeframe '3m'. This exchange supports.*"):
2018-07-07 12:42:53 +00:00
Exchange(default_conf)
2020-06-01 18:39:01 +00:00
default_conf["timeframe"] = "15s"
2020-01-11 10:36:28 +00:00
with pytest.raises(OperationalException,
match=r"Timeframes < 1m are currently not supported by Freqtrade."):
Exchange(default_conf)
2018-07-07 12:42:53 +00:00
2019-07-02 22:03:38 +00:00
def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
2020-06-01 18:39:01 +00:00
default_conf["timeframe"] = "3m"
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
# delete timeframes so magicmock does not autocreate it
del api_mock.timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
with pytest.raises(OperationalException,
2019-07-02 22:03:38 +00:00
match=r'The ccxt library does not provide the list of timeframes '
r'for the exchange ".*" and this exchange '
r'is therefore not supported. *'):
Exchange(default_conf)
def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
2020-06-01 18:39:01 +00:00
default_conf["timeframe"] = "3m"
2019-07-02 22:03:38 +00:00
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
# delete timeframes so magicmock does not autocreate it
del api_mock.timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets',
MagicMock(return_value={'timeframes': None}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
2019-07-02 22:03:38 +00:00
with pytest.raises(OperationalException,
match=r'The ccxt library does not provide the list of timeframes '
r'for the exchange ".*" and this exchange '
r'is therefore not supported. *'):
Exchange(default_conf)
def test_validate_timeframes_not_in_config(default_conf, mocker):
2020-06-01 18:39:01 +00:00
del default_conf["timeframe"]
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
timeframes = PropertyMock(return_value={'1m': '1m',
'5m': '5m',
'15m': '15m',
'1h': '1h'})
type(api_mock).timeframes = timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
2018-09-11 17:46:47 +00:00
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
def test_validate_order_types(default_conf, mocker):
api_mock = MagicMock()
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
Exchange(default_conf)
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
2018-11-25 18:09:11 +00:00
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'market',
2020-06-13 21:57:13 +00:00
'stoploss_on_exchange': False
2018-11-25 18:09:11 +00:00
}
with pytest.raises(OperationalException,
match=r'Exchange .* does not support market orders.'):
Exchange(default_conf)
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}
with pytest.raises(OperationalException,
match=r'On exchange stoploss is not supported for .*'):
Exchange(default_conf)
def test_validate_order_types_not_in_config(default_conf, mocker):
api_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
conf = copy.deepcopy(default_conf)
Exchange(conf)
def test_validate_required_startup_candles(default_conf, mocker, caplog):
api_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance'))
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
2019-10-27 09:56:38 +00:00
default_conf['startup_candle_count'] = 20
ex = Exchange(default_conf)
assert ex
default_conf['startup_candle_count'] = 600
with pytest.raises(OperationalException, match=r'This strategy requires 600.*'):
Exchange(default_conf)
2018-07-30 17:08:33 +00:00
def test_exchange_has(default_conf, mocker):
2018-06-28 20:11:45 +00:00
exchange = get_patched_exchange(mocker, default_conf)
assert not exchange.exchange_has('ASDFASDF')
api_mock = MagicMock()
type(api_mock).has = PropertyMock(return_value={'deadbeef': True})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert exchange.exchange_has("deadbeef")
type(api_mock).has = PropertyMock(return_value={'deadbeef': False})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert not exchange.exchange_has("deadbeef")
2019-02-22 00:48:35 +00:00
@pytest.mark.parametrize("side", [
("buy"),
("sell")
])
2019-02-24 19:08:27 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_dry_run_order(default_conf, mocker, side, exchange_name):
2019-02-22 00:48:35 +00:00
default_conf['dry_run'] = True
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
2019-02-22 00:48:35 +00:00
order = exchange.dry_run_order(
pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200)
assert 'id' in order
assert f'dry_run_{side}_' in order["id"]
assert order["side"] == side
assert order["type"] == "limit"
assert order["pair"] == "ETH/BTC"
2019-02-22 00:48:35 +00:00
@pytest.mark.parametrize("side", [
("buy"),
("sell")
])
@pytest.mark.parametrize("ordertype,rate,marketprice", [
("market", None, None),
("market", 200, True),
("limit", 200, None),
("stop_loss_limit", 200, None)
2019-02-22 00:48:35 +00:00
])
2019-02-24 19:08:27 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, exchange_name):
2019-02-22 00:48:35 +00:00
api_mock = MagicMock()
order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6))
api_mock.options = {} if not marketprice else {"createMarketBuyOrderRequiresPrice": True}
2019-02-22 00:48:35 +00:00
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2019-02-22 00:48:35 +00:00
order = exchange.create_order(
pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=200)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == ordertype
assert api_mock.create_order.call_args[0][2] == side
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is rate
def test_buy_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
2018-06-17 18:13:39 +00:00
exchange = get_patched_exchange(mocker, default_conf)
2018-12-09 15:22:21 +00:00
order = exchange.buy(pair='ETH/BTC', ordertype='limit',
amount=1, rate=200, time_in_force='gtc')
assert 'id' in order
assert 'dry_run_buy_' in order['id']
2018-04-12 16:13:35 +00:00
2019-02-24 19:08:27 +00:00
@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'
2018-12-09 15:22:21 +00:00
time_in_force = 'gtc'
api_mock.options = {}
2018-11-15 05:58:24 +00:00
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-12-09 15:22:21 +00:00
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
api_mock.create_order.reset_mock()
order_type = 'limit'
2018-12-09 15:22:21 +00:00
order = exchange.buy(
pair='ETH/BTC',
ordertype=order_type,
amount=1,
rate=200,
time_in_force=time_in_force)
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-12-09 15:22:21 +00:00
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("Order not found"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2019-08-18 13:48:20 +00:00
exchange.buy(pair='ETH/BTC', ordertype='limit',
amount=1, rate=200, time_in_force=time_in_force)
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.buy(pair='ETH/BTC', ordertype='market',
2018-12-09 15:22:21 +00:00
amount=1, rate=200, time_in_force=time_in_force)
2018-04-21 20:37:27 +00:00
with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-12-09 15:22:21 +00:00
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("Unknown error"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-12-09 15:22:21 +00:00
exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
2019-03-11 19:30:16 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
2019-03-11 19:30:16 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order_type = 'limit'
time_in_force = 'ioc'
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
2019-03-11 19:30:16 +00:00
assert "timeInForce" in api_mock.create_order.call_args[0][5]
assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force
order_type = 'market'
time_in_force = 'ioc'
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
2019-03-11 19:30:16 +00:00
# Market orders should not send timeInForce!!
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
def test_sell_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
2018-06-17 18:13:39 +00:00
exchange = get_patched_exchange(mocker, default_conf)
order = exchange.sell(pair='ETH/BTC', ordertype='limit', amount=1, rate=200)
assert 'id' in order
assert 'dry_run_sell_' in order['id']
2019-02-24 19:08:27 +00:00
@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'
api_mock.options = {}
2018-11-15 05:58:24 +00:00
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 18:13:39 +00:00
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
2019-02-19 20:56:20 +00:00
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
api_mock.create_order.reset_mock()
order_type = 'limit'
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
2019-02-24 19:08:27 +00:00
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("Order not found"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2019-08-18 13:45:30 +00:00
exchange.sell(pair='ETH/BTC', ordertype='limit', amount=1, rate=200)
# Market orders don't require price, so the behaviour is slightly different
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.sell(pair='ETH/BTC', ordertype='market', amount=1, rate=200)
2018-04-21 20:37:27 +00:00
with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection"))
2019-02-24 19:08:27 +00:00
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("DeadBeef"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
2019-03-11 19:30:16 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
api_mock.options = {}
2019-03-11 19:30:16 +00:00
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
2019-03-11 19:30:16 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order_type = 'limit'
time_in_force = 'ioc'
order = exchange.sell(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
assert "timeInForce" in api_mock.create_order.call_args[0][5]
assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force
order_type = 'market'
time_in_force = 'ioc'
order = exchange.sell(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
# Market orders should not send timeInForce!!
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
def test_get_balance_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
default_conf['dry_run_wallet'] = 999.9
2018-06-17 18:13:39 +00:00
exchange = get_patched_exchange(mocker, default_conf)
assert exchange.get_balance(currency='BTC') == 999.9
2019-02-24 19:08:27 +00:00
@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, 'total': 123.4}})
default_conf['dry_run'] = False
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 18:13:39 +00:00
assert exchange.get_balance(currency='BTC') == 123.4
with pytest.raises(OperationalException):
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 18:13:39 +00:00
exchange.get_balance(currency='BTC')
2018-06-28 19:56:37 +00:00
with pytest.raises(TemporaryError, match=r'.*balance due to malformed exchange response:.*'):
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-28 19:51:59 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_balances', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Kraken.get_balances', MagicMock(return_value={}))
2018-06-28 19:51:59 +00:00
exchange.get_balance(currency='BTC')
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_balances_dry_run(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
2018-06-17 18:13:39 +00:00
assert exchange.get_balances() == {}
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_balances_prod(default_conf, mocker, exchange_name):
balance_item = {
'free': 10.0,
'total': 10.0,
'used': 0.0
}
api_mock = MagicMock()
api_mock.fetch_balance = MagicMock(return_value={
'1ST': balance_item,
'2ST': balance_item,
'3ST': balance_item
})
default_conf['dry_run'] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 18:13:39 +00:00
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, exchange_name,
2018-06-28 20:18:38 +00:00
"get_balances", "fetch_balance")
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_tickers(default_conf, mocker, exchange_name):
2018-06-17 21:30:48 +00:00
api_mock = MagicMock()
tick = {'ETH/BTC': {
2019-10-17 20:40:29 +00:00
'symbol': 'ETH/BTC',
'bid': 0.5,
'ask': 1,
'last': 42,
}, 'BCH/BTC': {
'symbol': 'BCH/BTC',
'bid': 0.6,
'ask': 0.5,
'last': 41,
}
}
2018-06-17 21:30:48 +00:00
api_mock.fetch_tickers = MagicMock(return_value=tick)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 21:30:48 +00:00
# retrieve original ticker
tickers = exchange.get_tickers()
assert 'ETH/BTC' in tickers
assert 'BCH/BTC' in tickers
assert tickers['ETH/BTC']['bid'] == 0.5
assert tickers['ETH/BTC']['ask'] == 1
assert tickers['BCH/BTC']['bid'] == 0.6
assert tickers['BCH/BTC']['ask'] == 0.5
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2018-06-28 20:18:38 +00:00
"get_tickers", "fetch_tickers")
2018-06-17 21:30:48 +00:00
with pytest.raises(OperationalException):
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_tickers()
2018-06-17 21:30:48 +00:00
api_mock.fetch_tickers = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 21:30:48 +00:00
exchange.get_tickers()
@pytest.mark.parametrize("exchange_name", EXCHANGES)
2019-12-18 15:34:30 +00:00
def test_fetch_ticker(default_conf, mocker, exchange_name):
api_mock = MagicMock()
tick = {
'symbol': 'ETH/BTC',
'bid': 0.00001098,
'ask': 0.00001099,
'last': 0.0001,
}
api_mock.fetch_ticker = MagicMock(return_value=tick)
2019-07-03 18:07:26 +00:00
api_mock.markets = {'ETH/BTC': {'active': True}}
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-01-07 20:26:43 +00:00
# retrieve original ticker
2019-12-18 15:34:30 +00:00
ticker = exchange.fetch_ticker(pair='ETH/BTC')
assert ticker['bid'] == 0.00001098
assert ticker['ask'] == 0.00001099
2018-01-03 16:58:08 +00:00
2018-01-07 20:24:17 +00:00
# change the ticker
tick = {
'symbol': 'ETH/BTC',
'bid': 0.5,
'ask': 1,
'last': 42,
}
api_mock.fetch_ticker = MagicMock(return_value=tick)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-01-07 20:24:17 +00:00
2018-01-03 16:58:08 +00:00
# if not caching the result we should get the same ticker
2018-01-10 06:41:37 +00:00
# if not fetching a new result we should get the cached ticker
2019-12-18 15:34:30 +00:00
ticker = exchange.fetch_ticker(pair='ETH/BTC')
2018-01-03 16:58:08 +00:00
2018-06-06 18:24:47 +00:00
assert api_mock.fetch_ticker.call_count == 1
2018-01-07 20:24:17 +00:00
assert ticker['bid'] == 0.5
2018-01-03 16:58:08 +00:00
assert ticker['ask'] == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2019-12-18 15:34:30 +00:00
"fetch_ticker", "fetch_ticker",
2020-02-22 10:03:25 +00:00
pair='ETH/BTC')
2018-06-06 19:24:57 +00:00
api_mock.fetch_ticker = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2020-02-22 10:03:25 +00:00
exchange.fetch_ticker(pair='ETH/BTC')
2018-06-06 19:24:57 +00:00
with pytest.raises(DependencyException, match=r'Pair XRP/ETH not available'):
2020-02-22 10:03:25 +00:00
exchange.fetch_ticker(pair='XRP/ETH')
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
ohlcv = [
2018-08-14 18:53:58 +00:00
[
arrow.utcnow().timestamp * 1000, # unix timestamp ms
2018-08-14 18:53:58 +00:00
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
pair = 'ETH/BTC'
async def mock_candle_hist(pair, timeframe, since_ms):
return pair, timeframe, ohlcv
2018-08-14 18:53:58 +00:00
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
2018-08-14 18:53:58 +00:00
# one_call calculation * 1.8 should do 2 calls
2020-03-25 14:50:33 +00:00
since = 5 * 60 * exchange._ft_has['ohlcv_candle_limit'] * 1.8
ret = exchange.get_historic_ohlcv(pair, "5m", int((arrow.utcnow().timestamp - since) * 1000))
2018-08-14 18:53:58 +00:00
assert exchange._async_get_candle_history.call_count == 2
# Returns twice the above OHLCV data
2018-08-14 18:53:58 +00:00
assert len(ret) == 2
def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
ohlcv = [
[
2018-12-11 18:48:36 +00:00
(arrow.utcnow().timestamp - 1) * 1000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
2018-12-11 18:48:36 +00:00
],
[
arrow.utcnow().timestamp * 1000, # unix timestamp ms
3, # open
1, # high
4, # low
6, # close
5, # volume (in quote currency)
]
]
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf)
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
2018-12-30 06:15:49 +00:00
pairs = [('IOTA/ETH', '5m'), ('XRP/ETH', '5m')]
# empty dicts
2018-12-11 18:48:36 +00:00
assert not exchange._klines
2019-01-22 06:38:15 +00:00
exchange.refresh_latest_ohlcv(pairs)
assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog)
2018-12-11 18:48:36 +00:00
assert exchange._klines
assert exchange._api_async.fetch_ohlcv.call_count == 2
for pair in pairs:
2018-12-11 18:48:36 +00:00
assert isinstance(exchange.klines(pair), DataFrame)
assert len(exchange.klines(pair)) > 0
2018-12-22 18:03:42 +00:00
# klines function should return a different object on each call
# if copy is "True"
assert exchange.klines(pair) is not exchange.klines(pair)
assert exchange.klines(pair) is not exchange.klines(pair, copy=True)
assert exchange.klines(pair, copy=True) is not exchange.klines(pair, copy=True)
assert exchange.klines(pair, copy=False) is exchange.klines(pair, copy=False)
# test caching
2018-12-30 06:15:49 +00:00
exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, "
f"timeframe {pairs[0][1]} ...",
2019-08-11 18:17:39 +00:00
caplog)
2018-08-01 19:19:49 +00:00
@pytest.mark.asyncio
2019-02-24 19:08:27 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name):
ohlcv = [
2018-08-01 19:19:49 +00:00
[
arrow.utcnow().timestamp * 1000, # unix timestamp ms
2018-08-01 19:19:49 +00:00
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
caplog.set_level(logging.DEBUG)
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
2018-08-01 19:19:49 +00:00
# Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
2018-08-01 19:19:49 +00:00
pair = 'ETH/BTC'
res = await exchange._async_get_candle_history(pair, "5m")
2018-08-01 19:19:49 +00:00
assert type(res) is tuple
assert len(res) == 3
2018-08-01 19:19:49 +00:00
assert res[0] == pair
assert res[1] == "5m"
assert res[2] == ohlcv
assert exchange._api_async.fetch_ohlcv.call_count == 1
assert not log_has(f"Using cached candle (OHLCV) data for {pair} ...", caplog)
# exchange = Exchange(default_conf)
2018-08-01 19:19:49 +00:00
await async_ccxt_exception(mocker, default_conf, MagicMock(),
"_async_get_candle_history", "fetch_ohlcv",
pair='ABCD/BTC', timeframe=default_conf['timeframe'])
2018-08-14 18:35:12 +00:00
api_mock = MagicMock()
with pytest.raises(OperationalException,
match=r'Could not fetch historical candle \(OHLCV\) data.*'):
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
2019-02-24 19:08:27 +00:00
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)
2018-08-01 19:19:49 +00:00
2019-08-29 11:13:41 +00:00
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
r'historical candle \(OHLCV\) data\..*'):
2019-08-29 11:13:41 +00:00
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
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)
2018-08-01 19:19:49 +00:00
2018-09-02 17:15:23 +00:00
@pytest.mark.asyncio
async def test__async_get_candle_history_empty(default_conf, mocker, caplog):
""" Test empty exchange result """
ohlcv = []
2018-09-02 17:15:23 +00:00
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf)
# Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro([])
exchange = Exchange(default_conf)
pair = 'ETH/BTC'
res = await exchange._async_get_candle_history(pair, "5m")
assert type(res) is tuple
assert len(res) == 3
2018-09-02 17:15:23 +00:00
assert res[0] == pair
assert res[1] == "5m"
assert res[2] == ohlcv
2018-09-02 17:15:23 +00:00
assert exchange._api_async.fetch_ohlcv.call_count == 1
2019-01-22 06:38:15 +00:00
def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog):
async def mock_get_candle_hist(pair, *args, **kwargs):
if pair == 'ETH/BTC':
return [[]]
else:
raise TypeError()
exchange = get_patched_exchange(mocker, default_conf)
# Monkey-patch async function with empty result
exchange._api_async.fetch_ohlcv = MagicMock(side_effect=mock_get_candle_hist)
2019-01-22 06:38:15 +00:00
pairs = [("ETH/BTC", "5m"), ("XRP/BTC", "5m")]
res = exchange.refresh_latest_ohlcv(pairs)
assert exchange._klines
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert type(res) is list
assert len(res) == 2
2019-01-22 18:17:21 +00:00
# Test that each is in list at least once as order is not guaranteed
assert type(res[0]) is tuple or type(res[1]) is tuple
assert type(res[0]) is TypeError or type(res[1]) is TypeError
2019-08-11 18:17:39 +00:00
assert log_has("Error loading ETH/BTC. Result was [[]].", caplog)
assert log_has("Async code raised an exception: TypeError", caplog)
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_l2_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, id=exchange_name)
order_book = exchange.fetch_l2_order_book(pair='ETH/BTC', limit=10)
2018-08-05 04:41:06 +00:00
assert 'bids' in order_book
assert 'asks' in order_book
assert len(order_book['bids']) == 10
assert len(order_book['asks']) == 10
2018-08-05 04:41:06 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
2018-08-05 14:41:58 +00:00
api_mock = MagicMock()
with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.fetch_l2_order_book(pair='ETH/BTC', limit=50)
2018-08-05 14:41:58 +00:00
with pytest.raises(TemporaryError):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.fetch_l2_order_book(pair='ETH/BTC', limit=50)
2018-08-05 14:41:58 +00:00
with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.fetch_l2_order_book(pair='ETH/BTC', limit=50)
2018-08-05 14:41:58 +00:00
def make_fetch_ohlcv_mock(data):
def fetch_ohlcv_mock(pair, timeframe, since):
if since:
assert since > data[-1][0]
return []
return 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, exchange_name):
2018-11-25 14:00:50 +00:00
def sort_data(data, key):
return sorted(data, key=key)
# GDAX use-case (real data from GDAX)
# This OHLCV data is ordered DESC (newest first, oldest last)
ohlcv = [
[1527833100000, 0.07666, 0.07671, 0.07666, 0.07668, 16.65244264],
[1527832800000, 0.07662, 0.07666, 0.07662, 0.07666, 1.30051526],
[1527832500000, 0.07656, 0.07661, 0.07656, 0.07661, 12.034778840000001],
[1527832200000, 0.07658, 0.07658, 0.07655, 0.07656, 0.59780186],
[1527831900000, 0.07658, 0.07658, 0.07658, 0.07658, 1.76278136],
[1527831600000, 0.07658, 0.07658, 0.07658, 0.07658, 2.22646521],
[1527831300000, 0.07655, 0.07657, 0.07655, 0.07657, 1.1753],
[1527831000000, 0.07654, 0.07654, 0.07651, 0.07651, 0.8073060299999999],
[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, id=exchange_name)
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
2019-02-21 05:59:52 +00:00
sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data))
# Test the OHLCV data sort
res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe'])
assert res[0] == 'ETH/BTC'
res_ohlcv = res[2]
2018-11-25 14:00:50 +00:00
assert sort_mock.call_count == 1
assert res_ohlcv[0][0] == 1527830400000
assert res_ohlcv[0][1] == 0.07649
assert res_ohlcv[0][2] == 0.07651
assert res_ohlcv[0][3] == 0.07649
assert res_ohlcv[0][4] == 0.07651
assert res_ohlcv[0][5] == 2.5734867
assert res_ohlcv[9][0] == 1527833100000
assert res_ohlcv[9][1] == 0.07666
assert res_ohlcv[9][2] == 0.07671
assert res_ohlcv[9][3] == 0.07666
assert res_ohlcv[9][4] == 0.07668
assert res_ohlcv[9][5] == 16.65244264
# Bittrex use-case (real data from Bittrex)
# This OHLCV data is ordered ASC (oldest first, newest last)
ohlcv = [
[1527827700000, 0.07659999, 0.0766, 0.07627, 0.07657998, 1.85216924],
[1527828000000, 0.07657995, 0.07657995, 0.0763, 0.0763, 26.04051037],
[1527828300000, 0.0763, 0.07659998, 0.0763, 0.0764, 10.36434124],
[1527828600000, 0.0764, 0.0766, 0.0764, 0.0766, 5.71044773],
[1527828900000, 0.0764, 0.07666998, 0.0764, 0.07666998, 47.48888565],
[1527829200000, 0.0765, 0.07672999, 0.0765, 0.07672999, 3.37640326],
[1527829500000, 0.0766, 0.07675, 0.0765, 0.07675, 8.36203831],
[1527829800000, 0.07675, 0.07677999, 0.07620002, 0.076695, 119.22963884],
[1527830100000, 0.076695, 0.07671, 0.07624171, 0.07671, 1.80689244],
[1527830400000, 0.07671, 0.07674399, 0.07629216, 0.07655213, 2.31452783]
]
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
2018-11-25 14:00:50 +00:00
# Reset sort mock
sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data))
# Test the OHLCV data sort
res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe'])
assert res[0] == 'ETH/BTC'
assert res[1] == default_conf['timeframe']
res_ohlcv = res[2]
2018-11-25 14:00:50 +00:00
# Sorted not called again - data is already in order
assert sort_mock.call_count == 0
assert res_ohlcv[0][0] == 1527827700000
assert res_ohlcv[0][1] == 0.07659999
assert res_ohlcv[0][2] == 0.0766
assert res_ohlcv[0][3] == 0.07627
assert res_ohlcv[0][4] == 0.07657998
assert res_ohlcv[0][5] == 1.85216924
assert res_ohlcv[9][0] == 1527830400000
assert res_ohlcv[9][1] == 0.07671
assert res_ohlcv[9][2] == 0.07674399
assert res_ohlcv[9][3] == 0.07629216
assert res_ohlcv[9][4] == 0.07655213
assert res_ohlcv[9][5] == 2.31452783
2019-08-29 14:33:56 +00:00
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
fetch_trades_result):
2019-08-29 14:33:56 +00:00
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
# Monkey-patch async function
exchange._api_async.fetch_trades = get_mock_coro(fetch_trades_result)
2019-08-29 14:33:56 +00:00
pair = 'ETH/BTC'
res = await exchange._async_fetch_trades(pair, since=None, params=None)
assert type(res) is list
assert isinstance(res[0], list)
assert isinstance(res[1], list)
2019-08-29 14:33:56 +00:00
assert exchange._api_async.fetch_trades.call_count == 1
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
assert log_has_re(f"Fetching trades for pair {pair}, since .*", caplog)
caplog.clear()
exchange._api_async.fetch_trades.reset_mock()
res = await exchange._async_fetch_trades(pair, since=None, params={'from': '123'})
assert exchange._api_async.fetch_trades.call_count == 1
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
assert exchange._api_async.fetch_trades.call_args[1]['params'] == {'from': '123'}
assert log_has_re(f"Fetching trades for pair {pair}, params: .*", caplog)
exchange = Exchange(default_conf)
await async_ccxt_exception(mocker, default_conf, MagicMock(),
"_async_fetch_trades", "fetch_trades",
pair='ABCD/BTC', since=None)
api_mock = MagicMock()
with pytest.raises(OperationalException, match=r'Could not fetch trade data*'):
api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().timestamp - 2000) * 1000)
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
r'historical trade data\..*'):
api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().timestamp - 2000) * 1000)
2019-09-28 09:17:02 +00:00
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_id(default_conf, mocker, caplog, exchange_name,
trades_history):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
2019-09-28 11:35:25 +00:00
pagination_arg = exchange._trades_pagination_arg
async def mock_get_trade_hist(pair, *args, **kwargs):
if 'since' in kwargs:
# Return first 3
return trades_history[:-2]
elif kwargs.get('params', {}).get(pagination_arg) == trades_history[-3][1]:
2019-09-28 11:35:25 +00:00
# Return 2
return trades_history[-3:-1]
else:
# Return last 2
return trades_history[-2:]
2019-09-28 09:17:02 +00:00
# Monkey-patch async function
2019-09-28 11:35:25 +00:00
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
2019-09-28 09:17:02 +00:00
pair = 'ETH/BTC'
ret = await exchange._async_get_trade_history_id(pair, since=trades_history[0][0],
until=trades_history[-1][0]-1)
2019-09-28 11:35:25 +00:00
assert type(ret) is tuple
assert ret[0] == pair
assert type(ret[1]) is list
assert len(ret[1]) == len(trades_history)
assert exchange._async_fetch_trades.call_count == 3
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
# first call (using since, not fromId)
assert fetch_trades_cal[0][0][0] == pair
assert fetch_trades_cal[0][1]['since'] == trades_history[0][0]
2019-09-28 11:35:25 +00:00
# 2nd call
assert fetch_trades_cal[1][0][0] == pair
assert 'params' in fetch_trades_cal[1][1]
assert exchange._ft_has['trades_pagination_arg'] in fetch_trades_cal[1][1]['params']
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_time(default_conf, mocker, caplog, exchange_name,
trades_history):
caplog.set_level(logging.DEBUG)
async def mock_get_trade_hist(pair, *args, **kwargs):
if kwargs['since'] == trades_history[0][0]:
2019-09-28 11:35:25 +00:00
return trades_history[:-1]
else:
return trades_history[-1:]
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
# Monkey-patch async function
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
pair = 'ETH/BTC'
ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0][0],
until=trades_history[-1][0]-1)
2019-09-28 09:17:02 +00:00
assert type(ret) is tuple
assert ret[0] == pair
assert type(ret[1]) is list
2019-09-28 11:35:25 +00:00
assert len(ret[1]) == len(trades_history)
2019-09-28 09:17:02 +00:00
assert exchange._async_fetch_trades.call_count == 2
2019-09-28 11:35:25 +00:00
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
2019-09-28 09:17:02 +00:00
# first call (using since, not fromId)
2019-09-28 11:35:25 +00:00
assert fetch_trades_cal[0][0][0] == pair
assert fetch_trades_cal[0][1]['since'] == trades_history[0][0]
2019-09-28 09:17:02 +00:00
# 2nd call
2019-09-28 11:35:25 +00:00
assert fetch_trades_cal[1][0][0] == pair
assert fetch_trades_cal[0][1]['since'] == trades_history[0][0]
2019-09-28 11:35:25 +00:00
assert log_has_re(r"Stopping because until was reached.*", caplog)
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog, exchange_name,
trades_history):
caplog.set_level(logging.DEBUG)
async def mock_get_trade_hist(pair, *args, **kwargs):
if kwargs['since'] == trades_history[0][0]:
2019-09-28 11:35:25 +00:00
return trades_history[:-1]
else:
return []
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
# Monkey-patch async function
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
pair = 'ETH/BTC'
ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0][0],
until=trades_history[-1][0]-1)
2019-09-28 11:35:25 +00:00
assert type(ret) is tuple
assert ret[0] == pair
assert type(ret[1]) is list
assert len(ret[1]) == len(trades_history) - 1
assert exchange._async_fetch_trades.call_count == 2
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
# first call (using since, not fromId)
assert fetch_trades_cal[0][0][0] == pair
assert fetch_trades_cal[0][1]['since'] == trades_history[0][0]
2019-09-28 09:17:02 +00:00
2019-08-30 04:51:21 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades_history):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
pair = 'ETH/BTC'
exchange._async_get_trade_history_id = get_mock_coro((pair, trades_history))
exchange._async_get_trade_history_time = get_mock_coro((pair, trades_history))
ret = exchange.get_historic_trades(pair, since=trades_history[0][0],
until=trades_history[-1][0])
2019-08-30 04:51:21 +00:00
# Depending on the exchange, one or the other method should be called
assert sum([exchange._async_get_trade_history_id.call_count,
exchange._async_get_trade_history_time.call_count]) == 1
assert len(ret) == 2
assert ret[0] == pair
2019-09-28 11:35:25 +00:00
assert len(ret[1]) == len(trades_history)
2019-08-30 04:51:21 +00:00
2019-09-28 08:45:16 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange_name,
trades_history):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=False)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
pair = 'ETH/BTC'
with pytest.raises(OperationalException,
match="This exchange does not suport downloading Trades."):
exchange.get_historic_trades(pair, since=trades_history[0][0],
until=trades_history[-1][0])
2019-09-28 08:45:16 +00:00
@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, id=exchange_name)
2020-04-18 04:55:25 +00:00
assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {}
2020-03-25 16:01:45 +00:00
assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {}
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@pytest.mark.parametrize("order,result", [
({'status': 'closed', 'filled': 10}, False),
({'status': 'closed', 'filled': 0.0}, True),
({'status': 'canceled', 'filled': 0.0}, True),
({'status': 'canceled', 'filled': 10.0}, False),
({'status': 'unknown', 'filled': 10.0}, False),
({'result': 'testest123'}, False),
])
def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order, result):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert exchange.check_order_canceled_empty(order) == result
2020-04-17 05:18:46 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@pytest.mark.parametrize("order,result", [
({'status': 'closed', 'amount': 10, 'fee': {}}, True),
({'status': 'closed', 'amount': 0.0, 'fee': {}}, True),
({'status': 'canceled', 'amount': 0.0, 'fee': {}}, True),
({'status': 'canceled', 'amount': 10.0}, False),
({'amount': 10.0, 'fee': {}}, False),
({'result': 'testest123'}, False),
('hello_world', False),
])
def test_is_cancel_order_result_suitable(mocker, default_conf, exchange_name, order, result):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert exchange.is_cancel_order_result_suitable(order) == result
2020-04-17 15:53:18 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@pytest.mark.parametrize("corder,call_corder,call_forder", [
({'status': 'closed', 'amount': 10, 'fee': {}}, 1, 0),
({'amount': 10, 'fee': {}}, 1, 1),
])
def test_cancel_order_with_result(default_conf, mocker, exchange_name, corder,
call_corder, call_forder):
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.cancel_order = MagicMock(return_value=corder)
api_mock.fetch_order = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
res = exchange.cancel_order_with_result('1234', 'ETH/BTC', 1234)
assert isinstance(res, dict)
assert api_mock.cancel_order.call_count == call_corder
assert api_mock.fetch_order.call_count == call_forder
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_order_with_result_error(default_conf, mocker, exchange_name, caplog):
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
res = exchange.cancel_order_with_result('1234', 'ETH/BTC', 1541)
assert isinstance(res, dict)
2020-08-01 13:59:50 +00:00
assert log_has("Could not cancel order 1234 for ETH/BTC.", caplog)
2020-04-17 15:53:18 +00:00
assert log_has("Could not fetch cancelled order 1234.", caplog)
assert res['amount'] == 1541
2018-01-10 06:41:37 +00:00
# Ensure that if not dry_run, we should call API
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_order(default_conf, mocker, exchange_name):
2018-01-10 06:41:37 +00:00
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, id=exchange_name)
2018-06-17 18:13:39 +00:00
assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123
2019-04-02 16:45:18 +00:00
with pytest.raises(InvalidOrderException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 18:13:39 +00:00
exchange.cancel_order(order_id='_', pair='TKN/BTC')
2019-04-02 16:45:18 +00:00
assert api_mock.cancel_order.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2018-06-28 20:18:38 +00:00
"cancel_order", "cancel_order",
order_id='_', pair='TKN/BTC')
2018-01-10 06:41:37 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
2020-03-25 16:01:45 +00:00
def test_cancel_stoploss_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, id=exchange_name)
assert exchange.cancel_stoploss_order(order_id='_', pair='TKN/BTC') == 123
with pytest.raises(InvalidOrderException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.cancel_stoploss_order(order_id='_', pair='TKN/BTC')
assert api_mock.cancel_order.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
"cancel_stoploss_order", "cancel_order",
order_id='_', pair='TKN/BTC')
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_order(default_conf, mocker, exchange_name):
2018-01-10 06:41:37 +00:00
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
2018-06-18 20:09:46 +00:00
exchange._dry_run_open_orders['X'] = order
assert exchange.fetch_order('X', 'TKN/BTC').myid == 123
2018-01-10 06:41:37 +00:00
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
exchange.fetch_order('Y', 'TKN/BTC')
2018-01-10 06:41:37 +00:00
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, id=exchange_name)
assert exchange.fetch_order('X', 'TKN/BTC') == 456
with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.fetch_order(order_id='_', pair='TKN/BTC')
assert api_mock.fetch_order.call_count == 1
2020-06-28 17:45:42 +00:00
api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
with patch('freqtrade.exchange.common.time.sleep') as tm:
with pytest.raises(InvalidOrderException):
exchange.fetch_order(order_id='_', pair='TKN/BTC')
# Ensure backoff is called
assert tm.call_args_list[0][0][0] == 1
assert tm.call_args_list[1][0][0] == 2
assert tm.call_args_list[2][0][0] == 5
assert tm.call_args_list[3][0][0] == 10
assert api_mock.fetch_order.call_count == 6
2020-06-28 17:45:42 +00:00
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'fetch_order', 'fetch_order', retries=6,
2018-06-28 20:18:38 +00:00
order_id='_', pair='TKN/BTC')
2018-01-10 06:41:37 +00:00
2020-03-25 16:01:45 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
2020-03-25 16:01:45 +00:00
# Don't test FTX here - that needs a seperate test
if exchange_name == 'ftx':
return
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
exchange._dry_run_open_orders['X'] = order
assert exchange.fetch_stoploss_order('X', 'TKN/BTC').myid == 123
2020-03-25 16:01:45 +00:00
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
exchange.fetch_stoploss_order('Y', 'TKN/BTC')
2020-03-25 16:01:45 +00:00
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, id=exchange_name)
assert exchange.fetch_stoploss_order('X', 'TKN/BTC') == 456
2020-03-25 16:01:45 +00:00
with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.fetch_stoploss_order(order_id='_', pair='TKN/BTC')
2020-03-25 16:01:45 +00:00
assert api_mock.fetch_order.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'fetch_stoploss_order', 'fetch_order',
retries=6,
2018-06-28 20:18:38 +00:00
order_id='_', pair='TKN/BTC')
2018-01-10 06:41:37 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
2019-09-10 21:18:07 +00:00
def test_name(default_conf, mocker, exchange_name):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert exchange.name == exchange_name.title()
assert exchange.id == exchange_name
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_trades_for_order(default_conf, mocker, exchange_name):
2019-11-08 06:32:08 +00:00
2018-06-17 21:22:28 +00:00
order_id = 'ABCD-ABCD'
since = datetime(2018, 5, 5, 0, 0, 0)
2018-06-17 21:22:28 +00:00
default_conf["dry_run"] = False
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
api_mock = MagicMock()
api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV',
'order': 'ABCD-ABCD',
'info': {'pair': 'XLTCZBTC',
'time': 1519860024.4388,
'type': 'buy',
'ordertype': 'limit',
'price': '20.00000',
'cost': '38.62000',
'fee': '0.06179',
'vol': '5',
'id': 'ABCD-ABCD'},
'timestamp': 1519860024438,
'datetime': '2018-02-28T23:20:24.438Z',
'symbol': 'LTC/BTC',
'type': 'limit',
'side': 'buy',
'price': 165.0,
'amount': 0.2340606,
'fee': {'cost': 0.06179, 'currency': 'BTC'}
}])
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 21:22:28 +00:00
orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since)
assert len(orders) == 1
assert orders[0]['price'] == 165
assert api_mock.fetch_my_trades.call_count == 1
# since argument should be
assert isinstance(api_mock.fetch_my_trades.call_args[0][1], int)
assert api_mock.fetch_my_trades.call_args[0][0] == 'LTC/BTC'
# Same test twice, hardcoded number and doing the same calculation
2019-08-06 18:23:32 +00:00
assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000
assert api_mock.fetch_my_trades.call_args[0][1] == int(since.replace(
tzinfo=timezone.utc).timestamp() - 5) * 1000
2018-06-17 21:22:28 +00:00
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2018-06-28 20:18:38 +00:00
'get_trades_for_order', 'fetch_my_trades',
order_id=order_id, pair='LTC/BTC', since=since)
2018-06-17 21:22:28 +00:00
2018-06-28 19:51:59 +00:00
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False))
assert exchange.get_trades_for_order(order_id, 'LTC/BTC', since) == []
2018-06-17 21:22:28 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_fee(default_conf, mocker, exchange_name):
2018-01-10 06:41:37 +00:00
api_mock = MagicMock()
api_mock.calculate_fee = MagicMock(return_value={
'type': 'taker',
'currency': 'BTC',
'rate': 0.025,
'cost': 0.05
})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 18:13:39 +00:00
2019-12-14 12:22:42 +00:00
assert exchange.get_fee('ETH/BTC') == 0.025
2018-04-16 17:43:13 +00:00
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2019-12-14 12:22:42 +00:00
'get_fee', 'calculate_fee', symbol="ETH/BTC")
2018-11-22 16:41:01 +00:00
2018-11-22 18:38:20 +00:00
def test_stoploss_order_unsupported_exchange(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, 'bittrex')
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
2020-01-19 12:30:56 +00:00
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
2018-11-30 19:13:50 +00:00
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
exchange.stoploss_adjust(1, {})
2018-11-24 17:08:11 +00:00
2019-06-09 12:05:36 +00:00
def test_merge_ft_has_dict(default_conf, mocker):
2019-07-03 18:20:12 +00:00
mocker.patch.multiple('freqtrade.exchange.Exchange',
_init_ccxt=MagicMock(return_value=MagicMock()),
_load_async_markets=MagicMock(),
validate_pairs=MagicMock(),
validate_timeframes=MagicMock(),
validate_stakecurrency=MagicMock()
)
2019-06-09 12:05:36 +00:00
ex = Exchange(default_conf)
assert ex._ft_has == Exchange._ft_has_default
ex = Kraken(default_conf)
2019-08-15 14:49:54 +00:00
assert ex._ft_has != Exchange._ft_has_default
assert ex._ft_has['trades_pagination'] == 'id'
assert ex._ft_has['trades_pagination_arg'] == 'since'
2019-06-09 12:05:36 +00:00
# Binance defines different values
ex = Binance(default_conf)
assert ex._ft_has != Exchange._ft_has_default
assert ex._ft_has['stoploss_on_exchange']
assert ex._ft_has['order_time_in_force'] == ['gtc', 'fok', 'ioc']
2019-08-15 14:49:54 +00:00
assert ex._ft_has['trades_pagination'] == 'id'
assert ex._ft_has['trades_pagination_arg'] == 'fromId'
2019-06-09 12:05:36 +00:00
conf = copy.deepcopy(default_conf)
conf['exchange']['_ft_has_params'] = {"DeadBeef": 20,
"stoploss_on_exchange": False}
# Use settings from configuration (overriding stoploss_on_exchange)
ex = Binance(conf)
assert ex._ft_has != Exchange._ft_has_default
assert not ex._ft_has['stoploss_on_exchange']
assert ex._ft_has['DeadBeef'] == 20
2019-07-03 18:20:12 +00:00
def test_get_valid_pair_combination(default_conf, mocker, markets):
mocker.patch.multiple('freqtrade.exchange.Exchange',
_init_ccxt=MagicMock(return_value=MagicMock()),
_load_async_markets=MagicMock(),
validate_pairs=MagicMock(),
validate_timeframes=MagicMock(),
markets=PropertyMock(return_value=markets))
ex = Exchange(default_conf)
assert ex.get_valid_pair_combination("ETH", "BTC") == "ETH/BTC"
assert ex.get_valid_pair_combination("BTC", "ETH") == "ETH/BTC"
with pytest.raises(DependencyException, match=r"Could not combine.* to get a valid pair."):
ex.get_valid_pair_combination("NOPAIR", "ETH")
2019-08-12 13:43:10 +00:00
2019-10-17 20:40:29 +00:00
@pytest.mark.parametrize(
"base_currencies, quote_currencies, pairs_only, active_only, expected_keys", [
# Testing markets (in conftest.py):
# 'BLK/BTC': 'active': True
# 'BTT/BTC': 'active': True
# 'ETH/BTC': 'active': True
# 'ETH/USDT': 'active': True
# 'LTC/BTC': 'active': False
# 'LTC/ETH': 'active': True
2019-10-17 20:40:29 +00:00
# 'LTC/USD': 'active': True
# 'LTC/USDT': 'active': True
# 'NEO/BTC': 'active': False
# 'TKN/BTC': 'active' not set
# 'XLTCUSDT': 'active': True, not a pair
# 'XRP/BTC': 'active': False
# all markets
([], [], False, False,
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD',
2019-10-17 20:40:29 +00:00
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']),
# active markets
([], [], False, True,
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC',
2019-10-26 08:08:23 +00:00
'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']),
2019-10-17 20:40:29 +00:00
# all pairs
([], [], True, False,
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD',
2019-10-17 20:40:29 +00:00
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']),
# active pairs
([], [], True, True,
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC',
2019-10-26 08:08:23 +00:00
'TKN/BTC', 'XRP/BTC']),
2019-10-17 20:40:29 +00:00
# all markets, base=ETH, LTC
(['ETH', 'LTC'], [], False, False,
['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
2019-10-17 20:40:29 +00:00
# all markets, base=LTC
(['LTC'], [], False, False,
['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
2019-10-17 20:40:29 +00:00
# all markets, quote=USDT
([], ['USDT'], False, False,
['ETH/USDT', 'LTC/USDT', 'XLTCUSDT']),
# all markets, quote=USDT, USD
([], ['USDT', 'USD'], False, False,
['ETH/USDT', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
# all markets, base=LTC, quote=USDT
(['LTC'], ['USDT'], False, False,
['LTC/USDT', 'XLTCUSDT']),
# all pairs, base=LTC, quote=USDT
(['LTC'], ['USDT'], True, False,
['LTC/USDT']),
# all markets, base=LTC, quote=USDT, NONEXISTENT
(['LTC'], ['USDT', 'NONEXISTENT'], False, False,
['LTC/USDT', 'XLTCUSDT']),
# all markets, base=LTC, quote=NONEXISTENT
(['LTC'], ['NONEXISTENT'], False, False,
[]),
])
2019-10-17 19:45:20 +00:00
def test_get_markets(default_conf, mocker, markets,
base_currencies, quote_currencies, pairs_only, active_only,
expected_keys):
mocker.patch.multiple('freqtrade.exchange.Exchange',
_init_ccxt=MagicMock(return_value=MagicMock()),
_load_async_markets=MagicMock(),
validate_pairs=MagicMock(),
validate_timeframes=MagicMock(),
markets=PropertyMock(return_value=markets))
ex = Exchange(default_conf)
pairs = ex.get_markets(base_currencies, quote_currencies, pairs_only, active_only)
assert sorted(pairs.keys()) == sorted(expected_keys)
def test_get_markets_error(default_conf, mocker):
ex = get_patched_exchange(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=None))
with pytest.raises(OperationalException, match="Markets were not loaded."):
ex.get_markets('LTC', 'USDT', True, False)
2019-08-12 13:43:10 +00:00
def test_timeframe_to_minutes():
assert timeframe_to_minutes("5m") == 5
assert timeframe_to_minutes("10m") == 10
assert timeframe_to_minutes("1h") == 60
assert timeframe_to_minutes("1d") == 1440
def test_timeframe_to_seconds():
assert timeframe_to_seconds("5m") == 300
assert timeframe_to_seconds("10m") == 600
assert timeframe_to_seconds("1h") == 3600
assert timeframe_to_seconds("1d") == 86400
def test_timeframe_to_msecs():
assert timeframe_to_msecs("5m") == 300000
assert timeframe_to_msecs("10m") == 600000
assert timeframe_to_msecs("1h") == 3600000
assert timeframe_to_msecs("1d") == 86400000
2019-08-12 14:07:19 +00:00
2019-08-12 14:11:43 +00:00
def test_timeframe_to_prev_date():
# 2019-08-12 13:22:08
date = datetime.fromtimestamp(1565616128, tz=timezone.utc)
2019-08-12 14:17:06 +00:00
tf_list = [
# 5m -> 2019-08-12 13:20:00
("5m", datetime(2019, 8, 12, 13, 20, 0, tzinfo=timezone.utc)),
# 10m -> 2019-08-12 13:20:00
("10m", datetime(2019, 8, 12, 13, 20, 0, tzinfo=timezone.utc)),
# 1h -> 2019-08-12 13:00:00
("1h", datetime(2019, 8, 12, 13, 00, 0, tzinfo=timezone.utc)),
# 2h -> 2019-08-12 12:00:00
("2h", datetime(2019, 8, 12, 12, 00, 0, tzinfo=timezone.utc)),
# 4h -> 2019-08-12 12:00:00
("4h", datetime(2019, 8, 12, 12, 00, 0, tzinfo=timezone.utc)),
# 1d -> 2019-08-12 00:00:00
("1d", datetime(2019, 8, 12, 00, 00, 0, tzinfo=timezone.utc)),
]
for interval, result in tf_list:
assert timeframe_to_prev_date(interval, date) == result
2019-08-12 14:11:43 +00:00
date = datetime.now(tz=timezone.utc)
assert timeframe_to_prev_date("5m") < date
2019-08-12 14:11:43 +00:00
2019-08-12 14:07:19 +00:00
def test_timeframe_to_next_date():
# 2019-08-12 13:22:08
date = datetime.fromtimestamp(1565616128, tz=timezone.utc)
2019-08-12 14:17:06 +00:00
tf_list = [
# 5m -> 2019-08-12 13:25:00
("5m", datetime(2019, 8, 12, 13, 25, 0, tzinfo=timezone.utc)),
# 10m -> 2019-08-12 13:30:00
("10m", datetime(2019, 8, 12, 13, 30, 0, tzinfo=timezone.utc)),
# 1h -> 2019-08-12 14:00:00
("1h", datetime(2019, 8, 12, 14, 00, 0, tzinfo=timezone.utc)),
# 2h -> 2019-08-12 14:00:00
("2h", datetime(2019, 8, 12, 14, 00, 0, tzinfo=timezone.utc)),
# 4h -> 2019-08-12 14:00:00
("4h", datetime(2019, 8, 12, 16, 00, 0, tzinfo=timezone.utc)),
# 1d -> 2019-08-13 00:00:00
("1d", datetime(2019, 8, 13, 0, 0, 0, tzinfo=timezone.utc)),
]
2019-08-12 14:07:19 +00:00
2019-08-12 14:17:06 +00:00
for interval, result in tf_list:
assert timeframe_to_next_date(interval, date) == result
date = datetime.now(tz=timezone.utc)
assert timeframe_to_next_date("5m") > date
2019-10-17 15:19:50 +00:00
2020-06-02 18:30:31 +00:00
@pytest.mark.parametrize("market_symbol,base,quote,exchange,add_dict,expected_result", [
("BTC/USDT", 'BTC', 'USDT', "binance", {}, True),
("USDT/BTC", 'USDT', 'BTC', "binance", {}, True),
("USDT/BTC", 'BTC', 'USDT', "binance", {}, False), # Reversed currencies
("BTCUSDT", 'BTC', 'USDT', "binance", {}, False), # No seperating /
("BTCUSDT", None, "USDT", "binance", {}, False), #
("USDT/BTC", "BTC", None, "binance", {}, False),
("BTCUSDT", "BTC", None, "binance", {}, False),
("BTC/USDT", "BTC", "USDT", "binance", {}, True),
("BTC/USDT", "USDT", "BTC", "binance", {}, False), # reversed currencies
("BTC/USDT", "BTC", "USD", "binance", {}, False), # Wrong quote currency
("BTC/", "BTC", 'UNK', "binance", {}, False),
("/USDT", 'UNK', 'USDT', "binance", {}, False),
("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": False}, True),
("EUR/BTC", 'EUR', 'BTC', "kraken", {"darkpool": False}, True),
("EUR/BTC", 'BTC', 'EUR', "kraken", {"darkpool": False}, False), # Reversed currencies
("BTC/EUR", 'BTC', 'USD', "kraken", {"darkpool": False}, False), # wrong quote currency
("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools
("BTC/EUR.d", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools
("BTC/USD", 'BTC', 'USD', "ftx", {'spot': True}, True),
("USD/BTC", 'USD', 'BTC', "ftx", {'spot': True}, True),
("BTC/USD", 'BTC', 'USDT', "ftx", {'spot': True}, False), # Wrong quote currency
("BTC/USD", 'USD', 'BTC', "ftx", {'spot': True}, False), # Reversed currencies
("BTC/USD", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets
("BTC-PERP", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets
2019-10-17 15:19:50 +00:00
])
2020-06-02 18:30:31 +00:00
def test_market_is_tradable(mocker, default_conf, market_symbol, base,
quote, add_dict, exchange, expected_result) -> None:
ex = get_patched_exchange(mocker, default_conf, id=exchange)
market = {
'symbol': market_symbol,
'base': base,
'quote': quote,
**(add_dict),
}
assert ex.market_is_tradable(market) == expected_result
2019-10-17 16:24:39 +00:00
@pytest.mark.parametrize("market,expected_result", [
({'symbol': 'ETH/BTC', 'active': True}, True),
({'symbol': 'ETH/BTC', 'active': False}, False),
({'symbol': 'ETH/BTC', }, True),
])
def test_market_is_active(market, expected_result) -> None:
assert market_is_active(market) == expected_result
2020-04-30 18:05:27 +00:00
@pytest.mark.parametrize("order,expected", [
([{'fee'}], False),
({'fee': None}, False),
({'fee': {'currency': 'ETH/BTC'}}, False),
({'fee': {'currency': 'ETH/BTC', 'cost': None}}, False),
({'fee': {'currency': 'ETH/BTC', 'cost': 0.01}}, True),
])
def test_order_has_fee(order, expected) -> None:
assert Exchange.order_has_fee(order) == expected
@pytest.mark.parametrize("order,expected", [
2020-05-01 13:17:52 +00:00
({'symbol': 'ETH/BTC', 'fee': {'currency': 'ETH', 'cost': 0.43}},
(0.43, 'ETH', 0.01)),
({'symbol': 'ETH/USDT', 'fee': {'currency': 'USDT', 'cost': 0.01}},
(0.01, 'USDT', 0.01)),
({'symbol': 'BTC/USDT', 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}},
(0.34, 'USDT', 0.01)),
2020-04-30 18:05:27 +00:00
])
2020-05-01 13:17:52 +00:00
def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01))
ex = get_patched_exchange(mocker, default_conf)
assert ex.extract_cost_curr_rate(order) == expected
@pytest.mark.parametrize("order,expected", [
# Using base-currency
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.1),
({'symbol': 'ETH/BTC', 'amount': 0.05, 'cost': 0.05,
'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.08),
# Using quote currency
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'BTC', 'cost': 0.005}}, 0.1),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, 0.04),
# Using foreign currency
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'NEO', 'cost': 0.0012}}, 0.001944),
({'symbol': 'ETH/BTC', 'amount': 2.21, 'cost': 0.02992561,
'fee': {'currency': 'NEO', 'cost': 0.00027452}}, 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),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, 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),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0,
'fee': {'currency': 'ETH', 'cost': 0.0, 'rate': None}}, 0.0),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0,
'fee': {'currency': 'NEO', 'cost': 0.0, 'rate': None}}, None),
2020-05-01 13:17:52 +00:00
])
def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None:
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081})
ex = get_patched_exchange(mocker, default_conf)
assert ex.calculate_fee_rate(order) == expected
2020-06-28 14:18:39 +00:00
2020-06-28 17:40:33 +00:00
@pytest.mark.parametrize('retrycount,max_retries,expected', [
(0, 3, 10),
(1, 3, 5),
(2, 3, 2),
(3, 3, 1),
(0, 1, 2),
(1, 1, 1),
2020-08-02 08:18:19 +00:00
(0, 4, 17),
(1, 4, 10),
(2, 4, 5),
(3, 4, 2),
(4, 4, 1),
(0, 5, 26),
(1, 5, 17),
(2, 5, 10),
(3, 5, 5),
(4, 5, 2),
(5, 5, 1),
2020-06-28 14:18:39 +00:00
])
2020-06-28 17:40:33 +00:00
def test_calculate_backoff(retrycount, max_retries, expected):
assert calculate_backoff(retrycount, max_retries) == expected