Merge pull request #2779 from freqtrade/stoploss_market

Stoploss on exchange for Kraken
This commit is contained in:
hroff-1902
2020-02-02 14:48:45 +03:00
committed by GitHub
12 changed files with 264 additions and 75 deletions

View File

@@ -32,13 +32,23 @@ class Binance(Exchange):
return super().get_order_book(pair, limit)
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict:
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
"""
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
"""
return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice'])
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
"""
creates a stoploss limit order.
this stoploss-limit is binance-specific.
It may work with a limited number of other exchanges, but this has not been tested yet.
"""
# Limit price threshold: As limit price should always be below stop-price
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
rate = stop_price * limit_price_pct
ordertype = "stop_loss_limit"
stop_price = self.price_to_precision(pair, stop_price)
@@ -61,8 +71,8 @@ class Binance(Exchange):
rate = self.price_to_precision(pair, rate)
order = self._api.create_order(pair, ordertype, 'sell',
amount, rate, params)
order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
amount=amount, price=stop_price, params=params)
logger.info('stoploss limit order added for %s. '
'stop price: %s. limit: %s', pair, stop_price, rate)
return order

View File

@@ -282,8 +282,8 @@ class Exchange:
quote_currencies = self.get_quote_currencies()
if stake_currency not in quote_currencies:
raise OperationalException(
f"{stake_currency} is not available as stake on {self.name}. "
f"Available currencies are: {', '.join(quote_currencies)}")
f"{stake_currency} is not available as stake on {self.name}. "
f"Available currencies are: {', '.join(quote_currencies)}")
def validate_pairs(self, pairs: List[str]) -> None:
"""
@@ -460,7 +460,7 @@ class Exchange:
"status": "closed",
"filled": closed_order["amount"],
"remaining": 0
})
})
if closed_order["type"] in ["stop_loss_limit"]:
closed_order["info"].update({"stopPrice": closed_order["price"]})
self._dry_run_open_orders[closed_order["id"]] = closed_order
@@ -519,9 +519,17 @@ class Exchange:
return self.create_order(pair, ordertype, 'sell', amount, rate, params)
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict:
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
"""
creates a stoploss limit order.
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
"""
raise OperationalException(f"stoploss is not implemented for {self.name}.")
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
"""
creates a stoploss order.
The precise ordertype is determined by the order_types dict or exchange default.
Since ccxt does not unify stoploss-limit orders yet, this needs to be implemented in each
exchange's subclass.
The exception below should never raise, since we disallow
@@ -529,7 +537,7 @@ class Exchange:
Note: Changes to this interface need to be applied to all sub-classes too.
"""
raise OperationalException(f"stoploss_limit is not implemented for {self.name}.")
raise OperationalException(f"stoploss is not implemented for {self.name}.")
@retrier
def get_balance(self, currency: str) -> float:

View File

@@ -4,7 +4,8 @@ from typing import Dict
import ccxt
from freqtrade.exceptions import OperationalException, TemporaryError
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange import retrier
@@ -15,6 +16,7 @@ class Kraken(Exchange):
_params: Dict = {"trading_agreement": "agree"}
_ft_has: Dict = {
"stoploss_on_exchange": True,
"trades_pagination": "id",
"trades_pagination_arg": "since",
}
@@ -48,3 +50,51 @@ class Kraken(Exchange):
f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
"""
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
"""
return order['type'] == 'stop-loss' and stop_loss > float(order['price'])
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

@@ -658,13 +658,10 @@ class FreqtradeBot:
Force-sells the pair (using EmergencySell reason) in case of Problems creating the order.
:return: True if the order succeeded, and False in case of problems.
"""
# Limit price threshold: As limit price should always be below stop-price
LIMIT_PRICE_PCT = self.strategy.order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
try:
stoploss_order = self.exchange.stoploss_limit(pair=trade.pair, amount=trade.amount,
stop_price=stop_price,
rate=rate * LIMIT_PRICE_PCT)
stoploss_order = self.exchange.stoploss(pair=trade.pair, amount=trade.amount,
stop_price=stop_price,
order_types=self.strategy.order_types)
trade.stoploss_order_id = str(stoploss_order['id'])
return True
except InvalidOrderException as e:
@@ -743,8 +740,7 @@ class FreqtradeBot:
:param order: Current on exchange stoploss order
:return: None
"""
if trade.stop_loss > float(order['info']['stopPrice']):
if self.exchange.stoploss_adjust(trade.stop_loss, order):
# we check if the update is neccesary
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: