From 8702144387869958c8b5666ba4f5a252d8860d2b Mon Sep 17 00:00:00 2001 From: diay Date: Fri, 7 Aug 2026 13:23:31 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20apply=20Y=20minRange=20ab?= =?UTF-8?q?solute=20floor=20only=20for=20zero=20spans?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `rawRange * k || floor` only fell through when the product was exactly 0, so the absolute floor was easy to misread and did not express the intended zero-span semantics. Use an explicit finite/positive check in computeRange and the candle range helper. Fixes #18. Co-authored-by: Cursor --- src/math/__tests__/math.test.ts | 15 +++++++++++++++ src/math/range.ts | 9 ++++++++- src/useLivelineEngine.ts | 5 ++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/math/__tests__/math.test.ts b/src/math/__tests__/math.test.ts index 2bce73a..9e36b73 100644 --- a/src/math/__tests__/math.test.ts +++ b/src/math/__tests__/math.test.ts @@ -69,6 +69,21 @@ describe('computeRange', () => { const mid = (min + max) / 2 expect(mid).toBeCloseTo(50, 1) }) + + it('uses absolute floor only for zero-span data', () => { + const { min, max } = computeRange(pts([10, 10]), 10) + expect(max - min).toBeCloseTo(0.4) + }) + + it('does not force absolute floor for tiny nonzero spans', () => { + // Forcing 0.4 for every tiny span over-pads micro volatility into a + // flat-looking chart. Keep relative domain + margin; grid guards (#19) + // prevent hangs when the step size is denormal. + const rawRange = 1e-8 + const { min, max } = computeRange(pts([9, 9 + rawRange]), 9) + expect(max - min).toBeCloseTo(rawRange * (1 + 2 * 0.12), 12) + expect(max - min).toBeLessThan(0.4) + }) }) // -- detectMomentum -- diff --git a/src/math/range.ts b/src/math/range.ts index d987513..6ca7a63 100644 --- a/src/math/range.ts +++ b/src/math/range.ts @@ -29,7 +29,14 @@ export function computeRange( const rawRange = targetMax - targetMin const marginFactor = exaggerate ? 0.01 : 0.12 - const minRange = rawRange * (exaggerate ? 0.02 : 0.1) || (exaggerate ? 0.04 : 0.4) + // `rawRange * k || floor` skipped the absolute floor for any tiny nonzero + // span (JS || only falls through on 0/falsy), which produced denormal Y + // domains and could freeze the Y-grid loop. Apply the floor only when the + // span is zero / non-finite; keep relative min for real nonzero ranges. + const relativeMin = rawRange * (exaggerate ? 0.02 : 0.1) + const absoluteFloor = exaggerate ? 0.04 : 0.4 + const minRange = + rawRange > 0 && Number.isFinite(rawRange) ? relativeMin : absoluteFloor if (rawRange < minRange) { const mid = (targetMin + targetMax) / 2 diff --git a/src/useLivelineEngine.ts b/src/useLivelineEngine.ts index 7c29276..2c687cf 100644 --- a/src/useLivelineEngine.ts +++ b/src/useLivelineEngine.ts @@ -424,7 +424,10 @@ function computeCandleRange( if (!isFinite(min) || !isFinite(max)) return { min: 99, max: 101 } const range = max - min const margin = range * 0.12 - const minRange = range * 0.1 || 0.4 + // Same floor semantics as computeRange: do not use `||` (skips floor for + // tiny nonzero spans). Absolute floor only when span is zero / non-finite. + const relativeMin = range * 0.1 + const minRange = range > 0 && Number.isFinite(range) ? relativeMin : 0.4 if (range < minRange) { const mid = (min + max) / 2 return { min: mid - minRange / 2, max: mid + minRange / 2 }