From 5a9481772161e10832197d41c349294c4c57657b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 May 2024 15:52:29 +0200 Subject: [PATCH] ruff format: tests/exchange --- tests/exchange/test_exchange.py | 5826 +++++++++++++++++-------------- 1 file changed, 3269 insertions(+), 2557 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index aab0c9109..ff7ee0aa3 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -50,64 +50,71 @@ from tests.conftest import ( # Make sure to always keep one exchange here which is NOT subclassed!! -EXCHANGES = ['binance', 'kraken', 'gate', 'kucoin', 'bybit', 'okx'] +EXCHANGES = ["binance", "kraken", "gate", "kucoin", "bybit", "okx"] get_entry_rate_data = [ - ('other', 20, 19, 10, 0.0, 20), # Full ask side - ('ask', 20, 19, 10, 0.0, 20), # Full ask side - ('ask', 20, 19, 10, 1.0, 10), # Full last side - ('ask', 20, 19, 10, 0.5, 15), # Between ask and last - ('ask', 20, 19, 10, 0.7, 13), # Between ask and last - ('ask', 20, 19, 10, 0.3, 17), # Between ask and last - ('ask', 5, 6, 10, 1.0, 5), # last bigger than ask - ('ask', 5, 6, 10, 0.5, 5), # last bigger than ask - ('ask', 20, 19, 10, None, 20), # price_last_balance missing - ('ask', 10, 20, None, 0.5, 10), # last not available - uses ask - ('ask', 4, 5, None, 0.5, 4), # last not available - uses ask - ('ask', 4, 5, None, 1, 4), # last not available - uses ask - ('ask', 4, 5, None, 0, 4), # last not available - uses ask - ('same', 21, 20, 10, 0.0, 20), # Full bid side - ('bid', 21, 20, 10, 0.0, 20), # Full bid side - ('bid', 21, 20, 10, 1.0, 10), # Full last side - ('bid', 21, 20, 10, 0.5, 15), # Between bid and last - ('bid', 21, 20, 10, 0.7, 13), # Between bid and last - ('bid', 21, 20, 10, 0.3, 17), # Between bid and last - ('bid', 6, 5, 10, 1.0, 5), # last bigger than bid - ('bid', 21, 20, 10, None, 20), # price_last_balance missing - ('bid', 6, 5, 10, 0.5, 5), # last bigger than bid - ('bid', 21, 20, None, 0.5, 20), # last not available - uses bid - ('bid', 6, 5, None, 0.5, 5), # last not available - uses bid - ('bid', 6, 5, None, 1, 5), # last not available - uses bid - ('bid', 6, 5, None, 0, 5), # last not available - uses bid + ("other", 20, 19, 10, 0.0, 20), # Full ask side + ("ask", 20, 19, 10, 0.0, 20), # Full ask side + ("ask", 20, 19, 10, 1.0, 10), # Full last side + ("ask", 20, 19, 10, 0.5, 15), # Between ask and last + ("ask", 20, 19, 10, 0.7, 13), # Between ask and last + ("ask", 20, 19, 10, 0.3, 17), # Between ask and last + ("ask", 5, 6, 10, 1.0, 5), # last bigger than ask + ("ask", 5, 6, 10, 0.5, 5), # last bigger than ask + ("ask", 20, 19, 10, None, 20), # price_last_balance missing + ("ask", 10, 20, None, 0.5, 10), # last not available - uses ask + ("ask", 4, 5, None, 0.5, 4), # last not available - uses ask + ("ask", 4, 5, None, 1, 4), # last not available - uses ask + ("ask", 4, 5, None, 0, 4), # last not available - uses ask + ("same", 21, 20, 10, 0.0, 20), # Full bid side + ("bid", 21, 20, 10, 0.0, 20), # Full bid side + ("bid", 21, 20, 10, 1.0, 10), # Full last side + ("bid", 21, 20, 10, 0.5, 15), # Between bid and last + ("bid", 21, 20, 10, 0.7, 13), # Between bid and last + ("bid", 21, 20, 10, 0.3, 17), # Between bid and last + ("bid", 6, 5, 10, 1.0, 5), # last bigger than bid + ("bid", 21, 20, 10, None, 20), # price_last_balance missing + ("bid", 6, 5, 10, 0.5, 5), # last bigger than bid + ("bid", 21, 20, None, 0.5, 20), # last not available - uses bid + ("bid", 6, 5, None, 0.5, 5), # last not available - uses bid + ("bid", 6, 5, None, 1, 5), # last not available - uses bid + ("bid", 6, 5, None, 0, 5), # last not available - uses bid ] get_exit_rate_data = [ - ('bid', 12.0, 11.0, 11.5, 0.0, 11.0), # full bid side - ('bid', 12.0, 11.0, 11.5, 1.0, 11.5), # full last side - ('bid', 12.0, 11.0, 11.5, 0.5, 11.25), # between bid and lat - ('bid', 12.0, 11.2, 10.5, 0.0, 11.2), # Last smaller than bid - ('bid', 12.0, 11.2, 10.5, 1.0, 11.2), # Last smaller than bid - uses bid - ('bid', 12.0, 11.2, 10.5, 0.5, 11.2), # Last smaller than bid - uses bid - ('bid', 0.003, 0.002, 0.005, 0.0, 0.002), - ('bid', 0.003, 0.002, 0.005, None, 0.002), - ('ask', 12.0, 11.0, 12.5, 0.0, 12.0), # full ask side - ('ask', 12.0, 11.0, 12.5, 1.0, 12.5), # full last side - ('ask', 12.0, 11.0, 12.5, 0.5, 12.25), # between bid and lat - ('ask', 12.2, 11.2, 10.5, 0.0, 12.2), # Last smaller than ask - ('ask', 12.0, 11.0, 10.5, 1.0, 12.0), # Last smaller than ask - uses ask - ('ask', 12.0, 11.2, 10.5, 0.5, 12.0), # Last smaller than ask - uses ask - ('ask', 10.0, 11.0, 11.0, 0.0, 10.0), - ('ask', 10.11, 11.2, 11.0, 0.0, 10.11), - ('ask', 0.001, 0.002, 11.0, 0.0, 0.001), - ('ask', 0.006, 1.0, 11.0, 0.0, 0.006), - ('ask', 0.006, 1.0, 11.0, None, 0.006), + ("bid", 12.0, 11.0, 11.5, 0.0, 11.0), # full bid side + ("bid", 12.0, 11.0, 11.5, 1.0, 11.5), # full last side + ("bid", 12.0, 11.0, 11.5, 0.5, 11.25), # between bid and lat + ("bid", 12.0, 11.2, 10.5, 0.0, 11.2), # Last smaller than bid + ("bid", 12.0, 11.2, 10.5, 1.0, 11.2), # Last smaller than bid - uses bid + ("bid", 12.0, 11.2, 10.5, 0.5, 11.2), # Last smaller than bid - uses bid + ("bid", 0.003, 0.002, 0.005, 0.0, 0.002), + ("bid", 0.003, 0.002, 0.005, None, 0.002), + ("ask", 12.0, 11.0, 12.5, 0.0, 12.0), # full ask side + ("ask", 12.0, 11.0, 12.5, 1.0, 12.5), # full last side + ("ask", 12.0, 11.0, 12.5, 0.5, 12.25), # between bid and lat + ("ask", 12.2, 11.2, 10.5, 0.0, 12.2), # Last smaller than ask + ("ask", 12.0, 11.0, 10.5, 1.0, 12.0), # Last smaller than ask - uses ask + ("ask", 12.0, 11.2, 10.5, 0.5, 12.0), # Last smaller than ask - uses ask + ("ask", 10.0, 11.0, 11.0, 0.0, 10.0), + ("ask", 10.11, 11.2, 11.0, 0.0, 10.11), + ("ask", 0.001, 0.002, 11.0, 0.0, 0.001), + ("ask", 0.006, 1.0, 11.0, 0.0, 0.006), + ("ask", 0.006, 1.0, 11.0, None, 0.006), ] -def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - fun, mock_ccxt_fun, retries=API_RETRY_COUNT + 1, **kwargs): - - with patch('freqtrade.exchange.common.time.sleep'): +def ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + fun, + mock_ccxt_fun, + retries=API_RETRY_COUNT + 1, + **kwargs, +): + 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) @@ -127,10 +134,10 @@ def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 -async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun, - retries=API_RETRY_COUNT + 1, **kwargs): - - with patch('freqtrade.exchange.common.asyncio.sleep', get_mock_coro(None)): +async def async_ccxt_exception( + mocker, default_conf, api_mock, fun, mock_ccxt_fun, retries=API_RETRY_COUNT + 1, **kwargs +): + with patch("freqtrade.exchange.common.asyncio.sleep", get_mock_coro(None)): with pytest.raises(DDosProtection): api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("Dooh")) exchange = get_patched_exchange(mocker, default_conf, api_mock) @@ -156,35 +163,35 @@ async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fu def test_init(default_conf, mocker, caplog): caplog.set_level(logging.INFO) get_patched_exchange(mocker, default_conf) - assert log_has('Instance is running with dry_run enabled', caplog) + assert log_has("Instance is running with dry_run enabled", caplog) def test_remove_exchange_credentials(default_conf) -> None: conf = deepcopy(default_conf) - remove_exchange_credentials(conf['exchange'], False) + remove_exchange_credentials(conf["exchange"], False) - assert conf['exchange']['key'] != '' - assert conf['exchange']['secret'] != '' + assert conf["exchange"]["key"] != "" + assert conf["exchange"]["secret"] != "" - remove_exchange_credentials(conf['exchange'], True) - assert conf['exchange']['key'] == '' - assert conf['exchange']['secret'] == '' - assert conf['exchange']['password'] == '' - assert conf['exchange']['uid'] == '' + remove_exchange_credentials(conf["exchange"], True) + assert conf["exchange"]["key"] == "" + assert conf["exchange"]["secret"] == "" + assert conf["exchange"]["password"] == "" + assert conf["exchange"]["uid"] == "" def test_init_ccxt_kwargs(default_conf, mocker, caplog): - mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) - mocker.patch(f'{EXMS}.validate_stakecurrency') - aei_mock = mocker.patch(f'{EXMS}.additional_exchange_init') + mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.validate_stakecurrency") + aei_mock = mocker.patch(f"{EXMS}.additional_exchange_init") caplog.set_level(logging.INFO) conf = copy.deepcopy(default_conf) - conf['exchange']['ccxt_async_config'] = {'aiohttp_trust_env': True, 'asyncio_loop': True} + 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) + "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 assert aei_mock.call_count == 1 @@ -192,26 +199,26 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog): # 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} + 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 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 not hasattr(ex._api_async, "TestKWARG44") - assert hasattr(ex._api_async, 'TestKWARG') + assert hasattr(ex._api_async, "TestKWARG") assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog) assert log_has(asynclogmsg, caplog) # Test additional headers case - Exchange._ccxt_params = {'hello': 'world'} + Exchange._ccxt_params = {"hello": "world"} ex = Exchange(conf) assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog) - assert ex._api.hello == 'world' + assert ex._api.hello == "world" assert ex._ccxt_config == {} Exchange._headers = {} @@ -219,68 +226,74 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog): def test_destroy(default_conf, mocker, caplog): caplog.set_level(logging.DEBUG) get_patched_exchange(mocker, default_conf) - assert log_has('Exchange object destroyed, closing async loop', caplog) + assert log_has("Exchange object destroyed, closing async loop", caplog) def test_init_exception(default_conf, mocker): - default_conf['exchange']['name'] = 'wrong_exchange_name' + default_conf["exchange"]["name"] = "wrong_exchange_name" - with pytest.raises(OperationalException, - match=f"Exchange {default_conf['exchange']['name']} is not supported"): + with pytest.raises( + OperationalException, match=f"Exchange {default_conf['exchange']['name']} is not supported" + ): Exchange(default_conf) - default_conf['exchange']['name'] = 'binance' - with pytest.raises(OperationalException, - match=f"Exchange {default_conf['exchange']['name']} is not supported"): + default_conf["exchange"]["name"] = "binance" + with pytest.raises( + OperationalException, match=f"Exchange {default_conf['exchange']['name']} is not supported" + ): mocker.patch("ccxt.binance", MagicMock(side_effect=AttributeError)) Exchange(default_conf) - with pytest.raises(OperationalException, - match=r"Initialization of ccxt failed. Reason: DeadBeef"): + 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) def test_exchange_resolver(default_conf, mocker, caplog): - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=MagicMock())) - mocker.patch(f'{EXMS}._load_async_markets') - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') - default_conf['exchange']['name'] = 'zaif' + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=MagicMock())) + mocker.patch(f"{EXMS}._load_async_markets") + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") + default_conf["exchange"]["name"] = "zaif" exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) caplog.clear() - default_conf['exchange']['name'] = 'Bybit' + default_conf["exchange"]["name"] = "Bybit" exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Bybit) - assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", - caplog) + assert not log_has_re( + r"No .* specific subclass found. Using the generic class instead.", caplog + ) caplog.clear() - default_conf['exchange']['name'] = 'kraken' + default_conf["exchange"]["name"] = "kraken" exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Kraken) assert not isinstance(exchange, Binance) - assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", - caplog) + assert not log_has_re( + r"No .* specific subclass found. Using the generic class instead.", caplog + ) - default_conf['exchange']['name'] = 'binance' + default_conf["exchange"]["name"] = "binance" exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Binance) assert not isinstance(exchange, Kraken) - assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", - caplog) + assert not log_has_re( + r"No .* specific subclass found. Using the generic class instead.", caplog + ) # Test mapping - default_conf['exchange']['name'] = 'binanceus' + default_conf["exchange"]["name"] = "binanceus" exchange = ExchangeResolver.load_exchange(default_conf) assert isinstance(exchange, Exchange) assert isinstance(exchange, Binance) @@ -312,201 +325,202 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog): ex.validate_order_time_in_force(tif2) -@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), - -]) +@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}}}) + markets = PropertyMock(return_value={"ETH/BTC": {"precision": {"price": precision}}}) exchange = get_patched_exchange(mocker, default_conf, id="binance") - mocker.patch(f'{EXMS}.markets', markets) - mocker.patch(f'{EXMS}.precisionMode', PropertyMock(return_value=precision_mode)) - pair = 'ETH/BTC' + mocker.patch(f"{EXMS}.markets", markets) + mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=precision_mode)) + pair = "ETH/BTC" assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected def test__get_stake_amount_limit(mocker, default_conf) -> None: - exchange = get_patched_exchange(mocker, default_conf, id="binance") stoploss = -0.05 - markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} + markets = {"ETH/BTC": {"symbol": "ETH/BTC"}} # no pair found - mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) - with pytest.raises(ValueError, match=r'.*get market information.*'): - exchange.get_min_pair_stake_amount('BNB/BTC', 1, stoploss) + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) + with pytest.raises(ValueError, match=r".*get market information.*"): + exchange.get_min_pair_stake_amount("BNB/BTC", 1, stoploss) # no cost/amount Min markets["ETH/BTC"]["limits"] = { - 'cost': {'min': None, 'max': None}, - 'amount': {'min': None, 'max': None}, + "cost": {"min": None, "max": None}, + "amount": {"min": None, "max": None}, } - mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) - result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 1, stoploss) assert result is None - result = exchange.get_max_pair_stake_amount('ETH/BTC', 1) - assert result == float('inf') + result = exchange.get_max_pair_stake_amount("ETH/BTC", 1) + assert result == float("inf") # min/max cost is set markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 2, 'max': 10000}, - 'amount': {'min': None, 'max': None}, + "cost": {"min": 2, "max": 10000}, + "amount": {"min": None, "max": None}, } - mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) # min - result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 1, stoploss) expected_result = 2 * (1 + 0.05) / (1 - abs(stoploss)) assert pytest.approx(result) == expected_result # With Leverage - result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 1, stoploss, 3.0) assert pytest.approx(result) == expected_result / 3 # max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + result = exchange.get_max_pair_stake_amount("ETH/BTC", 2) assert result == 10000 # min amount is set markets["ETH/BTC"]["limits"] = { - 'cost': {'min': None, 'max': None}, - 'amount': {'min': 2, 'max': 10000}, + "cost": {"min": None, "max": None}, + "amount": {"min": 2, "max": 10000}, } - mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, stoploss) expected_result = 2 * 2 * (1 + 0.05) assert pytest.approx(result) == expected_result # With Leverage - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, stoploss, 5.0) assert pytest.approx(result) == expected_result / 5 # max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + result = exchange.get_max_pair_stake_amount("ETH/BTC", 2) assert result == 20000 # min amount and cost are set (cost is minimal and therefore ignored) markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 2, 'max': None}, - 'amount': {'min': 2, 'max': None}, + "cost": {"min": 2, "max": None}, + "amount": {"min": 2, "max": None}, } - mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, stoploss) expected_result = max(2, 2 * 2) * (1 + 0.05) assert pytest.approx(result) == expected_result # With Leverage - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, stoploss, 10) assert pytest.approx(result) == expected_result / 10 # min amount and cost are set (amount is minial) markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 8, 'max': 10000}, - 'amount': {'min': 2, 'max': 500}, + "cost": {"min": 8, "max": 10000}, + "amount": {"min": 2, "max": 500}, } - mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, stoploss) expected_result = max(8, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss)) assert pytest.approx(result) == expected_result # With Leverage - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, stoploss, 7.0) assert pytest.approx(result) == expected_result / 7.0 # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + result = exchange.get_max_pair_stake_amount("ETH/BTC", 2) assert result == 1000 - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, -0.4) expected_result = max(8, 2 * 2) * 1.5 assert pytest.approx(result) == expected_result # With Leverage - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, -0.4, 8.0) assert pytest.approx(result) == expected_result / 8.0 # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + result = exchange.get_max_pair_stake_amount("ETH/BTC", 2) assert result == 1000 # Really big stoploss - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, -1) expected_result = max(8, 2 * 2) * 1.5 assert pytest.approx(result) == expected_result # With Leverage - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, -1, 12.0) assert pytest.approx(result) == expected_result / 12 # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + result = exchange.get_max_pair_stake_amount("ETH/BTC", 2) assert result == 1000 - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, 12.0) + result = exchange.get_max_pair_stake_amount("ETH/BTC", 2, 12.0) assert result == 1000 / 12 - markets["ETH/BTC"]["contractSize"] = '0.01' - default_conf['trading_mode'] = 'futures' - default_conf['margin_mode'] = 'isolated' + markets["ETH/BTC"]["contractSize"] = "0.01" + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" exchange = get_patched_exchange(mocker, default_conf, id="binance") - mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) # Contract size 0.01 - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, -1) assert pytest.approx(result) == expected_result * 0.01 # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + result = exchange.get_max_pair_stake_amount("ETH/BTC", 2) assert result == 10 - markets["ETH/BTC"]["contractSize"] = '10' - mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) + markets["ETH/BTC"]["contractSize"] = "10" + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) # With Leverage, Contract size 10 - result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 2, -1, 12.0) assert pytest.approx(result) == (expected_result / 12) * 10.0 # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) + result = exchange.get_max_pair_stake_amount("ETH/BTC", 2) assert result == 10000 def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: exchange = get_patched_exchange(mocker, default_conf, id="binance") stoploss = -0.05 - markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} + markets = {"ETH/BTC": {"symbol": "ETH/BTC"}} # ~Real Binance data markets["ETH/BTC"]["limits"] = { - 'cost': {'min': 0.0001, 'max': 4000}, - 'amount': {'min': 0.001, 'max': 10000}, + "cost": {"min": 0.0001, "max": 4000}, + "amount": {"min": 0.001, "max": 10000}, } - mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets)) - result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss) + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 0.020405, stoploss) expected_result = max(0.0001, 0.001 * 0.020405) * (1 + 0.05) / (1 - abs(stoploss)) assert round(result, 8) == round(expected_result, 8) # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 2.0) + result = exchange.get_max_pair_stake_amount("ETH/BTC", 2.0) assert result == 4000 # Leverage - result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 0.020405, stoploss, 3.0) assert round(result, 8) == round(expected_result / 3, 8) # Contract_size markets["ETH/BTC"]["contractSize"] = 0.1 - result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) + result = exchange.get_min_pair_stake_amount("ETH/BTC", 0.020405, stoploss, 3.0) assert round(result, 8) == round((expected_result / 3), 8) # Max - result = exchange.get_max_pair_stake_amount('ETH/BTC', 12.0) + result = exchange.get_max_pair_stake_amount("ETH/BTC", 12.0) assert result == 4000 def test__load_async_markets(default_conf, mocker, caplog): - mocker.patch(f'{EXMS}._init_ccxt') - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}._load_markets') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f"{EXMS}._init_ccxt") + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}._load_markets") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") exchange = Exchange(default_conf) exchange._api_async.load_markets = get_mock_coro(None) exchange._load_async_markets() @@ -516,27 +530,27 @@ def test__load_async_markets(default_conf, mocker, caplog): exchange._api_async.load_markets = Mock(side_effect=ccxt.BaseError("deadbeef")) exchange._load_async_markets() - assert log_has('Could not load async markets. Reason: deadbeef', caplog) + assert log_has("Could not load async markets. Reason: deadbeef", caplog) 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")) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}._load_async_markets') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}._load_async_markets") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") Exchange(default_conf) - assert log_has('Unable to initialize markets.', caplog) + assert log_has("Unable to initialize markets.", caplog) - expected_return = {'ETH/BTC': 'available'} + expected_return = {"ETH/BTC": "available"} api_mock = MagicMock() api_mock.load_markets = MagicMock(return_value=expected_return) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - default_conf['exchange']['pair_whitelist'] = ['ETH/BTC'] + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + default_conf["exchange"]["pair_whitelist"] = ["ETH/BTC"] ex = Exchange(default_conf) assert ex.markets == expected_return @@ -544,15 +558,16 @@ def test__load_markets(default_conf, mocker, caplog): def test_reload_markets(default_conf, mocker, caplog, time_machine): caplog.set_level(logging.DEBUG) - initial_markets = {'ETH/BTC': {}} - updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}} + initial_markets = {"ETH/BTC": {}} + updated_markets = {"ETH/BTC": {}, "LTC/BTC": {}} start_dt = dt_now() time_machine.move_to(start_dt, tick=False) api_mock = MagicMock() api_mock.load_markets = MagicMock(return_value=initial_markets) - default_conf['exchange']['markets_refresh_interval'] = 10 - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance", - mock_markets=False) + default_conf["exchange"]["markets_refresh_interval"] = 10 + exchange = get_patched_exchange( + mocker, default_conf, api_mock, id="binance", mock_markets=False + ) exchange._load_async_markets = MagicMock() assert exchange._last_markets_refresh == dt_ts() @@ -570,7 +585,7 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine): exchange.reload_markets() assert exchange.markets == updated_markets assert exchange._load_async_markets.call_count == 1 - assert log_has('Performing scheduled market reload..', caplog) + assert log_has("Performing scheduled market reload..", caplog) # Not called again exchange._load_async_markets.reset_mock() @@ -584,7 +599,7 @@ def test_reload_markets_exception(default_conf, mocker, caplog): api_mock = MagicMock() api_mock.load_markets = MagicMock(side_effect=ccxt.NetworkError("LoadError")) - default_conf['exchange']['markets_refresh_interval'] = 10 + 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 @@ -593,71 +608,87 @@ def test_reload_markets_exception(default_conf, mocker, caplog): assert log_has_re(r"Could not reload markets.*", caplog) -@pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT']) +@pytest.mark.parametrize("stake_currency", ["ETH", "BTC", "USDT"]) def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): - default_conf['stake_currency'] = stake_currency + default_conf["stake_currency"] = stake_currency api_mock = MagicMock() - type(api_mock).load_markets = MagicMock(return_value={ - 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, - 'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'}, - }) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}._load_async_markets') - mocker.patch(f'{EXMS}.validate_pricing') + type(api_mock).load_markets = MagicMock( + return_value={ + "ETH/BTC": {"quote": "BTC"}, + "LTC/BTC": {"quote": "BTC"}, + "XRP/ETH": {"quote": "ETH"}, + "NEO/USDT": {"quote": "USDT"}, + } + ) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}._load_async_markets") + mocker.patch(f"{EXMS}.validate_pricing") Exchange(default_conf) def test_validate_stakecurrency_error(default_conf, mocker, caplog): - default_conf['stake_currency'] = 'XRP' + default_conf["stake_currency"] = "XRP" api_mock = MagicMock() - type(api_mock).load_markets = MagicMock(return_value={ - 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, - 'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'}, - }) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}._load_async_markets') - with pytest.raises(ConfigurationError, - match=r'XRP is not available as stake on .*' - 'Available currencies are: BTC, ETH, USDT'): + type(api_mock).load_markets = MagicMock( + return_value={ + "ETH/BTC": {"quote": "BTC"}, + "LTC/BTC": {"quote": "BTC"}, + "XRP/ETH": {"quote": "ETH"}, + "NEO/USDT": {"quote": "USDT"}, + } + ) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}._load_async_markets") + with pytest.raises( + ConfigurationError, + match=r"XRP is not available as stake on .*" "Available currencies are: BTC, ETH, USDT", + ): Exchange(default_conf) - type(api_mock).load_markets = MagicMock(side_effect=ccxt.NetworkError('No connection.')) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + type(api_mock).load_markets = MagicMock(side_effect=ccxt.NetworkError("No connection.")) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - with pytest.raises(OperationalException, - match=r'Could not load markets, therefore cannot start\. Please.*'): + with pytest.raises( + OperationalException, match=r"Could not load markets, therefore cannot start\. Please.*" + ): Exchange(default_conf) 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', 'BUSD']) + assert set(ex.get_quote_currencies()) == set(["USD", "ETH", "BTC", "USDT", "BUSD"]) -@pytest.mark.parametrize('pair,expected', [ - ('XRP/BTC', 'BTC'), - ('LTC/USD', 'USD'), - ('ETH/USDT', 'USDT'), - ('XLTCUSDT', 'USDT'), - ('XRP/NOCURRENCY', ''), -]) +@pytest.mark.parametrize( + "pair,expected", + [ + ("XRP/BTC", "BTC"), + ("LTC/USD", "USD"), + ("ETH/USDT", "USDT"), + ("XLTCUSDT", "USDT"), + ("XRP/NOCURRENCY", ""), + ], +) 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', ''), -]) +@pytest.mark.parametrize( + "pair,expected", + [ + ("XRP/BTC", "XRP"), + ("LTC/USD", "LTC"), + ("ETH/USDT", "ETH"), + ("XLTCUSDT", "LTC"), + ("XRP/NOCURRENCY", ""), + ], +) 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 @@ -665,218 +696,236 @@ def test_get_pair_base_currency(default_conf, mocker, pair, expected): def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly api_mock = MagicMock() - type(api_mock).load_markets = MagicMock(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).load_markets = MagicMock( + 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 - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}._load_async_markets') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}._load_async_markets") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") Exchange(default_conf) def test_validate_pairs_not_available(default_conf, mocker): api_mock = MagicMock() - type(api_mock).markets = PropertyMock(return_value={ - 'XRP/BTC': {'inactive': True, 'base': 'XRP', 'quote': 'BTC'} - }) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}._load_async_markets') + type(api_mock).markets = PropertyMock( + return_value={"XRP/BTC": {"inactive": True, "base": "XRP", "quote": "BTC"}} + ) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}._load_async_markets") - with pytest.raises(OperationalException, match=r'not available'): + with pytest.raises(OperationalException, match=r"not available"): Exchange(default_conf) def test_validate_pairs_exception(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() - mocker.patch(f'{EXMS}.name', PropertyMock(return_value='Binance')) + mocker.patch(f"{EXMS}.name", PropertyMock(return_value="Binance")) type(api_mock).markets = PropertyMock(return_value={}) - mocker.patch(f'{EXMS}._init_ccxt', api_mock) - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') - mocker.patch(f'{EXMS}._load_async_markets') + mocker.patch(f"{EXMS}._init_ccxt", api_mock) + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") + mocker.patch(f"{EXMS}._load_async_markets") - with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'): + with pytest.raises(OperationalException, match=r"Pair ETH/BTC is not available on Binance"): Exchange(default_conf) - mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={})) + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value={})) Exchange(default_conf) - assert log_has('Unable to validate pairs (assuming they are correct).', caplog) + 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).load_markets = MagicMock(return_value={ - 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, - 'XRP/BTC': {'quote': 'BTC', 'info': {'prohibitedIn': ['US']}}, - 'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ... - }) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}._load_async_markets') - mocker.patch(f'{EXMS}.validate_pricing') - mocker.patch(f'{EXMS}.validate_stakecurrency') + type(api_mock).load_markets = MagicMock( + return_value={ + "ETH/BTC": {"quote": "BTC"}, + "LTC/BTC": {"quote": "BTC"}, + "XRP/BTC": {"quote": "BTC", "info": {"prohibitedIn": ["US"]}}, + "NEO/BTC": {"quote": "BTC", "info": "TestString"}, # info can also be a string ... + } + ) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}._load_async_markets") + mocker.patch(f"{EXMS}.validate_pricing") + mocker.patch(f"{EXMS}.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) + 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).load_markets = MagicMock(return_value={ - 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, - 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, - 'HELLO-WORLD': {'quote': 'BTC'}, - }) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}._load_async_markets') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') + type(api_mock).load_markets = MagicMock( + return_value={ + "ETH/BTC": {"quote": "BTC"}, + "LTC/BTC": {"quote": "BTC"}, + "XRP/BTC": {"quote": "BTC"}, + "NEO/BTC": {"quote": "BTC"}, + "HELLO-WORLD": {"quote": "BTC"}, + } + ) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}._load_async_markets") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") Exchange(default_conf) def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, caplog): api_mock = MagicMock() - default_conf['stake_currency'] = '' - type(api_mock).load_markets = MagicMock(return_value={ - 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, - 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, - 'HELLO-WORLD': {'quote': 'BTC'}, - }) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}._load_async_markets') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') + default_conf["stake_currency"] = "" + type(api_mock).load_markets = MagicMock( + return_value={ + "ETH/BTC": {"quote": "BTC"}, + "LTC/BTC": {"quote": "BTC"}, + "XRP/BTC": {"quote": "BTC"}, + "NEO/BTC": {"quote": "BTC"}, + "HELLO-WORLD": {"quote": "BTC"}, + } + ) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}._load_async_markets") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") Exchange(default_conf) assert type(api_mock).load_markets.call_count == 1 def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): - default_conf['exchange']['pair_whitelist'].append('HELLO-WORLD') + default_conf["exchange"]["pair_whitelist"].append("HELLO-WORLD") api_mock = MagicMock() - type(api_mock).load_markets = MagicMock(return_value={ - 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, - 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, - 'HELLO-WORLD': {'quote': 'USDT'}, - }) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}._load_async_markets') - mocker.patch(f'{EXMS}.validate_stakecurrency') + type(api_mock).load_markets = MagicMock( + return_value={ + "ETH/BTC": {"quote": "BTC"}, + "LTC/BTC": {"quote": "BTC"}, + "XRP/BTC": {"quote": "BTC"}, + "NEO/BTC": {"quote": "BTC"}, + "HELLO-WORLD": {"quote": "USDT"}, + } + ) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}._load_async_markets") + mocker.patch(f"{EXMS}.validate_stakecurrency") with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"): Exchange(default_conf) -@pytest.mark.parametrize("timeframe", [ - ('5m'), ("1m"), ("15m"), ("1h") -]) +@pytest.mark.parametrize("timeframe", [("5m"), ("1m"), ("15m"), ("1h")]) def test_validate_timeframes(default_conf, mocker, timeframe): default_conf["timeframe"] = timeframe api_mock = MagicMock() - id_mock = PropertyMock(return_value='test_exchange') + 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'}) + timeframes = PropertyMock(return_value={"1m": "1m", "5m": "5m", "15m": "15m", "1h": "1h"}) type(api_mock).timeframes = timeframes - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") Exchange(default_conf) def test_validate_timeframes_failed(default_conf, mocker): default_conf["timeframe"] = "3m" api_mock = MagicMock() - id_mock = PropertyMock(return_value='test_exchange') + id_mock = PropertyMock(return_value="test_exchange") type(api_mock).id = id_mock - timeframes = PropertyMock(return_value={'15s': '15s', - '1m': '1m', - '5m': '5m', - '15m': '15m', - '1h': '1h'}) + timeframes = PropertyMock( + return_value={"15s": "15s", "1m": "1m", "5m": "5m", "15m": "15m", "1h": "1h"} + ) type(api_mock).timeframes = timeframes - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') - with pytest.raises(ConfigurationError, - match=r"Invalid timeframe '3m'. This exchange supports.*"): + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") + with pytest.raises( + ConfigurationError, match=r"Invalid timeframe '3m'. This exchange supports.*" + ): Exchange(default_conf) default_conf["timeframe"] = "15s" - with pytest.raises(ConfigurationError, - match=r"Timeframes < 1m are currently not supported by Freqtrade."): + with pytest.raises( + ConfigurationError, match=r"Timeframes < 1m are currently not supported by Freqtrade." + ): Exchange(default_conf) # Will not raise an exception in util mode. - default_conf['runmode'] = RunMode.UTIL_EXCHANGE + default_conf["runmode"] = RunMode.UTIL_EXCHANGE Exchange(default_conf) def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker): default_conf["timeframe"] = "3m" api_mock = MagicMock() - id_mock = PropertyMock(return_value='test_exchange') + 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(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_stakecurrency') - 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. *'): + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_stakecurrency") + 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_emulated_ohlcvi_2(default_conf, mocker): default_conf["timeframe"] = "3m" api_mock = MagicMock() - id_mock = PropertyMock(return_value='test_exchange') + 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(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}._load_markets', - MagicMock(return_value={'timeframes': None})) - mocker.patch(f'{EXMS}.validate_pairs', MagicMock()) - mocker.patch(f'{EXMS}.validate_stakecurrency') - 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. *'): + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={"timeframes": None})) + mocker.patch(f"{EXMS}.validate_pairs", MagicMock()) + mocker.patch(f"{EXMS}.validate_stakecurrency") + 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) @@ -884,57 +933,54 @@ def test_validate_timeframes_not_in_config(default_conf, mocker): # TODO: this test does not assert ... del default_conf["timeframe"] api_mock = MagicMock() - id_mock = PropertyMock(return_value='test_exchange') + 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'}) + timeframes = PropertyMock(return_value={"1m": "1m", "5m": "5m", "15m": "15m", "1h": "1h"}) type(api_mock).timeframes = timeframes - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') - mocker.patch(f'{EXMS}.validate_required_startup_candles') + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") + mocker.patch(f"{EXMS}.validate_required_startup_candles") Exchange(default_conf) def test_validate_pricing(default_conf, mocker): api_mock = MagicMock() has = { - 'fetchL2OrderBook': True, - 'fetchTicker': True, + "fetchL2OrderBook": True, + "fetchTicker": True, } type(api_mock).has = PropertyMock(return_value=has) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) - mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.name', 'Binance') - default_conf['exchange']['name'] = 'binance' + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.validate_trading_mode_and_margin_mode") + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.name", "Binance") + default_conf["exchange"]["name"] = "binance" ExchangeResolver.load_exchange(default_conf) - has.update({'fetchTicker': False}) + has.update({"fetchTicker": False}) with pytest.raises(OperationalException, match="Ticker pricing not available for .*"): ExchangeResolver.load_exchange(default_conf) - has.update({'fetchTicker': True}) + has.update({"fetchTicker": True}) - default_conf['exit_pricing']['use_order_book'] = True + default_conf["exit_pricing"]["use_order_book"] = True ExchangeResolver.load_exchange(default_conf) - has.update({'fetchL2OrderBook': False}) + has.update({"fetchL2OrderBook": False}) with pytest.raises(OperationalException, match="Orderbook not available for .*"): ExchangeResolver.load_exchange(default_conf) - has.update({'fetchL2OrderBook': True}) + has.update({"fetchL2OrderBook": True}) # Binance has no tickers on futures - default_conf['trading_mode'] = TradingMode.FUTURES - default_conf['margin_mode'] = MarginMode.ISOLATED + default_conf["trading_mode"] = TradingMode.FUTURES + default_conf["margin_mode"] = MarginMode.ISOLATED with pytest.raises(OperationalException, match="Ticker pricing not available for .*"): ExchangeResolver.load_exchange(default_conf) @@ -943,96 +989,97 @@ def test_validate_pricing(default_conf, mocker): def test_validate_ordertypes(default_conf, mocker): api_mock = MagicMock() - type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True}) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') + type(api_mock).has = PropertyMock(return_value={"createMarketOrder": True}) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") - default_conf['order_types'] = { - 'entry': 'limit', - 'exit': 'limit', - 'stoploss': 'market', - 'stoploss_on_exchange': False + default_conf["order_types"] = { + "entry": "limit", + "exit": "limit", + "stoploss": "market", + "stoploss_on_exchange": False, } Exchange(default_conf) - type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False}) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) + type(api_mock).has = PropertyMock(return_value={"createMarketOrder": False}) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - default_conf['order_types'] = { - 'entry': 'limit', - 'exit': 'limit', - 'stoploss': 'market', - 'stoploss_on_exchange': False + default_conf["order_types"] = { + "entry": "limit", + "exit": "limit", + "stoploss": "market", + "stoploss_on_exchange": False, } - with pytest.raises(OperationalException, - match=r'Exchange .* does not support market orders.'): + with pytest.raises(OperationalException, match=r"Exchange .* does not support market orders."): Exchange(default_conf) - default_conf['order_types'] = { - 'entry': 'limit', - 'exit': 'limit', - 'stoploss': 'limit', - 'stoploss_on_exchange': True + default_conf["order_types"] = { + "entry": "limit", + "exit": "limit", + "stoploss": "limit", + "stoploss_on_exchange": True, } - with pytest.raises(OperationalException, - match=r'On exchange stoploss is not supported for .*'): + with pytest.raises(OperationalException, match=r"On exchange stoploss is not supported for .*"): Exchange(default_conf) -@pytest.mark.parametrize('exchange_name,stopadv, expected', [ - ('binance', 'last', True), - ('binance', 'mark', True), - ('binance', 'index', False), - ('bybit', 'last', True), - ('bybit', 'mark', True), - ('bybit', 'index', True), - ('okx', 'last', True), - ('okx', 'mark', True), - ('okx', 'index', True), - ('gate', 'last', True), - ('gate', 'mark', True), - ('gate', 'index', True), - ]) +@pytest.mark.parametrize( + "exchange_name,stopadv, expected", + [ + ("binance", "last", True), + ("binance", "mark", True), + ("binance", "index", False), + ("bybit", "last", True), + ("bybit", "mark", True), + ("bybit", "index", True), + ("okx", "last", True), + ("okx", "mark", True), + ("okx", "index", True), + ("gate", "last", True), + ("gate", "mark", True), + ("gate", "index", True), + ], +) def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name, stopadv, expected): - api_mock = MagicMock() - default_conf['trading_mode'] = TradingMode.FUTURES - default_conf['margin_mode'] = MarginMode.ISOLATED - type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True}) - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}.validate_stakecurrency') - mocker.patch(f'{EXMS}.validate_pricing') - default_conf['order_types'] = { - 'entry': 'limit', - 'exit': 'limit', - 'stoploss': 'limit', - 'stoploss_on_exchange': True, - 'stoploss_price_type': stopadv, + default_conf["trading_mode"] = TradingMode.FUTURES + default_conf["margin_mode"] = MarginMode.ISOLATED + type(api_mock).has = PropertyMock(return_value={"createMarketOrder": True}) + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}.validate_stakecurrency") + mocker.patch(f"{EXMS}.validate_pricing") + default_conf["order_types"] = { + "entry": "limit", + "exit": "limit", + "stoploss": "limit", + "stoploss_on_exchange": True, + "stoploss_price_type": stopadv, } - default_conf['exchange']['name'] = exchange_name + default_conf["exchange"]["name"] = exchange_name if expected: ExchangeResolver.load_exchange(default_conf) else: - with pytest.raises(OperationalException, - match=r'On exchange stoploss price type is not supported for .*'): + with pytest.raises( + OperationalException, match=r"On exchange stoploss price type is not supported for .*" + ): ExchangeResolver.load_exchange(default_conf) def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() - mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}.validate_pricing') - mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}.validate_pricing") + mocker.patch(f"{EXMS}.validate_stakecurrency") conf = copy.deepcopy(default_conf) Exchange(conf) @@ -1040,82 +1087,81 @@ def test_validate_order_types_not_in_config(default_conf, mocker): def test_validate_required_startup_candles(default_conf, mocker, caplog): api_mock = MagicMock() - mocker.patch(f'{EXMS}.name', PropertyMock(return_value='Binance')) + mocker.patch(f"{EXMS}.name", PropertyMock(return_value="Binance")) - mocker.patch(f'{EXMS}._init_ccxt', api_mock) - mocker.patch(f'{EXMS}.validate_timeframes') - mocker.patch(f'{EXMS}._load_async_markets') - mocker.patch(f'{EXMS}.validate_pairs') - mocker.patch(f'{EXMS}.validate_pricing') - mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f"{EXMS}._init_ccxt", api_mock) + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch(f"{EXMS}._load_async_markets") + mocker.patch(f"{EXMS}.validate_pairs") + mocker.patch(f"{EXMS}.validate_pricing") + mocker.patch(f"{EXMS}.validate_stakecurrency") - default_conf['startup_candle_count'] = 20 + default_conf["startup_candle_count"] = 20 ex = Exchange(default_conf) assert ex # assumption is that the exchange provides 500 candles per call.s - assert ex.validate_required_startup_candles(200, '5m') == 1 - assert ex.validate_required_startup_candles(499, '5m') == 1 - assert ex.validate_required_startup_candles(600, '5m') == 2 - assert ex.validate_required_startup_candles(501, '5m') == 2 - assert ex.validate_required_startup_candles(499, '5m') == 1 - assert ex.validate_required_startup_candles(1000, '5m') == 3 - assert ex.validate_required_startup_candles(2499, '5m') == 5 - assert log_has_re(r'Using 5 calls to get OHLCV. This.*', caplog) + assert ex.validate_required_startup_candles(200, "5m") == 1 + assert ex.validate_required_startup_candles(499, "5m") == 1 + assert ex.validate_required_startup_candles(600, "5m") == 2 + assert ex.validate_required_startup_candles(501, "5m") == 2 + assert ex.validate_required_startup_candles(499, "5m") == 1 + assert ex.validate_required_startup_candles(1000, "5m") == 3 + assert ex.validate_required_startup_candles(2499, "5m") == 5 + assert log_has_re(r"Using 5 calls to get OHLCV. This.*", caplog) - with pytest.raises(OperationalException, match=r'This strategy requires 2500.*'): - ex.validate_required_startup_candles(2500, '5m') + with pytest.raises(OperationalException, match=r"This strategy requires 2500.*"): + ex.validate_required_startup_candles(2500, "5m") # Ensure the same also happens on init - default_conf['startup_candle_count'] = 6000 - with pytest.raises(OperationalException, match=r'This strategy requires 6000.*'): + default_conf["startup_candle_count"] = 6000 + with pytest.raises(OperationalException, match=r"This strategy requires 6000.*"): Exchange(default_conf) # Emulate kraken mode - ex._ft_has['ohlcv_has_history'] = False - with pytest.raises(OperationalException, - match=r'This strategy requires 2500.*, ' - r'which is more than the amount.*'): - ex.validate_required_startup_candles(2500, '5m') + ex._ft_has["ohlcv_has_history"] = False + with pytest.raises( + OperationalException, + match=r"This strategy requires 2500.*, " r"which is more than the amount.*", + ): + ex.validate_required_startup_candles(2500, "5m") def test_exchange_has(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf) - assert not exchange.exchange_has('ASDFASDF') + assert not exchange.exchange_has("ASDFASDF") api_mock = MagicMock() - type(api_mock).has = PropertyMock(return_value={'deadbeef': True}) + 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}) + type(api_mock).has = PropertyMock(return_value={"deadbeef": False}) exchange = get_patched_exchange(mocker, default_conf, api_mock) assert not exchange.exchange_has("deadbeef") - exchange._ft_has['exchange_has_overrides'] = {'deadbeef': True} + exchange._ft_has["exchange_has_overrides"] = {"deadbeef": True} assert exchange.exchange_has("deadbeef") -@pytest.mark.parametrize("side,leverage", [ - ("buy", 1), - ("buy", 5), - ("sell", 1.0), - ("sell", 5.0), -]) +@pytest.mark.parametrize( + "side,leverage", + [ + ("buy", 1), + ("buy", 5), + ("sell", 1.0), + ("sell", 5.0), + ], +) @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverage): - default_conf['dry_run'] = True + default_conf["dry_run"] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) order = exchange.create_dry_run_order( - pair='ETH/BTC', - ordertype='limit', - side=side, - amount=1, - rate=200, - leverage=leverage + pair="ETH/BTC", ordertype="limit", side=side, amount=1, rate=200, leverage=leverage ) - assert 'id' in order - assert f'dry_run_{side}_' in order["id"] + assert "id" in order + assert f"dry_run_{side}_" in order["id"] assert order["side"] == side assert order["type"] == "limit" assert order["symbol"] == "ETH/BTC" @@ -1124,18 +1170,24 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverag assert order["cost"] == 1 * 200 -@pytest.mark.parametrize('side,is_short,order_reason', [ - ("buy", False, "entry"), - ("sell", False, "exit"), - ("buy", True, "exit"), - ("sell", True, "entry"), -]) -@pytest.mark.parametrize("order_type,price_side,fee", [ - ("limit", "same", 1.0), - ("limit", "other", 2.0), - ("market", "same", 2.0), - ("market", "other", 2.0), -]) +@pytest.mark.parametrize( + "side,is_short,order_reason", + [ + ("buy", False, "entry"), + ("sell", False, "exit"), + ("buy", True, "exit"), + ("sell", True, "entry"), + ], +) +@pytest.mark.parametrize( + "order_type,price_side,fee", + [ + ("limit", "same", 1.0), + ("limit", "other", 2.0), + ("market", "same", 2.0), + ("market", "other", 2.0), + ], +) def test_create_dry_run_order_fees( default_conf, mocker, @@ -1148,64 +1200,73 @@ def test_create_dry_run_order_fees( ): exchange = get_patched_exchange(mocker, default_conf) mocker.patch( - f'{EXMS}.get_fee', - side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0 + f"{EXMS}.get_fee", + side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == "taker" else 1.0, ) - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=price_side == 'other') + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=price_side == "other") order = exchange.create_dry_run_order( - pair='LTC/USDT', - ordertype=order_type, - side=side, - amount=10, - rate=2.0, - leverage=1.0 + pair="LTC/USDT", ordertype=order_type, side=side, amount=10, rate=2.0, leverage=1.0 ) - if price_side == 'other' or order_type == 'market': - assert order['fee']['rate'] == fee + if price_side == "other" or order_type == "market": + assert order["fee"]["rate"] == fee return else: - assert order['fee'] is None + assert order["fee"] is None - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=price_side != 'other') + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=price_side != "other") - order1 = exchange.fetch_dry_run_order(order['id']) - assert order1['fee']['rate'] == fee + order1 = exchange.fetch_dry_run_order(order["id"]) + assert order1["fee"]["rate"] == fee -@pytest.mark.parametrize("side,price,filled,converted", [ - # order_book_l2_usd spread: - # best ask: 25.566 - # best bid: 25.563 - ("buy", 25.563, False, False), - ("buy", 25.566, True, False), - ("sell", 25.566, False, False), - ("sell", 25.563, True, False), - ("buy", 29.563, True, True), - ("sell", 21.563, True, True), -]) +@pytest.mark.parametrize( + "side,price,filled,converted", + [ + # order_book_l2_usd spread: + # best ask: 25.566 + # best bid: 25.563 + ("buy", 25.563, False, False), + ("buy", 25.566, True, False), + ("sell", 25.566, False, False), + ("sell", 25.563, True, False), + ("buy", 29.563, True, True), + ("sell", 21.563, True, True), + ], +) @pytest.mark.parametrize("leverage", [1, 2, 5]) @pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, filled, caplog, - exchange_name, order_book_l2_usd, converted, leverage): - default_conf['dry_run'] = True +def test_create_dry_run_order_limit_fill( + default_conf, + mocker, + side, + price, + filled, + caplog, + exchange_name, + order_book_l2_usd, + converted, + leverage, +): + default_conf["dry_run"] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch.multiple(EXMS, - exchange_has=MagicMock(return_value=True), - fetch_l2_order_book=order_book_l2_usd, - ) + mocker.patch.multiple( + EXMS, + exchange_has=MagicMock(return_value=True), + fetch_l2_order_book=order_book_l2_usd, + ) order = exchange.create_order( - pair='LTC/USDT', - ordertype='limit', + pair="LTC/USDT", + ordertype="limit", side=side, amount=1, rate=price, leverage=leverage, ) assert order_book_l2_usd.call_count == 1 - assert 'id' in order - assert f'dry_run_{side}_' in order["id"] + assert "id" in order + assert f"dry_run_{side}_" in order["id"] assert order["side"] == side if not converted: assert order["average"] == price @@ -1217,110 +1278,108 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill assert log_has_re(r"Converted .* to market order.*", caplog) assert order["symbol"] == "LTC/USDT" - assert order['status'] == 'open' if not filled else 'closed' + assert order["status"] == "open" if not filled else "closed" order_book_l2_usd.reset_mock() # fetch order again... - order_closed = exchange.fetch_dry_run_order(order['id']) + order_closed = exchange.fetch_dry_run_order(order["id"]) assert order_book_l2_usd.call_count == (1 if not filled else 0) - assert order_closed['status'] == ('open' if not filled else 'closed') - assert order_closed['filled'] == (0 if not filled else 1) - assert order_closed['cost'] == 1 * order_closed['average'] + assert order_closed["status"] == ("open" if not filled else "closed") + assert order_closed["filled"] == (0 if not filled else 1) + assert order_closed["cost"] == 1 * order_closed["average"] order_book_l2_usd.reset_mock() # Empty orderbook test - mocker.patch(f'{EXMS}.fetch_l2_order_book', return_value={'asks': [], 'bids': []}) - exchange._dry_run_open_orders[order['id']]['status'] = 'open' - order_closed = exchange.fetch_dry_run_order(order['id']) + mocker.patch(f"{EXMS}.fetch_l2_order_book", return_value={"asks": [], "bids": []}) + exchange._dry_run_open_orders[order["id"]]["status"] = "open" + order_closed = exchange.fetch_dry_run_order(order["id"]) -@pytest.mark.parametrize("side,rate,amount,endprice", [ - # spread is 25.263-25.266 - ("buy", 25.564, 1, 25.566), - ("buy", 25.564, 100, 25.5672), # Requires interpolation - ("buy", 25.590, 100, 25.5672), # Price above spread ... average is lower - ("buy", 25.564, 1000, 25.575), # More than orderbook return - ("buy", 24.000, 100000, 25.200), # Run into max_slippage of 5% - ("sell", 25.564, 1, 25.563), - ("sell", 25.564, 100, 25.5625), # Requires interpolation - ("sell", 25.510, 100, 25.5625), # price below spread - average is higher - ("sell", 25.564, 1000, 25.5555), # More than orderbook return - ("sell", 27, 10000, 25.65), # max-slippage 5% -]) +@pytest.mark.parametrize( + "side,rate,amount,endprice", + [ + # spread is 25.263-25.266 + ("buy", 25.564, 1, 25.566), + ("buy", 25.564, 100, 25.5672), # Requires interpolation + ("buy", 25.590, 100, 25.5672), # Price above spread ... average is lower + ("buy", 25.564, 1000, 25.575), # More than orderbook return + ("buy", 24.000, 100000, 25.200), # Run into max_slippage of 5% + ("sell", 25.564, 1, 25.563), + ("sell", 25.564, 100, 25.5625), # Requires interpolation + ("sell", 25.510, 100, 25.5625), # price below spread - average is higher + ("sell", 25.564, 1000, 25.5555), # More than orderbook return + ("sell", 27, 10000, 25.65), # max-slippage 5% + ], +) @pytest.mark.parametrize("leverage", [1, 2, 5]) @pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amount, endprice, - exchange_name, order_book_l2_usd, leverage): - default_conf['dry_run'] = True +def test_create_dry_run_order_market_fill( + default_conf, mocker, side, rate, amount, endprice, exchange_name, order_book_l2_usd, leverage +): + default_conf["dry_run"] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch.multiple(EXMS, - exchange_has=MagicMock(return_value=True), - fetch_l2_order_book=order_book_l2_usd, - ) + mocker.patch.multiple( + EXMS, + exchange_has=MagicMock(return_value=True), + fetch_l2_order_book=order_book_l2_usd, + ) order = exchange.create_order( - pair='LTC/USDT', - ordertype='market', + pair="LTC/USDT", + ordertype="market", side=side, amount=amount, rate=rate, leverage=leverage, ) - assert 'id' in order - assert f'dry_run_{side}_' in order["id"] + assert "id" in order + assert f"dry_run_{side}_" in order["id"] assert order["side"] == side assert order["type"] == "market" assert order["symbol"] == "LTC/USDT" - assert order['status'] == 'closed' - assert order['filled'] == amount - assert order['amount'] == amount - assert pytest.approx(order['cost']) == amount * order['average'] + assert order["status"] == "closed" + assert order["filled"] == amount + assert order["amount"] == amount + assert pytest.approx(order["cost"]) == amount * order["average"] assert round(order["average"], 4) == round(endprice, 4) @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) -]) +@pytest.mark.parametrize( + "ordertype,rate,marketprice", + [ + ("market", None, None), + ("market", 200, True), + ("limit", 200, None), + ("stop_loss_limit", 200, None), + ], +) @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, exchange_name): api_mock = MagicMock() - order_id = f'test_prod_{side}_{randint(0, 10 ** 6)}' + order_id = f"test_prod_{side}_{randint(0, 10 ** 6)}" api_mock.options = {} if not marketprice else {"createMarketBuyOrderRequiresPrice": True} - api_mock.create_order = MagicMock(return_value={ - 'id': order_id, - 'info': { - 'foo': 'bar' - }, - 'symbol': 'XLTCUSDT', - 'amount': 1 - }) - default_conf['dry_run'] = False - default_conf['margin_mode'] = 'isolated' - mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) - mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) + api_mock.create_order = MagicMock( + return_value={"id": order_id, "info": {"foo": "bar"}, "symbol": "XLTCUSDT", "amount": 1} + ) + default_conf["dry_run"] = False + default_conf["margin_mode"] = "isolated" + mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange._set_leverage = MagicMock() exchange.set_margin_mode = MagicMock() order = exchange.create_order( - pair='XLTCUSDT', - ordertype=ordertype, - side=side, - amount=1, - rate=rate, - leverage=1.0 + pair="XLTCUSDT", ordertype=ordertype, side=side, amount=1, rate=rate, leverage=1.0 ) - assert 'id' in order - assert 'info' in order - assert order['id'] == order_id - assert order['amount'] == 1 - assert api_mock.create_order.call_args[0][0] == 'XLTCUSDT' + assert "id" in order + assert "info" in order + assert order["id"] == order_id + assert order["amount"] == 1 + assert api_mock.create_order.call_args[0][0] == "XLTCUSDT" 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 @@ -1328,76 +1387,79 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, assert exchange._set_leverage.call_count == 0 assert exchange.set_margin_mode.call_count == 0 - api_mock.create_order = MagicMock(return_value={ - 'id': order_id, - 'info': { - 'foo': 'bar' - }, - 'symbol': 'ADA/USDT:USDT', - 'amount': 1 - }) + api_mock.create_order = MagicMock( + return_value={ + "id": order_id, + "info": {"foo": "bar"}, + "symbol": "ADA/USDT:USDT", + "amount": 1, + } + ) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.trading_mode = TradingMode.FUTURES exchange._set_leverage = MagicMock() exchange.set_margin_mode = MagicMock() order = exchange.create_order( - pair='ADA/USDT:USDT', - ordertype=ordertype, - side=side, - amount=1, - rate=200, - leverage=3.0 + pair="ADA/USDT:USDT", ordertype=ordertype, side=side, amount=1, rate=200, leverage=3.0 ) - if exchange_name != 'okx': + if exchange_name != "okx": assert exchange._set_leverage.call_count == 1 assert exchange.set_margin_mode.call_count == 1 else: assert api_mock.set_leverage.call_count == 1 - assert order['amount'] == 0.01 + assert order["amount"] == 0.01 @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_buy_dry_run(default_conf, mocker, exchange_name): - default_conf['dry_run'] = True + default_conf["dry_run"] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - order = exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy", - amount=1, rate=200, leverage=1.0, - time_in_force='gtc') - assert 'id' in order - assert 'dry_run_buy_' in order['id'] + order = exchange.create_order( + pair="ETH/BTC", + ordertype="limit", + side="buy", + amount=1, + rate=200, + leverage=1.0, + time_in_force="gtc", + ) + assert "id" in order + assert "dry_run_buy_" in order["id"] @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_buy_prod(default_conf, mocker, exchange_name): api_mock = MagicMock() - order_id = f'test_prod_buy_{randint(0, 10 ** 6)}' - order_type = 'market' - time_in_force = 'gtc' + order_id = f"test_prod_buy_{randint(0, 10 ** 6)}" + order_type = "market" + time_in_force = "gtc" api_mock.options = {} - api_mock.create_order = MagicMock(return_value={ - 'id': order_id, - 'symbol': 'ETH/BTC', - 'info': { - 'foo': 'bar' - } - }) - default_conf['dry_run'] = False - mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) - mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) + api_mock.create_order = MagicMock( + return_value={"id": order_id, "symbol": "ETH/BTC", "info": {"foo": "bar"}} + ) + default_conf["dry_run"] = False + mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force) + order = exchange.create_order( + pair="ETH/BTC", + ordertype=order_type, + side="buy", + amount=1, + rate=200, + leverage=1.0, + 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 "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][2] == "buy" assert api_mock.create_order.call_args[0][3] == 1 if exchange._order_needs_price(order_type): assert api_mock.create_order.call_args[0][4] == 200 @@ -1405,19 +1467,19 @@ def test_buy_prod(default_conf, mocker, exchange_name): assert api_mock.create_order.call_args[0][4] is None api_mock.create_order.reset_mock() - order_type = 'limit' + order_type = "limit" order = exchange.create_order( - pair='ETH/BTC', + pair="ETH/BTC", ordertype=order_type, side="buy", amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force + time_in_force=time_in_force, ) - assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' + 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][2] == "buy" assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][4] == 200 @@ -1425,88 +1487,126 @@ def test_buy_prod(default_conf, mocker, exchange_name): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force) + exchange.create_order( + pair="ETH/BTC", + ordertype=order_type, + side="buy", + amount=1, + rate=200, + leverage=1.0, + 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.create_order(pair='ETH/BTC', ordertype='limit', side="buy", - amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force) + exchange.create_order( + pair="ETH/BTC", + ordertype="limit", + side="buy", + amount=1, + rate=200, + leverage=1.0, + 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.create_order(pair='ETH/BTC', ordertype='market', side="buy", - amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force) + exchange.create_order( + pair="ETH/BTC", + ordertype="market", + side="buy", + amount=1, + rate=200, + leverage=1.0, + time_in_force=time_in_force, + ) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force) + exchange.create_order( + pair="ETH/BTC", + ordertype=order_type, + side="buy", + amount=1, + rate=200, + leverage=1.0, + time_in_force=time_in_force, + ) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force) + exchange.create_order( + pair="ETH/BTC", + ordertype=order_type, + side="buy", + amount=1, + rate=200, + leverage=1.0, + time_in_force=time_in_force, + ) @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): api_mock = MagicMock() - order_id = f'test_prod_buy_{randint(0, 10 ** 6)}' + order_id = f"test_prod_buy_{randint(0, 10 ** 6)}" api_mock.options = {} - api_mock.create_order = MagicMock(return_value={ - 'id': order_id, - 'symbol': 'ETH/BTC', - 'info': { - 'foo': 'bar' - } - }) - default_conf['dry_run'] = False - mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) - mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) + api_mock.create_order = MagicMock( + return_value={"id": order_id, "symbol": "ETH/BTC", "info": {"foo": "bar"}} + ) + default_conf["dry_run"] = False + mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - order_type = 'limit' - time_in_force = 'ioc' + order_type = "limit" + time_in_force = "ioc" - order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force) + order = exchange.create_order( + pair="ETH/BTC", + ordertype=order_type, + side="buy", + amount=1, + rate=200, + leverage=1.0, + time_in_force=time_in_force, + ) - assert 'id' in order - assert 'info' in order - assert order['status'] == 'open' - assert order['id'] == order_id - assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' + assert "id" in order + assert "info" in order + assert order["status"] == "open" + 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][2] == "buy" 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.upper() - order_type = 'market' - time_in_force = 'ioc' + order_type = "market" + time_in_force = "ioc" - order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force) + order = exchange.create_order( + pair="ETH/BTC", + ordertype=order_type, + side="buy", + amount=1, + rate=200, + leverage=1.0, + 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 "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][2] == "buy" assert api_mock.create_order.call_args[0][3] == 1 if exchange._order_needs_price(order_type): assert api_mock.create_order.call_args[0][4] == 200 @@ -1517,43 +1617,41 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): def test_sell_dry_run(default_conf, mocker): - default_conf['dry_run'] = True + default_conf["dry_run"] = True exchange = get_patched_exchange(mocker, default_conf) - order = exchange.create_order(pair='ETH/BTC', ordertype='limit', - side="sell", amount=1, rate=200, leverage=1.0) - assert 'id' in order - assert 'dry_run_sell_' in order['id'] + order = exchange.create_order( + pair="ETH/BTC", ordertype="limit", side="sell", amount=1, rate=200, leverage=1.0 + ) + assert "id" in order + assert "dry_run_sell_" in order["id"] @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_sell_prod(default_conf, mocker, exchange_name): api_mock = MagicMock() - order_id = f'test_prod_sell_{randint(0, 10 ** 6)}' - order_type = 'market' + order_id = f"test_prod_sell_{randint(0, 10 ** 6)}" + order_type = "market" api_mock.options = {} - api_mock.create_order = MagicMock(return_value={ - 'id': order_id, - 'symbol': 'ETH/BTC', - 'info': { - 'foo': 'bar' - } - }) - default_conf['dry_run'] = False + api_mock.create_order = MagicMock( + return_value={"id": order_id, "symbol": "ETH/BTC", "info": {"foo": "bar"}} + ) + default_conf["dry_run"] = False - mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) - mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) + mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, - side="sell", amount=1, rate=200, leverage=1.0) + order = exchange.create_order( + pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 + ) - 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 "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][2] == "sell" assert api_mock.create_order.call_args[0][3] == 1 if exchange._order_needs_price(order_type): assert api_mock.create_order.call_args[0][4] == 200 @@ -1561,13 +1659,13 @@ def test_sell_prod(default_conf, mocker, exchange_name): assert api_mock.create_order.call_args[0][4] is None api_mock.create_order.reset_mock() - order_type = 'limit' - order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, - side="sell", amount=1, rate=200, - leverage=1.0) - assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' + order_type = "limit" + order = exchange.create_order( + pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 + ) + 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][2] == "sell" assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][4] == 200 @@ -1575,82 +1673,95 @@ def test_sell_prod(default_conf, mocker, exchange_name): with pytest.raises(InsufficientFundsError): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, - leverage=1.0) + exchange.create_order( + pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 + ) with pytest.raises(InvalidOrderException): 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.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200, - leverage=1.0) + exchange.create_order( + pair="ETH/BTC", ordertype="limit", side="sell", amount=1, rate=200, leverage=1.0 + ) # 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.create_order(pair='ETH/BTC', ordertype='market', side="sell", amount=1, rate=200, - leverage=1.0) + exchange.create_order( + pair="ETH/BTC", ordertype="market", side="sell", amount=1, rate=200, leverage=1.0 + ) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, - leverage=1.0) + exchange.create_order( + pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 + ) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, - leverage=1.0) + exchange.create_order( + pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 + ) @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): api_mock = MagicMock() - order_id = f'test_prod_sell_{randint(0, 10 ** 6)}' - api_mock.create_order = MagicMock(return_value={ - 'id': order_id, - 'symbol': 'ETH/BTC', - 'info': { - 'foo': 'bar' - } - }) + order_id = f"test_prod_sell_{randint(0, 10 ** 6)}" + api_mock.create_order = MagicMock( + return_value={"id": order_id, "symbol": "ETH/BTC", "info": {"foo": "bar"}} + ) api_mock.options = {} - default_conf['dry_run'] = False - mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) - mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y) + default_conf["dry_run"] = False + mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - order_type = 'limit' - time_in_force = 'ioc' + order_type = "limit" + time_in_force = "ioc" - order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", - amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force) + order = exchange.create_order( + pair="ETH/BTC", + ordertype=order_type, + side="sell", + amount=1, + rate=200, + leverage=1.0, + 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 "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][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.upper() - order_type = 'market' - time_in_force = 'IOC' - order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", - amount=1, rate=200, leverage=1.0, - time_in_force=time_in_force) + order_type = "market" + time_in_force = "IOC" + order = exchange.create_order( + pair="ETH/BTC", + ordertype=order_type, + side="sell", + amount=1, + rate=200, + leverage=1.0, + 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 "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][2] == "sell" assert api_mock.create_order.call_args[0][3] == 1 if exchange._order_needs_price(order_type): assert api_mock.create_order.call_args[0][4] == 200 @@ -1662,116 +1773,123 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): @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 - } + 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 + 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) 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 + 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, - "get_balances", "fetch_balance") + ccxt_exceptionhandlers( + mocker, default_conf, api_mock, exchange_name, "get_balances", "fetch_balance" + ) @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_positions(default_conf, mocker, exchange_name): - mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') + mocker.patch(f"{EXMS}.validate_trading_mode_and_margin_mode") api_mock = MagicMock() - api_mock.fetch_positions = MagicMock(return_value=[ - {'symbol': 'ETH/USDT:USDT', 'leverage': 5}, - {'symbol': 'XRP/USDT:USDT', 'leverage': 5}, - ]) + api_mock.fetch_positions = MagicMock( + return_value=[ + {"symbol": "ETH/USDT:USDT", "leverage": 5}, + {"symbol": "XRP/USDT:USDT", "leverage": 5}, + ] + ) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.fetch_positions() == [] - default_conf['dry_run'] = False - default_conf['trading_mode'] = 'futures' + default_conf["dry_run"] = False + default_conf["trading_mode"] = "futures" exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) res = exchange.fetch_positions() assert len(res) == 2 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - "fetch_positions", "fetch_positions") + ccxt_exceptionhandlers( + mocker, default_conf, api_mock, exchange_name, "fetch_positions", "fetch_positions" + ) @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_orders(default_conf, mocker, exchange_name, limit_order): - api_mock = MagicMock() - api_mock.fetch_orders = MagicMock(return_value=[ - limit_order['buy'], - limit_order['sell'], - ]) - api_mock.fetch_open_orders = MagicMock(return_value=[limit_order['buy']]) - api_mock.fetch_closed_orders = MagicMock(return_value=[limit_order['buy']]) + api_mock.fetch_orders = MagicMock( + return_value=[ + limit_order["buy"], + limit_order["sell"], + ] + ) + api_mock.fetch_open_orders = MagicMock(return_value=[limit_order["buy"]]) + api_mock.fetch_closed_orders = MagicMock(return_value=[limit_order["buy"]]) - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + mocker.patch(f"{EXMS}.exchange_has", return_value=True) start_time = datetime.now(timezone.utc) - timedelta(days=20) expected = 1 - if exchange_name == 'bybit': + if exchange_name == "bybit": expected = 3 exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # Not available in dry-run - assert exchange.fetch_orders('mocked', start_time) == [] + assert exchange.fetch_orders("mocked", start_time) == [] assert api_mock.fetch_orders.call_count == 0 - default_conf['dry_run'] = False + default_conf["dry_run"] = False exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - res = exchange.fetch_orders('mocked', start_time) + res = exchange.fetch_orders("mocked", start_time) assert api_mock.fetch_orders.call_count == expected assert api_mock.fetch_open_orders.call_count == 0 assert api_mock.fetch_closed_orders.call_count == 0 assert len(res) == 2 * expected - res = exchange.fetch_orders('mocked', start_time) + res = exchange.fetch_orders("mocked", start_time) api_mock.fetch_orders.reset_mock() def has_resp(_, endpoint): - if endpoint == 'fetchOrders': + if endpoint == "fetchOrders": return False - if endpoint == 'fetchClosedOrders': + if endpoint == "fetchClosedOrders": return True - if endpoint == 'fetchOpenOrders': + if endpoint == "fetchOpenOrders": return True - if exchange_name == 'okx': + if exchange_name == "okx": # Special OKX case is tested separately return - mocker.patch(f'{EXMS}.exchange_has', has_resp) + mocker.patch(f"{EXMS}.exchange_has", has_resp) # happy path without fetchOrders - exchange.fetch_orders('mocked', start_time) + exchange.fetch_orders("mocked", start_time) assert api_mock.fetch_orders.call_count == 0 assert api_mock.fetch_open_orders.call_count == expected assert api_mock.fetch_closed_orders.call_count == expected - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + mocker.patch(f"{EXMS}.exchange_has", return_value=True) - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - "fetch_orders", "fetch_orders", retries=1, - pair='mocked', since=start_time) + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "fetch_orders", + "fetch_orders", + retries=1, + pair="mocked", + since=start_time, + ) # Unhappy path - first fetch-orders call fails. api_mock.fetch_orders = MagicMock(side_effect=ccxt.NotSupported()) api_mock.fetch_open_orders.reset_mock() api_mock.fetch_closed_orders.reset_mock() - exchange.fetch_orders('mocked', start_time) + exchange.fetch_orders("mocked", start_time) assert api_mock.fetch_orders.call_count == expected assert api_mock.fetch_open_orders.call_count == expected @@ -1781,86 +1899,95 @@ def test_fetch_orders(default_conf, mocker, exchange_name, limit_order): def test_fetch_trading_fees(default_conf, mocker): api_mock = MagicMock() tick = { - '1INCH/USDT:USDT': { - 'info': {'user_id': '', - 'taker_fee': '0.0018', - 'maker_fee': '0.0018', - 'gt_discount': False, - 'gt_taker_fee': '0', - 'gt_maker_fee': '0', - 'loan_fee': '0.18', - 'point_type': '1', - 'futures_taker_fee': '0.0005', - 'futures_maker_fee': '0'}, - 'symbol': '1INCH/USDT:USDT', - 'maker': 0.0, - 'taker': 0.0005}, - 'ETH/USDT:USDT': { - 'info': {'user_id': '', - 'taker_fee': '0.0018', - 'maker_fee': '0.0018', - 'gt_discount': False, - 'gt_taker_fee': '0', - 'gt_maker_fee': '0', - 'loan_fee': '0.18', - 'point_type': '1', - 'futures_taker_fee': '0.0005', - 'futures_maker_fee': '0'}, - 'symbol': 'ETH/USDT:USDT', - 'maker': 0.0, - 'taker': 0.0005} + "1INCH/USDT:USDT": { + "info": { + "user_id": "", + "taker_fee": "0.0018", + "maker_fee": "0.0018", + "gt_discount": False, + "gt_taker_fee": "0", + "gt_maker_fee": "0", + "loan_fee": "0.18", + "point_type": "1", + "futures_taker_fee": "0.0005", + "futures_maker_fee": "0", + }, + "symbol": "1INCH/USDT:USDT", + "maker": 0.0, + "taker": 0.0005, + }, + "ETH/USDT:USDT": { + "info": { + "user_id": "", + "taker_fee": "0.0018", + "maker_fee": "0.0018", + "gt_discount": False, + "gt_taker_fee": "0", + "gt_maker_fee": "0", + "loan_fee": "0.18", + "point_type": "1", + "futures_taker_fee": "0.0005", + "futures_maker_fee": "0", + }, + "symbol": "ETH/USDT:USDT", + "maker": 0.0, + "taker": 0.0005, + }, } - exchange_name = 'gate' - default_conf['dry_run'] = False - default_conf['trading_mode'] = TradingMode.FUTURES - default_conf['margin_mode'] = MarginMode.ISOLATED + exchange_name = "gate" + default_conf["dry_run"] = False + default_conf["trading_mode"] = TradingMode.FUTURES + default_conf["margin_mode"] = MarginMode.ISOLATED api_mock.fetch_trading_fees = MagicMock(return_value=tick) - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + mocker.patch(f"{EXMS}.exchange_has", return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - assert '1INCH/USDT:USDT' in exchange._trading_fees - assert 'ETH/USDT:USDT' in exchange._trading_fees + assert "1INCH/USDT:USDT" in exchange._trading_fees + assert "ETH/USDT:USDT" in exchange._trading_fees assert api_mock.fetch_trading_fees.call_count == 1 api_mock.fetch_trading_fees.reset_mock() - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - "fetch_trading_fees", "fetch_trading_fees") + ccxt_exceptionhandlers( + mocker, default_conf, api_mock, exchange_name, "fetch_trading_fees", "fetch_trading_fees" + ) api_mock.fetch_trading_fees = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.fetch_trading_fees() - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + mocker.patch(f"{EXMS}.exchange_has", return_value=True) assert exchange.fetch_trading_fees() == {} def test_fetch_bids_asks(default_conf, mocker): api_mock = MagicMock() - tick = {'ETH/BTC': { - 'symbol': 'ETH/BTC', - 'bid': 0.5, - 'ask': 1, - 'last': 42, - }, 'BCH/BTC': { - 'symbol': 'BCH/BTC', - 'bid': 0.6, - 'ask': 0.5, - 'last': 41, + tick = { + "ETH/BTC": { + "symbol": "ETH/BTC", + "bid": 0.5, + "ask": 1, + "last": 42, + }, + "BCH/BTC": { + "symbol": "BCH/BTC", + "bid": 0.6, + "ask": 0.5, + "last": 41, + }, } - } - exchange_name = 'binance' + exchange_name = "binance" api_mock.fetch_bids_asks = MagicMock(return_value=tick) - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + mocker.patch(f"{EXMS}.exchange_has", return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # retrieve original ticker bidsasks = exchange.fetch_bids_asks() - assert 'ETH/BTC' in bidsasks - assert 'BCH/BTC' in bidsasks - assert bidsasks['ETH/BTC']['bid'] == 0.5 - assert bidsasks['ETH/BTC']['ask'] == 1 - assert bidsasks['BCH/BTC']['bid'] == 0.6 - assert bidsasks['BCH/BTC']['ask'] == 0.5 + assert "ETH/BTC" in bidsasks + assert "BCH/BTC" in bidsasks + assert bidsasks["ETH/BTC"]["bid"] == 0.5 + assert bidsasks["ETH/BTC"]["ask"] == 1 + assert bidsasks["BCH/BTC"]["bid"] == 0.6 + assert bidsasks["BCH/BTC"]["ask"] == 0.5 assert api_mock.fetch_bids_asks.call_count == 1 api_mock.fetch_bids_asks.reset_mock() @@ -1872,8 +1999,9 @@ def test_fetch_bids_asks(default_conf, mocker): tickers2 = exchange.fetch_bids_asks(cached=False) assert api_mock.fetch_bids_asks.call_count == 1 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - "fetch_bids_asks", "fetch_bids_asks") + ccxt_exceptionhandlers( + mocker, default_conf, api_mock, exchange_name, "fetch_bids_asks", "fetch_bids_asks" + ) with pytest.raises(OperationalException): api_mock.fetch_bids_asks = MagicMock(side_effect=ccxt.NotSupported("DeadBeef")) @@ -1883,38 +2011,40 @@ def test_fetch_bids_asks(default_conf, mocker): api_mock.fetch_bids_asks = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.fetch_bids_asks() - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + mocker.patch(f"{EXMS}.exchange_has", return_value=True) assert exchange.fetch_bids_asks() == {} @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_tickers(default_conf, mocker, exchange_name, caplog): api_mock = MagicMock() - tick = {'ETH/BTC': { - 'symbol': 'ETH/BTC', - 'bid': 0.5, - 'ask': 1, - 'last': 42, - }, 'BCH/BTC': { - 'symbol': 'BCH/BTC', - 'bid': 0.6, - 'ask': 0.5, - 'last': 41, + tick = { + "ETH/BTC": { + "symbol": "ETH/BTC", + "bid": 0.5, + "ask": 1, + "last": 42, + }, + "BCH/BTC": { + "symbol": "BCH/BTC", + "bid": 0.6, + "ask": 0.5, + "last": 41, + }, } - } - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + mocker.patch(f"{EXMS}.exchange_has", return_value=True) api_mock.fetch_tickers = MagicMock(return_value=tick) api_mock.fetch_bids_asks = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # 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 + 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 assert api_mock.fetch_tickers.call_count == 1 assert api_mock.fetch_bids_asks.call_count == 0 @@ -1929,8 +2059,9 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog): assert api_mock.fetch_tickers.call_count == 1 assert api_mock.fetch_bids_asks.call_count == 0 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - "get_tickers", "fetch_tickers") + ccxt_exceptionhandlers( + mocker, default_conf, api_mock, exchange_name, "get_tickers", "fetch_tickers" + ) with pytest.raises(OperationalException): api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported("DeadBeef")) @@ -1942,7 +2073,7 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog): exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) x = exchange.get_tickers() assert x == [] - assert log_has_re(r'Could not load tickers due to BadSymbol\..*SomeSymbol', caplog) + assert log_has_re(r"Could not load tickers due to BadSymbol\..*SomeSymbol", caplog) caplog.clear() api_mock.fetch_tickers = MagicMock(return_value={}) @@ -1951,18 +2082,18 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog): api_mock.fetch_tickers.reset_mock() api_mock.fetch_bids_asks.reset_mock() - default_conf['trading_mode'] = TradingMode.FUTURES - default_conf['margin_mode'] = MarginMode.ISOLATED - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + default_conf["trading_mode"] = TradingMode.FUTURES + default_conf["margin_mode"] = MarginMode.ISOLATED + mocker.patch(f"{EXMS}.exchange_has", return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_tickers() assert api_mock.fetch_tickers.call_count == 1 - assert api_mock.fetch_bids_asks.call_count == (1 if exchange_name == 'binance' else 0) + assert api_mock.fetch_bids_asks.call_count == (1 if exchange_name == "binance" else 0) api_mock.fetch_tickers.reset_mock() api_mock.fetch_bids_asks.reset_mock() - mocker.patch(f'{EXMS}.exchange_has', return_value=False) + mocker.patch(f"{EXMS}.exchange_has", return_value=False) assert exchange.get_tickers() == {} @@ -1970,80 +2101,86 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog): 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, + "symbol": "ETH/BTC", + "bid": 0.00001098, + "ask": 0.00001099, + "last": 0.0001, } api_mock.fetch_ticker = MagicMock(return_value=tick) - api_mock.markets = {'ETH/BTC': {'active': True}} + api_mock.markets = {"ETH/BTC": {"active": True}} exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # retrieve original ticker - ticker = exchange.fetch_ticker(pair='ETH/BTC') + ticker = exchange.fetch_ticker(pair="ETH/BTC") - assert ticker['bid'] == 0.00001098 - assert ticker['ask'] == 0.00001099 + assert ticker["bid"] == 0.00001098 + assert ticker["ask"] == 0.00001099 # change the ticker tick = { - 'symbol': 'ETH/BTC', - 'bid': 0.5, - 'ask': 1, - 'last': 42, + "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) # if not caching the result we should get the same ticker # if not fetching a new result we should get the cached ticker - ticker = exchange.fetch_ticker(pair='ETH/BTC') + ticker = exchange.fetch_ticker(pair="ETH/BTC") assert api_mock.fetch_ticker.call_count == 1 - assert ticker['bid'] == 0.5 - assert ticker['ask'] == 1 + assert ticker["bid"] == 0.5 + assert ticker["ask"] == 1 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - "fetch_ticker", "fetch_ticker", - pair='ETH/BTC') + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "fetch_ticker", + "fetch_ticker", + pair="ETH/BTC", + ) api_mock.fetch_ticker = MagicMock(return_value={}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.fetch_ticker(pair='ETH/BTC') + exchange.fetch_ticker(pair="ETH/BTC") - with pytest.raises(DependencyException, match=r'Pair XRP/ETH not available'): - exchange.fetch_ticker(pair='XRP/ETH') + with pytest.raises(DependencyException, match=r"Pair XRP/ETH not available"): + exchange.fetch_ticker(pair="XRP/ETH") @pytest.mark.parametrize("exchange_name", EXCHANGES) def test___now_is_time_to_refresh(default_conf, mocker, exchange_name, time_machine): exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - pair = 'BTC/USDT' + pair = "BTC/USDT" candle_type = CandleType.SPOT start_dt = datetime(2023, 12, 1, 0, 10, 0, tzinfo=timezone.utc) time_machine.move_to(start_dt, tick=False) - assert (pair, '5m', candle_type) not in exchange._pairs_last_refresh_time + assert (pair, "5m", candle_type) not in exchange._pairs_last_refresh_time # not refreshed yet - assert exchange._now_is_time_to_refresh(pair, '5m', candle_type) is True + assert exchange._now_is_time_to_refresh(pair, "5m", candle_type) is True last_closed_candle = (start_dt - timedelta(minutes=5)).timestamp() - exchange._pairs_last_refresh_time[(pair, '5m', candle_type)] = last_closed_candle + exchange._pairs_last_refresh_time[(pair, "5m", candle_type)] = last_closed_candle # next candle not closed yet time_machine.move_to(start_dt + timedelta(minutes=4, seconds=59), tick=False) - assert exchange._now_is_time_to_refresh(pair, '5m', candle_type) is False + assert exchange._now_is_time_to_refresh(pair, "5m", candle_type) is False # next candle closed time_machine.move_to(start_dt + timedelta(minutes=5, seconds=0), tick=False) - assert exchange._now_is_time_to_refresh(pair, '5m', candle_type) is True + assert exchange._now_is_time_to_refresh(pair, "5m", candle_type) is True # 1 second later (last_refresh_time didn't change) time_machine.move_to(start_dt + timedelta(minutes=5, seconds=1), tick=False) - assert exchange._now_is_time_to_refresh(pair, '5m', candle_type) is True + assert exchange._now_is_time_to_refresh(pair, "5m", candle_type) is True @pytest.mark.parametrize("exchange_name", EXCHANGES) -@pytest.mark.parametrize('candle_type', ['mark', '']) +@pytest.mark.parametrize("candle_type", ["mark", ""]) def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_type): exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) ohlcv = [ @@ -2056,7 +2193,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_ 5, # volume (in quote currency) ] ] - pair = 'ETH/BTC' + pair = "ETH/BTC" async def mock_candle_hist(pair, timeframe, candle_type, since_ms): return pair, timeframe, candle_type, ohlcv, True @@ -2064,18 +2201,15 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_ exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) # one_call calculation * 1.8 should do 2 calls - since = 5 * 60 * exchange.ohlcv_candle_limit('5m', candle_type) * 1.8 + since = 5 * 60 * exchange.ohlcv_candle_limit("5m", candle_type) * 1.8 ret = exchange.get_historic_ohlcv( - pair, - "5m", - dt_ts(dt_now() - timedelta(seconds=since)), - candle_type=candle_type + pair, "5m", dt_ts(dt_now() - timedelta(seconds=since)), candle_type=candle_type ) assert exchange._async_get_candle_history.call_count == 2 # Returns twice the above OHLCV data assert len(ret) == 2 - assert log_has_re(r'Downloaded data for .* with length .*\.', caplog) + assert log_has_re(r"Downloaded data for .* with length .*\.", caplog) caplog.clear() @@ -2084,17 +2218,14 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_ exchange._async_get_candle_history = MagicMock(side_effect=mock_get_candle_hist_error) ret = exchange.get_historic_ohlcv( - pair, - "5m", - dt_ts(dt_now() - timedelta(seconds=since)), - candle_type=candle_type + pair, "5m", dt_ts(dt_now() - timedelta(seconds=since)), candle_type=candle_type ) assert log_has_re(r"Async code raised an exception: .*", caplog) @pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) -@pytest.mark.parametrize('candle_type', [CandleType.MARK, CandleType.SPOT]) +@pytest.mark.parametrize("candle_type", [CandleType.MARK, CandleType.SPOT]) async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_type): ohlcv = [ [ @@ -2110,11 +2241,12 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_ # Monkey-patch async function exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) - pair = 'ETH/USDT' + pair = "ETH/USDT" respair, restf, _, res, _ = await exchange._async_get_historic_ohlcv( - pair, "5m", 1500000000000, candle_type=candle_type, is_new_pair=False) + pair, "5m", 1500000000000, candle_type=candle_type, is_new_pair=False + ) assert respair == pair - assert restf == '5m' + assert restf == "5m" # Call with very old timestamp - causes tons of requests assert exchange._api_async.fetch_ohlcv.call_count > 200 assert res[0] == ohlcv[0] @@ -2123,18 +2255,17 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_ end_ts = 1_500_500_000_000 start_ts = 1_500_000_000_000 respair, restf, _, res, _ = await exchange._async_get_historic_ohlcv( - pair, "5m", since_ms=start_ts, candle_type=candle_type, is_new_pair=False, - until_ms=end_ts - ) + pair, "5m", since_ms=start_ts, candle_type=candle_type, is_new_pair=False, until_ms=end_ts + ) # Required candles candles = (end_ts - start_ts) / 300_000 - exp = candles // exchange.ohlcv_candle_limit('5m', candle_type, start_ts) + 1 + exp = candles // exchange.ohlcv_candle_limit("5m", candle_type, start_ts) + 1 # Depending on the exchange, this should be called between 1 and 6 times. assert exchange._api_async.fetch_ohlcv.call_count == exp -@pytest.mark.parametrize('candle_type', [CandleType.FUTURES, CandleType.MARK, CandleType.SPOT]) +@pytest.mark.parametrize("candle_type", [CandleType.FUTURES, CandleType.MARK, CandleType.SPOT]) def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None: ohlcv = [ [ @@ -2152,14 +2283,14 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None 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) - pairs = [('IOTA/ETH', '5m', candle_type), ('XRP/ETH', '5m', candle_type)] + pairs = [("IOTA/ETH", "5m", candle_type), ("XRP/ETH", "5m", candle_type)] # empty dicts assert not exchange._klines res = exchange.refresh_latest_ohlcv(pairs, cache=False) @@ -2174,7 +2305,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None res = exchange.refresh_latest_ohlcv(pairs) assert len(res) == len(pairs) - assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog) + assert log_has(f"Refreshing candle (OHLCV) data for {len(pairs)} pairs", caplog) assert exchange._klines assert exchange._api_async.fetch_ohlcv.call_count == 4 exchange._api_async.fetch_ohlcv.reset_mock() @@ -2191,18 +2322,21 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None # test caching res = exchange.refresh_latest_ohlcv( - [('IOTA/ETH', '5m', candle_type), ('XRP/ETH', '5m', candle_type)]) + [("IOTA/ETH", "5m", candle_type), ("XRP/ETH", "5m", candle_type)] + ) assert len(res) == len(pairs) assert exchange._api_async.fetch_ohlcv.call_count == 0 - assert log_has(f"Using cached candle (OHLCV) data for {pairs[0][0]}, " - f"{pairs[0][1]}, {candle_type} ...", - caplog) + assert log_has( + f"Using cached candle (OHLCV) data for {pairs[0][0]}, " f"{pairs[0][1]}, {candle_type} ...", + caplog, + ) caplog.clear() # Reset refresh times - must do 2 call per pair as cache is expired exchange._pairs_last_refresh_time = {} res = exchange.refresh_latest_ohlcv( - [('IOTA/ETH', '5m', candle_type), ('XRP/ETH', '5m', candle_type)]) + [("IOTA/ETH", "5m", candle_type), ("XRP/ETH", "5m", candle_type)] + ) assert len(res) == len(pairs) assert exchange._api_async.fetch_ohlcv.call_count == 4 @@ -2212,9 +2346,10 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None exchange.required_candle_call_count = 1 pairlist = [ - ('IOTA/ETH', '5m', candle_type), - ('XRP/ETH', '5m', candle_type), - ('XRP/ETH', '1d', candle_type)] + ("IOTA/ETH", "5m", candle_type), + ("XRP/ETH", "5m", candle_type), + ("XRP/ETH", "1d", candle_type), + ] res = exchange.refresh_latest_ohlcv(pairlist, cache=False) assert len(res) == 3 assert exchange._api_async.fetch_ohlcv.call_count == 3 @@ -2228,19 +2363,19 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None caplog.clear() # Call with invalid timeframe - res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m', candle_type)], cache=False) + res = exchange.refresh_latest_ohlcv([("IOTA/ETH", "3m", candle_type)], cache=False) if candle_type != CandleType.MARK: assert not res assert len(res) == 0 - assert log_has_re(r'Cannot download \(IOTA\/ETH, 3m\).*', caplog) + assert log_has_re(r"Cannot download \(IOTA\/ETH, 3m\).*", caplog) else: assert len(res) == 1 -@pytest.mark.parametrize('candle_type', [CandleType.FUTURES, CandleType.MARK, CandleType.SPOT]) +@pytest.mark.parametrize("candle_type", [CandleType.FUTURES, CandleType.MARK, CandleType.SPOT]) def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_machine) -> None: start = datetime(2021, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc) - ohlcv = generate_test_data_raw('1h', 100, start.strftime('%Y-%m-%d')) + ohlcv = generate_test_data_raw("1h", 100, start.strftime("%Y-%m-%d")) time_machine.move_to(start + timedelta(hours=99, minutes=30)) exchange = get_patched_exchange(mocker, default_conf) @@ -2248,8 +2383,8 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert exchange._startup_candle_count == 0 exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) - pair1 = ('IOTA/ETH', '1h', candle_type) - pair2 = ('XRP/ETH', '1h', candle_type) + pair1 = ("IOTA/ETH", "1h", candle_type) + pair2 = ("XRP/ETH", "1h", candle_type) pairs = [pair1, pair2] # No caching @@ -2287,15 +2422,15 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert len(res) == 2 assert len(res[pair1]) == 99 assert len(res[pair2]) == 99 - assert res[pair2].at[0, 'open'] + assert res[pair2].at[0, "open"] assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-2][0] // 1000 refresh_pior = exchange._pairs_last_refresh_time[pair1] # New candle on exchange - return 100 candles - but skip one candle so we actually get 2 candles # in one go - new_startdate = (start + timedelta(hours=2)).strftime('%Y-%m-%d %H:%M') + new_startdate = (start + timedelta(hours=2)).strftime("%Y-%m-%d %H:%M") # mocker.patch(f"{EXMS}.ohlcv_candle_limit", return_value=100) - ohlcv = generate_test_data_raw('1h', 100, new_startdate) + ohlcv = generate_test_data_raw("1h", 100, new_startdate) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) res = exchange.refresh_latest_ohlcv(pairs) assert exchange._api_async.fetch_ohlcv.call_count == 2 @@ -2303,7 +2438,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert len(res[pair1]) == 100 assert len(res[pair2]) == 100 # Verify index starts at 0 - assert res[pair2].at[0, 'open'] + assert res[pair2].at[0, "open"] assert refresh_pior != exchange._pairs_last_refresh_time[pair1] assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-2][0] // 1000 @@ -2316,11 +2451,11 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert len(res) == 2 assert len(res[pair1]) == 100 assert len(res[pair2]) == 100 - assert res[pair2].at[0, 'open'] + assert res[pair2].at[0, "open"] # Move to distant future (so a 1 call would cause a hole in the data) time_machine.move_to(start + timedelta(hours=2000)) - ohlcv = generate_test_data_raw('1h', 100, start + timedelta(hours=1900)) + ohlcv = generate_test_data_raw("1h", 100, start + timedelta(hours=1900)) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) res = exchange.refresh_latest_ohlcv(pairs) @@ -2329,24 +2464,22 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach # Cache eviction - new data. assert len(res[pair1]) == 99 assert len(res[pair2]) == 99 - assert res[pair2].at[0, 'open'] + assert res[pair2].at[0, "open"] def test_refresh_ohlcv_with_cache(mocker, default_conf, time_machine) -> None: start = datetime(2021, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc) - ohlcv = generate_test_data_raw('1h', 100, start.strftime('%Y-%m-%d')) + ohlcv = generate_test_data_raw("1h", 100, start.strftime("%Y-%m-%d")) time_machine.move_to(start, tick=False) pairs = [ - ('ETH/BTC', '1d', CandleType.SPOT), - ('TKN/BTC', '1d', CandleType.SPOT), - ('LTC/BTC', '1d', CandleType.SPOT), - ('LTC/BTC', '5m', CandleType.SPOT), - ('LTC/BTC', '1h', CandleType.SPOT), + ("ETH/BTC", "1d", CandleType.SPOT), + ("TKN/BTC", "1d", CandleType.SPOT), + ("LTC/BTC", "1d", CandleType.SPOT), + ("LTC/BTC", "5m", CandleType.SPOT), + ("LTC/BTC", "1h", CandleType.SPOT), ] - ohlcv_data = { - p: ohlcv for p in pairs - } + ohlcv_data = {p: ohlcv for p in pairs} ohlcv_mock = mocker.patch(f"{EXMS}.refresh_latest_ohlcv", return_value=ohlcv_data) mocker.patch(f"{EXMS}.ohlcv_candle_limit", return_value=100) exchange = get_patched_exchange(mocker, default_conf) @@ -2410,7 +2543,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ # Monkey-patch async function exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) - pair = 'ETH/BTC' + pair = "ETH/BTC" res = await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT) assert type(res) is tuple assert len(res) == 5 @@ -2422,67 +2555,95 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ assert not log_has(f"Using cached candle (OHLCV) data for {pair} ...", caplog) exchange.close() # exchange = Exchange(default_conf) - await async_ccxt_exception(mocker, default_conf, MagicMock(), - "_async_get_candle_history", "fetch_ohlcv", - pair='ABCD/BTC', timeframe=default_conf['timeframe'], - candle_type=CandleType.SPOT) + await async_ccxt_exception( + mocker, + default_conf, + MagicMock(), + "_async_get_candle_history", + "fetch_ohlcv", + pair="ABCD/BTC", + timeframe=default_conf["timeframe"], + candle_type=CandleType.SPOT, + ) api_mock = MagicMock() - with pytest.raises(OperationalException, - match=r'Could not fetch historical candle \(OHLCV\) data.*'): + with pytest.raises( + OperationalException, match=r"Could not fetch historical candle \(OHLCV\) data.*" + ): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT, - dt_ts(dt_now() - timedelta(seconds=2000))) + await exchange._async_get_candle_history( + pair, "5m", CandleType.SPOT, dt_ts(dt_now() - timedelta(seconds=2000)) + ) exchange.close() - with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching ' - r'historical candle \(OHLCV\) data\..*'): + with pytest.raises( + OperationalException, + match=r"Exchange.* does not support fetching " r"historical candle \(OHLCV\) data\..*", + ): 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", CandleType.SPOT, - dt_ts(dt_now() - timedelta(seconds=2000))) + await exchange._async_get_candle_history( + pair, "5m", CandleType.SPOT, dt_ts(dt_now() - timedelta(seconds=2000)) + ) exchange.close() async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog): from freqtrade.exchange.common import _reset_logging_mixin + _reset_logging_mixin() caplog.set_level(logging.INFO) api_mock = MagicMock() - api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.DDoSProtection( - "kucoin GET https://openapi-v2.kucoin.com/api/v1/market/candles?" - "symbol=ETH-BTC&type=5min&startAt=1640268735&endAt=1640418735" - "429 Too Many Requests" '{"code":"429000","msg":"Too Many Requests"}')) + api_mock.fetch_ohlcv = MagicMock( + side_effect=ccxt.DDoSProtection( + "kucoin GET https://openapi-v2.kucoin.com/api/v1/market/candles?" + "symbol=ETH-BTC&type=5min&startAt=1640268735&endAt=1640418735" + "429 Too Many Requests" + '{"code":"429000","msg":"Too Many Requests"}' + ) + ) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kucoin") - mocker.patch(f'{EXMS}.name', PropertyMock(return_value='KuCoin')) + mocker.patch(f"{EXMS}.name", PropertyMock(return_value="KuCoin")) msg = "Kucoin 429 error, avoid triggering DDosProtection backoff delay" assert not num_log_has_re(msg, caplog) for _ in range(3): - with pytest.raises(DDosProtection, match=r'429 Too Many Requests'): + with pytest.raises(DDosProtection, match=r"429 Too Many Requests"): await exchange._async_get_candle_history( - "ETH/BTC", "5m", CandleType.SPOT, - since_ms=dt_ts(dt_now() - timedelta(seconds=2000)), count=3) + "ETH/BTC", + "5m", + CandleType.SPOT, + since_ms=dt_ts(dt_now() - timedelta(seconds=2000)), + count=3, + ) assert num_log_has_re(msg, caplog) == 3 caplog.clear() # Test regular non-kucoin message - api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.DDoSProtection( - "kucoin GET https://openapi-v2.kucoin.com/api/v1/market/candles?" - "symbol=ETH-BTC&type=5min&startAt=1640268735&endAt=1640418735" - "429 Too Many Requests" '{"code":"2222222","msg":"Too Many Requests"}')) + api_mock.fetch_ohlcv = MagicMock( + side_effect=ccxt.DDoSProtection( + "kucoin GET https://openapi-v2.kucoin.com/api/v1/market/candles?" + "symbol=ETH-BTC&type=5min&startAt=1640268735&endAt=1640418735" + "429 Too Many Requests" + '{"code":"2222222","msg":"Too Many Requests"}' + ) + ) - msg = r'_async_get_candle_history\(\) returned exception: .*' - msg2 = r'Applying DDosProtection backoff delay: .*' - with patch('freqtrade.exchange.common.asyncio.sleep', get_mock_coro(None)): + msg = r"_async_get_candle_history\(\) returned exception: .*" + msg2 = r"Applying DDosProtection backoff delay: .*" + with patch("freqtrade.exchange.common.asyncio.sleep", get_mock_coro(None)): for _ in range(3): - with pytest.raises(DDosProtection, match=r'429 Too Many Requests'): + with pytest.raises(DDosProtection, match=r"429 Too Many Requests"): await exchange._async_get_candle_history( - "ETH/BTC", "5m", CandleType.SPOT, - dt_ts(dt_now() - timedelta(seconds=2000)), count=3) + "ETH/BTC", + "5m", + CandleType.SPOT, + dt_ts(dt_now() - timedelta(seconds=2000)), + count=3, + ) # Expect the "returned exception" message 12 times (4 retries * 3 (loop)) assert num_log_has_re(msg, caplog) == 12 assert num_log_has_re(msg2, caplog) == 9 @@ -2490,7 +2651,7 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog): async def test__async_get_candle_history_empty(default_conf, mocker, caplog): - """ Test empty exchange result """ + """Test empty exchange result""" ohlcv = [] caplog.set_level(logging.DEBUG) @@ -2499,7 +2660,7 @@ async def test__async_get_candle_history_empty(default_conf, mocker, caplog): exchange._api_async.fetch_ohlcv = get_mock_coro([]) exchange = Exchange(default_conf) - pair = 'ETH/BTC' + pair = "ETH/BTC" res = await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT) assert type(res) is tuple assert len(res) == 5 @@ -2512,9 +2673,8 @@ async def test__async_get_candle_history_empty(default_conf, mocker, caplog): def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog): - async def mock_get_candle_hist(pair, *args, **kwargs): - if pair == 'ETH/BTC': + if pair == "ETH/BTC": return [[]] else: raise TypeError() @@ -2524,7 +2684,7 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog): # Monkey-patch async function with empty result exchange._api_async.fetch_ohlcv = MagicMock(side_effect=mock_get_candle_hist) - pairs = [("ETH/BTC", "5m", ''), ("XRP/BTC", "5m", '')] + 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 @@ -2562,29 +2722,30 @@ def test_get_next_limit_in_list(): @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 + 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) - assert 'bids' in order_book - assert 'asks' in order_book - assert len(order_book['bids']) == 10 - assert len(order_book['asks']) == 10 - assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC' + order_book = exchange.fetch_l2_order_book(pair="ETH/BTC", limit=10) + assert "bids" in order_book + assert "asks" in order_book + assert len(order_book["bids"]) == 10 + assert len(order_book["asks"]) == 10 + assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == "ETH/BTC" for val in [1, 5, 10, 12, 20, 50, 100]: api_mock.fetch_l2_order_book.reset_mock() - order_book = exchange.fetch_l2_order_book(pair='ETH/BTC', limit=val) - assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC' + order_book = exchange.fetch_l2_order_book(pair="ETH/BTC", limit=val) + assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == "ETH/BTC" # Not all exchanges support all limits for orderbook - if (not exchange.get_option('l2_limit_range') - or val in exchange.get_option('l2_limit_range')): + if not exchange.get_option("l2_limit_range") or val in exchange.get_option( + "l2_limit_range" + ): assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == val else: - next_limit = exchange.get_next_limit_in_list(val, exchange.get_option('l2_limit_range')) + next_limit = exchange.get_next_limit_in_list(val, exchange.get_option("l2_limit_range")) assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == next_limit @@ -2594,64 +2755,66 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name): 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) + exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50) 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) + exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50) 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) + exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50) @pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_entry_rate_data) -def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid, - last, last_ab, expected, time_machine) -> None: +def test_get_entry_rate( + mocker, default_conf, caplog, side, ask, bid, last, last_ab, expected, time_machine +) -> None: caplog.set_level(logging.DEBUG) start_dt = datetime(2023, 12, 1, 0, 10, 0, tzinfo=timezone.utc) time_machine.move_to(start_dt, tick=False) if last_ab is None: - del default_conf['entry_pricing']['price_last_balance'] + del default_conf["entry_pricing"]["price_last_balance"] else: - default_conf['entry_pricing']['price_last_balance'] = last_ab - default_conf['entry_pricing']['price_side'] = side + default_conf["entry_pricing"]["price_last_balance"] = last_ab + default_conf["entry_pricing"]["price_side"] = side exchange = get_patched_exchange(mocker, default_conf) - mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'last': last, 'bid': bid}) + mocker.patch(f"{EXMS}.fetch_ticker", return_value={"ask": ask, "last": last, "bid": bid}) log_msg = "Using cached entry rate for ETH/BTC." - assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected + assert exchange.get_rate("ETH/BTC", side="entry", is_short=False, refresh=True) == expected assert not log_has(log_msg, caplog) time_machine.move_to(start_dt + timedelta(minutes=4), tick=False) # Running a 2nd time without Refresh! caplog.clear() - assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=False) == expected + assert exchange.get_rate("ETH/BTC", side="entry", is_short=False, refresh=False) == expected assert log_has(log_msg, caplog) time_machine.move_to(start_dt + timedelta(minutes=6), tick=False) # Running a 2nd time - forces refresh due to ttl timeout caplog.clear() - assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=False) == expected + assert exchange.get_rate("ETH/BTC", side="entry", is_short=False, refresh=False) == expected assert not log_has(log_msg, caplog) # Running a 2nd time with Refresh on! caplog.clear() - assert exchange.get_rate('ETH/BTC', side="entry", is_short=False, refresh=True) == expected + assert exchange.get_rate("ETH/BTC", side="entry", is_short=False, refresh=True) == expected assert not log_has(log_msg, caplog) -@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_exit_rate_data) -def test_get_exit_rate(default_conf, mocker, caplog, side, bid, ask, - last, last_ab, expected, time_machine) -> None: +@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_exit_rate_data) +def test_get_exit_rate( + default_conf, mocker, caplog, side, bid, ask, last, last_ab, expected, time_machine +) -> None: caplog.set_level(logging.DEBUG) start_dt = datetime(2023, 12, 1, 0, 10, 0, tzinfo=timezone.utc) time_machine.move_to(start_dt, tick=False) - default_conf['exit_pricing']['price_side'] = side + default_conf["exit_pricing"]["price_side"] = side if last_ab is not None: - default_conf['exit_pricing']['price_last_balance'] = last_ab - mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'bid': bid, 'last': last}) + default_conf["exit_pricing"]["price_last_balance"] = last_ab + mocker.patch(f"{EXMS}.fetch_ticker", return_value={"ask": ask, "bid": bid, "last": last}) pair = "ETH/BTC" log_msg = "Using cached exit rate for ETH/BTC." @@ -2679,49 +2842,57 @@ def test_get_exit_rate(default_conf, mocker, caplog, side, bid, ask, assert not log_has(log_msg, caplog) -@pytest.mark.parametrize("entry,is_short,side,ask,bid,last,last_ab,expected", [ - ('entry', False, 'ask', None, 4, 4, 0, 4), # ask not available - ('entry', False, 'ask', None, None, 4, 0, 4), # ask not available - ('entry', False, 'bid', 6, None, 4, 0, 5), # bid not available - ('entry', False, 'bid', None, None, 4, 0, 5), # No rate available - ('exit', False, 'ask', None, 4, 4, 0, 4), # ask not available - ('exit', False, 'ask', None, None, 4, 0, 4), # ask not available - ('exit', False, 'bid', 6, None, 4, 0, 5), # bid not available - ('exit', False, 'bid', None, None, 4, 0, 5), # bid not available -]) -def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, is_short, ask, bid, - last, last_ab, expected) -> None: +@pytest.mark.parametrize( + "entry,is_short,side,ask,bid,last,last_ab,expected", + [ + ("entry", False, "ask", None, 4, 4, 0, 4), # ask not available + ("entry", False, "ask", None, None, 4, 0, 4), # ask not available + ("entry", False, "bid", 6, None, 4, 0, 5), # bid not available + ("entry", False, "bid", None, None, 4, 0, 5), # No rate available + ("exit", False, "ask", None, 4, 4, 0, 4), # ask not available + ("exit", False, "ask", None, None, 4, 0, 4), # ask not available + ("exit", False, "bid", 6, None, 4, 0, 5), # bid not available + ("exit", False, "bid", None, None, 4, 0, 5), # bid not available + ], +) +def test_get_ticker_rate_error( + mocker, entry, default_conf, caplog, side, is_short, ask, bid, last, last_ab, expected +) -> None: caplog.set_level(logging.DEBUG) - default_conf['entry_pricing']['price_last_balance'] = last_ab - default_conf['entry_pricing']['price_side'] = side - default_conf['exit_pricing']['price_side'] = side - default_conf['exit_pricing']['price_last_balance'] = last_ab + default_conf["entry_pricing"]["price_last_balance"] = last_ab + default_conf["entry_pricing"]["price_side"] = side + default_conf["exit_pricing"]["price_side"] = side + default_conf["exit_pricing"]["price_last_balance"] = last_ab exchange = get_patched_exchange(mocker, default_conf) - mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': ask, 'last': last, 'bid': bid}) + mocker.patch(f"{EXMS}.fetch_ticker", return_value={"ask": ask, "last": last, "bid": bid}) with pytest.raises(PricingError): - exchange.get_rate('ETH/BTC', refresh=True, side=entry, is_short=is_short) + exchange.get_rate("ETH/BTC", refresh=True, side=entry, is_short=is_short) -@pytest.mark.parametrize('is_short,side,expected', [ - (False, 'bid', 0.043936), # Value from order_book_l2 fixture - bids side - (False, 'ask', 0.043949), # Value from order_book_l2 fixture - asks side - (False, 'other', 0.043936), # Value from order_book_l2 fixture - bids side - (False, 'same', 0.043949), # Value from order_book_l2 fixture - asks side - (True, 'bid', 0.043936), # Value from order_book_l2 fixture - bids side - (True, 'ask', 0.043949), # Value from order_book_l2 fixture - asks side - (True, 'other', 0.043949), # Value from order_book_l2 fixture - asks side - (True, 'same', 0.043936), # Value from order_book_l2 fixture - bids side -]) +@pytest.mark.parametrize( + "is_short,side,expected", + [ + (False, "bid", 0.043936), # Value from order_book_l2 fixture - bids side + (False, "ask", 0.043949), # Value from order_book_l2 fixture - asks side + (False, "other", 0.043936), # Value from order_book_l2 fixture - bids side + (False, "same", 0.043949), # Value from order_book_l2 fixture - asks side + (True, "bid", 0.043936), # Value from order_book_l2 fixture - bids side + (True, "ask", 0.043949), # Value from order_book_l2 fixture - asks side + (True, "other", 0.043949), # Value from order_book_l2 fixture - asks side + (True, "same", 0.043936), # Value from order_book_l2 fixture - bids side + ], +) def test_get_exit_rate_orderbook( - default_conf, mocker, caplog, is_short, side, expected, order_book_l2): + default_conf, mocker, caplog, is_short, side, expected, order_book_l2 +): caplog.set_level(logging.DEBUG) # Test orderbook mode - default_conf['exit_pricing']['price_side'] = side - default_conf['exit_pricing']['use_order_book'] = True - default_conf['exit_pricing']['order_book_top'] = 1 + default_conf["exit_pricing"]["price_side"] = side + default_conf["exit_pricing"]["use_order_book"] = True + default_conf["exit_pricing"]["order_book_top"] = 1 pair = "ETH/BTC" - mocker.patch(f'{EXMS}.fetch_l2_order_book', order_book_l2) + mocker.patch(f"{EXMS}.fetch_l2_order_book", order_book_l2) exchange = get_patched_exchange(mocker, default_conf) rate = exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) assert not log_has("Using cached exit rate for ETH/BTC.", caplog) @@ -2734,98 +2905,119 @@ def test_get_exit_rate_orderbook( def test_get_exit_rate_orderbook_exception(default_conf, mocker, caplog): # Test orderbook mode - default_conf['exit_pricing']['price_side'] = 'ask' - default_conf['exit_pricing']['use_order_book'] = True - default_conf['exit_pricing']['order_book_top'] = 1 + default_conf["exit_pricing"]["price_side"] = "ask" + default_conf["exit_pricing"]["use_order_book"] = True + default_conf["exit_pricing"]["order_book_top"] = 1 pair = "ETH/BTC" # Test What happens if the exchange returns an empty orderbook. - mocker.patch(f'{EXMS}.fetch_l2_order_book', return_value={'bids': [[]], 'asks': [[]]}) + mocker.patch(f"{EXMS}.fetch_l2_order_book", return_value={"bids": [[]], "asks": [[]]}) exchange = get_patched_exchange(mocker, default_conf) with pytest.raises(PricingError): exchange.get_rate(pair, refresh=True, side="exit", is_short=False) - assert log_has_re(rf"{pair} - Exit Price at location 1 from orderbook " - rf"could not be determined\..*", - caplog) + assert log_has_re( + rf"{pair} - Exit Price at location 1 from orderbook " rf"could not be determined\..*", + caplog, + ) -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_get_exit_rate_exception(default_conf, mocker, is_short): # Ticker on one side can be empty in certain circumstances. - default_conf['exit_pricing']['price_side'] = 'ask' + default_conf["exit_pricing"]["price_side"] = "ask" pair = "ETH/BTC" - mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': None, 'bid': 0.12, 'last': None}) + mocker.patch(f"{EXMS}.fetch_ticker", return_value={"ask": None, "bid": 0.12, "last": None}) exchange = get_patched_exchange(mocker, default_conf) with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."): exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) - exchange._config['exit_pricing']['price_side'] = 'bid' + exchange._config["exit_pricing"]["price_side"] = "bid" assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.12 # Reverse sides - mocker.patch(f'{EXMS}.fetch_ticker', return_value={'ask': 0.13, 'bid': None, 'last': None}) + mocker.patch(f"{EXMS}.fetch_ticker", return_value={"ask": 0.13, "bid": None, "last": None}) with pytest.raises(PricingError, match=r"Exit-Rate for ETH/BTC was empty."): exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) - exchange._config['exit_pricing']['price_side'] = 'ask' + exchange._config["exit_pricing"]["price_side"] = "ask" assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.13 @pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_entry_rate_data) -@pytest.mark.parametrize("side2", ['bid', 'ask']) +@pytest.mark.parametrize("side2", ["bid", "ask"]) @pytest.mark.parametrize("use_order_book", [True, False]) -def test_get_rates_testing_entry(mocker, default_conf, caplog, side, ask, bid, - last, last_ab, expected, - side2, use_order_book, order_book_l2) -> None: +def test_get_rates_testing_entry( + mocker, + default_conf, + caplog, + side, + ask, + bid, + last, + last_ab, + expected, + side2, + use_order_book, + order_book_l2, +) -> None: caplog.set_level(logging.DEBUG) if last_ab is None: - del default_conf['entry_pricing']['price_last_balance'] + del default_conf["entry_pricing"]["price_last_balance"] else: - default_conf['entry_pricing']['price_last_balance'] = last_ab - default_conf['entry_pricing']['price_side'] = side - default_conf['exit_pricing']['price_side'] = side2 - default_conf['exit_pricing']['use_order_book'] = use_order_book + default_conf["entry_pricing"]["price_last_balance"] = last_ab + default_conf["entry_pricing"]["price_side"] = side + default_conf["exit_pricing"]["price_side"] = side2 + default_conf["exit_pricing"]["use_order_book"] = use_order_book api_mock = MagicMock() api_mock.fetch_l2_order_book = order_book_l2 - api_mock.fetch_ticker = MagicMock( - return_value={'ask': ask, 'last': last, 'bid': bid}) + api_mock.fetch_ticker = MagicMock(return_value={"ask": ask, "last": last, "bid": bid}) exchange = get_patched_exchange(mocker, default_conf, api_mock) - assert exchange.get_rates('ETH/BTC', refresh=True, is_short=False)[0] == expected + assert exchange.get_rates("ETH/BTC", refresh=True, is_short=False)[0] == expected assert not log_has("Using cached buy rate for ETH/BTC.", caplog) api_mock.fetch_l2_order_book.reset_mock() api_mock.fetch_ticker.reset_mock() - assert exchange.get_rates('ETH/BTC', refresh=False, is_short=False)[0] == expected + assert exchange.get_rates("ETH/BTC", refresh=False, is_short=False)[0] == expected assert log_has("Using cached buy rate for ETH/BTC.", caplog) assert api_mock.fetch_l2_order_book.call_count == 0 assert api_mock.fetch_ticker.call_count == 0 # Running a 2nd time with Refresh on! caplog.clear() - assert exchange.get_rates('ETH/BTC', refresh=True, is_short=False)[0] == expected + assert exchange.get_rates("ETH/BTC", refresh=True, is_short=False)[0] == expected assert not log_has("Using cached buy rate for ETH/BTC.", caplog) assert api_mock.fetch_l2_order_book.call_count == int(use_order_book) assert api_mock.fetch_ticker.call_count == 1 -@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_exit_rate_data) -@pytest.mark.parametrize("side2", ['bid', 'ask']) +@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_exit_rate_data) +@pytest.mark.parametrize("side2", ["bid", "ask"]) @pytest.mark.parametrize("use_order_book", [True, False]) -def test_get_rates_testing_exit(default_conf, mocker, caplog, side, bid, ask, - last, last_ab, expected, - side2, use_order_book, order_book_l2) -> None: +def test_get_rates_testing_exit( + default_conf, + mocker, + caplog, + side, + bid, + ask, + last, + last_ab, + expected, + side2, + use_order_book, + order_book_l2, +) -> None: caplog.set_level(logging.DEBUG) - default_conf['exit_pricing']['price_side'] = side + default_conf["exit_pricing"]["price_side"] = side if last_ab is not None: - default_conf['exit_pricing']['price_last_balance'] = last_ab + default_conf["exit_pricing"]["price_last_balance"] = last_ab - default_conf['entry_pricing']['price_side'] = side2 - default_conf['entry_pricing']['use_order_book'] = use_order_book + default_conf["entry_pricing"]["price_side"] = side2 + default_conf["entry_pricing"]["use_order_book"] = use_order_book api_mock = MagicMock() api_mock.fetch_l2_order_book = order_book_l2 - api_mock.fetch_ticker = MagicMock( - return_value={'ask': ask, 'last': last, 'bid': bid}) + api_mock.fetch_ticker = MagicMock(return_value={"ask": ask, "last": last, "bid": bid}) exchange = get_patched_exchange(mocker, default_conf, api_mock) pair = "ETH/BTC" @@ -2865,15 +3057,16 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na [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] + [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) - sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data)) + 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'], CandleType.SPOT) - assert res[0] == 'ETH/BTC' + "ETH/BTC", default_conf["timeframe"], CandleType.SPOT + ) + assert res[0] == "ETH/BTC" res_ohlcv = res[3] assert sort_mock.call_count == 1 @@ -2902,16 +3095,17 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na [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] + [1527830400000, 0.07671, 0.07674399, 0.07629216, 0.07655213, 2.31452783], ] exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) # Reset sort mock - sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data)) + 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'], CandleType.SPOT) - assert res[0] == 'ETH/BTC' - assert res[1] == default_conf['timeframe'] + "ETH/BTC", default_conf["timeframe"], CandleType.SPOT + ) + assert res[0] == "ETH/BTC" + assert res[1] == default_conf["timeframe"] res_ohlcv = res[3] # Sorted not called again - data is already in order assert sort_mock.call_count == 0 @@ -2931,63 +3125,72 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na @pytest.mark.parametrize("exchange_name", EXCHANGES) -async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name, - fetch_trades_result): +async def test__async_fetch_trades( + default_conf, mocker, caplog, exchange_name, fetch_trades_result +): 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) - pair = 'ETH/BTC' + pair = "ETH/BTC" res, pagid = await exchange._async_fetch_trades(pair, since=None, params=None) assert isinstance(res, list) assert isinstance(res[0], list) assert isinstance(res[1], list) - if exchange._trades_pagination == 'id': - if exchange_name == 'kraken': + if exchange._trades_pagination == "id": + if exchange_name == "kraken": assert pagid == 1565798399872512133 else: - assert pagid == '126181333' + assert pagid == "126181333" else: assert pagid == 1565798399872 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]["limit"] == 1000 assert log_has_re(f"Fetching trades for pair {pair}, since .*", caplog) caplog.clear() exchange._api_async.fetch_trades.reset_mock() - res, pagid = await exchange._async_fetch_trades(pair, since=None, params={'from': '123'}) + res, pagid = 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 exchange._api_async.fetch_trades.call_args[1]["limit"] == 1000 + assert exchange._api_async.fetch_trades.call_args[1]["params"] == {"from": "123"} - if exchange._trades_pagination == 'id': - if exchange_name == 'kraken': + if exchange._trades_pagination == "id": + if exchange_name == "kraken": assert pagid == 1565798399872512133 else: - assert pagid == '126181333' + assert pagid == "126181333" else: assert pagid == 1565798399872 assert log_has_re(f"Fetching trades for pair {pair}, params: .*", caplog) exchange.close() - await async_ccxt_exception(mocker, default_conf, MagicMock(), - "_async_fetch_trades", "fetch_trades", - pair='ABCD/BTC', since=None) + 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*'): + 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=dt_ts(dt_now() - timedelta(seconds=2000))) exchange.close() - with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching ' - r'historical trade data\..*'): + 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=dt_ts(dt_now() - timedelta(seconds=2000))) @@ -2995,37 +3198,44 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name, @pytest.mark.parametrize("exchange_name", EXCHANGES) -async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, exchange_name, - fetch_trades_result): +async def test__async_fetch_trades_contract_size( + default_conf, mocker, caplog, exchange_name, fetch_trades_result +): caplog.set_level(logging.DEBUG) - default_conf['margin_mode'] = 'isolated' - default_conf['trading_mode'] = 'futures' + default_conf["margin_mode"] = "isolated" + default_conf["trading_mode"] = "futures" exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) # Monkey-patch async function - exchange._api_async.fetch_trades = get_mock_coro([ - {'info': {'a': 126181333, - 'p': '0.01952600', - 'q': '0.01200000', - 'f': 138604158, - 'l': 138604158, - 'T': 1565798399872, - 'm': True, - 'M': True}, - 'timestamp': 1565798399872, - 'datetime': '2019-08-14T15:59:59.872Z', - 'symbol': 'ETH/USDT:USDT', - 'id': '126181383', - 'order': None, - 'type': None, - 'takerOrMaker': None, - 'side': 'sell', - 'price': 2.0, - 'amount': 30.0, - 'cost': 60.0, - 'fee': None}] + exchange._api_async.fetch_trades = get_mock_coro( + [ + { + "info": { + "a": 126181333, + "p": "0.01952600", + "q": "0.01200000", + "f": 138604158, + "l": 138604158, + "T": 1565798399872, + "m": True, + "M": True, + }, + "timestamp": 1565798399872, + "datetime": "2019-08-14T15:59:59.872Z", + "symbol": "ETH/USDT:USDT", + "id": "126181383", + "order": None, + "type": None, + "takerOrMaker": None, + "side": "sell", + "price": 2.0, + "amount": 30.0, + "cost": 60.0, + "fee": None, + } + ] ) - pair = 'ETH/USDT:USDT' + pair = "ETH/USDT:USDT" res, pagid = await exchange._async_fetch_trades(pair, since=None, params=None) assert res[0][5] == 300 assert pagid is not None @@ -3034,90 +3244,99 @@ async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, e @pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) -async def test__async_get_trade_history_id(default_conf, mocker, exchange_name, - fetch_trades_result): - +async def test__async_get_trade_history_id( + default_conf, mocker, exchange_name, fetch_trades_result +): exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - if exchange._trades_pagination != 'id': + if exchange._trades_pagination != "id": exchange.close() pytest.skip("Exchange does not support pagination by trade id") pagination_arg = exchange._trades_pagination_arg async def mock_get_trade_hist(pair, *args, **kwargs): - if 'since' in kwargs: + if "since" in kwargs: # Return first 3 return fetch_trades_result[:-2] - elif kwargs.get('params', {}).get(pagination_arg) in ( - fetch_trades_result[-3]['id'], 1565798399752): + elif kwargs.get("params", {}).get(pagination_arg) in ( + fetch_trades_result[-3]["id"], + 1565798399752, + ): # Return 2 return fetch_trades_result[-3:-1] else: # Return last 2 return fetch_trades_result[-2:] + # Monkey-patch async function exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist) - pair = 'ETH/BTC' - ret = await exchange._async_get_trade_history_id(pair, - since=fetch_trades_result[0]['timestamp'], - until=fetch_trades_result[-1]['timestamp'] - 1) + pair = "ETH/BTC" + ret = await exchange._async_get_trade_history_id( + pair, + since=fetch_trades_result[0]["timestamp"], + until=fetch_trades_result[-1]["timestamp"] - 1, + ) assert isinstance(ret, tuple) assert ret[0] == pair assert isinstance(ret[1], list) - if exchange_name != 'kraken': + if exchange_name != "kraken": assert len(ret[1]) == len(fetch_trades_result) assert exchange._api_async.fetch_trades.call_count == 3 fetch_trades_cal = exchange._api_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'] == fetch_trades_result[0]['timestamp'] + assert fetch_trades_cal[0][1]["since"] == fetch_trades_result[0]["timestamp"] # 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'] + assert "params" in fetch_trades_cal[1][1] + assert exchange._ft_has["trades_pagination_arg"] in fetch_trades_cal[1][1]["params"] -@pytest.mark.parametrize('trade_id, expected', [ - ('1234', True), - ('170544369512007228', True), - ('1705443695120072285', True), - ('170544369512007228555', True), -]) +@pytest.mark.parametrize( + "trade_id, expected", + [ + ("1234", True), + ("170544369512007228", True), + ("1705443695120072285", True), + ("170544369512007228555", True), + ], +) @pytest.mark.parametrize("exchange_name", EXCHANGES) def test__valid_trade_pagination_id(mocker, default_conf_usdt, exchange_name, trade_id, expected): - if exchange_name == 'kraken': + if exchange_name == "kraken": pytest.skip("Kraken has a different pagination id format, and an explicit test.") exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) - assert exchange._valid_trade_pagination_id('XRP/USDT', trade_id) == expected + assert exchange._valid_trade_pagination_id("XRP/USDT", trade_id) == expected @pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) -async def test__async_get_trade_history_time(default_conf, mocker, caplog, exchange_name, - fetch_trades_result): - +async def test__async_get_trade_history_time( + default_conf, mocker, caplog, exchange_name, fetch_trades_result +): caplog.set_level(logging.DEBUG) async def mock_get_trade_hist(pair, *args, **kwargs): - if kwargs['since'] == fetch_trades_result[0]['timestamp']: + if kwargs["since"] == fetch_trades_result[0]["timestamp"]: return fetch_trades_result[:-1] else: return fetch_trades_result[-1:] caplog.set_level(logging.DEBUG) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - if exchange._trades_pagination != 'time': + if exchange._trades_pagination != "time": exchange.close() pytest.skip("Exchange does not support pagination by timestamp") # Monkey-patch async function exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist) - pair = 'ETH/BTC' + pair = "ETH/BTC" ret = await exchange._async_get_trade_history_time( pair, - since=fetch_trades_result[0]['timestamp'], - until=fetch_trades_result[-1]['timestamp'] - 1) + since=fetch_trades_result[0]["timestamp"], + until=fetch_trades_result[-1]["timestamp"] - 1, + ) assert isinstance(ret, tuple) assert ret[0] == pair assert isinstance(ret[1], list) @@ -3126,23 +3345,23 @@ async def test__async_get_trade_history_time(default_conf, mocker, caplog, excha fetch_trades_cal = exchange._api_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'] == fetch_trades_result[0]['timestamp'] + assert fetch_trades_cal[0][1]["since"] == fetch_trades_result[0]["timestamp"] # 2nd call assert fetch_trades_cal[1][0][0] == pair - assert fetch_trades_cal[1][1]['since'] == fetch_trades_result[-2]['timestamp'] + assert fetch_trades_cal[1][1]["since"] == fetch_trades_result[-2]["timestamp"] 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): - +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]: + if kwargs["since"] == trades_history[0][0]: return trades_history[:-1], trades_history[:-1][-1][0] else: return [], None @@ -3151,9 +3370,10 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog, 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) + pair = "ETH/BTC" + ret = await exchange._async_get_trade_history_time( + pair, since=trades_history[0][0], until=trades_history[-1][0] - 1 + ) assert isinstance(ret, tuple) assert ret[0] == pair assert isinstance(ret[1], list) @@ -3162,24 +3382,32 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog, 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] + assert fetch_trades_cal[0][1]["since"] == trades_history[0][0] @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades_history): - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + mocker.patch(f"{EXMS}.exchange_has", return_value=True) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - pair = 'ETH/BTC' + 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]) + ret = exchange.get_historic_trades( + pair, since=trades_history[0][0], until=trades_history[-1][0] + ) # 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 ( + 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 @@ -3187,91 +3415,101 @@ def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades @pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange_name, - trades_history): - mocker.patch(f'{EXMS}.exchange_has', return_value=False) +def test_get_historic_trades_notsupported( + default_conf, mocker, caplog, exchange_name, trades_history +): + mocker.patch(f"{EXMS}.exchange_has", return_value=False) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - pair = 'ETH/BTC' + pair = "ETH/BTC" - with pytest.raises(OperationalException, - match="This exchange does not support downloading Trades."): - exchange.get_historic_trades(pair, since=trades_history[0][0], - until=trades_history[-1][0]) + with pytest.raises( + OperationalException, match="This exchange does not support downloading Trades." + ): + exchange.get_historic_trades(pair, since=trades_history[0][0], until=trades_history[-1][0]) @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_cancel_order_dry_run(default_conf, mocker, exchange_name): - default_conf['dry_run'] = True + default_conf["dry_run"] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) - assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} - assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {} + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=True) + assert exchange.cancel_order(order_id="123", pair="TKN/BTC") == {} + assert exchange.cancel_stoploss_order(order_id="123", pair="TKN/BTC") == {} order = exchange.create_order( - pair='ETH/BTC', - ordertype='limit', - side='buy', + pair="ETH/BTC", + ordertype="limit", + side="buy", amount=5, rate=0.55, - time_in_force='gtc', + time_in_force="gtc", leverage=1.0, ) - cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC') - assert order['id'] == cancel_order['id'] - assert order['amount'] == cancel_order['amount'] - assert order['symbol'] == cancel_order['symbol'] - assert cancel_order['status'] == 'canceled' + cancel_order = exchange.cancel_order(order_id=order["id"], pair="ETH/BTC") + assert order["id"] == cancel_order["id"] + assert order["amount"] == cancel_order["amount"] + assert order["symbol"] == cancel_order["symbol"] + assert cancel_order["status"] == "canceled" @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), -]) +@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 @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), - ({'status': 'canceled', 'amount': None, 'fee': None}, False), - ({'status': 'canceled', 'filled': None, 'amount': None, 'fee': None}, False), - -]) +@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), + ({"status": "canceled", "amount": None, "fee": None}, False), + ({"status": "canceled", "filled": None, "amount": None, "fee": None}, 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 @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 +@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 mocker.patch(f"{EXMS}.exchange_has", return_value=True) 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) + 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 @@ -3279,136 +3517,145 @@ def test_cancel_order_with_result(default_conf, mocker, exchange_name, corder, @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_cancel_order_with_result_error(default_conf, mocker, exchange_name, caplog): - default_conf['dry_run'] = False + default_conf["dry_run"] = False mocker.patch(f"{EXMS}.exchange_has", return_value=True) 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) + res = exchange.cancel_order_with_result("1234", "ETH/BTC", 1541) assert isinstance(res, dict) assert log_has("Could not cancel order 1234 for ETH/BTC.", caplog) assert log_has("Could not fetch cancelled order 1234.", caplog) - assert res['amount'] == 1541 + assert res["amount"] == 1541 # 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): - default_conf['dry_run'] = False + default_conf["dry_run"] = False api_mock = MagicMock() - api_mock.cancel_order = MagicMock(return_value={'id': '123'}) + api_mock.cancel_order = MagicMock(return_value={"id": "123"}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == {'id': '123'} + assert exchange.cancel_order(order_id="_", pair="TKN/BTC") == {"id": "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_order(order_id='_', pair='TKN/BTC') + exchange.cancel_order(order_id="_", pair="TKN/BTC") assert api_mock.cancel_order.call_count == 1 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - "cancel_order", "cancel_order", - order_id='_', pair='TKN/BTC') + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "cancel_order", + "cancel_order", + order_id="_", + pair="TKN/BTC", + ) @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_cancel_stoploss_order(default_conf, mocker, exchange_name): - default_conf['dry_run'] = False + default_conf["dry_run"] = False api_mock = MagicMock() - api_mock.cancel_order = MagicMock(return_value={'id': '123'}) + api_mock.cancel_order = MagicMock(return_value={"id": "123"}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - assert exchange.cancel_stoploss_order(order_id='_', pair='TKN/BTC') == {'id': '123'} + assert exchange.cancel_stoploss_order(order_id="_", pair="TKN/BTC") == {"id": "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') + 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') + 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_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): - default_conf['dry_run'] = False - mock_prefix = 'freqtrade.exchange.gate.Gate' - if exchange_name == 'okx': - mock_prefix = 'freqtrade.exchange.okx.Okx' - mocker.patch(f'{EXMS}.fetch_stoploss_order', return_value={'for': 123}) - mocker.patch(f'{mock_prefix}.fetch_stoploss_order', return_value={'for': 123}) + default_conf["dry_run"] = False + mock_prefix = "freqtrade.exchange.gate.Gate" + if exchange_name == "okx": + mock_prefix = "freqtrade.exchange.okx.Okx" + mocker.patch(f"{EXMS}.fetch_stoploss_order", return_value={"for": 123}) + mocker.patch(f"{mock_prefix}.fetch_stoploss_order", return_value={"for": 123}) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - res = {'fee': {}, 'status': 'canceled', 'amount': 1234} - mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=res) - mocker.patch(f'{mock_prefix}.cancel_stoploss_order', return_value=res) - co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) + res = {"fee": {}, "status": "canceled", "amount": 1234} + mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value=res) + mocker.patch(f"{mock_prefix}.cancel_stoploss_order", return_value=res) + co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555) assert co == res - mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value='canceled') - mocker.patch(f'{mock_prefix}.cancel_stoploss_order', return_value='canceled') + mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value="canceled") + mocker.patch(f"{mock_prefix}.cancel_stoploss_order", return_value="canceled") # Fall back to fetch_stoploss_order - co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) - assert co == {'for': 123} + co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555) + assert co == {"for": 123} exc = InvalidOrderException("") - mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=exc) - mocker.patch(f'{mock_prefix}.fetch_stoploss_order', side_effect=exc) - co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) - assert co['amount'] == 555 - assert co == {'id': '_', 'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}} + mocker.patch(f"{EXMS}.fetch_stoploss_order", side_effect=exc) + mocker.patch(f"{mock_prefix}.fetch_stoploss_order", side_effect=exc) + co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555) + assert co["amount"] == 555 + assert co == {"id": "_", "fee": {}, "status": "canceled", "amount": 555, "info": {}} with pytest.raises(InvalidOrderException): exc = InvalidOrderException("Did not find order") - mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=exc) - mocker.patch(f'{mock_prefix}.cancel_stoploss_order', side_effect=exc) + mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=exc) + mocker.patch(f"{mock_prefix}.cancel_stoploss_order", side_effect=exc) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123) + exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=123) @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_order(default_conf, mocker, exchange_name, caplog): - default_conf['dry_run'] = True - default_conf['exchange']['log_responses'] = True + default_conf["dry_run"] = True + default_conf["exchange"]["log_responses"] = True order = MagicMock() order.myid = 123 - order.symbol = 'TKN/BTC' + order.symbol = "TKN/BTC" mocker.patch(f"{EXMS}.exchange_has", return_value=True) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - exchange._dry_run_open_orders['X'] = order - assert exchange.fetch_order('X', 'TKN/BTC').myid == 123 + exchange._dry_run_open_orders["X"] = order + assert exchange.fetch_order("X", "TKN/BTC").myid == 123 - with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'): - exchange.fetch_order('Y', 'TKN/BTC') + with pytest.raises(InvalidOrderException, match=r"Tried to get an invalid dry-run-order.*"): + exchange.fetch_order("Y", "TKN/BTC") - default_conf['dry_run'] = False + default_conf["dry_run"] = False api_mock = MagicMock() - api_mock.fetch_order = MagicMock(return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) + api_mock.fetch_order = MagicMock(return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - assert exchange.fetch_order( - 'X', 'TKN/BTC') == {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'} - assert log_has( - ("API fetch_order: {\'id\': \'123\', \'amount\': 2, \'symbol\': \'TKN/BTC\'}" - ), - caplog - ) + assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"} + assert log_has(("API fetch_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog) 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') + exchange.fetch_order(order_id="_", pair="TKN/BTC") assert api_mock.fetch_order.call_count == 1 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 patch("freqtrade.exchange.common.time.sleep") as tm: with pytest.raises(InvalidOrderException): - exchange.fetch_order(order_id='_', pair='TKN/BTC') + 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 @@ -3418,43 +3665,50 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog): assert tm.call_args_list[3][0][0] == 10 assert api_mock.fetch_order.call_count == API_FETCH_ORDER_RETRY_COUNT + 1 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - 'fetch_order', 'fetch_order', retries=API_FETCH_ORDER_RETRY_COUNT + 1, - order_id='_', pair='TKN/BTC') + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "fetch_order", + "fetch_order", + retries=API_FETCH_ORDER_RETRY_COUNT + 1, + order_id="_", + pair="TKN/BTC", + ) @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): - default_conf['dry_run'] = True - default_conf['exchange']['log_responses'] = True + default_conf["dry_run"] = True + default_conf["exchange"]["log_responses"] = True order = MagicMock() order.myid = 123 - order.symbol = 'TKN/BTC' + order.symbol = "TKN/BTC" exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch(f'{EXMS}.exchange_has', return_value=False) - exchange._dry_run_open_orders['X'] = order + mocker.patch(f"{EXMS}.exchange_has", return_value=False) + exchange._dry_run_open_orders["X"] = order # Dry run - regular fetch_order behavior - assert exchange.fetch_order('X', 'TKN/BTC').myid == 123 + assert exchange.fetch_order("X", "TKN/BTC").myid == 123 - with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'): - exchange.fetch_order('Y', 'TKN/BTC') + with pytest.raises(InvalidOrderException, match=r"Tried to get an invalid dry-run-order.*"): + exchange.fetch_order("Y", "TKN/BTC") - default_conf['dry_run'] = False - mocker.patch(f'{EXMS}.exchange_has', return_value=False) + default_conf["dry_run"] = False + mocker.patch(f"{EXMS}.exchange_has", return_value=False) api_mock = MagicMock() api_mock.fetch_open_order = MagicMock( - return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) + return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"} + ) api_mock.fetch_closed_order = MagicMock( - return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) + return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"} + ) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - assert exchange.fetch_order( - 'X', 'TKN/BTC') == {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'} + assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"} assert log_has( - ("API fetch_open_order: {\'id\': \'123\', \'amount\': 2, \'symbol\': \'TKN/BTC\'}" - ), - caplog + ("API fetch_open_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog ) assert api_mock.fetch_open_order.call_count == 1 assert api_mock.fetch_closed_order.call_count == 0 @@ -3463,14 +3717,12 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): # open_order doesn't find order api_mock.fetch_open_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found")) api_mock.fetch_closed_order = MagicMock( - return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) + return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"} + ) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - assert exchange.fetch_order( - 'X', 'TKN/BTC') == {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'} + assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"} assert log_has( - ("API fetch_closed_order: {\'id\': \'123\', \'amount\': 2, \'symbol\': \'TKN/BTC\'}" - ), - caplog + ("API fetch_closed_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog ) assert api_mock.fetch_open_order.call_count == 1 assert api_mock.fetch_closed_order.call_count == 1 @@ -3480,79 +3732,95 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): api_mock.fetch_open_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.fetch_closed_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') + exchange.fetch_order(order_id="_", pair="TKN/BTC") assert api_mock.fetch_open_order.call_count == 1 api_mock.fetch_open_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - 'fetch_order_emulated', 'fetch_open_order', - retries=1, - order_id='_', pair='TKN/BTC', params={}) + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "fetch_order_emulated", + "fetch_open_order", + retries=1, + order_id="_", + pair="TKN/BTC", + params={}, + ) @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_stoploss_order(default_conf, mocker, exchange_name): - default_conf['dry_run'] = True + default_conf["dry_run"] = True mocker.patch(f"{EXMS}.exchange_has", return_value=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 + exchange._dry_run_open_orders["X"] = order + assert exchange.fetch_stoploss_order("X", "TKN/BTC").myid == 123 - with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'): - exchange.fetch_stoploss_order('Y', 'TKN/BTC') + with pytest.raises(InvalidOrderException, match=r"Tried to get an invalid dry-run-order.*"): + exchange.fetch_stoploss_order("Y", "TKN/BTC") - default_conf['dry_run'] = False + default_conf["dry_run"] = False api_mock = MagicMock() - api_mock.fetch_order = MagicMock(return_value={'id': '123', 'symbol': 'TKN/BTC'}) + api_mock.fetch_order = MagicMock(return_value={"id": "123", "symbol": "TKN/BTC"}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - res = {'id': '123', 'symbol': 'TKN/BTC'} - if exchange_name == 'okx': - res = {'id': '123', 'symbol': 'TKN/BTC', 'type': 'stoploss'} - assert exchange.fetch_stoploss_order('X', 'TKN/BTC') == res + res = {"id": "123", "symbol": "TKN/BTC"} + if exchange_name == "okx": + res = {"id": "123", "symbol": "TKN/BTC", "type": "stoploss"} + assert exchange.fetch_stoploss_order("X", "TKN/BTC") == res - if exchange_name == 'okx': + if exchange_name == "okx": # Tested separately. return 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') + exchange.fetch_stoploss_order(order_id="_", pair="TKN/BTC") assert api_mock.fetch_order.call_count == 1 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - 'fetch_stoploss_order', 'fetch_order', - retries=API_FETCH_ORDER_RETRY_COUNT + 1, - order_id='_', pair='TKN/BTC') + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "fetch_stoploss_order", + "fetch_order", + retries=API_FETCH_ORDER_RETRY_COUNT + 1, + order_id="_", + pair="TKN/BTC", + ) def test_fetch_order_or_stoploss_order(default_conf, mocker): - exchange = get_patched_exchange(mocker, default_conf, id='binance') + exchange = get_patched_exchange(mocker, default_conf, id="binance") fetch_order_mock = MagicMock() fetch_stoploss_order_mock = MagicMock() - mocker.patch.multiple(EXMS, - fetch_order=fetch_order_mock, - fetch_stoploss_order=fetch_stoploss_order_mock, - ) + mocker.patch.multiple( + EXMS, + fetch_order=fetch_order_mock, + fetch_stoploss_order=fetch_stoploss_order_mock, + ) - exchange.fetch_order_or_stoploss_order('1234', 'ETH/BTC', False) + exchange.fetch_order_or_stoploss_order("1234", "ETH/BTC", False) assert fetch_order_mock.call_count == 1 - assert fetch_order_mock.call_args_list[0][0][0] == '1234' - assert fetch_order_mock.call_args_list[0][0][1] == 'ETH/BTC' + assert fetch_order_mock.call_args_list[0][0][0] == "1234" + assert fetch_order_mock.call_args_list[0][0][1] == "ETH/BTC" assert fetch_stoploss_order_mock.call_count == 0 fetch_order_mock.reset_mock() fetch_stoploss_order_mock.reset_mock() - exchange.fetch_order_or_stoploss_order('1234', 'ETH/BTC', True) + exchange.fetch_order_or_stoploss_order("1234", "ETH/BTC", True) assert fetch_order_mock.call_count == 0 assert fetch_stoploss_order_mock.call_count == 1 - assert fetch_stoploss_order_mock.call_args_list[0][0][0] == '1234' - assert fetch_stoploss_order_mock.call_args_list[0][0][1] == 'ETH/BTC' + assert fetch_stoploss_order_mock.call_args_list[0][0][0] == "1234" + assert fetch_stoploss_order_mock.call_args_list[0][0][1] == "ETH/BTC" @pytest.mark.parametrize("exchange_name", EXCHANGES) @@ -3563,119 +3831,135 @@ def test_name(default_conf, mocker, exchange_name): assert exchange.id == exchange_name -@pytest.mark.parametrize("trading_mode,amount", [ - ('spot', 0.2340606), - ('futures', 2.340606), -]) +@pytest.mark.parametrize( + "trading_mode,amount", + [ + ("spot", 0.2340606), + ("futures", 2.340606), + ], +) @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_trades_for_order(default_conf, mocker, exchange_name, trading_mode, amount): - order_id = 'ABCD-ABCD' + order_id = "ABCD-ABCD" since = datetime(2018, 5, 5, 0, 0, 0) default_conf["dry_run"] = False default_conf["trading_mode"] = trading_mode - default_conf["margin_mode"] = 'isolated' - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + default_conf["margin_mode"] = "isolated" + mocker.patch(f"{EXMS}.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': 'ETH/USDT:USDT', - 'type': 'limit', - 'side': 'buy', - 'price': 165.0, - 'amount': 0.2340606, - 'fee': {'cost': 0.06179, 'currency': 'BTC'} - }]) + 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": "ETH/USDT:USDT", + "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) - orders = exchange.get_trades_for_order(order_id, 'ETH/USDT:USDT', since) + orders = exchange.get_trades_for_order(order_id, "ETH/USDT:USDT", since) assert len(orders) == 1 - assert orders[0]['price'] == 165 - assert pytest.approx(orders[0]['amount']) == amount + assert orders[0]["price"] == 165 + assert pytest.approx(orders[0]["amount"]) == amount 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] == 'ETH/USDT:USDT' + assert api_mock.fetch_my_trades.call_args[0][0] == "ETH/USDT:USDT" # Same test twice, hardcoded number and doing the same calculation 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 + assert ( + api_mock.fetch_my_trades.call_args[0][1] + == int(since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000 + ) - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - 'get_trades_for_order', 'fetch_my_trades', - order_id=order_id, pair='ETH/USDT:USDT', since=since) + ccxt_exceptionhandlers( + mocker, + default_conf, + api_mock, + exchange_name, + "get_trades_for_order", + "fetch_my_trades", + order_id=order_id, + pair="ETH/USDT:USDT", + since=since, + ) - mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=False)) - assert exchange.get_trades_for_order(order_id, 'ETH/USDT:USDT', since) == [] + mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=False)) + assert exchange.get_trades_for_order(order_id, "ETH/USDT:USDT", since) == [] @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_fee(default_conf, mocker, exchange_name): api_mock = MagicMock() - api_mock.calculate_fee = MagicMock(return_value={ - 'type': 'taker', - 'currency': 'BTC', - 'rate': 0.025, - 'cost': 0.05 - }) + 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) - exchange._config.pop('fee', None) + exchange._config.pop("fee", None) - assert exchange.get_fee('ETH/BTC') == 0.025 + assert exchange.get_fee("ETH/BTC") == 0.025 assert api_mock.calculate_fee.call_count == 1 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - 'get_fee', 'calculate_fee', symbol="ETH/BTC") + ccxt_exceptionhandlers( + mocker, default_conf, api_mock, exchange_name, "get_fee", "calculate_fee", symbol="ETH/BTC" + ) api_mock.calculate_fee.reset_mock() - exchange._config['fee'] = 0.001 + exchange._config["fee"] = 0.001 - assert exchange.get_fee('ETH/BTC') == 0.001 + assert exchange.get_fee("ETH/BTC") == 0.001 assert api_mock.calculate_fee.call_count == 0 def test_stoploss_order_unsupported_exchange(default_conf, mocker): - exchange = get_patched_exchange(mocker, default_conf, id='bitpanda') + exchange = get_patched_exchange(mocker, default_conf, id="bitpanda") with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): exchange.create_stoploss( - pair='ETH/BTC', - amount=1, - stop_price=220, - order_types={}, - side="sell", - leverage=1.0 + pair="ETH/BTC", amount=1, stop_price=220, order_types={}, side="sell", leverage=1.0 ) with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): exchange.stoploss_adjust(1, {}, side="sell") -@pytest.mark.parametrize('side,ratio,expected', [ - ('sell', 0.99, 99.0), # Default - ('sell', 0.999, 99.9), - ('sell', 1, 100), - ('sell', 1.1, InvalidOrderException), - ('buy', 0.99, 101.0), # Default - ('buy', 0.999, 100.1), - ('buy', 1, 100), - ('buy', 1.1, InvalidOrderException), - ]) +@pytest.mark.parametrize( + "side,ratio,expected", + [ + ("sell", 0.99, 99.0), # Default + ("sell", 0.999, 99.9), + ("sell", 1, 100), + ("sell", 1.1, InvalidOrderException), + ("buy", 0.99, 101.0), # Default + ("buy", 0.999, 100.1), + ("buy", 1, 100), + ("buy", 1.1, InvalidOrderException), + ], +) def test__get_stop_limit_rate(default_conf_usdt, mocker, side, ratio, expected): - exchange = get_patched_exchange(mocker, default_conf_usdt, id='binance') + exchange = get_patched_exchange(mocker, default_conf_usdt, id="binance") - order_types = {'stoploss_on_exchange_limit_ratio': ratio} + order_types = {"stoploss_on_exchange_limit_ratio": ratio} if isinstance(expected, type) and issubclass(expected, Exception): with pytest.raises(expected): exchange._get_stop_limit_rate(100, order_types, side) @@ -3684,48 +3968,50 @@ def test__get_stop_limit_rate(default_conf_usdt, mocker, side, ratio, expected): def test_merge_ft_has_dict(default_conf, mocker): - mocker.patch.multiple(EXMS, - _init_ccxt=MagicMock(return_value=MagicMock()), - _load_async_markets=MagicMock(), - validate_pairs=MagicMock(), - validate_timeframes=MagicMock(), - validate_stakecurrency=MagicMock(), - validate_pricing=MagicMock(), - ) + mocker.patch.multiple( + EXMS, + _init_ccxt=MagicMock(return_value=MagicMock()), + _load_async_markets=MagicMock(), + validate_pairs=MagicMock(), + validate_timeframes=MagicMock(), + validate_stakecurrency=MagicMock(), + validate_pricing=MagicMock(), + ) ex = Exchange(default_conf) assert ex._ft_has == Exchange._ft_has_default ex = Kraken(default_conf) assert ex._ft_has != Exchange._ft_has_default - assert ex.get_option('trades_pagination') == 'id' - assert ex.get_option('trades_pagination_arg') == 'since' + assert ex.get_option("trades_pagination") == "id" + assert ex.get_option("trades_pagination_arg") == "since" # Binance defines different values ex = Binance(default_conf) assert ex._ft_has != Exchange._ft_has_default - assert ex.get_option('stoploss_on_exchange') - assert ex.get_option('order_time_in_force') == ['GTC', 'FOK', 'IOC', 'PO'] - assert ex.get_option('trades_pagination') == 'id' - assert ex.get_option('trades_pagination_arg') == 'fromId' + assert ex.get_option("stoploss_on_exchange") + assert ex.get_option("order_time_in_force") == ["GTC", "FOK", "IOC", "PO"] + assert ex.get_option("trades_pagination") == "id" + assert ex.get_option("trades_pagination_arg") == "fromId" conf = copy.deepcopy(default_conf) - conf['exchange']['_ft_has_params'] = {"DeadBeef": 20, - "stoploss_on_exchange": False} + 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 + assert not ex._ft_has["stoploss_on_exchange"] + assert ex._ft_has["DeadBeef"] == 20 def test_get_valid_pair_combination(default_conf, mocker, markets): - mocker.patch.multiple(EXMS, - _init_ccxt=MagicMock(return_value=MagicMock()), - _load_async_markets=MagicMock(), - validate_pairs=MagicMock(), - validate_timeframes=MagicMock(), - validate_pricing=MagicMock(), - markets=PropertyMock(return_value=markets)) + mocker.patch.multiple( + EXMS, + _init_ccxt=MagicMock(return_value=MagicMock()), + _load_async_markets=MagicMock(), + validate_pairs=MagicMock(), + validate_timeframes=MagicMock(), + validate_pricing=MagicMock(), + markets=PropertyMock(return_value=markets), + ) ex = Exchange(default_conf) assert ex.get_valid_pair_combination("ETH", "BTC") == "ETH/BTC" @@ -3736,7 +4022,8 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): @pytest.mark.parametrize( "base_currencies,quote_currencies,tradable_only,active_only,spot_only," - "futures_only,expected_keys,test_comment", [ + "futures_only,expected_keys,test_comment", + [ # Testing markets (in conftest.py): # 'BLK/BTC': 'active': True # 'BTT/BTC': 'active': True @@ -3750,98 +4037,287 @@ def test_get_valid_pair_combination(default_conf, mocker, markets): # 'TKN/BTC': 'active' not set # 'XLTCUSDT': 'active': True, not a pair # 'XRP/BTC': 'active': False - ([], [], False, False, False, False, - ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', - 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC', 'ADA/USDT:USDT', - 'ETH/USDT:USDT'], - 'all markets'), - ([], [], False, False, True, False, - ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', - 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC'], - 'all markets, only spot pairs'), - ([], [], False, True, False, False, - ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC', - 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC', 'ADA/USDT:USDT', 'ETH/USDT:USDT'], - 'active markets'), - ([], [], True, False, False, False, - ['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', - 'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC'], - 'all pairs'), - ([], [], True, True, False, False, - ['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC', - 'TKN/BTC', 'XRP/BTC'], - 'active pairs'), - (['ETH', 'LTC'], [], False, False, False, False, - ['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT', - 'ETH/USDT:USDT'], - 'all markets, base=ETH, LTC'), - (['LTC'], [], False, False, False, False, - ['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT'], - 'all markets, base=LTC'), - (['LTC'], [], False, False, True, False, - ['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT'], - 'spot markets, base=LTC'), - ([], ['USDT'], False, False, False, False, - ['ETH/USDT', 'LTC/USDT', 'XLTCUSDT', 'ADA/USDT:USDT', 'ETH/USDT:USDT'], - 'all markets, quote=USDT'), - ([], ['USDT'], False, False, False, True, - ['ADA/USDT:USDT', 'ETH/USDT:USDT'], - 'Futures markets, quote=USDT'), - ([], ['USDT', 'USD'], False, False, False, False, - ['ETH/USDT', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT', 'ADA/USDT:USDT', 'ETH/USDT:USDT'], - 'all markets, quote=USDT, USD'), - ([], ['USDT', 'USD'], False, False, True, False, - ['ETH/USDT', 'LTC/USD', 'LTC/USDT'], - 'spot markets, quote=USDT, USD'), - (['LTC'], ['USDT'], False, False, False, False, - ['LTC/USDT', 'XLTCUSDT'], - 'all markets, base=LTC, quote=USDT'), - (['LTC'], ['USDT'], True, False, False, False, - ['LTC/USDT'], - 'all pairs, base=LTC, quote=USDT'), - (['LTC'], ['USDT', 'NONEXISTENT'], False, False, False, False, - ['LTC/USDT', 'XLTCUSDT'], - 'all markets, base=LTC, quote=USDT, NONEXISTENT'), - (['LTC'], ['NONEXISTENT'], False, False, False, False, - [], - 'all markets, base=LTC, quote=NONEXISTENT'), - ]) -def test_get_markets(default_conf, mocker, markets_static, - base_currencies, quote_currencies, tradable_only, active_only, - spot_only, futures_only, expected_keys, - test_comment # Here for debugging purposes (Not used within method) - ): - mocker.patch.multiple(EXMS, - _init_ccxt=MagicMock(return_value=MagicMock()), - _load_async_markets=MagicMock(), - validate_pairs=MagicMock(), - validate_timeframes=MagicMock(), - validate_pricing=MagicMock(), - markets=PropertyMock(return_value=markets_static)) + ( + [], + [], + False, + False, + False, + False, + [ + "BLK/BTC", + "BTT/BTC", + "ETH/BTC", + "ETH/USDT", + "LTC/BTC", + "LTC/ETH", + "LTC/USD", + "LTC/USDT", + "NEO/BTC", + "TKN/BTC", + "XLTCUSDT", + "XRP/BTC", + "ADA/USDT:USDT", + "ETH/USDT:USDT", + ], + "all markets", + ), + ( + [], + [], + False, + False, + True, + False, + [ + "BLK/BTC", + "BTT/BTC", + "ETH/BTC", + "ETH/USDT", + "LTC/BTC", + "LTC/ETH", + "LTC/USD", + "LTC/USDT", + "NEO/BTC", + "TKN/BTC", + "XRP/BTC", + ], + "all markets, only spot pairs", + ), + ( + [], + [], + False, + True, + False, + False, + [ + "BLK/BTC", + "ETH/BTC", + "ETH/USDT", + "LTC/BTC", + "LTC/ETH", + "LTC/USD", + "NEO/BTC", + "TKN/BTC", + "XLTCUSDT", + "XRP/BTC", + "ADA/USDT:USDT", + "ETH/USDT:USDT", + ], + "active markets", + ), + ( + [], + [], + True, + False, + False, + False, + [ + "BLK/BTC", + "BTT/BTC", + "ETH/BTC", + "ETH/USDT", + "LTC/BTC", + "LTC/ETH", + "LTC/USD", + "LTC/USDT", + "NEO/BTC", + "TKN/BTC", + "XRP/BTC", + ], + "all pairs", + ), + ( + [], + [], + True, + True, + False, + False, + [ + "BLK/BTC", + "ETH/BTC", + "ETH/USDT", + "LTC/BTC", + "LTC/ETH", + "LTC/USD", + "NEO/BTC", + "TKN/BTC", + "XRP/BTC", + ], + "active pairs", + ), + ( + ["ETH", "LTC"], + [], + False, + False, + False, + False, + [ + "ETH/BTC", + "ETH/USDT", + "LTC/BTC", + "LTC/ETH", + "LTC/USD", + "LTC/USDT", + "XLTCUSDT", + "ETH/USDT:USDT", + ], + "all markets, base=ETH, LTC", + ), + ( + ["LTC"], + [], + False, + False, + False, + False, + ["LTC/BTC", "LTC/ETH", "LTC/USD", "LTC/USDT", "XLTCUSDT"], + "all markets, base=LTC", + ), + ( + ["LTC"], + [], + False, + False, + True, + False, + ["LTC/BTC", "LTC/ETH", "LTC/USD", "LTC/USDT"], + "spot markets, base=LTC", + ), + ( + [], + ["USDT"], + False, + False, + False, + False, + ["ETH/USDT", "LTC/USDT", "XLTCUSDT", "ADA/USDT:USDT", "ETH/USDT:USDT"], + "all markets, quote=USDT", + ), + ( + [], + ["USDT"], + False, + False, + False, + True, + ["ADA/USDT:USDT", "ETH/USDT:USDT"], + "Futures markets, quote=USDT", + ), + ( + [], + ["USDT", "USD"], + False, + False, + False, + False, + ["ETH/USDT", "LTC/USD", "LTC/USDT", "XLTCUSDT", "ADA/USDT:USDT", "ETH/USDT:USDT"], + "all markets, quote=USDT, USD", + ), + ( + [], + ["USDT", "USD"], + False, + False, + True, + False, + ["ETH/USDT", "LTC/USD", "LTC/USDT"], + "spot markets, quote=USDT, USD", + ), + ( + ["LTC"], + ["USDT"], + False, + False, + False, + False, + ["LTC/USDT", "XLTCUSDT"], + "all markets, base=LTC, quote=USDT", + ), + ( + ["LTC"], + ["USDT"], + True, + False, + False, + False, + ["LTC/USDT"], + "all pairs, base=LTC, quote=USDT", + ), + ( + ["LTC"], + ["USDT", "NONEXISTENT"], + False, + False, + False, + False, + ["LTC/USDT", "XLTCUSDT"], + "all markets, base=LTC, quote=USDT, NONEXISTENT", + ), + ( + ["LTC"], + ["NONEXISTENT"], + False, + False, + False, + False, + [], + "all markets, base=LTC, quote=NONEXISTENT", + ), + ], +) +def test_get_markets( + default_conf, + mocker, + markets_static, + base_currencies, + quote_currencies, + tradable_only, + active_only, + spot_only, + futures_only, + expected_keys, + test_comment, # Here for debugging purposes (Not used within method) +): + mocker.patch.multiple( + EXMS, + _init_ccxt=MagicMock(return_value=MagicMock()), + _load_async_markets=MagicMock(), + validate_pairs=MagicMock(), + validate_timeframes=MagicMock(), + validate_pricing=MagicMock(), + markets=PropertyMock(return_value=markets_static), + ) ex = Exchange(default_conf) - pairs = ex.get_markets(base_currencies, - quote_currencies, - tradable_only=tradable_only, - spot_only=spot_only, - futures_only=futures_only, - active_only=active_only) + pairs = ex.get_markets( + base_currencies, + quote_currencies, + tradable_only=tradable_only, + spot_only=spot_only, + futures_only=futures_only, + active_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(f'{EXMS}.markets', PropertyMock(return_value=None)) + mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=None)) with pytest.raises(OperationalException, match="Markets were not loaded."): - ex.get_markets('LTC', 'USDT', True, False) + ex.get_markets("LTC", "USDT", True, False) @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_ohlcv_candle_limit(default_conf, mocker, exchange_name): - if exchange_name == 'okx': + if exchange_name == "okx": pytest.skip("Tested separately for okx") exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - timeframes = ('1m', '5m', '1h') - expected = exchange._ft_has['ohlcv_candle_limit'] + timeframes = ("1m", "5m", "1h") + expected = exchange._ft_has["ohlcv_candle_limit"] for timeframe in timeframes: # if 'ohlcv_candle_limit_per_timeframe' in exchange._ft_has: # expected = exchange._ft_has['ohlcv_candle_limit_per_timeframe'][timeframe] @@ -3853,159 +4329,323 @@ def test_ohlcv_candle_limit(default_conf, mocker, exchange_name): @pytest.mark.parametrize( "market_symbol,base,quote,exchange,spot,margin,futures,trademode,add_dict,expected_result", [ - ("BTC/USDT", 'BTC', 'USDT', "binance", True, False, False, 'spot', {}, True), - ("USDT/BTC", 'USDT', 'BTC', "binance", True, False, False, 'spot', {}, True), + ("BTC/USDT", "BTC", "USDT", "binance", True, False, False, "spot", {}, True), + ("USDT/BTC", "USDT", "BTC", "binance", True, False, False, "spot", {}, True), # No separating / - ("BTCUSDT", 'BTC', 'USDT', "binance", True, False, False, 'spot', {}, True), - ("BTCUSDT", None, "USDT", "binance", True, False, False, 'spot', {}, False), - ("USDT/BTC", "BTC", None, "binance", True, False, False, 'spot', {}, False), - ("BTCUSDT", "BTC", None, "binance", True, False, False, 'spot', {}, False), - ("BTC/USDT", "BTC", "USDT", "binance", True, False, False, 'spot', {}, True), + ("BTCUSDT", "BTC", "USDT", "binance", True, False, False, "spot", {}, True), + ("BTCUSDT", None, "USDT", "binance", True, False, False, "spot", {}, False), + ("USDT/BTC", "BTC", None, "binance", True, False, False, "spot", {}, False), + ("BTCUSDT", "BTC", None, "binance", True, False, False, "spot", {}, False), + ("BTC/USDT", "BTC", "USDT", "binance", True, False, False, "spot", {}, True), # Futures mode, spot pair - ("BTC/USDT", "BTC", "USDT", "binance", True, False, False, 'futures', {}, False), - ("BTC/USDT", "BTC", "USDT", "binance", True, False, False, 'margin', {}, False), - ("BTC/USDT", "BTC", "USDT", "binance", True, True, True, 'margin', {}, True), - ("BTC/USDT", "BTC", "USDT", "binance", False, True, False, 'margin', {}, True), + ("BTC/USDT", "BTC", "USDT", "binance", True, False, False, "futures", {}, False), + ("BTC/USDT", "BTC", "USDT", "binance", True, False, False, "margin", {}, False), + ("BTC/USDT", "BTC", "USDT", "binance", True, True, True, "margin", {}, True), + ("BTC/USDT", "BTC", "USDT", "binance", False, True, False, "margin", {}, True), # Futures mode, futures pair - ("BTC/USDT", "BTC", "USDT", "binance", False, False, True, 'futures', {}, True), + ("BTC/USDT", "BTC", "USDT", "binance", False, False, True, "futures", {}, True), # Futures market - ("BTC/UNK", "BTC", 'UNK', "binance", False, False, True, 'spot', {}, False), - ("BTC/EUR", 'BTC', 'EUR', "kraken", True, False, False, 'spot', {"darkpool": False}, True), - ("EUR/BTC", 'EUR', 'BTC', "kraken", True, False, False, 'spot', {"darkpool": False}, True), + ("BTC/UNK", "BTC", "UNK", "binance", False, False, True, "spot", {}, False), + ("BTC/EUR", "BTC", "EUR", "kraken", True, False, False, "spot", {"darkpool": False}, True), + ("EUR/BTC", "EUR", "BTC", "kraken", True, False, False, "spot", {"darkpool": False}, True), # no darkpools - ("BTC/EUR", 'BTC', 'EUR', "kraken", True, False, False, 'spot', - {"darkpool": True}, False), + ("BTC/EUR", "BTC", "EUR", "kraken", True, False, False, "spot", {"darkpool": True}, False), # no darkpools - ("BTC/EUR.d", 'BTC', 'EUR', "kraken", True, False, False, 'spot', - {"darkpool": True}, False), - ("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'spot', {}, False), - ("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'margin', {}, False), - ("BTC/USDT:USDT", 'BTC', 'USD', "okx", False, False, True, 'futures', {}, True), - ]) + ( + "BTC/EUR.d", + "BTC", + "EUR", + "kraken", + True, + False, + False, + "spot", + {"darkpool": True}, + False, + ), + ("BTC/USDT:USDT", "BTC", "USD", "okx", False, False, True, "spot", {}, False), + ("BTC/USDT:USDT", "BTC", "USD", "okx", False, False, True, "margin", {}, False), + ("BTC/USDT:USDT", "BTC", "USD", "okx", False, False, True, "futures", {}, True), + ], +) def test_market_is_tradable( - mocker, default_conf, market_symbol, base, - quote, spot, margin, futures, trademode, add_dict, exchange, expected_result + mocker, + default_conf, + market_symbol, + base, + quote, + spot, + margin, + futures, + trademode, + add_dict, + exchange, + expected_result, ) -> None: - default_conf['trading_mode'] = trademode - mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') + default_conf["trading_mode"] = trademode + mocker.patch(f"{EXMS}.validate_trading_mode_and_margin_mode") ex = get_patched_exchange(mocker, default_conf, id=exchange) market = { - 'symbol': market_symbol, - 'base': base, - 'quote': quote, - 'spot': spot, - 'future': futures, - 'swap': futures, - 'margin': margin, - 'linear': True, + "symbol": market_symbol, + "base": base, + "quote": quote, + "spot": spot, + "future": futures, + "swap": futures, + "margin": margin, + "linear": True, **(add_dict), } assert ex.market_is_tradable(market) == expected_result -@pytest.mark.parametrize("market,expected_result", [ - ({'symbol': 'ETH/BTC', 'active': True}, True), - ({'symbol': 'ETH/BTC', 'active': False}, False), - ({'symbol': 'ETH/BTC', }, True), -]) +@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 -@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), -]) +@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", [ - ({'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)), -]) +@pytest.mark.parametrize( + "order,expected", + [ + ({"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), + ), + ], +) def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None: - mocker.patch(f'{EXMS}.calculate_fee_rate', MagicMock(return_value=0.01)) + mocker.patch(f"{EXMS}.calculate_fee_rate", MagicMock(return_value=0.01)) ex = get_patched_exchange(mocker, default_conf) - assert ex.extract_cost_curr_rate(order['fee'], order['symbol'], cost=20, amount=1) == expected + assert ex.extract_cost_curr_rate(order["fee"], order["symbol"], cost=20, amount=1) == expected -@pytest.mark.parametrize("order,unknown_fee_rate,expected", [ - # Using base-currency - ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, None, 0.1), - ({'symbol': 'ETH/BTC', 'amount': 0.05, 'cost': 0.05, - 'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, None, 0.08), - # Using quote currency - ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'BTC', 'cost': 0.005}}, None, 0.1), - ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, None, 0.04), - # Using foreign currency - ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'NEO', 'cost': 0.0012}}, None, 0.001944), - ({'symbol': 'ETH/BTC', 'amount': 2.21, 'cost': 0.02992561, - 'fee': {'currency': 'NEO', 'cost': 0.00027452}}, None, 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}}, None, 0.01), - ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, None, 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, None), - ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, - 'fee': {'currency': 'ETH', 'cost': 0.0, 'rate': None}}, None, 0.0), - ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, - 'fee': {'currency': 'NEO', 'cost': 0.0, 'rate': None}}, None, None), - # Invalid pair combination - POINT/BTC is not a pair - ({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5, - 'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, None, None), - ({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5, - 'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 1, 4.0), - ({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5, - 'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 2, 8.0), - # Missing currency - ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': None, 'cost': 0.005}}, None, None), -]) +@pytest.mark.parametrize( + "order,unknown_fee_rate,expected", + [ + # Using base-currency + ( + { + "symbol": "ETH/BTC", + "amount": 0.04, + "cost": 0.05, + "fee": {"currency": "ETH", "cost": 0.004, "rate": None}, + }, + None, + 0.1, + ), + ( + { + "symbol": "ETH/BTC", + "amount": 0.05, + "cost": 0.05, + "fee": {"currency": "ETH", "cost": 0.004, "rate": None}, + }, + None, + 0.08, + ), + # Using quote currency + ( + { + "symbol": "ETH/BTC", + "amount": 0.04, + "cost": 0.05, + "fee": {"currency": "BTC", "cost": 0.005}, + }, + None, + 0.1, + ), + ( + { + "symbol": "ETH/BTC", + "amount": 0.04, + "cost": 0.05, + "fee": {"currency": "BTC", "cost": 0.002, "rate": None}, + }, + None, + 0.04, + ), + # Using foreign currency + ( + { + "symbol": "ETH/BTC", + "amount": 0.04, + "cost": 0.05, + "fee": {"currency": "NEO", "cost": 0.0012}, + }, + None, + 0.001944, + ), + ( + { + "symbol": "ETH/BTC", + "amount": 2.21, + "cost": 0.02992561, + "fee": {"currency": "NEO", "cost": 0.00027452}, + }, + None, + 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}, + }, + None, + 0.01, + ), + ( + { + "symbol": "ETH/BTC", + "amount": 0.04, + "cost": 0.05, + "fee": {"currency": "USDT", "cost": 0.34, "rate": 0.005}, + }, + None, + 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, + None, + ), + ( + { + "symbol": "ETH/BTC", + "amount": 0.04, + "cost": 0.0, + "fee": {"currency": "ETH", "cost": 0.0, "rate": None}, + }, + None, + 0.0, + ), + ( + { + "symbol": "ETH/BTC", + "amount": 0.04, + "cost": 0.0, + "fee": {"currency": "NEO", "cost": 0.0, "rate": None}, + }, + None, + None, + ), + # Invalid pair combination - POINT/BTC is not a pair + ( + { + "symbol": "POINT/BTC", + "amount": 0.04, + "cost": 0.5, + "fee": {"currency": "POINT", "cost": 2.0, "rate": None}, + }, + None, + None, + ), + ( + { + "symbol": "POINT/BTC", + "amount": 0.04, + "cost": 0.5, + "fee": {"currency": "POINT", "cost": 2.0, "rate": None}, + }, + 1, + 4.0, + ), + ( + { + "symbol": "POINT/BTC", + "amount": 0.04, + "cost": 0.5, + "fee": {"currency": "POINT", "cost": 2.0, "rate": None}, + }, + 2, + 8.0, + ), + # Missing currency + ( + { + "symbol": "ETH/BTC", + "amount": 0.04, + "cost": 0.05, + "fee": {"currency": None, "cost": 0.005}, + }, + None, + None, + ), + ], +) def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None: - mocker.patch(f'{EXMS}.fetch_ticker', return_value={'last': 0.081}) + mocker.patch(f"{EXMS}.fetch_ticker", return_value={"last": 0.081}) if unknown_fee_rate: - default_conf['exchange']['unknown_fee_rate'] = unknown_fee_rate + default_conf["exchange"]["unknown_fee_rate"] = unknown_fee_rate ex = get_patched_exchange(mocker, default_conf) - assert ex.calculate_fee_rate(order['fee'], order['symbol'], - cost=order['cost'], amount=order['amount']) == expected + assert ( + ex.calculate_fee_rate( + order["fee"], order["symbol"], cost=order["cost"], amount=order["amount"] + ) + == expected + ) -@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), - (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), -]) +@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), + (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), + ], +) def test_calculate_backoff(retrycount, max_retries, expected): assert calculate_backoff(retrycount, max_retries) == expected @@ -4013,67 +4653,67 @@ def test_calculate_backoff(retrycount, max_retries, expected): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_funding_fees(default_conf_usdt, mocker, exchange_name, caplog): now = datetime.now(timezone.utc) - default_conf_usdt['trading_mode'] = 'futures' - default_conf_usdt['margin_mode'] = 'isolated' + default_conf_usdt["trading_mode"] = "futures" + default_conf_usdt["margin_mode"] = "isolated" exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) exchange._fetch_and_calculate_funding_fees = MagicMock(side_effect=ExchangeError) - assert exchange.get_funding_fees('BTC/USDT:USDT', 1, False, now) == 0.0 + assert exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now) == 0.0 assert exchange._fetch_and_calculate_funding_fees.call_count == 1 assert log_has("Could not update funding fees for BTC/USDT:USDT.", caplog) -@pytest.mark.parametrize("exchange_name", ['binance']) +@pytest.mark.parametrize("exchange_name", ["binance"]) def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name): api_mock = MagicMock() - api_mock.fetch_funding_history = MagicMock(return_value=[ - { - 'amount': 0.14542, - 'code': 'USDT', - 'datetime': '2021-09-01T08:00:01.000Z', - 'id': '485478', - 'info': {'asset': 'USDT', - 'income': '0.14542', - 'incomeType': 'FUNDING_FEE', - 'info': 'FUNDING_FEE', - 'symbol': 'XRPUSDT', - 'time': '1630382001000', - 'tradeId': '', - 'tranId': '993203'}, - 'symbol': 'XRP/USDT', - 'timestamp': 1630382001000 - }, - { - 'amount': -0.14642, - 'code': 'USDT', - 'datetime': '2021-09-01T16:00:01.000Z', - 'id': '485479', - 'info': {'asset': 'USDT', - 'income': '-0.14642', - 'incomeType': 'FUNDING_FEE', - 'info': 'FUNDING_FEE', - 'symbol': 'XRPUSDT', - 'time': '1630314001000', - 'tradeId': '', - 'tranId': '993204'}, - 'symbol': 'XRP/USDT', - 'timestamp': 1630314001000 - } - ]) - type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True}) + api_mock.fetch_funding_history = MagicMock( + return_value=[ + { + "amount": 0.14542, + "code": "USDT", + "datetime": "2021-09-01T08:00:01.000Z", + "id": "485478", + "info": { + "asset": "USDT", + "income": "0.14542", + "incomeType": "FUNDING_FEE", + "info": "FUNDING_FEE", + "symbol": "XRPUSDT", + "time": "1630382001000", + "tradeId": "", + "tranId": "993203", + }, + "symbol": "XRP/USDT", + "timestamp": 1630382001000, + }, + { + "amount": -0.14642, + "code": "USDT", + "datetime": "2021-09-01T16:00:01.000Z", + "id": "485479", + "info": { + "asset": "USDT", + "income": "-0.14642", + "incomeType": "FUNDING_FEE", + "info": "FUNDING_FEE", + "symbol": "XRPUSDT", + "time": "1630314001000", + "tradeId": "", + "tranId": "993204", + }, + "symbol": "XRP/USDT", + "timestamp": 1630314001000, + }, + ] + ) + type(api_mock).has = PropertyMock(return_value={"fetchFundingHistory": True}) # mocker.patch(f'{EXMS}.get_funding_fees', lambda pair, since: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - date_time = datetime.strptime("2021-09-01T00:00:01.000Z", '%Y-%m-%dT%H:%M:%S.%fZ') + date_time = datetime.strptime("2021-09-01T00:00:01.000Z", "%Y-%m-%dT%H:%M:%S.%fZ") unix_time = int(date_time.timestamp()) expected_fees = -0.001 # 0.14542341 + -0.14642341 - fees_from_datetime = exchange._get_funding_fees_from_exchange( - pair='XRP/USDT', - since=date_time - ) - fees_from_unix_time = exchange._get_funding_fees_from_exchange( - pair='XRP/USDT', - since=unix_time - ) + fees_from_datetime = exchange._get_funding_fees_from_exchange(pair="XRP/USDT", since=date_time) + fees_from_unix_time = exchange._get_funding_fees_from_exchange(pair="XRP/USDT", since=unix_time) assert pytest.approx(expected_fees) == fees_from_datetime assert pytest.approx(expected_fees) == fees_from_unix_time @@ -4086,39 +4726,31 @@ def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name): "_get_funding_fees_from_exchange", "fetch_funding_history", pair="XRP/USDT", - since=unix_time + since=unix_time, ) -@pytest.mark.parametrize('exchange', ['binance', 'kraken']) -@pytest.mark.parametrize('stake_amount,leverage,min_stake_with_lev', [ - (9.0, 3.0, 3.0), - (20.0, 5.0, 4.0), - (100.0, 100.0, 1.0) -]) +@pytest.mark.parametrize("exchange", ["binance", "kraken"]) +@pytest.mark.parametrize( + "stake_amount,leverage,min_stake_with_lev", + [(9.0, 3.0, 3.0), (20.0, 5.0, 4.0), (100.0, 100.0, 1.0)], +) def test_get_stake_amount_considering_leverage( - exchange, - stake_amount, - leverage, - min_stake_with_lev, - mocker, - default_conf + exchange, stake_amount, leverage, min_stake_with_lev, mocker, default_conf ): exchange = get_patched_exchange(mocker, default_conf, id=exchange) - assert exchange._get_stake_amount_considering_leverage( - stake_amount, leverage) == min_stake_with_lev + assert ( + exchange._get_stake_amount_considering_leverage(stake_amount, leverage) + == min_stake_with_lev + ) -@pytest.mark.parametrize("margin_mode", [ - (MarginMode.CROSS), - (MarginMode.ISOLATED) -]) +@pytest.mark.parametrize("margin_mode", [(MarginMode.CROSS), (MarginMode.ISOLATED)]) def test_set_margin_mode(mocker, default_conf, margin_mode): - api_mock = MagicMock() api_mock.set_margin_mode = MagicMock() - type(api_mock).has = PropertyMock(return_value={'setMarginMode': True}) - default_conf['dry_run'] = False + type(api_mock).has = PropertyMock(return_value={"setMarginMode": True}) + default_conf["dry_run"] = False ccxt_exceptionhandlers( mocker, @@ -4128,146 +4760,144 @@ def test_set_margin_mode(mocker, default_conf, margin_mode): "set_margin_mode", "set_margin_mode", pair="XRP/USDT", - margin_mode=margin_mode + margin_mode=margin_mode, ) -@pytest.mark.parametrize("exchange_name, trading_mode, margin_mode, exception_thrown", [ - ("binance", TradingMode.SPOT, None, False), - ("binance", TradingMode.MARGIN, MarginMode.ISOLATED, True), - ("kraken", TradingMode.SPOT, None, False), - ("kraken", TradingMode.MARGIN, MarginMode.ISOLATED, True), - ("kraken", TradingMode.FUTURES, MarginMode.ISOLATED, True), - ("bitmart", TradingMode.SPOT, None, False), - ("bitmart", TradingMode.MARGIN, MarginMode.CROSS, True), - ("bitmart", TradingMode.MARGIN, MarginMode.ISOLATED, True), - ("bitmart", TradingMode.FUTURES, MarginMode.CROSS, True), - ("bitmart", TradingMode.FUTURES, MarginMode.ISOLATED, True), - ("gate", TradingMode.MARGIN, MarginMode.ISOLATED, True), - ("okx", TradingMode.SPOT, None, False), - ("okx", TradingMode.MARGIN, MarginMode.CROSS, True), - ("okx", TradingMode.MARGIN, MarginMode.ISOLATED, True), - ("okx", TradingMode.FUTURES, MarginMode.CROSS, True), - - ("binance", TradingMode.FUTURES, MarginMode.ISOLATED, False), - ("gate", TradingMode.FUTURES, MarginMode.ISOLATED, False), - ("okx", TradingMode.FUTURES, MarginMode.ISOLATED, False), - - # * Remove once implemented - ("binance", TradingMode.MARGIN, MarginMode.CROSS, True), - ("binance", TradingMode.FUTURES, MarginMode.CROSS, True), - ("kraken", TradingMode.MARGIN, MarginMode.CROSS, True), - ("kraken", TradingMode.FUTURES, MarginMode.CROSS, True), - ("gate", TradingMode.MARGIN, MarginMode.CROSS, True), - ("gate", TradingMode.FUTURES, MarginMode.CROSS, True), - - # * Uncomment once implemented - # ("binance", TradingMode.MARGIN, MarginMode.CROSS, False), - # ("binance", TradingMode.FUTURES, MarginMode.CROSS, False), - # ("kraken", TradingMode.MARGIN, MarginMode.CROSS, False), - # ("kraken", TradingMode.FUTURES, MarginMode.CROSS, False), - # ("gate", TradingMode.MARGIN, MarginMode.CROSS, False), - # ("gate", TradingMode.FUTURES, MarginMode.CROSS, False), -]) +@pytest.mark.parametrize( + "exchange_name, trading_mode, margin_mode, exception_thrown", + [ + ("binance", TradingMode.SPOT, None, False), + ("binance", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("kraken", TradingMode.SPOT, None, False), + ("kraken", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("kraken", TradingMode.FUTURES, MarginMode.ISOLATED, True), + ("bitmart", TradingMode.SPOT, None, False), + ("bitmart", TradingMode.MARGIN, MarginMode.CROSS, True), + ("bitmart", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("bitmart", TradingMode.FUTURES, MarginMode.CROSS, True), + ("bitmart", TradingMode.FUTURES, MarginMode.ISOLATED, True), + ("gate", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("okx", TradingMode.SPOT, None, False), + ("okx", TradingMode.MARGIN, MarginMode.CROSS, True), + ("okx", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("okx", TradingMode.FUTURES, MarginMode.CROSS, True), + ("binance", TradingMode.FUTURES, MarginMode.ISOLATED, False), + ("gate", TradingMode.FUTURES, MarginMode.ISOLATED, False), + ("okx", TradingMode.FUTURES, MarginMode.ISOLATED, False), + # * Remove once implemented + ("binance", TradingMode.MARGIN, MarginMode.CROSS, True), + ("binance", TradingMode.FUTURES, MarginMode.CROSS, True), + ("kraken", TradingMode.MARGIN, MarginMode.CROSS, True), + ("kraken", TradingMode.FUTURES, MarginMode.CROSS, True), + ("gate", TradingMode.MARGIN, MarginMode.CROSS, True), + ("gate", TradingMode.FUTURES, MarginMode.CROSS, True), + # * Uncomment once implemented + # ("binance", TradingMode.MARGIN, MarginMode.CROSS, False), + # ("binance", TradingMode.FUTURES, MarginMode.CROSS, False), + # ("kraken", TradingMode.MARGIN, MarginMode.CROSS, False), + # ("kraken", TradingMode.FUTURES, MarginMode.CROSS, False), + # ("gate", TradingMode.MARGIN, MarginMode.CROSS, False), + # ("gate", TradingMode.FUTURES, MarginMode.CROSS, False), + ], +) def test_validate_trading_mode_and_margin_mode( - default_conf, - mocker, - exchange_name, - trading_mode, - margin_mode, - exception_thrown + default_conf, mocker, exchange_name, trading_mode, margin_mode, exception_thrown ): exchange = get_patched_exchange( - mocker, default_conf, id=exchange_name, mock_supported_modes=False) - if (exception_thrown): + mocker, default_conf, id=exchange_name, mock_supported_modes=False + ) + if exception_thrown: with pytest.raises(OperationalException): exchange.validate_trading_mode_and_margin_mode(trading_mode, margin_mode) else: exchange.validate_trading_mode_and_margin_mode(trading_mode, margin_mode) -@pytest.mark.parametrize("exchange_name,trading_mode,ccxt_config", [ - ("binance", "spot", {}), - ("binance", "margin", {"options": {"defaultType": "margin"}}), - ("binance", "futures", {"options": {"defaultType": "swap"}}), - ("bybit", "spot", {"options": {"defaultType": "spot"}}), - ("bybit", "futures", {"options": {"defaultType": "swap"}}), - ("gate", "futures", {"options": {"defaultType": "swap"}}), - ("hitbtc", "futures", {"options": {"defaultType": "swap"}}), - ("kraken", "futures", {"options": {"defaultType": "swap"}}), - ("kucoin", "futures", {"options": {"defaultType": "swap"}}), - ("okx", "futures", {"options": {"defaultType": "swap"}}), -]) -def test__ccxt_config( - default_conf, - mocker, - exchange_name, - trading_mode, - ccxt_config -): - default_conf['trading_mode'] = trading_mode - default_conf['margin_mode'] = 'isolated' +@pytest.mark.parametrize( + "exchange_name,trading_mode,ccxt_config", + [ + ("binance", "spot", {}), + ("binance", "margin", {"options": {"defaultType": "margin"}}), + ("binance", "futures", {"options": {"defaultType": "swap"}}), + ("bybit", "spot", {"options": {"defaultType": "spot"}}), + ("bybit", "futures", {"options": {"defaultType": "swap"}}), + ("gate", "futures", {"options": {"defaultType": "swap"}}), + ("hitbtc", "futures", {"options": {"defaultType": "swap"}}), + ("kraken", "futures", {"options": {"defaultType": "swap"}}), + ("kucoin", "futures", {"options": {"defaultType": "swap"}}), + ("okx", "futures", {"options": {"defaultType": "swap"}}), + ], +) +def test__ccxt_config(default_conf, mocker, exchange_name, trading_mode, ccxt_config): + default_conf["trading_mode"] = trading_mode + default_conf["margin_mode"] = "isolated" exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) assert exchange._ccxt_config == ccxt_config -@pytest.mark.parametrize('pair,nominal_value,max_lev', [ - ("ETH/BTC", 0.0, 2.0), - ("TKN/BTC", 100.0, 5.0), - ("BLK/BTC", 173.31, 3.0), - ("LTC/BTC", 0.0, 1.0), - ("TKN/USDT", 210.30, 1.0), -]) +@pytest.mark.parametrize( + "pair,nominal_value,max_lev", + [ + ("ETH/BTC", 0.0, 2.0), + ("TKN/BTC", 100.0, 5.0), + ("BLK/BTC", 173.31, 3.0), + ("LTC/BTC", 0.0, 1.0), + ("TKN/USDT", 210.30, 1.0), + ], +) def test_get_max_leverage_from_margin(default_conf, mocker, pair, nominal_value, max_lev): - default_conf['trading_mode'] = 'margin' - default_conf['margin_mode'] = 'isolated' + default_conf["trading_mode"] = "margin" + default_conf["margin_mode"] = "isolated" api_mock = MagicMock() - type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False}) + type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": False}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gate") assert exchange.get_max_leverage(pair, nominal_value) == max_lev @pytest.mark.parametrize( - 'size,funding_rate,mark_price,time_in_ratio,funding_fee,kraken_fee', [ + "size,funding_rate,mark_price,time_in_ratio,funding_fee,kraken_fee", + [ (10, 0.0001, 2.0, 1.0, 0.002, 0.002), (10, 0.0002, 2.0, 0.01, 0.004, 0.00004), (10, 0.0002, 2.5, None, 0.005, None), (10, 0.0002, NaN, None, 0.0, None), - ]) + ], +) def test_calculate_funding_fees( - default_conf, - mocker, - size, - funding_rate, - mark_price, - funding_fee, - kraken_fee, - time_in_ratio + default_conf, mocker, size, funding_rate, mark_price, funding_fee, kraken_fee, time_in_ratio ): exchange = get_patched_exchange(mocker, default_conf) kraken = get_patched_exchange(mocker, default_conf, id="kraken") - prior_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc) - timedelta(hours=1)) - trade_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc)) - funding_rates = DataFrame([ - {'date': prior_date, 'open': funding_rate}, # Line not used. - {'date': trade_date, 'open': funding_rate}, - ]) - mark_rates = DataFrame([ - {'date': prior_date, 'open': mark_price}, - {'date': trade_date, 'open': mark_price}, - ]) + prior_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc) - timedelta(hours=1)) + trade_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc)) + funding_rates = DataFrame( + [ + {"date": prior_date, "open": funding_rate}, # Line not used. + {"date": trade_date, "open": funding_rate}, + ] + ) + mark_rates = DataFrame( + [ + {"date": prior_date, "open": mark_price}, + {"date": trade_date, "open": mark_price}, + ] + ) df = exchange.combine_funding_and_mark(funding_rates, mark_rates) - assert exchange.calculate_funding_fees( - df, - amount=size, - is_short=True, - open_date=trade_date, - close_date=trade_date, - time_in_ratio=time_in_ratio, - ) == funding_fee + assert ( + exchange.calculate_funding_fees( + df, + amount=size, + is_short=True, + open_date=trade_date, + close_date=trade_date, + time_in_ratio=time_in_ratio, + ) + == funding_fee + ) - if (kraken_fee is None): + if kraken_fee is None: with pytest.raises(OperationalException): kraken.calculate_funding_fees( df, @@ -4279,23 +4909,28 @@ def test_calculate_funding_fees( ) else: - assert kraken.calculate_funding_fees( - df, - amount=size, - is_short=True, - open_date=trade_date, - close_date=trade_date, - time_in_ratio=time_in_ratio, - ) == kraken_fee + assert ( + kraken.calculate_funding_fees( + df, + amount=size, + is_short=True, + open_date=trade_date, + close_date=trade_date, + time_in_ratio=time_in_ratio, + ) + == kraken_fee + ) @pytest.mark.parametrize( - 'mark_price,funding_rate,futures_funding_rate', [ + "mark_price,funding_rate,futures_funding_rate", + [ (1000, 0.001, None), (1000, 0.001, 0.01), (1000, 0.001, 0.0), (1000, 0.001, -0.01), - ]) + ], +) def test_combine_funding_and_mark( default_conf, mocker, @@ -4304,88 +4939,99 @@ def test_combine_funding_and_mark( futures_funding_rate, ): exchange = get_patched_exchange(mocker, default_conf) - prior2_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc) - timedelta(hours=2)) - prior_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc) - timedelta(hours=1)) - trade_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc)) - funding_rates = DataFrame([ - {'date': prior2_date, 'open': funding_rate}, - {'date': prior_date, 'open': funding_rate}, - {'date': trade_date, 'open': funding_rate}, - ]) - mark_rates = DataFrame([ - {'date': prior2_date, 'open': mark_price}, - {'date': prior_date, 'open': mark_price}, - {'date': trade_date, 'open': mark_price}, - ]) + prior2_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc) - timedelta(hours=2)) + prior_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc) - timedelta(hours=1)) + trade_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc)) + funding_rates = DataFrame( + [ + {"date": prior2_date, "open": funding_rate}, + {"date": prior_date, "open": funding_rate}, + {"date": trade_date, "open": funding_rate}, + ] + ) + mark_rates = DataFrame( + [ + {"date": prior2_date, "open": mark_price}, + {"date": prior_date, "open": mark_price}, + {"date": trade_date, "open": mark_price}, + ] + ) df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate) - assert 'open_mark' in df.columns - assert 'open_fund' in df.columns + assert "open_mark" in df.columns + assert "open_fund" in df.columns assert len(df) == 3 - funding_rates = DataFrame([ - {'date': trade_date, 'open': funding_rate}, - ]) - mark_rates = DataFrame([ - {'date': prior2_date, 'open': mark_price}, - {'date': prior_date, 'open': mark_price}, - {'date': trade_date, 'open': mark_price}, - ]) + funding_rates = DataFrame( + [ + {"date": trade_date, "open": funding_rate}, + ] + ) + mark_rates = DataFrame( + [ + {"date": prior2_date, "open": mark_price}, + {"date": prior_date, "open": mark_price}, + {"date": trade_date, "open": mark_price}, + ] + ) df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate) if futures_funding_rate is not None: assert len(df) == 3 - assert df.iloc[0]['open_fund'] == futures_funding_rate - assert df.iloc[1]['open_fund'] == futures_funding_rate - assert df.iloc[2]['open_fund'] == funding_rate + assert df.iloc[0]["open_fund"] == futures_funding_rate + assert df.iloc[1]["open_fund"] == futures_funding_rate + assert df.iloc[2]["open_fund"] == funding_rate else: assert len(df) == 1 # Empty funding rates - funding_rates2 = DataFrame([], columns=['date', 'open']) + funding_rates2 = DataFrame([], columns=["date", "open"]) df = exchange.combine_funding_and_mark(funding_rates2, mark_rates, futures_funding_rate) if futures_funding_rate is not None: assert len(df) == 3 - assert df.iloc[0]['open_fund'] == futures_funding_rate - assert df.iloc[1]['open_fund'] == futures_funding_rate - assert df.iloc[2]['open_fund'] == futures_funding_rate + assert df.iloc[0]["open_fund"] == futures_funding_rate + assert df.iloc[1]["open_fund"] == futures_funding_rate + assert df.iloc[2]["open_fund"] == futures_funding_rate else: assert len(df) == 0 # Empty mark candles - mark_candles = DataFrame([], columns=['date', 'open']) + mark_candles = DataFrame([], columns=["date", "open"]) df = exchange.combine_funding_and_mark(funding_rates, mark_candles, futures_funding_rate) assert len(df) == 0 -@pytest.mark.parametrize('exchange,rate_start,rate_end,d1,d2,amount,expected_fees', [ - ('binance', 0, 2, "2021-09-01 01:00:00", "2021-09-01 04:00:00", 30.0, 0.0), - ('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.00091409999), - ('binance', 0, 2, "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0002493), - ('binance', 1, 2, "2021-09-01 01:00:14", "2021-09-01 08:00:00", 30.0, -0.0002493), - ('binance', 1, 2, "2021-09-01 00:00:16", "2021-09-01 08:00:00", 30.0, -0.0002493), - ('binance', 0, 1, "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.00066479999), - ('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.00091409999), - # :01 must be rounded down. - ('binance', 0, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.00091409999), - ('binance', 0, 2, "2021-08-31 23:58:00", "2021-09-01 08:00:00", 30.0, -0.00091409999), - ('binance', 0, 2, "2021-09-01 00:10:01", "2021-09-01 08:00:00", 30.0, -0.0002493), - # TODO: Uncomment once _calculate_funding_fees can pass time_in_ratio to exchange. - # ('kraken', "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0014937), - # ('kraken', "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0008289), - # ('kraken', "2021-09-01 01:00:14", "2021-09-01 08:00:00", 30.0, -0.0008289), - # ('kraken', "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.0012443999999999999), - # ('kraken', "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0045759), - # ('kraken', "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0008289), - ('gate', 0, 2, "2021-09-01 00:10:00", "2021-09-01 04:00:00", 30.0, 0.0), - ('gate', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999), - ('gate', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.0009140999), - ('gate', 1, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0002493), - ('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, -0.0015235), - # TODO: Uncomment once _calculate_funding_fees can pass time_in_ratio to exchange. - # ('kraken', "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, -0.0024895), -]) +@pytest.mark.parametrize( + "exchange,rate_start,rate_end,d1,d2,amount,expected_fees", + [ + ("binance", 0, 2, "2021-09-01 01:00:00", "2021-09-01 04:00:00", 30.0, 0.0), + ("binance", 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.00091409999), + ("binance", 0, 2, "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0002493), + ("binance", 1, 2, "2021-09-01 01:00:14", "2021-09-01 08:00:00", 30.0, -0.0002493), + ("binance", 1, 2, "2021-09-01 00:00:16", "2021-09-01 08:00:00", 30.0, -0.0002493), + ("binance", 0, 1, "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.00066479999), + ("binance", 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.00091409999), + # :01 must be rounded down. + ("binance", 0, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.00091409999), + ("binance", 0, 2, "2021-08-31 23:58:00", "2021-09-01 08:00:00", 30.0, -0.00091409999), + ("binance", 0, 2, "2021-09-01 00:10:01", "2021-09-01 08:00:00", 30.0, -0.0002493), + # TODO: Uncomment once _calculate_funding_fees can pass time_in_ratio to exchange. + # ('kraken', "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0014937), + # ('kraken', "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0008289), + # ('kraken', "2021-09-01 01:00:14", "2021-09-01 08:00:00", 30.0, -0.0008289), + # ('kraken', "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.0012443999999999999), + # ('kraken', "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0045759), + # ('kraken', "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0008289), + ("gate", 0, 2, "2021-09-01 00:10:00", "2021-09-01 04:00:00", 30.0, 0.0), + ("gate", 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999), + ("gate", 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.0009140999), + ("gate", 1, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0002493), + ("binance", 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, -0.0015235), + # TODO: Uncomment once _calculate_funding_fees can pass time_in_ratio to exchange. + # ('kraken', "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, -0.0024895), + ], +) def test__fetch_and_calculate_funding_fees( mocker, default_conf, @@ -4398,7 +5044,7 @@ def test__fetch_and_calculate_funding_fees( d1, d2, amount, - expected_fees + expected_fees, ): """ nominal_value = mark_price * size @@ -4435,26 +5081,28 @@ def test__fetch_and_calculate_funding_fees( time: 12, mark: 2.81, nominal_value: 140.5, fundRate: 0.000072, fundFee: 0.010116 time: 13, mark: 2.82, nominal_value: 141.0, fundRate: 0.000097, fundFee: 0.013677 """ - d1 = datetime.strptime(f"{d1} +0000", '%Y-%m-%d %H:%M:%S %z') - d2 = datetime.strptime(f"{d2} +0000", '%Y-%m-%d %H:%M:%S %z') + d1 = datetime.strptime(f"{d1} +0000", "%Y-%m-%d %H:%M:%S %z") + d2 = datetime.strptime(f"{d2} +0000", "%Y-%m-%d %H:%M:%S %z") funding_rate_history = { - 'binance': funding_rate_history_octohourly, - 'gate': funding_rate_history_octohourly, + "binance": funding_rate_history_octohourly, + "gate": funding_rate_history_octohourly, }[exchange][rate_start:rate_end] api_mock = MagicMock() api_mock.fetch_funding_rate_history = get_mock_coro(return_value=funding_rate_history) api_mock.fetch_ohlcv = get_mock_coro(return_value=mark_ohlcv) - type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) - type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True}) + type(api_mock).has = PropertyMock(return_value={"fetchOHLCV": True}) + type(api_mock).has = PropertyMock(return_value={"fetchFundingRateHistory": True}) ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) - mocker.patch(f'{EXMS}.timeframes', PropertyMock(return_value=['1h', '4h', '8h'])) + mocker.patch(f"{EXMS}.timeframes", PropertyMock(return_value=["1h", "4h", "8h"])) funding_fees = ex._fetch_and_calculate_funding_fees( - pair='ADA/USDT:USDT', amount=amount, is_short=True, open_date=d1, close_date=d2) + pair="ADA/USDT:USDT", amount=amount, is_short=True, open_date=d1, close_date=d2 + ) assert pytest.approx(funding_fees) == expected_fees # Fees for Longs are inverted funding_fees = ex._fetch_and_calculate_funding_fees( - pair='ADA/USDT:USDT', amount=amount, is_short=False, open_date=d1, close_date=d2) + pair="ADA/USDT:USDT", amount=amount, is_short=False, open_date=d1, close_date=d2 + ) assert pytest.approx(funding_fees) == -expected_fees # Return empty "refresh_latest" @@ -4462,13 +5110,17 @@ def test__fetch_and_calculate_funding_fees( ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) with pytest.raises(ExchangeError, match="Could not find funding rates."): ex._fetch_and_calculate_funding_fees( - pair='ADA/USDT:USDT', amount=amount, is_short=False, open_date=d1, close_date=d2) + pair="ADA/USDT:USDT", amount=amount, is_short=False, open_date=d1, close_date=d2 + ) -@pytest.mark.parametrize('exchange,expected_fees', [ - ('binance', -0.0009140999999999999), - ('gate', -0.0009140999999999999), -]) +@pytest.mark.parametrize( + "exchange,expected_fees", + [ + ("binance", -0.0009140999999999999), + ("gate", -0.0009140999999999999), + ], +) def test__fetch_and_calculate_funding_fees_datetime_called( mocker, default_conf, @@ -4476,62 +5128,72 @@ def test__fetch_and_calculate_funding_fees_datetime_called( mark_ohlcv, exchange, time_machine, - expected_fees + expected_fees, ): api_mock = MagicMock() api_mock.fetch_ohlcv = get_mock_coro(return_value=mark_ohlcv) api_mock.fetch_funding_rate_history = get_mock_coro( - return_value=funding_rate_history_octohourly) - type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) - type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True}) - mocker.patch(f'{EXMS}.timeframes', PropertyMock(return_value=['4h', '8h'])) + return_value=funding_rate_history_octohourly + ) + type(api_mock).has = PropertyMock(return_value={"fetchOHLCV": True}) + type(api_mock).has = PropertyMock(return_value={"fetchFundingRateHistory": True}) + mocker.patch(f"{EXMS}.timeframes", PropertyMock(return_value=["4h", "8h"])) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) - d1 = datetime.strptime("2021-08-31 23:00:01 +0000", '%Y-%m-%d %H:%M:%S %z') + d1 = datetime.strptime("2021-08-31 23:00:01 +0000", "%Y-%m-%d %H:%M:%S %z") time_machine.move_to("2021-09-01 08:00:00 +00:00") - funding_fees = exchange._fetch_and_calculate_funding_fees('ADA/USDT', 30.0, True, d1) + funding_fees = exchange._fetch_and_calculate_funding_fees("ADA/USDT", 30.0, True, d1) assert funding_fees == expected_fees - funding_fees = exchange._fetch_and_calculate_funding_fees('ADA/USDT', 30.0, False, d1) + funding_fees = exchange._fetch_and_calculate_funding_fees("ADA/USDT", 30.0, False, d1) assert funding_fees == 0 - expected_fees -@pytest.mark.parametrize('pair,expected_size,trading_mode', [ - ('XLTCUSDT', 1, 'spot'), - ('LTC/USD', 1, 'futures'), - ('XLTCUSDT', 0.01, 'futures'), - ('ETH/USDT:USDT', 10, 'futures'), - ('TORN/USDT:USDT', None, 'futures'), # Don't fail for unavailable pairs. -]) +@pytest.mark.parametrize( + "pair,expected_size,trading_mode", + [ + ("XLTCUSDT", 1, "spot"), + ("LTC/USD", 1, "futures"), + ("XLTCUSDT", 0.01, "futures"), + ("ETH/USDT:USDT", 10, "futures"), + ("TORN/USDT:USDT", None, "futures"), # Don't fail for unavailable pairs. + ], +) def test__get_contract_size(mocker, default_conf, pair, expected_size, trading_mode): api_mock = MagicMock() - default_conf['trading_mode'] = trading_mode - default_conf['margin_mode'] = 'isolated' + default_conf["trading_mode"] = trading_mode + default_conf["margin_mode"] = "isolated" exchange = get_patched_exchange(mocker, default_conf, api_mock) - mocker.patch(f'{EXMS}.markets', { - 'LTC/USD': { - 'symbol': 'LTC/USD', - 'contractSize': None, + mocker.patch( + f"{EXMS}.markets", + { + "LTC/USD": { + "symbol": "LTC/USD", + "contractSize": None, + }, + "XLTCUSDT": { + "symbol": "XLTCUSDT", + "contractSize": "0.01", + }, + "ETH/USDT:USDT": { + "symbol": "ETH/USDT:USDT", + "contractSize": "10", + }, }, - 'XLTCUSDT': { - 'symbol': 'XLTCUSDT', - 'contractSize': '0.01', - }, - 'ETH/USDT:USDT': { - 'symbol': 'ETH/USDT:USDT', - 'contractSize': '10', - } - }) + ) size = exchange.get_contract_size(pair) assert expected_size == size -@pytest.mark.parametrize('pair,contract_size,trading_mode', [ - ('XLTCUSDT', 1, 'spot'), - ('LTC/USD', 1, 'futures'), - ('ADA/USDT:USDT', 0.01, 'futures'), - ('LTC/ETH', 1, 'futures'), - ('ETH/USDT:USDT', 10, 'futures'), -]) +@pytest.mark.parametrize( + "pair,contract_size,trading_mode", + [ + ("XLTCUSDT", 1, "spot"), + ("LTC/USD", 1, "futures"), + ("ADA/USDT:USDT", 0.01, "futures"), + ("LTC/ETH", 1, "futures"), + ("ETH/USDT:USDT", 10, "futures"), + ], +) def test__order_contracts_to_amount( mocker, default_conf, @@ -4541,119 +5203,126 @@ def test__order_contracts_to_amount( trading_mode, ): api_mock = MagicMock() - default_conf['trading_mode'] = trading_mode - default_conf['margin_mode'] = 'isolated' - mocker.patch(f'{EXMS}.markets', markets) + default_conf["trading_mode"] = trading_mode + default_conf["margin_mode"] = "isolated" + mocker.patch(f"{EXMS}.markets", markets) exchange = get_patched_exchange(mocker, default_conf, api_mock) orders = [ { - 'id': '123456320', - 'clientOrderId': '12345632018', - 'timestamp': 1640124992000, - 'datetime': 'Tue 21 Dec 2021 22:16:32 UTC', - 'lastTradeTimestamp': 1640124911000, - 'status': 'active', - 'symbol': pair, - 'type': 'limit', - 'timeInForce': 'gtc', - 'postOnly': None, - 'side': 'buy', - 'price': 2.0, - 'stopPrice': None, - 'average': None, - 'amount': 30.0, - 'cost': 60.0, - 'filled': None, - 'remaining': 30.0, - 'fee': { - 'currency': 'USDT', - 'cost': 0.06, + "id": "123456320", + "clientOrderId": "12345632018", + "timestamp": 1640124992000, + "datetime": "Tue 21 Dec 2021 22:16:32 UTC", + "lastTradeTimestamp": 1640124911000, + "status": "active", + "symbol": pair, + "type": "limit", + "timeInForce": "gtc", + "postOnly": None, + "side": "buy", + "price": 2.0, + "stopPrice": None, + "average": None, + "amount": 30.0, + "cost": 60.0, + "filled": None, + "remaining": 30.0, + "fee": { + "currency": "USDT", + "cost": 0.06, }, - 'fees': [{ - 'currency': 'USDT', - 'cost': 0.06, - }], - 'trades': None, - 'info': {}, + "fees": [ + { + "currency": "USDT", + "cost": 0.06, + } + ], + "trades": None, + "info": {}, }, { - 'id': '123456380', - 'clientOrderId': '12345638203', - 'timestamp': 1640124992000, - 'datetime': 'Tue 21 Dec 2021 22:16:32 UTC', - 'lastTradeTimestamp': 1640124911000, - 'status': 'active', - 'symbol': pair, - 'type': 'limit', - 'timeInForce': 'gtc', - 'postOnly': None, - 'side': 'sell', - 'price': 2.2, - 'stopPrice': None, - 'average': None, - 'amount': 40.0, - 'cost': 80.0, - 'filled': None, - 'remaining': 40.0, - 'fee': { - 'currency': 'USDT', - 'cost': 0.08, + "id": "123456380", + "clientOrderId": "12345638203", + "timestamp": 1640124992000, + "datetime": "Tue 21 Dec 2021 22:16:32 UTC", + "lastTradeTimestamp": 1640124911000, + "status": "active", + "symbol": pair, + "type": "limit", + "timeInForce": "gtc", + "postOnly": None, + "side": "sell", + "price": 2.2, + "stopPrice": None, + "average": None, + "amount": 40.0, + "cost": 80.0, + "filled": None, + "remaining": 40.0, + "fee": { + "currency": "USDT", + "cost": 0.08, }, - 'fees': [{ - 'currency': 'USDT', - 'cost': 0.08, - }], - 'trades': None, - 'info': {}, + "fees": [ + { + "currency": "USDT", + "cost": 0.08, + } + ], + "trades": None, + "info": {}, }, { # Realistic stoploss order on gate. - 'id': '123456380', - 'clientOrderId': '12345638203', - 'timestamp': None, - 'datetime': None, - 'lastTradeTimestamp': None, - 'status': None, - 'symbol': None, - 'type': None, - 'timeInForce': None, - 'postOnly': None, - 'side': None, - 'price': None, - 'stopPrice': None, - 'average': None, - 'amount': None, - 'cost': None, - 'filled': None, - 'remaining': None, - 'fee': None, - 'fees': [], - 'trades': None, - 'info': {}, + "id": "123456380", + "clientOrderId": "12345638203", + "timestamp": None, + "datetime": None, + "lastTradeTimestamp": None, + "status": None, + "symbol": None, + "type": None, + "timeInForce": None, + "postOnly": None, + "side": None, + "price": None, + "stopPrice": None, + "average": None, + "amount": None, + "cost": None, + "filled": None, + "remaining": None, + "fee": None, + "fees": [], + "trades": None, + "info": {}, }, ] order1_bef = orders[0] order2_bef = orders[1] order1 = exchange._order_contracts_to_amount(deepcopy(order1_bef)) order2 = exchange._order_contracts_to_amount(deepcopy(order2_bef)) - assert order1['amount'] == order1_bef['amount'] * contract_size - assert order1['cost'] == order1_bef['cost'] * contract_size + assert order1["amount"] == order1_bef["amount"] * contract_size + assert order1["cost"] == order1_bef["cost"] * contract_size - assert order2['amount'] == order2_bef['amount'] * contract_size - assert order2['cost'] == order2_bef['cost'] * contract_size + assert order2["amount"] == order2_bef["amount"] * contract_size + assert order2["cost"] == order2_bef["cost"] * contract_size # Don't fail exchange._order_contracts_to_amount(orders[2]) -@pytest.mark.parametrize('pair,contract_size,trading_mode', [ - ('XLTCUSDT', 1, 'spot'), - ('LTC/USD', 1, 'futures'), - ('ADA/USDT:USDT', 0.01, 'futures'), - ('LTC/ETH', 1, 'futures'), - ('ETH/USDT:USDT', 10, 'futures'), -]) +@pytest.mark.parametrize( + "pair,contract_size,trading_mode", + [ + ("XLTCUSDT", 1, "spot"), + ("LTC/USD", 1, "futures"), + ("ADA/USDT:USDT", 0.01, "futures"), + ("LTC/ETH", 1, "futures"), + ("ETH/USDT:USDT", 10, "futures"), + ], +) def test__trades_contracts_to_amount( mocker, default_conf, @@ -4663,67 +5332,67 @@ def test__trades_contracts_to_amount( trading_mode, ): api_mock = MagicMock() - default_conf['trading_mode'] = trading_mode - default_conf['margin_mode'] = 'isolated' - mocker.patch(f'{EXMS}.markets', markets) + default_conf["trading_mode"] = trading_mode + default_conf["margin_mode"] = "isolated" + mocker.patch(f"{EXMS}.markets", markets) exchange = get_patched_exchange(mocker, default_conf, api_mock) trades = [ { - 'symbol': pair, - 'amount': 30.0, + "symbol": pair, + "amount": 30.0, }, { - 'symbol': pair, - 'amount': 40.0, - } + "symbol": pair, + "amount": 40.0, + }, ] new_amount_trades = exchange._trades_contracts_to_amount(trades) - assert new_amount_trades[0]['amount'] == 30.0 * contract_size - assert new_amount_trades[1]['amount'] == 40.0 * contract_size + assert new_amount_trades[0]["amount"] == 30.0 * contract_size + assert new_amount_trades[1]["amount"] == 40.0 * contract_size -@pytest.mark.parametrize('pair,param_amount,param_size', [ - ('ADA/USDT:USDT', 40, 4000), - ('LTC/ETH', 30, 30), - ('LTC/USD', 30, 30), - ('ETH/USDT:USDT', 10, 1), -]) -def test__amount_to_contracts( - mocker, - default_conf, - pair, - param_amount, - param_size -): +@pytest.mark.parametrize( + "pair,param_amount,param_size", + [ + ("ADA/USDT:USDT", 40, 4000), + ("LTC/ETH", 30, 30), + ("LTC/USD", 30, 30), + ("ETH/USDT:USDT", 10, 1), + ], +) +def test__amount_to_contracts(mocker, default_conf, pair, param_amount, param_size): api_mock = MagicMock() - default_conf['trading_mode'] = 'spot' - default_conf['margin_mode'] = 'isolated' + default_conf["trading_mode"] = "spot" + default_conf["margin_mode"] = "isolated" exchange = get_patched_exchange(mocker, default_conf, api_mock) - mocker.patch(f'{EXMS}.markets', { - 'LTC/USD': { - 'symbol': 'LTC/USD', - 'contractSize': None, + mocker.patch( + f"{EXMS}.markets", + { + "LTC/USD": { + "symbol": "LTC/USD", + "contractSize": None, + }, + "XLTCUSDT": { + "symbol": "XLTCUSDT", + "contractSize": "0.01", + }, + "LTC/ETH": { + "symbol": "LTC/ETH", + }, + "ETH/USDT:USDT": { + "symbol": "ETH/USDT:USDT", + "contractSize": "10", + }, }, - 'XLTCUSDT': { - 'symbol': 'XLTCUSDT', - 'contractSize': '0.01', - }, - 'LTC/ETH': { - 'symbol': 'LTC/ETH', - }, - 'ETH/USDT:USDT': { - 'symbol': 'ETH/USDT:USDT', - 'contractSize': '10', - } - }) + ) result_size = exchange._amount_to_contracts(pair, param_amount) assert result_size == param_amount result_amount = exchange._contracts_to_amount(pair, param_size) assert result_amount == param_size - default_conf['trading_mode'] = 'futures' + default_conf["trading_mode"] = "futures" exchange = get_patched_exchange(mocker, default_conf, api_mock) result_size = exchange._amount_to_contracts(pair, param_amount) assert result_size == param_size @@ -4731,18 +5400,21 @@ def test__amount_to_contracts( assert result_amount == param_amount -@pytest.mark.parametrize('pair,amount,expected_spot,expected_fut', [ - # Contract size of 0.01 - ('ADA/USDT:USDT', 40, 40, 40), - ('ADA/USDT:USDT', 10.4445555, 10.4, 10.444), - ('LTC/ETH', 30, 30, 30), - ('LTC/USD', 30, 30, 30), - ('ADA/USDT:USDT', 1.17, 1.1, 1.17), - # contract size of 10 - ('ETH/USDT:USDT', 10.111, 10.1, 10), - ('ETH/USDT:USDT', 10.188, 10.1, 10), - ('ETH/USDT:USDT', 10.988, 10.9, 10), -]) +@pytest.mark.parametrize( + "pair,amount,expected_spot,expected_fut", + [ + # Contract size of 0.01 + ("ADA/USDT:USDT", 40, 40, 40), + ("ADA/USDT:USDT", 10.4445555, 10.4, 10.444), + ("LTC/ETH", 30, 30, 30), + ("LTC/USD", 30, 30, 30), + ("ADA/USDT:USDT", 1.17, 1.1, 1.17), + # contract size of 10 + ("ETH/USDT:USDT", 10.111, 10.1, 10), + ("ETH/USDT:USDT", 10.188, 10.1, 10), + ("ETH/USDT:USDT", 10.988, 10.9, 10), + ], +) def test_amount_to_contract_precision( mocker, default_conf, @@ -4752,88 +5424,157 @@ def test_amount_to_contract_precision( expected_fut, ): api_mock = MagicMock() - default_conf['trading_mode'] = 'spot' - default_conf['margin_mode'] = 'isolated' + default_conf["trading_mode"] = "spot" + default_conf["margin_mode"] = "isolated" exchange = get_patched_exchange(mocker, default_conf, api_mock) result_size = exchange.amount_to_contract_precision(pair, amount) assert result_size == expected_spot - default_conf['trading_mode'] = 'futures' + default_conf["trading_mode"] = "futures" exchange = get_patched_exchange(mocker, default_conf, api_mock) result_size = exchange.amount_to_contract_precision(pair, amount) assert result_size == expected_fut -@pytest.mark.parametrize('exchange_name,open_rate,is_short,trading_mode,margin_mode', [ - # Bybit - ('bybit', 2.0, False, 'spot', None), - ('bybit', 2.0, False, 'spot', 'cross'), - ('bybit', 2.0, True, 'spot', 'isolated'), - # Binance - ('binance', 2.0, False, 'spot', None), - ('binance', 2.0, False, 'spot', 'cross'), - ('binance', 2.0, True, 'spot', 'isolated'), -]) +@pytest.mark.parametrize( + "exchange_name,open_rate,is_short,trading_mode,margin_mode", + [ + # Bybit + ("bybit", 2.0, False, "spot", None), + ("bybit", 2.0, False, "spot", "cross"), + ("bybit", 2.0, True, "spot", "isolated"), + # Binance + ("binance", 2.0, False, "spot", None), + ("binance", 2.0, False, "spot", "cross"), + ("binance", 2.0, True, "spot", "isolated"), + ], +) def test_liquidation_price_is_none( + mocker, default_conf, exchange_name, open_rate, is_short, trading_mode, margin_mode +): + default_conf["trading_mode"] = trading_mode + default_conf["margin_mode"] = margin_mode + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + assert ( + exchange.get_liquidation_price( + pair="DOGE/USDT", + open_rate=open_rate, + is_short=is_short, + amount=71200.81144, + stake_amount=open_rate * 71200.81144, + leverage=5, + wallet_balance=-56354.57, + mm_ex_1=0.10, + upnl_ex_1=0.0, + ) + is None + ) + + +@pytest.mark.parametrize( + "exchange_name, is_short, trading_mode, margin_mode, wallet_balance, " + "mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, " + "mm_ratio, expected", + [ + ( + "binance", + False, + "futures", + "isolated", + 1535443.01, + 0.0, + 0.0, + 135365.00, + 3683.979, + 1456.84, + 0.10, + 1114.78, + ), + ( + "binance", + False, + "futures", + "isolated", + 1535443.01, + 0.0, + 0.0, + 16300.000, + 109.488, + 32481.980, + 0.025, + 18778.73, + ), + ( + "binance", + False, + "futures", + "cross", + 1535443.01, + 71200.81144, + -56354.57, + 135365.00, + 3683.979, + 1456.84, + 0.10, + 1153.26, + ), + ( + "binance", + False, + "futures", + "cross", + 1535443.01, + 356512.508, + -448192.89, + 16300.000, + 109.488, + 32481.980, + 0.025, + 26316.89, + ), + ], +) +def test_liquidation_price_binance( mocker, default_conf, exchange_name, open_rate, is_short, trading_mode, - margin_mode + margin_mode, + wallet_balance, + mm_ex_1, + upnl_ex_1, + maintenance_amt, + amount, + mm_ratio, + expected, ): - default_conf['trading_mode'] = trading_mode - default_conf['margin_mode'] = margin_mode - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - assert exchange.get_liquidation_price( - pair='DOGE/USDT', - open_rate=open_rate, - is_short=is_short, - amount=71200.81144, - stake_amount=open_rate * 71200.81144, - leverage=5, - wallet_balance=-56354.57, - mm_ex_1=0.10, - upnl_ex_1=0.0 - ) is None - - -@pytest.mark.parametrize( - 'exchange_name, is_short, trading_mode, margin_mode, wallet_balance, ' - 'mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, ' - 'mm_ratio, expected', - [ - ("binance", False, 'futures', 'isolated', 1535443.01, 0.0, - 0.0, 135365.00, 3683.979, 1456.84, 0.10, 1114.78), - ("binance", False, 'futures', 'isolated', 1535443.01, 0.0, - 0.0, 16300.000, 109.488, 32481.980, 0.025, 18778.73), - ("binance", False, 'futures', 'cross', 1535443.01, 71200.81144, - -56354.57, 135365.00, 3683.979, 1456.84, 0.10, 1153.26), - ("binance", False, 'futures', 'cross', 1535443.01, 356512.508, - -448192.89, 16300.000, 109.488, 32481.980, 0.025, 26316.89) - ]) -def test_liquidation_price_binance( - mocker, default_conf, exchange_name, open_rate, is_short, trading_mode, - margin_mode, wallet_balance, mm_ex_1, upnl_ex_1, maintenance_amt, amount, mm_ratio, expected -): - default_conf['trading_mode'] = trading_mode - default_conf['margin_mode'] = margin_mode - default_conf['liquidation_buffer'] = 0.0 + default_conf["trading_mode"] = trading_mode + default_conf["margin_mode"] = margin_mode + default_conf["liquidation_buffer"] = 0.0 exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt)) - assert pytest.approx(round(exchange.get_liquidation_price( - pair='DOGE/USDT', - open_rate=open_rate, - is_short=is_short, - wallet_balance=wallet_balance, - mm_ex_1=mm_ex_1, - upnl_ex_1=upnl_ex_1, - amount=amount, - stake_amount=open_rate * amount, - leverage=5, - ), 2)) == expected + assert ( + pytest.approx( + round( + exchange.get_liquidation_price( + pair="DOGE/USDT", + open_rate=open_rate, + is_short=is_short, + wallet_balance=wallet_balance, + mm_ex_1=mm_ex_1, + upnl_ex_1=upnl_ex_1, + amount=amount, + stake_amount=open_rate * amount, + leverage=5, + ), + 2, + ) + ) + == expected + ) def test_get_max_pair_stake_amount( @@ -4841,198 +5582,164 @@ def test_get_max_pair_stake_amount( default_conf, ): api_mock = MagicMock() - default_conf['margin_mode'] = 'isolated' - default_conf['trading_mode'] = 'futures' + default_conf["margin_mode"] = "isolated" + default_conf["trading_mode"] = "futures" exchange = get_patched_exchange(mocker, default_conf, api_mock) markets = { - 'XRP/USDT:USDT': { - 'limits': { - 'amount': { - 'min': 0.001, - 'max': 10000 - }, - 'cost': { - 'min': 5, - 'max': None - }, + "XRP/USDT:USDT": { + "limits": { + "amount": {"min": 0.001, "max": 10000}, + "cost": {"min": 5, "max": None}, }, - 'contractSize': None, - 'spot': False, + "contractSize": None, + "spot": False, }, - 'LTC/USDT:USDT': { - 'limits': { - 'amount': { - 'min': 0.001, - 'max': None - }, - 'cost': { - 'min': 5, - 'max': None - }, + "LTC/USDT:USDT": { + "limits": { + "amount": {"min": 0.001, "max": None}, + "cost": {"min": 5, "max": None}, }, - 'contractSize': 0.01, - 'spot': False, + "contractSize": 0.01, + "spot": False, }, - 'ETH/USDT:USDT': { - 'limits': { - 'amount': { - 'min': 0.001, - 'max': 10000 - }, - 'cost': { - 'min': 5, - 'max': 30000, + "ETH/USDT:USDT": { + "limits": { + "amount": {"min": 0.001, "max": 10000}, + "cost": { + "min": 5, + "max": 30000, }, }, - 'contractSize': 0.01, - 'spot': False, + "contractSize": 0.01, + "spot": False, }, - 'BTC/USDT': { - 'limits': { - 'amount': { - 'min': 0.001, - 'max': 10000 - }, - 'cost': { - 'min': 5, - 'max': None - }, + "BTC/USDT": { + "limits": { + "amount": {"min": 0.001, "max": 10000}, + "cost": {"min": 5, "max": None}, }, - 'contractSize': 0.01, - 'spot': True, + "contractSize": 0.01, + "spot": True, }, - 'ADA/USDT': { - 'limits': { - 'amount': { - 'min': 0.001, - 'max': 10000 - }, - 'cost': { - 'min': 5, - 'max': 500, + "ADA/USDT": { + "limits": { + "amount": {"min": 0.001, "max": 10000}, + "cost": { + "min": 5, + "max": 500, }, }, - 'contractSize': 0.01, - 'spot': True, + "contractSize": 0.01, + "spot": True, }, - 'DOGE/USDT:USDT': { - 'limits': { - 'amount': { - 'min': 0.001, - 'max': 10000 - }, - 'cost': { - 'min': 5, - 'max': 500 - }, + "DOGE/USDT:USDT": { + "limits": { + "amount": {"min": 0.001, "max": 10000}, + "cost": {"min": 5, "max": 500}, }, - 'contractSize': None, - 'spot': False, + "contractSize": None, + "spot": False, }, - 'LUNA/USDT:USDT': { - 'limits': { - 'amount': { - 'min': 0.001, - 'max': 10000 - }, - 'cost': { - 'min': 5, - 'max': 500 - }, + "LUNA/USDT:USDT": { + "limits": { + "amount": {"min": 0.001, "max": 10000}, + "cost": {"min": 5, "max": 500}, }, - 'contractSize': 0.01, - 'spot': False, + "contractSize": 0.01, + "spot": False, }, } - mocker.patch(f'{EXMS}.markets', markets) - assert exchange.get_max_pair_stake_amount('XRP/USDT:USDT', 2.0) == 20000 - assert exchange.get_max_pair_stake_amount('XRP/USDT:USDT', 2.0, 5) == 4000 - assert exchange.get_max_pair_stake_amount('LTC/USDT:USDT', 2.0) == float('inf') - assert exchange.get_max_pair_stake_amount('ETH/USDT:USDT', 2.0) == 200 - assert exchange.get_max_pair_stake_amount('DOGE/USDT:USDT', 2.0) == 500 - assert exchange.get_max_pair_stake_amount('LUNA/USDT:USDT', 2.0) == 5.0 + mocker.patch(f"{EXMS}.markets", markets) + assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 2.0) == 20000 + assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 2.0, 5) == 4000 + assert exchange.get_max_pair_stake_amount("LTC/USDT:USDT", 2.0) == float("inf") + assert exchange.get_max_pair_stake_amount("ETH/USDT:USDT", 2.0) == 200 + assert exchange.get_max_pair_stake_amount("DOGE/USDT:USDT", 2.0) == 500 + assert exchange.get_max_pair_stake_amount("LUNA/USDT:USDT", 2.0) == 5.0 - default_conf['trading_mode'] = 'spot' + default_conf["trading_mode"] = "spot" exchange = get_patched_exchange(mocker, default_conf, api_mock) - mocker.patch(f'{EXMS}.markets', markets) - assert exchange.get_max_pair_stake_amount('BTC/USDT', 2.0) == 20000 - assert exchange.get_max_pair_stake_amount('ADA/USDT', 2.0) == 500 + mocker.patch(f"{EXMS}.markets", markets) + assert exchange.get_max_pair_stake_amount("BTC/USDT", 2.0) == 20000 + assert exchange.get_max_pair_stake_amount("ADA/USDT", 2.0) == 500 -@pytest.mark.parametrize('exchange_name', EXCHANGES) +@pytest.mark.parametrize("exchange_name", EXCHANGES) def test_load_leverage_tiers(mocker, default_conf, exchange_name): - if exchange_name == 'bybit': + if exchange_name == "bybit": # TODO: remove once get_leverage_tiers workaround has been removed. pytest.skip("Currently skipping") api_mock = MagicMock() api_mock.fetch_leverage_tiers = MagicMock() - type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) - default_conf['dry_run'] = False - mocker.patch(f'{EXMS}.validate_trading_mode_and_margin_mode') + type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": True}) + default_conf["dry_run"] = False + mocker.patch(f"{EXMS}.validate_trading_mode_and_margin_mode") - api_mock.fetch_leverage_tiers = MagicMock(return_value={ - 'ADA/USDT:USDT': [ - { - 'tier': 1, - 'minNotional': 0, - 'maxNotional': 500, - 'maintenanceMarginRate': 0.02, - 'maxLeverage': 75, - 'info': { - 'baseMaxLoan': '', - 'imr': '0.013', - 'instId': '', - 'maxLever': '75', - 'maxSz': '500', - 'minSz': '0', - 'mmr': '0.01', - 'optMgnFactor': '0', - 'quoteMaxLoan': '', - 'tier': '1', - 'uly': 'ADA-USDT' - } - }, - ] - }) + api_mock.fetch_leverage_tiers = MagicMock( + return_value={ + "ADA/USDT:USDT": [ + { + "tier": 1, + "minNotional": 0, + "maxNotional": 500, + "maintenanceMarginRate": 0.02, + "maxLeverage": 75, + "info": { + "baseMaxLoan": "", + "imr": "0.013", + "instId": "", + "maxLever": "75", + "maxSz": "500", + "minSz": "0", + "mmr": "0.01", + "optMgnFactor": "0", + "quoteMaxLoan": "", + "tier": "1", + "uly": "ADA-USDT", + }, + }, + ] + } + ) # SPOT exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.load_leverage_tiers() == {} - default_conf['trading_mode'] = 'futures' - default_conf['margin_mode'] = 'isolated' + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" - if exchange_name != 'binance': + if exchange_name != "binance": # FUTURES has.fetchLeverageTiers == False - type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False}) + type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": False}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.load_leverage_tiers() == {} # FUTURES regular - type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True}) + type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": True}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.load_leverage_tiers() == { - 'ADA/USDT:USDT': [ + "ADA/USDT:USDT": [ { - 'tier': 1, - 'minNotional': 0, - 'maxNotional': 500, - 'maintenanceMarginRate': 0.02, - 'maxLeverage': 75, - 'info': { - 'baseMaxLoan': '', - 'imr': '0.013', - 'instId': '', - 'maxLever': '75', - 'maxSz': '500', - 'minSz': '0', - 'mmr': '0.01', - 'optMgnFactor': '0', - 'quoteMaxLoan': '', - 'tier': '1', - 'uly': 'ADA-USDT' - } + "tier": 1, + "minNotional": 0, + "maxNotional": 500, + "maintenanceMarginRate": 0.02, + "maxLeverage": 75, + "info": { + "baseMaxLoan": "", + "imr": "0.013", + "instId": "", + "maxLever": "75", + "maxSz": "500", + "minSz": "0", + "mmr": "0.01", + "optMgnFactor": "0", + "quoteMaxLoan": "", + "tier": "1", + "uly": "ADA-USDT", + }, }, ] } @@ -5047,16 +5754,16 @@ def test_load_leverage_tiers(mocker, default_conf, exchange_name): ) -@pytest.mark.parametrize('exchange_name', EXCHANGES) +@pytest.mark.parametrize("exchange_name", EXCHANGES) async def test_get_market_leverage_tiers(mocker, default_conf, exchange_name): - default_conf['exchange']['name'] = exchange_name + default_conf["exchange"]["name"] = exchange_name await async_ccxt_exception( mocker, default_conf, MagicMock(), "get_market_leverage_tiers", "fetch_market_leverage_tiers", - symbol='BTC/USDT:USDT' + symbol="BTC/USDT:USDT", ) @@ -5075,8 +5782,8 @@ def test_parse_leverage_tier(mocker, default_conf): "maxNotional": "100000", "minNotional": "0", "maintMarginRatio": "0.025", - "cum": "0.0" - } + "cum": "0.0", + }, } assert exchange.parse_leverage_tier(tier) == { @@ -5088,48 +5795,48 @@ def test_parse_leverage_tier(mocker, default_conf): } tier2 = { - 'tier': 1, - 'minNotional': 0, - 'maxNotional': 2000, - 'maintenanceMarginRate': 0.01, - 'maxLeverage': 75, - 'info': { - 'baseMaxLoan': '', - 'imr': '0.013', - 'instId': '', - 'maxLever': '75', - 'maxSz': '2000', - 'minSz': '0', - 'mmr': '0.01', - 'optMgnFactor': '0', - 'quoteMaxLoan': '', - 'tier': '1', - 'uly': 'SHIB-USDT' - } + "tier": 1, + "minNotional": 0, + "maxNotional": 2000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75, + "info": { + "baseMaxLoan": "", + "imr": "0.013", + "instId": "", + "maxLever": "75", + "maxSz": "2000", + "minSz": "0", + "mmr": "0.01", + "optMgnFactor": "0", + "quoteMaxLoan": "", + "tier": "1", + "uly": "SHIB-USDT", + }, } assert exchange.parse_leverage_tier(tier2) == { - 'minNotional': 0, - 'maxNotional': 2000, - 'maintenanceMarginRate': 0.01, - 'maxLeverage': 75, + "minNotional": 0, + "maxNotional": 2000, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75, "maintAmt": None, } def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage_tiers): api_mock = MagicMock() - default_conf['trading_mode'] = 'futures' - default_conf['margin_mode'] = 'isolated' - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" + mocker.patch(f"{EXMS}.exchange_has", return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange._leverage_tiers = leverage_tiers with pytest.raises( DependencyException, - match='nominal value can not be lower than 0', + match="nominal value can not be lower than 0", ): - exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT:USDT', -1) + exchange.get_maintenance_ratio_and_amt("1000SHIB/USDT:USDT", -1) exchange._leverage_tiers = {} @@ -5137,42 +5844,38 @@ def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage InvalidOrderException, match="Maintenance margin rate for 1000SHIB/USDT:USDT is unavailable for", ): - exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT:USDT', 10000) + exchange.get_maintenance_ratio_and_amt("1000SHIB/USDT:USDT", 10000) -@pytest.mark.parametrize('pair,value,mmr,maintAmt', [ - ('ADA/USDT:USDT', 500, 0.025, 0.0), - ('ADA/USDT:USDT', 20000000, 0.5, 1527500.0), - ('ZEC/USDT:USDT', 500, 0.01, 0.0), - ('ZEC/USDT:USDT', 20000000, 0.5, 654500.0), -]) +@pytest.mark.parametrize( + "pair,value,mmr,maintAmt", + [ + ("ADA/USDT:USDT", 500, 0.025, 0.0), + ("ADA/USDT:USDT", 20000000, 0.5, 1527500.0), + ("ZEC/USDT:USDT", 500, 0.01, 0.0), + ("ZEC/USDT:USDT", 20000000, 0.5, 654500.0), + ], +) def test_get_maintenance_ratio_and_amt( - mocker, - default_conf, - leverage_tiers, - pair, - value, - mmr, - maintAmt + mocker, default_conf, leverage_tiers, pair, value, mmr, maintAmt ): api_mock = MagicMock() - default_conf['trading_mode'] = 'futures' - default_conf['margin_mode'] = 'isolated' - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" + mocker.patch(f"{EXMS}.exchange_has", return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange._leverage_tiers = leverage_tiers assert exchange.get_maintenance_ratio_and_amt(pair, value) == (mmr, maintAmt) def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): - # Test Spot exchange = get_patched_exchange(mocker, default_conf, id="binance") assert exchange.get_max_leverage("BNB/USDT", 100.0) == 1.0 # Test Futures - default_conf['trading_mode'] = 'futures' - default_conf['margin_mode'] = 'isolated' + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange._leverage_tiers = leverage_tiers @@ -5185,104 +5888,114 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): assert exchange.get_max_leverage("BTC/USDT:USDT", 300000000) == 2.0 assert exchange.get_max_leverage("BTC/USDT:USDT", 600000000) == 1.0 # Last tier - assert exchange.get_max_leverage("SPONGE/USDT:USDT", 200) == 1.0 # Pair not in leverage_tiers + assert exchange.get_max_leverage("SPONGE/USDT:USDT", 200) == 1.0 # Pair not in leverage_tiers assert exchange.get_max_leverage("BTC/USDT:USDT", 0.0) == 125.0 # No stake amount with pytest.raises( - InvalidOrderException, - match=r'Amount 1000000000.01 too high for BTC/USDT:USDT' + InvalidOrderException, match=r"Amount 1000000000.01 too high for BTC/USDT:USDT" ): exchange.get_max_leverage("BTC/USDT:USDT", 1000000000.01) -@pytest.mark.parametrize("exchange_name", ['binance', 'kraken', 'gate', 'okx', 'bybit']) +@pytest.mark.parametrize("exchange_name", ["binance", "kraken", "gate", "okx", "bybit"]) def test__get_params(mocker, default_conf, exchange_name): api_mock = MagicMock() - mocker.patch(f'{EXMS}.exchange_has', return_value=True) + mocker.patch(f"{EXMS}.exchange_has", return_value=True) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange._params = {'test': True} + exchange._params = {"test": True} - params1 = {'test': True} + params1 = {"test": True} params2 = { - 'test': True, - 'timeInForce': 'IOC', - 'reduceOnly': True, + "test": True, + "timeInForce": "IOC", + "reduceOnly": True, } - if exchange_name == 'kraken': - params2['leverage'] = 3.0 + if exchange_name == "kraken": + params2["leverage"] = 3.0 - if exchange_name == 'okx': - params2['tdMode'] = 'isolated' - params2['posSide'] = 'net' + if exchange_name == "okx": + params2["tdMode"] = "isolated" + params2["posSide"] = "net" - if exchange_name == 'bybit': - params2['position_idx'] = 0 + if exchange_name == "bybit": + params2["position_idx"] = 0 - assert exchange._get_params( - side="buy", - ordertype='market', - reduceOnly=False, - time_in_force='GTC', - leverage=1.0, - ) == params1 + assert ( + exchange._get_params( + side="buy", + ordertype="market", + reduceOnly=False, + time_in_force="GTC", + leverage=1.0, + ) + == params1 + ) - assert exchange._get_params( - side="buy", - ordertype='market', - reduceOnly=False, - time_in_force='IOC', - leverage=1.0, - ) == params1 + assert ( + exchange._get_params( + side="buy", + ordertype="market", + reduceOnly=False, + time_in_force="IOC", + leverage=1.0, + ) + == params1 + ) - assert exchange._get_params( - side="buy", - ordertype='limit', - reduceOnly=False, - time_in_force='GTC', - leverage=1.0, - ) == params1 + assert ( + exchange._get_params( + side="buy", + ordertype="limit", + reduceOnly=False, + time_in_force="GTC", + leverage=1.0, + ) + == params1 + ) - default_conf['trading_mode'] = 'futures' - default_conf['margin_mode'] = 'isolated' + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange._params = {'test': True} + exchange._params = {"test": True} - assert exchange._get_params( - side="buy", - ordertype='limit', - reduceOnly=True, - time_in_force='IOC', - leverage=3.0, - ) == params2 + assert ( + exchange._get_params( + side="buy", + ordertype="limit", + reduceOnly=True, + time_in_force="IOC", + leverage=3.0, + ) + == params2 + ) def test_get_liquidation_price1(mocker, default_conf): - api_mock = MagicMock() leverage = 9.97 positions = [ { - 'info': {}, - 'symbol': 'NEAR/USDT:USDT', - 'timestamp': 1642164737148, - 'datetime': '2022-01-14T12:52:17.148Z', - 'initialMargin': 1.51072, - 'initialMarginPercentage': 0.1, - 'maintenanceMargin': 0.38916147, - 'maintenanceMarginPercentage': 0.025, - 'entryPrice': 18.884, - 'notional': 15.1072, - 'leverage': leverage, - 'unrealizedPnl': 0.0048, - 'contracts': 8, - 'contractSize': 0.1, - 'marginRatio': None, - 'liquidationPrice': 17.47, - 'markPrice': 18.89, - 'margin_mode': 1.52549075, - 'marginType': 'isolated', - 'side': 'buy', - 'percentage': 0.003177292946409658 + "info": {}, + "symbol": "NEAR/USDT:USDT", + "timestamp": 1642164737148, + "datetime": "2022-01-14T12:52:17.148Z", + "initialMargin": 1.51072, + "initialMarginPercentage": 0.1, + "maintenanceMargin": 0.38916147, + "maintenanceMarginPercentage": 0.025, + "entryPrice": 18.884, + "notional": 15.1072, + "leverage": leverage, + "unrealizedPnl": 0.0048, + "contracts": 8, + "contractSize": 0.1, + "marginRatio": None, + "liquidationPrice": 17.47, + "markPrice": 18.89, + "margin_mode": 1.52549075, + "marginType": "isolated", + "side": "buy", + "percentage": 0.003177292946409658, } ] api_mock.fetch_positions = MagicMock(return_value=positions) @@ -5290,14 +6003,14 @@ def test_get_liquidation_price1(mocker, default_conf): EXMS, exchange_has=MagicMock(return_value=True), ) - default_conf['dry_run'] = False - default_conf['trading_mode'] = 'futures' - default_conf['margin_mode'] = 'isolated' - default_conf['liquidation_buffer'] = 0.0 + default_conf["dry_run"] = False + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" + default_conf["liquidation_buffer"] = 0.0 exchange = get_patched_exchange(mocker, default_conf, api_mock) liq_price = exchange.get_liquidation_price( - pair='NEAR/USDT:USDT', + pair="NEAR/USDT:USDT", open_rate=18.884, is_short=False, amount=0.8, @@ -5307,10 +6020,10 @@ def test_get_liquidation_price1(mocker, default_conf): ) assert liq_price == 17.47 - default_conf['liquidation_buffer'] = 0.05 + default_conf["liquidation_buffer"] = 0.05 exchange = get_patched_exchange(mocker, default_conf, api_mock) liq_price = exchange.get_liquidation_price( - pair='NEAR/USDT:USDT', + pair="NEAR/USDT:USDT", open_rate=18.884, is_short=False, amount=0.8, @@ -5323,7 +6036,7 @@ def test_get_liquidation_price1(mocker, default_conf): api_mock.fetch_positions = MagicMock(return_value=[]) exchange = get_patched_exchange(mocker, default_conf, api_mock) liq_price = exchange.get_liquidation_price( - pair='NEAR/USDT:USDT', + pair="NEAR/USDT:USDT", open_rate=18.884, is_short=False, amount=0.8, @@ -5332,12 +6045,12 @@ def test_get_liquidation_price1(mocker, default_conf): wallet_balance=18.884 * 0.8, ) assert liq_price is None - default_conf['trading_mode'] = 'margin' + default_conf["trading_mode"] = "margin" exchange = get_patched_exchange(mocker, default_conf, api_mock) - with pytest.raises(OperationalException, match=r'.*does not support .* margin'): + with pytest.raises(OperationalException, match=r".*does not support .* margin"): exchange.get_liquidation_price( - pair='NEAR/USDT:USDT', + pair="NEAR/USDT:USDT", open_rate=18.884, is_short=False, amount=0.8, @@ -5347,46 +6060,47 @@ def test_get_liquidation_price1(mocker, default_conf): ) -@pytest.mark.parametrize('liquidation_buffer', [0.0]) +@pytest.mark.parametrize("liquidation_buffer", [0.0]) @pytest.mark.parametrize( - "is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [ - (False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None), - (True, 'spot', 'binance', '', 5.0, 10.0, 1.0, None), - (False, 'spot', 'gate', '', 5.0, 10.0, 1.0, None), - (True, 'spot', 'gate', '', 5.0, 10.0, 1.0, None), - (False, 'spot', 'okx', '', 5.0, 10.0, 1.0, None), - (True, 'spot', 'okx', '', 5.0, 10.0, 1.0, None), + "is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", + [ + (False, "spot", "binance", "", 5.0, 10.0, 1.0, None), + (True, "spot", "binance", "", 5.0, 10.0, 1.0, None), + (False, "spot", "gate", "", 5.0, 10.0, 1.0, None), + (True, "spot", "gate", "", 5.0, 10.0, 1.0, None), + (False, "spot", "okx", "", 5.0, 10.0, 1.0, None), + (True, "spot", "okx", "", 5.0, 10.0, 1.0, None), # Binance, short - (True, 'futures', 'binance', 'isolated', 5.0, 10.0, 1.0, 11.89108910891089), - (True, 'futures', 'binance', 'isolated', 3.0, 10.0, 1.0, 13.211221122079207), - (True, 'futures', 'binance', 'isolated', 5.0, 8.0, 1.0, 9.514851485148514), - (True, 'futures', 'binance', 'isolated', 5.0, 10.0, 0.6, 11.897689768976898), + (True, "futures", "binance", "isolated", 5.0, 10.0, 1.0, 11.89108910891089), + (True, "futures", "binance", "isolated", 3.0, 10.0, 1.0, 13.211221122079207), + (True, "futures", "binance", "isolated", 5.0, 8.0, 1.0, 9.514851485148514), + (True, "futures", "binance", "isolated", 5.0, 10.0, 0.6, 11.897689768976898), # Binance, long - (False, 'futures', 'binance', 'isolated', 5, 10, 1.0, 8.070707070707071), - (False, 'futures', 'binance', 'isolated', 5, 8, 1.0, 6.454545454545454), - (False, 'futures', 'binance', 'isolated', 3, 10, 1.0, 6.723905723905723), - (False, 'futures', 'binance', 'isolated', 5, 10, 0.6, 8.063973063973064), + (False, "futures", "binance", "isolated", 5, 10, 1.0, 8.070707070707071), + (False, "futures", "binance", "isolated", 5, 8, 1.0, 6.454545454545454), + (False, "futures", "binance", "isolated", 3, 10, 1.0, 6.723905723905723), + (False, "futures", "binance", "isolated", 5, 10, 0.6, 8.063973063973064), # Gate/okx, short - (True, 'futures', 'gate', 'isolated', 5, 10, 1.0, 11.87413417771621), - (True, 'futures', 'gate', 'isolated', 5, 10, 2.0, 11.87413417771621), - (True, 'futures', 'gate', 'isolated', 3, 10, 1.0, 13.193482419684678), - (True, 'futures', 'gate', 'isolated', 5, 8, 1.0, 9.499307342172967), - (True, 'futures', 'okx', 'isolated', 3, 10, 1.0, 13.193482419684678), + (True, "futures", "gate", "isolated", 5, 10, 1.0, 11.87413417771621), + (True, "futures", "gate", "isolated", 5, 10, 2.0, 11.87413417771621), + (True, "futures", "gate", "isolated", 3, 10, 1.0, 13.193482419684678), + (True, "futures", "gate", "isolated", 5, 8, 1.0, 9.499307342172967), + (True, "futures", "okx", "isolated", 3, 10, 1.0, 13.193482419684678), # Gate/okx, long - (False, 'futures', 'gate', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207), - (False, 'futures', 'gate', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), - (False, 'futures', 'okx', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), + (False, "futures", "gate", "isolated", 5.0, 10.0, 1.0, 8.085708510208207), + (False, "futures", "gate", "isolated", 3.0, 10.0, 1.0, 6.738090425173506), + (False, "futures", "okx", "isolated", 3.0, 10.0, 1.0, 6.738090425173506), # bybit, long - (False, 'futures', 'bybit', 'isolated', 1.0, 10.0, 1.0, 0.1), - (False, 'futures', 'bybit', 'isolated', 3.0, 10.0, 1.0, 6.7666666), - (False, 'futures', 'bybit', 'isolated', 5.0, 10.0, 1.0, 8.1), - (False, 'futures', 'bybit', 'isolated', 10.0, 10.0, 1.0, 9.1), + (False, "futures", "bybit", "isolated", 1.0, 10.0, 1.0, 0.1), + (False, "futures", "bybit", "isolated", 3.0, 10.0, 1.0, 6.7666666), + (False, "futures", "bybit", "isolated", 5.0, 10.0, 1.0, 8.1), + (False, "futures", "bybit", "isolated", 10.0, 10.0, 1.0, 9.1), # bybit, short - (True, 'futures', 'bybit', 'isolated', 1.0, 10.0, 1.0, 19.9), - (True, 'futures', 'bybit', 'isolated', 3.0, 10.0, 1.0, 13.233333), - (True, 'futures', 'bybit', 'isolated', 5.0, 10.0, 1.0, 11.9), - (True, 'futures', 'bybit', 'isolated', 10.0, 10.0, 1.0, 10.9), - ] + (True, "futures", "bybit", "isolated", 1.0, 10.0, 1.0, 19.9), + (True, "futures", "bybit", "isolated", 3.0, 10.0, 1.0, 13.233333), + (True, "futures", "bybit", "isolated", 5.0, 10.0, 1.0, 11.9), + (True, "futures", "bybit", "isolated", 10.0, 10.0, 1.0, 10.9), + ], ) def test_get_liquidation_price( mocker, @@ -5453,11 +6167,11 @@ def test_get_liquidation_price( leverage = 5, open_rate = 8, amount = 1.0 (8 - (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 6.332871561448645 """ - default_conf_usdt['liquidation_buffer'] = liquidation_buffer - default_conf_usdt['trading_mode'] = trading_mode - default_conf_usdt['exchange']['name'] = exchange_name - default_conf_usdt['margin_mode'] = margin_mode - mocker.patch('freqtrade.exchange.gate.Gate.validate_ordertypes') + default_conf_usdt["liquidation_buffer"] = liquidation_buffer + default_conf_usdt["trading_mode"] = trading_mode + default_conf_usdt["exchange"]["name"] = exchange_name + default_conf_usdt["margin_mode"] = margin_mode + mocker.patch("freqtrade.exchange.gate.Gate.validate_ordertypes") exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) @@ -5466,7 +6180,7 @@ def test_get_liquidation_price( # "dry_run": False, # }) liq = exchange.get_liquidation_price( - pair='ETH/USDT:USDT', + pair="ETH/USDT:USDT", open_rate=open_rate, amount=amount, stake_amount=amount * open_rate / leverage, @@ -5482,47 +6196,45 @@ def test_get_liquidation_price( assert pytest.approx(expected_liq) == liq -@pytest.mark.parametrize('contract_size,order_amount', [ - (10, 10), - (0.01, 10000), -]) +@pytest.mark.parametrize( + "contract_size,order_amount", + [ + (10, 10), + (0.01, 10000), + ], +) def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amount): api_mock = MagicMock() - order_id = f'test_prod_buy_{randint(0, 10 ** 6)}' + order_id = f"test_prod_buy_{randint(0, 10 ** 6)}" - api_mock.create_order = MagicMock(return_value={ - 'id': order_id, - 'info': { - 'foo': 'bar' - }, - 'amount': order_amount, - 'cost': order_amount, - 'filled': order_amount, - 'remaining': order_amount, - 'symbol': 'ETH/BTC', - }) - default_conf['dry_run'] = False - mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y) - mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y) + api_mock.create_order = MagicMock( + return_value={ + "id": order_id, + "info": {"foo": "bar"}, + "amount": order_amount, + "cost": order_amount, + "filled": order_amount, + "remaining": order_amount, + "symbol": "ETH/BTC", + } + ) + default_conf["dry_run"] = False + mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) + mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y, **kwargs: y) exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange.get_contract_size = MagicMock(return_value=contract_size) api_mock.create_order.reset_mock() order = exchange.create_stoploss( - pair='ETH/BTC', - amount=100, - stop_price=220, - order_types={}, - side='buy', - leverage=1.0 + pair="ETH/BTC", amount=100, stop_price=220, order_types={}, side="buy", leverage=1.0 ) - assert api_mock.create_order.call_args_list[0][1]['amount'] == order_amount - assert order['amount'] == 100 - assert order['cost'] == order_amount - assert order['filled'] == 100 - assert order['remaining'] == 100 + assert api_mock.create_order.call_args_list[0][1]["amount"] == order_amount + assert order["amount"] == 100 + assert order["cost"] == order_amount + assert order["filled"] == 100 + assert order["remaining"] == 100 def test_price_to_precision_with_default_conf(default_conf, mocker):