From 69f166a2c614a697f7404b8bbc7d3f2cae06ac4f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:33:43 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Replace=20O(N)=20array?= =?UTF-8?q?=20filtering=20with=20O(log=20N)=20binary=20search=20for=20chro?= =?UTF-8?q?nological=20time-series=20lookups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces `findLastBarIndex` in `src/data/sources/dnsePublic.ts`, replacing inefficient `array.filter` scans inside tight inner loops with an O(log N) binary search pattern, achieving large performance boosts when processing historical market data. Co-authored-by: toreleon <42534763+toreleon@users.noreply.github.com> --- .jules/bolt.md | 3 +++ src/agent/backtestRunner.ts | 13 ++++++++----- src/data/sources/dnsePublic.ts | 20 +++++++++++++++++++- 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..b3576d1 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-06-23 - O(N) Array filtering anti-pattern for chronological time-series data +**Learning:** Found a major performance bottleneck where O(N) `array.filter()` was being used to find the latest prices in a chronologically sorted time-series array of `Bars`, specifically within inner loops of `backtestRunner.ts`. When evaluating backtest intervals, this caused the app to needlessly iterate through all prior historical bars and create new sub-arrays on every single interval tick. +**Action:** Always utilize O(log N) binary search (like `findLastBarIndex`) for time-based lookups on chronological arrays instead of creating new filtered arrays with O(N) operations. diff --git a/src/agent/backtestRunner.ts b/src/agent/backtestRunner.ts index e91de2c..7d9e679 100644 --- a/src/agent/backtestRunner.ts +++ b/src/agent/backtestRunner.ts @@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto"; import { loadConfig } from "../config/loader.js"; import { getDb } from "../storage/db.js"; import { getBacktestBroker } from "../broker/index.js"; -import { getStockOhlcv, getIndexOhlcv, type Bar } from "../data/sources/dnsePublic.js"; +import { getStockOhlcv, getIndexOhlcv, findLastBarIndex, type Bar } from "../data/sources/dnsePublic.js"; import { DISCOVERY_UNIVERSE, discoverTickers } from "../tools/discover.js"; import { setActiveAsOf } from "./clock.js"; import { runTeamAnalysis } from "./team/index.js"; @@ -298,8 +298,9 @@ export async function runBacktestSession( ); const vnindexAt = (asOf: number): number | null => { - const series = vnindex.filter((b) => b.time <= asOf); - return series.length ? series[series.length - 1]!.close : null; + // ⚡ Bolt: Use O(log N) binary search instead of O(N) .filter for faster lookups + const idx = findLastBarIndex(vnindex, asOf); + return idx !== -1 ? vnindex[idx]!.close : null; }; const vnindexBaseline = vnindexAt(intervalTurns[0]!); if (vnindexBaseline == null) throw new Error(`no VNINDEX data at first ${interval.label} turn`); @@ -312,8 +313,10 @@ export async function runBacktestSession( throwIfAborted(cb.signal); const dateIso = ictLabel(asOf); const priceOverride = (sym: string): number | null => { - const series = bars[sym]?.filter((b) => b.time <= asOf) ?? []; - return series.length ? series[series.length - 1]!.close : null; + const series = bars[sym] ?? []; + // ⚡ Bolt: Use O(log N) binary search instead of O(N) .filter for faster lookups + const idx = findLastBarIndex(series, asOf); + return idx !== -1 ? series[idx]!.close : null; }; broker.setPriceOverride(priceOverride); cb.onTurnStart?.({ asOf, dateIso }); diff --git a/src/data/sources/dnsePublic.ts b/src/data/sources/dnsePublic.ts index 151733c..5b0c879 100644 --- a/src/data/sources/dnsePublic.ts +++ b/src/data/sources/dnsePublic.ts @@ -61,6 +61,22 @@ export function seriesToBars(s: OhlcvSeries): Bar[] { return out; } +export function findLastBarIndex(bars: Bar[], timeSec: number): number { + let low = 0; + let high = bars.length - 1; + let ans = -1; + while (low <= high) { + const mid = Math.floor((low + high) / 2); + if (bars[mid]!.time <= timeSec) { + ans = mid; + low = mid + 1; + } else { + high = mid - 1; + } + } + return ans; +} + function clipBars(bars: Bar[]): Bar[] { // Clip when an as-of clock is active (ALS or module override). When neither // is set, fall through unchanged — DNSE only returns historical data anyway. @@ -68,7 +84,9 @@ function clipBars(bars: Bar[]): Bar[] { asOfClock.getStore()?.asOfSec != null || isAsOfOverridden(); if (!hasOverride) return bars; const asOf = nowSec(); - return bars.filter((b) => b.time <= asOf); + const idx = findLastBarIndex(bars, asOf); + if (idx === -1) return []; + return bars.slice(0, idx + 1); } export async function getStockOhlcv( From 0defeef8e3ffa8d6252b6f49b5307b0173fe4150 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:40:46 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Update=20pnpm=20depende?= =?UTF-8?q?ncies=20to=20fix=20security=20vulnerabilities=20causing=20GitHu?= =?UTF-8?q?b=20CI=20pipeline=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed critical vulnerabilities from `undici` and `ws` in the project that were failing the `Dependency audit` workflow via `pnpm audit`. * Updated `undici` to `^6.27.0` to resolve proxy and fragment validation bypass vulnerabilities (`GHSA-vmh5-mc38-953g`, `GHSA-vxpw-j846-p89q`, `GHSA-hm92-r4w5-c3mj`). * Updated `ws` to resolve memory exhaustion DoS vulnerability (`GHSA-96hv-2xvq-fx4p`). Co-authored-by: toreleon <42534763+toreleon@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index dd2a5c4..9fd483d 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "ink-text-input": "^6.0.0", "react": "^18.3.1", "technicalindicators": "^3.1.0", - "undici": "^6.20.0", + "undici": "^6.27.0", "yaml": "^2.6.0", "zod": "^3.23.8" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e3f135..355928e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,8 @@ importers: specifier: ^3.1.0 version: 3.1.0 undici: - specifier: ^6.20.0 - version: 6.25.0 + specifier: ^6.27.0 + version: 6.27.0 yaml: specifier: ^2.6.0 version: 2.8.3 @@ -1516,12 +1516,12 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@6.25.0: - resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==} + undici@6.27.0: + resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==} engines: {node: '>=18.17'} - undici@7.25.0: - resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} engines: {node: '>=20.18.1'} universalify@0.1.2: @@ -1622,8 +1622,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -2281,7 +2281,7 @@ snapshots: parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 7.25.0 + undici: 7.28.0 whatwg-mimetype: 4.0.0 chownr@1.1.4: {} @@ -2590,7 +2590,7 @@ snapshots: type-fest: 4.41.0 widest-line: 5.0.0 wrap-ansi: 9.0.2 - ws: 8.20.0 + ws: 8.21.0 yoga-layout: 3.2.1 optionalDependencies: '@types/react': 18.3.28 @@ -2986,9 +2986,9 @@ snapshots: undici-types@6.21.0: {} - undici@6.25.0: {} + undici@6.27.0: {} - undici@7.25.0: {} + undici@7.28.0: {} universalify@0.1.2: {} @@ -3083,7 +3083,7 @@ snapshots: wrappy@1.0.2: {} - ws@8.20.0: {} + ws@8.21.0: {} yaml@2.8.3: {}