Repository: freqtrade/freqtrade
Pull request: Add Startup period for strategies
## Summary
Since most indicators need a startup-period, freqtrade should know about it and handle this accordingly.

This PR adds the `startup_candle_count` attribute to the strategy, which should be set to the maximum any indicator looks back.

Backtesting then tries to load data earlier than the specified date - or moves the date by the specified amount of candles into the future.

closes #2311

## Quick changelog

- Document this behaviour and attribute
- move date by `startup_candle_count` in backtesting, hyperopt and edge
- Refactor data loading in backtesting / hyperopt to reduce code duplication 
- add validation to make sure strategies can run on an exchange (based on the specified startup candle count - with an offset of 5 candles).

Problem statement:
backtesting and timeranges - startup timerange
Signals that require X candles don't work as expected when timeranges are specified because the available data is limited to the timerange, both in timeframes and date notations (?)
This can cause wrong signals at the start of the timerange until enough data is available
a workaround is to add a guard to avoid signals if there aren't X ranges (but I think this effectly just prevents backtesting...)
This can skew hyperopt results since subsequent epochs might go the wrong way depending on how much the wrong signals affect the overall strategy

Hints from issue discussion:
yes, strategies will require startup time - but it's impossible to determine this automatically, since even if the column is called `ema20`, it could be a typo and contain `ema200` ...
It's an oddity when you try to exactly recreate a trading day - but normally not a problem for backtesting or hyperopt if the exact time of the first trade is not 100% important.

i thought we have something around this in the documentation too, but currently struggle to find it (maybe it was just me repeating it in the slack channel multiple times which makes me think this ...)

### To explain why it's not a problem

most common indicators that have startup time "don't" start up - so they remain empty (`NaN`) for the startup time  - making all comparisons to evaluate to False automatically, preventing buy signals.

very quick example:

``` python
dataframe['ema8'] = ta.EMA(dataframe, timeperiod=8)
dataframe['ema13'] = ta.EMA(dataframe, timeperiod=13)
dataframe['x'] = dataframe['ema8'] < dataframe['ema13']
dataframe['y'] = dataframe['ema8'] > dataframe['ema13']
dataframe['equal'] = dataframe['ema8'] == dataframe['ema13']

dataframe.head(20)
```
We're comparing for >, < and equality. 

![2019-09-27-203617_1116x756_scrot](https://user-images.githubusercontent.com/5024695/65793578-7a859780-e166-11e9-85cc-498e4b5c8cdc.png)

As you can see - until both ema8 and ema13 did "start up", not one element results to True (NaN != NaN). 
This means that you'll also have no buys in this timerange.

I would not know any indicator that works differently (it may depend on the implementation ...), but maybe you have an example of a common indicator that behaves differently?

To prove the NaN point:

``` python
import numpy as np
np.NaN == np.NaN

False
```


I'll think of it in more details, but I also thought of similar -- an ability to specify the 'startup' period for indicators, during which they are unstable and may produce false results for backtesting. This period should first be added (prepended) to the backtesting timerange, all indicators calculated, and then the data for this 'startup period' should simply be removed or ignored by backtesting.

Of course "it's impossible to determine this automatically", this should be an attribute for the custom strategy to be set by the user.

"This means that you'll also have no buys in this timerange." -- but what if you should? This adds additional skew to the backtesting results that can simply be avoided by introducing this 'startup period' 

adding an attribute is easy, but introduces other problems.

### main problem - data starts at the specified date

I'm curious how you plan on solving the following (i don't know how), since if we cannot solve it, implementing it makes no sense to me.

say you specify `startup=200` - 200 candles. 
Assuming i have data from 20190101 - and i specify `--timeframe 20190101-` - so i would expect data to be loaded from `20190101 - 200 candles` - which depending on the timeframe is sometime in 20181231 (or earlier).

Now i don't have this data, but my data starts at 20190101, making it impossible to fulfill the "startup period". 
Would you then move this startup period to the beginning of the startup time?

### 2nd caveat
I can now specify `--timeframe 20190101-` - but my backtest data starts a month later (20190201). Backtesting will warn about this per pair, and start backtesting from this date, since this is what's available from a data perspective.

Again, same question as above - how would you plan on handling that?

### impossible solution

Failing / exiting is not an option in my opinion, since i could specify `--timeframe 20190101-` and have data for 90% of the pairs starting from this timerange, but one pair only started trading in April. 
Failing if data at the beginning is missing is not possible, since that would force me to either
a) backtest starting from April 
b) force me to remove this new pair
- both options are not acceptable options in my opinion, since it's realistic to have new pairs listed at a later date.
I think the goal is to make sure live run and backtesting behave in the same way. Not to prevent backtesting to run if there aren't enough candles. If you don't have data in backtesting because a pair is not available for a timerange then it wouldn't have data at that time anyway, so both live and backtesting will give same results.
Any pair that doesn't have the number of required candles should simply be temporarily blacklisted until it does. (I think this shouldn't even be a setting, just fixed behaviour)

I haven't looked if the 500 candles number for live is configurable or not, but either way it should be the same for backtesting so the setting should be replicated.

_Comparing against NaN will always give false so it's not an issue_...is wrong for negations:
```python
~(X > NaN) == True
``` 
but it can also be:
```python 
~(X > Y) == True
```

Thinking about missing data for pairs, there are already warnings for when data is not available, maybe they could be colored like:
`RED: data is missing because hasn't been downloaded`
`YELLOW: data is missing because pair wasn't available at that time`
well, also `~(True) == False` ... but why negate if you can invert the sign to `<`?

> I haven't looked if the 500 candles number for live is configurable or not, but either way it should be the same for backtesting so the setting should be replicated.

this would come out wrong again, yes, you have 500 candles of history for live - but the bot may generate correct signals after 20 candles in both live and backtesting.

By requiring 500 candles, you will create the same problem you have, but the other way around, where you specify a start-date, we load 500 candles before that date - so the first possible buy is not at `20190101`, but at 20191231 20:05:00 ...


> well, also `~(True) == False` ... but why negate if you can invert the sign to `<`?

It is easier to reason about a collection of events not happening than to negate many, do you think this is an antipattern?

> By requiring 500 candles, you will create the same problem you have, but the other way around, where you specify a start-date, we load 500 candles before that date - so the first possible buy is not at `20190101`, but at 20191231 20:05:00 ...

~~This would be a lesser evil imo, because you would have less signals, not bad ones, so at worst the strategy would come out as less profitable.~~
Anyway the only solution is to expose the number of used candles (which are mirrored between live and backtesting) to the user.


> Anyway the only solution is to expose the number of used candles (which are mirrored between live and backtesting) to the user.

i'm not sure what you mean with this ...
when you use sma200, you know that you need 200 startup candles - so this is exposed to the user since the user decided this in the first place.

> when you use sma200, you know that you need 200 startup candles

You know. And your customs strategy knows. But freqtrade does not.

First, is live/dry trading. Now we use default length for data, fetched from the exchange. 500 candles is default for both binance and bittrex, right? Assume we add support for an exchange where the default frame is only 100 candles. In this case, a strategy including SMA200 will simply fail. There should be an attribute in the strategy class requesting the needed length of the dataframe for all of the indicators used in the strategy so that all of them will be at the end of the dataframe series 1) calculable (non producing NaNs) 2) stable (out of their instable 'startup' period, which may be longer.

Second, is backtesting. For the same reasons, many indicators at the beginning of the timerange's dataframe are 1) not calculable, producing NaNs, 2) in their period of instability, with incorrect data (though non NaNs). The comparisons and other calculations made for those bars at the beginning of the timerange may produce errorneous signals, with errors of both Type 1 and Type 2. In order to mitigate this instability and errorneous backtesting results, some amount of candles (which can be defined by the same strategy attribute, which is mentioned above) may be added (prepended) to the beginning of the timerange, the extended set of data downloaded and later loaded into backtesting, the calculations of the indicators (and probably buy/sell signals, but this is a TBD point) started from the beginning of that extended timerange, and finally that leading addendum to the backtesting timerange should simply be dropped in order to produce backtesting results for exactly same timerange that was requested by the user.

@orehunt It's a good enhancement, I'll probably try to implement it when the time allows...

> Assume we add support for an exchange where the default frame is only 100 candles.

Honestly, if an exchange only gives you 100 candles i would exclude it (or at least not actively support it) just because of that - 100 is not enough data for any medium term trend detection ...

> In order to mitigate this instability and errorneous backtesting results, some amount of candles (which can be defined by the same strategy attribute, which is mentioned above) may be added (prepended) to the beginning of the timerange,

How will you handle the problems i pointed out above? That the additional data you're asking for is not available?

Also, data-downloading should be (should remain) strategy independent (normally you'll reuse the same data to test multiple strategies) ...


I did not say it would not be a good enhancement, but i don't think it's as easy as adding an attribute and simply prepending that data.

Commit message: Small style fixes and adjusted tests

Patch to review:
diff --git a/docs/backtesting.md b/docs/backtesting.md
index 34c5f1fbe89..6d21fa2bf2f 100644
--- a/docs/backtesting.md
+++ b/docs/backtesting.md
@@ -72,6 +72,8 @@ The exported trades can be used for [further analysis](#further-backtest-result-
 freqtrade backtesting --export trades --export-filename=backtest_samplestrategy.json
 ```
 
+Please also read about the [strategy startup period](strategy-customization.md#strategy-startup-period).
+
 #### Supplying custom fee value
 
 Sometimes your account has certain fee rebates (fee reductions starting with a certain account size or monthly volume), which are not visible to ccxt.
diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md
index ab7dcfc30c8..4d3d9bce517 100644
--- a/docs/strategy-customization.md
+++ b/docs/strategy-customization.md
@@ -117,6 +117,37 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
     Look into the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/sample_strategy.py).
     Then uncomment indicators you need.
 
+### Strategy startup period
+
+Most indicators have an "instable period", in which they are either not available, or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this instable period should be.
+To account for this, the strategy has an attribute, `startup_candle_count`. 
+This should be set to the maximum number of candles that the strategy requires to calculate stable indicators.
+
+In this example strategy, this should be set to 100 (`startup_candle_count = 100`), since the longest needed history is 100 candles.
+
+``` python
+    dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
+```
+
+By letting the bot know how much history is needed, backtest trades can start at the specified timerange during backtesting and hyperopt.
+
+!!! Warning
+    `startup_candle_count` should be below `ohlcv_candle_limit` (which is 500 for most exchanges) - since only this amount of candles will be available during trading operations.
+
+#### Example
+
+Let's try to backtest 1 month (January 2019) of 5m candles.
+
+``` bash
+freqtrade backtesting --timerange 20190101-20190201 --ticker-interval 5m
+```
+
+Since backtesting knows it needs 100 candles to generate valid buy-signals, it'll load data from `20190101 - (100 * 5m)` - which is ~2019-12-31 15:30:00.
+If this data is available, Indicators will be calculated with this extended timerange. The startup period (Up to 2019-01-01 00:00:00) will then be removed before starting backtesting.
+
+!!! Note
+    If data for the startup-period is not available, then the timerange will be adjusted to account for this startup period - so Backtesting would start at 2019-01-01 08:30:00.
+
 ### Buy signal rules
 
 Edit the method `populate_buy_trend()` in your strategy file to update your buy strategy.
@@ -267,10 +298,10 @@ class Awesomestrategy(IStrategy):
 ```
 
 !!! Warning
-  The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash.
+    The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash.
 
 !!! Note
-  If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
+    If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
 
 ### Additional data (DataProvider)
 
diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py
index fc759ab6e44..df5c937cf52 100644
--- a/freqtrade/configuration/timerange.py
+++ b/freqtrade/configuration/timerange.py
@@ -1,11 +1,14 @@
 """
 This module contains the argument manager class
 """
+import logging
 import re
 from typing import Optional
 
 import arrow
 
+logger = logging.getLogger(__name__)
+
 
 class TimeRange:
     """
@@ -27,6 +30,34 @@ def __eq__(self, other):
         return (self.starttype == other.starttype and self.stoptype == other.stoptype
                 and self.startts == other.startts and self.stopts == other.stopts)
 
+    def subtract_start(self, seconds) -> None:
+        """
+        Subtracts <seconds> from startts if startts is set.
+        :param seconds: Seconds to subtract from starttime
+        :return: None (Modifies the object in place)
+        """
+        if self.startts:
+            self.startts = self.startts - seconds
+
+    def adjust_start_if_necessary(self, ticker_interval_secs: int, startup_candles: int,
+                                  min_date: arrow.Arrow) -> None:
+        """
+        Adjust startts by <startup_candles> candles.
+        Applies only if no startup-candles have been available.
+        :param ticker_interval_secs: Ticker interval in seconds e.g. `timeframe_to_seconds('5m')`
+        :param startup_candles: Number of candles to move start-date forward
+        :param min_date: Minimum data date loaded. Key kriterium to decide if start-time
+                         has to be moved
+        :return: None (Modifies the object in place)
+        """
+        if (not self.starttype or (startup_candles
+                                   and min_date.timestamp >= self.startts)):
+            # If no startts was defined, or test-data starts at the defined test-date
+            logger.warning("Moving start-date by %s candles to account for startup time.",
+                           startup_candles)
+            self.startts = (min_date.timestamp + ticker_interval_secs * startup_candles)
+            self.starttype = 'date'
+
     @staticmethod
     def parse_timerange(text: Optional[str]):
         """
diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py
index ed5d80b0e1f..c07b58da276 100644
--- a/freqtrade/data/history.py
+++ b/freqtrade/data/history.py
@@ -8,17 +8,19 @@
 
 import logging
 import operator
+from copy import deepcopy
 from datetime import datetime
 from pathlib import Path
 from typing import Any, Dict, List, Optional, Tuple
 
 import arrow
+import pytz
 from pandas import DataFrame
 
 from freqtrade import OperationalException, misc
 from freqtrade.configuration import TimeRange
 from freqtrade.data.converter import parse_ticker_dataframe, trades_to_ohlcv
-from freqtrade.exchange import Exchange, timeframe_to_minutes
+from freqtrade.exchange import Exchange, timeframe_to_minutes, timeframe_to_seconds
 
 logger = logging.getLogger(__name__)
 
@@ -49,6 +51,19 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
     return tickerlist[start_index:stop_index]
 
 
+def trim_dataframe(df: DataFrame, timerange: TimeRange) -> DataFrame:
+    """
+    Trim dataframe based on given timerange
+    """
+    if timerange.starttype == 'date':
+        start = datetime.fromtimestamp(timerange.startts, tz=pytz.utc)
+        df = df.loc[df['date'] >= start, :]
+    if timerange.stoptype == 'date':
+        stop = datetime.fromtimestamp(timerange.stopts, tz=pytz.utc)
+        df = df.loc[df['date'] <= stop, :]
+    return df
+
+
 def load_tickerdata_file(datadir: Path, pair: str, ticker_interval: str,
                          timerange: Optional[TimeRange] = None) -> Optional[list]:
     """
@@ -113,7 +128,8 @@ def load_pair_history(pair: str,
                       refresh_pairs: bool = False,
                       exchange: Optional[Exchange] = None,
                       fill_up_missing: bool = True,
-                      drop_incomplete: bool = True
+                      drop_incomplete: bool = True,
+                      startup_candles: int = 0,
                       ) -> DataFrame:
     """
     Loads cached ticker history for the given pair.
@@ -126,9 +142,15 @@ def load_pair_history(pair: str,
     :param exchange: Exchange object (needed when using "refresh_pairs")
     :param fill_up_missing: Fill missing values with "No action"-candles
     :param drop_incomplete: Drop last candle assuming it may be incomplete.
+    :param startup_candles: Additional candles to load at the start of the period
     :return: DataFrame with ohlcv data
     """
 
+    timerange_startup = deepcopy(timerange)
+    if startup_candles and timerange_startup:
+        logger.info('Using indicator startup period: %s ...', startup_candles)
+        timerange_startup.subtract_start(timeframe_to_seconds(ticker_interval) * startup_candles)
+
     # The user forced the refresh of pairs
     if refresh_pairs:
         download_pair_history(datadir=datadir,
@@ -137,11 +159,11 @@ def load_pair_history(pair: str,
                               ticker_interval=ticker_interval,
                               timerange=timerange)
 
-    pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
+    pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange_startup)
 
     if pairdata:
-        if timerange:
-            _validate_pairdata(pair, pairdata, timerange)
+        if timerange_startup:
+            _validate_pairdata(pair, pairdata, timerange_startup)
         return parse_ticker_dataframe(pairdata, ticker_interval, pair=pair,
                                       fill_missing=fill_up_missing,
                                       drop_incomplete=drop_incomplete)
@@ -160,10 +182,22 @@ def load_data(datadir: Path,
               exchange: Optional[Exchange] = None,
               timerange: Optional[TimeRange] = None,
               fill_up_missing: bool = True,
+              startup_candles: int = 0,
+              fail_without_data: bool = False
               ) -> Dict[str, DataFrame]:
     """
     Loads ticker history data for a list of pairs
-    :return: dict(<pair>:<tickerlist>)
+    :param datadir: Path to the data storage location.
+    :param ticker_interval: Ticker-interval (e.g. "5m")
+    :param pairs: List of pairs to load
+    :param refresh_pairs: Refresh pairs from exchange.
+        (Note: Requires exchange to be passed as well.)
+    :param exchange: Exchange object (needed when using "refresh_pairs")
+    :param timerange: Limit data to be loaded to this timerange
+    :param fill_up_missing: Fill missing values with "No action"-candles
+    :param startup_candles: Additional candles to load at the start of the period
+    :param fail_without_data: Raise OperationalException if no data is found.
+    :return: dict(<pair>:<Dataframe>)
     TODO: refresh_pairs is still used by edge to keep the data uptodate.
         This should be replaced in the future. Instead, writing the current candles to disk
         from dataprovider should be implemented, as this would avoid loading ohlcv data twice.
@@ -176,9 +210,13 @@ def load_data(datadir: Path,
                                  datadir=datadir, timerange=timerange,
                                  refresh_pairs=refresh_pairs,
                                  exchange=exchange,
-                                 fill_up_missing=fill_up_missing)
+                                 fill_up_missing=fill_up_missing,
+                                 startup_candles=startup_candles)
         if hist is not None:
             result[pair] = hist
+
+    if fail_without_data and not result:
+        raise OperationalException("No data found. Terminating.")
     return result
 
 
diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py
index 2655fbc65a3..883bf4a0fc9 100644
--- a/freqtrade/edge/__init__.py
+++ b/freqtrade/edge/__init__.py
@@ -100,7 +100,8 @@ def calculate(self) -> bool:
             ticker_interval=self.strategy.ticker_interval,
             refresh_pairs=self._refresh_pairs,
             exchange=self.exchange,
-            timerange=self._timerange
+            timerange=self._timerange,
+            startup_candles=self.strategy.startup_candle_count,
         )
 
         if not data:
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 71f0737ef1e..023e16cc5b4 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -228,6 +228,7 @@ def __init__(self, config: dict, validate: bool = True) -> None:
             self.validate_pairs(config['exchange']['pair_whitelist'])
             self.validate_ordertypes(config.get('order_types', {}))
             self.validate_order_time_in_force(config.get('order_time_in_force', {}))
+            self.validate_required_startup_candles(config.get('startup_candle_count', 0))
 
         # Converts the interval provided in minutes in config to seconds
         self.markets_refresh_interval: int = exchange_config.get(
@@ -443,6 +444,16 @@ def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
             raise OperationalException(
                 f'Time in force policies are not supported for {self.name} yet.')
 
+    def validate_required_startup_candles(self, startup_candles) -> None:
+        """
+        Checks if required startup_candles is more than ohlcv_candle_limit.
+        Requires a grace-period of 5 candles - so a startup-period up to 494 is allowed by default.
+        """
+        if startup_candles + 5 > self._ft_has['ohlcv_candle_limit']:
+            raise OperationalException(
+                f"This strategy requires {startup_candles} candles to start. "
+                f"{self.name} only provides {self._ft_has['ohlcv_candle_limit']}.")
+
     def exchange_has(self, endpoint: str) -> bool:
         """
         Checks if exchange implements a specific API endpoint.
diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py
index fb8c182ee76..fe31912bc0f 100644
--- a/freqtrade/optimize/backtesting.py
+++ b/freqtrade/optimize/backtesting.py
@@ -15,7 +15,7 @@
 from freqtrade.configuration import TimeRange
 from freqtrade.data import history
 from freqtrade.data.dataprovider import DataProvider
-from freqtrade.exchange import timeframe_to_minutes
+from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
 from freqtrade.misc import file_dump_json
 from freqtrade.persistence import Trade
 from freqtrade.resolvers import ExchangeResolver, StrategyResolver
@@ -90,6 +90,8 @@ def __init__(self, config: Dict[str, Any]) -> None:
         self.ticker_interval = str(self.config.get('ticker_interval'))
         self.ticker_interval_mins = timeframe_to_minutes(self.ticker_interval)
 
+        # Get maximum required startup period
+        self.required_startup = max([strat.startup_candle_count for strat in self.strategylist])
         # Load one (first) strategy
         self._set_strategy(self.strategylist[0])
 
@@ -103,6 +105,31 @@ def _set_strategy(self, strategy):
         # And the regular "stoploss" function would not apply to that case
         self.strategy.order_types['stoploss_on_exchange'] = False
 
+    def load_bt_data(self):
+        timerange = TimeRange.parse_timerange(None if self.config.get(
+            'timerange') is None else str(self.config.get('timerange')))
+
+        data = history.load_data(
+            datadir=Path(self.config['datadir']),
+            pairs=self.config['exchange']['pair_whitelist'],
+            ticker_interval=self.ticker_interval,
+            timerange=timerange,
+            startup_candles=self.required_startup,
+            fail_without_data=True,
+        )
+
+        min_date, max_date = history.get_timeframe(data)
+
+        logger.info(
+            'Loading data from %s up to %s (%s days)..',
+            min_date.isoformat(), max_date.isoformat(), (max_date - min_date).days
+        )
+        # Adjust startts forward if not enough data is available
+        timerange.adjust_start_if_necessary(timeframe_to_seconds(self.ticker_interval),
+                                            self.required_startup, min_date)
+
+        return data, timerange
+
     def _generate_text_table(self, data: Dict[str, Dict], results: DataFrame,
                              skip_nan: bool = False) -> str:
         """
@@ -412,39 +439,18 @@ def start(self) -> None:
         :return: None
         """
         data: Dict[str, Any] = {}
-        pairs = self.config['exchange']['pair_whitelist']
         logger.info('Using stake_currency: %s ...', self.config['stake_currency'])
         logger.info('Using stake_amount: %s ...', self.config['stake_amount'])
-
-        timerange = TimeRange.parse_timerange(None if self.config.get(
-            'timerange') is None else str(self.config.get('timerange')))
-        data = history.load_data(
-            datadir=Path(self.config['datadir']),
-            pairs=pairs,
-            ticker_interval=self.ticker_interval,
-            timerange=timerange,
-        )
-
-        if not data:
-            logger.critical("No data found. Terminating.")
-            return
         # Use max_open_trades in backtesting, except --disable-max-market-positions is set
         if self.config.get('use_max_market_positions', True):
             max_open_trades = self.config['max_open_trades']
         else:
             logger.info('Ignoring max_open_trades (--disable-max-market-positions was used) ...')
             max_open_trades = 0
-        all_results = {}
-
-        min_date, max_date = history.get_timeframe(data)
 
-        logger.info(
-            'Backtesting with data from %s up to %s (%s days)..',
-            min_date.isoformat(),
-            max_date.isoformat(),
-            (max_date - min_date).days
-        )
+        data, timerange = self.load_bt_data()
 
+        all_results = {}
         for strat in self.strategylist:
             logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
             self._set_strategy(strat)
@@ -452,6 +458,15 @@ def start(self) -> None:
             # need to reprocess data every time to populate signals
             preprocessed = self.strategy.tickerdata_to_dataframe(data)
 
+            # Trim startup period from analyzed dataframe
+            for pair, df in preprocessed.items():
+                preprocessed[pair] = history.trim_dataframe(df, timerange)
+            min_date, max_date = history.get_timeframe(preprocessed)
+
+            logger.info(
+                'Backtesting with data from %s up to %s (%s days)..',
+                min_date.isoformat(), max_date.isoformat(), (max_date - min_date).days
+            )
             # Execute backtest and print results
             all_results[self.strategy.get_strategy_name()] = self.backtest(
                 {
diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
index 07258a048af..c576ea6f807 100644
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -22,8 +22,7 @@
 from skopt import Optimizer
 from skopt.space import Dimension
 
-from freqtrade.configuration import TimeRange
-from freqtrade.data.history import load_data, get_timeframe
+from freqtrade.data.history import get_timeframe, trim_dataframe
 from freqtrade.misc import round_dict
 from freqtrade.optimize.backtesting import Backtesting
 # Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules
@@ -379,30 +378,19 @@ def load_previous_results(self):
             )
 
     def start(self) -> None:
-        timerange = TimeRange.parse_timerange(None if self.config.get(
-            'timerange') is None else str(self.config.get('timerange')))
-        data = load_data(
-            datadir=Path(self.config['datadir']),
-            pairs=self.config['exchange']['pair_whitelist'],
-            ticker_interval=self.backtesting.ticker_interval,
-            timerange=timerange
-        )
+        data, timerange = self.backtesting.load_bt_data()
 
-        if not data:
-            logger.critical("No data found. Terminating.")
-            return
+        preprocessed = self.backtesting.strategy.tickerdata_to_dataframe(data)
 
+        # Trim startup period from analyzed dataframe
+        for pair, df in preprocessed.items():
+            preprocessed[pair] = trim_dataframe(df, timerange)
         min_date, max_date = get_timeframe(data)
 
         logger.info(
             'Hyperopting with data from %s up to %s (%s days)..',
-            min_date.isoformat(),
-            max_date.isoformat(),
-            (max_date - min_date).days
+            min_date.isoformat(), max_date.isoformat(), (max_date - min_date).days
         )
-
-        preprocessed = self.backtesting.strategy.tickerdata_to_dataframe(data)
-
         dump(preprocessed, self.tickerdata_pickle)
 
         # We don't need exchange instance anymore while running hyperopt
diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py
index d6fbe9a7a57..5bea740275e 100644
--- a/freqtrade/resolvers/strategy_resolver.py
+++ b/freqtrade/resolvers/strategy_resolver.py
@@ -57,6 +57,7 @@ def __init__(self, config: Optional[Dict] = None) -> None:
                       ("order_time_in_force",             None,        False),
                       ("stake_currency",                  None,        False),
                       ("stake_amount",                    None,        False),
+                      ("startup_candle_count",            None,        False),
                       ("use_sell_signal",                 True,        True),
                       ("sell_profit_only",                False,       True),
                       ("ignore_roi_if_buy_signal",        False,       True),
diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py
index b839a961807..6c343b477ff 100644
--- a/freqtrade/strategy/default_strategy.py
+++ b/freqtrade/strategy/default_strategy.py
@@ -39,6 +39,9 @@ class DefaultStrategy(IStrategy):
         'stoploss_on_exchange': False
     }
 
+    # Number of candles the strategy requires before producing valid signals
+    startup_candle_count: int = 20
+
     # Optional time in force for orders
     order_time_in_force = {
         'buy': 'gtc',
@@ -105,9 +108,6 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
         # EMA - Exponential Moving Average
         dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
 
-        # SMA - Simple Moving Average
-        dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
-
         return dataframe
 
     def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py
index 014ca99684d..d42f8e9898e 100644
--- a/freqtrade/strategy/interface.py
+++ b/freqtrade/strategy/interface.py
@@ -103,6 +103,9 @@ class IStrategy(ABC):
     # run "populate_indicators" only for new candle
     process_only_new_candles: bool = False
 
+    # Count of candles the strategy requires before producing valid signals
+    startup_candle_count: int = 0
+
     # Class level variables (intentional) containing
     # the dataprovider (dp) (access to other candles, historic data, ...)
     # and wallets - access to the current balance.
@@ -421,6 +424,7 @@ def min_roi_reached(self, trade: Trade, current_profit: float, current_time: dat
     def tickerdata_to_dataframe(self, tickerdata: Dict[str, List]) -> Dict[str, DataFrame]:
         """
         Creates a dataframe and populates indicators for given ticker data
+        Used by optimize operations only, not during dry / live runs.
         """
         return {pair: self.advise_indicators(pair_data, {'pair': pair})
                 for pair, pair_data in tickerdata.items()}
diff --git a/tests/data/test_history.py b/tests/data/test_history.py
index 95382768a0f..057524fb327 100644
--- a/tests/data/test_history.py
+++ b/tests/data/test_history.py
@@ -95,6 +95,23 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog, testdatadir) -> N
     _clean_test_file(file)
 
 
+def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) -> None:
+    ltfmock = mocker.patch('freqtrade.data.history.load_tickerdata_file',
+                           MagicMock(return_value=None))
+    timerange = TimeRange('date', None, 1510639620, 0)
+    history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='1m',
+                              datadir=testdatadir, timerange=timerange,
+                              startup_candles=20,
+                              )
+    assert log_has(
+        'Using indicator startup period: 20 ...', caplog
+    )
+    assert ltfmock.call_count == 1
+    assert ltfmock.call_args_list[0][1]['timerange'] != timerange
+    # startts is 20 minutes earlier
+    assert ltfmock.call_args_list[0][1]['timerange'].startts == timerange.startts - 20 * 60
+
+
 def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog,
                                       default_conf, testdatadir) -> None:
     """
@@ -427,6 +444,46 @@ def test_trim_tickerlist(testdatadir) -> None:
     assert not ticker
 
 
+def test_trim_dataframe(testdatadir) -> None:
+    data = history.load_data(
+        datadir=testdatadir,
+        ticker_interval='1m',
+        pairs=['UNITTEST/BTC']
+    )['UNITTEST/BTC']
+    min_date = int(data.iloc[0]['date'].timestamp())
+    max_date = int(data.iloc[-1]['date'].timestamp())
+    data_modify = data.copy()
+
+    # Remove first 30 minutes (1800 s)
+    tr = TimeRange('date', None, min_date + 1800, 0)
+    data_modify = history.trim_dataframe(data_modify, tr)
+    assert not data_modify.equals(data)
+    assert len(data_modify) < len(data)
+    assert len(data_modify) == len(data) - 30
+    assert all(data_modify.iloc[-1] == data.iloc[-1])
+    assert all(data_modify.iloc[0] == data.iloc[30])
+
+    data_modify = data.copy()
+    # Remove last 30 minutes (1800 s)
+    tr = TimeRange(None, 'date', 0, max_date - 1800)
+    data_modify = history.trim_dataframe(data_modify, tr)
+    assert not data_modify.equals(data)
+    assert len(data_modify) < len(data)
+    assert len(data_modify) == len(data) - 30
+    assert all(data_modify.iloc[0] == data.iloc[0])
+    assert all(data_modify.iloc[-1] == data.iloc[-31])
+
+    data_modify = data.copy()
+    # Remove first 25 and last 30 minutes (1800 s)
+    tr = TimeRange('date', 'date', min_date + 1500, max_date - 1800)
+    data_modify = history.trim_dataframe(data_modify, tr)
+    assert not data_modify.equals(data)
+    assert len(data_modify) < len(data)
+    assert len(data_modify) == len(data) - 55
+    # first row matches 25th original row
+    assert all(data_modify.iloc[0] == data.iloc[25])
+
+
 def test_file_dump_json_tofile(testdatadir) -> None:
     file = testdatadir / 'test_{id}.json'.format(id=str(uuid.uuid4()))
     data = {'bar': 'foo'}
diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py
index 5e244a97e98..e1af50768a1 100644
--- a/tests/edge/test_edge.py
+++ b/tests/edge/test_edge.py
@@ -256,7 +256,7 @@ def test_edge_heartbeat_calculate(mocker, edge_conf):
 
 
 def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False,
-                     timerange=None, exchange=None):
+                     timerange=None, exchange=None, *args, **kwargs):
     hz = 0.1
     base = 0.001
 
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
index 1e0a5fdc3b9..6805d8e7347 100644
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -533,6 +533,24 @@ def test_validate_order_types_not_in_config(default_conf, mocker):
     Exchange(conf)
 
 
+def test_validate_required_startup_candles(default_conf, mocker, caplog):
+    api_mock = MagicMock()
+    mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance'))
+
+    mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
+    mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
+    mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
+    mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
+
+    default_conf['startup_candle_count'] = 20
+    ex = Exchange(default_conf)
+    assert ex
+    default_conf['startup_candle_count'] = 600
+
+    with pytest.raises(OperationalException, match=r'This strategy requires 600.*'):
+        Exchange(default_conf)
+
+
 def test_exchange_has(default_conf, mocker):
     exchange = get_patched_exchange(mocker, default_conf)
     assert not exchange.exchange_has('ASDFASDF')
diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py
index 998edda8a70..ba87848ec81 100644
--- a/tests/optimize/test_backtesting.py
+++ b/tests/optimize/test_backtesting.py
@@ -117,7 +117,7 @@ def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None:
 
 
 def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False,
-                     timerange=None, exchange=None, live=False):
+                     timerange=None, exchange=None, live=False, *args, **kwargs):
     tickerdata = history.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange)
     pairdata = {'UNITTEST/BTC': parse_ticker_dataframe(tickerdata, '1m', pair="UNITTEST/BTC",
                                                        fill_missing=True)}
@@ -494,7 +494,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) ->
     def get_timeframe(input1):
         return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
 
-    mocker.patch('freqtrade.data.history.load_data', MagicMock(return_value={}))
+    mocker.patch('freqtrade.data.history.load_pair_history', MagicMock(return_value=None))
     mocker.patch('freqtrade.data.history.get_timeframe', get_timeframe)
     mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
     patch_exchange(mocker)
@@ -511,10 +511,8 @@ def get_timeframe(input1):
     default_conf['timerange'] = '20180101-20180102'
 
     backtesting = Backtesting(default_conf)
-    backtesting.start()
-    # check the logs, that will contain the backtest result
-
-    assert log_has('No data found. Terminating.', caplog)
+    with pytest.raises(OperationalException, match='No data found. Terminating.'):
+        backtesting.start()
 
 
 def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
@@ -838,6 +836,8 @@ async def load_pairs(pair, timeframe, since):
         f'Using data directory: {testdatadir} ...',
         'Using stake_currency: BTC ...',
         'Using stake_amount: 0.001 ...',
+        'Loading data from 2017-11-14T20:57:00+00:00 '
+        'up to 2017-11-14T22:58:00+00:00 (0 days)..',
         'Backtesting with data from 2017-11-14T21:17:00+00:00 '
         'up to 2017-11-14T22:58:00+00:00 (0 days)..',
         'Parameter --enable-position-stacking detected ...'
@@ -892,6 +892,8 @@ async def load_pairs(pair, timeframe, since):
         f'Using data directory: {testdatadir} ...',
         'Using stake_currency: BTC ...',
         'Using stake_amount: 0.001 ...',
+        'Loading data from 2017-11-14T20:57:00+00:00 '
+        'up to 2017-11-14T22:58:00+00:00 (0 days)..',
         'Backtesting with data from 2017-11-14T21:17:00+00:00 '
         'up to 2017-11-14T22:58:00+00:00 (0 days)..',
         'Parameter --enable-position-stacking detected ...',
diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py
index 052c3ba7719..d0c37c40dbc 100644
--- a/tests/optimize/test_hyperopt.py
+++ b/tests/optimize/test_hyperopt.py
@@ -228,7 +228,7 @@ def test_start(mocker, default_conf, caplog) -> None:
 
 def test_start_no_data(mocker, default_conf, caplog) -> None:
     patched_configuration_load_config_file(mocker, default_conf)
-    mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock(return_value={}))
+    mocker.patch('freqtrade.data.history.load_pair_history', MagicMock(return_value=None))
     mocker.patch(
         'freqtrade.optimize.hyperopt.get_timeframe',
         MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
@@ -242,9 +242,8 @@ def test_start_no_data(mocker, default_conf, caplog) -> None:
         '--epochs', '5'
     ]
     args = get_args(args)
-    start_hyperopt(args)
-
-    assert log_has('No data found. Terminating.', caplog)
+    with pytest.raises(OperationalException, match='No data found. Terminating.'):
+        start_hyperopt(args)
 
 
 def test_start_filelock(mocker, default_conf, caplog) -> None:
@@ -393,7 +392,8 @@ def test_roi_table_generation(hyperopt) -> None:
 
 def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None:
     dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
-    mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
+    mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
+                 MagicMock(return_value=(MagicMock(), None)))
     mocker.patch(
         'freqtrade.optimize.hyperopt.get_timeframe',
         MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
@@ -608,7 +608,8 @@ def test_continue_hyperopt(mocker, default_conf, caplog):
 
 def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None:
     dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
-    mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
+    mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
+                 MagicMock(return_value=(MagicMock(), None)))
     mocker.patch(
         'freqtrade.optimize.hyperopt.get_timeframe',
         MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
@@ -645,7 +646,8 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None:
 
 def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> None:
     dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
-    mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
+    mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
+                 MagicMock(return_value=(MagicMock(), None)))
     mocker.patch(
         'freqtrade.optimize.hyperopt.get_timeframe',
         MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
@@ -682,7 +684,8 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) ->
 
 def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys) -> None:
     dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
-    mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
+    mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
+                 MagicMock(return_value=(MagicMock(), None)))
     mocker.patch(
         'freqtrade.optimize.hyperopt.get_timeframe',
         MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
@@ -728,7 +731,8 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys)
 
 def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) -> None:
     mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
-    mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
+    mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
+                 MagicMock(return_value=(MagicMock(), None)))
     mocker.patch(
         'freqtrade.optimize.hyperopt.get_timeframe',
         MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
@@ -757,7 +761,8 @@ def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) -
 
 def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None:
     dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
-    mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
+    mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
+                 MagicMock(return_value=(MagicMock(), None)))
     mocker.patch(
         'freqtrade.optimize.hyperopt.get_timeframe',
         MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
@@ -802,7 +807,8 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None:
 
 def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None:
     dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
-    mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
+    mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
+                 MagicMock(return_value=(MagicMock(), None)))
     mocker.patch(
         'freqtrade.optimize.hyperopt.get_timeframe',
         MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
@@ -853,7 +859,8 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None
 ])
 def test_simplified_interface_failed(mocker, default_conf, caplog, capsys, method, space) -> None:
     mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
-    mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
+    mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
+                 MagicMock(return_value=(MagicMock(), None)))
     mocker.patch(
         'freqtrade.optimize.hyperopt.get_timeframe',
         MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
diff --git a/tests/test_timerange.py b/tests/test_timerange.py
index 4851cbebd87..d758092ed9c 100644
--- a/tests/test_timerange.py
+++ b/tests/test_timerange.py
@@ -1,10 +1,11 @@
 # pragma pylint: disable=missing-docstring, C0103
+import arrow
 import pytest
 
 from freqtrade.configuration import TimeRange
 
 
-def test_parse_timerange_incorrect() -> None:
+def test_parse_timerange_incorrect():
 
     assert TimeRange('date', None, 1274486400, 0) == TimeRange.parse_timerange('20100522-')
     assert TimeRange(None, 'date', 0, 1274486400) == TimeRange.parse_timerange('-20100522')
@@ -28,3 +29,37 @@ def test_parse_timerange_incorrect() -> None:
 
     with pytest.raises(Exception, match=r'Incorrect syntax.*'):
         TimeRange.parse_timerange('-')
+
+
+def test_subtract_start():
+    x = TimeRange('date', 'date', 1274486400, 1438214400)
+    x.subtract_start(300)
+    assert x.startts == 1274486400 - 300
+
+    # Do nothing if no startdate exists
+    x = TimeRange(None, 'date', 0, 1438214400)
+    x.subtract_start(300)
+    assert not x.startts
+
+    x = TimeRange('date', None, 1274486400, 0)
+    x.subtract_start(300)
+    assert x.startts == 1274486400 - 300
+
+
+def test_adjust_start_if_necessary():
+    min_date = arrow.Arrow(2017, 11, 14, 21, 15, 00)
+
+    x = TimeRange('date', 'date', 1510694100, 1510780500)
+    # Adjust by 20 candles - min_date == startts
+    x.adjust_start_if_necessary(300, 20, min_date)
+    assert x.startts == 1510694100 + (20 * 300)
+
+    x = TimeRange('date', 'date', 1510700100, 1510780500)
+    # Do nothing, startupe is set and different min_date
+    x.adjust_start_if_necessary(300, 20, min_date)
+    assert x.startts == 1510694100 + (20 * 300)
+
+    x = TimeRange(None, 'date', 0, 1510780500)
+    # Adjust by 20 candles = 20 * 5m
+    x.adjust_start_if_necessary(300, 20, min_date)
+    assert x.startts == 1510694100 + (20 * 300)
diff --git a/user_data/strategies/sample_strategy.py b/user_data/strategies/sample_strategy.py
index 80c30283da2..c2fd681d235 100644
--- a/user_data/strategies/sample_strategy.py
+++ b/user_data/strategies/sample_strategy.py
@@ -59,6 +59,9 @@ class SampleStrategy(IStrategy):
     sell_profit_only = False
     ignore_roi_if_buy_signal = False
 
+    # Number of candles the strategy requires before producing valid signals
+    startup_candle_count: int = 20
+
     # Optional order type mapping.
     order_types = {
         'buy': 'limit',


Code review comments:
diff_hunk: @@ -126,9 +142,15 @@ def load_pair_history(pair: str,
     :param exchange: Exchange object (needed when using "refresh_pairs")
     :param fill_up_missing: Fill missing values with "No action"-candles
     :param drop_incomplete: Drop last candle assuming it may be incomplete.
+    :param startup_candles: Additional candles to load at the start of the period
     :return: DataFrame with ohlcv data
     """
 
+    timerange_startup = deepcopy(timerange)
+    if startup_candles and timerange_startup:
path: freqtrade/data/history.py
text: @user1:
`if startup_candles > 0 and` ?
-- this will ignore negative values passed


@author:
Good point :+1: