Merge branch 'develop' into fix/broken_getpairs

This commit is contained in:
Matthias
2020-08-12 20:13:06 +02:00
134 changed files with 4223 additions and 1290 deletions

View File

@@ -5,8 +5,9 @@ import ccxt
import pytest
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
OperationalException)
from tests.conftest import get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers
@pytest.mark.parametrize('limitratio,expected', [
@@ -62,15 +63,9 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(OperationalException, match=r".*DeadBeef.*"):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
def test_stoploss_order_dry_run_binance(default_conf, mocker):

View File

@@ -4,17 +4,18 @@ import copy
import logging
from datetime import datetime, timezone
from random import randint
from unittest.mock import MagicMock, Mock, PropertyMock
from unittest.mock import MagicMock, Mock, PropertyMock, patch
import arrow
import ccxt
import pytest
from pandas import DataFrame
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exceptions import (DDosProtection, DependencyException,
InvalidOrderException, OperationalException,
TemporaryError)
from freqtrade.exchange import Binance, Exchange, Kraken
from freqtrade.exchange.common import API_RETRY_COUNT
from freqtrade.exchange.common import API_RETRY_COUNT, calculate_backoff
from freqtrade.exchange.exchange import (market_is_active,
timeframe_to_minutes,
timeframe_to_msecs,
@@ -25,7 +26,7 @@ from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import get_patched_exchange, log_has, log_has_re
# Make sure to always keep one exchange here which is NOT subclassed!!
EXCHANGES = ['bittrex', 'binance', 'kraken', ]
EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx']
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
@@ -37,12 +38,20 @@ def get_mock_coro(return_value):
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
fun, mock_ccxt_fun, **kwargs):
fun, mock_ccxt_fun, retries=API_RETRY_COUNT + 1, **kwargs):
with patch('freqtrade.exchange.common.time.sleep'):
with pytest.raises(DDosProtection):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DDos"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
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)
getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
@@ -51,12 +60,21 @@ def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs):
async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun,
retries=API_RETRY_COUNT + 1, **kwargs):
with patch('freqtrade.exchange.common.asyncio.sleep', get_mock_coro(None)):
with pytest.raises(DDosProtection):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("Dooh"))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
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)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
@@ -88,15 +106,19 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog):
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 log_has("Applying additional ccxt config: {'aiohttp_trust_env': True}", caplog)
assert not ex._api_async.aiohttp_trust_env
assert hasattr(ex._api, 'TestKWARG')
assert ex._api.TestKWARG == 11
assert not hasattr(ex._api_async, 'TestKWARG')
assert log_has("Applying additional ccxt config: {'TestKWARG': 11}", caplog)
# ccxt_config is assigned to both sync and async
assert not hasattr(ex._api_async, 'TestKWARG44')
assert hasattr(ex._api_async, 'TestKWARG')
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
assert log_has(asynclogmsg, caplog)
def test_destroy(default_conf, mocker, caplog):
@@ -315,7 +337,12 @@ def test_set_sandbox_exception(default_conf, mocker):
def test__load_async_markets(default_conf, mocker, caplog):
exchange = get_patched_exchange(mocker, default_conf)
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
@@ -348,7 +375,7 @@ def test__load_markets(default_conf, mocker, caplog):
assert ex.markets == expected_return
def test__reload_markets(default_conf, mocker, caplog):
def test_reload_markets(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG)
initial_markets = {'ETH/BTC': {}}
@@ -361,23 +388,26 @@ def test__reload_markets(default_conf, mocker, caplog):
default_conf['exchange']['markets_refresh_interval'] = 10
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance",
mock_markets=False)
exchange._load_async_markets = MagicMock()
exchange._last_markets_refresh = arrow.utcnow().timestamp
updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}}
assert exchange.markets == initial_markets
# less than 10 minutes have passed, no reload
exchange._reload_markets()
exchange.reload_markets()
assert exchange.markets == initial_markets
assert exchange._load_async_markets.call_count == 0
# more than 10 minutes have passed, reload is executed
exchange._last_markets_refresh = arrow.utcnow().timestamp - 15 * 60
exchange._reload_markets()
exchange.reload_markets()
assert exchange.markets == updated_markets
assert exchange._load_async_markets.call_count == 1
assert log_has('Performing scheduled market reload..', caplog)
def test__reload_markets_exception(default_conf, mocker, caplog):
def test_reload_markets_exception(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG)
api_mock = MagicMock()
@@ -386,7 +416,7 @@ def test__reload_markets_exception(default_conf, mocker, caplog):
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
# less than 10 minutes have passed, no reload
exchange._reload_markets()
exchange.reload_markets()
assert exchange._last_markets_refresh == 0
assert log_has_re(r"Could not reload markets.*", caplog)
@@ -574,7 +604,7 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog):
('5m'), ("1m"), ("15m"), ("1h")
])
def test_validate_timeframes(default_conf, mocker, timeframe):
default_conf["ticker_interval"] = timeframe
default_conf["timeframe"] = timeframe
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
@@ -592,7 +622,7 @@ def test_validate_timeframes(default_conf, mocker, timeframe):
def test_validate_timeframes_failed(default_conf, mocker):
default_conf["ticker_interval"] = "3m"
default_conf["timeframe"] = "3m"
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
@@ -609,7 +639,7 @@ def test_validate_timeframes_failed(default_conf, mocker):
with pytest.raises(OperationalException,
match=r"Invalid timeframe '3m'. This exchange supports.*"):
Exchange(default_conf)
default_conf["ticker_interval"] = "15s"
default_conf["timeframe"] = "15s"
with pytest.raises(OperationalException,
match=r"Timeframes < 1m are currently not supported by Freqtrade."):
@@ -617,7 +647,7 @@ def test_validate_timeframes_failed(default_conf, mocker):
def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
default_conf["ticker_interval"] = "3m"
default_conf["timeframe"] = "3m"
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
@@ -637,7 +667,7 @@ def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
default_conf["ticker_interval"] = "3m"
default_conf["timeframe"] = "3m"
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
@@ -658,7 +688,7 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
def test_validate_timeframes_not_in_config(default_conf, mocker):
del default_conf["ticker_interval"]
del default_conf["timeframe"]
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
@@ -685,13 +715,13 @@ def test_validate_order_types(default_conf, mocker):
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})
@@ -701,9 +731,8 @@ def test_validate_order_types(default_conf, mocker):
'buy': 'limit',
'sell': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': 'false'
'stoploss_on_exchange': False
}
with pytest.raises(OperationalException,
match=r'Exchange .* does not support market orders.'):
Exchange(default_conf)
@@ -714,7 +743,6 @@ def test_validate_order_types(default_conf, mocker):
'stoploss': 'limit',
'stoploss_on_exchange': True
}
with pytest.raises(OperationalException,
match=r'On exchange stoploss is not supported for .*'):
Exchange(default_conf)
@@ -1115,9 +1143,10 @@ def test_get_balance_prod(default_conf, mocker, exchange_name):
exchange.get_balance(currency='BTC')
def test_get_balances_dry_run(default_conf, mocker):
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_balances_dry_run(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert exchange.get_balances() == {}
@@ -1254,7 +1283,8 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name):
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
# one_call calculation * 1.8 should do 2 calls
since = 5 * 60 * 500 * 1.8
since = 5 * 60 * exchange._ft_has['ohlcv_candle_limit'] * 1.8
ret = exchange.get_historic_ohlcv(pair, "5m", int((arrow.utcnow().timestamp - since) * 1000))
assert exchange._async_get_candle_history.call_count == 2
@@ -1346,7 +1376,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
# exchange = Exchange(default_conf)
await async_ccxt_exception(mocker, default_conf, MagicMock(),
"_async_get_candle_history", "fetch_ohlcv",
pair='ABCD/BTC', timeframe=default_conf['ticker_interval'])
pair='ABCD/BTC', timeframe=default_conf['timeframe'])
api_mock = MagicMock()
with pytest.raises(OperationalException,
@@ -1476,7 +1506,7 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
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['ticker_interval'])
res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe'])
assert res[0] == 'ETH/BTC'
res_ohlcv = res[2]
@@ -1513,9 +1543,9 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
# 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['ticker_interval'])
res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe'])
assert res[0] == 'ETH/BTC'
assert res[1] == default_conf['ticker_interval']
assert res[1] == default_conf['timeframe']
res_ohlcv = res[2]
# Sorted not called again - data is already in order
assert sort_mock.call_count == 0
@@ -1729,6 +1759,7 @@ 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)
assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {}
assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {}
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@@ -1788,7 +1819,7 @@ def test_cancel_order_with_result_error(default_conf, mocker, exchange_name, cap
res = exchange.cancel_order_with_result('1234', 'ETH/BTC', 1541)
assert isinstance(res, dict)
assert log_has("Could not cancel order 1234.", caplog)
assert log_has("Could not cancel order 1234 for ETH/BTC.", caplog)
assert log_has("Could not fetch cancelled order 1234.", caplog)
assert res['amount'] == 1541
@@ -1814,31 +1845,95 @@ def test_cancel_order(default_conf, mocker, exchange_name):
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_order(default_conf, mocker, exchange_name):
def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.cancel_order = MagicMock(return_value=123)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.cancel_stoploss_order(order_id='_', pair='TKN/BTC') == 123
with pytest.raises(InvalidOrderException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.cancel_stoploss_order(order_id='_', pair='TKN/BTC')
assert api_mock.cancel_order.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
"cancel_stoploss_order", "cancel_order",
order_id='_', pair='TKN/BTC')
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_order(default_conf, mocker, exchange_name):
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.get_order('X', 'TKN/BTC').myid == 123
assert exchange.fetch_order('X', 'TKN/BTC').myid == 123
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
exchange.get_order('Y', 'TKN/BTC')
exchange.fetch_order('Y', 'TKN/BTC')
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.get_order('X', 'TKN/BTC') == 456
assert exchange.fetch_order('X', 'TKN/BTC') == 456
with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_order(order_id='_', pair='TKN/BTC')
exchange.fetch_order(order_id='_', pair='TKN/BTC')
assert api_mock.fetch_order.call_count == 1
api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
with patch('freqtrade.exchange.common.time.sleep') as tm:
with pytest.raises(InvalidOrderException):
exchange.fetch_order(order_id='_', pair='TKN/BTC')
# Ensure backoff is called
assert tm.call_args_list[0][0][0] == 1
assert tm.call_args_list[1][0][0] == 2
assert tm.call_args_list[2][0][0] == 5
assert tm.call_args_list[3][0][0] == 10
assert api_mock.fetch_order.call_count == 6
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'fetch_order', 'fetch_order', retries=6,
order_id='_', pair='TKN/BTC')
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
# Don't test FTX here - that needs a seperate test
if exchange_name == 'ftx':
return
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
exchange._dry_run_open_orders['X'] = order
assert exchange.fetch_stoploss_order('X', 'TKN/BTC').myid == 123
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
exchange.fetch_stoploss_order('Y', 'TKN/BTC')
default_conf['dry_run'] = False
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
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')
assert api_mock.fetch_order.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'get_order', 'fetch_order',
'fetch_stoploss_order', 'fetch_order',
retries=6,
order_id='_', pair='TKN/BTC')
@@ -2046,6 +2141,13 @@ def test_get_markets(default_conf, mocker, markets,
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)
def test_timeframe_to_minutes():
assert timeframe_to_minutes("5m") == 5
assert timeframe_to_minutes("10m") == 10
@@ -2205,15 +2307,45 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
'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),
# TODO: More tests here!
# 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),
])
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
@pytest.mark.parametrize('retrycount,max_retries,expected', [
(0, 3, 10),
(1, 3, 5),
(2, 3, 2),
(3, 3, 1),
(0, 1, 2),
(1, 1, 1),
(0, 4, 17),
(1, 4, 10),
(2, 4, 5),
(3, 4, 2),
(4, 4, 1),
(0, 5, 26),
(1, 5, 17),
(2, 5, 10),
(3, 5, 5),
(4, 5, 2),
(5, 5, 1),
])
def test_calculate_backoff(retrycount, max_retries, expected):
assert calculate_backoff(retrycount, max_retries) == expected

158
tests/exchange/test_ftx.py Normal file
View File

@@ -0,0 +1,158 @@
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access
from random import randint
from unittest.mock import MagicMock
import ccxt
import pytest
from freqtrade.exceptions import DependencyException, InvalidOrderException
from tests.conftest import get_patched_exchange
from .test_exchange import ccxt_exceptionhandlers
STOPLOSS_ORDERTYPE = 'stop'
def test_stoploss_order_ftx(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
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)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
# stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 190
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
assert api_mock.create_order.call_count == 1
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={'stoploss': 'limit'})
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
assert 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params']
assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == 217.8
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
def test_stoploss_order_dry_run_ftx(default_conf, mocker):
api_mock = MagicMock()
default_conf['dry_run'] = True
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)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
assert 'id' in order
assert 'info' in order
assert 'type' in order
assert order['type'] == STOPLOSS_ORDERTYPE
assert order['price'] == 220
assert order['amount'] == 1
def test_stoploss_adjust_ftx(mocker, default_conf):
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
order = {
'type': STOPLOSS_ORDERTYPE,
'price': 1500,
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
# Test with invalid order case ...
order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(1501, order)
def test_fetch_stoploss_order(default_conf, mocker):
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
exchange._dry_run_open_orders['X'] = order
assert exchange.fetch_stoploss_order('X', 'TKN/BTC').myid == 123
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
exchange.fetch_stoploss_order('Y', 'TKN/BTC')
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': '456'}])
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
assert exchange.fetch_stoploss_order('X', 'TKN/BTC')['status'] == '456'
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'Y', 'status': '456'}])
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"):
exchange.fetch_stoploss_order('X', 'TKN/BTC')['status']
with pytest.raises(InvalidOrderException):
api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
exchange.fetch_stoploss_order(order_id='_', pair='TKN/BTC')
assert api_mock.fetch_orders.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'ftx',
'fetch_stoploss_order', 'fetch_orders',
retries=6,
order_id='_', pair='TKN/BTC')

View File

@@ -6,11 +6,12 @@ from unittest.mock import MagicMock
import ccxt
import pytest
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exceptions import DependencyException, InvalidOrderException
from tests.conftest import get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers
STOPLOSS_ORDERTYPE = 'stop-loss'
def test_buy_kraken_trading_agreement(default_conf, mocker):
api_mock = MagicMock()
@@ -159,7 +160,6 @@ def test_get_balances_prod(default_conf, mocker):
def test_stoploss_order_kraken(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'stop-loss'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
@@ -187,7 +187,7 @@ def test_stoploss_order_kraken(default_conf, mocker):
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == order_type
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
@@ -205,20 +205,13 @@ def test_stoploss_order_kraken(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(OperationalException, match=r".*DeadBeef.*"):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
def test_stoploss_order_dry_run_kraken(default_conf, mocker):
api_mock = MagicMock()
order_type = 'stop-loss'
default_conf['dry_run'] = True
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)
@@ -233,7 +226,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker):
assert 'info' in order
assert 'type' in order
assert order['type'] == order_type
assert order['type'] == STOPLOSS_ORDERTYPE
assert order['price'] == 220
assert order['amount'] == 1
@@ -241,7 +234,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker):
def test_stoploss_adjust_kraken(mocker, default_conf):
exchange = get_patched_exchange(mocker, default_conf, id='kraken')
order = {
'type': 'stop-loss',
'type': STOPLOSS_ORDERTYPE,
'price': 1500,
}
assert exchange.stoploss_adjust(1501, order)