일목균형표 수정
This commit is contained in:
@@ -39,8 +39,15 @@ import {
|
||||
getIchimokuPlotTitle,
|
||||
isIchimokuCloudVisible,
|
||||
resolveIchimokuCloudColors,
|
||||
resolveIchimokuDisplacements,
|
||||
} from './ichimokuConfig';
|
||||
import type { PlotDef, HLineStyle } from './indicatorRegistry';
|
||||
import { mapHistogramSeriesData, histogramBarColor } from './plotColorUtils';
|
||||
import {
|
||||
applyPaneSeparatorToChartOptions,
|
||||
resolveChartPaneSeparatorOptions,
|
||||
type ChartPaneSeparatorOptions,
|
||||
} from '../types/chartPaneSeparator';
|
||||
|
||||
function plotSeriesLineStyle(style?: HLineStyle): number {
|
||||
if (style === 'dashed') return LineStyle.Dashed;
|
||||
@@ -217,6 +224,10 @@ export class ChartManager {
|
||||
/** 차트 하단 거래량 pane 표시 */
|
||||
private _volumeVisible = true;
|
||||
|
||||
/** pane 간 구분선 (설정 화면) */
|
||||
private _paneSeparatorOptions: ChartPaneSeparatorOptions =
|
||||
resolveChartPaneSeparatorOptions(null, 'dark');
|
||||
|
||||
/** applyPaneLayout / resetPaneHeights 에서 측정한 마지막 가용 높이(px) */
|
||||
private _lastLayoutAvailableHeight?: number;
|
||||
|
||||
@@ -231,7 +242,7 @@ export class ChartManager {
|
||||
background: { type: ColorType.Solid, color: t.bgColor },
|
||||
textColor: t.textColor,
|
||||
fontSize: 12,
|
||||
panes: { enableResize: false },
|
||||
panes: { enableResize: false, separatorColor: 'transparent', separatorHoverColor: 'transparent' },
|
||||
},
|
||||
grid: {
|
||||
vertLines: { color: t.gridColor },
|
||||
@@ -258,6 +269,8 @@ export class ChartManager {
|
||||
});
|
||||
|
||||
this._applyDisplayTimezoneOptions();
|
||||
this._paneSeparatorOptions = resolveChartPaneSeparatorOptions(null, theme);
|
||||
this.chart.applyOptions(applyPaneSeparatorToChartOptions(this._paneSeparatorOptions));
|
||||
|
||||
// 크로스헤어 이동 시 hover 중인 시리즈 추적 (오버레이 클릭 감지에 사용)
|
||||
this.chart.subscribeCrosshairMove((params: MouseEventParams<Time>) => {
|
||||
@@ -470,7 +483,11 @@ export class ChartManager {
|
||||
this.currentTheme = theme;
|
||||
const t = getTheme(theme);
|
||||
this.chart.applyOptions({
|
||||
layout: { background: { type: ColorType.Solid, color: t.bgColor }, textColor: t.textColor },
|
||||
layout: {
|
||||
background: { type: ColorType.Solid, color: t.bgColor },
|
||||
textColor: t.textColor,
|
||||
panes: applyPaneSeparatorToChartOptions(this._paneSeparatorOptions).layout.panes,
|
||||
},
|
||||
grid: { vertLines: { color: t.gridColor }, horzLines: { color: t.gridColor } },
|
||||
crosshair: { vertLine: { color: t.crosshairColor, labelBackgroundColor: '#9598A1' }, horzLine: { color: t.crosshairColor, labelBackgroundColor: '#9598A1' } },
|
||||
timeScale: { borderColor: t.borderColor },
|
||||
@@ -480,6 +497,20 @@ export class ChartManager {
|
||||
this._applyVolumeSeriesTheme(t);
|
||||
}
|
||||
|
||||
getContainer(): HTMLElement {
|
||||
return this.container;
|
||||
}
|
||||
|
||||
getPaneSeparatorOptions(): ChartPaneSeparatorOptions {
|
||||
return this._paneSeparatorOptions;
|
||||
}
|
||||
|
||||
setPaneSeparatorOptions(opts: ChartPaneSeparatorOptions): void {
|
||||
this._paneSeparatorOptions = opts;
|
||||
this.chart.applyOptions(applyPaneSeparatorToChartOptions(opts));
|
||||
this._notifyPaneLayout();
|
||||
}
|
||||
|
||||
private _applyMainSeriesTheme(t: ThemeTokens): void {
|
||||
if (!this.mainSeries) return;
|
||||
switch (this.currentChartType) {
|
||||
@@ -582,14 +613,7 @@ export class ChartManager {
|
||||
let series: ISeriesApi<SeriesType>;
|
||||
if (plotDef.type === 'histogram') {
|
||||
series = this.chart.addSeries(HistogramSeries, { color: plotDef.color }, pane);
|
||||
const colorData = filtered.map((p, i, arr) => ({
|
||||
time: p.time as Time,
|
||||
value: p.value,
|
||||
color: p.color ?? (i > 0 && p.value < arr[i - 1].value
|
||||
? '#ef535088'
|
||||
: p.value < 0 ? '#ef535088' : `${plotDef.color}88`),
|
||||
}));
|
||||
series.setData(colorData);
|
||||
series.setData(mapHistogramSeriesData(filtered, plotDef));
|
||||
seriesMeta.push({ plotId: plotDef.id, isHistogram: true, color: plotDef.color });
|
||||
} else {
|
||||
const isPlotVisible = !indicatorHidden && config.plotVisibility?.[plotDef.id] !== false;
|
||||
@@ -743,6 +767,8 @@ export class ChartManager {
|
||||
await this.addIndicator(ind);
|
||||
}
|
||||
|
||||
if (this._indicatorLoadStale(loadGen)) return;
|
||||
|
||||
// 빈 pane stretch 정리 + 캔들 pane 비율 복구
|
||||
this.resetPaneHeights();
|
||||
this._notifyPaneLayout();
|
||||
@@ -907,7 +933,7 @@ export class ChartManager {
|
||||
_config: IndicatorConfig,
|
||||
seriesList: ISeriesApi<SeriesType>[],
|
||||
): Promise<{ cloudPlugin: IchimokuCloudPlugin; seriesMeta: IndicatorEntry['seriesMeta'] }> {
|
||||
const displacement = (_config.params?.displacement as number) ?? 26;
|
||||
const { senkou: displacement } = resolveIchimokuDisplacements(_config.params);
|
||||
const laggingPeriod = (_config.params?.laggingSpan2Periods as number) ?? 52;
|
||||
const seriesMeta: IndicatorEntry['seriesMeta'] = [];
|
||||
|
||||
@@ -1233,7 +1259,7 @@ export class ChartManager {
|
||||
// ── IchimokuCloud: SpanA/SpanB 미래 프로젝션 갱신 ──────────────────
|
||||
if (updateFutureSpans && entry.type === 'IchimokuCloud') {
|
||||
const config = entry.config;
|
||||
const disp = (config.params?.displacement as number) ?? 26;
|
||||
const { senkou: disp } = resolveIchimokuDisplacements(config.params);
|
||||
const lagPeriod = (config.params?.laggingSpan2Periods as number) ?? 52;
|
||||
const convPlot = (plots['plot0'] as PlotData) ?? [];
|
||||
const basePlot = (plots['plot1'] as PlotData) ?? [];
|
||||
@@ -1268,11 +1294,12 @@ export class ChartManager {
|
||||
|
||||
// ── Histogram: 색상 재계산 ──────────────────────────────────────────
|
||||
if (meta.isHistogram) {
|
||||
const prevVal = plotData.length >= 2 ? plotData[plotData.length - 2]?.value : null;
|
||||
const isDown = prevVal !== null && (curVal as number) < (prevVal as number);
|
||||
const isNeg = (curVal as number) < 0;
|
||||
const barColor = lastPoint.color
|
||||
?? (isDown || isNeg ? '#ef535088' : `${meta.color}88`);
|
||||
const plotDef = (entry.config?.plots ?? getIndicatorDef(entry.type)?.plots ?? [])
|
||||
.find(p => p.id === meta.plotId);
|
||||
const prevVal = plotData.length >= 2 ? plotData[plotData.length - 2]?.value : null;
|
||||
const barColor = plotDef
|
||||
? histogramBarColor(curVal as number, prevVal, plotDef, lastPoint.color)
|
||||
: (lastPoint.color ?? '#26A69A88');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(entry.seriesList[i] as ISeriesApi<any>).update({
|
||||
time: lastPoint.time as Time,
|
||||
@@ -1294,6 +1321,29 @@ export class ChartManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** histogram 막대 색상(상승/하락) 변경 후 전체 재색칠 */
|
||||
private async _repaintHistogramSeries(indicatorId: string, config: IndicatorConfig): Promise<void> {
|
||||
const entry = this.indicators.get(indicatorId);
|
||||
if (!entry || this.rawBars.length === 0) return;
|
||||
const plotDefs = config.plots ?? getIndicatorDef(config.type)?.plots ?? [];
|
||||
const plotById = Object.fromEntries(plotDefs.map((p: PlotDef) => [p.id, p]));
|
||||
try {
|
||||
const { plots } = await calculateIndicator(config.type, this.rawBars.slice(), config.params ?? {});
|
||||
for (let i = 0; i < entry.seriesList.length; i++) {
|
||||
const meta = entry.seriesMeta[i];
|
||||
if (!meta?.isHistogram) continue;
|
||||
const plot = plotById[meta.plotId];
|
||||
if (!plot) continue;
|
||||
const plotData = plots[meta.plotId] as PlotData | undefined;
|
||||
if (!plotData?.length) continue;
|
||||
const filtered = plotData.filter(p => p.value !== null && !isNaN(p.value));
|
||||
if (filtered.length === 0) continue;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(entry.seriesList[i] as ISeriesApi<any>).setData(mapHistogramSeriesData(filtered, plot));
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
/** 일목균형표 구름 플러그인 — 색상·가시성·데이터 동기화 */
|
||||
private _applyIchimokuCloudPlugin(
|
||||
plugin: IchimokuCloudPlugin,
|
||||
@@ -1302,6 +1352,7 @@ export class ChartManager {
|
||||
): void {
|
||||
const colors = resolveIchimokuCloudColors(config.cloudColors);
|
||||
plugin.setColors(colors.bullishColor, colors.bearishColor);
|
||||
plugin.setVisibility(colors.bullishVisible !== false, colors.bearishVisible !== false);
|
||||
|
||||
if (!isIchimokuCloudVisible(config)) {
|
||||
plugin.setCloudData([]);
|
||||
@@ -1314,7 +1365,7 @@ export class ChartManager {
|
||||
const basePlot = plots['plot1'] as PlotData ?? [];
|
||||
if (!spanAPlot || !spanBPlot) return;
|
||||
|
||||
const displacement = (config.params?.displacement as number) ?? 26;
|
||||
const { senkou: displacement } = resolveIchimokuDisplacements(config.params);
|
||||
const laggingPeriod = (config.params?.laggingSpan2Periods as number) ?? 52;
|
||||
|
||||
const cloud = this._buildIchimokuCloudData(
|
||||
@@ -1990,6 +2041,12 @@ export class ChartManager {
|
||||
}
|
||||
|
||||
entry.config = config;
|
||||
if (entry.seriesMeta.some(m => m.isHistogram)) {
|
||||
void this._repaintHistogramSeries(config.id, config);
|
||||
}
|
||||
if (entry.seriesMeta.some(m => m.isHistogram)) {
|
||||
void this._repaintHistogramSeries(config.id, config);
|
||||
}
|
||||
|
||||
if (this._candleOnlyLayout && this._isSubPaneIndicator(entry)) {
|
||||
this._hideSubPaneIndicator(entry);
|
||||
@@ -2773,18 +2830,10 @@ export class ChartManager {
|
||||
if (filtered.length === 0) continue;
|
||||
|
||||
if (meta.isHistogram) {
|
||||
const plotDef = (entry.config?.plots ?? getIndicatorDef(entry.type)?.plots ?? [])
|
||||
.find(p => p.id === meta.plotId) ?? { id: meta.plotId, title: '', color: meta.color, type: 'histogram' as const };
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(series as ISeriesApi<any>).setData(
|
||||
filtered.map((p, idx, arr) => ({
|
||||
time: p.time as Time,
|
||||
value: p.value,
|
||||
color: p.color ?? (
|
||||
idx > 0 && p.value < arr[idx - 1].value
|
||||
? '#ef535088'
|
||||
: p.value < 0 ? '#ef535088' : `${meta.color}88`
|
||||
),
|
||||
}))
|
||||
);
|
||||
(series as ISeriesApi<any>).setData(mapHistogramSeriesData(filtered, plotDef));
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(series as ISeriesApi<any>).setData(
|
||||
@@ -2809,7 +2858,7 @@ export class ChartManager {
|
||||
entry: IndicatorEntry,
|
||||
plots: Record<string, PlotData>,
|
||||
): void {
|
||||
const displacement = (entry.config?.params?.displacement as number) ?? 26;
|
||||
const { senkou: displacement } = resolveIchimokuDisplacements(entry.config?.params);
|
||||
const laggingPeriod = (entry.config?.params?.laggingSpan2Periods as number) ?? 52;
|
||||
const convPlot = (plots['plot0'] as PlotData) ?? [];
|
||||
const basePlot = (plots['plot1'] as PlotData) ?? [];
|
||||
|
||||
Reference in New Issue
Block a user