알림목록 백테스팅 차트에서 이동평균선, 일목균형표, 볼린저 그래프 표시
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user