freqtrade_origin/tests/freqtradebot/test_freqtradebot.py

5938 lines
202 KiB
Python
Raw Normal View History

# pragma pylint: disable=missing-docstring, C0103
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
import logging
import time
from copy import deepcopy
2023-05-14 16:14:35 +00:00
from datetime import timedelta
2021-12-21 20:23:01 +00:00
from typing import List
from unittest.mock import ANY, MagicMock, PropertyMock, patch
2018-03-17 21:44:47 +00:00
import pytest
2021-12-11 08:49:48 +00:00
from pandas import DataFrame
2023-03-16 06:25:04 +00:00
from sqlalchemy import select
from freqtrade.constants import CANCEL_REASON, UNLIMITED_STAKE_AMOUNT
2024-05-12 13:08:40 +00:00
from freqtrade.enums import (
CandleType,
ExitCheckTuple,
ExitType,
RPCMessageType,
RunMode,
SignalDirection,
State,
)
from freqtrade.exceptions import (
DependencyException,
ExchangeError,
InsufficientFundsError,
InvalidOrderException,
OperationalException,
PricingError,
TemporaryError,
)
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Order, PairLocks, Trade
2022-04-24 08:29:19 +00:00
from freqtrade.plugins.protections.iprotection import ProtectionReturn
2023-05-14 15:46:56 +00:00
from freqtrade.util.datetime_helpers import dt_now, dt_utc
from freqtrade.worker import Worker
2024-05-12 13:08:40 +00:00
from tests.conftest import (
EXMS,
create_mock_trades,
create_mock_trades_usdt,
get_patched_freqtradebot,
get_patched_worker,
log_has,
log_has_re,
patch_edge,
patch_exchange,
patch_get_signal,
patch_wallet,
patch_whitelist,
)
from tests.conftest_trades import (
MOCK_TRADE_COUNT,
entry_side,
exit_side,
mock_order_2,
mock_order_2_sell,
mock_order_3,
mock_order_3_sell,
mock_order_4,
mock_order_5_stoploss,
mock_order_6_sell,
)
2022-10-02 06:30:19 +00:00
from tests.conftest_trades_usdt import mock_trade_usdt_4
2021-09-15 03:08:15 +00:00
def patch_RPCManager(mocker) -> MagicMock:
"""
This function mock RPC manager to avoid repeating this code in almost every tests
:param mocker: mocker to patch RPCManager class
:return: RPCManager.send_msg MagicMock to track if this method is called
"""
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
rpc_mock = mocker.patch("freqtrade.freqtradebot.RPCManager.send_msg", MagicMock())
return rpc_mock
# Unit tests
2021-09-14 21:38:26 +00:00
def test_freqtradebot_state(mocker, default_conf_usdt, markets) -> None:
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets))
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
assert freqtrade.state is State.RUNNING
2024-05-12 14:13:11 +00:00
default_conf_usdt.pop("initial_state")
freqtrade = FreqtradeBot(default_conf_usdt)
assert freqtrade.state is State.STOPPED
def test_process_stopped(mocker, default_conf_usdt) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
coo_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.cancel_all_open_orders")
freqtrade.process_stopped()
assert coo_mock.call_count == 0
2024-05-12 14:13:11 +00:00
default_conf_usdt["cancel_open_orders_on_exit"] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.process_stopped()
assert coo_mock.call_count == 1
def test_process_calls_sendmsg(mocker, default_conf_usdt) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.process()
assert freqtrade.rpc.process_msg_queue.call_count == 1
def test_bot_cleanup(mocker, default_conf_usdt, caplog) -> None:
2024-05-12 14:13:11 +00:00
mock_cleanup = mocker.patch("freqtrade.freqtradebot.Trade.commit")
coo_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.cancel_all_open_orders")
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.cleanup()
2024-05-12 14:13:11 +00:00
assert log_has("Cleaning up modules ...", caplog)
2018-03-05 08:11:13 +00:00
assert mock_cleanup.call_count == 1
assert coo_mock.call_count == 0
2024-05-12 14:13:11 +00:00
freqtrade.config["cancel_open_orders_on_exit"] = True
freqtrade.cleanup()
assert coo_mock.call_count == 1
2018-03-05 08:11:13 +00:00
2022-12-12 19:01:54 +00:00
def test_bot_cleanup_db_errors(mocker, default_conf_usdt, caplog) -> None:
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.freqtradebot.Trade.commit", side_effect=OperationalException())
mocker.patch(
"freqtrade.freqtradebot.FreqtradeBot.check_for_open_trades",
side_effect=OperationalException(),
)
2022-12-12 19:01:54 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.emc = MagicMock()
freqtrade.emc.shutdown = MagicMock()
freqtrade.cleanup()
assert freqtrade.emc.shutdown.call_count == 1
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("runmode", [RunMode.DRY_RUN, RunMode.LIVE])
def test_order_dict(default_conf_usdt, mocker, runmode, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
conf = default_conf_usdt.copy()
2024-05-12 14:13:11 +00:00
conf["runmode"] = runmode
conf["order_types"] = {
"entry": "market",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": True,
}
2024-05-12 14:13:11 +00:00
conf["entry_pricing"]["price_side"] = "ask"
freqtrade = FreqtradeBot(conf)
2021-09-19 23:44:12 +00:00
if runmode == RunMode.LIVE:
2023-05-15 05:10:55 +00:00
assert not log_has_re(r".*stoploss_on_exchange .* dry-run", caplog)
2024-05-12 14:13:11 +00:00
assert freqtrade.strategy.order_types["stoploss_on_exchange"]
caplog.clear()
# is left untouched
conf = default_conf_usdt.copy()
2024-05-12 14:13:11 +00:00
conf["runmode"] = runmode
conf["order_types"] = {
"entry": "market",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": False,
}
freqtrade = FreqtradeBot(conf)
2024-05-12 14:13:11 +00:00
assert not freqtrade.strategy.order_types["stoploss_on_exchange"]
2023-05-15 05:10:55 +00:00
assert not log_has_re(r".*stoploss_on_exchange .* dry-run", caplog)
2021-10-02 06:58:33 +00:00
def test_get_trade_stake_amount(default_conf_usdt, mocker) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
freqtrade = FreqtradeBot(default_conf_usdt)
2024-05-12 14:13:11 +00:00
result = freqtrade.wallets.get_trade_stake_amount("ETH/USDT", 1)
assert result == default_conf_usdt["stake_amount"]
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("runmode", [RunMode.DRY_RUN, RunMode.LIVE])
2023-05-15 05:19:15 +00:00
def test_load_strategy_no_keys(default_conf_usdt, mocker, runmode, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
conf = deepcopy(default_conf_usdt)
2024-05-12 14:13:11 +00:00
conf["runmode"] = runmode
erm = mocker.patch("freqtrade.freqtradebot.ExchangeResolver.load_exchange")
2023-05-15 05:19:15 +00:00
freqtrade = FreqtradeBot(conf)
strategy_config = freqtrade.strategy.config
2024-05-12 14:13:11 +00:00
assert id(strategy_config["exchange"]) == id(conf["exchange"])
2023-05-15 05:19:15 +00:00
# Keys have been removed and are not passed to the exchange
2024-05-12 14:13:11 +00:00
assert strategy_config["exchange"]["key"] == ""
assert strategy_config["exchange"]["secret"] == ""
2023-05-15 05:19:15 +00:00
assert erm.call_count == 1
2024-05-12 14:13:11 +00:00
ex_conf = erm.call_args_list[0][1]["exchange_config"]
assert id(ex_conf) != id(conf["exchange"])
2023-05-15 05:19:15 +00:00
# Keys are still present
2024-05-12 14:13:11 +00:00
assert ex_conf["key"] != ""
assert ex_conf["key"] == default_conf_usdt["exchange"]["key"]
assert ex_conf["secret"] != ""
assert ex_conf["secret"] == default_conf_usdt["exchange"]["secret"]
@pytest.mark.parametrize(
"amend_last,wallet,max_open,lsamr,expected",
[
(False, 120, 2, 0.5, [60, None]),
(True, 120, 2, 0.5, [60, 58.8]),
(False, 180, 3, 0.5, [60, 60, None]),
(True, 180, 3, 0.5, [60, 60, 58.2]),
(False, 122, 3, 0.5, [60, 60, None]),
(True, 122, 3, 0.5, [60, 60, 0.0]),
(True, 167, 3, 0.5, [60, 60, 45.33]),
(True, 122, 3, 1, [60, 60, 0.0]),
],
)
2021-09-30 09:19:28 +00:00
def test_check_available_stake_amount(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
mocker,
fee,
limit_buy_order_usdt_open,
amend_last,
wallet,
max_open,
lsamr,
expected,
2021-09-30 09:19:28 +00:00
) -> None:
2020-01-05 12:25:21 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
2024-05-12 14:13:11 +00:00
get_fee=fee,
2020-01-05 12:25:21 +00:00
)
2024-05-12 14:13:11 +00:00
default_conf_usdt["dry_run_wallet"] = wallet
2020-01-05 12:25:21 +00:00
2024-05-12 14:13:11 +00:00
default_conf_usdt["amend_last_stake_amount"] = amend_last
default_conf_usdt["last_stake_amount_min_ratio"] = lsamr
2020-01-10 05:36:28 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2020-01-05 12:25:21 +00:00
for i in range(0, max_open):
if expected[i] is not None:
2024-05-12 14:13:11 +00:00
limit_buy_order_usdt_open["id"] = str(i)
result = freqtrade.wallets.get_trade_stake_amount("ETH/USDT", 1)
2020-01-05 12:25:21 +00:00
assert pytest.approx(result) == expected[i]
2024-05-12 14:13:11 +00:00
freqtrade.execute_entry("ETH/USDT", result)
2020-01-05 12:25:21 +00:00
else:
with pytest.raises(DependencyException):
2024-05-12 14:13:11 +00:00
freqtrade.wallets.get_trade_stake_amount("ETH/USDT", 1)
2020-01-05 12:25:21 +00:00
2018-11-10 17:03:46 +00:00
def test_edge_called_in_process(mocker, edge_conf) -> None:
patch_RPCManager(mocker)
patch_edge(mocker)
2018-11-10 17:09:32 +00:00
2018-11-10 17:03:46 +00:00
patch_exchange(mocker)
freqtrade = FreqtradeBot(edge_conf)
patch_get_signal(freqtrade)
2019-03-26 08:07:24 +00:00
freqtrade.process()
2024-05-12 14:13:11 +00:00
assert freqtrade.active_pair_whitelist == ["NEO/BTC", "LTC/BTC"]
2018-11-10 17:03:46 +00:00
2018-11-10 16:20:11 +00:00
def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_edge(mocker)
2024-05-12 14:13:11 +00:00
edge_conf["dry_run_wallet"] = 999.9
2018-11-10 16:20:11 +00:00
freqtrade = FreqtradeBot(edge_conf)
2024-05-12 14:13:11 +00:00
assert (
freqtrade.wallets.get_trade_stake_amount("NEO/BTC", 1, freqtrade.edge)
== (999.9 * 0.5 * 0.01) / 0.20
)
assert (
freqtrade.wallets.get_trade_stake_amount("LTC/BTC", 1, freqtrade.edge)
== (999.9 * 0.5 * 0.01) / 0.21
)
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"buy_price_mult,ignore_strat_sl",
[
(0.79, False), # Override stoploss
(0.85, True), # Override strategy stoploss
],
)
def test_edge_overrides_stoploss(
limit_order, fee, caplog, mocker, buy_price_mult, ignore_strat_sl, edge_conf
) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_edge(mocker)
2024-05-12 14:13:11 +00:00
edge_conf["max_open_trades"] = float("inf")
# Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
# Thus, if price falls 21%, stoploss should be triggered
#
2021-10-03 23:41:01 +00:00
# mocking the ticker: price is falling ...
2024-05-12 14:13:11 +00:00
enter_price = limit_order["buy"]["price"]
2022-06-12 15:09:47 +00:00
ticker_val = {
2024-05-12 14:13:11 +00:00
"bid": enter_price,
"ask": enter_price,
"last": enter_price,
}
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2022-06-12 15:09:47 +00:00
fetch_ticker=MagicMock(return_value=ticker_val),
get_fee=fee,
)
#############################################
2021-09-17 08:25:58 +00:00
# Create a trade with "limit_buy_order_usdt" price
2018-11-10 16:20:11 +00:00
freqtrade = FreqtradeBot(edge_conf)
2024-05-12 14:13:11 +00:00
freqtrade.active_pair_whitelist = ["NEO/BTC"]
2021-10-11 14:26:15 +00:00
patch_get_signal(freqtrade)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-03 23:41:01 +00:00
caplog.clear()
#############################################
2024-05-12 14:13:11 +00:00
ticker_val.update(
{
"bid": enter_price * buy_price_mult,
"ask": enter_price * buy_price_mult,
"last": enter_price * buy_price_mult,
}
)
2024-04-18 20:51:25 +00:00
# stoploss should be hit
2021-09-19 23:44:12 +00:00
assert freqtrade.handle_trade(trade) is not ignore_strat_sl
if not ignore_strat_sl:
2024-05-12 14:13:11 +00:00
assert log_has_re("Exit for NEO/BTC detected. Reason: stop_loss.*", caplog)
2022-03-24 19:33:47 +00:00
assert trade.exit_reason == ExitType.STOP_LOSS.value
2022-03-24 19:53:22 +00:00
# Test compatibility ...
assert trade.sell_reason == ExitType.STOP_LOSS.value
2018-10-04 16:07:47 +00:00
def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
default_conf_usdt["max_open_trades"] = 2
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
get_fee=fee,
2023-02-16 18:47:12 +00:00
_dry_is_price_crossed=MagicMock(return_value=False),
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade is not None
2021-10-03 07:22:50 +00:00
assert trade.stake_amount == 60.0
assert trade.is_open
assert trade.open_date is not None
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade).order_by(Trade.id.desc())).first()
assert trade is not None
2021-10-03 07:22:50 +00:00
assert trade.stake_amount == 60.0
assert trade.is_open
assert trade.open_date is not None
2021-10-03 07:22:50 +00:00
assert Trade.total_open_trades_stakes() == 120.0
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("is_short,open_rate", [(False, 2.0), (True, 2.2)])
def test_create_trade(
default_conf_usdt, ticker_usdt, limit_order, fee, mocker, is_short, open_rate
) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2018-04-21 17:39:18 +00:00
get_fee=fee,
2023-02-16 18:47:12 +00:00
_dry_is_price_crossed=MagicMock(return_value=False),
)
# Save state of current whitelist
2024-05-12 14:13:11 +00:00
whitelist = deepcopy(default_conf_usdt["exchange"]["pair_whitelist"])
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2024-05-12 14:13:11 +00:00
freqtrade.create_trade("ETH/USDT")
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
assert trade is not None
2022-03-20 19:00:30 +00:00
assert pytest.approx(trade.stake_amount) == 60.0
assert trade.is_open
assert trade.open_date is not None
2024-05-12 14:13:11 +00:00
assert trade.exchange == "binance"
# Simulate fulfilled LIMIT_BUY order for trade
2022-02-24 18:56:42 +00:00
oobj = Order.parse_from_ccxt_object(
2024-05-12 14:13:11 +00:00
limit_order[entry_side(is_short)], "ADA/USDT", entry_side(is_short)
)
trade.update_trade(oobj)
2021-09-14 21:38:26 +00:00
assert trade.open_rate == open_rate
2021-09-17 08:25:58 +00:00
assert trade.amount == 30.0
2024-05-12 14:13:11 +00:00
assert whitelist == default_conf_usdt["exchange"]["pair_whitelist"]
2021-10-02 06:58:33 +00:00
def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
2018-06-16 23:23:12 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
patch_wallet(mocker, free=default_conf_usdt["stake_amount"] * 0.5)
2018-06-16 23:23:12 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2018-06-16 23:23:12 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
2018-06-16 23:23:12 +00:00
2024-05-12 14:13:11 +00:00
with pytest.raises(DependencyException, match=r".*stake amount.*"):
freqtrade.create_trade("ETH/USDT")
2018-06-16 23:23:12 +00:00
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"stake_amount,create,amount_enough,max_open_trades",
[
(5.0, True, True, 99),
(0.042, True, False, 99), # Amount will be adjusted to min - which is 0.051
(0, False, True, 99),
(UNLIMITED_STAKE_AMOUNT, False, True, 0),
],
)
2021-09-19 23:44:12 +00:00
def test_create_trade_minimal_amount(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
limit_order_open,
fee,
mocker,
stake_amount,
create,
amount_enough,
max_open_trades,
caplog,
is_short,
2021-09-19 23:44:12 +00:00
) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
2022-04-06 01:02:13 +00:00
enter_mock = MagicMock(return_value=limit_order_open[entry_side(is_short)])
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
create_order=enter_mock,
get_fee=fee,
)
2024-05-12 14:13:11 +00:00
default_conf_usdt["max_open_trades"] = max_open_trades
freqtrade = FreqtradeBot(default_conf_usdt)
2024-05-12 14:13:11 +00:00
freqtrade.config["stake_amount"] = stake_amount
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2021-09-19 23:44:12 +00:00
if create:
2024-05-12 14:13:11 +00:00
assert freqtrade.create_trade("ETH/USDT")
2021-09-19 23:44:12 +00:00
if amount_enough:
2024-05-12 14:13:11 +00:00
rate, amount = enter_mock.call_args[1]["rate"], enter_mock.call_args[1]["amount"]
assert rate * amount <= default_conf_usdt["stake_amount"]
2021-09-19 23:44:12 +00:00
else:
2024-05-12 14:13:11 +00:00
assert log_has_re(r"Stake amount for pair .* is too small.*", caplog)
2021-09-19 23:44:12 +00:00
else:
2024-05-12 14:13:11 +00:00
assert not freqtrade.create_trade("ETH/USDT")
2021-09-19 23:44:12 +00:00
if not max_open_trades:
2024-05-12 14:13:11 +00:00
assert (
freqtrade.wallets.get_trade_stake_amount(
"ETH/USDT", default_conf_usdt["max_open_trades"], freqtrade.edge
)
== 0
)
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"whitelist,positions",
[
(["ETH/USDT"], 1), # No pairs left
([], 0), # No pairs in whitelist
],
)
def test_enter_positions_no_pairs_left(
default_conf_usdt,
ticker_usdt,
limit_buy_order_usdt_open,
fee,
whitelist,
positions,
mocker,
caplog,
) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.configuration.config_validation._validate_whitelist")
default_conf_usdt["exchange"]["pair_whitelist"] = whitelist
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
n = freqtrade.enter_positions()
2021-09-19 23:44:12 +00:00
assert n == positions
if positions:
assert not log_has_re(r"No currency pair in active pair whitelist.*", caplog)
n = freqtrade.enter_positions()
assert n == 0
assert log_has_re(r"No currency pair in active pair whitelist.*", caplog)
else:
assert n == 0
assert log_has("Active pair whitelist is empty.", caplog)
@pytest.mark.usefixtures("init_persistence")
2024-05-12 14:13:11 +00:00
def test_enter_positions_global_pairlock(
default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee, mocker, caplog
) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2024-05-12 14:13:11 +00:00
create_order=MagicMock(return_value={"id": limit_buy_order_usdt["id"]}),
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
n = freqtrade.enter_positions()
2020-11-25 10:54:11 +00:00
message = r"Global pairlock active until.* Not creating new trades."
n = freqtrade.enter_positions()
# 0 trades, but it's not because of pairlock.
assert n == 0
2020-11-25 10:54:11 +00:00
assert not log_has_re(message, caplog)
caplog.clear()
2024-05-12 14:13:11 +00:00
PairLocks.lock_pair("*", dt_now() + timedelta(minutes=20), "Just because", side="*")
n = freqtrade.enter_positions()
assert n == 0
2020-11-25 10:54:11 +00:00
assert log_has_re(message, caplog)
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_protections(mocker, default_conf_usdt, fee, is_short):
2024-05-12 14:13:11 +00:00
default_conf_usdt["protections"] = [
2021-09-20 17:49:18 +00:00
{"method": "CooldownPeriod", "stop_duration": 60},
{
"method": "StoplossGuard",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 4,
2024-05-12 14:13:11 +00:00
"only_per_pair": False,
},
2021-09-20 17:49:18 +00:00
]
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-09-20 17:49:18 +00:00
freqtrade.protections._protection_handlers[1].global_stop = MagicMock(
2024-05-12 14:13:11 +00:00
return_value=ProtectionReturn(True, dt_now() + timedelta(hours=1), "asdf")
)
2021-10-02 09:58:02 +00:00
create_mock_trades(fee, is_short)
2024-05-12 14:13:11 +00:00
freqtrade.handle_protections("ETC/BTC", "*")
2021-09-20 17:49:18 +00:00
send_msg_mock = freqtrade.rpc.send_msg
assert send_msg_mock.call_count == 2
2024-05-12 14:13:11 +00:00
assert send_msg_mock.call_args_list[0][0][0]["type"] == RPCMessageType.PROTECTION_TRIGGER
assert send_msg_mock.call_args_list[1][0][0]["type"] == RPCMessageType.PROTECTION_TRIGGER_GLOBAL
2021-09-20 17:49:18 +00:00
def test_create_trade_no_signal(default_conf_usdt, fee, mocker) -> None:
2024-05-12 14:13:11 +00:00
default_conf_usdt["dry_run"] = True
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
2024-05-12 14:13:11 +00:00
default_conf_usdt["stake_amount"] = 10
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
2024-05-12 14:13:11 +00:00
assert not freqtrade.create_trade("ETH/USDT")
2019-08-13 08:34:27 +00:00
@pytest.mark.parametrize("max_open", range(0, 5))
@pytest.mark.parametrize("tradable_balance_ratio,modifier", [(1.0, 1), (0.99, 0.8), (0.5, 0.5)])
2021-09-30 09:19:28 +00:00
def test_create_trades_multiple_trades(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
fee,
mocker,
limit_buy_order_usdt_open,
max_open,
tradable_balance_ratio,
modifier,
2021-09-30 09:19:28 +00:00
) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
default_conf_usdt["max_open_trades"] = max_open
default_conf_usdt["tradable_balance_ratio"] = tradable_balance_ratio
default_conf_usdt["dry_run_wallet"] = 60.0 * max_open
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
n = freqtrade.enter_positions()
trades = Trade.get_open_trades()
# Expected trades should be max_open * a modified value
# depending on the configured tradable_balance
assert n == max(int(max_open * modifier), 0)
assert len(trades) == max(int(max_open * modifier), 0)
2024-05-12 14:13:11 +00:00
def test_create_trades_preopen(
default_conf_usdt, ticker_usdt, fee, mocker, limit_buy_order_usdt_open, caplog
) -> None:
2019-08-14 04:21:15 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
default_conf_usdt["max_open_trades"] = 4
2019-08-14 04:21:15 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
2019-08-14 04:21:15 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2019-08-14 04:21:15 +00:00
patch_get_signal(freqtrade)
# Create 2 existing trades
2024-05-12 14:13:11 +00:00
freqtrade.execute_entry("ETH/USDT", default_conf_usdt["stake_amount"])
freqtrade.execute_entry("NEO/BTC", default_conf_usdt["stake_amount"])
2019-08-14 04:21:15 +00:00
assert len(Trade.get_open_trades()) == 2
# Change order_id for new orders
2024-05-12 14:13:11 +00:00
limit_buy_order_usdt_open["id"] = "123444"
2019-08-14 04:21:15 +00:00
# Create 2 new trades using create_trades
2024-05-12 14:13:11 +00:00
assert freqtrade.create_trade("ETH/USDT")
assert freqtrade.create_trade("NEO/BTC")
2019-08-14 04:21:15 +00:00
trades = Trade.get_open_trades()
assert len(trades) == 4
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_process_trade_creation(
default_conf_usdt, ticker_usdt, limit_order, limit_order_open, is_short, fee, mocker, caplog
) -> None:
ticker_side = "ask" if is_short else "bid"
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2022-04-06 01:02:13 +00:00
create_order=MagicMock(return_value=limit_order_open[entry_side(is_short)]),
fetch_order=MagicMock(return_value=limit_order[entry_side(is_short)]),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2023-03-16 06:25:04 +00:00
trades = Trade.get_open_trades()
assert not trades
2019-08-13 07:38:21 +00:00
freqtrade.process()
2023-03-16 06:25:04 +00:00
trades = Trade.get_open_trades()
assert len(trades) == 1
trade = trades[0]
assert trade is not None
2024-05-12 14:13:11 +00:00
assert pytest.approx(trade.stake_amount) == default_conf_usdt["stake_amount"]
assert trade.is_open
assert trade.open_date is not None
2024-05-12 14:13:11 +00:00
assert trade.exchange == "binance"
2022-02-13 15:17:41 +00:00
assert trade.open_rate == ticker_usdt.return_value[ticker_side]
assert pytest.approx(trade.amount) == 60 / ticker_usdt.return_value[ticker_side]
assert log_has(
f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT '
2024-05-13 17:49:15 +00:00
"with stake_amount: 60.0 ...",
2024-05-12 14:13:11 +00:00
caplog,
)
def test_process_exchange_failures(default_conf_usdt, ticker_usdt, mocker) -> None:
2024-06-04 05:04:28 +00:00
# TODO: Move this test to test_worker
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2024-06-04 05:04:28 +00:00
reload_markets=MagicMock(),
2024-01-14 14:11:10 +00:00
create_order=MagicMock(side_effect=TemporaryError),
)
2024-05-12 14:13:11 +00:00
sleep_mock = mocker.patch("time.sleep")
worker = Worker(args=None, config=default_conf_usdt)
2019-03-26 08:07:24 +00:00
patch_get_signal(worker.freqtrade)
2024-06-04 05:04:28 +00:00
mocker.patch(f"{EXMS}.reload_markets", MagicMock(side_effect=TemporaryError))
2020-02-22 22:45:15 +00:00
worker._process_running()
2024-01-14 14:11:10 +00:00
assert sleep_mock.called is True
def test_process_operational_exception(default_conf_usdt, ticker_usdt, mocker) -> None:
msg_mock = patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2024-05-12 14:13:11 +00:00
EXMS, fetch_ticker=ticker_usdt, create_order=MagicMock(side_effect=OperationalException)
)
worker = Worker(args=None, config=default_conf_usdt)
2019-03-26 08:07:24 +00:00
patch_get_signal(worker.freqtrade)
2020-01-29 14:08:36 +00:00
assert worker.freqtrade.state == State.RUNNING
2020-02-22 22:45:15 +00:00
worker._process_running()
2020-01-29 14:08:36 +00:00
assert worker.freqtrade.state == State.STOPPED
2024-05-12 14:13:11 +00:00
assert "OperationalException" in msg_mock.call_args_list[-1][0][0]["status"]
2024-05-12 14:13:11 +00:00
def test_process_trade_handling(
default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open, fee, mocker
) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
fetch_order=MagicMock(return_value=limit_buy_order_usdt_open),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
2023-03-16 06:25:04 +00:00
trades = Trade.get_open_trades()
assert not trades
2019-08-13 07:36:52 +00:00
freqtrade.process()
2023-03-16 06:25:04 +00:00
trades = Trade.get_open_trades()
assert len(trades) == 1
2019-08-13 07:36:52 +00:00
# Nothing happened ...
freqtrade.process()
assert len(trades) == 1
2024-05-12 14:13:11 +00:00
def test_process_trade_no_whitelist_pair(
default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee, mocker
) -> None:
"""Test process with trade not in pair list"""
2018-10-29 18:23:56 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2024-05-12 14:13:11 +00:00
create_order=MagicMock(return_value={"id": limit_buy_order_usdt["id"]}),
2021-09-17 08:25:58 +00:00
fetch_order=MagicMock(return_value=limit_buy_order_usdt),
2018-10-29 18:23:56 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2018-10-29 18:23:56 +00:00
patch_get_signal(freqtrade)
2024-05-12 14:13:11 +00:00
pair = "BLK/BTC"
# Ensure the pair is not in the whitelist!
2024-05-12 14:13:11 +00:00
assert pair not in default_conf_usdt["exchange"]["pair_whitelist"]
2018-10-29 18:23:56 +00:00
# create open trade not in whitelist
2024-05-12 14:13:11 +00:00
Trade.session.add(
Trade(
pair=pair,
stake_amount=0.001,
fee_open=fee.return_value,
fee_close=fee.return_value,
is_open=True,
amount=20,
open_rate=0.01,
exchange="binance",
)
)
Trade.session.add(
Trade(
pair="ETH/USDT",
stake_amount=0.001,
fee_open=fee.return_value,
fee_close=fee.return_value,
is_open=True,
amount=12,
open_rate=0.001,
exchange="binance",
)
)
2022-08-19 07:10:54 +00:00
Trade.commit()
2018-10-29 18:23:56 +00:00
assert pair not in freqtrade.active_pair_whitelist
2019-08-13 07:38:21 +00:00
freqtrade.process()
2018-10-29 18:23:56 +00:00
assert pair in freqtrade.active_pair_whitelist
# Make sure each pair is only in the list once
assert len(freqtrade.active_pair_whitelist) == len(set(freqtrade.active_pair_whitelist))
def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker) -> None:
2019-01-26 19:05:49 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
refresh_mock = MagicMock()
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=TemporaryError),
2019-01-26 19:05:49 +00:00
refresh_latest_ohlcv=refresh_mock,
)
2024-05-12 14:13:11 +00:00
inf_pairs = MagicMock(
return_value=[("BTC/ETH", "1m", CandleType.SPOT), ("ETH/USDT", "1h", CandleType.SPOT)]
)
mocker.patch.multiple(
2024-05-12 14:13:11 +00:00
"freqtrade.strategy.interface.IStrategy",
get_exit_signal=MagicMock(return_value=(False, False)),
2024-05-12 14:13:11 +00:00
get_entry_signal=MagicMock(return_value=(None, None)),
2021-07-20 16:56:03 +00:00
)
2024-05-12 14:13:11 +00:00
mocker.patch("time.sleep", return_value=None)
2019-01-26 19:05:49 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2019-01-26 19:05:49 +00:00
freqtrade.strategy.informative_pairs = inf_pairs
# patch_get_signal(freqtrade)
2019-03-26 08:07:24 +00:00
freqtrade.process()
2019-01-26 19:05:49 +00:00
assert inf_pairs.call_count == 1
assert refresh_mock.call_count == 1
2021-12-08 13:35:15 +00:00
assert ("BTC/ETH", "1m", CandleType.SPOT) in refresh_mock.call_args[0][0]
assert ("ETH/USDT", "1h", CandleType.SPOT) in refresh_mock.call_args[0][0]
2024-05-12 14:13:11 +00:00
assert ("ETH/USDT", default_conf_usdt["timeframe"], CandleType.SPOT) in refresh_mock.call_args[
0
][0]
@pytest.mark.parametrize(
"is_short,trading_mode,exchange_name,margin_mode,liq_buffer,liq_price",
[
(False, "spot", "binance", None, 0.0, None),
(True, "spot", "binance", None, 0.0, None),
(False, "spot", "gate", None, 0.0, None),
(True, "spot", "gate", None, 0.0, None),
(False, "spot", "okx", None, 0.0, None),
(True, "spot", "okx", None, 0.0, None),
(True, "futures", "binance", "isolated", 0.0, 11.88151815181518),
(False, "futures", "binance", "isolated", 0.0, 8.080471380471382),
(True, "futures", "gate", "isolated", 0.0, 11.87413417771621),
(False, "futures", "gate", "isolated", 0.0, 8.085708510208207),
(True, "futures", "binance", "isolated", 0.05, 11.7874422442244),
(False, "futures", "binance", "isolated", 0.05, 8.17644781144781),
(True, "futures", "gate", "isolated", 0.05, 11.7804274688304),
(False, "futures", "gate", "isolated", 0.05, 8.181423084697796),
(True, "futures", "okx", "isolated", 0.0, 11.87413417771621),
(False, "futures", "okx", "isolated", 0.0, 8.085708510208207),
(True, "futures", "bybit", "isolated", 0.0, 11.9),
(False, "futures", "bybit", "isolated", 0.0, 8.1),
],
)
def test_execute_entry(
mocker,
default_conf_usdt,
fee,
limit_order,
limit_order_open,
is_short,
trading_mode,
exchange_name,
margin_mode,
liq_buffer,
liq_price,
) -> None:
"""
exchange_name = binance, is_short = true
2022-01-15 16:23:20 +00:00
leverage = 5
position = 0.2 * 5
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
((2 + 0.01) - ((-1) * 1 * 10)) / ((1 * 0.01) - ((-1) * 1)) = 11.89108910891089
exchange_name = binance, is_short = false
2022-01-15 16:23:20 +00:00
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071
2023-02-10 19:58:02 +00:00
exchange_name = gate/okx, is_short = true
(open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate))
2022-01-15 16:23:20 +00:00
(10 + (2 / 1)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
2023-02-10 19:58:02 +00:00
exchange_name = gate/okx, is_short = false
(open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate))
2022-01-15 16:23:20 +00:00
(10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207
"""
2022-02-04 06:20:27 +00:00
# TODO: Split this test into multiple tests to improve readability
2022-04-06 01:02:13 +00:00
open_order = limit_order_open[entry_side(is_short)]
order = limit_order[entry_side(is_short)]
2024-05-12 14:13:11 +00:00
default_conf_usdt["trading_mode"] = trading_mode
default_conf_usdt["liquidation_buffer"] = liq_buffer
leverage = 1.0 if trading_mode == "spot" else 5.0
default_conf_usdt["exchange"]["name"] = exchange_name
if margin_mode:
2024-05-12 14:13:11 +00:00
default_conf_usdt["margin_mode"] = margin_mode
mocker.patch("freqtrade.exchange.gate.Gate.validate_ordertypes")
2018-10-09 05:06:11 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker, exchange=exchange_name)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
2021-11-19 18:34:59 +00:00
freqtrade.strategy.leverage = MagicMock(return_value=leverage)
2018-10-09 05:06:11 +00:00
stake_amount = 2
bid = 0.11
2021-09-14 21:38:26 +00:00
enter_rate_mock = MagicMock(return_value=bid)
enter_mm = MagicMock(return_value=open_order)
2018-10-09 05:06:11 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2021-09-14 21:38:26 +00:00
get_rate=enter_rate_mock,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
2021-09-14 21:38:26 +00:00
create_order=enter_mm,
get_min_pair_stake_amount=MagicMock(return_value=1),
get_max_pair_stake_amount=MagicMock(return_value=500000),
2018-10-09 05:06:11 +00:00
get_fee=fee,
2021-11-19 18:34:59 +00:00
get_funding_fees=MagicMock(return_value=0),
name=exchange_name,
get_maintenance_ratio_and_amt=MagicMock(return_value=(0.01, 0.01)),
get_max_leverage=MagicMock(return_value=10),
2018-10-09 05:06:11 +00:00
)
2022-02-09 09:38:25 +00:00
mocker.patch.multiple(
2024-05-12 14:13:11 +00:00
"freqtrade.exchange.okx.Okx",
2022-02-09 09:38:25 +00:00
get_max_pair_stake_amount=MagicMock(return_value=500000),
)
2024-05-12 14:13:11 +00:00
pair = "ETH/USDT"
2018-10-09 05:06:11 +00:00
2021-09-14 21:38:26 +00:00
assert not freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
assert enter_rate_mock.call_count == 1
assert enter_mm.call_count == 0
assert freqtrade.strategy.confirm_trade_entry.call_count == 1
2021-09-14 21:38:26 +00:00
enter_rate_mock.reset_mock()
2024-05-12 14:13:11 +00:00
open_order["id"] = "22"
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
2021-08-26 04:48:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
assert enter_rate_mock.call_count == 2
2021-09-14 21:38:26 +00:00
assert enter_mm.call_count == 1
call_args = enter_mm.call_args_list[0][1]
2024-05-12 14:13:11 +00:00
assert call_args["pair"] == pair
assert call_args["rate"] == bid
assert pytest.approx(call_args["amount"]) == round(stake_amount / bid * leverage, 8)
2021-09-14 21:38:26 +00:00
enter_rate_mock.reset_mock()
2018-10-09 05:06:11 +00:00
2018-12-12 12:05:55 +00:00
# Should create an open trade with an open order id
# As the order is not fulfilled yet
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2018-12-12 12:05:55 +00:00
assert trade
assert trade.is_open is True
2023-09-08 17:51:14 +00:00
assert trade.has_open_orders
2024-05-12 14:13:11 +00:00
assert "22" in trade.open_orders_ids
2018-12-12 12:05:55 +00:00
2018-10-09 05:06:11 +00:00
# Test calling with price
2024-05-12 14:13:11 +00:00
open_order["id"] = "33"
2018-10-09 05:06:11 +00:00
fix_price = 0.06
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, fix_price, is_short=is_short)
2021-07-18 03:58:54 +00:00
# Make sure get_rate wasn't called again
assert enter_rate_mock.call_count == 1
2018-10-09 05:06:11 +00:00
2021-09-14 21:38:26 +00:00
assert enter_mm.call_count == 2
call_args = enter_mm.call_args_list[1][1]
2024-05-12 14:13:11 +00:00
assert call_args["pair"] == pair
assert call_args["rate"] == fix_price
assert pytest.approx(call_args["amount"]) == round(stake_amount / fix_price * leverage, 8)
2018-10-09 05:06:11 +00:00
2018-12-12 12:05:55 +00:00
# In case of closed order
2024-05-12 14:13:11 +00:00
order["status"] = "closed"
order["average"] = 10
order["cost"] = 300
order["id"] = "444"
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=order))
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).all()[2]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2018-12-12 12:05:55 +00:00
assert trade
2023-09-08 17:51:14 +00:00
assert not trade.has_open_orders
2018-12-12 12:05:55 +00:00
assert trade.open_rate == 10
2024-05-12 14:13:11 +00:00
assert trade.stake_amount == round(order["average"] * order["filled"] / leverage, 8)
assert pytest.approx(trade.liquidation_price) == liq_price
2018-12-12 12:05:55 +00:00
# In case of rejected or expired order and partially filled
2024-05-12 14:13:11 +00:00
order["status"] = "expired"
order["amount"] = 30.0
order["filled"] = 20.0
order["remaining"] = 10.00
order["average"] = 0.5
order["cost"] = 10.0
order["id"] = "555"
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=order))
2021-08-26 04:48:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).all()[3]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2018-12-12 12:05:55 +00:00
assert trade
2023-09-08 17:51:14 +00:00
assert not trade.has_open_orders
2018-12-12 12:05:55 +00:00
assert trade.open_rate == 0.5
2024-05-12 14:13:11 +00:00
assert trade.stake_amount == round(order["average"] * order["filled"] / leverage, 8)
2018-12-12 12:05:55 +00:00
2021-07-11 12:10:41 +00:00
# Test with custom stake
2024-05-12 14:13:11 +00:00
order["status"] = "open"
order["id"] = "556"
2021-07-11 12:10:41 +00:00
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).all()[4]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2021-07-11 12:10:41 +00:00
assert trade
2022-03-20 19:00:30 +00:00
assert pytest.approx(trade.stake_amount) == 150
2021-07-11 12:10:41 +00:00
# Exception case
2024-05-12 14:13:11 +00:00
order["id"] = "557"
2021-07-11 12:10:41 +00:00
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).all()[5]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2021-07-11 12:10:41 +00:00
assert trade
2022-03-20 19:00:30 +00:00
assert pytest.approx(trade.stake_amount) == 2.0
2021-07-11 12:10:41 +00:00
2018-12-12 12:05:55 +00:00
# In case of the order is rejected and not filled at all
2024-05-12 14:13:11 +00:00
order["status"] = "rejected"
order["amount"] = 30.0 * leverage
order["filled"] = 0.0
order["remaining"] = 30.0
order["average"] = 0.5
order["cost"] = 0.0
order["id"] = "66"
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=order))
2021-08-26 04:48:26 +00:00
assert not freqtrade.execute_entry(pair, stake_amount)
2024-05-12 14:13:11 +00:00
assert freqtrade.strategy.leverage.call_count == 0 if trading_mode == "spot" else 2
2018-12-12 12:05:55 +00:00
# Fail to get price...
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_rate", MagicMock(return_value=0.0))
2022-03-28 17:16:12 +00:00
with pytest.raises(PricingError, match="Could not determine entry price."):
2021-09-14 21:38:26 +00:00
freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
# In case of custom entry price
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_rate", return_value=0.50)
order["status"] = "open"
order["id"] = "5566"
freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).all()[6]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
assert trade
assert trade.open_rate_requested == 0.508
# In case of custom entry price set to None
2024-05-12 14:13:11 +00:00
order["status"] = "open"
order["id"] = "5567"
freqtrade.strategy.custom_entry_price = lambda **kwargs: None
2018-10-09 05:06:11 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
get_rate=MagicMock(return_value=10),
)
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).all()[7]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
assert trade
assert trade.open_rate_requested == 10
# In case of custom entry price not float type
2024-05-12 14:13:11 +00:00
order["status"] = "open"
order["id"] = "5568"
freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price"
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).all()[8]
# Trade(id=9, pair=ETH/USDT, amount=0.20000000, is_short=False,
# leverage=1.0, open_rate=10.00000000, open_since=...)
# Trade(id=9, pair=ETH/USDT, amount=0.60000000, is_short=True,
# leverage=3.0, open_rate=10.00000000, open_since=...)
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
assert trade
assert trade.open_rate_requested == 10
# In case of too high stake amount
2024-05-12 14:13:11 +00:00
order["status"] = "open"
order["id"] = "55672"
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
get_max_pair_stake_amount=MagicMock(return_value=500),
)
freqtrade.exchange.get_max_pair_stake_amount = MagicMock(return_value=500)
assert freqtrade.execute_entry(pair, 2000, is_short=is_short)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).all()[9]
trade.is_short = is_short
2022-03-20 19:00:30 +00:00
assert pytest.approx(trade.stake_amount) == 500
2024-05-12 14:13:11 +00:00
order["id"] = "55673"
freqtrade.strategy.leverage.reset_mock()
assert freqtrade.execute_entry(pair, 200, leverage_=3)
assert freqtrade.strategy.leverage.call_count == 0
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).all()[10]
2024-05-12 14:13:11 +00:00
assert trade.leverage == 1 if trading_mode == "spot" else 3
2018-10-09 05:06:11 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-06-14 08:49:15 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
2022-04-06 01:02:13 +00:00
create_order=MagicMock(return_value=limit_order[entry_side(is_short)]),
2021-07-18 03:58:54 +00:00
get_rate=MagicMock(return_value=0.11),
get_min_pair_stake_amount=MagicMock(return_value=1),
2020-06-14 08:49:15 +00:00
get_fee=fee,
)
stake_amount = 2
2024-05-12 14:13:11 +00:00
pair = "ETH/USDT"
2020-06-14 08:49:15 +00:00
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError)
2021-08-26 04:48:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
2020-06-14 08:49:15 +00:00
2024-05-12 14:13:11 +00:00
limit_order[entry_side(is_short)]["id"] = "222"
2020-06-14 08:49:15 +00:00
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception)
2021-08-26 04:48:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
2020-06-14 08:49:15 +00:00
2024-05-12 14:13:11 +00:00
limit_order[entry_side(is_short)]["id"] = "2223"
2020-06-14 08:49:15 +00:00
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
2021-08-26 04:48:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
2020-06-14 08:49:15 +00:00
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
2021-08-26 04:48:26 +00:00
assert not freqtrade.execute_entry(pair, stake_amount)
2020-06-14 08:49:15 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_execute_entry_fully_canceled_on_create(
mocker, default_conf_usdt, fee, limit_order_open, is_short
) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_hce = mocker.spy(freqtrade, "handle_cancel_enter")
order = limit_order_open[entry_side(is_short)]
pair = "ETH/USDT"
order["symbol"] = pair
order["status"] = "canceled"
order["filled"] = 0.0
mocker.patch.multiple(
EXMS,
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
create_order=MagicMock(return_value=order),
get_rate=MagicMock(return_value=0.11),
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
)
stake_amount = 2
assert freqtrade.execute_entry(pair, stake_amount)
assert mock_hce.call_count == 1
# an order that immediately cancels completely should delete the order.
trades = Trade.get_trades().all()
assert len(trades) == 0
2022-03-19 13:54:36 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order, is_short) -> None:
2024-05-12 14:13:11 +00:00
default_conf_usdt["trading_mode"] = "futures"
default_conf_usdt["margin_mode"] = "isolated"
2022-03-19 13:54:36 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
2022-04-06 01:02:13 +00:00
create_order=MagicMock(return_value=limit_order[entry_side(is_short)]),
2022-03-19 13:54:36 +00:00
get_rate=MagicMock(return_value=0.11),
# Minimum stake-amount is ~5$
get_maintenance_ratio_and_amt=MagicMock(return_value=(0.0, 0.0)),
_fetch_and_calculate_funding_fees=MagicMock(return_value=0),
get_fee=fee,
get_max_leverage=MagicMock(return_value=5.0),
)
stake_amount = 2
2024-05-12 14:13:11 +00:00
pair = "SOL/BUSD:BUSD"
2022-03-19 13:54:36 +00:00
freqtrade.strategy.leverage = MagicMock(return_value=5.0)
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2022-03-19 13:54:36 +00:00
assert trade.leverage == 5.0
# assert trade.stake_amount == 2
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"return_value,side_effect,log_message",
[
(False, None, "Found no enter signals for whitelisted currencies. Trying again..."),
(None, DependencyException, "Unable to create trade for ETH/USDT: "),
],
)
def test_enter_positions(
mocker, default_conf_usdt, return_value, side_effect, log_message, caplog
) -> None:
caplog.set_level(logging.DEBUG)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2019-12-31 06:01:58 +00:00
mock_ct = mocker.patch(
2024-05-12 14:13:11 +00:00
"freqtrade.freqtradebot.FreqtradeBot.create_trade",
MagicMock(return_value=return_value, side_effect=side_effect),
)
n = freqtrade.enter_positions()
assert n == 0
2021-09-19 23:44:12 +00:00
assert log_has(log_message, caplog)
# create_trade should be called once for every pair in the whitelist.
2024-05-12 14:13:11 +00:00
assert mock_ct.call_count == len(default_conf_usdt["exchange"]["pair_whitelist"])
@pytest.mark.usefixtures("init_persistence")
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2022-03-30 17:32:52 +00:00
def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.freqtradebot.FreqtradeBot.handle_trade", MagicMock(return_value=True))
mocker.patch(f"{EXMS}.fetch_order", return_value=limit_order[entry_side(is_short)])
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=[])
2024-05-12 14:13:11 +00:00
order_id = "123"
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="ETH/USDT",
fee_open=0.001,
fee_close=0.001,
open_rate=0.01,
open_date=dt_now(),
stake_amount=0.01,
amount=11,
exchange="binance",
is_short=is_short,
leverage=1,
)
trade.orders.append(
Order(
ft_order_side=entry_side(is_short),
price=0.01,
ft_pair=trade.pair,
ft_amount=trade.amount,
ft_price=trade.open_rate,
order_id=order_id,
)
)
Trade.session.add(trade)
Trade.commit()
2019-10-02 00:27:42 +00:00
trades = [trade]
freqtrade.wallets.update()
n = freqtrade.exit_positions(trades)
assert n == 0
2018-04-22 09:05:23 +00:00
# Test amount not modified by fee-logic
2024-05-12 14:13:11 +00:00
assert not log_has_re(r"Applying fee to amount for Trade .*", caplog)
2018-04-22 09:05:23 +00:00
2024-05-12 14:13:11 +00:00
gra = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", return_value=0.0)
2018-04-22 09:05:23 +00:00
# test amount modified by fee-logic
n = freqtrade.exit_positions(trades)
assert n == 0
2022-08-30 18:49:53 +00:00
assert gra.call_count == 0
2018-04-22 09:05:23 +00:00
@pytest.mark.usefixtures("init_persistence")
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2022-03-30 17:32:52 +00:00
def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog, is_short) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2022-04-06 01:02:13 +00:00
order = limit_order[entry_side(is_short)]
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_order", return_value=order)
2024-05-12 14:13:11 +00:00
order_id = "123"
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="ETH/USDT",
fee_open=0.001,
fee_close=0.001,
open_rate=0.01,
2023-05-14 09:10:21 +00:00
open_date=dt_now(),
stake_amount=0.01,
amount=11,
exchange="binance",
is_short=is_short,
leverage=1,
)
2024-05-12 14:13:11 +00:00
trade.orders.append(
Order(
ft_order_side=entry_side(is_short),
price=0.01,
ft_pair=trade.pair,
ft_amount=trade.amount,
ft_price=trade.open_rate,
order_id=order_id,
ft_is_open=False,
)
)
Trade.session.add(trade)
Trade.commit()
freqtrade.wallets.update()
2019-10-02 00:27:42 +00:00
trades = [trade]
2019-03-31 13:51:45 +00:00
# Test raise of DependencyException exception
mocker.patch(
2024-05-12 14:13:11 +00:00
"freqtrade.freqtradebot.FreqtradeBot.handle_trade", side_effect=DependencyException()
)
caplog.clear()
n = freqtrade.exit_positions(trades)
assert n == 0
2024-05-12 14:13:11 +00:00
assert log_has("Unable to exit trade ETH/USDT: ", caplog)
2019-03-31 13:51:45 +00:00
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2022-03-30 17:32:52 +00:00
def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2022-04-06 01:02:13 +00:00
order = limit_order[entry_side(is_short)]
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.freqtradebot.FreqtradeBot.handle_trade", MagicMock(return_value=True))
mocker.patch("freqtrade.freqtradebot.FreqtradeBot._notify_enter")
mocker.patch(f"{EXMS}.fetch_order", return_value=order)
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=[])
mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", return_value=0.0)
order_id = order["id"]
2019-12-17 05:58:10 +00:00
trade = Trade(
fee_open=0.001,
fee_close=0.001,
open_rate=0.01,
2023-05-14 09:10:21 +00:00
open_date=dt_now(),
2019-12-17 05:58:10 +00:00
amount=11,
exchange="binance",
2022-03-20 19:00:30 +00:00
is_short=is_short,
leverage=1,
2019-12-17 05:58:10 +00:00
)
2024-05-12 14:13:11 +00:00
trade.orders.append(
Order(
ft_order_side=entry_side(is_short),
price=0.01,
order_id=order_id,
)
)
freqtrade.strategy.order_filled = MagicMock(return_value=None)
2020-09-06 13:05:47 +00:00
assert not freqtrade.update_trade_state(trade, None)
2024-05-12 14:13:11 +00:00
assert log_has_re(r"Orderid for trade .* is empty.", caplog)
caplog.clear()
2019-09-14 08:07:23 +00:00
# Add datetime explicitly since sqlalchemy defaults apply only once written to database
freqtrade.update_trade_state(trade, order_id)
# Test amount not modified by fee-logic
2024-05-12 14:13:11 +00:00
assert not log_has_re(r"Applying fee to .*", caplog)
caplog.clear()
assert not trade.has_open_orders
2024-05-12 14:13:11 +00:00
assert trade.amount == order["amount"]
assert freqtrade.strategy.order_filled.call_count == 1
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", return_value=0.01)
2022-08-30 18:46:06 +00:00
assert trade.amount == 30.0
# test amount modified by fee-logic
freqtrade.update_trade_state(trade, order_id)
2022-08-30 18:46:06 +00:00
assert trade.amount == 29.99
assert not trade.has_open_orders
trade.is_open = True
# Assert we call handle_trade() if trade is feasible for execution
freqtrade.update_trade_state(trade, order_id)
2024-05-12 14:13:11 +00:00
assert log_has_re("Found open order for.*", caplog)
2021-11-06 14:24:52 +00:00
limit_buy_order_usdt_new = deepcopy(limit_order)
2024-05-12 14:13:11 +00:00
limit_buy_order_usdt_new["filled"] = 0.0
limit_buy_order_usdt_new["status"] = "canceled"
freqtrade.strategy.order_filled = MagicMock(return_value=None)
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", side_effect=ValueError)
mocker.patch(f"{EXMS}.fetch_order", return_value=limit_buy_order_usdt_new)
res = freqtrade.update_trade_state(trade, order_id)
# Cancelled empty
assert res is True
assert freqtrade.strategy.order_filled.call_count == 0
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("initial_amount,has_rounding_fee", [(30.0 + 1e-14, True), (8.0, False)])
def test_update_trade_state_withorderdict(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
trades_for_order,
limit_order,
fee,
mocker,
initial_amount,
has_rounding_fee,
is_short,
caplog,
):
2022-04-06 01:02:13 +00:00
order = limit_order[entry_side(is_short)]
2024-05-12 14:13:11 +00:00
trades_for_order[0]["amount"] = initial_amount
order_id = "oid_123456"
2024-05-12 14:13:11 +00:00
order["id"] = order_id
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=trades_for_order)
mocker.patch("freqtrade.freqtradebot.FreqtradeBot._notify_enter")
# fetch_order should not be called!!
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_order", MagicMock(side_effect=ValueError))
2019-03-31 17:56:01 +00:00
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
amount = sum(x["amount"] for x in trades_for_order)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
caplog.clear()
2019-03-31 17:56:01 +00:00
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/USDT",
2019-03-31 17:56:01 +00:00
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
open_rate=2.0,
2023-05-14 09:10:21 +00:00
open_date=dt_now(),
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
is_open=True,
2022-03-20 19:00:30 +00:00
leverage=1,
is_short=is_short,
2019-03-31 17:56:01 +00:00
)
trade.orders.append(
Order(
2022-04-06 01:02:13 +00:00
ft_order_side=entry_side(is_short),
ft_pair=trade.pair,
ft_is_open=True,
order_id=order_id,
)
)
2024-05-12 14:13:11 +00:00
log_text = r"Applying fee on amount for .*"
2022-02-24 18:56:42 +00:00
freqtrade.update_trade_state(trade, order_id, order)
2019-03-31 17:56:01 +00:00
assert trade.amount != amount
2021-09-19 23:44:12 +00:00
if has_rounding_fee:
assert pytest.approx(trade.amount) == 29.992
assert log_has_re(log_text, caplog)
else:
2024-05-12 14:13:11 +00:00
assert pytest.approx(trade.amount) == order["amount"]
assert not log_has_re(log_text, caplog)
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
def test_update_trade_state_exception(
mocker, default_conf_usdt, is_short, limit_order, caplog
) -> None:
2022-04-06 01:02:13 +00:00
order = limit_order[entry_side(is_short)]
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_order", return_value=order)
mocker.patch("freqtrade.freqtradebot.FreqtradeBot._notify_enter")
2019-03-31 13:51:45 +00:00
2022-10-02 06:36:34 +00:00
# TODO: should not be magicmock
2019-03-31 13:51:45 +00:00
trade = MagicMock()
trade.amount = 123
2024-05-12 14:13:11 +00:00
open_order_id = "123"
2019-03-31 13:51:45 +00:00
# Test raise of OperationalException exception
mocker.patch(
2024-05-12 14:13:11 +00:00
"freqtrade.freqtradebot.FreqtradeBot.get_real_amount", side_effect=DependencyException()
)
freqtrade.update_trade_state(trade, open_order_id)
2024-05-12 14:13:11 +00:00
assert log_has("Could not update trade amount: ", caplog)
def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_order", MagicMock(side_effect=InvalidOrderException))
2022-10-02 06:36:34 +00:00
# TODO: should not be magicmock
trade = MagicMock()
2024-05-12 14:13:11 +00:00
open_order_id = "123"
# Test raise of OperationalException exception
grm_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", MagicMock())
freqtrade.update_trade_state(trade, open_order_id)
assert grm_mock.call_count == 0
2024-05-12 14:13:11 +00:00
assert log_has(f"Unable to fetch order {open_order_id}: ", caplog)
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_update_trade_state_sell(
default_conf_usdt, trades_for_order, limit_order_open, limit_order, is_short, mocker
):
buy_order = limit_order[entry_side(is_short)]
open_order = limit_order_open[exit_side(is_short)]
l_order = limit_order[exit_side(is_short)]
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=trades_for_order)
# fetch_order should not be called!!
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_order", MagicMock(side_effect=ValueError))
wallet_mock = MagicMock()
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.wallets.Wallets.update", wallet_mock)
patch_exchange(mocker)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
amount = l_order["amount"]
wallet_mock.reset_mock()
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
open_rate=0.245441,
fee_open=0.0025,
fee_close=0.0025,
2023-05-14 09:10:21 +00:00
open_date=dt_now(),
is_open=True,
2021-10-04 05:13:34 +00:00
interest_rate=0.0005,
2022-03-20 19:00:30 +00:00
leverage=1,
is_short=is_short,
)
2024-05-12 14:13:11 +00:00
order = Order.parse_from_ccxt_object(buy_order, "LTC/ETH", entry_side(is_short))
trade.orders.append(order)
2024-05-12 14:13:11 +00:00
order = Order.parse_from_ccxt_object(open_order, "LTC/ETH", exit_side(is_short))
2020-08-13 13:54:36 +00:00
trade.orders.append(order)
2024-05-12 14:13:11 +00:00
assert order.status == "open"
freqtrade.update_trade_state(trade, trade.open_orders_ids[-1], l_order)
2024-05-12 14:13:11 +00:00
assert trade.amount == l_order["amount"]
2024-04-18 20:51:25 +00:00
# Wallet needs to be updated after closing a limit-sell order to re-enable buying
assert wallet_mock.call_count == 1
assert not trade.is_open
2020-08-13 13:54:36 +00:00
# Order is updated by update_trade_state
2024-05-12 14:13:11 +00:00
assert order.status == "closed"
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"is_short,close_profit",
[
(False, 0.09451372),
(True, 0.08635224),
],
)
def test_handle_trade(
2021-10-14 10:05:50 +00:00
default_conf_usdt, limit_order_open, limit_order, fee, mocker, is_short, close_profit
) -> None:
open_order = limit_order_open[exit_side(is_short)]
2022-04-06 01:02:13 +00:00
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 2.19, "ask": 2.2, "last": 2.19}),
create_order=MagicMock(
side_effect=[
enter_order,
open_order,
]
),
2018-06-16 23:23:12 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
assert trade
time.sleep(0.01) # Race condition fix
assert trade.is_open is True
2019-12-18 19:16:53 +00:00
freqtrade.wallets.update()
2024-05-12 14:13:11 +00:00
patch_get_signal(
freqtrade,
enter_long=False,
exit_short=is_short,
exit_long=not is_short,
exit_tag="sell_signal1",
)
assert freqtrade.handle_trade(trade) is True
2024-05-12 14:13:11 +00:00
assert trade.open_orders_ids[-1] == exit_order["id"]
# Simulate fulfilled LIMIT_SELL order for trade
2022-07-23 13:14:38 +00:00
trade.orders[-1].ft_is_open = False
2024-05-12 14:13:11 +00:00
trade.orders[-1].status = "closed"
2022-07-23 13:14:38 +00:00
trade.orders[-1].filled = trade.orders[-1].remaining
trade.orders[-1].remaining = 0.0
trade.update_trade(trade.orders[-1])
2022-07-23 13:14:38 +00:00
assert trade.close_rate == (2.0 if is_short else 2.2)
assert pytest.approx(trade.close_profit) == close_profit
2022-08-16 06:01:07 +00:00
assert pytest.approx(trade.calc_profit(trade.close_rate)) == 5.685
assert trade.close_date is not None
2024-05-12 14:13:11 +00:00
assert trade.exit_reason == "sell_signal1"
@pytest.mark.parametrize("is_short", [False, True])
2021-09-14 21:38:26 +00:00
def test_handle_overlapping_signals(
default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, is_short
2021-09-14 21:38:26 +00:00
) -> None:
open_order = limit_order_open[exit_side(is_short)]
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2024-05-12 14:13:11 +00:00
create_order=MagicMock(
side_effect=[
open_order,
{"id": 1234553382},
]
),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
if is_short:
patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=True, exit_long=True)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
# Buy and Sell triggering, so doing nothing ...
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2021-09-14 21:38:26 +00:00
nb_trades = len(trades)
assert nb_trades == 0
# Buy is triggering, so buying ...
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2021-09-14 21:38:26 +00:00
for trade in trades:
trade.is_short = is_short
nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True
# Buy and Sell are not triggering, so doing nothing ...
patch_get_signal(freqtrade, enter_long=False)
assert freqtrade.handle_trade(trades[0]) is False
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2021-09-14 21:38:26 +00:00
for trade in trades:
trade.is_short = is_short
nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True
# Buy and Sell are triggering, so doing nothing ...
if is_short:
patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=True, exit_long=True)
assert freqtrade.handle_trade(trades[0]) is False
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2021-09-14 21:38:26 +00:00
for trade in trades:
trade.is_short = is_short
nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True
# Sell is triggering, guess what : we are Selling!
if is_short:
patch_get_signal(freqtrade, enter_long=False, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2021-09-14 21:38:26 +00:00
for trade in trades:
trade.is_short = is_short
assert freqtrade.handle_trade(trades[0]) is True
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
def test_handle_trade_roi(
default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short
) -> None:
2022-04-06 01:02:13 +00:00
open_order = limit_order_open[entry_side(is_short)]
2021-09-14 21:38:26 +00:00
caplog.set_level(logging.DEBUG)
patch_RPCManager(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2024-05-12 14:13:11 +00:00
create_order=MagicMock(
side_effect=[
open_order,
{"id": 1234553382},
]
),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
trade.is_open = True
# FIX: sniffing logs, suggest handle_trade should not execute_trade_exit
# instead that responsibility should be moved out of handle_trade(),
# we might just want to check if we are in a sell condition without
# executing
# if ROI is reached we must sell
caplog.clear()
2022-01-01 18:16:24 +00:00
patch_get_signal(freqtrade)
assert freqtrade.handle_trade(trade)
2024-05-12 14:13:11 +00:00
assert log_has("ETH/USDT - Required profit reached. exit_type=ExitType.ROI", caplog)
@pytest.mark.parametrize("is_short", [False, True])
2022-04-05 18:07:58 +00:00
def test_handle_trade_use_exit_signal(
default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short
2021-09-14 21:38:26 +00:00
) -> None:
enter_open_order = limit_order_open[exit_side(is_short)]
2022-04-06 01:02:13 +00:00
exit_open_order = limit_order_open[entry_side(is_short)]
2021-09-14 21:38:26 +00:00
2022-04-05 18:07:58 +00:00
# use_exit_signal is True buy default
caplog.set_level(logging.DEBUG)
patch_RPCManager(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2024-05-12 14:13:11 +00:00
create_order=MagicMock(
side_effect=[
enter_open_order,
exit_open_order,
]
),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
trade.is_open = True
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
assert not freqtrade.handle_trade(trade)
if is_short:
patch_get_signal(freqtrade, enter_long=False, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
assert freqtrade.handle_trade(trade)
2024-05-12 14:13:11 +00:00
assert log_has("ETH/USDT - Sell signal received. exit_type=ExitType.EXIT_SIGNAL", caplog)
@pytest.mark.parametrize("is_short", [False, True])
def test_close_trade(
2022-03-30 17:32:52 +00:00
default_conf_usdt, ticker_usdt, limit_order_open, limit_order, fee, mocker, is_short
) -> None:
open_order = limit_order_open[exit_side(is_short)]
enter_order = limit_order[exit_side(is_short)]
2022-04-06 01:02:13 +00:00
exit_order = limit_order[entry_side(is_short)]
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-09-14 21:38:26 +00:00
create_order=MagicMock(return_value=open_order),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create trade and sell it
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
assert trade
2024-05-12 14:13:11 +00:00
oobj = Order.parse_from_ccxt_object(enter_order, enter_order["symbol"], trade.entry_side)
trade.update_trade(oobj)
2024-05-12 14:13:11 +00:00
oobj = Order.parse_from_ccxt_object(exit_order, exit_order["symbol"], trade.exit_side)
trade.update_trade(oobj)
assert trade.is_open is False
2024-05-12 14:13:11 +00:00
with pytest.raises(DependencyException, match=r".*closed trade.*"):
freqtrade.handle_trade(trade)
def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog):
ftbot = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.freqtradebot.FreqtradeBot.create_trade")
patch_get_signal(ftbot)
ftbot.strategy.bot_loop_start = MagicMock(side_effect=ValueError)
ftbot.strategy.analyze = MagicMock()
ftbot.process()
2024-05-12 14:13:11 +00:00
assert log_has_re(r"Strategy caused the following exception.*", caplog)
assert ftbot.strategy.bot_loop_start.call_count == 1
assert ftbot.strategy.analyze.call_count == 1
@pytest.mark.parametrize("is_short", [False, True])
def test_manage_open_orders_entry_usercustom(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
limit_buy_order_old,
open_trade,
limit_sell_order_old,
fee,
mocker,
is_short,
) -> None:
2021-09-14 21:38:26 +00:00
old_order = limit_sell_order_old if is_short else limit_buy_order_old
2024-05-12 14:13:11 +00:00
old_order["id"] = open_trade.open_orders_ids[0]
2022-02-24 18:56:42 +00:00
2022-03-26 11:01:28 +00:00
default_conf_usdt["unfilledtimeout"] = {"entry": 1400, "exit": 30}
rpc_mock = patch_RPCManager(mocker)
2021-09-14 21:38:26 +00:00
cancel_order_mock = MagicMock(return_value=old_order)
cancel_enter_order = deepcopy(old_order)
2024-05-12 14:13:11 +00:00
cancel_enter_order["status"] = "canceled"
2021-09-14 21:38:26 +00:00
cancel_order_wr_mock = MagicMock(return_value=cancel_enter_order)
2020-08-01 13:59:50 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-09-14 21:38:26 +00:00
fetch_order=MagicMock(return_value=old_order),
cancel_order=cancel_order_mock,
cancel_order_with_result=cancel_order_wr_mock,
2024-05-12 14:13:11 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.is_short = is_short
2024-05-12 14:13:11 +00:00
open_trade.orders[0].side = "sell" if is_short else "buy"
open_trade.orders[0].ft_order_side = "sell" if is_short else "buy"
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade)
2022-08-19 07:10:54 +00:00
Trade.commit()
# Ensure default is to return empty (so not mocked yet)
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.manage_open_orders()
assert cancel_order_mock.call_count == 0
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(
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:13:11 +00:00
).all()
nb_trades = len(trades)
assert nb_trades == 1
assert freqtrade.strategy.check_entry_timeout.call_count == 1
freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError)
freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 0
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(
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:13:11 +00:00
).all()
nb_trades = len(trades)
assert nb_trades == 1
assert freqtrade.strategy.check_entry_timeout.call_count == 1
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True)
# Trade should be closed since the function returns true
freqtrade.manage_open_orders()
2020-08-01 13:59:50 +00:00
assert cancel_order_wr_mock.call_count == 1
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 2
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(
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:13:11 +00:00
).all()
nb_trades = len(trades)
assert nb_trades == 0
assert freqtrade.strategy.check_entry_timeout.call_count == 1
@pytest.mark.parametrize("is_short", [False, True])
def test_manage_open_orders_entry(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
limit_buy_order_old,
open_trade,
limit_sell_order_old,
fee,
mocker,
is_short,
) -> None:
2021-09-14 21:38:26 +00:00
old_order = limit_sell_order_old if is_short else limit_buy_order_old
rpc_mock = patch_RPCManager(mocker)
2023-08-25 05:13:39 +00:00
2024-05-12 14:13:11 +00:00
order = Order.parse_from_ccxt_object(old_order, "mocked", "buy")
open_trade.orders[0] = order
2023-09-22 04:30:43 +00:00
limit_entry_cancel = deepcopy(old_order)
2024-05-12 14:13:11 +00:00
limit_entry_cancel["status"] = "canceled"
2023-09-22 04:30:43 +00:00
cancel_order_mock = MagicMock(return_value=limit_entry_cancel)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-09-14 21:38:26 +00:00
fetch_order=MagicMock(return_value=old_order),
cancel_order_with_result=cancel_order_mock,
2024-05-12 14:13:11 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.is_short = is_short
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade)
2022-08-19 07:10:54 +00:00
Trade.commit()
2022-03-30 17:32:52 +00:00
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234)
2023-07-28 05:07:10 +00:00
# check it does cancel entry orders over the time limit
freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 1
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 2
trades = Trade.session.scalars(
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:13:11 +00:00
).all()
nb_trades = len(trades)
assert nb_trades == 0
2023-07-28 05:07:10 +00:00
# Custom user entry-timeout is never called
2022-03-30 17:32:52 +00:00
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(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
limit_buy_order_old,
open_trade,
limit_sell_order_old,
fee,
mocker,
caplog,
is_short,
) -> None:
2022-05-04 04:40:12 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
old_order = limit_sell_order_old if is_short else limit_buy_order_old
2024-05-12 14:13:11 +00:00
old_order["id"] = open_trade.open_orders[0].order_id
2023-09-22 04:30:43 +00:00
limit_entry_cancel = deepcopy(old_order)
2024-05-12 14:13:11 +00:00
limit_entry_cancel["status"] = "canceled"
2023-09-22 04:30:43 +00:00
cancel_order_mock = MagicMock(return_value=limit_entry_cancel)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=old_order),
cancel_order_with_result=cancel_order_mock,
2024-05-12 14:13:11 +00:00
get_fee=fee,
)
open_trade.is_short = is_short
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade)
2022-08-19 07:10:54 +00:00
Trade.commit()
# 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()
2024-05-12 14:13:11 +00:00
trades = Trade.session.scalars(select(Trade).where(Order.ft_trade_id == Trade.id)).all()
2022-05-04 05:13:02 +00:00
assert len(trades) == 0
2023-03-16 06:25:04 +00:00
assert len(Order.session.scalars(select(Order)).all()) == 0
2024-05-12 14:13:11 +00:00
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
2023-09-22 04:37:36 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_adjust_entry_replace_fail(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
limit_buy_order_old,
open_trade,
limit_sell_order_old,
fee,
mocker,
caplog,
is_short,
2023-09-22 04:37:36 +00:00
) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
old_order = limit_sell_order_old if is_short else limit_buy_order_old
2024-05-12 14:13:11 +00:00
old_order["id"] = open_trade.open_orders[0].order_id
2023-09-22 04:37:36 +00:00
limit_entry_cancel = deepcopy(old_order)
2024-05-12 14:13:11 +00:00
limit_entry_cancel["status"] = "open"
2023-09-22 04:37:36 +00:00
cancel_order_mock = MagicMock(return_value=limit_entry_cancel)
fetch_order_mock = MagicMock(return_value=old_order)
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=fetch_order_mock,
cancel_order_with_result=cancel_order_mock,
2024-05-12 14:13:11 +00:00
get_fee=fee,
2023-09-22 04:37:36 +00:00
)
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.freqtradebot.sleep")
2023-09-22 04:37:36 +00:00
open_trade.is_short = is_short
Trade.session.add(open_trade)
Trade.commit()
# Timeout to not interfere
freqtrade.strategy.ft_check_timed_out = MagicMock(return_value=False)
# Attempt replace order - which fails
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=12234)
freqtrade.manage_open_orders()
2024-05-12 14:13:11 +00:00
trades = Trade.session.scalars(select(Trade).where(Order.ft_trade_id == Trade.id)).all()
2023-09-22 04:37:36 +00:00
assert len(trades) == 0
assert len(Order.session.scalars(select(Order)).all()) == 0
assert fetch_order_mock.call_count == 4
2024-05-12 14:13:11 +00:00
assert log_has_re(r"Could not cancel order.*, therefore not replacing\.", caplog)
2023-09-22 04:37:36 +00:00
# Entry adjustment is called
assert freqtrade.strategy.adjust_entry_price.call_count == 1
2023-10-06 18:13:11 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_adjust_entry_replace_fail_create_order(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
limit_buy_order_old,
open_trade,
limit_sell_order_old,
fee,
mocker,
caplog,
is_short,
2023-10-06 18:13:11 +00:00
) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
old_order = limit_sell_order_old if is_short else limit_buy_order_old
2024-05-12 14:13:11 +00:00
old_order["id"] = open_trade.open_orders[0].order_id
2023-10-06 18:13:11 +00:00
limit_entry_cancel = deepcopy(old_order)
2024-05-12 14:13:11 +00:00
limit_entry_cancel["status"] = "canceled"
2023-10-06 18:13:11 +00:00
cancel_order_mock = MagicMock(return_value=limit_entry_cancel)
fetch_order_mock = MagicMock(return_value=old_order)
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=fetch_order_mock,
cancel_order_with_result=cancel_order_mock,
2024-05-12 14:13:11 +00:00
get_fee=fee,
)
mocker.patch("freqtrade.freqtradebot.sleep")
mocker.patch(
"freqtrade.freqtradebot.FreqtradeBot.execute_entry", side_effect=DependencyException()
2023-10-06 18:13:11 +00:00
)
open_trade.is_short = is_short
Trade.session.add(open_trade)
Trade.commit()
# Timeout to not interfere
freqtrade.strategy.ft_check_timed_out = MagicMock(return_value=False)
# Attempt replace order - which fails
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=12234)
freqtrade.manage_open_orders()
2024-05-12 14:13:11 +00:00
trades = Trade.session.scalars(select(Trade).where(Trade.is_open.is_(True))).all()
2023-10-06 18:13:11 +00:00
assert len(trades) == 0
assert len(Order.session.scalars(select(Order)).all()) == 0
assert fetch_order_mock.call_count == 1
2024-05-12 14:13:11 +00:00
assert log_has_re(r"Could not replace order for.*", caplog)
2023-10-06 18:13:11 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_adjust_entry_maintain_replace(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
limit_buy_order_old,
open_trade,
limit_sell_order_old,
fee,
mocker,
caplog,
is_short,
) -> None:
2022-05-04 04:40:12 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
old_order = limit_sell_order_old if is_short else limit_buy_order_old
2024-05-12 14:13:11 +00:00
old_order["id"] = open_trade.open_orders_ids[0]
2023-09-22 04:30:43 +00:00
limit_entry_cancel = deepcopy(old_order)
2024-05-12 14:13:11 +00:00
limit_entry_cancel["status"] = "canceled"
2023-09-22 04:30:43 +00:00
cancel_order_mock = MagicMock(return_value=limit_entry_cancel)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=old_order),
cancel_order_with_result=cancel_order_mock,
2023-08-27 08:02:41 +00:00
get_fee=fee,
_dry_is_price_crossed=MagicMock(return_value=False),
)
open_trade.is_short = is_short
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade)
2022-08-19 07:10:54 +00:00
Trade.commit()
# Timeout to not interfere
freqtrade.strategy.ft_check_timed_out = MagicMock(return_value=False)
# Check that order is maintained
2024-05-12 14:13:11 +00:00
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=old_order["price"])
freqtrade.manage_open_orders()
trades = Trade.session.scalars(
2024-05-12 14:13:11 +00:00
select(Trade).where(Order.ft_is_open.is_(True)).where(Order.ft_trade_id == Trade.id)
).all()
2022-05-04 05:13:02 +00:00
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()
2023-08-27 08:02:41 +00:00
assert freqtrade.strategy.adjust_entry_price.call_count == 1
trades = Trade.session.scalars(
2024-05-12 14:13:11 +00:00
select(Trade).where(Order.ft_is_open.is_(True)).where(Order.ft_trade_id == Trade.id)
).all()
2022-05-04 05:13:02 +00:00
assert len(trades) == 1
2023-03-16 06:25:04 +00:00
nb_all_orders = len(Order.session.scalars(select(Order)).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
2024-05-12 14:13:11 +00:00
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
2022-04-16 12:44:07 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_cancelled_buy(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
limit_buy_order_old,
open_trade,
limit_sell_order_old,
fee,
mocker,
caplog,
is_short,
) -> None:
2024-05-12 14:13:11 +00:00
"""Handle Buy order cancelled on exchange"""
2021-09-14 21:38:26 +00:00
old_order = limit_sell_order_old if is_short else limit_buy_order_old
2019-02-03 12:39:19 +00:00
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
old_order.update({"status": "canceled", "filled": 0.0})
old_order["side"] = "buy" if is_short else "sell"
old_order["id"] = open_trade.open_orders[0].order_id
2019-02-03 12:39:19 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-09-14 21:38:26 +00:00
fetch_order=MagicMock(return_value=old_order),
2019-02-03 12:39:19 +00:00
cancel_order=cancel_order_mock,
2024-05-12 14:13:11 +00:00
get_fee=fee,
2019-02-03 12:39:19 +00:00
)
freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.is_short = is_short
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade)
2022-08-19 07:10:54 +00:00
Trade.commit()
2019-02-03 12:39:19 +00:00
# check it does cancel buy orders over the time limit
freqtrade.manage_open_orders()
2019-02-03 12:39:19 +00:00
assert cancel_order_mock.call_count == 0
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 2
trades = Trade.session.scalars(
2024-05-12 14:13:11 +00:00
select(Trade).where(Order.ft_is_open.is_(True)).where(Order.ft_trade_id == Trade.id)
).all()
2022-05-04 05:13:02 +00:00
assert len(trades) == 0
2024-05-12 14:13:11 +00:00
exit_name = "Buy" if is_short else "Sell"
2023-09-09 15:37:05 +00:00
assert log_has_re(f"{exit_name} order cancelled on exchange for Trade.*", caplog)
2019-02-03 12:39:19 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_manage_open_orders_buy_exception(
2022-03-30 17:32:52 +00:00
default_conf_usdt, ticker_usdt, open_trade, is_short, fee, mocker
) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2020-06-28 17:45:42 +00:00
fetch_order=MagicMock(side_effect=ExchangeError),
cancel_order=cancel_order_mock,
2024-05-12 14:13:11 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2022-03-03 06:00:10 +00:00
open_trade.is_short = is_short
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade)
2022-08-19 07:10:54 +00:00
Trade.commit()
# check it does cancel buy orders over the time limit
freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 0
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 1
2023-09-09 07:54:12 +00:00
assert len(open_trade.open_orders) == 1
@pytest.mark.parametrize("is_short", [False, True])
def test_manage_open_orders_exit_usercustom(
2024-05-12 14:13:11 +00:00
default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker, is_short, open_trade_usdt, caplog
) -> None:
default_conf_usdt["unfilledtimeout"] = {"entry": 1440, "exit": 1440, "exit_timeout_count": 1}
2024-05-12 14:13:11 +00:00
limit_sell_order_old["amount"] = open_trade_usdt.amount
limit_sell_order_old["remaining"] = open_trade_usdt.amount
2023-08-25 05:13:39 +00:00
2022-03-06 13:18:25 +00:00
if is_short:
2024-05-12 14:13:11 +00:00
limit_sell_order_old["side"] = "buy"
2022-03-06 13:18:25 +00:00
open_trade_usdt.is_short = is_short
2024-05-12 14:13:11 +00:00
open_exit_order = Order.parse_from_ccxt_object(
limit_sell_order_old, "mocked", "buy" if is_short else "sell"
)
2023-06-20 16:14:16 +00:00
open_trade_usdt.orders[-1] = open_exit_order
2022-01-30 18:25:09 +00:00
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.0)
et_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit")
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_sell_order_old),
2024-05-12 14:13:11 +00:00
cancel_order=cancel_order_mock,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2023-05-14 16:14:35 +00:00
open_trade_usdt.open_date = dt_now() - timedelta(hours=5)
open_trade_usdt.close_date = dt_now() - timedelta(minutes=601)
open_trade_usdt.close_profit_abs = 0.001
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade_usdt)
2022-08-19 07:10:54 +00:00
Trade.commit()
# Ensure default is false
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.manage_open_orders()
assert cancel_order_mock.call_count == 0
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 1
2023-06-20 16:14:16 +00:00
assert freqtrade.strategy.check_exit_timeout.call_count == 1
assert freqtrade.strategy.check_entry_timeout.call_count == 0
freqtrade.strategy.check_exit_timeout = MagicMock(side_effect=KeyError)
freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError)
# Return Error - No impact
freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 0
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 1
2023-06-20 16:14:16 +00:00
assert freqtrade.strategy.check_exit_timeout.call_count == 1
assert freqtrade.strategy.check_entry_timeout.call_count == 0
# Return True - sells!
freqtrade.strategy.check_exit_timeout = MagicMock(return_value=True)
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True)
freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 1
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 2
2023-06-20 16:14:16 +00:00
assert freqtrade.strategy.check_exit_timeout.call_count == 1
assert freqtrade.strategy.check_entry_timeout.call_count == 0
2023-03-13 19:28:13 +00:00
# 2nd canceled trade - Fail execute exit
2021-11-06 12:10:41 +00:00
caplog.clear()
2023-08-25 05:13:39 +00:00
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.persistence.Trade.get_canceled_exit_order_count", return_value=1)
mocker.patch(
"freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit", side_effect=DependencyException
)
freqtrade.manage_open_orders()
2024-05-12 14:13:11 +00:00
assert log_has_re("Unable to emergency exit .*", caplog)
2024-05-12 14:13:11 +00:00
et_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit")
caplog.clear()
# 2nd canceled trade ...
2023-03-13 19:28:13 +00:00
# If cancelling fails - no emergency exit!
2024-05-12 14:13:11 +00:00
with patch("freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit", return_value=False):
freqtrade.manage_open_orders()
assert et_mock.call_count == 0
freqtrade.manage_open_orders()
2024-05-12 14:13:11 +00:00
assert log_has_re("Emergency exiting trade.*", caplog)
2021-11-06 12:10:41 +00:00
assert et_mock.call_count == 1
@pytest.mark.parametrize("is_short", [False, True])
def test_manage_open_orders_exit(
2022-03-30 17:32:52 +00:00
default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker, is_short, open_trade_usdt
) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
2024-05-12 14:13:11 +00:00
limit_sell_order_old["id"] = "123456789_exit"
limit_sell_order_old["side"] = "buy" if is_short else "sell"
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_sell_order_old),
2022-09-30 13:26:34 +00:00
cancel_order=cancel_order_mock,
get_min_pair_stake_amount=MagicMock(return_value=0),
)
freqtrade = FreqtradeBot(default_conf_usdt)
2023-05-14 16:14:35 +00:00
open_trade_usdt.open_date = dt_now() - timedelta(hours=5)
open_trade_usdt.close_date = dt_now() - timedelta(minutes=601)
open_trade_usdt.close_profit_abs = 0.001
2022-03-06 13:33:04 +00:00
open_trade_usdt.is_short = is_short
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade_usdt)
2022-08-19 07:10:54 +00:00
Trade.commit()
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.manage_open_orders()
assert cancel_order_mock.call_count == 1
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 2
assert open_trade_usdt.is_open is True
2020-02-23 12:11:33 +00:00
# Custom user sell-timeout is never called
assert freqtrade.strategy.check_exit_timeout.call_count == 0
assert freqtrade.strategy.check_entry_timeout.call_count == 0
@pytest.mark.parametrize("is_short", [False, True])
2022-03-30 17:32:52 +00:00
def test_check_handle_cancelled_exit(
2024-05-12 14:13:11 +00:00
default_conf_usdt, ticker_usdt, limit_sell_order_old, open_trade_usdt, is_short, mocker, caplog
) -> None:
2024-05-12 14:13:11 +00:00
"""Handle sell order cancelled on exchange"""
2019-02-03 12:39:19 +00:00
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
2024-05-12 14:13:11 +00:00
limit_sell_order_old.update({"status": "canceled", "filled": 0.0})
limit_sell_order_old["side"] = "buy" if is_short else "sell"
limit_sell_order_old["id"] = open_trade_usdt.open_orders[0].order_id
2019-02-03 12:39:19 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_sell_order_old),
2024-05-12 14:13:11 +00:00
cancel_order_with_result=cancel_order_mock,
2019-02-03 12:39:19 +00:00
)
freqtrade = FreqtradeBot(default_conf_usdt)
2019-02-03 12:39:19 +00:00
2023-05-14 16:14:35 +00:00
open_trade_usdt.open_date = dt_now() - timedelta(hours=5)
open_trade_usdt.close_date = dt_now() - timedelta(minutes=601)
2022-03-06 13:33:04 +00:00
open_trade_usdt.is_short = is_short
2019-02-03 12:39:19 +00:00
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade_usdt)
2022-08-19 07:10:54 +00:00
Trade.commit()
2019-02-03 12:39:19 +00:00
# check it does cancel sell orders over the time limit
freqtrade.manage_open_orders()
2019-02-03 12:39:19 +00:00
assert cancel_order_mock.call_count == 0
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 2
assert open_trade_usdt.is_open is True
2024-05-12 14:13:11 +00:00
exit_name = "Buy" if is_short else "Sell"
2022-03-06 13:33:04 +00:00
assert log_has_re(f"{exit_name} order cancelled on exchange for Trade.*", caplog)
2019-02-03 12:39:19 +00:00
@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.parametrize("leverage", [1, 3, 5, 10])
def test_manage_open_orders_partial(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
limit_buy_order_old_partial,
is_short,
leverage,
open_trade,
mocker,
) -> None:
rpc_mock = patch_RPCManager(mocker)
2022-03-06 13:33:04 +00:00
open_trade.is_short = is_short
open_trade.leverage = leverage
2024-05-12 14:13:11 +00:00
open_trade.orders[0].ft_order_side = "sell" if is_short else "buy"
2023-08-25 05:13:39 +00:00
2024-05-12 14:13:11 +00:00
limit_buy_order_old_partial["id"] = open_trade.orders[0].order_id
limit_buy_order_old_partial["side"] = "sell" if is_short else "buy"
2020-08-01 13:59:50 +00:00
limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
2024-05-12 14:13:11 +00:00
limit_buy_canceled["status"] = "canceled"
2020-08-01 13:59:50 +00:00
cancel_order_mock = MagicMock(return_value=limit_buy_canceled)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_buy_order_old_partial),
2024-05-12 14:13:11 +00:00
cancel_order_with_result=cancel_order_mock,
)
freqtrade = FreqtradeBot(default_conf_usdt)
prior_stake = open_trade.stake_amount
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade)
2022-08-19 07:10:54 +00:00
Trade.commit()
# check it does cancel buy orders over the time limit
# note this is for a partially-complete buy order
freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 1
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 3
2024-05-12 14:13:11 +00:00
trades = Trade.session.scalars(select(Trade)).all()
assert len(trades) == 1
assert trades[0].amount == 23.0
assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount / leverage
assert trades[0].stake_amount != prior_stake
2023-09-08 17:51:14 +00:00
assert not trades[0].has_open_orders
@pytest.mark.parametrize("is_short", [False, True])
def test_manage_open_orders_partial_fee(
2024-05-12 14:13:11 +00:00
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,
) -> None:
2022-03-06 13:33:04 +00:00
open_trade.is_short = is_short
2024-05-12 14:13:11 +00:00
open_trade.orders[0].ft_order_side = "sell" if is_short else "buy"
2019-10-18 04:38:07 +00:00
rpc_mock = patch_RPCManager(mocker)
2024-05-12 14:13:11 +00:00
limit_buy_order_old_partial["id"] = open_trade.orders[0].order_id
limit_buy_order_old_partial_canceled["id"] = open_trade.open_orders_ids[0]
limit_buy_order_old_partial["side"] = "sell" if is_short else "buy"
limit_buy_order_old_partial_canceled["side"] = "sell" if is_short else "buy"
2022-03-06 13:33:04 +00:00
2019-10-18 04:38:07 +00:00
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=0))
2019-10-18 04:38:07 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_buy_order_old_partial),
cancel_order_with_result=cancel_order_mock,
2019-10-18 04:38:07 +00:00
get_trades_for_order=MagicMock(return_value=trades_for_order),
)
freqtrade = FreqtradeBot(default_conf_usdt)
2024-05-12 14:13:11 +00:00
assert open_trade.amount == limit_buy_order_old_partial["amount"]
2019-10-18 04:38:07 +00:00
open_trade.fee_open = fee()
open_trade.fee_close = fee()
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade)
2022-08-19 07:10:54 +00:00
Trade.commit()
2019-10-18 04:38:07 +00:00
# cancelling a half-filled order should update the amount to the bought amount
# and apply fees if necessary.
freqtrade.manage_open_orders()
assert log_has_re(r"Applying fee on amount for Trade.*", caplog)
2019-10-18 04:38:07 +00:00
assert cancel_order_mock.call_count == 1
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 3
2024-05-12 14:13:11 +00:00
trades = Trade.session.scalars(select(Trade).where(Order.ft_trade_id == Trade.id)).all()
2019-10-18 04:38:07 +00:00
assert len(trades) == 1
2020-03-24 15:16:10 +00:00
# Verify that trade has been updated
2024-05-12 14:13:11 +00:00
assert (
trades[0].amount
== (limit_buy_order_old_partial["amount"] - limit_buy_order_old_partial["remaining"])
- 0.023
)
assert not trades[0].has_open_orders
2022-04-06 01:02:13 +00:00
assert trades[0].fee_updated(open_trade.entry_side)
assert pytest.approx(trades[0].fee_open) == 0.001
2019-10-18 04:38:07 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_manage_open_orders_partial_except(
2024-05-12 14:13:11 +00:00
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,
2021-09-15 03:08:15 +00:00
) -> None:
2022-02-26 16:30:35 +00:00
open_trade.is_short = is_short
2024-05-12 14:13:11 +00:00
open_trade.orders[0].ft_order_side = "sell" if is_short else "buy"
rpc_mock = patch_RPCManager(mocker)
2024-05-12 14:13:11 +00:00
limit_buy_order_old_partial_canceled["id"] = open_trade.open_orders_ids[0]
limit_buy_order_old_partial["id"] = open_trade.open_orders_ids[0]
2022-03-03 06:00:10 +00:00
if is_short:
2024-05-12 14:13:11 +00:00
limit_buy_order_old_partial["side"] = "sell"
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_buy_order_old_partial),
cancel_order_with_result=cancel_order_mock,
get_trades_for_order=MagicMock(return_value=trades_for_order),
)
2024-05-12 14:13:11 +00:00
mocker.patch(
"freqtrade.freqtradebot.FreqtradeBot.get_real_amount",
MagicMock(side_effect=DependencyException),
)
freqtrade = FreqtradeBot(default_conf_usdt)
2024-05-12 14:13:11 +00:00
assert open_trade.amount == limit_buy_order_old_partial["amount"]
open_trade.fee_open = fee()
open_trade.fee_close = fee()
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade)
2022-08-19 07:10:54 +00:00
Trade.commit()
# cancelling a half-filled order should update the amount to the bought amount
# and apply fees if necessary.
freqtrade.manage_open_orders()
assert log_has_re(r"Could not update trade amount: .*", caplog)
assert cancel_order_mock.call_count == 1
2022-09-08 16:10:32 +00:00
assert rpc_mock.call_count == 3
2024-05-12 14:13:11 +00:00
trades = Trade.session.scalars(select(Trade)).all()
assert len(trades) == 1
2020-03-24 15:16:10 +00:00
# Verify that trade has been updated
2024-05-12 14:13:11 +00:00
assert trades[0].amount == (
limit_buy_order_old_partial["amount"] - limit_buy_order_old_partial["remaining"]
)
assert not trades[0].has_open_orders
assert trades[0].fee_open == fee()
2024-05-12 14:13:11 +00:00
def test_manage_open_orders_exception(
default_conf_usdt, ticker_usdt, open_trade_usdt, mocker, caplog
) -> None:
2018-03-05 08:11:13 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2018-03-05 08:11:13 +00:00
cancel_order_mock = MagicMock()
mocker.patch.multiple(
2024-05-12 14:13:11 +00:00
"freqtrade.freqtradebot.FreqtradeBot",
handle_cancel_enter=MagicMock(),
handle_cancel_exit=MagicMock(),
2018-03-05 08:11:13 +00:00
)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2024-05-12 14:13:11 +00:00
fetch_order=MagicMock(side_effect=ExchangeError("Oh snap")),
cancel_order=cancel_order_mock,
2018-03-05 08:11:13 +00:00
)
freqtrade = FreqtradeBot(default_conf_usdt)
2018-03-05 08:11:13 +00:00
2023-03-16 06:25:04 +00:00
Trade.session.add(open_trade_usdt)
2022-08-19 07:10:54 +00:00
Trade.commit()
2018-03-05 08:11:13 +00:00
caplog.clear()
freqtrade.manage_open_orders()
2024-05-12 14:13:11 +00:00
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="
f"{open_trade_usdt.open_date.strftime('%Y-%m-%d %H:%M:%S')}"
r"\) due to Traceback \(most recent call last\):\n*",
caplog,
)
2018-03-05 08:11:13 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2022-10-02 06:30:19 +00:00
def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_short, fee) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2023-09-21 18:58:07 +00:00
l_order = deepcopy(limit_order[entry_side(is_short)])
2023-09-21 18:58:58 +00:00
cancel_entry_order = deepcopy(limit_order[entry_side(is_short)])
2024-05-12 14:13:11 +00:00
cancel_entry_order["status"] = "canceled"
del cancel_entry_order["filled"]
2020-08-01 13:59:50 +00:00
2023-09-21 18:58:58 +00:00
cancel_order_mock = MagicMock(return_value=cancel_entry_order)
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.cancel_order_with_result", cancel_order_mock)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade._notify_enter_cancel = MagicMock()
2022-10-02 06:30:19 +00:00
trade = mock_trade_usdt_4(fee, is_short)
2023-03-16 06:25:04 +00:00
Trade.session.add(trade)
2022-10-02 06:30:19 +00:00
Trade.commit()
2024-05-12 14:13:11 +00:00
l_order["filled"] = 0.0
l_order["status"] = "open"
reason = CANCEL_REASON["TIMEOUT"]
assert freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
assert cancel_order_mock.call_count == 1
2021-05-21 17:32:26 +00:00
cancel_order_mock.reset_mock()
caplog.clear()
2024-05-12 14:13:11 +00:00
l_order["filled"] = 0.01
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
2021-05-21 17:32:26 +00:00
assert cancel_order_mock.call_count == 0
2021-09-10 17:34:57 +00:00
assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unexitable.*", caplog)
2021-05-21 17:32:26 +00:00
caplog.clear()
cancel_order_mock.reset_mock()
2024-05-12 14:13:11 +00:00
l_order["filled"] = 2
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
assert cancel_order_mock.call_count == 1
2020-08-01 13:59:50 +00:00
# Order remained open for some reason (cancel failed)
2024-05-12 14:13:11 +00:00
cancel_entry_order["status"] = "open"
2023-09-21 18:58:58 +00:00
cancel_order_mock = MagicMock(return_value=cancel_entry_order)
2023-08-25 05:13:39 +00:00
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.cancel_order_with_result", cancel_order_mock)
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
2020-08-01 13:59:50 +00:00
assert log_has_re(r"Order .* for .* not cancelled.", caplog)
2022-02-19 05:36:23 +00:00
# min_pair_stake empty should not crash
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=None)
assert not freqtrade.handle_cancel_enter(
trade, limit_order[entry_side(is_short)], trade.open_orders[0], reason
)
2023-09-21 18:58:07 +00:00
# Retry ...
cbo = limit_order[entry_side(is_short)]
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.freqtradebot.sleep")
cbo["status"] = "open"
co_mock = mocker.patch(f"{EXMS}.cancel_order_with_result", return_value=cbo)
fo_mock = mocker.patch(f"{EXMS}.fetch_order", return_value=cbo)
2023-09-21 18:58:07 +00:00
assert not freqtrade.handle_cancel_enter(
trade, cbo, trade.open_orders[0], reason, replacing=True
2023-09-21 18:58:07 +00:00
)
assert co_mock.call_count == 1
assert fo_mock.call_count == 3
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"limit_buy_order_canceled_empty",
["binance", "kraken", "bybit"],
indirect=["limit_buy_order_canceled_empty"],
)
def test_handle_cancel_enter_exchanges(
mocker, caplog, default_conf_usdt, is_short, fee, limit_buy_order_canceled_empty
) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
cancel_order_mock = mocker.patch(
2024-05-12 14:13:11 +00:00
f"{EXMS}.cancel_order_with_result", return_value=limit_buy_order_canceled_empty
)
notify_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot._notify_enter_cancel")
freqtrade = FreqtradeBot(default_conf_usdt)
2024-05-12 14:13:11 +00:00
reason = CANCEL_REASON["TIMEOUT"]
2022-10-02 06:30:19 +00:00
trade = mock_trade_usdt_4(fee, is_short)
2023-03-16 06:25:04 +00:00
Trade.session.add(trade)
2022-10-02 06:30:19 +00:00
Trade.commit()
assert freqtrade.handle_cancel_enter(
trade, limit_buy_order_canceled_empty, trade.open_orders[0], reason
)
assert cancel_order_mock.call_count == 0
2022-02-26 16:30:35 +00:00
assert log_has_re(
2024-05-12 14:13:11 +00:00
f"{trade.entry_side.capitalize()} order fully cancelled. " r"Removing .* from database\.",
caplog,
2022-02-26 16:30:35 +00:00
)
assert notify_mock.call_count == 1
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("cancelorder", [{}, {"remaining": None}, "String Return value", 123])
def test_handle_cancel_enter_corder_empty(
mocker, default_conf_usdt, limit_order, is_short, fee, cancelorder
) -> None:
2019-11-20 19:37:46 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
2022-04-06 01:02:13 +00:00
l_order = limit_order[entry_side(is_short)]
cancel_order_mock = MagicMock(return_value=cancelorder)
2019-11-20 19:37:46 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2022-10-02 06:36:34 +00:00
cancel_order=cancel_order_mock,
2024-05-12 14:13:11 +00:00
fetch_order=MagicMock(side_effect=InvalidOrderException),
2019-11-20 19:37:46 +00:00
)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade._notify_enter_cancel = MagicMock()
2022-10-02 06:36:34 +00:00
trade = mock_trade_usdt_4(fee, is_short)
2023-03-16 06:25:04 +00:00
Trade.session.add(trade)
2022-10-02 06:36:34 +00:00
Trade.commit()
2024-05-12 14:13:11 +00:00
l_order["filled"] = 0.0
l_order["status"] = "open"
reason = CANCEL_REASON["TIMEOUT"]
assert freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
2019-11-20 19:37:46 +00:00
assert cancel_order_mock.call_count == 1
cancel_order_mock.reset_mock()
2024-05-12 14:13:11 +00:00
l_order["filled"] = 1.0
2022-10-02 16:11:52 +00:00
order = deepcopy(l_order)
2024-05-12 14:13:11 +00:00
order["status"] = "canceled"
mocker.patch(f"{EXMS}.fetch_order", return_value=order)
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
2019-11-20 19:37:46 +00:00
assert cancel_order_mock.call_count == 1
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("is_short", [True, False])
@pytest.mark.parametrize("leverage", [1, 5])
@pytest.mark.parametrize("amount", [2, 50])
def test_handle_cancel_exit_limit(
mocker, default_conf_usdt, fee, is_short, leverage, amount
) -> None:
send_msg_mock = patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
cancel_order_mock = MagicMock()
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
cancel_order=cancel_order_mock,
)
entry_price = 0.245441
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_rate", return_value=entry_price)
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.2)
2022-09-30 14:17:48 +00:00
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.freqtradebot.FreqtradeBot.handle_order_fee")
freqtrade = FreqtradeBot(default_conf_usdt)
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/USDT",
amount=amount * leverage,
2024-05-12 14:13:11 +00:00
exchange="binance",
open_rate=entry_price,
2023-05-14 16:14:35 +00:00
open_date=dt_now() - timedelta(days=2),
fee_open=fee.return_value,
fee_close=fee.return_value,
close_rate=0.555,
2023-05-14 09:10:21 +00:00
close_date=dt_now(),
2022-03-24 19:53:22 +00:00
exit_reason="sell_reason_whatever",
stake_amount=entry_price * amount,
leverage=leverage,
is_short=is_short,
)
trade.orders = [
2022-09-30 13:26:34 +00:00
Order(
ft_order_side=entry_side(is_short),
ft_pair=trade.pair,
2022-09-30 13:26:34 +00:00
ft_is_open=False,
2024-05-12 14:13:11 +00:00
order_id="buy_123456",
status="closed",
symbol=trade.pair,
order_type="market",
side=entry_side(is_short),
price=trade.open_rate,
average=trade.open_rate,
filled=trade.amount,
remaining=0,
cost=trade.open_rate * trade.amount,
order_date=trade.open_date,
order_filled_date=trade.open_date,
2024-05-12 14:13:11 +00:00
),
2022-09-30 13:26:34 +00:00
Order(
ft_order_side=exit_side(is_short),
2022-09-30 13:26:34 +00:00
ft_pair=trade.pair,
ft_is_open=True,
2024-05-12 14:13:11 +00:00
order_id="sell_123456",
2022-09-30 13:26:34 +00:00
status="open",
symbol=trade.pair,
order_type="limit",
side=exit_side(is_short),
2022-09-30 13:26:34 +00:00
price=trade.open_rate,
average=trade.open_rate,
filled=0.0,
remaining=trade.amount,
cost=trade.open_rate * trade.amount,
order_date=trade.open_date,
order_filled_date=trade.open_date,
2024-05-12 14:13:11 +00:00
),
]
2024-05-12 14:13:11 +00:00
order = {"id": "sell_123456", "remaining": 1, "amount": 1, "status": "open"}
reason = CANCEL_REASON["TIMEOUT"]
order_obj = trade.open_orders[-1]
2022-09-30 13:26:34 +00:00
send_msg_mock.reset_mock()
assert freqtrade.handle_cancel_exit(trade, order, order_obj, reason)
assert cancel_order_mock.call_count == 1
2022-09-30 13:26:34 +00:00
assert send_msg_mock.call_count == 1
assert trade.close_rate is None
2022-03-24 19:33:47 +00:00
assert trade.exit_reason is None
assert not trade.has_open_orders
send_msg_mock.reset_mock()
2022-09-30 14:17:48 +00:00
# Partial exit - below exit threshold
2024-05-12 14:13:11 +00:00
order["amount"] = amount * leverage
order["filled"] = amount * 0.99 * leverage
assert not freqtrade.handle_cancel_exit(trade, order, order_obj, reason)
# Assert cancel_order was not called (callcount remains unchanged)
assert cancel_order_mock.call_count == 1
assert send_msg_mock.call_count == 1
2024-05-12 14:13:11 +00:00
assert (
send_msg_mock.call_args_list[0][0][0]["reason"]
== CANCEL_REASON["PARTIALLY_FILLED_KEEP_OPEN"]
)
assert not freqtrade.handle_cancel_exit(trade, order, order_obj, reason)
2024-05-12 14:13:11 +00:00
assert (
send_msg_mock.call_args_list[0][0][0]["reason"]
== CANCEL_REASON["PARTIALLY_FILLED_KEEP_OPEN"]
)
# Message should not be iterated again
2024-05-12 14:13:11 +00:00
assert trade.exit_order_status == CANCEL_REASON["PARTIALLY_FILLED_KEEP_OPEN"]
assert send_msg_mock.call_count == 1
2022-09-30 14:17:48 +00:00
send_msg_mock.reset_mock()
2024-05-12 14:13:11 +00:00
order["filled"] = amount * 0.5 * leverage
assert freqtrade.handle_cancel_exit(trade, order, order_obj, reason)
2022-09-30 14:17:48 +00:00
assert send_msg_mock.call_count == 1
2024-05-12 14:13:11 +00:00
assert send_msg_mock.call_args_list[0][0][0]["reason"] == CANCEL_REASON["PARTIALLY_FILLED"]
2022-09-30 14:17:48 +00:00
def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
2020-05-10 14:24:00 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.0)
mocker.patch(f"{EXMS}.cancel_order_with_result", side_effect=InvalidOrderException())
2020-05-10 14:24:00 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2020-05-10 14:24:00 +00:00
2022-10-02 06:36:34 +00:00
# TODO: should not be magicmock
2020-05-10 14:24:00 +00:00
trade = MagicMock()
order_obj = MagicMock()
2024-05-12 14:13:11 +00:00
order_obj.order_id = "125"
reason = CANCEL_REASON["TIMEOUT"]
order = {"remaining": 1, "id": "125", "amount": 1, "status": "open"}
assert not freqtrade.handle_cancel_exit(trade, order, order_obj, reason)
2020-05-10 14:24:00 +00:00
# mocker.patch(f'{EXMS}.cancel_order_with_result', return_value=order)
# assert not freqtrade.handle_cancel_exit(trade, order, reason)
2020-05-10 14:24:00 +00:00
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"is_short, open_rate, amt",
[
(False, 2.0, 30.0),
(True, 2.02, 29.70297029),
],
)
def test_execute_trade_exit_up(
default_conf_usdt,
ticker_usdt,
fee,
ticker_usdt_sell_up,
mocker,
ticker_usdt_sell_down,
is_short,
open_rate,
amt,
) -> None:
rpc_mock = patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2024-09-21 14:39:54 +00:00
_dry_is_price_crossed=MagicMock(side_effect=[True, False]),
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
# Create some test data
freqtrade.enter_positions()
rpc_mock.reset_mock()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-17 08:55:20 +00:00
assert trade.is_short == is_short
assert trade
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
# Increase the price and sell it
mocker.patch.multiple(
2024-05-12 14:13:11 +00:00
EXMS, fetch_ticker=ticker_usdt_sell_down if is_short else ticker_usdt_sell_up
)
# Prevented sell ...
freqtrade.execute_trade_exit(
trade=trade,
2024-05-12 14:13:11 +00:00
limit=(ticker_usdt_sell_down()["ask"] if is_short else ticker_usdt_sell_up()["bid"]),
exit_check=ExitCheckTuple(exit_type=ExitType.ROI),
)
assert rpc_mock.call_count == 0
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
2024-05-12 14:13:11 +00:00
assert id(freqtrade.strategy.confirm_trade_exit.call_args_list[0][1]["trade"]) != id(trade)
assert freqtrade.strategy.confirm_trade_exit.call_args_list[0][1]["trade"].id == trade.id
# Repatch with true
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
freqtrade.execute_trade_exit(
trade=trade,
2024-05-12 14:13:11 +00:00
limit=(ticker_usdt_sell_down()["ask"] if is_short else ticker_usdt_sell_up()["bid"]),
exit_check=ExitCheckTuple(exit_type=ExitType.ROI),
)
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
assert rpc_mock.call_count == 1
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
2024-05-12 14:13:11 +00:00
"trade_id": 1,
"type": RPCMessageType.EXIT,
"exchange": "Binance",
"pair": "ETH/USDT",
"gain": "profit",
"limit": 2.0 if is_short else 2.2,
"order_rate": 2.0 if is_short else 2.2,
"amount": pytest.approx(amt),
"order_type": "limit",
"buy_tag": None,
"direction": "Short" if trade.is_short else "Long",
"leverage": 1.0,
"enter_tag": None,
"open_rate": open_rate,
"current_rate": 2.01 if is_short else 2.3,
"profit_amount": 0.29554455 if is_short else 5.685,
"profit_ratio": 0.00493809 if is_short else 0.09451372,
"stake_currency": "USDT",
"quote_currency": "USDT",
"fiat_currency": "USD",
"base_currency": "ETH",
"exit_reason": ExitType.ROI.value,
"open_date": ANY,
"close_date": ANY,
"close_rate": ANY,
"sub_trade": False,
"cumulative_profit": 0.0,
"stake_amount": pytest.approx(60),
"is_final_exit": False,
"final_profit_ratio": None,
} == last_msg
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
def test_execute_trade_exit_down(
default_conf_usdt,
ticker_usdt,
fee,
ticker_usdt_sell_down,
ticker_usdt_sell_up,
mocker,
is_short,
) -> None:
rpc_mock = patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2024-09-21 14:39:54 +00:00
_dry_is_price_crossed=MagicMock(side_effect=[True, False]),
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create some test data
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
assert trade
# Decrease the price and sell it
mocker.patch.multiple(
2024-05-12 14:13:11 +00:00
EXMS, fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down
)
2021-10-17 08:55:20 +00:00
freqtrade.execute_trade_exit(
2024-05-12 14:13:11 +00:00
trade=trade,
limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()["bid"],
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS),
)
2024-09-21 14:39:54 +00:00
assert rpc_mock.call_count == 3
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
2024-05-12 14:13:11 +00:00
"type": RPCMessageType.EXIT,
"trade_id": 1,
"exchange": "Binance",
"pair": "ETH/USDT",
"direction": "Short" if trade.is_short else "Long",
"leverage": 1.0,
"gain": "loss",
"limit": 2.2 if is_short else 2.01,
"order_rate": 2.2 if is_short else 2.01,
"amount": pytest.approx(29.70297029) if is_short else 30.0,
"order_type": "limit",
"buy_tag": None,
"enter_tag": None,
"open_rate": 2.02 if is_short else 2.0,
"current_rate": 2.2 if is_short else 2.0,
"profit_amount": -5.65990099 if is_short else -0.00075,
"profit_ratio": -0.0945681 if is_short else -1.247e-05,
"stake_currency": "USDT",
"quote_currency": "USDT",
"base_currency": "ETH",
"fiat_currency": "USD",
"exit_reason": ExitType.STOP_LOSS.value,
"open_date": ANY,
"close_date": ANY,
"close_rate": ANY,
"sub_trade": False,
"cumulative_profit": 0.0,
"stake_amount": pytest.approx(60),
"is_final_exit": False,
"final_profit_ratio": None,
} == last_msg
@pytest.mark.parametrize(
2024-05-12 14:13:11 +00:00
"is_short,amount,open_rate,current_rate,limit,profit_amount,profit_ratio,profit_or_loss",
[
(False, 30, 2.0, 2.3, 2.25, 7.18125, 0.11938903, "profit"),
(True, 29.70297029, 2.02, 2.2, 2.25, -7.14876237, -0.11944465, "loss"),
],
)
def test_execute_trade_exit_custom_exit_price(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
fee,
ticker_usdt_sell_up,
is_short,
amount,
open_rate,
current_rate,
limit,
profit_amount,
profit_ratio,
profit_or_loss,
mocker,
) -> None:
2021-08-05 21:57:45 +00:00
rpc_mock = patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-08-05 21:57:45 +00:00
get_fee=fee,
2024-09-21 14:39:54 +00:00
_dry_is_price_crossed=MagicMock(side_effect=[True, False]),
2021-08-05 21:57:45 +00:00
)
config = deepcopy(default_conf_usdt)
2024-05-12 14:13:11 +00:00
config["custom_price_max_distance_ratio"] = 0.1
patch_whitelist(mocker, config)
freqtrade = FreqtradeBot(config)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2021-08-05 21:57:45 +00:00
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
# Create some test data
freqtrade.enter_positions()
rpc_mock.reset_mock()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2021-08-05 21:57:45 +00:00
assert trade
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
# Increase the price and sell it
2024-05-12 14:13:11 +00:00
mocker.patch.multiple(EXMS, fetch_ticker=ticker_usdt_sell_up)
2021-08-05 21:57:45 +00:00
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
# Set a custom exit price
freqtrade.strategy.custom_exit_price = lambda **kwargs: 2.25
2021-11-10 06:43:55 +00:00
freqtrade.execute_trade_exit(
trade=trade,
2024-05-12 14:13:11 +00:00
limit=ticker_usdt_sell_up()["ask" if is_short else "bid"],
exit_check=ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL, exit_reason="foo"),
2021-11-10 06:43:55 +00:00
)
2021-08-05 21:57:45 +00:00
# Sell price must be different to default bid price
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
assert rpc_mock.call_count == 1
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
2024-05-12 14:13:11 +00:00
"trade_id": 1,
"type": RPCMessageType.EXIT,
"exchange": "Binance",
"pair": "ETH/USDT",
"direction": "Short" if trade.is_short else "Long",
"leverage": 1.0,
"gain": profit_or_loss,
"limit": limit,
"order_rate": limit,
"amount": pytest.approx(amount),
"order_type": "limit",
"buy_tag": None,
"enter_tag": None,
"open_rate": open_rate,
"current_rate": current_rate,
"profit_amount": pytest.approx(profit_amount),
"profit_ratio": profit_ratio,
"stake_currency": "USDT",
"quote_currency": "USDT",
"base_currency": "ETH",
"fiat_currency": "USD",
"exit_reason": "foo",
"open_date": ANY,
"close_date": ANY,
"close_rate": ANY,
"sub_trade": False,
"cumulative_profit": 0.0,
"stake_amount": pytest.approx(60),
"is_final_exit": False,
"final_profit_ratio": None,
2021-08-05 21:57:45 +00:00
} == last_msg
@pytest.mark.parametrize(
2024-05-12 14:13:11 +00:00
"is_short,amount,current_rate,limit,profit_amount,profit_ratio,profit_or_loss",
[
(False, 30, 2.3, 2.2, 5.685, 0.09451372, "profit"),
(True, 29.70297029, 2.2, 2.3, -8.63762376, -0.1443212, "loss"),
],
)
def test_execute_trade_exit_market_order(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
fee,
is_short,
current_rate,
amount,
caplog,
limit,
profit_amount,
profit_ratio,
profit_or_loss,
ticker_usdt_sell_up,
mocker,
) -> None:
"""
amount
long: 60 / 2.0 = 30
short: 60 / 2.02 = 29.70297029
open_value
long: (30 * 2.0) + (30 * 2.0 * 0.0025) = 60.15
short: (29.702970297029704 * 2.02) - (29.702970297029704 * 2.02 * 0.0025) = 59.85
close_value
long: (30 * 2.2) - (30 * 2.2 * 0.0025) = 65.835
short: (29.702970297029704 * 2.3) + (29.702970297029704 * 2.3 * 0.0025) = 68.48762376237624
profit
long: 65.835 - 60.15 = 5.684999999999995
short: 59.85 - 68.48762376237624 = -8.637623762376244
profit_ratio
long: (65.835/60.15) - 1 = 0.0945137157107232
short: 1 - (68.48762376237624/59.85) = -0.1443211990371971
"""
2024-05-12 14:13:11 +00:00
open_rate = ticker_usdt.return_value["ask" if is_short else "bid"]
2019-08-12 14:47:00 +00:00
rpc_mock = patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
2019-08-12 14:47:00 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2019-08-12 14:47:00 +00:00
get_fee=fee,
2023-02-16 18:47:12 +00:00
_dry_is_price_crossed=MagicMock(return_value=True),
2019-08-12 14:47:00 +00:00
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2019-08-12 14:47:00 +00:00
# Create some test data
freqtrade.enter_positions()
2019-08-12 14:47:00 +00:00
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2019-08-12 14:47:00 +00:00
assert trade
# Increase the price and sell it
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt_sell_up,
2023-02-16 18:47:12 +00:00
_dry_is_price_crossed=MagicMock(return_value=False),
2019-08-12 14:47:00 +00:00
)
2024-05-12 14:13:11 +00:00
freqtrade.config["order_types"]["exit"] = "market"
2019-08-12 14:47:00 +00:00
freqtrade.execute_trade_exit(
trade=trade,
2024-05-12 14:13:11 +00:00
limit=ticker_usdt_sell_up()["ask" if is_short else "bid"],
exit_check=ExitCheckTuple(exit_type=ExitType.ROI),
)
2019-08-12 14:47:00 +00:00
assert not trade.is_open
assert pytest.approx(trade.close_profit) == profit_ratio
2019-08-12 14:47:00 +00:00
assert rpc_mock.call_count == 4
last_msg = rpc_mock.call_args_list[-2][0][0]
2019-08-12 14:47:00 +00:00
assert {
2024-05-12 14:13:11 +00:00
"type": RPCMessageType.EXIT,
"trade_id": 1,
"exchange": "Binance",
"pair": "ETH/USDT",
"direction": "Short" if trade.is_short else "Long",
"leverage": 1.0,
"gain": profit_or_loss,
"limit": limit,
"order_rate": limit,
"amount": pytest.approx(amount),
"order_type": "market",
"buy_tag": None,
"enter_tag": None,
"open_rate": open_rate,
"current_rate": current_rate,
"profit_amount": pytest.approx(profit_amount),
"profit_ratio": profit_ratio,
"stake_currency": "USDT",
"quote_currency": "USDT",
"base_currency": "ETH",
"fiat_currency": "USD",
"exit_reason": ExitType.ROI.value,
"open_date": ANY,
"close_date": ANY,
"close_rate": ANY,
"sub_trade": False,
"cumulative_profit": 0.0,
"stake_amount": pytest.approx(60),
"is_final_exit": False,
"final_profit_ratio": None,
2019-08-12 14:47:00 +00:00
} == last_msg
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
def test_execute_trade_exit_insufficient_funds_error(
default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_up, mocker
) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mock_insuf = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds")
2020-09-06 12:59:30 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2020-09-06 12:59:30 +00:00
get_fee=fee,
2024-05-12 14:13:11 +00:00
create_order=MagicMock(
side_effect=[
{"id": 1234553382},
InsufficientFundsError(),
]
),
2020-09-06 12:59:30 +00:00
)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2020-09-06 12:59:30 +00:00
# Create some test data
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2020-09-06 12:59:30 +00:00
assert trade
# Increase the price and sell it
2024-05-12 14:13:11 +00:00
mocker.patch.multiple(EXMS, fetch_ticker=ticker_usdt_sell_up)
2020-09-06 12:59:30 +00:00
2022-03-25 05:55:37 +00:00
sell_reason = ExitCheckTuple(exit_type=ExitType.ROI)
2021-11-10 06:43:55 +00:00
assert not freqtrade.execute_trade_exit(
trade=trade,
2024-05-12 14:13:11 +00:00
limit=ticker_usdt_sell_up()["ask" if is_short else "bid"],
exit_check=sell_reason,
2021-11-10 06:43:55 +00:00
)
2020-09-06 12:59:30 +00:00
assert mock_insuf.call_count == 1
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"profit_only,bid,ask,handle_first,handle_second,exit_type,is_short",
[
# Enable profit
(True, 2.18, 2.2, False, True, ExitType.EXIT_SIGNAL.value, False),
(True, 2.18, 2.2, False, True, ExitType.EXIT_SIGNAL.value, True),
# # Disable profit
(False, 3.19, 3.2, True, False, ExitType.EXIT_SIGNAL.value, False),
(False, 3.19, 3.2, True, False, ExitType.EXIT_SIGNAL.value, True),
# # Enable loss
# # * Shouldn't this be ExitType.STOP_LOSS.value
(True, 0.21, 0.22, False, False, None, False),
(True, 2.41, 2.42, False, False, None, True),
# Disable loss
(False, 0.10, 0.22, True, False, ExitType.EXIT_SIGNAL.value, False),
(False, 0.10, 0.22, True, False, ExitType.EXIT_SIGNAL.value, True),
],
)
def test_exit_profit_only(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
limit_order,
limit_order_open,
is_short,
fee,
mocker,
profit_only,
bid,
ask,
handle_first,
handle_second,
exit_type,
) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2022-04-06 01:02:13 +00:00
eside = entry_side(is_short)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": bid, "ask": ask, "last": bid}),
create_order=MagicMock(
side_effect=[
limit_order[eside],
{"id": 1234553382},
]
),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
2024-05-12 14:13:11 +00:00
default_conf_usdt.update(
{
"use_exit_signal": True,
"exit_profit_only": profit_only,
"exit_profit_offset": 0.1,
}
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.custom_exit = MagicMock(return_value=None)
2022-04-04 15:10:02 +00:00
if exit_type == ExitType.EXIT_SIGNAL.value:
2021-09-19 23:44:12 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
else:
2024-05-12 14:13:11 +00:00
freqtrade.strategy.ft_stoploss_reached = MagicMock(
return_value=ExitCheckTuple(exit_type=ExitType.NONE)
)
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade.is_short == is_short
2024-05-12 14:13:11 +00:00
oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]["symbol"], eside)
2022-06-13 08:50:12 +00:00
trade.update_order(limit_order[eside])
trade.update_trade(oobj)
2019-12-18 19:16:53 +00:00
freqtrade.wallets.update()
if profit_only:
assert freqtrade.handle_trade(trade) is False
# Custom-exit is called
2024-04-20 07:39:11 +00:00
assert freqtrade.strategy.custom_exit.call_count == 1
2021-10-11 14:26:15 +00:00
patch_get_signal(freqtrade, enter_long=False, exit_short=is_short, exit_long=not is_short)
2021-09-19 23:44:12 +00:00
assert freqtrade.handle_trade(trade) is handle_first
2021-09-19 23:44:12 +00:00
if handle_second:
freqtrade.strategy.exit_profit_offset = 0.0
2021-09-19 23:44:12 +00:00
assert freqtrade.handle_trade(trade) is True
2018-04-15 17:38:58 +00:00
2024-05-12 14:13:11 +00:00
def test_sell_not_enough_balance(
default_conf_usdt, limit_order, limit_order_open, fee, mocker, caplog
) -> None:
2019-12-13 06:06:54 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(
return_value={"bid": 0.00002172, "ask": 0.00002173, "last": 0.00002172}
),
create_order=MagicMock(
side_effect=[
limit_order_open["buy"],
{"id": 1234553382},
]
),
2019-12-13 06:06:54 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-11 14:26:15 +00:00
patch_get_signal(freqtrade)
2019-12-13 06:06:54 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
2019-12-13 06:06:54 +00:00
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2019-12-13 06:06:54 +00:00
amnt = trade.amount
2024-05-12 14:13:11 +00:00
oobj = Order.parse_from_ccxt_object(limit_order["buy"], limit_order["buy"]["symbol"], "buy")
trade.update_trade(oobj)
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=trade.amount * 0.985))
2019-12-13 06:06:54 +00:00
assert freqtrade.handle_trade(trade) is True
2024-05-12 14:13:11 +00:00
assert log_has_re(r".*Falling back to wallet-amount.", caplog)
2019-12-13 06:06:54 +00:00
assert trade.amount != amnt
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("amount_wallet,has_err", [(95.29, False), (91.29, True)])
def test__safe_exit_amount(default_conf_usdt, fee, caplog, mocker, amount_wallet, has_err):
2019-12-13 06:06:54 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
amount = 95.33
2021-09-19 23:44:12 +00:00
amount_wallet = amount_wallet
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=amount_wallet))
wallet_update = mocker.patch("freqtrade.wallets.Wallets.update")
2019-12-13 06:06:54 +00:00
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
2019-12-13 06:06:54 +00:00
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
2019-12-13 06:06:54 +00:00
open_rate=0.245441,
2019-12-18 19:16:53 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2019-12-13 06:06:54 +00:00
)
freqtrade = FreqtradeBot(default_conf_usdt)
2019-12-13 06:06:54 +00:00
patch_get_signal(freqtrade)
2021-09-19 23:44:12 +00:00
if has_err:
with pytest.raises(DependencyException, match=r"Not enough amount to exit trade."):
2022-10-21 14:30:01 +00:00
assert freqtrade._safe_exit_amount(trade, trade.pair, trade.amount)
2021-09-19 23:44:12 +00:00
else:
wallet_update.reset_mock()
2022-10-21 14:30:01 +00:00
assert trade.amount != amount_wallet
assert freqtrade._safe_exit_amount(trade, trade.pair, trade.amount) == amount_wallet
2024-05-12 14:13:11 +00:00
assert log_has_re(r".*Falling back to wallet-amount.", caplog)
2022-10-21 14:30:01 +00:00
assert trade.amount == amount_wallet
2021-09-19 23:44:12 +00:00
assert wallet_update.call_count == 1
caplog.clear()
wallet_update.reset_mock()
2022-10-21 14:30:01 +00:00
assert freqtrade._safe_exit_amount(trade, trade.pair, amount_wallet) == amount_wallet
2024-05-12 14:13:11 +00:00
assert not log_has_re(r".*Falling back to wallet-amount.", caplog)
2021-09-19 23:44:12 +00:00
assert wallet_update.call_count == 1
2019-12-13 06:06:54 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
def test_locked_pairs(
default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, mocker, caplog, is_short
) -> None:
2019-08-12 18:48:21 +00:00
patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
2019-08-12 18:48:21 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2019-08-12 18:48:21 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2019-08-12 18:48:21 +00:00
# Create some test data
freqtrade.enter_positions()
2019-08-12 18:48:21 +00:00
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2019-08-12 18:48:21 +00:00
assert trade
# Decrease the price and sell it
2024-05-12 14:13:11 +00:00
mocker.patch.multiple(EXMS, fetch_ticker=ticker_usdt_sell_down)
2019-08-12 18:48:21 +00:00
2021-11-10 06:43:55 +00:00
freqtrade.execute_trade_exit(
trade=trade,
2024-05-12 14:13:11 +00:00
limit=ticker_usdt_sell_down()["ask" if is_short else "bid"],
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS),
2021-11-10 06:43:55 +00:00
)
2024-05-12 14:13:11 +00:00
trade.close(ticker_usdt_sell_down()["bid"])
assert freqtrade.strategy.is_pair_locked(trade.pair, side="*")
2024-04-18 20:51:25 +00:00
# Both sides are locked
2024-05-12 14:13:11 +00:00
assert freqtrade.strategy.is_pair_locked(trade.pair, side="long")
assert freqtrade.strategy.is_pair_locked(trade.pair, side="short")
2019-08-12 18:48:21 +00:00
# reinit - should buy other pair.
caplog.clear()
freqtrade.enter_positions()
2019-08-12 18:48:21 +00:00
2024-05-12 14:13:11 +00:00
assert log_has_re(rf"Pair {trade.pair} \* is locked.*", caplog)
2019-08-12 18:48:21 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
def test_ignore_roi_if_entry_signal(
default_conf_usdt, limit_order, limit_order_open, is_short, fee, mocker
) -> None:
2018-06-22 18:10:05 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2022-04-06 01:02:13 +00:00
eside = entry_side(is_short)
2018-06-22 18:10:05 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 2.19, "ask": 2.2, "last": 2.19}),
create_order=MagicMock(
side_effect=[
limit_order_open[eside],
{"id": 1234553382},
]
),
2018-06-22 18:10:05 +00:00
get_fee=fee,
)
2024-05-12 14:13:11 +00:00
default_conf_usdt["ignore_roi_if_entry_signal"] = True
2021-06-26 14:39:01 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.enter_positions()
2018-06-22 18:10:05 +00:00
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2024-05-12 14:13:11 +00:00
oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]["symbol"], eside)
trade.update_trade(oobj)
2019-12-18 19:16:53 +00:00
freqtrade.wallets.update()
if is_short:
patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=True, exit_long=True)
2018-06-22 18:10:05 +00:00
assert freqtrade.handle_trade(trade) is False
2022-03-09 06:03:37 +00:00
# Test if entry-signal is absent (should sell due to roi = true)
if is_short:
2024-05-12 14:13:11 +00:00
patch_get_signal(freqtrade, enter_long=False, exit_short=False, exit_tag="something")
else:
2024-05-12 14:13:11 +00:00
patch_get_signal(freqtrade, enter_long=False, exit_long=False, exit_tag="something")
2018-06-22 18:10:05 +00:00
assert freqtrade.handle_trade(trade) is True
2022-03-24 19:33:47 +00:00
assert trade.exit_reason == ExitType.ROI.value
2018-06-22 18:10:05 +00:00
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("is_short,val1,val2", [(False, 1.5, 1.1), (True, 0.5, 0.9)])
def test_trailing_stop_loss(
default_conf_usdt, limit_order_open, is_short, val1, val2, fee, caplog, mocker
) -> None:
2018-06-26 21:40:36 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2018-06-26 21:40:36 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 2.0, "ask": 2.0, "last": 2.0}),
create_order=MagicMock(
side_effect=[
limit_order_open[entry_side(is_short)],
{"id": 1234553382},
]
),
2018-06-26 21:40:36 +00:00
get_fee=fee,
)
2024-05-12 14:13:11 +00:00
default_conf_usdt["trailing_stop"] = True
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-17 08:55:20 +00:00
assert trade.is_short == is_short
2019-06-02 11:27:31 +00:00
assert freqtrade.handle_trade(trade) is False
2021-10-17 08:55:20 +00:00
# Raise praise into profits
2024-05-12 14:13:11 +00:00
mocker.patch(
f"{EXMS}.fetch_ticker",
MagicMock(return_value={"bid": 2.0 * val1, "ask": 2.0 * val1, "last": 2.0 * val1}),
)
2019-06-02 11:27:31 +00:00
# Stoploss should be adjusted
assert freqtrade.handle_trade(trade) is False
2021-09-18 04:18:14 +00:00
caplog.clear()
2019-06-02 11:27:31 +00:00
# Price fell
2024-05-12 14:13:11 +00:00
mocker.patch(
f"{EXMS}.fetch_ticker",
MagicMock(return_value={"bid": 2.0 * val2, "ask": 2.0 * val2, "last": 2.0 * val2}),
)
2019-06-02 11:27:31 +00:00
2018-06-26 21:40:36 +00:00
caplog.set_level(logging.DEBUG)
2018-06-26 22:16:19 +00:00
# Sell as trailing-stop is reached
2018-06-26 21:40:36 +00:00
assert freqtrade.handle_trade(trade) is True
2021-10-17 08:55:20 +00:00
stop_multi = 1.1 if is_short else 0.9
2024-05-12 14:13:11 +00:00
assert log_has(
f"ETH/USDT - HIT STOP: current price at {(2.0 * val2):6f}, "
f"stoploss is {(2.0 * val1 * stop_multi):6f}, "
f"initial stoploss was at {(2.0 * stop_multi):6f}, trade opened at 2.000000",
caplog,
)
2022-03-24 19:33:47 +00:00
assert trade.exit_reason == ExitType.TRAILING_STOP_LOSS.value
2018-06-26 21:40:36 +00:00
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"offset,trail_if_reached,second_sl,is_short",
[
(0, False, 2.0394, False),
(0.011, False, 2.0394, False),
(0.055, True, 1.8, False),
(0, False, 2.1614, True),
(0.011, False, 2.1614, True),
(0.055, True, 2.42, True),
],
)
2021-09-30 09:19:28 +00:00
def test_trailing_stop_loss_positive(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
limit_order,
limit_order_open,
offset,
fee,
caplog,
mocker,
trail_if_reached,
second_sl,
is_short,
2021-09-30 09:19:28 +00:00
) -> None:
2024-05-12 14:13:11 +00:00
enter_price = limit_order[entry_side(is_short)]["price"]
2018-06-26 22:16:19 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2022-04-06 01:02:13 +00:00
eside = entry_side(is_short)
2018-06-26 22:16:19 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(
return_value={
"bid": enter_price - (-0.01 if is_short else 0.01),
"ask": enter_price - (-0.01 if is_short else 0.01),
"last": enter_price - (-0.01 if is_short else 0.01),
}
),
create_order=MagicMock(
side_effect=[
limit_order[eside],
{"id": 1234553382},
]
),
2018-06-26 22:16:19 +00:00
get_fee=fee,
)
2024-05-12 14:13:11 +00:00
default_conf_usdt["trailing_stop"] = True
default_conf_usdt["trailing_stop_positive"] = 0.01
2021-09-18 04:18:14 +00:00
if offset:
2024-05-12 14:13:11 +00:00
default_conf_usdt["trailing_stop_positive_offset"] = offset
default_conf_usdt["trailing_only_offset_is_reached"] = trail_if_reached
patch_whitelist(mocker, default_conf_usdt)
2019-10-26 11:28:04 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
2018-06-26 22:16:19 +00:00
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade.is_short == is_short
2024-05-12 14:13:11 +00:00
oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]["symbol"], eside)
2022-06-13 08:50:12 +00:00
trade.update_order(limit_order[eside])
trade.update_trade(oobj)
2018-06-26 22:16:19 +00:00
caplog.set_level(logging.DEBUG)
# stop-loss not reached
assert freqtrade.handle_trade(trade) is False
2018-06-27 04:51:48 +00:00
# Raise ticker_usdt above buy price
2021-09-18 04:18:14 +00:00
mocker.patch(
2024-05-12 14:13:11 +00:00
f"{EXMS}.fetch_ticker",
MagicMock(
return_value={
"bid": enter_price + (-0.06 if is_short else 0.06),
"ask": enter_price + (-0.06 if is_short else 0.06),
"last": enter_price + (-0.06 if is_short else 0.06),
}
),
2021-09-18 04:18:14 +00:00
)
2022-08-17 12:05:12 +00:00
caplog.clear()
2018-06-27 04:51:48 +00:00
# stop-loss not reached, adjusted stoploss
assert freqtrade.handle_trade(trade) is False
2024-05-12 14:13:11 +00:00
caplog_text = (
f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
f"{'2.49' if not is_short else '2.24'}%"
)
2021-09-18 04:18:14 +00:00
if trail_if_reached:
assert not log_has(caplog_text, caplog)
assert not log_has("ETH/USDT - Adjusting stoploss...", caplog)
2021-09-18 04:18:14 +00:00
else:
assert log_has(caplog_text, caplog)
assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
2021-10-17 07:54:38 +00:00
assert pytest.approx(trade.stop_loss) == second_sl
caplog.clear()
2018-07-16 19:23:35 +00:00
2021-09-18 04:18:14 +00:00
mocker.patch(
2024-05-12 14:13:11 +00:00
f"{EXMS}.fetch_ticker",
MagicMock(
return_value={
"bid": enter_price + (-0.135 if is_short else 0.125),
"ask": enter_price + (-0.135 if is_short else 0.125),
"last": enter_price + (-0.135 if is_short else 0.125),
}
),
2018-07-16 19:23:35 +00:00
)
assert freqtrade.handle_trade(trade) is False
2021-09-18 04:18:14 +00:00
assert log_has(
2021-10-17 07:54:38 +00:00
f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
2021-11-18 19:20:01 +00:00
f"{'5.72' if not is_short else '5.67'}%",
2024-05-12 14:13:11 +00:00
caplog,
2021-09-18 04:18:14 +00:00
)
assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
2018-07-16 19:23:35 +00:00
2021-09-18 04:18:14 +00:00
mocker.patch(
2024-05-12 14:13:11 +00:00
f"{EXMS}.fetch_ticker",
MagicMock(
return_value={
"bid": enter_price + (-0.02 if is_short else 0.02),
"ask": enter_price + (-0.02 if is_short else 0.02),
"last": enter_price + (-0.02 if is_short else 0.02),
}
),
2021-09-18 04:18:14 +00:00
)
2018-06-27 04:51:48 +00:00
# Lower price again (but still positive)
2018-06-26 22:16:19 +00:00
assert freqtrade.handle_trade(trade) is True
assert log_has(
2021-10-07 11:03:38 +00:00
f"ETH/USDT - HIT STOP: current price at {enter_price + (-0.02 if is_short else 0.02):.6f}, "
2019-09-10 07:43:15 +00:00
f"stoploss is {trade.stop_loss:.6f}, "
2021-10-17 07:54:38 +00:00
f"initial stoploss was at {'2.42' if is_short else '1.80'}0000, "
f"trade opened at {2.2 if is_short else 2.0}00000",
2024-05-12 14:13:11 +00:00
caplog,
)
2022-03-24 19:33:47 +00:00
assert trade.exit_reason == ExitType.TRAILING_STOP_LOSS.value
2018-06-26 21:40:36 +00:00
2018-06-27 04:51:48 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
def test_disable_ignore_roi_if_entry_signal(
default_conf_usdt, limit_order, limit_order_open, is_short, fee, mocker
) -> None:
2018-06-22 18:10:05 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2022-04-06 01:02:13 +00:00
eside = entry_side(is_short)
2018-06-22 18:10:05 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 2.0, "ask": 2.0, "last": 2.0}),
create_order=MagicMock(
side_effect=[limit_order_open[eside], {"id": 1234553382}, {"id": 1234553383}]
),
2018-04-21 17:39:18 +00:00
get_fee=fee,
2023-02-16 18:47:12 +00:00
_dry_is_price_crossed=MagicMock(return_value=False),
)
2024-05-12 14:13:11 +00:00
default_conf_usdt["exit_pricing"] = {"ignore_roi_if_entry_signal": False}
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.enter_positions()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:13:29 +00:00
trade.is_short = is_short
2022-02-24 18:56:42 +00:00
2024-05-12 14:13:11 +00:00
oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]["symbol"], eside)
trade.update_trade(oobj)
2018-06-22 18:10:05 +00:00
# Sell due to min_roi_reached
2022-01-01 18:16:24 +00:00
patch_get_signal(freqtrade, enter_long=not is_short, enter_short=is_short, exit_short=is_short)
2018-06-22 18:10:05 +00:00
assert freqtrade.handle_trade(trade) is True
2022-03-09 06:03:37 +00:00
# Test if entry-signal is absent
2022-01-01 18:16:24 +00:00
patch_get_signal(freqtrade)
assert freqtrade.handle_trade(trade) is True
2022-03-24 19:33:47 +00:00
assert trade.exit_reason == ExitType.ROI.value
2018-04-15 17:38:58 +00:00
2024-05-12 14:13:11 +00:00
def test_get_real_amount_quote(
default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog, mocker
):
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=trades_for_order)
amount = sum(x["amount"] for x in trades_for_order)
2018-04-15 17:38:58 +00:00
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
2018-04-15 17:56:33 +00:00
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
2018-04-25 06:52:08 +00:00
open_rate=0.245441,
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2018-10-04 16:07:47 +00:00
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-10-02 09:15:12 +00:00
caplog.clear()
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(buy_order_fee, "LTC/ETH", "buy")
2018-04-15 17:56:33 +00:00
# Amount is reduced by "fee"
2022-08-30 18:46:06 +00:00
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == (amount * 0.001)
assert log_has(
2024-05-12 14:13:11 +00:00
"Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, is_short=False,"
" leverage=1.0, open_rate=0.24544100, open_since=closed), fee=0.008.",
caplog,
)
2018-04-15 17:56:33 +00:00
2024-05-12 14:13:11 +00:00
def test_get_real_amount_quote_dust(
default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog, mocker
):
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=trades_for_order)
walletmock = mocker.patch("freqtrade.wallets.Wallets.update")
mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=8.1122)
amount = sum(x["amount"] for x in trades_for_order)
2020-05-03 09:13:59 +00:00
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
2020-05-03 09:13:59 +00:00
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
2020-05-03 09:13:59 +00:00
open_rate=0.245441,
fee_open=fee.return_value,
fee_close=fee.return_value,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-05-03 09:13:59 +00:00
walletmock.reset_mock()
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(buy_order_fee, "LTC/ETH", "buy")
2020-05-03 09:13:59 +00:00
# Amount is kept as is
2022-08-30 18:46:06 +00:00
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) is None
2020-05-03 09:13:59 +00:00
assert walletmock.call_count == 1
2024-05-12 14:13:11 +00:00
assert log_has_re(
2024-05-12 15:51:21 +00:00
r"Fee amount for Trade.* was in base currency - Eating Fee 0.008 into dust", caplog
2024-05-12 14:13:11 +00:00
)
2020-05-03 09:13:59 +00:00
def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mocker, fee):
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=[])
2018-04-25 07:08:02 +00:00
2024-07-04 14:32:29 +00:00
# Invalid nested trade object
buy_order_fee["trades"] = [{"amount": None, "cost": 22}]
2024-05-12 14:13:11 +00:00
amount = buy_order_fee["amount"]
2018-04-25 07:08:02 +00:00
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
2018-04-25 07:08:02 +00:00
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
2018-04-25 07:08:02 +00:00
open_rate=0.245441,
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2018-04-25 07:08:02 +00:00
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(buy_order_fee, "LTC/ETH", "buy")
2018-04-25 07:08:02 +00:00
# Amount is reduced by "fee"
2022-08-30 18:46:06 +00:00
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) is None
assert log_has(
2024-05-12 14:13:11 +00:00
"Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, "
"is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) failed: "
"myTrade-Dict empty found",
caplog,
)
2018-04-25 07:08:02 +00:00
2018-04-25 07:13:56 +00:00
@pytest.mark.parametrize(
2024-05-12 14:13:11 +00:00
"fee_par,fee_reduction_amount,use_ticker_usdt_rate,expected_log",
[
2021-09-22 22:11:27 +00:00
# basic, amount does not change
2024-05-12 14:13:11 +00:00
({"cost": 0.008, "currency": "ETH"}, 0, False, None),
2021-09-22 22:11:27 +00:00
# no currency in fee
2024-05-12 14:13:11 +00:00
({"cost": 0.004, "currency": None}, 0, True, None),
2021-09-22 22:11:27 +00:00
# BNB no rate
2024-05-12 14:13:11 +00:00
(
{"cost": 0.00094518, "currency": "BNB"},
0,
True,
(
"Fee for Trade Trade(id=None, pair=LTC/ETH, amount=8.00000000, is_short=False, "
"leverage=1.0, open_rate=0.24544100, open_since=closed) [buy]: 0.00094518 BNB -"
" rate: None"
),
),
2021-09-22 22:11:27 +00:00
# from order
2024-05-12 14:13:11 +00:00
(
{"cost": 0.004, "currency": "LTC"},
0.004,
False,
(
"Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, "
"is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed), fee=0.004."
),
),
2021-09-22 22:11:27 +00:00
# invalid, no currency in from fee dict
2024-05-12 14:13:11 +00:00
({"cost": 0.008, "currency": None}, 0, True, None),
],
)
2021-09-21 13:56:16 +00:00
def test_get_real_amount(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
trades_for_order,
buy_order_fee,
fee,
mocker,
caplog,
fee_par,
fee_reduction_amount,
use_ticker_usdt_rate,
expected_log,
2021-09-21 13:56:16 +00:00
):
buy_order = deepcopy(buy_order_fee)
2024-05-12 14:13:11 +00:00
buy_order["fee"] = fee_par
trades_for_order[0]["fee"] = fee_par
2018-04-15 17:38:58 +00:00
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=trades_for_order)
amount = sum(x["amount"] for x in trades_for_order)
2018-04-15 17:38:58 +00:00
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
2018-04-15 17:56:33 +00:00
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2018-04-25 06:52:08 +00:00
open_rate=0.245441,
2018-04-15 17:38:58 +00:00
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
if not use_ticker_usdt_rate:
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_ticker", side_effect=ExchangeError)
2018-04-15 17:56:33 +00:00
caplog.clear()
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(buy_order_fee, "LTC/ETH", "buy")
2022-08-30 18:46:06 +00:00
res = freqtrade.get_real_amount(trade, buy_order, order_obj)
if fee_reduction_amount == 0:
assert res is None
else:
assert res == fee_reduction_amount
2021-09-21 13:56:16 +00:00
if expected_log:
assert log_has(expected_log, caplog)
@pytest.mark.parametrize(
2024-05-12 14:13:11 +00:00
"fee_cost, fee_currency, fee_reduction_amount, expected_fee, expected_log_amount",
[
2021-09-22 22:11:27 +00:00
# basic, amount is reduced by fee
2021-09-22 17:28:42 +00:00
(None, None, 0.001, 0.001, 7.992),
2021-09-22 22:11:27 +00:00
# different fee currency on both trades, fee is average of both trade's fee
2024-05-12 14:13:11 +00:00
(0.02, "BNB", 0.0005, 0.001518575, 7.996),
],
)
2021-09-21 13:56:16 +00:00
def test_get_real_amount_multi(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
trades_for_order2,
buy_order_fee,
caplog,
fee,
mocker,
markets,
fee_cost,
fee_currency,
fee_reduction_amount,
expected_fee,
expected_log_amount,
2021-09-21 13:56:16 +00:00
):
trades_for_order = deepcopy(trades_for_order2)
if fee_cost:
2024-05-12 14:13:11 +00:00
trades_for_order[0]["fee"]["cost"] = fee_cost
2021-09-21 13:56:16 +00:00
if fee_currency:
2024-05-12 14:13:11 +00:00
trades_for_order[0]["fee"]["currency"] = fee_currency
2018-04-15 17:38:58 +00:00
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=trades_for_order)
amount = float(sum(x["amount"] for x in trades_for_order))
default_conf_usdt["stake_currency"] = "ETH"
2018-04-15 17:56:33 +00:00
2018-04-15 17:38:58 +00:00
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
2018-04-15 17:56:33 +00:00
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2024-05-12 14:13:11 +00:00
open_rate=0.245441,
2018-04-15 17:38:58 +00:00
)
2020-12-12 10:43:47 +00:00
# Fake markets entry to enable fee parsing
2024-05-12 14:13:11 +00:00
markets["BNB/ETH"] = markets["ETH/USDT"]
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets))
mocker.patch(f"{EXMS}.fetch_ticker", return_value={"ask": 0.19, "last": 0.2})
2020-12-12 10:43:47 +00:00
# Amount is reduced by "fee"
2022-08-30 18:46:06 +00:00
expected_amount = amount * fee_reduction_amount
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(buy_order_fee, "LTC/ETH", "buy")
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == expected_amount
assert log_has(
2021-09-22 16:32:30 +00:00
(
2024-05-12 14:13:11 +00:00
"Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, "
"is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed), "
f"fee={expected_amount}."
2021-09-22 16:32:30 +00:00
),
2024-05-12 14:13:11 +00:00
caplog,
)
2021-09-21 13:56:16 +00:00
assert trade.fee_open == expected_fee
assert trade.fee_close == expected_fee
2020-12-12 10:43:47 +00:00
assert trade.fee_open_cost is not None
assert trade.fee_open_currency is not None
assert trade.fee_close_cost is None
assert trade.fee_close_currency is None
2018-04-25 06:52:08 +00:00
2024-05-12 14:13:11 +00:00
def test_get_real_amount_invalid_order(
default_conf_usdt, trades_for_order, buy_order_fee, fee, mocker
):
2021-09-17 08:25:58 +00:00
limit_buy_order_usdt = deepcopy(buy_order_fee)
2024-05-12 14:13:11 +00:00
limit_buy_order_usdt["fee"] = {"cost": 0.004}
2018-05-15 18:13:43 +00:00
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=[])
amount = float(sum(x["amount"] for x in trades_for_order))
2018-05-15 18:13:43 +00:00
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
2018-05-15 18:13:43 +00:00
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2018-05-15 18:13:43 +00:00
open_rate=0.245441,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(buy_order_fee, "LTC/ETH", "buy")
2018-05-15 18:13:43 +00:00
# Amount does not change
2022-08-30 18:46:06 +00:00
assert freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) is None
2018-05-15 18:13:43 +00:00
2024-05-12 14:13:11 +00:00
def test_get_real_amount_fees_order(
default_conf_usdt, market_buy_order_usdt_doublefee, fee, mocker
):
tfo_mock = mocker.patch(f"{EXMS}.get_trades_for_order", return_value=[])
mocker.patch(f"{EXMS}.get_valid_pair_combination", return_value="BNB/USDT")
mocker.patch(f"{EXMS}.fetch_ticker", return_value={"last": 200})
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/USDT",
amount=30.0,
2024-05-12 14:13:11 +00:00
exchange="binance",
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.245441,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
# Amount does not change
assert trade.fee_open == 0.0025
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(market_buy_order_usdt_doublefee, "LTC/ETH", "buy")
2022-08-30 18:46:06 +00:00
assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee, order_obj) is None
assert tfo_mock.call_count == 0
# Fetch fees from trades dict if available to get "proper" values
assert round(trade.fee_open, 4) == 0.001
2024-05-12 14:13:11 +00:00
def test_get_real_amount_wrong_amount(
default_conf_usdt, trades_for_order, buy_order_fee, fee, mocker
):
2021-09-17 08:25:58 +00:00
limit_buy_order_usdt = deepcopy(buy_order_fee)
2024-05-12 14:13:11 +00:00
limit_buy_order_usdt["amount"] = limit_buy_order_usdt["amount"] - 0.001
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=trades_for_order)
amount = float(sum(x["amount"] for x in trades_for_order))
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
open_rate=0.245441,
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(buy_order_fee, "LTC/ETH", "buy")
# Amount does not change
2019-10-17 17:33:21 +00:00
with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"):
freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj)
2024-05-12 14:13:11 +00:00
def test_get_real_amount_wrong_amount_rounding(
default_conf_usdt, trades_for_order, buy_order_fee, fee, mocker
):
# Floats should not be compared directly.
2021-09-17 08:25:58 +00:00
limit_buy_order_usdt = deepcopy(buy_order_fee)
2024-05-12 14:13:11 +00:00
trades_for_order[0]["amount"] = trades_for_order[0]["amount"] + 1e-15
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=trades_for_order)
amount = float(sum(x["amount"] for x in trades_for_order))
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.245441,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(buy_order_fee, "LTC/ETH", "buy")
# Amount changes by fee amount.
2024-05-12 14:13:11 +00:00
assert pytest.approx(freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj)) == (
amount * 0.001
)
def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
amount = 12345
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
open_rate=0.245441,
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
)
order = {
2024-05-12 14:13:11 +00:00
"id": "mocked_order",
"amount": amount,
"status": "open",
"side": "buy",
"price": 0.245441,
}
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(order, "LTC/ETH", "buy")
2022-08-30 18:46:06 +00:00
assert freqtrade.get_real_amount(trade, order, order_obj) is None
2018-08-15 04:05:56 +00:00
def test_get_real_amount_in_point(default_conf_usdt, buy_order_fee, fee, mocker, caplog):
limit_buy_order_usdt = deepcopy(buy_order_fee)
# Fees amount in "POINT"
2024-05-12 14:13:11 +00:00
trades = [
{
"info": {},
"id": "some_trade_id",
"timestamp": 1660092505903,
"datetime": "2022-08-10T00:48:25.903Z",
"symbol": "CEL/USDT",
"order": "some_order_id",
"type": None,
"side": "sell",
"takerOrMaker": "taker",
"price": 1.83255,
"amount": 83.126,
"cost": 152.3325513,
"fee": {"currency": "POINT", "cost": 0.3046651026},
"fees": [
{"cost": "0", "currency": "USDT"},
{"cost": "0", "currency": "GT"},
{"cost": "0.3046651026", "currency": "POINT"},
],
}
]
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=trades)
amount = float(sum(x["amount"] for x in trades))
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="CEL/USDT",
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
fee_open=fee.return_value,
fee_close=fee.return_value,
2024-05-12 14:13:11 +00:00
open_rate=0.245441,
)
2024-05-12 14:13:11 +00:00
limit_buy_order_usdt["amount"] = amount
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
order_obj = Order.parse_from_ccxt_object(buy_order_fee, "LTC/ETH", "buy")
res = freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj)
2022-08-30 18:46:06 +00:00
assert res is None
assert trade.fee_open_currency is None
assert trade.fee_open_cost is None
message = "Not updating buy-fee - rate: None, POINT."
assert log_has(message, caplog)
caplog.clear()
2024-05-12 14:13:11 +00:00
freqtrade.config["exchange"]["unknown_fee_rate"] = 1
res = freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj)
2022-08-30 18:46:06 +00:00
assert res is None
2024-05-12 14:13:11 +00:00
assert trade.fee_open_currency == "POINT"
assert pytest.approx(trade.fee_open_cost) == 0.3046651026
assert trade.fee_open == 0.002
assert trade.fee_open != fee.return_value
assert not log_has(message, caplog)
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"amount,fee_abs,wallet,amount_exp",
[
(8.0, 0.0, 10, None),
(8.0, 0.0, 0, None),
(8.0, 0.1, 0, 0.1),
(8.0, 0.1, 10, None),
(8.0, 0.1, 8.0, None),
(8.0, 0.1, 7.9, 0.1),
],
)
def test_apply_fee_conditional(
default_conf_usdt, fee, mocker, caplog, amount, fee_abs, wallet, amount_exp
):
walletmock = mocker.patch("freqtrade.wallets.Wallets.update")
mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=wallet)
2020-05-03 09:28:29 +00:00
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
2020-05-03 09:28:29 +00:00
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
2020-05-03 09:28:29 +00:00
open_rate=0.245441,
fee_open=fee.return_value,
fee_close=fee.return_value,
)
order = Order(
2024-05-12 14:13:11 +00:00
ft_order_side="buy",
order_id="100",
ft_pair=trade.pair,
ft_is_open=True,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-05-03 09:28:29 +00:00
walletmock.reset_mock()
# Amount is kept as is
2024-05-12 14:13:11 +00:00
assert freqtrade.apply_fee_conditional(trade, "LTC", amount, fee_abs, order) == amount_exp
2020-05-03 09:28:29 +00:00
assert walletmock.call_count == 1
2023-07-13 05:05:14 +00:00
if fee_abs != 0 and amount_exp is None:
assert log_has_re(r"Fee amount.*Eating.*dust\.", caplog)
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"amount,fee_abs,wallet,amount_exp",
[
(8.0, 0.0, 16, None),
(8.0, 0.0, 0, None),
(8.0, 0.1, 8, 0.1),
(8.0, 0.1, 20, None),
(8.0, 0.1, 16.0, None),
(8.0, 0.1, 7.9, 0.1),
(8.0, 0.1, 12, 0.1),
(8.0, 0.1, 15.9, 0.1),
],
)
def test_apply_fee_conditional_multibuy(
default_conf_usdt, fee, mocker, caplog, amount, fee_abs, wallet, amount_exp
):
walletmock = mocker.patch("freqtrade.wallets.Wallets.update")
mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=wallet)
2023-07-13 05:05:14 +00:00
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="LTC/ETH",
2023-07-13 05:05:14 +00:00
amount=amount,
2024-05-12 14:13:11 +00:00
exchange="binance",
2023-07-13 05:05:14 +00:00
open_rate=0.245441,
fee_open=fee.return_value,
2024-05-12 14:13:11 +00:00
fee_close=fee.return_value,
2023-07-13 05:05:14 +00:00
)
# One closed order
order = Order(
2024-05-12 14:13:11 +00:00
ft_order_side="buy",
order_id="10",
2023-07-13 05:05:14 +00:00
ft_pair=trade.pair,
ft_is_open=False,
filled=amount,
2024-05-12 14:13:11 +00:00
status="closed",
2023-07-13 05:05:14 +00:00
)
trade.orders.append(order)
# Add additional order - this should NOT eat into dust unless the wallet was bigger already.
order1 = Order(
2024-05-12 14:13:11 +00:00
ft_order_side="buy",
order_id="100",
2023-07-13 05:05:14 +00:00
ft_pair=trade.pair,
ft_is_open=True,
)
trade.orders.append(order1)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
walletmock.reset_mock()
# The new trade amount will be 2x amount - fee / wallet will have to be adapted to this.
2024-05-12 14:13:11 +00:00
assert freqtrade.apply_fee_conditional(trade, "LTC", amount, fee_abs, order1) == amount_exp
2023-07-13 05:05:14 +00:00
assert walletmock.call_count == 1
if fee_abs != 0 and amount_exp is None:
assert log_has_re(r"Fee amount.*Eating.*dust\.", caplog)
2020-05-03 09:28:29 +00:00
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"delta, is_high_delta",
[
(0.1, False),
(100, True),
],
)
@pytest.mark.parametrize("is_short", [False, True])
2021-09-30 09:19:28 +00:00
def test_order_book_depth_of_market(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
ticker_usdt,
limit_order_open,
fee,
mocker,
order_book_l2,
delta,
is_high_delta,
is_short,
2021-09-30 09:19:28 +00:00
):
2024-05-12 14:13:11 +00:00
ticker_side = "ask" if is_short else "bid"
2022-02-13 15:17:41 +00:00
2024-05-12 14:13:11 +00:00
default_conf_usdt["entry_pricing"]["check_depth_of_market"]["enabled"] = True
default_conf_usdt["entry_pricing"]["check_depth_of_market"]["bids_to_ask_delta"] = delta
2018-08-05 04:41:06 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_l2_order_book", order_book_l2)
2018-08-05 04:41:06 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2022-04-06 01:02:13 +00:00
create_order=MagicMock(return_value=limit_order_open[entry_side(is_short)]),
2018-08-05 04:41:06 +00:00
get_fee=fee,
)
# Save state of current whitelist
2024-05-12 14:13:11 +00:00
whitelist = deepcopy(default_conf_usdt["exchange"]["pair_whitelist"])
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
2018-08-05 04:41:06 +00:00
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
if is_high_delta:
assert trade is None
else:
trade.is_short = is_short
assert trade is not None
2022-03-20 19:00:30 +00:00
assert pytest.approx(trade.stake_amount) == 60.0
assert trade.is_open
assert trade.open_date is not None
2024-05-12 14:13:11 +00:00
assert trade.exchange == "binance"
2018-08-05 14:41:58 +00:00
2023-03-16 06:25:04 +00:00
assert len(Trade.session.scalars(select(Trade)).all()) == 1
2018-08-05 14:41:58 +00:00
# Simulate fulfilled LIMIT_BUY order for trade
2022-02-24 18:56:42 +00:00
oobj = Order.parse_from_ccxt_object(
2024-05-12 14:13:11 +00:00
limit_order_open[entry_side(is_short)], "ADA/USDT", entry_side(is_short)
)
trade.update_trade(oobj)
2018-08-05 14:41:58 +00:00
2022-02-13 15:17:41 +00:00
assert trade.open_rate == ticker_usdt.return_value[ticker_side]
2024-05-12 14:13:11 +00:00
assert whitelist == default_conf_usdt["exchange"]["pair_whitelist"]
2018-08-05 14:41:58 +00:00
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"exception_thrown,ask,last,order_book_top,order_book",
[(False, 0.045, 0.046, 2, None), (True, 0.042, 0.046, 1, {"bids": [[]], "asks": [[]]})],
)
def test_order_book_entry_pricing1(
mocker,
default_conf_usdt,
order_book_l2,
exception_thrown,
ask,
last,
order_book_top,
order_book,
caplog,
) -> None:
2018-08-07 10:29:37 +00:00
"""
2021-09-19 23:44:12 +00:00
test if function get_rate will return the order book price instead of the ask rate
2018-08-07 10:29:37 +00:00
"""
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
ticker_usdt_mock = MagicMock(return_value={"ask": ask, "last": last})
2018-08-25 11:21:10 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2021-09-19 23:44:12 +00:00
fetch_l2_order_book=MagicMock(return_value=order_book) if order_book else order_book_l2,
fetch_ticker=ticker_usdt_mock,
2018-08-25 11:21:10 +00:00
)
2024-05-12 14:13:11 +00:00
default_conf_usdt["exchange"]["name"] = "binance"
default_conf_usdt["entry_pricing"]["use_order_book"] = True
default_conf_usdt["entry_pricing"]["order_book_top"] = order_book_top
default_conf_usdt["entry_pricing"]["price_last_balance"] = 0
default_conf_usdt["telegram"]["enabled"] = False
2018-08-07 10:29:37 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2021-09-19 23:44:12 +00:00
if exception_thrown:
with pytest.raises(PricingError):
2024-05-12 14:13:11 +00:00
freqtrade.exchange.get_rate("ETH/USDT", side="entry", is_short=False, refresh=True)
2021-09-19 23:44:12 +00:00
assert log_has_re(
2024-05-12 14:13:11 +00:00
r"ETH/USDT - Entry Price at location 1 from orderbook could not be determined.", caplog
)
2021-09-19 23:44:12 +00:00
else:
2024-05-12 14:13:11 +00:00
assert (
freqtrade.exchange.get_rate("ETH/USDT", side="entry", is_short=False, refresh=True)
== 0.043935
)
assert ticker_usdt_mock.call_count == 0
2018-08-05 14:41:58 +00:00
def test_check_depth_of_market(default_conf_usdt, mocker, order_book_l2) -> None:
2018-08-14 10:12:44 +00:00
"""
test check depth of market
"""
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
mocker.patch.multiple(EXMS, fetch_l2_order_book=order_book_l2)
default_conf_usdt["telegram"]["enabled"] = False
default_conf_usdt["exchange"]["name"] = "binance"
default_conf_usdt["entry_pricing"]["check_depth_of_market"]["enabled"] = True
2018-08-07 10:29:37 +00:00
# delta is 100 which is impossible to reach. hence function will return false
2024-05-12 14:13:11 +00:00
default_conf_usdt["entry_pricing"]["check_depth_of_market"]["bids_to_ask_delta"] = 100
freqtrade = FreqtradeBot(default_conf_usdt)
2018-08-05 14:41:58 +00:00
2024-05-12 14:13:11 +00:00
conf = default_conf_usdt["entry_pricing"]["check_depth_of_market"]
assert freqtrade._check_depth_of_market("ETH/BTC", conf, side=SignalDirection.LONG) is False
2018-08-05 14:41:58 +00:00
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_order_book_exit_pricing(
2024-05-12 14:13:11 +00:00
default_conf_usdt,
limit_buy_order_usdt_open,
limit_buy_order_usdt,
fee,
is_short,
limit_sell_order_usdt_open,
mocker,
order_book_l2,
caplog,
) -> None:
2018-08-14 10:12:44 +00:00
"""
test order book ask strategy
"""
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_l2_order_book", order_book_l2)
default_conf_usdt["exchange"]["name"] = "binance"
default_conf_usdt["exit_pricing"]["use_order_book"] = True
default_conf_usdt["exit_pricing"]["order_book_top"] = 1
default_conf_usdt["telegram"]["enabled"] = False
2018-08-05 14:41:58 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
2018-08-05 14:41:58 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
create_order=MagicMock(
side_effect=[
limit_buy_order_usdt_open,
limit_sell_order_usdt_open,
]
),
2018-08-05 14:41:58 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2018-08-05 14:41:58 +00:00
patch_get_signal(freqtrade)
freqtrade.enter_positions()
2018-08-05 14:41:58 +00:00
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2018-08-05 14:41:58 +00:00
assert trade
2018-08-05 14:56:14 +00:00
time.sleep(0.01) # Race condition fix
2024-05-12 14:13:11 +00:00
oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt["symbol"], "buy")
trade.update_trade(oobj)
2019-12-18 19:16:53 +00:00
freqtrade.wallets.update()
2018-08-05 14:56:14 +00:00
assert trade.is_open is True
if is_short:
2024-05-12 14:13:11 +00:00
patch_get_signal(freqtrade, enter_long=False, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
2018-08-05 14:56:14 +00:00
assert freqtrade.handle_trade(trade) is True
2024-05-12 14:13:11 +00:00
assert trade.close_rate_requested == order_book_l2.return_value["asks"][0][0]
2020-05-26 18:24:44 +00:00
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_l2_order_book", return_value={"bids": [[]], "asks": [[]]})
with pytest.raises(PricingError):
2020-05-26 18:24:44 +00:00
freqtrade.handle_trade(trade)
assert log_has_re(
2024-05-12 14:13:11 +00:00
r"ETH/USDT - Exit Price at location 1 from orderbook could not be determined\..*", caplog
)
2018-08-29 17:32:44 +00:00
def test_startup_state(default_conf_usdt, mocker):
2024-05-12 14:13:11 +00:00
default_conf_usdt["pairlist"] = {"method": "VolumePairList", "config": {"number_assets": 20}}
mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True))
worker = get_patched_worker(mocker, default_conf_usdt)
2020-01-29 14:08:36 +00:00
assert worker.freqtrade.state is State.RUNNING
def test_startup_trade_reinit(default_conf_usdt, edge_conf, mocker):
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True))
reinit_mock = MagicMock()
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.persistence.Trade.stoploss_reinitialization", reinit_mock)
ftbot = get_patched_freqtradebot(mocker, default_conf_usdt)
ftbot.startup()
assert reinit_mock.call_count == 1
reinit_mock.reset_mock()
ftbot = get_patched_freqtradebot(mocker, edge_conf)
ftbot.startup()
assert reinit_mock.call_count == 0
2019-10-25 13:00:16 +00:00
@pytest.mark.usefixtures("init_persistence")
2024-05-12 14:13:11 +00:00
def test_sync_wallet_dry_run(
mocker, default_conf_usdt, ticker_usdt, fee, limit_buy_order_usdt_open, caplog
):
default_conf_usdt["dry_run"] = True
2019-12-15 09:26:56 +00:00
# Initialize to 2 times stake amount
2024-05-12 14:13:11 +00:00
default_conf_usdt["dry_run_wallet"] = 120.0
default_conf_usdt["max_open_trades"] = 2
default_conf_usdt["tradable_balance_ratio"] = 1.0
2019-12-15 09:26:56 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
2019-12-15 09:26:56 +00:00
get_fee=fee,
)
bot = get_patched_freqtradebot(mocker, default_conf_usdt)
2019-12-15 09:26:56 +00:00
patch_get_signal(bot)
2024-05-12 14:13:11 +00:00
assert bot.wallets.get_free("USDT") == 120.0
2019-12-15 09:26:56 +00:00
n = bot.enter_positions()
assert n == 2
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2019-12-15 09:26:56 +00:00
assert len(trades) == 2
2024-05-12 14:13:11 +00:00
bot.config["max_open_trades"] = 3
n = bot.enter_positions()
assert n == 0
2024-05-12 14:13:11 +00:00
assert log_has_re(
r"Unable to create trade for XRP/USDT: "
r"Available balance \(0.0 USDT\) is lower than stake amount \(60.0 USDT\)",
caplog,
)
2020-05-16 10:40:25 +00:00
@pytest.mark.usefixtures("init_persistence")
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"is_short,buy_calls,sell_calls",
[
(False, 1, 1),
(True, 1, 1),
],
)
def test_cancel_all_open_orders(
mocker, default_conf_usdt, fee, limit_order, limit_order_open, is_short, buy_calls, sell_calls
):
default_conf_usdt["cancel_open_orders_on_exit"] = True
mocker.patch(
2024-05-12 14:13:11 +00:00
f"{EXMS}.fetch_order",
side_effect=[
ExchangeError(),
limit_order[exit_side(is_short)],
2022-04-06 01:02:13 +00:00
limit_order_open[entry_side(is_short)],
limit_order_open[exit_side(is_short)],
2024-05-12 14:13:11 +00:00
],
)
2024-05-12 14:13:11 +00:00
buy_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter")
sell_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit")
2020-05-16 10:40:25 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short=is_short)
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2020-09-10 05:40:19 +00:00
assert len(trades) == MOCK_TRADE_COUNT
2020-05-16 10:40:25 +00:00
freqtrade.cancel_all_open_orders()
assert buy_mock.call_count == buy_calls
assert sell_mock.call_count == sell_calls
2020-06-30 05:16:08 +00:00
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-06-30 05:16:08 +00:00
freqtrade.check_for_open_trades()
assert freqtrade.rpc.send_msg.call_count == 0
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2020-06-30 05:16:08 +00:00
trade.is_open = True
freqtrade.check_for_open_trades()
assert freqtrade.rpc.send_msg.call_count == 1
2024-05-12 14:13:11 +00:00
assert "Handle these trades manually" in freqtrade.rpc.send_msg.call_args[0][0]["status"]
2020-09-06 17:32:00 +00:00
@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.usefixtures("init_persistence")
def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short=is_short)
2020-09-06 17:32:00 +00:00
freqtrade.startup_update_open_orders()
assert not log_has_re(r"Error updating Order .*", caplog)
caplog.clear()
2024-05-12 14:13:11 +00:00
freqtrade.config["dry_run"] = False
freqtrade.startup_update_open_orders()
2023-08-24 15:53:46 +00:00
assert len(Order.get_open_orders()) == 4
2021-09-15 03:08:15 +00:00
matching_buy_order = mock_order_4(is_short=is_short)
2024-05-12 14:13:11 +00:00
matching_buy_order.update(
{
"status": "closed",
}
)
mocker.patch(f"{EXMS}.fetch_order", return_value=matching_buy_order)
freqtrade.startup_update_open_orders()
2020-09-10 05:40:19 +00:00
# Only stoploss and sell orders are kept open
2023-08-24 15:53:46 +00:00
assert len(Order.get_open_orders()) == 3
2022-05-19 04:56:38 +00:00
caplog.clear()
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_order", side_effect=ExchangeError)
2022-05-19 04:56:38 +00:00
freqtrade.startup_update_open_orders()
assert log_has_re(r"Error updating Order .*", caplog)
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_order", side_effect=InvalidOrderException)
hto_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.handle_cancel_order")
# Orders which are no longer found after X days should be assumed as canceled.
freqtrade.startup_update_open_orders()
assert log_has_re(r"Order is older than \d days.*", caplog)
2023-08-24 15:53:46 +00:00
assert hto_mock.call_count == 3
2024-05-12 14:13:11 +00:00
assert hto_mock.call_args_list[0][0][0]["status"] == "canceled"
assert hto_mock.call_args_list[1][0][0]["status"] == "canceled"
2022-08-16 08:59:43 +00:00
@pytest.mark.usefixtures("init_persistence")
def test_startup_backpopulate_precision(mocker, default_conf_usdt, fee, caplog):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
create_mock_trades_usdt(fee)
trades = Trade.get_trades().all()
2024-05-12 14:13:11 +00:00
trades[-1].exchange = "some_other_exchange"
2022-08-16 08:59:43 +00:00
for trade in trades:
assert trade.price_precision is None
assert trade.amount_precision is None
assert trade.precision_mode is None
freqtrade.startup_backpopulate_precision()
trades = Trade.get_trades().all()
for trade in trades:
2024-05-12 14:13:11 +00:00
if trade.exchange == "some_other_exchange":
2022-08-16 08:59:43 +00:00
assert trade.price_precision is None
assert trade.amount_precision is None
assert trade.precision_mode is None
else:
assert trade.price_precision is not None
assert trade.amount_precision is not None
assert trade.precision_mode is not None
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_update_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
def patch_with_fee(order):
2024-05-12 14:13:11 +00:00
order.update(
{"fee": {"cost": 0.1, "rate": 0.01, "currency": order["symbol"].split("/")[0]}}
)
return order
2024-05-12 14:13:11 +00:00
mocker.patch(
f"{EXMS}.fetch_order_or_stoploss_order",
side_effect=[
patch_with_fee(mock_order_2_sell(is_short=is_short)),
patch_with_fee(mock_order_3_sell(is_short=is_short)),
patch_with_fee(mock_order_2(is_short=is_short)),
patch_with_fee(mock_order_3(is_short=is_short)),
patch_with_fee(mock_order_4(is_short=is_short)),
],
)
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short=is_short)
trades = Trade.get_trades().all()
2020-09-10 05:40:19 +00:00
assert len(trades) == MOCK_TRADE_COUNT
for trade in trades:
2021-09-15 03:08:15 +00:00
trade.is_short = is_short
assert trade.fee_open_cost is None
assert trade.fee_open_currency is None
assert trade.fee_close_cost is None
assert trade.fee_close_currency is None
freqtrade.update_trades_without_assigned_fees()
# Does nothing for dry-run
trades = Trade.get_trades().all()
assert len(trades) == MOCK_TRADE_COUNT
for trade in trades:
assert trade.fee_open_cost is None
assert trade.fee_open_currency is None
assert trade.fee_close_cost is None
assert trade.fee_close_currency is None
2024-05-12 14:13:11 +00:00
freqtrade.config["dry_run"] = False
freqtrade.update_trades_without_assigned_fees()
2020-09-09 05:01:43 +00:00
trades = Trade.get_trades().all()
2020-09-10 05:40:19 +00:00
assert len(trades) == MOCK_TRADE_COUNT
for trade in trades:
if trade.is_open:
# Exclude Trade 4 - as the order is still open.
2022-04-06 01:02:13 +00:00
if trade.select_order(entry_side(is_short), False):
assert trade.fee_open_cost is not None
assert trade.fee_open_currency is not None
else:
assert trade.fee_open_cost is None
assert trade.fee_open_currency is None
else:
assert trade.fee_close_cost is not None
assert trade.fee_close_currency is not None
2020-09-09 04:40:40 +00:00
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mock_uts = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.update_trade_state")
2020-09-09 04:40:40 +00:00
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.fetch_order_or_stoploss_order", return_value={"status": "open"})
2022-02-23 05:27:56 +00:00
create_mock_trades(fee, is_short)
2020-09-09 04:40:40 +00:00
trades = Trade.get_trades().all()
freqtrade.handle_insufficient_funds(trades[3])
# assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
2020-09-09 04:40:40 +00:00
assert mock_uts.call_count == 1
assert mock_uts.call_args_list[0][0][0] == trades[3]
2024-05-12 14:13:11 +00:00
assert mock_uts.call_args_list[0][0][1] == mock_order_4(is_short)["id"]
assert log_has_re(r"Trying to refind lost order for .*", caplog)
2020-09-09 04:40:40 +00:00
mock_uts.reset_mock()
caplog.clear()
# Test with trade without orders
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="XRP/ETH",
stake_amount=60.0,
2020-09-09 04:40:40 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2023-05-14 09:10:21 +00:00
open_date=dt_now(),
2020-09-09 04:40:40 +00:00
is_open=True,
amount=30,
open_rate=2.0,
2024-05-12 14:13:11 +00:00
exchange="binance",
is_short=is_short,
2020-09-09 04:40:40 +00:00
)
2023-03-16 06:25:04 +00:00
Trade.session.add(trade)
2020-09-09 04:40:40 +00:00
freqtrade.handle_insufficient_funds(trade)
# assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
2020-09-09 04:40:40 +00:00
assert mock_uts.call_count == 0
2020-09-09 04:46:38 +00:00
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
2022-02-23 05:27:56 +00:00
def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, caplog):
2020-09-19 07:46:32 +00:00
caplog.set_level(logging.DEBUG)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mock_uts = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.update_trade_state")
2020-09-09 04:46:38 +00:00
2024-05-12 14:13:11 +00:00
mock_fo = mocker.patch(f"{EXMS}.fetch_order_or_stoploss_order", return_value={"status": "open"})
def reset_open_orders(trade):
2021-09-15 03:08:15 +00:00
trade.is_short = is_short
2020-09-09 04:46:38 +00:00
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short=is_short)
2020-09-09 04:46:38 +00:00
trades = Trade.get_trades().all()
2020-09-09 05:50:52 +00:00
caplog.clear()
# No open order
2023-08-24 15:53:46 +00:00
trade = trades[1]
reset_open_orders(trade)
assert not trade.has_open_orders
2023-12-23 08:25:39 +00:00
assert trade.has_open_sl_orders is False
freqtrade.handle_insufficient_funds(trade)
2023-08-24 15:53:46 +00:00
order = trade.orders[0]
2024-05-12 14:13:11 +00:00
assert log_has_re(
r"Order Order(.*order_id=" + order.order_id + ".*) is no longer open.", caplog
)
2020-09-09 05:50:52 +00:00
assert mock_fo.call_count == 0
assert mock_uts.call_count == 0
# No change to orderid - as update_trade_state is mocked
assert not trade.has_open_orders
2023-12-23 08:25:39 +00:00
assert trade.has_open_sl_orders is False
2020-09-09 04:46:38 +00:00
2020-09-09 05:50:52 +00:00
caplog.clear()
mock_fo.reset_mock()
# Open buy order
trade = trades[3]
reset_open_orders(trade)
# This part in not relevant anymore
# assert not trade.has_open_orders
2023-12-23 08:25:39 +00:00
assert trade.has_open_sl_orders is False
freqtrade.handle_insufficient_funds(trade)
2021-09-15 03:08:15 +00:00
order = mock_order_4(is_short=is_short)
2020-09-09 05:50:52 +00:00
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 1
assert mock_uts.call_count == 1
# Found open buy order
2023-12-23 08:25:39 +00:00
assert trade.has_open_orders is True
assert trade.has_open_sl_orders is False
2020-09-09 05:50:52 +00:00
caplog.clear()
mock_fo.reset_mock()
# Open stoploss order
trade = trades[4]
reset_open_orders(trade)
assert not trade.has_open_orders
2023-12-23 08:25:39 +00:00
assert trade.has_open_sl_orders
freqtrade.handle_insufficient_funds(trade)
2021-09-15 03:08:15 +00:00
order = mock_order_5_stoploss(is_short=is_short)
2020-09-09 05:50:52 +00:00
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 1
assert mock_uts.call_count == 2
2024-01-19 05:48:32 +00:00
# stoploss order is "refound" and added to the trade
assert not trade.has_open_orders
2023-12-23 08:25:39 +00:00
assert trade.has_open_sl_orders is True
caplog.clear()
mock_fo.reset_mock()
mock_uts.reset_mock()
# Open sell order
trade = trades[5]
reset_open_orders(trade)
# This part in not relevant anymore
# assert not trade.has_open_orders
2023-12-23 08:25:39 +00:00
assert trade.has_open_sl_orders is False
freqtrade.handle_insufficient_funds(trade)
2021-09-15 03:08:15 +00:00
order = mock_order_6_sell(is_short=is_short)
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 1
assert mock_uts.call_count == 1
# sell-orderid is "refound" and added to the trade
2024-05-12 14:13:11 +00:00
assert trade.open_orders_ids[0] == order["id"]
2023-12-23 08:25:39 +00:00
assert trade.has_open_sl_orders is False
2020-09-09 05:50:52 +00:00
2020-09-09 05:57:02 +00:00
caplog.clear()
2020-09-09 05:50:52 +00:00
# Test error case
2024-05-12 14:13:11 +00:00
mock_fo = mocker.patch(f"{EXMS}.fetch_order_or_stoploss_order", side_effect=ExchangeError())
2021-09-15 03:08:15 +00:00
order = mock_order_5_stoploss(is_short=is_short)
freqtrade.handle_insufficient_funds(trades[4])
2020-09-09 05:50:52 +00:00
assert log_has(f"Error updating {order['id']}.", caplog)
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_onexchange_order(mocker, default_conf_usdt, limit_order, is_short, caplog):
2024-05-12 14:13:11 +00:00
default_conf_usdt["dry_run"] = False
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mock_uts = mocker.spy(freqtrade, "update_trade_state")
entry_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
2024-05-12 14:13:11 +00:00
mock_fo = mocker.patch(
f"{EXMS}.fetch_orders",
return_value=[
entry_order,
exit_order,
],
)
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="ETH/USDT",
fee_open=0.001,
fee_close=0.001,
2024-05-12 14:13:11 +00:00
open_rate=entry_order["price"],
open_date=dt_now(),
2024-05-12 14:13:11 +00:00
stake_amount=entry_order["cost"],
amount=entry_order["amount"],
exchange="binance",
is_short=is_short,
leverage=1,
)
2024-05-12 14:13:11 +00:00
trade.orders.append(Order.parse_from_ccxt_object(entry_order, "ADA/USDT", entry_side(is_short)))
Trade.session.add(trade)
freqtrade.handle_onexchange_order(trade)
assert log_has_re(r"Found previously unknown order .*", caplog)
2023-09-11 18:18:42 +00:00
# Update trade state is called twice, once for the known and once for the unknown order.
assert mock_uts.call_count == 2
assert mock_fo.call_count == 1
trade = Trade.session.scalars(select(Trade)).first()
assert len(trade.orders) == 2
assert trade.is_open is False
assert trade.exit_reason == ExitType.SOLD_ON_EXCHANGE.value
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"factor,adjusts",
[
(0.99, True),
(0.97, False),
],
)
def test_handle_onexchange_order_changed_amount(
2024-05-12 14:13:11 +00:00
mocker,
default_conf_usdt,
limit_order,
is_short,
caplog,
factor,
adjusts,
):
2024-05-12 14:13:11 +00:00
default_conf_usdt["dry_run"] = False
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mock_uts = mocker.spy(freqtrade, "update_trade_state")
entry_order = limit_order[entry_side(is_short)]
2024-05-12 14:13:11 +00:00
mock_fo = mocker.patch(
f"{EXMS}.fetch_orders",
return_value=[
entry_order,
],
)
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="ETH/USDT",
fee_open=0.001,
2024-05-12 14:13:11 +00:00
base_currency="ETH",
fee_close=0.001,
2024-05-12 14:13:11 +00:00
open_rate=entry_order["price"],
open_date=dt_now(),
2024-05-12 14:13:11 +00:00
stake_amount=entry_order["cost"],
amount=entry_order["amount"],
exchange="binance",
is_short=is_short,
leverage=1,
)
freqtrade.wallets = MagicMock()
freqtrade.wallets.get_owned = MagicMock(return_value=entry_order["amount"] * factor)
2024-05-12 14:13:11 +00:00
trade.orders.append(Order.parse_from_ccxt_object(entry_order, "ADA/USDT", entry_side(is_short)))
Trade.session.add(trade)
# assert trade.amount > entry_order['amount']
freqtrade.handle_onexchange_order(trade)
assert mock_uts.call_count == 1
assert mock_fo.call_count == 1
trade = Trade.session.scalars(select(Trade)).first()
2024-05-12 14:13:11 +00:00
assert log_has_re(r".*has a total of .* but the Wallet shows.*", caplog)
if adjusts:
# Trade amount is updated
2024-05-12 14:13:11 +00:00
assert trade.amount == entry_order["amount"] * factor
assert log_has_re(r".*Adjusting trade amount to.*", caplog)
else:
2024-05-12 14:13:11 +00:00
assert log_has_re(r".*Refusing to adjust as the difference.*", caplog)
assert trade.amount == entry_order["amount"]
assert len(trade.orders) == 1
assert trade.is_open is True
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_onexchange_order_exit(mocker, default_conf_usdt, limit_order, is_short, caplog):
2024-05-12 14:13:11 +00:00
default_conf_usdt["dry_run"] = False
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mock_uts = mocker.spy(freqtrade, "update_trade_state")
entry_order = limit_order[entry_side(is_short)]
add_entry_order = deepcopy(entry_order)
2024-05-12 14:13:11 +00:00
add_entry_order.update(
{
"id": "_partial_entry_id",
"amount": add_entry_order["amount"] / 1.5,
"cost": add_entry_order["cost"] / 1.5,
"filled": add_entry_order["filled"] / 1.5,
}
)
exit_order_part = deepcopy(limit_order[exit_side(is_short)])
2024-05-12 14:13:11 +00:00
exit_order_part.update(
{
"id": "some_random_partial_id",
"amount": exit_order_part["amount"] / 2,
"cost": exit_order_part["cost"] / 2,
"filled": exit_order_part["filled"] / 2,
}
)
exit_order = limit_order[exit_side(is_short)]
# Orders intentionally in the wrong sequence
2024-05-12 14:13:11 +00:00
mock_fo = mocker.patch(
f"{EXMS}.fetch_orders",
return_value=[
entry_order,
exit_order_part,
exit_order,
add_entry_order,
],
)
trade = Trade(
2024-05-12 14:13:11 +00:00
pair="ETH/USDT",
fee_open=0.001,
fee_close=0.001,
2024-05-12 14:13:11 +00:00
open_rate=entry_order["price"],
open_date=dt_now(),
2024-05-12 14:13:11 +00:00
stake_amount=entry_order["cost"],
amount=entry_order["amount"],
exchange="binance",
is_short=is_short,
leverage=1,
is_open=True,
)
trade.orders = [
Order.parse_from_ccxt_object(entry_order, trade.pair, entry_side(is_short)),
Order.parse_from_ccxt_object(exit_order_part, trade.pair, exit_side(is_short)),
Order.parse_from_ccxt_object(add_entry_order, trade.pair, entry_side(is_short)),
Order.parse_from_ccxt_object(exit_order, trade.pair, exit_side(is_short)),
]
trade.recalc_trade_from_orders()
Trade.session.add(trade)
Trade.commit()
freqtrade.handle_onexchange_order(trade)
# assert log_has_re(r"Found previously unknown order .*", caplog)
# Update trade state is called three times, once for every order
assert mock_uts.call_count == 4
assert mock_fo.call_count == 1
trade = Trade.session.scalars(select(Trade)).first()
assert len(trade.orders) == 4
assert trade.is_open is True
assert trade.exit_reason is None
assert trade.amount == 5.0
2024-05-26 06:35:41 +00:00
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_onexchange_order_fully_canceled_enter(
mocker, default_conf_usdt, limit_order, is_short, caplog
):
default_conf_usdt["dry_run"] = False
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
entry_order = limit_order[entry_side(is_short)]
entry_order["status"] = "canceled"
entry_order["filled"] = 0.0
mock_fo = mocker.patch(
f"{EXMS}.fetch_orders",
return_value=[
entry_order,
],
)
mocker.patch(f"{EXMS}.get_rate", return_value=entry_order["price"])
trade = Trade(
pair="ETH/USDT",
fee_open=0.001,
fee_close=0.001,
open_rate=entry_order["price"],
open_date=dt_now(),
stake_amount=entry_order["cost"],
amount=entry_order["amount"],
exchange="binance",
is_short=is_short,
leverage=1,
)
trade.orders.append(Order.parse_from_ccxt_object(entry_order, "ADA/USDT", entry_side(is_short)))
Trade.session.add(trade)
assert freqtrade.handle_onexchange_order(trade) is True
assert log_has_re(r"Trade only had fully canceled entry orders\. .*", caplog)
assert mock_fo.call_count == 1
trades = Trade.get_trades().all()
assert len(trades) == 0
def test_get_valid_price(mocker, default_conf_usdt) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
freqtrade = FreqtradeBot(default_conf_usdt)
2024-05-12 14:13:11 +00:00
freqtrade.config["custom_price_max_distance_ratio"] = 0.02
custom_price_string = "10"
custom_price_badstring = "10abc"
custom_price_float = 10.0
custom_price_int = 10
custom_price_over_max_alwd = 11.0
custom_price_under_min_alwd = 9.0
proposed_price = 10.1
valid_price_from_string = freqtrade.get_valid_price(custom_price_string, proposed_price)
valid_price_from_badstring = freqtrade.get_valid_price(custom_price_badstring, proposed_price)
valid_price_from_int = freqtrade.get_valid_price(custom_price_int, proposed_price)
valid_price_from_float = freqtrade.get_valid_price(custom_price_float, proposed_price)
valid_price_at_max_alwd = freqtrade.get_valid_price(custom_price_over_max_alwd, proposed_price)
valid_price_at_min_alwd = freqtrade.get_valid_price(custom_price_under_min_alwd, proposed_price)
assert isinstance(valid_price_from_string, float)
assert isinstance(valid_price_from_badstring, float)
assert isinstance(valid_price_from_int, float)
assert isinstance(valid_price_from_float, float)
assert valid_price_from_string == custom_price_float
assert valid_price_from_badstring == proposed_price
assert valid_price_from_int == custom_price_int
assert valid_price_from_float == custom_price_float
assert valid_price_at_max_alwd < custom_price_over_max_alwd
assert valid_price_at_max_alwd > proposed_price
assert valid_price_at_min_alwd > custom_price_under_min_alwd
assert valid_price_at_min_alwd < proposed_price
2021-09-14 21:38:26 +00:00
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"trading_mode,calls,t1,t2",
[
("spot", 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
("margin", 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
("futures", 15, "2021-09-01 00:01:02", "2021-09-01 08:00:01"),
("futures", 16, "2021-09-01 00:00:02", "2021-09-01 08:00:01"),
("futures", 16, "2021-08-31 23:59:59", "2021-09-01 08:00:01"),
("futures", 16, "2021-09-01 00:00:02", "2021-09-01 08:00:02"),
("futures", 16, "2021-08-31 23:59:59", "2021-09-01 08:00:02"),
("futures", 16, "2021-08-31 23:59:59", "2021-09-01 08:00:03"),
("futures", 16, "2021-08-31 23:59:59", "2021-09-01 08:00:04"),
("futures", 17, "2021-08-31 23:59:59", "2021-09-01 08:01:05"),
("futures", 17, "2021-08-31 23:59:59", "2021-09-01 08:01:06"),
("futures", 17, "2021-08-31 23:59:59", "2021-09-01 08:01:07"),
("futures", 17, "2021-08-31 23:59:58", "2021-09-01 08:01:07"),
],
)
@pytest.mark.parametrize(
"tzoffset",
[
"+00:00",
"+01:00",
"-02:00",
],
)
def test_update_funding_fees_schedule(
mocker, default_conf, trading_mode, calls, time_machine, t1, t2, tzoffset
):
time_machine.move_to(f"{t1} {tzoffset}", tick=False)
2021-09-30 05:11:01 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
mocker.patch("freqtrade.freqtradebot.FreqtradeBot.update_funding_fees", return_value=True)
default_conf["trading_mode"] = trading_mode
default_conf["margin_mode"] = "isolated"
freqtrade = get_patched_freqtradebot(mocker, default_conf)
2021-09-30 05:11:01 +00:00
time_machine.move_to(f"{t2} {tzoffset}", tick=False)
2021-10-11 18:35:18 +00:00
# Check schedule jobs in debugging with freqtrade._schedule.jobs
freqtrade._schedule.run_pending()
2021-10-06 05:05:34 +00:00
assert freqtrade.update_funding_fees.call_count == calls
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize("schedule_off", [False, True])
@pytest.mark.parametrize("is_short", [True, False])
def test_update_funding_fees(
mocker,
default_conf,
time_machine,
fee,
ticker_usdt_sell_up,
is_short,
limit_order_open,
2024-05-12 14:13:11 +00:00
schedule_off,
):
"""
2021-11-09 07:00:57 +00:00
nominal_value = mark_price * size
funding_fee = nominal_value * funding_rate
size = 123
"LTC/USDT"
2021-11-09 07:00:57 +00:00
time: 0, mark: 3.3, fundRate: 0.00032583, nominal_value: 405.9, fundFee: 0.132254397
time: 8, mark: 3.2, fundRate: 0.00024472, nominal_value: 393.6, fundFee: 0.096321792
"ETH/USDT"
2021-11-09 07:00:57 +00:00
time: 0, mark: 2.4, fundRate: 0.0001, nominal_value: 295.2, fundFee: 0.02952
time: 8, mark: 2.5, fundRate: 0.0001, nominal_value: 307.5, fundFee: 0.03075
"ETC/USDT"
2021-11-09 07:00:57 +00:00
time: 0, mark: 4.3, fundRate: 0.00031077, nominal_value: 528.9, fundFee: 0.164366253
time: 8, mark: 4.1, fundRate: 0.00022655, nominal_value: 504.3, fundFee: 0.114249165
"XRP/USDT"
2021-11-09 07:00:57 +00:00
time: 0, mark: 1.2, fundRate: 0.00049426, nominal_value: 147.6, fundFee: 0.072952776
time: 8, mark: 1.2, fundRate: 0.00032715, nominal_value: 147.6, fundFee: 0.04828734
"""
# SETUP
time_machine.move_to("2021-09-01 00:00:16 +00:00")
2022-04-06 01:02:13 +00:00
open_order = limit_order_open[entry_side(is_short)]
2021-11-11 19:07:56 +00:00
open_exit_order = limit_order_open[exit_side(is_short)]
bid = 0.11
enter_rate_mock = MagicMock(return_value=bid)
enter_mm = MagicMock(return_value=open_order)
patch_RPCManager(mocker)
patch_exchange(mocker)
2024-05-12 14:13:11 +00:00
default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated"
2023-05-14 15:46:56 +00:00
date_midnight = dt_utc(2021, 9, 1)
date_eight = dt_utc(2021, 9, 1, 8)
date_sixteen = dt_utc(2021, 9, 1, 16)
2024-05-12 14:13:11 +00:00
columns = ["date", "open", "high", "low", "close", "volume"]
2021-12-11 08:49:48 +00:00
# 16:00 entry is actually never used
# But should be kept in the test to ensure we're filtering correctly.
funding_rates = {
2024-05-12 14:13:11 +00:00
"LTC/USDT": DataFrame(
[
2021-12-11 08:49:48 +00:00
[date_midnight, 0.00032583, 0, 0, 0, 0],
[date_eight, 0.00024472, 0, 0, 0, 0],
[date_sixteen, 0.00024472, 0, 0, 0, 0],
2024-05-12 14:13:11 +00:00
],
columns=columns,
),
"ETH/USDT": DataFrame(
[
2021-12-11 08:49:48 +00:00
[date_midnight, 0.0001, 0, 0, 0, 0],
[date_eight, 0.0001, 0, 0, 0, 0],
[date_sixteen, 0.0001, 0, 0, 0, 0],
2024-05-12 14:13:11 +00:00
],
columns=columns,
),
"XRP/USDT": DataFrame(
[
2021-12-11 08:49:48 +00:00
[date_midnight, 0.00049426, 0, 0, 0, 0],
[date_eight, 0.00032715, 0, 0, 0, 0],
[date_sixteen, 0.00032715, 0, 0, 0, 0],
2024-05-12 14:13:11 +00:00
],
columns=columns,
),
}
mark_prices = {
2024-05-12 14:13:11 +00:00
"LTC/USDT": DataFrame(
[
2021-12-11 08:49:48 +00:00
[date_midnight, 3.3, 0, 0, 0, 0],
[date_eight, 3.2, 0, 0, 0, 0],
[date_sixteen, 3.2, 0, 0, 0, 0],
2024-05-12 14:13:11 +00:00
],
columns=columns,
),
"ETH/USDT": DataFrame(
[
2021-12-11 08:49:48 +00:00
[date_midnight, 2.4, 0, 0, 0, 0],
[date_eight, 2.5, 0, 0, 0, 0],
[date_sixteen, 2.5, 0, 0, 0, 0],
2024-05-12 14:13:11 +00:00
],
columns=columns,
),
"XRP/USDT": DataFrame(
[
2021-12-11 08:49:48 +00:00
[date_midnight, 1.2, 0, 0, 0, 0],
[date_eight, 1.2, 0, 0, 0, 0],
[date_sixteen, 1.2, 0, 0, 0, 0],
2024-05-12 14:13:11 +00:00
],
columns=columns,
),
}
2021-12-11 08:49:48 +00:00
def refresh_latest_ohlcv_mock(pairlist, **kwargs):
ret = {}
for p, tf, ct in pairlist:
if ct == CandleType.MARK:
ret[(p, tf, ct)] = mark_prices[p]
else:
ret[(p, tf, ct)] = funding_rates[p]
2021-12-11 08:49:48 +00:00
return ret
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.refresh_latest_ohlcv", side_effect=refresh_latest_ohlcv_mock)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
get_rate=enter_rate_mock,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
create_order=enter_mm,
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
2022-02-06 04:36:28 +00:00
get_maintenance_ratio_and_amt=MagicMock(return_value=(0.01, 0.01)),
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
# initial funding fees,
2024-05-12 14:13:11 +00:00
freqtrade.execute_entry("ETH/USDT", 123, is_short=is_short)
freqtrade.execute_entry("LTC/USDT", 2.0, is_short=is_short)
freqtrade.execute_entry("XRP/USDT", 123, is_short=is_short)
2024-04-18 20:51:25 +00:00
multiple = 1 if is_short else -1
trades = Trade.get_open_trades()
assert len(trades) == 3
for trade in trades:
2022-03-22 18:28:13 +00:00
assert pytest.approx(trade.funding_fees) == 0
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", return_value=open_exit_order)
time_machine.move_to("2021-09-01 08:00:00 +00:00")
if schedule_off:
for trade in trades:
freqtrade.execute_trade_exit(
trade=trade,
# The values of the next 2 params are irrelevant for this test
2024-05-12 14:13:11 +00:00
limit=ticker_usdt_sell_up()["bid"],
exit_check=ExitCheckTuple(exit_type=ExitType.ROI),
)
assert trade.funding_fees == pytest.approx(
sum(
trade.amount
* mark_prices[trade.pair].iloc[1:2]["open"]
* funding_rates[trade.pair].iloc[1:2]["open"]
* multiple
)
)
2021-12-11 08:49:48 +00:00
else:
freqtrade._schedule.run_pending()
# Funding fees for 00:00 and 08:00
for trade in trades:
2024-05-12 14:13:11 +00:00
assert trade.funding_fees == pytest.approx(
sum(
trade.amount
* mark_prices[trade.pair].iloc[1:2]["open"]
* funding_rates[trade.pair].iloc[1:2]["open"]
* multiple
)
)
2022-01-22 16:25:21 +00:00
def test_update_funding_fees_error(mocker, default_conf, caplog):
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.get_funding_fees", side_effect=ExchangeError())
default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated"
freqtrade = get_patched_freqtradebot(mocker, default_conf)
freqtrade.update_funding_fees()
log_has("Could not update funding fees for open trades.", caplog)
def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
2021-12-19 10:27:17 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_wallet(mocker, free=10000)
2024-05-12 14:13:11 +00:00
default_conf_usdt.update(
{
"position_adjustment_enable": True,
"dry_run": False,
"stake_amount": 10.0,
"dry_run_wallet": 1000.0,
}
)
2021-12-19 10:27:17 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
bid = 11
stake_amount = 10
buy_rate_mock = MagicMock(return_value=bid)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2021-12-19 10:27:17 +00:00
get_rate=buy_rate_mock,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 10, "ask": 12, "last": 11}),
2021-12-19 10:27:17 +00:00
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
)
2024-05-12 14:13:11 +00:00
pair = "ETH/USDT"
2021-12-19 10:27:17 +00:00
# Initial buy
closed_successful_buy_order = {
2024-05-12 14:13:11 +00:00
"pair": pair,
"ft_pair": pair,
"ft_order_side": "buy",
"side": "buy",
"type": "limit",
"status": "closed",
"price": bid,
"average": bid,
"cost": bid * stake_amount,
"amount": stake_amount,
"filled": stake_amount,
"ft_is_open": False,
"id": "650",
"order_id": "650",
}
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=closed_successful_buy_order))
mocker.patch(
f"{EXMS}.fetch_order_or_stoploss_order", MagicMock(return_value=closed_successful_buy_order)
)
2021-12-19 10:27:17 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
# Should create an closed trade with an no open order id
# Order is filled and trade is open
2023-03-16 06:25:04 +00:00
orders = Order.session.scalars(select(Order)).all()
2021-12-19 10:27:17 +00:00
assert orders
assert len(orders) == 1
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-12-19 10:27:17 +00:00
assert trade
assert trade.is_open is True
assert not trade.has_open_orders
2021-12-19 10:27:17 +00:00
assert trade.open_rate == 11
assert trade.stake_amount == 110
# Assume it does nothing since order is closed and trade is open
freqtrade.update_trades_without_assigned_fees()
2021-12-19 10:27:17 +00:00
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-12-19 10:27:17 +00:00
assert trade
assert trade.is_open is True
assert not trade.has_open_orders
2021-12-19 10:27:17 +00:00
assert trade.open_rate == 11
assert trade.stake_amount == 110
2024-05-12 14:13:11 +00:00
assert not trade.fee_updated("buy")
freqtrade.manage_open_orders()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade
assert trade.is_open is True
assert not trade.has_open_orders
assert trade.open_rate == 11
assert trade.stake_amount == 110
2024-05-12 14:13:11 +00:00
assert not trade.fee_updated("buy")
2021-12-19 10:27:17 +00:00
# First position adjustment buy.
open_dca_order_1 = {
2024-05-12 14:13:11 +00:00
"ft_pair": pair,
"ft_order_side": "buy",
"side": "buy",
"type": "limit",
"status": None,
"price": 9,
"amount": 12,
"cost": 108,
"ft_is_open": True,
"id": "651",
"order_id": "651",
}
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=open_dca_order_1))
mocker.patch(f"{EXMS}.fetch_order_or_stoploss_order", MagicMock(return_value=open_dca_order_1))
2021-12-19 10:27:17 +00:00
assert freqtrade.execute_entry(pair, stake_amount, trade=trade)
2023-03-16 06:25:04 +00:00
orders = Order.session.scalars(select(Order)).all()
2021-12-19 10:27:17 +00:00
assert orders
assert len(orders) == 2
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-12-19 10:27:17 +00:00
assert trade
2024-05-12 14:13:11 +00:00
assert "651" in trade.open_orders_ids
2021-12-19 10:27:17 +00:00
assert trade.open_rate == 11
assert trade.amount == 10
assert trade.stake_amount == 110
2024-05-12 14:13:11 +00:00
assert not trade.fee_updated("buy")
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
assert len(trades) == 1
assert trade.is_open
2024-05-12 14:13:11 +00:00
assert not trade.fee_updated("buy")
order = trade.select_order("buy", False)
assert order
2024-05-12 14:13:11 +00:00
assert order.order_id == "650"
2021-12-19 10:27:17 +00:00
2021-12-26 14:29:10 +00:00
def make_sure_its_651(*args, **kwargs):
2024-05-12 14:13:11 +00:00
if args[0] == "650":
return closed_successful_buy_order
2024-05-12 14:13:11 +00:00
if args[0] == "651":
return open_dca_order_1
return None
# Assume it does nothing since order is still open
fetch_order_mm = MagicMock(side_effect=make_sure_its_651)
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", fetch_order_mm)
mocker.patch(f"{EXMS}.fetch_order", fetch_order_mm)
mocker.patch(f"{EXMS}.fetch_order_or_stoploss_order", fetch_order_mm)
freqtrade.update_trades_without_assigned_fees()
2021-12-19 10:27:17 +00:00
2023-03-16 06:25:04 +00:00
orders = Order.session.scalars(select(Order)).all()
2021-12-19 10:27:17 +00:00
assert orders
assert len(orders) == 2
# Assert that the trade is found as open and without fees
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
assert len(trades) == 1
# Assert trade is as expected
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-12-19 10:27:17 +00:00
assert trade
2024-05-12 14:13:11 +00:00
assert "651" in trade.open_orders_ids
2021-12-19 10:27:17 +00:00
assert trade.open_rate == 11
assert trade.amount == 10
assert trade.stake_amount == 110
2024-05-12 14:13:11 +00:00
assert not trade.fee_updated("buy")
2021-12-19 10:27:17 +00:00
# Make sure the closed order is found as the first order.
2024-05-12 14:13:11 +00:00
order = trade.select_order("buy", False)
assert order.order_id == "650"
2021-12-19 10:27:17 +00:00
# Now close the order so it should update.
closed_dca_order_1 = {
2024-05-12 14:13:11 +00:00
"ft_pair": pair,
"ft_order_side": "buy",
"side": "buy",
"type": "limit",
"status": "closed",
"price": 9,
"average": 9,
"amount": 12,
"filled": 12,
"cost": 108,
"ft_is_open": False,
"id": "651",
"order_id": "651",
"datetime": dt_now().isoformat(),
}
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=closed_dca_order_1))
mocker.patch(f"{EXMS}.fetch_order", MagicMock(return_value=closed_dca_order_1))
mocker.patch(
f"{EXMS}.fetch_order_or_stoploss_order", MagicMock(return_value=closed_dca_order_1)
)
freqtrade.manage_open_orders()
2021-12-19 10:27:17 +00:00
# Assert trade is as expected (averaged dca)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
2021-12-19 10:27:17 +00:00
assert trade
assert not trade.has_open_orders
2021-12-19 10:27:17 +00:00
assert pytest.approx(trade.open_rate) == 9.90909090909
assert trade.amount == 22
assert pytest.approx(trade.stake_amount) == 218
2021-12-19 10:27:17 +00:00
2023-03-16 06:25:04 +00:00
orders = Order.session.scalars(select(Order)).all()
2021-12-19 10:27:17 +00:00
assert orders
assert len(orders) == 2
# Make sure the closed order is found as the second order.
2024-05-12 14:13:11 +00:00
order = trade.select_order("buy", False)
assert order.order_id == "651"
2021-12-19 10:27:17 +00:00
# Assert that the trade is not found as open and without fees
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
assert len(trades) == 1
# Add a second DCA
closed_dca_order_2 = {
2024-05-12 14:13:11 +00:00
"ft_pair": pair,
"status": "closed",
"ft_order_side": "buy",
"side": "buy",
"type": "limit",
"price": 7,
"average": 7,
"amount": 15,
"filled": 15,
"cost": 105,
"ft_is_open": False,
"id": "652",
"order_id": "652",
}
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=closed_dca_order_2))
mocker.patch(f"{EXMS}.fetch_order", MagicMock(return_value=closed_dca_order_2))
mocker.patch(
f"{EXMS}.fetch_order_or_stoploss_order", MagicMock(return_value=closed_dca_order_2)
)
assert freqtrade.execute_entry(pair, stake_amount, trade=trade)
# Assert trade is as expected (averaged dca)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade
assert not trade.has_open_orders
assert pytest.approx(trade.open_rate) == 8.729729729729
assert trade.amount == 37
assert trade.stake_amount == 323
2023-03-16 06:25:04 +00:00
orders = Order.session.scalars(select(Order)).all()
assert orders
assert len(orders) == 3
# Make sure the closed order is found as the second order.
2024-05-12 14:13:11 +00:00
order = trade.select_order("buy", False)
assert order.order_id == "652"
closed_sell_dca_order_1 = {
2024-05-12 14:13:11 +00:00
"ft_pair": pair,
"status": "closed",
"ft_order_side": "sell",
"side": "sell",
"type": "limit",
"price": 8,
"average": 8,
"amount": 15,
"filled": 15,
"cost": 120,
"ft_is_open": False,
"id": "653",
"order_id": "653",
}
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=closed_sell_dca_order_1))
mocker.patch(f"{EXMS}.fetch_order", MagicMock(return_value=closed_sell_dca_order_1))
mocker.patch(
f"{EXMS}.fetch_order_or_stoploss_order", MagicMock(return_value=closed_sell_dca_order_1)
)
assert freqtrade.execute_trade_exit(
trade=trade,
limit=8,
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT),
sub_trade_amt=15,
)
# Assert trade is as expected (averaged dca)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade
assert not trade.has_open_orders
assert trade.is_open
assert trade.amount == 22
assert trade.stake_amount == 192.05405405405406
assert pytest.approx(trade.open_rate) == 8.729729729729
2023-03-16 06:25:04 +00:00
orders = Order.session.scalars(select(Order)).all()
assert orders
assert len(orders) == 4
# Make sure the closed order is found as the second order.
2024-05-12 14:13:11 +00:00
order = trade.select_order("sell", False)
assert order.order_id == "653"
def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
"""
TODO: Should be adjusted to test both long and short
buy 100 @ 11
sell 50 @ 8
sell 50 @ 16
"""
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_wallet(mocker, free=10000)
2024-05-12 14:13:11 +00:00
default_conf_usdt.update(
{
"position_adjustment_enable": True,
"dry_run": False,
"stake_amount": 200.0,
"dry_run_wallet": 1000.0,
}
)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
bid = 11
amount = 100
buy_rate_mock = MagicMock(return_value=bid)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
get_rate=buy_rate_mock,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 10, "ask": 12, "last": 11}),
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
)
2024-05-12 14:13:11 +00:00
pair = "ETH/USDT"
# Initial buy
closed_successful_buy_order = {
2024-05-12 14:13:11 +00:00
"pair": pair,
"ft_pair": pair,
"ft_order_side": "buy",
"side": "buy",
"type": "limit",
"status": "closed",
"price": bid,
"average": bid,
"cost": bid * amount,
"amount": amount,
"filled": amount,
"ft_is_open": False,
"id": "600",
"order_id": "600",
}
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=closed_successful_buy_order))
mocker.patch(
f"{EXMS}.fetch_order_or_stoploss_order", MagicMock(return_value=closed_successful_buy_order)
)
assert freqtrade.execute_entry(pair, amount)
# Should create an closed trade with an no open order id
# Order is filled and trade is open
2023-03-16 06:25:04 +00:00
orders = Order.session.scalars(select(Order)).all()
assert orders
assert len(orders) == 1
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade
assert trade.is_open is True
assert not trade.has_open_orders
assert trade.open_rate == bid
assert trade.stake_amount == bid * amount
# Assume it does nothing since order is closed and trade is open
freqtrade.update_trades_without_assigned_fees()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade
assert trade.is_open is True
assert not trade.has_open_orders
assert trade.open_rate == bid
assert trade.stake_amount == bid * amount
assert not trade.fee_updated(trade.entry_side)
freqtrade.manage_open_orders()
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade
assert trade.is_open is True
assert not trade.has_open_orders
assert trade.open_rate == bid
assert trade.stake_amount == bid * amount
assert not trade.fee_updated(trade.entry_side)
amount = 50
ask = 8
closed_sell_dca_order_1 = {
2024-05-12 14:13:11 +00:00
"ft_pair": pair,
"status": "closed",
"ft_order_side": "sell",
"side": "sell",
"type": "limit",
"price": ask,
"average": ask,
"amount": amount,
"filled": amount,
"cost": amount * ask,
"ft_is_open": False,
"id": "601",
"order_id": "601",
}
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=closed_sell_dca_order_1))
mocker.patch(f"{EXMS}.fetch_order", MagicMock(return_value=closed_sell_dca_order_1))
mocker.patch(
f"{EXMS}.fetch_order_or_stoploss_order", MagicMock(return_value=closed_sell_dca_order_1)
)
assert freqtrade.execute_trade_exit(
trade=trade,
limit=ask,
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT),
sub_trade_amt=amount,
)
trades: List[Trade] = trade.get_open_trades_without_assigned_fees()
assert len(trades) == 1
# Assert trade is as expected (averaged dca)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade
assert not trade.has_open_orders
assert trade.amount == 50
assert trade.open_rate == 11
assert trade.stake_amount == 550
assert pytest.approx(trade.realized_profit) == -152.375
assert pytest.approx(trade.close_profit_abs) == -152.375
2023-03-16 06:25:04 +00:00
orders = Order.session.scalars(select(Order)).all()
assert orders
assert len(orders) == 2
# Make sure the closed order is found as the second order.
2024-05-12 14:13:11 +00:00
order = trade.select_order("sell", False)
assert order.order_id == "601"
amount = 50
ask = 16
closed_sell_dca_order_2 = {
2024-05-12 14:13:11 +00:00
"ft_pair": pair,
"status": "closed",
"ft_order_side": "sell",
"side": "sell",
"type": "limit",
"price": ask,
"average": ask,
"amount": amount,
"filled": amount,
"cost": amount * ask,
"ft_is_open": False,
"id": "602",
"order_id": "602",
}
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=closed_sell_dca_order_2))
mocker.patch(f"{EXMS}.fetch_order", MagicMock(return_value=closed_sell_dca_order_2))
mocker.patch(
f"{EXMS}.fetch_order_or_stoploss_order", MagicMock(return_value=closed_sell_dca_order_2)
)
assert freqtrade.execute_trade_exit(
trade=trade,
limit=ask,
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT),
sub_trade_amt=amount,
)
# Assert trade is as expected (averaged dca)
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade
assert not trade.has_open_orders
assert trade.amount == 50
assert trade.open_rate == 11
assert trade.stake_amount == 550
# Trade fully realized
assert pytest.approx(trade.realized_profit) == 94.25
assert pytest.approx(trade.close_profit_abs) == 94.25
2023-03-16 06:25:04 +00:00
orders = Order.session.scalars(select(Order)).all()
assert orders
assert len(orders) == 3
# Make sure the closed order is found as the second order.
2024-05-12 14:13:11 +00:00
order = trade.select_order("sell", False)
assert order.order_id == "602"
assert trade.is_open is False
2024-05-12 14:13:11 +00:00
@pytest.mark.parametrize(
"data",
[
# tuple 1 - side amount, price
# tuple 2 - amount, open_rate, stake_amount, cumulative_profit, realized_profit, rel_profit
(
(("buy", 100, 10), (100.0, 10.0, 1000.0, 0.0, None, None)),
(("buy", 100, 15), (200.0, 12.5, 2500.0, 0.0, None, None)),
(("sell", 50, 12), (150.0, 12.5, 1875.0, -28.0625, -28.0625, -0.011197)),
(("sell", 100, 20), (50.0, 12.5, 625.0, 713.8125, 741.875, 0.2848129)),
(
("sell", 50, 5),
(50.0, 12.5, 625.0, 336.625, 336.625, 0.1343142),
), # final profit (sum)
),
(
(("buy", 100, 3), (100.0, 3.0, 300.0, 0.0, None, None)),
(("buy", 100, 7), (200.0, 5.0, 1000.0, 0.0, None, None)),
(("sell", 100, 11), (100.0, 5.0, 500.0, 596.0, 596.0, 0.5945137)),
(("buy", 150, 15), (250.0, 11.0, 2750.0, 596.0, 596.0, 0.5945137)),
(("sell", 100, 19), (150.0, 11.0, 1650.0, 1388.5, 792.5, 0.4261653)),
(("sell", 150, 23), (150.0, 11.0, 1650.0, 3175.75, 3175.75, 0.9747170)), # final profit
),
],
)
def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None:
2024-05-12 14:13:11 +00:00
default_conf_usdt.update(
{
"position_adjustment_enable": True,
"dry_run": False,
"stake_amount": 200.0,
"dry_run_wallet": 1000.0,
}
)
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_wallet(mocker, free=10000)
freqtrade = FreqtradeBot(default_conf_usdt)
trade = None
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
for idx, (order, result) in enumerate(data):
amount = order[1]
price = order[2]
price_mock = MagicMock(return_value=price)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
get_rate=price_mock,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 10, "ask": 12, "last": 11}),
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
)
2024-05-12 14:13:11 +00:00
pair = "ETH/USDT"
closed_successful_order = {
2024-05-12 14:13:11 +00:00
"pair": pair,
"ft_pair": pair,
"ft_order_side": order[0],
"side": order[0],
"type": "limit",
"status": "closed",
"price": price,
"average": price,
"cost": price * amount,
"amount": amount,
"filled": amount,
"ft_is_open": False,
"id": f"60{idx}",
"order_id": f"60{idx}",
}
2024-05-12 14:13:11 +00:00
mocker.patch(f"{EXMS}.create_order", MagicMock(return_value=closed_successful_order))
mocker.patch(
f"{EXMS}.fetch_order_or_stoploss_order", MagicMock(return_value=closed_successful_order)
)
if order[0] == "buy":
assert freqtrade.execute_entry(pair, amount, trade=trade)
else:
assert freqtrade.execute_trade_exit(
2024-05-12 14:13:11 +00:00
trade=trade,
limit=price,
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT),
2024-05-12 14:13:11 +00:00
sub_trade_amt=amount,
)
2023-03-16 06:25:04 +00:00
orders1 = Order.session.scalars(select(Order)).all()
assert orders1
assert len(orders1) == idx + 1
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade
if idx < len(data) - 1:
assert trade.is_open is True
assert not trade.has_open_orders
assert trade.amount == result[0]
assert trade.open_rate == result[1]
assert trade.stake_amount == result[2]
assert pytest.approx(trade.realized_profit) == result[3]
assert pytest.approx(trade.close_profit_abs) == result[4]
assert pytest.approx(trade.close_profit) == result[5]
order_obj = trade.select_order(order[0], False)
2024-05-12 14:13:11 +00:00
assert order_obj.order_id == f"60{idx}"
2023-03-16 06:25:04 +00:00
trade = Trade.session.scalars(select(Trade)).first()
assert trade
assert not trade.has_open_orders
assert trade.is_open is False
def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None:
2024-05-12 14:13:11 +00:00
default_conf_usdt.update(
{
"position_adjustment_enable": True,
}
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 14:13:11 +00:00
mocker.patch(
"freqtrade.freqtradebot.FreqtradeBot.check_and_call_adjust_trade_position",
side_effect=DependencyException(),
)
create_mock_trades(fee)
freqtrade.process_open_trade_positions()
assert log_has_re(r"Unable to adjust position of trade for .*", caplog)
def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, caplog) -> None:
2024-05-12 14:13:11 +00:00
default_conf_usdt.update(
{
"position_adjustment_enable": True,
"max_entry_position_adjustment": 0,
}
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
buy_rate_mock = MagicMock(return_value=10)
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
get_rate=buy_rate_mock,
2024-05-12 14:13:11 +00:00
fetch_ticker=MagicMock(return_value={"bid": 10, "ask": 12, "last": 11}),
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
)
create_mock_trades(fee)
caplog.set_level(logging.DEBUG)
2024-05-12 14:13:11 +00:00
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(10, "aaaa"))
freqtrade.process_open_trade_positions()
assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog)
2024-01-28 22:05:01 +00:00
assert freqtrade.strategy.adjust_trade_position.call_count == 1
caplog.clear()
2024-05-12 14:13:11 +00:00
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-0.0005, "partial_exit_c"))
freqtrade.process_open_trade_positions()
assert log_has_re(r"LIMIT_SELL has been fulfilled.*", caplog)
2024-01-28 22:05:01 +00:00
assert freqtrade.strategy.adjust_trade_position.call_count == 1
trade = Trade.get_trades(trade_filter=[Trade.id == 5]).first()
2024-05-12 14:13:11 +00:00
assert trade.orders[-1].ft_order_tag == "partial_exit_c"
2024-03-13 06:07:42 +00:00
assert trade.is_open