Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-19 - Fast Lookups in Sorted Timeseries
**Learning:** O(n) operations like `Array.filter` inside hot loops for timeseries lookups (e.g., retrieving prices `asOf` a timestamp during thousands of backtest intervals) can create severe bottlenecks. The time-series data is naturally sorted by time, which allows for `O(log n)` lookups.
**Action:** When searching for the most recent data point up to a certain time in a sorted array, always use binary search instead of `filter()`. It provides a massive performance improvement.
25 changes: 18 additions & 7 deletions src/agent/backtestRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,24 @@ export async function runBacktestSession(
Math.floor(Date.now() / 1000),
);

const vnindexAt = (asOf: number): number | null => {
const series = vnindex.filter((b) => b.time <= asOf);
return series.length ? series[series.length - 1]!.close : null;
const findPriceAt = (barsList: Bar[] | undefined, asOf: number): number | null => {
if (!barsList || barsList.length === 0) return null;
let l = 0;
let r = barsList.length - 1;
let ans: number | null = null;
while (l <= r) {
const m = (l + r) >> 1;
if (barsList[m]!.time <= asOf) {
ans = barsList[m]!.close;
l = m + 1;
} else {
r = m - 1;
}
}
return ans;
};

const vnindexAt = (asOf: number): number | null => findPriceAt(vnindex, asOf);
const vnindexBaseline = vnindexAt(intervalTurns[0]!);
if (vnindexBaseline == null) throw new Error(`no VNINDEX data at first ${interval.label} turn`);

Expand All @@ -311,10 +325,7 @@ export async function runBacktestSession(
for (const asOf of intervalTurns) {
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 priceOverride = (sym: string): number | null => findPriceAt(bars[sym], asOf);
broker.setPriceOverride(priceOverride);
cb.onTurnStart?.({ asOf, dateIso });

Expand Down
Loading