From ae37f49b51bfafb93fd16b1502337af7d5e595c3 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 16 Dec 2017 01:09:07 +0100 Subject: [PATCH] /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)