diff --git a/freqtrade/rpc/__init__.py b/freqtrade/rpc/__init__.py index 6f1bea53f..24c9b754e 100644 --- a/freqtrade/rpc/__init__.py +++ b/freqtrade/rpc/__init__.py @@ -1,11 +1,13 @@ import logging import re import arrow +from datetime import datetime, timedelta from pandas import DataFrame from freqtrade.persistence import Trade from freqtrade.misc import State, get_state from freqtrade import exchange +from freqtrade.fiat_convert import CryptoToFiatConverter from . import telegram logger = logging.getLogger(__name__) @@ -142,3 +144,41 @@ def rpc_status_table(): # Another approach would be to just return the # result, or raise error return (False, df_statuses) + + +def rpc_daily_profit(timescale, stake_currency, fiat_display_currency): + today = datetime.utcnow().date() + profit_days = {} + + if not (isinstance(timescale, int) and timescale > 0): + return (True, '*Daily [n]:* `must be an integer greater than 0`') + + # FIX: we might not want to call CryptoToFiatConverter, for every call + fiat = CryptoToFiatConverter() + for day in range(0, timescale): + profitday = today - timedelta(days=day) + trades = Trade.query \ + .filter(Trade.is_open.is_(False)) \ + .filter(Trade.close_date >= profitday)\ + .filter(Trade.close_date < (profitday + timedelta(days=1)))\ + .order_by(Trade.close_date)\ + .all() + curdayprofit = sum(trade.calc_profit() for trade in trades) + profit_days[profitday] = format(curdayprofit, '.8f') + + stats = [ + [ + key, + '{value:.8f} {symbol}'.format(value=float(value), symbol=stake_currency), + '{value:.3f} {symbol}'.format( + value=fiat.convert_amount( + value, + stake_currency, + fiat_display_currency + ), + symbol=fiat_display_currency + ) + ] + for key, value in profit_days.items() + ] + return (False, stats) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index d15f971c2..82bbec7be 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1,5 +1,5 @@ import logging -from datetime import datetime, timedelta +from datetime import timedelta from decimal import Decimal from typing import Any, Callable @@ -10,7 +10,7 @@ from telegram import Bot, ParseMode, ReplyKeyboardMarkup, Update from telegram.error import NetworkError, TelegramError from telegram.ext import CommandHandler, Updater -from freqtrade.rpc.__init__ import rpc_status_table, rpc_trade_status +from freqtrade.rpc.__init__ import rpc_status_table, rpc_trade_status, rpc_daily_profit from freqtrade import __version__, exchange from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.misc import State, get_state, update_state @@ -166,54 +166,26 @@ def _daily(bot: Bot, update: Update) -> None: :param update: message update :return: None """ - today = datetime.utcnow().date() - profit_days = {} - try: timescale = int(update.message.text.replace('/daily', '').strip()) except (TypeError, ValueError): timescale = 7 - - if not (isinstance(timescale, int) and timescale > 0): - send_msg('*Daily [n]:* `must be an integer greater than 0`', bot=bot) - return - - for day in range(0, timescale): - profitday = today - timedelta(days=day) - trades = Trade.query \ - .filter(Trade.is_open.is_(False)) \ - .filter(Trade.close_date >= profitday)\ - .filter(Trade.close_date < (profitday + timedelta(days=1)))\ - .order_by(Trade.close_date)\ - .all() - curdayprofit = sum(trade.calc_profit() for trade in trades) - profit_days[profitday] = format(curdayprofit, '.8f') - - stats = [ - [ - key, - '{value:.8f} {symbol}'.format(value=float(value), symbol=_CONF['stake_currency']), - '{value:.3f} {symbol}'.format( - value=_FIAT_CONVERT.convert_amount( - value, - _CONF['stake_currency'], - _CONF['fiat_display_currency'] - ), - symbol=_CONF['fiat_display_currency'] - ) - ] - for key, value in profit_days.items() - ] - stats = tabulate(stats, - headers=[ - 'Day', - 'Profit {}'.format(_CONF['stake_currency']), - 'Profit {}'.format(_CONF['fiat_display_currency']) - ], - tablefmt='simple') - - message = 'Daily Profit over the last {} days:\n
{}'.format(timescale, stats) - send_msg(message, bot=bot, parse_mode=ParseMode.HTML) + (error, stats) = rpc_daily_profit(timescale, + _CONF['stake_currency'], + _CONF['fiat_display_currency']) + if error: + send_msg(stats, bot=bot) + else: + stats = tabulate(stats, + headers=[ + 'Day', + 'Profit {}'.format(_CONF['stake_currency']), + 'Profit {}'.format(_CONF['fiat_display_currency']) + ], + tablefmt='simple') + message = 'Daily Profit over the last {} days:\n
{}'.format( + timescale, stats) + send_msg(message, bot=bot, parse_mode=ParseMode.HTML) @authorized_only diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index a8d00551d..199a3a99c 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -1,9 +1,11 @@ # pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103 +from datetime import datetime from copy import deepcopy from unittest.mock import MagicMock +from sqlalchemy import create_engine from freqtrade.rpc import init, cleanup, send_msg -from sqlalchemy import create_engine +from freqtrade.persistence import Trade import freqtrade.main as main import freqtrade.misc as misc import freqtrade.rpc as rpc @@ -88,3 +90,55 @@ def test_rpc_trade_status(default_conf, update, ticker, mocker): assert not error trade = result[0] assert trade.find('[BTC_ETH]') >= 0 + + +def test_rpc_daily_profit(default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker): + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) + mocker.patch.multiple('freqtrade.rpc.telegram', + _CONF=default_conf, + init=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker) + mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', + ticker=MagicMock(return_value={'price_usd': 15000.0}), + _cache_symbols=MagicMock(return_value={'BTC': 1})) + mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) + main.init(default_conf, create_engine('sqlite://')) + stake_currency = default_conf['stake_currency'] + fiat_display_currency = default_conf['fiat_display_currency'] + + # Create some test data + main.create_trade(0.001) + trade = Trade.query.first() + assert trade + + # Simulate buy & sell + trade.update(limit_buy_order) + trade.update(limit_sell_order) + trade.close_date = datetime.utcnow() + trade.is_open = False + + # Try valid data + update.message.text = '/daily 2' + (error, days) = rpc.rpc_daily_profit(7, stake_currency, + fiat_display_currency) + assert not error + assert len(days) == 7 + for day in days: + # [datetime.date(2018, 1, 11), '0.00000000 BTC', '0.000 USD'] + assert (day[1] == '0.00000000 BTC' or + day[1] == '0.00006217 BTC') + + assert (day[2] == '0.000 USD' or + day[2] == '0.933 USD') + # ensure first day is current date + assert str(days[0][0]) == str(datetime.utcnow().date()) + + # Try invalid data + (error, days) = rpc.rpc_daily_profit(0, stake_currency, + fiat_display_currency) + assert error + assert days.find('must be an integer greater than 0') >= 0