diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 4ba30b626..a195788dd 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -3,12 +3,8 @@ import logging from typing import Dict, List, Tuple import arrow -import ccxt -from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, - OperationalException, TemporaryError) from freqtrade.exchange import Exchange -from freqtrade.exchange.common import retrier logger = logging.getLogger(__name__) @@ -18,6 +14,7 @@ class Binance(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, + "stoploss_order_type": "stop_loss_limit", "order_time_in_force": ['gtc', 'fok', 'ioc'], "time_in_force_parameter": "timeInForce", "ohlcv_candle_limit": 1000, @@ -33,65 +30,6 @@ class Binance(Exchange): """ 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: - """ - 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) - - # Ensure rate is less than stop price - if stop_price <= rate: - raise OperationalException( - 'In stoploss limit order, stop price should be more than limit price') - - if self._config['dry_run']: - dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) - return dry_order - - try: - params = self._params.copy() - params.update({'stopPrice': stop_price}) - - amount = self.amount_to_precision(pair, amount) - - rate = self.price_to_precision(pair, rate) - - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', - amount=amount, price=rate, params=params) - logger.info('stoploss limit order added for %s. ' - 'stop price: %s. limit: %s', pair, stop_price, rate) - self._log_exchange_response('create_stoploss_order', order) - return order - except ccxt.InsufficientFunds as e: - raise InsufficientFundsError( - f'Insufficient funds to create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' - f'Message: {e}') from e - except ccxt.InvalidOrder as e: - # Errors: - # `binance Order would trigger immediately.` - raise InvalidOrderException( - f'Could not create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' - 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 sell order due to {e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e - async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int, is_new_pair: bool = False, raise_: bool = False diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a2217a02e..cd4c2ce83 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -791,18 +791,79 @@ class Exchange: """ raise OperationalException(f"stoploss is not implemented for {self.name}.") + @retrier(retries=0) def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ creates a stoploss order. + creates a stoploss limit order. + Should an exchange support more ordertypes, the exchange should implement this method, + using `order_types.get('stoploss', 'market')` to get the correct ordertype (e.g. FTX). + 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 starting the bot in validate_ordertypes() - Note: Changes to this interface need to be applied to all sub-classes too. - """ - raise OperationalException(f"stoploss is not implemented for {self.name}.") + This may work with a limited number of other exchanges, but correct working + needs to be tested individually. + WARNING: setting `stoploss_on_exchange` to True will NOT auto-enable stoploss on exchange. + `stoploss_adjust` must still be implemented for this to work. + """ + if not self._ft_has['stoploss_on_exchange']: + raise OperationalException(f"stoploss is not implemented for {self.name}.") + + # 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 = self._ft_has["stoploss_order_type"] + + stop_price = self.price_to_precision(pair, stop_price) + + # Ensure rate is less than stop price + if stop_price <= rate: + raise OperationalException( + 'In stoploss limit order, stop price should be more than limit price') + + if self._config['dry_run']: + dry_order = self.create_dry_run_order( + pair, ordertype, "sell", amount, stop_price) + return dry_order + + try: + params = self._params.copy() + # Verify if stopPrice works for your exchange! + params.update({'stopPrice': stop_price}) + + amount = self.amount_to_precision(pair, amount) + + rate = self.price_to_precision(pair, rate) + + order = self._api.create_order(symbol=pair, type=ordertype, side='sell', + amount=amount, price=rate, params=params) + logger.info(f"stoploss limit order added for {pair}. " + f"stop price: {stop_price}. limit: {rate}") + self._log_exchange_response('create_stoploss_order', order) + return order + except ccxt.InsufficientFunds as e: + raise InsufficientFundsError( + f'Insufficient funds to create {ordertype} sell order on market {pair}. ' + f'Tried to sell amount {amount} at rate {rate}. ' + f'Message: {e}') from e + except ccxt.InvalidOrder as e: + # Errors: + # `Order would trigger immediately.` + raise InvalidOrderException( + f'Could not create {ordertype} sell order on market {pair}. ' + f'Tried to sell amount {amount} at rate {rate}. ' + 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 sell order due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e @retrier(retries=API_FETCH_ORDER_RETRY_COUNT) def fetch_order(self, order_id: str, pair: str) -> Dict: