Implement stoploss for kraken

This commit is contained in:
Matthias 2020-01-19 14:08:47 +01:00
parent e6f1912443
commit f1629c907a
2 changed files with 131 additions and 1 deletions

View File

@ -4,7 +4,8 @@ from typing import Dict
import ccxt import ccxt
from freqtrade.exceptions import OperationalException, TemporaryError from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange import retrier from freqtrade.exchange.exchange import retrier
@ -15,6 +16,7 @@ class Kraken(Exchange):
_params: Dict = {"trading_agreement": "agree"} _params: Dict = {"trading_agreement": "agree"}
_ft_has: Dict = { _ft_has: Dict = {
"stoploss_on_exchange": True,
"trades_pagination": "id", "trades_pagination": "id",
"trades_pagination_arg": "since", "trades_pagination_arg": "since",
} }
@ -48,3 +50,44 @@ class Kraken(Exchange):
f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
"""
Creates a stoploss market order.
Stoploss market orders is the only stoploss type supported by kraken.
"""
ordertype = "stop-loss"
stop_price = self.price_to_precision(pair, stop_price)
if self._config['dry_run']:
dry_order = self.dry_run_order(
pair, ordertype, "sell", amount, stop_price)
return dry_order
try:
params = self._params.copy()
amount = self.amount_to_precision(pair, amount)
order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
amount=amount, price=stop_price, params=params)
logger.info('stoploss order added for %s. '
'stop price: %s.', pair, stop_price)
return order
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create {ordertype} sell order on market {pair}.'
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
f'Message: {e}') from e
except ccxt.InvalidOrder as e:
raise InvalidOrderException(
f'Could not create {ordertype} sell order on market {pair}. '
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
f'Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e

View File

@ -3,6 +3,11 @@
from random import randint from random import randint
from unittest.mock import MagicMock from unittest.mock import MagicMock
import ccxt
import pytest
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
@ -149,3 +154,85 @@ def test_get_balances_prod(default_conf, mocker):
assert balances['4ST']['used'] == 0.0 assert balances['4ST']['used'] == 0.0
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
"get_balances", "fetch_balance") "get_balances", "fetch_balance")
def test_stoploss_order_kraken(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'stop-loss'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
# stoploss_on_exchange_limit_ratio is irrelevant for kraken market orders
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
assert api_mock.create_order.call_count == 1
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == order_type
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
assert api_mock.create_order.call_args_list[0][1]['params'] == {'trading_agreement': 'agree'}
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
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):
api_mock = MagicMock()
order_type = 'stop-loss'
default_conf['dry_run'] = True
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
assert 'id' in order
assert 'info' in order
assert 'type' in order
assert order['type'] == order_type
assert order['price'] == 220
assert order['amount'] == 1