mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-02-10 08:10:34 +00:00
Merge pull request #9695 from freqtrade/kraken/stop
kraken stoploss behavior
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
2
setup.py
2
setup.py
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user