알림목록 백테스팅 차트에서 이동평균선, 일목균형표, 볼린저 그래프 표시

This commit is contained in:
Macbook
2026-06-08 13:21:30 +09:00
parent 7f29bbd309
commit 1d1c277e3a
8 changed files with 182 additions and 36 deletions
+15 -7
View File
@@ -43,6 +43,8 @@ export interface ChartRightToolbarProps {
custom1OverlayActive?: boolean;
onCustom1OverlayActiveChange?: (active: boolean) => void;
onOpenCustom1OverlaySettings?: () => void;
/** true — 캔들 pane 상단 오버레이 토글만 (미니 차트 등) */
overlayControlsOnly?: boolean;
}
const BTN_SIZE = 32;
@@ -357,6 +359,7 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
onSplit, onRemove, onDuplicate,
onExpand, onRestore, focusedId,
onDragStart, onToggleHidden, onSettings,
overlayControlsOnly = false,
}) => {
const [paneLayouts, setPaneLayouts] = useState<Array<{ paneIndex: number; topY: number; height: number }>>([]);
const [paneItems, setPaneItems] = useState<ReturnType<typeof buildPaneItems>>([]);
@@ -413,7 +416,11 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
if (closeTimer.current) { clearTimeout(closeTimer.current); closeTimer.current = null; }
}, []);
const candlePane = paneLayouts.find(l => l.paneIndex === 0);
const showOverlayIcons = !!(candleOverlayToggles?.length && candlePane && onToggleCandleOverlay);
if (paused || chartHeight <= 0) return null;
if (overlayControlsOnly && !showOverlayIcons) return null;
const innerHeight = paneLayouts.reduce(
(max, l) => Math.max(max, l.topY + l.height),
@@ -427,10 +434,8 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
? paneItems.find(p => p.id === openMenuId)
: null;
const candlePane = paneLayouts.find(l => l.paneIndex === 0);
const showOverlayIcons = !!(candleOverlayToggles?.length && candlePane && onToggleCandleOverlay);
const showCustomBtn = !!(showOverlayIcons && onCustomOverlayActiveChange && onOpenCustomOverlaySettings);
const showCustom1Btn = !!(showOverlayIcons && onCustom1OverlayActiveChange && onOpenCustom1OverlaySettings);
const showCustomBtn = !!(showOverlayIcons && !overlayControlsOnly && onCustomOverlayActiveChange && onOpenCustomOverlaySettings);
const showCustom1Btn = !!(showOverlayIcons && !overlayControlsOnly && onCustom1OverlayActiveChange && onOpenCustom1OverlaySettings);
const customMenuOpen = openMenuId === CUSTOM_MENU_ID;
const custom1MenuOpen = openMenuId === CUSTOM1_MENU_ID;
@@ -446,7 +451,10 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
: null;
return (
<aside className="chart-right-toolbar" aria-label="차트 우측 도구">
<aside
className={`chart-right-toolbar${overlayControlsOnly ? ' chart-right-toolbar--overlay-only' : ''}`}
aria-label="차트 우측 도구"
>
<div
className="chart-right-toolbar-inner"
style={{ height: Math.max(innerHeight, chartHeight) }}
@@ -517,7 +525,7 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
</div>
)}
{paneLayouts.map(layout => (
{!overlayControlsOnly && paneLayouts.map(layout => (
<div
key={layout.paneIndex}
className="chart-right-toolbar-segment"
@@ -525,7 +533,7 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
/>
))}
{paneItems.map(item => {
{!overlayControlsOnly && paneItems.map(item => {
const pl = resolvePaneItemLayout(manager, item);
if (!pl) return null;
+22 -2
View File
@@ -155,8 +155,12 @@ interface TradingChartProps {
showPaneResizeHandle?: boolean;
/** 캔들+거래량 vs 보조 pane 높이 비율 (aux 0 이면 미적용) */
paneAreaRatio?: { candle: number; aux: number } | null;
/** 보조지표 pane 레전드(이름·값 오버레이) 표시 (기본 true, 알림 미니차트는 false) */
/** 보조지표 pane 레전드(이름·값 오버레이) 표시 (기본 true) */
showPaneLegend?: boolean;
/** pane 레전드와 별도 — 우측 드래그·설정 툴바 (미지정 시 showPaneLegend 와 동일) */
showChartRightToolbar?: boolean;
/** 캔들 pane 우측 상단 — 이동평균·일목·볼린저 on/off (미니 차트 등, showChartRightToolbar 없이도 가능) */
showCandleOverlayControls?: boolean;
/** 크로스헤어 이동 시 OHLC·보조지표 수치 표시 (기본 true) */
crosshairInfoVisible?: boolean;
/** 외부 캔들 데이터 fetch 중 (실시간 차트 초기 로딩) */
@@ -213,6 +217,8 @@ const TradingChart: React.FC<TradingChartProps> = ({
showPaneResizeHandle = false,
paneAreaRatio = null,
showPaneLegend = true,
showChartRightToolbar,
showCandleOverlayControls = false,
crosshairInfoVisible = true,
dataLoading = false,
defaultCandleOnly = false,
@@ -226,6 +232,13 @@ const TradingChart: React.FC<TradingChartProps> = ({
onCustom1OverlayActiveChange,
onOpenCustom1OverlaySettings,
}) => {
const effectiveShowChartRightToolbar = showChartRightToolbar ?? showPaneLegend;
const showOverlayControlsOnly = !!(
showCandleOverlayControls
&& candleOverlayToggles?.length
&& onToggleCandleOverlay
&& !effectiveShowChartRightToolbar
);
const containerRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null); // 스크롤 래퍼
const managerRef = useRef<ChartManager | null>(null);
@@ -1685,6 +1698,12 @@ const TradingChart: React.FC<TradingChartProps> = ({
}, [ctxMenu, market]);
const showChartLoading = !chartPaintReady || dataLoading;
const showRightToolbar = !!(
chartMgr
&& !candleOnlyMode
&& chartPaintReady
&& (effectiveShowChartRightToolbar || showOverlayControlsOnly)
);
return (
// wrapperRef: 스크롤 영역 (overflow-y: auto → App.css .tv-chart-wrap)
@@ -1716,7 +1735,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
onPointerCancel={onPaneResizePointerUp}
/>
)}
{chartMgr && !candleOnlyMode && showPaneLegend && chartPaintReady && (
{showRightToolbar && (
<ChartRightToolbar
manager={chartMgr}
indicators={indicators}
@@ -1724,6 +1743,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
paused={paneLegendPaused}
candleOverlayToggles={candleOverlayToggles}
onToggleCandleOverlay={onToggleCandleOverlay}
overlayControlsOnly={showOverlayControlsOnly}
customOverlayActive={customOverlayActive}
onCustomOverlayActiveChange={onCustomOverlayActiveChange}
onOpenCustomOverlaySettings={onOpenCustomOverlaySettings}
@@ -25,7 +25,9 @@ import {
import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge';
import {
applyPaperOverlayVisibility,
DEFAULT_PAPER_OVERLAY_VISIBILITY,
listPaperOverlayToggleItems,
paperOverlayVisibilityToChart,
type PaperOverlayToggleKey,
type PaperOverlayVisibility,
} from '../../utils/paperChartOverlayVisibility';
@@ -61,11 +63,6 @@ interface Props {
const noop = () => {};
/** PaperOverlayVisibility → ChartManager 동기화용 (candle 항상 true) */
function paperVisToChartVis(pv: PaperOverlayVisibility) {
return { ma: pv.ma, bollinger: pv.bollinger, ichimoku: pv.ichimoku, candle: true };
}
let _indSeq = 0;
function newIndId() {
_indSeq += 1;
@@ -128,12 +125,17 @@ const BacktestAnalysisChart: React.FC<Props> = ({
const [strategyLoading, setStrategyLoading] = useState(false);
/** 개별 지표 숨김 ID 집합 (실시간 차트와 동일한 per-indicator 숨김 지원) */
const [hiddenIndicatorIds, setHiddenIndicatorIds] = useState<ReadonlySet<string>>(new Set());
/** 백테스트·분석 레포트 — 세션 전용 오버레이 on/off (투자관리 탭은 부모 props 사용) */
const [internalOverlayVisibility, setInternalOverlayVisibility] = useState<PaperOverlayVisibility>(
() => ({ ...DEFAULT_PAPER_OVERLAY_VISIBILITY }),
);
const effectiveOverlayVisibility = overlayVisibility ?? internalOverlayVisibility;
const managerRef = useRef<ChartManager | null>(null);
const overlayVisibilityRef = useRef(overlayVisibility);
const overlayVisibilityRef = useRef(effectiveOverlayVisibility);
const logicalRangeUnsubRef = useRef<(() => void) | null>(null);
const viewportUnsubRef = useRef<(() => void) | null>(null);
const loadMoreRefRef = useRef<MutableRefObject<() => void>>({ current: () => {} });
overlayVisibilityRef.current = overlayVisibility;
overlayVisibilityRef.current = effectiveOverlayVisibility;
const signalsRef = useRef(signals);
signalsRef.current = signals;
const onHistoryBarsLoadedRef = useRef(onHistoryBarsLoaded);
@@ -193,9 +195,7 @@ const BacktestAnalysisChart: React.FC<Props> = ({
}, [strategy, strategyId, strategyLoading, getParams, getVisualConfig, chartTimeframe, overlayVisibility, showOscillatorPanel]);
const indicators = useMemo(() => {
let inds = overlayVisibility
? applyPaperOverlayVisibility(baseIndicators, overlayVisibility)
: baseIndicators;
let inds = applyPaperOverlayVisibility(baseIndicators, effectiveOverlayVisibility);
// 개별 지표 숨김 상태 반영 (per-indicator hidden toggle)
if (hiddenIndicatorIds.size > 0) {
inds = inds.map(ind =>
@@ -203,12 +203,12 @@ const BacktestAnalysisChart: React.FC<Props> = ({
);
}
return inds;
}, [baseIndicators, overlayVisibility, hiddenIndicatorIds]);
}, [baseIndicators, effectiveOverlayVisibility, hiddenIndicatorIds]);
const candleOverlayToggles = useMemo(() => {
if (!overlayVisibility || !onToggleOverlay) return undefined;
return listPaperOverlayToggleItems(baseIndicators, overlayVisibility);
}, [baseIndicators, overlayVisibility, onToggleOverlay]);
const candleOverlayToggles = useMemo(
() => listPaperOverlayToggleItems(baseIndicators, effectiveOverlayVisibility),
[baseIndicators, effectiveOverlayVisibility],
);
/**
* 캔들 오버레이 그룹 토글 — 실시간 차트와 동일하게 ChartManager 를 즉시 호출한 뒤
@@ -217,11 +217,15 @@ const BacktestAnalysisChart: React.FC<Props> = ({
const handleToggleCandleOverlay = useCallback((key: ChartOverlayToggleKey) => {
const pKey = key as PaperOverlayToggleKey;
const prev = overlayVisibilityRef.current;
if (prev && pKey in prev) {
if (pKey in prev) {
const newVisible = !prev[pKey];
managerRef.current?.setChartOverlayGroupVisible(key, newVisible);
}
onToggleOverlay?.(pKey);
if (onToggleOverlay) {
onToggleOverlay(pKey);
} else {
setInternalOverlayVisibility(prev => ({ ...prev, [pKey]: !prev[pKey] }));
}
}, [onToggleOverlay]);
/**
@@ -360,7 +364,7 @@ const BacktestAnalysisChart: React.FC<Props> = ({
managerRef.current = mgr;
// 실시간 차트와 동일하게: 마운트 시 오버레이 가시성 ChartManager 에 동기화
if (overlayVisibilityRef.current) {
mgr.setChartOverlayVisibility(paperVisToChartVis(overlayVisibilityRef.current));
mgr.setChartOverlayVisibility(paperOverlayVisibilityToChart(overlayVisibilityRef.current));
}
setupHistoryScroll(mgr);
setLoadedBarCount(mgr.getRawBarsLength());
@@ -472,7 +476,8 @@ const BacktestAnalysisChart: React.FC<Props> = ({
seriesPriceLabelsEnabled={priceLabels}
showHoverToolbar
candleOverlayToggles={candleOverlayToggles}
onToggleCandleOverlay={onToggleOverlay ? handleToggleCandleOverlay : undefined}
onToggleCandleOverlay={handleToggleCandleOverlay}
showCandleOverlayControls
onToggleIndicatorHidden={handleToggleIndicatorHidden}
/>
)}
@@ -16,6 +16,9 @@ import type { ChartManager } from '../../utils/ChartManager';
import { pinCandleWatch } from '../../utils/backendApi';
import { timeframeToCandleType } from '../../utils/chartCandleType';
import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone';
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
import { useSessionCandleOverlayControls } from '../../hooks/useSessionCandleOverlayControls';
import { ensurePaperChartOverlays } from '../../utils/strategyToChartIndicators';
interface Props {
market: string;
@@ -40,6 +43,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
fillHeight = false,
onRealtimeActivity,
}) => {
const { getParams, getVisualConfig } = useIndicatorSettings();
const managerRef = useRef<ChartManager | null>(null);
const canvasWrapRef = useRef<HTMLDivElement | null>(null);
const [chartReloadTick, setChartReloadTick] = useState(0);
@@ -50,6 +54,18 @@ const TradeSignalMiniChart: React.FC<Props> = ({
const marketRef = useRef(market);
marketRef.current = market;
const baseIndicators = useMemo(
() => ensurePaperChartOverlays(indicators, getParams, getVisualConfig),
[indicators, getParams, getVisualConfig],
);
const {
indicators: chartIndicators,
candleOverlayToggles,
handleToggleCandleOverlay,
syncToManager,
} = useSessionCandleOverlayControls(managerRef, baseIndicators);
const syncChartLayout = useCallback(() => {
const wrap = canvasWrapRef.current;
const mgr = managerRef.current;
@@ -191,7 +207,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
</div>
)}
<TradingChart
key={`${market}-${timeframe}-${chartReloadTick}-${indicators.map(i => i.id).join(',')}`}
key={`${market}-${timeframe}-${chartReloadTick}-${chartIndicators.map(i => i.id).join(',')}`}
chartVisible
bars={bars}
barsMarket={barsMarket}
@@ -200,14 +216,17 @@ const TradeSignalMiniChart: React.FC<Props> = ({
chartType={'candlestick' as ChartType}
theme={theme}
mode={'chart' as ChartMode}
indicators={indicators}
indicators={chartIndicators}
drawingTool="cursor"
drawings={[] as Drawing[]}
logScale={false}
drawingsLocked
drawingsVisible={false}
onCrosshair={noop as (d: LegendData | null) => void}
onManagerReady={mgr => { managerRef.current = mgr; }}
onManagerReady={mgr => {
managerRef.current = mgr;
syncToManager(mgr);
}}
onAddDrawing={noop as (d: Drawing) => void}
onCandlesReady={handleCandlesReady}
magnifierEnabled={false}
@@ -215,7 +234,12 @@ const TradeSignalMiniChart: React.FC<Props> = ({
showHoverToolbar={false}
seriesPriceLabelsEnabled={false}
paneLayoutClamp
showPaneLegend={false}
showPaneLegend
showChartRightToolbar={false}
showCandleOverlayControls
candleOverlayToggles={candleOverlayToggles}
onToggleCandleOverlay={handleToggleCandleOverlay}
crosshairInfoVisible={false}
/>
</div>
);