from freqtrade/develop

This commit is contained in:
Nullart
2018-07-09 08:19:28 +08:00
parent 70f2aed0a7
commit e5cd756cca
119 changed files with 15166 additions and 6207 deletions

705
freqtrade/tests/exchange/test_exchange.py Normal file → Executable file
View File

@@ -1,23 +1,39 @@
# pragma pylint: disable=missing-docstring,C0103
from unittest.mock import MagicMock
from requests.exceptions import RequestException
from random import randint
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access
import logging
from copy import deepcopy
from datetime import datetime
from random import randint
from unittest.mock import MagicMock, PropertyMock
import ccxt
import pytest
from freqtrade import OperationalException
from freqtrade.exchange import init, validate_pairs, buy, sell, get_balance, get_balances, \
get_ticker, cancel_order, get_name, get_fee
from freqtrade import DependencyException, OperationalException, TemporaryError
from freqtrade.exchange import API_RETRY_COUNT, Exchange
from freqtrade.tests.conftest import get_patched_exchange, log_has
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs):
"""Function to test ccxt exception handling """
with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1
with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
def test_init(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.validate_pairs',
side_effect=lambda s: True)
init(config=default_conf)
assert ('freqtrade.exchange',
logging.INFO,
'Instance is running with dry_run enabled'
) in caplog.record_tuples
caplog.set_level(logging.INFO)
get_patched_exchange(mocker, default_conf)
assert log_has('Instance is running with dry_run enabled', caplog.record_tuples)
def test_init_exception(default_conf, mocker):
@@ -26,186 +42,661 @@ def test_init_exception(default_conf, mocker):
with pytest.raises(
OperationalException,
match='Exchange {} is not supported'.format(default_conf['exchange']['name'])):
init(config=default_conf)
Exchange(default_conf)
default_conf['exchange']['name'] = 'binance'
with pytest.raises(
OperationalException,
match='Exchange {} is not supported'.format(default_conf['exchange']['name'])):
mocker.patch("ccxt.binance", MagicMock(side_effect=AttributeError))
Exchange(default_conf)
def test_validate_pairs(default_conf, mocker):
api_mock = MagicMock()
api_mock.get_markets = MagicMock(return_value=[
'BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT', 'BTC_BCC',
])
mocker.patch('freqtrade.exchange._API', api_mock)
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
validate_pairs(default_conf['exchange']['pair_whitelist'])
api_mock.load_markets = MagicMock(return_value={
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
})
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
Exchange(default_conf)
def test_validate_pairs_not_available(default_conf, mocker):
api_mock = MagicMock()
api_mock.get_markets = MagicMock(return_value=[])
mocker.patch('freqtrade.exchange._API', api_mock)
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
api_mock.load_markets = MagicMock(return_value={})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
with pytest.raises(OperationalException, match=r'not available'):
validate_pairs(default_conf['exchange']['pair_whitelist'])
Exchange(default_conf)
def test_validate_pairs_not_compatible(default_conf, mocker):
api_mock = MagicMock()
api_mock.get_markets = MagicMock(
return_value=['BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT'])
default_conf['stake_currency'] = 'ETH'
mocker.patch('freqtrade.exchange._API', api_mock)
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
api_mock.load_markets = MagicMock(return_value={
'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': ''
})
conf = deepcopy(default_conf)
conf['stake_currency'] = 'ETH'
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
with pytest.raises(OperationalException, match=r'not compatible'):
validate_pairs(default_conf['exchange']['pair_whitelist'])
Exchange(conf)
def test_validate_pairs_exception(default_conf, mocker, caplog):
caplog.set_level(logging.INFO)
api_mock = MagicMock()
api_mock.get_markets = MagicMock(side_effect=RequestException())
mocker.patch('freqtrade.exchange._API', api_mock)
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance'))
# with pytest.raises(RequestException, match=r'Unable to validate pairs'):
validate_pairs(default_conf['exchange']['pair_whitelist'])
assert ('freqtrade.exchange',
logging.WARNING,
'Unable to validate pairs (assuming they are correct). Reason: '
) in caplog.record_tuples
api_mock.load_markets = MagicMock(return_value={})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available at Binance'):
Exchange(default_conf)
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError())
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
Exchange(default_conf)
assert log_has('Unable to validate pairs (assuming they are correct). Reason: ',
caplog.record_tuples)
def test_validate_pairs_stake_exception(default_conf, mocker, caplog):
caplog.set_level(logging.INFO)
conf = deepcopy(default_conf)
conf['stake_currency'] = 'ETH'
api_mock = MagicMock()
api_mock.name = MagicMock(return_value='binance')
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
with pytest.raises(
OperationalException,
match=r'Pair ETH/BTC not compatible with stake_currency: ETH'
):
Exchange(conf)
def test_exchangehas(default_conf, mocker):
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")
def test_buy_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
exchange = get_patched_exchange(mocker, default_conf)
assert 'dry_run_buy_' in buy(pair='BTC_ETH', rate=200, amount=1)
order = exchange.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):
api_mock = MagicMock()
api_mock.buy = MagicMock(
return_value='dry_run_buy_{}'.format(randint(0, 10**6)))
mocker.patch('freqtrade.exchange._API', api_mock)
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
api_mock.create_limit_buy_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert 'dry_run_buy_' in buy(pair='BTC_ETH', rate=200, amount=1)
order = exchange.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)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(DependencyException):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InvalidOrder)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(TemporaryError):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(OperationalException):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
def test_sell_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
exchange = get_patched_exchange(mocker, default_conf)
assert 'dry_run_sell_' in sell(pair='BTC_ETH', rate=200, amount=1)
order = exchange.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):
api_mock = MagicMock()
api_mock.sell = MagicMock(
return_value='dry_run_sell_{}'.format(randint(0, 10**6)))
mocker.patch('freqtrade.exchange._API', api_mock)
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
api_mock.create_limit_sell_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
assert 'dry_run_sell_' in sell(pair='BTC_ETH', rate=200, amount=1)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
order = exchange.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)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(DependencyException):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InvalidOrder)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(TemporaryError):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(OperationalException):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
def test_get_balance_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
assert get_balance(currency='BTC') == 999.9
exchange = get_patched_exchange(mocker, default_conf)
assert exchange.get_balance(currency='BTC') == 999.9
def test_get_balance_prod(default_conf, mocker):
api_mock = MagicMock()
api_mock.get_balance = MagicMock(return_value=123.4)
mocker.patch('freqtrade.exchange._API', api_mock)
api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4}})
default_conf['dry_run'] = False
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
assert get_balance(currency='BTC') == 123.4
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert exchange.get_balance(currency='BTC') == 123.4
with pytest.raises(OperationalException):
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_balance(currency='BTC')
with pytest.raises(TemporaryError, match=r'.*balance due to malformed exchange response:.*'):
exchange = get_patched_exchange(mocker, default_conf, api_mock)
mocker.patch('freqtrade.exchange.Exchange.get_balances', MagicMock(return_value={}))
exchange.get_balance(currency='BTC')
def test_get_balances_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
assert get_balances() == []
exchange = get_patched_exchange(mocker, default_conf)
assert exchange.get_balances() == {}
def test_get_balances_prod(default_conf, mocker):
balance_item = {
'Currency': '1ST',
'Balance': 10.0,
'Available': 10.0,
'Pending': 0.0,
'CryptoAddress': None
'free': 10.0,
'total': 10.0,
'used': 0.0
}
api_mock = MagicMock()
api_mock.get_balances = MagicMock(
return_value=[balance_item, balance_item, balance_item])
mocker.patch('freqtrade.exchange._API', api_mock)
api_mock.fetch_balance = MagicMock(return_value={
'1ST': balance_item,
'2ST': balance_item,
'3ST': balance_item
})
default_conf['dry_run'] = False
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert len(exchange.get_balances()) == 3
assert exchange.get_balances()['1ST']['free'] == 10.0
assert exchange.get_balances()['1ST']['total'] == 10.0
assert exchange.get_balances()['1ST']['used'] == 0.0
assert len(get_balances()) == 3
assert get_balances()[0]['Currency'] == '1ST'
assert get_balances()[0]['Balance'] == 10.0
assert get_balances()[0]['Available'] == 10.0
assert get_balances()[0]['Pending'] == 0.0
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
"get_balances", "fetch_balance")
def test_get_ticker(mocker, ticker):
def test_get_tickers(default_conf, mocker):
api_mock = MagicMock()
tick = {"success": True, 'result': {'Bid': 0.00001098, 'Ask': 0.00001099, 'Last': 0.0001}}
api_mock.get_ticker = MagicMock(return_value=tick)
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
tick = {'ETH/BTC': {
'symbol': 'ETH/BTC',
'bid': 0.5,
'ask': 1,
'last': 42,
}, 'BCH/BTC': {
'symbol': 'BCH/BTC',
'bid': 0.6,
'ask': 0.5,
'last': 41,
}
}
api_mock.fetch_tickers = MagicMock(return_value=tick)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# retrieve original ticker
ticker = get_ticker(pair='BTC_ETH')
tickers = exchange.get_tickers()
assert 'ETH/BTC' in tickers
assert 'BCH/BTC' in tickers
assert tickers['ETH/BTC']['bid'] == 0.5
assert tickers['ETH/BTC']['ask'] == 1
assert tickers['BCH/BTC']['bid'] == 0.6
assert tickers['BCH/BTC']['ask'] == 0.5
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
"get_tickers", "fetch_tickers")
with pytest.raises(OperationalException):
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_tickers()
api_mock.fetch_tickers = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_tickers()
def test_get_ticker(default_conf, mocker):
api_mock = MagicMock()
tick = {
'symbol': 'ETH/BTC',
'bid': 0.00001098,
'ask': 0.00001099,
'last': 0.0001,
}
api_mock.fetch_ticker = MagicMock(return_value=tick)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# retrieve original ticker
ticker = exchange.get_ticker(pair='ETH/BTC')
assert ticker['bid'] == 0.00001098
assert ticker['ask'] == 0.00001099
# change the ticker
tick = {"success": True, 'result': {"Bid": 0.5, "Ask": 1, "Last": 42}}
api_mock.get_ticker = MagicMock(return_value=tick)
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
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)
# if not caching the result we should get the same ticker
ticker = get_ticker(pair='BTC_ETH', refresh=False)
assert ticker['bid'] == 0.00001098
assert ticker['ask'] == 0.00001099
# if not fetching a new result we should get the cached ticker
ticker = exchange.get_ticker(pair='ETH/BTC')
# force ticker refresh
ticker = get_ticker(pair='BTC_ETH', refresh=True)
assert api_mock.fetch_ticker.call_count == 1
assert ticker['bid'] == 0.5
assert ticker['ask'] == 1
assert 'ETH/BTC' in exchange._cached_ticker
assert exchange._cached_ticker['ETH/BTC']['bid'] == 0.5
assert exchange._cached_ticker['ETH/BTC']['ask'] == 1
# Test caching
api_mock.fetch_ticker = MagicMock()
exchange.get_ticker(pair='ETH/BTC', refresh=False)
assert api_mock.fetch_ticker.call_count == 0
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
"get_ticker", "fetch_ticker",
pair='ETH/BTC', refresh=True)
api_mock.fetch_ticker = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_ticker(pair='ETH/BTC', refresh=True)
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
def test_get_ticker_history(default_conf, mocker):
api_mock = MagicMock()
tick = [
[
1511686200000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# retrieve original ticker
ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1511686200000
assert ticks[0][1] == 1
assert ticks[0][2] == 2
assert ticks[0][3] == 3
assert ticks[0][4] == 4
assert ticks[0][5] == 5
# change ticker and ensure tick changes
new_tick = [
[
1511686210000, # unix timestamp ms
6, # open
7, # high
8, # low
9, # close
10, # volume (in quote currency)
]
]
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(new_tick))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1511686210000
assert ticks[0][1] == 6
assert ticks[0][2] == 7
assert ticks[0][3] == 8
assert ticks[0][4] == 9
assert ticks[0][5] == 10
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
"get_ticker_history", "fetch_ohlcv",
pair='ABCD/BTC', tick_interval=default_conf['ticker_interval'])
with pytest.raises(OperationalException, match=r'Exchange .* does not support.*'):
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_ticker_history(pair='ABCD/BTC', tick_interval=default_conf['ticker_interval'])
def test_get_ticker_history_sort(default_conf, mocker):
api_mock = MagicMock()
# GDAX use-case (real data from GDAX)
# This ticker history is ordered DESC (newest first, oldest last)
tick = [
[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]
]
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# Test the ticker history sort
ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1527830400000
assert ticks[0][1] == 0.07649
assert ticks[0][2] == 0.07651
assert ticks[0][3] == 0.07649
assert ticks[0][4] == 0.07651
assert ticks[0][5] == 2.5734867
assert ticks[9][0] == 1527833100000
assert ticks[9][1] == 0.07666
assert ticks[9][2] == 0.07671
assert ticks[9][3] == 0.07666
assert ticks[9][4] == 0.07668
assert ticks[9][5] == 16.65244264
# Bittrex use-case (real data from Bittrex)
# This ticker history is ordered ASC (oldest first, newest last)
tick = [
[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]
]
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# Test the ticker history sort
ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1527827700000
assert ticks[0][1] == 0.07659999
assert ticks[0][2] == 0.0766
assert ticks[0][3] == 0.07627
assert ticks[0][4] == 0.07657998
assert ticks[0][5] == 1.85216924
assert ticks[9][0] == 1527830400000
assert ticks[9][1] == 0.07671
assert ticks[9][2] == 0.07674399
assert ticks[9][3] == 0.07629216
assert ticks[9][4] == 0.07655213
assert ticks[9][5] == 2.31452783
def test_cancel_order_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
assert cancel_order(order_id='123') is None
exchange = get_patched_exchange(mocker, default_conf)
assert exchange.cancel_order(order_id='123', pair='TKN/BTC') is None
def test_get_name(default_conf, mocker):
mocker.patch('freqtrade.exchange.validate_pairs',
# Ensure that if not dry_run, we should call API
def test_cancel_order(default_conf, mocker):
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)
assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123
with pytest.raises(DependencyException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.cancel_order(order_id='_', pair='TKN/BTC')
assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
"cancel_order", "cancel_order",
order_id='_', pair='TKN/BTC')
def test_get_order(default_conf, mocker):
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123
exchange = get_patched_exchange(mocker, default_conf)
exchange._dry_run_open_orders['X'] = order
print(exchange.get_order('X', 'TKN/BTC'))
assert exchange.get_order('X', 'TKN/BTC').myid == 123
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)
assert exchange.get_order('X', 'TKN/BTC') == 456
with pytest.raises(DependencyException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order(order_id='_', pair='TKN/BTC')
assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
'get_order', 'fetch_order',
order_id='_', pair='TKN/BTC')
def test_name(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.validate_pairs',
side_effect=lambda s: True)
default_conf['exchange']['name'] = 'bittrex'
init(default_conf)
default_conf['exchange']['name'] = 'binance'
exchange = Exchange(default_conf)
assert get_name() == 'Bittrex'
assert exchange.name == 'Binance'
def test_id(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.validate_pairs',
side_effect=lambda s: True)
default_conf['exchange']['name'] = 'binance'
exchange = Exchange(default_conf)
assert exchange.id == 'binance'
def test_get_pair_detail_url(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange.validate_pairs',
side_effect=lambda s: True)
default_conf['exchange']['name'] = 'binance'
exchange = Exchange(default_conf)
url = exchange.get_pair_detail_url('TKN/ETH')
assert 'TKN' in url
assert 'ETH' in url
url = exchange.get_pair_detail_url('LOOONG/BTC')
assert 'LOOONG' in url
assert 'BTC' in url
default_conf['exchange']['name'] = 'bittrex'
exchange = Exchange(default_conf)
url = exchange.get_pair_detail_url('TKN/ETH')
assert 'TKN' in url
assert 'ETH' in url
url = exchange.get_pair_detail_url('LOOONG/BTC')
assert 'LOOONG' in url
assert 'BTC' in url
default_conf['exchange']['name'] = 'poloniex'
exchange = Exchange(default_conf)
url = exchange.get_pair_detail_url('LOOONG/BTC')
assert '' == url
assert log_has('Could not get exchange url for Poloniex', caplog.record_tuples)
def test_get_trades_for_order(default_conf, mocker):
order_id = 'ABCD-ABCD'
since = datetime(2018, 5, 5)
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)
orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since)
assert len(orders) == 1
assert orders[0]['price'] == 165
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
'get_trades_for_order', 'fetch_my_trades',
order_id=order_id, pair='LTC/BTC', since=since)
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False))
assert exchange.get_trades_for_order(order_id, 'LTC/BTC', since) == []
def test_get_markets(default_conf, mocker, markets):
api_mock = MagicMock()
api_mock.fetch_markets = markets
exchange = get_patched_exchange(mocker, default_conf, api_mock)
ret = exchange.get_markets()
assert isinstance(ret, list)
assert len(ret) == 6
assert ret[0]["id"] == "ethbtc"
assert ret[0]["symbol"] == "ETH/BTC"
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
'get_markets', 'fetch_markets')
def test_get_fee(default_conf, mocker):
mocker.patch('freqtrade.exchange.validate_pairs',
side_effect=lambda s: True)
init(default_conf)
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)
assert get_fee() == 0.0025
assert exchange.get_fee() == 0.025
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
'get_fee', 'calculate_fee')
def test_get_amount_lots(default_conf, mocker):
api_mock = MagicMock()
api_mock.amount_to_lots = MagicMock(return_value=1.0)
api_mock.markets = None
marketmock = MagicMock()
api_mock.load_markets = marketmock
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert exchange.get_amount_lots('LTC/BTC', 1.54) == 1
assert marketmock.call_count == 1

View File

@@ -1,337 +0,0 @@
# pragma pylint: disable=missing-docstring,C0103
import pytest
from unittest.mock import MagicMock
from requests.exceptions import ContentDecodingError
from freqtrade.exchange.bittrex import Bittrex
import freqtrade.exchange.bittrex as btx
# Eat this flake8
# +------------------+
# | bittrex.Bittrex |
# +------------------+
# |
# (mock Fake_bittrex)
# |
# +-----------------------------+
# | freqtrade.exchange.Bittrex |
# +-----------------------------+
# Call into Bittrex will flow up to the
# external package bittrex.Bittrex.
# By inserting a mock, we redirect those
# calls.
# The faked bittrex API is called just 'fb'
# The freqtrade.exchange.Bittrex is a
# wrapper, and is called 'wb'
def _stub_config():
return {'key': '',
'secret': ''}
class FakeBittrex():
def __init__(self, success=True):
self.success = True # Believe in yourself
self.result = None
self.get_ticker_call_count = 0
# This is really ugly, doing side-effect during instance creation
# But we're allowed to in testing-code
btx._API = MagicMock()
btx._API.buy_limit = self.fake_buysell_limit
btx._API.sell_limit = self.fake_buysell_limit
btx._API.get_balance = self.fake_get_balance
btx._API.get_balances = self.fake_get_balances
btx._API.get_ticker = self.fake_get_ticker
btx._API.get_order = self.fake_get_order
btx._API.cancel = self.fake_cancel_order
btx._API.get_markets = self.fake_get_markets
btx._API.get_market_summaries = self.fake_get_market_summaries
btx._API_V2 = MagicMock()
btx._API_V2.get_candles = self.fake_get_candles
btx._API_V2.get_wallet_health = self.fake_get_wallet_health
def fake_buysell_limit(self, pair, amount, limit):
return {'success': self.success,
'result': {'uuid': '1234'},
'message': 'barter'}
def fake_get_balance(self, cur):
return {'success': self.success,
'result': {'Balance': 1234},
'message': 'unbalanced'}
def fake_get_balances(self):
return {'success': self.success,
'result': [{'BTC_ETH': 1234}],
'message': 'no balances'}
def fake_get_ticker(self, pair):
self.get_ticker_call_count += 1
return self.result or {'success': self.success,
'result': {'Bid': 1, 'Ask': 1, 'Last': 1},
'message': 'NO_API_RESPONSE'}
def fake_get_candles(self, pair, interval):
return self.result or {'success': self.success,
'result': [{'C': 0, 'V': 0, 'O': 0, 'H': 0, 'L': 0, 'T': 0}],
'message': 'candles lit'}
def fake_get_order(self, uuid):
return {'success': self.success,
'result': {'OrderUuid': 'ABC123',
'Type': 'Type',
'Exchange': 'BTC_ETH',
'Opened': True,
'PricePerUnit': 1,
'Quantity': 1,
'QuantityRemaining': 1,
'Closed': True
},
'message': 'lost'}
def fake_cancel_order(self, uuid):
return self.result or {'success': self.success,
'message': 'no such order'}
def fake_get_markets(self):
return self.result or {'success': self.success,
'message': 'market gone',
'result': [{'MarketName': '-_'}]}
def fake_get_market_summaries(self):
return self.result or {'success': self.success,
'message': 'no summary',
'result': ['sum']}
def fake_get_wallet_health(self):
return self.result or {'success': self.success,
'message': 'bad health',
'result': [{'Health': {'Currency': 'BTC_ETH',
'IsActive': True,
'LastChecked': 0},
'Currency': {'Notice': True}}]}
# The freqtrade.exchange.bittrex is called wrap_bittrex
# to not confuse naming with bittrex.bittrex
def make_wrap_bittrex():
conf = _stub_config()
wb = btx.Bittrex(conf)
return wb
def test_exchange_bittrex_class():
conf = _stub_config()
b = Bittrex(conf)
assert isinstance(b, Bittrex)
slots = dir(b)
for name in ['fee', 'buy', 'sell', 'get_balance', 'get_balances',
'get_ticker', 'get_ticker_history', 'get_order',
'cancel_order', 'get_pair_detail_url', 'get_markets',
'get_market_summaries', 'get_wallet_health']:
assert name in slots
# FIX: ensure that the slot is also a method in the class
# getattr(b, name) => bound method Bittrex.buy
# type(getattr(b, name)) => class 'method'
def test_exchange_bittrex_fee():
fee = Bittrex.fee.__get__(Bittrex)
assert fee >= 0 and fee < 0.1 # Fee is 0-10 %
def test_exchange_bittrex_buy_good(mocker):
wb = make_wrap_bittrex()
fb = FakeBittrex()
uuid = wb.buy('BTC_ETH', 1, 1)
assert uuid == fb.fake_buysell_limit(1, 2, 3)['result']['uuid']
fb.success = False
with pytest.raises(btx.OperationalException, match=r'barter.*'):
wb.buy('BAD', 1, 1)
def test_exchange_bittrex_sell_good(mocker):
wb = make_wrap_bittrex()
fb = FakeBittrex()
uuid = wb.sell('BTC_ETH', 1, 1)
assert uuid == fb.fake_buysell_limit(1, 2, 3)['result']['uuid']
fb.success = False
with pytest.raises(btx.OperationalException, match=r'barter.*'):
uuid = wb.sell('BAD', 1, 1)
def test_exchange_bittrex_get_balance(mocker):
wb = make_wrap_bittrex()
fb = FakeBittrex()
bal = wb.get_balance('BTC_ETH')
assert bal == fb.fake_get_balance(1)['result']['Balance']
fb.success = False
with pytest.raises(btx.OperationalException, match=r'unbalanced'):
wb.get_balance('BTC_ETH')
def test_exchange_bittrex_get_balances():
wb = make_wrap_bittrex()
fb = FakeBittrex()
bals = wb.get_balances()
assert bals == fb.fake_get_balances()['result']
fb.success = False
with pytest.raises(btx.OperationalException, match=r'no balances'):
wb.get_balances()
def test_exchange_bittrex_get_ticker():
wb = make_wrap_bittrex()
fb = FakeBittrex()
# Poll ticker, which updates the cache
tick = wb.get_ticker('BTC_ETH')
for x in ['bid', 'ask', 'last']:
assert x in tick
# Ensure the side-effect was made (update the ticker cache)
assert 'BTC_ETH' in wb.cached_ticker.keys()
# taint the cache, so we can recognize the cache wall utilized
wb.cached_ticker['BTC_ETH']['bid'] = 1234
# Poll again, getting the cached result
fb.get_ticker_call_count = 0
tick = wb.get_ticker('BTC_ETH', False)
# Ensure the result was from the cache, and that we didn't call exchange
assert wb.cached_ticker['BTC_ETH']['bid'] == 1234
assert fb.get_ticker_call_count == 0
def test_exchange_bittrex_get_ticker_bad():
wb = make_wrap_bittrex()
fb = FakeBittrex()
fb.result = {'success': True,
'result': {'Bid': 1}} # incomplete result
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'):
wb.get_ticker('BTC_ETH')
fb.result = {'success': False,
'message': 'gone bad'
}
with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'):
wb.get_ticker('BTC_ETH')
def test_exchange_bittrex_get_ticker_history_one():
wb = make_wrap_bittrex()
FakeBittrex()
assert wb.get_ticker_history('BTC_ETH', 1)
def test_exchange_bittrex_get_ticker_history():
wb = make_wrap_bittrex()
fb = FakeBittrex()
assert wb.get_ticker_history('BTC_ETH', 5)
with pytest.raises(ValueError, match=r'.*Cannot parse tick_interval.*'):
wb.get_ticker_history('BTC_ETH', 2)
fb.success = False
with pytest.raises(btx.OperationalException, match=r'candles lit.*'):
wb.get_ticker_history('BTC_ETH', 5)
fb.success = True
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex.*'):
fb.result = {'bad': 0}
wb.get_ticker_history('BTC_ETH', 5)
with pytest.raises(ContentDecodingError, match=r'.*Required property C not present.*'):
fb.result = {'success': True,
'result': [{'V': 0, 'O': 0, 'H': 0, 'L': 0, 'T': 0}], # close is missing
'message': 'candles lit'}
wb.get_ticker_history('BTC_ETH', 5)
def test_exchange_bittrex_get_order():
wb = make_wrap_bittrex()
fb = FakeBittrex()
order = wb.get_order('someUUID')
assert order['id'] == 'ABC123'
fb.success = False
with pytest.raises(btx.OperationalException, match=r'lost'):
wb.get_order('someUUID')
def test_exchange_bittrex_cancel_order():
wb = make_wrap_bittrex()
fb = FakeBittrex()
wb.cancel_order('someUUID')
with pytest.raises(btx.OperationalException, match=r'no such order'):
fb.success = False
wb.cancel_order('someUUID')
# Note: this can be a bug in exchange.bittrex._validate_response
with pytest.raises(KeyError):
fb.result = {'success': False} # message is missing!
wb.cancel_order('someUUID')
with pytest.raises(btx.OperationalException, match=r'foo'):
fb.result = {'success': False, 'message': 'foo'}
wb.cancel_order('someUUID')
def test_exchange_get_pair_detail_url():
wb = make_wrap_bittrex()
assert wb.get_pair_detail_url('BTC_ETH')
def test_exchange_get_markets():
wb = make_wrap_bittrex()
fb = FakeBittrex()
x = wb.get_markets()
assert x == ['__']
with pytest.raises(btx.OperationalException, match=r'market gone'):
fb.success = False
wb.get_markets()
def test_exchange_get_market_summaries():
wb = make_wrap_bittrex()
fb = FakeBittrex()
assert ['sum'] == wb.get_market_summaries()
with pytest.raises(btx.OperationalException, match=r'no summary'):
fb.success = False
wb.get_market_summaries()
def test_exchange_get_wallet_health():
wb = make_wrap_bittrex()
fb = FakeBittrex()
x = wb.get_wallet_health()
assert x[0]['Currency'] == 'BTC_ETH'
with pytest.raises(btx.OperationalException, match=r'bad health'):
fb.success = False
wb.get_wallet_health()
def test_validate_response_success():
response = {
'message': '',
'result': [],
}
Bittrex._validate_response(response)
def test_validate_response_no_api_response():
response = {
'message': 'NO_API_RESPONSE',
'result': None,
}
with pytest.raises(ContentDecodingError, match=r'.*NO_API_RESPONSE.*'):
Bittrex._validate_response(response)
def test_validate_response_min_trade_requirement_not_met():
response = {
'message': 'MIN_TRADE_REQUIREMENT_NOT_MET',
'result': None,
}
with pytest.raises(ContentDecodingError, match=r'.*MIN_TRADE_REQUIREMENT_NOT_MET.*'):
Bittrex._validate_response(response)