stable/tests/exchange/test_exchange.py

3303 lines
136 KiB
Python
Raw Normal View History

import copy
import logging
2021-09-16 04:28:10 +00:00
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from math import isclose
2018-07-04 07:31:35 +00:00
from random import randint
2020-06-28 09:17:06 +00:00
from unittest.mock import MagicMock, Mock, PropertyMock, patch
2018-03-17 21:44:47 +00:00
import arrow
import ccxt
import pytest
2018-12-11 18:48:36 +00:00
from pandas import DataFrame
2021-09-19 23:02:09 +00:00
from freqtrade.enums import Collateral, TradingMode
2020-09-28 17:43:15 +00:00
from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException,
2021-06-02 09:39:18 +00:00
OperationalException, PricingError, TemporaryError)
from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken
2020-09-28 17:43:15 +00:00
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
2021-09-16 04:28:10 +00:00
calculate_backoff, remove_credentials)
2020-09-28 17:43:15 +00:00
from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs,
timeframe_to_next_date, timeframe_to_prev_date,
timeframe_to_seconds)
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
2021-03-13 14:36:34 +00:00
from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re
2020-09-28 17:43:15 +00:00
# Make sure to always keep one exchange here which is NOT subclassed!!
2020-03-25 14:50:33 +00:00
EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx']
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
fun, mock_ccxt_fun, retries=API_RETRY_COUNT + 1, **kwargs):
2020-06-28 09:17:06 +00:00
with patch('freqtrade.exchange.common.time.sleep'):
with pytest.raises(DDosProtection):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DDos"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
2020-06-28 09:17:06 +00:00
2018-06-28 19:22:43 +00:00
with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeaDBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-28 19:22:43 +00:00
getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
2018-06-28 19:22:43 +00:00
with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-28 19:22:43 +00:00
getattr(exchange, fun)(**kwargs)
2018-07-01 17:37:55 +00:00
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
2018-06-28 19:22:43 +00:00
async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun,
retries=API_RETRY_COUNT + 1, **kwargs):
2020-06-28 09:17:06 +00:00
with patch('freqtrade.exchange.common.asyncio.sleep', get_mock_coro(None)):
2020-06-28 09:17:06 +00:00
with pytest.raises(DDosProtection):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("Dooh"))
2020-06-28 09:17:06 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
2020-06-28 09:17:06 +00:00
2018-08-01 19:19:49 +00:00
with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
2018-08-01 19:19:49 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
2018-08-01 19:19:49 +00:00
with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
2018-08-01 19:19:49 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
def test_init(default_conf, mocker, caplog):
2018-01-31 17:37:38 +00:00
caplog.set_level(logging.INFO)
2018-06-17 18:13:39 +00:00
get_patched_exchange(mocker, default_conf)
2019-08-11 18:17:39 +00:00
assert log_has('Instance is running with dry_run enabled', caplog)
2021-09-16 04:28:10 +00:00
def test_remove_credentials(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf['dry_run'] = False
remove_credentials(conf)
assert conf['exchange']['key'] != ''
assert conf['exchange']['secret'] != ''
conf['dry_run'] = True
remove_credentials(conf)
assert conf['exchange']['key'] == ''
assert conf['exchange']['secret'] == ''
assert conf['exchange']['password'] == ''
assert conf['exchange']['uid'] == ''
2019-09-10 21:18:07 +00:00
def test_init_ccxt_kwargs(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
caplog.set_level(logging.INFO)
conf = copy.deepcopy(default_conf)
conf['exchange']['ccxt_async_config'] = {'aiohttp_trust_env': True, 'asyncio_loop': True}
ex = Exchange(conf)
assert log_has(
"Applying additional ccxt config: {'aiohttp_trust_env': True, 'asyncio_loop': True}",
caplog)
assert ex._api_async.aiohttp_trust_env
assert not ex._api.aiohttp_trust_env
# Reset logging and config
caplog.clear()
conf = copy.deepcopy(default_conf)
conf['exchange']['ccxt_config'] = {'TestKWARG': 11}
conf['exchange']['ccxt_sync_config'] = {'TestKWARG44': 11}
conf['exchange']['ccxt_async_config'] = {'asyncio_loop': True}
asynclogmsg = "Applying additional ccxt config: {'TestKWARG': 11, 'asyncio_loop': True}"
ex = Exchange(conf)
assert not ex._api_async.aiohttp_trust_env
assert hasattr(ex._api, 'TestKWARG')
assert ex._api.TestKWARG == 11
# ccxt_config is assigned to both sync and async
assert not hasattr(ex._api_async, 'TestKWARG44')
assert hasattr(ex._api_async, 'TestKWARG')
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
assert log_has(asynclogmsg, caplog)
2021-09-16 04:28:10 +00:00
# Test additional headers case
Exchange._headers = {'hello': 'world'}
ex = Exchange(conf)
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
assert ex._api.headers == {'hello': 'world'}
2021-09-19 23:02:09 +00:00
assert ex._ccxt_config == {}
2021-09-16 04:28:10 +00:00
Exchange._headers = {}
def test_destroy(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG)
get_patched_exchange(mocker, default_conf)
2019-08-11 18:17:39 +00:00
assert log_has('Exchange object destroyed, closing async loop', caplog)
2018-06-28 20:32:28 +00:00
def test_init_exception(default_conf, mocker):
default_conf['exchange']['name'] = 'wrong_exchange_name'
2019-08-25 08:13:35 +00:00
with pytest.raises(OperationalException,
match=f"Exchange {default_conf['exchange']['name']} is not supported"):
2018-06-17 18:13:39 +00:00
Exchange(default_conf)
2018-06-28 20:32:28 +00:00
default_conf['exchange']['name'] = 'binance'
2019-08-25 08:13:35 +00:00
with pytest.raises(OperationalException,
match=f"Exchange {default_conf['exchange']['name']} is not supported"):
2018-06-28 20:32:28 +00:00
mocker.patch("ccxt.binance", MagicMock(side_effect=AttributeError))
Exchange(default_conf)
2019-08-25 08:13:35 +00:00
with pytest.raises(OperationalException,
match=r"Initialization of ccxt failed. Reason: DeadBeef"):
mocker.patch("ccxt.binance", MagicMock(side_effect=ccxt.BaseError("DeadBeef")))
Exchange(default_conf)
2018-07-29 08:10:55 +00:00
def test_exchange_resolver(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock()))
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
exchange = ExchangeResolver.load_exchange('huobi', default_conf)
assert isinstance(exchange, Exchange)
2019-08-11 18:17:39 +00:00
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog)
caplog.clear()
exchange = ExchangeResolver.load_exchange('Bittrex', default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Bittrex)
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
caplog)
caplog.clear()
exchange = ExchangeResolver.load_exchange('kraken', default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Kraken)
2019-02-24 19:08:27 +00:00
assert not isinstance(exchange, Binance)
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
2019-08-11 18:17:39 +00:00
caplog)
2019-02-24 19:08:27 +00:00
exchange = ExchangeResolver.load_exchange('binance', default_conf)
2019-02-24 19:08:27 +00:00
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Binance)
assert not isinstance(exchange, Kraken)
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
2019-08-11 18:17:39 +00:00
caplog)
# Test mapping
exchange = ExchangeResolver.load_exchange('binanceus', default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Binance)
assert not isinstance(exchange, Kraken)
def test_validate_order_time_in_force(default_conf, mocker, caplog):
caplog.set_level(logging.INFO)
2021-09-16 04:28:10 +00:00
# explicitly test bittrex, exchanges implementing other policies need separate tests
ex = get_patched_exchange(mocker, default_conf, id="bittrex")
tif = {
"buy": "gtc",
"sell": "gtc",
}
ex.validate_order_time_in_force(tif)
tif2 = {
"buy": "fok",
"sell": "ioc",
}
with pytest.raises(OperationalException, match=r"Time in force.*not supported for .*"):
ex.validate_order_time_in_force(tif2)
# Patch to see if this will pass if the values are in the ft dict
ex._ft_has.update({"order_time_in_force": ["gtc", "fok", "ioc"]})
ex.validate_order_time_in_force(tif2)
@pytest.mark.parametrize("amount,precision_mode,precision,expected", [
(2.34559, 2, 4, 2.3455),
(2.34559, 2, 5, 2.34559),
(2.34559, 2, 3, 2.345),
(2.9999, 2, 3, 2.999),
(2.9909, 2, 3, 2.990),
# Tests for Tick-size
(2.34559, 4, 0.0001, 2.3455),
(2.34559, 4, 0.00001, 2.34559),
(2.34559, 4, 0.001, 2.345),
(2.9999, 4, 0.001, 2.999),
(2.9909, 4, 0.001, 2.990),
(2.9909, 4, 0.005, 2.990),
(2.9999, 4, 0.005, 2.995),
])
def test_amount_to_precision(default_conf, mocker, amount, precision_mode, precision, expected):
'''
Test rounds down
'''
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': precision}}})
2019-10-26 08:08:23 +00:00
exchange = get_patched_exchange(mocker, default_conf, id="binance")
# digits counting mode
# DECIMAL_PLACES = 2
# SIGNIFICANT_DIGITS = 3
# TICK_SIZE = 4
mocker.patch('freqtrade.exchange.Exchange.precisionMode',
PropertyMock(return_value=precision_mode))
2019-10-26 08:08:23 +00:00
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
pair = 'ETH/BTC'
assert exchange.amount_to_precision(pair, amount) == expected
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
(2.34559, 2, 4, 2.3456),
(2.34559, 2, 5, 2.34559),
(2.34559, 2, 3, 2.346),
(2.9999, 2, 3, 3.000),
(2.9909, 2, 3, 2.991),
# Tests for Tick_size
(2.34559, 4, 0.0001, 2.3456),
(2.34559, 4, 0.00001, 2.34559),
(2.34559, 4, 0.001, 2.346),
(2.9999, 4, 0.001, 3.000),
(2.9909, 4, 0.001, 2.991),
2020-01-14 19:16:47 +00:00
(2.9909, 4, 0.005, 2.995),
(2.9973, 4, 0.005, 3.0),
(2.9977, 4, 0.005, 3.0),
2020-01-14 19:16:47 +00:00
(234.43, 4, 0.5, 234.5),
(234.53, 4, 0.5, 235.0),
(0.891534, 4, 0.0001, 0.8916),
2021-10-03 07:48:19 +00:00
(64968.89, 4, 0.01, 64968.89),
])
def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected):
'''
Test price to precision
'''
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}})
2019-10-26 08:08:23 +00:00
exchange = get_patched_exchange(mocker, default_conf, id="binance")
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
# digits counting mode
# DECIMAL_PLACES = 2
# SIGNIFICANT_DIGITS = 3
# TICK_SIZE = 4
mocker.patch('freqtrade.exchange.Exchange.precisionMode',
PropertyMock(return_value=precision_mode))
pair = 'ETH/BTC'
2021-10-03 07:48:19 +00:00
assert exchange.price_to_precision(pair, price) == expected
2020-04-15 05:53:31 +00:00
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
(2.34559, 2, 4, 0.0001),
(2.34559, 2, 5, 0.00001),
(2.34559, 2, 3, 0.001),
(2.9999, 2, 3, 0.001),
(200.0511, 2, 3, 0.001),
# Tests for Tick_size
(2.34559, 4, 0.0001, 0.0001),
(2.34559, 4, 0.00001, 0.00001),
(2.34559, 4, 0.0025, 0.0025),
(2.9909, 4, 0.0025, 0.0025),
(234.43, 4, 0.5, 0.5),
(234.43, 4, 0.0025, 0.0025),
(234.43, 4, 0.00013, 0.00013),
])
def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precision, expected):
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}})
exchange = get_patched_exchange(mocker, default_conf, id="binance")
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
mocker.patch('freqtrade.exchange.Exchange.precisionMode',
PropertyMock(return_value=precision_mode))
pair = 'ETH/BTC'
assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected
def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
exchange = get_patched_exchange(mocker, default_conf, id="binance")
stoploss = -0.05
markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}}
# no pair found
mocker.patch(
'freqtrade.exchange.Exchange.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 'limits' section
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
assert result is None
# empty 'limits' section
markets["ETH/BTC"]["limits"] = {}
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
assert result is None
# no cost Min
markets["ETH/BTC"]["limits"] = {
'cost': {"min": None},
'amount': {}
}
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
assert result is None
# no amount Min
markets["ETH/BTC"]["limits"] = {
'cost': {},
'amount': {"min": None}
}
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
assert result is None
# empty 'cost'/'amount' section
markets["ETH/BTC"]["limits"] = {
'cost': {},
'amount': {}
}
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
assert result is None
# min cost is set
markets["ETH/BTC"]["limits"] = {
'cost': {'min': 2},
'amount': {}
}
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
2021-09-19 23:02:09 +00:00
expected_result = 2 * (1+0.05) / (1-abs(stoploss))
assert isclose(result, expected_result)
# With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0)
assert isclose(result, expected_result/3)
# min amount is set
markets["ETH/BTC"]["limits"] = {
'cost': {},
'amount': {'min': 2}
}
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
2021-09-19 23:02:09 +00:00
expected_result = 2 * 2 * (1+0.05) / (1-abs(stoploss))
assert isclose(result, expected_result)
# With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0)
assert isclose(result, expected_result/5)
# min amount and cost are set (cost is minimal)
markets["ETH/BTC"]["limits"] = {
'cost': {'min': 2},
'amount': {'min': 2}
}
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
2021-09-19 23:02:09 +00:00
expected_result = max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss))
assert isclose(result, expected_result)
# With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10)
assert isclose(result, expected_result/10)
# min amount and cost are set (amount is minial)
markets["ETH/BTC"]["limits"] = {
'cost': {'min': 8},
'amount': {'min': 2}
}
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
2021-09-19 23:02:09 +00:00
expected_result = max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss))
assert isclose(result, expected_result)
# With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0)
assert isclose(result, expected_result/7.0)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4)
2021-09-19 23:02:09 +00:00
expected_result = max(8, 2 * 2) * 1.5
assert isclose(result, expected_result)
# With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0)
assert isclose(result, expected_result/8.0)
# Really big stoploss
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1)
2021-09-19 23:02:09 +00:00
expected_result = max(8, 2 * 2) * 1.5
assert isclose(result, expected_result)
# With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
assert isclose(result, expected_result/12)
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'}}
# Real Binance data
markets["ETH/BTC"]["limits"] = {
'cost': {'min': 0.0001},
'amount': {'min': 0.001}
}
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss)
2021-09-19 23:02:09 +00:00
expected_result = max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss))
assert round(result, 8) == round(expected_result, 8)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0)
assert round(result, 8) == round(expected_result/3, 8)
def test_set_sandbox(default_conf, mocker):
"""
Test working scenario
"""
api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
})
2018-07-29 08:10:55 +00:00
url_mock = PropertyMock(return_value={'test': "api-public.sandbox.gdax.com",
'api': 'https://api.gdax.com'})
type(api_mock).urls = url_mock
2019-03-05 18:46:03 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock)
liveurl = exchange._api.urls['api']
default_conf['exchange']['sandbox'] = True
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
assert exchange._api.urls['api'] != liveurl
2018-07-29 08:10:55 +00:00
def test_set_sandbox_exception(default_conf, mocker):
"""
Test Fail scenario
"""
api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
})
url_mock = PropertyMock(return_value={'api': 'https://api.gdax.com'})
type(api_mock).urls = url_mock
with pytest.raises(OperationalException, match=r'does not provide a sandbox api'):
2019-03-05 18:46:03 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock)
default_conf['exchange']['sandbox'] = True
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
def test__load_async_markets(default_conf, mocker, caplog):
2020-06-17 06:33:53 +00:00
mocker.patch('freqtrade.exchange.Exchange._init_ccxt')
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
exchange = Exchange(default_conf)
exchange._api_async.load_markets = get_mock_coro(None)
exchange._load_async_markets()
assert exchange._api_async.load_markets.call_count == 1
caplog.set_level(logging.DEBUG)
exchange._api_async.load_markets = Mock(side_effect=ccxt.BaseError("deadbeef"))
exchange._load_async_markets()
assert log_has('Could not load async markets. Reason: deadbeef', caplog)
2018-09-11 17:46:47 +00:00
def test__load_markets(default_conf, mocker, caplog):
caplog.set_level(logging.INFO)
api_mock = MagicMock()
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError("SomeError"))
2019-03-05 18:46:03 +00:00
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
2019-03-05 18:46:03 +00:00
Exchange(default_conf)
assert log_has('Unable to initialize markets.', caplog)
2018-09-11 17:46:47 +00:00
expected_return = {'ETH/BTC': 'available'}
2019-03-05 18:46:03 +00:00
api_mock = MagicMock()
2018-09-11 17:46:47 +00:00
api_mock.load_markets = MagicMock(return_value=expected_return)
2021-01-28 18:40:10 +00:00
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
2018-09-11 17:46:47 +00:00
default_conf['exchange']['pair_whitelist'] = ['ETH/BTC']
2021-01-28 18:40:10 +00:00
ex = Exchange(default_conf)
2018-09-11 17:46:47 +00:00
assert ex.markets == expected_return
2020-06-09 22:39:23 +00:00
def test_reload_markets(default_conf, mocker, caplog):
2019-03-10 15:36:25 +00:00
caplog.set_level(logging.DEBUG)
initial_markets = {'ETH/BTC': {}}
2021-01-28 18:40:10 +00:00
updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}}
2019-03-12 16:54:16 +00:00
2019-03-10 16:40:54 +00:00
api_mock = MagicMock()
2021-01-28 18:40:10 +00:00
api_mock.load_markets = MagicMock(return_value=initial_markets)
2019-03-10 15:36:25 +00:00
default_conf['exchange']['markets_refresh_interval'] = 10
2019-10-26 08:08:23 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance",
mock_markets=False)
exchange._load_async_markets = MagicMock()
exchange._last_markets_refresh = arrow.utcnow().int_timestamp
2019-03-10 15:36:25 +00:00
assert exchange.markets == initial_markets
# less than 10 minutes have passed, no reload
2020-06-09 22:39:23 +00:00
exchange.reload_markets()
2019-03-10 15:36:25 +00:00
assert exchange.markets == initial_markets
assert exchange._load_async_markets.call_count == 0
2019-03-10 15:36:25 +00:00
2021-01-28 18:40:10 +00:00
api_mock.load_markets = MagicMock(return_value=updated_markets)
2019-03-10 15:36:25 +00:00
# more than 10 minutes have passed, reload is executed
exchange._last_markets_refresh = arrow.utcnow().int_timestamp - 15 * 60
2020-06-09 22:39:23 +00:00
exchange.reload_markets()
2019-03-10 15:36:25 +00:00
assert exchange.markets == updated_markets
assert exchange._load_async_markets.call_count == 1
2019-08-11 18:17:39 +00:00
assert log_has('Performing scheduled market reload..', caplog)
2019-03-10 15:36:25 +00:00
2020-06-09 22:39:23 +00:00
def test_reload_markets_exception(default_conf, mocker, caplog):
2019-04-24 19:56:24 +00:00
caplog.set_level(logging.DEBUG)
api_mock = MagicMock()
api_mock.load_markets = MagicMock(side_effect=ccxt.NetworkError("LoadError"))
2019-04-24 19:56:24 +00:00
default_conf['exchange']['markets_refresh_interval'] = 10
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
# less than 10 minutes have passed, no reload
2020-06-09 22:39:23 +00:00
exchange.reload_markets()
2019-04-24 19:56:24 +00:00
assert exchange._last_markets_refresh == 0
2019-08-11 18:17:39 +00:00
assert log_has_re(r"Could not reload markets.*", caplog)
2019-04-24 19:56:24 +00:00
2020-01-11 11:01:34 +00:00
@pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT'])
def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog):
2020-01-11 11:01:34 +00:00
default_conf['stake_currency'] = stake_currency
api_mock = MagicMock()
2021-01-28 18:40:10 +00:00
type(api_mock).load_markets = MagicMock(return_value={
2020-01-11 11:01:34 +00:00
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'},
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
2020-01-11 11:01:34 +00:00
Exchange(default_conf)
def test_validate_stakecurrency_error(default_conf, mocker, caplog):
2020-01-11 12:14:19 +00:00
default_conf['stake_currency'] = 'XRP'
api_mock = MagicMock()
2021-01-28 18:40:10 +00:00
type(api_mock).load_markets = MagicMock(return_value={
2020-01-11 12:14:19 +00:00
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'},
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
2020-01-11 12:14:19 +00:00
with pytest.raises(OperationalException,
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('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
with pytest.raises(OperationalException,
match=r'Could not load markets, therefore cannot start\. Please.*'):
Exchange(default_conf)
2020-01-11 12:14:19 +00:00
2020-01-11 11:01:34 +00:00
def test_get_quote_currencies(default_conf, mocker):
ex = get_patched_exchange(mocker, default_conf)
assert set(ex.get_quote_currencies()) == set(['USD', 'ETH', 'BTC', 'USDT'])
2020-01-11 11:01:34 +00:00
2020-02-25 06:01:23 +00:00
@pytest.mark.parametrize('pair,expected', [
('XRP/BTC', 'BTC'),
('LTC/USD', 'USD'),
('ETH/USDT', 'USDT'),
('XLTCUSDT', 'USDT'),
('XRP/NOCURRENCY', ''),
2020-02-25 06:01:23 +00:00
])
def test_get_pair_quote_currency(default_conf, mocker, pair, expected):
ex = get_patched_exchange(mocker, default_conf)
assert ex.get_pair_quote_currency(pair) == expected
@pytest.mark.parametrize('pair,expected', [
('XRP/BTC', 'XRP'),
('LTC/USD', 'LTC'),
('ETH/USDT', 'ETH'),
('XLTCUSDT', 'LTC'),
('XRP/NOCURRENCY', ''),
2020-02-25 06:01:23 +00:00
])
def test_get_pair_base_currency(default_conf, mocker, pair, expected):
ex = get_patched_exchange(mocker, default_conf)
assert ex.get_pair_base_currency(pair) == expected
2020-01-11 11:01:34 +00:00
2019-03-05 18:46:03 +00:00
def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly
api_mock = MagicMock()
2021-01-28 18:40:10 +00:00
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
2018-06-17 18:13:39 +00:00
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
2018-06-17 18:13:39 +00:00
Exchange(default_conf)
def test_validate_pairs_not_available(default_conf, mocker):
api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={
'XRP/BTC': {'inactive': True, 'base': 'XRP', 'quote': 'BTC'}
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
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('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance'))
type(api_mock).markets = PropertyMock(return_value={})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'):
Exchange(default_conf)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}))
Exchange(default_conf)
assert log_has('Unable to validate pairs (assuming they are correct).', caplog)
def test_validate_pairs_restricted(default_conf, mocker, caplog):
api_mock = MagicMock()
2021-01-28 18:40:10 +00:00
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('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
assert log_has("Pair XRP/BTC is restricted for some users on this exchange."
"Please check if you are impacted by this restriction "
"on the exchange and eventually remove XRP/BTC from your whitelist.", caplog)
def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog):
api_mock = MagicMock()
2021-01-28 18:40:10 +00:00
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('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
2020-02-29 13:56:04 +00:00
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, caplog):
api_mock = MagicMock()
default_conf['stake_currency'] = ''
2021-01-28 18:40:10 +00:00
type(api_mock).load_markets = MagicMock(return_value={
2020-02-29 13:56:04 +00:00
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'},
'HELLO-WORLD': {'quote': 'BTC'},
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
2021-01-28 18:40:10 +00:00
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')
api_mock = MagicMock()
2021-01-28 18:40:10 +00:00
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('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"):
Exchange(default_conf)
2020-01-11 10:36:28 +00:00
@pytest.mark.parametrize("timeframe", [
('5m'), ("1m"), ("15m"), ("1h")
])
def test_validate_timeframes(default_conf, mocker, timeframe):
2020-06-01 18:39:01 +00:00
default_conf["timeframe"] = timeframe
2018-07-07 12:42:53 +00:00
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
timeframes = PropertyMock(return_value={'1m': '1m',
'5m': '5m',
'15m': '15m',
'1h': '1h'})
type(api_mock).timeframes = timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
2018-09-11 17:46:47 +00:00
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
2018-07-07 12:42:53 +00:00
Exchange(default_conf)
def test_validate_timeframes_failed(default_conf, mocker):
2020-06-01 18:39:01 +00:00
default_conf["timeframe"] = "3m"
2018-07-07 12:42:53 +00:00
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
2020-01-11 10:36:28 +00:00
timeframes = PropertyMock(return_value={'15s': '15s',
'1m': '1m',
2018-07-07 12:42:53 +00:00
'5m': '5m',
'15m': '15m',
'1h': '1h'})
type(api_mock).timeframes = timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
2018-09-11 17:46:47 +00:00
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
2019-03-05 18:46:03 +00:00
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
with pytest.raises(OperationalException,
match=r"Invalid timeframe '3m'. This exchange supports.*"):
2018-07-07 12:42:53 +00:00
Exchange(default_conf)
2020-06-01 18:39:01 +00:00
default_conf["timeframe"] = "15s"
2020-01-11 10:36:28 +00:00
with pytest.raises(OperationalException,
match=r"Timeframes < 1m are currently not supported by Freqtrade."):
Exchange(default_conf)
2018-07-07 12:42:53 +00:00
2019-07-02 22:03:38 +00:00
def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
2020-06-01 18:39:01 +00:00
default_conf["timeframe"] = "3m"
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
# delete timeframes so magicmock does not autocreate it
del api_mock.timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
with pytest.raises(OperationalException,
2019-07-02 22:03:38 +00:00
match=r'The ccxt library does not provide the list of timeframes '
r'for the exchange ".*" and this exchange '
r'is therefore not supported. *'):
Exchange(default_conf)
def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
2020-06-01 18:39:01 +00:00
default_conf["timeframe"] = "3m"
2019-07-02 22:03:38 +00:00
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
# delete timeframes so magicmock does not autocreate it
del api_mock.timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets',
MagicMock(return_value={'timeframes': None}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
2019-07-02 22:03:38 +00:00
with pytest.raises(OperationalException,
match=r'The ccxt library does not provide the list of timeframes '
r'for the exchange ".*" and this exchange '
r'is therefore not supported. *'):
Exchange(default_conf)
def test_validate_timeframes_not_in_config(default_conf, mocker):
2020-06-01 18:39:01 +00:00
del default_conf["timeframe"]
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
timeframes = PropertyMock(return_value={'1m': '1m',
'5m': '5m',
'15m': '15m',
'1h': '1h'})
type(api_mock).timeframes = timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
2018-09-11 17:46:47 +00:00
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
def test_validate_order_types(default_conf, mocker):
api_mock = MagicMock()
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
Exchange(default_conf)
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
2018-11-25 18:09:11 +00:00
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'market',
2020-06-13 21:57:13 +00:00
'stoploss_on_exchange': False
2018-11-25 18:09:11 +00:00
}
with pytest.raises(OperationalException,
match=r'Exchange .* does not support market orders.'):
Exchange(default_conf)
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}
with pytest.raises(OperationalException,
match=r'On exchange stoploss is not supported for .*'):
Exchange(default_conf)
def test_validate_order_types_not_in_config(default_conf, mocker):
api_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
conf = copy.deepcopy(default_conf)
Exchange(conf)
def test_validate_required_startup_candles(default_conf, mocker, caplog):
api_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance'))
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
2019-10-27 09:56:38 +00:00
default_conf['startup_candle_count'] = 20
ex = Exchange(default_conf)
assert ex
default_conf['startup_candle_count'] = 600
with pytest.raises(OperationalException, match=r'This strategy requires 600.*'):
Exchange(default_conf)
2018-07-30 17:08:33 +00:00
def test_exchange_has(default_conf, mocker):
2018-06-28 20:11:45 +00:00
exchange = get_patched_exchange(mocker, default_conf)
assert not exchange.exchange_has('ASDFASDF')
api_mock = MagicMock()
type(api_mock).has = PropertyMock(return_value={'deadbeef': True})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert exchange.exchange_has("deadbeef")
type(api_mock).has = PropertyMock(return_value={'deadbeef': False})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert not exchange.exchange_has("deadbeef")
2019-02-22 00:48:35 +00:00
@pytest.mark.parametrize("side", [
("buy"),
("sell")
])
2019-02-24 19:08:27 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order(default_conf, mocker, side, exchange_name):
2019-02-22 00:48:35 +00:00
default_conf['dry_run'] = True
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
2019-02-22 00:48:35 +00:00
order = exchange.create_dry_run_order(
2021-09-19 23:02:09 +00:00
pair='ETH/BTC',
ordertype='limit',
side=side,
amount=1,
rate=200,
leverage=1.0
)
2019-02-22 00:48:35 +00:00
assert 'id' in order
assert f'dry_run_{side}_' in order["id"]
assert order["side"] == side
assert order["type"] == "limit"
2020-08-13 13:39:29 +00:00
assert order["symbol"] == "ETH/BTC"
2019-02-22 00:48:35 +00:00
2021-06-04 04:44:51 +00:00
@pytest.mark.parametrize("side,startprice,endprice", [
("buy", 25.563, 25.566),
("sell", 25.566, 25.563)
])
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order_limit_fill(default_conf, mocker, side, startprice, endprice,
exchange_name, order_book_l2_usd):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
mocker.patch.multiple('freqtrade.exchange.Exchange',
exchange_has=MagicMock(return_value=True),
fetch_l2_order_book=order_book_l2_usd,
)
order = exchange.create_dry_run_order(
2021-09-19 23:02:09 +00:00
pair='LTC/USDT',
ordertype='limit',
side=side,
amount=1,
rate=startprice,
leverage=1.0
)
2021-06-05 13:22:52 +00:00
assert order_book_l2_usd.call_count == 1
2021-06-04 04:44:51 +00:00
assert 'id' in order
assert f'dry_run_{side}_' in order["id"]
assert order["side"] == side
assert order["type"] == "limit"
assert order["symbol"] == "LTC/USDT"
2021-06-05 13:22:52 +00:00
order_book_l2_usd.reset_mock()
2021-06-04 04:44:51 +00:00
order_closed = exchange.fetch_dry_run_order(order['id'])
assert order_book_l2_usd.call_count == 1
assert order_closed['status'] == 'open'
assert not order['fee']
order_book_l2_usd.reset_mock()
order_closed['price'] = endprice
order_closed = exchange.fetch_dry_run_order(order['id'])
assert order_closed['status'] == 'closed'
assert order['fee']
@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%
2021-06-04 04:44:51 +00:00
])
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amount, endprice,
2021-06-04 04:44:51 +00:00
exchange_name, order_book_l2_usd):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
mocker.patch.multiple('freqtrade.exchange.Exchange',
exchange_has=MagicMock(return_value=True),
fetch_l2_order_book=order_book_l2_usd,
)
order = exchange.create_dry_run_order(
2021-09-19 23:02:09 +00:00
pair='LTC/USDT',
ordertype='market',
side=side,
amount=amount,
rate=rate,
leverage=1.0
)
2021-06-04 04:44:51 +00:00
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 round(order["average"], 4) == round(endprice, 4)
2021-09-19 23:02:09 +00:00
@pytest.mark.parametrize("side", ["buy", "sell"])
@pytest.mark.parametrize("ordertype,rate,marketprice", [
("market", None, None),
("market", 200, True),
("limit", 200, None),
("stop_loss_limit", 200, None)
2019-02-22 00:48:35 +00:00
])
2019-02-24 19:08:27 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, exchange_name):
2019-02-22 00:48:35 +00:00
api_mock = MagicMock()
order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6))
api_mock.options = {} if not marketprice else {"createMarketBuyOrderRequiresPrice": True}
2019-02-22 00:48:35 +00:00
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2021-09-19 23:02:09 +00:00
exchange._set_leverage = MagicMock()
exchange.set_margin_mode = MagicMock()
2019-02-22 00:48:35 +00:00
order = exchange.create_order(
2021-09-19 23:02:09 +00:00
pair='ETH/BTC',
ordertype=ordertype,
side=side,
amount=1,
rate=200,
leverage=1.0
)
2019-02-22 00:48:35 +00:00
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == ordertype
assert api_mock.create_order.call_args[0][2] == side
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is rate
2021-09-19 23:02:09 +00:00
assert exchange._set_leverage.call_count == 0
assert exchange.set_margin_mode.call_count == 0
exchange.trading_mode = TradingMode.FUTURES
order = exchange.create_order(
pair='ETH/BTC',
ordertype=ordertype,
side=side,
amount=1,
rate=200,
leverage=3.0
)
assert exchange._set_leverage.call_count == 1
assert exchange.set_margin_mode.call_count == 1
2019-02-22 00:48:35 +00:00
def test_buy_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
2018-06-17 18:13:39 +00:00
exchange = get_patched_exchange(mocker, default_conf)
order = exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy",
amount=1, rate=200, time_in_force='gtc')
assert 'id' in order
assert 'dry_run_buy_' in order['id']
2018-04-12 16:13:35 +00:00
2019-02-24 19:08:27 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_buy_prod(default_conf, mocker, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'market'
2018-12-09 15:22:21 +00:00
time_in_force = 'gtc'
api_mock.options = {}
2018-11-15 05:58:24 +00:00
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
amount=1, rate=200, time_in_force=time_in_force)
2018-12-09 15:22:21 +00:00
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
api_mock.create_order.reset_mock()
order_type = 'limit'
order = exchange.create_order(
2018-12-09 15:22:21 +00:00
pair='ETH/BTC',
ordertype=order_type,
side="buy",
2018-12-09 15:22:21 +00:00
amount=1,
rate=200,
time_in_force=time_in_force)
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
amount=1, rate=200, time_in_force=time_in_force)
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy",
amount=1, rate=200, time_in_force=time_in_force)
2019-08-18 13:48:20 +00:00
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, time_in_force=time_in_force)
2018-04-21 20:37:27 +00:00
with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
amount=1, rate=200, time_in_force=time_in_force)
with pytest.raises(OperationalException):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
amount=1, rate=200, time_in_force=time_in_force)
2019-03-11 19:30:16 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
2019-03-11 19:30:16 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order_type = 'limit'
time_in_force = 'ioc'
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
amount=1, rate=200, time_in_force=time_in_force)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
2019-03-11 19:30:16 +00:00
assert "timeInForce" in api_mock.create_order.call_args[0][5]
assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force
order_type = 'market'
time_in_force = 'ioc'
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
amount=1, rate=200, time_in_force=time_in_force)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
2019-03-11 19:30:16 +00:00
# Market orders should not send timeInForce!!
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
def test_sell_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
2018-06-17 18:13:39 +00:00
exchange = get_patched_exchange(mocker, default_conf)
order = exchange.create_order(pair='ETH/BTC', ordertype='limit',
side="sell", amount=1, rate=200)
assert 'id' in order
assert 'dry_run_sell_' in order['id']
2019-02-24 19:08:27 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_sell_prod(default_conf, mocker, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
order_type = 'market'
api_mock.options = {}
2018-11-15 05:58:24 +00:00
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 18:13:39 +00:00
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
side="sell", amount=1, rate=200)
2019-02-19 20:56:20 +00:00
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
api_mock.create_order.reset_mock()
order_type = 'limit'
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
side="sell", amount=1, rate=200)
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
2018-11-17 19:09:05 +00:00
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200)
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200)
2019-08-18 13:45:30 +00:00
# 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)
2018-04-21 20:37:27 +00:00
with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200)
with pytest.raises(OperationalException):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200)
2019-03-11 19:30:16 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
api_mock.options = {}
2019-03-11 19:30:16 +00:00
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
2019-03-11 19:30:16 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order_type = 'limit'
time_in_force = 'ioc'
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell",
amount=1, rate=200, time_in_force=time_in_force)
2019-03-11 19:30:16 +00:00
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
assert "timeInForce" in api_mock.create_order.call_args[0][5]
assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force
order_type = 'market'
time_in_force = 'ioc'
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell",
amount=1, rate=200, time_in_force=time_in_force)
2019-03-11 19:30:16 +00:00
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
# Market orders should not send timeInForce!!
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_balances_prod(default_conf, mocker, exchange_name):
balance_item = {
'free': 10.0,
'total': 10.0,
'used': 0.0
}
api_mock = MagicMock()
api_mock.fetch_balance = MagicMock(return_value={
'1ST': balance_item,
'2ST': balance_item,
'3ST': balance_item
})
default_conf['dry_run'] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 18:13:39 +00:00
assert len(exchange.get_balances()) == 3
assert exchange.get_balances()['1ST']['free'] == 10.0
assert exchange.get_balances()['1ST']['total'] == 10.0
assert exchange.get_balances()['1ST']['used'] == 0.0
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2018-06-28 20:18:38 +00:00
"get_balances", "fetch_balance")
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_tickers(default_conf, mocker, exchange_name):
2018-06-17 21:30:48 +00:00
api_mock = MagicMock()
tick = {'ETH/BTC': {
2019-10-17 20:40:29 +00:00
'symbol': 'ETH/BTC',
'bid': 0.5,
'ask': 1,
'last': 42,
}, 'BCH/BTC': {
'symbol': 'BCH/BTC',
'bid': 0.6,
'ask': 0.5,
'last': 41,
}
}
2018-06-17 21:30:48 +00:00
api_mock.fetch_tickers = MagicMock(return_value=tick)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 21:30:48 +00:00
# retrieve original ticker
tickers = exchange.get_tickers()
assert 'ETH/BTC' in tickers
assert 'BCH/BTC' in tickers
assert tickers['ETH/BTC']['bid'] == 0.5
assert tickers['ETH/BTC']['ask'] == 1
assert tickers['BCH/BTC']['bid'] == 0.6
assert tickers['BCH/BTC']['ask'] == 0.5
assert api_mock.fetch_tickers.call_count == 1
api_mock.fetch_tickers.reset_mock()
# Cached ticker should not call api again
tickers2 = exchange.get_tickers(cached=True)
assert tickers2 == tickers
assert api_mock.fetch_tickers.call_count == 0
2021-04-13 19:54:06 +00:00
tickers2 = exchange.get_tickers(cached=False)
assert api_mock.fetch_tickers.call_count == 1
2018-06-17 21:30:48 +00:00
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2018-06-28 20:18:38 +00:00
"get_tickers", "fetch_tickers")
2018-06-17 21:30:48 +00:00
with pytest.raises(OperationalException):
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_tickers()
2018-06-17 21:30:48 +00:00
api_mock.fetch_tickers = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 21:30:48 +00:00
exchange.get_tickers()
@pytest.mark.parametrize("exchange_name", EXCHANGES)
2019-12-18 15:34:30 +00:00
def test_fetch_ticker(default_conf, mocker, exchange_name):
api_mock = MagicMock()
tick = {
'symbol': 'ETH/BTC',
'bid': 0.00001098,
'ask': 0.00001099,
'last': 0.0001,
}
api_mock.fetch_ticker = MagicMock(return_value=tick)
2019-07-03 18:07:26 +00:00
api_mock.markets = {'ETH/BTC': {'active': True}}
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-01-07 20:26:43 +00:00
# retrieve original ticker
2019-12-18 15:34:30 +00:00
ticker = exchange.fetch_ticker(pair='ETH/BTC')
assert ticker['bid'] == 0.00001098
assert ticker['ask'] == 0.00001099
2018-01-03 16:58:08 +00:00
2018-01-07 20:24:17 +00:00
# change the ticker
tick = {
'symbol': 'ETH/BTC',
'bid': 0.5,
'ask': 1,
'last': 42,
}
api_mock.fetch_ticker = MagicMock(return_value=tick)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-01-07 20:24:17 +00:00
2018-01-03 16:58:08 +00:00
# if not caching the result we should get the same ticker
2018-01-10 06:41:37 +00:00
# if not fetching a new result we should get the cached ticker
2019-12-18 15:34:30 +00:00
ticker = exchange.fetch_ticker(pair='ETH/BTC')
2018-01-03 16:58:08 +00:00
2018-06-06 18:24:47 +00:00
assert api_mock.fetch_ticker.call_count == 1
2018-01-07 20:24:17 +00:00
assert ticker['bid'] == 0.5
2018-01-03 16:58:08 +00:00
assert ticker['ask'] == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2019-12-18 15:34:30 +00:00
"fetch_ticker", "fetch_ticker",
2020-02-22 10:03:25 +00:00
pair='ETH/BTC')
2018-06-06 19:24:57 +00:00
api_mock.fetch_ticker = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2020-02-22 10:03:25 +00:00
exchange.fetch_ticker(pair='ETH/BTC')
2018-06-06 19:24:57 +00:00
with pytest.raises(DependencyException, match=r'Pair XRP/ETH not available'):
2020-02-22 10:03:25 +00:00
exchange.fetch_ticker(pair='XRP/ETH')
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
ohlcv = [
2018-08-14 18:53:58 +00:00
[
arrow.utcnow().int_timestamp * 1000, # unix timestamp ms
2018-08-14 18:53:58 +00:00
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
pair = 'ETH/BTC'
async def mock_candle_hist(pair, timeframe, since_ms):
return pair, timeframe, ohlcv
2018-08-14 18:53:58 +00:00
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
2018-08-14 18:53:58 +00:00
# one_call calculation * 1.8 should do 2 calls
2020-03-25 14:50:33 +00:00
since = 5 * 60 * exchange.ohlcv_candle_limit('5m') * 1.8
ret = exchange.get_historic_ohlcv(pair, "5m", int((
arrow.utcnow().int_timestamp - since) * 1000))
2018-08-14 18:53:58 +00:00
assert exchange._async_get_candle_history.call_count == 2
# Returns twice the above OHLCV data
2018-08-14 18:53:58 +00:00
assert len(ret) == 2
caplog.clear()
async def mock_get_candle_hist_error(pair, *args, **kwargs):
raise TimeoutError()
exchange._async_get_candle_history = MagicMock(side_effect=mock_get_candle_hist_error)
ret = exchange.get_historic_ohlcv(pair, "5m", int(
(arrow.utcnow().int_timestamp - since) * 1000))
assert log_has_re(r"Async code raised an exception: .*", caplog)
2018-08-14 18:53:58 +00:00
2020-11-22 09:56:19 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
ohlcv = [
[
arrow.utcnow().int_timestamp * 1000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
],
[
arrow.utcnow().shift(minutes=5).int_timestamp * 1000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
],
[
arrow.utcnow().shift(minutes=10).int_timestamp * 1000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
pair = 'ETH/BTC'
async def mock_candle_hist(pair, timeframe, since_ms):
return pair, timeframe, ohlcv
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') * 1.8
2020-11-22 09:56:19 +00:00
ret = exchange.get_historic_ohlcv_as_df(pair, "5m", int((
arrow.utcnow().int_timestamp - since) * 1000))
assert exchange._async_get_candle_history.call_count == 2
# Returns twice the above OHLCV data
assert len(ret) == 2
assert isinstance(ret, DataFrame)
assert 'date' in ret.columns
assert 'open' in ret.columns
assert 'close' in ret.columns
assert 'high' in ret.columns
2021-09-16 04:28:10 +00:00
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name):
ohlcv = [
[
int((datetime.now(timezone.utc).timestamp() - 1000) * 1000),
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
# Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
pair = 'ETH/USDT'
res = await exchange._async_get_historic_ohlcv(pair, "5m",
1500000000000, is_new_pair=False)
# Call with very old timestamp - causes tons of requests
assert exchange._api_async.fetch_ohlcv.call_count > 200
assert res[0] == ohlcv[0]
assert log_has_re(r'Downloaded data for .* with length .*\.', caplog)
def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
ohlcv = [
[
(arrow.utcnow().int_timestamp - 1) * 1000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
2018-12-11 18:48:36 +00:00
],
[
arrow.utcnow().int_timestamp * 1000, # unix timestamp ms
2018-12-11 18:48:36 +00:00
3, # open
1, # high
4, # low
6, # close
5, # volume (in quote currency)
]
]
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf)
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
2018-12-30 06:15:49 +00:00
pairs = [('IOTA/ETH', '5m'), ('XRP/ETH', '5m')]
# empty dicts
2018-12-11 18:48:36 +00:00
assert not exchange._klines
res = exchange.refresh_latest_ohlcv(pairs, cache=False)
# No caching
assert not exchange._klines
assert len(res) == len(pairs)
assert exchange._api_async.fetch_ohlcv.call_count == 2
exchange._api_async.fetch_ohlcv.reset_mock()
res = exchange.refresh_latest_ohlcv(pairs)
assert len(res) == len(pairs)
assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog)
2018-12-11 18:48:36 +00:00
assert exchange._klines
assert exchange._api_async.fetch_ohlcv.call_count == 2
for pair in pairs:
2018-12-11 18:48:36 +00:00
assert isinstance(exchange.klines(pair), DataFrame)
assert len(exchange.klines(pair)) > 0
2018-12-22 18:03:42 +00:00
# klines function should return a different object on each call
# if copy is "True"
assert exchange.klines(pair) is not exchange.klines(pair)
assert exchange.klines(pair) is not exchange.klines(pair, copy=True)
assert exchange.klines(pair, copy=True) is not exchange.klines(pair, copy=True)
assert exchange.klines(pair, copy=False) is exchange.klines(pair, copy=False)
# test caching
res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
assert len(res) == len(pairs)
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, "
f"timeframe {pairs[0][1]} ...",
2019-08-11 18:17:39 +00:00
caplog)
res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m'), ('XRP/ETH', '1d')],
cache=False)
assert len(res) == 3
2018-08-01 19:19:49 +00:00
@pytest.mark.asyncio
2019-02-24 19:08:27 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name):
ohlcv = [
2018-08-01 19:19:49 +00:00
[
arrow.utcnow().int_timestamp * 1000, # unix timestamp ms
2018-08-01 19:19:49 +00:00
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
caplog.set_level(logging.DEBUG)
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
2018-08-01 19:19:49 +00:00
# Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
2018-08-01 19:19:49 +00:00
pair = 'ETH/BTC'
res = await exchange._async_get_candle_history(pair, "5m")
2018-08-01 19:19:49 +00:00
assert type(res) is tuple
assert len(res) == 3
2018-08-01 19:19:49 +00:00
assert res[0] == pair
assert res[1] == "5m"
assert res[2] == ohlcv
assert exchange._api_async.fetch_ohlcv.call_count == 1
assert not log_has(f"Using cached candle (OHLCV) data for {pair} ...", caplog)
# exchange = Exchange(default_conf)
2018-08-01 19:19:49 +00:00
await async_ccxt_exception(mocker, default_conf, MagicMock(),
"_async_get_candle_history", "fetch_ohlcv",
pair='ABCD/BTC', timeframe=default_conf['timeframe'])
2018-08-14 18:35:12 +00:00
api_mock = MagicMock()
with pytest.raises(OperationalException,
match=r'Could not fetch historical candle \(OHLCV\) data.*'):
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
2019-02-24 19:08:27 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_get_candle_history(pair, "5m",
(arrow.utcnow().int_timestamp - 2000) * 1000)
2018-08-01 19:19:49 +00:00
2019-08-29 11:13:41 +00:00
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
r'historical candle \(OHLCV\) data\..*'):
2019-08-29 11:13:41 +00:00
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_get_candle_history(pair, "5m",
(arrow.utcnow().int_timestamp - 2000) * 1000)
2019-08-29 11:13:41 +00:00
2018-08-01 19:19:49 +00:00
2018-09-02 17:15:23 +00:00
@pytest.mark.asyncio
async def test__async_get_candle_history_empty(default_conf, mocker, caplog):
""" Test empty exchange result """
ohlcv = []
2018-09-02 17:15:23 +00:00
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf)
# Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro([])
exchange = Exchange(default_conf)
pair = 'ETH/BTC'
res = await exchange._async_get_candle_history(pair, "5m")
assert type(res) is tuple
assert len(res) == 3
2018-09-02 17:15:23 +00:00
assert res[0] == pair
assert res[1] == "5m"
assert res[2] == ohlcv
2018-09-02 17:15:23 +00:00
assert exchange._api_async.fetch_ohlcv.call_count == 1
2019-01-22 06:38:15 +00:00
def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog):
async def mock_get_candle_hist(pair, *args, **kwargs):
if pair == 'ETH/BTC':
return [[]]
else:
raise TypeError()
exchange = get_patched_exchange(mocker, default_conf)
# Monkey-patch async function with empty result
exchange._api_async.fetch_ohlcv = MagicMock(side_effect=mock_get_candle_hist)
2019-01-22 06:38:15 +00:00
pairs = [("ETH/BTC", "5m"), ("XRP/BTC", "5m")]
res = exchange.refresh_latest_ohlcv(pairs)
assert exchange._klines
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert type(res) is dict
assert len(res) == 1
2019-01-22 18:17:21 +00:00
# Test that each is in list at least once as order is not guaranteed
2019-08-11 18:17:39 +00:00
assert log_has("Error loading ETH/BTC. Result was [[]].", caplog)
assert log_has("Async code raised an exception: TypeError", caplog)
def test_get_next_limit_in_list():
limit_range = [5, 10, 20, 50, 100, 500, 1000]
assert Exchange.get_next_limit_in_list(1, limit_range) == 5
assert Exchange.get_next_limit_in_list(5, limit_range) == 5
assert Exchange.get_next_limit_in_list(6, limit_range) == 10
assert Exchange.get_next_limit_in_list(9, limit_range) == 10
assert Exchange.get_next_limit_in_list(10, limit_range) == 10
assert Exchange.get_next_limit_in_list(11, limit_range) == 20
assert Exchange.get_next_limit_in_list(19, limit_range) == 20
assert Exchange.get_next_limit_in_list(21, limit_range) == 50
assert Exchange.get_next_limit_in_list(51, limit_range) == 100
assert Exchange.get_next_limit_in_list(1000, limit_range) == 1000
# Going over the limit ...
assert Exchange.get_next_limit_in_list(1001, limit_range) == 1000
assert Exchange.get_next_limit_in_list(2000, limit_range) == 1000
# Without required range
assert Exchange.get_next_limit_in_list(2000, limit_range, False) is None
assert Exchange.get_next_limit_in_list(15, limit_range, False) == 20
assert Exchange.get_next_limit_in_list(21, None) == 21
assert Exchange.get_next_limit_in_list(100, None) == 100
assert Exchange.get_next_limit_in_list(1000, None) == 1000
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name):
default_conf['exchange']['name'] = exchange_name
api_mock = MagicMock()
api_mock.fetch_l2_order_book = order_book_l2
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order_book = exchange.fetch_l2_order_book(pair='ETH/BTC', limit=10)
2018-08-05 04:41:06 +00:00
assert 'bids' in order_book
assert 'asks' in order_book
assert len(order_book['bids']) == 10
assert len(order_book['asks']) == 10
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'
# Not all exchanges support all limits for orderbook
if not exchange._ft_has['l2_limit_range'] or val in exchange._ft_has['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._ft_has['l2_limit_range'])
assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == next_limit
2018-08-05 04:41:06 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
2018-08-05 14:41:58 +00:00
api_mock = MagicMock()
with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.fetch_l2_order_book(pair='ETH/BTC', limit=50)
2018-08-05 14:41:58 +00:00
with pytest.raises(TemporaryError):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.fetch_l2_order_book(pair='ETH/BTC', limit=50)
2018-08-05 14:41:58 +00:00
with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.fetch_l2_order_book(pair='ETH/BTC', limit=50)
2018-08-05 14:41:58 +00:00
2021-06-02 09:30:19 +00:00
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", [
('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), # ask_last_balance missing
2021-06-02 09:30:19 +00:00
('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
('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), # ask_last_balance missing
2021-06-02 09:30:19 +00:00
('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
])
def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
last, last_ab, expected) -> None:
caplog.set_level(logging.DEBUG)
if last_ab is None:
del default_conf['bid_strategy']['ask_last_balance']
else:
default_conf['bid_strategy']['ask_last_balance'] = last_ab
2021-06-02 09:30:19 +00:00
default_conf['bid_strategy']['price_side'] = side
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': ask, 'last': last, 'bid': bid})
2021-07-18 03:58:54 +00:00
assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected
2021-06-02 09:30:19 +00:00
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
2021-07-18 03:58:54 +00:00
assert exchange.get_rate('ETH/BTC', refresh=False, side="buy") == expected
2021-06-02 09:30:19 +00:00
assert log_has("Using cached buy rate for ETH/BTC.", caplog)
# Running a 2nd time with Refresh on!
caplog.clear()
2021-07-18 03:58:54 +00:00
assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected
2021-06-02 09:30:19 +00:00
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
2021-06-02 09:39:18 +00:00
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', [
('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),
2021-06-02 09:39:18 +00:00
('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),
2021-06-02 09:39:18 +00:00
])
def test_get_sell_rate(default_conf, mocker, caplog, side, bid, ask,
last, last_ab, expected) -> None:
caplog.set_level(logging.DEBUG)
default_conf['ask_strategy']['price_side'] = side
if last_ab is not None:
default_conf['ask_strategy']['bid_last_balance'] = last_ab
2021-06-02 09:39:18 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': ask, 'bid': bid, 'last': last})
pair = "ETH/BTC"
# Test regular mode
exchange = get_patched_exchange(mocker, default_conf)
2021-07-18 03:58:54 +00:00
rate = exchange.get_rate(pair, refresh=True, side="sell")
2021-06-02 09:39:18 +00:00
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
assert isinstance(rate, float)
assert rate == expected
# Use caching
2021-07-18 03:58:54 +00:00
rate = exchange.get_rate(pair, refresh=False, side="sell")
2021-06-02 09:39:18 +00:00
assert rate == expected
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
@pytest.mark.parametrize("entry,side,ask,bid,last,last_ab,expected", [
('buy', 'ask', None, 4, 4, 0, 4), # ask not available
('buy', 'ask', None, None, 4, 0, 4), # ask not available
('buy', 'bid', 6, None, 4, 0, 5), # bid not available
('buy', 'bid', None, None, 4, 0, 5), # No rate available
('sell', 'ask', None, 4, 4, 0, 4), # ask not available
('sell', 'ask', None, None, 4, 0, 4), # ask not available
('sell', 'bid', 6, None, 4, 0, 5), # bid not available
('sell', 'bid', None, None, 4, 0, 5), # bid not available
])
def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, ask, bid,
last, last_ab, expected) -> None:
caplog.set_level(logging.DEBUG)
default_conf['bid_strategy']['ask_last_balance'] = last_ab
default_conf['bid_strategy']['price_side'] = side
default_conf['ask_strategy']['price_side'] = side
default_conf['ask_strategy']['ask_last_balance'] = last_ab
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': ask, 'last': last, 'bid': bid})
with pytest.raises(PricingError):
exchange.get_rate('ETH/BTC', refresh=True, side=entry)
2021-06-02 09:39:18 +00:00
@pytest.mark.parametrize('side,expected', [
('bid', 0.043936), # Value from order_book_l2 fiture - bids side
('ask', 0.043949), # Value from order_book_l2 fiture - asks side
])
def test_get_sell_rate_orderbook(default_conf, mocker, caplog, side, expected, order_book_l2):
caplog.set_level(logging.DEBUG)
# Test orderbook mode
default_conf['ask_strategy']['price_side'] = side
default_conf['ask_strategy']['use_order_book'] = True
default_conf['ask_strategy']['order_book_top'] = 1
2021-06-02 09:39:18 +00:00
pair = "ETH/BTC"
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
exchange = get_patched_exchange(mocker, default_conf)
2021-07-18 03:58:54 +00:00
rate = exchange.get_rate(pair, refresh=True, side="sell")
2021-06-02 09:39:18 +00:00
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
assert isinstance(rate, float)
assert rate == expected
2021-07-18 03:58:54 +00:00
rate = exchange.get_rate(pair, refresh=False, side="sell")
2021-06-02 09:39:18 +00:00
assert rate == expected
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
def test_get_sell_rate_orderbook_exception(default_conf, mocker, caplog):
# Test orderbook mode
default_conf['ask_strategy']['price_side'] = 'ask'
default_conf['ask_strategy']['use_order_book'] = True
default_conf['ask_strategy']['order_book_top'] = 1
2021-06-02 09:39:18 +00:00
pair = "ETH/BTC"
# Test What happens if the exchange returns an empty orderbook.
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book',
return_value={'bids': [[]], 'asks': [[]]})
exchange = get_patched_exchange(mocker, default_conf)
with pytest.raises(PricingError):
2021-07-18 03:58:54 +00:00
exchange.get_rate(pair, refresh=True, side="sell")
2021-06-25 18:51:45 +00:00
assert log_has_re(r"Sell Price at location 1 from orderbook could not be determined\..*",
caplog)
2021-06-02 09:39:18 +00:00
def test_get_sell_rate_exception(default_conf, mocker, caplog):
# Ticker on one side can be empty in certain circumstances.
default_conf['ask_strategy']['price_side'] = 'ask'
pair = "ETH/BTC"
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': None, 'bid': 0.12, 'last': None})
exchange = get_patched_exchange(mocker, default_conf)
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
2021-07-18 03:58:54 +00:00
exchange.get_rate(pair, refresh=True, side="sell")
2021-06-02 09:39:18 +00:00
exchange._config['ask_strategy']['price_side'] = 'bid'
2021-07-18 03:58:54 +00:00
assert exchange.get_rate(pair, refresh=True, side="sell") == 0.12
2021-06-02 09:39:18 +00:00
# Reverse sides
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': 0.13, 'bid': None, 'last': None})
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
2021-07-18 03:58:54 +00:00
exchange.get_rate(pair, refresh=True, side="sell")
2021-06-02 09:39:18 +00:00
exchange._config['ask_strategy']['price_side'] = 'ask'
2021-07-18 03:58:54 +00:00
assert exchange.get_rate(pair, refresh=True, side="sell") == 0.13
2021-06-02 09:39:18 +00:00
def make_fetch_ohlcv_mock(data):
def fetch_ohlcv_mock(pair, timeframe, since):
if since:
assert since > data[-1][0]
return []
return data
return fetch_ohlcv_mock
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@pytest.mark.asyncio
async def test___async_get_candle_history_sort(default_conf, mocker, exchange_name):
2018-11-25 14:00:50 +00:00
def sort_data(data, key):
return sorted(data, key=key)
# GDAX use-case (real data from GDAX)
# This OHLCV data is ordered DESC (newest first, oldest last)
ohlcv = [
[1527833100000, 0.07666, 0.07671, 0.07666, 0.07668, 16.65244264],
[1527832800000, 0.07662, 0.07666, 0.07662, 0.07666, 1.30051526],
[1527832500000, 0.07656, 0.07661, 0.07656, 0.07661, 12.034778840000001],
[1527832200000, 0.07658, 0.07658, 0.07655, 0.07656, 0.59780186],
[1527831900000, 0.07658, 0.07658, 0.07658, 0.07658, 1.76278136],
[1527831600000, 0.07658, 0.07658, 0.07658, 0.07658, 2.22646521],
[1527831300000, 0.07655, 0.07657, 0.07655, 0.07657, 1.1753],
[1527831000000, 0.07654, 0.07654, 0.07651, 0.07651, 0.8073060299999999],
[1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687],
[1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867]
]
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
2019-02-21 05:59:52 +00:00
sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data))
# Test the OHLCV data sort
res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe'])
assert res[0] == 'ETH/BTC'
res_ohlcv = res[2]
2018-11-25 14:00:50 +00:00
assert sort_mock.call_count == 1
assert res_ohlcv[0][0] == 1527830400000
assert res_ohlcv[0][1] == 0.07649
assert res_ohlcv[0][2] == 0.07651
assert res_ohlcv[0][3] == 0.07649
assert res_ohlcv[0][4] == 0.07651
assert res_ohlcv[0][5] == 2.5734867
assert res_ohlcv[9][0] == 1527833100000
assert res_ohlcv[9][1] == 0.07666
assert res_ohlcv[9][2] == 0.07671
assert res_ohlcv[9][3] == 0.07666
assert res_ohlcv[9][4] == 0.07668
assert res_ohlcv[9][5] == 16.65244264
# Bittrex use-case (real data from Bittrex)
# This OHLCV data is ordered ASC (oldest first, newest last)
ohlcv = [
[1527827700000, 0.07659999, 0.0766, 0.07627, 0.07657998, 1.85216924],
[1527828000000, 0.07657995, 0.07657995, 0.0763, 0.0763, 26.04051037],
[1527828300000, 0.0763, 0.07659998, 0.0763, 0.0764, 10.36434124],
[1527828600000, 0.0764, 0.0766, 0.0764, 0.0766, 5.71044773],
[1527828900000, 0.0764, 0.07666998, 0.0764, 0.07666998, 47.48888565],
[1527829200000, 0.0765, 0.07672999, 0.0765, 0.07672999, 3.37640326],
[1527829500000, 0.0766, 0.07675, 0.0765, 0.07675, 8.36203831],
[1527829800000, 0.07675, 0.07677999, 0.07620002, 0.076695, 119.22963884],
[1527830100000, 0.076695, 0.07671, 0.07624171, 0.07671, 1.80689244],
[1527830400000, 0.07671, 0.07674399, 0.07629216, 0.07655213, 2.31452783]
]
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
2018-11-25 14:00:50 +00:00
# Reset sort mock
sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data))
# Test the OHLCV data sort
res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe'])
assert res[0] == 'ETH/BTC'
assert res[1] == default_conf['timeframe']
res_ohlcv = res[2]
2018-11-25 14:00:50 +00:00
# Sorted not called again - data is already in order
assert sort_mock.call_count == 0
assert res_ohlcv[0][0] == 1527827700000
assert res_ohlcv[0][1] == 0.07659999
assert res_ohlcv[0][2] == 0.0766
assert res_ohlcv[0][3] == 0.07627
assert res_ohlcv[0][4] == 0.07657998
assert res_ohlcv[0][5] == 1.85216924
assert res_ohlcv[9][0] == 1527830400000
assert res_ohlcv[9][1] == 0.07671
assert res_ohlcv[9][2] == 0.07674399
assert res_ohlcv[9][3] == 0.07629216
assert res_ohlcv[9][4] == 0.07655213
assert res_ohlcv[9][5] == 2.31452783
2019-08-29 14:33:56 +00:00
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
fetch_trades_result):
2019-08-29 14:33:56 +00:00
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
# Monkey-patch async function
exchange._api_async.fetch_trades = get_mock_coro(fetch_trades_result)
2019-08-29 14:33:56 +00:00
pair = 'ETH/BTC'
res = await exchange._async_fetch_trades(pair, since=None, params=None)
assert type(res) is list
assert isinstance(res[0], list)
assert isinstance(res[1], list)
2019-08-29 14:33:56 +00:00
assert exchange._api_async.fetch_trades.call_count == 1
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
assert log_has_re(f"Fetching trades for pair {pair}, since .*", caplog)
caplog.clear()
exchange._api_async.fetch_trades.reset_mock()
res = await exchange._async_fetch_trades(pair, since=None, params={'from': '123'})
assert exchange._api_async.fetch_trades.call_count == 1
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
assert exchange._api_async.fetch_trades.call_args[1]['params'] == {'from': '123'}
assert log_has_re(f"Fetching trades for pair {pair}, params: .*", caplog)
exchange = Exchange(default_conf)
await async_ccxt_exception(mocker, default_conf, MagicMock(),
"_async_fetch_trades", "fetch_trades",
pair='ABCD/BTC', since=None)
api_mock = MagicMock()
with pytest.raises(OperationalException, match=r'Could not fetch trade data*'):
api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000)
2019-08-29 14:33:56 +00:00
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
r'historical trade data\..*'):
api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000)
2019-08-29 14:33:56 +00:00
2019-09-28 09:17:02 +00:00
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_id(default_conf, mocker, exchange_name,
fetch_trades_result):
2019-09-28 09:17:02 +00:00
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
2019-09-28 11:35:25 +00:00
pagination_arg = exchange._trades_pagination_arg
async def mock_get_trade_hist(pair, *args, **kwargs):
if 'since' in kwargs:
# Return first 3
return fetch_trades_result[:-2]
elif kwargs.get('params', {}).get(pagination_arg) == fetch_trades_result[-3]['id']:
2019-09-28 11:35:25 +00:00
# Return 2
return fetch_trades_result[-3:-1]
2019-09-28 11:35:25 +00:00
else:
# Return last 2
return fetch_trades_result[-2:]
2019-09-28 09:17:02 +00:00
# Monkey-patch async function
exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
2019-09-28 11:35:25 +00:00
2019-09-28 09:17:02 +00:00
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)
2019-09-28 11:35:25 +00:00
assert type(ret) is tuple
assert ret[0] == pair
assert type(ret[1]) is list
assert len(ret[1]) == len(fetch_trades_result)
assert exchange._api_async.fetch_trades.call_count == 3
fetch_trades_cal = exchange._api_async.fetch_trades.call_args_list
2019-09-28 11:35:25 +00:00
# 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']
2019-09-28 11:35:25 +00:00
# 2nd call
assert fetch_trades_cal[1][0][0] == pair
assert 'params' in fetch_trades_cal[1][1]
assert exchange._ft_has['trades_pagination_arg'] in fetch_trades_cal[1][1]['params']
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_time(default_conf, mocker, caplog, exchange_name,
fetch_trades_result):
2019-09-28 11:35:25 +00:00
caplog.set_level(logging.DEBUG)
async def mock_get_trade_hist(pair, *args, **kwargs):
if kwargs['since'] == fetch_trades_result[0]['timestamp']:
return fetch_trades_result[:-1]
2019-09-28 11:35:25 +00:00
else:
return fetch_trades_result[-1:]
2019-09-28 11:35:25 +00:00
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
# Monkey-patch async function
exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
2019-09-28 11:35:25 +00:00
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)
2019-09-28 09:17:02 +00:00
assert type(ret) is tuple
assert ret[0] == pair
assert type(ret[1]) is list
assert len(ret[1]) == len(fetch_trades_result)
assert exchange._api_async.fetch_trades.call_count == 2
fetch_trades_cal = exchange._api_async.fetch_trades.call_args_list
2019-09-28 09:17:02 +00:00
# first call (using since, not fromId)
2019-09-28 11:35:25 +00:00
assert fetch_trades_cal[0][0][0] == pair
assert fetch_trades_cal[0][1]['since'] == fetch_trades_result[0]['timestamp']
2019-09-28 09:17:02 +00:00
# 2nd call
2019-09-28 11:35:25 +00:00
assert fetch_trades_cal[1][0][0] == pair
assert fetch_trades_cal[1][1]['since'] == fetch_trades_result[-2]['timestamp']
2019-09-28 11:35:25 +00:00
assert log_has_re(r"Stopping because until was reached.*", caplog)
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog, exchange_name,
trades_history):
caplog.set_level(logging.DEBUG)
async def mock_get_trade_hist(pair, *args, **kwargs):
if kwargs['since'] == trades_history[0][0]:
2019-09-28 11:35:25 +00:00
return trades_history[:-1]
else:
return []
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
# Monkey-patch async function
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
pair = 'ETH/BTC'
ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0][0],
until=trades_history[-1][0]-1)
2019-09-28 11:35:25 +00:00
assert type(ret) is tuple
assert ret[0] == pair
assert type(ret[1]) is list
assert len(ret[1]) == len(trades_history) - 1
assert exchange._async_fetch_trades.call_count == 2
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
# first call (using since, not fromId)
assert fetch_trades_cal[0][0][0] == pair
assert fetch_trades_cal[0][1]['since'] == trades_history[0][0]
2019-09-28 09:17:02 +00:00
2019-08-30 04:51:21 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades_history):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
pair = 'ETH/BTC'
exchange._async_get_trade_history_id = get_mock_coro((pair, trades_history))
exchange._async_get_trade_history_time = get_mock_coro((pair, trades_history))
ret = exchange.get_historic_trades(pair, since=trades_history[0][0],
until=trades_history[-1][0])
2019-08-30 04:51:21 +00:00
# Depending on the exchange, one or the other method should be called
assert sum([exchange._async_get_trade_history_id.call_count,
exchange._async_get_trade_history_time.call_count]) == 1
assert len(ret) == 2
assert ret[0] == pair
2019-09-28 11:35:25 +00:00
assert len(ret[1]) == len(trades_history)
2019-08-30 04:51:21 +00:00
2019-09-28 08:45:16 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange_name,
trades_history):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=False)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
pair = 'ETH/BTC'
with pytest.raises(OperationalException,
2021-08-16 18:02:25 +00:00
match="This exchange does not support downloading Trades."):
exchange.get_historic_trades(pair, since=trades_history[0][0],
until=trades_history[-1][0])
2019-09-28 08:45:16 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
2021-06-05 13:22:52 +00:00
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True)
2020-04-18 04:55:25 +00:00
assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {}
2020-03-25 16:01:45 +00:00
assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {}
2021-08-02 06:31:33 +00:00
order = exchange.create_order('ETH/BTC', 'limit', "buy", 5, 0.55, 'gtc')
2020-08-29 08:07:02 +00:00
cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC')
assert order['id'] == cancel_order['id']
assert order['amount'] == cancel_order['amount']
2020-09-06 13:27:16 +00:00
assert order['symbol'] == cancel_order['symbol']
2020-08-29 08:07:02 +00:00
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),
2021-07-18 03:58:54 +00:00
])
def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order, result):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert exchange.check_order_canceled_empty(order) == result
2020-04-17 05:18:46 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@pytest.mark.parametrize("order,result", [
({'status': 'closed', 'amount': 10, 'fee': {}}, True),
({'status': 'closed', 'amount': 0.0, 'fee': {}}, True),
({'status': 'canceled', 'amount': 0.0, 'fee': {}}, True),
({'status': 'canceled', 'amount': 10.0}, False),
({'amount': 10.0, 'fee': {}}, False),
({'result': 'testest123'}, False),
('hello_world', False),
])
def test_is_cancel_order_result_suitable(mocker, default_conf, exchange_name, order, result):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert exchange.is_cancel_order_result_suitable(order) == result
2020-04-17 15:53:18 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@pytest.mark.parametrize("corder,call_corder,call_forder", [
({'status': 'closed', 'amount': 10, 'fee': {}}, 1, 0),
({'amount': 10, 'fee': {}}, 1, 1),
])
def test_cancel_order_with_result(default_conf, mocker, exchange_name, corder,
call_corder, call_forder):
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.cancel_order = MagicMock(return_value=corder)
api_mock.fetch_order = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
res = exchange.cancel_order_with_result('1234', 'ETH/BTC', 1234)
assert isinstance(res, dict)
assert api_mock.cancel_order.call_count == call_corder
assert api_mock.fetch_order.call_count == call_forder
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_order_with_result_error(default_conf, mocker, exchange_name, caplog):
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
res = exchange.cancel_order_with_result('1234', 'ETH/BTC', 1541)
assert isinstance(res, dict)
2020-08-01 13:59:50 +00:00
assert log_has("Could not cancel order 1234 for ETH/BTC.", caplog)
2020-04-17 15:53:18 +00:00
assert log_has("Could not fetch cancelled order 1234.", caplog)
assert res['amount'] == 1541
2018-01-10 06:41:37 +00:00
# Ensure that if not dry_run, we should call API
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_order(default_conf, mocker, exchange_name):
2018-01-10 06:41:37 +00:00
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.cancel_order = MagicMock(return_value={'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'}
2019-04-02 16:45:18 +00:00
with pytest.raises(InvalidOrderException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 18:13:39 +00:00
exchange.cancel_order(order_id='_', pair='TKN/BTC')
2019-04-02 16:45:18 +00:00
assert api_mock.cancel_order.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2018-06-28 20:18:38 +00:00
"cancel_order", "cancel_order",
order_id='_', pair='TKN/BTC')
2018-01-10 06:41:37 +00:00
2020-03-25 16:01:45 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.cancel_order = MagicMock(return_value={'id': '123'})
2020-03-25 16:01:45 +00:00
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.cancel_stoploss_order(order_id='_', pair='TKN/BTC') == {'id': '123'}
2020-03-25 16:01:45 +00:00
with pytest.raises(InvalidOrderException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
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')
2021-05-16 12:31:53 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123})
mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', return_value={'for': 123})
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
return_value={'fee': {}, 'status': 'canceled', 'amount': 1234})
mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
return_value={'fee': {}, 'status': 'canceled', 'amount': 1234})
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
assert co == {'fee': {}, 'status': 'canceled', 'amount': 1234}
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
return_value='canceled')
mocker.patch('freqtrade.exchange.Ftx.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}
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order',
side_effect=InvalidOrderException(""))
mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order',
side_effect=InvalidOrderException(""))
co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
assert co['amount'] == 555
assert co == {'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}}
with pytest.raises(InvalidOrderException):
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
side_effect=InvalidOrderException("Did not find order"))
mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
side_effect=InvalidOrderException("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123)
@pytest.mark.parametrize("exchange_name", EXCHANGES)
2021-06-10 18:09:25 +00:00
def test_fetch_order(default_conf, mocker, exchange_name, caplog):
2018-01-10 06:41:37 +00:00
default_conf['dry_run'] = True
2021-06-10 18:09:25 +00:00
default_conf['exchange']['log_responses'] = True
2018-01-10 06:41:37 +00:00
order = MagicMock()
order.myid = 123
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
2018-06-18 20:09:46 +00:00
exchange._dry_run_open_orders['X'] = order
assert exchange.fetch_order('X', 'TKN/BTC').myid == 123
2018-01-10 06:41:37 +00:00
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
exchange.fetch_order('Y', 'TKN/BTC')
2018-01-10 06:41:37 +00:00
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.fetch_order = MagicMock(return_value=456)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.fetch_order('X', 'TKN/BTC') == 456
2021-06-10 18:09:25 +00:00
assert log_has("API fetch_order: 456", 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')
assert api_mock.fetch_order.call_count == 1
2020-06-28 17:45:42 +00:00
api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
with patch('freqtrade.exchange.common.time.sleep') as tm:
with pytest.raises(InvalidOrderException):
exchange.fetch_order(order_id='_', pair='TKN/BTC')
# Ensure backoff is called
assert tm.call_args_list[0][0][0] == 1
assert tm.call_args_list[1][0][0] == 2
2020-08-22 15:35:42 +00:00
if API_FETCH_ORDER_RETRY_COUNT > 2:
assert tm.call_args_list[2][0][0] == 5
if API_FETCH_ORDER_RETRY_COUNT > 3:
assert tm.call_args_list[3][0][0] == 10
assert api_mock.fetch_order.call_count == API_FETCH_ORDER_RETRY_COUNT + 1
2020-06-28 17:45:42 +00:00
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2020-08-22 15:35:42 +00:00
'fetch_order', 'fetch_order', retries=API_FETCH_ORDER_RETRY_COUNT + 1,
2018-06-28 20:18:38 +00:00
order_id='_', pair='TKN/BTC')
2018-01-10 06:41:37 +00:00
2020-03-25 16:01:45 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
2021-09-16 04:28:10 +00:00
# Don't test FTX here - that needs a separate test
2020-03-25 16:01:45 +00:00
if exchange_name == 'ftx':
return
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
exchange._dry_run_open_orders['X'] = order
assert exchange.fetch_stoploss_order('X', 'TKN/BTC').myid == 123
2020-03-25 16:01:45 +00:00
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
exchange.fetch_stoploss_order('Y', 'TKN/BTC')
2020-03-25 16:01:45 +00:00
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.fetch_order = MagicMock(return_value=456)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.fetch_stoploss_order('X', 'TKN/BTC') == 456
2020-03-25 16:01:45 +00:00
with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.fetch_stoploss_order(order_id='_', pair='TKN/BTC')
2020-03-25 16:01:45 +00:00
assert api_mock.fetch_order.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'fetch_stoploss_order', 'fetch_order',
2020-08-22 15:35:42 +00:00
retries=API_FETCH_ORDER_RETRY_COUNT + 1,
2020-03-25 16:01:45 +00:00
order_id='_', pair='TKN/BTC')
2020-06-01 09:24:23 +00:00
2020-08-22 07:24:14 +00:00
def test_fetch_order_or_stoploss_order(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='binance')
fetch_order_mock = MagicMock()
fetch_stoploss_order_mock = MagicMock()
mocker.patch.multiple('freqtrade.exchange.Exchange',
fetch_order=fetch_order_mock,
fetch_stoploss_order=fetch_stoploss_order_mock,
)
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_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)
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'
@pytest.mark.parametrize("exchange_name", EXCHANGES)
2019-09-10 21:18:07 +00:00
def test_name(default_conf, mocker, exchange_name):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert exchange.name == exchange_name.title()
assert exchange.id == exchange_name
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_trades_for_order(default_conf, mocker, exchange_name):
2019-11-08 06:32:08 +00:00
2018-06-17 21:22:28 +00:00
order_id = 'ABCD-ABCD'
since = datetime(2018, 5, 5, 0, 0, 0)
2018-06-17 21:22:28 +00:00
default_conf["dry_run"] = False
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
api_mock = MagicMock()
api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV',
'order': 'ABCD-ABCD',
'info': {'pair': 'XLTCZBTC',
'time': 1519860024.4388,
'type': 'buy',
'ordertype': 'limit',
'price': '20.00000',
'cost': '38.62000',
'fee': '0.06179',
'vol': '5',
'id': 'ABCD-ABCD'},
'timestamp': 1519860024438,
'datetime': '2018-02-28T23:20:24.438Z',
'symbol': 'LTC/BTC',
'type': 'limit',
'side': 'buy',
'price': 165.0,
'amount': 0.2340606,
'fee': {'cost': 0.06179, 'currency': 'BTC'}
}])
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
2018-06-17 21:22:28 +00:00
orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since)
assert len(orders) == 1
assert orders[0]['price'] == 165
assert api_mock.fetch_my_trades.call_count == 1
# since argument should be
assert isinstance(api_mock.fetch_my_trades.call_args[0][1], int)
assert api_mock.fetch_my_trades.call_args[0][0] == 'LTC/BTC'
# Same test twice, hardcoded number and doing the same calculation
2019-08-06 18:23:32 +00:00
assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000
assert api_mock.fetch_my_trades.call_args[0][1] == int(since.replace(
tzinfo=timezone.utc).timestamp() - 5) * 1000
2018-06-17 21:22:28 +00:00
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2018-06-28 20:18:38 +00:00
'get_trades_for_order', 'fetch_my_trades',
order_id=order_id, pair='LTC/BTC', since=since)
2018-06-17 21:22:28 +00:00
2018-06-28 19:51:59 +00:00
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False))
assert exchange.get_trades_for_order(order_id, 'LTC/BTC', since) == []
2018-06-17 21:22:28 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_fee(default_conf, mocker, exchange_name):
2018-01-10 06:41:37 +00:00
api_mock = MagicMock()
api_mock.calculate_fee = MagicMock(return_value={
'type': 'taker',
'currency': 'BTC',
'rate': 0.025,
'cost': 0.05
})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange._config.pop('fee', None)
2018-06-17 18:13:39 +00:00
2019-12-14 12:22:42 +00:00
assert exchange.get_fee('ETH/BTC') == 0.025
assert api_mock.calculate_fee.call_count == 1
2018-04-16 17:43:13 +00:00
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
2019-12-14 12:22:42 +00:00
'get_fee', 'calculate_fee', symbol="ETH/BTC")
2018-11-22 16:41:01 +00:00
api_mock.calculate_fee.reset_mock()
exchange._config['fee'] = 0.001
assert exchange.get_fee('ETH/BTC') == 0.001
assert api_mock.calculate_fee.call_count == 0
2018-11-22 18:38:20 +00:00
def test_stoploss_order_unsupported_exchange(default_conf, mocker):
2020-12-20 18:38:12 +00:00
exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
2021-09-19 23:02:09 +00:00
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side="sell",
leverage=1.0
)
2018-11-30 19:13:50 +00:00
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
2021-09-19 23:02:09 +00:00
exchange.stoploss_adjust(1, {}, side="sell")
2018-11-24 17:08:11 +00:00
2019-06-09 12:05:36 +00:00
def test_merge_ft_has_dict(default_conf, mocker):
2019-07-03 18:20:12 +00:00
mocker.patch.multiple('freqtrade.exchange.Exchange',
_init_ccxt=MagicMock(return_value=MagicMock()),
_load_async_markets=MagicMock(),
2019-07-03 18:20:12 +00:00
validate_pairs=MagicMock(),
validate_timeframes=MagicMock(),
validate_stakecurrency=MagicMock()
)
2019-06-09 12:05:36 +00:00
ex = Exchange(default_conf)
assert ex._ft_has == Exchange._ft_has_default
ex = Kraken(default_conf)
2019-08-15 14:49:54 +00:00
assert ex._ft_has != Exchange._ft_has_default
assert ex._ft_has['trades_pagination'] == 'id'
assert ex._ft_has['trades_pagination_arg'] == 'since'
2019-06-09 12:05:36 +00:00
# Binance defines different values
ex = Binance(default_conf)
assert ex._ft_has != Exchange._ft_has_default
assert ex._ft_has['stoploss_on_exchange']
assert ex._ft_has['order_time_in_force'] == ['gtc', 'fok', 'ioc']
2019-08-15 14:49:54 +00:00
assert ex._ft_has['trades_pagination'] == 'id'
assert ex._ft_has['trades_pagination_arg'] == 'fromId'
2019-06-09 12:05:36 +00:00
conf = copy.deepcopy(default_conf)
conf['exchange']['_ft_has_params'] = {"DeadBeef": 20,
"stoploss_on_exchange": False}
# Use settings from configuration (overriding stoploss_on_exchange)
ex = Binance(conf)
assert ex._ft_has != Exchange._ft_has_default
assert not ex._ft_has['stoploss_on_exchange']
assert ex._ft_has['DeadBeef'] == 20
2019-07-03 18:20:12 +00:00
def test_get_valid_pair_combination(default_conf, mocker, markets):
mocker.patch.multiple('freqtrade.exchange.Exchange',
_init_ccxt=MagicMock(return_value=MagicMock()),
_load_async_markets=MagicMock(),
2019-07-03 18:20:12 +00:00
validate_pairs=MagicMock(),
validate_timeframes=MagicMock(),
markets=PropertyMock(return_value=markets))
ex = Exchange(default_conf)
assert ex.get_valid_pair_combination("ETH", "BTC") == "ETH/BTC"
assert ex.get_valid_pair_combination("BTC", "ETH") == "ETH/BTC"
with pytest.raises(DependencyException, match=r"Could not combine.* to get a valid pair."):
ex.get_valid_pair_combination("NOPAIR", "ETH")
2019-08-12 13:43:10 +00:00
2019-10-17 20:40:29 +00:00
@pytest.mark.parametrize(
"base_currencies, quote_currencies, pairs_only, active_only, expected_keys", [
# Testing markets (in conftest.py):
# 'BLK/BTC': 'active': True
# 'BTT/BTC': 'active': True
# 'ETH/BTC': 'active': True
# 'ETH/USDT': 'active': True
# 'LTC/BTC': 'active': False
# 'LTC/ETH': 'active': True
2019-10-17 20:40:29 +00:00
# 'LTC/USD': 'active': True
# 'LTC/USDT': 'active': True
# 'NEO/BTC': 'active': False
# 'TKN/BTC': 'active' not set
# 'XLTCUSDT': 'active': True, not a pair
# 'XRP/BTC': 'active': False
# all markets
([], [], False, False,
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD',
2019-10-17 20:40:29 +00:00
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']),
# active markets
([], [], False, True,
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC',
2019-10-26 08:08:23 +00:00
'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']),
2019-10-17 20:40:29 +00:00
# all pairs
([], [], True, False,
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD',
2019-10-17 20:40:29 +00:00
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']),
# active pairs
([], [], True, True,
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC',
2019-10-26 08:08:23 +00:00
'TKN/BTC', 'XRP/BTC']),
2019-10-17 20:40:29 +00:00
# all markets, base=ETH, LTC
(['ETH', 'LTC'], [], False, False,
['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
2019-10-17 20:40:29 +00:00
# all markets, base=LTC
(['LTC'], [], False, False,
['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
2019-10-17 20:40:29 +00:00
# all markets, quote=USDT
([], ['USDT'], False, False,
['ETH/USDT', 'LTC/USDT', 'XLTCUSDT']),
# all markets, quote=USDT, USD
([], ['USDT', 'USD'], False, False,
['ETH/USDT', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
# all markets, base=LTC, quote=USDT
(['LTC'], ['USDT'], False, False,
['LTC/USDT', 'XLTCUSDT']),
# all pairs, base=LTC, quote=USDT
(['LTC'], ['USDT'], True, False,
['LTC/USDT']),
# all markets, base=LTC, quote=USDT, NONEXISTENT
(['LTC'], ['USDT', 'NONEXISTENT'], False, False,
['LTC/USDT', 'XLTCUSDT']),
# all markets, base=LTC, quote=NONEXISTENT
(['LTC'], ['NONEXISTENT'], False, False,
[]),
])
def test_get_markets(default_conf, mocker, markets_static,
2019-10-17 19:45:20 +00:00
base_currencies, quote_currencies, pairs_only, active_only,
expected_keys):
mocker.patch.multiple('freqtrade.exchange.Exchange',
_init_ccxt=MagicMock(return_value=MagicMock()),
_load_async_markets=MagicMock(),
2019-10-17 19:45:20 +00:00
validate_pairs=MagicMock(),
validate_timeframes=MagicMock(),
markets=PropertyMock(return_value=markets_static))
2019-10-17 19:45:20 +00:00
ex = Exchange(default_conf)
pairs = ex.get_markets(base_currencies, quote_currencies, pairs_only, active_only)
assert sorted(pairs.keys()) == sorted(expected_keys)
def test_get_markets_error(default_conf, mocker):
ex = get_patched_exchange(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=None))
with pytest.raises(OperationalException, match="Markets were not loaded."):
ex.get_markets('LTC', 'USDT', True, False)
2021-02-14 09:38:49 +00:00
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_ohlcv_candle_limit(default_conf, mocker, exchange_name):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
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]
# This should only run for bittrex
assert exchange_name == 'bittrex'
assert exchange.ohlcv_candle_limit(timeframe) == expected
2019-08-12 13:43:10 +00:00
def test_timeframe_to_minutes():
assert timeframe_to_minutes("5m") == 5
assert timeframe_to_minutes("10m") == 10
assert timeframe_to_minutes("1h") == 60
assert timeframe_to_minutes("1d") == 1440
def test_timeframe_to_seconds():
assert timeframe_to_seconds("5m") == 300
assert timeframe_to_seconds("10m") == 600
assert timeframe_to_seconds("1h") == 3600
assert timeframe_to_seconds("1d") == 86400
def test_timeframe_to_msecs():
assert timeframe_to_msecs("5m") == 300000
assert timeframe_to_msecs("10m") == 600000
assert timeframe_to_msecs("1h") == 3600000
assert timeframe_to_msecs("1d") == 86400000
2019-08-12 14:07:19 +00:00
2019-08-12 14:11:43 +00:00
def test_timeframe_to_prev_date():
# 2019-08-12 13:22:08
date = datetime.fromtimestamp(1565616128, tz=timezone.utc)
2019-08-12 14:17:06 +00:00
tf_list = [
# 5m -> 2019-08-12 13:20:00
("5m", datetime(2019, 8, 12, 13, 20, 0, tzinfo=timezone.utc)),
# 10m -> 2019-08-12 13:20:00
("10m", datetime(2019, 8, 12, 13, 20, 0, tzinfo=timezone.utc)),
# 1h -> 2019-08-12 13:00:00
("1h", datetime(2019, 8, 12, 13, 00, 0, tzinfo=timezone.utc)),
# 2h -> 2019-08-12 12:00:00
("2h", datetime(2019, 8, 12, 12, 00, 0, tzinfo=timezone.utc)),
# 4h -> 2019-08-12 12:00:00
("4h", datetime(2019, 8, 12, 12, 00, 0, tzinfo=timezone.utc)),
# 1d -> 2019-08-12 00:00:00
("1d", datetime(2019, 8, 12, 00, 00, 0, tzinfo=timezone.utc)),
]
for interval, result in tf_list:
assert timeframe_to_prev_date(interval, date) == result
2019-08-12 14:11:43 +00:00
date = datetime.now(tz=timezone.utc)
assert timeframe_to_prev_date("5m") < date
2021-02-09 19:03:03 +00:00
# Does not round
time = datetime(2019, 8, 12, 13, 20, 0, tzinfo=timezone.utc)
assert timeframe_to_prev_date('5m', time) == time
2019-08-12 14:11:43 +00:00
2019-08-12 14:07:19 +00:00
def test_timeframe_to_next_date():
# 2019-08-12 13:22:08
date = datetime.fromtimestamp(1565616128, tz=timezone.utc)
2019-08-12 14:17:06 +00:00
tf_list = [
# 5m -> 2019-08-12 13:25:00
("5m", datetime(2019, 8, 12, 13, 25, 0, tzinfo=timezone.utc)),
# 10m -> 2019-08-12 13:30:00
("10m", datetime(2019, 8, 12, 13, 30, 0, tzinfo=timezone.utc)),
# 1h -> 2019-08-12 14:00:00
("1h", datetime(2019, 8, 12, 14, 00, 0, tzinfo=timezone.utc)),
# 2h -> 2019-08-12 14:00:00
("2h", datetime(2019, 8, 12, 14, 00, 0, tzinfo=timezone.utc)),
# 4h -> 2019-08-12 14:00:00
("4h", datetime(2019, 8, 12, 16, 00, 0, tzinfo=timezone.utc)),
# 1d -> 2019-08-13 00:00:00
("1d", datetime(2019, 8, 13, 0, 0, 0, tzinfo=timezone.utc)),
]
2019-08-12 14:07:19 +00:00
2019-08-12 14:17:06 +00:00
for interval, result in tf_list:
assert timeframe_to_next_date(interval, date) == result
date = datetime.now(tz=timezone.utc)
assert timeframe_to_next_date("5m") > date
2019-10-17 15:19:50 +00:00
date = datetime(2019, 8, 12, 13, 30, 0, tzinfo=timezone.utc)
assert timeframe_to_next_date("5m", date) == date + timedelta(minutes=5)
2019-10-17 15:19:50 +00:00
2020-06-02 18:30:31 +00:00
@pytest.mark.parametrize("market_symbol,base,quote,exchange,add_dict,expected_result", [
("BTC/USDT", 'BTC', 'USDT', "binance", {}, True),
("USDT/BTC", 'USDT', 'BTC', "binance", {}, True),
("USDT/BTC", 'BTC', 'USDT', "binance", {}, False), # Reversed currencies
("BTCUSDT", 'BTC', 'USDT', "binance", {}, False), # No seperating /
("BTCUSDT", None, "USDT", "binance", {}, False), #
("USDT/BTC", "BTC", None, "binance", {}, False),
("BTCUSDT", "BTC", None, "binance", {}, False),
("BTC/USDT", "BTC", "USDT", "binance", {}, True),
("BTC/USDT", "USDT", "BTC", "binance", {}, False), # reversed currencies
("BTC/USDT", "BTC", "USD", "binance", {}, False), # Wrong quote currency
("BTC/", "BTC", 'UNK', "binance", {}, False),
("/USDT", 'UNK', 'USDT', "binance", {}, False),
("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": False}, True),
("EUR/BTC", 'EUR', 'BTC', "kraken", {"darkpool": False}, True),
("EUR/BTC", 'BTC', 'EUR', "kraken", {"darkpool": False}, False), # Reversed currencies
("BTC/EUR", 'BTC', 'USD', "kraken", {"darkpool": False}, False), # wrong quote currency
("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools
("BTC/EUR.d", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools
("BTC/USD", 'BTC', 'USD', "ftx", {'spot': True}, True),
("USD/BTC", 'USD', 'BTC', "ftx", {'spot': True}, True),
("BTC/USD", 'BTC', 'USDT', "ftx", {'spot': True}, False), # Wrong quote currency
("BTC/USD", 'USD', 'BTC', "ftx", {'spot': True}, False), # Reversed currencies
("BTC/USD", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets
("BTC-PERP", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets
2019-10-17 15:19:50 +00:00
])
2020-06-02 18:30:31 +00:00
def test_market_is_tradable(mocker, default_conf, market_symbol, base,
quote, add_dict, exchange, expected_result) -> None:
ex = get_patched_exchange(mocker, default_conf, id=exchange)
market = {
'symbol': market_symbol,
'base': base,
'quote': quote,
**(add_dict),
}
assert ex.market_is_tradable(market) == expected_result
2019-10-17 16:24:39 +00:00
@pytest.mark.parametrize("market,expected_result", [
({'symbol': 'ETH/BTC', 'active': True}, True),
({'symbol': 'ETH/BTC', 'active': False}, False),
({'symbol': 'ETH/BTC', }, True),
])
def test_market_is_active(market, expected_result) -> None:
assert market_is_active(market) == expected_result
2020-04-30 18:05:27 +00:00
@pytest.mark.parametrize("order,expected", [
([{'fee'}], False),
({'fee': None}, False),
({'fee': {'currency': 'ETH/BTC'}}, False),
({'fee': {'currency': 'ETH/BTC', 'cost': None}}, False),
({'fee': {'currency': 'ETH/BTC', 'cost': 0.01}}, True),
])
def test_order_has_fee(order, expected) -> None:
assert Exchange.order_has_fee(order) == expected
@pytest.mark.parametrize("order,expected", [
2020-05-01 13:17:52 +00:00
({'symbol': 'ETH/BTC', 'fee': {'currency': 'ETH', 'cost': 0.43}},
(0.43, 'ETH', 0.01)),
({'symbol': 'ETH/USDT', 'fee': {'currency': 'USDT', 'cost': 0.01}},
(0.01, 'USDT', 0.01)),
({'symbol': 'BTC/USDT', 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}},
(0.34, 'USDT', 0.01)),
2020-04-30 18:05:27 +00:00
])
2020-05-01 13:17:52 +00:00
def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01))
ex = get_patched_exchange(mocker, default_conf)
assert ex.extract_cost_curr_rate(order) == expected
@pytest.mark.parametrize("order,expected", [
# Using base-currency
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.1),
({'symbol': 'ETH/BTC', 'amount': 0.05, 'cost': 0.05,
'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.08),
# Using quote currency
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'BTC', 'cost': 0.005}}, 0.1),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, 0.04),
# Using foreign currency
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'NEO', 'cost': 0.0012}}, 0.001944),
({'symbol': 'ETH/BTC', 'amount': 2.21, 'cost': 0.02992561,
'fee': {'currency': 'NEO', 'cost': 0.00027452}}, 0.00074305),
# Rate included in return - return as is
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}}, 0.01),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, 0.005),
# 0.1% filled - no costs (kraken - #3431)
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0,
'fee': {'currency': 'BTC', 'cost': 0.0, 'rate': None}}, None),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0,
'fee': {'currency': 'ETH', 'cost': 0.0, 'rate': None}}, 0.0),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0,
'fee': {'currency': 'NEO', 'cost': 0.0, 'rate': None}}, None),
2020-05-01 13:17:52 +00:00
])
def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None:
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081})
ex = get_patched_exchange(mocker, default_conf)
assert ex.calculate_fee_rate(order) == expected
2020-06-28 14:18:39 +00:00
2020-06-28 17:40:33 +00:00
@pytest.mark.parametrize('retrycount,max_retries,expected', [
(0, 3, 10),
(1, 3, 5),
(2, 3, 2),
(3, 3, 1),
(0, 1, 2),
(1, 1, 1),
2020-08-02 08:18:19 +00:00
(0, 4, 17),
(1, 4, 10),
(2, 4, 5),
(3, 4, 2),
(4, 4, 1),
(0, 5, 26),
(1, 5, 17),
(2, 5, 10),
(3, 5, 5),
(4, 5, 2),
(5, 5, 1),
2020-06-28 14:18:39 +00:00
])
2020-06-28 17:40:33 +00:00
def test_calculate_backoff(retrycount, max_retries, expected):
assert calculate_backoff(retrycount, max_retries) == expected
@pytest.mark.parametrize("exchange_name", ['binance', 'ftx'])
2021-09-20 02:33:08 +00:00
def test_get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
api_mock = MagicMock()
api_mock.fetch_funding_history = MagicMock(return_value=[
{
2021-10-11 10:14:59 +00:00
'amount': 0.14542,
'code': 'USDT',
'datetime': '2021-09-01T08:00:01.000Z',
'id': '485478',
'info': {'asset': 'USDT',
2021-10-11 10:14:59 +00:00
'income': '0.14542',
'incomeType': 'FUNDING_FEE',
'info': 'FUNDING_FEE',
'symbol': 'XRPUSDT',
2021-10-11 10:14:59 +00:00
'time': '1630382001000',
'tradeId': '',
2021-10-11 10:14:59 +00:00
'tranId': '993203'},
'symbol': 'XRP/USDT',
2021-10-11 10:14:59 +00:00
'timestamp': 1630382001000
},
{
2021-10-11 10:14:59 +00:00
'amount': -0.14642,
'code': 'USDT',
'datetime': '2021-09-01T16:00:01.000Z',
'id': '485479',
'info': {'asset': 'USDT',
2021-10-11 10:14:59 +00:00
'income': '-0.14642',
'incomeType': 'FUNDING_FEE',
'info': 'FUNDING_FEE',
'symbol': 'XRPUSDT',
2021-10-11 10:14:59 +00:00
'time': '1630314001000',
'tradeId': '',
2021-10-11 10:14:59 +00:00
'tranId': '993204'},
'symbol': 'XRP/USDT',
2021-10-11 10:14:59 +00:00
'timestamp': 1630314001000
}
])
type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True})
# mocker.patch('freqtrade.exchange.Exchange.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')
2021-09-19 08:26:59 +00:00
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
)
assert(isclose(expected_fees, fees_from_datetime))
assert(isclose(expected_fees, fees_from_unix_time))
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
exchange_name,
"get_funding_fees_from_exchange",
"fetch_funding_history",
pair="XRP/USDT",
since=unix_time
)
2021-09-19 23:02:09 +00:00
@pytest.mark.parametrize('exchange', ['binance', 'kraken', 'ftx'])
@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 = get_patched_exchange(mocker, default_conf, id=exchange)
assert exchange._get_stake_amount_considering_leverage(
stake_amount, leverage) == min_stake_with_lev
@pytest.mark.parametrize("exchange_name,trading_mode", [
("binance", TradingMode.FUTURES),
("ftx", TradingMode.MARGIN),
("ftx", TradingMode.FUTURES)
])
def test__set_leverage(mocker, default_conf, exchange_name, trading_mode):
api_mock = MagicMock()
api_mock.set_leverage = MagicMock()
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
default_conf['dry_run'] = False
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
exchange_name,
"_set_leverage",
"set_leverage",
pair="XRP/USDT",
leverage=5.0,
trading_mode=trading_mode
)
@pytest.mark.parametrize("collateral", [
(Collateral.CROSS),
(Collateral.ISOLATED)
])
def test_set_margin_mode(mocker, default_conf, collateral):
api_mock = MagicMock()
api_mock.set_margin_mode = MagicMock()
type(api_mock).has = PropertyMock(return_value={'setMarginMode': True})
default_conf['dry_run'] = False
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"binance",
"set_margin_mode",
"set_margin_mode",
pair="XRP/USDT",
collateral=collateral
)
@pytest.mark.parametrize("exchange_name, trading_mode, collateral, exception_thrown", [
("binance", TradingMode.SPOT, None, False),
("binance", TradingMode.MARGIN, Collateral.ISOLATED, True),
("kraken", TradingMode.SPOT, None, False),
("kraken", TradingMode.MARGIN, Collateral.ISOLATED, True),
("kraken", TradingMode.FUTURES, Collateral.ISOLATED, True),
("ftx", TradingMode.SPOT, None, False),
("ftx", TradingMode.MARGIN, Collateral.ISOLATED, True),
("ftx", TradingMode.FUTURES, Collateral.ISOLATED, True),
("bittrex", TradingMode.SPOT, None, False),
("bittrex", TradingMode.MARGIN, Collateral.CROSS, True),
("bittrex", TradingMode.MARGIN, Collateral.ISOLATED, True),
("bittrex", TradingMode.FUTURES, Collateral.CROSS, True),
("bittrex", TradingMode.FUTURES, Collateral.ISOLATED, True),
# TODO-lev: Remove once implemented
("binance", TradingMode.MARGIN, Collateral.CROSS, True),
("binance", TradingMode.FUTURES, Collateral.CROSS, True),
("binance", TradingMode.FUTURES, Collateral.ISOLATED, True),
("kraken", TradingMode.MARGIN, Collateral.CROSS, True),
("kraken", TradingMode.FUTURES, Collateral.CROSS, True),
("ftx", TradingMode.MARGIN, Collateral.CROSS, True),
("ftx", TradingMode.FUTURES, Collateral.CROSS, True),
# TODO-lev: Uncomment once implemented
# ("binance", TradingMode.MARGIN, Collateral.CROSS, False),
# ("binance", TradingMode.FUTURES, Collateral.CROSS, False),
# ("binance", TradingMode.FUTURES, Collateral.ISOLATED, False),
# ("kraken", TradingMode.MARGIN, Collateral.CROSS, False),
# ("kraken", TradingMode.FUTURES, Collateral.CROSS, False),
# ("ftx", TradingMode.MARGIN, Collateral.CROSS, False),
# ("ftx", TradingMode.FUTURES, Collateral.CROSS, False)
])
def test_validate_trading_mode_and_collateral(
default_conf,
mocker,
exchange_name,
trading_mode,
collateral,
exception_thrown
):
exchange = get_patched_exchange(
mocker, default_conf, id=exchange_name, mock_supported_modes=False)
if (exception_thrown):
with pytest.raises(OperationalException):
exchange.validate_trading_mode_and_collateral(trading_mode, collateral)
else:
exchange.validate_trading_mode_and_collateral(trading_mode, collateral)
2021-10-20 11:33:09 +00:00
@pytest.mark.parametrize("exchange_name,trading_mode,ccxt_config", [
("binance", "spot", {}),
("binance", "margin", {"options": {"defaultType": "margin"}}),
("binance", "futures", {"options": {"defaultType": "future"}}),
("kraken", "spot", {}),
("kraken", "margin", {}),
("kraken", "futures", {}),
("ftx", "spot", {}),
("ftx", "margin", {}),
("ftx", "futures", {}),
("bittrex", "spot", {}),
("bittrex", "margin", {}),
("bittrex", "futures", {}),
])
def test__ccxt_config(
default_conf,
mocker,
exchange_name,
trading_mode,
ccxt_config
):
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = '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),
])
def test_get_max_leverage(default_conf, mocker, pair, nominal_value, max_lev):
# Binance has a different method of getting the max leverage
exchange = get_patched_exchange(mocker, default_conf, id="kraken")
assert exchange.get_max_leverage(pair, nominal_value) == max_lev
2021-09-26 10:11:35 +00:00
def test_get_mark_price():
return
def test_get_funding_fee_dates():
return
def test_calculate_funding_fees():
return
def test__get_funding_rate(default_conf, mocker):
return
def test__get_funding_fee():
return