예제 #1
0
def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair):
    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_fee', fee)
    patch_exchange(mocker)

    pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
    data = history.load_data(datadir=None, ticker_interval='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()
    # We need to enable sell-signal - otherwise it sells on ROI!!
    default_conf['experimental'] = {"use_sell_signal": True}
    default_conf['ticker_interval'] = '5m'

    backtesting = Backtesting(default_conf)
    backtesting.advise_buy = _trend_alternate_hold  # Override
    backtesting.advise_sell = _trend_alternate_hold  # Override

    data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
    min_date, max_date = get_timeframe(data_processed)
    backtest_conf = {
        'stake_amount': default_conf['stake_amount'],
        'processed': data_processed,
        'max_open_trades': 3,
        'position_stacking': False,
        'start_date': min_date,
        'end_date': max_date,
    }

    results = backtesting.backtest(backtest_conf)

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

    backtest_conf = {
        'stake_amount': default_conf['stake_amount'],
        'processed': data_processed,
        'max_open_trades': 1,
        'position_stacking': False,
        'start_date': min_date,
        'end_date': max_date,
    }
    results = backtesting.backtest(backtest_conf)
    assert len(evaluate_result_multi(results, '5min', 1)) == 0
예제 #2
0
def test_backtest_1min_ticker_interval(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.tickerdata_to_dataframe(data)
    min_date, max_date = get_timeframe(processed)
    results = backtesting.backtest({
        'stake_amount': default_conf['stake_amount'],
        'processed': processed,
        'max_open_trades': 1,
        'position_stacking': False,
        'start_date': min_date,
        'end_date': max_date,
    })
    assert not results.empty
    assert len(results) == 1
예제 #3
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)
    data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
    min_date, max_date = get_timeframe(data_processed)
    results = backtesting.backtest({
        'stake_amount': default_conf['stake_amount'],
        'processed': data_processed,
        'max_open_trades': 10,
        'position_stacking': False,
        'start_date': min_date,
        'end_date': max_date,
    })
    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 = data_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))
예제 #4
0
    def start(self) -> None:
        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_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)
        dump(preprocessed, self.tickerdata_pickle)

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

        self.load_previous_results()

        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 = self.hyperopt_space()
        self.opt = self.get_optimizer(self.dimensions, config_jobs)

        if self.config.get('print_colorized', False):
            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 index 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(val)
                        self.log_results(val)
                        self.trials.append(val)
                        if is_best or current % 100 == 0:
                            self.save_trials()
        except KeyboardInterrupt:
            print('User interrupted..')

        self.save_trials(final=True)
        self.log_trials_result()
예제 #5
0
    def generate_optimizer(self, _params: Dict) -> Dict:
        """
        Used Optimize function. Called once per epoch to optimize whatever is configured.
        Keep this function as optimized as possible!
        """
        params = self.get_args(_params)
        if self.has_space('roi'):
            self.strategy.minimal_roi = self.custom_hyperopt.generate_roi_table(
                params)

        if self.has_space('buy'):
            self.advise_buy = self.custom_hyperopt.buy_strategy_generator(
                params)

        if self.has_space('sell'):
            self.advise_sell = self.custom_hyperopt.sell_strategy_generator(
                params)

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

        processed = load(TICKERDATA_PICKLE)

        min_date, max_date = get_timeframe(processed)

        results = self.backtest({
            'stake_amount': self.config['stake_amount'],
            'processed': processed,
            'max_open_trades': self.max_open_trades,
            'position_stacking': self.position_stacking,
            'start_date': min_date,
            'end_date': max_date,
        })
        result_explanation = self.format_results(results)

        trade_count = len(results.index)

        # If this evaluation contains too short amount of trades to be
        # interesting -- consider it as 'bad' (assigned max. loss value)
        # in order to cast this hyperspace point away from optimization
        # path. We do not want to optimize 'hodl' strategies.
        if trade_count < self.config['hyperopt_min_trades']:
            return {
                'loss': MAX_LOSS,
                'params': params,
                'result': result_explanation,
            }

        loss = self.calculate_loss(results=results,
                                   trade_count=trade_count,
                                   min_date=min_date.datetime,
                                   max_date=max_date.datetime)

        return {
            'loss': loss,
            'params': params,
            'result': result_explanation,
        }
예제 #6
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 = \
                    self.custom_hyperopt.generate_roi_table(params_dict)

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

        if self.has_space('sell'):
            self.backtesting.strategy.advise_sell = \
                    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'):
            self.backtesting.strategy.trailing_stop = params_dict[
                'trailing_stop']
            self.backtesting.strategy.trailing_stop_positive = \
                params_dict['trailing_stop_positive']
            self.backtesting.strategy.trailing_stop_positive_offset = \
                params_dict['trailing_stop_positive_offset']
            self.backtesting.strategy.trailing_only_offset_is_reached = \
                params_dict['trailing_only_offset_is_reached']

        processed = load(self.tickerdata_pickle)

        min_date, max_date = get_timeframe(processed)

        backtesting_results = self.backtesting.backtest({
            'stake_amount':
            self.config['stake_amount'],
            'processed':
            processed,
            'max_open_trades':
            self.max_open_trades,
            'position_stacking':
            self.position_stacking,
            'start_date':
            min_date,
            'end_date':
            max_date,
        })
        return self._get_results_dict(backtesting_results, min_date, max_date,
                                      params_dict, params_details)
예제 #7
0
def test_get_timeframe(default_conf, mocker, testdatadir) -> None:
    patch_exchange(mocker)
    strategy = DefaultStrategy(default_conf)

    data = strategy.tickerdata_to_dataframe(
        history.load_data(datadir=testdatadir,
                          ticker_interval='1m',
                          pairs=['UNITTEST/BTC']))
    min_date, max_date = history.get_timeframe(data)
    assert min_date.isoformat() == '2017-11-04T23:02:00+00:00'
    assert max_date.isoformat() == '2017-11-14T22:58:00+00:00'
예제 #8
0
    def generate_optimizer(self, _params: Dict) -> Dict:
        params = self.get_args(_params)
        if self.has_space('roi'):
            self.strategy.minimal_roi = self.custom_hyperopt.generate_roi_table(params)

        if self.has_space('buy'):
            self.advise_buy = self.custom_hyperopt.buy_strategy_generator(params)
        elif hasattr(self.custom_hyperopt, 'populate_buy_trend'):
            self.advise_buy = self.custom_hyperopt.populate_buy_trend  # type: ignore

        if self.has_space('sell'):
            self.advise_sell = self.custom_hyperopt.sell_strategy_generator(params)
        elif hasattr(self.custom_hyperopt, 'populate_sell_trend'):
            self.advise_sell = self.custom_hyperopt.populate_sell_trend  # type: ignore

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

        processed = load(TICKERDATA_PICKLE)
        min_date, max_date = get_timeframe(processed)
        results = self.backtest(
            {
                'stake_amount': self.config['stake_amount'],
                'processed': processed,
                'position_stacking': self.config.get('position_stacking', True),
                'start_date': min_date,
                'end_date': max_date,
            }
        )
        result_explanation = self.format_results(results)

        total_profit = results.profit_percent.sum()
        trade_count = len(results.index)
        trade_duration = results.trade_duration.mean()

        # If this evaluation contains too short amount of trades to be
        # interesting -- consider it as 'bad' (assigned max. loss value)
        # in order to cast this hyperspace point away from optimization
        # path. We do not want to optimize 'hodl' strategies.
        if trade_count < self.config['hyperopt_min_trades']:
            return {
                'loss': MAX_LOSS,
                'params': params,
                'result': result_explanation,
            }

        loss = self.calculate_loss(total_profit, trade_count, trade_duration)

        return {
            'loss': loss,
            'params': params,
            'result': result_explanation,
        }
예제 #9
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"] = {"0": data.roi}
    default_conf["ticker_interval"] = tests_ticker_interval
    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:
        default_conf["trailing_stop_positive"] = data.trailing_stop_positive
    default_conf[
        "trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
    default_conf["experimental"] = {"use_sell_signal": data.use_sell_signal}

    mocker.patch("freqtrade.exchange.Exchange.get_fee",
                 MagicMock(return_value=0.0))
    patch_exchange(mocker)
    frame = _build_backtest_dataframe(data.data)
    backtesting = Backtesting(default_conf)
    backtesting.advise_buy = lambda a, m: frame
    backtesting.advise_sell = lambda a, m: frame
    caplog.set_level(logging.DEBUG)

    pair = "UNITTEST/BTC"
    # Dummy data as we mock the analyze functions
    data_processed = {pair: DataFrame()}
    min_date, max_date = get_timeframe({pair: frame})
    results = backtesting.backtest({
        'stake_amount': default_conf['stake_amount'],
        'processed': data_processed,
        'max_open_trades': 10,
        'start_date': min_date,
        'end_date': max_date,
    })
    print(results.T)

    assert len(results) == len(data.trades)
    assert round(results["profit_percent"].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
        assert res.open_time == _get_frame_time_from_offset(trade.open_tick)
        assert res.close_time == _get_frame_time_from_offset(trade.close_tick)
예제 #10
0
def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None):
    data = history.load_data(datadir=None, ticker_interval='1m', pairs=[pair])
    data = trim_dictlist(data, -201)
    patch_exchange(mocker)
    backtesting = Backtesting(conf)
    processed = backtesting.strategy.tickerdata_to_dataframe(data)
    min_date, max_date = get_timeframe(processed)
    return {
        'stake_amount': conf['stake_amount'],
        'processed': processed,
        'max_open_trades': 10,
        'position_stacking': False,
        'record': record,
        'start_date': min_date,
        'end_date': max_date,
    }
예제 #11
0
def test_validate_backtest_data(default_conf, mocker, caplog) -> None:
    patch_exchange(mocker)
    strategy = DefaultStrategy(default_conf)

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

    min_date, max_date = history.get_timeframe(data)
    caplog.clear()
    assert not history.validate_backtest_data(
        data['UNITTEST/BTC'], 'UNITTEST/BTC', min_date, max_date,
        timeframe_to_minutes('5m'))
    assert len(caplog.record_tuples) == 0
예제 #12
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_timeframe({'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)
예제 #13
0
def test_validate_backtest_data_warn(default_conf, mocker, caplog) -> None:
    patch_exchange(mocker)
    strategy = DefaultStrategy(default_conf)

    data = strategy.tickerdata_to_dataframe(
        history.load_data(datadir=None,
                          ticker_interval='1m',
                          pairs=['UNITTEST/BTC'],
                          fill_up_missing=False))
    min_date, max_date = history.get_timeframe(data)
    caplog.clear()
    assert history.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.record_tuples)
예제 #14
0
def simple_backtest(config, contour, num_results, mocker) -> None:
    patch_exchange(mocker)
    config['ticker_interval'] = '1m'
    backtesting = Backtesting(config)

    data = load_data_test(contour)
    processed = backtesting.strategy.tickerdata_to_dataframe(data)
    min_date, max_date = get_timeframe(processed)
    assert isinstance(processed, dict)
    results = backtesting.backtest({
        'stake_amount': config['stake_amount'],
        'processed': processed,
        'max_open_trades': 1,
        'position_stacking': False,
        'start_date': min_date,
        'end_date': max_date,
    })
    # results :: <class 'pandas.core.frame.DataFrame'>
    assert len(results) == num_results
예제 #15
0
def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
    mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
    patch_exchange(mocker)
    backtesting = Backtesting(default_conf)

    # Run a backtesting for an exiting 1min ticker_interval
    timerange = TimeRange(None, 'line', 0, -200)
    data = history.load_data(datadir=None,
                             ticker_interval='1m',
                             pairs=['UNITTEST/BTC'],
                             timerange=timerange)
    processed = backtesting.strategy.tickerdata_to_dataframe(data)
    min_date, max_date = get_timeframe(processed)
    results = backtesting.backtest({
        'stake_amount': default_conf['stake_amount'],
        'processed': processed,
        'max_open_trades': 1,
        'position_stacking': False,
        'start_date': min_date,
        'end_date': max_date,
    })
    assert not results.empty
    assert len(results) == 1
예제 #16
0
    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'],
            timeframe=self.timeframe,
            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.timeframe), self.required_startup,
            min_date)

        return data, timerange
예제 #17
0
    def start(self) -> None:
        timerange = Arguments.parse_timerange(None if self.config.get(
            'timerange') is None else str(self.config.get('timerange')))
        data = load_data(
            datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
            pairs=self.config['exchange']['pair_whitelist'],
            ticker_interval=self.ticker_interval,
            refresh_pairs=self.config.get('refresh_pairs', False),
            exchange=self.exchange,
            timerange=timerange
        )

        if not data:
            logger.critical("No data found. Terminating.")
            return

        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
        )

        if self.has_space('buy') or self.has_space('sell'):
            self.strategy.advise_indicators = \
                self.custom_hyperopt.populate_indicators  # type: ignore

        preprocessed = self.strategy.tickerdata_to_dataframe(data)

        dump(preprocessed, TICKERDATA_PICKLE)

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

        self.load_previous_results()

        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}')

        opt = self.get_optimizer(config_jobs)
        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_tries // jobs, 1)
                for i in range(EVALS):
                    asked = opt.ask(n_points=jobs)
                    f_val = self.run_optimizer_parallel(parallel, asked)
                    opt.tell(asked, [i['loss'] for i in f_val])

                    self.trials += f_val
                    for j in range(jobs):
                        current = i * jobs + j
                        self.log_results({
                            'loss': f_val[j]['loss'],
                            'current_tries': current,
                            'initial_point': current < INITIAL_POINTS,
                            'total_tries': self.total_tries,
                            'result': f_val[j]['result'],
                        })
                        logger.debug(f"Optimizer params: {f_val[j]['params']}")
                    for j in range(jobs):
                        logger.debug(f"Optimizer state: Xi: {opt.Xi[-j-1]}, yi: {opt.yi[-j-1]}")
        except KeyboardInterrupt:
            print('User interrupted..')

        self.save_trials()
        self.log_trials_result()
예제 #18
0
    def start(self) -> None:
        """
        Run a 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

        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_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({
                'stake_amount':
                self.config.get('stake_amount'),
                'processed':
                preprocessed,
                'max_open_trades':
                max_open_trades,
                'position_stacking':
                self.config.get('position_stacking', False),
                'start_date':
                min_date,
                'end_date':
                max_date,
            })

        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(self._generate_text_table(data, results))

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

            print(' LEFT OPEN TRADES REPORT '.center(133, '='))
            print(
                self._generate_text_table(data,
                                          results.loc[results.open_at_end],
                                          True))
            print()
        if len(all_results) > 1:
            # Print Strategy summary table
            print(' Strategy Summary '.center(133, '='))
            print(self._generate_text_table_strategy(all_results))
            print('\nFor more details, please look at the detail tables above')
예제 #19
0
    def start(self) -> None:
        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_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)
        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.")
예제 #20
0
    def start(self) -> None:
        """
        Run a backtesting end-to-end
        :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 = Arguments.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'])
                                 if self.config.get('datadir') else None,
                                 pairs=pairs,
                                 ticker_interval=self.ticker_interval,
                                 refresh_pairs=self.config.get(
                                     'refresh_pairs', False),
                                 exchange=self.exchange,
                                 timerange=timerange,
                                 live=self.config.get('live', False))

        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)

        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)

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

        for strategy, results in all_results.items():

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

            print(f"Result for strategy {strategy}")
            print(' BACKTESTING REPORT '.center(133, '='))
            print(self._generate_text_table(data, results))

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

            print(' LEFT OPEN TRADES REPORT '.center(133, '='))
            print(
                self._generate_text_table(data,
                                          results.loc[results.open_at_end],
                                          True))
            print()
        if len(all_results) > 1:
            # Print Strategy summary table
            print(' Strategy Summary '.center(133, '='))
            print(self._generate_text_table_strategy(all_results))
            print('\nFor more details, please look at the detail tables above')
예제 #21
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) ...')

        data = history.load_data(
            datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
            pairs=pairs,
            ticker_interval=self.strategy.ticker_interval,
            refresh_pairs=self._refresh_pairs,
            exchange=self.exchange,
            timerange=self._timerange
        )

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

        preprocessed = self.strategy.tickerdata_to_dataframe(data)

        # Print timeframe
        min_date, max_date = history.get_timeframe(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)

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

            trades += self._find_trades_for_stoploss_range(ticker_data, 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
예제 #22
0
    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'])
                         if self.config.get('datadir') else None,
                         pairs=self.config['exchange']['pair_whitelist'],
                         ticker_interval=self.backtesting.ticker_interval,
                         refresh_pairs=self.config.get('refresh_pairs', False),
                         exchange=self.backtesting.exchange,
                         timerange=timerange)

        if not data:
            logger.critical("No data found. Terminating.")
            return

        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)

        self.backtesting.strategy.advise_indicators = \
            self.custom_hyperopt.populate_indicators  # type: ignore

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

        dump(preprocessed, self.tickerdata_pickle)

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

        self.load_previous_results()

        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}')

        opt = self.get_optimizer(config_jobs)

        if self.config.get('print_colorized', False):
            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 = opt.ask(n_points=jobs)
                    f_val = self.run_optimizer_parallel(parallel, asked)
                    opt.tell(asked, [v['loss'] for v in f_val])
                    for j in range(jobs):
                        current = i * jobs + j
                        val = f_val[j]
                        val['current_epoch'] = current
                        val['is_initial_point'] = current < INITIAL_POINTS
                        self.log_results(val)
                        self.trials.append(val)
                        logger.debug(f"Optimizer epoch evaluated: {val}")
        except KeyboardInterrupt:
            print('User interrupted..')

        self.save_trials()
        self.log_trials_result()