Repository: freqtrade/freqtrade

Issue:
INSUFFICIENT_FUNDS after /forcesell all
Disclose: bot was stoped but killed afterward, after selling all. Sell was completed.

`Traceback (most recent call last):
  File "./freqtrade/main.py", line 91, in _process
    state_changed = handle_trade(trade) or state_changed
  File "./freqtrade/main.py", line 183, in handle_trade
    execute_sell(trade, current_rate)
  File "./freqtrade/main.py", line 137, in execute_sell
    order_id = exchange.sell(str(trade.pair), limit, trade.amount)
  File "/var/local/user/freqtrade/freqtrade/exchange/__init__.py", line 110, in sell
    return _API.sell(pair, rate, amount)
  File "/var/local/user/freqtrade/freqtrade/exchange/bittrex.py", line 62, in sell
    amount=amount))
RuntimeError: INSUFFICIENT_FUNDS params=(BTC_DASH, 0.0626001, 0.09763458)`



Discussion:
This happens even when you have no funds to fill a buy signal.
Transaction fees or decimal aproximation could be one of the reasons.

Traceback (most recent call last):
  File "./freqtrade/main.py", line 70, in _process
    trade = create_trade(float(_CONF['stake_amount']))
  File "./freqtrade/main.py", line 234, in create_trade
    order_id = exchange.buy(pair, buy_limit, amount)
  File "/vagrant/freqtrade/freqtrade/exchange/__init__.py", line 92, in buy
    return _API.buy(pair, rate, amount)
  File "/vagrant/freqtrade/freqtrade/exchange/bittrex.py", line 52, in buy
    amount=amount))
RuntimeError: INSUFFICIENT_FUNDS params=(BTC_QTUM, 0.0016725, 0.5964125560538117)
is there any way to fix it? since after this error occurs, the bot will force close again. 
and when the bot starts again, it will immediately be killed again with the same error but different amount.

`Status: Got RuntimeError:
Traceback (most recent call last):
  File "./freqtrade/main.py", line 91, in _process
    state_changed = handle_trade(trade) or state_changed
  File "./freqtrade/main.py", line 162, in handle_trade
    execute_sell(trade, current_rate)
  File "./freqtrade/main.py", line 118, in execute_sell
    order_id = exchange.sell(str(trade.pair), limit, trade.amount)
  File "/home/development/freqtrade-master/freqtrade/exchange/__init__.py", line 118, in sell
    return _API.sell(pair, rate, amount)
  File "/home/development/freqtrade-master/freqtrade/exchange/bittrex.py", line 63, in sell
    amount=amount))
RuntimeError: INSUFFICIENT_FUNDS params=(BTC_TIME, 0.00290784, 0.35031466)`

Resolving patch:
diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py
--- a/freqtrade/persistence.py
+++ b/freqtrade/persistence.py
@@ -87,7 +87,8 @@ def update(self, order: Dict) -> None:
         :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)
@@ -96,21 +97,27 @@ def update(self, order: Dict) -> None:
             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']))
+        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
-        Trade.session.flush()
+        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:
         """
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
--- 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
@@ -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')
-
-    stats = []
-    for key, value in profit_days.items():
-        stats.append([key, str(value) + ' BTC'])
+        # 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 = [[key, str(value) + ' BTC'] for key, value in profit_days.items()]
     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
 def _profit(bot: Bot, update: Update) -> None:
     """
@@ -391,10 +388,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
@@ -406,10 +400,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
@@ -504,11 +496,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)
@@ -516,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
@@ -529,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']]
 


Test patch:
diff --git a/freqtrade/tests/test_rpc_telegram.py b/freqtrade/tests/test_rpc_telegram.py
--- a/freqtrade/tests/test_rpc_telegram.py
+++ b/freqtrade/tests/test_rpc_telegram.py
@@ -14,7 +14,8 @@
 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)