전략편집기 복합지표 기능 반영

This commit is contained in:
Macbook
2026-05-25 01:34:52 +09:00
parent ca249ad318
commit 20dd257c29
21 changed files with 1781 additions and 183 deletions
+59 -6
View File
@@ -210,6 +210,9 @@ export class ChartManager {
/** 차트 하단 거래량 pane 표시 */
private _volumeVisible = true;
/** applyPaneLayout / resetPaneHeights 에서 측정한 마지막 가용 높이(px) */
private _lastLayoutAvailableHeight?: number;
constructor(container: HTMLElement, theme: Theme) {
this.container = container;
const t = getTheme(theme);
@@ -221,6 +224,7 @@ export class ChartManager {
background: { type: ColorType.Solid, color: t.bgColor },
textColor: t.textColor,
fontSize: 12,
panes: { enableResize: false },
},
grid: {
vertLines: { color: t.gridColor },
@@ -288,6 +292,7 @@ export class ChartManager {
}
this.indicators.clear();
this.patternMarkers = [];
this._removeExtraSubPanes();
this._disposeMainMarkersPlugin();
if (this.mainSeries) { try { this.chart.removeSeries(this.mainSeries); } catch { /* ok */ } this.mainSeries = null; }
@@ -535,6 +540,10 @@ export class ChartManager {
}
} catch (e) {
console.error(`[Indicator] ${config.type} error:`, e);
} finally {
if (this._activeIndicatorPaneIndices().size > 0) {
this.resetPaneHeights(this._lastLayoutAvailableHeight);
}
}
}
@@ -557,6 +566,7 @@ export class ChartManager {
this.indicators.clear();
this.patternMarkers = this.patternMarkers.filter(() => false);
this._reapplyAllPatternMarkers();
this._removeExtraSubPanes();
// ② 새 순서로 재추가 (병합 호스트 → 멤버 순)
this._indRunning = false;
@@ -590,7 +600,8 @@ export class ChartManager {
this.patternMarkers = this.patternMarkers.filter(p => p.id !== id);
this._reapplyAllPatternMarkers();
this.indicators.delete(id);
this.resetPaneHeights();
this._removeExtraSubPanes();
this.resetPaneHeights(this._lastLayoutAvailableHeight);
this._notifyPaneLayout();
}
@@ -1232,6 +1243,7 @@ export class ChartManager {
this.indicators.clear();
this.patternMarkers = [];
this._reapplyAllPatternMarkers();
this._removeExtraSubPanes();
for (const cfg of configs) {
await this.addIndicator(cfg);
@@ -2047,6 +2059,20 @@ export class ChartManager {
return indices;
}
/** pane 2+ 빈 sub-pane 제거 — 제거·재로드 후 고아 pane 이 높이를 나눠 가져가는 것 방지 */
private _removeExtraSubPanes(): void {
for (;;) {
const panes = this.chart.panes();
if (panes.length <= 2) return;
const last = panes.length - 1;
try {
this.chart.removePane(last);
} catch {
return;
}
}
}
/** chart-container 기준 Y(px) → pane index (0=캔들, 1=거래량, 2+=보조지표) */
getPaneIndexAtChartY(chartY: number): number {
const layouts = this.getPaneLayouts();
@@ -2374,6 +2400,16 @@ export class ChartManager {
return this.indicators.size;
}
/** 실제 시리즈·구름이 붙은 보조지표 수 (부분 로드·중단 감지용) */
getLoadedIndicatorCount(): number {
let n = 0;
for (const entry of this.indicators.values()) {
if (entry.config?.hidden === true) continue;
if (entry.cloudPlugin || entry.seriesList.length > 0) n += 1;
}
return n;
}
/** 현재 로드된 rawBars 개수 반환 (데이터 로드 여부 확인용) */
getRawBarsLength(): number {
return this.rawBars.length;
@@ -2732,12 +2768,16 @@ export class ChartManager {
const N = activeIndPanes.size;
const H = (availableHeight && availableHeight > 0)
? availableHeight
: this.container.clientHeight;
: (this._lastLayoutAvailableHeight && this._lastLayoutAvailableHeight > 0
? this._lastLayoutAvailableHeight
: this.container.clientHeight);
const volumeShown = this._volumeVisible && !this._candleOnlyLayout;
const VOL_FRAC = this._volumeFrac(H);
const MIN_IND_PX = 80;
const ORPHAN_STRETCH = 0.0001;
/** 보조지표 pane 은 동일 stretch weight → 항상 같은 높이 */
const IND_EQUAL_WEIGHT = 1;
const remaining = Math.max(0, 1 - mainFrac - (volumeShown ? VOL_FRAC : 0));
const eachIndFrac = N > 0 ? remaining / N : 0;
@@ -2746,13 +2786,20 @@ export class ChartManager {
? eachIndFrac
: MIN_IND_PX / H;
const indWeight = IND_EQUAL_WEIGHT;
const indUnit = Math.max(actualIndFrac, 1e-9);
const mainWeight = N > 0 ? mainFrac / indUnit : mainFrac;
const volWeight = volumeShown
? (N > 0 ? VOL_FRAC / indUnit : VOL_FRAC)
: ORPHAN_STRETCH;
for (let i = 0; i < panes.length; i++) {
if (i === 0) {
panes[i].setStretchFactor(mainFrac);
panes[i].setStretchFactor(mainWeight);
} else if (i === 1) {
panes[i].setStretchFactor(volumeShown ? VOL_FRAC : ORPHAN_STRETCH);
panes[i].setStretchFactor(volumeShown ? volWeight : ORPHAN_STRETCH);
} else if (activeIndPanes.has(i)) {
panes[i].setStretchFactor(actualIndFrac);
panes[i].setStretchFactor(indWeight);
} else {
panes[i].setStretchFactor(ORPHAN_STRETCH);
}
@@ -2801,13 +2848,19 @@ export class ChartManager {
const panes = this.chart.panes();
if (panes.length === 0) return availableHeight ?? this.container.clientHeight;
if (availableHeight != null && availableHeight > 0) {
this._lastLayoutAvailableHeight = availableHeight;
}
if (this._candleOnlyLayout) {
return this._resetPaneHeightsCandleFullscreen(availableHeight);
}
const H = (availableHeight && availableHeight > 0)
? availableHeight
: this.container.clientHeight;
: (this._lastLayoutAvailableHeight && this._lastLayoutAvailableHeight > 0
? this._lastLayoutAvailableHeight
: this.container.clientHeight);
const N = this._activeIndicatorPaneIndices().size;
const { mainFrac } = this._paneHeightFractions(N, this._volumeFrac(H));