diff --git a/.jules/bolt.md b/.jules/bolt.md index 989a6ff..48161ba 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,3 +1,3 @@ -## 2024-05-14 - Time-Series O(log n) Search Pattern -**Learning:** `src/agent/backtestRunner.ts` makes heavy use of `.filter((b) => b.time <= asOf)` to get the latest close price inside loop bounds, changing potentially O(1) loop lookups or O(log n) lookups into repetitive O(n) filtering over large arrays. -**Action:** Expose `findLastBarIndex` in `src/data/sources/dnsePublic.ts` and use binary search or track indices explicitly in tight loops. +## 2024-05-24 - O(log n) over O(n) array operations for Time-Series +**Learning:** Backtest intervals involve frequent repeated lookups on chronologically sorted time-series market data arrays. Existing implementations using `.filter()` and `.sort()` on large `Bar` arrays have an $O(N \log N)$ complexity and unnecessarily reiterate arrays that are already inherently sorted. +**Action:** Replace full-array filters and sorts on `Bar` data with a combination of `findLastBarIndex()` (for $O(\log n)$ boundary lookups) followed by `.slice()`. This pattern is dramatically more CPU-efficient on large historical datasets. diff --git a/src/agent/backtestRunner.ts b/src/agent/backtestRunner.ts index 610ee01..59753e5 100644 --- a/src/agent/backtestRunner.ts +++ b/src/agent/backtestRunner.ts @@ -101,10 +101,16 @@ function parseBacktestInterval(value: string | undefined): { label: string; minu } function intervalCloses(vnindexBars: Bar[], startSec: number, endSec: number, intervalMinutes: number): number[] { - const base = vnindexBars - .filter((b) => b.time >= startSec && b.time <= endSec) - .map((b) => b.time) - .sort((a, b) => a - b); + // ⚡ Bolt: Optimize sub-interval extraction from O(n log n) filter+sort to O(log n) binary search + const startIdx = findLastBarIndex(vnindexBars, startSec - 1) + 1; + const endIdx = findLastBarIndex(vnindexBars, endSec); + + if (startIdx > endIdx || startIdx >= vnindexBars.length || endIdx < 0) { + return []; + } + + const base = vnindexBars.slice(startIdx, endIdx + 1).map((b) => b.time); + const step = Math.max(1, Math.round(intervalMinutes / 30)); if (step === 1) return base;