Merge pull request #6692 from eSeR1805/feat_readjust_entry
Feature: Readjust Entry Order
This commit is contained in:
@@ -40,6 +40,8 @@ class BTContainer(NamedTuple):
|
||||
custom_entry_price: Optional[float] = None
|
||||
custom_exit_price: Optional[float] = None
|
||||
leverage: float = 1.0
|
||||
timeout: Optional[int] = None
|
||||
adjust_entry_price: Optional[float] = None
|
||||
|
||||
|
||||
def _get_frame_time_from_offset(offset):
|
||||
|
@@ -754,6 +754,62 @@ tc47 = BTContainer(data=[
|
||||
trades=[]
|
||||
)
|
||||
|
||||
# Test 48: Custom-entry-price below all candles - readjust order
|
||||
tc48 = BTContainer(data=[
|
||||
# D O H L C V EL XL ES Xs BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # timeout
|
||||
[2, 4900, 5250, 4500, 5100, 6172, 0, 0], # Order readjust
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 1],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.087,
|
||||
use_exit_signal=True, timeout=1000,
|
||||
custom_entry_price=4200, adjust_entry_price=5200,
|
||||
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4, is_short=False)]
|
||||
)
|
||||
|
||||
|
||||
# Test 49: Custom-entry-price short above all candles - readjust order
|
||||
tc49 = BTContainer(data=[
|
||||
# D O H L C V EL XL ES Xs BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
|
||||
[1, 5000, 5200, 4951, 5000, 6172, 0, 0, 0, 0], # timeout
|
||||
[2, 4900, 5250, 4900, 5100, 6172, 0, 0, 0, 0], # Order readjust
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 1],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0]],
|
||||
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.05,
|
||||
use_exit_signal=True, timeout=1000,
|
||||
custom_entry_price=5300, adjust_entry_price=5000,
|
||||
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4, is_short=True)]
|
||||
)
|
||||
|
||||
# Test 50: Custom-entry-price below all candles - readjust order cancels order
|
||||
tc50 = BTContainer(data=[
|
||||
# D O H L C V EL XL ES Xs BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0], # Enter long - place order
|
||||
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # Order readjust - cancel order
|
||||
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.0,
|
||||
use_exit_signal=True, timeout=1000,
|
||||
custom_entry_price=4200, adjust_entry_price=None,
|
||||
trades=[]
|
||||
)
|
||||
|
||||
# Test 51: Custom-entry-price below all candles - readjust order leaves order in place and timeout.
|
||||
tc51 = BTContainer(data=[
|
||||
# D O H L C V EL XL ES Xs BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0], # Enter long - place order
|
||||
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # Order readjust - replace order
|
||||
[2, 4900, 5250, 4500, 5100, 6172, 0, 0], # Order readjust - maintain order
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0], # Timeout
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.0,
|
||||
use_exit_signal=True, timeout=60,
|
||||
custom_entry_price=4200, adjust_entry_price=4100,
|
||||
trades=[]
|
||||
)
|
||||
|
||||
TESTS = [
|
||||
tc0,
|
||||
@@ -804,6 +860,10 @@ TESTS = [
|
||||
tc45,
|
||||
tc46,
|
||||
tc47,
|
||||
tc48,
|
||||
tc49,
|
||||
tc50,
|
||||
tc51,
|
||||
]
|
||||
|
||||
|
||||
@@ -817,6 +877,11 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
|
||||
default_conf["timeframe"] = tests_timeframe
|
||||
default_conf["trailing_stop"] = data.trailing_stop
|
||||
default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached
|
||||
if data.timeout:
|
||||
default_conf['unfilledtimeout'].update({
|
||||
'entry': data.timeout,
|
||||
'exit': data.timeout,
|
||||
})
|
||||
# Only add this to configuration If it's necessary
|
||||
if data.trailing_stop_positive is not None:
|
||||
default_conf["trailing_stop_positive"] = data.trailing_stop_positive
|
||||
@@ -840,6 +905,8 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
|
||||
backtesting.strategy.custom_entry_price = MagicMock(return_value=data.custom_entry_price)
|
||||
if data.custom_exit_price:
|
||||
backtesting.strategy.custom_exit_price = MagicMock(return_value=data.custom_exit_price)
|
||||
backtesting.strategy.adjust_entry_price = MagicMock(return_value=data.adjust_entry_price)
|
||||
|
||||
backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss
|
||||
backtesting.strategy.leverage = lambda **kwargs: data.leverage
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
@@ -2362,7 +2362,7 @@ def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_check_handle_timedout_entry_usercustom(
|
||||
def test_manage_open_orders_entry_usercustom(
|
||||
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
|
||||
limit_sell_order_old, fee, mocker, is_short
|
||||
) -> None:
|
||||
@@ -2394,12 +2394,12 @@ def test_check_handle_timedout_entry_usercustom(
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# Ensure default is to return empty (so not mocked yet)
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
|
||||
# Return false - trade remains open
|
||||
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
nb_trades = len(trades)
|
||||
@@ -2407,7 +2407,7 @@ def test_check_handle_timedout_entry_usercustom(
|
||||
assert freqtrade.strategy.check_entry_timeout.call_count == 1
|
||||
freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError)
|
||||
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
nb_trades = len(trades)
|
||||
@@ -2416,7 +2416,7 @@ def test_check_handle_timedout_entry_usercustom(
|
||||
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True)
|
||||
|
||||
# Trade should be closed since the function returns true
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_wr_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
@@ -2426,7 +2426,7 @@ def test_check_handle_timedout_entry_usercustom(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_check_handle_timedout_entry(
|
||||
def test_manage_open_orders_entry(
|
||||
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
|
||||
limit_sell_order_old, fee, mocker, is_short
|
||||
) -> None:
|
||||
@@ -2450,8 +2450,9 @@ def test_check_handle_timedout_entry(
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234)
|
||||
# check it does cancel buy orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
@@ -2459,6 +2460,99 @@ def test_check_handle_timedout_entry(
|
||||
assert nb_trades == 0
|
||||
# Custom user buy-timeout is never called
|
||||
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||
# Entry adjustment is never called
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_adjust_entry_cancel(
|
||||
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
|
||||
limit_sell_order_old, fee, mocker, caplog, is_short
|
||||
) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
old_order = limit_sell_order_old if is_short else limit_buy_order_old
|
||||
old_order['id'] = open_trade.open_order_id
|
||||
limit_buy_cancel = deepcopy(old_order)
|
||||
limit_buy_cancel['status'] = 'canceled'
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker_usdt,
|
||||
fetch_order=MagicMock(return_value=old_order),
|
||||
cancel_order_with_result=cancel_order_mock,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
open_trade.is_short = is_short
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# Timeout to not interfere
|
||||
freqtrade.strategy.ft_check_timed_out = MagicMock(return_value=False)
|
||||
|
||||
# check that order is cancelled
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=None)
|
||||
freqtrade.manage_open_orders()
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
assert len(trades) == 0
|
||||
assert len(Order.query.all()) == 0
|
||||
assert log_has_re(
|
||||
f"{'Sell' if is_short else 'Buy'} order user requested order cancel*", caplog)
|
||||
assert log_has_re(
|
||||
f"{'Sell' if is_short else 'Buy'} order fully cancelled.*", caplog)
|
||||
|
||||
# Entry adjustment is called
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_adjust_entry_maintain_replace(
|
||||
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
|
||||
limit_sell_order_old, fee, mocker, caplog, is_short
|
||||
) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
old_order = limit_sell_order_old if is_short else limit_buy_order_old
|
||||
old_order['id'] = open_trade.open_order_id
|
||||
limit_buy_cancel = deepcopy(old_order)
|
||||
limit_buy_cancel['status'] = 'canceled'
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker_usdt,
|
||||
fetch_order=MagicMock(return_value=old_order),
|
||||
cancel_order_with_result=cancel_order_mock,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
open_trade.is_short = is_short
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# Timeout to not interfere
|
||||
freqtrade.strategy.ft_check_timed_out = MagicMock(return_value=False)
|
||||
|
||||
# Check that order is maintained
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=old_order['price'])
|
||||
freqtrade.manage_open_orders()
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
assert len(trades) == 1
|
||||
assert len(Order.get_open_orders()) == 1
|
||||
# Entry adjustment is called
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 1
|
||||
|
||||
# Check that order is replaced
|
||||
freqtrade.get_valid_enter_price_and_stake = MagicMock(return_value={100, 10, 1})
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234)
|
||||
freqtrade.manage_open_orders()
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
assert len(trades) == 1
|
||||
nb_all_orders = len(Order.query.all())
|
||||
assert nb_all_orders == 2
|
||||
# New order seems to be in closed status?
|
||||
# nb_open_orders = len(Order.get_open_orders())
|
||||
# assert nb_open_orders == 1
|
||||
assert log_has_re(
|
||||
f"{'Sell' if is_short else 'Buy'} order cancelled to be replaced*", caplog)
|
||||
# Entry adjustment is called
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -2484,18 +2578,17 @@ def test_check_handle_cancelled_buy(
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# check it does cancel buy orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 1
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 0
|
||||
assert len(trades) == 0
|
||||
assert log_has_re(
|
||||
f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_check_handle_timedout_buy_exception(
|
||||
def test_manage_open_orders_buy_exception(
|
||||
default_conf_usdt, ticker_usdt, open_trade, is_short, fee, mocker
|
||||
) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
@@ -2515,7 +2608,7 @@ def test_check_handle_timedout_buy_exception(
|
||||
Trade.query.session.add(open_trade)
|
||||
|
||||
# check it does cancel buy orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 0
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
@@ -2524,7 +2617,7 @@ def test_check_handle_timedout_buy_exception(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_check_handle_timedout_exit_usercustom(
|
||||
def test_manage_open_orders_exit_usercustom(
|
||||
default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
|
||||
is_short, open_trade_usdt, caplog
|
||||
) -> None:
|
||||
@@ -2553,13 +2646,13 @@ def test_check_handle_timedout_exit_usercustom(
|
||||
|
||||
Trade.query.session.add(open_trade_usdt)
|
||||
# Ensure default is false
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
|
||||
freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
|
||||
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
|
||||
# Return false - No impact
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 0
|
||||
assert open_trade_usdt.is_open is False
|
||||
@@ -2569,7 +2662,7 @@ def test_check_handle_timedout_exit_usercustom(
|
||||
freqtrade.strategy.check_exit_timeout = MagicMock(side_effect=KeyError)
|
||||
freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError)
|
||||
# Return Error - No impact
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 0
|
||||
assert open_trade_usdt.is_open is False
|
||||
@@ -2579,7 +2672,7 @@ def test_check_handle_timedout_exit_usercustom(
|
||||
# Return True - sells!
|
||||
freqtrade.strategy.check_exit_timeout = MagicMock(return_value=True)
|
||||
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True)
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
assert open_trade_usdt.is_open is True
|
||||
@@ -2592,7 +2685,7 @@ def test_check_handle_timedout_exit_usercustom(
|
||||
mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit',
|
||||
side_effect=DependencyException)
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert log_has_re('Unable to emergency sell .*', caplog)
|
||||
|
||||
et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit')
|
||||
@@ -2602,16 +2695,16 @@ def test_check_handle_timedout_exit_usercustom(
|
||||
|
||||
# If cancelling fails - no emergency sell!
|
||||
with patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit', return_value=False):
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert et_mock.call_count == 0
|
||||
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert log_has_re('Emergency exiting trade.*', caplog)
|
||||
assert et_mock.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_check_handle_timedout_exit(
|
||||
def test_manage_open_orders_exit(
|
||||
default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker, is_short, open_trade_usdt
|
||||
) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
@@ -2638,7 +2731,7 @@ def test_check_handle_timedout_exit(
|
||||
freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
|
||||
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
|
||||
# check it does cancel sell orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
assert open_trade_usdt.is_open is True
|
||||
@@ -2674,7 +2767,7 @@ def test_check_handle_cancelled_exit(
|
||||
Trade.query.session.add(open_trade_usdt)
|
||||
|
||||
# check it does cancel sell orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 1
|
||||
assert open_trade_usdt.is_open is True
|
||||
@@ -2684,7 +2777,7 @@ def test_check_handle_cancelled_exit(
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@pytest.mark.parametrize("leverage", [1, 3, 5, 10])
|
||||
def test_check_handle_timedout_partial(
|
||||
def test_manage_open_orders_partial(
|
||||
default_conf_usdt, ticker_usdt, limit_buy_order_old_partial, is_short, leverage,
|
||||
open_trade, mocker
|
||||
) -> None:
|
||||
@@ -2710,7 +2803,7 @@ def test_check_handle_timedout_partial(
|
||||
|
||||
# check it does cancel buy orders over the time limit
|
||||
# note this is for a partially-complete buy order
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 2
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
@@ -2721,7 +2814,7 @@ def test_check_handle_timedout_partial(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_check_handle_timedout_partial_fee(
|
||||
def test_manage_open_orders_partial_fee(
|
||||
default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
|
||||
limit_buy_order_old_partial, trades_for_order,
|
||||
limit_buy_order_old_partial_canceled, mocker
|
||||
@@ -2753,7 +2846,7 @@ def test_check_handle_timedout_partial_fee(
|
||||
Trade.query.session.add(open_trade)
|
||||
# cancelling a half-filled order should update the amount to the bought amount
|
||||
# and apply fees if necessary.
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
|
||||
assert log_has_re(r"Applying fee on amount for Trade.*", caplog)
|
||||
|
||||
@@ -2770,7 +2863,7 @@ def test_check_handle_timedout_partial_fee(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_check_handle_timedout_partial_except(
|
||||
def test_manage_open_orders_partial_except(
|
||||
default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
|
||||
limit_buy_order_old_partial, trades_for_order,
|
||||
limit_buy_order_old_partial_canceled, mocker
|
||||
@@ -2801,7 +2894,7 @@ def test_check_handle_timedout_partial_except(
|
||||
Trade.query.session.add(open_trade)
|
||||
# cancelling a half-filled order should update the amount to the bought amount
|
||||
# and apply fees if necessary.
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
|
||||
assert log_has_re(r"Could not update trade amount: .*", caplog)
|
||||
|
||||
@@ -2817,8 +2910,8 @@ def test_check_handle_timedout_partial_except(
|
||||
assert trades[0].fee_open == fee()
|
||||
|
||||
|
||||
def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_trade_usdt, mocker,
|
||||
caplog) -> None:
|
||||
def test_manage_open_orders_exception(default_conf_usdt, ticker_usdt, open_trade_usdt, mocker,
|
||||
caplog) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
@@ -2839,7 +2932,7 @@ def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_tr
|
||||
Trade.query.session.add(open_trade_usdt)
|
||||
|
||||
caplog.clear()
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
assert log_has_re(r"Cannot query order for Trade\(id=1, pair=ADA/USDT, amount=30.00000000, "
|
||||
r"is_short=False, leverage=1.0, "
|
||||
r"open_rate=2.00000000, open_since="
|
||||
@@ -3396,7 +3489,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
|
||||
assert trade
|
||||
trades = [trade]
|
||||
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
freqtrade.exit_positions(trades)
|
||||
|
||||
# Increase the price and sell it
|
||||
@@ -3448,7 +3541,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
|
||||
|
||||
# Create some test data
|
||||
freqtrade.enter_positions()
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
trade = Trade.query.first()
|
||||
trades = [trade]
|
||||
assert trade.stoploss_order_id is None
|
||||
@@ -5214,7 +5307,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
assert trade.stake_amount == 110
|
||||
assert not trade.fee_updated('buy')
|
||||
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
@@ -5320,7 +5413,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
MagicMock(return_value=closed_dca_order_1))
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||
MagicMock(return_value=closed_dca_order_1))
|
||||
freqtrade.check_handle_timedout()
|
||||
freqtrade.manage_open_orders()
|
||||
|
||||
# Assert trade is as expected (averaged dca)
|
||||
trade = Trade.query.first()
|
||||
|
@@ -351,3 +351,95 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
||||
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
assert trade.nr_of_successful_exits == 1
|
||||
|
||||
|
||||
def test_dca_order_adjust(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
||||
default_conf_usdt['position_adjustment_enable'] = True
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker_usdt,
|
||||
get_fee=fee,
|
||||
amount_to_precision=lambda s, x, y: y,
|
||||
price_to_precision=lambda s, x, y: y,
|
||||
)
|
||||
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=False)
|
||||
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade.strategy.custom_entry_price = lambda **kwargs: ticker_usdt['ask'] * 0.96
|
||||
|
||||
freqtrade.enter_positions()
|
||||
|
||||
assert len(Trade.get_trades().all()) == 1
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert trade.open_order_id is not None
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.open_rate == 1.96
|
||||
# No adjustment
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert trade.open_order_id is not None
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
|
||||
# Cancel order and place new one
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1.99)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
assert trade.open_order_id is not None
|
||||
# Open rate is not adjusted yet
|
||||
assert trade.open_rate == 1.96
|
||||
|
||||
# Fill order
|
||||
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
assert trade.open_order_id is None
|
||||
# Open rate is not adjusted yet
|
||||
assert trade.open_rate == 1.99
|
||||
|
||||
# 2nd order - not filling
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120)
|
||||
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=False)
|
||||
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 3
|
||||
assert trade.open_order_id is not None
|
||||
assert trade.open_rate == 1.99
|
||||
assert trade.orders[-1].price == 1.96
|
||||
assert trade.orders[-1].cost == 120
|
||||
|
||||
# Replace new order with diff. order at a lower price
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1.95)
|
||||
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 4
|
||||
assert trade.open_order_id is not None
|
||||
assert trade.open_rate == 1.99
|
||||
assert trade.orders[-1].price == 1.95
|
||||
assert pytest.approx(trade.orders[-1].cost) == 120
|
||||
|
||||
# Fill DCA order
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None)
|
||||
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True)
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(side_effect=ValueError)
|
||||
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 4
|
||||
assert trade.open_order_id is None
|
||||
assert pytest.approx(trade.open_rate) == 1.963153456
|
||||
assert trade.orders[-1].price == 1.95
|
||||
assert pytest.approx(trade.orders[-1].cost) == 120
|
||||
assert trade.orders[-1].status == 'closed'
|
||||
|
||||
assert pytest.approx(trade.amount) == 91.689215
|
||||
# Check the 2 filled orders equal the above amount
|
||||
assert pytest.approx(trade.orders[1].amount) == 30.150753768
|
||||
assert pytest.approx(trade.orders[-1].amount) == 61.538461232
|
||||
|
Reference in New Issue
Block a user