diff --git a/.jules/bolt.md b/.jules/bolt.md index 9312d3f..15bc6eb 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -9,3 +9,7 @@ ## 2024-05-18 - Avoid unnecessary array allocations in frequent I/O paths **Learning:** `upsertSession` in `src/runtime/sessionStore.ts` is called very frequently (every time a session record is appended, which happens constantly during agent streaming). The original implementation used `.filter()` to remove the existing session and then pushed the updated one, resulting in significant garbage collection overhead and an O(N) array allocation on every single token/event stream chunk. Since this function is the bottleneck for chat interactivity, replacing `.filter()` with `.findIndex()` and in-place assignment yielded a > 2x speedup on session updates. **Action:** When updating arrays that back frequent disk I/O operations (like the session store), always prefer in-place mutation and sorting over immutable array recreation (`.filter()`, `.map()`) to minimize garbage collection pauses. + +## 2026-08-26 - Avoid O(N) array allocations in candidate discovery +**Learning:** In `src/tools/discover.ts`, the `buildCandidate` function was chaining `.map()`, `.slice()`, and `.reduce()` on large OHLCV bars arrays for every ticker in the universe (hundreds of stocks). This caused unnecessary O(N) memory allocations and garbage collection overhead in a hot path that runs at the start of every session. +**Action:** Replaced the chained array methods with indexed `for` loops that read directly from the source `bars` array. Updated the `rsi14` function to accept the source array and calculate its window iteratively to avoid `.slice(-15)` allocations. diff --git a/src/tools/discover.ts b/src/tools/discover.ts index 413e476..f44bb31 100644 --- a/src/tools/discover.ts +++ b/src/tools/discover.ts @@ -54,13 +54,15 @@ function pct(now: number, prev: number | undefined): number | null { return ((now - prev) / prev) * 100; } -function rsi14(closes: number[]): number | null { - if (closes.length < 15) return null; - const window = closes.slice(-15); +// ⚡ Bolt: Calculate RSI in-place directly from the bars array to avoid O(N) .slice() allocations. +function rsi14(bars: readonly { close: number }[]): number | null { + const len = bars.length; + if (len < 15) return null; let gains = 0; let losses = 0; - for (let i = 1; i < window.length; i++) { - const d = window[i]! - window[i - 1]!; + const startIdx = Math.max(0, len - 15); + for (let i = startIdx + 1; i < len; i++) { + const d = bars[i]!.close - bars[i - 1]!.close; if (d > 0) gains += d; else losses -= d; } @@ -91,16 +93,30 @@ async function buildCandidate(ticker: string): Promise { vol_ratio: null, }; } - const closes = bars.map((b) => b.close); - const vols = bars.map((b) => b.volume); - const last = closes[closes.length - 1]!; - const prev1w = closes[closes.length - 6]; - const prev1m = closes[closes.length - 22]; - const recentVol = vols.slice(-5).reduce((a, b) => a + b, 0) / 5; - const priorVol = - vols.length >= 25 - ? vols.slice(-25, -5).reduce((a, b) => a + b, 0) / 20 - : null; + // ⚡ Bolt: Removed .map() and .slice().reduce() chains to avoid O(N) array allocations per candidate. + const len = bars.length; + const last = bars[len - 1]!.close; + const prev1w = bars[len - 6]?.close; + const prev1m = bars[len - 22]?.close; + + let recentVolSum = 0; + const recentStart = Math.max(0, len - 5); + for (let i = recentStart; i < len; i++) { + recentVolSum += bars[i]!.volume; + } + const recentVol = recentVolSum / 5; + + let priorVol: number | null = null; + if (len >= 25) { + let priorVolSum = 0; + const priorStart = Math.max(0, len - 25); + const priorEnd = len - 5; + for (let i = priorStart; i < priorEnd; i++) { + priorVolSum += bars[i]!.volume; + } + priorVol = priorVolSum / 20; + } + const volRatio = priorVol != null && priorVol > 0 ? recentVol / priorVol : null; return { ticker, @@ -108,7 +124,7 @@ async function buildCandidate(ticker: string): Promise { latest_close: last, ret_1w: pct(last, prev1w), ret_1m: pct(last, prev1m), - rsi14: rsi14(closes), + rsi14: rsi14(bars), vol_ratio: volRatio, }; }