Repository: freqtrade/freqtrade
Pull request: Gracefully fail on timeframes exception

## Summary
This can happen on exchanges which don't have a "real" fetch_OHLCV method.

The example (bitstamp) has this emulated.
From the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html?highlight=timeframes)

> timeframes: An associative array of timeframes, supported by the fetchOHLCV method of the exchange. This is only populated when has['fetchOHLCV'] property is true.

Closes #1983

## Quick changelog

- Check if element is available, fail with explaining the error otherwise.
- Add test for this specific case


Problem statement:
Bitstamp has no attribute 'timeframes'

    Python Version: Python 3.7.3
    CCXT version: ccxt==1.18.805
    Branch: Develop
    Last Commit ID: ca7080c
  
## Step 3: Describe the problem:

```
Traceback (most recent call last):
  File "/opt/Projects/freqtrade/freqtrade/main.py", line 51, in main
    worker = Worker(args)
  File "/opt/Projects/freqtrade/freqtrade/worker.py", line 35, in __init__
    self._init(False)
  File "/opt/Projects/freqtrade/freqtrade/worker.py", line 51, in _init
    self.freqtrade = FreqtradeBot(self._config)
  File "/opt/Projects/freqtrade/freqtrade/freqtradebot.py", line 56, in __init__
    self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange
  File "/opt/Projects/freqtrade/freqtrade/resolvers/exchange_resolver.py", line 31, in __init__
    self.exchange = Exchange(config)
  File "/opt/Projects/freqtrade/freqtrade/exchange/exchange.py", line 140, in __init__
    self.validate_timeframes(config['ticker_interval'])
  File "/opt/Projects/freqtrade/freqtrade/exchange/exchange.py", line 272, in validate_timeframes
    timeframes = self._api.timeframes
AttributeError: 'bitstamp' object has no attribute 'timeframes'
```

### Steps to reproduce:

  1. Attempt to use bitstamp
  
### Observed Results:

  * Exchange initialization failed
  * Exchange initialization succeed

### What to do ?
Is there a way to fix this ?
If not, how about a way to have it fail in a little bit nicer fashion ?

Hints from issue discussion:
> timeframes: An associative array of timeframes, supported by the fetchOHLCV method of the exchange. This is only populated when has['fetchOHLCV'] property is true.

(source: [ccxt docs](https://ccxt.readthedocs.io/en/latest/manual.html?highlight=timeframe))

fetchOHLCV is "emulated" for bitstamp, which means that ccxt uses "some other" means to get some OHLCV data (bitstamp api apparently does not offer a fetchOHLCV method).

Trying that interactively yields 9 5m candles. With that low amount of history,  don't think bitstamp will work with the bot, since almost all indicators require 20-100 candles of history to be populated correctly.


``` python
import ccxt
bst = ccxt.bitstamp()
len(bst.fetch_ohlcv('ETH/BTC', timeframe="5m", limit=500))
```

Yes, we should fail "nicer" ... but running the bot with this exchange will probably not work, sorry
Hmmm, then : 
- we have a "white list" of known working & well supported exchanges
- shouldn't we also have a "black list" (documented somewhere) of exchanges that are known not to work, for reasons outside of freqtrade control ? I mean I had hoped that bitstamp was "implementable", even if it was not working out of the box...
well we do:
https://github.com/freqtrade/freqtrade/blob/152e138c17c1f19882c497eb36cee727fb15da57/freqtrade/exchange/exchange.py#L725-L726

we'll add exchanges as we discover problems with them - but we're not actively testing all 100+ exchanges for problems. 
I guess Bitstamp needs to be added to that list 
Oh ok, I didn't noticed that one, I meant on the README ;-)
Anyway, here you go : #1984

Commit message: Check if timeframes is available and fail gracefully otherwise

Patch to review:
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index b649a7b65f1..cefee999c53 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -269,6 +269,13 @@ def validate_timeframes(self, timeframe: List[str]) -> None:
         """
         Checks if ticker interval from config is a supported timeframe on the exchange
         """
+        logger.warning("validate_timerames")
+        if not hasattr(self._api, "timeframes"):
+            # If timeframes is missing, the exchange probably has no fetchOHLCV method.
+            # Therefore we also show that.
+            raise OperationalException(
+                f"This exchange ({self.name}) does not have a `timeframes` attribute and "
+                f"is therefore not supported. fetchOHLCV: {self.exchange_has('fetchOHLCV')}")
         timeframes = self._api.timeframes
         if timeframe not in timeframes:
             raise OperationalException(
diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py
index 25a86dd0eee..089f6306fa6 100644
--- a/freqtrade/resolvers/exchange_resolver.py
+++ b/freqtrade/resolvers/exchange_resolver.py
@@ -28,6 +28,7 @@ def __init__(self, exchange_name: str, config: dict) -> None:
         except ImportError:
             logger.info(
                 f"No {exchange_name} specific subclass found. Using the generic class instead.")
+        if not hasattr(self, "exchange"):
             self.exchange = Exchange(config)
 
     def _load_exchange(


Code review comments:
diff_hunk: @@ -269,6 +269,13 @@ def validate_timeframes(self, timeframe: List[str]) -> None:
         """
         Checks if ticker interval from config is a supported timeframe on the exchange
         """
+        logger.warning("validate_timerames")
path: freqtrade/exchange/exchange.py
text: @user1:
should be something like
```
logger.info("Validating timeframes...")
```

logger.warning() is not proper here since it's printed before checking for attr an generating the exception. A warning or even a critical message may additionally be issued below, under the `if not hasattr()` part.


@author:
This ... was never intendet to be commited - forgot to remove it after having problems hitting this section due to non-matching pairlists :laughing: .