Example #1
0
def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair,
                             testdatadir):
    def _trend_alternate_hold(dataframe=None, metadata=None):
        """
        Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
        """
        if metadata['pair'] in ('ETH/BTC', 'LTC/BTC'):
            multi = 20
        else:
            multi = 18
        dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
        dataframe['sell'] = np.where(
            (dataframe.index + multi - 2) % multi == 0, 1, 0)
        return dataframe

    mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount",
                 return_value=0.00001)
    mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
    patch_exchange(mocker)

    pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
    data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=pairs)
    # Only use 500 lines to increase performance
    data = trim_dictlist(data, -500)

    # Remove data for one pair from the beginning of the data
    data[pair] = data[pair][tres:].reset_index()
    default_conf['timeframe'] = '5m'

    backtesting = Backtesting(default_conf)
    backtesting._set_strategy(backtesting.strategylist[0])
    backtesting.strategy.advise_buy = _trend_alternate_hold  # Override
    backtesting.strategy.advise_sell = _trend_alternate_hold  # Override

    processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
    min_date, max_date = get_timerange(processed)
    backtest_conf = {
        'processed': processed,
        'start_date': min_date,
        'end_date': max_date,
        'max_open_trades': 3,
        'position_stacking': False,
    }

    results = backtesting.backtest(**backtest_conf)

    # Make sure we have parallel trades
    assert len(evaluate_result_multi(results['results'], '5m', 2)) > 0
    # make sure we don't have trades with more than configured max_open_trades
    assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0

    backtest_conf = {
        'processed': processed,
        'start_date': min_date,
        'end_date': max_date,
        'max_open_trades': 1,
        'position_stacking': False,
    }
    results = backtesting.backtest(**backtest_conf)
    assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0
Example #2
0
    def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]:
        timerange = TimeRange.parse_timerange(None if self.config.get(
            'timerange') is None else str(self.config.get('timerange')))

        data = history.load_data(
            datadir=self.config['datadir'],
            pairs=self.pairlists.whitelist,
            timeframe=self.timeframe,
            timerange=timerange,
            startup_candles=self.required_startup,
            fail_without_data=True,
            data_format=self.config.get('dataformat_ohlcv', 'json'),
        )

        min_date, max_date = history.get_timerange(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.timeframe), self.required_startup,
            min_date)

        return data, timerange
Example #3
0
def test_backtest_1min_timeframe(default_conf, fee, mocker,
                                 testdatadir) -> None:
    default_conf['use_sell_signal'] = False
    mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
    mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount",
                 return_value=0.00001)
    patch_exchange(mocker)
    backtesting = Backtesting(default_conf)
    backtesting._set_strategy(backtesting.strategylist[0])

    # Run a backtesting for an exiting 1min timeframe
    timerange = TimeRange.parse_timerange('1510688220-1510700340')
    data = history.load_data(datadir=testdatadir,
                             timeframe='1m',
                             pairs=['UNITTEST/BTC'],
                             timerange=timerange)
    processed = backtesting.strategy.advise_all_indicators(data)
    min_date, max_date = get_timerange(processed)
    results = backtesting.backtest(
        processed=processed,
        start_date=min_date,
        end_date=max_date,
        max_open_trades=1,
        position_stacking=False,
    )
    assert not results['results'].empty
    assert len(results['results']) == 1
def test_backtest_1min_timeframe(default_conf, fee, mocker,
                                 testdatadir) -> None:
    default_conf['ask_strategy']['use_sell_signal'] = False
    mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
    patch_exchange(mocker)
    backtesting = Backtesting(default_conf)

    # Run a backtesting for an exiting 1min timeframe
    timerange = TimeRange.parse_timerange('1510688220-1510700340')
    data = history.load_data(datadir=testdatadir,
                             timeframe='1m',
                             pairs=['UNITTEST/BTC'],
                             timerange=timerange)
    processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
    min_date, max_date = get_timerange(processed)
    results = backtesting.backtest(
        processed=processed,
        stake_amount=default_conf['stake_amount'],
        start_date=min_date,
        end_date=max_date,
        max_open_trades=1,
        position_stacking=False,
    )
    assert not results.empty
    assert len(results) == 1
Example #5
0
def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
    default_conf['ask_strategy']['use_sell_signal'] = False
    mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
    patch_exchange(mocker)
    backtesting = Backtesting(default_conf)
    pair = 'UNITTEST/BTC'
    timerange = TimeRange('date', None, 1517227800, 0)
    data = history.load_data(datadir=testdatadir,
                             timeframe='5m',
                             pairs=['UNITTEST/BTC'],
                             timerange=timerange)
    processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
    min_date, max_date = get_timerange(processed)
    results = backtesting.backtest(
        processed=processed,
        stake_amount=default_conf['stake_amount'],
        start_date=min_date,
        end_date=max_date,
        max_open_trades=10,
        position_stacking=False,
    )
    assert not results.empty
    assert len(results) == 2

    expected = pd.DataFrame({
        'pair': [pair, pair],
        'profit_percent': [0.0, 0.0],
        'profit_abs': [0.0, 0.0],
        'open_time':
        pd.to_datetime([
            Arrow(2018, 1, 29, 18, 40, 0).datetime,
            Arrow(2018, 1, 30, 3, 30, 0).datetime
        ],
                       utc=True),
        'close_time':
        pd.to_datetime([
            Arrow(2018, 1, 29, 22, 35, 0).datetime,
            Arrow(2018, 1, 30, 4, 10, 0).datetime
        ],
                       utc=True),
        'open_index': [78, 184],
        'close_index': [125, 192],
        'trade_duration': [235, 40],
        'open_at_end': [False, False],
        'open_rate': [0.104445, 0.10302485],
        'close_rate': [0.104969, 0.103541],
        'sell_reason': [SellType.ROI, SellType.ROI]
    })
    pd.testing.assert_frame_equal(results, expected)
    data_pair = processed[pair]
    for _, t in results.iterrows():
        ln = data_pair.loc[data_pair["date"] == t["open_time"]]
        # Check open trade rate alignes to open rate
        assert ln is not None
        assert round(ln.iloc[0]["open"], 6) == round(t["open_rate"], 6)
        # check close trade rate alignes to close rate or is between high and low
        ln = data_pair.loc[data_pair["date"] == t["close_time"]]
        assert (round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6)
                or round(ln.iloc[0]["low"], 6) < round(
                    t["close_rate"], 6) < round(ln.iloc[0]["high"], 6))
Example #6
0
    def backtest_one_strategy(self, strat: IStrategy, data: Dict[str,
                                                                 DataFrame],
                              timerange: TimeRange):
        self.progress.init_step(BacktestState.ANALYZE, 0)

        logger.info("Running backtesting for Strategy %s",
                    strat.get_strategy_name())
        backtest_start_time = datetime.now(timezone.utc)
        self._set_strategy(strat)

        strategy_safe_wrapper(self.strategy.bot_loop_start,
                              supress_error=True)()

        # Use max_open_trades in backtesting, except --disable-max-market-positions is set
        if self.config.get('use_max_market_positions', True):
            # Must come from strategy config, as the strategy may modify this setting.
            max_open_trades = self.strategy.config['max_open_trades']
        else:
            logger.info(
                'Ignoring max_open_trades (--disable-max-market-positions was used) ...'
            )
            max_open_trades = 0

        # need to reprocess data every time to populate signals
        preprocessed = self.strategy.advise_all_indicators(data)

        # Trim startup period from analyzed dataframe
        preprocessed_tmp = trim_dataframes(preprocessed, timerange,
                                           self.required_startup)

        if not preprocessed_tmp:
            raise OperationalException(
                "No data left after adjusting for startup candles.")

        # Use preprocessed_tmp for date generation (the trimmed dataframe).
        # Backtesting will re-trim the dataframes after buy/sell signal generation.
        min_date, max_date = history.get_timerange(preprocessed_tmp)
        logger.info(
            f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
            f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
            f'({(max_date - min_date).days} days).')
        # Execute backtest and store results
        results = self.backtest(
            processed=preprocessed,
            start_date=min_date,
            end_date=max_date,
            max_open_trades=max_open_trades,
            position_stacking=self.config.get('position_stacking', False),
            enable_protections=self.config.get('enable_protections', False),
        )
        backtest_end_time = datetime.now(timezone.utc)
        results.update({
            'backtest_start_time':
            int(backtest_start_time.timestamp()),
            'backtest_end_time':
            int(backtest_end_time.timestamp()),
        })
        self.all_results[self.strategy.get_strategy_name()] = results

        return min_date, max_date
    def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]:
        """
        Loads backtest data and returns the data combined with the timerange
        as tuple.
        """
        self.progress.init_step(BacktestState.DATALOAD, 1)

        data = history.load_data(
            datadir=self.config['datadir'],
            pairs=self.pairlists.whitelist,
            timeframe=self.timeframe,
            timerange=self.timerange,
            startup_candles=self.required_startup,
            fail_without_data=True,
            data_format=self.config.get('dataformat_ohlcv', 'json'),
        )

        min_date, max_date = history.get_timerange(data)

        logger.info(
            f'Loading data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
            f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
            f'({(max_date - min_date).days} days).')

        # Adjust startts forward if not enough data is available
        self.timerange.adjust_start_if_necessary(
            timeframe_to_seconds(self.timeframe), self.required_startup,
            min_date)

        self.progress.set_new_value(1)
        return data, self.timerange
Example #8
0
    def start(self) -> None:
        """
        Run backtesting end-to-end
        :return: None
        """
        data: Dict[str, Any] = {}

        logger.info('Using stake_currency: %s ...',
                    self.config['stake_currency'])
        logger.info('Using stake_amount: %s ...', self.config['stake_amount'])

        # 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
        position_stacking = self.config.get('position_stacking', False)

        data, timerange = self.load_bt_data()

        all_results = {}
        preprocessed_data = {}
        for strat in self.strategylist:
            logger.info("Running backtesting for Strategy %s",
                        strat.get_strategy_name())
            self._set_strategy(strat)

            # need to reprocess data every time to populate signals
            preprocessed = self.strategy.ohlcvdata_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 = history.get_timerange(preprocessed)

            logger.info('Backtesting with data from %s up to %s (%s days)..',
                        min_date.isoformat(), max_date.isoformat(),
                        (max_date - min_date).days)

            # Store reprocessed data for later use in export
            preprocessed_data[self.strategy.get_strategy_name()] = preprocessed

            # Execute backtest and print results
            all_results[self.strategy.get_strategy_name()] = self.backtest(
                processed=preprocessed,
                stake_amount=self.config['stake_amount'],
                start_date=min_date,
                end_date=max_date,
                max_open_trades=max_open_trades,
                position_stacking=position_stacking,
            )

        if self.config.get('export', False):
            store_backtest_result(self.config, preprocessed_data, all_results)
        # Show backtest results
        show_backtest_results(self.config, data, all_results)
Example #9
0
    def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any],
                              timerange: TimeRange):
        logger.info("Running backtesting for Strategy %s",
                    strat.get_strategy_name())
        backtest_start_time = datetime.now(timezone.utc)
        self._set_strategy(strat)

        strategy_safe_wrapper(self.strategy.bot_loop_start,
                              supress_error=True)()

        # Use max_open_trades in backtesting, except --disable-max-market-positions is set
        if self.config.get('use_max_market_positions', True):
            # Must come from strategy config, as the strategy may modify this setting.
            max_open_trades = self.strategy.config['max_open_trades']
        else:
            logger.info(
                'Ignoring max_open_trades (--disable-max-market-positions was used) ...'
            )
            max_open_trades = 0

        # need to reprocess data every time to populate signals
        preprocessed = self.strategy.ohlcvdata_to_dataframe(data)

        # Trim startup period from analyzed dataframe
        for pair, df in preprocessed.items():
            preprocessed[pair] = trim_dataframe(
                df, timerange, startup_candles=self.required_startup)
        min_date, max_date = history.get_timerange(preprocessed)

        logger.info(
            f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
            f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
            f'({(max_date - min_date).days} days)..')
        # Execute backtest and store results
        results = self.backtest(
            processed=preprocessed,
            start_date=min_date.datetime,
            end_date=max_date.datetime,
            max_open_trades=max_open_trades,
            position_stacking=self.config.get('position_stacking', False),
            enable_protections=self.config.get('enable_protections', False),
        )
        backtest_end_time = datetime.now(timezone.utc)
        self.all_results[self.strategy.get_strategy_name()] = {
            'results':
            results,
            'config':
            self.strategy.config,
            'locks':
            PairLocks.get_all_locks(),
            'final_balance':
            self.wallets.get_total(self.strategy.config['stake_currency']),
            'backtest_start_time':
            int(backtest_start_time.timestamp()),
            'backtest_end_time':
            int(backtest_end_time.timestamp()),
        }
        return min_date, max_date
Example #10
0
def init_plotscript(config, markets: List, startup_candles: int = 0):
    """
    Initialize objects needed for plotting
    :return: Dict with candle (OHLCV) data, trades and pairs
    """

    if "pairs" in config:
        pairs = expand_pairlist(config['pairs'], markets)
    else:
        pairs = expand_pairlist(config['exchange']['pair_whitelist'], markets)

    # Set timerange to use
    timerange = TimeRange.parse_timerange(config.get('timerange'))

    data = load_data(
        datadir=config.get('datadir'),
        pairs=pairs,
        timeframe=config['timeframe'],
        timerange=timerange,
        startup_candles=startup_candles,
        data_format=config.get('dataformat_ohlcv', 'json'),
    )

    if startup_candles and data:
        min_date, max_date = get_timerange(data)
        logger.info(f"Loading data from {min_date} to {max_date}")
        timerange.adjust_start_if_necessary(
            timeframe_to_seconds(config['timeframe']), startup_candles,
            min_date)

    no_trades = False
    filename = config.get('exportfilename')
    if config.get('no_trades', False):
        no_trades = True
    elif config['trade_source'] == 'file':
        if not filename.is_dir() and not filename.is_file():
            logger.warning("Backtest file is missing skipping trades.")
            no_trades = True
    try:
        trades = load_trades(
            config['trade_source'],
            db_url=config.get('db_url'),
            exportfilename=filename,
            no_trades=no_trades,
            strategy=config.get('strategy'),
        )
    except ValueError as e:
        raise OperationalException(e) from e
    if not trades.empty:
        trades = trim_dataframe(trades, timerange, 'open_date')

    return {
        "ohlcv": data,
        "trades": trades,
        "pairs": pairs,
        "timerange": timerange,
    }
Example #11
0
def test_get_timerange(default_conf, mocker, testdatadir) -> None:
    patch_exchange(mocker)
    strategy = DefaultStrategy(default_conf)

    data = strategy.tickerdata_to_dataframe(
        load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC']))
    min_date, max_date = get_timerange(data)
    assert min_date.isoformat() == '2017-11-04T23:02:00+00:00'
    assert max_date.isoformat() == '2017-11-14T22:58:00+00:00'
Example #12
0
    def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange):
        logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
        backtest_start_time = datetime.now(timezone.utc)
        self._set_strategy(strat)

        strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)()

        # Use max_open_trades in backtesting, except --disable-max-market-positions is set
        if self.config.get('use_max_market_positions', True):
            # Must come from strategy config, as the strategy may modify this setting.
            max_open_trades = self.strategy.config['max_open_trades']
        else:
            logger.info(
                'Ignoring max_open_trades (--disable-max-market-positions was used) ...')
            max_open_trades = 0

        # need to reprocess data every time to populate signals
        preprocessed = self.strategy.ohlcvdata_to_dataframe(data)

        # Trim startup period from analyzed dataframe
        for pair in list(preprocessed):
            df = preprocessed[pair]
            df = trim_dataframe(df, timerange, startup_candles=self.required_startup)
            if len(df) > 0:
                preprocessed[pair] = df
            else:
                logger.warning(f'{pair} has no data left after adjusting for startup candles, '
                               f'skipping.')
                del preprocessed[pair]

        if not preprocessed:
            raise OperationalException(
                "No data left after adjusting for startup candles.")

        min_date, max_date = history.get_timerange(preprocessed)
        logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
                    f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
                    f'({(max_date - min_date).days} days).')
        # Execute backtest and store results
        results = self.backtest(
            processed=preprocessed,
            start_date=min_date,
            end_date=max_date,
            max_open_trades=max_open_trades,
            position_stacking=self.config.get('position_stacking', False),
            enable_protections=self.config.get('enable_protections', False),
        )
        backtest_end_time = datetime.now(timezone.utc)
        results.update({
            'backtest_start_time': int(backtest_start_time.timestamp()),
            'backtest_end_time': int(backtest_end_time.timestamp()),
        })
        self.all_results[self.strategy.get_strategy_name()] = results

        return min_date, max_date
Example #13
0
    def generate_optimizer(self,
                           raw_params: List[Any],
                           iteration=None) -> Dict:
        """
        Used Optimize function. Called once per epoch to optimize whatever is configured.
        Keep this function as optimized as possible!
        """
        params_dict = self._get_params_dict(raw_params)
        params_details = self._get_params_details(params_dict)

        if self.has_space('roi'):
            self.backtesting.strategy.minimal_roi = (  # type: ignore
                self.custom_hyperopt.generate_roi_table(params_dict))

        if self.has_space('buy'):
            self.backtesting.strategy.advise_buy = (  # type: ignore
                self.custom_hyperopt.buy_strategy_generator(params_dict))

        if self.has_space('sell'):
            self.backtesting.strategy.advise_sell = (  # type: ignore
                self.custom_hyperopt.sell_strategy_generator(params_dict))

        if self.has_space('stoploss'):
            self.backtesting.strategy.stoploss = params_dict['stoploss']

        if self.has_space('trailing'):
            d = self.custom_hyperopt.generate_trailing_params(params_dict)
            self.backtesting.strategy.trailing_stop = d['trailing_stop']
            self.backtesting.strategy.trailing_stop_positive = d[
                'trailing_stop_positive']
            self.backtesting.strategy.trailing_stop_positive_offset = \
                d['trailing_stop_positive_offset']
            self.backtesting.strategy.trailing_only_offset_is_reached = \
                d['trailing_only_offset_is_reached']

        processed = load(self.data_pickle_file)

        min_date, max_date = get_timerange(processed)

        backtesting_results = self.backtesting.backtest(
            processed=processed,
            stake_amount=self.config['stake_amount'],
            start_date=min_date.datetime,
            end_date=max_date.datetime,
            max_open_trades=self.max_open_trades,
            position_stacking=self.position_stacking,
            enable_protections=self.config.get('enable_protections', False),
        )
        return self._get_results_dict(backtesting_results,
                                      min_date,
                                      max_date,
                                      params_dict,
                                      params_details,
                                      processed=processed)
Example #14
0
def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
    """
    run functional tests
    """
    default_conf["stoploss"] = data.stop_loss
    default_conf["minimal_roi"] = data.roi
    default_conf["timeframe"] = tests_timeframe
    default_conf["trailing_stop"] = data.trailing_stop
    default_conf[
        "trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached
    # Only add this to configuration If it's necessary
    if data.trailing_stop_positive is not None:
        default_conf["trailing_stop_positive"] = data.trailing_stop_positive
    default_conf[
        "trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
    default_conf["use_sell_signal"] = data.use_sell_signal

    mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0)
    mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount",
                 return_value=0.00001)
    patch_exchange(mocker)
    frame = _build_backtest_dataframe(data.data)
    backtesting = Backtesting(default_conf)
    backtesting._set_strategy(backtesting.strategylist[0])
    backtesting.required_startup = 0
    backtesting.strategy.advise_buy = lambda a, m: frame
    backtesting.strategy.advise_sell = lambda a, m: frame
    backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss
    caplog.set_level(logging.DEBUG)

    pair = "UNITTEST/BTC"
    # Dummy data as we mock the analyze functions
    data_processed = {pair: frame.copy()}
    min_date, max_date = get_timerange({pair: frame})
    result = backtesting.backtest(
        processed=data_processed,
        start_date=min_date,
        end_date=max_date,
        max_open_trades=10,
    )

    results = result['results']
    assert len(results) == len(data.trades)
    assert round(results["profit_ratio"].sum(),
                 3) == round(data.profit_perc, 3)

    for c, trade in enumerate(data.trades):
        res = results.iloc[c]
        assert res.sell_reason == trade.sell_reason.value
        assert res.buy_tag == trade.buy_tag
        assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
        assert res.close_date == _get_frame_time_from_offset(trade.close_tick)
Example #15
0
def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
    data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair])
    data = trim_dictlist(data, -201)
    patch_exchange(mocker)
    backtesting = Backtesting(conf)
    processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
    min_date, max_date = get_timerange(processed)
    return {
        'processed': processed,
        'start_date': min_date,
        'end_date': max_date,
        'max_open_trades': 10,
        'position_stacking': False,
    }
Example #16
0
    def prepare_hyperopt_data(self) -> None:
        data, timerange = self.backtesting.load_bt_data()
        logger.info("Dataload complete. Calculating indicators")

        preprocessed = self.backtesting.strategy.advise_all_indicators(data)

        # Trim startup period from analyzed dataframe to get correct dates for output.
        processed = trim_dataframes(preprocessed, timerange, self.backtesting.required_startup)
        self.min_date, self.max_date = get_timerange(processed)

        logger.info(f'Hyperopting with data from {self.min_date.strftime(DATETIME_PRINT_FORMAT)} '
                    f'up to {self.max_date.strftime(DATETIME_PRINT_FORMAT)} '
                    f'({(self.max_date - self.min_date).days} days)..')
        # Store non-trimmed data - will be trimmed after signal generation.
        dump(preprocessed, self.data_pickle_file)
Example #17
0
def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
    data = load_pair_history(datadir=testdatadir,
                             timeframe='1m',
                             pair='UNITTEST/BTC',
                             fill_up_missing=False)
    caplog.set_level(logging.DEBUG)
    data2 = ohlcv_fill_up_missing_data(data, '1m', 'UNITTEST/BTC')
    assert len(data2) > len(data)
    # Column names should not change
    assert (data.columns == data2.columns).all()

    assert log_has(f"Missing data fillup for UNITTEST/BTC: before: "
                   f"{len(data)} - after: {len(data2)}", caplog)

    # Test fillup actually fixes invalid backtest data
    min_date, max_date = get_timerange({'UNITTEST/BTC': data})
    assert validate_backtest_data(data, 'UNITTEST/BTC', min_date, max_date, 1)
    assert not validate_backtest_data(data2, 'UNITTEST/BTC', min_date, max_date, 1)
Example #18
0
def test_validate_backtest_data(default_conf, mocker, caplog,
                                testdatadir) -> None:
    patch_exchange(mocker)
    strategy = DefaultStrategy(default_conf)

    timerange = TimeRange('index', 'index', 200, 250)
    data = strategy.tickerdata_to_dataframe(
        load_data(datadir=testdatadir,
                  timeframe='5m',
                  pairs=['UNITTEST/BTC'],
                  timerange=timerange))

    min_date, max_date = get_timerange(data)
    caplog.clear()
    assert not validate_backtest_data(data['UNITTEST/BTC'], 'UNITTEST/BTC',
                                      min_date, max_date,
                                      timeframe_to_minutes('5m'))
    assert len(caplog.record_tuples) == 0
Example #19
0
    def prepare_hyperopt_data(self) -> None:
        data, timerange = self.backtesting.load_bt_data()
        logger.info("Dataload complete. Calculating indicators")

        preprocessed = self.backtesting.strategy.ohlcvdata_to_dataframe(data)

        # Trim startup period from analyzed dataframe
        processed = trim_dataframes(preprocessed, timerange,
                                    self.backtesting.required_startup)

        self.min_date, self.max_date = get_timerange(processed)

        logger.info(
            f'Hyperopting with data from {self.min_date.strftime(DATETIME_PRINT_FORMAT)} '
            f'up to {self.max_date.strftime(DATETIME_PRINT_FORMAT)} '
            f'({(self.max_date - self.min_date).days} days)..')

        dump(processed, self.data_pickle_file)
Example #20
0
def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None:
    patch_exchange(mocker)
    config['timeframe'] = '1m'
    backtesting = Backtesting(config)

    data = load_data_test(contour, testdatadir)
    processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
    min_date, max_date = get_timerange(processed)
    assert isinstance(processed, dict)
    results = backtesting.backtest(
        processed=processed,
        stake_amount=config['stake_amount'],
        start_date=min_date,
        end_date=max_date,
        max_open_trades=1,
        position_stacking=False,
    )
    # results :: <class 'pandas.core.frame.DataFrame'>
    assert len(results) == num_results
Example #21
0
def test_validate_backtest_data_warn(default_conf, mocker, caplog,
                                     testdatadir) -> None:
    patch_exchange(mocker)
    strategy = DefaultStrategy(default_conf)

    data = strategy.tickerdata_to_dataframe(
        load_data(datadir=testdatadir,
                  timeframe='1m',
                  pairs=['UNITTEST/BTC'],
                  fill_up_missing=False))
    min_date, max_date = get_timerange(data)
    caplog.clear()
    assert validate_backtest_data(data['UNITTEST/BTC'],
                                  'UNITTEST/BTC', min_date, max_date,
                                  timeframe_to_minutes('1m'))
    assert len(caplog.record_tuples) == 1
    assert log_has(
        "UNITTEST/BTC has missing frames: expected 14396, got 13680, that's 716 missing values",
        caplog)
Example #22
0
def simple_backtest(config, contour, mocker, testdatadir) -> None:
    patch_exchange(mocker)
    config['timeframe'] = '1m'
    backtesting = Backtesting(config)

    data = load_data_test(contour, testdatadir)
    processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
    min_date, max_date = get_timerange(processed)
    assert isinstance(processed, dict)
    results = backtesting.backtest(
        processed=processed,
        start_date=min_date,
        end_date=max_date,
        max_open_trades=1,
        position_stacking=False,
        enable_protections=config.get('enable_protections', False),
    )
    # results :: <class 'pandas.core.frame.DataFrame'>
    return results
Example #23
0
    def load_bt_data(self):
        timerange = TimeRange.parse_timerange(self.config['gym_parameters']['timerange'])

        data = history.load_data(
            datadir=self.config['datadir'],
            pairs=self.config['exchange']['pair_whitelist'],
            timeframe=self.timeframe,
            timerange=timerange,
            startup_candles=self.required_startup,
            fail_without_data=True,
            data_format=self.config.get('dataformat_ohlcv', 'json'),
        )

        min_date, max_date = history.get_timerange(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.timeframe),
                                            self.required_startup, min_date)

        return data, timerange
Example #24
0
    def start(self) -> None:
        """
        Run backtesting end-to-end
        :return: None
        """
        data: Dict[str, Any] = {}

        logger.info('Using stake_currency: %s ...',
                    self.config['stake_currency'])
        logger.info('Using stake_amount: %s ...', self.config['stake_amount'])

        # 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
        position_stacking = self.config.get('position_stacking', False)

        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)

            # 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_timerange(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(
                processed=preprocessed,
                stake_amount=self.config['stake_amount'],
                start_date=min_date,
                end_date=max_date,
                max_open_trades=max_open_trades,
                position_stacking=position_stacking,
            )

        for strategy, results in all_results.items():

            if self.config.get('export', False):
                self._store_backtest_result(
                    Path(self.config['exportfilename']), results,
                    strategy if len(self.strategylist) > 1 else None)

            print(f"Result for strategy {strategy}")
            print(' BACKTESTING REPORT '.center(133, '='))
            print(
                generate_text_table(
                    data,
                    stake_currency=self.config['stake_currency'],
                    max_open_trades=self.config['max_open_trades'],
                    results=results))

            print(' SELL REASON STATS '.center(133, '='))
            print(generate_text_table_sell_reason(data, results))

            print(' LEFT OPEN TRADES REPORT '.center(133, '='))
            print(
                generate_text_table(
                    data,
                    stake_currency=self.config['stake_currency'],
                    max_open_trades=self.config['max_open_trades'],
                    results=results.loc[results.open_at_end],
                    skip_nan=True))
            print()
        if len(all_results) > 1:
            # Print Strategy summary table
            print(' Strategy Summary '.center(133, '='))
            print(
                generate_text_table_strategy(self.config['stake_currency'],
                                             self.config['max_open_trades'],
                                             all_results=all_results))
            print('\nFor more details, please look at the detail tables above')
Example #25
0
    def calculate(self) -> bool:
        pairs = self.config['exchange']['pair_whitelist']
        heartbeat = self.edge_config.get('process_throttle_secs')

        if (self._last_updated > 0) and (
                self._last_updated + heartbeat > arrow.utcnow().timestamp):
            return False

        data: Dict[str, Any] = {}
        logger.info('Using stake_currency: %s ...', self.config['stake_currency'])
        logger.info('Using local backtesting data (using whitelist in given config) ...')

        if self._refresh_pairs:
            refresh_data(
                datadir=self.config['datadir'],
                pairs=pairs,
                exchange=self.exchange,
                timeframe=self.strategy.ticker_interval,
                timerange=self._timerange,
            )

        data = load_data(
            datadir=self.config['datadir'],
            pairs=pairs,
            timeframe=self.strategy.ticker_interval,
            timerange=self._timerange,
            startup_candles=self.strategy.startup_candle_count,
            data_format=self.config.get('dataformat_ohlcv', 'json'),
        )

        if not data:
            # Reinitializing cached pairs
            self._cached_pairs = {}
            logger.critical("No data found. Edge is stopped ...")
            return False

        preprocessed = self.strategy.ohlcvdata_to_dataframe(data)

        # Print timeframe
        min_date, max_date = get_timerange(preprocessed)
        logger.info(
            'Measuring data from %s up to %s (%s days) ...',
            min_date.isoformat(),
            max_date.isoformat(),
            (max_date - min_date).days
        )
        headers = ['date', 'buy', 'open', 'close', 'sell', 'high', 'low']

        trades: list = []
        for pair, pair_data in preprocessed.items():
            # Sorting dataframe by date and reset index
            pair_data = pair_data.sort_values(by=['date'])
            pair_data = pair_data.reset_index(drop=True)

            df_analyzed = self.strategy.advise_sell(
                self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()

            trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range)

        # If no trade found then exit
        if len(trades) == 0:
            logger.info("No trades found.")
            return False

        # Fill missing, calculable columns, profit, duration , abs etc.
        trades_df = self._fill_calculable_fields(DataFrame(trades))
        self._cached_pairs = self._process_expectancy(trades_df)
        self._last_updated = arrow.utcnow().timestamp

        return True
Example #26
0
    def start(self) -> None:
        self.random_state = self._set_random_state(
            self.config.get('hyperopt_random_state', None))
        logger.info(f"Using optimizer random state: {self.random_state}")
        self.hyperopt_table_header = -1
        # Initialize spaces ...
        self.init_spaces()
        data, timerange = self.backtesting.load_bt_data()
        logger.info("Dataload complete. Calculating indicators")
        preprocessed = self.backtesting.strategy.ohlcvdata_to_dataframe(data)

        # Trim startup period from analyzed dataframe
        for pair, df in preprocessed.items():
            preprocessed[pair] = trim_dataframe(
                df,
                timerange,
                startup_candles=self.backtesting.required_startup)
        self.min_date, self.max_date = get_timerange(preprocessed)

        logger.info(
            f'Hyperopting with data from {self.min_date.strftime(DATETIME_PRINT_FORMAT)} '
            f'up to {self.max_date.strftime(DATETIME_PRINT_FORMAT)} '
            f'({(self.max_date - self.min_date).days} days)..')

        dump(preprocessed, self.data_pickle_file)

        # We don't need exchange instance anymore while running hyperopt
        self.backtesting.exchange.close()
        self.backtesting.exchange._api = None  # type: ignore
        self.backtesting.exchange._api_async = None  # type: ignore
        # self.backtesting.exchange = None  # type: ignore
        self.backtesting.pairlists = None  # type: ignore

        cpus = cpu_count()
        logger.info(f"Found {cpus} CPU cores. Let's make them scream!")
        config_jobs = self.config.get('hyperopt_jobs', -1)
        logger.info(f'Number of parallel jobs set as: {config_jobs}')

        self.opt = self.get_optimizer(self.dimensions, config_jobs)

        if self.print_colorized:
            colorama_init(autoreset=True)

        try:
            with Parallel(n_jobs=config_jobs) as parallel:
                jobs = parallel._effective_n_jobs()
                logger.info(
                    f'Effective number of parallel workers used: {jobs}')

                # Define progressbar
                if self.print_colorized:
                    widgets = [
                        ' [Epoch ',
                        progressbar.Counter(),
                        ' of ',
                        str(self.total_epochs),
                        ' (',
                        progressbar.Percentage(),
                        ')] ',
                        progressbar.Bar(marker=progressbar.AnimatedMarker(
                            fill='\N{FULL BLOCK}',
                            fill_wrap=Fore.GREEN + '{}' + Fore.RESET,
                            marker_wrap=Style.BRIGHT + '{}' + Style.RESET_ALL,
                        )),
                        ' [',
                        progressbar.ETA(),
                        ', ',
                        progressbar.Timer(),
                        ']',
                    ]
                else:
                    widgets = [
                        ' [Epoch ',
                        progressbar.Counter(),
                        ' of ',
                        str(self.total_epochs),
                        ' (',
                        progressbar.Percentage(),
                        ')] ',
                        progressbar.Bar(marker=progressbar.AnimatedMarker(
                            fill='\N{FULL BLOCK}', )),
                        ' [',
                        progressbar.ETA(),
                        ', ',
                        progressbar.Timer(),
                        ']',
                    ]
                with progressbar.ProgressBar(max_value=self.total_epochs,
                                             redirect_stdout=False,
                                             redirect_stderr=False,
                                             widgets=widgets) as pbar:
                    EVALS = ceil(self.total_epochs / jobs)
                    for i in range(EVALS):
                        # Correct the number of epochs to be processed for the last
                        # iteration (should not exceed self.total_epochs in total)
                        n_rest = (i + 1) * jobs - self.total_epochs
                        current_jobs = jobs - n_rest if n_rest > 0 else jobs

                        asked = self.opt.ask(n_points=current_jobs)
                        f_val = self.run_optimizer_parallel(parallel, asked, i)
                        self.opt.tell(asked, [v['loss'] for v in f_val])

                        # Calculate progressbar outputs
                        for j, val in enumerate(f_val):
                            # Use human-friendly indexes here (starting from 1)
                            current = i * jobs + j + 1
                            val['current_epoch'] = current
                            val['is_initial_point'] = current <= INITIAL_POINTS

                            logger.debug(f"Optimizer epoch evaluated: {val}")

                            is_best = HyperoptTools.is_best_loss(
                                val, self.current_best_loss)
                            # This value is assigned here and not in the optimization method
                            # to keep proper order in the list of results. That's because
                            # evaluations can take different time. Here they are aligned in the
                            # order they will be shown to the user.
                            val['is_best'] = is_best
                            self.print_results(val)

                            if is_best:
                                self.current_best_loss = val['loss']
                            self.epochs.append(val)

                            # Save results after each best epoch and every 100 epochs
                            if is_best or current % 100 == 0:
                                self._save_results()

                            pbar.update(current)

        except KeyboardInterrupt:
            print('User interrupted..')

        self._save_results()
        logger.info(
            f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} "
            f"saved to '{self.results_file}'.")

        if self.epochs:
            sorted_epochs = sorted(self.epochs, key=itemgetter('loss'))
            best_epoch = sorted_epochs[0]
            HyperoptTools.print_epoch_details(best_epoch, self.total_epochs,
                                              self.print_json)
        else:
            # This is printed when Ctrl+C is pressed quickly, before first epochs have
            # a chance to be evaluated.
            print("No epochs evaluated yet, no best result.")
Example #27
0
    def start(self) -> None:
        self.random_state = self._set_random_state(
            self.config.get('hyperopt_random_state', None))
        logger.info(f"Using optimizer random state: {self.random_state}")

        data, timerange = self.backtesting.load_bt_data()

        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_timerange(data)

        logger.info('Hyperopting with data from %s up to %s (%s days)..',
                    min_date.isoformat(), max_date.isoformat(),
                    (max_date - min_date).days)
        dump(preprocessed, self.tickerdata_pickle)

        # We don't need exchange instance anymore while running hyperopt
        self.backtesting.exchange = None  # type: ignore

        self.trials = self.load_previous_results(self.trials_file)

        cpus = cpu_count()
        logger.info(f"Found {cpus} CPU cores. Let's make them scream!")
        config_jobs = self.config.get('hyperopt_jobs', -1)
        logger.info(f'Number of parallel jobs set as: {config_jobs}')

        self.dimensions: List[Dimension] = self.hyperopt_space()
        self.opt = self.get_optimizer(self.dimensions, config_jobs)

        if self.print_colorized:
            colorama_init(autoreset=True)

        try:
            with Parallel(n_jobs=config_jobs) as parallel:
                jobs = parallel._effective_n_jobs()
                logger.info(
                    f'Effective number of parallel workers used: {jobs}')
                EVALS = max(self.total_epochs // jobs, 1)
                for i in range(EVALS):
                    asked = self.opt.ask(n_points=jobs)
                    f_val = self.run_optimizer_parallel(parallel, asked, i)
                    self.opt.tell(asked, [v['loss'] for v in f_val])
                    self.fix_optimizer_models_list()
                    for j in range(jobs):
                        # Use human-friendly indexes here (starting from 1)
                        current = i * jobs + j + 1
                        val = f_val[j]
                        val['current_epoch'] = current
                        val['is_initial_point'] = current <= INITIAL_POINTS
                        logger.debug(f"Optimizer epoch evaluated: {val}")

                        is_best = self.is_best_loss(val,
                                                    self.current_best_loss)
                        # This value is assigned here and not in the optimization method
                        # to keep proper order in the list of results. That's because
                        # evaluations can take different time. Here they are aligned in the
                        # order they will be shown to the user.
                        val['is_best'] = is_best

                        self.print_results(val)

                        if is_best:
                            self.current_best_loss = val['loss']
                        self.trials.append(val)
                        # Save results after each best epoch and every 100 epochs
                        if is_best or current % 100 == 0:
                            self.save_trials()
        except KeyboardInterrupt:
            print('User interrupted..')

        self.save_trials(final=True)

        if self.trials:
            sorted_trials = sorted(self.trials, key=itemgetter('loss'))
            results = sorted_trials[0]
            self.print_epoch_details(results, self.total_epochs,
                                     self.print_json)
        else:
            # This is printed when Ctrl+C is pressed quickly, before first epochs have
            # a chance to be evaluated.
            print("No epochs evaluated yet, no best result.")
Example #28
0
    def calculate(self, pairs: List[str]) -> bool:
        if self.fee is None and pairs:
            self.fee = self.exchange.get_fee(pairs[0])

        heartbeat = self.edge_config.get('process_throttle_secs')

        if (self._last_updated > 0) and (
                self._last_updated + heartbeat > arrow.utcnow().int_timestamp):
            return False

        data: Dict[str, Any] = {}
        logger.info('Using stake_currency: %s ...', self.config['stake_currency'])
        logger.info('Using local backtesting data (using whitelist in given config) ...')

        if self._refresh_pairs:
            timerange_startup = deepcopy(self._timerange)
            timerange_startup.subtract_start(timeframe_to_seconds(
                self.strategy.timeframe) * self.strategy.startup_candle_count)
            refresh_data(
                datadir=self.config['datadir'],
                pairs=pairs,
                exchange=self.exchange,
                timeframe=self.strategy.timeframe,
                timerange=timerange_startup,
                data_format=self.config.get('dataformat_ohlcv', 'json'),
            )
            # Download informative pairs too
            res = defaultdict(list)
            for p, t in self.strategy.gather_informative_pairs():
                res[t].append(p)
            for timeframe, inf_pairs in res.items():
                timerange_startup = deepcopy(self._timerange)
                timerange_startup.subtract_start(timeframe_to_seconds(
                    timeframe) * self.strategy.startup_candle_count)
                refresh_data(
                    datadir=self.config['datadir'],
                    pairs=inf_pairs,
                    exchange=self.exchange,
                    timeframe=timeframe,
                    timerange=timerange_startup,
                    data_format=self.config.get('dataformat_ohlcv', 'json'),
                )

        data = load_data(
            datadir=self.config['datadir'],
            pairs=pairs,
            timeframe=self.strategy.timeframe,
            timerange=self._timerange,
            startup_candles=self.strategy.startup_candle_count,
            data_format=self.config.get('dataformat_ohlcv', 'json'),
        )

        if not data:
            # Reinitializing cached pairs
            self._cached_pairs = {}
            logger.critical("No data found. Edge is stopped ...")
            return False
        # Fake run-mode to Edge
        prior_rm = self.config['runmode']
        self.config['runmode'] = RunMode.EDGE
        preprocessed = self.strategy.advise_all_indicators(data)
        self.config['runmode'] = prior_rm

        # Print timeframe
        min_date, max_date = get_timerange(preprocessed)
        logger.info(f'Measuring data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
                    f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
                    f'({(max_date - min_date).days} days)..')
        headers = ['date', 'buy', 'open', 'close', 'sell', 'high', 'low']

        trades: list = []
        for pair, pair_data in preprocessed.items():
            # Sorting dataframe by date and reset index
            pair_data = pair_data.sort_values(by=['date'])
            pair_data = pair_data.reset_index(drop=True)

            df_analyzed = self.strategy.advise_sell(
                self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()

            trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range)

        # If no trade found then exit
        if len(trades) == 0:
            logger.info("No trades found.")
            return False

        # Fill missing, calculable columns, profit, duration , abs etc.
        trades_df = self._fill_calculable_fields(DataFrame(trades))
        self._cached_pairs = self._process_expectancy(trades_df)
        self._last_updated = arrow.utcnow().int_timestamp

        return True
Example #29
0
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
    default_conf['ask_strategy']['use_sell_signal'] = False
    mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
    mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount",
                 return_value=0.00001)
    patch_exchange(mocker)
    backtesting = Backtesting(default_conf)
    backtesting._set_strategy(backtesting.strategylist[0])
    pair = 'UNITTEST/BTC'
    timerange = TimeRange('date', None, 1517227800, 0)
    data = history.load_data(datadir=testdatadir,
                             timeframe='5m',
                             pairs=['UNITTEST/BTC'],
                             timerange=timerange)
    processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
    min_date, max_date = get_timerange(processed)
    result = backtesting.backtest(
        processed=processed,
        start_date=min_date,
        end_date=max_date,
        max_open_trades=10,
        position_stacking=False,
    )
    results = result['results']
    assert not results.empty
    assert len(results) == 2

    expected = pd.DataFrame({
        'pair': [pair, pair],
        'stake_amount': [0.001, 0.001],
        'amount': [0.00957442, 0.0097064],
        'open_date':
        pd.to_datetime([
            Arrow(2018, 1, 29, 18, 40, 0).datetime,
            Arrow(2018, 1, 30, 3, 30, 0).datetime
        ],
                       utc=True),
        'close_date':
        pd.to_datetime([
            Arrow(2018, 1, 29, 22, 35, 0).datetime,
            Arrow(2018, 1, 30, 4, 10, 0).datetime
        ],
                       utc=True),
        'open_rate': [0.104445, 0.10302485],
        'close_rate': [0.104969, 0.103541],
        'fee_open': [0.0025, 0.0025],
        'fee_close': [0.0025, 0.0025],
        'trade_duration': [235, 40],
        'profit_ratio': [0.0, 0.0],
        'profit_abs': [0.0, 0.0],
        'sell_reason': [SellType.ROI.value, SellType.ROI.value],
        'initial_stop_loss_abs': [0.0940005, 0.09272236],
        'initial_stop_loss_ratio': [-0.1, -0.1],
        'stop_loss_abs': [0.0940005, 0.09272236],
        'stop_loss_ratio': [-0.1, -0.1],
        'min_rate': [0.1038, 0.10302485],
        'max_rate': [0.10501, 0.1038888],
        'is_open': [False, False],
    })
    pd.testing.assert_frame_equal(results, expected)
    data_pair = processed[pair]
    for _, t in results.iterrows():
        ln = data_pair.loc[data_pair["date"] == t["open_date"]]
        # Check open trade rate alignes to open rate
        assert ln is not None
        assert round(ln.iloc[0]["open"], 6) == round(t["open_rate"], 6)
        # check close trade rate alignes to close rate or is between high and low
        ln = data_pair.loc[data_pair["date"] == t["close_date"]]
        assert (round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6)
                or round(ln.iloc[0]["low"], 6) < round(
                    t["close_rate"], 6) < round(ln.iloc[0]["high"], 6))
Example #30
0
    def start(self) -> None:
        """
        Run backtesting end-to-end
        :return: None
        """
        data: Dict[str, Any] = {}

        logger.info('Using stake_currency: %s ...',
                    self.config['stake_currency'])
        logger.info('Using stake_amount: %s ...', self.config['stake_amount'])

        position_stacking = self.config.get('position_stacking', False)

        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)

            # Use max_open_trades in backtesting, except --disable-max-market-positions is set
            if self.config.get('use_max_market_positions', True):
                # Must come from strategy config, as the strategy may modify this setting.
                max_open_trades = self.strategy.config['max_open_trades']
            else:
                logger.info(
                    'Ignoring max_open_trades (--disable-max-market-positions was used) ...'
                )
                max_open_trades = 0

            # need to reprocess data every time to populate signals
            preprocessed = self.strategy.ohlcvdata_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 = history.get_timerange(preprocessed)

            logger.info(
                f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
                f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
                f'({(max_date - min_date).days} days)..')
            # Execute backtest and print results
            results = self.backtest(
                processed=preprocessed,
                stake_amount=self.config['stake_amount'],
                start_date=min_date.datetime,
                end_date=max_date.datetime,
                max_open_trades=max_open_trades,
                position_stacking=position_stacking,
                enable_protections=self.config.get('enable_protections',
                                                   False),
            )
            all_results[self.strategy.get_strategy_name()] = {
                'results': results,
                'config': self.strategy.config,
                'locks': PairLocks.locks,
            }

        stats = generate_backtest_stats(data,
                                        all_results,
                                        min_date=min_date,
                                        max_date=max_date)

        if self.config.get('export', False):
            store_backtest_stats(self.config['exportfilename'], stats)

        # Show backtest results
        show_backtest_results(self.config, stats)