매매 시그널 디테일 팝업 기능 적용

This commit is contained in:
Macbook
2026-06-10 21:13:53 +09:00
parent bf5554d375
commit 0ff1992b64
19 changed files with 1507 additions and 58 deletions
+46 -3
View File
@@ -64,6 +64,7 @@ import { chartHasStaleIndicators, classifyIndicatorChartChange, singleIndParamKe
import { sortIndicatorsForPaneLoad } from '../utils/indicatorPaneMerge';
import type { ChartPaneSeparatorOptions } from '../types/chartPaneSeparator';
import PaneSeparatorOverlay from './PaneSeparatorOverlay';
import { centerChartOnSignalBar } from '../utils/tradeSignalChartMarkers';
interface TradingChartProps {
bars: OHLCVBar[];
@@ -182,6 +183,12 @@ interface TradingChartProps {
resolveInitialDisplayCount?: (plotWidth: number) => number;
/** true — reload·layout 복구 타이머의 반복 viewport 재조정 생략 (미니 카드 깜빡임 방지) */
skipViewportRecovery?: boolean;
/** true — 캔들 숨김, 보조지표 pane 선형 그래프만 표시 (시그널 상세 하단 차트 등) */
auxIndicatorOnly?: boolean;
/** 지정 시 초기 visible range 대신 시그널 봉 시간축 중앙 정렬 */
centerOnSignalTime?: number;
/** centerOnSignalTime 사용 시 화면에 보일 봉 수 (미지정 시 전체) */
centerVisibleBarCount?: number;
}
const TradingChart: React.FC<TradingChartProps> = ({
@@ -237,6 +244,9 @@ const TradingChart: React.FC<TradingChartProps> = ({
onOpenCustom1OverlaySettings,
resolveInitialDisplayCount,
skipViewportRecovery = false,
auxIndicatorOnly = false,
centerOnSignalTime,
centerVisibleBarCount,
}) => {
const effectiveShowChartRightToolbar = showChartRightToolbar ?? showPaneLegend;
const showOverlayControlsOnly = !!(
@@ -324,6 +334,9 @@ const TradingChart: React.FC<TradingChartProps> = ({
const chartVisibleRef = useRef(chartVisible);
const resolveInitialDisplayCountRef = useRef(resolveInitialDisplayCount);
const skipViewportRecoveryRef = useRef(skipViewportRecovery);
const auxIndicatorOnlyRef = useRef(auxIndicatorOnly);
const centerOnSignalTimeRef = useRef(centerOnSignalTime);
const centerVisibleBarCountRef = useRef(centerVisibleBarCount);
const lastViewportFitRef = useRef<{ count: number; width: number } | null>(null);
useEffect(() => {
@@ -334,6 +347,27 @@ const TradingChart: React.FC<TradingChartProps> = ({
skipViewportRecoveryRef.current = skipViewportRecovery;
}, [skipViewportRecovery]);
useEffect(() => {
auxIndicatorOnlyRef.current = auxIndicatorOnly;
}, [auxIndicatorOnly]);
useEffect(() => {
centerOnSignalTimeRef.current = centerOnSignalTime;
}, [centerOnSignalTime]);
useEffect(() => {
centerVisibleBarCountRef.current = centerVisibleBarCount;
}, [centerVisibleBarCount]);
const applySnapshotViewport = useCallback((mgr: ChartManager) => {
const t = centerOnSignalTimeRef.current;
if (t != null && Number.isFinite(t)) {
centerChartOnSignalBar(mgr, t, centerVisibleBarCountRef.current);
} else {
mgr.fitContent();
}
}, []);
const chartTimeFormat = useChartTimeFormat();
const [chartMgr, setChartMgr] = useState<ChartManager | null>(null);
/** 초기 reloadAll 완료 전 — pane 구분선·부분 레이아웃 노출 방지 */
@@ -523,7 +557,9 @@ const TradingChart: React.FC<TradingChartProps> = ({
mgr.applyCandleOnlyLayout(true, wrapperH);
required = wrapperH;
} else {
if (mgr.isCandleOnlyLayout()) {
if (auxIndicatorOnlyRef.current) {
mgr.applyAuxIndicatorOnlyLayout(true);
} else if (mgr.isCandleOnlyLayout()) {
mgr.restoreFromCandleFullscreen(wrapperH);
}
// 사용자 드래그 비율 우선, 없으면 prop 기반 비율
@@ -587,9 +623,13 @@ const TradingChart: React.FC<TradingChartProps> = ({
applyPaneLayout(mgr);
requestAnimationFrame(() => {
try { mgr.autoScale(); } catch { /* ok */ }
applyInitialVisibleRange(mgr);
if (centerOnSignalTimeRef.current != null && Number.isFinite(centerOnSignalTimeRef.current)) {
applySnapshotViewport(mgr);
} else {
applyInitialVisibleRange(mgr);
}
});
}, [applyPaneLayout, applyInitialVisibleRange]);
}, [applyPaneLayout, applyInitialVisibleRange, applySnapshotViewport]);
/** 캔들 확대 보기 → 원복 직후 가격·시간축·pane 비율 정상화 */
useEffect(() => {
@@ -647,6 +687,8 @@ const TradingChart: React.FC<TradingChartProps> = ({
const afterIndicatorPaneMutation = useCallback((mgr: ChartManager) => {
if (candleOnlyModeRef.current) {
mgr.applyCandleOnlyLayout(true, wrapperRef.current?.clientHeight);
} else if (auxIndicatorOnlyRef.current) {
mgr.applyAuxIndicatorOnlyLayout(true);
} else if (mgr.isCandleOnlyLayout()) {
mgr.restoreFromCandleFullscreen(wrapperRef.current?.clientHeight);
}
@@ -960,6 +1002,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
mgr.setCandleAreaPriceLabelsEnabled(resolvedCandlePriceLabels);
mgr.setIndicatorAreaPriceLabelsEnabled(resolvedIndicatorPriceLabels);
if (paneSeparatorOptions) mgr.setPaneSeparatorOptions(paneSeparatorOptions);
if (auxIndicatorOnlyRef.current) mgr.applyAuxIndicatorOnlyLayout(true);
onManagerReady(mgr);
const unsub = mgr.subscribeCrosshair((p: MouseEventParams<Time>) => {