Ejemplo n.º 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:]
    # 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
Ejemplo n.º 2
0
def test_backtest(default_conf, fee, mocker) -> None:
    mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
    patch_exchange(mocker)
    backtesting = Backtesting(default_conf)
    pair = 'UNITTEST/BTC'
    timerange = TimeRange(None, 'line', 0, -201)
    data = history.load_data(datadir=None,
                             ticker_interval='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))
Ejemplo n.º 3
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)
        self.min_date = min_date
        self.max_date = max_date
        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()
        total_profit = results.profit_percent
        trade_count = len(results.index)
        trade_duration = results.trade_duration.mean()

        if trade_count == 0:
            return {
                'loss': MAX_LOSS,
                'params': params,
                'result': result_explanation,
            }

        loss = self.calculate_loss(total_profit, trade_count)

        return {
            'loss': loss,
            'params': params,
            'result': result_explanation,
        }
Ejemplo n.º 4
0
def test_get_timeframe(default_conf, mocker) -> None:
    patch_exchange(mocker)
    strategy = DefaultStrategy(default_conf)

    data = strategy.tickerdata_to_dataframe(
        history.load_data(datadir=None,
                          ticker_interval='1m',
                          pairs=['UNITTEST/BTC']))
    min_date, max_date = optimize.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'
Ejemplo n.º 5
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,
    }
Ejemplo n.º 6
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 = optimize.get_timeframe(data)
    caplog.clear()
    assert not optimize.validate_backtest_data(
        data, min_date, max_date, constants.TICKER_INTERVAL_MINUTES["5m"])
    assert len(caplog.record_tuples) == 0
Ejemplo n.º 7
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']))
    min_date, max_date = optimize.get_timeframe(data)
    caplog.clear()
    assert optimize.validate_backtest_data(
        data, min_date, max_date, constants.TICKER_INTERVAL_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)
Ejemplo n.º 8
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
Ejemplo n.º 9
0
def test_ohlcv_fill_up_missing_data(caplog):
    data = load_pair_history(datadir=None,
                             ticker_interval='1m',
                             refresh_pairs=False,
                             pair='UNITTEST/BTC',
                             fill_up_missing=False)
    caplog.set_level(logging.DEBUG)
    data2 = ohlcv_fill_up_missing_data(data, '1m')
    assert len(data2) > len(data)
    # Column names should not change
    assert (data.columns == data2.columns).all()

    assert log_has(
        f"Missing data fillup: before: {len(data)} - after: {len(data2)}",
        caplog.record_tuples)

    # Test fillup actually fixes invalid backtest data
    min_date, max_date = get_timeframe({'UNITTEST/BTC': data})
    assert validate_backtest_data({'UNITTEST/BTC': data}, min_date, max_date,
                                  1)
    assert not validate_backtest_data({'UNITTEST/BTC': data2}, min_date,
                                      max_date, 1)
Ejemplo n.º 10
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
    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)
Ejemplo n.º 11
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
Ejemplo n.º 12
0
def test_backtest_multi_pair(default_conf, fee, mocker):
    def evaluate_result_multi(results, freq, max_open_trades):
        # Find overlapping trades by expanding each trade once per period
        # and then counting overlaps
        dates = [
            pd.Series(
                pd.date_range(row[1].open_time, row[1].close_time, freq=freq))
            for row in results[['open_time', 'close_time']].iterrows()
        ]
        deltas = [len(x) for x in dates]
        dates = pd.Series(pd.concat(dates).values, name='date')
        df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0),
                           columns=results.columns)

        df2 = df2.astype(dtype={
            "open_time": "datetime64",
            "close_time": "datetime64"
        })
        df2 = pd.concat([dates, df2], axis=1)
        df2 = df2.set_index('date')
        df_final = df2.resample(freq)[['pair']].count()
        return df_final[df_final['pair'] > max_open_trades]

    def _trend_alternate_hold(dataframe=None, metadata=None):
        """
        Buy every 8th candle - sell every other 8th -2 (hold on to pairs a bit)
        """
        multi = 8
        dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
        dataframe['sell'] = np.where(
            (dataframe.index + multi - 2) % multi == 0, 1, 0)
        if metadata['pair'] in ('ETH/BTC', 'LTC/BTC'):
            dataframe['buy'] = dataframe['buy'].shift(-4)
            dataframe['sell'] = dataframe['sell'].shift(-4)
        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)
    data = trim_dictlist(data, -500)
    # 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
Ejemplo n.º 13
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'])

        if self.config.get('live'):
            logger.info('Downloading data for all pairs in whitelist ...')
            self.exchange.refresh_tickers(pairs, self.ticker_interval)
            data = self.exchange._klines
        else:
            logger.info(
                'Using local backtesting data (using whitelist in given config) ...'
            )

            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)

        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 = {}

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

            min_date, max_date = optimize.get_timeframe(data)
            # Validate dataframe for missing values (mainly at start and end, as fillup is called)
            optimize.validate_backtest_data(
                data, min_date, max_date,
                constants.TICKER_INTERVAL_MINUTES[self.ticker_interval])
            logger.info('Measuring data from %s up to %s (%s days)..',
                        min_date.isoformat(), max_date.isoformat(),
                        (max_date - min_date).days)
            # 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(119, '='))
            print(self._generate_text_table(data, results))

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

            print(' LEFT OPEN TRADES REPORT '.center(119, '='))
            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(119, '='))
            print(self._generate_text_table_strategy(all_results))
            print('\nFor more details, please look at the detail tables above')