diff --git a/frontend/src/utils/ChartManager.ts b/frontend/src/utils/ChartManager.ts index c3a8d38..c766514 100644 --- a/frontend/src/utils/ChartManager.ts +++ b/frontend/src/utils/ChartManager.ts @@ -1368,7 +1368,7 @@ export class ChartManager { return out; } - /** 특정 바 인덱스에서 Donchian 중선을 계산 */ + /** 특정 바 인덱스에서 Donchian 중선 (전환선·기준선·선행스팬B 공통) */ private _donchianAt(idx: number, period: number): number | null { const start = idx - period + 1; if (start < 0) return null; @@ -1378,35 +1378,33 @@ export class ChartManager { if (this.rawBars[i].low < lo) lo = this.rawBars[i].low; } return (hi + lo) / 2; - } - - /** 구름 데이터 빌드: 과거(역사 데이터) + 미래 26봉 포함 */ + } + + /** + * 일목 구름: rawBars만으로 계산 (vite dev / 프로덕션 번들 동일 결과). + * lightweight-charts-indicators 의 plot3·plot4는 서버 프로덕션 빌드에서 + * spanA===spanB 로 붕괴할 수 있어 사용하지 않는다. + */ private _buildIchimokuCloudData( - _spanAPlot: PlotData, - _spanBPlot: PlotData, - convPlot: PlotData, - basePlot: PlotData, + conversionPeriods: number, + basePeriods: number, displacement: number, laggingPeriod: number, ): IchimokuCloudPoint[] { - // 라이브러리의 plot3/plot4(SpanA/SpanB)는 프로덕션 빌드에서 - // 동일한 값을 반환하는 버그가 있으므로, convPlot(전환선)/basePlot(기준선)과 - // rawBars 기반 _donchianAt으로 직접 재계산한다. const N = this.rawBars.length; const historical: IchimokuCloudPoint[] = []; - 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; + for (let i = displacement; i < N; i++) { + const srcIdx = i - displacement; + const tenkan = this._donchianAt(srcIdx, conversionPeriods); + const kijun = this._donchianAt(srcIdx, basePeriods); + if (tenkan == null || kijun == null) continue; + const spanA = (tenkan + kijun) / 2; const spanB = this._donchianAt(srcIdx, laggingPeriod); - if (spanB === null) continue; + if (spanB == null) continue; historical.push({ time: this.rawBars[i].time, spanA, spanB }); } - // 미래 구름 if (N < 2) return historical; const interval = this.rawBars[N - 1].time - this.rawBars[N - 2].time; const lastTime = this.rawBars[N - 1].time; @@ -1415,12 +1413,12 @@ export class ChartManager { for (let k = 1; k <= displacement; k++) { const srcIdx = N - displacement - 1 + k; if (srcIdx < 0 || srcIdx >= N) continue; - 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 tenkan = this._donchianAt(srcIdx, conversionPeriods); + const kijun = this._donchianAt(srcIdx, basePeriods); + if (tenkan == null || kijun == null) continue; + const spanA = (tenkan + kijun) / 2; const spanB = this._donchianAt(srcIdx, laggingPeriod); - if (spanB === null) continue; + if (spanB == null) continue; future.push({ time: lastTime + k * interval, spanA, spanB }); } return [...historical, ...future]; @@ -1782,17 +1780,13 @@ export class ChartManager { return; } - const spanAPlot = plots['plot3'] as PlotData | undefined; - const spanBPlot = plots['plot4'] as PlotData | undefined; - const convPlot = plots['plot0'] as PlotData ?? []; - const basePlot = plots['plot1'] as PlotData ?? []; - if (!spanAPlot || !spanBPlot) return; - const { senkou: displacement } = resolveIchimokuDisplacements(config.params); + const conversionPeriods = (config.params?.conversionPeriods as number) ?? 9; + const basePeriods = (config.params?.basePeriods as number) ?? 26; const laggingPeriod = (config.params?.laggingSpan2Periods as number) ?? 52; const cloud = this._buildIchimokuCloudData( - spanAPlot, spanBPlot, convPlot, basePlot, displacement, laggingPeriod, + conversionPeriods, basePeriods, displacement, laggingPeriod, ); plugin.setCloudData(cloud.length >= 2 ? cloud : []); }