diff --git a/src/agent/backtestRunner.ts b/src/agent/backtestRunner.ts index 610ee01..b386285 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, findLastBarIndex, findFirstBarIndex, 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,11 @@ 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 []; + + 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(