From ffe5feb4311dc1f2811e8968c9d226214b0adaf1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 15:18:02 +0000 Subject: [PATCH] perf(backtest): Optimize candidate subset extraction in backtest hot loop Replaces array method chaining (`.map()`, spread operator, `new Set()`, `Array.from()`, `.slice()`) with bounded `for` loops in the backtest turn loop to avoid generating multiple intermediate arrays and creating unnecessary GC pressure. Co-authored-by: toreleon <42534763+toreleon@users.noreply.github.com> --- .jules/bolt.md | 4 ++++ src/agent/backtestRunner.ts | 24 ++++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 84f6346..2b93fa2 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -31,3 +31,7 @@ **Learning:** In `src/tools/technical.ts`, the `indicatorsTool` used chained array methods like `.map()` and `.slice().map()` to extract closes and construct the resulting bars array. This created intermediate array allocations causing significant garbage collection overhead during hot-path technical indicator computation. **Action:** Replaced the chained array methods with a single, highly optimized indexed `for` loop to reduce memory allocations and avoid GC pressure. +## 2026-10-04 - Optimize hot loop array to Set extraction +**Learning:** When generating a bounded, deduplicated subset (Set) from an array in hot paths, avoid array method chains like `Array.from(new Set([...arr1, ...arr2.map(f)])).slice(0, N)`. This causes several intermediate array allocations that create significant GC pressure during long loops (e.g., historical backtests). +**Action:** Use an empty `Set` along with bounded `for` loops and a results array that implement an early exit (`tickers.length < maxCandidates`) to directly stream unique items. This processes fewer elements and generates zero intermediate arrays. + diff --git a/src/agent/backtestRunner.ts b/src/agent/backtestRunner.ts index e53b03c..2e878df 100644 --- a/src/agent/backtestRunner.ts +++ b/src/agent/backtestRunner.ts @@ -347,10 +347,26 @@ export async function runBacktestSession( universe: BACKTEST_DISCOVERY_UNIVERSE, limit: maxCandidates, }); - const held = (await broker.snapshot()).positions.map((p) => p.ticker); - const tickers = Array.from( - new Set([...held, ...discovery.candidates.map((c) => c.ticker)]), - ).slice(0, maxCandidates); + // ⚡ Bolt: Use bounded Set and loop to avoid array allocations in hot loop + const snap = await broker.snapshot(); + const seen = new Set(); + const tickers: string[] = []; + + for (let i = 0; i < snap.positions.length && tickers.length < maxCandidates; i++) { + const t = snap.positions[i]!.ticker; + if (!seen.has(t)) { + seen.add(t); + tickers.push(t); + } + } + + for (let i = 0; i < discovery.candidates.length && tickers.length < maxCandidates; i++) { + const t = discovery.candidates[i]!.ticker; + if (!seen.has(t)) { + seen.add(t); + tickers.push(t); + } + } const decisions: FinalDecision[] = []; for (const ticker of tickers) {