Merge pull request #2206 from freqtrade/sloe_handling
Improve stoploss on exchange handling
This commit is contained in:
commit
caec5ac941
@ -38,6 +38,7 @@
|
|||||||
"order_types": {
|
"order_types": {
|
||||||
"buy": "limit",
|
"buy": "limit",
|
||||||
"sell": "limit",
|
"sell": "limit",
|
||||||
|
"emergencysell": "market",
|
||||||
"stoploss": "market",
|
"stoploss": "market",
|
||||||
"stoploss_on_exchange": false,
|
"stoploss_on_exchange": false,
|
||||||
"stoploss_on_exchange_interval": 60
|
"stoploss_on_exchange_interval": 60
|
||||||
|
@ -192,19 +192,20 @@ end up paying more then would probably have been necessary.
|
|||||||
|
|
||||||
### Understand order_types
|
### Understand order_types
|
||||||
|
|
||||||
The `order_types` configuration parameter contains a dict mapping order-types to
|
The `order_types` configuration parameter maps actions (`buy`, `sell`, `stoploss`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds.
|
||||||
market-types as well as stoploss on or off exchange type and stoploss on exchange
|
|
||||||
update interval in seconds. This allows to buy using limit orders, sell using
|
|
||||||
limit-orders, and create stoploss orders using market. It also allows to set the
|
|
||||||
stoploss "on exchange" which means stoploss order would be placed immediately once
|
|
||||||
the buy order is fulfilled. In case stoploss on exchange and `trailing_stop` are
|
|
||||||
both set, then the bot will use `stoploss_on_exchange_interval` to check it periodically
|
|
||||||
and update it if necessary (e.x. in case of trailing stoploss).
|
|
||||||
This can be set in the configuration file or in the strategy.
|
|
||||||
Values set in the configuration file overwrites values set in the strategy.
|
|
||||||
|
|
||||||
If this is configured, all 4 values (`buy`, `sell`, `stoploss` and
|
This allows to buy using limit orders, sell using
|
||||||
`stoploss_on_exchange`) need to be present, otherwise the bot will warn about it and fail to start.
|
limit-orders, and create stoplosses using using market orders. It also allows to set the
|
||||||
|
stoploss "on exchange" which means stoploss order would be placed immediately once
|
||||||
|
the buy order is fulfilled.
|
||||||
|
If `stoploss_on_exchange` and `trailing_stop` are both set, then the bot will use `stoploss_on_exchange_interval` to check and update the stoploss on exchange periodically.
|
||||||
|
`order_types` can be set in the configuration file or in the strategy.
|
||||||
|
`order_types` set in the configuration file overwrites values set in the strategy as a whole, so you need to configure the whole `order_types` dictionary in one place.
|
||||||
|
|
||||||
|
If this is configured, the following 4 values (`buy`, `sell`, `stoploss` and
|
||||||
|
`stoploss_on_exchange`) need to be present, otherwise the bot will fail to start.
|
||||||
|
|
||||||
|
`emergencysell` is an optional value, which defaults to `market` and is used when creating stoploss on exchange orders fails.
|
||||||
The below is the default which is used if this is not configured in either strategy or configuration file.
|
The below is the default which is used if this is not configured in either strategy or configuration file.
|
||||||
|
|
||||||
Syntax for Strategy:
|
Syntax for Strategy:
|
||||||
@ -213,6 +214,7 @@ Syntax for Strategy:
|
|||||||
order_types = {
|
order_types = {
|
||||||
"buy": "limit",
|
"buy": "limit",
|
||||||
"sell": "limit",
|
"sell": "limit",
|
||||||
|
"emergencysell": "market",
|
||||||
"stoploss": "market",
|
"stoploss": "market",
|
||||||
"stoploss_on_exchange": False,
|
"stoploss_on_exchange": False,
|
||||||
"stoploss_on_exchange_interval": 60
|
"stoploss_on_exchange_interval": 60
|
||||||
@ -225,6 +227,7 @@ Configuration:
|
|||||||
"order_types": {
|
"order_types": {
|
||||||
"buy": "limit",
|
"buy": "limit",
|
||||||
"sell": "limit",
|
"sell": "limit",
|
||||||
|
"emergencysell": "market",
|
||||||
"stoploss": "market",
|
"stoploss": "market",
|
||||||
"stoploss_on_exchange": false,
|
"stoploss_on_exchange": false,
|
||||||
"stoploss_on_exchange_interval": 60
|
"stoploss_on_exchange_interval": 60
|
||||||
@ -239,11 +242,13 @@ Configuration:
|
|||||||
!!! Note
|
!!! Note
|
||||||
Stoploss on exchange interval is not mandatory. Do not change its value if you are
|
Stoploss on exchange interval is not mandatory. Do not change its value if you are
|
||||||
unsure of what you are doing. For more information about how stoploss works please
|
unsure of what you are doing. For more information about how stoploss works please
|
||||||
read [the stoploss documentation](stoploss.md).
|
refer to [the stoploss documentation](stoploss.md).
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
In case of stoploss on exchange if the stoploss is cancelled manually then
|
If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new order.
|
||||||
the bot would recreate one.
|
|
||||||
|
!!! Warning stoploss_on_exchange failures
|
||||||
|
If stoploss on exchange creation fails for some reason, then an "emergency sell" is initiated. By default, this will sell the asset using a market order. The order-type for the emergency-sell can be changed by setting the `emergencysell` value in the `order_types` dictionary - however this is not advised.
|
||||||
|
|
||||||
### Understand order_time_in_force
|
### Understand order_time_in_force
|
||||||
|
|
||||||
|
@ -224,7 +224,7 @@ This would signify a stoploss of -10%.
|
|||||||
|
|
||||||
For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md).
|
For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md).
|
||||||
|
|
||||||
If your exchange supports it, it's recommended to also set `"stoploss_on_exchange"` in the order dict, so your stoploss is on the exchange and cannot be missed for network-problems (or other problems).
|
If your exchange supports it, it's recommended to also set `"stoploss_on_exchange"` in the order_types dictionary, so your stoploss is on the exchange and cannot be missed due to network problems, high load or other reasons.
|
||||||
|
|
||||||
For more information on order_types please look [here](configuration.md#understand-order_types).
|
For more information on order_types please look [here](configuration.md#understand-order_types).
|
||||||
|
|
||||||
|
@ -121,6 +121,7 @@ CONF_SCHEMA = {
|
|||||||
'properties': {
|
'properties': {
|
||||||
'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||||
'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||||
|
'emergencysell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||||
'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||||
'stoploss_on_exchange': {'type': 'boolean'},
|
'stoploss_on_exchange': {'type': 'boolean'},
|
||||||
'stoploss_on_exchange_interval': {'type': 'number'}
|
'stoploss_on_exchange_interval': {'type': 'number'}
|
||||||
|
@ -4,7 +4,8 @@ from typing import Dict
|
|||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
from freqtrade import DependencyException, OperationalException, TemporaryError
|
from freqtrade import (DependencyException, InvalidOrderException,
|
||||||
|
OperationalException, TemporaryError)
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -66,12 +67,14 @@ class Binance(Exchange):
|
|||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
f'Insufficient funds to create {ordertype} sell order on market {pair}.'
|
f'Insufficient funds to create {ordertype} sell order on market {pair}.'
|
||||||
f'Tried to sell amount {amount} at rate {rate}.'
|
f'Tried to sell amount {amount} at rate {rate}. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise DependencyException(
|
# Errors:
|
||||||
|
# `binance Order would trigger immediately.`
|
||||||
|
raise InvalidOrderException(
|
||||||
f'Could not create {ordertype} sell order on market {pair}. '
|
f'Could not create {ordertype} sell order on market {pair}. '
|
||||||
f'Tried to sell amount {amount} at rate {rate}.'
|
f'Tried to sell amount {amount} at rate {rate}. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
|
@ -611,6 +611,33 @@ class FreqtradeBot(object):
|
|||||||
logger.debug('Found no sell signal for %s.', trade)
|
logger.debug('Found no sell signal for %s.', trade)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def create_stoploss_order(self, trade: Trade, stop_price: float, rate: float) -> bool:
|
||||||
|
"""
|
||||||
|
Abstracts creating stoploss orders from the logic.
|
||||||
|
Handles errors and updates the trade database object.
|
||||||
|
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 price
|
||||||
|
LIMIT_PRICE_PCT = 0.99
|
||||||
|
|
||||||
|
try:
|
||||||
|
stoploss_order = self.exchange.stoploss_limit(pair=trade.pair, amount=trade.amount,
|
||||||
|
stop_price=stop_price,
|
||||||
|
rate=rate * LIMIT_PRICE_PCT)
|
||||||
|
trade.stoploss_order_id = str(stoploss_order['id'])
|
||||||
|
return True
|
||||||
|
except InvalidOrderException as e:
|
||||||
|
trade.stoploss_order_id = None
|
||||||
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||||
|
logger.warning('Selling the trade forcefully')
|
||||||
|
self.execute_sell(trade, trade.stop_loss, sell_reason=SellType.EMERGENCY_SELL)
|
||||||
|
|
||||||
|
except DependencyException:
|
||||||
|
trade.stoploss_order_id = None
|
||||||
|
logger.exception('Unable to place a stoploss order on exchange.')
|
||||||
|
return False
|
||||||
|
|
||||||
def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
|
def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if trade is fulfilled in which case the stoploss
|
Check if trade is fulfilled in which case the stoploss
|
||||||
@ -629,49 +656,25 @@ class FreqtradeBot(object):
|
|||||||
except InvalidOrderException as exception:
|
except InvalidOrderException as exception:
|
||||||
logger.warning('Unable to fetch stoploss order: %s', exception)
|
logger.warning('Unable to fetch stoploss order: %s', exception)
|
||||||
|
|
||||||
# If trade open order id does not exist: buy order is fulfilled
|
|
||||||
buy_order_fulfilled = not trade.open_order_id
|
|
||||||
|
|
||||||
# Limit price threshold: As limit price should always be below price
|
|
||||||
limit_price_pct = 0.99
|
|
||||||
|
|
||||||
# If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange
|
# If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange
|
||||||
if (buy_order_fulfilled and not stoploss_order):
|
if (not trade.open_order_id and not stoploss_order):
|
||||||
if self.edge:
|
|
||||||
stoploss = self.edge.stoploss(pair=trade.pair)
|
stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss
|
||||||
else:
|
|
||||||
stoploss = self.strategy.stoploss
|
|
||||||
|
|
||||||
stop_price = trade.open_rate * (1 + stoploss)
|
stop_price = trade.open_rate * (1 + stoploss)
|
||||||
|
|
||||||
# limit price should be less than stop price.
|
if self.create_stoploss_order(trade=trade, stop_price=stop_price, rate=stop_price):
|
||||||
limit_price = stop_price * limit_price_pct
|
|
||||||
|
|
||||||
try:
|
|
||||||
stoploss_order_id = self.exchange.stoploss_limit(
|
|
||||||
pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price
|
|
||||||
)['id']
|
|
||||||
trade.stoploss_order_id = str(stoploss_order_id)
|
|
||||||
trade.stoploss_last_update = datetime.now()
|
trade.stoploss_last_update = datetime.now()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except DependencyException as exception:
|
|
||||||
trade.stoploss_order_id = None
|
|
||||||
logger.warning('Unable to place a stoploss order on exchange: %s', exception)
|
|
||||||
|
|
||||||
# If stoploss order is canceled for some reason we add it
|
# If stoploss order is canceled for some reason we add it
|
||||||
if stoploss_order and stoploss_order['status'] == 'canceled':
|
if stoploss_order and stoploss_order['status'] == 'canceled':
|
||||||
try:
|
if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss,
|
||||||
stoploss_order_id = self.exchange.stoploss_limit(
|
rate=trade.stop_loss):
|
||||||
pair=trade.pair, amount=trade.amount,
|
|
||||||
stop_price=trade.stop_loss, rate=trade.stop_loss * limit_price_pct
|
|
||||||
)['id']
|
|
||||||
trade.stoploss_order_id = str(stoploss_order_id)
|
|
||||||
return False
|
return False
|
||||||
except DependencyException as exception:
|
else:
|
||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
logger.warning('Stoploss order was cancelled, '
|
logger.warning('Stoploss order was cancelled, but unable to recreate one.')
|
||||||
'but unable to recreate one: %s', exception)
|
|
||||||
|
|
||||||
# We check if stoploss order is fulfilled
|
# We check if stoploss order is fulfilled
|
||||||
if stoploss_order and stoploss_order['status'] == 'closed':
|
if stoploss_order and stoploss_order['status'] == 'closed':
|
||||||
@ -680,7 +683,7 @@ class FreqtradeBot(object):
|
|||||||
# Lock pair for one candle to prevent immediate rebuys
|
# Lock pair for one candle to prevent immediate rebuys
|
||||||
self.strategy.lock_pair(trade.pair,
|
self.strategy.lock_pair(trade.pair,
|
||||||
timeframe_to_next_date(self.config['ticker_interval']))
|
timeframe_to_next_date(self.config['ticker_interval']))
|
||||||
self._notify_sell(trade)
|
self._notify_sell(trade, "stoploss")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Finally we check if stoploss on exchange should be moved up because of trailing.
|
# Finally we check if stoploss on exchange should be moved up because of trailing.
|
||||||
@ -714,16 +717,12 @@ class FreqtradeBot(object):
|
|||||||
logger.exception(f"Could not cancel stoploss order {order['id']} "
|
logger.exception(f"Could not cancel stoploss order {order['id']} "
|
||||||
f"for pair {trade.pair}")
|
f"for pair {trade.pair}")
|
||||||
|
|
||||||
try:
|
# Create new stoploss order
|
||||||
# creating the new one
|
if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss,
|
||||||
stoploss_order_id = self.exchange.stoploss_limit(
|
rate=trade.stop_loss):
|
||||||
pair=trade.pair, amount=trade.amount,
|
return False
|
||||||
stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99
|
else:
|
||||||
)['id']
|
logger.warning(f"Could not create trailing stoploss order "
|
||||||
trade.stoploss_order_id = str(stoploss_order_id)
|
|
||||||
except DependencyException:
|
|
||||||
trade.stoploss_order_id = None
|
|
||||||
logger.exception(f"Could not create trailing stoploss order "
|
|
||||||
f"for pair {trade.pair}.")
|
f"for pair {trade.pair}.")
|
||||||
|
|
||||||
def _check_and_execute_sell(self, trade: Trade, sell_rate: float,
|
def _check_and_execute_sell(self, trade: Trade, sell_rate: float,
|
||||||
@ -877,9 +876,14 @@ class FreqtradeBot(object):
|
|||||||
except InvalidOrderException:
|
except InvalidOrderException:
|
||||||
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
|
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
|
||||||
|
|
||||||
|
ordertype = self.strategy.order_types[sell_type]
|
||||||
|
if sell_reason == SellType.EMERGENCY_SELL:
|
||||||
|
# Emergencysells (default to market!)
|
||||||
|
ordertype = self.strategy.order_types.get("emergencysell", "market")
|
||||||
|
|
||||||
# Execute sell and update trade record
|
# Execute sell and update trade record
|
||||||
order = self.exchange.sell(pair=str(trade.pair),
|
order = self.exchange.sell(pair=str(trade.pair),
|
||||||
ordertype=self.strategy.order_types[sell_type],
|
ordertype=ordertype,
|
||||||
amount=trade.amount, rate=limit,
|
amount=trade.amount, rate=limit,
|
||||||
time_in_force=self.strategy.order_time_in_force['sell']
|
time_in_force=self.strategy.order_time_in_force['sell']
|
||||||
)
|
)
|
||||||
@ -895,9 +899,9 @@ class FreqtradeBot(object):
|
|||||||
# Lock pair for one candle to prevent immediate rebuys
|
# Lock pair for one candle to prevent immediate rebuys
|
||||||
self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['ticker_interval']))
|
self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['ticker_interval']))
|
||||||
|
|
||||||
self._notify_sell(trade)
|
self._notify_sell(trade, ordertype)
|
||||||
|
|
||||||
def _notify_sell(self, trade: Trade):
|
def _notify_sell(self, trade: Trade, order_type: str):
|
||||||
"""
|
"""
|
||||||
Sends rpc notification when a sell occured.
|
Sends rpc notification when a sell occured.
|
||||||
"""
|
"""
|
||||||
@ -914,7 +918,7 @@ class FreqtradeBot(object):
|
|||||||
'pair': trade.pair,
|
'pair': trade.pair,
|
||||||
'gain': gain,
|
'gain': gain,
|
||||||
'limit': trade.close_rate_requested,
|
'limit': trade.close_rate_requested,
|
||||||
'order_type': self.strategy.order_types['sell'],
|
'order_type': order_type,
|
||||||
'amount': trade.amount,
|
'amount': trade.amount,
|
||||||
'open_rate': trade.open_rate,
|
'open_rate': trade.open_rate,
|
||||||
'current_rate': current_rate,
|
'current_rate': current_rate,
|
||||||
|
@ -39,6 +39,7 @@ class SellType(Enum):
|
|||||||
TRAILING_STOP_LOSS = "trailing_stop_loss"
|
TRAILING_STOP_LOSS = "trailing_stop_loss"
|
||||||
SELL_SIGNAL = "sell_signal"
|
SELL_SIGNAL = "sell_signal"
|
||||||
FORCE_SELL = "force_sell"
|
FORCE_SELL = "force_sell"
|
||||||
|
EMERGENCY_SELL = "emergency_sell"
|
||||||
NONE = ""
|
NONE = ""
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,8 @@ from unittest.mock import MagicMock
|
|||||||
import ccxt
|
import ccxt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade import DependencyException, OperationalException, TemporaryError
|
from freqtrade import (DependencyException, InvalidOrderException,
|
||||||
|
OperationalException, TemporaryError)
|
||||||
from freqtrade.tests.conftest import get_patched_exchange
|
from freqtrade.tests.conftest import get_patched_exchange
|
||||||
|
|
||||||
|
|
||||||
@ -49,8 +50,9 @@ def test_stoploss_limit_order(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
|
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
|
||||||
|
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(InvalidOrderException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
api_mock.create_order = MagicMock(
|
||||||
|
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
|
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
|
||||||
|
|
||||||
|
@ -1152,7 +1152,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
|||||||
side_effect=DependencyException()
|
side_effect=DependencyException()
|
||||||
)
|
)
|
||||||
freqtrade.handle_stoploss_on_exchange(trade)
|
freqtrade.handle_stoploss_on_exchange(trade)
|
||||||
assert log_has('Unable to place a stoploss order on exchange: ', caplog)
|
assert log_has('Unable to place a stoploss order on exchange.', caplog)
|
||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
|
|
||||||
# Fifth case: get_order returns InvalidOrder
|
# Fifth case: get_order returns InvalidOrder
|
||||||
@ -1200,6 +1200,50 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf, fee, caplog,
|
|||||||
assert trade.is_open is True
|
assert trade.is_open is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
||||||
|
markets, limit_buy_order, limit_sell_order):
|
||||||
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
|
patch_exchange(mocker)
|
||||||
|
sell_mock = MagicMock(return_value={'id': limit_sell_order['id']})
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
get_ticker=MagicMock(return_value={
|
||||||
|
'bid': 0.00001172,
|
||||||
|
'ask': 0.00001173,
|
||||||
|
'last': 0.00001172
|
||||||
|
}),
|
||||||
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
|
sell=sell_mock,
|
||||||
|
get_fee=fee,
|
||||||
|
markets=PropertyMock(return_value=markets),
|
||||||
|
get_order=MagicMock(return_value={'status': 'canceled'}),
|
||||||
|
stoploss_limit=MagicMock(side_effect=InvalidOrderException()),
|
||||||
|
)
|
||||||
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
patch_get_signal(freqtrade)
|
||||||
|
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||||
|
|
||||||
|
freqtrade.create_trades()
|
||||||
|
trade = Trade.query.first()
|
||||||
|
caplog.clear()
|
||||||
|
freqtrade.create_stoploss_order(trade, 200, 199)
|
||||||
|
assert trade.stoploss_order_id is None
|
||||||
|
assert trade.sell_reason == SellType.EMERGENCY_SELL.value
|
||||||
|
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
|
||||||
|
assert log_has("Selling the trade forcefully", caplog)
|
||||||
|
|
||||||
|
# Should call a market sell
|
||||||
|
assert sell_mock.call_count == 1
|
||||||
|
assert sell_mock.call_args[1]['ordertype'] == 'market'
|
||||||
|
assert sell_mock.call_args[1]['pair'] == trade.pair
|
||||||
|
assert sell_mock.call_args[1]['amount'] == trade.amount
|
||||||
|
|
||||||
|
# Rpc is sending first buy, then sell
|
||||||
|
assert rpc_mock.call_count == 2
|
||||||
|
assert rpc_mock.call_args_list[1][0][0]['sell_reason'] == SellType.EMERGENCY_SELL.value
|
||||||
|
assert rpc_mock.call_args_list[1][0][0]['order_type'] == 'market'
|
||||||
|
|
||||||
|
|
||||||
def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
|
def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
|
||||||
markets, limit_buy_order, limit_sell_order) -> None:
|
markets, limit_buy_order, limit_sell_order) -> None:
|
||||||
# When trailing stoploss is set
|
# When trailing stoploss is set
|
||||||
|
Loading…
Reference in New Issue
Block a user