Merge pull request #9267 from Axel-CH/feature/update_sl_order_mgt

Update stoploss order management
This commit is contained in:
Matthias
2024-02-02 06:43:02 +01:00
committed by GitHub
14 changed files with 241 additions and 189 deletions
-1
View File
@@ -266,7 +266,6 @@ def mock_trade_5(fee, is_short: bool):
exchange='binance',
strategy='SampleStrategy',
enter_tag='TEST1',
stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455',
timeframe=5,
is_short=is_short,
stop_loss_pct=0.10,
-1
View File
@@ -282,7 +282,6 @@ def mock_trade_usdt_5(fee, is_short: bool):
open_rate=2.0,
exchange='binance',
strategy='SampleStrategy',
stoploss_order_id=f'prod_stoploss_3455_{direc(is_short)}',
timeframe=5,
is_short=is_short,
)
+3 -4
View File
@@ -74,7 +74,7 @@ def test_init_dryrun_db(default_conf, tmpdir):
assert Path(filename).is_file()
def test_migrate_new(mocker, default_conf, fee, caplog):
def test_migrate(mocker, default_conf, fee, caplog):
"""
Test Database migration (starting with new pairformat)
"""
@@ -277,8 +277,6 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert trade.exit_reason is None
assert trade.strategy is None
assert trade.timeframe == '5m'
assert trade.stoploss_order_id == 'dry_stop_order_id222'
assert trade.stoploss_last_update is None
assert log_has("trying trades_bak1", caplog)
assert log_has("trying trades_bak2", caplog)
assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0",
@@ -294,9 +292,10 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert orders[0].order_id == 'dry_buy_order'
assert orders[0].ft_order_side == 'buy'
# All dry-run stoploss orders will be closed
assert orders[-1].order_id == 'dry_stop_order_id222'
assert orders[-1].ft_order_side == 'stoploss'
assert orders[-1].ft_is_open is True
assert orders[-1].ft_is_open is False
assert orders[1].order_id == 'dry_buy_order22'
assert orders[1].ft_order_side == 'buy'
-2
View File
@@ -1432,7 +1432,6 @@ def test_to_json(fee):
'stop_loss_abs': None,
'stop_loss_ratio': None,
'stop_loss_pct': None,
'stoploss_order_id': None,
'stoploss_last_update': None,
'stoploss_last_update_timestamp': None,
'initial_stop_loss_abs': None,
@@ -1500,7 +1499,6 @@ def test_to_json(fee):
'stop_loss_abs': None,
'stop_loss_pct': None,
'stop_loss_ratio': None,
'stoploss_order_id': None,
'stoploss_last_update': None,
'stoploss_last_update_timestamp': None,
'initial_stop_loss_abs': None,
-1
View File
@@ -54,7 +54,6 @@ def test_trade_fromjson():
"stop_loss_abs": 0.1981,
"stop_loss_ratio": -0.216,
"stop_loss_pct": -21.6,
"stoploss_order_id": null,
"stoploss_last_update": "2022-10-18 09:13:42",
"stoploss_last_update_timestamp": 1666077222000,
"initial_stop_loss_abs": 0.1981,
-2
View File
@@ -63,7 +63,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stop_loss_abs': 9.89e-06,
'stop_loss_pct': -10.0,
'stop_loss_ratio': -0.1,
'stoploss_order_id': None,
'stoploss_last_update': ANY,
'stoploss_last_update_timestamp': ANY,
'initial_stop_loss_abs': 9.89e-06,
@@ -355,7 +354,6 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
rpc._rpc_delete('200')
trades = Trade.session.scalars(select(Trade)).all()
trades[2].stoploss_order_id = '102'
trades[2].orders.append(
Order(
ft_order_side='stoploss',
-2
View File
@@ -1174,7 +1174,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
'stop_loss_abs': ANY,
'stop_loss_pct': ANY,
'stop_loss_ratio': ANY,
'stoploss_order_id': None,
'stoploss_last_update': ANY,
'stoploss_last_update_timestamp': ANY,
'initial_stop_loss_abs': 0.0,
@@ -1378,7 +1377,6 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
'stop_loss_abs': None,
'stop_loss_pct': None,
'stop_loss_ratio': None,
'stoploss_order_id': None,
'stoploss_last_update': None,
'stoploss_last_update_timestamp': None,
'initial_stop_loss_abs': None,
+117 -75
View File
@@ -1120,12 +1120,11 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
freqtrade.enter_positions()
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.stoploss_order_id = None
trade.is_open = True
trades = [trade]
freqtrade.exit_positions(trades)
assert trade.stoploss_order_id == '13434334'
assert trade.has_open_sl_orders is True
assert stoploss.call_count == 1
assert trade.is_open is True
@@ -1164,11 +1163,11 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
trade = Trade.session.scalars(select(Trade)).first()
assert trade.is_short == is_short
assert trade.is_open
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
assert trade.stoploss_order_id == "13434334"
assert trade.open_sl_orders[-1].order_id == "13434334"
# Second case: when stoploss is set but it is not yet hit
# should do nothing and return false
@@ -1179,7 +1178,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
assert freqtrade.handle_stoploss_on_exchange(trade) is False
hanging_stoploss_order.assert_called_once_with('13434334', trade.pair)
assert trade.stoploss_order_id == "13434334"
assert len(trade.open_sl_orders) == 1
assert trade.open_sl_orders[-1].order_id == "13434334"
# Third case: when stoploss was set but it was canceled for some reason
# should set a stoploss immediately and return False
@@ -1195,12 +1195,12 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
assert trade.stoploss_order_id == "103_1"
assert len(trade.open_sl_orders) == 1
assert trade.open_sl_orders[-1].order_id == "103_1"
assert trade.amount == amount_before
# Fourth case: when stoploss is set and it is hit
# should unset stoploss_order_id and return true
# as a trade actually happened
# should return true as a trade actually happened
caplog.clear()
stop_order_dict.update({'id': "103_1"})
@@ -1221,7 +1221,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
assert freqtrade.handle_stoploss_on_exchange(trade) is True
assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog)
assert trade.stoploss_order_id is None
assert len(trade.open_sl_orders) == 0
assert trade.is_open is False
caplog.clear()
@@ -1229,26 +1229,27 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
trade.is_open = True
freqtrade.handle_stoploss_on_exchange(trade)
assert log_has('Unable to place a stoploss order on exchange.', caplog)
assert trade.stoploss_order_id is None
assert len(trade.open_sl_orders) == 0
# Fifth case: fetch_order returns InvalidOrder
# It should try to add stoploss order
stop_order_dict.update({'id': "105"})
trade.stoploss_order_id = "105"
stoploss.reset_mock()
mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=InvalidOrderException())
mocker.patch(f'{EXMS}.create_stoploss', stoploss)
freqtrade.handle_stoploss_on_exchange(trade)
assert len(trade.open_sl_orders) == 1
assert stoploss.call_count == 1
# Sixth case: Closed Trade
# Should not create new order
trade.stoploss_order_id = None
trade.is_open = False
trade.open_sl_orders[-1].ft_is_open = False
stoploss.reset_mock()
mocker.patch(f'{EXMS}.fetch_order')
mocker.patch(f'{EXMS}.create_stoploss', stoploss)
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert trade.has_open_sl_orders is False
assert stoploss.call_count == 0
@@ -1282,7 +1283,7 @@ def test_handle_stoploss_on_exchange_emergency(mocker, default_conf_usdt, fee, i
trade = Trade.session.scalars(select(Trade)).first()
assert trade.is_short == is_short
assert trade.is_open
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
# emergency exit triggered
# Trailing stop should not act anymore
@@ -1297,7 +1298,6 @@ def test_handle_stoploss_on_exchange_emergency(mocker, default_conf_usdt, fee, i
'remaining': enter_order['amount'],
'info': {'stopPrice': 22},
}])
trade.stoploss_order_id = "107"
trade.stoploss_last_update = dt_now() - timedelta(hours=1)
trade.stop_loss = 24
trade.exit_reason = None
@@ -1314,14 +1314,14 @@ def test_handle_stoploss_on_exchange_emergency(mocker, default_conf_usdt, fee, i
)
freqtrade.config['trailing_stop'] = True
stoploss = MagicMock(side_effect=InvalidOrderException())
assert trade.has_open_sl_orders is True
Trade.commit()
mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result',
side_effect=InvalidOrderException())
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_cancelled)
mocker.patch(f'{EXMS}.create_stoploss', stoploss)
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
assert trade.is_open is False
assert trade.exit_reason == str(ExitType.EMERGENCY_EXIT)
@@ -1356,11 +1356,11 @@ def test_handle_stoploss_on_exchange_partial(
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.stoploss_order_id = None
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
assert trade.stoploss_order_id == "101"
assert trade.has_open_sl_orders is True
assert trade.open_sl_orders[-1].order_id == "101"
assert trade.amount == 30
stop_order_dict.update({'id': "102"})
# Stoploss on exchange is cancelled on exchange, but filled partially.
@@ -1380,13 +1380,14 @@ def test_handle_stoploss_on_exchange_partial(
# Stoploss filled partially ...
assert trade.amount == 15
assert trade.stoploss_order_id == "102"
assert trade.open_sl_orders[-1].order_id == "102"
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange_partial_cancel_here(
mocker, default_conf_usdt, fee, is_short, limit_order, caplog) -> None:
mocker, default_conf_usdt, fee, is_short, limit_order, caplog, time_machine) -> None:
stop_order_dict = {'id': "101", "status": "open"}
time_machine.move_to(dt_now())
default_conf_usdt['trailing_stop'] = True
stoploss = MagicMock(return_value=stop_order_dict)
enter_order = limit_order[entry_side(is_short)]
@@ -1414,11 +1415,11 @@ def test_handle_stoploss_on_exchange_partial_cancel_here(
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.stoploss_order_id = None
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
assert trade.stoploss_order_id == "101"
assert trade.has_open_sl_orders is True
assert trade.open_sl_orders[-1].order_id == "101"
assert trade.amount == 30
stop_order_dict.update({'id': "102"})
# Stoploss on exchange is open.
@@ -1445,13 +1446,14 @@ def test_handle_stoploss_on_exchange_partial_cancel_here(
})
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', stoploss_order_cancel)
trade.stoploss_last_update = dt_now() - timedelta(minutes=10)
time_machine.shift(timedelta(minutes=15))
assert freqtrade.handle_stoploss_on_exchange(trade) is False
# Canceled Stoploss filled partially ...
assert log_has_re('Cancelling current stoploss on exchange.*', caplog)
assert trade.stoploss_order_id == "102"
assert trade.has_open_sl_orders is True
assert trade.open_sl_orders[-1].order_id == "102"
assert trade.amount == 15
@@ -1478,7 +1480,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
)
mocker.patch.multiple(
EXMS,
fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}),
fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': '100'}),
create_stoploss=MagicMock(side_effect=ExchangeError()),
)
freqtrade = FreqtradeBot(default_conf_usdt)
@@ -1488,7 +1490,6 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
trade = Trade.session.scalars(select(Trade)).first()
assert trade.is_short == is_short
trade.is_open = True
trade.stoploss_order_id = "100"
trade.orders.append(
Order(
ft_order_side='stoploss',
@@ -1503,8 +1504,8 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
assert trade
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert log_has_re(r'Stoploss order was cancelled, but unable to recreate one.*', caplog)
assert trade.stoploss_order_id is None
assert log_has_re(r'All Stoploss orders are cancelled, but unable to recreate one\.', caplog)
assert trade.has_open_sl_orders is False
assert trade.is_open is True
@@ -1545,7 +1546,7 @@ def test_create_stoploss_order_invalid_order(
caplog.clear()
rpc_mock.reset_mock()
freqtrade.create_stoploss_order(trade, 200)
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
assert trade.exit_reason == ExitType.EMERGENCY_EXIT.value
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
assert log_has("Exiting the trade forcefully", caplog)
@@ -1599,14 +1600,13 @@ def test_create_stoploss_order_insufficient_funds(
caplog.clear()
freqtrade.create_stoploss_order(trade, 200)
# stoploss_orderid was empty before
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
assert mock_insuf.call_count == 1
mock_insuf.reset_mock()
trade.stoploss_order_id = 'stoploss_orderid'
freqtrade.create_stoploss_order(trade, 200)
# No change to stoploss-orderid
assert trade.stoploss_order_id == 'stoploss_orderid'
assert trade.has_open_sl_orders is False
assert mock_insuf.call_count == 1
@@ -1668,7 +1668,7 @@ def test_handle_stoploss_on_exchange_trailing(
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.stoploss_order_id = '100'
assert trade.has_open_sl_orders is False
trade.stoploss_last_update = dt_now() - timedelta(minutes=20)
trade.orders.append(
Order(
@@ -1678,27 +1678,35 @@ def test_handle_stoploss_on_exchange_trailing(
ft_amount=trade.amount,
ft_price=trade.stop_loss,
order_id='100',
order_date=dt_now() - timedelta(minutes=20),
)
)
stoploss_order_hanging = MagicMock(return_value={
stoploss_order_hanging = {
'id': '100',
'status': 'open',
'type': 'stop_loss_limit',
'price': hang_price,
'average': 2,
'fee': {},
'amount': 0,
'info': {
'stopPrice': stop_price[0]
}
})
}
stoploss_order_cancel = deepcopy(stoploss_order_hanging)
stoploss_order_cancel['status'] = 'canceled'
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging)
mocker.patch(f'{EXMS}.fetch_stoploss_order', return_value=stoploss_order_hanging)
mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=stoploss_order_cancel)
# stoploss initially at 5%
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert trade.stoploss_order_id == '13434334'
assert len(trade.open_sl_orders) == 1
assert trade.open_sl_orders[-1].order_id == '13434334'
# price jumped 2x
mocker.patch(
@@ -1710,14 +1718,17 @@ def test_handle_stoploss_on_exchange_trailing(
})
)
cancel_order_mock = MagicMock()
cancel_order_mock = MagicMock(return_value={
'id': '13434334', 'status': 'canceled', 'fee': {}, 'amount': trade.amount})
stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'})
mocker.patch(f'{EXMS}.fetch_stoploss_order')
mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
# stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert len(trade.open_sl_orders) == 1
cancel_order_mock.assert_not_called()
stoploss_order_mock.assert_not_called()
@@ -1748,14 +1759,21 @@ def test_handle_stoploss_on_exchange_trailing(
'last': bid[1],
})
)
mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result',
return_value={'id': 'so1', 'status': 'canceled'})
assert len(trade.open_sl_orders) == 1
assert trade.open_sl_orders[-1].order_id == 'so1'
assert freqtrade.handle_trade(trade) is True
assert trade.stoploss_order_id is None
assert trade.is_open is False
assert trade.has_open_sl_orders is False
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange_trailing_error(
mocker, default_conf_usdt, fee, caplog, limit_order, is_short
mocker, default_conf_usdt, fee, caplog, limit_order, is_short, time_machine
) -> None:
time_machine.move_to(dt_now() - timedelta(minutes=601))
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
# When trailing stoploss is set
@@ -1774,9 +1792,6 @@ def test_handle_stoploss_on_exchange_trailing_error(
{'id': exit_order['id']},
]),
get_fee=fee,
)
mocker.patch.multiple(
EXMS,
create_stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True),
)
@@ -1798,10 +1813,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.stoploss_order_id = "abcd"
trade.stop_loss = 0.2
trade.stoploss_last_update = (dt_now() - timedelta(minutes=601)).replace(tzinfo=None)
trade.is_short = is_short
stoploss_order_hanging = {
'id': "abcd",
@@ -1813,23 +1825,37 @@ def test_handle_stoploss_on_exchange_trailing_error(
'stopPrice': '0.1'
}
}
trade.orders.append(
Order(
ft_order_side='stoploss',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
ft_price=3,
order_id='abcd',
order_date=dt_now(),
)
)
mocker.patch(f'{EXMS}.cancel_stoploss_order',
side_effect=InvalidOrderException())
mocker.patch(f'{EXMS}.fetch_stoploss_order',
return_value=stoploss_order_hanging)
time_machine.shift(timedelta(minutes=50))
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog)
# Still try to create order
assert stoploss.call_count == 1
# TODO: Is this actually correct ? This will create a new order every time,
assert len(trade.open_sl_orders) == 2
# Fail creating stoploss order
trade.stoploss_last_update = dt_now() - timedelta(minutes=601)
caplog.clear()
cancel_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order')
mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError())
time_machine.shift(timedelta(minutes=50))
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert cancel_mock.call_count == 1
assert cancel_mock.call_count == 2
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog)
@@ -1850,7 +1876,6 @@ def test_stoploss_on_exchange_price_rounding(
price_to_precision=price_mock,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
open_trade_usdt.stoploss_order_id = '13434334'
open_trade_usdt.stop_loss = 222.55
freqtrade.handle_trailing_stoploss_on_exchange(open_trade_usdt, {})
@@ -1881,6 +1906,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
exit_order,
]),
get_fee=fee,
is_cancel_order_result_suitable=MagicMock(return_value=True),
)
mocker.patch.multiple(
EXMS,
@@ -1911,8 +1937,6 @@ def test_handle_stoploss_on_exchange_custom_stop(
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.stoploss_order_id = '100'
trade.stoploss_last_update = dt_now() - timedelta(minutes=601)
trade.orders.append(
Order(
ft_order_side='stoploss',
@@ -1920,11 +1944,12 @@ def test_handle_stoploss_on_exchange_custom_stop(
ft_is_open=True,
ft_amount=trade.amount,
ft_price=trade.stop_loss,
order_date=dt_now() - timedelta(minutes=601),
order_id='100',
)
)
stoploss_order_hanging = MagicMock(return_value={
Trade.commit()
slo = {
'id': '100',
'status': 'open',
'type': 'stop_loss_limit',
@@ -1933,9 +1958,17 @@ def test_handle_stoploss_on_exchange_custom_stop(
'info': {
'stopPrice': '2.0805'
}
})
}
slo_canceled = deepcopy(slo)
slo_canceled.update({'status': 'canceled'})
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging)
def fetch_stoploss_order_mock(order_id, *args, **kwargs):
x = deepcopy(slo)
x['id'] = order_id
return x
mocker.patch(f'{EXMS}.fetch_stoploss_order', MagicMock(fetch_stoploss_order_mock))
mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=slo_canceled)
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
@@ -1954,7 +1987,6 @@ def test_handle_stoploss_on_exchange_custom_stop(
stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'})
mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
trade.stoploss_order_id = '100'
# stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False
@@ -1968,10 +2000,12 @@ def test_handle_stoploss_on_exchange_custom_stop(
# setting stoploss_on_exchange_interval to 0 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
cancel_order_mock.assert_not_called()
stoploss_order_mock.assert_not_called()
assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_called_once_with('100', 'ETH/USDT')
cancel_order_mock.assert_called_once_with('13434334', 'ETH/USDT')
# Long uses modified ask - offset, short modified bid + offset
stoploss_order_mock.assert_called_once_with(
amount=pytest.approx(trade.amount),
@@ -2048,7 +2082,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
freqtrade.enter_positions()
trade = Trade.session.scalars(select(Trade)).first()
trade.is_open = True
trade.stoploss_order_id = '100'
trade.stoploss_last_update = dt_now()
trade.orders.append(
Order(
@@ -4054,7 +4088,17 @@ def test_execute_trade_exit_sloe_cancel_exception(
PairLock.session = MagicMock()
freqtrade.config['dry_run'] = False
trade.stoploss_order_id = "abcd"
trade.orders.append(
Order(
ft_order_side='stoploss',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
ft_price=trade.stop_loss,
order_id='abcd',
status='open',
)
)
freqtrade.execute_trade_exit(trade=trade, limit=1234,
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
@@ -4157,16 +4201,15 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
freqtrade.manage_open_orders()
trade = Trade.session.scalars(select(Trade)).first()
trades = [trade]
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
freqtrade.exit_positions(trades)
assert trade
assert trade.stoploss_order_id == '123'
assert trade.has_open_sl_orders is True
assert not trade.has_open_orders
# Assuming stoploss on exchange is hit
# stoploss_order_id should become None
# and trade should be sold at the price of stoploss
# trade should be sold at the price of stoploss, with exit_reaeon STOPLOSS_ON_EXCHANGE
stoploss_executed = MagicMock(return_value={
"id": "123",
"timestamp": 1542707426845,
@@ -4188,7 +4231,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_executed)
freqtrade.exit_positions(trades)
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
assert trade.is_open is False
assert trade.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value
assert rpc_mock.call_count == 4
@@ -5693,7 +5736,6 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
def reset_open_orders(trade):
trade.stoploss_order_id = None
trade.is_short = is_short
create_mock_trades(fee, is_short=is_short)
@@ -5705,7 +5747,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
trade = trades[1]
reset_open_orders(trade)
assert not trade.has_open_orders
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
freqtrade.handle_insufficient_funds(trade)
order = trade.orders[0]
@@ -5715,7 +5757,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
assert mock_uts.call_count == 0
# No change to orderid - as update_trade_state is mocked
assert not trade.has_open_orders
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
caplog.clear()
mock_fo.reset_mock()
@@ -5726,7 +5768,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
# This part in not relevant anymore
# assert not trade.has_open_orders
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
freqtrade.handle_insufficient_funds(trade)
order = mock_order_4(is_short=is_short)
@@ -5734,8 +5776,8 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
assert mock_fo.call_count == 1
assert mock_uts.call_count == 1
# Found open buy order
assert trade.has_open_orders
assert trade.stoploss_order_id is None
assert trade.has_open_orders is True
assert trade.has_open_sl_orders is False
caplog.clear()
mock_fo.reset_mock()
@@ -5744,16 +5786,16 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
trade = trades[4]
reset_open_orders(trade)
assert not trade.has_open_orders
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders
freqtrade.handle_insufficient_funds(trade)
order = mock_order_5_stoploss(is_short=is_short)
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 1
assert mock_uts.call_count == 2
# stoploss_order_id is "refound" and added to the trade
# stoploss order is "refound" and added to the trade
assert not trade.has_open_orders
assert trade.stoploss_order_id is not None
assert trade.has_open_sl_orders is True
caplog.clear()
mock_fo.reset_mock()
@@ -5764,7 +5806,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
reset_open_orders(trade)
# This part in not relevant anymore
# assert not trade.has_open_orders
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
freqtrade.handle_insufficient_funds(trade)
order = mock_order_6_sell(is_short=is_short)
@@ -5773,7 +5815,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
assert mock_uts.call_count == 1
# sell-orderid is "refound" and added to the trade
assert trade.open_orders_ids[0] == order['id']
assert trade.stoploss_order_id is None
assert trade.has_open_sl_orders is False
caplog.clear()
+4 -2
View File
@@ -49,7 +49,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
stoploss_order_closed['filled'] = stoploss_order_closed['amount']
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
stop_orders = [stoploss_order_closed, stoploss_order_open, stoploss_order_open]
stop_orders = [stoploss_order_closed, stoploss_order_open.copy(), stoploss_order_open.copy()]
stoploss_order_mock = MagicMock(
side_effect=stop_orders)
# Sell 3rd trade (not called for the first trade)
@@ -100,9 +100,10 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
stop_order = stop_orders[idx]
stop_order['id'] = f"stop{idx}"
oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss')
oobj.ft_is_open = True
trade.orders.append(oobj)
trade.stoploss_order_id = f"stop{idx}"
assert len(trade.open_sl_orders) == 1
n = freqtrade.exit_positions(trades)
assert n == 2
@@ -113,6 +114,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
# Only order for 3rd trade needs to be cancelled
assert cancel_order_mock.call_count == 1
assert stoploss_order_mock.call_count == 3
# Wallets must be updated between stoploss cancellation and selling, and will be updated again
# during update_trade_state
assert wallets_mock.call_count == 4