From 486def9dc32f70970a6ce552953f16d257724188 Mon Sep 17 00:00:00 2001 From: nyxst4ck Date: Wed, 5 Aug 2026 00:11:29 -0300 Subject: [PATCH] BUG: Size shared memory by materialized array, not lazy Index.nbytes RangeIndex is lazy: its .nbytes reports the size of the range object itself (a constant 132 bytes), not the int64 array it materializes into. SharedMemoryManager.arr2shm sized the block with vals.nbytes and then built an ndarray of vals.shape/vals.dtype.base over the buffer, so Backtest.optimize() on range-indexed data raised 'TypeError: buffer is too small for requested array' while bt.run() on the same data worked. Size the block from the same shape/dtype pair the buffer view is built with. For eager indexes (Index[int64], DatetimeIndex, tz-aware) the expression equals .nbytes, so only the broken RangeIndex path changes. Fixes #1237 --- backtesting/_util.py | 4 +++- backtesting/test/_test.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/backtesting/_util.py b/backtesting/_util.py index a76a3ee2f..408d764ff 100644 --- a/backtesting/_util.py +++ b/backtesting/_util.py @@ -309,7 +309,9 @@ def __exit__(self, *args, **kwargs): def arr2shm(self, vals): """Array to shared memory. Returns (shm_name, shape, dtype) used for restore.""" assert vals.ndim == 1, (vals.ndim, vals.shape, vals) - shm = self.SharedMemory(size=vals.nbytes, create=True) + # Not `vals.nbytes`; lazy indexes (e.g. RangeIndex) report their own, + # constant size rather than that of the array they materialize into + shm = self.SharedMemory(size=vals.size * vals.dtype.base.itemsize, create=True) # np.array can't handle pandas' tz-aware datetimes # https://github.com/numpy/numpy/issues/18279 buf = np.ndarray(vals.shape, dtype=vals.dtype.base, buffer=shm.buf) diff --git a/backtesting/test/_test.py b/backtesting/test/_test.py index 7a6e5d1d5..d74fde9f9 100644 --- a/backtesting/test/_test.py +++ b/backtesting/test/_test.py @@ -1273,6 +1273,16 @@ def test_optimize_datetime_index_with_timezone(self): res = Backtest(data, SmaCross).optimize(fast=range(2, 3), slow=range(4, 5)) self.assertGreater(res['# Trades'], 0) + def test_optimize_range_index(self): + # Data is passed to the workers through shared memory sized by + # `.nbytes`, which a lazy RangeIndex under-reports, previously raising + # "TypeError: buffer is too small for requested array". See GH issue #1237. + data: pd.DataFrame = GOOG.iloc[:100].reset_index(drop=True) + with self.assertWarnsRegex(UserWarning, 'index is not datetime'): + bt = Backtest(data, SmaCross) + res = bt.optimize(fast=range(2, 3), slow=range(4, 5)) + self.assertGreater(res['# Trades'], 0) + def test_sl_tp_values_in_trades_df(self): class S(_S): def next(self):