kernc / backtesting.py

:mag_right: :chart_with_upwards_trend: :snake: :moneybag: Backtest trading strategies in Python.
https://kernc.github.io/backtesting.py/
GNU Affero General Public License v3.0
5.05k stars 989 forks source link

Plotting result in TypeError: Index.get_loc() got an unexpected keyword argument 'method' #1010

Open trueToastedCode opened 1 year ago

trueToastedCode commented 1 year ago

Expected Behavior

Plotting should work.

Actual Behavior

Fails, but only using large dataframe. >>> bt.plot()

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [86], in <cell line: 1>()
----> 1 bt.plot()

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/backtesting/backtesting.py:1592, in Backtest.plot(self, results, filename, plot_width, plot_equity, plot_return, plot_pl, plot_volume, plot_drawdown, smooth_equity, relative_equity, superimpose, resample, reverse_indicators, show_legend, open_browser)
   1589         raise RuntimeError('First issue `backtest.run()` to obtain results.')
   1590     results = self._results
-> 1592 return plot(
   1593     results=results,
   1594     df=self._data,
   1595     indicators=results._strategy._indicators,
   1596     filename=filename,
   1597     plot_width=plot_width,
   1598     plot_equity=plot_equity,
   1599     plot_return=plot_return,
   1600     plot_pl=plot_pl,
   1601     plot_volume=plot_volume,
   1602     plot_drawdown=plot_drawdown,
   1603     smooth_equity=smooth_equity,
   1604     relative_equity=relative_equity,
   1605     superimpose=superimpose,
   1606     resample=resample,
   1607     reverse_indicators=reverse_indicators,
   1608     show_legend=show_legend,
   1609     open_browser=open_browser)

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/backtesting/_plotting.py:203, in plot(results, df, indicators, filename, plot_width, plot_equity, plot_return, plot_pl, plot_volume, plot_drawdown, smooth_equity, relative_equity, superimpose, resample, reverse_indicators, show_legend, open_browser)
    201 # Limit data to max_candles
    202 if is_datetime_index:
--> 203     df, indicators, equity_data, trades = _maybe_resample_data(
    204         resample, df, indicators, equity_data, trades)
    206 df.index.name = None  # Provides source name @index
    207 df['datetime'] = df.index  # Save original, maybe datetime index

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/backtesting/_plotting.py:153, in _maybe_resample_data(resample_rule, df, indicators, equity_data, trades)
    150     return f
    152 if len(trades):  # Avoid pandas "resampling on Int64 index" error
--> 153     trades = trades.assign(count=1).resample(freq, on='ExitTime', label='right').agg(dict(
    154         TRADES_AGG,
    155         ReturnPct=_weighted_returns,
    156         count='sum',
    157         EntryBar=_group_trades('EntryTime'),
    158         ExitBar=_group_trades('ExitTime'),
    159     )).dropna()
    161 return df, indicators, equity_data, trades

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/pandas/core/resample.py:329, in Resampler.aggregate(self, func, *args, **kwargs)
    321 @doc(
    322     _shared_docs["aggregate"],
    323     see_also=_agg_see_also_doc,
   (...)
    327 )
    328 def aggregate(self, func=None, *args, **kwargs):
--> 329     result = ResamplerWindowApply(self, func, args=args, kwargs=kwargs).agg()
    330     if result is None:
    331         how = func

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/pandas/core/apply.py:163, in Apply.agg(self)
    160     return self.apply_str()
    162 if is_dict_like(arg):
--> 163     return self.agg_dict_like()
    164 elif is_list_like(arg):
    165     # we require a list, but not a 'str'
    166     return self.agg_list_like()

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/pandas/core/apply.py:420, in Apply.agg_dict_like(self)
    417         results = {key: colg.agg(how) for key, how in arg.items()}
    418     else:
    419         # key used for column selection and output
--> 420         results = {
    421             key: obj._gotitem(key, ndim=1).agg(how) for key, how in arg.items()
    422         }
    424 # set the final keys
    425 keys = list(arg.keys())

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/pandas/core/apply.py:421, in <dictcomp>(.0)
    417         results = {key: colg.agg(how) for key, how in arg.items()}
    418     else:
    419         # key used for column selection and output
    420         results = {
--> 421             key: obj._gotitem(key, ndim=1).agg(how) for key, how in arg.items()
    422         }
    424 # set the final keys
    425 keys = list(arg.keys())

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/pandas/core/groupby/generic.py:269, in SeriesGroupBy.aggregate(self, func, engine, engine_kwargs, *args, **kwargs)
    266     return self._python_agg_general(func, *args, **kwargs)
    268 try:
--> 269     return self._python_agg_general(func, *args, **kwargs)
    270 except KeyError:
    271     # KeyError raised in test_groupby.test_basic is bc the func does
    272     #  a dictionary lookup on group.name, but group name is not
    273     #  pinned in _python_agg_general, only in _aggregate_named
    274     result = self._aggregate_named(func, *args, **kwargs)

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/pandas/core/groupby/generic.py:288, in SeriesGroupBy._python_agg_general(self, func, *args, **kwargs)
    285 f = lambda x: func(x, *args, **kwargs)
    287 obj = self._obj_with_exclusions
--> 288 result = self.grouper.agg_series(obj, f)
    289 res = obj._constructor(result, name=obj.name)
    290 return self._wrap_aggregated_output(res)

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/pandas/core/groupby/ops.py:994, in BaseGrouper.agg_series(self, obj, func, preserve_dtype)
    987 if len(obj) > 0 and not isinstance(obj._values, np.ndarray):
    988     # we can preserve a little bit more aggressively with EA dtype
    989     #  because maybe_cast_pointwise_result will do a try/except
    990     #  with _from_sequence.  NB we are assuming here that _from_sequence
    991     #  is sufficiently strict that it casts appropriately.
    992     preserve_dtype = True
--> 994 result = self._aggregate_series_pure_python(obj, func)
    996 npvalues = lib.maybe_convert_objects(result, try_float=False)
    997 if preserve_dtype:

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/pandas/core/groupby/ops.py:1015, in BaseGrouper._aggregate_series_pure_python(self, obj, func)
   1012 splitter = self._get_splitter(obj, axis=0)
   1014 for i, group in enumerate(splitter):
-> 1015     res = func(group)
   1016     res = libreduction.extract_result(res)
   1018     if not initialized:
   1019         # We only do this validation on the first iteration

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/pandas/core/groupby/generic.py:285, in SeriesGroupBy._python_agg_general.<locals>.<lambda>(x)
    283 def _python_agg_general(self, func, *args, **kwargs):
    284     func = com.is_builtin_func(func)
--> 285     f = lambda x: func(x, *args, **kwargs)
    287     obj = self._obj_with_exclusions
    288     result = self.grouper.agg_series(obj, f)

File ~/miniconda3/envs/tensorflow/lib/python3.10/site-packages/backtesting/_plotting.py:148, in _maybe_resample_data.<locals>._group_trades.<locals>.f(s, new_index, bars)
    145 if s.size:
    146     # Via int64 because on pandas recently broken datetime
    147     mean_time = int(bars.loc[s.index].view(int).mean())
--> 148     new_bar_idx = new_index.get_loc(mean_time, method='nearest')
    149     return new_bar_idx

TypeError: Index.get_loc() got an unexpected keyword argument 'method'

Steps to Reproduce

def CLOSE():
    return df.Close

def ATR():
    return df.ATR

def SIGNAL():
    return df.TRADE_SIGNAL_0

class MyStrategy(Strategy):
    def init(self):
        super().init()
        self.close = self.I(CLOSE)
        self.atr = self.I(ATR)
        self.signal = self.I(SIGNAL)

    def next(self):
        global pbar
        super().next()
        pbar.update(1)

        if self.trades:
            return

        if self.signal == LONG_SIGNAL:
            sl = self.close - self.atr * SL_FACTOR
            tp = self.close + self.atr * SL_FACTOR * TP_FACTOR

            self.buy(sl=sl, tp=tp, size=0.99)

pbar = tqdm(total=len(df))
bt = Backtest(df, MyStrategy, cash=100_000, margin=1/1, commission=0.0)
stats = bt.run()
print(stats)
bt.plot()

[ ! ] Error only occurs when using huge dataframe, in my case it had a length of 499197. Same code however worked when making it smaller.

Additional info

vladGriguta commented 12 months ago

hey @trueToastedCode, to save you some time (which I spent digging into this), this issue has been addressed by this commit. Although it is not released, you can just install the most recent version of the library from here

rbarton29 commented 10 months ago

I am getting the same issue on a large dataframe.

When I shrink the dataframe (~1,000 rows), I get a different error message: TypeError: bokeh.models.tools.Toolbar() got multiple values for keyword argument 'logo'

It seems with the latter issue I am also limited by the bokeh developers to release a bug fix.

I have Mac OS Ventura 13.5, M1 chip, Backtesting 0.3.3, Bokeh 3.2.2

valentinmk commented 8 months ago

To install latest version from repo instead of PYPI, please find instruction in here: https://github.com/kernc/backtesting.py/issues/987#issuecomment-1571602520