Merge pull request #6692 from eSeR1805/feat_readjust_entry

Feature: Readjust Entry Order
This commit is contained in:
Matthias
2022-05-07 20:11:20 +02:00
committed by GitHub
13 changed files with 594 additions and 97 deletions

View File

@@ -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):

View File

@@ -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)

View File

@@ -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()

View File

@@ -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