telegram refactor /4

move out rpc_trade_statistics
This commit is contained in:
kryofly 2018-01-17 14:31:28 +01:00
parent 112913531c
commit bce00aeaad
3 changed files with 197 additions and 71 deletions

View File

@ -1,8 +1,10 @@
import logging import logging
import re import re
import arrow import arrow
from decimal import Decimal
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pandas import DataFrame from pandas import DataFrame
import sqlalchemy as sql
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.misc import State, get_state from freqtrade.misc import State, get_state
@ -182,3 +184,114 @@ def rpc_daily_profit(timescale, stake_currency, fiat_display_currency):
for key, value in profit_days.items() for key, value in profit_days.items()
] ]
return (False, stats) return (False, stats)
def rpc_trade_statistics(stake_currency, fiat_display_currency) -> None:
"""
:return: cumulative profit statistics.
"""
trades = Trade.query.order_by(Trade.id).all()
profit_all_coin = []
profit_all_percent = []
profit_closed_coin = []
profit_closed_percent = []
durations = []
for trade in trades:
current_rate = None
if not trade.open_rate:
continue
if trade.close_date:
durations.append((trade.close_date - trade.open_date).total_seconds())
if not trade.is_open:
profit_percent = trade.calc_profit_percent()
profit_closed_coin.append(trade.calc_profit())
profit_closed_percent.append(profit_percent)
else:
# Get current rate
current_rate = exchange.get_ticker(trade.pair, False)['bid']
profit_percent = trade.calc_profit_percent(rate=current_rate)
profit_all_coin.append(trade.calc_profit(rate=Decimal(trade.close_rate or current_rate)))
profit_all_percent.append(profit_percent)
best_pair = Trade.session.query(Trade.pair,
sql.func.sum(Trade.close_profit).label('profit_sum')) \
.filter(Trade.is_open.is_(False)) \
.group_by(Trade.pair) \
.order_by(sql.text('profit_sum DESC')) \
.first()
if not best_pair:
return (True, '*Status:* `no closed trade`')
bp_pair, bp_rate = best_pair
# FIX: we want to keep fiatconverter in a state/environment,
# doing this will utilize its caching functionallity, instead we reinitialize it here
fiat = CryptoToFiatConverter()
# Prepare data to display
profit_closed_coin = round(sum(profit_closed_coin), 8)
profit_closed_percent = round(sum(profit_closed_percent) * 100, 2)
profit_closed_fiat = fiat.convert_amount(
profit_closed_coin,
stake_currency,
fiat_display_currency
)
profit_all_coin = round(sum(profit_all_coin), 8)
profit_all_percent = round(sum(profit_all_percent) * 100, 2)
profit_all_fiat = fiat.convert_amount(
profit_all_coin,
stake_currency,
fiat_display_currency
)
return (False,
{'profit_closed_coin': profit_closed_coin,
'profit_closed_percent': profit_closed_percent,
'profit_closed_fiat': profit_closed_fiat,
'profit_all_coin': profit_all_coin,
'profit_all_percent': profit_all_percent,
'profit_all_fiat': profit_all_fiat,
'trade_count': len(trades),
'first_trade_date': arrow.get(trades[0].open_date).humanize(),
'latest_trade_date': arrow.get(trades[-1].open_date).humanize(),
'avg_duration': str(timedelta(seconds=sum(durations) /
float(len(durations)))).split('.')[0],
'best_pair': bp_pair,
'best_rate': round(bp_rate * 100, 2)
})
# Message to display
markdown_msg = """
*ROI:* Close trades
`{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)`
`{profit_closed_fiat:.3f} {fiat}`
*ROI:* All trades
`{profit_all_coin:.8f} {coin} ({profit_all_percent:.2f}%)`
`{profit_all_fiat:.3f} {fiat}`
*Total Trade Count:* `{trade_count}`
*First Trade opened:* `{first_trade_date}`
*Latest Trade opened:* `{latest_trade_date}`
*Avg. Duration:* `{avg_duration}`
*Best Performing:* `{best_pair}: {best_rate:.2f}%`
""".format(
coin=stake_currency,
fiat=fiat_display_currency,
profit_closed_coin=profit_closed_coin,
profit_closed_percent=profit_closed_percent,
profit_closed_fiat=profit_closed_fiat,
profit_all_coin=profit_all_coin,
profit_all_percent=profit_all_percent,
profit_all_fiat=profit_all_fiat,
trade_count=len(trades),
first_trade_date=arrow.get(trades[0].open_date).humanize(),
latest_trade_date=arrow.get(trades[-1].open_date).humanize(),
avg_duration=str(timedelta(seconds=sum(durations) / float(len(durations)))).split('.')[0],
best_pair=bp_pair,
best_rate=round(bp_rate * 100, 2),
)
return markdown_msg

View File

@ -1,16 +1,18 @@
import logging import logging
from datetime import timedelta
from decimal import Decimal
from typing import Any, Callable from typing import Any, Callable
import arrow
from sqlalchemy import and_, func, text from sqlalchemy import and_, func, text
from tabulate import tabulate from tabulate import tabulate
from telegram import Bot, ParseMode, ReplyKeyboardMarkup, Update from telegram import Bot, ParseMode, ReplyKeyboardMarkup, Update
from telegram.error import NetworkError, TelegramError from telegram.error import NetworkError, TelegramError
from telegram.ext import CommandHandler, Updater from telegram.ext import CommandHandler, Updater
from freqtrade.rpc.__init__ import rpc_status_table, rpc_trade_status, rpc_daily_profit from freqtrade.rpc.__init__ import (rpc_status_table,
rpc_trade_status,
rpc_daily_profit,
rpc_trade_statistics
)
from freqtrade import __version__, exchange from freqtrade import __version__, exchange
from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.misc import State, get_state, update_state from freqtrade.misc import State, get_state, update_state
@ -197,63 +199,12 @@ def _profit(bot: Bot, update: Update) -> None:
:param update: message update :param update: message update
:return: None :return: None
""" """
trades = Trade.query.order_by(Trade.id).all() (error, stats) = rpc_trade_statistics(_CONF['stake_currency'],
_CONF['fiat_display_currency'])
profit_all_coin = [] if error:
profit_all_percent = [] send_msg(stats, bot=bot)
profit_closed_coin = []
profit_closed_percent = []
durations = []
for trade in trades:
current_rate = None
if not trade.open_rate:
continue
if trade.close_date:
durations.append((trade.close_date - trade.open_date).total_seconds())
if not trade.is_open:
profit_percent = trade.calc_profit_percent()
profit_closed_coin.append(trade.calc_profit())
profit_closed_percent.append(profit_percent)
else:
# Get current rate
current_rate = exchange.get_ticker(trade.pair, False)['bid']
profit_percent = trade.calc_profit_percent(rate=current_rate)
profit_all_coin.append(trade.calc_profit(rate=Decimal(trade.close_rate or current_rate)))
profit_all_percent.append(profit_percent)
best_pair = Trade.session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \
.filter(Trade.is_open.is_(False)) \
.group_by(Trade.pair) \
.order_by(text('profit_sum DESC')) \
.first()
if not best_pair:
send_msg('*Status:* `no closed trade`', bot=bot)
return return
bp_pair, bp_rate = best_pair
# Prepare data to display
profit_closed_coin = round(sum(profit_closed_coin), 8)
profit_closed_percent = round(sum(profit_closed_percent) * 100, 2)
profit_closed_fiat = _FIAT_CONVERT.convert_amount(
profit_closed_coin,
_CONF['stake_currency'],
_CONF['fiat_display_currency']
)
profit_all_coin = round(sum(profit_all_coin), 8)
profit_all_percent = round(sum(profit_all_percent) * 100, 2)
profit_all_fiat = _FIAT_CONVERT.convert_amount(
profit_all_coin,
_CONF['stake_currency'],
_CONF['fiat_display_currency']
)
# Message to display
markdown_msg = """ markdown_msg = """
*ROI:* Close trades *ROI:* Close trades
`{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)` `{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)`
@ -270,18 +221,18 @@ def _profit(bot: Bot, update: Update) -> None:
""".format( """.format(
coin=_CONF['stake_currency'], coin=_CONF['stake_currency'],
fiat=_CONF['fiat_display_currency'], fiat=_CONF['fiat_display_currency'],
profit_closed_coin=profit_closed_coin, profit_closed_coin=stats['profit_closed_coin'],
profit_closed_percent=profit_closed_percent, profit_closed_percent=stats['profit_closed_percent'],
profit_closed_fiat=profit_closed_fiat, profit_closed_fiat=stats['profit_closed_fiat'],
profit_all_coin=profit_all_coin, profit_all_coin=stats['profit_all_coin'],
profit_all_percent=profit_all_percent, profit_all_percent=stats['profit_all_percent'],
profit_all_fiat=profit_all_fiat, profit_all_fiat=stats['profit_all_fiat'],
trade_count=len(trades), trade_count=stats['trade_count'],
first_trade_date=arrow.get(trades[0].open_date).humanize(), first_trade_date=stats['first_trade_date'],
latest_trade_date=arrow.get(trades[-1].open_date).humanize(), latest_trade_date=stats['latest_trade_date'],
avg_duration=str(timedelta(seconds=sum(durations) / float(len(durations)))).split('.')[0], avg_duration=stats['avg_duration'],
best_pair=bp_pair, best_pair=stats['best_pair'],
best_rate=round(bp_rate * 100, 2), best_rate=stats['best_rate']
) )
send_msg(markdown_msg, bot=bot) send_msg(markdown_msg, bot=bot)

View File

@ -11,6 +11,13 @@ import freqtrade.misc as misc
import freqtrade.rpc as rpc import freqtrade.rpc as rpc
def prec_satoshi(a, b):
"""
:return: True if A and B differs less than one satoshi.
"""
return abs(a - b) < 0.00000001
def test_init_telegram_enabled(default_conf, mocker): def test_init_telegram_enabled(default_conf, mocker):
module_list = [] module_list = []
mocker.patch('freqtrade.rpc.REGISTERED_MODULES', module_list) mocker.patch('freqtrade.rpc.REGISTERED_MODULES', module_list)
@ -142,3 +149,58 @@ def test_rpc_daily_profit(default_conf, update, ticker, limit_buy_order, limit_s
fiat_display_currency) fiat_display_currency)
assert error assert error
assert days.find('must be an integer greater than 0') >= 0 assert days.find('must be an integer greater than 0') >= 0
def test_rpc_trade_statistics(
default_conf, update, ticker, ticker_sell_up, 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())
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']
(error, stats) = rpc.rpc_trade_statistics(stake_currency,
fiat_display_currency)
assert error
assert stats.find('no closed trade') >= 0
# Create some test data
main.create_trade(0.001)
trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
# Update the ticker with a market going up
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_up)
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
(error, stats) = rpc.rpc_trade_statistics(stake_currency,
fiat_display_currency)
assert not error
assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05)
assert prec_satoshi(stats['profit_closed_percent'], 6.2)
assert prec_satoshi(stats['profit_closed_fiat'], 0.93255)
assert prec_satoshi(stats['profit_all_coin'], 6.217e-05)
assert prec_satoshi(stats['profit_all_percent'], 6.2)
assert prec_satoshi(stats['profit_all_fiat'], 0.93255)
assert stats['trade_count'] == 1
assert stats['first_trade_date'] == 'just now'
assert stats['latest_trade_date'] == 'just now'
assert stats['avg_duration'] == '0:00:00'
assert stats['best_pair'] == 'BTC_ETH'
assert prec_satoshi(stats['best_rate'], 6.2)