diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f3b825c79..26a55542f 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -10,10 +10,8 @@ from typing import Dict, Any, List import arrow import sqlalchemy as sql from numpy import mean, nan_to_num -from pandas import DataFrame from freqtrade import exchange -from freqtrade.misc import shorten_date from freqtrade.persistence import Trade from freqtrade.state import State @@ -93,7 +91,7 @@ class RPC(object): trade_id=trade.id, pair=trade.pair, market_url=exchange.get_pair_detail_url(trade.pair), - date=arrow.get(trade.open_date).humanize(), + open_date=arrow.get(trade.open_date), open_rate=trade.open_rate, close_rate=trade.close_rate, current_rate=current_rate, @@ -106,29 +104,6 @@ class RPC(object): )) return results - def _rpc_status_table(self) -> DataFrame: - trades = Trade.query.filter(Trade.is_open.is_(True)).all() - if self._freqtrade.state != State.RUNNING: - raise RPCException('trader is not running') - elif not trades: - raise RPCException('no active order') - else: - trades_list = [] - for trade in trades: - # calculate profit and send message to user - current_rate = exchange.get_ticker(trade.pair, False)['bid'] - trades_list.append([ - trade.id, - trade.pair, - shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), - '{:.2f}%'.format(100 * trade.calc_profit_percent(current_rate)) - ]) - - columns = ['ID', 'Pair', 'Since', 'Profit'] - df_statuses = DataFrame.from_records(trades_list, columns=columns) - df_statuses = df_statuses.set_index(columns[0]) - return df_statuses - def _rpc_daily_profit( self, timescale: int, stake_currency: str, fiat_display_currency: str) -> List[List[Any]]: diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 1d186f9df..90913b4a5 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -6,12 +6,15 @@ This module manage Telegram communication import logging from typing import Any, Callable +import arrow +from pandas import DataFrame from tabulate import tabulate from telegram import Bot, ParseMode, ReplyKeyboardMarkup, Update from telegram.error import NetworkError, TelegramError from telegram.ext import CommandHandler, Updater from freqtrade.__init__ import __version__ +from freqtrade.misc import shorten_date from freqtrade.rpc.rpc import RPC, RPCException logger = logging.getLogger(__name__) @@ -137,21 +140,25 @@ class Telegram(RPC): try: results = self._rpc_trade_status() - messages = [ - "*Trade ID:* `{trade_id}`\n" - "*Current Pair:* [{pair}]({market_url})\n" - "*Open Since:* `{date}`\n" - "*Amount:* `{amount}`\n" - "*Open Rate:* `{open_rate:.8f}`\n" - "*Close Rate:* `{close_rate}`\n" - "*Current Rate:* `{current_rate:.8f}`\n" - "*Close Profit:* `{close_profit}`\n" - "*Current Profit:* `{current_profit:.2f}%`\n" - "*Open Order:* `{open_order}`".format(**result) - for result in results - ] + messages = [] + for result in results: + result['open_date'] = arrow.get(result['open_date']).humanize() + messages.append( + "*Trade ID:* `{trade_id}`\n" + "*Current Pair:* [{pair}]({market_url})\n" + "*Open Since:* `{open_date}`\n" + "*Amount:* `{amount}`\n" + "*Open Rate:* `{open_rate:.8f}`\n" + "*Close Rate:* `{close_rate}`\n" + "*Current Rate:* `{current_rate:.8f}`\n" + "*Close Profit:* `{close_profit}`\n" + "*Current Profit:* `{current_profit:.2f}%`\n" + "*Open Order:* `{open_order}`".format(**result) + ) + for msg in messages: self._send_msg(msg, bot=bot) + except RPCException as e: self._send_msg(str(e), bot=bot) @@ -165,7 +172,20 @@ class Telegram(RPC): :return: None """ try: - df_statuses = self._rpc_status_table() + + results = self._rpc_trade_status() + data = [ + [ + result['trade_id'], + result['pair'], + shorten_date(arrow.get(result['open_date']).humanize(only_distance=True)), + result['current_profit'], + ] for result in results + ] + columns = ['ID', 'Pair', 'Since', 'Profit'] + df_statuses = DataFrame.from_records(data, columns=columns) + df_statuses = df_statuses.set_index(columns[0]) + message = tabulate(df_statuses, headers='keys', tablefmt='simple') self._send_msg("
{}".format(message), parse_mode=ParseMode.HTML) except RPCException as e: diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 2092f02b6..dc2a262a6 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -7,6 +7,7 @@ Unit test file for rpc/rpc.py from datetime import datetime from unittest.mock import MagicMock +import arrow import pytest from freqtrade.freqtradebot import FreqtradeBot @@ -39,6 +40,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: get_fee=fee ) + now = arrow.utcnow() + now_mock = mocker.patch('freqtrade.freqtradebot.datetime', MagicMock()) + now_mock.utcnow = lambda: now.datetime + freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) @@ -57,7 +62,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'trade_id': 1, 'pair': 'ETH/BTC', 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', - 'date': 'just now', + 'open_date': now, 'open_rate': 1.099e-05, 'close_rate': None, 'current_rate': 1.098e-05, @@ -68,38 +73,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: } == results[0] -def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: - """ - Test rpc_status_table() method - """ - patch_get_signal(mocker, (True, False)) - patch_coinmarketcap(mocker) - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', - validate_pairs=MagicMock(), - get_ticker=ticker, - get_fee=fee - ) - - freqtradebot = FreqtradeBot(default_conf) - rpc = RPC(freqtradebot) - - freqtradebot.state = State.STOPPED - with pytest.raises(RPCException, match=r'.*trader is not running*'): - rpc._rpc_status_table() - - freqtradebot.state = State.RUNNING - with pytest.raises(RPCException, match=r'.*no active order*'): - rpc._rpc_status_table() - - freqtradebot.create_trade() - result = rpc._rpc_status_table() - assert 'just now' in result['Since'].all() - assert 'ETH/BTC' in result['Pair'].all() - assert '-0.59%' in result['Profit'].all() - - def test_rpc_daily_profit(default_conf, update, ticker, fee, limit_buy_order, limit_sell_order, mocker) -> None: """ diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 97afe1aac..6bf76ce8a 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -213,7 +213,7 @@ def test_status(default_conf, update, mocker, fee, ticker) -> None: 'trade_id': 1, 'pair': 'ETH/BTC', 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', - 'date': 'just now', + 'open_date': datetime.utcnow(), 'open_rate': 1.099e-05, 'close_rate': None, 'current_rate': 1.098e-05, @@ -289,7 +289,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0] -def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: +def test_status_table_handle(default_conf, limit_buy_order, update, ticker, fee, mocker) -> None: """ Test _status_table() method """ @@ -299,7 +299,8 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), get_ticker=ticker, - buy=MagicMock(return_value={'id': 'mocked_order_id'}), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_order=MagicMock(return_value=limit_buy_order), get_fee=fee, ) msg_mock = MagicMock() @@ -324,7 +325,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: freqtradebot.state = State.RUNNING telegram._status_table(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 - assert 'no active order' in msg_mock.call_args_list[0][0][0] + assert 'no active trade' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data