Merge pull request #171 from stephendade/dailymsg

Added daily profit telegram command
This commit is contained in:
Samuel Husso 2017-12-12 19:42:31 +02:00 committed by GitHub
commit cb09cabbdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 5 deletions

View File

@ -25,6 +25,7 @@ Persistence is achieved through sqlite.
* /forcesell <trade_id>|all: Instantly sells the given trade (Ignoring `minimum_roi`). * /forcesell <trade_id>|all: Instantly sells the given trade (Ignoring `minimum_roi`).
* /performance: Show performance of each finished trade grouped by pair * /performance: Show performance of each finished trade grouped by pair
* /balance: Show account balance per currency * /balance: Show account balance per currency
* /daily <n>: Shows profit or loss per day, over the last n days
* /help: Show help message * /help: Show help message
* /version: Show version * /version: Show version

View File

@ -1,11 +1,11 @@
import logging import logging
import re import re
from datetime import timedelta from datetime import timedelta, date
from typing import Callable, Any from typing import Callable, Any
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
from sqlalchemy import and_, func, text from sqlalchemy import and_, func, text, between
from tabulate import tabulate from tabulate import tabulate
from telegram import ParseMode, Bot, Update, ReplyKeyboardMarkup from telegram import ParseMode, Bot, Update, ReplyKeyboardMarkup
from telegram.error import NetworkError, TelegramError from telegram.error import NetworkError, TelegramError
@ -49,6 +49,7 @@ def init(config: dict) -> None:
CommandHandler('stop', _stop), CommandHandler('stop', _stop),
CommandHandler('forcesell', _forcesell), CommandHandler('forcesell', _forcesell),
CommandHandler('performance', _performance), CommandHandler('performance', _performance),
CommandHandler('daily', _daily),
CommandHandler('count', _count), CommandHandler('count', _count),
CommandHandler('help', _help), CommandHandler('help', _help),
CommandHandler('version', _version), CommandHandler('version', _version),
@ -206,6 +207,46 @@ def _status_table(bot: Bot, update: Update) -> None:
send_msg(message, parse_mode=ParseMode.HTML) send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only
def _daily(bot: Bot, update: Update) -> None:
"""
Handler for /daily <n>
Returns a daily profit (in BTC) over the last n 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 = int(update.message.text.replace('/daily', '').strip())
except:
send_msg('*Daily <n>:* `must be an integer greater than 0`', bot=bot)
return
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):
#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 = []
for key, value in profit_days.items():
stats.append([key, str(value) + ' BTC'])
stats = tabulate(stats, headers=['Day', 'Profit'], tablefmt='simple')
message = '<b>Daily Profit over the last {} days</b>:\n<pre>{}</pre>'.format(timescale, stats)
send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
@authorized_only @authorized_only
def _profit(bot: Bot, update: Update) -> None: def _profit(bot: Bot, update: Update) -> None:
@ -431,6 +472,7 @@ def _help(bot: Bot, update: Update) -> None:
*/profit:* `Lists cumulative profit from all finished trades` */profit:* `Lists cumulative profit from all finished trades`
*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, regardless of profit` */forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, regardless of profit`
*/performance:* `Show performance of each finished trade grouped by pair` */performance:* `Show performance of each finished trade grouped by pair`
*/daily <n>:* `Shows profit or loss per day, over the last n days`
*/count:* `Show number of trades running compared to allowed number of trades` */count:* `Show number of trades running compared to allowed number of trades`
*/balance:* `Show account balance per currency` */balance:* `Show account balance per currency`
*/help:* `This help message` */help:* `This help message`
@ -476,7 +518,7 @@ def send_msg(msg: str, bot: Bot = None, parse_mode: ParseMode = ParseMode.MARKDO
bot = bot or _UPDATER.bot bot = bot or _UPDATER.bot
keyboard = [['/status table', '/profit', '/performance', ], keyboard = [['/daily', '/status table', '/profit', '/performance', ],
['/balance', '/status', '/count'], ['/balance', '/status', '/count'],
['/start', '/stop', '/help']] ['/start', '/stop', '/help']]

View File

@ -1,6 +1,6 @@
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103 # pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103
import re import re
from datetime import datetime from datetime import datetime, date
from random import randint from random import randint
from unittest.mock import MagicMock 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.persistence import Trade
from freqtrade.rpc import telegram from freqtrade.rpc import telegram
from freqtrade.rpc.telegram import authorized_only, is_enabled, send_msg, _status, _status_table, \ 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): def test_is_enabled(default_conf, mocker):
@ -316,6 +316,50 @@ def test_performance_handle(
assert 'Performance' in msg_mock.call_args_list[0][0][0] assert 'Performance' in msg_mock.call_args_list[0][0][0]
assert '<code>BTC_ETH\t10.05%</code>' in msg_mock.call_args_list[0][0][0] assert '<code>BTC_ETH\t10.05%</code>' 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
#try valid data
update.message.text = '/daily 7'
_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()) + ' 1.50701325 BTC' in msg_mock.call_args_list[0][0][0]
#try invalid data
msg_mock.reset_mock()
update_state(State.RUNNING)
update.message.text = '/daily'
_daily(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0]
def test_count_handle(default_conf, update, ticker, mocker): def test_count_handle(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)