diff --git a/package.json b/package.json index 92516ea..b88a672 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "better-sqlite3" ], "overrides": { - "undici": ">=7.28.0 <8.0.0", + "undici": ">=7.29.0 <8.0.0", "ws": ">=8.21.0" } }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index abb401c..5e9a243 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - undici: '>=7.28.0 <8.0.0' + undici: '>=7.29.0 <8.0.0' ws: '>=8.21.0' importers: @@ -37,8 +37,8 @@ importers: specifier: ^3.1.0 version: 3.1.0 undici: - specifier: '>=7.28.0 <8.0.0' - version: 7.28.0 + specifier: '>=7.29.0 <8.0.0' + version: 7.29.0 yaml: specifier: ^2.6.0 version: 2.8.3 @@ -1520,8 +1520,8 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@7.28.0: - resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + undici@7.29.0: + resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} engines: {node: '>=20.18.1'} universalify@0.1.2: @@ -2281,7 +2281,7 @@ snapshots: parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 7.28.0 + undici: 7.29.0 whatwg-mimetype: 4.0.0 chownr@1.1.4: {} @@ -2986,7 +2986,7 @@ snapshots: undici-types@6.21.0: {} - undici@7.28.0: {} + undici@7.29.0: {} universalify@0.1.2: {} diff --git a/src/tools/discover.ts b/src/tools/discover.ts index 413e476..838df56 100644 --- a/src/tools/discover.ts +++ b/src/tools/discover.ts @@ -54,23 +54,34 @@ function pct(now: number, prev: number | undefined): number | null { return ((now - prev) / prev) * 100; } -function rsi14(closes: number[]): number | null { - if (closes.length < 15) return null; - const window = closes.slice(-15); +// Optimization: Avoids intermediate array allocations by iterating directly over the bars array +function rsi14(bars: readonly { close: number }[]): number | null { + const len = bars.length; + if (len < 15) return null; + let gains = 0; let losses = 0; - for (let i = 1; i < window.length; i++) { - const d = window[i]! - window[i - 1]!; + const start = len - 15; + let prev = bars[start]!.close; + + for (let i = start + 1; i < len; i++) { + const curr = bars[i]!.close; + const d = curr - prev; if (d > 0) gains += d; else losses -= d; + prev = curr; } - const avgG = gains / 14; + const avgL = losses / 14; if (avgL === 0) return 100; + const avgG = gains / 14; const rs = avgG / avgL; return 100 - 100 / (1 + rs); } +// Optimization: Computes metrics (volRatio, RSI, pct) using direct index lookups and loops +// instead of array.map, slice, and reduce, significantly reducing memory allocations in hot paths. +// Impact: Expected to reduce GC pressure and speed up discovery ticker loops. async function buildCandidate(ticker: string): Promise { const to = nowSec(); const from = to - 90 * DAY; @@ -91,16 +102,25 @@ async function buildCandidate(ticker: string): Promise { vol_ratio: null, }; } - const closes = bars.map((b) => b.close); - const vols = bars.map((b) => b.volume); - const last = closes[closes.length - 1]!; - const prev1w = closes[closes.length - 6]; - const prev1m = closes[closes.length - 22]; - const recentVol = vols.slice(-5).reduce((a, b) => a + b, 0) / 5; - const priorVol = - vols.length >= 25 - ? vols.slice(-25, -5).reduce((a, b) => a + b, 0) / 20 - : null; + const len = bars.length; + const last = bars[len - 1]!.close; + const prev1w = bars[len - 6]?.close; + const prev1m = bars[len - 22]?.close; + + let recentVolSum = 0; + for (let i = Math.max(0, len - 5); i < len; i++) { + recentVolSum += bars[i]!.volume; + } + const recentVol = recentVolSum / 5; + + let priorVol = null; + if (len >= 25) { + let priorVolSum = 0; + for (let i = len - 25; i < len - 5; i++) { + priorVolSum += bars[i]!.volume; + } + priorVol = priorVolSum / 20; + } const volRatio = priorVol != null && priorVol > 0 ? recentVol / priorVol : null; return { ticker, @@ -108,7 +128,7 @@ async function buildCandidate(ticker: string): Promise { latest_close: last, ret_1w: pct(last, prev1w), ret_1m: pct(last, prev1m), - rsi14: rsi14(closes), + rsi14: rsi14(bars), vol_ratio: volRatio, }; }