freqtrade_origin/tests/rpc/test_rpc.py

1314 lines
45 KiB
Python
Raw Normal View History

from copy import deepcopy
2021-03-01 06:51:33 +00:00
from datetime import datetime, timedelta, timezone
from unittest.mock import ANY, MagicMock, PropertyMock
2018-02-13 03:45:59 +00:00
2018-06-08 02:52:50 +00:00
import pytest
2018-10-10 20:03:54 +00:00
from numpy import isnan
2023-03-16 06:25:04 +00:00
from sqlalchemy import select
2018-06-08 02:52:50 +00:00
from freqtrade.edge import PairInfo
2022-03-03 06:07:33 +00:00
from freqtrade.enums import SignalDirection, State, TradingMode
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
2023-12-23 08:50:42 +00:00
from freqtrade.persistence import Order, Trade
from freqtrade.persistence.key_value_store import set_startup_time
from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
2024-05-12 13:08:40 +00:00
from tests.conftest import (
EXMS,
create_mock_trades,
create_mock_trades_usdt,
get_patched_freqtradebot,
patch_get_signal,
)
2019-10-26 08:08:23 +00:00
def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
gen_response = {
2024-05-12 13:44:42 +00:00
"trade_id": 1,
"pair": "ETH/BTC",
"base_currency": "ETH",
"quote_currency": "BTC",
"open_date": ANY,
"open_timestamp": ANY,
"open_fill_date": ANY,
"open_fill_timestamp": ANY,
"is_open": ANY,
"fee_open": ANY,
"fee_open_cost": ANY,
"fee_open_currency": ANY,
"fee_close": fee.return_value,
"fee_close_cost": ANY,
"fee_close_currency": ANY,
"open_rate_requested": ANY,
"open_trade_value": 0.0010025,
"close_rate_requested": ANY,
"exit_reason": ANY,
"exit_order_status": ANY,
"min_rate": ANY,
"max_rate": ANY,
"strategy": ANY,
"enter_tag": ANY,
"timeframe": 5,
"close_date": None,
"close_timestamp": None,
"open_rate": 1.098e-05,
"close_rate": None,
"current_rate": 1.099e-05,
"amount": 91.07468123,
"amount_requested": 91.07468124,
"stake_amount": 0.001,
"max_stake_amount": None,
"trade_duration": None,
"trade_duration_s": None,
"close_profit": None,
"close_profit_pct": None,
"close_profit_abs": None,
"profit_ratio": -0.00408133,
"profit_pct": -0.41,
"profit_abs": -4.09e-06,
"profit_fiat": ANY,
"stop_loss_abs": 9.89e-06,
"stop_loss_pct": -10.0,
"stop_loss_ratio": -0.1,
"stoploss_last_update": ANY,
"stoploss_last_update_timestamp": ANY,
"initial_stop_loss_abs": 9.89e-06,
"initial_stop_loss_pct": -10.0,
"initial_stop_loss_ratio": -0.1,
"stoploss_current_dist": pytest.approx(-1.0999999e-06),
"stoploss_current_dist_ratio": -0.10009099,
"stoploss_current_dist_pct": -10.01,
"stoploss_entry_dist": -0.00010402,
"stoploss_entry_dist_ratio": -0.10376381,
"open_orders": "",
"realized_profit": 0.0,
"realized_profit_ratio": None,
"total_profit_abs": -4.09e-06,
"total_profit_fiat": ANY,
"total_profit_ratio": None,
"exchange": "binance",
"leverage": 1.0,
"interest_rate": 0.0,
"liquidation_price": None,
"is_short": False,
"funding_fees": 0.0,
"trading_mode": TradingMode.SPOT,
"amount_precision": 8.0,
"price_precision": 8.0,
"precision_mode": 2,
"precision_mode_price": 2,
2024-05-12 13:44:42 +00:00
"contract_size": 1,
"has_open_orders": False,
"orders": [
{
"amount": 91.07468123,
"average": 1.098e-05,
"safe_price": 1.098e-05,
"cost": 0.0009999999999054,
"filled": 91.07468123,
"ft_order_side": "buy",
"order_date": ANY,
"order_timestamp": ANY,
"order_filled_date": ANY,
"order_filled_timestamp": ANY,
"order_type": "limit",
"price": 1.098e-05,
"is_open": False,
"pair": "ETH/BTC",
"order_id": ANY,
"remaining": ANY,
"status": ANY,
"ft_is_entry": True,
"ft_fee_base": None,
"funding_fee": ANY,
"ft_order_tag": None,
}
],
2020-06-04 05:20:50 +00:00
}
2024-04-27 17:52:48 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
fetch_ticker=ticker,
get_fee=fee,
2023-02-16 18:47:12 +00:00
_dry_is_price_crossed=MagicMock(side_effect=[False, True]),
)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r".*no active trade*"):
rpc._rpc_trade_status()
freqtradebot.enter_positions()
# Open order...
results = rpc._rpc_trade_status()
response_unfilled = deepcopy(gen_response)
# Different from "filled" response:
2024-05-12 13:44:42 +00:00
response_unfilled.update(
{
2024-09-21 14:56:20 +00:00
"amount": 0.0,
"open_trade_value": 0.0,
"stoploss_entry_dist": 0.0,
"stoploss_entry_dist_ratio": 0.0,
2024-05-12 13:44:42 +00:00
"profit_ratio": 0.0,
"profit_pct": 0.0,
"profit_abs": 0.0,
"total_profit_abs": 0.0,
"open_orders": "(limit buy rem=91.07468123)",
"has_open_orders": True,
}
)
response_unfilled["orders"][0].update(
{"is_open": True, "filled": 0.0, "remaining": 91.07468123}
)
assert results[0] == response_unfilled
# Open order without remaining
trade = Trade.get_open_trades()[0]
# kucoin case (no remaining set).
trade.orders[0].remaining = None
Trade.commit()
results = rpc._rpc_trade_status()
# Reuse above object, only remaining changed.
2024-05-12 13:44:42 +00:00
response_unfilled["orders"][0].update(
{
"remaining": None,
}
)
assert results[0] == response_unfilled
trade = Trade.get_open_trades()[0]
trade.orders[0].remaining = trade.amount
Trade.commit()
# Fill open order ...
freqtradebot.manage_open_orders()
trades = Trade.get_open_trades()
freqtradebot.exit_positions(trades)
results = rpc._rpc_trade_status()
response = deepcopy(gen_response)
2024-05-12 13:44:42 +00:00
response.update(
{
"max_stake_amount": 0.001,
"total_profit_ratio": pytest.approx(-0.00409153),
"has_open_orders": False,
}
)
assert results[0] == response
2024-05-12 13:44:42 +00:00
mocker.patch(
f"{EXMS}.get_rate", MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))
)
results = rpc._rpc_trade_status()
2024-05-12 13:44:42 +00:00
assert isnan(results[0]["profit_ratio"])
assert isnan(results[0]["current_rate"])
response_norate = deepcopy(gen_response)
# Update elements that are NaN when no rate is available.
2024-05-12 13:44:42 +00:00
response_norate.update(
{
"stoploss_current_dist": ANY,
"stoploss_current_dist_ratio": ANY,
"stoploss_current_dist_pct": ANY,
"max_stake_amount": 0.001,
"profit_ratio": ANY,
"profit_pct": ANY,
"profit_abs": ANY,
"total_profit_abs": ANY,
"total_profit_ratio": ANY,
"current_rate": ANY,
}
)
assert results[0] == response_norate
2019-10-26 08:08:23 +00:00
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
2019-11-12 13:58:41 +00:00
mocker.patch.multiple(
2024-05-20 12:15:53 +00:00
"freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
2024-05-12 13:44:42 +00:00
get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}),
2019-11-12 13:58:41 +00:00
)
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0)
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
2024-05-12 13:44:42 +00:00
del default_conf["fiat_display_currency"]
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r".*no active trade*"):
rpc._rpc_status_table(default_conf["stake_currency"], "USD")
mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False)
freqtradebot.enter_positions()
2019-11-12 13:58:41 +00:00
2024-05-12 13:44:42 +00:00
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD")
2022-05-08 14:04:06 +00:00
assert "Since" in headers
assert "Pair" in headers
2024-05-12 13:44:42 +00:00
assert "now" == result[0][2]
assert "ETH/BTC" in result[0][1]
assert "0.00 (0.00)" == result[0][3]
assert "0.00" == f"{fiat_profit_sum:.2f}"
2022-05-08 14:04:06 +00:00
2024-05-12 13:44:42 +00:00
mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=True)
2022-05-08 14:04:06 +00:00
freqtradebot.process()
2024-05-12 13:44:42 +00:00
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD")
2019-11-12 13:58:41 +00:00
assert "Since" in headers
assert "Pair" in headers
2024-05-12 13:44:42 +00:00
assert "now" == result[0][2]
assert "ETH/BTC" in result[0][1]
assert "-0.41% (-0.00)" == result[0][3]
assert "-0.00" == f"{fiat_profit_sum:.2f}"
2019-11-12 13:58:41 +00:00
# Test with fiat convert
2024-05-20 13:29:22 +00:00
rpc._fiat_converter = CryptoToFiatConverter({})
2024-05-12 13:44:42 +00:00
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD")
2019-11-12 12:54:26 +00:00
assert "Since" in headers
assert "Pair" in headers
2022-01-21 06:04:25 +00:00
assert len(result[0]) == 4
2024-05-12 13:44:42 +00:00
assert "now" == result[0][2]
assert "ETH/BTC" in result[0][1]
assert "-0.41% (-0.06)" == result[0][3]
assert "-0.06" == f"{fiat_profit_sum:.2f}"
rpc._config["position_adjustment_enable"] = True
rpc._config["max_entry_position_adjustment"] = 3
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD")
2022-02-07 07:31:35 +00:00
assert "# Entries" in headers
2022-01-21 06:04:25 +00:00
assert len(result[0]) == 5
# 4th column should be 1/4 - as 1 order filled (a total of 4 is possible)
# 3 on top of the initial one.
2024-05-12 13:44:42 +00:00
assert result[0][4] == "1/4"
mocker.patch(
f"{EXMS}.get_rate", MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))
)
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD")
assert "now" == result[0][2]
assert "ETH/BTC" in result[0][1]
assert "nan%" == result[0][3]
assert isnan(fiat_profit_sum)
def test__rpc_timeunit_profit(
2024-05-12 13:44:42 +00:00
default_conf_usdt, ticker, fee, markets, mocker, time_machine
) -> None:
time_machine.move_to("2023-09-05 10:00:00 +00:00", tick=False)
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2024-05-12 13:44:42 +00:00
EXMS, fetch_ticker=ticker, get_fee=fee, markets=PropertyMock(return_value=markets)
2018-02-13 03:45:59 +00:00
)
2022-06-11 06:58:36 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
create_mock_trades_usdt(fee)
2024-05-12 13:44:42 +00:00
stake_currency = default_conf_usdt["stake_currency"]
fiat_display_currency = default_conf_usdt["fiat_display_currency"]
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
2024-05-20 13:29:22 +00:00
rpc._fiat_converter = CryptoToFiatConverter({})
# Try valid data
2022-06-09 04:45:22 +00:00
days = rpc._rpc_timeunit_profit(7, stake_currency, fiat_display_currency)
2024-05-12 13:44:42 +00:00
assert len(days["data"]) == 7
assert days["stake_currency"] == default_conf_usdt["stake_currency"]
assert days["fiat_display_currency"] == default_conf_usdt["fiat_display_currency"]
for day in days["data"]:
2022-06-11 06:58:36 +00:00
# {'date': datetime.date(2022, 6, 11), 'abs_profit': 13.8299999,
# 'starting_balance': 1055.37, 'rel_profit': 0.0131044,
2022-06-11 06:58:36 +00:00
# 'fiat_value': 0.0, 'trade_count': 2}
2024-05-12 13:44:42 +00:00
assert day["abs_profit"] in (0.0, pytest.approx(6.83), pytest.approx(-4.09))
assert day["rel_profit"] in (0.0, pytest.approx(0.00642902), pytest.approx(-0.00383512))
assert day["trade_count"] in (0, 1, 2)
assert day["starting_balance"] in (pytest.approx(1062.37), pytest.approx(1066.46))
assert day["fiat_value"] in (0.0,)
# ensure first day is current date
2024-05-12 13:44:42 +00:00
assert str(days["data"][0]["date"]) == str(datetime.now(timezone.utc).date())
# Try invalid data
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r".*must be an integer greater than 0*"):
2022-06-09 04:45:22 +00:00
rpc._rpc_timeunit_profit(0, stake_currency, fiat_display_currency)
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize("is_short", [True, False])
2021-09-20 02:24:22 +00:00
def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short):
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets))
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2021-09-20 02:24:22 +00:00
create_mock_trades(fee, is_short)
rpc = RPC(freqtradebot)
2024-05-20 13:29:22 +00:00
rpc._fiat_converter = CryptoToFiatConverter({})
trades = rpc._rpc_trade_history(2)
2024-05-12 13:44:42 +00:00
assert len(trades["trades"]) == 2
assert trades["trades_count"] == 2
assert isinstance(trades["trades"][0], dict)
assert isinstance(trades["trades"][1], dict)
trades = rpc._rpc_trade_history(0)
2024-05-12 13:44:42 +00:00
assert len(trades["trades"]) == 2
assert trades["trades_count"] == 2
# The first closed trade is for ETC ... sorting is descending
2024-05-12 13:44:42 +00:00
assert trades["trades"][-1]["pair"] == "ETC/BTC"
assert trades["trades"][0]["pair"] == "XRP/BTC"
2024-05-12 13:44:42 +00:00
@pytest.mark.parametrize("is_short", [True, False])
2021-09-20 02:24:22 +00:00
def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
markets=PropertyMock(return_value=markets),
cancel_order=cancel_mock,
cancel_stoploss_order=stoploss_mock,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2024-05-12 13:44:42 +00:00
freqtradebot.strategy.order_types["stoploss_on_exchange"] = True
2021-09-20 02:24:22 +00:00
create_mock_trades(fee, is_short)
rpc = RPC(freqtradebot)
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match="invalid argument"):
rpc._rpc_delete("200")
2023-03-16 06:25:04 +00:00
trades = Trade.session.scalars(select(Trade)).all()
2023-12-23 08:50:42 +00:00
trades[2].orders.append(
Order(
2024-05-12 13:44:42 +00:00
ft_order_side="stoploss",
2023-12-23 08:50:42 +00:00
ft_pair=trades[2].pair,
ft_is_open=True,
ft_amount=trades[2].amount,
ft_price=trades[2].stop_loss,
2024-05-12 13:44:42 +00:00
order_id="102",
status="open",
2023-12-23 08:50:42 +00:00
)
)
assert len(trades) > 2
2024-05-12 13:44:42 +00:00
res = rpc._rpc_delete("1")
assert isinstance(res, dict)
2024-05-12 13:44:42 +00:00
assert res["result"] == "success"
assert res["trade_id"] == "1"
assert res["cancel_order_count"] == 1
assert cancel_mock.call_count == 1
assert stoploss_mock.call_count == 0
cancel_mock.reset_mock()
stoploss_mock.reset_mock()
2024-05-12 13:44:42 +00:00
res = rpc._rpc_delete("5")
assert isinstance(res, dict)
assert stoploss_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert res["cancel_order_count"] == 1
2024-05-12 13:44:42 +00:00
stoploss_mock = mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=InvalidOrderException)
2024-05-12 13:44:42 +00:00
res = rpc._rpc_delete("3")
assert stoploss_mock.call_count == 1
stoploss_mock.reset_mock()
2024-05-12 13:44:42 +00:00
cancel_mock = mocker.patch(f"{EXMS}.cancel_order", side_effect=InvalidOrderException)
2024-05-12 13:44:42 +00:00
res = rpc._rpc_delete("4")
assert cancel_mock.call_count == 1
assert stoploss_mock.call_count == 0
def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.1)
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
2022-06-13 04:46:34 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
2024-05-12 13:44:42 +00:00
stake_currency = default_conf_usdt["stake_currency"]
fiat_display_currency = default_conf_usdt["fiat_display_currency"]
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
2024-05-20 13:29:22 +00:00
rpc._fiat_converter = CryptoToFiatConverter({})
2018-02-13 03:45:59 +00:00
res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
2024-05-12 13:44:42 +00:00
assert res["trade_count"] == 0
assert res["first_trade_date"] == ""
assert res["first_trade_timestamp"] == 0
assert res["latest_trade_date"] == ""
assert res["latest_trade_timestamp"] == 0
assert res["expectancy"] == 0
assert res["expectancy_ratio"] == 100
# Create some test data
2022-06-13 04:46:34 +00:00
create_mock_trades_usdt(fee)
2018-06-08 02:52:50 +00:00
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
2024-05-12 13:44:42 +00:00
assert pytest.approx(stats["profit_closed_coin"]) == 2.74
assert pytest.approx(stats["profit_closed_percent_mean"]) == -1.67
assert pytest.approx(stats["profit_closed_fiat"]) == 3.014
assert pytest.approx(stats["profit_all_coin"]) == -57.40975881
assert pytest.approx(stats["profit_all_percent_mean"]) == -50.83
assert pytest.approx(stats["profit_all_fiat"]) == -63.150734691
assert pytest.approx(stats["winrate"]) == 0.666666667
assert pytest.approx(stats["expectancy"]) == 0.913333333
assert pytest.approx(stats["expectancy_ratio"]) == 0.223308883
assert stats["trade_count"] == 7
assert stats["first_trade_humanized"] == "2 days ago"
assert stats["latest_trade_humanized"] == "17 minutes ago"
assert stats["avg_duration"] in ("0:17:40")
assert stats["best_pair"] == "XRP/USDT"
assert stats["best_rate"] == 10.0
# Test non-available pair
2024-05-12 13:44:42 +00:00
mocker.patch(
f"{EXMS}.get_rate", MagicMock(side_effect=ExchangeError("Pair 'XRP/USDT' not available"))
)
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
2024-05-12 13:44:42 +00:00
assert stats["trade_count"] == 7
assert stats["first_trade_humanized"] == "2 days ago"
assert stats["latest_trade_humanized"] == "17 minutes ago"
assert stats["avg_duration"] in ("0:17:40")
assert stats["best_pair"] == "XRP/USDT"
assert stats["best_rate"] == 10.0
assert isnan(stats["profit_all_coin"])
2018-10-10 20:03:54 +00:00
2019-07-03 18:07:26 +00:00
def test_rpc_balance_handle_error(default_conf, mocker):
2018-05-14 21:31:56 +00:00
mock_balance = {
2024-05-12 13:44:42 +00:00
"BTC": {
"free": 10.0,
"total": 12.0,
"used": 2.0,
},
"ETH": {
"free": 1.0,
"total": 5.0,
"used": 4.0,
2018-02-13 03:45:59 +00:00
},
2018-05-14 21:31:56 +00:00
}
2018-08-08 19:55:48 +00:00
# ETH will be skipped due to mocked Error below
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2024-05-20 12:15:53 +00:00
"freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
2024-05-12 13:44:42 +00:00
get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}),
2018-02-13 03:45:59 +00:00
)
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0)
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2018-08-08 19:55:48 +00:00
get_balances=MagicMock(return_value=mock_balance),
2024-05-12 13:44:42 +00:00
get_tickers=MagicMock(side_effect=TemporaryError("Could not load ticker due to xxx")),
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
2024-05-20 13:29:22 +00:00
rpc._fiat_converter = CryptoToFiatConverter({})
2019-11-14 19:12:41 +00:00
with pytest.raises(RPCException, match="Error getting current tickers."):
2024-05-12 13:44:42 +00:00
rpc._rpc_balance(default_conf["stake_currency"], default_conf["fiat_display_currency"])
2018-08-08 19:54:52 +00:00
2024-03-24 10:28:24 +00:00
def test_rpc_balance_handle(default_conf_usdt, mocker, tickers):
2019-07-03 18:07:26 +00:00
mock_balance = {
2024-05-12 13:44:42 +00:00
"BTC": {
"free": 0.01,
"total": 0.012,
"used": 0.002,
2019-07-03 18:07:26 +00:00
},
2024-05-12 13:44:42 +00:00
"ETH": {
"free": 1.0,
"total": 5.0,
"used": 4.0,
},
"USDT": {
"free": 50.0,
"total": 100.0,
"used": 5.0,
2019-07-03 18:07:26 +00:00
},
}
2022-02-23 06:40:15 +00:00
mock_pos = [
{
"symbol": "ETH/USDT:USDT",
"timestamp": None,
"datetime": None,
"initialMargin": 0.0,
"initialMarginPercentage": None,
"maintenanceMargin": 0.0,
"maintenanceMarginPercentage": 0.005,
"entryPrice": 0.0,
"notional": 10.0,
2022-02-23 06:40:15 +00:00
"leverage": 5.0,
"unrealizedPnl": 0.0,
"contracts": 1.0,
2022-02-23 06:40:15 +00:00
"contractSize": 1,
"marginRatio": None,
"liquidationPrice": 0.0,
"markPrice": 2896.41,
"collateral": 20,
"marginType": "isolated",
2024-05-12 13:44:42 +00:00
"side": "short",
"percentage": None,
2022-02-23 06:40:15 +00:00
}
]
2019-07-03 18:07:26 +00:00
mocker.patch.multiple(
2024-05-20 12:15:53 +00:00
"freqtrade.rpc.fiat_convert.FtCoinGeckoApi",
2024-05-12 13:44:42 +00:00
get_price=MagicMock(return_value={"bitcoin": {"usd": 1.2}}),
2019-07-03 18:07:26 +00:00
)
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.2)
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2019-07-03 18:07:26 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2022-02-23 06:40:15 +00:00
validate_trading_mode_and_margin_mode=MagicMock(),
2019-07-03 18:07:26 +00:00
get_balances=MagicMock(return_value=mock_balance),
fetch_positions=MagicMock(return_value=mock_pos),
2019-11-14 19:12:41 +00:00
get_tickers=tickers,
2019-07-03 18:07:26 +00:00
get_valid_pair_combination=MagicMock(
2024-05-12 13:44:42 +00:00
side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}"
),
2019-07-03 18:07:26 +00:00
)
2024-05-12 13:44:42 +00:00
default_conf_usdt["dry_run"] = False
default_conf_usdt["trading_mode"] = "futures"
2024-03-24 10:28:24 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
patch_get_signal(freqtradebot)
2019-07-03 18:07:26 +00:00
rpc = RPC(freqtradebot)
2024-05-20 13:29:22 +00:00
rpc._fiat_converter = CryptoToFiatConverter({})
2019-07-03 18:07:26 +00:00
2024-03-24 10:28:24 +00:00
result = rpc._rpc_balance(
2024-05-12 13:44:42 +00:00
default_conf_usdt["stake_currency"], default_conf_usdt["fiat_display_currency"]
)
2024-03-24 10:28:24 +00:00
2024-05-12 13:44:42 +00:00
assert pytest.approx(result["total"]) == 2824.83464
assert pytest.approx(result["value"]) == 2824.83464 * 1.2
assert tickers.call_count == 1
2024-05-12 13:44:42 +00:00
assert tickers.call_args_list[0][1]["cached"] is True
assert "USD" == result["symbol"]
assert result["currencies"] == [
2022-02-23 06:40:15 +00:00
{
2024-05-12 13:44:42 +00:00
"currency": "BTC",
"free": 0.01,
"balance": 0.012,
"used": 0.002,
"bot_owned": 0,
"est_stake": 103.78464,
"est_stake_bot": 0,
"stake": "USDT",
"side": "long",
"position": 0,
"is_bot_managed": False,
"is_position": False,
2022-02-23 06:40:15 +00:00
},
{
2024-05-12 13:44:42 +00:00
"currency": "ETH",
"free": 1.0,
"balance": 5.0,
"used": 4.0,
"bot_owned": 0,
"est_stake": 2651.05,
"est_stake_bot": 0,
"stake": "USDT",
"side": "long",
"position": 0,
"is_bot_managed": False,
"is_position": False,
2022-02-23 06:40:15 +00:00
},
{
2024-05-12 13:44:42 +00:00
"currency": "USDT",
"free": 50.0,
"balance": 100.0,
"used": 5.0,
"bot_owned": 49.5,
"est_stake": 50.0,
"est_stake_bot": 49.5,
"stake": "USDT",
"side": "long",
"position": 0,
"is_bot_managed": True,
"is_position": False,
2022-02-23 06:40:15 +00:00
},
{
2024-05-12 13:44:42 +00:00
"currency": "ETH/USDT:USDT",
"free": 0,
"balance": 0,
"used": 0,
"position": 10.0,
"est_stake": 20,
"est_stake_bot": 20,
"stake": "USDT",
"side": "short",
"is_bot_managed": True,
"is_position": True,
},
2019-07-03 18:07:26 +00:00
]
2024-05-12 13:44:42 +00:00
assert pytest.approx(result["total_bot"]) == 69.5
assert pytest.approx(result["total"]) == 2824.83464 # ETH stake is missing.
assert result["starting_capital"] == 50
assert result["starting_capital_ratio"] == pytest.approx(0.3899999)
2019-07-03 18:07:26 +00:00
2018-02-13 03:45:59 +00:00
def test_rpc_start(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(EXMS, fetch_ticker=MagicMock())
2018-02-13 03:45:59 +00:00
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
2018-02-13 03:45:59 +00:00
2018-06-08 02:52:50 +00:00
result = rpc._rpc_start()
2024-05-12 13:44:42 +00:00
assert {"status": "starting trader ..."} == result
assert freqtradebot.state == State.RUNNING
2018-02-13 03:45:59 +00:00
2018-06-08 02:52:50 +00:00
result = rpc._rpc_start()
2024-05-12 13:44:42 +00:00
assert {"status": "already running"} == result
assert freqtradebot.state == State.RUNNING
2018-02-13 03:45:59 +00:00
def test_rpc_stop(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(EXMS, fetch_ticker=MagicMock())
2018-02-13 03:45:59 +00:00
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING
2018-02-13 03:45:59 +00:00
2018-06-08 02:52:50 +00:00
result = rpc._rpc_stop()
2024-05-12 13:44:42 +00:00
assert {"status": "stopping trader ..."} == result
assert freqtradebot.state == State.STOPPED
2018-02-13 03:45:59 +00:00
2018-06-08 02:52:50 +00:00
result = rpc._rpc_stop()
2024-05-12 13:44:42 +00:00
assert {"status": "already stopped"} == result
assert freqtradebot.state == State.STOPPED
2018-02-13 03:45:59 +00:00
2022-08-28 09:32:53 +00:00
def test_rpc_stopentry(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(EXMS, fetch_ticker=MagicMock())
2019-03-17 18:36:02 +00:00
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
2019-03-17 18:36:02 +00:00
rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING
2024-05-12 13:44:42 +00:00
assert freqtradebot.config["max_open_trades"] != 0
2022-08-28 09:32:53 +00:00
result = rpc._rpc_stopentry()
2024-05-12 13:44:42 +00:00
assert {"status": "No more entries will occur from now. Run /reload_config to reset."} == result
assert freqtradebot.config["max_open_trades"] == 0
2019-03-17 18:36:02 +00:00
2022-04-05 11:28:30 +00:00
def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-02-13 03:45:59 +00:00
cancel_order_mock = MagicMock()
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-02-13 03:45:59 +00:00
cancel_order=cancel_order_mock,
fetch_order=MagicMock(
2018-02-13 03:45:59 +00:00
return_value={
2024-05-12 13:44:42 +00:00
"status": "closed",
"type": "limit",
"side": "buy",
"filled": 0.0,
2018-02-13 03:45:59 +00:00
}
2018-04-21 17:39:18 +00:00
),
2023-02-16 18:47:12 +00:00
_dry_is_price_crossed=MagicMock(return_value=True),
2018-04-21 17:39:18 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=1000)
2018-02-13 03:45:59 +00:00
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r".*trader is not running*"):
2022-04-05 11:28:30 +00:00
rpc._rpc_force_exit(None)
2018-02-13 03:45:59 +00:00
freqtradebot.state = State.RUNNING
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r".*invalid argument*"):
2022-04-05 11:28:30 +00:00
rpc._rpc_force_exit(None)
2018-02-13 03:45:59 +00:00
2024-05-12 13:44:42 +00:00
msg = rpc._rpc_force_exit("all")
assert msg == {"result": "Created exit orders for all open trades."}
2018-02-13 03:45:59 +00:00
freqtradebot.enter_positions()
2024-05-12 13:44:42 +00:00
msg = rpc._rpc_force_exit("all")
assert msg == {"result": "Created exit orders for all open trades."}
2018-02-13 03:45:59 +00:00
2020-08-26 19:37:52 +00:00
freqtradebot.enter_positions()
2024-05-12 13:44:42 +00:00
msg = rpc._rpc_force_exit("2")
assert msg == {"result": "Created exit order for trade 2."}
2018-02-13 03:45:59 +00:00
freqtradebot.state = State.STOPPED
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r".*trader is not running*"):
2022-04-05 11:28:30 +00:00
rpc._rpc_force_exit(None)
2018-02-13 03:45:59 +00:00
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r".*trader is not running*"):
rpc._rpc_force_exit("all")
2018-02-13 03:45:59 +00:00
freqtradebot.state = State.RUNNING
2018-02-13 03:45:59 +00:00
assert cancel_order_mock.call_count == 0
2024-05-12 13:44:42 +00:00
mocker.patch(f"{EXMS}._dry_is_price_crossed", MagicMock(return_value=False))
2020-08-26 19:37:52 +00:00
freqtradebot.enter_positions()
2018-02-13 03:45:59 +00:00
# make an limit-buy open trade
2024-05-12 13:44:42 +00:00
trade = Trade.session.scalars(select(Trade).filter(Trade.id == "3")).first()
filled_amount = trade.amount_requested / 2
2020-08-26 19:37:52 +00:00
# Fetch order - it's open first, and closed after cancel_order is called.
2018-02-13 03:45:59 +00:00
mocker.patch(
2024-05-12 13:44:42 +00:00
f"{EXMS}.fetch_order",
side_effect=[
{
"id": trade.orders[0].order_id,
"status": "open",
"type": "limit",
"side": "buy",
"filled": filled_amount,
},
{
"id": trade.orders[0].order_id,
"status": "closed",
"type": "limit",
"side": "buy",
"filled": filled_amount,
},
],
2018-02-13 03:45:59 +00:00
)
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
# and trade amount is updated
2024-05-12 13:44:42 +00:00
rpc._rpc_force_exit("3")
2018-02-13 03:45:59 +00:00
assert cancel_order_mock.call_count == 1
2022-08-15 18:29:05 +00:00
assert pytest.approx(trade.amount) == filled_amount
2020-08-26 19:37:52 +00:00
mocker.patch(
2024-05-12 13:44:42 +00:00
f"{EXMS}.fetch_order",
return_value={"status": "open", "type": "limit", "side": "buy", "filled": filled_amount},
)
2020-08-26 19:37:52 +00:00
2024-05-12 13:44:42 +00:00
freqtradebot.config["max_open_trades"] = 3
freqtradebot.enter_positions()
2023-05-31 12:59:41 +00:00
cancel_order_mock.reset_mock()
2024-05-12 13:44:42 +00:00
trade = Trade.session.scalars(select(Trade).filter(Trade.id == "3")).first()
amount = trade.amount_requested
# make an limit-sell open order trade
mocker.patch(
2024-05-12 13:44:42 +00:00
f"{EXMS}.fetch_order",
return_value={
2024-05-12 13:44:42 +00:00
"status": "open",
"type": "limit",
"side": "sell",
"amount": amount,
"remaining": amount,
"filled": 0.0,
"id": trade.orders[-1].order_id,
},
)
cancel_order_3 = mocker.patch(
2024-05-12 13:44:42 +00:00
f"{EXMS}.cancel_order_with_result",
return_value={
2024-05-12 13:44:42 +00:00
"status": "canceled",
"type": "limit",
"side": "sell",
"amount": amount,
"remaining": amount,
"filled": 0.0,
"id": trade.orders[-1].order_id,
},
)
2024-05-12 13:44:42 +00:00
msg = rpc._rpc_force_exit("3")
assert msg == {"result": "Created exit order for trade 3."}
# status quo, no exchange calls
assert cancel_order_3.call_count == 1
assert cancel_order_mock.call_count == 0
2018-02-13 03:45:59 +00:00
2024-05-12 13:44:42 +00:00
trade = Trade.session.scalars(select(Trade).filter(Trade.id == "4")).first()
amount = trade.amount_requested
# make an limit-buy open trade, if there is no 'filled', don't sell it
2018-02-13 03:45:59 +00:00
mocker.patch(
2024-05-12 13:44:42 +00:00
f"{EXMS}.fetch_order",
return_value={"status": "open", "type": "limit", "side": "buy", "filled": None},
2018-02-13 03:45:59 +00:00
)
cancel_order_4 = mocker.patch(
2024-05-12 13:44:42 +00:00
f"{EXMS}.cancel_order_with_result",
return_value={
"status": "canceled",
"type": "limit",
"side": "sell",
"amount": amount,
"remaining": 0.0,
"filled": amount,
"id": trade.orders[0].order_id,
},
)
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
2024-05-12 13:44:42 +00:00
msg = rpc._rpc_force_exit("4")
assert msg == {"result": "Created exit order for trade 4."}
assert cancel_order_4.call_count == 1
assert cancel_order_mock.call_count == 0
assert pytest.approx(trade.amount) == amount
2018-02-13 03:45:59 +00:00
def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2018-02-13 03:45:59 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
patch_get_signal(freqtradebot)
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
# Create some test data
create_mock_trades_usdt(fee)
2018-06-08 02:52:50 +00:00
res = rpc._rpc_performance()
assert len(res) == 3
2024-05-12 13:44:42 +00:00
assert res[0]["pair"] == "NEO/USDT"
assert res[0]["count"] == 1
assert res[0]["profit_pct"] == 5.0
def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
get_balances=MagicMock(return_value=ticker),
fetch_ticker=ticker,
get_fee=fee,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
# Create some test data
create_mock_trades_usdt(fee)
freqtradebot.enter_positions()
2021-11-21 08:51:16 +00:00
res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 3
2024-05-12 13:44:42 +00:00
assert res[0]["enter_tag"] == "TEST1"
assert res[0]["count"] == 1
assert res[0]["profit_pct"] == 5.0
2021-11-21 08:51:16 +00:00
res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 3
2024-05-12 13:44:42 +00:00
assert res[0]["enter_tag"] == "TEST1"
assert res[0]["count"] == 1
assert res[0]["profit_pct"] == 5.0
2021-11-21 08:51:16 +00:00
def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets))
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
create_mock_trades(fee)
rpc = RPC(freqtradebot)
2021-11-21 08:51:16 +00:00
res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 2
2024-05-12 13:44:42 +00:00
assert res[0]["enter_tag"] == "TEST1"
assert res[0]["count"] == 1
assert pytest.approx(res[0]["profit_pct"]) == 0.5
assert res[1]["enter_tag"] == "Other"
assert res[1]["count"] == 1
assert pytest.approx(res[1]["profit_pct"]) == 1.0
# Test for a specific pair
2024-05-12 13:44:42 +00:00
res = rpc._rpc_enter_tag_performance("ETC/BTC")
assert len(res) == 1
2024-05-12 13:44:42 +00:00
assert res[0]["count"] == 1
assert res[0]["enter_tag"] == "TEST1"
assert pytest.approx(res[0]["profit_pct"]) == 0.5
def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
get_balances=MagicMock(return_value=ticker),
fetch_ticker=ticker,
get_fee=fee,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
# Create some test data
create_mock_trades_usdt(fee)
2022-04-03 08:39:35 +00:00
res = rpc._rpc_exit_reason_performance(None)
assert len(res) == 3
2024-05-12 13:44:42 +00:00
assert res[0]["exit_reason"] == "exit_signal"
assert res[0]["count"] == 1
assert res[0]["profit_pct"] == 5.0
2024-05-12 13:44:42 +00:00
assert res[1]["exit_reason"] == "roi"
assert res[2]["exit_reason"] == "Other"
2022-04-03 08:39:35 +00:00
def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee):
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets))
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
create_mock_trades(fee)
rpc = RPC(freqtradebot)
2022-04-03 08:39:35 +00:00
res = rpc._rpc_exit_reason_performance(None)
assert len(res) == 2
2024-05-12 13:44:42 +00:00
assert res[0]["exit_reason"] == "sell_signal"
assert res[0]["count"] == 1
assert pytest.approx(res[0]["profit_pct"]) == 0.5
assert res[1]["exit_reason"] == "roi"
assert res[1]["count"] == 1
assert pytest.approx(res[1]["profit_pct"]) == 1.0
# Test for a specific pair
2024-05-12 13:44:42 +00:00
res = rpc._rpc_exit_reason_performance("ETC/BTC")
assert len(res) == 1
2024-05-12 13:44:42 +00:00
assert res[0]["count"] == 1
assert res[0]["exit_reason"] == "sell_signal"
assert pytest.approx(res[0]["profit_pct"]) == 0.5
def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
get_balances=MagicMock(return_value=ticker),
fetch_ticker=ticker,
get_fee=fee,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
# Create some test data
create_mock_trades_usdt(fee)
res = rpc._rpc_mix_tag_performance(None)
assert len(res) == 3
2024-05-12 13:44:42 +00:00
assert res[0]["mix_tag"] == "TEST1 exit_signal"
assert res[0]["count"] == 1
assert res[0]["profit_pct"] == 5.0
2018-02-13 03:45:59 +00:00
def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee):
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets))
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
create_mock_trades(fee)
rpc = RPC(freqtradebot)
res = rpc._rpc_mix_tag_performance(None)
assert len(res) == 2
2024-05-12 13:44:42 +00:00
assert res[0]["mix_tag"] == "TEST1 sell_signal"
assert res[0]["count"] == 1
assert pytest.approx(res[0]["profit_pct"]) == 0.5
assert res[1]["mix_tag"] == "Other roi"
assert res[1]["count"] == 1
assert pytest.approx(res[1]["profit_pct"]) == 1.0
# Test for a specific pair
2024-05-12 13:44:42 +00:00
res = rpc._rpc_mix_tag_performance("ETC/BTC")
assert len(res) == 1
2024-05-12 13:44:42 +00:00
assert res[0]["count"] == 1
assert res[0]["mix_tag"] == "TEST1 sell_signal"
assert pytest.approx(res[0]["profit_pct"]) == 0.5
2018-02-13 03:45:59 +00:00
2019-10-26 08:08:23 +00:00
def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2018-02-13 03:45:59 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-04-21 17:39:18 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
2019-04-06 18:01:29 +00:00
counts = rpc._rpc_count()
assert counts["current"] == 0
2018-02-13 03:45:59 +00:00
# Create some test data
freqtradebot.enter_positions()
2019-04-06 18:01:29 +00:00
counts = rpc._rpc_count()
assert counts["current"] == 1
2018-10-09 18:04:53 +00:00
2022-04-05 11:28:30 +00:00
def test_rpc_force_entry(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None:
2024-05-12 13:44:42 +00:00
default_conf["force_entry_enable"] = True
default_conf["max_open_trades"] = 0
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
buy_mm = MagicMock(return_value=limit_buy_order_open)
2018-10-09 18:04:53 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2018-10-09 18:04:53 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-10-09 18:04:53 +00:00
get_fee=fee,
2024-05-12 13:44:42 +00:00
create_order=buy_mm,
2018-10-09 18:04:53 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
2018-10-09 18:04:53 +00:00
rpc = RPC(freqtradebot)
2024-05-12 13:44:42 +00:00
pair = "ETH/BTC"
with pytest.raises(RPCException, match="Maximum number of trades is reached."):
rpc._rpc_force_entry(pair, None)
2024-05-12 13:44:42 +00:00
freqtradebot.config["max_open_trades"] = 5
2022-01-26 18:08:37 +00:00
trade = rpc._rpc_force_entry(pair, None)
2018-10-09 18:04:53 +00:00
assert isinstance(trade, Trade)
assert trade.pair == pair
2024-05-12 13:44:42 +00:00
assert trade.open_rate == ticker()["bid"]
2018-10-09 18:04:53 +00:00
# Test buy duplicate
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r"position for ETH/BTC already open - id: 1"):
2022-01-26 18:08:37 +00:00
rpc._rpc_force_entry(pair, 0.0001)
2024-05-12 13:44:42 +00:00
pair = "XRP/BTC"
trade = rpc._rpc_force_entry(pair, 0.0001, order_type="limit")
2018-10-09 18:04:53 +00:00
assert isinstance(trade, Trade)
assert trade.pair == pair
assert trade.open_rate == 0.0001
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r"Symbol does not exist or market is not active."):
rpc._rpc_force_entry("LTC/NOTHING", 0.0001)
2022-11-29 17:27:08 +00:00
2018-10-09 18:04:53 +00:00
# Test buy pair not with stakes
2024-05-12 13:44:42 +00:00
with pytest.raises(
RPCException, match=r"Wrong pair selected. Only pairs with stake-currency.*"
):
rpc._rpc_force_entry("LTC/ETH", 0.0001)
# Test with defined stake_amount
2024-05-12 13:44:42 +00:00
pair = "LTC/BTC"
trade = rpc._rpc_force_entry(pair, 0.0001, order_type="limit", stake_amount=0.05)
assert trade.stake_amount == 0.05
2024-05-12 13:44:42 +00:00
assert trade.buy_tag == "force_entry"
2023-08-25 05:09:14 +00:00
2024-05-12 13:44:42 +00:00
assert trade.open_orders_ids[-1] == "mocked_limit_buy"
freqtradebot.strategy.position_adjustment_enable = True
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r"position for LTC/BTC already open.*open order.*"):
rpc._rpc_force_entry(pair, 0.0001, order_type="limit", stake_amount=0.05)
2018-10-09 18:04:53 +00:00
# Test not buying
2024-05-12 13:44:42 +00:00
pair = "XRP/BTC"
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2024-05-12 13:44:42 +00:00
freqtradebot.config["stake_amount"] = 0
patch_get_signal(freqtradebot)
2018-10-09 18:04:53 +00:00
rpc = RPC(freqtradebot)
2024-05-12 13:44:42 +00:00
pair = "TKN/BTC"
with pytest.raises(RPCException, match=r"Failed to enter position for TKN/BTC."):
trade = rpc._rpc_force_entry(pair, None)
2018-10-09 18:04:53 +00:00
2022-04-05 11:28:30 +00:00
def test_rpc_force_entry_stopped(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
default_conf["force_entry_enable"] = True
default_conf["initial_state"] = "stopped"
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-10-09 18:04:53 +00:00
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
2018-10-09 18:04:53 +00:00
rpc = RPC(freqtradebot)
2024-05-12 13:44:42 +00:00
pair = "ETH/BTC"
with pytest.raises(RPCException, match=r"trader is not running"):
2022-01-26 18:08:37 +00:00
rpc._rpc_force_entry(pair, None)
2018-10-09 18:04:53 +00:00
2022-04-05 11:28:30 +00:00
def test_rpc_force_entry_disabled(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-10-09 18:04:53 +00:00
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
2018-10-09 18:04:53 +00:00
rpc = RPC(freqtradebot)
2024-05-12 13:44:42 +00:00
pair = "ETH/BTC"
with pytest.raises(RPCException, match=r"Force_entry not enabled."):
2022-01-26 18:08:37 +00:00
rpc._rpc_force_entry(pair, None)
2018-11-10 19:07:09 +00:00
2022-04-05 11:28:30 +00:00
def test_rpc_force_entry_wrong_mode(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
default_conf["force_entry_enable"] = True
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2022-01-27 05:40:41 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
2024-05-12 13:44:42 +00:00
pair = "ETH/BTC"
2022-01-27 05:40:41 +00:00
with pytest.raises(RPCException, match="Can't go short on Spot markets."):
rpc._rpc_force_entry(pair, None, order_side=SignalDirection.SHORT)
2021-03-01 06:51:33 +00:00
@pytest.mark.usefixtures("init_persistence")
2024-03-30 13:30:00 +00:00
def test_rpc_add_and_delete_lock(mocker, default_conf):
2021-03-01 06:51:33 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtradebot)
2024-05-12 13:44:42 +00:00
pair = "ETH/BTC"
2021-03-01 06:51:33 +00:00
2024-05-12 13:44:42 +00:00
rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=4), "", "*")
rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=5), "", "*")
rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=10), "", "*")
2024-03-30 13:30:00 +00:00
2021-03-01 06:51:33 +00:00
locks = rpc._rpc_locks()
2024-05-12 13:44:42 +00:00
assert locks["lock_count"] == 3
locks1 = rpc._rpc_delete_lock(lockid=locks["locks"][0]["id"])
assert locks1["lock_count"] == 2
2021-03-01 06:51:33 +00:00
locks2 = rpc._rpc_delete_lock(pair=pair)
2024-05-12 13:44:42 +00:00
assert locks2["lock_count"] == 0
2021-03-01 06:51:33 +00:00
2018-11-10 19:07:09 +00:00
def test_rpc_whitelist(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-11-10 19:07:09 +00:00
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2018-11-10 19:07:09 +00:00
rpc = RPC(freqtradebot)
ret = rpc._rpc_whitelist()
2024-05-12 13:44:42 +00:00
assert len(ret["method"]) == 1
assert "StaticPairList" in ret["method"]
assert ret["whitelist"] == default_conf["exchange"]["pair_whitelist"]
2018-11-10 19:07:09 +00:00
def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
default_conf["pairlists"] = [
{
"method": "VolumePairList",
"number_assets": 4,
}
]
mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True))
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2018-11-10 19:07:09 +00:00
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2018-11-10 19:07:09 +00:00
rpc = RPC(freqtradebot)
ret = rpc._rpc_whitelist()
2024-05-12 13:44:42 +00:00
assert len(ret["method"]) == 1
assert "VolumePairList" in ret["method"]
assert ret["length"] == 4
assert ret["whitelist"] == default_conf["exchange"]["pair_whitelist"]
2019-03-24 15:09:20 +00:00
def test_rpc_blacklist(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2019-03-24 15:09:20 +00:00
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2019-03-24 15:09:20 +00:00
rpc = RPC(freqtradebot)
ret = rpc._rpc_blacklist(None)
2024-05-12 13:44:42 +00:00
assert len(ret["method"]) == 1
assert "StaticPairList" in ret["method"]
assert len(ret["blacklist"]) == 2
assert ret["blacklist"] == default_conf["exchange"]["pair_blacklist"]
assert ret["blacklist"] == ["DOGE/BTC", "HOT/BTC"]
ret = rpc._rpc_blacklist(["ETH/BTC"])
2024-05-12 13:44:42 +00:00
assert "StaticPairList" in ret["method"]
assert len(ret["blacklist"]) == 3
assert ret["blacklist"] == default_conf["exchange"]["pair_blacklist"]
assert ret["blacklist"] == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"]
ret = rpc._rpc_blacklist(["ETH/BTC"])
2024-05-12 13:44:42 +00:00
assert "errors" in ret
assert isinstance(ret["errors"], dict)
assert ret["errors"]["ETH/BTC"]["error_msg"] == "Pair ETH/BTC already in pairlist."
ret = rpc._rpc_blacklist(["*/BTC"])
2024-05-12 13:44:42 +00:00
assert "StaticPairList" in ret["method"]
assert len(ret["blacklist"]) == 3
assert ret["blacklist"] == default_conf["exchange"]["pair_blacklist"]
assert ret["blacklist"] == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"]
assert ret["blacklist_expanded"] == ["ETH/BTC"]
assert "errors" in ret
assert isinstance(ret["errors"], dict)
assert ret["errors"] == {"*/BTC": {"error_msg": "Pair */BTC is not a valid wildcard."}}
ret = rpc._rpc_blacklist(["XRP/.*"])
2024-05-12 13:44:42 +00:00
assert "StaticPairList" in ret["method"]
assert len(ret["blacklist"]) == 4
assert ret["blacklist"] == default_conf["exchange"]["pair_blacklist"]
assert ret["blacklist"] == ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"]
assert ret["blacklist_expanded"] == ["ETH/BTC", "XRP/BTC", "XRP/USDT"]
assert "errors" in ret
assert isinstance(ret["errors"], dict)
2021-12-11 15:09:20 +00:00
2024-05-12 13:44:42 +00:00
ret = rpc._rpc_blacklist_delete(["DOGE/BTC", "HOT/BTC"])
2021-12-11 15:09:20 +00:00
2024-05-12 13:44:42 +00:00
assert "StaticPairList" in ret["method"]
assert len(ret["blacklist"]) == 2
assert ret["blacklist"] == default_conf["exchange"]["pair_blacklist"]
assert ret["blacklist"] == ["ETH/BTC", "XRP/.*"]
assert ret["blacklist_expanded"] == ["ETH/BTC", "XRP/BTC", "XRP/USDT"]
assert "errors" in ret
assert isinstance(ret["errors"], dict)
2019-03-27 13:04:33 +00:00
def test_rpc_edge_disabled(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtradebot)
2024-05-12 13:44:42 +00:00
with pytest.raises(RPCException, match=r"Edge is not enabled."):
2019-03-27 13:04:33 +00:00
rpc._rpc_edge()
def test_rpc_edge_enabled(mocker, edge_conf) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch(
"freqtrade.edge.Edge._cached_pairs",
mocker.PropertyMock(
return_value={
"E/F": PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60),
}
),
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, edge_conf)
rpc = RPC(freqtradebot)
ret = rpc._rpc_edge()
assert len(ret) == 1
2024-05-12 13:44:42 +00:00
assert ret[0]["Pair"] == "E/F"
assert ret[0]["Winrate"] == 0.66
assert ret[0]["Expectancy"] == 1.71
assert ret[0]["Stoploss"] == -0.02
def test_rpc_health(mocker, default_conf) -> None:
2024-05-12 13:44:42 +00:00
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
set_startup_time()
rpc = RPC(freqtradebot)
result = rpc.health()
2024-05-12 13:44:42 +00:00
assert result["last_process"] is None
assert result["last_process_ts"] is None