From c1c9dd03cec7828e0d35d13960aac36144d917b1 Mon Sep 17 00:00:00 2001 From: gcarq Date: Fri, 15 Dec 2017 23:56:02 +0100 Subject: [PATCH 1/6] /daily: fix identation and simplify loops --- freqtrade/rpc/telegram.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 4b0465ddb..417feeced 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -208,6 +208,7 @@ def _status_table(bot: Bot, update: Update) -> None: send_msg(message, parse_mode=ParseMode.HTML) + @authorized_only def _daily(bot: Bot, update: Update) -> None: """ @@ -217,37 +218,33 @@ def _daily(bot: Bot, update: Update) -> None: :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: + except (TypeError, ValueError): timescale = 5 - + 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)] = format(curdayprofit, '.8f') + # 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 = sum(trade.close_profit * trade.stake_amount for trade in trades) + profit_days[date.fromordinal(today-day)] = format(curdayprofit, '.8f') - stats = [] - for key, value in profit_days.items(): - stats.append([key, str(value) + ' BTC']) + stats = [[key, str(value) + ' BTC'] for key, value in profit_days.items()] stats = tabulate(stats, headers=['Day', 'Profit'], tablefmt='simple') message = 'Daily Profit over the last {} days:\n
{}
'.format(timescale, stats) send_msg(message, bot=bot, parse_mode=ParseMode.HTML) - + + @authorized_only def _profit(bot: Bot, update: Update) -> None: """ @@ -504,11 +501,11 @@ def _version(bot: Bot, update: Update) -> None: send_msg('*Version:* `{}`'.format(__version__), bot=bot) -def shorten_date(date): +def shorten_date(_date): """ Trim the date so it fits on small screens """ - new_date = re.sub('seconds?', 'sec', date) + new_date = re.sub('seconds?', 'sec', _date) new_date = re.sub('minutes?', 'min', new_date) new_date = re.sub('hours?', 'h', new_date) new_date = re.sub('days?', 'd', new_date) From 6e68315d2c4ed1070e76db2f5159e0dc84a8f3d4 Mon Sep 17 00:00:00 2001 From: gcarq Date: Fri, 15 Dec 2017 23:58:21 +0100 Subject: [PATCH 2/6] reorder imports --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 417feeced..9ce785882 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1,10 +1,10 @@ import logging import re from datetime import timedelta, date +from decimal import Decimal from typing import Callable, Any import arrow -from decimal import Decimal from pandas import DataFrame from sqlalchemy import and_, func, text, between from tabulate import tabulate From ae37f49b51bfafb93fd16b1502337af7d5e595c3 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 16 Dec 2017 01:09:07 +0100 Subject: [PATCH 3/6] /forcesell: handle trades with open orders --- freqtrade/persistence.py | 28 +++++++++++++--------- freqtrade/rpc/telegram.py | 35 +++++++++++++++++++++------- freqtrade/tests/test_rpc_telegram.py | 27 ++++++++++++++++++++- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 2198fd917..299843906 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -96,22 +96,28 @@ class Trade(_DECL_BASE): self.open_rate = order['rate'] self.amount = order['amount'] logger.info('LIMIT_BUY has been fulfilled for %s.', self) + self.open_order_id = None elif order['type'] == 'LIMIT_SELL': - # Set close rate and set actual profit - self.close_rate = order['rate'] - self.close_profit = self.calc_profit() - self.close_date = datetime.utcnow() - self.is_open = False - logger.info( - 'Marking %s as closed as the trade is fulfilled and found no open orders for it.', - self - ) + self.close(order['rate']) else: raise ValueError('Unknown order type: {}'.format(order['type'])) - - self.open_order_id = None Trade.session.flush() + def close(self, rate: float) -> None: + """ + Sets close_rate to the given rate, calculates total profit + and marks trade as closed + """ + self.close_rate = rate + self.close_profit = self.calc_profit() + self.close_date = datetime.utcnow() + self.is_open = False + self.open_order_id = None + logger.info( + 'Marking %s as closed as the trade is fulfilled and found no open orders for it.', + self + ) + def calc_profit(self, rate: Optional[float] = None) -> float: """ Calculates the profit in percentage (including fee). diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 9ce785882..a20cb5f2d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -371,6 +371,28 @@ def _stop(bot: Bot, update: Update) -> None: send_msg('*Status:* `already stopped`', bot=bot) +def _exec_forcesell(trade: Trade) -> None: + # Check if there is there is an open order + if trade.open_order_id: + order = exchange.get_order(trade.open_order_id) + + # Cancel open LIMIT_BUY orders and close trade + if order and not order['closed'] and order['type'] == 'LIMIT_BUY': + exchange.cancel_order(trade.open_order_id) + trade.close(order.get('rate', trade.open_rate)) + # TODO: sell amount which has been bought already + return + + # Ignore trades with an attached LIMIT_SELL order + if order and not order['closed'] and order['type'] == 'LIMIT_SELL': + return + + # Get current rate and execute sell + current_rate = exchange.get_ticker(trade.pair)['bid'] + from freqtrade.main import execute_sell + execute_sell(trade, current_rate) + + @authorized_only def _forcesell(bot: Bot, update: Update) -> None: """ @@ -388,10 +410,7 @@ def _forcesell(bot: Bot, update: Update) -> None: if trade_id == 'all': # Execute sell for all open orders for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): - # Get current rate - current_rate = exchange.get_ticker(trade.pair)['bid'] - from freqtrade.main import execute_sell - execute_sell(trade, current_rate) + _exec_forcesell(trade) return # Query for trade @@ -403,10 +422,8 @@ def _forcesell(bot: Bot, update: Update) -> None: send_msg('Invalid argument. See `/help` to view usage') logger.warning('/forcesell: Invalid argument received') return - # Get current rate - current_rate = exchange.get_ticker(trade.pair)['bid'] - from freqtrade.main import execute_sell - execute_sell(trade, current_rate) + + _exec_forcesell(trade) @authorized_only @@ -526,7 +543,7 @@ def send_msg(msg: str, bot: Bot = None, parse_mode: ParseMode = ParseMode.MARKDO bot = bot or _UPDATER.bot - keyboard = [['/daily', '/profit', '/balance' ], + keyboard = [['/daily', '/profit', '/balance'], ['/status', '/status table', '/performance'], ['/count', '/start', '/stop', '/help']] diff --git a/freqtrade/tests/test_rpc_telegram.py b/freqtrade/tests/test_rpc_telegram.py index 58a9fcaa9..901450f14 100644 --- a/freqtrade/tests/test_rpc_telegram.py +++ b/freqtrade/tests/test_rpc_telegram.py @@ -14,7 +14,8 @@ 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, _daily, _count, _start, _stop, _balance, _version, _help + _profit, _forcesell, _performance, _daily, _count, _start, _stop, _balance, _version, _help, \ + _exec_forcesell def test_is_enabled(default_conf, mocker): @@ -220,6 +221,30 @@ def test_forcesell_handle(default_conf, update, ticker, mocker): assert '0.07256061 (profit: ~-0.64%)' in rpc_mock.call_args_list[-1][0][0] +def test_exec_forcesell_open_orders(default_conf, ticker, mocker): + mocker.patch.dict('freqtrade.main._CONF', default_conf) + cancel_order_mock = MagicMock() + mocker.patch.multiple('freqtrade.main.exchange', + get_ticker=ticker, + get_order=MagicMock(return_value={ + 'closed': None, + 'type': 'LIMIT_BUY', + }), + cancel_order=cancel_order_mock) + trade = Trade( + pair='BTC_ETH', + open_rate=1, + exchange='BITTREX', + open_order_id='123456789', + amount=1, + fee=0.0, + ) + _exec_forcesell(trade) + + assert cancel_order_mock.call_count == 1 + assert trade.is_open is False + + def test_forcesell_all_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) From f4b59492ab4ddcfe7197c191a8ee535d6b612681 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 16 Dec 2017 01:31:15 +0100 Subject: [PATCH 4/6] fix NoneType issue --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index a20cb5f2d..62f0aff5c 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -379,7 +379,7 @@ def _exec_forcesell(trade: Trade) -> None: # Cancel open LIMIT_BUY orders and close trade if order and not order['closed'] and order['type'] == 'LIMIT_BUY': exchange.cancel_order(trade.open_order_id) - trade.close(order.get('rate', trade.open_rate)) + trade.close(order.get('rate') or trade.open_rate) # TODO: sell amount which has been bought already return From cb4ecfd3a3600c0b959eea0354947afc112e235f Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 16 Dec 2017 01:37:06 +0100 Subject: [PATCH 5/6] move function --- freqtrade/rpc/telegram.py | 44 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 62f0aff5c..dc5dbf734 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -371,28 +371,6 @@ def _stop(bot: Bot, update: Update) -> None: send_msg('*Status:* `already stopped`', bot=bot) -def _exec_forcesell(trade: Trade) -> None: - # Check if there is there is an open order - if trade.open_order_id: - order = exchange.get_order(trade.open_order_id) - - # Cancel open LIMIT_BUY orders and close trade - if order and not order['closed'] and order['type'] == 'LIMIT_BUY': - exchange.cancel_order(trade.open_order_id) - trade.close(order.get('rate') or trade.open_rate) - # TODO: sell amount which has been bought already - return - - # Ignore trades with an attached LIMIT_SELL order - if order and not order['closed'] and order['type'] == 'LIMIT_SELL': - return - - # Get current rate and execute sell - current_rate = exchange.get_ticker(trade.pair)['bid'] - from freqtrade.main import execute_sell - execute_sell(trade, current_rate) - - @authorized_only def _forcesell(bot: Bot, update: Update) -> None: """ @@ -530,6 +508,28 @@ def shorten_date(_date): return new_date +def _exec_forcesell(trade: Trade) -> None: + # Check if there is there is an open order + if trade.open_order_id: + order = exchange.get_order(trade.open_order_id) + + # Cancel open LIMIT_BUY orders and close trade + if order and not order['closed'] and order['type'] == 'LIMIT_BUY': + exchange.cancel_order(trade.open_order_id) + trade.close(order.get('rate') or trade.open_rate) + # TODO: sell amount which has been bought already + return + + # Ignore trades with an attached LIMIT_SELL order + if order and not order['closed'] and order['type'] == 'LIMIT_SELL': + return + + # Get current rate and execute sell + current_rate = exchange.get_ticker(trade.pair)['bid'] + from freqtrade.main import execute_sell + execute_sell(trade, current_rate) + + def send_msg(msg: str, bot: Bot = None, parse_mode: ParseMode = ParseMode.MARKDOWN) -> None: """ Send given markdown message From ddd3d2d0a9a1d01faecb89a1915107d7f3cac096 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 16 Dec 2017 02:36:43 +0100 Subject: [PATCH 6/6] ignore cancelled order during trade state update --- freqtrade/persistence.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 299843906..ba0ad1785 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -87,7 +87,8 @@ class Trade(_DECL_BASE): :param order: order retrieved by exchange.get_order() :return: None """ - if not order['closed']: + # Ignore open and cancelled orders + if not order['closed'] or order['rate'] is None: return logger.info('Updating trade (id=%d) ...', self.id)