freqtrade_origin/tests/freqtradebot/test_integration.py

697 lines
26 KiB
Python
Raw Normal View History

2023-11-25 15:17:43 +00:00
import time
from unittest.mock import MagicMock
import pytest
2023-03-16 06:25:04 +00:00
from sqlalchemy import select
2022-09-27 17:47:49 +00:00
from freqtrade.enums import ExitCheckTuple, ExitType, TradingMode
from freqtrade.persistence import Trade
2022-02-20 13:27:26 +00:00
from freqtrade.persistence.models import Order
from freqtrade.rpc.rpc import RPC
from tests.conftest import EXMS, get_patched_freqtradebot, log_has_re, patch_get_signal
2024-01-02 15:26:43 +00:00
def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, mocker) -> None:
"""
Tests workflow of selling stoploss_on_exchange.
Sells
* first trade as stoploss
* 2nd trade is kept
* 3rd trade is sold via sell-signal
"""
default_conf['max_open_trades'] = 3
default_conf['exchange']['name'] = 'binance'
stoploss = {
'id': 123,
'info': {}
}
stoploss_order_open = {
"id": "123",
"timestamp": 1542707426845,
"datetime": "2018-11-20T09:50:26.845Z",
"lastTradeTimestamp": None,
"symbol": "BTC/USDT",
"type": "stop_loss_limit",
"side": "sell",
"price": 1.08801,
2023-03-28 14:09:46 +00:00
"amount": 91.07468123,
"cost": 0.0,
"average": 0.0,
"filled": 0.0,
"remaining": 0.0,
"status": "open",
"fee": None,
"trades": None
}
stoploss_order_closed = stoploss_order_open.copy()
stoploss_order_closed['status'] = 'closed'
stoploss_order_closed['filled'] = stoploss_order_closed['amount']
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
2024-01-02 18:22:46 +00:00
stop_orders = [stoploss_order_closed, stoploss_order_open.copy(), stoploss_order_open.copy()]
stoploss_order_mock = MagicMock(
2023-03-29 04:47:34 +00:00
side_effect=stop_orders)
# Sell 3rd trade (not called for the first trade)
should_sell_mock = MagicMock(side_effect=[
2022-05-22 08:20:01 +00:00
[],
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]]
)
cancel_order_mock = MagicMock()
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2023-03-08 06:05:59 +00:00
create_stoploss=stoploss,
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
get_fee=fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
fetch_stoploss_order=stoploss_order_mock,
2021-05-16 12:15:24 +00:00
cancel_stoploss_order_with_result=cancel_order_mock,
)
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
create_stoploss_order=MagicMock(return_value=True),
_notify_exit=MagicMock(),
)
2021-08-24 19:03:13 +00:00
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
2023-04-27 04:23:34 +00:00
wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update")
mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=1000)
mocker.patch("freqtrade.wallets.Wallets.check_exit_amount", return_value=True)
2019-10-31 06:11:57 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# Switch ordertype to market to close trade immediately
2022-03-08 05:59:14 +00:00
freqtrade.strategy.order_types['exit'] = 'market'
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
patch_get_signal(freqtrade)
# Create some test data
freqtrade.enter_positions()
assert freqtrade.strategy.confirm_trade_entry.call_count == 3
freqtrade.strategy.confirm_trade_entry.reset_mock()
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
wallets_mock.reset_mock()
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2023-03-28 16:10:26 +00:00
# Make sure stoploss-order is open and trade is bought
2023-03-29 04:47:34 +00:00
for idx, trade in enumerate(trades):
stop_order = stop_orders[idx]
stop_order['id'] = f"stop{idx}"
oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss')
2024-01-02 18:22:46 +00:00
oobj.ft_is_open = True
2022-02-20 13:27:26 +00:00
trade.orders.append(oobj)
2024-01-02 18:22:46 +00:00
assert len(trade.open_sl_orders) == 1
n = freqtrade.exit_positions(trades)
assert n == 2
assert should_sell_mock.call_count == 2
assert freqtrade.strategy.confirm_trade_entry.call_count == 0
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
freqtrade.strategy.confirm_trade_exit.reset_mock()
# Only order for 3rd trade needs to be cancelled
assert cancel_order_mock.call_count == 1
2024-01-02 18:22:46 +00:00
assert stoploss_order_mock.call_count == 3
2020-05-01 13:35:57 +00:00
# Wallets must be updated between stoploss cancellation and selling, and will be updated again
# during update_trade_state
assert wallets_mock.call_count == 4
trade = trades[0]
2022-03-24 19:33:47 +00:00
assert trade.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value
assert not trade.is_open
trade = trades[1]
2022-03-24 19:33:47 +00:00
assert not trade.exit_reason
assert trade.is_open
trade = trades[2]
2022-04-04 15:10:02 +00:00
assert trade.exit_reason == ExitType.EXIT_SIGNAL.value
assert not trade.is_open
2019-10-31 09:04:28 +00:00
@pytest.mark.parametrize("balance_ratio,result1", [
(1, 200),
(0.99, 198),
])
2022-01-02 19:20:56 +00:00
def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_ratio, result1) -> None:
2019-10-31 09:04:28 +00:00
"""
Tests workflow unlimited stake-amount
Buy 4 trades, forcebuy a 5th trade
Sell one trade, calculated stake amount should now be lower than before since
one trade was sold at a loss.
2019-10-31 09:04:28 +00:00
"""
default_conf['max_open_trades'] = 5
2022-04-08 11:39:41 +00:00
default_conf['force_entry_enable'] = True
2019-10-31 09:04:28 +00:00
default_conf['stake_amount'] = 'unlimited'
default_conf['tradable_balance_ratio'] = balance_ratio
default_conf['dry_run_wallet'] = 1000
2019-10-31 09:04:28 +00:00
default_conf['exchange']['name'] = 'binance'
default_conf['telegram']['enabled'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2019-10-31 09:04:28 +00:00
get_fee=fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
2019-10-31 09:04:28 +00:00
)
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
create_stoploss_order=MagicMock(return_value=True),
_notify_exit=MagicMock(),
2019-10-31 09:04:28 +00:00
)
should_sell_mock = MagicMock(side_effect=[
2022-05-22 08:20:01 +00:00
[],
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)],
[],
[],
[]]
)
2021-08-24 19:03:13 +00:00
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
2019-10-31 09:04:28 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtrade)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# Switch ordertype to market to close trade immediately
2022-03-08 05:59:14 +00:00
freqtrade.strategy.order_types['exit'] = 'market'
2019-10-31 09:04:28 +00:00
patch_get_signal(freqtrade)
# Create 4 trades
n = freqtrade.enter_positions()
assert n == 4
2019-10-31 09:04:28 +00:00
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2019-10-31 09:04:28 +00:00
assert len(trades) == 4
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC', 5) == result1
2022-01-26 18:08:37 +00:00
rpc._rpc_force_entry('TKN/BTC', None)
2019-10-31 09:04:28 +00:00
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2019-10-31 09:04:28 +00:00
assert len(trades) == 5
for trade in trades:
assert pytest.approx(trade.stake_amount) == result1
2023-08-25 05:09:14 +00:00
trades = Trade.get_open_trades()
assert len(trades) == 5
bals = freqtrade.wallets.get_all_balances()
n = freqtrade.exit_positions(trades)
assert n == 1
trades = Trade.get_open_trades()
# One trade sold
assert len(trades) == 4
# stake-amount should now be reduced, since one trade was sold at a loss.
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC', 5) < result1
# Validate that balance of sold trade is not in dry-run balances anymore.
bals2 = freqtrade.wallets.get_all_balances()
assert bals != bals2
assert len(bals) == 6
assert len(bals2) == 5
assert 'LTC' in bals
assert 'LTC' not in bals2
2022-01-02 19:20:56 +00:00
def test_dca_buying(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(
2023-03-01 19:14:15 +00:00
EXMS,
2022-01-02 19:20:56 +00:00
fetch_ticker=ticker_usdt,
get_fee=fee,
)
patch_get_signal(freqtrade)
freqtrade.enter_positions()
assert len(Trade.get_trades().all()) == 1
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
2022-02-13 15:01:44 +00:00
assert pytest.approx(trade.stake_amount) == 60
2022-01-02 19:20:56 +00:00
assert trade.open_rate == 2.0
# No adjustment
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
2022-02-13 15:01:44 +00:00
assert pytest.approx(trade.stake_amount) == 60
2022-01-02 19:20:56 +00:00
# Reduce bid amount
ticker_usdt_modif = ticker_usdt.return_value
ticker_usdt_modif['bid'] = ticker_usdt_modif['bid'] * 0.995
mocker.patch(f'{EXMS}.fetch_ticker', return_value=ticker_usdt_modif)
2022-01-02 19:20:56 +00:00
# additional buy order
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
for o in trade.orders:
assert o.status == "closed"
assert pytest.approx(trade.stake_amount) == 120
2022-01-02 19:20:56 +00:00
# Open-rate averaged between 2.0 and 2.0 * 0.995
assert trade.open_rate < 2.0
assert trade.open_rate > 2.0 * 0.995
# No action - profit raised above 1% (the bar set in the strategy).
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert pytest.approx(trade.stake_amount) == 120
2022-01-02 19:20:56 +00:00
assert trade.orders[0].amount == 30
assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif['bid']
2022-01-02 19:20:56 +00:00
2022-08-16 06:01:07 +00:00
assert pytest.approx(trade.amount) == trade.orders[0].amount + trade.orders[1].amount
2022-01-13 18:31:03 +00:00
assert trade.nr_of_successful_buys == 2
2022-02-13 15:01:44 +00:00
assert trade.nr_of_successful_entries == 2
2022-01-02 19:20:56 +00:00
# Sell
2022-01-22 16:25:21 +00:00
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
2022-01-02 19:20:56 +00:00
freqtrade.process()
trade = Trade.get_trades().first()
assert trade.is_open is False
assert trade.orders[0].amount == 30
assert trade.orders[0].side == 'buy'
assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif['bid']
2022-01-02 19:20:56 +00:00
# Sold everything
assert trade.orders[-1].side == 'sell'
assert trade.orders[2].amount == trade.amount
2022-01-13 18:31:03 +00:00
assert trade.nr_of_successful_buys == 2
2022-02-13 15:01:44 +00:00
assert trade.nr_of_successful_entries == 2
def test_dca_short(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(
2023-03-01 19:14:15 +00:00
EXMS,
2022-02-13 15:01:44 +00:00
fetch_ticker=ticker_usdt,
get_fee=fee,
2022-08-11 11:35:24 +00:00
amount_to_precision=lambda s, x, y: round(y, 4),
2022-02-13 15:01:44 +00:00
price_to_precision=lambda s, x, y: y,
)
patch_get_signal(freqtrade, enter_long=False, enter_short=True)
freqtrade.enter_positions()
assert len(Trade.get_trades().all()) == 1
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert pytest.approx(trade.stake_amount) == 60
assert trade.open_rate == 2.02
2022-08-11 11:35:24 +00:00
assert trade.orders[0].amount == trade.amount
2022-02-13 15:01:44 +00:00
# No adjustment
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert pytest.approx(trade.stake_amount) == 60
# Reduce bid amount
ticker_usdt_modif = ticker_usdt.return_value
2022-03-27 15:00:45 +00:00
ticker_usdt_modif['ask'] = ticker_usdt_modif['ask'] * 1.004
mocker.patch(f'{EXMS}.fetch_ticker', return_value=ticker_usdt_modif)
2022-02-13 15:01:44 +00:00
# additional buy order
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
for o in trade.orders:
assert o.status == "closed"
assert pytest.approx(trade.stake_amount) == 120
# Open-rate averaged between 2.0 and 2.0 * 1.015
assert trade.open_rate >= 2.02
assert trade.open_rate < 2.02 * 1.015
# No action - profit raised above 1% (the bar set in the strategy).
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert pytest.approx(trade.stake_amount) == 120
2022-08-11 11:35:24 +00:00
assert trade.orders[1].amount == round(60 / ticker_usdt_modif['ask'], 4)
2022-02-13 15:01:44 +00:00
assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
assert trade.nr_of_successful_entries == 2
# Buy
patch_get_signal(freqtrade, enter_long=False, exit_short=True)
freqtrade.process()
trade = Trade.get_trades().first()
assert trade.is_open is False
# assert trade.orders[0].amount == 30
assert trade.orders[0].side == 'sell'
2022-08-11 11:35:24 +00:00
assert trade.orders[1].amount == round(60 / ticker_usdt_modif['ask'], 4)
2022-02-13 15:01:44 +00:00
# Sold everything
assert trade.orders[-1].side == 'buy'
assert trade.orders[2].amount == trade.amount
assert trade.nr_of_successful_entries == 2
assert trade.nr_of_successful_exits == 1
2022-05-06 17:49:39 +00:00
@pytest.mark.parametrize('leverage', [
1, 2
])
def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) -> None:
2022-05-06 17:49:39 +00:00
default_conf_usdt['position_adjustment_enable'] = True
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
2022-05-06 17:49:39 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2022-05-06 17:49:39 +00:00
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(f'{EXMS}._dry_is_price_crossed', return_value=False)
mocker.patch(f"{EXMS}.get_max_leverage", return_value=10)
mocker.patch(f"{EXMS}.get_funding_fees", return_value=0)
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0, 0))
2022-05-06 17:49:39 +00:00
patch_get_signal(freqtrade)
freqtrade.strategy.custom_entry_price = lambda **kwargs: ticker_usdt['ask'] * 0.96
freqtrade.strategy.leverage = MagicMock(return_value=leverage)
freqtrade.strategy.minimal_roi = {0: 0.2}
2022-05-06 17:49:39 +00:00
freqtrade.enter_positions()
assert len(Trade.get_trades().all()) == 1
trade: Trade = Trade.get_trades().first()
2022-05-06 17:49:39 +00:00
assert len(trade.orders) == 1
2023-06-20 15:02:03 +00:00
assert trade.has_open_orders
2022-05-06 17:49:39 +00:00
assert pytest.approx(trade.stake_amount) == 60
assert trade.open_rate == 1.96
assert trade.stop_loss_pct == -0.1
assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
assert trade.initial_stop_loss_pct == -0.1
assert trade.leverage == leverage
assert trade.stake_amount == 60
2022-05-06 17:49:39 +00:00
# No adjustment
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
2023-06-20 15:02:03 +00:00
assert trade.has_open_orders
2022-05-06 17:49:39 +00:00
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
2023-06-20 15:02:03 +00:00
assert trade.has_open_orders
2022-05-06 17:49:39 +00:00
# Open rate is not adjusted yet
assert trade.open_rate == 1.96
assert trade.stop_loss_pct == -0.1
assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
assert trade.stake_amount == 60
assert trade.initial_stop_loss_pct == -0.1
2022-05-06 17:49:39 +00:00
# Fill order
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True)
2022-05-06 17:49:39 +00:00
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
2023-06-20 15:02:03 +00:00
assert not trade.has_open_orders
2022-05-06 17:49:39 +00:00
# Open rate is not adjusted yet
assert trade.open_rate == 1.99
assert pytest.approx(trade.stake_amount) == 60
assert trade.stop_loss_pct == -0.1
assert pytest.approx(trade.stop_loss) == 1.99 * (1 - 0.1 / leverage)
assert pytest.approx(trade.initial_stop_loss) == 1.96 * (1 - 0.1 / leverage)
assert trade.initial_stop_loss_pct == -0.1
assert pytest.approx(trade.orders[-1].stake_amount) == trade.stake_amount
2022-05-06 17:49:39 +00:00
# 2nd order - not filling
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120)
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False)
2022-05-06 17:49:39 +00:00
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 3
2023-06-20 15:02:03 +00:00
assert trade.has_open_orders
2022-05-06 17:49:39 +00:00
assert trade.open_rate == 1.99
assert trade.orders[-1].price == 1.96
assert trade.orders[-1].cost == 120 * leverage
time.sleep(0.1)
2022-05-06 17:49:39 +00:00
# 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
2023-06-20 15:02:03 +00:00
assert trade.has_open_orders
2022-05-06 17:49:39 +00:00
assert trade.open_rate == 1.99
assert pytest.approx(trade.stake_amount) == 60
2022-05-06 17:49:39 +00:00
assert trade.orders[-1].price == 1.95
assert pytest.approx(trade.orders[-1].cost) == 120 * leverage
2022-05-06 17:49:39 +00:00
# Fill DCA order
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None)
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True)
2022-05-06 17:49:39 +00:00
freqtrade.strategy.adjust_entry_price = MagicMock(side_effect=ValueError)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 4
2023-06-20 15:02:03 +00:00
assert not trade.has_open_orders
2022-05-06 17:49:39 +00:00
assert pytest.approx(trade.open_rate) == 1.963153456
assert trade.orders[-1].price == 1.95
assert pytest.approx(trade.orders[-1].cost) == 120 * leverage
2022-05-06 17:49:39 +00:00
assert trade.orders[-1].status == 'closed'
assert pytest.approx(trade.amount) == 91.689215 * leverage
2022-05-06 17:49:39 +00:00
# Check the 2 filled orders equal the above amount
assert pytest.approx(trade.orders[1].amount) == 30.150753768 * leverage
assert pytest.approx(trade.orders[-1].amount) == 61.538461232 * leverage
# Full exit
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False)
freqtrade.strategy.custom_exit = MagicMock(return_value='Exit now')
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=2.02)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 5
assert trade.orders[-1].side == trade.exit_side
assert trade.orders[-1].status == 'open'
assert trade.orders[-1].price == 2.02
assert pytest.approx(trade.amount) == 91.689215 * leverage
assert pytest.approx(trade.orders[-1].amount) == 91.689215 * leverage
assert freqtrade.strategy.adjust_entry_price.call_count == 0
# Process again, should not adjust entry price
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 5
assert trade.orders[-1].status == 'open'
assert trade.orders[-1].price == 2.02
# Adjust entry price cannot be called - this is an exit order
assert freqtrade.strategy.adjust_entry_price.call_count == 0
@pytest.mark.parametrize('leverage', [1, 2])
@pytest.mark.parametrize("is_short", [False, True])
def test_dca_order_adjust_entry_replace_fails(
default_conf_usdt, ticker_usdt, fee, mocker, caplog, is_short, leverage
) -> None:
spot = leverage == 1
if not spot:
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
default_conf_usdt['position_adjustment_enable'] = True
default_conf_usdt['max_open_trades'] = 2
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker_usdt,
get_fee=fee,
get_funding_fees=MagicMock(return_value=0),
)
# no order fills.
mocker.patch(f'{EXMS}._dry_is_price_crossed', side_effect=[False, True])
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trades = Trade.session.scalars(
2023-09-07 18:24:40 +00:00
select(Trade)
.where(Order.ft_is_open.is_(True))
.where(Order.ft_order_side != "stoploss")
.where(Order.ft_trade_id == Trade.id)
).all()
assert len(trades) == 1
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False)
# Timeout to not interfere
freqtrade.strategy.ft_check_timed_out = MagicMock(return_value=False)
# Create DCA order for 2nd trade (so we have 2 open orders on 2 trades)
# this 2nd order won't fill.
2024-01-28 22:05:01 +00:00
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(20, 'PeNF'))
freqtrade.process()
assert freqtrade.strategy.adjust_trade_position.call_count == 1
trades = Trade.session.scalars(
2023-09-07 18:24:40 +00:00
select(Trade)
.where(Order.ft_is_open.is_(True))
.where(Order.ft_order_side != "stoploss")
.where(Order.ft_trade_id == Trade.id)
).all()
assert len(trades) == 2
# We now have 2 orders open
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=2.05)
freqtrade.manage_open_orders()
trades = Trade.session.scalars(
2023-09-07 18:24:40 +00:00
select(Trade)
.where(Order.ft_is_open.is_(True))
.where(Order.ft_order_side != "stoploss")
.where(Order.ft_trade_id == Trade.id)
).all()
assert len(trades) == 2
assert len(Order.get_open_orders()) == 2
# Entry adjustment is called
assert freqtrade.strategy.adjust_entry_price.call_count == 2
# Attempt order replacement - fails.
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234)
entry_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_entry',
return_value=False)
msg = r"Could not replace order for.*"
assert not log_has_re(msg, caplog)
freqtrade.manage_open_orders()
assert log_has_re(msg, caplog)
assert entry_mock.call_count == 2
assert len(Trade.get_trades().all()) == 1
assert len(Order.get_open_orders()) == 0
2022-09-27 17:47:49 +00:00
@pytest.mark.parametrize('leverage', [1, 2])
def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, leverage) -> None:
default_conf_usdt['position_adjustment_enable'] = True
2023-07-15 15:30:30 +00:00
spot = leverage == 1
if not spot:
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2023-07-15 15:30:30 +00:00
assert freqtrade.trading_mode == TradingMode.FUTURES if not spot else TradingMode.SPOT
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
get_fee=fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
get_min_pair_stake_amount=MagicMock(return_value=10),
2023-07-15 15:30:30 +00:00
get_funding_fees=MagicMock(return_value=0),
)
mocker.patch(f"{EXMS}.get_max_leverage", return_value=10)
2023-07-16 14:02:23 +00:00
starting_amount = freqtrade.wallets.get_total('USDT')
assert starting_amount == 1000
patch_get_signal(freqtrade)
2022-09-27 17:47:49 +00:00
freqtrade.strategy.leverage = MagicMock(return_value=leverage)
freqtrade.enter_positions()
assert len(Trade.get_trades().all()) == 1
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert pytest.approx(trade.stake_amount) == 60
2023-07-15 15:30:30 +00:00
assert trade.leverage == leverage
2022-09-27 17:47:49 +00:00
assert pytest.approx(trade.amount) == 30.0 * leverage
assert trade.open_rate == 2.0
2023-07-16 14:02:23 +00:00
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == starting_amount - 60
if spot:
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == starting_amount - 60
else:
assert freqtrade.wallets.get_total('USDT') == starting_amount
# Too small size
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-59)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert pytest.approx(trade.stake_amount) == 60
2022-09-27 17:47:49 +00:00
assert pytest.approx(trade.amount) == 30.0 * leverage
assert log_has_re(
r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog)
2024-01-28 22:05:01 +00:00
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-20, 'PES'))
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert trade.orders[-1].ft_order_side == 'sell'
2024-01-28 22:05:01 +00:00
assert trade.orders[-1].ft_order_tag == 'PES'
assert pytest.approx(trade.stake_amount) == 40.198
2022-09-27 17:47:49 +00:00
assert pytest.approx(trade.amount) == 20.099 * leverage
assert trade.open_rate == 2.0
assert trade.is_open
2023-07-15 15:30:30 +00:00
assert trade.realized_profit > 0.098 * leverage
expected_profit = starting_amount - 40.1980 + trade.realized_profit
2023-07-16 14:02:23 +00:00
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
if spot:
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit
else:
# total won't change in futures mode, only free / used will.
assert freqtrade.wallets.get_total('USDT') == starting_amount + trade.realized_profit
caplog.clear()
# Sell more than what we got (we got ~20 coins left)
# Doesn't exit, as the amount is too high.
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-50)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
# Amount too low...
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-(trade.stake_amount * 0.99))
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
# Amount exactly comes out as exactly 0
freqtrade.strategy.adjust_trade_position = MagicMock(
return_value=-(trade.amount / trade.leverage * 2.02))
freqtrade.process()
trade = Trade.get_trades().first()
2023-12-10 08:27:24 +00:00
assert len(trade.orders) == 3
assert trade.orders[-1].ft_order_side == 'sell'
assert pytest.approx(trade.stake_amount) == 40.198
2023-12-10 08:34:02 +00:00
assert trade.is_open is False
2022-09-07 04:35:58 +00:00
# use amount that would trunc to 0.0 once selling
mocker.patch(f"{EXMS}.amount_to_contract_precision", lambda s, p, v: round(v, 1))
2022-09-07 04:35:58 +00:00
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-0.01)
freqtrade.process()
trade = Trade.get_trades().first()
2023-12-10 08:27:24 +00:00
assert len(trade.orders) == 3
2022-09-07 04:35:58 +00:00
assert trade.orders[-1].ft_order_side == 'sell'
assert pytest.approx(trade.stake_amount) == 40.198
2023-12-10 08:34:02 +00:00
assert trade.is_open is False
2022-09-21 04:42:55 +00:00
assert log_has_re('Amount to exit is 0.0 due to exchange limits - not exiting.', caplog)
2023-12-10 08:27:24 +00:00
expected_profit = starting_amount - 60 + trade.realized_profit
2023-07-16 14:02:23 +00:00
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
if spot:
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit
else:
# total won't change in futures mode, only free / used will.
assert freqtrade.wallets.get_total('USDT') == starting_amount + trade.realized_profit