catch ccxt.ExchangeError and retry

This commit is contained in:
gcarq 2018-04-21 22:37:27 +02:00
parent acb1b50924
commit bbe3bc4423
3 changed files with 67 additions and 64 deletions

View File

@ -16,9 +16,9 @@ class OperationalException(BaseException):
""" """
class NetworkException(BaseException): class TemporaryError(BaseException):
""" """
Network related error. Temporary network or exchange related error.
This could happen when an exchange is congested, unavailable, or the user This could happen when an exchange is congested, unavailable, or the user
has networking problems. Usually resolves itself after a time. has networking problems. Usually resolves itself after a time.
""" """

View File

@ -7,7 +7,7 @@ from typing import List, Dict, Any, Optional
import ccxt import ccxt
import arrow import arrow
from freqtrade import OperationalException, DependencyException, NetworkException from freqtrade import OperationalException, DependencyException, TemporaryError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
# Current selected exchange # Current selected exchange
_API: ccxt.Exchange = None _API: ccxt.Exchange = None
_CONF: dict = {} _CONF: Dict = {}
API_RETRY_COUNT = 4 API_RETRY_COUNT = 4
# Holds all open sell orders for dry_run # Holds all open sell orders for dry_run
@ -33,15 +33,15 @@ def retrier(f):
count = kwargs.pop('count', API_RETRY_COUNT) count = kwargs.pop('count', API_RETRY_COUNT)
try: try:
return f(*args, **kwargs) return f(*args, **kwargs)
except (NetworkException, DependencyException) as ex: except (TemporaryError, DependencyException) as ex:
logger.warning('%s returned exception: "%s"', f, ex) logger.warning('%s() returned exception: "%s"', f.__name__, ex)
if count > 0: if count > 0:
count -= 1 count -= 1
kwargs.update({'count': count}) kwargs.update({'count': count})
logger.warning('retrying %s still for %s times', f, count) logger.warning('retrying %s() still for %s times', f.__name__, count)
return wrapper(*args, **kwargs) return wrapper(*args, **kwargs)
else: else:
raise OperationalException('Giving up retrying: %s', f) raise OperationalException('Giving up retrying: %s()', f.__name__)
return wrapper return wrapper
@ -151,10 +151,10 @@ def buy(pair: str, rate: float, amount: float) -> Dict:
'Tried to buy amount {} at rate {} (total {}).' 'Tried to buy amount {} at rate {} (total {}).'
'Message: {}'.format(pair, amount, rate, rate*amount, e) 'Message: {}'.format(pair, amount, rate, rate*amount, e)
) )
except ccxt.NetworkError as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise NetworkException( raise TemporaryError(
'Could not place buy order due to networking error. Message: {}'.format(e) 'Could not place buy order due to {}. Message: {}'.format(
) e.__class__.__name__, e))
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
@ -189,10 +189,10 @@ def sell(pair: str, rate: float, amount: float) -> Dict:
'Tried to sell amount {} at rate {} (total {}).' 'Tried to sell amount {} at rate {} (total {}).'
'Message: {}'.format(pair, amount, rate, rate*amount, e) 'Message: {}'.format(pair, amount, rate, rate*amount, e)
) )
except ccxt.NetworkError as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise NetworkException( raise TemporaryError(
'Could not place sell order due to networking error. Message: {}'.format(e) 'Could not place sell order due to {}. Message: {}'.format(
) e.__class__.__name__, e))
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
@ -219,10 +219,10 @@ def get_balances() -> dict:
balances.pop("used", None) balances.pop("used", None)
return balances return balances
except ccxt.NetworkError as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise NetworkException( raise TemporaryError(
'Could not get balance due to networking error. Message: {}'.format(e) 'Could not get balance due to {}. Message: {}'.format(
) e.__class__.__name__, e))
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
@ -231,17 +231,17 @@ def get_balances() -> dict:
def get_tickers() -> Dict: def get_tickers() -> Dict:
try: try:
return _API.fetch_tickers() return _API.fetch_tickers()
except ccxt.NetworkError as e:
raise NetworkException(
'Could not load tickers due to networking error. Message: {}'.format(e)
)
except ccxt.BaseError as e:
raise OperationalException(e)
except ccxt.NotSupported as e: except ccxt.NotSupported as e:
raise OperationalException( raise OperationalException(
'Exchange {} does not support fetching tickers in batch.' 'Exchange {} does not support fetching tickers in batch.'
'Message: {}'.format(_API.name, e) 'Message: {}'.format(_API.name, e)
) )
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not load tickers due to {}. Message: {}'.format(
e.__class__.__name__, e))
except ccxt.BaseError as e:
raise OperationalException(e)
# TODO: remove refresh argument, keeping it to keep track of where it was intended to be used # TODO: remove refresh argument, keeping it to keep track of where it was intended to be used
@ -249,10 +249,10 @@ def get_tickers() -> Dict:
def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict: def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
try: try:
return _API.fetch_ticker(pair) return _API.fetch_ticker(pair)
except ccxt.NetworkError as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise NetworkException( raise TemporaryError(
'Could not load tickers due to networking error. Message: {}'.format(e) 'Could not load ticker history due to {}. Message: {}'.format(
) e.__class__.__name__, e))
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
@ -261,17 +261,17 @@ def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
def get_ticker_history(pair: str, tick_interval: str) -> List[Dict]: def get_ticker_history(pair: str, tick_interval: str) -> List[Dict]:
try: try:
return _API.fetch_ohlcv(pair, timeframe=tick_interval) return _API.fetch_ohlcv(pair, timeframe=tick_interval)
except ccxt.NetworkError as e:
raise NetworkException(
'Could not load ticker history due to networking error. Message: {}'.format(e)
)
except ccxt.BaseError as e:
raise OperationalException('Could not fetch ticker data. Msg: {}'.format(e))
except ccxt.NotSupported as e: except ccxt.NotSupported as e:
raise OperationalException( raise OperationalException(
'Exchange {} does not support fetching historical candlestick data.' 'Exchange {} does not support fetching historical candlestick data.'
'Message: {}'.format(_API.name, e) 'Message: {}'.format(_API.name, e)
) )
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not load ticker history due to {}. Message: {}'.format(
e.__class__.__name__, e))
except ccxt.BaseError as e:
raise OperationalException('Could not fetch ticker data. Msg: {}'.format(e))
def cancel_order(order_id: str, pair: str) -> None: def cancel_order(order_id: str, pair: str) -> None:
@ -280,14 +280,10 @@ def cancel_order(order_id: str, pair: str) -> None:
try: try:
return _API.cancel_order(order_id, pair) return _API.cancel_order(order_id, pair)
except ccxt.NetworkError as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise NetworkException( raise TemporaryError(
'Could not get order due to networking error. Message: {}'.format(e) 'Could not cancel order due to {}. Message: {}'.format(
) e.__class__.__name__, e))
except ccxt.InvalidOrder as e:
raise DependencyException(
'Could not cancel order. Message: {}'.format(e)
)
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
@ -301,14 +297,14 @@ def get_order(order_id: str, pair: str) -> Dict:
return order return order
try: try:
return _API.fetch_order(order_id, pair) return _API.fetch_order(order_id, pair)
except ccxt.NetworkError as e:
raise NetworkException(
'Could not get order due to networking error. Message: {}'.format(e)
)
except ccxt.InvalidOrder as e: except ccxt.InvalidOrder as e:
raise DependencyException( raise DependencyException(
'Could not get order. Message: {}'.format(e) 'Could not get order. Message: {}'.format(e)
) )
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not get order due to {}. Message: {}'.format(
e.__class__.__name__, e))
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
@ -327,10 +323,10 @@ def get_pair_detail_url(pair: str) -> str:
def get_markets() -> List[dict]: def get_markets() -> List[dict]:
try: try:
return _API.fetch_markets() return _API.fetch_markets()
except ccxt.NetworkError as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise NetworkException( raise TemporaryError(
'Could not load markets due to networking error. Message: {}'.format(e) 'Could not load markets due to {}. Message: {}'.format(
) e.__class__.__name__, e))
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
@ -345,9 +341,16 @@ def get_id() -> str:
def get_fee(symbol='ETH/BTC', type='', side='', amount=1, def get_fee(symbol='ETH/BTC', type='', side='', amount=1,
price=1, taker_or_maker='maker') -> float: price=1, taker_or_maker='maker') -> float:
try:
# validate that markets are loaded before trying to get fee # validate that markets are loaded before trying to get fee
if _API.markets is None or len(_API.markets) == 0: if _API.markets is None or len(_API.markets) == 0:
_API.load_markets() _API.load_markets()
return _API.calculate_fee(symbol=symbol, type=type, side=side, amount=amount, return _API.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
price=price, takerOrMaker=taker_or_maker)['rate'] price=price, takerOrMaker=taker_or_maker)['rate']
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not get fee info due to {}. Message: {}'.format(
e.__class__.__name__, e))
except ccxt.BaseError as e:
raise OperationalException(e)

View File

@ -8,7 +8,7 @@ import ccxt
import pytest import pytest
from freqtrade import OperationalException, DependencyException, NetworkException from freqtrade import OperationalException, DependencyException, TemporaryError
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_id, get_pair_detail_url get_ticker, get_ticker_history, cancel_order, get_name, get_fee, get_id, get_pair_detail_url
import freqtrade.exchange as exchange import freqtrade.exchange as exchange
@ -148,7 +148,7 @@ def test_buy_prod(default_conf, mocker):
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
buy(pair='ETH/BTC', rate=200, amount=1) buy(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(NetworkException): with pytest.raises(TemporaryError):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
buy(pair='ETH/BTC', rate=200, amount=1) buy(pair='ETH/BTC', rate=200, amount=1)
@ -198,7 +198,7 @@ def test_sell_prod(default_conf, mocker):
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
sell(pair='ETH/BTC', rate=200, amount=1) sell(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(NetworkException): with pytest.raises(TemporaryError):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
sell(pair='ETH/BTC', rate=200, amount=1) sell(pair='ETH/BTC', rate=200, amount=1)
@ -262,7 +262,7 @@ def test_get_balances_prod(default_conf, mocker):
assert get_balances()['1ST']['total'] == 10.0 assert get_balances()['1ST']['total'] == 10.0
assert get_balances()['1ST']['used'] == 0.0 assert get_balances()['1ST']['used'] == 0.0
with pytest.raises(NetworkException): with pytest.raises(TemporaryError):
api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError) api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
get_balances() get_balances()
@ -397,12 +397,12 @@ def test_cancel_order(default_conf, mocker):
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
assert cancel_order(order_id='_', pair='TKN/BTC') == 123 assert cancel_order(order_id='_', pair='TKN/BTC') == 123
with pytest.raises(NetworkException): with pytest.raises(TemporaryError):
api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
cancel_order(order_id='_', pair='TKN/BTC') cancel_order(order_id='_', pair='TKN/BTC')
with pytest.raises(DependencyException): with pytest.raises(TemporaryError):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder) api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
cancel_order(order_id='_', pair='TKN/BTC') cancel_order(order_id='_', pair='TKN/BTC')
@ -429,7 +429,7 @@ def test_get_order(default_conf, mocker):
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
assert exchange.get_order('X', 'TKN/BTC') == 456 assert exchange.get_order('X', 'TKN/BTC') == 456
with pytest.raises(NetworkException): with pytest.raises(TemporaryError):
api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
exchange.get_order(order_id='_', pair='TKN/BTC') exchange.get_order(order_id='_', pair='TKN/BTC')