Repository: freqtrade/freqtrade

Issue:
No ticker data for pairs opened days ago while using dynamic-whitelist
Environment

  * Python Version: Python 3.6.6+ (`python -V`) 
  * CCXT version:  ccxt==1.17.399 (`pip freeze | grep ccxt`)
  * Branch: Develop
  * Last Commit ID: 7da127d28e01d6d633b14b9c155b7a998e7b1635 (`git log --format="%H" -n 1`) 
  
## Step 3: Describe the problem:

While using the dynamic-whitelist to trade, if the pairs are not in the top x by volume anymore after a given amount of time (days, weeks) the ticker history is empty for the opened trades.

freqtrade.strategy.interface - WARNING - Empty ticker history for pair GRS/BTC



Discussion:
clearly a bug - I think this bug was introduced in #1101 

will have a look.

Resolving patch:
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -54,6 +54,7 @@ def __init__(self, config: Dict[str, Any])-> None:
         self.rpc: RPCManager = RPCManager(self)
         self.persistence = None
         self.exchange = Exchange(self.config)
+        self.active_pair_whitelist: List[str] = self.config['exchange']['pair_whitelist']
         self._init_modules()
 
     def _init_modules(self) -> None:
@@ -107,11 +108,8 @@ def worker(self, old_state: State = None) -> State:
                 constants.PROCESS_THROTTLE_SECS
             )
 
-            nb_assets = self.config.get('dynamic_whitelist', None)
-
             self._throttle(func=self._process,
-                           min_secs=min_secs,
-                           nb_assets=nb_assets)
+                           min_secs=min_secs)
         return state
 
     def _startup_messages(self) -> None:
@@ -162,15 +160,15 @@ def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs)
         time.sleep(duration)
         return result
 
-    def _process(self, nb_assets: Optional[int] = 0) -> bool:
+    def _process(self) -> bool:
         """
         Queries the persistence layer for open trades and handles them,
         otherwise a new trade is created.
-        :param: nb_assets: the maximum number of pairs to be traded at the same time
         :return: True if one or more trades has been created or closed, False otherwise
         """
         state_changed = False
         try:
+            nb_assets = self.config.get('dynamic_whitelist', None)
             # Refresh whitelist based on wallet maintenance
             sanitized_list = self._refresh_whitelist(
                 self._gen_pair_whitelist(
@@ -179,15 +177,19 @@ def _process(self, nb_assets: Optional[int] = 0) -> bool:
             )
 
             # Keep only the subsets of pairs wanted (up to nb_assets)
-            final_list = sanitized_list[:nb_assets] if nb_assets else sanitized_list
-            self.config['exchange']['pair_whitelist'] = final_list
-
-            # Refreshing candles
-            self.exchange.refresh_tickers(final_list, self.strategy.ticker_interval)
+            self.active_pair_whitelist = sanitized_list[:nb_assets] if nb_assets else sanitized_list
 
             # Query trades from persistence layer
             trades = Trade.query.filter(Trade.is_open.is_(True)).all()
 
+            # Extend active-pair whitelist with pairs from open trades
+            # ensures that tickers are downloaded for open trades
+            self.active_pair_whitelist.extend([trade.pair for trade in trades
+                                               if trade.pair not in self.active_pair_whitelist])
+
+            # Refreshing candles
+            self.exchange.refresh_tickers(self.active_pair_whitelist, self.strategy.ticker_interval)
+
             # First process current opened trades
             for trade in trades:
                 state_changed |= self.process_maybe_execute_sell(trade)
@@ -380,7 +382,7 @@ def create_trade(self) -> bool:
             'Checking buy signals to create a new trade with stake_amount: %f ...',
             stake_amount
         )
-        whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist'])
+        whitelist = copy.deepcopy(self.active_pair_whitelist)
 
         # Remove currently opened and latest pairs from whitelist
         for trade in Trade.query.filter(Trade.is_open.is_(True)).all():


Test patch:
diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -663,6 +663,52 @@ def test_process_trade_handling(
     assert result is False
 
 
+def test_process_trade_no_whitelist_pair(
+        default_conf, ticker, limit_buy_order, markets, fee, mocker) -> None:
+    """ Test _process with trade not in pair list """
+    patch_RPCManager(mocker)
+    patch_exchange(mocker)
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        get_ticker=ticker,
+        get_markets=markets,
+        buy=MagicMock(return_value={'id': limit_buy_order['id']}),
+        get_order=MagicMock(return_value=limit_buy_order),
+        get_fee=fee,
+    )
+    freqtrade = FreqtradeBot(default_conf)
+    patch_get_signal(freqtrade)
+    pair = 'NOCLUE/BTC'
+    # create open trade not in whitelist
+    Trade.session.add(Trade(
+        pair=pair,
+        stake_amount=0.001,
+        fee_open=fee.return_value,
+        fee_close=fee.return_value,
+        is_open=True,
+        amount=20,
+        open_rate=0.01,
+        exchange='bittrex',
+    ))
+    Trade.session.add(Trade(
+        pair='ETH/BTC',
+        stake_amount=0.001,
+        fee_open=fee.return_value,
+        fee_close=fee.return_value,
+        is_open=True,
+        amount=12,
+        open_rate=0.001,
+        exchange='bittrex',
+    ))
+
+    assert pair not in freqtrade.active_pair_whitelist
+    result = freqtrade._process()
+    assert pair in freqtrade.active_pair_whitelist
+    # Make sure each pair is only in the list once
+    assert len(freqtrade.active_pair_whitelist) == len(set(freqtrade.active_pair_whitelist))
+    assert result is True
+
+
 def test_balance_fully_ask_side(mocker, default_conf) -> None:
     default_conf['bid_strategy']['ask_last_balance'] = 0.0
     freqtrade = get_patched_freqtradebot(mocker, default_conf)