Repository: freqtrade/freqtrade

Issue:
kraken timeout crash
Hello,
After updating to latest timeout fixes i had crash on kraken. Here is the log.

```
2019-11-20 08:25:42,695 - freqtrade.freqtradebot - INFO - Found open order for Trade(id=5, pair=XTZ/BTC, amount=49.08835905, open_rate=0.00014260, open_since=2019-11-20 06:15:50)
2019-11-20 08:25:51,656 - freqtrade.utils - INFO - worker found ... calling exit
2019-11-20 08:25:51,656 - freqtrade.rpc.rpc_manager - INFO - Sending rpc message: {'type': status, 'status': 'process died'}
2019-11-20 08:25:51,714 - freqtrade.freqtradebot - INFO - Cleaning up modules ...
2019-11-20 08:25:51,714 - freqtrade.rpc.rpc_manager - INFO - Cleaning up rpc modules ...
2019-11-20 08:26:13,304 - freqtrade - ERROR - Fatal exception!
Traceback (most recent call last):
  File "/home/dri/miner/freqtrade/freqtrade/main.py", line 36, in main
    return_code = args['func'](args)
  File "/home/dri/miner/freqtrade/freqtrade/utils.py", line 51, in start_trading
    worker.run()
  File "/home/dri/miner/freqtrade/freqtrade/worker.py", line 71, in run
    state = self._worker(old_state=state)
  File "/home/dri/miner/freqtrade/freqtrade/worker.py", line 109, in _worker
    self._throttle(func=self._process, min_secs=throttle_secs)
  File "/home/dri/miner/freqtrade/freqtrade/worker.py", line 122, in _throttle
    result = func(*args, **kwargs)
  File "/home/dri/miner/freqtrade/freqtrade/worker.py", line 132, in _process
    self.freqtrade.process()
  File "/home/dri/miner/freqtrade/freqtrade/freqtradebot.py", line 143, in process
    self.check_handle_timedout()
  File "/home/dri/miner/freqtrade/freqtrade/freqtradebot.py", line 798, in check_handle_timedout
    self.handle_timedout_limit_buy(trade, order)
  File "/home/dri/miner/freqtrade/freqtrade/freqtradebot.py", line 828, in handle_timedout_limit_buy
    if corder['remaining'] == corder['amount']:
KeyError: 'remaining'
```

How i can help to fix this ?


Discussion:
(Edited formatting)
Please fill out what version you're running.
Also, what version did you run previously (approximately) - when it was still working.
Hello,
i am running latest version. Unfortunately i do not remember my previous version but it was just before starting these timeout changes. I will continue watching the bot for next crash.
I'm not certain what you refer to with "these timeout changes" ... ? 

when you say latest version, is it develop? or master (latest official release) ... 

I am working using develop only. It was for sure before pull #2523 may be 1 week before that.
I am still waiting for new trade/crash to happen and will report it.

Resolving patch:
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -787,7 +787,7 @@ def check_handle_timedout(self) -> None:
                 continue
 
             # Check if trade is still actually open
-            if float(order['remaining']) == 0.0:
+            if float(order.get('remaining', 0.0)) == 0.0:
                 self.wallets.update()
                 continue
 
@@ -813,7 +813,8 @@ def handle_buy_order_full_cancel(self, trade: Trade, reason: str) -> None:
         })
 
     def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
-        """Buy timeout - cancel order
+        """
+        Buy timeout - cancel order
         :return: True if order was fully cancelled
         """
         reason = "cancelled due to timeout"
@@ -824,18 +825,22 @@ def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
             corder = order
             reason = "canceled on Exchange"
 
-        if corder['remaining'] == corder['amount']:
+        if corder.get('remaining', order['remaining']) == order['amount']:
             # if trade is not partially completed, just delete the trade
             self.handle_buy_order_full_cancel(trade, reason)
             return True
 
         # if trade is partially complete, edit the stake details for the trade
         # and close the order
-        trade.amount = corder['amount'] - corder['remaining']
+        # cancel_order may not contain the full order dict, so we need to fallback
+        # to the order dict aquired before cancelling.
+        # we need to fall back to the values from order if corder does not contain these keys.
+        trade.amount = order['amount'] - corder.get('remaining', order['remaining'])
         trade.stake_amount = trade.amount * trade.open_rate
         # verify if fees were taken from amount to avoid problems during selling
         try:
-            new_amount = self.get_real_amount(trade, corder, trade.amount)
+            new_amount = self.get_real_amount(trade, corder if 'fee' in corder else order,
+                                              trade.amount)
             if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC):
                 trade.amount = new_amount
                 # Fee was applied, so set to 0


Test patch:
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -1804,7 +1804,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
 def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, open_trade,
                                    fee, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
-    cancel_order_mock = MagicMock()
+    cancel_order_mock = MagicMock(return_value=limit_buy_order_old)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -2089,6 +2089,29 @@ def test_handle_timedout_limit_buy(mocker, default_conf, limit_buy_order) -> Non
     assert cancel_order_mock.call_count == 1
 
 
+def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_order) -> None:
+    patch_RPCManager(mocker)
+    patch_exchange(mocker)
+    cancel_order_mock = MagicMock(return_value={})
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        cancel_order=cancel_order_mock
+    )
+
+    freqtrade = FreqtradeBot(default_conf)
+
+    Trade.session = MagicMock()
+    trade = MagicMock()
+    limit_buy_order['remaining'] = limit_buy_order['amount']
+    assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
+    assert cancel_order_mock.call_count == 1
+
+    cancel_order_mock.reset_mock()
+    limit_buy_order['amount'] = 2
+    assert not freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
+    assert cancel_order_mock.call_count == 1
+
+
 def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)