diff --git a/docs/webhook-config.md b/docs/webhook-config.md index 112f8a77e..9e0a34eae 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -63,6 +63,8 @@ Possible parameters are: * `fiat_currency` * `sell_reason` * `order_type` +* `open_date` +* `close_date` ### Webhookstatus diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ec341ff0a..0595e0d35 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -962,7 +962,9 @@ class FreqtradeBot: 'current_rate': current_rate, 'profit_amount': profit_trade, 'profit_percent': profit_percent, - 'sell_reason': trade.sell_reason + 'sell_reason': trade.sell_reason, + 'open_date': trade.open_date, + 'close_date': trade.close_date or datetime.utcnow() } # For regular case, when the configuration exists diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2ae22f472..51736968b 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -9,7 +9,7 @@ from typing import Any, Callable, Dict from tabulate import tabulate from telegram import ParseMode, ReplyKeyboardMarkup, Update from telegram.error import NetworkError, TelegramError -from telegram.ext import CommandHandler, Updater, CallbackContext +from telegram.ext import CallbackContext, CommandHandler, Updater from freqtrade.__init__ import __version__ from freqtrade.rpc import RPC, RPCException, RPCMessageType @@ -144,6 +144,9 @@ class Telegram(RPC): elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: msg['amount'] = round(msg['amount'], 8) msg['profit_percent'] = round(msg['profit_percent'] * 100, 2) + msg['duration'] = msg['close_date'].replace( + microsecond=0) - msg['open_date'].replace(microsecond=0) + msg['duration_min'] = msg['duration'].total_seconds() / 60 message = ("*{exchange}:* Selling {pair}\n" "*Rate:* `{limit:.8f}`\n" @@ -151,6 +154,7 @@ class Telegram(RPC): "*Open Rate:* `{open_rate:.8f}`\n" "*Current Rate:* `{current_rate:.8f}`\n" "*Sell Reason:* `{sell_reason}`\n" + "*Duration:* `{duration} ({duration_min:.1f} min)`\n" "*Profit:* `{profit_percent:.2f}%`").format(**msg) # Check if all sell properties are available. diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 367eb8366..7c4a8f0d6 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -6,7 +6,7 @@ import re from datetime import datetime from random import choice, randint from string import ascii_uppercase -from unittest.mock import MagicMock, PropertyMock +from unittest.mock import ANY, MagicMock, PropertyMock import arrow import pytest @@ -736,7 +736,9 @@ def test_forcesell_handle(default_conf, update, ticker, fee, 'profit_percent': 0.0611052, 'stake_currency': 'BTC', 'fiat_currency': 'USD', - 'sell_reason': SellType.FORCE_SELL.value + 'sell_reason': SellType.FORCE_SELL.value, + 'open_date': ANY, + 'close_date': ANY, } == last_msg @@ -793,7 +795,9 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, 'profit_percent': -0.05478342, 'stake_currency': 'BTC', 'fiat_currency': 'USD', - 'sell_reason': SellType.FORCE_SELL.value + 'sell_reason': SellType.FORCE_SELL.value, + 'open_date': ANY, + 'close_date': ANY, } == last_msg @@ -839,7 +843,9 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None 'profit_percent': -0.00589291, 'stake_currency': 'BTC', 'fiat_currency': 'USD', - 'sell_reason': SellType.FORCE_SELL.value + 'sell_reason': SellType.FORCE_SELL.value, + 'open_date': ANY, + 'close_date': ANY, } == msg @@ -1224,7 +1230,9 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'profit_percent': -0.57405275, 'stake_currency': 'ETH', 'fiat_currency': 'USD', - 'sell_reason': SellType.STOP_LOSS.value + 'sell_reason': SellType.STOP_LOSS.value, + 'open_date': arrow.utcnow().shift(hours=-1), + 'close_date': arrow.utcnow(), }) assert msg_mock.call_args[0][0] \ == ('*Binance:* Selling KEY/ETH\n' @@ -1233,6 +1241,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: '*Open Rate:* `0.00007500`\n' '*Current Rate:* `0.00003201`\n' '*Sell Reason:* `stop_loss`\n' + '*Duration:* `1:00:00 (60.0 min)`\n' '*Profit:* `-57.41%`` (loss: -0.05746268 ETH`` / -24.812 USD)`') msg_mock.reset_mock() @@ -1249,7 +1258,9 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'profit_amount': -0.05746268, 'profit_percent': -0.57405275, 'stake_currency': 'ETH', - 'sell_reason': SellType.STOP_LOSS.value + 'sell_reason': SellType.STOP_LOSS.value, + 'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), + 'close_date': arrow.utcnow(), }) assert msg_mock.call_args[0][0] \ == ('*Binance:* Selling KEY/ETH\n' @@ -1258,6 +1269,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: '*Open Rate:* `0.00007500`\n' '*Current Rate:* `0.00003201`\n' '*Sell Reason:* `stop_loss`\n' + '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n' '*Profit:* `-57.41%`') # Reset singleton function to avoid random breaks telegram._fiat_converter.convert_amount = old_convamount @@ -1377,7 +1389,9 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: 'profit_percent': -0.57405275, 'stake_currency': 'ETH', 'fiat_currency': 'USD', - 'sell_reason': SellType.STOP_LOSS.value + 'sell_reason': SellType.STOP_LOSS.value, + 'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3), + 'close_date': arrow.utcnow(), }) assert msg_mock.call_args[0][0] \ == '*Binance:* Selling KEY/ETH\n' \ @@ -1386,6 +1400,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: '*Open Rate:* `0.00007500`\n' \ '*Current Rate:* `0.00003201`\n' \ '*Sell Reason:* `stop_loss`\n' \ + '*Duration:* `2:35:03 (155.1 min)`\n' \ '*Profit:* `-57.41%`' diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index fe58ccf37..efab64a6a 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -5,7 +5,7 @@ import logging import time from copy import deepcopy from math import isclose -from unittest.mock import MagicMock, PropertyMock +from unittest.mock import ANY, MagicMock, PropertyMock import arrow import pytest @@ -2199,7 +2199,9 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N 'profit_percent': 0.0611052, 'stake_currency': 'BTC', 'fiat_currency': 'USD', - 'sell_reason': SellType.ROI.value + 'sell_reason': SellType.ROI.value, + 'open_date': ANY, + 'close_date': ANY, } == last_msg @@ -2246,7 +2248,9 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) 'profit_percent': -0.05478342, 'stake_currency': 'BTC', 'fiat_currency': 'USD', - 'sell_reason': SellType.STOP_LOSS.value + 'sell_reason': SellType.STOP_LOSS.value, + 'open_date': ANY, + 'close_date': ANY, } == last_msg @@ -2300,7 +2304,9 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe 'profit_percent': -0.01493766, 'stake_currency': 'BTC', 'fiat_currency': 'USD', - 'sell_reason': SellType.STOP_LOSS.value + 'sell_reason': SellType.STOP_LOSS.value, + 'open_date': ANY, + 'close_date': ANY, } == last_msg @@ -2497,7 +2503,9 @@ def test_execute_sell_market_order(default_conf, ticker, fee, 'profit_percent': 0.0611052, 'stake_currency': 'BTC', 'fiat_currency': 'USD', - 'sell_reason': SellType.ROI.value + 'sell_reason': SellType.ROI.value, + 'open_date': ANY, + 'close_date': ANY, } == last_msg