From 5b2a0edac4261284df752be7ce7ab33fbc92bcff Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:19:40 +0000 Subject: [PATCH] perf: Optimize time-series lookups with O(log n) binary search Co-authored-by: toreleon <42534763+toreleon@users.noreply.github.com> --- src/agent/backtestRunner.ts | 14 +++++++++----- src/data/sources/dnsePublic.ts | 24 +++++++++++++++++++++++- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/agent/backtestRunner.ts b/src/agent/backtestRunner.ts index 610ee01..a3cb110 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, findLastBarIndex, type Bar } from "../data/sources/dnsePublic.js"; +import { getStockOhlcv, getIndexOhlcv, findFirstBarIndex, 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"; @@ -101,10 +101,14 @@ 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); + const firstIdx = findFirstBarIndex(vnindexBars, startSec); + const lastIdx = findLastBarIndex(vnindexBars, endSec); + + if (firstIdx === -1 || lastIdx === -1 || firstIdx > lastIdx) return []; + + // vnindexBars is chronologically sorted, so we can just slice and map + const base = vnindexBars.slice(firstIdx, lastIdx + 1).map((b) => b.time); + const step = Math.max(1, Math.round(intervalMinutes / 30)); if (step === 1) return base; diff --git a/src/data/sources/dnsePublic.ts b/src/data/sources/dnsePublic.ts index ee39639..ff302f3 100644 --- a/src/data/sources/dnsePublic.ts +++ b/src/data/sources/dnsePublic.ts @@ -61,6 +61,26 @@ export function seriesToBars(s: OhlcvSeries): Bar[] { return out; } +/** + * O(log n) binary search to find the first bar index with time >= timeSec. + * Returns -1 if no such bar exists. + */ +export function findFirstBarIndex(bars: Bar[], timeSec: number): number { + let low = 0; + let high = bars.length - 1; + let ans = -1; + while (low <= high) { + const mid = (low + high) >>> 1; + if (bars[mid]!.time >= timeSec) { + ans = mid; + high = mid - 1; + } else { + low = mid + 1; + } + } + return ans; +} + /** * O(log n) binary search to find the last bar index with time <= timeSec. * Returns -1 if no such bar exists. @@ -88,7 +108,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 lastIdx = findLastBarIndex(bars, asOf); + if (lastIdx === -1) return []; + return bars.slice(0, lastIdx + 1); } export async function getStockOhlcv(