Exchange stoploss function takes side

This commit is contained in:
Sam Germain 2021-07-26 00:01:57 -06:00
parent ebf5310817
commit f4e26a616f
10 changed files with 85 additions and 58 deletions

View File

@ -25,20 +25,25 @@ class Binance(Exchange):
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
} }
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
""" """
Verify stop_loss against stoploss-order value (limit or price) Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary. Returns True if adjustment is necessary.
:param side: "buy" or "sell"
""" """
# TODO-mg: Short support
return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice'])
@retrier(retries=0) @retrier(retries=0)
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: def stoploss(self, pair: str, amount: float,
stop_price: float, order_types: Dict, side: str) -> Dict:
""" """
creates a stoploss limit order. creates a stoploss limit order.
this stoploss-limit is binance-specific. this stoploss-limit is binance-specific.
It may work with a limited number of other exchanges, but this has not been tested yet. It may work with a limited number of other exchanges, but this has not been tested yet.
:param side: "buy" or "sell"
""" """
# TODO-mg: Short support
# Limit price threshold: As limit price should always be below stop-price # 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) limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
rate = stop_price * limit_price_pct rate = stop_price * limit_price_pct

View File

@ -802,14 +802,15 @@ class Exchange:
): ):
raise OperationalException(f"Leverage is not available on {self.name} using freqtrade") raise OperationalException(f"Leverage is not available on {self.name} using freqtrade")
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
""" """
Verify stop_loss against stoploss-order value (limit or price) Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary. Returns True if adjustment is necessary.
""" """
raise OperationalException(f"stoploss is not implemented for {self.name}.") raise OperationalException(f"stoploss is not implemented for {self.name}.")
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: def stoploss(self, pair: str, amount: float,
stop_price: float, order_types: Dict, side: str) -> Dict:
""" """
creates a stoploss order. creates a stoploss order.
The precise ordertype is determined by the order_types dict or exchange default. The precise ordertype is determined by the order_types dict or exchange default.

View File

@ -31,21 +31,25 @@ class Ftx(Exchange):
return (parent_check and return (parent_check and
market.get('spot', False) is True) market.get('spot', False) is True)
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
""" """
Verify stop_loss against stoploss-order value (limit or price) Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary. Returns True if adjustment is necessary.
""" """
# TODO-mg: Short support
return order['type'] == 'stop' and stop_loss > float(order['price']) return order['type'] == 'stop' and stop_loss > float(order['price'])
@retrier(retries=0) @retrier(retries=0)
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: def stoploss(self, pair: str, amount: float,
stop_price: float, order_types: Dict, side: str) -> Dict:
""" """
Creates a stoploss order. Creates a stoploss order.
depending on order_types.stoploss configuration, uses 'market' or limit order. depending on order_types.stoploss configuration, uses 'market' or limit order.
Limit orders are defined by having orderPrice set, otherwise a market order is used. Limit orders are defined by having orderPrice set, otherwise a market order is used.
""" """
# TODO-mg: Short support
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
limit_rate = stop_price * limit_price_pct limit_rate = stop_price * limit_price_pct

View File

@ -67,20 +67,23 @@ class Kraken(Exchange):
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
""" """
Verify stop_loss against stoploss-order value (limit or price) Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary. Returns True if adjustment is necessary.
""" """
# TODO-mg: Short support
return (order['type'] in ('stop-loss', 'stop-loss-limit') return (order['type'] in ('stop-loss', 'stop-loss-limit')
and stop_loss > float(order['price'])) and stop_loss > float(order['price']))
@retrier(retries=0) @retrier(retries=0)
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: def stoploss(self, pair: str, amount: float,
stop_price: float, order_types: Dict, side: str) -> Dict:
""" """
Creates a stoploss market order. Creates a stoploss market order.
Stoploss market orders is the only stoploss type supported by kraken. Stoploss market orders is the only stoploss type supported by kraken.
""" """
# TODO-mg: Short support
params = self._params.copy() params = self._params.copy()
if order_types.get('stoploss', 'market') == 'limit': if order_types.get('stoploss', 'market') == 'limit':

View File

@ -728,9 +728,13 @@ class FreqtradeBot(LoggingMixin):
:return: True if the order succeeded, and False in case of problems. :return: True if the order succeeded, and False in case of problems.
""" """
try: try:
stoploss_order = self.exchange.stoploss(pair=trade.pair, amount=trade.amount, stoploss_order = self.exchange.stoploss(
pair=trade.pair,
amount=trade.amount,
stop_price=stop_price, stop_price=stop_price,
order_types=self.strategy.order_types) order_types=self.strategy.order_types,
side=trade.exit_side
)
order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss') order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss')
trade.orders.append(order_obj) trade.orders.append(order_obj)
@ -819,11 +823,11 @@ class FreqtradeBot(LoggingMixin):
# if trailing stoploss is enabled we check if stoploss value has changed # if trailing stoploss is enabled we check if stoploss value has changed
# in which case we cancel stoploss order and put another one with new # in which case we cancel stoploss order and put another one with new
# value immediately # value immediately
self.handle_trailing_stoploss_on_exchange(trade, stoploss_order) self.handle_trailing_stoploss_on_exchange(trade, stoploss_order, side=trade.exit_side)
return False return False
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict) -> None: def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict, side: str) -> None:
""" """
Check to see if stoploss on exchange should be updated Check to see if stoploss on exchange should be updated
in case of trailing stoploss on exchange in case of trailing stoploss on exchange
@ -831,7 +835,7 @@ class FreqtradeBot(LoggingMixin):
:param order: Current on exchange stoploss order :param order: Current on exchange stoploss order
:return: None :return: None
""" """
if self.exchange.stoploss_adjust(trade.stop_loss, order): if self.exchange.stoploss_adjust(trade.stop_loss, order, side):
# we check if the update is necessary # we check if the update is necessary
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat:

View File

@ -32,12 +32,13 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell",
order_types={'stoploss_on_exchange_limit_ratio': 1.05}) order_types={'stoploss_on_exchange_limit_ratio': 1.05})
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types) order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types=order_types, side="sell")
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -54,17 +55,17 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock( api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) 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(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
"stoploss", "create_order", retries=1, "stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={}) pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
def test_stoploss_order_dry_run_binance(default_conf, mocker): def test_stoploss_order_dry_run_binance(default_conf, mocker):
@ -77,12 +78,12 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell",
order_types={'stoploss_on_exchange_limit_ratio': 1.05}) order_types={'stoploss_on_exchange_limit_ratio': 1.05})
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -100,8 +101,8 @@ def test_stoploss_adjust_binance(mocker, default_conf):
'price': 1500, 'price': 1500,
'info': {'stopPrice': 1500}, 'info': {'stopPrice': 1500},
} }
assert exchange.stoploss_adjust(1501, order) assert exchange.stoploss_adjust(1501, order, side="sell")
assert not exchange.stoploss_adjust(1499, order) assert not exchange.stoploss_adjust(1499, order, side="sell")
# Test with invalid order case # Test with invalid order case
order['type'] = 'stop_loss' order['type'] = 'stop_loss'
assert not exchange.stoploss_adjust(1501, order) assert not exchange.stoploss_adjust(1501, order, side="sell")

View File

@ -2581,10 +2581,10 @@ def test_get_fee(default_conf, mocker, exchange_name):
def test_stoploss_order_unsupported_exchange(default_conf, mocker): def test_stoploss_order_unsupported_exchange(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='bittrex') exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
exchange.stoploss_adjust(1, {}) exchange.stoploss_adjust(1, {}, side="sell")
def test_merge_ft_has_dict(default_conf, mocker): def test_merge_ft_has_dict(default_conf, mocker):

View File

@ -32,7 +32,7 @@ def test_stoploss_order_ftx(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
# stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders # stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell",
order_types={'stoploss_on_exchange_limit_ratio': 1.05}) order_types={'stoploss_on_exchange_limit_ratio': 1.05})
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
@ -47,7 +47,7 @@ def test_stoploss_order_ftx(default_conf, mocker):
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -61,7 +61,7 @@ def test_stoploss_order_ftx(default_conf, mocker):
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={'stoploss': 'limit'}) order_types={'stoploss': 'limit'}, side="sell")
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -78,17 +78,17 @@ def test_stoploss_order_ftx(default_conf, mocker):
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock( api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately.")) side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
"stoploss", "create_order", retries=1, "stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={}) pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
def test_stoploss_order_dry_run_ftx(default_conf, mocker): def test_stoploss_order_dry_run_ftx(default_conf, mocker):
@ -101,7 +101,7 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker):
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -118,11 +118,11 @@ def test_stoploss_adjust_ftx(mocker, default_conf):
'type': STOPLOSS_ORDERTYPE, 'type': STOPLOSS_ORDERTYPE,
'price': 1500, 'price': 1500,
} }
assert exchange.stoploss_adjust(1501, order) assert exchange.stoploss_adjust(1501, order, side="sell")
assert not exchange.stoploss_adjust(1499, order) assert not exchange.stoploss_adjust(1499, order, side="sell")
# Test with invalid order case ... # Test with invalid order case ...
order['type'] = 'stop_loss_limit' order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(1501, order) assert not exchange.stoploss_adjust(1501, order, side="sell")
def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order):

View File

@ -183,7 +183,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, side="sell",
order_types={'stoploss': ordertype, order_types={'stoploss': ordertype,
'stoploss_on_exchange_limit_ratio': 0.99 'stoploss_on_exchange_limit_ratio': 0.99
}) })
@ -208,17 +208,17 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype):
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock( api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately.")) side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
"stoploss", "create_order", retries=1, "stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={}) pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
def test_stoploss_order_dry_run_kraken(default_conf, mocker): def test_stoploss_order_dry_run_kraken(default_conf, mocker):
@ -231,7 +231,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker):
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -248,8 +248,8 @@ def test_stoploss_adjust_kraken(mocker, default_conf):
'type': STOPLOSS_ORDERTYPE, 'type': STOPLOSS_ORDERTYPE,
'price': 1500, 'price': 1500,
} }
assert exchange.stoploss_adjust(1501, order) assert exchange.stoploss_adjust(1501, order, side="sell")
assert not exchange.stoploss_adjust(1499, order) assert not exchange.stoploss_adjust(1499, order, side="sell")
# Test with invalid order case ... # Test with invalid order case ...
order['type'] = 'stop_loss_limit' order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(1501, order) assert not exchange.stoploss_adjust(1501, order, side="sell")

View File

@ -1343,10 +1343,13 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee,
assert freqtrade.handle_stoploss_on_exchange(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') cancel_order_mock.assert_called_once_with(100, 'ETH/BTC')
stoploss_order_mock.assert_called_once_with(amount=85.32423208, stoploss_order_mock.assert_called_once_with(
amount=85.32423208,
pair='ETH/BTC', pair='ETH/BTC',
order_types=freqtrade.strategy.order_types, order_types=freqtrade.strategy.order_types,
stop_price=0.00002346 * 0.95) stop_price=0.00002346 * 0.95,
side="sell"
)
# price fell below stoploss, so dry-run sells trade. # price fell below stoploss, so dry-run sells trade.
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
@ -1417,7 +1420,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
side_effect=InvalidOrderException()) side_effect=InvalidOrderException())
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order',
return_value=stoploss_order_hanging) return_value=stoploss_order_hanging)
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="buy")
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog) assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog)
# Still try to create order # Still try to create order
@ -1427,7 +1430,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
caplog.clear() caplog.clear()
cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock()) cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock())
mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError()) mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError())
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging, side="buy")
assert cancel_mock.call_count == 1 assert cancel_mock.call_count == 1
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/BTC\..*", caplog) assert log_has_re(r"Could not create trailing stoploss order for pair ETH/BTC\..*", caplog)
@ -1526,10 +1529,13 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee,
assert freqtrade.handle_stoploss_on_exchange(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') cancel_order_mock.assert_called_once_with(100, 'ETH/BTC')
stoploss_order_mock.assert_called_once_with(amount=85.32423208, stoploss_order_mock.assert_called_once_with(
amount=85.32423208,
pair='ETH/BTC', pair='ETH/BTC',
order_types=freqtrade.strategy.order_types, order_types=freqtrade.strategy.order_types,
stop_price=0.00002346 * 0.96) stop_price=0.00002346 * 0.96,
side="sell"
)
# price fell below stoploss, so dry-run sells trade. # price fell below stoploss, so dry-run sells trade.
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
@ -1647,10 +1653,13 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
# stoploss should be set to 1% as trailing is on # stoploss should be set to 1% as trailing is on
assert trade.stop_loss == 0.00002346 * 0.99 assert trade.stop_loss == 0.00002346 * 0.99
cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') cancel_order_mock.assert_called_once_with(100, 'NEO/BTC')
stoploss_order_mock.assert_called_once_with(amount=2132892.49146757, stoploss_order_mock.assert_called_once_with(
amount=2132892.49146757,
pair='NEO/BTC', pair='NEO/BTC',
order_types=freqtrade.strategy.order_types, order_types=freqtrade.strategy.order_types,
stop_price=0.00002346 * 0.99) stop_price=0.00002346 * 0.99,
side="sell"
)
def test_enter_positions(mocker, default_conf, caplog) -> None: def test_enter_positions(mocker, default_conf, caplog) -> None: