perf: replace O(n^2) data slice with a windowed view (~2.6x faster py-next)

This commit is contained in:
KhizarImran
2026-07-22 22:37:26 +01:00
parent 494f157c13
commit 4972863b5f
2 changed files with 48 additions and 1 deletions
+33 -1
View File
@@ -4,12 +4,39 @@ from backtestingfx import _backtestingfx as _rust # type: ignore
import pandas as pd
class _DataView:
# ponytail: a window over the full bars list, visible up to the current bar.
# Slicing copies only the requested slice, not the growing prefix — this is
# what keeps data access O(1) per bar instead of O(n) (was O(n^2) overall).
__slots__ = ("_bars", "_len")
def __init__(self, bars):
self._bars = bars
self._len = 0
def __len__(self):
return self._len
def __getitem__(self, i):
if isinstance(i, slice):
return [self._bars[k] for k in range(*i.indices(self._len))]
if i < 0:
i += self._len
if not 0 <= i < self._len:
raise IndexError("bar index out of range")
return self._bars[i]
def __iter__(self):
return (self._bars[k] for k in range(self._len))
class Strategy:
def __init__(self):
self._bars: Any = None
self._bar: Any = None
self._broker: Any = None
self._index: int = 0
self._data_view: Any = None
@property
def positions(self):
@@ -17,7 +44,12 @@ class Strategy:
@property
def data(self):
return self._bars[: self._index + 1] if self._bars else []
if not self._bars:
return []
if self._data_view is None:
self._data_view = _DataView(self._bars)
self._data_view._len = self._index + 1
return self._data_view
@property
def index(self):
+15
View File
@@ -5,6 +5,21 @@ import unittest
import pandas as pd
from backtestingfx import Backtest, Strategy
from backtestingfx.backtest import _DataView
class DataViewTest(unittest.TestCase):
def test_window_hides_future_bars_and_slices_correctly(self):
view = _DataView(["a", "b", "c", "d"])
view._len = 3 # only the first 3 bars are "visible" so far
self.assertEqual(len(view), 3)
self.assertEqual(view[-1], "c") # last visible, not "d"
self.assertEqual(view[0], "a")
self.assertEqual(view[-2:], ["b", "c"]) # trailing slice, no "d"
self.assertEqual(list(view), ["a", "b", "c"])
with self.assertRaises(IndexError):
view[3] # "d" is in the future, not addressable yet
class BuyAndHold(Strategy):