알림목록 백테스팅 차트에서 이동평균선, 일목균형표, 볼린저 그래프 표시
This commit is contained in:
@@ -43,6 +43,8 @@ export interface ChartRightToolbarProps {
|
|||||||
custom1OverlayActive?: boolean;
|
custom1OverlayActive?: boolean;
|
||||||
onCustom1OverlayActiveChange?: (active: boolean) => void;
|
onCustom1OverlayActiveChange?: (active: boolean) => void;
|
||||||
onOpenCustom1OverlaySettings?: () => void;
|
onOpenCustom1OverlaySettings?: () => void;
|
||||||
|
/** true — 캔들 pane 상단 오버레이 토글만 (미니 차트 등) */
|
||||||
|
overlayControlsOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BTN_SIZE = 32;
|
const BTN_SIZE = 32;
|
||||||
@@ -357,6 +359,7 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
|
|||||||
onSplit, onRemove, onDuplicate,
|
onSplit, onRemove, onDuplicate,
|
||||||
onExpand, onRestore, focusedId,
|
onExpand, onRestore, focusedId,
|
||||||
onDragStart, onToggleHidden, onSettings,
|
onDragStart, onToggleHidden, onSettings,
|
||||||
|
overlayControlsOnly = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [paneLayouts, setPaneLayouts] = useState<Array<{ paneIndex: number; topY: number; height: number }>>([]);
|
const [paneLayouts, setPaneLayouts] = useState<Array<{ paneIndex: number; topY: number; height: number }>>([]);
|
||||||
const [paneItems, setPaneItems] = useState<ReturnType<typeof buildPaneItems>>([]);
|
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; }
|
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 (paused || chartHeight <= 0) return null;
|
||||||
|
if (overlayControlsOnly && !showOverlayIcons) return null;
|
||||||
|
|
||||||
const innerHeight = paneLayouts.reduce(
|
const innerHeight = paneLayouts.reduce(
|
||||||
(max, l) => Math.max(max, l.topY + l.height),
|
(max, l) => Math.max(max, l.topY + l.height),
|
||||||
@@ -427,10 +434,8 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
|
|||||||
? paneItems.find(p => p.id === openMenuId)
|
? paneItems.find(p => p.id === openMenuId)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const candlePane = paneLayouts.find(l => l.paneIndex === 0);
|
const showCustomBtn = !!(showOverlayIcons && !overlayControlsOnly && onCustomOverlayActiveChange && onOpenCustomOverlaySettings);
|
||||||
const showOverlayIcons = !!(candleOverlayToggles?.length && candlePane && onToggleCandleOverlay);
|
const showCustom1Btn = !!(showOverlayIcons && !overlayControlsOnly && onCustom1OverlayActiveChange && onOpenCustom1OverlaySettings);
|
||||||
const showCustomBtn = !!(showOverlayIcons && onCustomOverlayActiveChange && onOpenCustomOverlaySettings);
|
|
||||||
const showCustom1Btn = !!(showOverlayIcons && onCustom1OverlayActiveChange && onOpenCustom1OverlaySettings);
|
|
||||||
const customMenuOpen = openMenuId === CUSTOM_MENU_ID;
|
const customMenuOpen = openMenuId === CUSTOM_MENU_ID;
|
||||||
const custom1MenuOpen = openMenuId === CUSTOM1_MENU_ID;
|
const custom1MenuOpen = openMenuId === CUSTOM1_MENU_ID;
|
||||||
|
|
||||||
@@ -446,7 +451,10 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="chart-right-toolbar" aria-label="차트 우측 도구">
|
<aside
|
||||||
|
className={`chart-right-toolbar${overlayControlsOnly ? ' chart-right-toolbar--overlay-only' : ''}`}
|
||||||
|
aria-label="차트 우측 도구"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="chart-right-toolbar-inner"
|
className="chart-right-toolbar-inner"
|
||||||
style={{ height: Math.max(innerHeight, chartHeight) }}
|
style={{ height: Math.max(innerHeight, chartHeight) }}
|
||||||
@@ -517,7 +525,7 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{paneLayouts.map(layout => (
|
{!overlayControlsOnly && paneLayouts.map(layout => (
|
||||||
<div
|
<div
|
||||||
key={layout.paneIndex}
|
key={layout.paneIndex}
|
||||||
className="chart-right-toolbar-segment"
|
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);
|
const pl = resolvePaneItemLayout(manager, item);
|
||||||
if (!pl) return null;
|
if (!pl) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -155,8 +155,12 @@ interface TradingChartProps {
|
|||||||
showPaneResizeHandle?: boolean;
|
showPaneResizeHandle?: boolean;
|
||||||
/** 캔들+거래량 vs 보조 pane 높이 비율 (aux 0 이면 미적용) */
|
/** 캔들+거래량 vs 보조 pane 높이 비율 (aux 0 이면 미적용) */
|
||||||
paneAreaRatio?: { candle: number; aux: number } | null;
|
paneAreaRatio?: { candle: number; aux: number } | null;
|
||||||
/** 보조지표 pane 레전드(이름·값 오버레이) 표시 (기본 true, 알림 미니차트는 false) */
|
/** 보조지표 pane 레전드(이름·값 오버레이) 표시 (기본 true) */
|
||||||
showPaneLegend?: boolean;
|
showPaneLegend?: boolean;
|
||||||
|
/** pane 레전드와 별도 — 우측 드래그·설정 툴바 (미지정 시 showPaneLegend 와 동일) */
|
||||||
|
showChartRightToolbar?: boolean;
|
||||||
|
/** 캔들 pane 우측 상단 — 이동평균·일목·볼린저 on/off (미니 차트 등, showChartRightToolbar 없이도 가능) */
|
||||||
|
showCandleOverlayControls?: boolean;
|
||||||
/** 크로스헤어 이동 시 OHLC·보조지표 수치 표시 (기본 true) */
|
/** 크로스헤어 이동 시 OHLC·보조지표 수치 표시 (기본 true) */
|
||||||
crosshairInfoVisible?: boolean;
|
crosshairInfoVisible?: boolean;
|
||||||
/** 외부 캔들 데이터 fetch 중 (실시간 차트 초기 로딩) */
|
/** 외부 캔들 데이터 fetch 중 (실시간 차트 초기 로딩) */
|
||||||
@@ -213,6 +217,8 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
showPaneResizeHandle = false,
|
showPaneResizeHandle = false,
|
||||||
paneAreaRatio = null,
|
paneAreaRatio = null,
|
||||||
showPaneLegend = true,
|
showPaneLegend = true,
|
||||||
|
showChartRightToolbar,
|
||||||
|
showCandleOverlayControls = false,
|
||||||
crosshairInfoVisible = true,
|
crosshairInfoVisible = true,
|
||||||
dataLoading = false,
|
dataLoading = false,
|
||||||
defaultCandleOnly = false,
|
defaultCandleOnly = false,
|
||||||
@@ -226,6 +232,13 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
onCustom1OverlayActiveChange,
|
onCustom1OverlayActiveChange,
|
||||||
onOpenCustom1OverlaySettings,
|
onOpenCustom1OverlaySettings,
|
||||||
}) => {
|
}) => {
|
||||||
|
const effectiveShowChartRightToolbar = showChartRightToolbar ?? showPaneLegend;
|
||||||
|
const showOverlayControlsOnly = !!(
|
||||||
|
showCandleOverlayControls
|
||||||
|
&& candleOverlayToggles?.length
|
||||||
|
&& onToggleCandleOverlay
|
||||||
|
&& !effectiveShowChartRightToolbar
|
||||||
|
);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null); // 스크롤 래퍼
|
const wrapperRef = useRef<HTMLDivElement>(null); // 스크롤 래퍼
|
||||||
const managerRef = useRef<ChartManager | null>(null);
|
const managerRef = useRef<ChartManager | null>(null);
|
||||||
@@ -1685,6 +1698,12 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
}, [ctxMenu, market]);
|
}, [ctxMenu, market]);
|
||||||
|
|
||||||
const showChartLoading = !chartPaintReady || dataLoading;
|
const showChartLoading = !chartPaintReady || dataLoading;
|
||||||
|
const showRightToolbar = !!(
|
||||||
|
chartMgr
|
||||||
|
&& !candleOnlyMode
|
||||||
|
&& chartPaintReady
|
||||||
|
&& (effectiveShowChartRightToolbar || showOverlayControlsOnly)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// wrapperRef: 스크롤 영역 (overflow-y: auto → App.css .tv-chart-wrap)
|
// wrapperRef: 스크롤 영역 (overflow-y: auto → App.css .tv-chart-wrap)
|
||||||
@@ -1716,7 +1735,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
onPointerCancel={onPaneResizePointerUp}
|
onPointerCancel={onPaneResizePointerUp}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{chartMgr && !candleOnlyMode && showPaneLegend && chartPaintReady && (
|
{showRightToolbar && (
|
||||||
<ChartRightToolbar
|
<ChartRightToolbar
|
||||||
manager={chartMgr}
|
manager={chartMgr}
|
||||||
indicators={indicators}
|
indicators={indicators}
|
||||||
@@ -1724,6 +1743,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
paused={paneLegendPaused}
|
paused={paneLegendPaused}
|
||||||
candleOverlayToggles={candleOverlayToggles}
|
candleOverlayToggles={candleOverlayToggles}
|
||||||
onToggleCandleOverlay={onToggleCandleOverlay}
|
onToggleCandleOverlay={onToggleCandleOverlay}
|
||||||
|
overlayControlsOnly={showOverlayControlsOnly}
|
||||||
customOverlayActive={customOverlayActive}
|
customOverlayActive={customOverlayActive}
|
||||||
onCustomOverlayActiveChange={onCustomOverlayActiveChange}
|
onCustomOverlayActiveChange={onCustomOverlayActiveChange}
|
||||||
onOpenCustomOverlaySettings={onOpenCustomOverlaySettings}
|
onOpenCustomOverlaySettings={onOpenCustomOverlaySettings}
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ import {
|
|||||||
import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge';
|
import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge';
|
||||||
import {
|
import {
|
||||||
applyPaperOverlayVisibility,
|
applyPaperOverlayVisibility,
|
||||||
|
DEFAULT_PAPER_OVERLAY_VISIBILITY,
|
||||||
listPaperOverlayToggleItems,
|
listPaperOverlayToggleItems,
|
||||||
|
paperOverlayVisibilityToChart,
|
||||||
type PaperOverlayToggleKey,
|
type PaperOverlayToggleKey,
|
||||||
type PaperOverlayVisibility,
|
type PaperOverlayVisibility,
|
||||||
} from '../../utils/paperChartOverlayVisibility';
|
} from '../../utils/paperChartOverlayVisibility';
|
||||||
@@ -61,11 +63,6 @@ interface Props {
|
|||||||
|
|
||||||
const noop = () => {};
|
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;
|
let _indSeq = 0;
|
||||||
function newIndId() {
|
function newIndId() {
|
||||||
_indSeq += 1;
|
_indSeq += 1;
|
||||||
@@ -128,12 +125,17 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
const [strategyLoading, setStrategyLoading] = useState(false);
|
const [strategyLoading, setStrategyLoading] = useState(false);
|
||||||
/** 개별 지표 숨김 ID 집합 (실시간 차트와 동일한 per-indicator 숨김 지원) */
|
/** 개별 지표 숨김 ID 집합 (실시간 차트와 동일한 per-indicator 숨김 지원) */
|
||||||
const [hiddenIndicatorIds, setHiddenIndicatorIds] = useState<ReadonlySet<string>>(new Set());
|
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 managerRef = useRef<ChartManager | null>(null);
|
||||||
const overlayVisibilityRef = useRef(overlayVisibility);
|
const overlayVisibilityRef = useRef(effectiveOverlayVisibility);
|
||||||
const logicalRangeUnsubRef = useRef<(() => void) | null>(null);
|
const logicalRangeUnsubRef = useRef<(() => void) | null>(null);
|
||||||
const viewportUnsubRef = useRef<(() => void) | null>(null);
|
const viewportUnsubRef = useRef<(() => void) | null>(null);
|
||||||
const loadMoreRefRef = useRef<MutableRefObject<() => void>>({ current: () => {} });
|
const loadMoreRefRef = useRef<MutableRefObject<() => void>>({ current: () => {} });
|
||||||
overlayVisibilityRef.current = overlayVisibility;
|
overlayVisibilityRef.current = effectiveOverlayVisibility;
|
||||||
const signalsRef = useRef(signals);
|
const signalsRef = useRef(signals);
|
||||||
signalsRef.current = signals;
|
signalsRef.current = signals;
|
||||||
const onHistoryBarsLoadedRef = useRef(onHistoryBarsLoaded);
|
const onHistoryBarsLoadedRef = useRef(onHistoryBarsLoaded);
|
||||||
@@ -193,9 +195,7 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
}, [strategy, strategyId, strategyLoading, getParams, getVisualConfig, chartTimeframe, overlayVisibility, showOscillatorPanel]);
|
}, [strategy, strategyId, strategyLoading, getParams, getVisualConfig, chartTimeframe, overlayVisibility, showOscillatorPanel]);
|
||||||
|
|
||||||
const indicators = useMemo(() => {
|
const indicators = useMemo(() => {
|
||||||
let inds = overlayVisibility
|
let inds = applyPaperOverlayVisibility(baseIndicators, effectiveOverlayVisibility);
|
||||||
? applyPaperOverlayVisibility(baseIndicators, overlayVisibility)
|
|
||||||
: baseIndicators;
|
|
||||||
// 개별 지표 숨김 상태 반영 (per-indicator hidden toggle)
|
// 개별 지표 숨김 상태 반영 (per-indicator hidden toggle)
|
||||||
if (hiddenIndicatorIds.size > 0) {
|
if (hiddenIndicatorIds.size > 0) {
|
||||||
inds = inds.map(ind =>
|
inds = inds.map(ind =>
|
||||||
@@ -203,12 +203,12 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return inds;
|
return inds;
|
||||||
}, [baseIndicators, overlayVisibility, hiddenIndicatorIds]);
|
}, [baseIndicators, effectiveOverlayVisibility, hiddenIndicatorIds]);
|
||||||
|
|
||||||
const candleOverlayToggles = useMemo(() => {
|
const candleOverlayToggles = useMemo(
|
||||||
if (!overlayVisibility || !onToggleOverlay) return undefined;
|
() => listPaperOverlayToggleItems(baseIndicators, effectiveOverlayVisibility),
|
||||||
return listPaperOverlayToggleItems(baseIndicators, overlayVisibility);
|
[baseIndicators, effectiveOverlayVisibility],
|
||||||
}, [baseIndicators, overlayVisibility, onToggleOverlay]);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 캔들 오버레이 그룹 토글 — 실시간 차트와 동일하게 ChartManager 를 즉시 호출한 뒤
|
* 캔들 오버레이 그룹 토글 — 실시간 차트와 동일하게 ChartManager 를 즉시 호출한 뒤
|
||||||
@@ -217,11 +217,15 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
const handleToggleCandleOverlay = useCallback((key: ChartOverlayToggleKey) => {
|
const handleToggleCandleOverlay = useCallback((key: ChartOverlayToggleKey) => {
|
||||||
const pKey = key as PaperOverlayToggleKey;
|
const pKey = key as PaperOverlayToggleKey;
|
||||||
const prev = overlayVisibilityRef.current;
|
const prev = overlayVisibilityRef.current;
|
||||||
if (prev && pKey in prev) {
|
if (pKey in prev) {
|
||||||
const newVisible = !prev[pKey];
|
const newVisible = !prev[pKey];
|
||||||
managerRef.current?.setChartOverlayGroupVisible(key, newVisible);
|
managerRef.current?.setChartOverlayGroupVisible(key, newVisible);
|
||||||
}
|
}
|
||||||
onToggleOverlay?.(pKey);
|
if (onToggleOverlay) {
|
||||||
|
onToggleOverlay(pKey);
|
||||||
|
} else {
|
||||||
|
setInternalOverlayVisibility(prev => ({ ...prev, [pKey]: !prev[pKey] }));
|
||||||
|
}
|
||||||
}, [onToggleOverlay]);
|
}, [onToggleOverlay]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -360,7 +364,7 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
managerRef.current = mgr;
|
managerRef.current = mgr;
|
||||||
// 실시간 차트와 동일하게: 마운트 시 오버레이 가시성 ChartManager 에 동기화
|
// 실시간 차트와 동일하게: 마운트 시 오버레이 가시성 ChartManager 에 동기화
|
||||||
if (overlayVisibilityRef.current) {
|
if (overlayVisibilityRef.current) {
|
||||||
mgr.setChartOverlayVisibility(paperVisToChartVis(overlayVisibilityRef.current));
|
mgr.setChartOverlayVisibility(paperOverlayVisibilityToChart(overlayVisibilityRef.current));
|
||||||
}
|
}
|
||||||
setupHistoryScroll(mgr);
|
setupHistoryScroll(mgr);
|
||||||
setLoadedBarCount(mgr.getRawBarsLength());
|
setLoadedBarCount(mgr.getRawBarsLength());
|
||||||
@@ -472,7 +476,8 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
seriesPriceLabelsEnabled={priceLabels}
|
seriesPriceLabelsEnabled={priceLabels}
|
||||||
showHoverToolbar
|
showHoverToolbar
|
||||||
candleOverlayToggles={candleOverlayToggles}
|
candleOverlayToggles={candleOverlayToggles}
|
||||||
onToggleCandleOverlay={onToggleOverlay ? handleToggleCandleOverlay : undefined}
|
onToggleCandleOverlay={handleToggleCandleOverlay}
|
||||||
|
showCandleOverlayControls
|
||||||
onToggleIndicatorHidden={handleToggleIndicatorHidden}
|
onToggleIndicatorHidden={handleToggleIndicatorHidden}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import type { ChartManager } from '../../utils/ChartManager';
|
|||||||
import { pinCandleWatch } from '../../utils/backendApi';
|
import { pinCandleWatch } from '../../utils/backendApi';
|
||||||
import { timeframeToCandleType } from '../../utils/chartCandleType';
|
import { timeframeToCandleType } from '../../utils/chartCandleType';
|
||||||
import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone';
|
import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone';
|
||||||
|
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
|
||||||
|
import { useSessionCandleOverlayControls } from '../../hooks/useSessionCandleOverlayControls';
|
||||||
|
import { ensurePaperChartOverlays } from '../../utils/strategyToChartIndicators';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
market: string;
|
market: string;
|
||||||
@@ -40,6 +43,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
|
|||||||
fillHeight = false,
|
fillHeight = false,
|
||||||
onRealtimeActivity,
|
onRealtimeActivity,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { getParams, getVisualConfig } = useIndicatorSettings();
|
||||||
const managerRef = useRef<ChartManager | null>(null);
|
const managerRef = useRef<ChartManager | null>(null);
|
||||||
const canvasWrapRef = useRef<HTMLDivElement | null>(null);
|
const canvasWrapRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [chartReloadTick, setChartReloadTick] = useState(0);
|
const [chartReloadTick, setChartReloadTick] = useState(0);
|
||||||
@@ -50,6 +54,18 @@ const TradeSignalMiniChart: React.FC<Props> = ({
|
|||||||
const marketRef = useRef(market);
|
const marketRef = useRef(market);
|
||||||
marketRef.current = 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 syncChartLayout = useCallback(() => {
|
||||||
const wrap = canvasWrapRef.current;
|
const wrap = canvasWrapRef.current;
|
||||||
const mgr = managerRef.current;
|
const mgr = managerRef.current;
|
||||||
@@ -191,7 +207,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<TradingChart
|
<TradingChart
|
||||||
key={`${market}-${timeframe}-${chartReloadTick}-${indicators.map(i => i.id).join(',')}`}
|
key={`${market}-${timeframe}-${chartReloadTick}-${chartIndicators.map(i => i.id).join(',')}`}
|
||||||
chartVisible
|
chartVisible
|
||||||
bars={bars}
|
bars={bars}
|
||||||
barsMarket={barsMarket}
|
barsMarket={barsMarket}
|
||||||
@@ -200,14 +216,17 @@ const TradeSignalMiniChart: React.FC<Props> = ({
|
|||||||
chartType={'candlestick' as ChartType}
|
chartType={'candlestick' as ChartType}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
mode={'chart' as ChartMode}
|
mode={'chart' as ChartMode}
|
||||||
indicators={indicators}
|
indicators={chartIndicators}
|
||||||
drawingTool="cursor"
|
drawingTool="cursor"
|
||||||
drawings={[] as Drawing[]}
|
drawings={[] as Drawing[]}
|
||||||
logScale={false}
|
logScale={false}
|
||||||
drawingsLocked
|
drawingsLocked
|
||||||
drawingsVisible={false}
|
drawingsVisible={false}
|
||||||
onCrosshair={noop as (d: LegendData | null) => void}
|
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}
|
onAddDrawing={noop as (d: Drawing) => void}
|
||||||
onCandlesReady={handleCandlesReady}
|
onCandlesReady={handleCandlesReady}
|
||||||
magnifierEnabled={false}
|
magnifierEnabled={false}
|
||||||
@@ -215,7 +234,12 @@ const TradeSignalMiniChart: React.FC<Props> = ({
|
|||||||
showHoverToolbar={false}
|
showHoverToolbar={false}
|
||||||
seriesPriceLabelsEnabled={false}
|
seriesPriceLabelsEnabled={false}
|
||||||
paneLayoutClamp
|
paneLayoutClamp
|
||||||
showPaneLegend={false}
|
showPaneLegend
|
||||||
|
showChartRightToolbar={false}
|
||||||
|
showCandleOverlayControls
|
||||||
|
candleOverlayToggles={candleOverlayToggles}
|
||||||
|
onToggleCandleOverlay={handleToggleCandleOverlay}
|
||||||
|
crosshairInfoVisible={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { useCallback, useMemo, useRef, useState, type RefObject } from 'react';
|
||||||
|
import type { IndicatorConfig } from '../types';
|
||||||
|
import type { ChartManager } from '../utils/ChartManager';
|
||||||
|
import type { ChartOverlayToggleKey } from '../utils/chartOverlayVisibility';
|
||||||
|
import {
|
||||||
|
applyPaperOverlayVisibility,
|
||||||
|
DEFAULT_PAPER_OVERLAY_VISIBILITY,
|
||||||
|
listPaperOverlayToggleItems,
|
||||||
|
paperOverlayVisibilityToChart,
|
||||||
|
type PaperOverlayToggleKey,
|
||||||
|
type PaperOverlayVisibility,
|
||||||
|
} from '../utils/paperChartOverlayVisibility';
|
||||||
|
|
||||||
|
/** 알림·백테스트 등 — 캔들 오버레이 표시/숨김 (세션만, DB 미저장) */
|
||||||
|
export function useSessionCandleOverlayControls(
|
||||||
|
managerRef: RefObject<ChartManager | null>,
|
||||||
|
baseIndicators: IndicatorConfig[],
|
||||||
|
) {
|
||||||
|
const [overlayVisibility, setOverlayVisibility] = useState<PaperOverlayVisibility>(
|
||||||
|
() => ({ ...DEFAULT_PAPER_OVERLAY_VISIBILITY }),
|
||||||
|
);
|
||||||
|
const overlayVisibilityRef = useRef(overlayVisibility);
|
||||||
|
overlayVisibilityRef.current = overlayVisibility;
|
||||||
|
|
||||||
|
const indicators = useMemo(
|
||||||
|
() => applyPaperOverlayVisibility(baseIndicators, overlayVisibility),
|
||||||
|
[baseIndicators, overlayVisibility],
|
||||||
|
);
|
||||||
|
|
||||||
|
const candleOverlayToggles = useMemo(
|
||||||
|
() => listPaperOverlayToggleItems(baseIndicators, overlayVisibility),
|
||||||
|
[baseIndicators, overlayVisibility],
|
||||||
|
);
|
||||||
|
|
||||||
|
const syncToManager = useCallback((mgr: ChartManager) => {
|
||||||
|
mgr.setChartOverlayVisibility(paperOverlayVisibilityToChart(overlayVisibilityRef.current));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleToggleCandleOverlay = useCallback((key: ChartOverlayToggleKey) => {
|
||||||
|
const pKey = key as PaperOverlayToggleKey;
|
||||||
|
if (!(pKey in DEFAULT_PAPER_OVERLAY_VISIBILITY)) return;
|
||||||
|
setOverlayVisibility(prev => {
|
||||||
|
const nextVisible = !prev[pKey];
|
||||||
|
managerRef.current?.setChartOverlayGroupVisible(key, nextVisible);
|
||||||
|
return { ...prev, [pKey]: nextVisible };
|
||||||
|
});
|
||||||
|
}, [managerRef]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
indicators,
|
||||||
|
candleOverlayToggles,
|
||||||
|
handleToggleCandleOverlay,
|
||||||
|
syncToManager,
|
||||||
|
overlayVisibility,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1013,6 +1013,23 @@
|
|||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 보조지표 pane 좌측 상단 지표명 */
|
||||||
|
.tnl-mini-chart-canvas .pane-legend-name {
|
||||||
|
font-size: 10px;
|
||||||
|
max-width: calc(100% - 12px);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 캔들 pane 우측 상단 오버레이 on/off (미니 차트) */
|
||||||
|
.tnl-mini-chart-canvas .chart-right-toolbar--overlay-only .chart-right-toolbar-btn {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
min-width: 24px;
|
||||||
|
min-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.tnl-mini-chart-loading {
|
.tnl-mini-chart-loading {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
|||||||
@@ -47,8 +47,15 @@ export function applyPaperOverlayVisibility(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 투자관리 차트 — 표시 설정 메뉴에 항상 노출할 오버레이 (지표 인스턴스 유무와 무관) */
|
/** 캔들 오버레이 토글 — 전략·지표 유무와 무관하게 항상 노출 */
|
||||||
const PAPER_OVERLAY_ALWAYS_MENU: PaperOverlayToggleKey[] = ['ma', 'bollinger'];
|
const PAPER_OVERLAY_ALWAYS_MENU: PaperOverlayToggleKey[] = ['ma', 'ichimoku', 'bollinger'];
|
||||||
|
|
||||||
|
/** ChartManager.setChartOverlayVisibility 용 */
|
||||||
|
export function paperOverlayVisibilityToChart(
|
||||||
|
visibility: PaperOverlayVisibility,
|
||||||
|
): import('./chartOverlayVisibility').ChartOverlayVisibility {
|
||||||
|
return { ma: visibility.ma, bollinger: visibility.bollinger, ichimoku: visibility.ichimoku, candle: true };
|
||||||
|
}
|
||||||
|
|
||||||
export function listPaperOverlayToggleItems(
|
export function listPaperOverlayToggleItems(
|
||||||
indicators: IndicatorConfig[],
|
indicators: IndicatorConfig[],
|
||||||
|
|||||||
@@ -254,8 +254,8 @@ export function buildStrategyChartIndicators(
|
|||||||
const MA_OVERLAY_TYPES = new Set(['SMA', 'EMA']);
|
const MA_OVERLAY_TYPES = new Set(['SMA', 'EMA']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 투자관리 차트 탭 — 캔들 영역에 이동평균(SMA)·볼린저밴드를 기본 포함
|
* 캔들 영역 기본 오버레이 — 이동평균(SMA)·일목균형표·볼린저밴드
|
||||||
* (전략 조건에 없어도 표시 설정 메뉴로 표시/숨김 가능)
|
* (전략에 없어도 표시, 우측 상단 토글로 표시/숨김)
|
||||||
*/
|
*/
|
||||||
export function ensurePaperChartOverlays(
|
export function ensurePaperChartOverlays(
|
||||||
indicators: IndicatorConfig[],
|
indicators: IndicatorConfig[],
|
||||||
@@ -263,6 +263,7 @@ export function ensurePaperChartOverlays(
|
|||||||
getVisual: GetVisual,
|
getVisual: GetVisual,
|
||||||
): IndicatorConfig[] {
|
): IndicatorConfig[] {
|
||||||
const hasMa = indicators.some(i => MA_OVERLAY_TYPES.has(i.type));
|
const hasMa = indicators.some(i => MA_OVERLAY_TYPES.has(i.type));
|
||||||
|
const hasIchimoku = indicators.some(i => i.type === 'IchimokuCloud');
|
||||||
const hasBb = indicators.some(i => i.type === 'BollingerBands');
|
const hasBb = indicators.some(i => i.type === 'BollingerBands');
|
||||||
const prepend: IndicatorConfig[] = [];
|
const prepend: IndicatorConfig[] = [];
|
||||||
|
|
||||||
@@ -274,6 +275,14 @@ export function ensurePaperChartOverlays(
|
|||||||
);
|
);
|
||||||
if (sma) prepend.push(sma);
|
if (sma) prepend.push(sma);
|
||||||
}
|
}
|
||||||
|
if (!hasIchimoku) {
|
||||||
|
const ichimoku = buildOneIndicator(
|
||||||
|
{ dslType: 'ICHIMOKU', registryType: 'IchimokuCloud' },
|
||||||
|
getParams,
|
||||||
|
getVisual,
|
||||||
|
);
|
||||||
|
if (ichimoku) prepend.push(ichimoku);
|
||||||
|
}
|
||||||
if (!hasBb) {
|
if (!hasBb) {
|
||||||
const bb = buildOneIndicator(
|
const bb = buildOneIndicator(
|
||||||
{ dslType: 'BOLLINGER', registryType: 'BollingerBands' },
|
{ dslType: 'BOLLINGER', registryType: 'BollingerBands' },
|
||||||
|
|||||||
Reference in New Issue
Block a user