From 65d470e69aac3acfdf4f7ef238ec49918a810482 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:57:14 +0000 Subject: [PATCH] perf(portfolio): replace .reduce() with for-loop for accumulation Co-authored-by: toreleon <42534763+toreleon@users.noreply.github.com> --- .jules/bolt.md | 4 ++++ src/tools/portfolio.ts | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 7985023..8eccce3 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -41,3 +41,7 @@ ## 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-12 - Replace .reduce() with for-loops to prevent object accumulation overhead +**Learning:** Using `.reduce()` to accumulate state across an array of objects (like calculating portfolio totals in `src/tools/portfolio.ts`) often involves destructuring or object mutations inside the reducer, which causes unnecessary garbage collection and performance degradation in hot paths. +**Action:** Replace `.reduce()` operations over arrays with a standard `for` loop and accumulate directly into scalar variables (`let total_a = 0`, etc.), then construct the final object once at the end. diff --git a/src/tools/portfolio.ts b/src/tools/portfolio.ts index 406d74a..11839e8 100644 --- a/src/tools/portfolio.ts +++ b/src/tools/portfolio.ts @@ -46,15 +46,18 @@ export async function shapeBrokerPortfolio( }), ); - const totals = positions.reduce( - (a, p) => { - a.cost_basis_vnd += p.cost_basis_vnd; - if (p.market_value_vnd != null) a.market_value_vnd += p.market_value_vnd; - if (p.unrealized_pnl_vnd != null) a.unrealized_pnl_vnd += p.unrealized_pnl_vnd; - return a; - }, - { cost_basis_vnd: 0, market_value_vnd: 0, unrealized_pnl_vnd: 0 }, - ); + let cost_basis_vnd = 0; + let market_value_vnd = 0; + let unrealized_pnl_vnd = 0; + + for (let i = 0; i < positions.length; i++) { + const p = positions[i]!; + cost_basis_vnd += p.cost_basis_vnd; + if (p.market_value_vnd != null) market_value_vnd += p.market_value_vnd; + if (p.unrealized_pnl_vnd != null) unrealized_pnl_vnd += p.unrealized_pnl_vnd; + } + + const totals = { cost_basis_vnd, market_value_vnd, unrealized_pnl_vnd }; const total_equity_vnd = snap.cashVnd + totals.market_value_vnd; return {