diff --git a/.jules/bolt.md b/.jules/bolt.md index 7985023..7e63e30 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -41,3 +41,6 @@ ## 2024-05-15 - Fast cache keys using integer math **Learning:** Calling `new Date().toISOString().slice()` in hot paths (like `cached()` key generation during a backtest loop) adds up significantly (~1ms per thousand calls). **Action:** When grouping cache keys by day, use fast integer math `Math.floor(unixSecs / 86400)` instead of string-based date allocations to maintain identical semantics while avoiding object instantiation. +## 2026-10-18 - Single-Pass Loop Consolidation +**Learning:** In backtest generation and portfolio/account summarization (`src/agent/backtestRunner.ts`, `src/broker/fhsc.ts`), multiple sequential `.reduce()` operations on the same large arrays (`turnRows`, `equityRows`, `paymentAccounts`) were causing redundant $O(n)$ iteration cycles and GC overhead. Replacing them with single-pass `for...of` loops successfully aggregated multiple metrics in one pass, yielding a clear algorithmic improvement without severely impacting readability. However, merely swapping a single `.reduce()` with a `for` loop on small arrays constitutes a micro-optimization that degrades declarative readability with no measurable real-world speedup. +**Action:** When extracting multiple aggregates from an array, combine them into a single `for...of` loop. Do not blindly replace single `.reduce()` calls unless they are in proven hot paths or chained with other functional passes. diff --git a/src/agent/backtestRunner.ts b/src/agent/backtestRunner.ts index 2e878df..70c72a9 100644 --- a/src/agent/backtestRunner.ts +++ b/src/agent/backtestRunner.ts @@ -465,13 +465,29 @@ export async function runBacktestSession( const totalReturn = (finalMtm / opts.initialCash - 1) * 100; const benchReturn = (finalBench / opts.initialCash - 1) * 100; let runningPeak = opts.initialCash; - const maxDD = equityRows.reduce((mn, r) => { - runningPeak = Math.max(runningPeak, r.mtm_vnd); - return Math.min(mn, r.mtm_vnd / runningPeak - 1); - }, 0); - const totalCost = turnRows.reduce((s, r) => s + (r.cost_usd ?? 0), 0); - const totalIn = turnRows.reduce((s, r) => s + (r.in_tokens ?? 0), 0); - const totalOut = turnRows.reduce((s, r) => s + (r.out_tokens ?? 0), 0); + + // ⚡ Bolt: Consolidated multiple .reduce() passes over turn rows and equity rows + // into single-pass for...of loops to significantly reduce GC overhead + // and iteration cycles in the backtest reporting hot path. + let maxDD = 0; + for (const r of equityRows) { + if (r.mtm_vnd > runningPeak) { + runningPeak = r.mtm_vnd; + } + const dd = r.mtm_vnd / runningPeak - 1; + if (dd < maxDD) { + maxDD = dd; + } + } + + let totalCost = 0; + let totalIn = 0; + let totalOut = 0; + for (const r of turnRows) { + totalCost += r.cost_usd ?? 0; + totalIn += r.in_tokens ?? 0; + totalOut += r.out_tokens ?? 0; + } const summary: SummaryPayload = { runId, diff --git a/src/broker/fhsc.ts b/src/broker/fhsc.ts index a91073b..b25ccfe 100644 --- a/src/broker/fhsc.ts +++ b/src/broker/fhsc.ts @@ -653,11 +653,16 @@ export class FHSCBroker implements Broker { const account = accountResult.status === "fulfilled" ? accountResult.value : ({} as FhscSubAccount); const portfolio = portfolioResults.flatMap((result) => (result.status === "fulfilled" ? result.value : [])); const positions = this.normalizePositions(portfolio); - const paymentCash = paymentAccounts.reduce( - (sum, account) => sum + numberOf(account.balance ?? account.total_balance), - 0, - ); - const blockedBalance = paymentAccounts.reduce((sum, account) => sum + numberOf(account.blocked_balance), 0); + + // ⚡ Bolt: Consolidated two separate .reduce() passes into a single for...of loop + // to improve execution speed when aggregating sub-account balances. + let paymentCash = 0; + let blockedBalance = 0; + for (const pa of paymentAccounts) { + paymentCash += numberOf(pa.balance ?? pa.total_balance); + blockedBalance += numberOf(pa.blocked_balance); + } + return { broker: NAME, cashVnd: