Repository: freqtrade/freqtrade

Issue:
`Error: cannot unpack non-iterable TimeoutError object` when downloading data
## Describe your environment

  * Operating system: Arch Linux
  * Python Version: 3.8.6 (`python -V`)
  * CCXT version: Whatever `freqtrade:stable` image provides (`pip freeze | grep ccxt`)
  * Freqtrade Version: 2020.9.1 (`freqtrade -V` or `docker-compose run --rm freqtrade -V` for Freqtrade running in docker)
  
## Describe the problem:

When attempting to download 1m data for long periods of time, the process runs for a while then randomly crashes with this:
```
2020-10-22 16:32:12,697 - freqtrade.exchange.exchange - DEBUG - Done fetching pair BTC/USDT, interval 1m ...
2020-10-22 16:32:12,908 - freqtrade.exchange.exchange - DEBUG - Done fetching pair BTC/USDT, interval 1m ...
2020-10-22 16:32:13,087 - freqtrade.exchange.exchange - DEBUG - Done fetching pair BTC/USDT, interval 1m ...
2020-10-22 16:32:13,096 - freqtrade.data.history.history_utils - ERROR - Failed to download history data for pair: "BTC/USDT", timeframe: 1m. Error: cannot unpack non-iterable TimeoutError object
2020-10-22 16:32:13,282 - freqtrade.exchange.exchange - DEBUG - Exchange object destroyed, closing async loop
```

This happened for downloads that span longer periods of time. In few instances, I had to re-run the command and the download finished successfully -- but I can't seem to be doing the same with download that spans 2 years no matter how many times I restart.

### Steps to reproduce:

  1. Run `sudo docker-compose run --rm freqtrade download-data --pairs BTC/USDT --exchange binance --timerange 20190101-20190601 -t 1m -v`
  
### Observed Results:

Error was thrown. I expected data to be downloaded instead.

I guess the correct behavior would be to ignore the TimeoutError and re-try without failing whole download.

Might be related to #1491 and #1500


Discussion:
to fix the main part of the problem, you should use a configuration and specify an appropriate rate-limit
(for binance, the following works usually fine)

```
 "ccxt_async_config": {
            "enableRateLimit": true,
            "rateLimit": 200
        },
```


Now i suspected this part of the code to have a problem a while ago - but it never appeared when i've been runnign the bot - so it's difficult to fix it if it's not happening.

The above error should (hopefully) also include a stacktrace with line-numbers - that would be very helpful in order to fix this error (hopefully for good, this time).

Resolving patch:
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -687,6 +687,9 @@ def get_historic_ohlcv(self, pair: str, timeframe: str,
     async def _async_get_historic_ohlcv(self, pair: str,
                                         timeframe: str,
                                         since_ms: int) -> List:
+        """
+        Download historic ohlcv
+        """
 
         one_call = timeframe_to_msecs(timeframe) * self._ohlcv_candle_limit
         logger.debug(
@@ -702,9 +705,14 @@ async def _async_get_historic_ohlcv(self, pair: str,
 
         # Combine gathered results
         data: List = []
-        for p, timeframe, res in results:
+        for res in results:
+            if isinstance(res, Exception):
+                logger.warning("Async code raised an exception: %s", res.__class__.__name__)
+                continue
+            # Deconstruct tuple if it's not an exception
+            p, _, new_data = res
             if p == pair:
-                data.extend(res)
+                data.extend(new_data)
         # Sort data again after extending the result - above calls return in "async order"
         data = sorted(data, key=lambda x: x[0])
         logger.info("Downloaded data for %s with length %s.", pair, len(data))
@@ -741,9 +749,8 @@ def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes) -> List[Tuple
             if isinstance(res, Exception):
                 logger.warning("Async code raised an exception: %s", res.__class__.__name__)
                 continue
-            pair = res[0]
-            timeframe = res[1]
-            ticks = res[2]
+            # Deconstruct tuple (has 3 elements)
+            pair, timeframe, ticks = res
             # keeping last candle time as last refreshed time of the pair
             if ticks:
                 self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000


Test patch:
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -1295,6 +1295,15 @@ async def mock_candle_hist(pair, timeframe, since_ms):
     # Returns twice the above OHLCV data
     assert len(ret) == 2
 
+    caplog.clear()
+
+    async def mock_get_candle_hist_error(pair, *args, **kwargs):
+        raise TimeoutError()
+
+    exchange._async_get_candle_history = MagicMock(side_effect=mock_get_candle_hist_error)
+    ret = exchange.get_historic_ohlcv(pair, "5m", int((arrow.utcnow().timestamp - since) * 1000))
+    assert log_has_re(r"Async code raised an exception: .*", caplog)
+
 
 def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
     ohlcv = [