fix: 일목 구름을 rawBars 기준으로 계산해 dev/프로덕션 동일화

서버 프로덕션 번들에서 lightweight-charts-indicators plot3·plot4가
spanA와 spanB에 동일 값을 줄 수 있어 구름이 사라짐. 로컬 vite dev는 정상.

plot0~4 의존을 제거하고 OHLCV rawBars만으로 선행스팬·구름을 계산한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Macbook
2026-06-02 23:06:43 +09:00
parent 6527793eb6
commit 2950442f69
+25 -31
View File
@@ -1368,7 +1368,7 @@ export class ChartManager {
return out; return out;
} }
/** 특정 바 인덱스에서 Donchian 중선을 계산 */ /** 특정 바 인덱스에서 Donchian 중선 (전환선·기준선·선행스팬B 공통) */
private _donchianAt(idx: number, period: number): number | null { private _donchianAt(idx: number, period: number): number | null {
const start = idx - period + 1; const start = idx - period + 1;
if (start < 0) return null; if (start < 0) return null;
@@ -1378,35 +1378,33 @@ export class ChartManager {
if (this.rawBars[i].low < lo) lo = this.rawBars[i].low; if (this.rawBars[i].low < lo) lo = this.rawBars[i].low;
} }
return (hi + lo) / 2; return (hi + lo) / 2;
} }
/** 구름 데이터 빌드: 과거(역사 데이터) + 미래 26봉 포함 */ /**
* 일목 구름: rawBars만으로 계산 (vite dev / 프로덕션 번들 동일 결과).
* lightweight-charts-indicators 의 plot3·plot4는 서버 프로덕션 빌드에서
* spanA===spanB 로 붕괴할 수 있어 사용하지 않는다.
*/
private _buildIchimokuCloudData( private _buildIchimokuCloudData(
_spanAPlot: PlotData, conversionPeriods: number,
_spanBPlot: PlotData, basePeriods: number,
convPlot: PlotData,
basePlot: PlotData,
displacement: number, displacement: number,
laggingPeriod: number, laggingPeriod: number,
): IchimokuCloudPoint[] { ): IchimokuCloudPoint[] {
// 라이브러리의 plot3/plot4(SpanA/SpanB)는 프로덕션 빌드에서
// 동일한 값을 반환하는 버그가 있으므로, convPlot(전환선)/basePlot(기준선)과
// rawBars 기반 _donchianAt으로 직접 재계산한다.
const N = this.rawBars.length; const N = this.rawBars.length;
const historical: IchimokuCloudPoint[] = []; const historical: IchimokuCloudPoint[] = [];
for (let i = displacement - 1; i < N; i++) { for (let i = displacement; i < N; i++) {
const srcIdx = i - (displacement - 1); const srcIdx = i - displacement;
const conv = convPlot[srcIdx]?.value; const tenkan = this._donchianAt(srcIdx, conversionPeriods);
const base = basePlot[srcIdx]?.value; const kijun = this._donchianAt(srcIdx, basePeriods);
if (conv == null || isNaN(conv) || base == null || isNaN(base)) continue; if (tenkan == null || kijun == null) continue;
const spanA = (conv + base) / 2; const spanA = (tenkan + kijun) / 2;
const spanB = this._donchianAt(srcIdx, laggingPeriod); const spanB = this._donchianAt(srcIdx, laggingPeriod);
if (spanB === null) continue; if (spanB == null) continue;
historical.push({ time: this.rawBars[i].time, spanA, spanB }); historical.push({ time: this.rawBars[i].time, spanA, spanB });
} }
// 미래 구름
if (N < 2) return historical; if (N < 2) return historical;
const interval = this.rawBars[N - 1].time - this.rawBars[N - 2].time; const interval = this.rawBars[N - 1].time - this.rawBars[N - 2].time;
const lastTime = this.rawBars[N - 1].time; const lastTime = this.rawBars[N - 1].time;
@@ -1415,12 +1413,12 @@ export class ChartManager {
for (let k = 1; k <= displacement; k++) { for (let k = 1; k <= displacement; k++) {
const srcIdx = N - displacement - 1 + k; const srcIdx = N - displacement - 1 + k;
if (srcIdx < 0 || srcIdx >= N) continue; if (srcIdx < 0 || srcIdx >= N) continue;
const conv = convPlot[srcIdx]?.value; const tenkan = this._donchianAt(srcIdx, conversionPeriods);
const base = basePlot[srcIdx]?.value; const kijun = this._donchianAt(srcIdx, basePeriods);
if (conv == null || isNaN(conv) || base == null || isNaN(base)) continue; if (tenkan == null || kijun == null) continue;
const spanA = (conv + base) / 2; const spanA = (tenkan + kijun) / 2;
const spanB = this._donchianAt(srcIdx, laggingPeriod); const spanB = this._donchianAt(srcIdx, laggingPeriod);
if (spanB === null) continue; if (spanB == null) continue;
future.push({ time: lastTime + k * interval, spanA, spanB }); future.push({ time: lastTime + k * interval, spanA, spanB });
} }
return [...historical, ...future]; return [...historical, ...future];
@@ -1782,17 +1780,13 @@ export class ChartManager {
return; 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 { 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 laggingPeriod = (config.params?.laggingSpan2Periods as number) ?? 52;
const cloud = this._buildIchimokuCloudData( const cloud = this._buildIchimokuCloudData(
spanAPlot, spanBPlot, convPlot, basePlot, displacement, laggingPeriod, conversionPeriods, basePeriods, displacement, laggingPeriod,
); );
plugin.setCloudData(cloud.length >= 2 ? cloud : []); plugin.setCloudData(cloud.length >= 2 ? cloud : []);
} }