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

plot(resample='interval') doesn't work. #919

Open moonhwilee81 opened 1 year ago

moonhwilee81 commented 1 year ago

Hi. Thanks for providing such a nice library.

I am a beginner developer, basktesting.py documentation, github resources, and questions and answers to organize my backtesting system.

I have a question. I'm using 5 minutes kline data, so I need to set resample to false to see the whole period.

I looked for several answers to solve this problem, and I found that I can down sample only the plot without affecting the backtesting results. When I plot, it says that I can solve the problem with resample=interval(time unit), but no matter what I do, it doesn't work.

How can I solve this problem? Any help would be greatly appreciated.

Be happy everyone!

kernc commented 1 year ago

When I plot, it says that I can solve the problem with resample=interval(time unit), but no matter what I do, it doesn't work.

Saying simply "it doesn't work" is not particularly helpful. :stuck_out_tongue: Can you show some of your code and more thoroughly describe "doesn't work" error output?

moonhwilee81 commented 1 year ago

> ok. this is my plot code. bt.plot(superimpose=False, resample='1D')

> and this is an error message.

Traceback (most recent call last):
  File "c:/Users/moone/Downloads/projects/_sa_backup/230223_rsid_fs_backtesting/bt_rsid.py", line 261, in <module>
    print(back_testing())
  File "c:/Users/moone/Downloads/projects/_sa_backup/230223_rsid_fs_backtesting/bt_rsid.py", line 235, in back_testing
    bt.plot(superimpose=False, resample='1D')
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\backtesting\backtesting.py", line 1592, in plot
    return plot(
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\backtesting\_plotting.py", line 203, in plot
    df, indicators, equity_data, trades = _maybe_resample_data(
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\backtesting\_plotting.py", line 153, in _maybe_resample_data
    trades = trades.assign(count=1).resample(freq, on='ExitTime', label='right').agg(dict(
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\resample.py", line 352, in aggregate
    result = ResamplerWindowApply(self, func, args=args, kwargs=kwargs).agg()
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\apply.py", line 172, in agg
    return self.agg_dict_like()
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\apply.py", line 504, in agg_dict_like
    results = {
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\apply.py", line 505, in <dictcomp>
    key: obj._gotitem(key, ndim=1).agg(how) for key, how in arg.items()
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\groupby\generic.py", line 297, in aggregate
    return self._python_agg_general(func, *args, **kwargs)
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\groupby\groupby.py", line 1682, in _python_agg_general
    result = self.grouper.agg_series(obj, f)
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\groupby\ops.py", line 1081, in agg_series
    result = self._aggregate_series_pure_python(obj, func)
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\groupby\ops.py", line 1104, in _aggregate_series_pure_python
    res = func(group)
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\groupby\groupby.py", line 1668, in <lambda>
    f = lambda x: func(x, *args, **kwargs)
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\backtesting\_plotting.py", line 147, in f
    mean_time = int(bars.loc[s.index].view(int).mean())
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\series.py", line 839, in view
    res_ser = self._constructor(res_values, index=self.index)
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\series.py", line 461, in __init__
    com.require_length_match(data, index)
  File "C:\Users\moone\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\common.py", line 571, in require_length_match
    raise ValueError(
ValueError: Length of values (2) does not match length of index (1)

> I thought I read somewhere here that resample=interval, but according to the popup in vscode, resample is a bool. I apologize if I made a mistake. Thanks for the good library and kind reply :-)


(*, results: Series = None, filename: Any | None = None, plot_width: Any | None = None, plot_equity: bool = True, plot_return: bool = False, plot_pl: bool = True, plot_volume: bool = True, plot_drawdown: bool = False, smooth_equity: bool = False, relative_equity: bool = True, superimpose: bool | str = True, resample: bool = True, reverse_indicators: bool = False, show_legend: bool = True, open_browser: bool = True) -> LayoutDOM
Plot the progression of the last backtest run.
Zoroastrian-nova commented 11 months ago

I got the same error, too. Here is my error message:

ValueError Traceback (most recent call last) Cell In[8], line 3 1 from backtesting import set_bokeh_output 2 set_bokeh_output(True) ----> 3 bt.plot()

File d:\Program Files\Python\Lib\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 d:\Program Files\Python\Lib\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 d:\Program Files\Python\Lib\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 d:\Program Files\Python\Lib\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 d:\Program Files\Python\Lib\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 d:\Program Files\Python\Lib\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 d:\Program Files\Python\Lib\site-packages\pandas\core\apply.py:421, in (.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 d:\Program Files\Python\Lib\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 d:\Program Files\Python\Lib\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 d:\Program Files\Python\Lib\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 d:\Program Files\Python\Lib\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 d:\Program Files\Python\Lib\site-packages\pandas\core\groupby\generic.py:285, in SeriesGroupBy._python_agg_general..(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 d:\Program Files\Python\Lib\site-packages\backtesting_plotting.py:147, in _maybe_resample_data.._group_trades..f(s, new_index, bars) 144 def f(s, new_index=pd.Index(df.index.view(int)), bars=trades[column]): 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')[0] 149 return new_bar_idx

File d:\Program Files\Python\Lib\site-packages\pandas\core\series.py:858, in Series.view(self, dtype) 855 # self.array instead of self._values so we piggyback on PandasArray 856 # implementation 857 res_values = self.array.view(dtype) --> 858 res_ser = self._constructor(res_values, index=self.index, copy=False) 859 if isinstance(res_ser._mgr, SingleBlockManager) and using_copy_on_write(): 860 blk = res_ser._mgr._block

File d:\Program Files\Python\Lib\site-packages\pandas\core\series.py:500, in Series.init(self, data, index, dtype, name, copy, fastpath) 498 index = default_index(len(data)) 499 elif is_list_like(data): --> 500 com.require_length_match(data, index) 502 # create/copy the manager 503 if isinstance(data, (SingleBlockManager, SingleArrayManager)):

File d:\Program Files\Python\Lib\site-packages\pandas\core\common.py:576, in require_length_match(data, index) 572 """ 573 Check the length of data matches the length of the index. 574 """ 575 if len(data) != len(index): --> 576 raise ValueError( 577 "Length of values " 578 f"({len(data)}) " 579 "does not match length of index " 580 f"({len(index)})" 581 )

ValueError: Length of values (2) does not match length of index (1)

eervin123 commented 9 months ago

Any update on this issue?

kernc commented 9 months ago

No bug-reproducing test case ...

eervin123 commented 9 months ago

It's happening to a friend. I'll get him to post a more thorough bug report @kernc. Thanks.