From cc067ce2d1fe79829a9217e21288f89fdfc6056c Mon Sep 17 00:00:00 2001 From: Macbook Date: Tue, 2 Jun 2026 22:23:51 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9D=BC=EB=AA=A9=EA=B7=A0=ED=98=95?= =?UTF-8?q?=ED=91=9C=20=EA=B5=AC=EB=A6=84=20-=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EB=8D=95=EC=85=98=20=EB=B9=8C=EB=93=9C=20SpanA=3D=3DSpanB=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프로덕션 빌드에서 lightweight-charts-indicators 라이브러리가 plot3(SpanA)와 plot4(SpanB)에 동일한 값을 반환하는 버그 존재. SpanA==SpanB 시 폴리곤 면적=0으로 구름이 보이지 않음. 해결: 라이브러리의 plot3/plot4 대신 convPlot(전환선)/basePlot(기준선)과 rawBars 기반 _donchianAt()으로 SpanA/SpanB를 직접 계산 Co-authored-by: Cursor --- frontend/src/utils/ChartManager.ts | 41 +++++++++++------------ frontend/src/utils/IchimokuCloudPlugin.ts | 26 ++------------ 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/frontend/src/utils/ChartManager.ts b/frontend/src/utils/ChartManager.ts index 87ac240..ba1bce5 100644 --- a/frontend/src/utils/ChartManager.ts +++ b/frontend/src/utils/ChartManager.ts @@ -1382,34 +1382,31 @@ export class ChartManager { /** 구름 데이터 빌드: 과거(역사 데이터) + 미래 26봉 포함 */ private _buildIchimokuCloudData( - spanAPlot: PlotData, - spanBPlot: PlotData, - convPlot: PlotData, - basePlot: PlotData, + _spanAPlot: PlotData, + _spanBPlot: PlotData, + convPlot: PlotData, + basePlot: PlotData, displacement: number, laggingPeriod: number, ): IchimokuCloudPoint[] { - // 과거 구름 - const bMap = new Map( - (spanBPlot ?? []).filter(p => p.value != null && !isNaN(p.value)).map(p => [p.time, p.value]) - ); - const historical: IchimokuCloudPoint[] = (spanAPlot ?? []) - .filter(p => p.value != null && !isNaN(p.value) && bMap.has(p.time)) - .map(p => ({ time: p.time as number, spanA: p.value, spanB: bMap.get(p.time)! })); + // 라이브러리의 plot3/plot4(SpanA/SpanB)는 프로덕션 빌드에서 + // 동일한 값을 반환하는 버그가 있으므로, convPlot(전환선)/basePlot(기준선)과 + // rawBars 기반 _donchianAt으로 직접 재계산한다. + const N = this.rawBars.length; + const historical: IchimokuCloudPoint[] = []; - // 진단 로그: spanA/spanB 원본 값 비교 - const aValid = (spanAPlot ?? []).filter(p => p.value != null && !isNaN(p.value)); - const bValid = (spanBPlot ?? []).filter(p => p.value != null && !isNaN(p.value)); - const aLast3 = aValid.slice(-3).map(p => p.value); - const bLast3 = bValid.slice(-3).map(p => p.value); - const histLast3 = historical.slice(-3).map(p => ({ a: p.spanA, b: p.spanB, eq: p.spanA === p.spanB })); - const maxHistDiff = historical.length > 0 ? Math.max(...historical.map(p => Math.abs(p.spanA - p.spanB))) : 0; - console.log('[IchimokuCloud] BUILD RAW: spanA last3=', aLast3, 'spanB last3=', bLast3, - '| hist last3=', JSON.stringify(histLast3), '| maxDiff=', maxHistDiff, - '| histLen=', historical.length, 'sameRef=', spanAPlot === spanBPlot); + for (let i = displacement - 1; i < N; i++) { + const srcIdx = i - (displacement - 1); + const conv = convPlot[srcIdx]?.value; + const base = basePlot[srcIdx]?.value; + if (conv == null || isNaN(conv) || base == null || isNaN(base)) continue; + const spanA = (conv + base) / 2; + const spanB = this._donchianAt(srcIdx, laggingPeriod); + if (spanB === null) continue; + historical.push({ time: this.rawBars[i].time, spanA, spanB }); + } // 미래 구름 - const N = this.rawBars.length; if (N < 2) return historical; const interval = this.rawBars[N - 1].time - this.rawBars[N - 2].time; const lastTime = this.rawBars[N - 1].time; diff --git a/frontend/src/utils/IchimokuCloudPlugin.ts b/frontend/src/utils/IchimokuCloudPlugin.ts index 1dc76cd..ac5d5a9 100644 --- a/frontend/src/utils/IchimokuCloudPlugin.ts +++ b/frontend/src/utils/IchimokuCloudPlugin.ts @@ -71,35 +71,24 @@ class CloudRenderer implements IPrimitivePaneRenderer { // eslint-disable-next-line @typescript-eslint/no-explicit-any target.useBitmapCoordinateSpace(({ context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr }: any) => { - // spanA/spanB 차이 진단 - const maxPriceDiff = Math.max(...this._data.map(d => Math.abs(d.spanA - d.spanB))); - const mid = this._data[Math.floor(this._data.length / 2)]; - const last = this._data[this._data.length - 1]; - console.log('[IchimokuCloud] DATA CHECK: maxPriceDiff=', maxPriceDiff, - '| mid[', Math.floor(this._data.length / 2), '] spanA=', mid.spanA, 'spanB=', mid.spanB, - '| last spanA=', last.spanA, 'spanB=', last.spanB, - 'canvas_w=', ctx.canvas?.width, 'canvas_h=', ctx.canvas?.height, 'hpr=', hpr, 'vpr=', vpr); - - let drawn = 0, skippedTime = 0, skippedPrice = 0; for (let i = 0; i < this._data.length - 1; i++) { const a = this._data[i]; const b = this._data[i + 1]; const x1 = timeScale.timeToCoordinate(a.time as Time); const x2 = timeScale.timeToCoordinate(b.time as Time); - if (x1 == null || x2 == null) { skippedTime++; continue; } + if (x1 == null || x2 == null) continue; const aAy = this._series.priceToCoordinate(a.spanA); const aBy = this._series.priceToCoordinate(a.spanB); const bAy = this._series.priceToCoordinate(b.spanA); const bBy = this._series.priceToCoordinate(b.spanB); - if (aAy == null || aBy == null || bAy == null || bBy == null) { skippedPrice++; continue; } + if (aAy == null || aBy == null || bAy == null || bBy == null) continue; const bullish = (a.spanA + b.spanA) > (a.spanB + b.spanB); if (bullish && !this._bullishVisible) continue; if (!bullish && !this._bearishVisible) continue; - drawn++; ctx.save(); ctx.beginPath(); ctx.moveTo(x1 * hpr, aAy * vpr); @@ -111,8 +100,6 @@ class CloudRenderer implements IPrimitivePaneRenderer { ctx.fill(); ctx.restore(); } - console.log('[IchimokuCloud] drawn=', drawn, 'skippedTime=', skippedTime, 'skippedPrice=', skippedPrice, - 'bullishColor=', this._bullishColor, 'bearishColor=', this._bearishColor); }); } } @@ -153,17 +140,14 @@ export class IchimokuCloudPlugin implements ISeriesPrimitive