From 0920fb61200ca206be153c399b0d1422f321dead Mon Sep 17 00:00:00 2001 From: gcarq Date: Tue, 3 Jul 2018 20:26:48 +0200 Subject: [PATCH] use more granular msg dict for buy/sell notifications --- freqtrade/freqtradebot.py | 75 ++++++++++--------- freqtrade/main.py | 3 + freqtrade/rpc/__init__.py | 2 + freqtrade/rpc/rpc.py | 15 ++-- freqtrade/rpc/rpc_manager.py | 6 +- freqtrade/rpc/telegram.py | 39 +++++++++- freqtrade/tests/rpc/test_rpc.py | 2 +- freqtrade/tests/rpc/test_rpc_manager.py | 18 +++-- freqtrade/tests/rpc/test_rpc_telegram.py | 92 +++++++++++++++++------ freqtrade/tests/test_freqtradebot.py | 93 ++++++++++++++++-------- 10 files changed, 244 insertions(+), 101 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index bbece2f03..72b5190b9 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -19,7 +19,8 @@ from freqtrade.analyze import Analyze from freqtrade.exchange import Exchange from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.persistence import Trade -from freqtrade.rpc.rpc_manager import RPCManager +from freqtrade.rpc import RPCMessageType +from freqtrade.rpc import RPCManager from freqtrade.state import State logger = logging.getLogger(__name__) @@ -92,6 +93,7 @@ class FreqtradeBot(object): state = self.state if state != old_state: self.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, 'status': f'{state.name.lower()}' }) logger.info('Changing state to: %s', state.name) @@ -170,6 +172,7 @@ class FreqtradeBot(object): tb = traceback.format_exc() hint = 'Issue `/start` if you think it is safe to restart.' self.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, 'status': f'OperationalException:\n```\n{tb}```{hint}' }) logger.exception('OperationalException. Stopping trader ...') @@ -340,7 +343,6 @@ class FreqtradeBot(object): pair_url = self.exchange.get_pair_detail_url(pair) stake_currency = self.config['stake_currency'] fiat_currency = self.config['fiat_display_currency'] - exc_name = self.exchange.name # Calculate amount buy_limit = self.get_target_bid(self.exchange.get_ticker(pair)) @@ -363,12 +365,16 @@ class FreqtradeBot(object): fiat_currency ) - # Create trade entity and return self.rpc.send_msg({ - 'status': - f"""*{exc_name}:* Buying [{pair_s}]({pair_url}) \ - with limit `{buy_limit:.8f} ({stake_amount:.6f} \ - {stake_currency}, {stake_amount_fiat:.3f} {fiat_currency})`""" + 'type': RPCMessageType.BUY_NOTIFICATION, + 'exchange': self.exchange.name.capitalize(), + 'pair': pair_s, + 'market_url': pair_url, + 'limit': buy_limit, + 'stake_amount': stake_amount, + 'stake_amount_fiat': stake_amount_fiat, + 'stake_currency': stake_currency, + 'fiat_currency': fiat_currency }) # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') @@ -555,6 +561,7 @@ class FreqtradeBot(object): Trade.session.flush() logger.info('Buy order timeout for %s.', trade) self.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, 'status': f'Unfilled buy order for {pair_s} cancelled due to timeout' }) return True @@ -566,6 +573,7 @@ class FreqtradeBot(object): trade.open_order_id = None logger.info('Partial buy order timeout for %s.', trade) self.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, 'status': f'Remaining buy order for {pair_s} cancelled due to timeout' }) return False @@ -586,6 +594,7 @@ class FreqtradeBot(object): trade.is_open = True trade.open_order_id = None self.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, 'status': f'Unfilled sell order for {pair_s} cancelled due to timeout' }) logger.info('Sell order timeout for %s.', trade) @@ -601,47 +610,47 @@ class FreqtradeBot(object): :param limit: limit rate for the sell order :return: None """ - exc = trade.exchange - pair = trade.pair # Execute sell and update trade record order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id'] trade.open_order_id = order_id trade.close_rate_requested = limit - fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2) profit_trade = trade.calc_profit(rate=limit) current_rate = self.exchange.get_ticker(trade.pair)['bid'] - profit = trade.calc_profit_percent(limit) + profit_percent = trade.calc_profit_percent(limit) pair_url = self.exchange.get_pair_detail_url(trade.pair) - gain = "profit" if fmt_exp_profit > 0 else "loss" + gain = "profit" if profit_percent > 0 else "loss" - message = f"*{exc}:* Selling\n" \ - f"*Current Pair:* [{pair}]({pair_url})\n" \ - f"*Limit:* `{limit}`\n" \ - f"*Amount:* `{round(trade.amount, 8)}`\n" \ - f"*Open Rate:* `{trade.open_rate:.8f}`\n" \ - f"*Current Rate:* `{current_rate:.8f}`\n" \ - f"*Profit:* `{round(profit * 100, 2):.2f}%`" \ - "" + msg = { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': trade.exchange.capitalize(), + 'pair': trade.pair, + 'gain': gain, + 'market_url': pair_url, + 'limit': limit, + 'amount': trade.amount, + 'open_rate': trade.open_rate, + 'current_rate': current_rate, + 'profit_amount': profit_trade, + 'profit_percent': profit_percent, + } # For regular case, when the configuration exists if 'stake_currency' in self.config and 'fiat_display_currency' in self.config: - stake = self.config['stake_currency'] - fiat = self.config['fiat_display_currency'] + stake_currency = self.config['stake_currency'] + fiat_currency = self.config['fiat_display_currency'] fiat_converter = CryptoToFiatConverter() profit_fiat = fiat_converter.convert_amount( profit_trade, - stake, - fiat + stake_currency, + fiat_currency, ) - message += f'` ({gain}: {fmt_exp_profit:.2f}%, {profit_trade:.8f} {stake}`' \ - f'` / {profit_fiat:.3f} {fiat})`'\ - '' - # Because telegram._forcesell does not have the configuration - # Ignore the FIAT value and does not show the stake_currency as well - else: - gain = "profit" if fmt_exp_profit > 0 else "loss" - message += f'` ({gain}: {fmt_exp_profit:.2f}%, {profit_trade:.8f})`' + msg.update({ + 'profit_fiat': profit_fiat, + 'stake_currency': stake_currency, + 'fiat_currency': fiat_currency, + }) + # Send the message - self.rpc.send_msg({'status': message}) + self.rpc.send_msg(msg) Trade.session.flush() diff --git a/freqtrade/main.py b/freqtrade/main.py index 74d8da031..977212faf 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -13,6 +13,7 @@ from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade.freqtradebot import FreqtradeBot from freqtrade.state import State +from freqtrade.rpc import RPCMessageType logger = logging.getLogger('freqtrade') @@ -60,6 +61,7 @@ def main(sysargv: List[str]) -> None: finally: if freqtrade: freqtrade.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, 'status': 'process died' }) freqtrade.cleanup() @@ -76,6 +78,7 @@ def reconfigure(freqtrade: FreqtradeBot, args: Namespace) -> FreqtradeBot: # Create new instance freqtrade = FreqtradeBot(Configuration(args).get_config()) freqtrade.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, 'status': 'config reloaded' }) return freqtrade diff --git a/freqtrade/rpc/__init__.py b/freqtrade/rpc/__init__.py index e69de29bb..31c854f82 100644 --- a/freqtrade/rpc/__init__.py +++ b/freqtrade/rpc/__init__.py @@ -0,0 +1,2 @@ +from .rpc import RPC, RPCMessageType, RPCException # noqa +from .rpc_manager import RPCManager # noqa diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 9ad506b82..faf0653b1 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -5,6 +5,7 @@ import logging from abc import abstractmethod from datetime import timedelta, datetime, date from decimal import Decimal +from enum import Enum from typing import Dict, Any, List import arrow @@ -19,6 +20,15 @@ from freqtrade.state import State logger = logging.getLogger(__name__) +class RPCMessageType(Enum): + STATUS_NOTIFICATION = 'status' + BUY_NOTIFICATION = 'buy' + SELL_NOTIFICATION = 'sell' + + def __repr__(self): + return self.value + + class RPCException(Exception): """ Should be raised with a rpc-formatted message in an _rpc_* method @@ -33,11 +43,6 @@ class RPCException(Exception): def __str__(self): return self.message - def __json__(self): - return { - 'msg': self.message - } - class RPC(object): """ diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index ec193d23d..34094ee20 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -2,9 +2,9 @@ This module contains class to manage RPC communications (Telegram, Slack, ...) """ import logging -from typing import List, Dict +from typing import List, Dict, Any -from freqtrade.rpc.rpc import RPC +from freqtrade.rpc import RPC logger = logging.getLogger(__name__) @@ -32,7 +32,7 @@ class RPCManager(object): mod.cleanup() del mod - def send_msg(self, msg: Dict[str, str]) -> None: + def send_msg(self, msg: Dict[str, Any]) -> None: """ Send given message to all registered rpc modules. A message consists of one or more key value pairs of strings. diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index cab5d1d74..39724a2f9 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -12,7 +12,7 @@ from telegram.error import NetworkError, TelegramError from telegram.ext import CommandHandler, Updater from freqtrade.__init__ import __version__ -from freqtrade.rpc.rpc import RPC, RPCException +from freqtrade.rpc import RPC, RPCException, RPCMessageType logger = logging.getLogger(__name__) @@ -110,9 +110,42 @@ class Telegram(RPC): """ self._updater.stop() - def send_msg(self, msg: Dict[str, str]) -> None: + def send_msg(self, msg: Dict[str, Any]) -> None: """ Send a message to telegram channel """ - self._send_msg('*Status:* `{status}`'.format(**msg)) + + if msg['type'] == RPCMessageType.BUY_NOTIFICATION: + message = "*{exchange}:* Buying [{pair}]({market_url})\n" \ + "with limit `{limit:.8f}\n" \ + "({stake_amount:.6f} {stake_currency}," \ + "{stake_amount_fiat:.3f} {fiat_currency})`" \ + .format(**msg) + + elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: + msg['amount'] = round(msg['amount'], 8) + msg['profit_percent'] = round(msg['profit_percent'] * 100, 2) + + message = "*{exchange}:* Selling [{pair}]({market_url})\n" \ + "*Limit:* `{limit:.8f}`\n" \ + "*Amount:* `{amount:.8f}`\n" \ + "*Open Rate:* `{open_rate:.8f}`\n" \ + "*Current Rate:* `{current_rate:.8f}`\n" \ + "*Profit:* `{profit_percent:.2f}%`".format(**msg) + + # Check if all sell properties are available. + # This might not be the case if the message origin is triggered by /forcesell + if all(prop in msg for prop in ['gain', 'profit_fiat', + 'fiat_currency', 'stake_currency']): + message += '` ({gain}: {profit_amount:.8f} {stake_currency}`' \ + '` / {profit_fiat:.3f} {fiat_currency})`'.format(**msg) + + self._send_msg(message) + elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION: + message = '*Status:* `{status}`'.format(**msg) + + else: + raise NotImplementedError('Unknown message type: {}'.format(msg['type'])) + + self._send_msg(message) @authorized_only def _status(self, bot: Bot, update: Update) -> None: diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 654547078..23e14cfbb 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -11,7 +11,7 @@ import pytest from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade -from freqtrade.rpc.rpc import RPC, RPCException +from freqtrade.rpc import RPC, RPCException from freqtrade.state import State from freqtrade.tests.test_freqtradebot import (patch_coinmarketcap, patch_get_signal) diff --git a/freqtrade/tests/rpc/test_rpc_manager.py b/freqtrade/tests/rpc/test_rpc_manager.py index 3c17a4270..1f9b034b9 100644 --- a/freqtrade/tests/rpc/test_rpc_manager.py +++ b/freqtrade/tests/rpc/test_rpc_manager.py @@ -6,8 +6,8 @@ import logging from copy import deepcopy from unittest.mock import MagicMock -from freqtrade.rpc.rpc_manager import RPCManager -from freqtrade.tests.conftest import get_patched_freqtradebot, log_has +from freqtrade.rpc import RPCMessageType, RPCManager +from freqtrade.tests.conftest import log_has, get_patched_freqtradebot def test_rpc_manager_object() -> None: @@ -102,9 +102,12 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None: freqtradebot = get_patched_freqtradebot(mocker, conf) rpc_manager = RPCManager(freqtradebot) - rpc_manager.send_msg({'status': 'test'}) + rpc_manager.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': 'test' + }) - assert log_has("Sending rpc message: {'status': 'test'}", caplog.record_tuples) + assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog.record_tuples) assert telegram_mock.call_count == 0 @@ -117,7 +120,10 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None: freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc_manager = RPCManager(freqtradebot) - rpc_manager.send_msg({'status': 'test'}) + rpc_manager.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': 'test' + }) - assert log_has("Sending rpc message: {'status': 'test'}", caplog.record_tuples) + assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog.record_tuples) assert telegram_mock.call_count == 1 diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 30782d598..baf9f124d 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -9,7 +9,7 @@ import re from copy import deepcopy from datetime import datetime from random import randint -from unittest.mock import MagicMock +from unittest.mock import MagicMock, ANY from telegram import Chat, Message, Update from telegram.error import NetworkError @@ -17,6 +17,7 @@ from telegram.error import NetworkError from freqtrade import __version__ from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade +from freqtrade.rpc import RPCMessageType from freqtrade.rpc.telegram import Telegram, authorized_only from freqtrade.state import State from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, @@ -757,13 +758,23 @@ def test_forcesell_handle(default_conf, update, ticker, fee, telegram._forcesell(bot=MagicMock(), update=update) assert rpc_mock.call_count == 2 - last_call = rpc_mock.call_args_list[-1][0][0]['status'] - assert 'Selling' in last_call - assert '[ETH/BTC]' in last_call - assert 'Amount' in last_call - assert '0.00001172' in last_call - assert 'profit: 6.11%, 0.00006126' in last_call - assert '0.919 USD' in last_call + last_msg = rpc_mock.call_args_list[-1][0][0] + assert { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'gain': 'profit', + 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', + 'limit': 1.172e-05, + 'amount': 90.99181073703367, + 'open_rate': 1.099e-05, + 'current_rate': 1.172e-05, + 'profit_amount': 6.126e-05, + 'profit_percent': 0.06110514, + 'profit_fiat': 0.9189, + 'stake_currency': 'BTC', + 'fiat_currency': 'USD', + } == last_msg def test_forcesell_down_handle(default_conf, update, ticker, fee, @@ -803,14 +814,25 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, update.message.text = '/forcesell 1' telegram._forcesell(bot=MagicMock(), update=update) - last_call = rpc_mock.call_args_list[-1][0][0]['status'] assert rpc_mock.call_count == 2 - assert 'Selling' in last_call - assert '[ETH/BTC]' in last_call - assert 'Amount' in last_call - assert '0.00001044' in last_call - assert 'loss: -5.48%, -0.00005492' in last_call - assert '-0.824 USD' in last_call + + last_msg = rpc_mock.call_args_list[-1][0][0] + assert { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'gain': 'loss', + 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', + 'limit': 1.044e-05, + 'amount': 90.99181073703367, + 'open_rate': 1.099e-05, + 'current_rate': 1.044e-05, + 'profit_amount': -5.492e-05, + 'profit_percent': -0.05478343, + 'profit_fiat': -0.8238000000000001, + 'stake_currency': 'BTC', + 'fiat_currency': 'USD', + } == last_msg def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None: @@ -843,10 +865,23 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker telegram._forcesell(bot=MagicMock(), update=update) assert rpc_mock.call_count == 4 - for args in rpc_mock.call_args_list: - assert '0.00001098' in args[0][0]['status'] - assert 'loss: -0.59%, -0.00000591 BTC' in args[0][0]['status'] - assert '-0.089 USD' in args[0][0]['status'] + msg = rpc_mock.call_args_list[0][0][0] + assert { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'gain': 'loss', + 'market_url': ANY, + 'limit': 1.098e-05, + 'amount': 90.99181073703367, + 'open_rate': 1.099e-05, + 'current_rate': 1.098e-05, + 'profit_amount': -5.91e-06, + 'profit_percent': -0.00589292, + 'profit_fiat': -0.08865, + 'stake_currency': 'BTC', + 'fiat_currency': 'USD', + } == msg def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: @@ -1040,7 +1075,22 @@ def test_version_handle(default_conf, update, mocker) -> None: assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0] -def test_send_msg(default_conf, mocker) -> None: +def test_send_msg_buy_notification() -> None: + # TODO: implement me + pass + + +def test_send_msg_sell_notification() -> None: + # TODO: implement me + pass + + +def test_send_msg_status_notification() -> None: + # TODO: implement me + pass + + +def test__send_msg(default_conf, mocker) -> None: """ Test send_msg() method """ @@ -1056,7 +1106,7 @@ def test_send_msg(default_conf, mocker) -> None: assert len(bot.method_calls) == 1 -def test_send_msg_network_error(default_conf, mocker, caplog) -> None: +def test__send_msg_network_error(default_conf, mocker, caplog) -> None: """ Test send_msg() method """ diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 678e54fe6..450504f57 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -18,9 +18,9 @@ from freqtrade import (DependencyException, OperationalException, TemporaryError, constants) from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade +from freqtrade.rpc import RPCMessageType from freqtrade.state import State -from freqtrade.tests.conftest import (log_has, patch_coinmarketcap, - patch_exchange) +from freqtrade.tests.conftest import log_has, patch_coinmarketcap, patch_exchange # Functions for recurrent object patching @@ -1375,14 +1375,23 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid']) assert rpc_mock.call_count == 2 - last_call = rpc_mock.call_args_list[-1][0][0]['status'] - assert 'Selling' in last_call - assert '[ETH/BTC]' in last_call - assert 'Amount' in last_call - assert 'Profit' in last_call - assert '0.00001172' in last_call - assert 'profit: 6.11%, 0.00006126' in last_call - assert '0.919 USD' in last_call + last_msg = rpc_mock.call_args_list[-1][0][0] + assert { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'gain': 'profit', + 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', + 'limit': 1.172e-05, + 'amount': 90.99181073703367, + 'open_rate': 1.099e-05, + 'current_rate': 1.172e-05, + 'profit_amount': 6.126e-05, + 'profit_percent': 0.06110514, + 'profit_fiat': 0.9189, + 'stake_currency': 'BTC', + 'fiat_currency': 'USD', + } == last_msg def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, mocker) -> None: @@ -1418,13 +1427,23 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid']) assert rpc_mock.call_count == 2 - last_call = rpc_mock.call_args_list[-1][0][0]['status'] - assert 'Selling' in last_call - assert '[ETH/BTC]' in last_call - assert 'Amount' in last_call - assert '0.00001044' in last_call - assert 'loss: -5.48%, -0.00005492' in last_call - assert '-0.824 USD' in last_call + last_msg = rpc_mock.call_args_list[-1][0][0] + assert { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'gain': 'loss', + 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', + 'limit': 1.044e-05, + 'amount': 90.99181073703367, + 'open_rate': 1.099e-05, + 'current_rate': 1.044e-05, + 'profit_amount': -5.492e-05, + 'profit_percent': -0.05478343, + 'profit_fiat': -0.8238000000000001, + 'stake_currency': 'BTC', + 'fiat_currency': 'USD', + } == last_msg def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, @@ -1461,13 +1480,20 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid']) assert rpc_mock.call_count == 2 - last_call = rpc_mock.call_args_list[-1][0][0]['status'] - assert 'Selling' in last_call - assert '[ETH/BTC]' in last_call - assert 'Amount' in last_call - assert '0.00001172' in last_call - assert '(profit: 6.11%, 0.00006126)' in last_call - assert 'USD' not in last_call + last_msg = rpc_mock.call_args_list[-1][0][0] + assert { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'gain': 'profit', + 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', + 'limit': 1.172e-05, + 'amount': 90.99181073703367, + 'open_rate': 1.099e-05, + 'current_rate': 1.172e-05, + 'profit_amount': 6.126e-05, + 'profit_percent': 0.06110514, + } == last_msg def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, @@ -1504,11 +1530,20 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid']) assert rpc_mock.call_count == 2 - last_call = rpc_mock.call_args_list[-1][0][0]['status'] - assert 'Selling' in last_call - assert '[ETH/BTC]' in last_call - assert '0.00001044' in last_call - assert 'loss: -5.48%, -0.00005492' in last_call + last_msg = rpc_mock.call_args_list[-1][0][0] + assert { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'gain': 'loss', + 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', + 'limit': 1.044e-05, + 'amount': 90.99181073703367, + 'open_rate': 1.099e-05, + 'current_rate': 1.044e-05, + 'profit_amount': -5.492e-05, + 'profit_percent': -0.05478343, + } == last_msg def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,