캔들차트, 이동평균선, 일목균형표, 볼린저밴드 보이기 숨기기 기능적용

This commit is contained in:
Macbook
2026-06-03 00:19:44 +09:00
parent 08fb442d4a
commit 76e5b12d19
15 changed files with 427 additions and 69 deletions
+53 -1
View File
@@ -47,6 +47,13 @@ import {
} from './utils/indicatorMainConfig';
import { normalizeSmaConfig, createDefaultSmaPlotVisibility } from './utils/smaConfig';
import { normalizeIchimokuConfig } from './utils/ichimokuConfig';
import {
type ChartOverlayToggleKey,
type ChartOverlayVisibility,
chartOverlayKeyForType,
DEFAULT_CHART_OVERLAY_VISIBILITY,
listChartOverlayToggleItems,
} from './utils/chartOverlayVisibility';
import { isUpbitMarket, UPBIT_MARKETS } from './utils/upbitApi';
import { getFavorites, saveFavorites, initFavoritesFromDb, initHoldingsFromDb, FAVORITES_CHANGED_EVENT } from './utils/marketStorage';
import { useChartRealtimeData, type WsStatus } from './hooks/useChartRealtimeData';
@@ -215,6 +222,8 @@ function App() {
const [mode, setMode] = useState<ChartMode>('chart');
const [logScale, setLogScale] = useState(false);
const [indicators, setIndicators] = useState<IndicatorConfig[]>(initial.indicators ?? []);
const indicatorsRef = useRef(indicators);
useEffect(() => { indicatorsRef.current = indicators; }, [indicators]);
const [drawingTool, setDrawingTool] = useState('cursor');
const [drawings, setDrawings] = useState<Drawing[]>([]);
const [drawingsLocked, setDrawingsLocked] = useState(false);
@@ -668,6 +677,7 @@ function App() {
const chartLiveReceiveHighlight = appDefaults.chartLiveReceiveHighlight ?? true;
const chartLegendOptions = appDefaults.chartLegendOptions;
const chartCrosshairInfoVisible = appDefaults.chartCrosshairInfoVisible ?? true;
const chartHoverToolbarVisible = appDefaults.chartHoverToolbarVisible ?? true;
const chartPaneSeparator = appDefaults.chartPaneSeparator;
const paperTradingEnabled = appDefaults.paperTradingEnabled ?? true;
const paperAutoTradeEnabled = appDefaults.paperAutoTradeEnabled ?? false;
@@ -1432,6 +1442,37 @@ function App() {
setFocusedIndicatorId(null);
}, []);
const [overlayVisibility, setOverlayVisibility] = useState<ChartOverlayVisibility>(
() => ({ ...DEFAULT_CHART_OVERLAY_VISIBILITY }),
);
const overlayVisibilityRef = useRef(overlayVisibility);
useEffect(() => {
overlayVisibilityRef.current = overlayVisibility;
}, [overlayVisibility]);
const candleOverlayToggles = useMemo(
() => listChartOverlayToggleItems(overlayVisibility),
[overlayVisibility],
);
const handleToggleCandleOverlay = useCallback((key: ChartOverlayToggleKey) => {
setOverlayVisibility(prev => {
const next = { ...prev, [key]: !prev[key] };
const mgr = managerRef.current;
mgr?.setChartOverlayGroupVisible(key, next[key]);
if (mgr) {
for (const info of mgr.getIndicatorPaneInfo()) {
if (chartOverlayKeyForType(info.type) !== key) continue;
const reactInd = indicatorsRef.current.find(i => i.id === info.id);
mgr.applyIndicatorStyle(reactInd
? { ...info.config, ...reactInd, id: info.id, type: info.type }
: info.config);
}
}
return next;
});
}, []);
/**
* 보조지표 순서 변경 (드래그 핸들): fromId 를 indicators 배열에서 이동.
* overlay/markers 지표는 건드리지 않고 비오버레이 지표 위치만 변경됨.
@@ -2007,6 +2048,8 @@ function App() {
}}
chartCrosshairInfoVisible={chartCrosshairInfoVisible}
onChartCrosshairInfoVisible={v => saveAppDef({ chartCrosshairInfoVisible: v })}
chartHoverToolbarVisible={chartHoverToolbarVisible}
onChartHoverToolbarVisible={v => saveAppDef({ chartHoverToolbarVisible: v })}
chartPaneSeparator={chartPaneSeparator}
onChartPaneSeparatorChange={opts => {
saveAppDef({ chartPaneSeparator: opts });
@@ -2322,6 +2365,7 @@ function App() {
chartVolumeVisible={chartVolumeVisible}
chartPaneSeparator={chartPaneSeparator}
chartCrosshairInfoVisible={chartCrosshairInfoVisible}
chartHoverToolbarVisible={chartHoverToolbarVisible}
chartLiveReceiveHighlight={chartLiveReceiveHighlight}
chartRealtimeSource={chartRealtimeSource as 'BACKEND_STOMP' | 'UPBIT_DIRECT'}
displayTimezone={displayTimezone}
@@ -2364,6 +2408,7 @@ function App() {
chartVolumeVisible={chartVolumeVisible}
chartPaneSeparator={chartPaneSeparator}
chartCrosshairInfoVisible={chartCrosshairInfoVisible}
chartHoverToolbarVisible={chartHoverToolbarVisible}
chartLiveReceiveHighlight={chartLiveReceiveHighlight}
chartRealtimeSource={chartRealtimeSource as 'BACKEND_STOMP' | 'UPBIT_DIRECT'}
displayTimezone={displayTimezone}
@@ -2405,7 +2450,10 @@ function App() {
crosshairInfoVisible={chartCrosshairInfoVisible}
onCrosshair={setLegend}
onTradeOrderRequest={applyTradeFill}
onDataLoaded={() => syncBacktestMarkersRef.current()}
onDataLoaded={() => {
managerRef.current?.setChartOverlayVisibility(overlayVisibilityRef.current);
syncBacktestMarkersRef.current();
}}
onCandlesReady={handleCandlesReady}
onManagerReady={mgr => {
managerRef.current = mgr;
@@ -2425,6 +2473,7 @@ function App() {
mgr.setVolumeVisible(chartVolumeVisible, wrapper?.clientHeight);
mgr.setPaneSeparatorOptions(chartPaneSeparator);
mgr.setDisplayTimezone(displayTimezone, timeframe, chartTimeFormat);
mgr.setChartOverlayVisibility(overlayVisibilityRef.current);
syncBacktestMarkersRef.current();
// 왼쪽 스크롤 감지 → 과거 데이터 추가 로드
mgr.subscribeVisibleLogicalRange(r => {
@@ -2469,6 +2518,9 @@ function App() {
candleAreaPriceLabelsEnabled={chartCandleAreaPriceLabels}
indicatorAreaPriceLabelsEnabled={chartSeriesPriceLabels}
paneSeparatorOptions={chartPaneSeparator}
candleOverlayToggles={candleOverlayToggles}
onToggleCandleOverlay={handleToggleCandleOverlay}
showHoverToolbar={chartHoverToolbarVisible}
/>
{isSingleLoadingMore && (
<div className="chart-history-loading">