freqtrade_origin/tests/freqtradebot/test_integration.py

688 lines
26 KiB
Python
Raw Permalink 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
"""
2024-05-12 14:04:01 +00:00
default_conf["max_open_trades"] = 3
default_conf["exchange"]["name"] = "binance"
2024-05-12 14:04:01 +00:00
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,
2024-05-12 14:04:01 +00:00
"trades": None,
}
stoploss_order_closed = stoploss_order_open.copy()
2024-05-12 14:04:01 +00:00
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()]
2024-05-12 14:04:01 +00:00
stoploss_order_mock = MagicMock(side_effect=stop_orders)
# Sell 3rd trade (not called for the first trade)
2024-05-12 14:04:01 +00:00
should_sell_mock = MagicMock(side_effect=[[], [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(
2024-05-12 14:04:01 +00:00
"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)
2024-05-12 14:04:01 +00:00
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
# Switch ordertype to market to close trade immediately
2024-05-12 14:04:01 +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]
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +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
"""
2024-05-12 14:04:01 +00:00
default_conf["max_open_trades"] = 5
default_conf["force_entry_enable"] = True
default_conf["stake_amount"] = "unlimited"
default_conf["tradable_balance_ratio"] = balance_ratio
default_conf["dry_run_wallet"] = 1000
default_conf["exchange"]["name"] = "binance"
default_conf["telegram"]["enabled"] = True
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2019-10-31 09:04:28 +00:00
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(
2024-05-12 14:04:01 +00:00
"freqtrade.freqtradebot.FreqtradeBot",
2019-10-31 09:04:28 +00:00
create_stoploss_order=MagicMock(return_value=True),
_notify_exit=MagicMock(),
2019-10-31 09:04:28 +00:00
)
2024-05-12 14:04:01 +00:00
should_sell_mock = MagicMock(
side_effect=[[], [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)
2024-05-12 14:04:01 +00:00
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
2019-10-31 09:04:28 +00:00
# Switch ordertype to market to close trade immediately
2024-05-12 14:04:01 +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
2024-05-12 14:04:01 +00:00
assert freqtrade.wallets.get_trade_stake_amount("XRP/BTC", 5) == result1
2024-05-12 14:04:01 +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.
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
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:
2024-05-12 14:04:01 +00:00
default_conf_usdt["position_adjustment_enable"] = True
2022-01-02 19:20:56 +00:00
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
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
assert trade.orders[-1].side == "sell"
2022-01-02 19:20:56 +00:00
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:
2024-05-12 14:04:01 +00:00
default_conf_usdt["position_adjustment_enable"] = True
2022-02-13 15:01:44 +00:00
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
2024-05-12 14:04:01 +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
2024-05-12 14:04:01 +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
2024-05-12 14:04:01 +00:00
assert trade.orders[0].side == "sell"
assert trade.orders[1].amount == round(60 / ticker_usdt_modif["ask"], 4)
2022-02-13 15:01:44 +00:00
# Sold everything
2024-05-12 14:04:01 +00:00
assert trade.orders[-1].side == "buy"
2022-02-13 15:01:44 +00:00
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
2024-05-12 14:04:01 +00:00
@pytest.mark.parametrize("leverage", [1, 2])
def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) -> None:
2024-05-12 14:04:01 +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,
)
2024-05-12 14:04:01 +00:00
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)
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
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)
2024-05-12 14:04:01 +00:00
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)
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
assert trade.orders[-1].status == "closed"
2022-05-06 17:49:39 +00:00
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
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
@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:
2024-05-12 14:04:01 +00:00
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.
2024-05-12 14:04:01 +00:00
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)
2024-05-12 14:04:01 +00:00
).all()
assert len(trades) == 1
2024-05-12 14:04:01 +00:00
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-05-12 14:04: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)
2024-05-12 14:04:01 +00:00
).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)
2024-05-12 14:04:01 +00:00
).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)
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
@pytest.mark.parametrize("leverage", [1, 2])
2022-09-27 17:47:49 +00:00
def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, leverage) -> None:
2024-05-12 14:04:01 +00:00
default_conf_usdt["position_adjustment_enable"] = True
2023-07-15 15:30:30 +00:00
spot = leverage == 1
if not spot:
2024-05-12 14:04:01 +00:00
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)
2024-05-12 14:04:01 +00:00
starting_amount = freqtrade.wallets.get_total("USDT")
2023-07-16 14:02:23 +00:00
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
2024-05-12 14:04:01 +00:00
assert pytest.approx(freqtrade.wallets.get_free("USDT")) == starting_amount - 60
2023-07-16 14:02:23 +00:00
if spot:
2024-05-12 14:04:01 +00:00
assert pytest.approx(freqtrade.wallets.get_total("USDT")) == starting_amount - 60
2023-07-16 14:02:23 +00:00
else:
2024-05-12 14:04:01 +00:00
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(
2024-05-12 14:04:01 +00:00
r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog
)
2024-05-12 14:04: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
2024-05-12 14:04:01 +00:00
assert trade.orders[-1].ft_order_side == "sell"
assert trade.orders[-1].ft_order_tag == "PES"
assert pytest.approx(trade.stake_amount) == 40
assert pytest.approx(trade.amount) == 20 * 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 + trade.realized_profit
2024-05-12 14:04:01 +00:00
assert pytest.approx(freqtrade.wallets.get_free("USDT")) == expected_profit
2023-07-16 14:02:23 +00:00
if spot:
2024-05-12 14:04:01 +00:00
assert pytest.approx(freqtrade.wallets.get_total("USDT")) == expected_profit
2023-07-16 14:02:23 +00:00
else:
# total won't change in futures mode, only free / used will.
2024-05-12 14:04:01 +00:00
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
2024-05-12 14:04:01 +00:00
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-trade.stake_amount)
freqtrade.process()
trade = Trade.get_trades().first()
2023-12-10 08:27:24 +00:00
assert len(trade.orders) == 3
2024-05-12 14:04:01 +00:00
assert trade.orders[-1].ft_order_side == "sell"
assert pytest.approx(trade.stake_amount) == 40
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
2024-05-12 14:04:01 +00:00
assert trade.orders[-1].ft_order_side == "sell"
assert pytest.approx(trade.stake_amount) == 40
2023-12-10 08:34:02 +00:00
assert trade.is_open is False
2024-05-12 14:04:01 +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
2024-05-12 14:04:01 +00:00
assert pytest.approx(freqtrade.wallets.get_free("USDT")) == expected_profit
2023-07-16 14:02:23 +00:00
if spot:
2024-05-12 14:04:01 +00:00
assert pytest.approx(freqtrade.wallets.get_total("USDT")) == expected_profit
2023-07-16 14:02:23 +00:00
else:
# total won't change in futures mode, only free / used will.
2024-05-12 14:04:01 +00:00
assert freqtrade.wallets.get_total("USDT") == starting_amount + trade.realized_profit