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-06-11 - Binary search for time-series lookups
**Learning:** O(n) `.filter()` over time-series data for latest-price lookups inside interval loops causes significant performance bottleneck.
**Action:** Always prefer O(log n) binary search when finding the latest element at or before a target timestamp in a sorted time-series array.
23 changes: 19 additions & 4 deletions src/agent/backtestRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@ function lotRound(qty: number): number {
return Math.floor(qty / 100) * 100;
}

function findLatestClose(series: Bar[] | undefined, asOf: number): number | null {
if (!series || series.length === 0) return null;
let low = 0;
let high = series.length - 1;
let ans = -1;
while (low <= high) {
const mid = (low + high) >> 1;
if (series[mid]!.time <= asOf) {
ans = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
return ans >= 0 ? series[ans]!.close : null;
}

function positionValue(p: BrokerPosition, price: number | null): number {
return (price ?? p.avgCost) * p.quantity * 1000;
}
Expand Down Expand Up @@ -298,8 +315,7 @@ 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;
return findLatestClose(vnindex, asOf);
};
const vnindexBaseline = vnindexAt(intervalTurns[0]!);
if (vnindexBaseline == null) throw new Error(`no VNINDEX data at first ${interval.label} turn`);
Expand All @@ -312,8 +328,7 @@ 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;
return findLatestClose(bars[sym], asOf);
};
broker.setPriceOverride(priceOverride);
cb.onTurnStart?.({ asOf, dateIso });
Expand Down
Loading