freqtrade_origin/tests/rpc/test_rpc_webhook.py

481 lines
17 KiB
Python
Raw Normal View History

2018-07-29 14:09:44 +00:00
# pragma pylint: disable=missing-docstring, C0103, protected-access
2023-12-21 18:26:36 +00:00
import logging
from datetime import datetime, timedelta
2018-07-05 19:33:01 +00:00
from unittest.mock import MagicMock
2018-07-04 19:42:17 +00:00
2018-07-05 19:33:01 +00:00
from requests import RequestException
2022-03-25 07:36:03 +00:00
from freqtrade.enums import ExitType, RPCMessageType
2021-06-09 17:51:44 +00:00
from freqtrade.rpc import RPC
from freqtrade.rpc.discord import Discord
2018-07-05 19:33:01 +00:00
from freqtrade.rpc.webhook import Webhook
2019-09-08 07:54:15 +00:00
from tests.conftest import get_patched_freqtradebot, log_has
2018-07-05 19:33:01 +00:00
def get_webhook_dict() -> dict:
return {
2020-02-08 20:02:52 +00:00
"enabled": True,
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
2022-04-04 17:32:27 +00:00
"webhookentry": {
2023-04-27 16:27:09 +00:00
# Intentionally broken, as "entry" should have priority.
"value1": "Buying {pair55555}",
},
"entry": {
2020-02-08 20:02:52 +00:00
"value1": "Buying {pair}",
"value2": "limit {limit:8f}",
2021-12-29 13:24:12 +00:00
"value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
2024-05-12 13:44:42 +00:00
"value5": "direction {direction}",
2020-02-08 20:02:52 +00:00
},
2022-04-04 17:32:27 +00:00
"webhookentrycancel": {
2020-02-11 14:58:40 +00:00
"value1": "Cancelling Open Buy Order for {pair}",
2020-02-08 20:02:52 +00:00
"value2": "limit {limit:8f}",
2021-12-29 13:24:12 +00:00
"value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
2024-05-12 13:44:42 +00:00
"value5": "direction {direction}",
2020-02-08 20:02:52 +00:00
},
2022-04-04 17:32:27 +00:00
"webhookentryfill": {
2021-04-19 17:53:16 +00:00
"value1": "Buy Order for {pair} filled",
"value2": "at {open_rate:8f}",
2021-12-29 13:24:12 +00:00
"value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
2024-05-12 13:44:42 +00:00
"value5": "direction {direction}",
2021-04-19 17:53:16 +00:00
},
2022-04-04 17:05:36 +00:00
"webhookexit": {
2020-02-08 20:02:52 +00:00
"value1": "Selling {pair}",
"value2": "limit {limit:8f}",
2024-05-12 13:44:42 +00:00
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})",
2020-02-08 20:02:52 +00:00
},
2022-04-04 17:05:36 +00:00
"webhookexitcancel": {
2020-02-11 14:58:40 +00:00
"value1": "Cancelling Open Sell Order for {pair}",
2020-02-08 20:02:52 +00:00
"value2": "limit {limit:8f}",
2024-05-12 13:44:42 +00:00
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})",
2020-02-08 20:02:52 +00:00
},
2022-04-04 17:05:36 +00:00
"webhookexitfill": {
2021-04-19 17:53:16 +00:00
"value1": "Sell Order for {pair} filled",
"value2": "at {close_rate:8f}",
2024-05-12 13:44:42 +00:00
"value3": "",
2021-04-19 17:53:16 +00:00
},
2024-05-12 13:44:42 +00:00
"webhookstatus": {"value1": "Status: {status}", "value2": "", "value3": ""},
2020-02-08 20:02:52 +00:00
}
2018-07-04 19:42:17 +00:00
2018-07-05 19:33:01 +00:00
def test__init__(mocker, default_conf):
2024-05-12 13:44:42 +00:00
default_conf["webhook"] = {"enabled": True, "url": "https://DEADBEEF.com"}
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
2018-07-05 19:33:01 +00:00
assert webhook._config == default_conf
2018-07-04 19:42:17 +00:00
2018-07-05 19:33:01 +00:00
def test_send_msg_webhook(default_conf, mocker):
2018-07-05 19:33:01 +00:00
default_conf["webhook"] = get_webhook_dict()
msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
# Test buy
msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
2018-07-05 19:33:01 +00:00
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.ENTRY,
"exchange": "Binance",
"pair": "ETH/BTC",
"leverage": 1.0,
"direction": "Long",
"limit": 0.005,
"stake_amount": 0.8,
"stake_amount_fiat": 500,
"stake_currency": "BTC",
"fiat_currency": "EUR",
2018-07-05 19:33:01 +00:00
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["entry"]["value1"].format(
**msg
)
assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["entry"]["value2"].format(
**msg
)
assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["entry"]["value3"].format(
**msg
)
assert msg_mock.call_args[0][0]["value4"] == default_conf["webhook"]["entry"]["value4"].format(
**msg
)
assert msg_mock.call_args[0][0]["value5"] == default_conf["webhook"]["entry"]["value5"].format(
**msg
)
2021-12-29 13:24:12 +00:00
# Test short
msg_mock.reset_mock()
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.ENTRY,
"exchange": "Binance",
"pair": "ETH/BTC",
"leverage": 2.0,
"direction": "Short",
"limit": 0.005,
"stake_amount": 0.8,
"stake_amount_fiat": 500,
"stake_currency": "BTC",
"fiat_currency": "EUR",
2021-12-29 13:24:12 +00:00
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["entry"]["value1"].format(
**msg
)
assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["entry"]["value2"].format(
**msg
)
assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["entry"]["value3"].format(
**msg
)
assert msg_mock.call_args[0][0]["value4"] == default_conf["webhook"]["entry"]["value4"].format(
**msg
)
assert msg_mock.call_args[0][0]["value5"] == default_conf["webhook"]["entry"]["value5"].format(
**msg
)
# Test buy cancel
2021-04-19 17:53:16 +00:00
msg_mock.reset_mock()
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.ENTRY_CANCEL,
"exchange": "Binance",
"pair": "ETH/BTC",
"leverage": 1.0,
"direction": "Long",
"limit": 0.005,
"stake_amount": 0.8,
"stake_amount_fiat": 500,
"stake_currency": "BTC",
"fiat_currency": "EUR",
2021-12-29 13:24:12 +00:00
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookentrycancel"][
"value1"
].format(**msg)
assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookentrycancel"][
"value2"
].format(**msg)
assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookentrycancel"][
"value3"
].format(**msg)
2021-12-29 13:24:12 +00:00
# Test short cancel
msg_mock.reset_mock()
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.ENTRY_CANCEL,
"exchange": "Binance",
"pair": "ETH/BTC",
"leverage": 2.0,
"direction": "Short",
"limit": 0.005,
"stake_amount": 0.8,
"stake_amount_fiat": 500,
"stake_currency": "BTC",
"fiat_currency": "EUR",
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookentrycancel"][
"value1"
].format(**msg)
assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookentrycancel"][
"value2"
].format(**msg)
assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookentrycancel"][
"value3"
].format(**msg)
assert msg_mock.call_args[0][0]["value4"] == default_conf["webhook"]["webhookentrycancel"][
"value4"
].format(**msg)
assert msg_mock.call_args[0][0]["value5"] == default_conf["webhook"]["webhookentrycancel"][
"value5"
].format(**msg)
# Test buy fill
msg_mock.reset_mock()
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.ENTRY_FILL,
"exchange": "Binance",
"pair": "ETH/BTC",
"leverage": 1.0,
"direction": "Long",
"open_rate": 0.005,
"stake_amount": 0.8,
"stake_amount_fiat": 500,
"stake_currency": "BTC",
"fiat_currency": "EUR",
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookentryfill"][
"value1"
].format(**msg)
assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookentryfill"][
"value2"
].format(**msg)
assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookentryfill"][
"value3"
].format(**msg)
assert msg_mock.call_args[0][0]["value4"] == default_conf["webhook"]["webhookentrycancel"][
"value4"
].format(**msg)
assert msg_mock.call_args[0][0]["value5"] == default_conf["webhook"]["webhookentrycancel"][
"value5"
].format(**msg)
2021-12-29 13:24:12 +00:00
# Test short fill
msg_mock.reset_mock()
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.ENTRY_FILL,
"exchange": "Binance",
"pair": "ETH/BTC",
"leverage": 2.0,
"direction": "Short",
"open_rate": 0.005,
"stake_amount": 0.8,
"stake_amount_fiat": 500,
"stake_currency": "BTC",
"fiat_currency": "EUR",
2021-12-29 13:24:12 +00:00
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookentryfill"][
"value1"
].format(**msg)
assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookentryfill"][
"value2"
].format(**msg)
assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookentryfill"][
"value3"
].format(**msg)
assert msg_mock.call_args[0][0]["value4"] == default_conf["webhook"]["webhookentrycancel"][
"value4"
].format(**msg)
assert msg_mock.call_args[0][0]["value5"] == default_conf["webhook"]["webhookentrycancel"][
"value5"
].format(**msg)
2018-07-05 19:33:01 +00:00
# Test sell
2021-04-19 17:53:16 +00:00
msg_mock.reset_mock()
2021-12-29 13:24:12 +00:00
2018-07-05 19:33:01 +00:00
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.EXIT,
"exchange": "Binance",
"pair": "ETH/BTC",
"gain": "profit",
"limit": 0.005,
"amount": 0.8,
"order_type": "limit",
"open_rate": 0.004,
"current_rate": 0.005,
"profit_amount": 0.001,
"profit_ratio": 0.20,
"stake_currency": "BTC",
"sell_reason": ExitType.STOP_LOSS.value,
2018-07-05 19:33:01 +00:00
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookexit"][
"value1"
].format(**msg)
assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookexit"][
"value2"
].format(**msg)
assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookexit"][
"value3"
].format(**msg)
# Test sell cancel
2021-04-19 17:53:16 +00:00
msg_mock.reset_mock()
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.EXIT_CANCEL,
"exchange": "Binance",
"pair": "ETH/BTC",
"gain": "profit",
"limit": 0.005,
"amount": 0.8,
"order_type": "limit",
"open_rate": 0.004,
"current_rate": 0.005,
"profit_amount": 0.001,
"profit_ratio": 0.20,
"stake_currency": "BTC",
"sell_reason": ExitType.STOP_LOSS.value,
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookexitcancel"][
"value1"
].format(**msg)
assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookexitcancel"][
"value2"
].format(**msg)
assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookexitcancel"][
"value3"
].format(**msg)
# Test Sell fill
msg_mock.reset_mock()
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.EXIT_FILL,
"exchange": "Binance",
"pair": "ETH/BTC",
"gain": "profit",
"close_rate": 0.005,
"amount": 0.8,
"order_type": "limit",
"open_rate": 0.004,
"current_rate": 0.005,
"profit_amount": 0.001,
"profit_ratio": 0.20,
"stake_currency": "BTC",
"sell_reason": ExitType.STOP_LOSS.value,
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookexitfill"][
"value1"
].format(**msg)
assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookexitfill"][
"value2"
].format(**msg)
assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookexitfill"][
"value3"
].format(**msg)
for msgtype in [RPCMessageType.STATUS, RPCMessageType.WARNING, RPCMessageType.STARTUP]:
2019-08-30 05:05:22 +00:00
# Test notification
2024-05-12 13:44:42 +00:00
msg = {"type": msgtype, "status": "Unfilled sell order for BTC cancelled due to timeout"}
2019-08-30 05:05:22 +00:00
msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
webhook.send_msg(msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookstatus"][
"value1"
].format(**msg)
assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookstatus"][
"value2"
].format(**msg)
assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookstatus"][
"value3"
].format(**msg)
2018-07-05 19:33:01 +00:00
def test_exception_send_msg(default_conf, mocker, caplog):
2023-12-21 18:20:27 +00:00
caplog.set_level(logging.DEBUG)
2018-07-05 19:33:01 +00:00
default_conf["webhook"] = get_webhook_dict()
2023-04-27 16:27:09 +00:00
del default_conf["webhook"]["entry"]
2022-04-04 17:32:27 +00:00
del default_conf["webhook"]["webhookentry"]
2018-07-05 19:33:01 +00:00
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
2024-05-12 13:44:42 +00:00
webhook.send_msg({"type": RPCMessageType.ENTRY})
assert log_has(f"Message type '{RPCMessageType.ENTRY}' not configured for webhooks", caplog)
2018-07-05 19:33:01 +00:00
default_conf["webhook"] = get_webhook_dict()
2022-10-07 18:52:14 +00:00
default_conf["webhook"]["strategy_msg"] = {"value1": "{DEADBEEF:8f}"}
2018-07-05 19:33:01 +00:00
msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
2018-07-05 19:33:01 +00:00
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.STRATEGY_MSG,
"msg": "hello world",
2018-07-05 19:33:01 +00:00
}
webhook.send_msg(msg)
2024-05-12 13:44:42 +00:00
assert log_has(
"Problem calling Webhook. Please check your webhook configuration. "
"Exception: 'DEADBEEF'",
caplog,
)
2018-07-05 19:33:01 +00:00
# Test no failure for not implemented but known messagetypes
for e in RPCMessageType:
2024-05-12 13:44:42 +00:00
msg = {"type": e, "status": "whatever"}
2018-07-05 19:33:01 +00:00
webhook.send_msg(msg)
# Test no failure for not implemented but known messagetypes
for e in RPCMessageType:
2024-05-12 13:44:42 +00:00
msg = {"type": e, "status": "whatever"}
webhook.send_msg(msg)
2018-07-05 19:33:01 +00:00
def test__send_msg(default_conf, mocker, caplog):
default_conf["webhook"] = get_webhook_dict()
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
2024-05-12 13:44:42 +00:00
msg = {"value1": "DEADBEEF", "value2": "ALIVEBEEF", "value3": "FREQTRADE"}
2018-07-05 19:33:01 +00:00
post = MagicMock()
mocker.patch("freqtrade.rpc.webhook.post", post)
webhook._send_msg(msg)
assert post.call_count == 1
2024-05-12 13:44:42 +00:00
assert post.call_args[1] == {"data": msg, "timeout": 10}
assert post.call_args[0] == (default_conf["webhook"]["url"],)
2018-07-05 19:33:01 +00:00
post = MagicMock(side_effect=RequestException)
mocker.patch("freqtrade.rpc.webhook.post", post)
webhook._send_msg(msg)
2024-05-12 13:44:42 +00:00
assert log_has("Could not call webhook url. Exception: ", caplog)
2021-02-26 15:12:10 +00:00
2021-02-26 15:12:10 +00:00
def test__send_msg_with_json_format(default_conf, mocker, caplog):
default_conf["webhook"] = get_webhook_dict()
default_conf["webhook"]["format"] = "json"
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
2024-05-12 13:44:42 +00:00
msg = {"text": "Hello"}
2021-02-26 15:12:10 +00:00
post = MagicMock()
mocker.patch("freqtrade.rpc.webhook.post", post)
webhook._send_msg(msg)
2024-05-12 13:44:42 +00:00
assert post.call_args[1] == {"json": msg, "timeout": 10}
2021-11-29 18:54:54 +00:00
def test__send_msg_with_raw_format(default_conf, mocker, caplog):
default_conf["webhook"] = get_webhook_dict()
default_conf["webhook"]["format"] = "raw"
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
2024-05-12 13:44:42 +00:00
msg = {"data": "Hello"}
post = MagicMock()
mocker.patch("freqtrade.rpc.webhook.post", post)
webhook._send_msg(msg)
assert post.call_args[1] == {
2024-05-12 13:44:42 +00:00
"data": msg["data"],
"headers": {"Content-Type": "text/plain"},
"timeout": 10,
}
def test_send_msg_discord(default_conf, mocker):
2024-05-12 13:44:42 +00:00
default_conf["discord"] = {"enabled": True, "webhook_url": "https://webhookurl..."}
msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
discord = Discord(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
msg = {
2024-05-12 13:44:42 +00:00
"type": RPCMessageType.EXIT_FILL,
"trade_id": 1,
"exchange": "Binance",
"pair": "ETH/BTC",
"direction": "Long",
"gain": "profit",
"close_rate": 0.005,
"amount": 0.8,
"order_type": "limit",
"open_date": datetime.now() - timedelta(days=1),
"close_date": datetime.now(),
"open_rate": 0.004,
"current_rate": 0.005,
"profit_amount": 0.001,
"profit_ratio": 0.20,
"stake_currency": "BTC",
"enter_tag": "enter_tagggg",
"exit_reason": ExitType.STOP_LOSS.value,
}
discord.send_msg(msg=msg)
assert msg_mock.call_count == 1
2024-05-12 13:44:42 +00:00
assert "embeds" in msg_mock.call_args_list[0][0][0]
assert "title" in msg_mock.call_args_list[0][0][0]["embeds"][0]
assert "color" in msg_mock.call_args_list[0][0][0]["embeds"][0]
assert "fields" in msg_mock.call_args_list[0][0][0]["embeds"][0]