매매 시그널 알림 화면 수정
This commit is contained in:
@@ -25,7 +25,14 @@ import { BollingerBandFillPrimitive } from './BollingerBandFillPrimitive';
|
||||
import { resolveBbBandBackground } from './bollingerConfig';
|
||||
import type { OHLCVBar, ChartType, Theme, IndicatorConfig, Timeframe } from '../types';
|
||||
import { formatChartAxisPrice } from './dataGenerator';
|
||||
import { formatLwcTime, formatLwcTickMark, DEFAULT_DISPLAY_TIMEZONE } from './timezone';
|
||||
import {
|
||||
DEFAULT_CHART_TIME_FORMAT,
|
||||
formatUnixWithChartPattern,
|
||||
formatLwcTimeWithPattern,
|
||||
formatLwcTickMarkWithPattern,
|
||||
normalizeChartTimeFormat,
|
||||
} from './chartTimeFormat';
|
||||
import { DEFAULT_DISPLAY_TIMEZONE } from './timezone';
|
||||
import { sortIndicatorsForPaneLoad } from './indicatorPaneMerge';
|
||||
import { calculateIndicator, enrichIndicatorConfig, getIndicatorDef, getHLineLabel, type PlotData, type MarkerData } from './indicatorRegistry';
|
||||
import { IchimokuCloudPlugin, type IchimokuCloudPoint } from './IchimokuCloudPlugin';
|
||||
@@ -203,6 +210,7 @@ export class ChartManager {
|
||||
private currentChartType: ChartType = 'candlestick';
|
||||
private displayTimezone = DEFAULT_DISPLAY_TIMEZONE;
|
||||
private displayTimeframe: Timeframe = '1D';
|
||||
private chartTimeFormat = DEFAULT_CHART_TIME_FORMAT;
|
||||
private mainMarkersPlugin: ISeriesMarkersPluginApi<Time> | null = null;
|
||||
private patternMarkers: Array<{ id: string; markers: MarkerData[] }> = [];
|
||||
/** 백테스팅 매수/매도 시그널 마커 */
|
||||
@@ -461,29 +469,37 @@ export class ChartManager {
|
||||
|
||||
// ─── Display timezone ───────────────────────────────────────────────────
|
||||
private _tickMarkFormatter(): TickMarkFormatter {
|
||||
const tf = this.displayTimeframe;
|
||||
const tz = this.displayTimezone;
|
||||
const fmt = this.chartTimeFormat;
|
||||
return (time, tickMarkType) =>
|
||||
formatLwcTickMark(
|
||||
time as Time,
|
||||
tickMarkType,
|
||||
this.displayTimeframe,
|
||||
this.displayTimezone,
|
||||
);
|
||||
formatLwcTickMarkWithPattern(time as Time, tickMarkType, tf, tz, fmt);
|
||||
}
|
||||
|
||||
private _applyDisplayTimezoneOptions(): void {
|
||||
const tf = this.displayTimeframe;
|
||||
const tz = this.displayTimezone;
|
||||
const fmt = this.chartTimeFormat;
|
||||
const timeFormatter = (time: Time) =>
|
||||
formatLwcTime(time as number | { year: number; month: number; day: number }, tf, tz);
|
||||
formatLwcTimeWithPattern(
|
||||
time as number | { year: number; month: number; day: number },
|
||||
tf,
|
||||
tz,
|
||||
fmt,
|
||||
);
|
||||
this.chart.applyOptions({
|
||||
localization: { timeFormatter, priceFormatter: formatChartAxisPrice },
|
||||
timeScale: { tickMarkFormatter: this._tickMarkFormatter() },
|
||||
});
|
||||
this._notifyPaneLayout();
|
||||
}
|
||||
|
||||
setDisplayTimezone(tz: string, timeframe?: Timeframe): void {
|
||||
setDisplayTimezone(tz: string, timeframe?: Timeframe, chartTimeFormat?: string): void {
|
||||
this.displayTimezone = tz || DEFAULT_DISPLAY_TIMEZONE;
|
||||
if (timeframe) this.displayTimeframe = timeframe;
|
||||
if (chartTimeFormat != null) {
|
||||
this.chartTimeFormat = normalizeChartTimeFormat(chartTimeFormat);
|
||||
}
|
||||
this._applyDisplayTimezoneOptions();
|
||||
}
|
||||
|
||||
@@ -805,12 +821,27 @@ export class ChartManager {
|
||||
/** 파라미터 변경된 지표만 제거 후 재추가 (전체 보조지표 재로드 회피) */
|
||||
async refreshIndicators(configs: IndicatorConfig[]): Promise<void> {
|
||||
if (configs.length === 0) return;
|
||||
this.cancelPendingIndicatorUpdates();
|
||||
const loadGen = ++this._dataGeneration;
|
||||
|
||||
let any = false;
|
||||
for (const c of configs) {
|
||||
if (this._detachIndicatorEntry(c.id)) any = true;
|
||||
}
|
||||
if (any) this._trimTrailingEmptySubPanes();
|
||||
await this.addIndicatorsBatch(configs);
|
||||
if (this._indicatorLoadStale(loadGen)) return;
|
||||
|
||||
for (const config of configs) {
|
||||
if (this._indicatorLoadStale(loadGen)) break;
|
||||
await this.addIndicator(config, { skipLayout: true });
|
||||
}
|
||||
if (this._indicatorLoadStale(loadGen)) return;
|
||||
|
||||
this._removeOrphanSubPanes();
|
||||
if (this._activeIndicatorPaneIndices().size > 0) {
|
||||
this.resetPaneHeights(this._lastLayoutAvailableHeight);
|
||||
this._notifyPaneLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/** 메인 시리즈 마커 플러그인 해제 (setData 등 시리즈 교체 전 호출) */
|
||||
@@ -2437,6 +2468,76 @@ export class ChartManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** 캔들 pane(0) 플롯 영역 너비 (우측 가격축 제외) */
|
||||
private _candlePlotWidth(): number {
|
||||
try {
|
||||
const ps = this.mainSeries?.priceScale() ?? this.chart.priceScale('right', 0);
|
||||
const scaleW = ps.width();
|
||||
return Math.max(40, this.container.clientWidth - scaleW);
|
||||
} catch {
|
||||
return Math.max(40, this.container.clientWidth - 56);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 캔들 pane 하단 시간축 오버레이 — 거래량·보조지표가 있을 때 캔들 영역 바로 아래에도 시각 표시.
|
||||
*/
|
||||
getCandlePaneTimeAxisOverlay(): {
|
||||
topY: number;
|
||||
height: number;
|
||||
plotWidth: number;
|
||||
labels: Array<{ x: number; text: string }>;
|
||||
} | null {
|
||||
if (!this.mainSeries || this.rawBars.length < 2) return null;
|
||||
|
||||
const layouts = this.getPaneLayouts();
|
||||
const hasLowerPane = layouts.some(l => l.paneIndex > 0 && l.height > 8);
|
||||
if (!hasLowerPane) return null;
|
||||
|
||||
const main = layouts.find(l => l.paneIndex === 0);
|
||||
if (!main || main.height < 48) return null;
|
||||
|
||||
const AXIS_H = 22;
|
||||
const ts = this.chart.timeScale();
|
||||
const lr = ts.getVisibleLogicalRange();
|
||||
if (!lr) return null;
|
||||
|
||||
const plotWidth = this._candlePlotWidth();
|
||||
const from = Math.max(0, Math.ceil(lr.from));
|
||||
const to = Math.min(this.rawBars.length - 1, Math.floor(lr.to));
|
||||
if (to <= from) return null;
|
||||
|
||||
const span = to - from;
|
||||
const targetCount = Math.max(4, Math.min(12, Math.floor(plotWidth / 76)));
|
||||
const step = Math.max(1, Math.ceil(span / targetCount));
|
||||
|
||||
const labels: Array<{ x: number; text: string }> = [];
|
||||
const fmt = this.chartTimeFormat;
|
||||
const tz = this.displayTimezone;
|
||||
|
||||
for (let i = from; i <= to; i += step) {
|
||||
const bar = this.rawBars[i];
|
||||
if (!bar?.time) continue;
|
||||
const coord = ts.timeToCoordinate(bar.time as Time);
|
||||
if (coord == null) continue;
|
||||
const x = Number(coord);
|
||||
if (!Number.isFinite(x) || x < 12 || x > plotWidth - 12) continue;
|
||||
labels.push({
|
||||
x,
|
||||
text: formatUnixWithChartPattern(bar.time, fmt, tz),
|
||||
});
|
||||
}
|
||||
|
||||
if (labels.length === 0) return null;
|
||||
|
||||
return {
|
||||
topY: main.topY + main.height - AXIS_H,
|
||||
height: AXIS_H,
|
||||
plotWidth,
|
||||
labels,
|
||||
};
|
||||
}
|
||||
|
||||
/** 하단 시간축 영역 클릭 여부 */
|
||||
isOnTimeAxis(chartY: number): boolean {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user