Add /weekly and /monthly to Telegram RPC
/weekly now list weeks starting from monday instead of rolling weeks. /monthly now list months starting from the 1st. Signed-off-by: Antoine Merino <antoine.merino.dev@gmail.com>
This commit is contained in:
parent
5f40158c0b
commit
459ff9692d
@ -4,12 +4,12 @@ This module contains class to define a RPC communications
|
|||||||
import logging
|
import logging
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from datetime import date, datetime, timedelta, timezone
|
from datetime import date, datetime, timedelta, timezone
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
from math import isnan
|
from math import isnan
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import psutil
|
import psutil
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
from numpy import NAN, inf, int64, mean
|
from numpy import NAN, inf, int64, mean
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
@ -294,13 +294,14 @@ class RPC:
|
|||||||
self, timescale: int,
|
self, timescale: int,
|
||||||
stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
|
stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
|
||||||
today = datetime.utcnow().date()
|
today = datetime.utcnow().date()
|
||||||
|
first_iso_day_of_week = today - timedelta(days=today.weekday()) # Monday
|
||||||
profit_weeks: Dict[date, Dict] = {}
|
profit_weeks: Dict[date, Dict] = {}
|
||||||
|
|
||||||
if not (isinstance(timescale, int) and timescale > 0):
|
if not (isinstance(timescale, int) and timescale > 0):
|
||||||
raise RPCException('timescale must be an integer greater than 0')
|
raise RPCException('timescale must be an integer greater than 0')
|
||||||
|
|
||||||
for week in range(0, timescale):
|
for week in range(0, timescale):
|
||||||
profitweek = today - timedelta(weeks=week)
|
profitweek = first_iso_day_of_week - timedelta(weeks=week)
|
||||||
trades = Trade.get_trades(trade_filter=[
|
trades = Trade.get_trades(trade_filter=[
|
||||||
Trade.is_open.is_(False),
|
Trade.is_open.is_(False),
|
||||||
Trade.close_date >= profitweek,
|
Trade.close_date >= profitweek,
|
||||||
@ -335,14 +336,14 @@ class RPC:
|
|||||||
def _rpc_monthly_profit(
|
def _rpc_monthly_profit(
|
||||||
self, timescale: int,
|
self, timescale: int,
|
||||||
stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
|
stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
|
||||||
today = datetime.utcnow().date()
|
first_day_of_month = datetime.utcnow().date().replace(day=1)
|
||||||
profit_months: Dict[date, Dict] = {}
|
profit_months: Dict[date, Dict] = {}
|
||||||
|
|
||||||
if not (isinstance(timescale, int) and timescale > 0):
|
if not (isinstance(timescale, int) and timescale > 0):
|
||||||
raise RPCException('timescale must be an integer greater than 0')
|
raise RPCException('timescale must be an integer greater than 0')
|
||||||
|
|
||||||
for month in range(0, timescale):
|
for month in range(0, timescale):
|
||||||
profitmonth = today - relativedelta(months=month)
|
profitmonth = first_day_of_month - relativedelta(months=month)
|
||||||
trades = Trade.get_trades(trade_filter=[
|
trades = Trade.get_trades(trade_filter=[
|
||||||
Trade.is_open.is_(False),
|
Trade.is_open.is_(False),
|
||||||
Trade.close_date >= profitmonth,
|
Trade.close_date >= profitmonth,
|
||||||
@ -357,7 +358,7 @@ class RPC:
|
|||||||
|
|
||||||
data = [
|
data = [
|
||||||
{
|
{
|
||||||
'date': key,
|
'date': f"{key.year}-{key.month}",
|
||||||
'abs_profit': value["amount"],
|
'abs_profit': value["amount"],
|
||||||
'fiat_value': self._fiat_converter.convert_amount(
|
'fiat_value': self._fiat_converter.convert_amount(
|
||||||
value['amount'],
|
value['amount'],
|
||||||
|
@ -492,7 +492,7 @@ class Telegram(RPCHandler):
|
|||||||
f"{day['fiat_value']:.3f} {stats['fiat_display_currency']}",
|
f"{day['fiat_value']:.3f} {stats['fiat_display_currency']}",
|
||||||
f"{day['trade_count']} trades"] for day in stats['data']],
|
f"{day['trade_count']} trades"] for day in stats['data']],
|
||||||
headers=[
|
headers=[
|
||||||
'Month',
|
'Day',
|
||||||
f'Profit {stake_cur}',
|
f'Profit {stake_cur}',
|
||||||
f'Profit {fiat_disp_cur}',
|
f'Profit {fiat_disp_cur}',
|
||||||
'Trades',
|
'Trades',
|
||||||
@ -531,13 +531,14 @@ class Telegram(RPCHandler):
|
|||||||
f"{week['fiat_value']:.3f} {stats['fiat_display_currency']}",
|
f"{week['fiat_value']:.3f} {stats['fiat_display_currency']}",
|
||||||
f"{week['trade_count']} trades"] for week in stats['data']],
|
f"{week['trade_count']} trades"] for week in stats['data']],
|
||||||
headers=[
|
headers=[
|
||||||
'Week',
|
'Monday',
|
||||||
f'Profit {stake_cur}',
|
f'Profit {stake_cur}',
|
||||||
f'Profit {fiat_disp_cur}',
|
f'Profit {fiat_disp_cur}',
|
||||||
'Trades',
|
'Trades',
|
||||||
],
|
],
|
||||||
tablefmt='simple')
|
tablefmt='simple')
|
||||||
message = f'<b>Weekly Profit over the last {timescale} weeks</b>:\n<pre>{stats_tab}</pre>'
|
message = f'<b>Weekly Profit over the last {timescale} weeks ' \
|
||||||
|
f'(starting from Monday)</b>:\n<pre>{stats_tab}</pre> '
|
||||||
self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True,
|
self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True,
|
||||||
callback_path="update_weekly", query=update.callback_query)
|
callback_path="update_weekly", query=update.callback_query)
|
||||||
except RPCException as e:
|
except RPCException as e:
|
||||||
@ -576,7 +577,8 @@ class Telegram(RPCHandler):
|
|||||||
'Trades',
|
'Trades',
|
||||||
],
|
],
|
||||||
tablefmt='simple')
|
tablefmt='simple')
|
||||||
message = f'<b>Monthly Profit over the last {timescale} months</b>:\n<pre>{stats_tab}</pre>'
|
message = f'<b>Monthly Profit over the last {timescale} months' \
|
||||||
|
f'</b>:\n<pre>{stats_tab}</pre> '
|
||||||
self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True,
|
self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True,
|
||||||
callback_path="update_monthly", query=update.callback_query)
|
callback_path="update_monthly", query=update.callback_query)
|
||||||
except RPCException as e:
|
except RPCException as e:
|
||||||
|
@ -43,4 +43,6 @@ colorama==0.4.4
|
|||||||
questionary==1.10.0
|
questionary==1.10.0
|
||||||
prompt-toolkit==3.0.21
|
prompt-toolkit==3.0.21
|
||||||
|
|
||||||
|
# Extensions to datetime library
|
||||||
|
types-python-dateutil==2.8.2
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
@ -5,7 +5,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from random import choice, randint
|
from random import choice, randint
|
||||||
from string import ascii_uppercase
|
from string import ascii_uppercase
|
||||||
@ -13,6 +12,7 @@ from unittest.mock import ANY, MagicMock
|
|||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import pytest
|
import pytest
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
from telegram import Chat, Message, ReplyKeyboardMarkup, Update
|
from telegram import Chat, Message, ReplyKeyboardMarkup, Update
|
||||||
from telegram.error import BadRequest, NetworkError, TelegramError
|
from telegram.error import BadRequest, NetworkError, TelegramError
|
||||||
|
|
||||||
@ -356,7 +356,8 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
context.args = ["2"]
|
context.args = ["2"]
|
||||||
telegram._daily(update=update, context=context)
|
telegram._daily(update=update, context=context)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'Daily' in msg_mock.call_args_list[0][0][0]
|
assert "Daily Profit over the last 2 days</b>:" in msg_mock.call_args_list[0][0][0]
|
||||||
|
assert 'Day ' in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
||||||
@ -368,7 +369,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
context.args = []
|
context.args = []
|
||||||
telegram._daily(update=update, context=context)
|
telegram._daily(update=update, context=context)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'Daily' in msg_mock.call_args_list[0][0][0]
|
assert "Daily Profit over the last 7 days</b>:" in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
||||||
@ -424,7 +425,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
|||||||
context = MagicMock()
|
context = MagicMock()
|
||||||
context.args = ["today"]
|
context.args = ["today"]
|
||||||
telegram._daily(update=update, context=context)
|
telegram._daily(update=update, context=context)
|
||||||
assert str('Daily Profit over the last 7 days') in msg_mock.call_args_list[0][0][0]
|
assert str('Daily Profit over the last 7 days</b>:') in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
||||||
@ -464,8 +465,12 @@ def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
context.args = ["2"]
|
context.args = ["2"]
|
||||||
telegram._weekly(update=update, context=context)
|
telegram._weekly(update=update, context=context)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'Weekly' in msg_mock.call_args_list[0][0][0]
|
assert "Weekly Profit over the last 2 weeks (starting from Monday)</b>:" \
|
||||||
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
in msg_mock.call_args_list[0][0][0]
|
||||||
|
assert 'Monday ' in msg_mock.call_args_list[0][0][0]
|
||||||
|
today = datetime.utcnow().date()
|
||||||
|
first_iso_day_of_current_week = today - timedelta(days=today.weekday())
|
||||||
|
assert str(first_iso_day_of_current_week) in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
|
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
|
||||||
@ -476,8 +481,9 @@ def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
context.args = []
|
context.args = []
|
||||||
telegram._weekly(update=update, context=context)
|
telegram._weekly(update=update, context=context)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
|
assert "Weekly Profit over the last 8 weeks (starting from Monday)</b>:" \
|
||||||
|
in msg_mock.call_args_list[0][0][0]
|
||||||
assert 'Weekly' in msg_mock.call_args_list[0][0][0]
|
assert 'Weekly' in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
|
||||||
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
|
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
|
||||||
@ -521,8 +527,8 @@ def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
context.args = ["10"]
|
context.args = ["10"]
|
||||||
telegram._weekly(update=update, context=context)
|
telegram._weekly(update=update, context=context)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
|
assert "Weekly Profit over the last 10 weeks (starting from Monday)</b>:" \
|
||||||
a = msg_mock.call_args_list[0][0][0]
|
in msg_mock.call_args_list[0][0][0]
|
||||||
# Now, the time-shifted trade should be included
|
# Now, the time-shifted trade should be included
|
||||||
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
||||||
@ -551,11 +557,12 @@ def test_weekly_wrong_input(default_conf, update, ticker, mocker) -> None:
|
|||||||
# Try invalid data
|
# Try invalid data
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
freqtradebot.state = State.RUNNING
|
freqtradebot.state = State.RUNNING
|
||||||
# /daily today
|
# /weekly this week
|
||||||
context = MagicMock()
|
context = MagicMock()
|
||||||
context.args = ["this week"]
|
context.args = ["this week"]
|
||||||
telegram._weekly(update=update, context=context)
|
telegram._weekly(update=update, context=context)
|
||||||
assert str('Weekly Profit over the last 8 weeks') in msg_mock.call_args_list[0][0][0]
|
assert str('Weekly Profit over the last 8 weeks (starting from Monday)</b>:') \
|
||||||
|
in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
||||||
@ -595,8 +602,11 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
context.args = ["2"]
|
context.args = ["2"]
|
||||||
telegram._monthly(update=update, context=context)
|
telegram._monthly(update=update, context=context)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'Monthly' in msg_mock.call_args_list[0][0][0]
|
assert 'Monthly Profit over the last 2 months</b>:' in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
assert 'Month ' in msg_mock.call_args_list[0][0][0]
|
||||||
|
today = datetime.utcnow().date()
|
||||||
|
current_month = f"{today.year}-{today.month} "
|
||||||
|
assert current_month in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
|
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
|
||||||
@ -607,8 +617,9 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
context.args = []
|
context.args = []
|
||||||
telegram._monthly(update=update, context=context)
|
telegram._monthly(update=update, context=context)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'Monthly' in msg_mock.call_args_list[0][0][0]
|
assert 'Monthly Profit over the last 6 months</b>:' in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
assert 'Month ' in msg_mock.call_args_list[0][0][0]
|
||||||
|
assert current_month in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
|
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
|
||||||
@ -632,7 +643,7 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
trades[0].open_date = datetime.utcnow() - relativedelta(months=6, days=5)
|
trades[0].open_date = datetime.utcnow() - relativedelta(months=6, days=5)
|
||||||
trades[0].close_date = datetime.utcnow() - relativedelta(months=6, days=3)
|
trades[0].close_date = datetime.utcnow() - relativedelta(months=6, days=3)
|
||||||
|
|
||||||
# /weekly
|
# /monthly
|
||||||
# By default, the 6 previous months are shown
|
# By default, the 6 previous months are shown
|
||||||
# So the previous modified trade should be excluded from the stats
|
# So the previous modified trade should be excluded from the stats
|
||||||
context = MagicMock()
|
context = MagicMock()
|
||||||
@ -653,13 +664,41 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
telegram._monthly(update=update, context=context)
|
telegram._monthly(update=update, context=context)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
|
|
||||||
a = msg_mock.call_args_list[0][0][0]
|
|
||||||
# Now, the time-shifted trade should be included
|
# Now, the time-shifted trade should be included
|
||||||
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
|
||||||
assert str(' 1 trades') in msg_mock.call_args_list[0][0][0]
|
assert str(' 1 trades') in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_monthly_wrong_input(default_conf, update, ticker, mocker) -> None:
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
fetch_ticker=ticker
|
||||||
|
)
|
||||||
|
|
||||||
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
|
# Try invalid data
|
||||||
|
msg_mock.reset_mock()
|
||||||
|
freqtradebot.state = State.RUNNING
|
||||||
|
# /daily -2
|
||||||
|
context = MagicMock()
|
||||||
|
context.args = ["-3"]
|
||||||
|
telegram._monthly(update=update, context=context)
|
||||||
|
assert msg_mock.call_count == 1
|
||||||
|
assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
# Try invalid data
|
||||||
|
msg_mock.reset_mock()
|
||||||
|
freqtradebot.state = State.RUNNING
|
||||||
|
# /monthly february
|
||||||
|
context = MagicMock()
|
||||||
|
context.args = ["february"]
|
||||||
|
telegram._monthly(update=update, context=context)
|
||||||
|
assert str('Monthly Profit over the last 6 months</b>:') in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
|
Loading…
Reference in New Issue
Block a user