Merge pull request #9695 from freqtrade/kraken/stop

kraken stoploss behavior
This commit is contained in:
Matthias
2024-01-17 20:21:05 +01:00
committed by GitHub
4 changed files with 21 additions and 93 deletions

View File

@@ -8,11 +8,9 @@ from pandas import DataFrame
from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_utils import ROUND_DOWN, ROUND_UP
from freqtrade.exchange.types import Tickers
@@ -24,8 +22,9 @@ class Kraken(Exchange):
_params: Dict = {"trading_agreement": "agree"}
_ft_has: Dict = {
"stoploss_on_exchange": True,
"stop_price_param": "stopPrice",
"stop_price_prop": "stopPrice",
"stop_price_param": "stopLossPrice",
"stop_price_prop": "stopLossPrice",
"stoploss_order_types": {"limit": "limit", "market": "market"},
"order_time_in_force": ["GTC", "IOC", "PO"],
"ohlcv_candle_limit": 720,
"ohlcv_has_history": False,
@@ -90,75 +89,6 @@ class Kraken(Exchange):
except ccxt.BaseError as e:
raise OperationalException(e) from e
def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
"""
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
"""
return (order['type'] in ('stop-loss', 'stop-loss-limit') and (
(side == "sell" and stop_loss > float(order['price'])) or
(side == "buy" and stop_loss < float(order['price']))
))
@retrier(retries=0)
def create_stoploss(self, pair: str, amount: float, stop_price: float,
order_types: Dict, side: BuySell, leverage: float) -> Dict:
"""
Creates a stoploss market order.
Stoploss market orders is the only stoploss type supported by kraken.
TODO: investigate if this can be combined with generic implementation
(careful, prices are reversed)
"""
params = self._params.copy()
if self.trading_mode == TradingMode.FUTURES:
params.update({'reduceOnly': True})
round_mode = ROUND_DOWN if side == 'buy' else ROUND_UP
if order_types.get('stoploss', 'market') == 'limit':
ordertype = "stop-loss-limit"
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
if side == "sell":
limit_rate = stop_price * limit_price_pct
else:
limit_rate = stop_price * (2 - limit_price_pct)
params['price2'] = self.price_to_precision(pair, limit_rate, rounding_mode=round_mode)
else:
ordertype = "stop-loss"
stop_price = self.price_to_precision(pair, stop_price, rounding_mode=round_mode)
if self._config['dry_run']:
dry_order = self.create_dry_run_order(
pair, ordertype, side, amount, stop_price, leverage, stop_loss=True)
return dry_order
try:
amount = self.amount_to_precision(pair, amount)
order = self._api.create_order(symbol=pair, type=ordertype, side=side,
amount=amount, price=stop_price, params=params)
self._log_exchange_response('create_stoploss_order', order)
logger.info('stoploss order added for %s. '
'stop price: %s.', pair, stop_price)
return order
except ccxt.InsufficientFunds as e:
raise InsufficientFundsError(
f'Insufficient funds to create {ordertype} {side} 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} {side} order on market {pair}. '
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
f'Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def _set_leverage(
self,
leverage: float,

View File

@@ -2,7 +2,7 @@ numpy==1.26.3
pandas==2.1.4
pandas-ta==0.3.14b
ccxt==4.2.14
ccxt==4.2.15
cryptography==41.0.7
aiohttp==3.9.1
SQLAlchemy==2.0.25

View File

@@ -70,7 +70,7 @@ setup(
],
install_requires=[
# from requirements.txt
'ccxt>=4.0.0',
'ccxt>=4.2.15',
'SQLAlchemy>=2.0.6',
'python-telegram-bot>=20.1',
'arrow>=1.0.0',

View File

@@ -183,19 +183,17 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
if ordertype == 'limit':
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['params'] == {
'trading_agreement': 'agree',
'price2': adjustedprice
}
else:
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['params'] == {
'trading_agreement': 'agree'}
assert api_mock.create_order.call_args_list[0][1]['type'] == ordertype
assert api_mock.create_order.call_args_list[0][1]['params'] == {
'trading_agreement': 'agree',
'stopLossPrice': 220
}
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
if ordertype == 'limit':
assert api_mock.create_order.call_args_list[0][1]['price'] == adjustedprice
else:
assert api_mock.create_order.call_args_list[0][1]['price'] is None
# test exception handling
with pytest.raises(DependencyException):
@@ -253,7 +251,7 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
assert 'info' in order
assert 'type' in order
assert order['type'] == STOPLOSS_ORDERTYPE
assert order['type'] == 'market'
assert order['price'] == 220
assert order['amount'] == 1
@@ -265,11 +263,11 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='kraken')
order = {
'type': STOPLOSS_ORDERTYPE,
'price': 1500,
'type': 'market',
'stopLossPrice': 1500,
}
assert exchange.stoploss_adjust(sl1, order, side=side)
assert not exchange.stoploss_adjust(sl2, order, side=side)
# Test with invalid order case ...
order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(sl3, order, side=side)
# diff. order type ...
order['type'] = 'limit'
assert exchange.stoploss_adjust(sl3, order, side=side)