백테스팅 캔들차트 하단에 보조지표 표시
This commit is contained in:
@@ -85,12 +85,27 @@ const RightSidePanel: React.FC<RightSidePanelProps> = ({
|
|||||||
const isOpenControlled = openControlled !== undefined && onOpenChange !== undefined;
|
const isOpenControlled = openControlled !== undefined && onOpenChange !== undefined;
|
||||||
const open = isOpenControlled ? !!openControlled : openInternal;
|
const open = isOpenControlled ? !!openControlled : openInternal;
|
||||||
const { trades: recentTrades, strength: tradeStrength } = useUpbitRecentTrades(market, open && wsFeedEnabled);
|
const { trades: recentTrades, strength: tradeStrength } = useUpbitRecentTrades(market, open && wsFeedEnabled);
|
||||||
const setOpen = (next: boolean | ((p: boolean) => boolean)) => {
|
|
||||||
const cur = isOpenControlled ? !!openControlled : openInternal;
|
/** prop 최신값을 ref 로 유지 — setOpen 콜백이 stale closure 를 참조하지 않도록 */
|
||||||
|
const openControlledRef = useRef(openControlled);
|
||||||
|
openControlledRef.current = openControlled;
|
||||||
|
const isOpenControlledRef = useRef(isOpenControlled);
|
||||||
|
isOpenControlledRef.current = isOpenControlled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useCallback 으로 안정적인 참조 유지.
|
||||||
|
* setOpenInternal 은 useState 가 보장하는 stable identity 이므로 deps 불필요.
|
||||||
|
* fillRequest useEffect 에서 deps 변경으로 인한 재실행을 방지한다.
|
||||||
|
*/
|
||||||
|
const setOpen = useCallback((next: boolean | ((p: boolean) => boolean)) => {
|
||||||
|
if (isOpenControlledRef.current && onOpenChange) {
|
||||||
|
const cur = !!openControlledRef.current;
|
||||||
const value = typeof next === 'function' ? next(cur) : next;
|
const value = typeof next === 'function' ? next(cur) : next;
|
||||||
if (isOpenControlled && onOpenChange) onOpenChange(value);
|
onOpenChange(value);
|
||||||
else setOpenInternal(value);
|
} else {
|
||||||
};
|
setOpenInternal(prev => typeof next === 'function' ? next(prev) : next);
|
||||||
|
}
|
||||||
|
}, [onOpenChange]);
|
||||||
|
|
||||||
const activeTab = activeTabControlled ?? tabInternal;
|
const activeTab = activeTabControlled ?? tabInternal;
|
||||||
const setActiveTab = useCallback((tab: TradeRightPanelTab) => {
|
const setActiveTab = useCallback((tab: TradeRightPanelTab) => {
|
||||||
@@ -102,7 +117,9 @@ const RightSidePanel: React.FC<RightSidePanelProps> = ({
|
|||||||
if (!fillRequest) return;
|
if (!fillRequest) return;
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setActiveTab('trade');
|
setActiveTab('trade');
|
||||||
}, [fillRequest?.seq, fillRequest?.side, setOpen, setActiveTab]);
|
// setOpen / setActiveTab 은 안정적인 useCallback 참조이므로 deps 에서 제외
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [fillRequest?.seq, fillRequest?.side]);
|
||||||
|
|
||||||
const handleOrderbookRowClick = useCallback((price: number, rowType: 'ask' | 'bid') => {
|
const handleOrderbookRowClick = useCallback((price: number, rowType: 'ask' | 'bid') => {
|
||||||
setActiveTab('trade');
|
setActiveTab('trade');
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ interface TradingChartProps {
|
|||||||
paneSeparatorOptions?: ChartPaneSeparatorOptions;
|
paneSeparatorOptions?: ChartPaneSeparatorOptions;
|
||||||
/** true: pane 합산 높이로 chart-container 를 키우지 않고 래퍼 높이에 맞춤 (미니 차트·알림 목록) */
|
/** true: pane 합산 높이로 chart-container 를 키우지 않고 래퍼 높이에 맞춤 (미니 차트·알림 목록) */
|
||||||
paneLayoutClamp?: boolean;
|
paneLayoutClamp?: boolean;
|
||||||
|
/** paneLayoutClamp=true 환경에서도 캔들/보조지표 비율 드래그 핸들을 표시 (백테스팅 차트 등) */
|
||||||
|
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, 알림 미니차트는 false) */
|
||||||
@@ -207,6 +209,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
chartVisible = true,
|
chartVisible = true,
|
||||||
paneSeparatorOptions,
|
paneSeparatorOptions,
|
||||||
paneLayoutClamp = false,
|
paneLayoutClamp = false,
|
||||||
|
showPaneResizeHandle = false,
|
||||||
paneAreaRatio = null,
|
paneAreaRatio = null,
|
||||||
showPaneLegend = true,
|
showPaneLegend = true,
|
||||||
crosshairInfoVisible = true,
|
crosshairInfoVisible = true,
|
||||||
@@ -250,6 +253,8 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
const candleOnlyModeRef = useRef(false);
|
const candleOnlyModeRef = useRef(false);
|
||||||
const paneAreaRatioRef = useRef(paneAreaRatio);
|
const paneAreaRatioRef = useRef(paneAreaRatio);
|
||||||
paneAreaRatioRef.current = paneAreaRatio;
|
paneAreaRatioRef.current = paneAreaRatio;
|
||||||
|
const paneLayoutClampRef = useRef(paneLayoutClamp);
|
||||||
|
paneLayoutClampRef.current = paneLayoutClamp;
|
||||||
/** 사용자 드래그로 설정한 pane 비율 — prop 변경 시 초기화 */
|
/** 사용자 드래그로 설정한 pane 비율 — prop 변경 시 초기화 */
|
||||||
const userDraggedRatioRef = useRef<{ candle: number; aux: number } | null>(null);
|
const userDraggedRatioRef = useRef<{ candle: number; aux: number } | null>(null);
|
||||||
const lastCrosshairPriceRef = useRef<number | null>(null);
|
const lastCrosshairPriceRef = useRef<number | null>(null);
|
||||||
@@ -390,8 +395,11 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
userDraggedRatioRef.current = { candle: newCandle, aux: newAux };
|
userDraggedRatioRef.current = { candle: newCandle, aux: newAux };
|
||||||
const required = mgr.resetPaneHeights(H);
|
const required = mgr.resetPaneHeights(H);
|
||||||
|
|
||||||
// 스크롤 영역이 필요한 경우 컨테이너 확장
|
// paneLayoutClamp 모드: 컨테이너를 래퍼 높이에 고정 (overflow:hidden 으로 클립)
|
||||||
const newContainerH = required > H + 4 ? required : H;
|
// 일반 모드: 보조지표 필요 시 컨테이너 확장하여 스크롤 영역 확보
|
||||||
|
const newContainerH = paneLayoutClampRef.current
|
||||||
|
? H
|
||||||
|
: (required > H + 4 ? required : H);
|
||||||
if (container.style.height !== `${newContainerH}px`) {
|
if (container.style.height !== `${newContainerH}px`) {
|
||||||
container.style.height = `${newContainerH}px`;
|
container.style.height = `${newContainerH}px`;
|
||||||
}
|
}
|
||||||
@@ -1698,7 +1706,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
className="chart-container"
|
className="chart-container"
|
||||||
/>
|
/>
|
||||||
{/* 캔들 pane 하단 드래그 핸들 — 캔들/보조지표 높이 비율 조절 */}
|
{/* 캔들 pane 하단 드래그 핸들 — 캔들/보조지표 높이 비율 조절 */}
|
||||||
{candlePaneBottomY != null && !candleOnlyMode && !paneLayoutClamp && chartPaintReady && (
|
{candlePaneBottomY != null && !candleOnlyMode && (!paneLayoutClamp || showPaneResizeHandle) && chartPaintReady && (
|
||||||
<div
|
<div
|
||||||
className="chart-pane-resize-handle"
|
className="chart-pane-resize-handle"
|
||||||
style={{ top: candlePaneBottomY }}
|
style={{ top: candlePaneBottomY }}
|
||||||
|
|||||||
@@ -190,9 +190,12 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
let inds: IndicatorConfig[];
|
let inds: IndicatorConfig[];
|
||||||
if (strategy) {
|
if (strategy) {
|
||||||
inds = buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig);
|
inds = buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig);
|
||||||
// [수정] 오실레이터 필터 제거 — TradingChart sub-pane 에서 직접 렌더링
|
|
||||||
// (기존: showOscillatorPanel 시 overlay 지표만 남기고 나머지는 SVG 패널로 분리)
|
|
||||||
inds = inds.filter(i => i.timeframeVisibility?.[chartTimeframe] !== false);
|
inds = inds.filter(i => i.timeframeVisibility?.[chartTimeframe] !== false);
|
||||||
|
// 전략 지표가 모두 캔들 오버레이(SMA/EMA/일목 등)인 경우 기본 오실레이터(RSI·MACD)를 추가.
|
||||||
|
// 기존 SVG 기반 StrategyOscillatorPanes 의 fallback 동작과 동일.
|
||||||
|
if (showOscillatorPanel && inds.every(i => isOverlayType(i.type))) {
|
||||||
|
inds = [...inds, ...defaultOscillatorIndicators(getParams)];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
inds = defaultOverlayIndicators(getParams);
|
inds = defaultOverlayIndicators(getParams);
|
||||||
// 전략 미선택 시 기본 오실레이터(RSI, MACD)도 TradingChart sub-pane 으로 추가
|
// 전략 미선택 시 기본 오실레이터(RSI, MACD)도 TradingChart sub-pane 으로 추가
|
||||||
@@ -477,6 +480,7 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
volumeVisible={false}
|
volumeVisible={false}
|
||||||
paneSeparatorOptions={appDefaults.chartPaneSeparator}
|
paneSeparatorOptions={appDefaults.chartPaneSeparator}
|
||||||
paneLayoutClamp
|
paneLayoutClamp
|
||||||
|
showPaneResizeHandle={auxPaneCount > 0}
|
||||||
paneAreaRatio={paneAreaRatio}
|
paneAreaRatio={paneAreaRatio}
|
||||||
showPaneLegend
|
showPaneLegend
|
||||||
crosshairInfoVisible
|
crosshairInfoVisible
|
||||||
|
|||||||
Reference in New Issue
Block a user