Add retrier to stoploss calls (but without retrying)

This commit is contained in:
Matthias 2020-06-28 11:56:29 +02:00
parent 2c45114a64
commit 5bd4798ed0
8 changed files with 61 additions and 57 deletions

View File

@ -8,6 +8,7 @@ from freqtrade.exceptions import (DDosProtection, DependencyException,
InvalidOrderException, OperationalException, InvalidOrderException, OperationalException,
TemporaryError) TemporaryError)
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -40,6 +41,7 @@ class Binance(Exchange):
""" """
return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice'])
@retrier(retries=0)
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
""" """
creates a stoploss limit order. creates a stoploss limit order.

View File

@ -1,6 +1,7 @@
import asyncio import asyncio
import logging import logging
import time import time
from functools import wraps
from freqtrade.exceptions import DDosProtection, TemporaryError from freqtrade.exceptions import DDosProtection, TemporaryError
@ -110,21 +111,28 @@ def retrier_async(f):
return wrapper return wrapper
def retrier(f): def retrier(_func=None, retries=API_RETRY_COUNT):
def wrapper(*args, **kwargs): def decorator(f):
count = kwargs.pop('count', API_RETRY_COUNT) @wraps(f)
try: def wrapper(*args, **kwargs):
return f(*args, **kwargs) count = kwargs.pop('count', retries)
except TemporaryError as ex: try:
logger.warning('%s() returned exception: "%s"', f.__name__, ex) return f(*args, **kwargs)
if count > 0: except TemporaryError as ex:
count -= 1 logger.warning('%s() returned exception: "%s"', f.__name__, ex)
kwargs.update({'count': count}) if count > 0:
logger.warning('retrying %s() still for %s times', f.__name__, count) count -= 1
if isinstance(ex, DDosProtection): kwargs.update({'count': count})
time.sleep(1) logger.warning('retrying %s() still for %s times', f.__name__, count)
return wrapper(*args, **kwargs) if isinstance(ex, DDosProtection):
else: time.sleep(1)
logger.warning('Giving up retrying: %s()', f.__name__) return wrapper(*args, **kwargs)
raise ex else:
return wrapper logger.warning('Giving up retrying: %s()', f.__name__)
raise ex
return wrapper
# Support both @retrier and @retrier() syntax
if _func is None:
return decorator
else:
return decorator(_func)

View File

@ -27,6 +27,7 @@ class Ftx(Exchange):
""" """
return order['type'] == 'stop' and stop_loss > float(order['price']) return order['type'] == 'stop' and stop_loss > float(order['price'])
@retrier(retries=0)
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
""" """
Creates a stoploss order. Creates a stoploss order.

View File

@ -60,6 +60,7 @@ class Kraken(Exchange):
""" """
return order['type'] == 'stop-loss' and stop_loss > float(order['price']) return order['type'] == 'stop-loss' and stop_loss > float(order['price'])
@retrier(retries=0)
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
""" """
Creates a stoploss market order. Creates a stoploss market order.

View File

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

View File

@ -37,20 +37,20 @@ def get_mock_coro(return_value):
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
fun, mock_ccxt_fun, **kwargs): fun, mock_ccxt_fun, retries=API_RETRY_COUNT + 1, **kwargs):
with patch('freqtrade.exchange.common.time.sleep'): with patch('freqtrade.exchange.common.time.sleep'):
with pytest.raises(DDosProtection): with pytest.raises(DDosProtection):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DDos")) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DDos"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
getattr(exchange, fun)(**kwargs) getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1 assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeaDBeef")) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeaDBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
getattr(exchange, fun)(**kwargs) getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1 assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
@ -59,19 +59,21 @@ def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs): async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun,
retries=API_RETRY_COUNT + 1, **kwargs):
with patch('freqtrade.exchange.common.asyncio.sleep'): with patch('freqtrade.exchange.common.asyncio.sleep'):
with pytest.raises(DDosProtection): with pytest.raises(DDosProtection):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DeadBeef")) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs) await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef")) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs) await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1 assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
@ -1142,9 +1144,10 @@ def test_get_balance_prod(default_conf, mocker, exchange_name):
exchange.get_balance(currency='BTC') exchange.get_balance(currency='BTC')
def test_get_balances_dry_run(default_conf, mocker): @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_balances_dry_run(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert exchange.get_balances() == {} assert exchange.get_balances() == {}
@ -2126,6 +2129,13 @@ def test_get_markets(default_conf, mocker, markets,
assert sorted(pairs.keys()) == sorted(expected_keys) assert sorted(pairs.keys()) == sorted(expected_keys)
def test_get_markets_error(default_conf, mocker):
ex = get_patched_exchange(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=None))
with pytest.raises(OperationalException, match="Markets were not loaded."):
ex.get_markets('LTC', 'USDT', True, False)
def test_timeframe_to_minutes(): def test_timeframe_to_minutes():
assert timeframe_to_minutes("5m") == 5 assert timeframe_to_minutes("5m") == 5
assert timeframe_to_minutes("10m") == 10 assert timeframe_to_minutes("10m") == 10

View File

@ -6,9 +6,9 @@ from unittest.mock import MagicMock
import ccxt import ccxt
import pytest import pytest
from freqtrade.exceptions import (DependencyException, InvalidOrderException, from freqtrade.exceptions import DependencyException, InvalidOrderException
OperationalException, TemporaryError)
from tests.conftest import get_patched_exchange from tests.conftest import get_patched_exchange
from .test_exchange import ccxt_exceptionhandlers from .test_exchange import ccxt_exceptionhandlers
STOPLOSS_ORDERTYPE = 'stop' STOPLOSS_ORDERTYPE = 'stop'
@ -85,15 +85,9 @@ def test_stoploss_order_ftx(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(TemporaryError): ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection")) "stoploss", "create_order", retries=1,
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(OperationalException, match=r".*DeadBeef.*"):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
def test_stoploss_order_dry_run_ftx(default_conf, mocker): def test_stoploss_order_dry_run_ftx(default_conf, mocker):

View File

@ -6,8 +6,7 @@ from unittest.mock import MagicMock
import ccxt import ccxt
import pytest import pytest
from freqtrade.exceptions import (DependencyException, InvalidOrderException, from freqtrade.exceptions import DependencyException, InvalidOrderException
OperationalException, TemporaryError)
from tests.conftest import get_patched_exchange from tests.conftest import get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers from tests.exchange.test_exchange import ccxt_exceptionhandlers
@ -206,15 +205,9 @@ def test_stoploss_order_kraken(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(TemporaryError): ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection")) "stoploss", "create_order", retries=1,
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(OperationalException, match=r".*DeadBeef.*"):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
def test_stoploss_order_dry_run_kraken(default_conf, mocker): def test_stoploss_order_dry_run_kraken(default_conf, mocker):