Update most of exchange functions and tests for ccxt

This commit is contained in:
enenn 2018-02-03 21:28:27 +01:00
parent 260f1728d1
commit 6bd606eb3e
2 changed files with 78 additions and 66 deletions

View File

@ -16,7 +16,7 @@ from freqtrade.exchange.interface import Exchange
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Current selected exchange # Current selected exchange
_API: Exchange = None _API: ccxt.Exchange = None
_CONF: dict = {} _CONF: dict = {}
# Holds all open sell orders for dry_run # Holds all open sell orders for dry_run
@ -49,7 +49,6 @@ def init(config: dict) -> None:
raise OperationalException('Exchange {} is not supported'.format(name)) raise OperationalException('Exchange {} is not supported'.format(name))
try: try:
# exchange_class = Exchanges[name.upper()].value
_API = getattr(ccxt, name.lower())({ _API = getattr(ccxt, name.lower())({
'apiKey': exchange_config.get('key'), 'apiKey': exchange_config.get('key'),
'secret': exchange_config.get('secret'), 'secret': exchange_config.get('secret'),
@ -72,12 +71,9 @@ def validate_pairs(pairs: List[str]) -> None:
:return: None :return: None
""" """
if not _API.markets:
_API.load_markets()
try: try:
markets = _API.markets markets = _API.load_markets()
except requests.exceptions.RequestException as e: except ccxt.BaseError as e:
logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e) logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e)
return return
@ -93,7 +89,7 @@ def validate_pairs(pairs: List[str]) -> None:
) )
if pair not in markets: if pair not in markets:
raise OperationalException( raise OperationalException(
'Pair {} is not available at {}'.format(pair, _API.name.lower())) 'Pair {} is not available at {}'.format(pair, _API.id.lower()))
def buy(pair: str, rate: float, amount: float) -> str: def buy(pair: str, rate: float, amount: float) -> str:
@ -136,7 +132,7 @@ def get_balance(currency: str) -> float:
if _CONF['dry_run']: if _CONF['dry_run']:
return 999.9 return 999.9
return _API.fetch_balance()[currency] return _API.fetch_balance()[currency]['free']
def get_balances(): def get_balances():
@ -147,12 +143,14 @@ def get_balances():
def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict: def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
return _API.get_ticker(pair, refresh) # TODO: add caching
return _API.fetch_ticker(pair)
@cached(TTLCache(maxsize=100, ttl=30)) @cached(TTLCache(maxsize=100, ttl=30))
def get_ticker_history(pair: str, tick_interval) -> List[Dict]: def get_ticker_history(pair: str, tick_interval) -> List[Dict]:
return _API.get_ticker_history(pair, tick_interval) # TODO: check if exchange supports fetch_ohlcv
return _API.fetch_ohlcv(pair, timeframe=tick_interval)
def cancel_order(order_id: str) -> None: def cancel_order(order_id: str) -> None:
@ -190,7 +188,7 @@ def get_name() -> str:
def get_fee() -> float: def get_fee() -> float:
return _API.fee return _API.calculate_fee('ETH/BTC', '', '', 1, 1)['rate']
def get_wallet_health() -> List[Dict]: def get_wallet_health() -> List[Dict]:

View File

@ -1,8 +1,9 @@
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement # pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access # pragma pylint: disable=protected-access
from unittest.mock import MagicMock from unittest.mock import Mock, MagicMock, PropertyMock
from random import randint from random import randint
import logging import logging
import ccxt
from requests.exceptions import RequestException from requests.exceptions import RequestException
import pytest import pytest
@ -42,9 +43,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.load_markets = MagicMock(return_value=[ api_mock.load_markets = MagicMock(return_value={
'ETH/BTC', 'TKN/BTC', 'TRST/BTC', 'SWT/BTC', 'BCC/BTC', '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 +56,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 +65,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=['ETH/BTC', 'TKN/BTC', 'TRST/BTC', 'SWT/BTC']) '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)
@ -72,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):
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 ('freqtrade.exchange', assert ('freqtrade.exchange',
logging.WARNING, logging.WARNING,
@ -130,7 +134,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
@ -148,26 +152,26 @@ def test_get_balances_dry_run(default_conf, mocker):
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
# This test is somewhat redundant with # This test is somewhat redundant with
@ -175,9 +179,14 @@ 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='ETH/BTC') ticker = get_ticker(pair='ETH/BTC')
@ -185,15 +194,20 @@ def test_get_ticker(default_conf, mocker):
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='ETH/BTC', 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='ETH/BTC', refresh=True) ticker = get_ticker(pair='ETH/BTC', refresh=True)
@ -204,7 +218,7 @@ def test_get_ticker(default_conf, mocker):
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 = 123
api_mock.get_ticker_history = MagicMock(return_value=tick) 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
@ -212,13 +226,13 @@ def test_get_ticker_history(default_conf, mocker):
assert ticks == 123 assert ticks == 123
# change the ticker # change the ticker
tick = 999 #tick = 999
api_mock.get_ticker_history = MagicMock(return_value=tick) #api_mock.get_ticker_history = MagicMock(return_value=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('ETH/BTC', int(default_conf['ticker_interval'])) #ticks = get_ticker_history('ETH/BTC', int(default_conf['ticker_interval']))
assert ticks == 123 #assert ticks == 123
def test_cancel_order_dry_run(default_conf, mocker): def test_cancel_order_dry_run(default_conf, mocker):
@ -265,23 +279,23 @@ def test_get_name(default_conf, mocker):
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.001, 'cost': 0.002})
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
exchange.get_markets() assert get_fee() == 0.001
assert api_mock.get_markets.call_count == 1
exchange.get_market_summaries()
assert api_mock.get_market_summaries.call_count == 1 # TODO: disable until caching implemented
api_mock.name = 123 # def test_exchange_misc(mocker):
assert exchange.get_name() == 123 # api_mock = MagicMock()
api_mock.fee = 456 # mocker.patch('freqtrade.exchange._API', api_mock)
assert exchange.get_fee() == 456 # exchange.get_markets()
exchange.get_wallet_health() # assert api_mock.get_markets.call_count == 1
assert api_mock.get_wallet_health.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