Add retrier to stoploss calls (but without retrying)
This commit is contained in:
parent
2c45114a64
commit
5bd4798ed0
@ -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.
|
||||||
|
@ -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,9 +111,11 @@ def retrier_async(f):
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def retrier(f):
|
def retrier(_func=None, retries=API_RETRY_COUNT):
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
count = kwargs.pop('count', API_RETRY_COUNT)
|
count = kwargs.pop('count', retries)
|
||||||
try:
|
try:
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
except TemporaryError as ex:
|
except TemporaryError as ex:
|
||||||
@ -128,3 +131,8 @@ def retrier(f):
|
|||||||
logger.warning('Giving up retrying: %s()', f.__name__)
|
logger.warning('Giving up retrying: %s()', f.__name__)
|
||||||
raise ex
|
raise ex
|
||||||
return wrapper
|
return wrapper
|
||||||
|
# Support both @retrier and @retrier() syntax
|
||||||
|
if _func is None:
|
||||||
|
return decorator
|
||||||
|
else:
|
||||||
|
return decorator(_func)
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user