示例#1
0
def _maybe_resample_data(resample_rule, df, indicators, equity_data, trades):
    if isinstance(resample_rule, str):
        freq = resample_rule
    else:
        if len(df) < _MAX_CANDLES:
            return df, indicators, equity_data, trades

        from_index = dict(day=-2, hour=-6, minute=1, second=0, millisecond=0,
                          microsecond=0, nanosecond=0)[df.index.resolution]
        FREQS = ('1T', '5T', '10T', '15T', '30T', '1H', '2H', '4H', '8H', '1D', '1W', '1M')
        freq = next((f for f in FREQS[from_index:]
                     if len(df.resample(f)) <= _MAX_CANDLES), FREQS[-1])
        warnings.warn("Data contains too many candlesticks to plot; downsampling to {!r}. "
                      "See `Backtest.plot(resample=...)`".format(freq))

    from .lib import OHLCV_AGG, TRADES_AGG, _EQUITY_AGG
    df = df.resample(freq, label='right').agg(OHLCV_AGG).dropna()

    indicators = [_Indicator(i.df.resample(freq, label='right').mean()
                             .dropna().reindex(df.index).values.T,
                             **dict(i._opts, name=i.name,
                                    # HACK: override `data` for its index
                                    data=pd.Series(np.nan, index=df.index)))
                  for i in indicators]
    assert not indicators or indicators[0].df.index.equals(df.index)

    equity_data = equity_data.resample(freq, label='right').agg(_EQUITY_AGG).dropna(how='all')
    assert equity_data.index.equals(df.index)

    def _weighted_returns(s, trades=trades):
        df = trades.loc[s.index]
        return ((df['Size'].abs() * df['ReturnPct']) / df['Size'].abs().sum()).sum()

    def _group_trades(column):
        def f(s, new_index=df.index.astype(np.int64), bars=trades[column]):
            if s.size:
                # Via int64 because on pandas recently broken datetime
                mean_time = int(bars.loc[s.index].view('i8').mean())
                new_bar_idx = new_index.get_loc(mean_time, method='nearest')
                return new_bar_idx
        return f

    if len(trades):  # Avoid pandas "resampling on Int64 index" error
        trades = trades.assign(count=1).resample(freq, on='ExitTime', label='right').agg(dict(
            TRADES_AGG,
            ReturnPct=_weighted_returns,
            count='sum',
            EntryBar=_group_trades('EntryTime'),
            ExitBar=_group_trades('ExitTime'),
        )).dropna()

    return df, indicators, equity_data, trades
示例#2
0
    def test_as_str(self):
        def func():
            pass

        class Class:
            pass

        self.assertEqual(_as_str('4'), '4')
        self.assertEqual(_as_str(4), '4')
        self.assertEqual(_as_str(_Indicator([1, 2], name='x')), 'x')
        self.assertEqual(_as_str(func), 'func')
        self.assertEqual(_as_str(Class), 'Class')
        self.assertEqual(_as_str(lambda x: x), '')
        for s in ('Open', 'High', 'Low', 'Close'):
            self.assertEqual(_as_str(_Array([1], name=s)), s[0])
示例#3
0
    def test_as_str(self):
        def func():
            pass

        class Class:
            def __call__(self):
                pass

        self.assertEqual(_as_str('4'), '4')
        self.assertEqual(_as_str(4), '4')
        self.assertEqual(_as_str(_Indicator([1, 2], name='x')), 'x')
        self.assertEqual(_as_str(func), 'func')
        self.assertEqual(_as_str(Class), 'Class')
        self.assertEqual(_as_str(Class()), 'Class')
        self.assertEqual(_as_str(pd.Series([1, 2], name='x')), 'x')
        self.assertEqual(_as_str(pd.DataFrame()), 'df')
        self.assertEqual(_as_str(lambda x: x), 'λ')
        for s in ('Open', 'High', 'Low', 'Close', 'Volume'):
            self.assertEqual(_as_str(_Array([1], name=s)), s[0])
示例#4
0
def _maybe_resample_data(resample_rule, df, indicators, equity_data, trades):
    if isinstance(resample_rule, str):
        freq = resample_rule
    else:
        if resample_rule is False or len(df) <= _MAX_CANDLES:
            return df, indicators, equity_data, trades

        minutes_to_freq = {
            1: "1T",
            5: "5T",
            10: "10T",
            15: "15T",
            30: "30T",
            60: "1H",
            120: "2H",
            240: "4H",
            480: "8H",
            1440: "1D",
            10080: "1W",
            43800: "1M",
        }
        index_timedelta = df.index[-1] - df.index[0]
        req_minutes = (index_timedelta / 10000).total_seconds() // 60
        freq = [v for k, v in minutes_to_freq.items() if k >= req_minutes]
        freq = freq[0] if freq else "1M"

        warnings.warn(
            f'Data contains too many candlesticks to plot; downsampling to {freq!r}. '
            'See `Backtest.plot(resample=...)`')

    from .lib import TRADES_AGG, _EQUITY_AGG
    from backtesting.ohlc_helpers import OHLCV_AGG

    df = df.resample(freq, label='right').agg(OHLCV_AGG).dropna()

    indicators = [
        _Indicator(
            i.df.resample(freq, label='right').mean().dropna().reindex(
                df.index).values.T,
            **dict(
                i._opts,
                name=i.name,
                # Replace saved index with the resampled one
                index=df.index),
        ) for i in indicators
    ]
    assert not indicators or indicators[0].df.index.equals(df.index)

    equity_data = equity_data.resample(
        freq, label='right').agg(_EQUITY_AGG).dropna(how='all')
    assert equity_data.index.equals(df.index)

    def _weighted_returns(s, trades=trades):
        df = trades.loc[s.index]
        return ((df['Size'].abs() * df['ReturnPct']) /
                df['Size'].abs().sum()).sum()

    def _group_trades(column):
        def f(s, new_index=df.index.astype(np.int64), bars=trades[column]):
            if s.size:
                # Via int64 because on pandas recently broken datetime
                mean_time = int(bars.loc[s.index].view('i8').mean())
                new_bar_idx = new_index.get_loc(mean_time, method='nearest')
                return new_bar_idx

        return f

    if len(trades):  # Avoid pandas "resampling on Int64 index" error
        trades = (trades.assign(count=1).resample(
            freq, on='ExitTime', label='right').agg(
                dict(
                    TRADES_AGG,
                    ReturnPct=_weighted_returns,
                    count='sum',
                    EntryBar=_group_trades('EntryTime'),
                    ExitBar=_group_trades('ExitTime'),
                )).dropna())

    return df, indicators, equity_data, trades
示例#5
0
def _maybe_resample_data(resample_rule, df, indicators, equity_data, trades):
    if isinstance(resample_rule, str):
        freq = resample_rule
    else:
        if resample_rule is False or len(df) <= _MAX_CANDLES:
            return df, indicators, equity_data, trades

        freq_minutes = pd.Series({
            "1T": 1,
            "5T": 5,
            "10T": 10,
            "15T": 15,
            "30T": 30,
            "1H": 60,
            "2H": 60 * 2,
            "4H": 60 * 4,
            "8H": 60 * 8,
            "1D": 60 * 24,
            "1W": 60 * 24 * 7,
            "1M": np.inf,
        })
        timespan = df.index[-1] - df.index[0]
        require_minutes = (timespan / _MAX_CANDLES).total_seconds() // 60
        freq = freq_minutes.where(
            freq_minutes >= require_minutes).first_valid_index()
        warnings.warn(
            f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
            "See `Backtest.plot(resample=...)`")

    from .lib import OHLCV_AGG, TRADES_AGG, _EQUITY_AGG
    df = df.resample(freq, label='right').agg(OHLCV_AGG).dropna()

    indicators = [
        _Indicator(
            i.df.resample(freq, label='right').mean().dropna().reindex(
                df.index).values.T,
            **dict(
                i._opts,
                name=i.name,
                # Replace saved index with the resampled one
                index=df.index)) for i in indicators
    ]
    assert not indicators or indicators[0].df.index.equals(df.index)

    equity_data = equity_data.resample(
        freq, label='right').agg(_EQUITY_AGG).dropna(how='all')
    assert equity_data.index.equals(df.index)

    def _weighted_returns(s, trades=trades):
        df = trades.loc[s.index]
        return ((df['Size'].abs() * df['ReturnPct']) /
                df['Size'].abs().sum()).sum()

    def _group_trades(column):
        def f(s, new_index=pd.Index(df.index.view(int)), bars=trades[column]):
            if s.size:
                # Via int64 because on pandas recently broken datetime
                mean_time = int(bars.loc[s.index].view(int).mean())
                new_bar_idx = new_index.get_loc(mean_time, method='nearest')
                return new_bar_idx

        return f

    if len(trades):  # Avoid pandas "resampling on Int64 index" error
        trades = trades.assign(count=1).resample(
            freq, on='ExitTime', label='right').agg(
                dict(
                    TRADES_AGG,
                    ReturnPct=_weighted_returns,
                    count='sum',
                    EntryBar=_group_trades('EntryTime'),
                    ExitBar=_group_trades('ExitTime'),
                )).dropna()

    return df, indicators, equity_data, trades
示例#6
0
    def I(
        self,
        func: Callable,
        *args,
        name=None,
        plot=True,
        overlay=None,
        color=None,
        scatter=False,
        **kwargs,
    ) -> np.ndarray:
        if name is None:
            params = ','.join(filter(None, map(_as_str, chain(args, kwargs.values()))))
            func_name = _as_str(func)
            name = f'{func_name}({params})' if params else f'{func_name}'
        else:
            name = name.format(
                *map(_as_str, args),
                **dict(zip(kwargs.keys(), map(_as_str, kwargs.values()))),
            )

        try:
            value = func(*args, **kwargs)
        except Exception as e:
            raise RuntimeError(f'I "{name}" errored with exception: {e}')

        if isinstance(value, pd.DataFrame):
            value = value.values.T

        if value is not None:
            value = try_(lambda: np.asarray(value, order='C'), None)
        is_arraylike = value is not None

        # Optionally flip the array if the user returned e.g. `df.values`
        if is_arraylike and np.argmax(value.shape) == 0:
            value = value.T

        if not is_arraylike or not 1 <= value.ndim <= 2 or value.shape[-1] != len(self._data.Close):
            raise ValueError(
                'Indicators must return (optionally a tuple of) numpy.arrays of same '
                f'length as `data` (data shape: {self._data.Close.shape}; indicator "{name}"'
                f'shape: {getattr(value, "shape" , "")}, returned value: {value})'
            )

        if plot and overlay is None and np.issubdtype(value.dtype, np.number):
            x = value / self._data.Close
            # By default, overlay if strong majority of indicator values
            # is within 30% of Close
            with np.errstate(invalid='ignore'):
                overlay = ((x < 1.4) & (x > 0.6)).mean() > 0.6

        value = _Indicator(
            value,
            name=name,
            plot=plot,
            overlay=overlay,
            color=color,
            scatter=scatter,
            index=self.data.index,
        )
        self._indicators.append(value)
        return value