diff --git a/README.md b/README.md index 28cba0855..af4fdc887 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Persistence is achieved through sqlite. * /forcesell |all: Instantly sells the given trade (Ignoring `minimum_roi`). * /performance: Show performance of each finished trade grouped by pair * /balance: Show account balance per currency +* /daily [n]: Shows profit or loss per day, over the last n (default 7) days * /help: Show help message * /version: Show version diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e2b472119..0ef55efaf 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1,11 +1,11 @@ import logging import re -from datetime import timedelta +from datetime import timedelta, date from typing import Callable, Any import arrow from pandas import DataFrame -from sqlalchemy import and_, func, text +from sqlalchemy import and_, func, text, between from tabulate import tabulate from telegram import ParseMode, Bot, Update, ReplyKeyboardMarkup from telegram.error import NetworkError, TelegramError @@ -49,6 +49,7 @@ def init(config: dict) -> None: CommandHandler('stop', _stop), CommandHandler('forcesell', _forcesell), CommandHandler('performance', _performance), + CommandHandler('daily', _daily), CommandHandler('count', _count), CommandHandler('help', _help), CommandHandler('version', _version), @@ -206,7 +207,44 @@ def _status_table(bot: Bot, update: Update) -> None: send_msg(message, parse_mode=ParseMode.HTML) +@authorized_only +def _daily(bot: Bot, update: Update) -> None: + """ + Handler for /daily + Returns a daily profit (in BTC) over the last n days. + Default is 7 days + :param bot: telegram bot + :param update: message update + :return: None + """ + trades = Trade.query.order_by(Trade.close_date).all() + today = date.today().toordinal() + profit_days = {} + + try: + timescale = update.message.text.replace('/daily', '').strip() + except: + timescale = 7 + for day in range(0, timescale): + #need to query between day+1 and day-1 + nextdate = date.fromordinal(today-day+1) + prevdate = date.fromordinal(today-day-1) + trades = Trade.query.filter(between(Trade.close_date, prevdate, nextdate)).all() + curdayprofit = 0 + for trade in trades: + curdayprofit += trade.close_profit * trade.stake_amount + profit_days[date.fromordinal(today-day)] = curdayprofit + + + stats = '\n'.join('{index}\t{profit} BTC'.format( + index=key, + profit=value, + ) for key, value in profit_days.items()) + + message = 'Daily Profit:\n{}'.format(stats) + send_msg(message, bot=bot) + @authorized_only def _profit(bot: Bot, update: Update) -> None: """ diff --git a/freqtrade/tests/test_rpc_telegram.py b/freqtrade/tests/test_rpc_telegram.py index d8cb43966..359810b8a 100644 --- a/freqtrade/tests/test_rpc_telegram.py +++ b/freqtrade/tests/test_rpc_telegram.py @@ -1,6 +1,6 @@ # pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103 import re -from datetime import datetime +from datetime import datetime, date from random import randint from unittest.mock import MagicMock @@ -14,7 +14,7 @@ from freqtrade.misc import update_state, State, get_state from freqtrade.persistence import Trade from freqtrade.rpc import telegram from freqtrade.rpc.telegram import authorized_only, is_enabled, send_msg, _status, _status_table, \ - _profit, _forcesell, _performance, _count, _start, _stop, _balance, _version, _help + _profit, _forcesell, _performance, _daily, _count, _start, _stop, _balance, _version, _help def test_is_enabled(default_conf, mocker): @@ -316,7 +316,40 @@ def test_performance_handle( assert 'Performance' in msg_mock.call_args_list[0][0][0] assert 'BTC_ETH\t10.05%' in msg_mock.call_args_list[0][0][0] +def test_daily_handle( + 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) + msg_mock = MagicMock() + mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) + mocker.patch.multiple('freqtrade.rpc.telegram', + _CONF=default_conf, + init=MagicMock(), + send_msg=msg_mock) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker) + init(default_conf, create_engine('sqlite://')) + # Create some test data + create_trade(15.0) + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + + _daily(bot=MagicMock(), update=update) + assert msg_mock.call_count == 1 + assert 'Daily' in msg_mock.call_args_list[0][0][0] + assert str(date.today()) + '\t1.50701325 BTC' in msg_mock.call_args_list[0][0][0] + def test_count_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)