Add tests for exchange functions that comply with ccxt
This commit is contained in:
parent
d387b68b85
commit
800c327b12
@ -73,11 +73,11 @@ def default_conf():
|
|||||||
"key": "key",
|
"key": "key",
|
||||||
"secret": "secret",
|
"secret": "secret",
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"BTC_ETH",
|
"ETH/BTC",
|
||||||
"BTC_TKN",
|
"TKN/BTC",
|
||||||
"BTC_TRST",
|
"TRST/BTC",
|
||||||
"BTC_SWT",
|
"SWT/BTC",
|
||||||
"BTC_BCC"
|
"BCC/BTC"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"telegram": {
|
"telegram": {
|
||||||
@ -160,13 +160,14 @@ def health():
|
|||||||
def limit_buy_order():
|
def limit_buy_order():
|
||||||
return {
|
return {
|
||||||
'id': 'mocked_limit_buy',
|
'id': 'mocked_limit_buy',
|
||||||
'type': 'LIMIT_BUY',
|
'type': 'limit',
|
||||||
|
'side': 'buy',
|
||||||
'pair': 'mocked',
|
'pair': 'mocked',
|
||||||
'opened': str(arrow.utcnow().datetime),
|
'datetime': arrow.utcnow().isoformat(),
|
||||||
'rate': 0.00001099,
|
'price': 0.00001099,
|
||||||
'amount': 90.99181073,
|
'amount': 90.99181073,
|
||||||
'remaining': 0.0,
|
'remaining': 0.0,
|
||||||
'closed': str(arrow.utcnow().datetime),
|
'status': 'closed'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -174,12 +175,14 @@ def limit_buy_order():
|
|||||||
def limit_buy_order_old():
|
def limit_buy_order_old():
|
||||||
return {
|
return {
|
||||||
'id': 'mocked_limit_buy_old',
|
'id': 'mocked_limit_buy_old',
|
||||||
'type': 'LIMIT_BUY',
|
'type': 'limit',
|
||||||
'pair': 'BTC_ETH',
|
'side': 'buy',
|
||||||
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
|
'pair': 'mocked',
|
||||||
'rate': 0.00001099,
|
'datetime': str(arrow.utcnow().shift(minutes=-601).datetime),
|
||||||
|
'price': 0.00001099,
|
||||||
'amount': 90.99181073,
|
'amount': 90.99181073,
|
||||||
'remaining': 90.99181073,
|
'remaining': 90.99181073,
|
||||||
|
'status': 'open'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -187,12 +190,14 @@ def limit_buy_order_old():
|
|||||||
def limit_sell_order_old():
|
def limit_sell_order_old():
|
||||||
return {
|
return {
|
||||||
'id': 'mocked_limit_sell_old',
|
'id': 'mocked_limit_sell_old',
|
||||||
'type': 'LIMIT_SELL',
|
'type': 'limit',
|
||||||
'pair': 'BTC_ETH',
|
'side': 'sell',
|
||||||
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
|
'pair': 'ETH/BTC',
|
||||||
'rate': 0.00001099,
|
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
||||||
|
'price': 0.00001099,
|
||||||
'amount': 90.99181073,
|
'amount': 90.99181073,
|
||||||
'remaining': 90.99181073,
|
'remaining': 90.99181073,
|
||||||
|
'status': 'open'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -200,12 +205,14 @@ def limit_sell_order_old():
|
|||||||
def limit_buy_order_old_partial():
|
def limit_buy_order_old_partial():
|
||||||
return {
|
return {
|
||||||
'id': 'mocked_limit_buy_old_partial',
|
'id': 'mocked_limit_buy_old_partial',
|
||||||
'type': 'LIMIT_BUY',
|
'type': 'limit',
|
||||||
'pair': 'BTC_ETH',
|
'side': 'buy',
|
||||||
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
|
'pair': 'ETH/BTC',
|
||||||
'rate': 0.00001099,
|
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
||||||
|
'price': 0.00001099,
|
||||||
'amount': 90.99181073,
|
'amount': 90.99181073,
|
||||||
'remaining': 67.99181073,
|
'remaining': 67.99181073,
|
||||||
|
'status': 'open'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -213,16 +220,47 @@ def limit_buy_order_old_partial():
|
|||||||
def limit_sell_order():
|
def limit_sell_order():
|
||||||
return {
|
return {
|
||||||
'id': 'mocked_limit_sell',
|
'id': 'mocked_limit_sell',
|
||||||
'type': 'LIMIT_SELL',
|
'type': 'limit',
|
||||||
|
'side': 'sell',
|
||||||
'pair': 'mocked',
|
'pair': 'mocked',
|
||||||
'opened': str(arrow.utcnow().datetime),
|
'datetime': arrow.utcnow().isoformat(),
|
||||||
'rate': 0.00001173,
|
'price': 0.00001173,
|
||||||
'amount': 90.99181073,
|
'amount': 90.99181073,
|
||||||
'remaining': 0.0,
|
'remaining': 0.0,
|
||||||
'closed': str(arrow.utcnow().datetime),
|
'status': 'closed'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ticker_history_api():
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
1511686200000, # unix timestamp ms
|
||||||
|
8.794e-05, # open
|
||||||
|
8.948e-05, # high
|
||||||
|
8.794e-05, # low
|
||||||
|
8.88e-05, # close
|
||||||
|
0.0877869, # volume (in quote currency)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1511686500000,
|
||||||
|
8.88e-05,
|
||||||
|
8.942e-05,
|
||||||
|
8.88e-05,
|
||||||
|
8.893e-05,
|
||||||
|
0.05874751,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1511686800,
|
||||||
|
8.891e-05,
|
||||||
|
8.893e-05,
|
||||||
|
8.875e-05,
|
||||||
|
8.877e-05,
|
||||||
|
0.7039405
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def ticker_history():
|
def ticker_history():
|
||||||
return [
|
return [
|
||||||
@ -299,133 +337,3 @@ def result():
|
|||||||
# return the open-order-id
|
# return the open-order-id
|
||||||
# See tests in rpc/main that could use this
|
# See tests in rpc/main that could use this
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def get_market_summaries_data():
|
|
||||||
"""
|
|
||||||
This fixture is a real result from exchange.get_market_summaries() but reduced to only
|
|
||||||
8 entries. 4 BTC, 4 USTD
|
|
||||||
:return: JSON market summaries
|
|
||||||
"""
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'Ask': 1.316e-05,
|
|
||||||
'BaseVolume': 5.72599471,
|
|
||||||
'Bid': 1.3e-05,
|
|
||||||
'Created': '2014-04-14T00:00:00',
|
|
||||||
'High': 1.414e-05,
|
|
||||||
'Last': 1.298e-05,
|
|
||||||
'Low': 1.282e-05,
|
|
||||||
'MarketName': 'BTC-XWC',
|
|
||||||
'OpenBuyOrders': 2000,
|
|
||||||
'OpenSellOrders': 1484,
|
|
||||||
'PrevDay': 1.376e-05,
|
|
||||||
'TimeStamp': '2018-02-05T01:32:40.493',
|
|
||||||
'Volume': 424041.21418375
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Ask': 0.00627051,
|
|
||||||
'BaseVolume': 93.23302388,
|
|
||||||
'Bid': 0.00618192,
|
|
||||||
'Created': '2016-10-20T04:48:30.387',
|
|
||||||
'High': 0.00669897,
|
|
||||||
'Last': 0.00618192,
|
|
||||||
'Low': 0.006,
|
|
||||||
'MarketName': 'BTC-XZC',
|
|
||||||
'OpenBuyOrders': 343,
|
|
||||||
'OpenSellOrders': 2037,
|
|
||||||
'PrevDay': 0.00668229,
|
|
||||||
'TimeStamp': '2018-02-05T01:32:43.383',
|
|
||||||
'Volume': 14863.60730702
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Ask': 0.01137247,
|
|
||||||
'BaseVolume': 383.55922657,
|
|
||||||
'Bid': 0.01136006,
|
|
||||||
'Created': '2016-11-15T20:29:59.73',
|
|
||||||
'High': 0.012,
|
|
||||||
'Last': 0.01137247,
|
|
||||||
'Low': 0.01119883,
|
|
||||||
'MarketName': 'BTC-ZCL',
|
|
||||||
'OpenBuyOrders': 1332,
|
|
||||||
'OpenSellOrders': 5317,
|
|
||||||
'PrevDay': 0.01179603,
|
|
||||||
'TimeStamp': '2018-02-05T01:32:42.773',
|
|
||||||
'Volume': 33308.07358285
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Ask': 0.04155821,
|
|
||||||
'BaseVolume': 274.75369074,
|
|
||||||
'Bid': 0.04130002,
|
|
||||||
'Created': '2016-10-28T17:13:10.833',
|
|
||||||
'High': 0.04354429,
|
|
||||||
'Last': 0.041585,
|
|
||||||
'Low': 0.0413,
|
|
||||||
'MarketName': 'BTC-ZEC',
|
|
||||||
'OpenBuyOrders': 863,
|
|
||||||
'OpenSellOrders': 5579,
|
|
||||||
'PrevDay': 0.0429,
|
|
||||||
'TimeStamp': '2018-02-05T01:32:43.21',
|
|
||||||
'Volume': 6479.84033259
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Ask': 210.99999999,
|
|
||||||
'BaseVolume': 615132.70989532,
|
|
||||||
'Bid': 210.05503736,
|
|
||||||
'Created': '2017-07-21T01:08:49.397',
|
|
||||||
'High': 257.396,
|
|
||||||
'Last': 211.0,
|
|
||||||
'Low': 209.05333589,
|
|
||||||
'MarketName': 'USDT-XMR',
|
|
||||||
'OpenBuyOrders': 180,
|
|
||||||
'OpenSellOrders': 1203,
|
|
||||||
'PrevDay': 247.93528899,
|
|
||||||
'TimeStamp': '2018-02-05T01:32:43.117',
|
|
||||||
'Volume': 2688.17410793
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Ask': 0.79589979,
|
|
||||||
'BaseVolume': 9349557.01853031,
|
|
||||||
'Bid': 0.789226,
|
|
||||||
'Created': '2017-07-14T17:10:10.737',
|
|
||||||
'High': 0.977,
|
|
||||||
'Last': 0.79589979,
|
|
||||||
'Low': 0.781,
|
|
||||||
'MarketName': 'USDT-XRP',
|
|
||||||
'OpenBuyOrders': 1075,
|
|
||||||
'OpenSellOrders': 6508,
|
|
||||||
'PrevDay': 0.93300218,
|
|
||||||
'TimeStamp': '2018-02-05T01:32:42.383',
|
|
||||||
'Volume': 10801663.00788851
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Ask': 0.05154982,
|
|
||||||
'BaseVolume': 2311087.71232136,
|
|
||||||
'Bid': 0.05040107,
|
|
||||||
'Created': '2017-12-29T19:29:18.357',
|
|
||||||
'High': 0.06668561,
|
|
||||||
'Last': 0.0508,
|
|
||||||
'Low': 0.05006731,
|
|
||||||
'MarketName': 'USDT-XVG',
|
|
||||||
'OpenBuyOrders': 655,
|
|
||||||
'OpenSellOrders': 5544,
|
|
||||||
'PrevDay': 0.0627,
|
|
||||||
'TimeStamp': '2018-02-05T01:32:41.507',
|
|
||||||
'Volume': 40031424.2152716
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'Ask': 332.65500022,
|
|
||||||
'BaseVolume': 562911.87455665,
|
|
||||||
'Bid': 330.00000001,
|
|
||||||
'Created': '2017-07-14T17:10:10.673',
|
|
||||||
'High': 401.59999999,
|
|
||||||
'Last': 332.65500019,
|
|
||||||
'Low': 330.0,
|
|
||||||
'MarketName': 'USDT-ZEC',
|
|
||||||
'OpenBuyOrders': 161,
|
|
||||||
'OpenSellOrders': 1731,
|
|
||||||
'PrevDay': 391.42,
|
|
||||||
'TimeStamp': '2018-02-05T01:32:42.947',
|
|
||||||
'Volume': 1571.09647946
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
@ -2,15 +2,15 @@
|
|||||||
# pragma pylint: disable=protected-access
|
# pragma pylint: disable=protected-access
|
||||||
import logging
|
import logging
|
||||||
from random import randint
|
from random import randint
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
|
import ccxt
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from requests.exceptions import RequestException
|
|
||||||
|
|
||||||
import freqtrade.exchange as exchange
|
from freqtrade import OperationalException, DependencyException, NetworkException
|
||||||
from freqtrade import OperationalException
|
|
||||||
from freqtrade.exchange import init, validate_pairs, buy, sell, get_balance, get_balances, \
|
from freqtrade.exchange import init, validate_pairs, buy, sell, get_balance, get_balances, \
|
||||||
get_ticker, get_ticker_history, cancel_order, get_name, get_fee
|
get_ticker, get_ticker_history, cancel_order, get_name, get_fee
|
||||||
|
import freqtrade.exchange as exchange
|
||||||
from freqtrade.tests.conftest import log_has
|
from freqtrade.tests.conftest import log_has
|
||||||
|
|
||||||
API_INIT = False
|
API_INIT = False
|
||||||
@ -42,9 +42,12 @@ def test_init_exception(default_conf):
|
|||||||
|
|
||||||
def test_validate_pairs(default_conf, mocker):
|
def test_validate_pairs(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_markets = MagicMock(return_value=[
|
api_mock.load_markets = MagicMock(return_value={
|
||||||
'BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT', 'BTC_BCC',
|
'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': ''
|
||||||
])
|
})
|
||||||
|
id_mock = PropertyMock(return_value='test_exchange')
|
||||||
|
type(api_mock).id = id_mock
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||||
@ -52,7 +55,7 @@ def test_validate_pairs(default_conf, mocker):
|
|||||||
|
|
||||||
def test_validate_pairs_not_available(default_conf, mocker):
|
def test_validate_pairs_not_available(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_markets = MagicMock(return_value=[])
|
api_mock.load_markets = MagicMock(return_value={})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
with pytest.raises(OperationalException, match=r'not available'):
|
with pytest.raises(OperationalException, match=r'not available'):
|
||||||
@ -61,8 +64,9 @@ def test_validate_pairs_not_available(default_conf, mocker):
|
|||||||
|
|
||||||
def test_validate_pairs_not_compatible(default_conf, mocker):
|
def test_validate_pairs_not_compatible(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_markets = MagicMock(
|
api_mock.load_markets = MagicMock(return_value={
|
||||||
return_value=['BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT'])
|
'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': ''
|
||||||
|
})
|
||||||
default_conf['stake_currency'] = 'ETH'
|
default_conf['stake_currency'] = 'ETH'
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
@ -73,10 +77,9 @@ def test_validate_pairs_not_compatible(default_conf, mocker):
|
|||||||
def test_validate_pairs_exception(default_conf, mocker, caplog):
|
def test_validate_pairs_exception(default_conf, mocker, caplog):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_markets = MagicMock(side_effect=RequestException())
|
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError())
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
# with pytest.raises(RequestException, match=r'Unable to validate pairs'):
|
|
||||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||||
assert log_has('Unable to validate pairs (assuming they are correct). Reason: ',
|
assert log_has('Unable to validate pairs (assuming they are correct). Reason: ',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
@ -86,38 +89,100 @@ def test_buy_dry_run(default_conf, mocker):
|
|||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert 'dry_run_buy_' in buy(pair='BTC_ETH', rate=200, amount=1)
|
order = buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
assert 'id' in order
|
||||||
|
assert 'dry_run_buy_' in order['id']
|
||||||
|
|
||||||
|
|
||||||
def test_buy_prod(default_conf, mocker):
|
def test_buy_prod(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.buy = MagicMock(
|
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||||
return_value='dry_run_buy_{}'.format(randint(0, 10**6)))
|
api_mock.create_limit_buy_order = MagicMock(return_value={
|
||||||
|
'id': order_id,
|
||||||
|
'info': {
|
||||||
|
'foo': 'bar'
|
||||||
|
}
|
||||||
|
})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert 'dry_run_buy_' in buy(pair='BTC_ETH', rate=200, amount=1)
|
order = buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
assert 'id' in order
|
||||||
|
assert 'info' in order
|
||||||
|
assert order['id'] == order_id
|
||||||
|
|
||||||
|
# test exception handling
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
|
||||||
def test_sell_dry_run(default_conf, mocker):
|
def test_sell_dry_run(default_conf, mocker):
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert 'dry_run_sell_' in sell(pair='BTC_ETH', rate=200, amount=1)
|
order = sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
assert 'id' in order
|
||||||
|
assert 'dry_run_sell_' in order['id']
|
||||||
|
|
||||||
|
|
||||||
def test_sell_prod(default_conf, mocker):
|
def test_sell_prod(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.sell = MagicMock(
|
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
|
||||||
return_value='dry_run_sell_{}'.format(randint(0, 10**6)))
|
api_mock.create_limit_sell_order = MagicMock(return_value={
|
||||||
|
'id': order_id,
|
||||||
|
'info': {
|
||||||
|
'foo': 'bar'
|
||||||
|
}
|
||||||
|
})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert 'dry_run_sell_' in sell(pair='BTC_ETH', rate=200, amount=1)
|
order = sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
assert 'id' in order
|
||||||
|
assert 'info' in order
|
||||||
|
assert order['id'] == order_id
|
||||||
|
|
||||||
|
# test exception handling
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
|
||||||
def test_get_balance_dry_run(default_conf, mocker):
|
def test_get_balance_dry_run(default_conf, mocker):
|
||||||
@ -129,7 +194,7 @@ def test_get_balance_dry_run(default_conf, mocker):
|
|||||||
|
|
||||||
def test_get_balance_prod(default_conf, mocker):
|
def test_get_balance_prod(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_balance = MagicMock(return_value=123.4)
|
api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4}})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
@ -137,36 +202,51 @@ def test_get_balance_prod(default_conf, mocker):
|
|||||||
|
|
||||||
assert get_balance(currency='BTC') == 123.4
|
assert get_balance(currency='BTC') == 123.4
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
get_balance(currency='BTC')
|
||||||
|
|
||||||
|
|
||||||
def test_get_balances_dry_run(default_conf, mocker):
|
def test_get_balances_dry_run(default_conf, mocker):
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert get_balances() == []
|
assert get_balances() == {}
|
||||||
|
|
||||||
|
|
||||||
def test_get_balances_prod(default_conf, mocker):
|
def test_get_balances_prod(default_conf, mocker):
|
||||||
balance_item = {
|
balance_item = {
|
||||||
'Currency': '1ST',
|
'free': 10.0,
|
||||||
'Balance': 10.0,
|
'total': 10.0,
|
||||||
'Available': 10.0,
|
'used': 0.0
|
||||||
'Pending': 0.0,
|
|
||||||
'CryptoAddress': None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_balances = MagicMock(
|
api_mock.fetch_balance = MagicMock(return_value={
|
||||||
return_value=[balance_item, balance_item, balance_item])
|
'1ST': balance_item,
|
||||||
|
'2ST': balance_item,
|
||||||
|
'3ST': balance_item
|
||||||
|
})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert len(get_balances()) == 3
|
assert len(get_balances()) == 3
|
||||||
assert get_balances()[0]['Currency'] == '1ST'
|
assert get_balances()['1ST']['free'] == 10.0
|
||||||
assert get_balances()[0]['Balance'] == 10.0
|
assert get_balances()['1ST']['total'] == 10.0
|
||||||
assert get_balances()[0]['Available'] == 10.0
|
assert get_balances()['1ST']['used'] == 0.0
|
||||||
assert get_balances()[0]['Pending'] == 0.0
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
get_balances()
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
get_balances()
|
||||||
|
|
||||||
|
|
||||||
# This test is somewhat redundant with
|
# This test is somewhat redundant with
|
||||||
@ -174,57 +254,135 @@ def test_get_balances_prod(default_conf, mocker):
|
|||||||
def test_get_ticker(default_conf, mocker):
|
def test_get_ticker(default_conf, mocker):
|
||||||
maybe_init_api(default_conf, mocker)
|
maybe_init_api(default_conf, mocker)
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
tick = {"success": True, 'result': {'Bid': 0.00001098, 'Ask': 0.00001099, 'Last': 0.0001}}
|
tick = {
|
||||||
api_mock.get_ticker = MagicMock(return_value=tick)
|
'symbol': 'ETH/BTC',
|
||||||
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
|
'bid': 0.00001098,
|
||||||
|
'ask': 0.00001099,
|
||||||
|
'last': 0.0001,
|
||||||
|
}
|
||||||
|
api_mock.fetch_ticker = MagicMock(return_value=tick)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
# retrieve original ticker
|
# retrieve original ticker
|
||||||
ticker = get_ticker(pair='BTC_ETH')
|
ticker = get_ticker(pair='ETH/BTC')
|
||||||
assert ticker['bid'] == 0.00001098
|
assert ticker['bid'] == 0.00001098
|
||||||
assert ticker['ask'] == 0.00001099
|
assert ticker['ask'] == 0.00001099
|
||||||
|
|
||||||
# change the ticker
|
# change the ticker
|
||||||
tick = {"success": True, 'result': {"Bid": 0.5, "Ask": 1, "Last": 42}}
|
tick = {
|
||||||
api_mock.get_ticker = MagicMock(return_value=tick)
|
'symbol': 'ETH/BTC',
|
||||||
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
|
'bid': 0.5,
|
||||||
|
'ask': 1,
|
||||||
|
'last': 42,
|
||||||
|
}
|
||||||
|
api_mock.fetch_ticker = MagicMock(return_value=tick)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
# if not caching the result we should get the same ticker
|
# if not caching the result we should get the same ticker
|
||||||
# if not fetching a new result we should get the cached ticker
|
# if not fetching a new result we should get the cached ticker
|
||||||
ticker = get_ticker(pair='BTC_ETH', refresh=False)
|
ticker = get_ticker(pair='ETH/BTC', refresh=False)
|
||||||
assert ticker['bid'] == 0.00001098
|
assert ticker['bid'] == 0.00001098
|
||||||
assert ticker['ask'] == 0.00001099
|
assert ticker['ask'] == 0.00001099
|
||||||
|
|
||||||
# force ticker refresh
|
# force ticker refresh
|
||||||
ticker = get_ticker(pair='BTC_ETH', refresh=True)
|
ticker = get_ticker(pair='ETH/BTC', refresh=True)
|
||||||
assert ticker['bid'] == 0.5
|
assert ticker['bid'] == 0.5
|
||||||
assert ticker['ask'] == 1
|
assert ticker['ask'] == 1
|
||||||
|
|
||||||
|
# change the ticker to a different pair which should not be cached
|
||||||
|
tick = {
|
||||||
|
'symbol': 'LTC/BTC',
|
||||||
|
'bid': 2,
|
||||||
|
'ask': 3,
|
||||||
|
'last': 4,
|
||||||
|
}
|
||||||
|
api_mock.fetch_ticker = MagicMock(return_value=tick, refresh=False)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
ticker = get_ticker(pair='LTC/BTC', refresh=False)
|
||||||
|
assert ticker['bid'] == 2
|
||||||
|
assert ticker['ask'] == 3
|
||||||
|
|
||||||
|
# check that ETH/BTC is still cached
|
||||||
|
ticker = get_ticker(pair='ETH/BTC', refresh=False)
|
||||||
|
assert ticker['bid'] == 0.5
|
||||||
|
assert ticker['ask'] == 1
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
get_ticker(pair='ETH/BTC', refresh=True)
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
get_ticker(pair='ETH/BTC', refresh=True)
|
||||||
|
|
||||||
|
|
||||||
def test_get_ticker_history(default_conf, mocker):
|
def test_get_ticker_history(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
tick = 123
|
tick = [
|
||||||
api_mock.get_ticker_history = MagicMock(return_value=tick)
|
[
|
||||||
|
1511686200000, # unix timestamp ms
|
||||||
|
1, # open
|
||||||
|
2, # high
|
||||||
|
3, # low
|
||||||
|
4, # close
|
||||||
|
5, # volume (in quote currency)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
has = PropertyMock(return_value={'fetchOHLCV': True})
|
||||||
|
type(api_mock).has = has
|
||||||
|
api_mock.fetch_ohlcv = MagicMock(return_value=tick)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
# retrieve original ticker
|
# retrieve original ticker
|
||||||
ticks = get_ticker_history('BTC_ETH', int(default_conf['ticker_interval']))
|
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
|
||||||
assert ticks == 123
|
assert ticks[0]['O'] == 1
|
||||||
|
assert ticks[0]['H'] == 2
|
||||||
|
assert ticks[0]['L'] == 3
|
||||||
|
assert ticks[0]['C'] == 4
|
||||||
|
assert ticks[0]['V'] == 5
|
||||||
|
|
||||||
# change the ticker
|
# change the ticker
|
||||||
tick = 999
|
new_tick = [
|
||||||
api_mock.get_ticker_history = MagicMock(return_value=tick)
|
[
|
||||||
|
1511686200000, # unix timestamp ms
|
||||||
|
6, # open
|
||||||
|
7, # high
|
||||||
|
8, # low
|
||||||
|
9, # close
|
||||||
|
10, # volume (in quote currency)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
api_mock.get_ticker_history = MagicMock(return_value=new_tick)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
# ensure caching will still return the original ticker
|
# ensure caching will still return the original ticker
|
||||||
ticks = get_ticker_history('BTC_ETH', int(default_conf['ticker_interval']))
|
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
|
||||||
assert ticks == 123
|
assert ticks[0]['O'] == 1
|
||||||
|
assert ticks[0]['H'] == 2
|
||||||
|
assert ticks[0]['L'] == 3
|
||||||
|
assert ticks[0]['C'] == 4
|
||||||
|
assert ticks[0]['V'] == 5
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
# new symbol to get around cache
|
||||||
|
get_ticker_history('ABCD/BTC', default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
# new symbol to get around cache
|
||||||
|
get_ticker_history('EFGH/BTC', default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
|
||||||
def test_cancel_order_dry_run(default_conf, mocker):
|
def test_cancel_order_dry_run(default_conf, mocker):
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert cancel_order(order_id='123') is None
|
assert cancel_order(order_id='123', pair='TKN/BTC') is None
|
||||||
|
|
||||||
|
|
||||||
# Ensure that if not dry_run, we should call API
|
# Ensure that if not dry_run, we should call API
|
||||||
@ -234,7 +392,22 @@ def test_cancel_order(default_conf, mocker):
|
|||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.cancel_order = MagicMock(return_value=123)
|
api_mock.cancel_order = MagicMock(return_value=123)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
assert cancel_order(order_id='_') == 123
|
assert cancel_order(order_id='_', pair='TKN/BTC') == 123
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
cancel_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
cancel_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.cancel_order = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
cancel_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
|
||||||
def test_get_order(default_conf, mocker):
|
def test_get_order(default_conf, mocker):
|
||||||
@ -243,44 +416,48 @@ def test_get_order(default_conf, mocker):
|
|||||||
order = MagicMock()
|
order = MagicMock()
|
||||||
order.myid = 123
|
order.myid = 123
|
||||||
exchange._DRY_RUN_OPEN_ORDERS['X'] = order
|
exchange._DRY_RUN_OPEN_ORDERS['X'] = order
|
||||||
print(exchange.get_order('X'))
|
print(exchange.get_order('X', 'TKN/BTC'))
|
||||||
assert exchange.get_order('X').myid == 123
|
assert exchange.get_order('X', 'TKN/BTC').myid == 123
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_order = MagicMock(return_value=456)
|
api_mock.fetch_order = MagicMock(return_value=456)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
assert exchange.get_order('X') == 456
|
assert exchange.get_order('X', 'TKN/BTC') == 456
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
|
||||||
def test_get_name(default_conf, mocker):
|
def test_get_name(default_conf, mocker):
|
||||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
mocker.patch('freqtrade.exchange.validate_pairs',
|
||||||
side_effect=lambda s: True)
|
side_effect=lambda s: True)
|
||||||
default_conf['exchange']['name'] = 'bittrex'
|
default_conf['exchange']['name'] = 'binance'
|
||||||
init(default_conf)
|
init(default_conf)
|
||||||
|
|
||||||
assert get_name() == 'Bittrex'
|
assert get_name() == 'Binance'
|
||||||
|
|
||||||
|
|
||||||
def test_get_fee(default_conf, mocker):
|
def test_get_fee(default_conf, mocker):
|
||||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
|
||||||
side_effect=lambda s: True)
|
|
||||||
init(default_conf)
|
|
||||||
|
|
||||||
assert get_fee() == 0.0025
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_misc(mocker):
|
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
|
api_mock.calculate_fee = MagicMock(return_value={
|
||||||
|
'type': 'taker',
|
||||||
|
'currency': 'BTC',
|
||||||
|
'rate': 0.025,
|
||||||
|
'cost': 0.05
|
||||||
|
})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
exchange.get_markets()
|
assert get_fee() == 0.025
|
||||||
assert api_mock.get_markets.call_count == 1
|
|
||||||
exchange.get_market_summaries()
|
|
||||||
assert api_mock.get_market_summaries.call_count == 1
|
|
||||||
api_mock.name = 123
|
|
||||||
assert exchange.get_name() == 123
|
|
||||||
api_mock.fee = 456
|
|
||||||
assert exchange.get_fee() == 456
|
|
||||||
exchange.get_wallet_health()
|
|
||||||
assert api_mock.get_wallet_health.call_count == 1
|
|
||||||
|
Loading…
Reference in New Issue
Block a user