import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { useLocation } from 'react-router-dom';
import { Time } from 'lightweight-charts';
import {
  Box,
  Typography,
  Paper,
  CircularProgress,
  Select,
  MenuItem,
  FormControl,
  InputLabel,
  IconButton,
  Tooltip as MuiTooltip,
  Chip,
  Divider,
  Grid,
  Button,
  useTheme,
  useMediaQuery,
  Skeleton,
  Fade,
  LinearProgress,
  Dialog,
  DialogTitle,
  DialogContent,
  DialogActions,
  Alert,
  Menu,
  ListItemIcon,
  ListItemText,
  Checkbox,
  TextField,
  ToggleButton,
  ToggleButtonGroup,
  Backdrop,
  Snackbar,
  Drawer,
} from '@mui/material';
import { keyframes } from '@mui/material/styles';
import {
  Settings as SettingsIcon,
  FileDownload as FileDownloadIcon,
  FileUpload as FileUploadIcon,
  Fullscreen,
  FullscreenExit,
  Refresh, // ✅ 새로고침
  CalendarMonth,
  TrendingUp,
  TrendingDown,
  Timeline,
  Close as CloseIcon,
  Psychology,
  AutoAwesome as AIChatIcon,
  Analytics as AnalyticsIcon,
  DragIndicator,
  Search as SearchIcon,
  BarChart as BarChartIcon,
  KeyboardArrowDown,
  CheckBox,
  CheckBoxOutlineBlank,
  Info, // ✅ 툴팁 아이콘
  ZoomIn, // ✅ 확대
  ZoomOut, // ✅ 축소
  RestartAlt, // ✅ 초기화
  NavigateBefore, // ✅ 이전 기간
  NavigateNext, // ✅ 다음 기간
  CheckCircle, // ✅ 전략 체크
  Tune as TuneIcon, // ✅ 전체 차트 설정
  ShowChart, // ✅ 이동평균선 아이콘
  ViewList as ViewListIcon, // ✅ 즐겨찾기 목록 아이콘
  Schedule, // ✅ 봉 완성 시간 아이콘
  TableRows,
  ChevronRight as ChevronRightIcon,
} from '@mui/icons-material';
import {
  DndContext,
  closestCenter,
  KeyboardSensor,
  PointerSensor,
  useSensor,
  useSensors,
  DragEndEvent,
  DragOverEvent,
  DragStartEvent,
  useDroppable,
  DragOverlay,
} from '@dnd-kit/core';
import {
  arrayMove,
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import CandlestickChart, { ChartHandle } from '../components/CandlestickChart';
import MarketList from '../components/MarketList';
import { useUpbitTicker } from '../hooks/useUpbitTicker';
import ChartToolbar, { ChartTool } from '../components/ChartToolbar';
import ChartSettingsDialog from '../components/ChartSettingsDialog';
import IndicatorSettingsPopup, { IndicatorType } from '../components/IndicatorSettingsPopup';
import MASettingsDialog from '../components/MASettingsDialog';
import IchimokuSettingsDialog from '../components/IchimokuSettingsDialog';
import BollingerSettingsDialog from '../components/BollingerSettingsDialog';
import IndicatorParamsSettingsDialog from '../components/IndicatorParamsSettingsDialog';
import FibonacciTimezoneDialog, { FibonacciTimezoneSettings, FibonacciLevel, FibonacciBaseline, defaultBaseline, defaultLevels } from '../components/FibonacciTimezoneDialog';
import MeasureSettingsDialog, { MeasureSettings, defaultMeasureSettings } from '../components/MeasureSettingsDialog';
import VerticalLineDialog, { VerticalLineSettings, defaultVerticalLineSettings } from '../components/VerticalLineDialog';
import FibonacciTimezoneOverlay, { FibonacciTimezoneObject } from '../components/FibonacciTimezoneOverlay';
import FibonacciToolbar from '../components/FibonacciToolbar';
import FibonacciFan, { FibonacciFanObject } from '../components/FibonacciFan';
import FibonacciCircle, { FibonacciCircleObject } from '../components/FibonacciCircle';
import FibonacciWedge, { FibonacciWedgeObject } from '../components/FibonacciWedge';
import FibonacciDrawingSettingsDialog from '../components/FibonacciDrawingSettingsDialog';
import CandleTimeRemaining from '../components/CandleTimeRemaining';
import SignalTable, { TradeSimulation } from '../components/SignalTable';
import CryptoNameComboBox from '../components/CryptoNameComboBox';
import CryptoListPanel from '../components/CryptoListPanel';
import ChartDetailPopup, { ChartDetailType } from '../components/ChartDetailPopup';
import AIChatPanel, { UICommand } from '../components/AIChatPanel';
import RealTimeAnalysisPanel, { RealTimeAnalysisPanelRef } from '../components/RealTimeAnalysisPanel';
import IndicatorChart, { IndicatorChartHandle } from '../components/IndicatorChart';
import { useMarketDataWebSocket } from '../hooks/useMarketDataWebSocket';
import { acquireWsCandleSubscription, releaseWsCandleSubscription } from '../services/upbitWsSubscriptionApi';
import axios from 'axios';
import { useIndicatorVisibility, IndicatorVisibilitySettings } from '../contexts/IndicatorVisibilityContext';
import { useIndicatorSettings } from '../contexts/IndicatorSettingsContext';
import { useChartColors } from '../contexts/ChartColorContext';
import { useNavigation } from '../contexts/NavigationContext';
import { getApiBaseUrl } from '../services/apiConfig';
import { CandleData, IndicatorData, runBacktest, BacktestResult, TradeRecord } from '../services/backtestApi';
import { getStrategyRules, StrategyRuleDto } from '../services/strategyRuleApi';
import { analyzeSignals, SignalAnalysisRequest, CandleSignal } from '../services/strategySignalApi';
import type { UpbitMarket } from '../services/upbitApi';
import { settingsApi } from '../services/settingsApi';
import { getAlertBySymbol, toggleAlert, createAlert, getScheduleConfig, saveScheduleConfig } from '../services/alertApi';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer, ReferenceLine, ComposedChart, Bar, Cell, ReferenceDot } from 'recharts';
import ChartDrawingOverlay, { DrawingObject } from '../components/ChartDrawingOverlay';
import DrawingSettingsDialog from '../components/DrawingSettingsDialog';
import DraggablePaper from '../components/DraggablePaper';
import RealtimeChartValuesPopup from '../components/RealtimeChartValuesPopup';
import IndicatorGaugePanel from '../components/IndicatorGaugePanel';
import { TradeSignalDetailDialog } from '../components/TradeSignalDetailDialog';
import {
  getStorageMode,
  loadFromFile,
  saveToFile,
  downloadConfigFile,
  applyConfigFromFile,
  type InvestmentAnalysisConfig,
} from '../services/investmentAnalysisSettingsStorage';
import { mapWsIndicatorsToChartFormat } from '../utils/investmentChart/wsIndicatorMap';
import {
  candleTimeToUnixSeconds,
  candleOpenTimeForChartRow,
  chartCandleOpenToEpochMs,
  findIndicatorRowIndexForCandle,
  isWsCandleSameBarAsChartLast,
  isWsCandleStaleForChart,
  timeframeToIntervalMs,
  wsTickEpochMs,
} from '../utils/investmentChart/wsCandleHelpers';

/** WS가 REST보다 앞선 봉이 너무 많으면 평봉 대량 삽입 대신 API 증분에 맡김 */
const MAX_WS_GAP_FILL_BARS = 48;

/** 데이터 수신 순간: 테두리 바깥 10–15px 부드러운 네온 라임(#CCFF00) 할로 — 미수신 시 글로우 없음 */
const realtimeOutlineBlink = keyframes({
  '0%': {
    opacity: 0,
    boxShadow: 'none',
  },
  '42%': {
    opacity: 1,
    boxShadow:
      '0 0 8px 2px rgba(204, 255, 0, 0.38), 0 0 14px 6px rgba(204, 255, 0, 0.28), 0 0 22px 12px rgba(200, 255, 80, 0.14)',
  },
  '100%': {
    opacity: 0,
    boxShadow: 'none',
  },
});

/** 이미지2(수신): 차콜 안 + 선명한 라임 테두리 */
const realtimeCapsuleBorderLive = 'rgba(204, 255, 0, 0.82)';
const realtimeCapsuleBg = '#1e2128';
/** 이미지1(미수신): 라임 테두리만(채움 없음에 가깝게) */
const realtimeCapsuleBorderIdle = 'rgba(180, 230, 100, 0.55)';
const realtimeGreenSolid = '#3D6C33';
const realtimeBlueSolid = '#4589E1';

const API_BASE_URL = getApiBaseUrl();

// ✅ LocalStorage 키 정의 (분석 화면 상태 유지용)
const STORAGE_KEYS = {
  SYMBOL: 'candlestick_symbol',
  TIME_INTERVAL: 'candlestick_timeInterval',
  UPTREND_STRATEGY: 'candlestick_uptrendStrategyId',
  DOWNTREND_STRATEGY: 'candlestick_downtrendStrategyId',
  GENERAL_STRATEGY: 'candlestick_generalStrategyId',
  TREND_STRATEGY: 'candlestick_trendStrategy',
  CHART_VIEW_MODE: 'candlestick_chartViewMode',
  AUTO_REFRESH: 'candlestick_autoRefresh',
  BUY_AMOUNT: 'candlestick_buyAmount',
  SELL_AMOUNT: 'candlestick_sellAmount',
  TOTAL_BALANCE: 'candlestick_totalBalance',
};

// 캔들차트 드롭존 컴포넌트
const CandleChartDropZone: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const { setNodeRef, isOver } = useDroppable({
    id: 'candle-chart-drop-zone',
  });

  return (
    <Box
      ref={setNodeRef}
      sx={{
        position: 'relative',
        width: '100%',
        height: '100%',
        border: isOver ? '2px dashed #1976d2' : '2px solid transparent',
        borderRadius: 1,
        transition: 'border-color 0.2s',
        bgcolor: isOver ? 'rgba(25, 118, 210, 0.05)' : 'transparent',
        pointerEvents: 'auto',
        zIndex: 1,
      }}
    >
      {children}
    </Box>
  );
};

// Sortable 보조지표 차트 래퍼
interface SortableIndicatorWrapperProps {
  id: string;
  children: React.ReactNode;
  height: number;
  onResize: (newHeight: number) => void;
  onResizeStart?: () => void;
  onResizeEnd?: () => void;
  onTitleClick?: (e: React.MouseEvent) => void;
  onClose?: () => void;
  title: string;
}

const SortableIndicatorWrapper: React.FC<SortableIndicatorWrapperProps> = ({
  id,
  children,
  height,
  onResize,
  onResizeStart,
  onResizeEnd,
  onTitleClick,
  onClose,
  title
}) => {
  const theme = useTheme();  // ✅ theme hook 추가
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({ id });

  const [isResizing, setIsResizing] = React.useState(false);
  const [startY, setStartY] = React.useState(0);
  const [startHeight, setStartHeight] = React.useState(height);

  const style = {
    transform: CSS.Transform.toString(transform),
    transition: isResizing ? 'none' : transition,
    opacity: isDragging ? 0.5 : 1,
  };

  // 리사이즈 시작
  const handleResizeStart = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
    setIsResizing(true);
    setStartY(e.clientY);
    setStartHeight(height);
    if (onResizeStart) {
      onResizeStart();
    }
  };

  // 리사이즈 중
  React.useEffect(() => {
    if (!isResizing) return;

    const handleMouseMove = (e: MouseEvent) => {
      const deltaY = e.clientY - startY;
      const newHeight = Math.max(100, Math.min(400, startHeight + deltaY));
      if (onResize) {
        onResize(newHeight);
      }
    };

    const handleMouseUp = () => {
      setIsResizing(false);
      if (onResizeEnd) {
        onResizeEnd();
      }
    };

    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);

    return () => {
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
    };
  }, [isResizing, startY, startHeight, onResize]);

  return (
    <Box
      ref={setNodeRef}
      style={style}
      sx={{
        position: 'relative',
        mb: 1,
        borderRadius: 1,
        bgcolor: 'background.paper',
        border: '1px solid',
        borderColor: 'divider',
        overflow: 'hidden',
        boxShadow: isDragging ? 4 : 1,
        p: 0,  // ✅ 패딩 제거
      }}
    >
      {/* 드래그 핸들 - 오른쪽 상단 (업비트 스타일) */}
      <Box
        {...attributes}
        {...listeners}
        sx={{
          position: 'absolute',
          top: 4,
          right: 4,  // ✅ 오른쪽으로 이동
          zIndex: 20,
          cursor: 'grab',
          '&:active': { cursor: 'grabbing' },
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          opacity: 0.4,
          '&:hover': {
            opacity: 1,
          },
          transition: 'opacity 0.2s',
          p: 0.5,
          borderRadius: 0.5,
          bgcolor: 'action.hover',
        }}
      >
        <DragIndicator sx={{ fontSize: 18, color: '#8b95a5' }} />
      </Box>

      {/* 타이틀 - 클릭 시 설정 팝업 (좌측 정렬, 업비트 스타일) */}
      {title && onTitleClick && (
        <Box
          onClick={onTitleClick}
          sx={{
            position: 'absolute',
            top: 4,
            left: 4,  // ✅ 좌측으로 이동
            zIndex: 10,
            cursor: 'pointer',
            display: 'flex',  // ✅ flex로 변경
            alignItems: 'center',  // ✅ 세로 중앙 정렬
            gap: 1,  // ✅ 텍스트와 X 버튼 사이 간격
            pl: 1.5,
            pr: 0.5,  // ✅ 우측 패딩 줄임 (X 버튼 공간)
            py: 0.3,  // ✅ 높이 줄임 (0.5 → 0.3)
            borderRadius: 0.8,
            bgcolor: theme.palette.mode === 'dark'
              ? 'rgba(59, 130, 246, 0.85)'  // 다크모드: 밝은 파란색 (불투명도 높임)
              : 'rgba(37, 99, 235, 0.9)',    // 라이트모드: 진한 파란색 (불투명도 높임)
            '&:hover': {
              bgcolor: theme.palette.mode === 'dark'
                ? 'rgba(96, 165, 250, 0.95)'  // 다크모드 호버: 더 밝은 파란색
                : 'rgba(29, 78, 216, 1)',      // 라이트모드 호버: 더 진한 파란색
            },
            transition: 'background-color 0.2s',
            border: '1px solid',
            borderColor: theme.palette.mode === 'dark'
              ? 'rgba(147, 197, 253, 0.3)'
              : 'rgba(191, 219, 254, 0.5)',
            boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
          }}
        >
          <Typography
            variant="caption"
            sx={{
              fontSize: '11px',
              color: '#ffffff',  // 항상 흰색으로 명확하게
              fontWeight: 700,   // 더 굵게
              textShadow: '0 1px 2px rgba(0,0,0,0.3)',  // 텍스트 그림자로 가독성 향상
            }}
          >
            {title}
          </Typography>

          {/* x 버튼 - 타이틀 사각형 내부 우측 */}
          {onClose && (
            <IconButton
              size="small"
              onClick={(e) => {
                e.stopPropagation();
                onClose();
              }}
              sx={{
                bgcolor: 'transparent',  // ✅ 배경색 제거
                color: 'rgba(255, 255, 255, 0.8)',  // ✅ 흰색 (타이틀 박스 내부이므로)
                '&:hover': {
                  bgcolor: 'rgba(255, 255, 255, 0.15)',  // ✅ 호버 시 약간의 배경
                  color: 'rgba(255, 255, 255, 1)',
                },
                width: 20,
                height: 20,
                padding: 0,
                minWidth: 20,
              }}
            >
              <CloseIcon sx={{ fontSize: 14 }} />
            </IconButton>
          )}
        </Box>
      )}

      {children}

      {/* 리사이즈 핸들 - 하단 */}
      <Box
        onMouseDown={handleResizeStart}
        sx={{
          position: 'absolute',
          bottom: 0,
          left: 0,
          right: 0,
          height: 6,
          cursor: 'ns-resize',
          bgcolor: isResizing ? 'primary.main' : 'transparent',
          '&:hover': {
            bgcolor: 'primary.light',
          },
          transition: 'background-color 0.2s',
          zIndex: 15,
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        <Box sx={{
          width: 40,
          height: 3,
          bgcolor: isResizing ? 'primary.contrastText' : '#8b95a5',
          borderRadius: 1,
          opacity: 0.6,
        }} />
      </Box>
    </Box>
  );
};

// 메모이제이션된 시그널 테이블 컴포넌트
const MemoizedSignalTable = React.memo(SignalTable, (prevProps, nextProps) => {
  return (
    prevProps.data === nextProps.data &&
    prevProps.interval === nextProps.interval
  );
});

/**
 * CandlestickChart 통합 테스트 페이지
 * - lightweight-charts 기반 메인 차트
 * - lightweight-charts 기반 보조지표 차트 (IndicatorChart)
 * - 차트 분석 및 투자 전략 테스트
 */
const CandlestickTestPage: React.FC = () => {
  const location = useLocation();
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
  const isDark = theme.palette.mode === 'dark';

  // Context 통합
  const { settings: indicatorVisibilitySettings, order: indicatorVisibilityOrder, updateSettings: updateIndicatorSettings, loadUserSettings: loadIndicatorSettings, maLinesVersion } = useIndicatorVisibility();
  const { indicatorSettings: paramSettings, settingsVersion, getLatestSettings } = useIndicatorSettings();
  const { colors: colorSettings, colorVersion } = useChartColors();
  const { navigationState } = useNavigation();
  
  // ✅ indicatorVisibilitySettings를 ref에도 저장하여 항상 최신 값 참조
  const indicatorVisibilitySettingsRef = useRef(indicatorVisibilitySettings);
  indicatorVisibilitySettingsRef.current = indicatorVisibilitySettings;
  
  // ✅ 디버그: CandlestickTestPage 렌더링 확인
  console.log(`🎬 [CandlestickTestPage] 렌더링됨 - colorVersion: ${colorVersion}, settingsVersion: ${settingsVersion} [BUILD_V6]`);

  // ✅ 차트에 전달할 props 메모이제이션 (불필요한 재렌더링 방지)
  const memoizedIchimokuSettings = useMemo(() => ({
    tenkan: indicatorVisibilitySettings.ichimokuTenkan,
    kijun: indicatorVisibilitySettings.ichimokuKijun,
    chikou: indicatorVisibilitySettings.ichimokuChikou,
    senkouA: indicatorVisibilitySettings.ichimokuSenkouA,
    senkouB: indicatorVisibilitySettings.ichimokuSenkouB,
    tenkanPeriod: indicatorVisibilitySettings.ichimokuTenkanPeriod,
    kijunPeriod: indicatorVisibilitySettings.ichimokuKijunPeriod,
    senkouBPeriod: indicatorVisibilitySettings.ichimokuSenkouBPeriod,
  }), [
    indicatorVisibilitySettings.ichimokuTenkan,
    indicatorVisibilitySettings.ichimokuKijun,
    indicatorVisibilitySettings.ichimokuChikou,
    indicatorVisibilitySettings.ichimokuSenkouA,
    indicatorVisibilitySettings.ichimokuSenkouB,
    indicatorVisibilitySettings.ichimokuTenkanPeriod,
    indicatorVisibilitySettings.ichimokuKijunPeriod,
    indicatorVisibilitySettings.ichimokuSenkouBPeriod,
  ]);

  const memoizedIchimokuColors = useMemo(() => {
    const result = {
      tenkan: colorSettings.ichimokuTenkan,
      kijun: colorSettings.ichimokuKijun,
      chikou: colorSettings.ichimokuChikou,
      senkouA: colorSettings.ichimokuSenkouA,
      senkouB: colorSettings.ichimokuSenkouB,
      cloudBullish: colorSettings.ichimokuCloudBullish,
      cloudBearish: colorSettings.ichimokuCloudBearish,
      tenkanWidth: colorSettings.ichimokuTenkanWidth,
      kijunWidth: colorSettings.ichimokuKijunWidth,
      chikouWidth: colorSettings.ichimokuChikouWidth,
      senkouAWidth: colorSettings.ichimokuSenkouAWidth,
      senkouBWidth: colorSettings.ichimokuSenkouBWidth,
    };
    console.log('🎨 [memoizedIchimokuColors 재계산]', result);
    return result;
  }, [
    colorSettings.ichimokuTenkan,
    colorSettings.ichimokuKijun,
    colorSettings.ichimokuChikou,
    colorSettings.ichimokuSenkouA,
    colorSettings.ichimokuSenkouB,
    colorSettings.ichimokuCloudBullish,
    colorSettings.ichimokuCloudBearish,
    colorSettings.ichimokuTenkanWidth,
    colorSettings.ichimokuKijunWidth,
    colorSettings.ichimokuChikouWidth,
    colorSettings.ichimokuSenkouAWidth,
    colorSettings.ichimokuSenkouBWidth,
  ]);

  // ✅ maLines 메모이제이션 (JSON.stringify로 깊은 비교)
  const memoizedMaLines = useMemo(() => {
    return indicatorVisibilitySettings.maLines;
  }, [JSON.stringify(indicatorVisibilitySettings.maLines)]);

  // ✅ 차트 표시 기준 상수 (업비트 API 정책 기반)
  const CHART_DISPLAY_COUNT = 200;    // 화면에 표시할 캔들 개수 (UI 성능)
  const MA_CALCULATION_COUNT = 500;   // MA/Ichimoku 계산용 캔들 개수 (MA480 지원)
  const INDICATOR_WARMUP_COUNT = 200; // 보조지표 워밍업 캔들 개수 (MA200, EMA200 등 모든 지표 대응)

  // ✅ 시간봉별 표시 기간 계산 헬퍼 함수
  const getDisplayPeriodDescription = useCallback((interval: string, count: number): string => {
    const intervalMinutes: { [key: string]: number } = {
      '1m': 1,
      '3m': 3,
      '5m': 5,
      '15m': 15,
      '30m': 30,
      '1h': 60,
      '4h': 240,
      '1d': 1440,
      '1w': 10080,
      '1M': 43200, // 대략 30일
    };

    const minutes = intervalMinutes[interval] || 60;
    const totalMinutes = minutes * count;

    if (totalMinutes < 60) {
      return `${totalMinutes}분`;
    } else if (totalMinutes < 1440) {
      const hours = (totalMinutes / 60).toFixed(1);
      return `${hours}시간`;
    } else if (totalMinutes < 10080) {
      const days = (totalMinutes / 1440).toFixed(1);
      return `${days}일`;
    } else if (totalMinutes < 43200) {
      const weeks = (totalMinutes / 10080).toFixed(1);
      return `${weeks}주`;
    } else {
      const months = (totalMinutes / 43200).toFixed(1);
      return `${months}개월`;
    }
  }, []);

  // ✅ 시간봉에 따라 다음 캔들 시간 계산 (히스토리 스택용)
  const getNextCandleTime = useCallback((currentTime: string, interval: string): string => {
    // currentTime은 이미 ISO 형식 문자열 (예: "2025-12-27T13:00:00")
    // Date 객체를 거치지 않고 직접 문자열 파싱으로 처리 (timezone 문제 방지)
    const match = interval.match(/^(\d+)/);
    const value = match ? parseInt(match[1]) : 1;

    // ISO 문자열을 직접 파싱
    const [datePart, timePart] = currentTime.split('T');
    const [year, month, day] = datePart.split('-').map(Number);
    const [hour, minute, second] = (timePart || '00:00:00').split(':').map(Number);

    // Date 객체 생성 (UTC 기준)
    const date = new Date(Date.UTC(year, month - 1, day, hour, minute, second || 0));

    if (interval.includes('m') || interval.match(/^\d+$/)) {
      // 분봉: 1m, 3m, 5m, 15m, 30m
      date.setUTCMinutes(date.getUTCMinutes() + value);
    } else if (interval.includes('h')) {
      // 시간봉: 1h, 4h
      date.setUTCHours(date.getUTCHours() + value);
    } else if (interval.includes('d')) {
      // 일봉: 1d
      date.setUTCDate(date.getUTCDate() + value);
    } else if (interval.includes('w')) {
      // 주봉: 1w
      date.setUTCDate(date.getUTCDate() + (value * 7));
    } else if (interval.includes('M')) {
      // 월봉: 1M
      date.setUTCMonth(date.getUTCMonth() + value);
    }

    // ISO_LOCAL_DATE_TIME 형식으로 반환 (Z와 밀리초 제거, 백엔드 요구사항)
    return date.toISOString().split('.')[0];
  }, []);

  // ✅ 기본 상태 (localStorage에서 복원)
  const [symbol, setSymbol] = useState(() => {
    return localStorage.getItem(STORAGE_KEYS.SYMBOL) || 'KRW-BTC';
  });
  const [timeInterval, setTimeInterval] = useState(() => {
    return localStorage.getItem(STORAGE_KEYS.TIME_INTERVAL) || '5m';
  });

  // ✅ 우측 실시간 호가(종목 목록) 패널
  const {
    tickers: marketTickers,
    wsStatus: marketWsStatus,
  } = useUpbitTicker();
  
  // ✅ 알림에서 전달받은 종목 정보 처리
  useEffect(() => {
    const state = location.state as { symbol?: string; koreanName?: string; englishName?: string } | null;
    if (state?.symbol) {
      console.log('🔔 [알림에서 이동] 종목 선택:', state);
      setSymbol(state.symbol);
      // location.state 초기화 (뒤로가기 시 재적용 방지)
      window.history.replaceState({}, document.title);
    }
  }, [location]);

  // ✅ NavigationContext에서 전달받은 종목 정보 처리
  useEffect(() => {
    if (navigationState?.symbol) {
      console.log('🔔 [알림에서 이동 - NavigationContext] 종목 선택:', navigationState);
      setSymbol(navigationState.symbol);
      if (navigationState.timeInterval) {
        setTimeInterval(navigationState.timeInterval);
      }
    }
  }, [navigationState]);
  const [candleData, setCandleData] = useState<CandleData[]>([]);
  const [indicatorData, setIndicatorData] = useState<IndicatorData[]>([]);
  const [maCalculationData, setMaCalculationData] = useState<CandleData[]>([]); // ✅ MA/Ichimoku 계산용 추가 데이터 (원본 캔들)
  const [allIndicatorData, setAllIndicatorData] = useState<IndicatorData[]>([]); // ✅ 전체 400개 지표 데이터 (차트 MA 렌더링용)
  
  // ✅ 실시간 가격 (WebSocket으로부터 업데이트)
  const [realtimePrice, setRealtimePrice] = useState<number | null>(null);
  
  // ✅ 종목/타임프레임 변경 시 실시간 가격 초기화
  useEffect(() => {
    setRealtimePrice(null);
  }, [symbol, timeInterval]);
  
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [forceRenderKey, setForceRenderKey] = useState(0); // ✅ 강제 리렌더링용 더미 상태
  const [isFullscreen, setIsFullscreen] = useState(false);
  const [hasInitialData, setHasInitialData] = useState(false);


  // ✅ 날짜 범위 제어용 상태 (특정 시간 이전/이후의 데이터를 로드하기 위함)
  const [loadToTime, setLoadToTime] = useState<string | null>(null);
  const [isLoadingHistory, setIsLoadingHistory] = useState(false); // 과거 데이터 로드 중인지 여부
  const [timeHistory, setTimeHistory] = useState<string[]>([]); // 기간 이동 히스토리 스택
  const timeHistoryRef = useRef<string[]>([]); // ✅ timeHistory ref (클로저 문제 방지)
  const isViewingHistoricalDataRef = useRef<boolean>(false); // 이전 기간 보기 중인지 (ref로 즉시 반영)
  const [shouldSetFullRange, setShouldSetFullRange] = useState(false); // 기간 이동 후 전체 범위 설정 플래그
  const savedViewRangeRef = useRef<{ start: number; end: number; size?: number } | null>(null); // 로딩 전 차트 범위 저장
  const isIncrementalUpdateInProgressRef = useRef<boolean>(false); // 증분 업데이트 진행 중인지 (차트 리셋 방지)
  const loadDataInProgressRef = useRef<boolean>(false); // loadData 중복 실행 방지
  const pendingLoadDataRef = useRef<{ forceReset: boolean; toTime: string | null } | null>(null); // 대기 중인 loadData 요청

  // ✅ 우측 호가 패널
  const [orderbookPanelOpen, setOrderbookPanelOpen] = useState(false);

  // ✅ 우측 지표 게이지 패널 (투자분석3과 동일 UI — indicatorData는 기존 WS/REST 갱신 로직 그대로 사용)
  const [gaugePanelOpen, setGaugePanelOpen] = useState(false);

  /** WS가 차트보다 앞선 봉일 때 loadData 증분 호출을 한 번으로 묶음 */
  const wsCatchupReloadTimerRef = useRef<number | null>(null);

  // ✅ 증분 업데이트용: 최신 데이터 참조 (클로저 문제 방지)
  const candleDataRef = useRef<CandleData[]>([]);
  const indicatorDataRef = useRef<IndicatorData[]>([]);
  const maCalculationDataRef = useRef<CandleData[]>([]);
  const allIndicatorDataRef = useRef<IndicatorData[]>([]); // ✅ 전체 지표 데이터 ref
  
  // ✅ loadData 함수 ref (클로저 문제 방지 - 항상 최신 함수 참조)
  const loadDataRef = useRef<((forceReset?: boolean, toTime?: string | null, isPrepend?: boolean, isAppend?: boolean, isIncrementalUpdate?: boolean) => Promise<void>) | null>(null);

  useEffect(() => {
    return () => {
      if (wsCatchupReloadTimerRef.current != null) {
        window.clearTimeout(wsCatchupReloadTimerRef.current);
        wsCatchupReloadTimerRef.current = null;
      }
    };
  }, []);

  // ✅ 프리페치(Prefetch) 데이터 상태
  const [prefetchedPastData, setPrefetchedPastData] = useState<{
    candles: CandleData[];
    indicators: IndicatorData[];
  } | null>(null);
  const [prefetchedFutureData, setPrefetchedFutureData] = useState<{
    candles: CandleData[];
    indicators: IndicatorData[];
  } | null>(null);
  const [isPrefetchingPast, setIsPrefetchingPast] = useState(false);
  const [isPrefetchingFuture, setIsPrefetchingFuture] = useState(false);

  // 차트 ref
  const chartRef = useRef<ChartHandle>(null);
  const chartContainerRef = useRef<HTMLDivElement>(null); // ✅ 차트 컨테이너 ref 추가
  const volumeChartRef = useRef<IndicatorChartHandle>(null);
  const newPsychologicalChartRef = useRef<IndicatorChartHandle>(null);
  const investPsychologicalChartRef = useRef<IndicatorChartHandle>(null);
  const macdChartRef = useRef<IndicatorChartHandle>(null);
  const cciChartRef = useRef<IndicatorChartHandle>(null);
  const adxChartRef = useRef<IndicatorChartHandle>(null);
  const bwiChartRef = useRef<IndicatorChartHandle>(null);
  const dmiChartRef = useRef<IndicatorChartHandle>(null);
  const obvChartRef = useRef<IndicatorChartHandle>(null);
  const rsiChartRef = useRef<IndicatorChartHandle>(null);
  const stochasticChartRef = useRef<IndicatorChartHandle>(null);
  const williamsRChartRef = useRef<IndicatorChartHandle>(null);
  const trixChartRef = useRef<IndicatorChartHandle>(null);
  const volumeOscChartRef = useRef<IndicatorChartHandle>(null);
  const vrChartRef = useRef<IndicatorChartHandle>(null);
  const disparityChartRef = useRef<IndicatorChartHandle>(null);

  // 크로스헤어 동기화 상태 (캔들차트 ↔ 보조지표)
  const [sharedHoverTime, setSharedHoverTime] = useState<string | null>(null);

  // X축 범위 동기화 상태 (캔들차트 → 보조지표)
  // 기본값은 전체 데이터 범위
  const [sharedViewRange, setSharedViewRange] = useState<{ start: number; end: number }>({ start: 0, end: 500 });
  const prevChartDataLengthRef = useRef<number>(0);

  // ✅ 초기 visible range (보조지표 차트 생성 시 사용)
  const [initialVisibleRange, setInitialVisibleRange] = useState<{ from: number; to: number } | null>(null);

  // ✅ 전체화면 전환 시 뷰 범위 저장용 ref (404줄에 이미 선언됨 - 로딩 전 차트 범위 저장용으로도 사용)
  const isRestoringViewRangeRef = useRef<boolean>(false);

  // ✅ Resize 중 플래그 (window resize 시 onVisibleRangeChange 무시용)
  const isResizingRef = useRef<boolean>(false);

  // (백데이터 관련 ref 제거됨 - 200개 단위 교체 방식 사용)

  // ✅ 초기 로드 완료 플래그 (정말로 처음 한 번만 차트 동기화)
  const isInitialLoadDoneRef = useRef<boolean>(false);

  // 차트 툴바 상태
  const [activeTool, setActiveTool] = useState<ChartTool | null>(null);
  const [chartMode, setChartMode] = useState<'basic' | 'grid' | 'clean'>('basic');

  // 차트 설정 다이얼로그
  const [settingsDialogOpen, setSettingsDialogOpen] = useState(false);
  // 설정 내보내기/가져오기 메뉴
  const [settingsMenuAnchor, setSettingsMenuAnchor] = useState<null | HTMLElement>(null);
  const importConfigInputRef = useRef<HTMLInputElement | null>(null);
  const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity: 'success' | 'error' | 'info' }>({ open: false, message: '', severity: 'success' });

  // ✅ 매수/매도 금액 설정 상태 (localStorage에서 복원)
  const [tradeMode, setTradeMode] = useState<'buy' | 'sell'>('buy'); // 매수/매도 토글
  const [amountType, setAmountType] = useState<'amount' | 'available'>('amount');
  const [buyAmount, setBuyAmount] = useState<number>(() => {
    const saved = localStorage.getItem(STORAGE_KEYS.BUY_AMOUNT);
    return saved ? Number(saved) : 10000000;
  });
  const [sellAmount, setSellAmount] = useState<number>(() => {
    const saved = localStorage.getItem(STORAGE_KEYS.SELL_AMOUNT);
    return saved ? Number(saved) : 10000000;
  });
  const [totalBalance, setTotalBalance] = useState<number>(() => {
    const saved = localStorage.getItem(STORAGE_KEYS.TOTAL_BALANCE);
    return saved ? Number(saved) : 100000000;
  });
  const [tempTotalBalance, setTempTotalBalance] = useState<string>(() => {
    const saved = localStorage.getItem(STORAGE_KEYS.TOTAL_BALANCE);
    return saved || '100000000';
  });

  // ✅ 전체화면 모드 상태
  const [isChartFullscreen, setIsChartFullscreen] = useState<boolean>(false);
  /** 실시간 차트 값 Popover 앵커 (null이면 닫힘) */
  const [chartValuesAnchorEl, setChartValuesAnchorEl] = useState<HTMLElement | null>(null);

  // ✅ 캔들차트 높이 상태
  const [candleChartHeight, setCandleChartHeight] = useState(400);

  // ✅ 보조지표 패널 높이 상태
  const [indicatorPanelHeight, setIndicatorPanelHeight] = useState(120);

  // ✅ 전체화면 시 차트 영역 높이 (캔들+보조지표 꽉 채우기용)
  const [fullscreenChartAreaHeight, setFullscreenChartAreaHeight] = useState(0);
  // ✅ 전체화면 시 캔들/보조지표 비율 (0.3~0.9)
  const [fullscreenCandleRatio, setFullscreenCandleRatio] = useState(0.6);
  /** 캔들차트 하단 정배열/역배열 추세바 영역 — 0이면 미표시 (`hideTrendAlignmentBar`와 동일) */
  const TREND_BAR_HEIGHT = 0;

  // ✅ 전체화면 시 캔들/보조지표 높이 (영역 꽉 채우기)
  const fullscreenHeights = useMemo(() => {
    if (!isChartFullscreen || fullscreenChartAreaHeight <= 0) return null;
    const enabledCount = [
      indicatorVisibilitySettings.volume,
      indicatorVisibilitySettings.macdLine || indicatorVisibilitySettings.macdHistogram,
      indicatorVisibilitySettings.rsi,
      indicatorVisibilitySettings.stochasticK || indicatorVisibilitySettings.stochasticD || indicatorVisibilitySettings.stochasticSlow,
      indicatorVisibilitySettings.cci,
      indicatorVisibilitySettings.adx,
      indicatorVisibilitySettings.obv,
      indicatorVisibilitySettings.newPsychological || indicatorVisibilitySettings.investPsychological,
      indicatorVisibilitySettings.bwi,
      indicatorVisibilitySettings.plusDi || indicatorVisibilitySettings.minusDi,
      indicatorVisibilitySettings.williamsR,
      indicatorVisibilitySettings.trix,
      indicatorVisibilitySettings.volumeOsc,
      indicatorVisibilitySettings.vr,
      indicatorVisibilitySettings.disparity,
    ].filter(Boolean).length;
    const RESIZE_HANDLE = 8;
    const available = fullscreenChartAreaHeight - TREND_BAR_HEIGHT - RESIZE_HANDLE;
    if (available <= 0) return null;
    if (enabledCount === 0) return { candle: Math.max(200, available), indicator: 120 };
    const candle = Math.max(200, Math.min(available - 60, available * fullscreenCandleRatio));
    const indicator = (available - candle) / enabledCount; // 꽉 채우기
    return { candle, indicator: Math.max(60, indicator) };
  }, [isChartFullscreen, fullscreenChartAreaHeight, fullscreenCandleRatio, indicatorVisibilitySettings]);

  // ✅ 보조지표 리사이즈 상태
  const [isIndicatorResizing, setIsIndicatorResizing] = useState(false);

  // ✅ 서브패널 순서 (기본 순서)
  const [subPanelsOrder, setSubPanelsOrder] = useState<string[]>([
    'volume', 'macd', 'rsi', 'stochastic', 'cci', 'adx', 'obv', 'newPsychological', 'investPsychological', 'bwi', 'dmi', 'williamsR', 'trix', 'volumeOsc', 'vr', 'disparity'
  ]);

  // ✅ 캔들차트 리사이즈 상태
  const [isCandleChartResizing, setIsCandleChartResizing] = useState(false);

  // ✅ 차트 뷰 모드 ('basic' | 'tradingview') - localStorage에서 복원
  const [chartViewMode, setChartViewMode] = useState<'basic' | 'tradingview'>(() => {
    const saved = localStorage.getItem(STORAGE_KEYS.CHART_VIEW_MODE);
    return (saved as 'basic' | 'tradingview') || 'basic';
  });

  // ✅ 툴팁 On/Off 상태
  const [tooltipEnabled, setTooltipEnabled] = useState<boolean>(false);

  // ✅ 봉 완성 시간 표시 On/Off 상태
  const [candleTimeRemainingEnabled, setCandleTimeRemainingEnabled] = useState<boolean>(() => {
    const saved = localStorage.getItem('candleTimeRemainingEnabled');
    return saved !== null ? saved === 'true' : true; // 기본값 true
  });

  // ✅ 실시간 차트 갱신 설정
  const [realtimeUpdateEnabled, setRealtimeUpdateEnabled] = useState<boolean>(() => {
    const saved = localStorage.getItem('realtimeUpdateEnabled');
    return saved !== null ? saved === 'true' : true; // 기본값 true
  });
  const [realtimeUpdateInterval, setRealtimeUpdateInterval] = useState<number>(() => {
    const saved = localStorage.getItem('realtimeUpdateInterval');
    return saved ? parseInt(saved) : 5000; // 기본값 5초
  });
  const [realtimeGlowTick, setRealtimeGlowTick] = useState(0);
  /** true: 이미지2(수신·솔리드+글로우) / false: 이미지1(미수신·아웃라인만) */
  const [realtimeDataReceivedLook, setRealtimeDataReceivedLook] = useState(false);
  /** 브라우저 setTimeout은 number 반환(NodeJS.Timeout과 충돌 방지) */
  const realtimeReceiveLookTimeoutRef = useRef<number | null>(null);
  const triggerRealtimeGlow = useCallback(() => {
    setRealtimeDataReceivedLook(true);
    setRealtimeGlowTick((n) => n + 1);
    if (realtimeReceiveLookTimeoutRef.current != null) {
      window.clearTimeout(realtimeReceiveLookTimeoutRef.current);
    }
    realtimeReceiveLookTimeoutRef.current = window.setTimeout(() => {
      setRealtimeDataReceivedLook(false);
      realtimeReceiveLookTimeoutRef.current = null;
    }, 580);
  }, []);

  // ✅ 시간봉을 WebSocket 타임프레임으로 변환
  const convertToWebSocketTimeframe = useCallback((interval: string): string => {
    // 이미 '1m', '3m', '1h', '1M' 등 형식이면 그대로 반환 (1M=월봉, 1m=분봉 구분)
    const wsFormats = ['1m', '3m', '5m', '10m', '15m', '30m', '1h', '4h', '1d', '1w', '1M'];
    if (wsFormats.includes(interval)) {
      return interval;
    }
    // '1', '3', '5', '15', '30', '60', '240', 'D', 'W', 'M' -> '1m', '3m', '5m', ...
    const mapping: { [key: string]: string } = {
      '1': '1m',
      '3': '3m',
      '5': '5m',
      '10': '10m',
      '15': '15m',
      '30': '30m',
      '60': '1h',
      '240': '4h',
      'D': '1d',
      'W': '1w',
      'M': '1M',
    };
    return mapping[interval] || '1m';
  }, []);

  // ✅ WebSocket Hook 사용 (실시간 데이터 수신)
  const wsTimeframe = convertToWebSocketTimeframe(timeInterval);
  
  // onError 콜백 안정화 (useCallback)
  const handleWsError = useCallback((error: any) => {
    console.error('[WebSocket] 에러:', error);
  }, []);
  
  const applyWsCandleToChart = useCallback((latestWsCandle: { open: number; high: number; low: number; close: number; volume?: number; timestamp?: number | string; time?: string | number }) => {
    if (!chartRef.current || candleDataRef.current.length === 0) return;
    // ✅ WebSocket 데이터 그대로 전달 (updateLastCandle 내부에서 timestamp/time 처리)
    chartRef.current.updateLastCandle(latestWsCandle);
    const lastIdx = candleDataRef.current.length - 1;
    if (lastIdx >= 0 && candleDataRef.current[lastIdx]) {
      candleDataRef.current[lastIdx] = {
        ...candleDataRef.current[lastIdx],
        open: latestWsCandle.open,
        high: latestWsCandle.high,
        low: latestWsCandle.low,
        close: latestWsCandle.close,
        volume: latestWsCandle.volume ?? 0,
      };
    }
  }, []);

  const { 
    status: wsStatus, 
    candles: wsCandles, 
    indicators: wsIndicators 
  } = useMarketDataWebSocket({
    market: symbol,
    timeframe: wsTimeframe,
    enabled: realtimeUpdateEnabled,
    onData: (data) => {
      // ✅ WebSocket 실시간 데이터 수신 시 차트·지표 갱신
      // Hook은 전체 캔들 배열(data.candles)을 전달 → 마지막 봉만 처리
      if (data.isUpdate && data.candles?.length > 0 && !isViewingHistoricalDataRef.current && candleDataRef.current.length > 0) {
        const latestWsCandle = data.candles[data.candles.length - 1];
        const lastChartCandle = candleDataRef.current[candleDataRef.current.length - 1];
        
        // ✅ WebSocket 캔들 시각 → Unix 초 (timestamp 우선, 없으면 time)
        const rawWsTs = latestWsCandle.timestamp ?? latestWsCandle.time;
        const wsTimeInSeconds =
          typeof rawWsTs === 'number'
            ? rawWsTs > 1e12
              ? Math.floor(rawWsTs / 1000)
              : Math.floor(rawWsTs)
            : Math.floor(new Date(rawWsTs as string).getTime() / 1000);
        if (!Number.isFinite(wsTimeInSeconds)) {
          return;
        }
        
        // ✅ 차트 마지막 캔들의 timestamp를 초 단위로 변환
        const chartLastOpenSec = candleTimeToUnixSeconds(lastChartCandle?.time);

        // ✅ 과거 봉 틱은 무시 (버킷 기준으로도 이전 봉이면 스킵)
        if (!Number.isFinite(chartLastOpenSec) || wsTimeInSeconds < chartLastOpenSec) {
          return;
        }
        if (isWsCandleStaleForChart(wsTimeInSeconds, candleDataRef.current, wsTimeframe)) {
          return;
        }

        // ✅ 같은 진행 봉: 마지막 캔들 OHLC·지표 갱신
        if (isWsCandleSameBarAsChartLast(wsTimeInSeconds, candleDataRef.current, wsTimeframe)) {
          setRealtimePrice(latestWsCandle.close);
          
          if (chartRef.current) {
            chartRef.current.updateLastCandle(latestWsCandle);
          }
          
          const lastIdx = candleDataRef.current.length - 1;
          if (lastIdx >= 0 && candleDataRef.current[lastIdx]) {
            candleDataRef.current[lastIdx] = {
              ...candleDataRef.current[lastIdx],
              open: latestWsCandle.open,
              high: latestWsCandle.high,
              low: latestWsCandle.low,
              close: latestWsCandle.close,
              volume: latestWsCandle.volume,
            };
          }
          // ✅ React state 동기화: chartData(data prop)와 ref만 갱신하면 일부 UI·크로스헤어는 최신가를 쓰고 캔들 시리즈만 구버전처럼 보일 수 있음
          setCandleData((prev) => {
            if (prev.length === 0 || lastIdx < 0) return prev;
            const last = prev[lastIdx];
            if (!last) return prev;
            const next = [...prev];
            next[lastIdx] = {
              ...last,
              open: latestWsCandle.open,
              high: latestWsCandle.high,
              low: latestWsCandle.low,
              close: latestWsCandle.close,
              volume: (latestWsCandle.volume as number | undefined) ?? last.volume,
            };
            return next;
          });
          
          const lastCandle = candleDataRef.current[lastIdx];
          const candleTime = lastCandle?.time;
          
          if (candleTime && data.indicators && Object.keys(data.indicators).length > 0) {
            const mappedIndicators = mapWsIndicatorsToChartFormat(data.indicators as Record<string, unknown>);
            console.log(`📊 [WS onData] 보조지표 수신:`, Object.keys(mappedIndicators));

            setIndicatorData((prev) => {
              const updated = [...prev];
              const chartLen = candleDataRef.current.length;
              const aligned =
                updated.length === chartLen && lastIdx >= 0 && lastIdx < updated.length;
              const idx = aligned
                ? lastIdx
                : findIndicatorRowIndexForCandle(updated, lastIdx, candleTime);
              if (idx >= 0 && updated[idx]) {
                updated[idx] = {
                  ...updated[idx],
                  ...mappedIndicators,
                  time: candleTime,
                  timestamp: candleTime,
                  date: candleTime,
                };
                indicatorDataRef.current = updated;
                // ✅ React 배치와 무관하게 동일 틱에서 서브차트 갱신 (캔들·지표 길이 불일치 대응)
                if (chartRef.current && lastIdx >= 0 && candleDataRef.current[lastIdx]) {
                  chartRef.current.updateLastIndicators(
                    candleDataRef.current[lastIdx],
                    candleDataRef.current,
                    updated
                  );
                }
              }
              return updated;
            });
          } else {
            // ⚠️ 폴백: 프론트엔드에서 간단한 지표들을 즉시 계산
            if (candleTime && candleDataRef.current.length >= 14) {
              const calculatedIndicators: any = {};
              const candles = candleDataRef.current;
              const lastIdx = candles.length - 1;
              
              // RSI 계산 (14일)
              if (lastIdx >= 14) {
                const period = 14;
                let gains = 0, losses = 0;
                for (let i = lastIdx - period + 1; i <= lastIdx; i++) {
                  if (i > 0) {
                    const change = candles[i].close - candles[i - 1].close;
                    if (change > 0) gains += change;
                    else losses += Math.abs(change);
                  }
                }
                const avgGain = gains / period;
                const avgLoss = losses / period;
                const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
                calculatedIndicators.rsi = 100 - (100 / (1 + rs));
              }
              
              // MACD 계산 (간이 버전 - SMA 기반)
              if (lastIdx >= 26) {
                const fast = 12, slow = 26;
                const calcSMA = (period: number) => {
                  const slice = candles.slice(lastIdx - period + 1, lastIdx + 1);
                  return slice.reduce((sum, c) => sum + c.close, 0) / period;
                };
                const fastSMA = calcSMA(fast);
                const slowSMA = calcSMA(slow);
                calculatedIndicators.macdLine = fastSMA - slowSMA;
                calculatedIndicators.macdHistogram = calculatedIndicators.macdLine;
              }
              
              // Stochastic 계산 (14일)
              if (lastIdx >= 14) {
                const period = 14;
                let highestHigh = -Infinity, lowestLow = Infinity;
                for (let i = lastIdx - period + 1; i <= lastIdx; i++) {
                  if (candles[i].high > highestHigh) highestHigh = candles[i].high;
                  if (candles[i].low < lowestLow) lowestLow = candles[i].low;
                }
                const currentClose = candles[lastIdx].close;
                if (highestHigh !== lowestLow) {
                  const k = ((currentClose - lowestLow) / (highestHigh - lowestLow)) * 100;
                  calculatedIndicators.stochasticK = k;
                  calculatedIndicators.stochK = k;
                }
              }
              
              setIndicatorData((prev) => {
                const updated = [...prev];
                const candleLastIdx = candleDataRef.current.length - 1;
                const chartLen = candleDataRef.current.length;
                const aligned =
                  updated.length === chartLen && candleLastIdx >= 0 && candleLastIdx < updated.length;
                const idx = aligned
                  ? candleLastIdx
                  : findIndicatorRowIndexForCandle(updated, candleLastIdx, candleTime);
                if (idx >= 0 && updated[idx]) {
                  updated[idx] = {
                    ...updated[idx],
                    ...calculatedIndicators,
                    time: candleTime,
                    timestamp: candleTime,
                    date: candleTime,
                  };
                  indicatorDataRef.current = updated;
                  if (chartRef.current && lastIdx >= 0 && candleDataRef.current[lastIdx]) {
                    chartRef.current.updateLastIndicators(
                      candleDataRef.current[lastIdx],
                      candleDataRef.current,
                      updated
                    );
                  }
                }
                return updated;
              });
            }
          }

          // ✅ WS에 indicators가 없거나 폴백 계산을 못 했을 때도 거래량 등은 마지막 캔들 기준으로 서브차트 동기화
          if (
            chartRef.current &&
            lastIdx >= 0 &&
            candleDataRef.current[lastIdx] &&
            indicatorDataRef.current.length > 0 &&
            (!data.indicators || Object.keys(data.indicators).length === 0) &&
            !(candleTime && candleDataRef.current.length >= 14)
          ) {
            chartRef.current.updateLastIndicators(
              candleDataRef.current[lastIdx],
              candleDataRef.current,
              indicatorDataRef.current
            );
          }

          triggerRealtimeGlow();
        } else {
          // ✅ REST는 마감 봉까지만 있고 WS는 이미 다음(들) 봉 버킷 → API 증분은 newCandles 0으로 루프만 돌 수 있음.
          //    WS 훅이 가진 candles 꼬리를 차트에 직접 붙여 버킷을 맞춘다.
          setRealtimePrice(latestWsCandle.close);
          if (wsCatchupReloadTimerRef.current != null) {
            window.clearTimeout(wsCatchupReloadTimerRef.current);
            wsCatchupReloadTimerRef.current = null;
          }

          const intervalMs = timeframeToIntervalMs(wsTimeframe);
          const lastOpenMs = chartCandleOpenToEpochMs(lastChartCandle?.time);
          const wsMs = wsTickEpochMs(latestWsCandle);
          const toAdd: CandleData[] = [];

          // REST 마지막 봉 시각은 (거래소 기준) interval 경계에 안 맞을 수 있음.
          // b*intervalMs 그리드로 끼워 넣으면 첫 합성 봉이 "실제 다음 봉"과 어긋나 LW가 비정상 캔들처럼 그릴 수 있음 → lastOpen + k*interval만 사용.
          if (Number.isFinite(lastOpenMs) && Number.isFinite(wsMs) && intervalMs > 0) {
            const wsBucketOpenMs = Math.floor(wsMs / intervalMs) * intervalMs;
            const deltaMs = wsBucketOpenMs - lastOpenMs;
            const rawSteps = deltaMs / intervalMs;
            const nSteps = Math.round(rawSteps);
            const aligned =
              deltaMs > 0 &&
              nSteps >= 1 &&
              nSteps <= MAX_WS_GAP_FILL_BARS &&
              Math.abs(rawSteps - nSteps) < 1e-3 &&
              Math.abs(lastOpenMs + nSteps * intervalMs - wsBucketOpenMs) < 1;

            if (aligned) {
              const lastCloseRaw = lastChartCandle?.close;
              const fillPrice =
                typeof lastCloseRaw === 'number' && Number.isFinite(lastCloseRaw)
                  ? lastCloseRaw
                  : latestWsCandle.close;
              const lastTimeRef = lastChartCandle?.time;
              for (let k = 1; k <= nSteps; k++) {
                const openMs = lastOpenMs + k * intervalMs;
                const isForming = k === nSteps;
                const timeField = candleOpenTimeForChartRow(openMs, lastTimeRef);
                toAdd.push({
                  time: timeField as CandleData['time'],
                  open: isForming ? latestWsCandle.open : fillPrice,
                  high: isForming ? latestWsCandle.high : fillPrice,
                  low: isForming ? latestWsCandle.low : fillPrice,
                  close: isForming ? latestWsCandle.close : fillPrice,
                  volume: isForming ? ((latestWsCandle.volume as number | undefined) ?? 0) : 0,
                  isBuyPoint: false,
                  isSellPoint: false,
                });
              }
            }
          }

          if (toAdd.length > 0) {
            if (process.env.NODE_ENV === 'development') {
              console.log(
                `🆕 [WS onData] REST보다 앞선 봉 ${toAdd.length}개 — WS 꼬리를 차트에 병합 (증분 API 스킵)`
              );
            }
            setCandleData((prev) => {
              if (prev.length === 0) return prev;
              const merged = [...prev];
              for (const row of toAdd) {
                const m = chartCandleOpenToEpochMs(row.time);
                const tailM = chartCandleOpenToEpochMs(merged[merged.length - 1]?.time);
                if (Number.isFinite(m) && Number.isFinite(tailM) && m > tailM) merged.push(row);
              }
              candleDataRef.current = merged;
              return merged;
            });
            setMaCalculationData((prev) => {
              if (prev.length === 0) return prev;
              const merged = [...prev];
              for (const row of toAdd) {
                const m = chartCandleOpenToEpochMs(row.time);
                const tailM = chartCandleOpenToEpochMs(merged[merged.length - 1]?.time);
                if (Number.isFinite(m) && Number.isFinite(tailM) && m > tailM) merged.push(row);
              }
              maCalculationDataRef.current = merged;
              return merged;
            });
            setIndicatorData((prev) => {
              if (prev.length === 0) return prev;
              const template = prev[prev.length - 1];
              const merged = [...prev];
              const lastOpenMsForInd =
                toAdd.length > 0 ? chartCandleOpenToEpochMs(toAdd[toAdd.length - 1]!.time) : NaN;
              for (const row of toAdd) {
                const m = chartCandleOpenToEpochMs(row.time);
                const tailM = chartCandleOpenToEpochMs(merged[merged.length - 1]?.time as any);
                if (!Number.isFinite(m) || m <= tailM) continue;
                const useWsInd =
                  Number.isFinite(lastOpenMsForInd) &&
                  m === lastOpenMsForInd &&
                  data.indicators &&
                  Object.keys(data.indicators).length > 0;
                const mapped = useWsInd
                  ? mapWsIndicatorsToChartFormat(data.indicators as Record<string, unknown>)
                  : {};
                merged.push({
                  ...(template || {}),
                  ...mapped,
                  time: row.time,
                  timestamp: row.time,
                  date: row.time,
                } as IndicatorData);
              }
              indicatorDataRef.current = merged;
              return merged;
            });
            queueMicrotask(() => {
              setSharedViewRange((prev) => ({
                start: prev.start,
                end: Math.max(prev.end, candleDataRef.current.length),
              }));
            });
            triggerRealtimeGlow();
            return;
          }

          if (isIncrementalUpdateInProgressRef.current) {
            return;
          }
          if (wsCatchupReloadTimerRef.current != null) {
            window.clearTimeout(wsCatchupReloadTimerRef.current);
          }
          wsCatchupReloadTimerRef.current = window.setTimeout(() => {
            wsCatchupReloadTimerRef.current = null;
            if (isIncrementalUpdateInProgressRef.current) return;
            if (process.env.NODE_ENV === 'development') {
              console.log('🆕 [WS onData] WS 꼬리 없음 — 증분 loadData (디바운스)');
            }
            void loadDataRef.current?.(false, null, false, false, true);
          }, 350);
          return;
        }
      }
    },
    onError: handleWsError,
  });

  // upbit_ws_subscription: Python 캔들 WS 워커가 DB에 적재하도록 (market, 봉) 등록
  const wsSubKeyRef = useRef<string | null>(null);
  useEffect(() => {
    const m = (symbol || '').trim();
    const tf = wsTimeframe;
    if (!m || !tf || !realtimeUpdateEnabled) {
      return undefined;
    }
    const tid = window.setTimeout(() => {
      void acquireWsCandleSubscription(m, tf);
      wsSubKeyRef.current = `${m}|${tf}`;
    }, 400);
    return () => {
      window.clearTimeout(tid);
      const k = wsSubKeyRef.current;
      wsSubKeyRef.current = null;
      if (k) {
        const sep = k.indexOf('|');
        if (sep > 0) {
          void releaseWsCandleSubscription(k.slice(0, sep), k.slice(sep + 1));
        }
      }
    };
  }, [symbol, wsTimeframe, realtimeUpdateEnabled]);

  // ✅ WebSocket 데이터를 ref에 저장 (useEffect 의존성 문제 해결)
  const wsCandlesRef = useRef(wsCandles);
  const wsIndicatorsRef = useRef(wsIndicators);
  
  useEffect(() => {
    wsCandlesRef.current = wsCandles;
    wsIndicatorsRef.current = wsIndicators;
  }, [wsCandles, wsIndicators]);

  // ✅ 폴백: WebSocket 연결 불안정 시 주기적 갱신 (설정된 갱신 간격마다)
  // WebSocket onData 콜백에서 실시간 갱신되므로 이 타이머는 보조 역할
  useEffect(() => {
    if (!realtimeUpdateEnabled) {
      console.log('🔴 [폴백 갱신] 비활성화됨');
      return;
    }
    if (isLoadingHistory || isViewingHistoricalDataRef.current) {
      console.log('⏸️ [폴백 갱신] 과거 데이터 보기 중, 스킵');
      return;
    }
    
    // WebSocket이 연결되지 않은 경우에만 폴백 타이머 활성화
    if (wsStatus === 'connected') {
      console.log('✅ [폴백 갱신] WebSocket 연결됨, 폴백 타이머 불필요');
      return;
    }

    console.log(`🟡 [폴백 갱신 타이머 시작] WebSocket 미연결 상태, ${realtimeUpdateInterval/1000}초마다 갱신`);

    const intervalId = setInterval(() => {
      const wsCandles = wsCandlesRef.current;
      const wsIndicators = wsIndicatorsRef.current;
      
      if (wsCandles.length === 0) {
        console.log('⏭️ [폴백 갱신] WebSocket 데이터 없음, 스킵');
        return;
      }
      
      if (candleDataRef.current.length === 0) {
        console.log('⏭️ [폴백 갱신] 차트 데이터 없음, 스킵');
        return;
      }

      // ✅ 최신 WebSocket 캔들로 차트를 갱신
      const latestWsCandle = wsCandles[wsCandles.length - 1];
      const lastChartCandle = candleDataRef.current[candleDataRef.current.length - 1];
      
      const rawWsTs = latestWsCandle.timestamp ?? latestWsCandle.time;
      const wsTimeInSeconds =
        typeof rawWsTs === 'number'
          ? rawWsTs > 1e12
            ? Math.floor(rawWsTs / 1000)
            : Math.floor(rawWsTs)
          : Math.floor(new Date(rawWsTs as string).getTime() / 1000);
      if (!Number.isFinite(wsTimeInSeconds)) {
        return;
      }
      
      const chartLastOpenSec = candleTimeToUnixSeconds(lastChartCandle?.time);
      
      console.log(`📊 [폴백 갱신] WS 최신 캔들:`, {
        wsTimestamp: latestWsCandle.timestamp,
        wsTimeInSeconds,
        chartLastOpenSec,
        sameBar: isWsCandleSameBarAsChartLast(wsTimeInSeconds, candleDataRef.current, wsTimeframe),
        close: latestWsCandle.close,
        volume: latestWsCandle.volume,
      });
      
      if (!Number.isFinite(chartLastOpenSec) || wsTimeInSeconds < chartLastOpenSec) {
        console.warn(`⚠️ [폴백 갱신] 과거/비정상 시간 무시`);
        return;
      }
      
      if (isWsCandleSameBarAsChartLast(wsTimeInSeconds, candleDataRef.current, wsTimeframe)) {
        // 차트 업데이트
        if (chartRef.current) {
          chartRef.current.updateLastCandle(latestWsCandle);
          console.log(`✅ [폴백 갱신 완료] 차트 업데이트: close=${latestWsCandle.close}`);
        }
        
        // candleDataRef 업데이트
        const lastIdx = candleDataRef.current.length - 1;
        if (lastIdx >= 0 && candleDataRef.current[lastIdx]) {
          candleDataRef.current[lastIdx] = {
            ...candleDataRef.current[lastIdx],
            open: latestWsCandle.open,
            high: latestWsCandle.high,
            low: latestWsCandle.low,
            close: latestWsCandle.close,
            volume: latestWsCandle.volume,
          };
        }
        
        // 보조지표 업데이트
        const lastCandle = candleDataRef.current[lastIdx];
        const candleTime = lastCandle?.time;
        if (candleTime && wsIndicators && Object.keys(wsIndicators).length > 0) {
          const mappedIndicators = mapWsIndicatorsToChartFormat(wsIndicators as Record<string, unknown>);
          
          // 백엔드 계산 지표 로깅 (MA, 일목균형표)
          const hasBackendMA = mappedIndicators.ma5 || mappedIndicators.ma10 || mappedIndicators.ma20;
          const hasBackendIchimoku = mappedIndicators.ichimokuTenkan || mappedIndicators.ichimokuKijun;
          if (hasBackendMA || hasBackendIchimoku) {
            const ma5 = typeof mappedIndicators.ma5 === 'number' ? mappedIndicators.ma5.toFixed(0) : 'N/A';
            const ma20 = typeof mappedIndicators.ma20 === 'number' ? mappedIndicators.ma20.toFixed(0) : 'N/A';
            const tenkan = typeof mappedIndicators.ichimokuTenkan === 'number' ? mappedIndicators.ichimokuTenkan.toFixed(0) : 'N/A';
            console.log(`📊 [폴백-백엔드 지표] MA5=${ma5}, MA20=${ma20}, Tenkan=${tenkan}`);
          }
          
          setIndicatorData(prev => {
            const updated = [...prev];
            const idx = updated.length - 1;
            if (idx >= 0 && updated[idx]) {
              updated[idx] = {
                ...updated[idx],
                ...mappedIndicators,
                time: candleTime,
                timestamp: candleTime,
                date: candleTime,
              };
              indicatorDataRef.current = updated;
              console.log(`📊 [폴백 갱신] 보조지표 업데이트 (지표 ${Object.keys(mappedIndicators).length}개)`);
              
              // ✅ 서브차트 보조지표 실시간 갱신 (WebSocket onData와 동일한 로직)
              if (chartRef.current && lastIdx >= 0 && candleDataRef.current[lastIdx]) {
                chartRef.current.updateLastIndicators(
                  candleDataRef.current[lastIdx],
                  candleDataRef.current,
                  updated
                );
                console.log(`✅ [폴백 갱신] 서브차트 보조지표 갱신 완료`);
              }
            }
            return updated;
          });
        }
        triggerRealtimeGlow();
      } else {
        console.log(`🆕 [폴백 갱신] 새로운 캔들 (시간대 변경) — 증분 로드는 onData와 동일하게 WS 경로에서 처리`);
      }
      
    }, realtimeUpdateInterval);

    return () => {
      console.log('🧹 [폴백 갱신] 타이머 정리');
      clearInterval(intervalId);
    };
  }, [realtimeUpdateEnabled, realtimeUpdateInterval, isLoadingHistory, wsStatus, triggerRealtimeGlow, wsTimeframe]);

  // ✅ 차트 돋보기 (확대/축소) 상태
  const [zoomLevel, setZoomLevel] = useState<number>(100); // 100 = 기본 크기

  // ✅ 전략 체크 팝업 상태
  const [strategyCheckPopupOpen, setStrategyCheckPopupOpen] = useState(false);

  // ✅ 전체 차트 설정 팝업 상태
  const [chartSettingsPopupOpen, setChartSettingsPopupOpen] = useState(false);

  // ✅ 이동평균선 & 일목균형표 & 볼린저밴드 메뉴 앵커
  const [maMenuAnchor, setMaMenuAnchor] = useState<null | HTMLElement>(null);
  const [ichimokuMenuAnchor, setIchimokuMenuAnchor] = useState<null | HTMLElement>(null);
  const [bollingerMenuAnchor, setBollingerMenuAnchor] = useState<null | HTMLElement>(null);
  const [realtimeIntervalMenuAnchor, setRealtimeIntervalMenuAnchor] = useState<null | HTMLElement>(null);

  // 보조지표 순서 관리 (MacdChart와 동일한 순서)
  const [indicatorOrder, setIndicatorOrder] = useState<string[]>([
    'volume',
    'newPsychological',
    'investPsychological',
    'macd',
    'cci',
    'adx',
    'bwi',
    'dmi',
    'obv',
    'rsi',
    'stochastic',
    'williamsR',
    'trix',
    'volumeOsc',
    'vr',
    'disparity',
  ]);

  // 보조지표 높이 관리
  const [indicatorHeights, setIndicatorHeights] = useState<Record<string, number>>({
    volume: 150,
    newPsychological: 150,
    investPsychological: 150,
    macd: 150,
    cci: 150,
    adx: 150,
    bwi: 150,
    dmi: 150,
    obv: 150,
    rsi: 150,
    stochastic: 150,
    williamsR: 150,
    trix: 150,
    volumeOsc: 150,
    vr: 150,
    disparity: 150,
  });

  // 캔들차트에 추가된 보조지표 관리 (하나만 추가 가능)
  const [candleChartOverlayIndicator, setCandleChartOverlayIndicator] = useState<string | null>(null);

  // 드래그 중인 보조지표 추적
  const [activeDragId, setActiveDragId] = useState<string | null>(null);

  // 보조지표 설정 팝업 상태 (업비트와 동일)
  const [settingsPopupOpen, setSettingsPopupOpen] = useState(false);
  const [selectedIndicator, setSelectedIndicator] = useState<IndicatorType | null>(null);

  // MA, Ichimoku, Bollinger 설정 다이얼로그 상태
  const [maSettingsOpen, setMaSettingsOpen] = useState(false);
  const [ichimokuSettingsOpen, setIchimokuSettingsOpen] = useState(false);
  const [bollingerSettingsOpen, setBollingerSettingsOpen] = useState(false);

  // 보조지표 메뉴 상태
  const [indicatorMenuAnchor, setIndicatorMenuAnchor] = useState<null | HTMLElement>(null);
  
  // 보조지표 설정 다이얼로그 상태
  const [indicatorSettingsDialogOpen, setIndicatorSettingsDialogOpen] = useState(false);

  // ✅ 자동 갱신 상태 (localStorage에서 복원)
  const [autoRefreshEnabled, setAutoRefreshEnabled] = useState<boolean>(() => {
    const saved = localStorage.getItem(STORAGE_KEYS.AUTO_REFRESH);
    return saved !== null ? saved === 'true' : true;
  });
  const autoRefreshIntervalRef = useRef<NodeJS.Timeout | null>(null);
  
  // ✅ 알림 상태 관리
  const [alertEnabled, setAlertEnabled] = useState<boolean>(false);
  const [alertId, setAlertId] = useState<number | null>(null);
  const [loadingAlert, setLoadingAlert] = useState<boolean>(false);
  
  // ✅ timeInterval의 최신 값을 항상 참조하기 위한 ref (자동 갱신 시 클로저 문제 방지)
  const timeIntervalRef = useRef<string>(timeInterval);

  // ✅ 전략 관련 상태 (localStorage에서 복원)
  const [strategyRules, setStrategyRules] = useState<StrategyRuleDto[]>([]);
  const [uptrendStrategyId, setUptrendStrategyId] = useState<number | null>(() => {
    const saved = localStorage.getItem(STORAGE_KEYS.UPTREND_STRATEGY);
    return saved ? Number(saved) : null;
  });
  const [downtrendStrategyId, setDowntrendStrategyId] = useState<number | null>(() => {
    const saved = localStorage.getItem(STORAGE_KEYS.DOWNTREND_STRATEGY);
    return saved ? Number(saved) : null;
  });
  const [generalStrategyId, setGeneralStrategyId] = useState<number | null>(() => {
    const saved = localStorage.getItem(STORAGE_KEYS.GENERAL_STRATEGY);
    return saved ? Number(saved) : null;
  });

  // ✅ 추세 판단 전략 설정 (localStorage에서 복원)
  type TrendStrategy = 'none' | 'aggressive' | 'balanced' | 'conservative';
  const [trendStrategy, setTrendStrategy] = useState<TrendStrategy>(() => {
    const saved = localStorage.getItem(STORAGE_KEYS.TREND_STRATEGY);
    return (saved as TrendStrategy) || 'none';
  });

  // 추세 전략에 따른 MA 값
  const getTrendMAs = (strategy: TrendStrategy) => {
    switch (strategy) {
      case 'none':
        return { short: 10, medium: 20, long: 60 }; // 사용안함일 때도 기본값 제공 (차트 표시용)
      case 'aggressive':
        return { short: 5, medium: 10, long: 20 };
      case 'balanced':
        return { short: 10, medium: 20, long: 60 };
      case 'conservative':
        return { short: 20, medium: 60, long: 120 };
    }
  };

  const trendMAs = getTrendMAs(trendStrategy);
  const trendShortMA = trendMAs.short;
  const trendMediumMA = trendMAs.medium;
  const trendLongMA = trendMAs.long;

  // ✅ 전략 ID를 ref로 관리하여 loadData에서 최신 값 참조 가능하도록 함
  const uptrendStrategyIdRef = useRef<number | null>(null);
  const downtrendStrategyIdRef = useRef<number | null>(null);
  const generalStrategyIdRef = useRef<number | null>(null);

  useEffect(() => {
    uptrendStrategyIdRef.current = uptrendStrategyId;
  }, [uptrendStrategyId]);

  useEffect(() => {
    downtrendStrategyIdRef.current = downtrendStrategyId;
  }, [downtrendStrategyId]);

  useEffect(() => {
    generalStrategyIdRef.current = generalStrategyId;
  }, [generalStrategyId]);

  // ✅ localStorage 저장: 상태 변경 시 자동 저장
  useEffect(() => {
    localStorage.setItem(STORAGE_KEYS.SYMBOL, symbol);
  }, [symbol]);

  // ✅ 알림 상태 조회 함수 (symbol 변경 시 + 이벤트/visibility 시 재사용)
  const loadAlertStatus = useCallback(async () => {
    if (!symbol) {
      setAlertEnabled(false);
      setAlertId(null);
      setLoadingAlert(false);
      return;
    }
    try {
      setLoadingAlert(true);
      const alert = await getAlertBySymbol(symbol);
      if (alert) {
        setAlertEnabled(alert.enabled || false);
        setAlertId(alert.id || null);
      } else {
        setAlertEnabled(false);
        setAlertId(null);
      }
    } catch (error) {
      console.error('알림 상태 조회 실패:', error);
      setAlertEnabled(false);
      setAlertId(null);
    } finally {
      setLoadingAlert(false);
    }
  }, [symbol]);

  // ✅ 알림 상태 조회: symbol 변경 시 해당 종목의 알림 상태 로드
  useEffect(() => {
    loadAlertStatus();
  }, [loadAlertStatus]);

  // ✅ 알림 설정 화면에서 변경 시 동기화: cryptoAlertChanged 이벤트 수신
  useEffect(() => {
    const handleCryptoAlertChanged = (e: Event) => {
      const detail = (e as CustomEvent).detail as { symbol?: string };
      if (!detail?.symbol || detail.symbol === symbol) {
        loadAlertStatus();
      }
    };
    window.addEventListener('cryptoAlertChanged', handleCryptoAlertChanged);
    return () => window.removeEventListener('cryptoAlertChanged', handleCryptoAlertChanged);
  }, [symbol, loadAlertStatus]);

  // ✅ 탭 전환 후 복귀 시 알림 상태 재조회 (알림 설정 화면에서 변경했을 수 있음)
  useEffect(() => {
    const handleVisibilityChange = () => {
      if (!document.hidden) {
        loadAlertStatus();
      }
    };
    document.addEventListener('visibilitychange', handleVisibilityChange);
    return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
  }, [loadAlertStatus]);

  useEffect(() => {
    localStorage.setItem(STORAGE_KEYS.TIME_INTERVAL, timeInterval);
    timeIntervalRef.current = timeInterval; // ref도 동시 업데이트
  }, [timeInterval]);

  // ✅ timeHistory가 변경될 때마다 ref 업데이트 (클로저 문제 방지)
  useEffect(() => {
    timeHistoryRef.current = timeHistory;
  }, [timeHistory]);

  useEffect(() => {
    if (uptrendStrategyId !== null) {
      localStorage.setItem(STORAGE_KEYS.UPTREND_STRATEGY, String(uptrendStrategyId));
    } else {
      localStorage.removeItem(STORAGE_KEYS.UPTREND_STRATEGY);
    }
  }, [uptrendStrategyId]);

  useEffect(() => {
    if (downtrendStrategyId !== null) {
      localStorage.setItem(STORAGE_KEYS.DOWNTREND_STRATEGY, String(downtrendStrategyId));
    } else {
      localStorage.removeItem(STORAGE_KEYS.DOWNTREND_STRATEGY);
    }
  }, [downtrendStrategyId]);

  useEffect(() => {
    if (generalStrategyId !== null) {
      localStorage.setItem(STORAGE_KEYS.GENERAL_STRATEGY, String(generalStrategyId));
    } else {
      localStorage.removeItem(STORAGE_KEYS.GENERAL_STRATEGY);
    }
  }, [generalStrategyId]);

  useEffect(() => {
    localStorage.setItem(STORAGE_KEYS.TREND_STRATEGY, trendStrategy);
  }, [trendStrategy]);

  useEffect(() => {
    localStorage.setItem(STORAGE_KEYS.CHART_VIEW_MODE, chartViewMode);
  }, [chartViewMode]);

  useEffect(() => {
    localStorage.setItem(STORAGE_KEYS.AUTO_REFRESH, String(autoRefreshEnabled));
  }, [autoRefreshEnabled]);

  useEffect(() => {
    localStorage.setItem('candleTimeRemainingEnabled', String(candleTimeRemainingEnabled));
  }, [candleTimeRemainingEnabled]);

  useEffect(() => {
    localStorage.setItem(STORAGE_KEYS.BUY_AMOUNT, String(buyAmount));
  }, [buyAmount]);

  useEffect(() => {
    localStorage.setItem(STORAGE_KEYS.SELL_AMOUNT, String(sellAmount));
  }, [sellAmount]);

  useEffect(() => {
    localStorage.setItem(STORAGE_KEYS.TOTAL_BALANCE, String(totalBalance));
  }, [totalBalance]);

  // ✅ 파일 모드: 마운트 시 설정 로드
  const fileModeInitDoneRef = useRef(false);
  useEffect(() => {
    if (getStorageMode() !== 'file' || fileModeInitDoneRef.current) return;
    fileModeInitDoneRef.current = true;
    const cfg = loadFromFile();
    if (!cfg) return;
    if (cfg.timeInterval) setTimeInterval(cfg.timeInterval);
    if (cfg.trendStrategy) setTrendStrategy(cfg.trendStrategy as 'none' | 'aggressive' | 'balanced' | 'conservative');
    if (cfg.generalStrategyId !== undefined) setGeneralStrategyId(cfg.generalStrategyId);
    if (cfg.uptrendStrategyId !== undefined) setUptrendStrategyId(cfg.uptrendStrategyId);
    if (cfg.downtrendStrategyId !== undefined) setDowntrendStrategyId(cfg.downtrendStrategyId);
  }, []);

  // ✅ 파일 모드: 설정 변경 시 파일에 저장 (첫 렌더 스킵)
  const fileModeSaveSkippedRef = useRef(true);
  useEffect(() => {
    if (getStorageMode() !== 'file') return;
    if (fileModeSaveSkippedRef.current) {
      fileModeSaveSkippedRef.current = false;
      return;
    }
    saveToFile({
      timeInterval,
      trendStrategy,
      generalStrategyId,
      uptrendStrategyId,
      downtrendStrategyId,
      indicatorSettings: indicatorVisibilitySettings,
      indicatorOrder: indicatorVisibilityOrder,
    });
  }, [timeInterval, trendStrategy, generalStrategyId, uptrendStrategyId, downtrendStrategyId, indicatorVisibilitySettings, indicatorVisibilityOrder]);

  // ✅ Drawing state
  const [drawings, setDrawings] = useState<DrawingObject[]>([]);
  const [drawingSettingsOpen, setDrawingSettingsOpen] = useState(false);
  const [editingDrawing, setEditingDrawing] = useState<DrawingObject | null>(null);
  const [fibonacciSettingsOpen, setFibonacciSettingsOpen] = useState(false);
  const [editingFibonacci, setEditingFibonacci] = useState<DrawingObject | null>(null);
  
  // ✅ Undo/Redo state
  const [drawingHistory, setDrawingHistory] = useState<DrawingObject[][]>([[]]);
  const [historyIndex, setHistoryIndex] = useState(0);
  
  // ✅ 수직선 설정 state
  const [verticalLineDialogOpen, setVerticalLineDialogOpen] = useState(false);
  const [editingVerticalLine, setEditingVerticalLine] = useState<DrawingObject | null>(null);
  const [verticalLineSettings, setVerticalLineSettings] = useState<VerticalLineSettings>(defaultVerticalLineSettings);
  
  // ✅ 수직선 드래그 state
  const [draggingDrawingId, setDraggingDrawingId] = useState<string | null>(null);
  const [dragStartDrawingTime, setDragStartDrawingTime] = useState<number | null>(null);
  const [dragStartMouseX, setDragStartMouseX] = useState<number | null>(null);

  // ✅ Drawing 업데이트 with history
  const updateDrawingsWithHistory = useCallback((newDrawings: DrawingObject[]) => {
    setDrawings(newDrawings);
    setDrawingHistory(prev => {
      const newHistory = prev.slice(0, historyIndex + 1);
      newHistory.push(newDrawings);
      return newHistory.slice(-50);
    });
    setHistoryIndex(prev => Math.min(prev + 1, 49));
  }, [historyIndex]);

  // ✅ 수직선 설정 저장 핸들러
  const handleVerticalLineSave = useCallback((settings: VerticalLineSettings) => {
    if (!editingVerticalLine) return;

    const updatedDrawings = drawings.map(d => {
      if (d.id === editingVerticalLine.id) {
        return {
          ...d,
          extended: settings.extended,
          showLabel: settings.showLabel,
          customText: settings.customText,
          style: {
            ...d.style,
            color: settings.color,
            lineStyle: settings.lineStyle,
            lineWidth: settings.lineWidth,
          },
        };
      }
      return d;
    });

    updateDrawingsWithHistory(updatedDrawings);
    console.log('✅ [수직선 설정 저장]', settings);
  }, [editingVerticalLine, drawings, updateDrawingsWithHistory]);

  // ✅ Undo 핸들러
  const handleUndo = useCallback(() => {
    if (historyIndex > 0) {
      const newIndex = historyIndex - 1;
      setHistoryIndex(newIndex);
      setDrawings(drawingHistory[newIndex]);
      console.log('↩️ [Undo] 이전 상태로 복원');
    }
  }, [historyIndex, drawingHistory]);

  // ✅ Redo 핸들러
  const handleRedo = useCallback(() => {
    if (historyIndex < drawingHistory.length - 1) {
      const newIndex = historyIndex + 1;
      setHistoryIndex(newIndex);
      setDrawings(drawingHistory[newIndex]);
      console.log('↪️ [Redo] 다음 상태로 복원');
    }
  }, [historyIndex, drawingHistory]);

  // ✅ 수직선 삭제 핸들러
  const handleVerticalLineDelete = useCallback(() => {
    if (!editingVerticalLine) return;

    const updatedDrawings = drawings.filter(d => d.id !== editingVerticalLine.id);
    updateDrawingsWithHistory(updatedDrawings);
    console.log('🗑️ [수직선 삭제]', editingVerticalLine.id);
  }, [editingVerticalLine, drawings, updateDrawingsWithHistory]);

  // ✅ 수직선 드래그 시작 핸들러
  const handleDrawingDragStart = useCallback((drawingId: string, startX: number) => {
    const drawing = drawings.find(d => d.id === drawingId);
    if (!drawing || drawing.type !== 'verticalLine') return;

    console.log(`🖱️ [수직선 드래그 시작] ID: ${drawingId}, X: ${startX}`);
    setDraggingDrawingId(drawingId);
    setDragStartDrawingTime(drawing.points[0].time);
    setDragStartMouseX(startX);
  }, [drawings]);

  // ✅ 수직선 드래그 종료 핸들러
  const handleDrawingDragEnd = useCallback(() => {
    console.log(`🖱️ [수직선 드래그 종료]`);
    setDraggingDrawingId(null);
    setDragStartDrawingTime(null);
    setDragStartMouseX(null);
  }, []);

  // ✅ 수직선 드래그 중 전역 마우스 이벤트 리스너
  useEffect(() => {
    if (!draggingDrawingId || !chartRef.current || dragStartDrawingTime === null || dragStartMouseX === null) return;

    const handleGlobalMouseMove = (e: MouseEvent) => {
      const chartContainer = chartContainerRef.current;
      if (!chartContainer) return;

      const rect = chartContainer.getBoundingClientRect();
      const currentX = e.clientX - rect.left;

      // X 좌표를 시간으로 변환
      const timeScale = chartRef.current?.timeScale();
      if (!timeScale) return;

      const currentTime = timeScale.coordinateToTime(currentX);
      if (!currentTime) return;

      // 드래그 시작 위치의 시간 계산
      const startXRelative = dragStartMouseX - rect.left;
      const startTime = timeScale.coordinateToTime(startXRelative);
      if (!startTime) return;

      // 시간 차이 계산
      const timeDiff = (currentTime as number) - (startTime as number);
      const newTime = dragStartDrawingTime + timeDiff;

      // 수직선 업데이트
      setDrawings(prev => prev.map(d => {
        if (d.id !== draggingDrawingId) return d;
        return {
          ...d,
          points: [{
            time: newTime,
            price: d.points[0].price,
          }],
        };
      }));
    };

    const handleGlobalMouseUp = () => {
      handleDrawingDragEnd();
    };

    document.addEventListener('mousemove', handleGlobalMouseMove);
    document.addEventListener('mouseup', handleGlobalMouseUp);

    console.log('🖱️ [전역 이벤트] 수직선 드래그 중 - 전역 마우스 이벤트 리스너 추가됨');

    return () => {
      document.removeEventListener('mousemove', handleGlobalMouseMove);
      document.removeEventListener('mouseup', handleGlobalMouseUp);
      console.log('🖱️ [전역 이벤트] 수직선 드래그 종료 - 전역 마우스 이벤트 리스너 제거됨');
    };
  }, [draggingDrawingId, dragStartDrawingTime, dragStartMouseX, handleDrawingDragEnd]);

  // Coordinate conversion functions for Drawing Overlay
  const timeToCoordinate = useCallback((time: number) => {
    if (chartRef.current) {
      return chartRef.current.timeScale().timeToCoordinate(time);
    }
    return null;
  }, []);

  const coordinateToTime = useCallback((x: number) => {
    if (chartRef.current) {
      return chartRef.current.timeScale().coordinateToTime(x);
    }
    return null;
  }, []);

  const priceToCoordinate = useCallback((price: number) => {
    if (chartRef.current && chartRef.current.priceScale) {
      return chartRef.current.priceScale().priceToCoordinate(price);
    }
    return null;
  }, []);

  const coordinateToPrice = useCallback((y: number) => {
    if (chartRef.current && chartRef.current.priceScale) {
      return chartRef.current.priceScale().coordinateToPrice(y);
    }
    return null;
  }, []);

  const [backtestResult, setBacktestResult] = useState<BacktestResult | null>(null);
  const [isRunningBacktest, setIsRunningBacktest] = useState(false);

  // ✅ 시간봉/종목 변경 시 백테스트 결과(매수/매도 마커) 초기화
  useEffect(() => {
    if (backtestResult !== null) {
      console.log('🔄 [시간봉/종목 변경] 기존 백테스트 결과 초기화 (매수/매도 마커 제거)');
      setBacktestResult(null);
    }
    
    // ✅ 초기 로드 플래그 리셋 (새 종목/시간봉 로드 시 차트 범위 초기화 허용)
    isInitialLoadDoneRef.current = false;
    console.log('🔄 [시간봉/종목 변경] 초기 로드 플래그 리셋');
  }, [timeInterval, symbol]); // timeInterval 또는 symbol이 변경될 때 실행

  // ✅ 뷰 복원 중 플래그 (onVisibleRangeChange 콜백 무시용)
  const isRestoringViewRef = useRef<boolean>(false);

  // ✅ 거래 상세 정보 팝업 상태
  const [tradeDetailOpen, setTradeDetailOpen] = useState(false);
  const [selectedTrade, setSelectedTrade] = useState<TradeRecord | null>(null);

  // 차트 상세보기 팝업 상태
  const [chartDetailOpen, setChartDetailOpen] = useState(false);
  const [chartDetailIndex, setChartDetailIndex] = useState(0);
  const [chartDetailType, setChartDetailType] = useState<ChartDetailType>('candle');
  const [chartDetailSignal, setChartDetailSignal] = useState<'BUY' | 'SELL' | null>(null);

  // AI 테스트 팝업 상태
  const [aiTestPopupOpen, setAiTestPopupOpen] = useState(false);
  const [aiTestLoading, setAiTestLoading] = useState(false);
  const [aiTestResult, setAiTestResult] = useState<string>('');
  const [aiTestError, setAiTestError] = useState<string | null>(null);

  // AI 채팅 패널 상태
  const [aiChatPanelOpen, setAiChatPanelOpen] = useState(false);

  // 실시간 AI 분석 패널 상태
  const [realTimeAnalysisPanelOpen, setRealTimeAnalysisPanelOpen] = useState(false);
  const [realTimeAnalysisEnabled, setRealTimeAnalysisEnabled] = useState(false);
  const [lastAnalysisTime, setLastAnalysisTime] = useState<Date | null>(null);
  const [nextAnalysisTime, setNextAnalysisTime] = useState<Date | null>(null);
  const realTimeAnalysisRef = useRef<RealTimeAnalysisPanelRef>(null);

  // 차트 hover 디바운스 타이머
  const hoverDebounceTimerRef = useRef<NodeJS.Timeout | null>(null);

  // 매매신호 상세 팝업 상태 (현재 미사용)
  const [signalDetailOpen, setSignalDetailOpen] = useState(false);
  
  // 하단 매매 신호 패널 상태
  const [signalPanelOpen, setSignalPanelOpen] = useState(false);
  const [selectedSignal, setSelectedSignal] = useState<any>(null);
  const [selectedSimulation, setSelectedSimulation] = useState<TradeSimulation | undefined>(undefined);

  // ✅ DeviceId 생성 또는 가져오기
  const getDeviceId = (): string => {
    let deviceId = localStorage.getItem('deviceId');
    if (!deviceId) {
      deviceId = `guest-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
      localStorage.setItem('deviceId', deviceId);
      console.log('🆔 [DeviceId] 새로 생성:', deviceId);
    }
    return deviceId;
  };

  // ✅ 피보나치 타임존 기본 설정 로드 (DB에서)
  const loadFibonacciDefaultSettings = async (): Promise<FibonacciTimezoneSettings> => {
    try {
      const deviceId = getDeviceId();
      console.log('📂 [loadFibonacciDefaultSettings] deviceId:', deviceId);

      const saved = await settingsApi.getGuestFibonacciSettings(deviceId);
      console.log('📂 [loadFibonacciDefaultSettings] DB 값 (원본):', saved);
      console.log('📂 [loadFibonacciDefaultSettings] DB 값 타입:', typeof saved);
      console.log('📂 [loadFibonacciDefaultSettings] DB 값 길이:', saved?.length);

      // ✅ 빈 문자열, null, undefined, '{}' 체크
      if (saved && saved.trim() !== '' && saved.trim() !== '{}') {
        console.log('📂 [loadFibonacciDefaultSettings] 파싱 시도...');
        const parsed = JSON.parse(saved);
        console.log('📂 [loadFibonacciDefaultSettings] 파싱 성공:', parsed);

        // ✅ levels가 있는지 확인
        if (parsed && parsed.levels && Array.isArray(parsed.levels)) {
          console.log('📂 [loadFibonacciDefaultSettings] levels 개수:', parsed.levels.length);

          // ✅ baseline이 없으면 기본값 추가
          if (!parsed.baseline) {
            console.log('⚠️ [loadFibonacciDefaultSettings] baseline 없음, 기본값 추가');
            parsed.baseline = defaultBaseline;
          }

          console.log('✅ [loadFibonacciDefaultSettings] DB에서 로드 완료:', parsed);
          return parsed;
        } else {
          console.warn('⚠️ [loadFibonacciDefaultSettings] levels가 없거나 비어있음');
        }
      } else {
        console.log('📂 [loadFibonacciDefaultSettings] DB 값이 비어있음 또는 {}');
      }
    } catch (error) {
      console.error('❌ [loadFibonacciDefaultSettings] 로드 실패:', error);
    }

    console.log('📂 [loadFibonacciDefaultSettings] 기본 설정 사용:', { baseline: defaultBaseline, levels: defaultLevels });
    return { baseline: defaultBaseline, levels: defaultLevels };
  };

  // ✅ 피보나치 타임존 상태
  const [fibonacciTimezones, setFibonacciTimezones] = useState<FibonacciTimezoneObject[]>([]);
  const [fibDialogOpen, setFibDialogOpen] = useState(false);
  const [editingFibId, setEditingFibId] = useState<string | null>(null);
  const [fibDefaultSettings, setFibDefaultSettings] = useState<FibonacciTimezoneSettings>({ baseline: defaultBaseline, levels: defaultLevels });
  const [fibSettings, setFibSettings] = useState<FibonacciTimezoneSettings>({ baseline: defaultBaseline, levels: defaultLevels });
  const [isFibSettingsLoaded, setIsFibSettingsLoaded] = useState(false); // ✅ 로딩 상태
  const [isDrawingFib, setIsDrawingFib] = useState(false);
  const [fibStartTime, setFibStartTime] = useState<number | null>(null);
  const [fibStartIndex, setFibStartIndex] = useState<number | null>(null);
  const [chartContainerWidth, setChartContainerWidth] = useState(800);
  
  // ✅ 피보나치 툴바 state
  const [fibToolbarOpen, setFibToolbarOpen] = useState(false);
  const [fibToolbarPosition, setFibToolbarPosition] = useState<{ top: number; left: number } | null>(null);
  const [selectedFibId, setSelectedFibId] = useState<string | null>(null);
  const [isFibVisible, setIsFibVisible] = useState(true);

  // ✅ 새로운 피보나치 툴 state
  const [fibonacciFans, setFibonacciFans] = useState<FibonacciFanObject[]>([]);
  const [fibonacciCircles, setFibonacciCircles] = useState<FibonacciCircleObject[]>([]);
  const [fibonacciWedges, setFibonacciWedges] = useState<FibonacciWedgeObject[]>([]);
  
  // ✅ 새로운 피보나치 툴 설정 state
  const [fibFanDefaultSettings, setFibFanDefaultSettings] = useState({
    color: '#2962FF',
    lineWidth: 2,
    lineStyle: 'solid' as const,
    levels: [0.382, 0.5, 0.618],
    showLabels: true,
  });
  
  const [fibCircleDefaultSettings, setFibCircleDefaultSettings] = useState({
    color: '#2962FF',
    lineWidth: 2,
    lineStyle: 'solid' as const,
    fillOpacity: 0.1,
    levels: [0.382, 0.5, 0.618, 1.0],
    showLabels: true,
  });
  
  const [fibWedgeDefaultSettings, setFibWedgeDefaultSettings] = useState({
    color: '#2962FF',
    lineWidth: 2,
    lineStyle: 'solid' as const,
    fillOpacity: 0.1,
    levels: [0.382, 0.5, 0.618],
    showLabels: true,
  });

  // ✅ 차트 컨테이너 너비 측정 (ResizeObserver)
  useEffect(() => {
    const container = chartContainerRef.current;
    if (!container) return;

    const updateWidth = () => {
      const width = container.getBoundingClientRect().width;
      setChartContainerWidth(width);
      console.log(`📏 [차트 컨테이너 너비 업데이트] ${width}px`);
    };

    // 초기 너비 설정
    updateWidth();

    // ResizeObserver로 크기 변경 감지
    const resizeObserver = new ResizeObserver(() => {
      updateWidth();
    });

    resizeObserver.observe(container);

    return () => {
      resizeObserver.disconnect();
    };
  }, []);
  const [draggingFibId, setDraggingFibId] = useState<string | null>(null);
  const [dragStartX, setDragStartX] = useState<number | null>(null);
  const [dragStartFibTime, setDragStartFibTime] = useState<number | null>(null);
  const [dragStartMouseTime, setDragStartMouseTime] = useState<number | null>(null); // ✅ 드래그 시작 시 마우스 위치

  // ✅ 기간 측정 도구 상태
  const [measureDialogOpen, setMeasureDialogOpen] = useState(false);
  const [editingMeasureId, setEditingMeasureId] = useState<string | null>(null);
  const [measureDefaultSettings, setMeasureDefaultSettings] = useState<MeasureSettings>(defaultMeasureSettings);
  const [measureSettings, setMeasureSettings] = useState<MeasureSettings>(defaultMeasureSettings);
  const [isMeasureSettingsLoaded, setIsMeasureSettingsLoaded] = useState(false);

  // ✅ 피보나치 객체들 로드 함수
  const loadFibonacciObjects = async (): Promise<FibonacciTimezoneObject[]> => {
    try {
      const deviceId = getDeviceId();
      console.log('📂 [loadFibonacciObjects] deviceId:', deviceId);

      const saved = await settingsApi.getGuestFibonacciObjects(deviceId);
      console.log('📂 [loadFibonacciObjects] DB 값 (원본):', saved);

      if (saved && saved.trim() !== '' && saved !== '[]') {
        const parsed: FibonacciTimezoneObject[] = JSON.parse(saved);
        console.log('✅ [loadFibonacciObjects] DB에서 로드 완료:', parsed);
        return parsed;
      }
    } catch (error) {
      console.error('❌ [loadFibonacciObjects] 로드 실패:', error);
    }
    console.log('📂 [loadFibonacciObjects] 빈 배열 반환');
    return [];
  };

  // ✅ 피보나치 객체들 저장 함수
  const saveFibonacciObjects = async (objects: FibonacciTimezoneObject[]) => {
    try {
      const deviceId = getDeviceId();
      const objectsJson = JSON.stringify(objects);
      console.log('💾 [saveFibonacciObjects] 저장 시도:', { deviceId, count: objects.length });
      await settingsApi.saveGuestFibonacciObjects(deviceId, objectsJson);
      console.log('✅ [saveFibonacciObjects] 저장 완료');
    } catch (error) {
      console.error('❌ [saveFibonacciObjects] 저장 실패:', error);
    }
  };

  // ✅ 기간 측정 기본 설정 로드 함수
  const loadMeasureDefaultSettings = async (): Promise<MeasureSettings> => {
    try {
      const deviceId = getDeviceId();
      const saved = await settingsApi.getGuestMeasureSettings(deviceId);
      console.log('📂 [기간 측정 설정 로드] DB 값:', saved);

      if (saved && saved.trim() !== '' && saved !== '{}') {
        const parsed: MeasureSettings = JSON.parse(saved);
        console.log('✅ [기간 측정 설정 로드] DB에서 로드 완료:', parsed);
        return parsed;
      }
    } catch (error) {
      console.error('❌ [기간 측정 설정 로드] 로드 실패:', error);
    }
    console.log('📂 [기간 측정 설정 로드] 기본값 반환');
    return defaultMeasureSettings;
  };

  // ✅ 브라우저 resize 이벤트 감지 (개발자 도구 토글, 창 크기 변경 등)
  useEffect(() => {
    let resizeTimer: number | null = null;

    const handleWindowResize = () => {
      // 브라우저 탭이 숨겨진 상태이거나 차트 컨테이너가 렌더링되지 않은 경우 무시
      if (document.hidden) return;

      // Resize 시작
      isResizingRef.current = true;

      // 기존 타이머 취소
      if (resizeTimer !== null) {
        window.clearTimeout(resizeTimer);
      }

      // 300ms 후 resize 종료로 간주
      resizeTimer = window.setTimeout(() => {
        isResizingRef.current = false;
      }, 300);
    };

    window.addEventListener('resize', handleWindowResize);

    return () => {
      window.removeEventListener('resize', handleWindowResize);
      if (resizeTimer !== null) {
        window.clearTimeout(resizeTimer);
      }
    };
  }, []);

  // ✅ 컴포넌트 마운트 시 DB에서 기본 설정과 객체들 로드
  useEffect(() => {
    const loadSettings = async () => {
      console.log('🔄 [피보나치 로드] 시작...');

      // 기본 설정 로드
      const loadedSettings = await loadFibonacciDefaultSettings();
      console.log('📂 [피보나치 설정 로드] DB에서 로드 완료:', JSON.stringify(loadedSettings, null, 2));
      setFibDefaultSettings(loadedSettings);

      // 그려진 객체들 로드
      const loadedObjects = await loadFibonacciObjects();
      console.log('📂 [피보나치 객체 로드] DB에서 로드 완료:', loadedObjects);
      setFibonacciTimezones(loadedObjects);

      setIsFibSettingsLoaded(true);
      console.log('✅ [피보나치 로드] 완료, isFibSettingsLoaded=true');

      // 기간 측정 설정 로드
      console.log('🔄 [기간 측정 로드] 시작...');
      const loadedMeasureSettings = await loadMeasureDefaultSettings();
      console.log('📂 [기간 측정 설정 로드] DB에서 로드 완료:', loadedMeasureSettings);
      setMeasureDefaultSettings(loadedMeasureSettings);
      setIsMeasureSettingsLoaded(true);
      console.log('✅ [기간 측정 로드] 완료');
    };
    loadSettings();
  }, []);

  // ✅ 피보나치 객체들이 변경될 때마다 DB에 자동 저장
  useEffect(() => {
    if (isFibSettingsLoaded) {
      console.log('🔄 [피보나치 객체 자동 저장] 객체 수:', fibonacciTimezones.length);
      saveFibonacciObjects(fibonacciTimezones);
    }
  }, [fibonacciTimezones, isFibSettingsLoaded]);

  // ✅ fibDefaultSettings 상태 변경 감지 (디버깅용)
  useEffect(() => {
    console.log('🔔 [fibDefaultSettings 변경됨]:', JSON.stringify(fibDefaultSettings, null, 2));
    console.log('🔔 [fibDefaultSettings] 활성화된 레벨:', fibDefaultSettings.levels.filter(l => l.enabled).map(l => l.value));
  }, [fibDefaultSettings]);

  // ✅ 설정 대화상자가 열릴 때 최신 설정 로드
  useEffect(() => {
    if (fibDialogOpen) {
      if (editingFibId) {
        // 개별 피보나치 객체 편집
        const fib = fibonacciTimezones.find(f => f.id === editingFibId);
        if (fib) {
          console.log('📂 [피보나치 설정 대화상자] 개별 객체 설정 로드:', fib.settings);
          setFibSettings(fib.settings);
        }
      } else {
        // 기본 설정 편집
        if (isFibSettingsLoaded) {
          console.log('📂 [피보나치 설정 대화상자] 기본 설정 로드 (로드 완료):', fibDefaultSettings);
          setFibSettings(fibDefaultSettings);
        } else {
          console.warn('⚠️ [피보나치 설정 대화상자] 아직 설정이 로드되지 않았습니다');
          // DB에서 다시 로드
          const reloadSettings = async () => {
            const loadedSettings = await loadFibonacciDefaultSettings();
            console.log('📂 [피보나치 설정 대화상자] 재로드 완료:', loadedSettings);
            setFibDefaultSettings(loadedSettings);
            setFibSettings(loadedSettings);
            setIsFibSettingsLoaded(true);
          };
          reloadSettings();
        }
      }
    }
  }, [fibDialogOpen, editingFibId, fibonacciTimezones, fibDefaultSettings, isFibSettingsLoaded]);

  // ✅ 차트 뷰 모드 변경 시 activeTool 리셋
  useEffect(() => {
    if (chartViewMode === 'basic') {
      // 기본차트 모드에서는 툴을 crosshair로 리셋
      setActiveTool('crosshair');
    }
  }, [chartViewMode]);

  // ✅ activeTool 변경 추적 (디버깅)
  useEffect(() => {
    console.log(`🎯 [activeTool 변경] 현재 툴: ${activeTool}`);
  }, [activeTool]);

  // ✅ chartViewMode 변경 추적 (디버깅)
  useEffect(() => {
    console.log(`📺 [chartViewMode 변경] 현재 모드: ${chartViewMode}`);
  }, [chartViewMode]);

  // ✅ 차트 컨테이너 너비 측정 — ResizeObserver 기반으로 교체
  // window.resize + clientWidth=0 조합이 라이브러리 내부 resize를 유발해 무한 루프를 만드는 것을 방지
  useEffect(() => {
    const el = chartContainerRef.current;
    if (!el) return;

    const applyWidth = (width: number) => {
      if (width <= 0) return; // 숨겨진 상태(0px)에서는 업데이트하지 않음
      setChartContainerWidth(width);
    };

    // ResizeObserver: 실제 크기 변화 시에만 콜백 호출 → 0px 상태에서는 콜백 없음
    const ro = new ResizeObserver((entries) => {
      for (const entry of entries) {
        applyWidth(Math.round(entry.contentRect.width));
      }
    });
    ro.observe(el);

    // 초기 측정 (이미 렌더링된 경우)
    applyWidth(el.clientWidth);

    // 전체화면 전환 시 DOM 레이아웃 안정화 대기 후 재측정
    const timer = setTimeout(() => applyWidth(el.clientWidth), 150);

    return () => {
      ro.disconnect();
      clearTimeout(timer);
    };
  }, [isChartFullscreen]); // 전체화면 상태 변경 시 재실행

  // ✅ 전체화면 시 차트 영역 높이 측정 (캔들+보조지표 꽉 채우기)
  useEffect(() => {
    if (!isChartFullscreen) return;
    const el = chartContainerRef.current;
    if (!el) return;
    const ro = new ResizeObserver((entries) => {
      for (const e of entries) {
        const h = e.contentRect.height;
        if (h > 0) setFullscreenChartAreaHeight(h);
      }
    });
    ro.observe(el);
    if (el.clientHeight > 0) setFullscreenChartAreaHeight(el.clientHeight);
    return () => ro.disconnect();
  }, [isChartFullscreen]);

  // ✅ 오버레이 보조지표가 추가될 때 캔들차트의 현재 visible range를 동기화
  useEffect(() => {
    if (candleChartOverlayIndicator && chartRef.current) {
      // 약간의 지연 후 동기화 (ref가 준비될 시간 확보)
      const syncTimer = setTimeout(() => {
        try {
          const currentRange = chartRef.current?.getVisibleRange();
          if (currentRange) {
            console.log(`🔄 [오버레이 보조지표 줌 동기화] ${candleChartOverlayIndicator}: ${currentRange.from} ~ ${currentRange.to}`);

            const chartRefMap: Record<string, React.RefObject<IndicatorChartHandle>> = {
              volume: volumeChartRef,
              newPsychological: newPsychologicalChartRef,
              investPsychological: investPsychologicalChartRef,
              macd: macdChartRef,
              cci: cciChartRef,
              adx: adxChartRef,
              bwi: bwiChartRef,
              dmi: dmiChartRef,
              obv: obvChartRef,
              rsi: rsiChartRef,
              stochastic: stochasticChartRef,
              williamsR: williamsRChartRef,
              trix: trixChartRef,
              volumeOsc: volumeOscChartRef,
              vr: vrChartRef,
              disparity: disparityChartRef,
            };

            // 오버레이 보조지표에 캔들차트의 현재 visible range 적용
            chartRefMap[candleChartOverlayIndicator]?.current?.setVisibleRange(currentRange.from, currentRange.to);

            // 한 번 더 확실하게 적용 (타이밍 이슈 방지)
            setTimeout(() => {
              chartRefMap[candleChartOverlayIndicator]?.current?.setVisibleRange(currentRange.from, currentRange.to);
            }, 100);
          }
        } catch (e) {
          console.error('❌ [오버레이 보조지표 줌 동기화 실패]', e);
        }
      }, 150);

      return () => clearTimeout(syncTimer);
    }
  }, [candleChartOverlayIndicator]);

  // 통계 정보
  const statistics = useMemo(() => {
    if (candleData.length === 0) return null;
    const latestCandle = candleData[candleData.length - 1];
    const firstCandle = candleData[0];
    
    // ✅ 실시간 가격이 있으면 우선 사용, 없으면 최신 캔들의 종가 사용
    const currentPriceValue = realtimePrice !== null ? realtimePrice : latestCandle.close;
    const priceChange = currentPriceValue - firstCandle.close;
    const priceChangePercent = (priceChange / firstCandle.close) * 100;

    // 신호 카운트 계산
    let buySignals = 0;
    let sellSignals = 0;
    candleData.forEach(candle => {
      if (candle.isBuyPoint) buySignals++;
      if (candle.isSellPoint) sellSignals++;
    });

    return {
      currentPrice: currentPriceValue,
      priceChangePercent,
      totalDays: candleData.length,
      buySignals,
      sellSignals,
      currentSignal: latestCandle.isBuyPoint ? 'BUY' : latestCandle.isSellPoint ? 'SELL' : 'HOLD',
    };
  }, [candleData, realtimePrice]);

  // 차트 데이터 변환 - 시간순 정렬 필수!
  // lightweight-charts는 내부적으로 시간순 정렬 필요
  // ✅ 기간 이동 중 이전 데이터 유지용
  const previousChartDataRef = useRef<any[]>([]);
  const previousIndicatorDataRef = useRef<IndicatorData[]>([]);

  const chartData = useMemo(() => {
    // ✅ 성능 최적화: 개발 모드에서만 상세 로그 출력
    const isDev = process.env.NODE_ENV === 'development';
    if (isDev) {
      console.log(`🔍 [chartData useMemo] 실행: candleData=${candleData.length}개, indicatorData=${indicatorData.length}개, loading=${loading}, isLoadingHistory=${isLoadingHistory}`);
    }

    // ✅ 로딩 중에는 이전 데이터 유지 (시간봉 변경, 기간 이동 모두 포함)
    // ⚠️ loading 또는 isLoadingHistory가 true인 동안에는 절대 새 데이터를 반환하지 않음
    if ((loading || isLoadingHistory) && previousChartDataRef.current.length > 0) {
      if (isDev) console.log('🔒 [chartData] 로딩 중 - 이전 데이터 유지 (새 데이터 차단)');
      return previousChartDataRef.current;
    }

    if (candleData.length === 0) {
      if (isDev) console.warn('⚠️ [chartData] candleData가 비어있음 - 빈 배열 반환');
      return [];
    }

    // candleData를 시간순으로 정렬 (오래된 것 → 최신)
    // API가 역순으로 반환할 수 있으므로 반드시 정렬 필요
    const sortedData = [...candleData].sort((a, b) => {
      return new Date(a.time).getTime() - new Date(b.time).getTime();
    });

    // ✅ indicatorData를 시간 기반으로 빠르게 찾기 위한 Map 생성
    const indicatorMap = new Map<string, any>();
    indicatorData.forEach(ind => {
      const indAny = ind as any;
      // timestamp, time, date 순으로 시간 필드 확인
      const timeKey = indAny.timestamp || indAny.time || indAny.date;
      if (timeKey) {
        indicatorMap.set(timeKey, indAny);
      }
    });

    // ✅ backtestResult.tradeRecords를 기반으로 매수/매도 포인트 Map 생성
    const tradeMap = new Map<string, 'BUY' | 'SELL'>();
    if (backtestResult?.tradeRecords) {
      if (isDev) console.log('🔍 [chartData useMemo] backtestResult.tradeRecords:', backtestResult.tradeRecords.length, '개');
      backtestResult.tradeRecords.forEach((trade, idx) => {
        // timestamp 정규화 (다양한 형식 지원)
        const normalizeTs = (ts: string) => {
          if (!ts) return '';
          // "YYYY-MM-DDTHH:mm" 형식으로 통일
          const match = ts.match(/(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2})/);
          return match ? `${match[1]}T${match[2]}` : ts;
        };
        const normalizedTimestamp = normalizeTs(trade.timestamp || '');
        if (normalizedTimestamp && (trade.tradeType === 'BUY' || trade.tradeType === 'SELL')) {
          tradeMap.set(normalizedTimestamp, trade.tradeType as 'BUY' | 'SELL');
          if (idx < 3) {
            console.log(`  [${idx}] ${normalizedTimestamp} -> ${trade.tradeType}`);
          }
        }
      });
      console.log('📊 tradeMap 생성 완료:', tradeMap.size, '개의 거래 기록');
    } else {
      console.warn('⚠️ [chartData useMemo] backtestResult가 없거나 tradeRecords가 없음');
    }

    const result = sortedData.map((candle) => {
      // ✅ 시간 기반으로 정확한 지표 데이터 매칭
      const indicator = indicatorMap.get(candle.time) || {};

      // 백엔드에서 온 지표 데이터를 any로 타입캐스팅하여 모든 필드 접근 가능
      const indicatorAny = indicator as any;

      // ✅ 캔들 시간 정보를 저장 (indicatorAny의 date/timestamp가 덮어쓰는 것을 방지)
      const candleTime = candle.time;

      // ✅ backtestResult에서 매수/매도 포인트 확인 (candleData보다 우선)
      const normalizeTs = (ts: string) => {
        if (!ts) return '';
        const match = ts.match(/(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2})/);
        return match ? `${match[1]}T${match[2]}` : ts;
      };
      const normalizedCandleTime = normalizeTs(candleTime);
      const tradeType = tradeMap.get(normalizedCandleTime);

      const isBuyPoint = tradeType === 'BUY';
      const isSellPoint = tradeType === 'SELL';

      // 🔍 디버깅: 2025-12-24 데이터 확인
      if (candleTime.startsWith('2025-12-24T02:') || candleTime.startsWith('2025-12-24T03:') || candleTime.startsWith('2025-12-24T04:')) {
        console.log('🔍 [chartData] 2025-12-24 데이터 생성:', {
          candleTime,
          normalizedCandleTime,
          tradeType,
          isBuyPoint,
          isSellPoint,
          candle_isBuyPoint: candle.isBuyPoint,
          candle_isSellPoint: candle.isSellPoint,
        });
      }

      return {
        // 백엔드 응답 데이터를 먼저 spread (MacdResultDto 형식)
        ...indicatorAny,
        // ✅ 캔들 시간 정보를 나중에 설정하여 indicatorAny의 date/timestamp를 덮어씀
        time: candleTime, // IndicatorChart를 위한 time 필드
        date: candleTime,
        timestamp: candleTime,
        originalDate: candleTime,
        originalTimestamp: candleTime,
        open: candle.open,
        high: candle.high,
        low: candle.low,
        close: candle.close,
        volume: candle.volume || 0,
        volumeColor: candle.close >= candle.open ? '#ef4444' : '#2196f3', // 상승: 빨강, 하락: 파랑
        signal: isBuyPoint ? 'BUY' : isSellPoint ? 'SELL' : undefined,
        // ✅ CandlestickChart에서 마커 클릭 시 사용 (backtestResult 기준으로 설정)
        isBuyPoint,
        isSellPoint,
      };
    });

    // 디버그: 데이터 범위 및 MACD 데이터 확인
    console.log(`✅ [chartData 재계산] 범위: 첫번째=${result[0]?.date}, 마지막=${result[result.length - 1]?.date}, 총 ${result.length}개`);
    console.log(`📊 candleData(원본) 범위: 첫번째=${candleData[0]?.time}, 마지막=${candleData[candleData.length - 1]?.time}, 총 ${candleData.length}개`);
    console.log(`📊 indicatorMap 크기: ${indicatorMap.size}, indicatorData 길이: ${indicatorData.length}`);

    // 매칭 성공 확인
    if (sortedData.length > 0) {
      const firstMatch = indicatorMap.has(sortedData[0].time);
      const lastMatch = indicatorMap.has(sortedData[sortedData.length - 1].time);
      console.log(`📊 첫 candle 매칭: ${firstMatch ? '✅' : '❌'}, 마지막 candle 매칭: ${lastMatch ? '✅' : '❌'}`);
    }

    // MACD 데이터 샘플 확인
    const sampleWithMacd = result.find(d => d.macdLine !== undefined);
    if (sampleWithMacd) {
      console.log(`✅ MACD 데이터 존재:`, {
        date: sampleWithMacd.date,
        macdLine: sampleWithMacd.macdLine,
        signalLine: sampleWithMacd.signalLine,
        histogram: sampleWithMacd.histogram
      });
    } else {
      console.warn(`⚠️ MACD 데이터가 없습니다! indicatorData 길이: ${indicatorData.length}`);
      if (indicatorData.length > 0) {
        console.log(`📊 indicatorData[0] 샘플:`, indicatorData[0]);
        console.log(`📊 indicatorData[0]의 모든 키:`, Object.keys(indicatorData[0]));
      }
    }

    // ✅ 일목균형표 선행스팬이 활성화되면 미래 영역 추가
    // **핵심**: 백엔드는 len-26까지만 계산하므로 마지막 26개 캔들의 선행스팬 미래 값이 누락됨
    // 예: candles[174~199]의 선행스팬은 candles[200~225]에 저장되어야 하지만, 백엔드는 200개만 보냄
    const ichimokuSenkouEnabled = indicatorVisibilitySettings.ichimokuSenkouA || indicatorVisibilitySettings.ichimokuSenkouB;
    let finalResult = result;

    console.log(`🔍 [일목균형표] ichimokuSenkouEnabled=${ichimokuSenkouEnabled}, result.length=${result.length}`);
    console.log(`🔍 [일목균형표] senkouA=${indicatorVisibilitySettings.ichimokuSenkouA}, senkouB=${indicatorVisibilitySettings.ichimokuSenkouB}`);

    if (ichimokuSenkouEnabled && result.length > 0) {
      console.log('☁️ [chartData] 일목균형표 선행스팬 활성화 - 미래 영역 추가 시작');

      const TENKAN_PERIOD = indicatorVisibilitySettings.ichimokuTenkanPeriod || 9;
      const KIJUN_PERIOD = indicatorVisibilitySettings.ichimokuKijunPeriod || 26;
      const CHIKOU_PERIOD = indicatorVisibilitySettings.ichimokuChikouPeriod || 26;  // 후행스팬 기간
      const SENKOU_A_PERIOD = indicatorVisibilitySettings.ichimokuSenkouAPeriod || 26;  // 선행스팬1 기간
      const SENKOU_B_PERIOD = indicatorVisibilitySettings.ichimokuSenkouBPeriod || 52;
      const DISPLACEMENT = SENKOU_A_PERIOD;  // 미래 영역 확장은 선행스팬1 기간 사용

      // 시간 간격 계산
      const getIntervalMinutes = (interval: string): number => {
        const match = interval.match(/^(\d+)([mhd])$/);
        if (!match) return 60;
        const value = parseInt(match[1]);
        const unit = match[2];
        if (unit === 'm') return value;
        if (unit === 'h') return value * 60;
        if (unit === 'd') return value * 1440;
        return 60;
      };

      const intervalMinutes = getIntervalMinutes(timeInterval);

      // ✅ close가 정의된 실제 캔들의 마지막을 찾음 (미래 데이터 제외)
      const lastRealCandleIndex = result.length - 1;
      let lastCandle = result[lastRealCandleIndex];

      // 안전성 체크: close가 undefined면 실제 캔들을 역순으로 찾음
      if (lastCandle.close === undefined) {
        console.warn('⚠️ [미래 영역] 마지막 캔들의 close가 undefined - 실제 캔들 탐색');
        for (let i = result.length - 1; i >= 0; i--) {
          if (result[i].close !== undefined) {
            lastCandle = result[i];
            console.log(`✅ [미래 영역] 실제 마지막 캔들 발견: index=${i}, time=${lastCandle.time}`);
            break;
          }
        }
      }

      const lastTime = new Date(lastCandle.time);
      console.log(`✅ [미래 영역] 기준 시간: ${lastCandle.time}, intervalMinutes=${intervalMinutes}`);

      // ✅ 올바른 미래 데이터 생성 로직:
      // - 미래 시간: 마지막 캔들 이후 1시간씩 증가 (lastTime + 1시간, lastTime + 2시간, ..., lastTime + 26시간)
      // - 선행스팬 값: 해당 미래 시간보다 displacement(26개) 이전의 캔들에서 가져옴
      const futureDataPoints: any[] = [];

      for (let i = 0; i < DISPLACEMENT; i++) {
        // ✅ 미래 시간: 마지막 캔들 + (i+1) * intervalMinutes
        const futureTime = new Date(lastTime.getTime() + (i + 1) * intervalMinutes * 60 * 1000);

        // ✅ 로컬 시간 문자열로 변환 (기존 캔들 데이터 형식과 동일하게)
        const pad = (n: number) => String(n).padStart(2, '0');
        const futureTimeStr = `${futureTime.getFullYear()}-${pad(futureTime.getMonth() + 1)}-${pad(futureTime.getDate())}T${pad(futureTime.getHours())}:${pad(futureTime.getMinutes())}:00`;

        // ✅ 선행스팬 값의 소스 캔들 인덱스: 현재 미래 포인트로부터 displacement(26개) 이전
        // 미래[0] → source[결과 길이 - 26 + 0] = source[174] (마지막 캔들이 199일 때)
        // 미래[1] → source[결과 길이 - 26 + 1] = source[175]
        // ...
        // 미래[25] → source[결과 길이 - 26 + 25] = source[199]
        const sourceIndex = result.length - DISPLACEMENT + i;

        if (sourceIndex < 0 || sourceIndex >= result.length) continue;

        const sourceCandle = result[sourceIndex];

        // 선행스팬1 계산: (전환선 + 기준선) / 2
        let senkouA: number | undefined = undefined;
        if (sourceCandle.ichimokuTenkan !== undefined && sourceCandle.ichimokuKijun !== undefined) {
          senkouA = (sourceCandle.ichimokuTenkan + sourceCandle.ichimokuKijun) / 2;
        } else if (i === 0) {
          console.warn(`⚠️ [미래 데이터] sourceCandle[${sourceIndex}]에 전환선/기준선 없음:`, {
            tenkan: sourceCandle.ichimokuTenkan,
            kijun: sourceCandle.ichimokuKijun,
            time: sourceCandle.time
          });
        }

        // 선행스팬2 계산: 최근 52일 고저의 중간값
        let senkouB: number | undefined = undefined;
        if (sourceIndex >= SENKOU_B_PERIOD - 1) {
          let highestHigh = -Infinity;
          let lowestLow = Infinity;

          for (let j = 0; j < SENKOU_B_PERIOD; j++) {
            const idx = sourceIndex - j;
            if (idx >= 0 && idx < result.length) {
              const candle = result[idx];
              if (candle.high !== undefined) highestHigh = Math.max(highestHigh, candle.high);
              if (candle.low !== undefined) lowestLow = Math.min(lowestLow, candle.low);
            }
          }

          if (highestHigh !== -Infinity && lowestLow !== Infinity) {
            senkouB = (highestHigh + lowestLow) / 2;
          }
        }

        // ✅ 선행스팬 값이 둘 다 있고, 유효한 숫자인 경우에만 미래 포인트 추가
        const hasSenkouA = senkouA !== undefined && senkouA !== null && !isNaN(senkouA) && isFinite(senkouA) && senkouA > 0;
        const hasSenkouB = senkouB !== undefined && senkouB !== null && !isNaN(senkouB) && isFinite(senkouB) && senkouB > 0;

        if (hasSenkouA || hasSenkouB) {
          futureDataPoints.push({
            time: futureTimeStr,
            date: futureTimeStr,
            timestamp: futureTimeStr,
            // 캔들 데이터는 undefined (미래 영역이므로)
            open: undefined,
            high: undefined,
            low: undefined,
            close: undefined,
            volume: undefined,
            // 일목균형표 선행스팬만 포함 (undefined가 아닌 경우에만 포함)
            ...(hasSenkouA && { ichimokuSenkouA: senkouA }),
            ...(hasSenkouB && { ichimokuSenkouB: senkouB }),
            // 다른 일목균형표 선은 미래에 표시하지 않음
            ichimokuTenkan: undefined,
            ichimokuKijun: undefined,
            ichimokuChikou: undefined,
            // ✅ 모든 보조지표 필드를 undefined로 명시적으로 추가 (시간 축 동기화를 위해)
            macdLine: undefined,
            macdSignal: undefined,
            macdHistogram: undefined,
            rsi: undefined,
            stochasticK: undefined,
            stochasticD: undefined,
            cci: undefined,
            cci13: undefined,
            cci14: undefined,
            adx: undefined,
            plusDI: undefined,
            minusDI: undefined,
            obv: undefined,
            trix: undefined,
            trixSignal: undefined,
            williamsR: undefined,
            newPsy: undefined,
            investPsy: undefined,
            disparity: undefined,
            disparityUltraShort: undefined,
            disparityShort: undefined,
            disparityMid: undefined,
            disparityLong: undefined,
            bwi: undefined,
            volumeOsc: undefined,
            vr: undefined,
          });

          if (i < 3) {
            console.log(`☁️ [미래 ${i + 1}] lastTime=${lastCandle.time?.slice(0, 16)} + ${(i + 1) * intervalMinutes}분 → future=${futureTimeStr}, source[${sourceIndex}]=${sourceCandle.time?.slice(0, 16)}, senkouA=${senkouA?.toFixed(0)}, senkouB=${senkouB?.toFixed(0)}`);
          }
        } else if (i < 3) {
          console.warn(`⚠️ [미래 ${i + 1}] 유효한 선행스팬 값 없음: senkouA=${senkouA}, senkouB=${senkouB}`);
        }
      }

      if (futureDataPoints.length > 0) {
        finalResult = [...result, ...futureDataPoints];
        console.log(`☁️ [chartData] 미래 영역 추가 완료: ${result.length}개 → ${finalResult.length}개 (미래 ${futureDataPoints.length}개 추가)`);
        console.log(`☁️ [미래 영역 범위] 실제 데이터: ${result[0].time} ~ ${result[result.length - 1].time}`);
        console.log(`☁️ [미래 영역 범위] 미래 데이터: ${futureDataPoints[0].time} ~ ${futureDataPoints[futureDataPoints.length - 1].time}`);
        console.log(`☁️ [미래 영역 범위] 전체 범위: ${result[0].time} ~ ${futureDataPoints[futureDataPoints.length - 1].time}`);

        // ✅ 디버깅: 미래 데이터의 실제 시간 값 확인 (밀리초 타임스탬프로 변환)
        const resultLastTime = new Date(result[result.length - 1].time).getTime();
        const futureFirstTime = new Date(futureDataPoints[0].time).getTime();
        const futureLastTime = new Date(futureDataPoints[futureDataPoints.length - 1].time).getTime();
        console.log(`🔍 [미래 데이터 검증] 실제 마지막 캔들: ${resultLastTime} (${new Date(resultLastTime).toISOString()})`);
        console.log(`🔍 [미래 데이터 검증] 미래 첫번째: ${futureFirstTime} (${new Date(futureFirstTime).toISOString()})`);
        console.log(`🔍 [미래 데이터 검증] 미래 마지막: ${futureLastTime} (${new Date(futureLastTime).toISOString()})`);
        console.log(`🔍 [미래 데이터 검증] 시간 순서 확인: 미래 첫번째 ${futureFirstTime > resultLastTime ? '>' : '<='} 실제 마지막`);
      } else {
        console.warn(`⚠️ [chartData] 선행스팬 계산 실패 - 미래 영역 추가 불가`);
      }
    }

    // ✅ 전체 데이터 반환 (차트 자체 줌 기능 사용)
    console.log(`📊 [chartData] 전체: ${finalResult.length}개 (차트 자체 줌 사용)`);

    // ✅ 정상 상태: 최신 데이터 사용 및 저장
    // ⚠️ loading 또는 isLoadingHistory가 false일 때만 저장 (로딩 중에는 이전 데이터 유지)
    if (!loading && !isLoadingHistory && finalResult.length > 0) {
      previousChartDataRef.current = finalResult;
    }

    return finalResult;
  }, [
    candleData,
    indicatorData,
    backtestResult,
    loading,
    isLoadingHistory,
    indicatorVisibilitySettings.ichimokuSenkouA,
    indicatorVisibilitySettings.ichimokuSenkouB,
    indicatorVisibilitySettings.ichimokuTenkanPeriod,
    indicatorVisibilitySettings.ichimokuKijunPeriod,
    indicatorVisibilitySettings.ichimokuSenkouBPeriod,
    timeInterval
  ]);

  // ✅ 보조지표용 데이터: 실제 캔들 데이터만 사용 (미래 영역 제외)
  // X축 범위는 차트 동기화로 처리
  // ✅ indicatorChartData는 chartData와 완전히 동일하게 사용하여 X축 범위를 완벽히 동기화
  // - 일목균형표 활성화 시: 미래 영역 포함 (보조지표 값은 undefined이므로 그래프 안 그려짐)
  // - 일목균형표 비활성화 시: 실제 데이터만 포함
  const indicatorChartData = useMemo(() => {
    console.log(`📊 [보조지표 데이터] chartData 그대로 사용: ${chartData.length}개 (X축 완벽 동기화)`);
    if (chartData.length > 0) {
      const lastData = chartData[chartData.length - 1];
      console.log(`   마지막 데이터 지표:`, {
        time: lastData.time,
        rsi: lastData.rsi,
        macdLine: lastData.macdLine,
        ma5: lastData.ma5,
        ma20: lastData.ma20,
      });
    }
    return chartData;
  }, [chartData]);

  // ✅ 보조지표 데이터는 실제 캔들 데이터만 사용 (미래 영역 제외)
  // indicatorChartData에서 필터링하여 전달됨

  // ✅ 현재 표시 중인 날짜 범위 계산 (단순화)
  // ✅ chartData 기준으로 계산 (미래 영역 포함)
  const visibleDateRange = useMemo(() => {
    if (chartData.length === 0 || sharedViewRange.start >= chartData.length) {
      return { start: null, end: null };
    }

    // chartData를 시간순으로 정렬 (미래 영역 포함)
    const sortedData = [...chartData].sort((a, b) =>
      new Date(a.time || a.date).getTime() - new Date(b.time || b.date).getTime()
    );

    const startIndex = Math.max(0, sharedViewRange.start);
    const endIndex = Math.min(sharedViewRange.end - 1, sortedData.length - 1);

    return {
      start: sortedData[startIndex]?.time || sortedData[startIndex]?.date || null,
      end: sortedData[endIndex]?.time || sortedData[endIndex]?.date || null,
    };
  }, [chartData, sharedViewRange]);


  // 시간봉에 따른 갱신 간격(분) 계산
  const getUpdateIntervalMinutes = useCallback((interval: string): number => {
    const match = interval.match(/^(\d+)/);
    if (match) {
      return parseInt(match[1]);
    }
    return 1;
  }, []);

  // 다음 갱신 시간 계산
  const calculateNextUpdateTime = useCallback((lastTime: Date | null, interval: string): Date => {
    const now = new Date();
    const intervalMinutes = getUpdateIntervalMinutes(interval);

    if (!lastTime) {
      const next = new Date(now);
      const currentMinutes = next.getMinutes();
      const nextMinutes = Math.ceil((currentMinutes + 1) / intervalMinutes) * intervalMinutes;
      next.setMinutes(nextMinutes);
      next.setSeconds(0);
      next.setMilliseconds(0);
      return next;
    }

    const next = new Date(lastTime);
    next.setMinutes(next.getMinutes() + intervalMinutes);
    return next;
  }, [getUpdateIntervalMinutes]);

  // 갱신 시간이 되었는지 확인
  const shouldUpdateNow = useCallback((lastTime: Date | null, interval: string): boolean => {
    const now = new Date();
    const intervalMinutes = getUpdateIntervalMinutes(interval);

    if (!lastTime) {
      return true;
    }

    const elapsedMinutes = Math.floor((now.getTime() - lastTime.getTime()) / (1000 * 60));
    return elapsedMinutes >= intervalMinutes;
  }, [getUpdateIntervalMinutes]);

  // ✅ 알림 토글 핸들러
  const handleToggleAlert = async () => {
    try {
      setLoadingAlert(true);
      
      if (alertId) {
        // 기존 알림이 있으면 토글
        await toggleAlert(alertId, !alertEnabled);
        setAlertEnabled(!alertEnabled);
      } else {
        // 알림이 없으면 새로 생성
        const newAlert = await createAlert({
          symbol: symbol,
          koreanName: symbol, // symbol을 기본값으로 사용
          englishName: symbol, // symbol을 기본값으로 사용
          enabled: true,
        });
        setAlertId(newAlert.id || null);
        setAlertEnabled(true);
      }
      // 알림 ON 시 스케줄 자동 활성화 (알림이 동작하도록)
      if (!alertEnabled) {
        try {
          const config = await getScheduleConfig();
          if (!config.enabled) {
            await saveScheduleConfig({ ...config, enabled: true });
          }
        } catch (e) {
          console.warn('스케줄 설정 저장 실패:', e);
        }
      }
      // 알림 설정 화면과 동기화: cryptoAlertChanged 이벤트 dispatch
      window.dispatchEvent(new CustomEvent('cryptoAlertChanged', { detail: { symbol } }));
    } catch (error) {
      console.error('알림 토글 실패:', error);
      alert('알림 설정에 실패했습니다.');
    } finally {
      setLoadingAlert(false);
    }
  };

  // 데이터 로드 함수
  // forceReset: true이면 초기 로드로 간주하고 차트 범위를 전체로 리셋
  // isPrepend: true이면 이전 기간 데이터를 기존 데이터 앞에 추가
  // isAppend: true이면 다음 기간 데이터를 기존 데이터 뒤에 추가
  // isIncrementalUpdate: true이면 최신 데이터만 가져오고 워밍업 스킵 (자동 갱신용)
  const loadData = useCallback(async (forceReset: boolean = false, toTime: string | null = null, isPrepend: boolean = false, isAppend: boolean = false, isIncrementalUpdate: boolean = false, isPreloadForBacktest: boolean = false) => {
    // ✅ 동시 실행 방지: 이미 loadData가 진행 중이면 대기 요청으로 저장 후 스킵
    if (loadDataInProgressRef.current && !isIncrementalUpdate) {
      console.warn(`⚠️ [loadData] 이미 로딩 중 — 요청 대기 등록 (forceReset=${forceReset}, toTime=${toTime})`);
      // forceReset이 true인 요청(심볼/시간봉 변경)은 우선 저장해둬서 완료 후 재실행
      if (forceReset || pendingLoadDataRef.current === null) {
        pendingLoadDataRef.current = { forceReset, toTime };
      }
      return;
    }
    loadDataInProgressRef.current = true;

    setLoading(true);
    setError(null);
    
    // ✅ 성능 최적화: 개발 모드에서만 상세 로그 출력
    const isDev = process.env.NODE_ENV === 'development';

    // ✅ 항상 최신 timeInterval 값 사용 (클로저 문제 방지)
    const currentTimeInterval = timeIntervalRef.current;
    console.log(`📊 [loadData] 현재 시간봉: ${currentTimeInterval} (state: ${timeInterval})`);

    // ★ 초기 로드 시에만 hasInitialData를 false로 리셋 (차트 범위 리셋 허용)
    if (forceReset) {
      setHasInitialData(false);
      setLoadToTime(null); // 초기 로드 시 toTime 초기화
      isInitialLoadDoneRef.current = false; // ✅ 초기 로드 플래그 리셋 (차트 동기화 다시 허용)
    }

    try {
      // 백엔드 프록시를 통해 업비트 캔들 데이터 조회 (CORS 우회)
      // ✅ 1단계: 최신 200개 캔들 요청
      const displayPeriod = getDisplayPeriodDescription(currentTimeInterval, CHART_DISPLAY_COUNT);

      if (isDev) {
        console.log(`📊 [1단계] 최신 ${CHART_DISPLAY_COUNT}개 캔들 요청 = ${displayPeriod}`);
        console.log(`📊 [데이터 로드] 시간봉: ${currentTimeInterval}, toTime: ${toTime || '최신'}, isPrepend: ${isPrepend}, isAppend: ${isAppend}, isIncrementalUpdate: ${isIncrementalUpdate}`);
      }

      // ✅ 증분 업데이트 시에는 최신 몇 개만 요청 (기존 데이터와 겹치지 않도록)
      // ✅ 기간 이동(toTime 지정) 또는 백테스트 사전 로드 시에는 워밍업 데이터 포함
      const needsWarmup = (toTime !== null && !isIncrementalUpdate) || isPreloadForBacktest;
      const requestCount = isIncrementalUpdate 
        ? 10 
        : (needsWarmup ? CHART_DISPLAY_COUNT + INDICATOR_WARMUP_COUNT : CHART_DISPLAY_COUNT); // 증분: 10개, 워밍업 필요: 400개, 일반: 200개
      
      if (needsWarmup) {
        console.log(`📊 [워밍업 포함] ${requestCount}개 요청 (표시용 ${CHART_DISPLAY_COUNT}개 + 워밍업 ${INDICATOR_WARMUP_COUNT}개)`);
      }
      
      // ✅ 증분 업데이트 플래그 설정 (chartData useEffect 실행 방지)
      if (isIncrementalUpdate) {
        isIncrementalUpdateInProgressRef.current = true;
        console.log(`🔒 [증분 업데이트] 플래그 설정 - chartData useEffect 차단`);
      }
      
      const mainParams: any = {
        market: symbol,
        timeframe: currentTimeInterval,
        count: requestCount,
      };
      
      if (isIncrementalUpdate) {
        console.log(`📊 [증분 업데이트] 최신 ${requestCount}개만 요청 (기존 데이터 유지)`);
      }

      // toTime이 지정되면 해당 시간 이전의 데이터를 가져옴
      if (toTime) {
        mainParams.to = toTime;
        if (isDev) console.log(`🔍 [메인 요청] to 파라미터 설정: ${toTime}`);
      } else {
        if (isDev) console.log(`🔍 [메인 요청] to 파라미터 없음 (최신 데이터 요청)`);
      }

      if (isDev) console.log(`🌐 [API 요청] ${API_BASE_URL}/pattern/candles`, mainParams);
      const mainResponse = await axios.get(`${API_BASE_URL}/pattern/candles`, {
        params: mainParams,
      });

      // ✅ API 응답 실패 처리 (success: false)
      if (mainResponse.data && mainResponse.data.success === false) {
        console.error(`❌ [1단계] API 오류 응답:`, mainResponse.data.error);
        throw new Error(mainResponse.data.error || `${symbol} ${currentTimeInterval} 데이터를 가져올 수 없습니다.`);
      }

      const mainCandleDataList = mainResponse.data.data || [];

      // 업비트 API는 최신 데이터가 먼저 오므로 시간순으로 정렬 (과거 → 현재)
      const sortedMainCandles = [...mainCandleDataList].sort((a: any, b: any) => {
        return new Date(a.time).getTime() - new Date(b.time).getTime();
      });

      const mainCandles: CandleData[] = sortedMainCandles.map((candle: any) => ({
        time: candle.time,
        open: candle.open,
        high: candle.high,
        low: candle.low,
        close: candle.close,
        volume: candle.volume,
        isBuyPoint: false,
        isSellPoint: false,
      }));

      console.log(`✅ [1단계 완료] ${mainCandles.length}개 캔들 수신`);

      // ✅ 데이터 검증: 초기 데이터가 없으면 에러 처리
      if (mainCandles.length === 0 && !isIncrementalUpdate) {
        console.error(`❌ 초기 데이터 로드 실패: ${symbol} ${timeInterval} 데이터가 없습니다.`);
        throw new Error(`${symbol} ${timeInterval} 데이터를 가져올 수 없습니다. 백엔드 API를 확인해주세요.`);
      }

      // ✅ needsWarmup이 true인 경우 mainCandles에 워밍업 데이터가 이미 포함되어 있음
      // → 앞 200개: 워밍업용, 뒤 200개: 표시용으로 분리
      let actualMainCandles: CandleData[] = mainCandles;
      let embeddedWarmupCandles: CandleData[] = [];
      
      if (needsWarmup && mainCandles.length > CHART_DISPLAY_COUNT) {
        embeddedWarmupCandles = mainCandles.slice(0, mainCandles.length - CHART_DISPLAY_COUNT);
        actualMainCandles = mainCandles.slice(mainCandles.length - CHART_DISPLAY_COUNT);
        console.log(`📊 [1-워밍업 분리] mainCandles ${mainCandles.length}개 → 워밍업 ${embeddedWarmupCandles.length}개 + 표시용 ${actualMainCandles.length}개`);
      }

      // ✅ 2단계: 워밍업 데이터 요청 (첫 번째 캔들 이전의 200개)
      // ⚠️ 증분 업데이트 시 또는 needsWarmup이 true인 경우에는 워밍업 데이터를 로드하지 않음
      let warmupCandles: CandleData[] = [];

      if (mainCandles.length > 0 && !isIncrementalUpdate && !needsWarmup) {
        const firstCandleTime = mainCandles[0].time; // ISO 문자열 형식
        console.log(`📊 [2단계] 워밍업 ${INDICATOR_WARMUP_COUNT}개 캔들 요청 (to=${firstCandleTime})`);
        console.log(`   → MA200, EMA200, 장기 MACD 등 모든 지표 설정에 대응`);

        const warmupParams: any = {
          market: symbol,
          timeframe: currentTimeInterval,
          count: INDICATOR_WARMUP_COUNT, // 200개 (업비트 API 최대값)
          to: firstCandleTime, // 첫 번째 캔들 이전 데이터
        };

        try {
          const warmupResponse = await axios.get(`${API_BASE_URL}/pattern/candles`, {
            params: warmupParams,
          });

          const warmupCandleDataList = warmupResponse.data.data || [];
          const sortedWarmupCandles = [...warmupCandleDataList].sort((a: any, b: any) => {
            return new Date(a.time).getTime() - new Date(b.time).getTime();
          });

          warmupCandles = sortedWarmupCandles.map((candle: any) => ({
            time: candle.time,
            open: candle.open,
            high: candle.high,
            low: candle.low,
            close: candle.close,
            volume: candle.volume,
            isBuyPoint: false,
            isSellPoint: false,
          }));

          console.log(`✅ [2단계 완료] ${warmupCandles.length}개 워밍업 캔들 수신`);
        } catch (warmupErr) {
          console.warn('⚠️ 워밍업 데이터 요청 실패 (메인 데이터만 사용):', warmupErr);
        }
      }

      // ✅ 3단계: MA/Ichimoku 계산용 추가 데이터 백그라운드 로드 (동적 계산)
      // ⚠️ 증분 업데이트 시에는 추가 데이터를 로드하지 않음 (기존 데이터 활용)
      let additionalCandles: CandleData[] = [];

      if (!isIncrementalUpdate) {
        // 현재 활성화된 MA 중 최대 period 계산
        const enabledMAPeriods = indicatorVisibilitySettings.maLines
          ?.filter(ma => ma.enabled)
          .map(ma => ma.period) || [];
        const maxMAPeriod = enabledMAPeriods.length > 0 ? Math.max(...enabledMAPeriods) : 0;

        // 일목균형표 최대 period (선행스팬B: 52일 기본)
        const ichimokuMaxPeriod = (
          indicatorVisibilitySettings.ichimokuSenkouA ||
          indicatorVisibilitySettings.ichimokuSenkouB ||
          indicatorVisibilitySettings.ichimokuKijun ||
          indicatorVisibilitySettings.ichimokuTenkan
        ) ? (indicatorVisibilitySettings.ichimokuSenkouBPeriod || 52) : 0;

        // MA와 일목균형표 중 최대값
        const MAX_INDICATOR_PERIOD = Math.max(maxMAPeriod, ichimokuMaxPeriod);
        const REQUIRED_TOTAL = MAX_INDICATOR_PERIOD > 0
          ? CHART_DISPLAY_COUNT + MAX_INDICATOR_PERIOD
          : CHART_DISPLAY_COUNT + INDICATOR_WARMUP_COUNT; // 기본값

        const currentTotal = warmupCandles.length + mainCandles.length;

        console.log(`📊 [3단계] 필요 데이터 계산: MA최대=${maxMAPeriod}, 일목최대=${ichimokuMaxPeriod}, 필요총계=${REQUIRED_TOTAL}, 현재=${currentTotal}`);

        if (currentTotal < REQUIRED_TOTAL) {
        const remainingNeeded = REQUIRED_TOTAL - currentTotal;
        console.log(`📊 [3단계] 추가 데이터 로드 시작 (부족: ${remainingNeeded}개)`);

        // ✅ 안전 가드: 데이터가 없으면 추가 로드하지 않음
        if (warmupCandles.length === 0 && mainCandles.length === 0) {
          console.warn(`⚠️ [3단계 스킵] 초기 데이터가 없어 추가 로드 불가`);
        } else {
          let lastTime = warmupCandles.length > 0 ? warmupCandles[0].time : mainCandles[0].time;
          let totalAdditionalLoaded = 0;
          const maxRequests = Math.ceil(remainingNeeded / 200) + 1; // 필요한 만큼 요청 (최대 5회)

          for (let i = 0; i < Math.min(maxRequests, 5) && totalAdditionalLoaded < remainingNeeded; i++) {
            const requestCount = Math.min(200, remainingNeeded - totalAdditionalLoaded);
            console.log(`📊 [3-${i + 1}단계] ${requestCount}개 추가 캔들 요청 (to=${lastTime})`);

            const additionalParams: any = {
              market: symbol,
              timeframe: currentTimeInterval,
              count: requestCount,
              to: lastTime,
            };

            try {
              const additionalResponse = await axios.get(`${API_BASE_URL}/pattern/candles`, {
                params: additionalParams,
              });

              const additionalCandleDataList = additionalResponse.data.data || [];
              if (additionalCandleDataList.length === 0) {
                console.log(`⚠️ [3-${i + 1}단계] 더 이상 과거 데이터 없음`);
                break;
              }

              const sortedAdditionalCandles = [...additionalCandleDataList].sort((a: any, b: any) => {
                return new Date(a.time).getTime() - new Date(b.time).getTime();
              });

              const newCandles = sortedAdditionalCandles.map((candle: any) => ({
                time: candle.time,
                open: candle.open,
                high: candle.high,
                low: candle.low,
                close: candle.close,
                volume: candle.volume,
                isBuyPoint: false,
                isSellPoint: false,
              }));

              additionalCandles = [...newCandles, ...additionalCandles]; // 앞에 추가
              totalAdditionalLoaded += newCandles.length;
              lastTime = newCandles[0].time; // 다음 요청의 기준 시간

              console.log(`✅ [3-${i + 1}단계 완료] ${newCandles.length}개 추가 캔들 수신 (누적: ${totalAdditionalLoaded}개)`);
            } catch (additionalErr) {
              console.warn(`⚠️ [3-${i + 1}단계] 추가 데이터 요청 실패:`, additionalErr);
              break;
            }
          }

          console.log(`✅ [3단계 완료] 총 ${additionalCandles.length}개 추가 캔들 로드 (전체: ${additionalCandles.length + currentTotal}개로 MA${maxMAPeriod} 전체 표시 가능)`);
        }
        } else {
          console.log(`📊 [3단계 스킵] 현재 ${currentTotal}개 데이터로 충분 (필요: ${REQUIRED_TOTAL}개)`);
        }
      } else {
        console.log(`📊 [3단계 스킵] 증분 업데이트 - 추가 데이터 로드 안 함`);
      }

      // ✅ 4단계: 모든 데이터 결합 (지표 계산용)
      let allCandles: CandleData[];
      let maCalculationCandles: CandleData[]; // 원본 캔들 데이터
      
      if (isIncrementalUpdate && maCalculationDataRef.current.length > 0) {
        // 증분 업데이트: 기존 계산 데이터 + 새 메인 캔들
        console.log(`📊 [4단계] 증분 업데이트 - 기존 데이터 ${maCalculationDataRef.current.length}개 + 새 캔들 ${actualMainCandles.length}개`);
        allCandles = [...maCalculationDataRef.current, ...actualMainCandles];
        maCalculationCandles = allCandles;
      } else {
        // 일반 로드: 추가 + embeddedWarmup + 워밍업 + 메인
        allCandles = [...additionalCandles, ...embeddedWarmupCandles, ...warmupCandles, ...actualMainCandles];
        maCalculationCandles = allCandles;
        console.log(`📊 [4단계] 지표 계산용 데이터: 추가 ${additionalCandles.length}개 + 임베디드워밍업 ${embeddedWarmupCandles.length}개 + 워밍업 ${warmupCandles.length}개 + 메인 ${actualMainCandles.length}개 = 총 ${allCandles.length}개`);
        console.log(`   → 총 ${allCandles.length}개로 정확한 지표 계산 (MA480, EMA200, 장기 MACD 등 모든 설정 대응)`);
      }

      // ✅ 5단계: 보조지표 계산 (모든 데이터 포함하여 정확하게 계산)
      console.log(`📊 [5단계] 보조지표 계산 시작 (${allCandles.length}개 캔들 기준)`);
      const allIndicatorData = await loadIndicatorDataSync(allCandles, false);
      console.log(`✅ [5단계 완료] ${allIndicatorData.length}개 지표 데이터 계산 완료`);

      // ✅ 6단계: 메인 200개에 해당하는 지표 데이터만 추출
      let displayCandles: CandleData[];
      let displayIndicators: IndicatorData[];
      
      if (isIncrementalUpdate && maCalculationDataRef.current.length > 0) {
        // 증분 업데이트: 새로운 actualMainCandles만 표시 (나중에 병합됨)
        displayCandles = actualMainCandles;
        // 전체 지표 데이터에서 새 캔들에 해당하는 부분만 추출
        const skipCount = maCalculationDataRef.current.length;
        displayIndicators = allIndicatorData.slice(skipCount);
        console.log(`📊 [6단계] 증분 업데이트 - 새 캔들: ${displayCandles.length}개, 새 지표: ${displayIndicators.length}개 (skipCount: ${skipCount})`);
      } else {
        // 일반 로드: 추가+embeddedWarmup+워밍업 제외, 메인 200개만 표시
        displayCandles = actualMainCandles;
        const skipCount = additionalCandles.length + embeddedWarmupCandles.length + warmupCandles.length;
        displayIndicators = allIndicatorData.slice(skipCount);
        console.log(`📊 [6단계] 차트 표시용 데이터: ${displayCandles.length}개 캔들, ${displayIndicators.length}개 지표`);
        console.log(`   → 화면 표시: ${actualMainCandles[0]?.time} ~ ${actualMainCandles[actualMainCandles.length - 1]?.time}`);
        
        // ✅ WebSocket 비교용: 마지막 캔들 시간을 Unix timestamp로 출력
        if (actualMainCandles.length > 0) {
          const lastCandle = actualMainCandles[actualMainCandles.length - 1];
          const lastTime = new Date(lastCandle.time).getTime() / 1000;
          console.log(`   → REST API 마지막 캔들: ${lastCandle.time} (Unix: ${Math.floor(lastTime)})`);
        }
        
        console.log(`   → 추가 ${additionalCandles.length}개 + 임베디드워밍업 ${embeddedWarmupCandles.length}개 + 워밍업 ${warmupCandles.length}개는 MA/지표 계산에만 사용됨`);
        console.log(`   → 지표 슬라이스: allIndicatorData[${skipCount}:] (전체: ${allIndicatorData.length}개) = ${displayIndicators.length}개`);
      }

      // ✅ 전체 지표 데이터 저장 (차트 MA 렌더링용 400개)
      setAllIndicatorData(allIndicatorData);
      allIndicatorDataRef.current = allIndicatorData;
      
      // ✅ 모든 데이터 준비 완료 후 한번에 state 업데이트 (변경된 경우에만 업데이트)
      // React 18의 자동 배칭을 활용하여 여러 state 업데이트를 하나의 리렌더링으로 처리
      
      // 효율적인 데이터 변경 감지: 길이와 처음/마지막 항목만 비교 (JSON.stringify는 성능 저하 유발)
      const isCandleDataChanged = (prev: CandleData[], next: CandleData[]) => {
        if (prev.length !== next.length) return true;
        if (prev.length === 0) return false;
        const firstChanged = prev[0]?.time !== next[0]?.time || prev[0]?.close !== next[0]?.close;
        const lastChanged = prev[prev.length - 1]?.time !== next[next.length - 1]?.time || prev[prev.length - 1]?.close !== next[next.length - 1]?.close;
        return firstChanged || lastChanged;
      };
      
      const isIndicatorDataChanged = (prev: IndicatorData[], next: IndicatorData[]) => {
        if (prev.length !== next.length) return true;
        if (prev.length === 0) return false;
        const firstChanged = prev[0]?.rsi !== next[0]?.rsi || prev[0]?.macdLine !== next[0]?.macdLine;
        const la = prev[prev.length - 1] as any;
        const lb = next[next.length - 1] as any;
        const lastChanged =
          la?.rsi !== lb?.rsi ||
          la?.macdLine !== lb?.macdLine ||
          la?.macdHistogram !== lb?.macdHistogram ||
          la?.stochK !== lb?.stochK ||
          la?.stochasticK !== lb?.stochasticK ||
          la?.cci !== lb?.cci ||
          la?.adx !== lb?.adx ||
          la?.plusDI !== lb?.plusDI ||
          la?.minusDI !== lb?.minusDI ||
          la?.trix !== lb?.trix ||
          la?.momentum !== lb?.momentum ||
          la?.volumeOsc !== lb?.volumeOsc ||
          la?.vr !== lb?.vr ||
          la?.bwi !== lb?.bwi ||
          la?.newPsychological !== lb?.newPsychological ||
          la?.newPsy !== lb?.newPsy ||
          la?.investPsychological !== lb?.investPsychological ||
          la?.investPsy !== lb?.investPsy;
        return firstChanged || lastChanged;
      };
      
      // ✅ 증분 업데이트: 기존 데이터에 새로운 데이터만 추가
      let finalDisplayCandles = displayCandles;
      let finalDisplayIndicators = displayIndicators;
      let finalMaCalculationCandles = maCalculationCandles;

      if (isIncrementalUpdate && candleDataRef.current.length > 0) {
        console.log(`🔄 [증분 업데이트] 기존 캔들 ${candleDataRef.current.length}개에 새 데이터 병합 시작`);
        
        // 기존 데이터의 마지막 시간 (ref에서 가져옴)
        const lastExistingTime = candleDataRef.current[candleDataRef.current.length - 1]?.time;
        console.log(`📊 [증분 업데이트] 기존 마지막 시간: ${lastExistingTime}`);
        
        // 새로운 캔들 중 기존에 없는 것만 필터링 (시간이 더 나중인 것)
        const newCandles = displayCandles.filter(candle => {
          return candle.time > lastExistingTime;
        });
        
        console.log(`📊 [증분 업데이트] 새로운 캔들: ${newCandles.length}개`);
        
        if (newCandles.length > 0) {
          // 기존 데이터 + 새 데이터 병합 (ref 사용)
          finalDisplayCandles = [...candleDataRef.current, ...newCandles];
          
          // 보조지표도 동일하게 병합 (ref 사용)
          const newIndicators = displayIndicators.slice(-newCandles.length);
          finalDisplayIndicators = [...indicatorDataRef.current, ...newIndicators];
          
          // MA 계산 데이터도 병합 (ref 사용)
          const newMaCandles = maCalculationCandles.filter(candle => {
            const existingMaLastTime = maCalculationDataRef.current[maCalculationDataRef.current.length - 1]?.time;
            return candle.time > existingMaLastTime;
          });
          finalMaCalculationCandles = [...maCalculationDataRef.current, ...newMaCandles];
          
          console.log(`✅ [증분 업데이트] 병합 완료: 캔들 ${finalDisplayCandles.length}개 (기존 ${candleDataRef.current.length} + 신규 ${newCandles.length})`);
        } else {
          console.log(`⏭️ [증분 업데이트] 새로운 캔들 없음, 기존 데이터 유지`);
          // 새 데이터가 없으면 기존 데이터 유지 (ref 사용)
          finalDisplayCandles = candleDataRef.current;
          finalDisplayIndicators = indicatorDataRef.current;
          finalMaCalculationCandles = maCalculationDataRef.current;
        }
      }

      const candleDataChanged = isCandleDataChanged(candleData, finalDisplayCandles);
      const indicatorDataChanged = isIndicatorDataChanged(indicatorData, finalDisplayIndicators);
      const maDataChanged = isCandleDataChanged(maCalculationData, finalMaCalculationCandles);
      
      if (candleDataChanged || indicatorDataChanged || maDataChanged || forceReset) {
        console.log(`🎯 [차트 데이터 업데이트] 업데이트 시작 (캔들: ${candleDataChanged}, 지표: ${indicatorDataChanged}, MA: ${maDataChanged})`);
        
        // 모든 상태 업데이트를 한번에 처리 (React 18 자동 배칭)
        setCandleData(finalDisplayCandles);
        setIndicatorData(finalDisplayIndicators);
        setMaCalculationData(finalMaCalculationCandles);
        
        // ✅ ref도 동기화 (증분 업데이트용)
        candleDataRef.current = finalDisplayCandles;
        indicatorDataRef.current = finalDisplayIndicators;
        maCalculationDataRef.current = finalMaCalculationCandles;
        
        console.log(`✅ [차트 데이터 업데이트] 완료 (캔들: ${finalDisplayCandles.length}, 지표: ${finalDisplayIndicators.length}, MA: ${finalMaCalculationCandles.length})`);
      } else {
        console.log(`⏭️ [차트 데이터 업데이트] 데이터 변경 없음, 업데이트 스킵`);
      }

      // ✅ 증분 업데이트 플래그 해제 (충분한 시간 후 - 모든 useEffect 실행 후)
      if (isIncrementalUpdate) {
        setTimeout(() => {
          isIncrementalUpdateInProgressRef.current = false;
          console.log(`🔓 [증분 업데이트] 플래그 해제 - chartData useEffect 다시 허용`);
        }, 1500); // 1.5초 후 해제 (모든 차트 동기화 완료 대기)
      }

      // ⚠️ 기간 변경 시에는 시그널 재계산을 하지 않음
      // 이유: 시그널 재계산 시 기존 backtestResult (17개 tradeRecords)가 새로운 결과 (0개)로 덮어써짐
      // 해결: 기존 backtestResult를 유지하고, chartData useMemo가 자동으로 마커를 표시하도록 함
      console.log('✅ 데이터 로드 완료 (워밍업 데이터로 정확한 보조지표 계산 완료)');

      setHasInitialData(true);

      // ✅ 증분 업데이트 시 차트 범위 확장 (기존 뷰 유지, 끝만 확장)
      if (isIncrementalUpdate && finalDisplayCandles.length > candleData.length) {
        const newCandleCount = finalDisplayCandles.length - candleData.length;
        console.log(`📊 [증분 업데이트] 새 캔들 ${newCandleCount}개 추가됨, 차트 범위 확장`);
        // ✅ 기존 뷰 범위 유지, 끝만 확장 (사용자가 보던 부분 그대로 유지)
        setSharedViewRange(prev => {
          const newEnd = finalDisplayCandles.length;
          console.log(`   → 기존 범위 유지: ${prev.start} ~ ${prev.end} → ${prev.start} ~ ${newEnd}`);
          return { start: prev.start, end: newEnd };
        });
      } else if (forceReset || (!toTime && !isIncrementalUpdate)) {
        // ✅ 초기 로드 시에만 전체 범위로 sharedViewRange 설정 (증분 업데이트 제외)
        setSharedViewRange({ start: 0, end: finalDisplayCandles.length });
        console.log(`📊 [초기 로드] sharedViewRange 전체 범위 설정: 0 ~ ${finalDisplayCandles.length}`);
      }

      // ✅ 기간 이동인 경우, shouldSetFullRange 플래그만 설정 (로딩은 계속 유지)
      // isLoadingHistory는 shouldSetFullRange useEffect에서 범위 설정 완료 후 해제
      if (!forceReset && (toTime || isPrepend || isAppend)) {
        await new Promise(resolve => setTimeout(resolve, 50));
        setShouldSetFullRange(true);
        console.log('🎯 [데이터 로드 완료] shouldSetFullRange 플래그 설정 (로딩 계속 유지)');
      }

    } catch (err: any) {
      console.error('데이터 로드 실패:', err);
      setError(err.message || '데이터를 불러오는 중 오류가 발생했습니다.');
      setShouldSetFullRange(false);
      setIsLoadingHistory(false);
    } finally {
      setLoading(false);
      loadDataInProgressRef.current = false;
      // ✅ 대기 중인 요청이 있으면 처리 (심볼/시간봉 변경 등 forceReset 요청 우선)
      const pending = pendingLoadDataRef.current;
      if (pending !== null) {
        pendingLoadDataRef.current = null;
        console.log(`▶️ [loadData] 대기 요청 실행 (forceReset=${pending.forceReset}, toTime=${pending.toTime})`);
        // 다음 tick에 실행해 React 상태 업데이트가 반영된 후 호출
        setTimeout(() => loadDataRef.current?.(pending.forceReset, pending.toTime), 0);
      }
    }
  // ✅ timeInterval은 의존성에서 제거 (ref를 통해 항상 최신 값 사용)
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [symbol]);

  // ✅ loadData 함수 ref 업데이트 (항상 최신 함수 참조)
  useEffect(() => {
    loadDataRef.current = loadData;
  }, [loadData]);

  // ✅ 지표 데이터를 로드하고 반환하는 동기화 함수
  // useCallback으로 감싸서 설정 변경 시 함수가 재생성되도록 함
  const loadIndicatorDataSync = useCallback(async (candles: CandleData[], forceFullReload: boolean = false): Promise<IndicatorData[]> => {
    const isDev = process.env.NODE_ENV === 'development';
    // ✅ 항상 최신 timeInterval 값 사용
    const currentTimeInterval = timeIntervalRef.current;
    
    try {
      // ✅ 항상 전달받은 candles 파라미터 사용 (이미 finalCandles가 전달됨)
      // ⚠️ candleData는 React 상태 업데이트가 비동기이므로 사용하면 안 됨!
      if (isDev) console.log(`📊 [보조지표 데이터 요청] symbol=${symbol}, interval=${currentTimeInterval}, candles=${candles.length}개 (forceFullReload=${forceFullReload})`);
      
      // ✅ Context의 getLatestSettings()로 최신 설정값 동기적으로 읽기 (DB 기반)
      const latestSettings = getLatestSettings();
      const vrPeriod = latestSettings.vrPeriod || 10;
      const vr2Enabled = latestSettings.vr2Enabled || false;
      const vr2Period = latestSettings.vr2Period || 10;
      
      console.log(`🔍 [loadIndicatorDataSync] VR 설정 읽기 (DB 기반 최신값): vrPeriod=${vrPeriod}, vr2Enabled=${vr2Enabled}, vr2Period=${vr2Period}`);
      
      // ✅ ref를 통해 항상 최신 MA 설정 읽기 (클로저 문제 방지)
      const maPeriods = indicatorVisibilitySettingsRef.current.maLines
        .filter(ma => ma.enabled)
        .map(ma => ma.period);
      
      // ✅ Context에서 최신 보조지표 기간 설정 읽기
      const currentParamSettings = getLatestSettings();
      
      const requestPayload = {
        symbol,
        interval: currentTimeInterval,
        candleData: candles,
        // ✅ MA 기간을 최상위에도 추가 (백엔드가 먼저 확인)
        maPeriods: maPeriods,
        // ✅ 모든 보조지표 설정 포함
        indicatorSettings: {
          // 볼린저밴드
          bollingerPeriod: indicatorVisibilitySettingsRef.current.bollingerPeriod,
          bollingerStdDev: indicatorVisibilitySettingsRef.current.bollingerStdDev,
          // MA 기간
          maPeriods: maPeriods,
          // CCI
          cciPeriod: currentParamSettings.cciPeriod || 13,
          cciSignalPeriod: currentParamSettings.cciSignalPeriod || 10,
          // RSI
          rsiPeriod: currentParamSettings.rsiPeriod || 9,
          rsiSignalPeriod: currentParamSettings.rsiSignalPeriod || 9,
          // MACD
          macdFastPeriod: currentParamSettings.macdFastPeriod || 12,
          macdSlowPeriod: currentParamSettings.macdSlowPeriod || 26,
          macdSignalPeriod: currentParamSettings.macdSignalPeriod || 9,
          // Stochastic
          stochasticKPeriod: currentParamSettings.stochasticKPeriod || 12,
          stochasticDPeriod: currentParamSettings.stochasticDPeriod || 5,
          stochasticSlowing: currentParamSettings.stochasticSlowing || 5,
          // ADX
          adxPeriod: currentParamSettings.adxPeriod || 14,
          // DMI
          dmiPeriod: currentParamSettings.dmiPeriod || 14,
          // OBV
          obvPeriod: currentParamSettings.obvPeriod || 10,
          obvSignalPeriod: currentParamSettings.obvSignalPeriod || 10,
          // Williams %R
          williamsRPeriod: currentParamSettings.williamsRPeriod || 14,
          // TRIX
          trixPeriod: currentParamSettings.trixPeriod || 12,
          trixSignalPeriod: currentParamSettings.trixSignalPeriod || 9,
          // Volume Oscillator
          volumeOscShortPeriod: currentParamSettings.volumeOscShortPeriod || 5,
          volumeOscLongPeriod: currentParamSettings.volumeOscLongPeriod || 10,
          // 이격도
          disparityUltraShortPeriod: currentParamSettings.disparityUltraShortPeriod || 5,
          disparityShortPeriod: currentParamSettings.disparityShortPeriod || 20,
          disparityMidPeriod: currentParamSettings.disparityMidPeriod || 60,
          disparityLongPeriod: currentParamSettings.disparityLongPeriod || 120,
          // 심리도
          newPsychologicalPeriod: currentParamSettings.newPsychologicalPeriod || 12,
          investPsychologicalPeriod: currentParamSettings.investPsychologicalPeriod || 24,
          // BWI
          bwiPeriod: currentParamSettings.bwiPeriod || 20,
        },
        // VR 설정 추가
        vrPeriod: vrPeriod,
        vr2Enabled: vr2Enabled,
        vr2Period: vr2Period,
      };
      
      console.log(`📤 [loadIndicatorDataSync] 백엔드 요청 payload:`, {
        vrPeriod: requestPayload.vrPeriod,
        vr2Enabled: requestPayload.vr2Enabled,
        vr2Period: requestPayload.vr2Period,
        maPeriods: maPeriods,
        cciPeriod: requestPayload.indicatorSettings.cciPeriod,
        rsiPeriod: requestPayload.indicatorSettings.rsiPeriod,
        macdFastPeriod: requestPayload.indicatorSettings.macdFastPeriod,
        macdSlowPeriod: requestPayload.indicatorSettings.macdSlowPeriod,
      });
      
      const response = await axios.post(`${API_BASE_URL}/upbit/analyze`, requestPayload);
      
      if (response.data && response.data.length > 0) {
        console.log(`✅ [보조지표 데이터 수신] interval=${currentTimeInterval}, 개수=${response.data.length}개`);
        
        // ✅ MA 데이터 확인
        const sampleIndex = Math.min(100, response.data.length - 1);
        const sampleData = response.data[sampleIndex];
        console.log('📊 MA 데이터 샘플 (인덱스:', sampleIndex, '):', {
          maValues: sampleData.maValues,
          ma10: sampleData.ma10,
          ma20: sampleData.ma20,
          sma10: sampleData.sma10,
          sma20: sampleData.sma20,
        });
        
        console.log('📊 첫 번째 지표 데이터 샘플:', response.data[0]);
        console.log('📊 MACD 데이터 확인:', {
          macdLine: response.data[0]?.macdLine,
          signalLine: response.data[0]?.signalLine,
          macdHistogram: response.data[0]?.macdHistogram
        });
        console.log('📊 OBV 데이터 확인:', {
          obv: response.data[0]?.obv,
          count: response.data.filter((d: any) => d.obv !== undefined && d.obv !== null).length
        });
        console.log('📊 CCI 데이터 확인:', {
          cci: response.data[0]?.cci,
          cci13: response.data[0]?.cci13,
          cciCount: response.data.filter((d: any) => d.cci !== undefined && d.cci !== null).length,
          cci13Count: response.data.filter((d: any) => d.cci13 !== undefined && d.cci13 !== null).length,
          firstData: response.data[0]
        });
        return response.data;
      } else {
        console.warn('⚠️ 지표 데이터가 비어있습니다');
        return [];
      }
    } catch (err) {
      console.error('지표 데이터 로드 실패:', err);
      return [];
    }
  // ✅ timeInterval은 의존성에서 제거 (ref를 통해 항상 최신 값 사용)
  // ✅ settingsVersion 추가: 모든 보조지표 기간 변경 시 함수가 재생성되도록
  }, [symbol, settingsVersion, indicatorVisibilitySettings.bollingerPeriod, indicatorVisibilitySettings.bollingerStdDev, JSON.stringify(indicatorVisibilitySettings.maLines), paramSettings.vrPeriod, paramSettings.vr2Enabled, paramSettings.vr2Period]);

  // ✅ timeInterval 변경 시 ref 업데이트, 데이터 로드
  useEffect(() => {
    // 1. ref 먼저 업데이트
    timeIntervalRef.current = timeInterval;
    console.log(`🔄 [시간봉 변경] ref 업데이트 완료: ${timeInterval}`);
    
    // 2. 히스토리 초기화
    setTimeHistory([]);
    isInitialIndicatorLoadDoneRef.current = false;
    
    // 3. 새 데이터 로드 (ref가 이미 업데이트되었으므로 올바른 시간봉 사용)
    console.log(`🔄 [시간봉 변경] 데이터 로드 시작 (ref: ${timeIntervalRef.current})`);
    loadData(true);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [symbol, timeInterval]);


  // ✅ 보조지표 설정 변경 시 차트 데이터 갱신 (settingsVersion 기반)
  // 초기 로딩 완료 여부를 추적하는 ref
  const isInitialIndicatorLoadDoneRef = useRef(false);
  // 이전 settingsVersion 추적 (실제 변경 감지용)
  const prevSettingsVersionRef = useRef(settingsVersion);
  
  useEffect(() => {
    // ✅ 초기 로딩 시에는 실행하지 않음 (loadData에서 이미 처리)
    if (!isInitialIndicatorLoadDoneRef.current) {
      if (hasInitialData && maCalculationData.length > 0) {
        console.log(`✅ [초기 보조지표 로딩 완료] 이후 settingsVersion 변경만 감지 (현재 버전: ${settingsVersion})`);
        isInitialIndicatorLoadDoneRef.current = true;
        prevSettingsVersionRef.current = settingsVersion; // 초기 버전 저장
      }
      return;
    }
    
    // ✅ settingsVersion이 실제로 변경된 경우에만 실행 (0→1도 포함)
    if (hasInitialData && maCalculationData.length > 0 && settingsVersion !== prevSettingsVersionRef.current) {
      console.log(`🔄 [보조지표 설정 변경 감지] settingsVersion=${prevSettingsVersionRef.current} → ${settingsVersion}, vrPeriod=${paramSettings.vrPeriod}, vr2Enabled=${paramSettings.vr2Enabled}, vr2Period=${paramSettings.vr2Period}`);
      prevSettingsVersionRef.current = settingsVersion; // 새 버전으로 업데이트
      
      // ✅ maCalculationData 사용 (전체 데이터로 정확한 지표 계산)
      (async () => {
        try {
          const allIndicators = await loadIndicatorDataSync(maCalculationData, false);
          
          // ✅ 워밍업 데이터 제외하고 표시용 데이터만 추출
          const skipCount = maCalculationData.length - candleData.length;
          const displayIndicators = allIndicators.slice(skipCount);
          
          // 항상 업데이트 (설정 변경 시에는 반드시 갱신)
          setIndicatorData(displayIndicators);
          console.log(`✅ [보조지표 갱신 완료] ${displayIndicators.length}개 데이터 업데이트 (settingsVersion: ${settingsVersion}, vrPeriod=${paramSettings.vrPeriod}, vr2Enabled=${paramSettings.vr2Enabled}, vr2Period=${paramSettings.vr2Period})`);
        } catch (error) {
          console.error('❌ [보조지표 갱신 실패]:', error);
        }
      })();
    }
  }, [settingsVersion, hasInitialData, maCalculationData.length, loadIndicatorDataSync]);

  // (휠 줌 기능은 lightweight-charts 자체 기능 사용 - handleScale.mouseWheel: true)

  // ⚠️ 기존 자동 갱신 로직 (비활성화됨)
  // → 1490줄의 새로운 자동 갱신 로직으로 대체됨 (autoRefreshEnabled, timeHistory 기반)
  // useEffect(() => {
  //   console.log('⏰ [CandlestickTestPage] 1분 자동 갱신 타이머 시작');
  //   const intervalId = setInterval(() => {
  //     // ✅ 사용자가 최신 기간을 보고 있는지 확인 (sharedViewRange.end가 candleData.length 근처)
  //     const isViewingLatest = candleData.length > 0 && 
  //                             sharedViewRange.end >= candleData.length - 20; // 마지막 20개 이내면 최신 기간
  //     
  //     if (isViewingLatest) {
  //       console.log('🔄 [CandlestickTestPage] 1분 경과 - 최신 기간 표시 중이므로 차트 데이터 자동 갱신');
  //       loadData(false); // 현재 뷰 범위 유지하면서 데이터만 갱신
  //     } else {
  //       console.log('⏸️ [CandlestickTestPage] 1분 경과 - 과거 기간 표시 중이므로 자동 갱신 건너뜀');
  //     }
  //   }, 60000); // 60초 = 1분
  //
  //   return () => {
  //     console.log('⏰ [CandlestickTestPage] 1분 자동 갱신 타이머 정리');
  //     clearInterval(intervalId);
  //   };
  // }, [loadData, candleData.length, sharedViewRange]);

  // candleData가 로드되면 초기 viewRange 설정 (200개 전체 표시)
  // ★ 초기 로드 시에만 실행 (데이터 업데이트 시에는 현재 범위 유지)
  useEffect(() => {
    if (candleData.length > 0 && !hasInitialData) {
      // ✅ CHART_DISPLAY_COUNT개 (200개) 전체 표시
      const startIndex = 0;
      const endIndex = candleData.length;

      const displayPeriod = getDisplayPeriodDescription(timeInterval, candleData.length);

      console.log(`📊 [초기 로드] 총 ${candleData.length}개 로드 → 전체 표시 (인덱스 ${startIndex}~${endIndex}) = ${displayPeriod}`);
      console.log(`📊 [초기 로드] 표시 기간: ${candleData[0]?.time} ~ ${candleData[candleData.length - 1]?.time}`);

      setSharedViewRange({ start: startIndex, end: endIndex });

      // ★ 보조지표 차트들의 X축 범위를 캔들차트와 동기화
      // 데이터 로드 직후 짧은 딜레이를 두고 실행 (차트 초기화 완료 대기)
      setTimeout(() => {
        // ✅ chartData 기준으로 visible range 설정 (일목균형표 미래 영역 포함)
        if (chartData.length > 0) {
          // chartData를 시간순으로 정렬
          const sortedChartData = [...chartData].sort((a, b) =>
            new Date(a.time || a.date).getTime() - new Date(b.time || b.date).getTime()
          );

          const firstTime = new Date(sortedChartData[0].time || sortedChartData[0].date).getTime() / 1000;
          const lastTime = new Date(sortedChartData[sortedChartData.length - 1].time || sortedChartData[sortedChartData.length - 1].date).getTime() / 1000;

          console.log(`🔄 [초기 로드] visible range 동기화 (미래 영역 포함): ${sortedChartData[0].time || sortedChartData[0].date} ~ ${sortedChartData[sortedChartData.length - 1].time || sortedChartData[sortedChartData.length - 1].date}`);

          // 모든 차트에 동일한 범위를 적용하는 함수
          const applyInitialRange = () => {
            volumeChartRef.current?.setVisibleRange(firstTime, lastTime);
            macdChartRef.current?.setVisibleRange(firstTime, lastTime);
            rsiChartRef.current?.setVisibleRange(firstTime, lastTime);
            stochasticChartRef.current?.setVisibleRange(firstTime, lastTime);
            cciChartRef.current?.setVisibleRange(firstTime, lastTime);
            adxChartRef.current?.setVisibleRange(firstTime, lastTime);
            obvChartRef.current?.setVisibleRange(firstTime, lastTime);
            newPsychologicalChartRef.current?.setVisibleRange(firstTime, lastTime);
            investPsychologicalChartRef.current?.setVisibleRange(firstTime, lastTime);
            bwiChartRef.current?.setVisibleRange(firstTime, lastTime);
            dmiChartRef.current?.setVisibleRange(firstTime, lastTime);
            williamsRChartRef.current?.setVisibleRange(firstTime, lastTime);
            trixChartRef.current?.setVisibleRange(firstTime, lastTime);
            volumeOscChartRef.current?.setVisibleRange(firstTime, lastTime);
            vrChartRef.current?.setVisibleRange(firstTime, lastTime);
            disparityChartRef.current?.setVisibleRange(firstTime, lastTime);
          };

          // 즉시 적용
          applyInitialRange();

          // 다음 프레임에 한 번 더 적용하여 확실히 일치시킴
          requestAnimationFrame(() => {
            applyInitialRange();
          });
        }
      }, 300); // 300ms 딜레이
    }
  }, [chartData, hasInitialData, timeInterval, getDisplayPeriodDescription]);

  // ✅ 기간 이동 후 범위 설정 (shouldSetFullRange 플래그 기반)
  useEffect(() => {
    if (shouldSetFullRange && chartData.length > 0 && !loading) {
      // ✅ chartData 기준으로 설정 (일목균형표 미래 영역 포함)
      const sortedChartData = [...chartData].sort((a, b) =>
        new Date(a.time || a.date).getTime() - new Date(b.time || b.date).getTime()
      );

      console.log(`🎯 [기간 이동] 범위 설정 시작 (미래 영역 포함): ${sortedChartData[0].time || sortedChartData[0].date} ~ ${sortedChartData[sortedChartData.length - 1].time || sortedChartData[sortedChartData.length - 1].date}`);
      console.log(`   → 데이터: ${sortedChartData.length}개 (일목균형표 미래 영역 포함)`);
      console.log(`   → 로딩 오버레이 유지 중 (백그라운드 작업)`);

      // ✅ 저장된 visible range가 있으면 해당 크기만큼만 표시 (스케일 유지)
      let fromTime: number;
      let toTime: number;
      let targetRange: { start: number; end: number };

      if (savedViewRangeRef.current && savedViewRangeRef.current.size) {
        // 저장된 범위 크기를 새 데이터의 마지막 부분에 적용
        const savedSize = savedViewRangeRef.current.size;
        const lastTime = new Date(sortedChartData[sortedChartData.length - 1].time || sortedChartData[sortedChartData.length - 1].date).getTime() / 1000;
        fromTime = lastTime - savedSize;
        toTime = lastTime;

        console.log(`📏 [기간 이동] 저장된 스케일 적용 (크기: ${savedSize}초)`);
        console.log(`   → 적용 범위: ${new Date(fromTime * 1000).toISOString()} ~ ${new Date(toTime * 1000).toISOString()}`);

        // ✅ fromTime과 toTime에 해당하는 정확한 인덱스 범위 계산
        const fromTimeMs = fromTime * 1000;
        const toTimeMs = toTime * 1000;

        let startIndex = 0;
        let endIndex = sortedChartData.length;

        // 시작 인덱스 찾기: fromTime 이상인 첫 번째 데이터
        for (let i = 0; i < sortedChartData.length; i++) {
          const dataTime = new Date(sortedChartData[i].time || sortedChartData[i].date).getTime();
          if (dataTime >= fromTimeMs) {
            startIndex = Math.max(0, i - 1); // 이전 데이터부터 시작 (여유 공간)
            break;
          }
        }

        // 끝 인덱스 찾기: toTime 이하인 마지막 데이터
        for (let i = sortedChartData.length - 1; i >= 0; i--) {
          const dataTime = new Date(sortedChartData[i].time || sortedChartData[i].date).getTime();
          if (dataTime <= toTimeMs) {
            endIndex = Math.min(sortedChartData.length, i + 2); // 다음 데이터까지 포함 (여유 공간)
            break;
          }
        }

        targetRange = { start: startIndex, end: endIndex };
        console.log(`   → 인덱스 범위: ${startIndex} ~ ${endIndex} (총 ${endIndex - startIndex}개)`);
      } else {
        // 저장된 범위가 없으면 전체 범위 표시
        const startIndex = 0;
        const endIndex = sortedChartData.length - 1;
        fromTime = new Date(sortedChartData[startIndex].time || sortedChartData[startIndex].date).getTime() / 1000;
        toTime = new Date(sortedChartData[endIndex].time || sortedChartData[endIndex].date).getTime() / 1000;
        targetRange = { start: 0, end: sortedChartData.length };

        console.log(`📏 [기간 이동] 전체 범위 표시`);
      }

      // ✅ 간단하게: sharedViewRange만 업데이트하면 나머지는 자동으로 동기화됨
      console.log(`📊 [기간 이동] sharedViewRange 업데이트: 인덱스 ${targetRange.start} ~ ${targetRange.end}`);
      console.log(`   → 시간 범위: ${new Date(fromTime * 1000).toISOString()} ~ ${new Date(toTime * 1000).toISOString()}`);

      // 1. shouldSetFullRange 플래그 해제
      setShouldSetFullRange(false);

      // 2. 로딩 오버레이 제거 (데이터 로드 완료)
      setIsLoadingHistory(false);

      // 3. 저장된 범위 초기화
      savedViewRangeRef.current = null;

      // 4. sharedViewRange 업데이트 (sharedViewRange useEffect가 모든 차트 동기화)
      setSharedViewRange(targetRange);

      console.log('🎉 [기간 이동 완료] sharedViewRange 업데이트 → 모든 차트 자동 동기화');
    }
  }, [shouldSetFullRange, chartData, loading]);

  // (백데이터 prepend useEffect 제거됨 - 200개 단위 교체 방식 사용)

  // ✅ sharedViewRange 변경 시 모든 차트 동기화 (핵심!)
  // ⚠️ 수동 스크롤 모드: 초기 로드 시에만 실행, 이후에는 onVisibleRangeChange에서 동기화
  useEffect(() => {
    if (chartData.length === 0) return;
    
    // ✅ 초기 로드 완료 후에는 실행하지 않음 (수동 스크롤 위치 유지)
    if (isInitialLoadDoneRef.current) {
      console.log(`⏸️ [sharedViewRange 동기화] 초기 로드 완료 - 자동 동기화 스킵 (수동 스크롤 위치 유지)`);
      return;
    }

    // ✅ chartData 기준으로 동기화 (일목균형표 미래 영역 포함)
    const sortedChartData = [...chartData].sort((a, b) =>
      new Date(a.time || a.date).getTime() - new Date(b.time || b.date).getTime()
    );

    const startIndex = Math.max(0, sharedViewRange.start);
    const endIndex = Math.min(sharedViewRange.end - 1, sortedChartData.length - 1);

    if (sortedChartData[startIndex] && sortedChartData[endIndex]) {
      const fromTime = new Date(sortedChartData[startIndex].time || sortedChartData[startIndex].date).getTime() / 1000;
      const toTime = new Date(sortedChartData[endIndex].time || sortedChartData[endIndex].date).getTime() / 1000;

      console.log(`🔄 [sharedViewRange 동기화] 초기 로드 - 인덱스 ${startIndex} ~ ${endIndex}`);

      // ✅ initialVisibleRange 항상 업데이트 (chartData 전체 범위 기준 - 일목균형표 미래 영역 포함)
      setInitialVisibleRange({ from: fromTime, to: toTime });

      // ✅ 모든 차트에 setVisibleRange 단순 적용
      const applyRangeToAllCharts = () => {
        chartRef.current?.setVisibleRange(fromTime, toTime);
        volumeChartRef.current?.setVisibleRange(fromTime, toTime);
        macdChartRef.current?.setVisibleRange(fromTime, toTime);
        rsiChartRef.current?.setVisibleRange(fromTime, toTime);
        stochasticChartRef.current?.setVisibleRange(fromTime, toTime);
        cciChartRef.current?.setVisibleRange(fromTime, toTime);
        adxChartRef.current?.setVisibleRange(fromTime, toTime);
        obvChartRef.current?.setVisibleRange(fromTime, toTime);
        newPsychologicalChartRef.current?.setVisibleRange(fromTime, toTime);
        investPsychologicalChartRef.current?.setVisibleRange(fromTime, toTime);
        bwiChartRef.current?.setVisibleRange(fromTime, toTime);
        dmiChartRef.current?.setVisibleRange(fromTime, toTime);
        williamsRChartRef.current?.setVisibleRange(fromTime, toTime);
        trixChartRef.current?.setVisibleRange(fromTime, toTime);
        volumeOscChartRef.current?.setVisibleRange(fromTime, toTime);
        vrChartRef.current?.setVisibleRange(fromTime, toTime);
        disparityChartRef.current?.setVisibleRange(fromTime, toTime);
      };

      // 단순하게 한 번만 적용
      applyRangeToAllCharts();
      
      // ✅ 초기 로드 완료 플래그 설정
      setTimeout(() => {
        isInitialLoadDoneRef.current = true;
        console.log('✅ [sharedViewRange 동기화] 초기 로드 완료 플래그 설정');
      }, 500);
    }
  }, [sharedViewRange, chartData]); // ✅ 단순하게 sharedViewRange와 chartData만 의존

  // sharedViewRange 변경 추적
  useEffect(() => {
    console.log(`📊 CandlestickTestPage sharedViewRange 변경됨:`, sharedViewRange);
  }, [sharedViewRange]);

  // ✅ 현재 표시 중인 날짜 범위 추적
  useEffect(() => {
    if (visibleDateRange.start && visibleDateRange.end) {
      console.log(`📅 현재 표시 날짜 범위: ${visibleDateRange.start} ~ ${visibleDateRange.end}`);
    }
  }, [visibleDateRange]);

  // ✅ 경량 지표 재계산 함수 - 차트 컴포넌트 remount 없이 데이터만 업데이트
  const reloadIndicatorsOnly = useCallback(async (updatedSettings?: typeof paramSettings) => {
    console.log('🔄 [지표만 재계산] 시작 - 차트 컴포넌트는 유지, 데이터만 업데이트');
    console.log('📥 [reloadIndicatorsOnly] 전달받은 updatedSettings:', {
      obvSignalPeriod: updatedSettings?.obvSignalPeriod,
      rsiPeriod: updatedSettings?.rsiPeriod,
    });
    console.log('📥 [reloadIndicatorsOnly] 클로저의 paramSettings:', {
      obvSignalPeriod: paramSettings.obvSignalPeriod,
      rsiPeriod: paramSettings.rsiPeriod,
    });
    
    // ✅ 최신 settings 사용 (인자로 전달된 값 우선, 없으면 현재 paramSettings 사용)
    const currentSettings = updatedSettings || paramSettings;
    console.log('📥 [reloadIndicatorsOnly] 최종 사용할 currentSettings:', {
      obvSignalPeriod: currentSettings.obvSignalPeriod,
      rsiPeriod: currentSettings.rsiPeriod,
    });
    
    try {
      // ✅ 0. 크로스헤어 동기화 일시 중단 (데이터 업데이트 중 크로스헤어 에러 방지)
      const previousHoverTime = sharedHoverTime;
      setSharedHoverTime(null);
      console.log('🔒 [크로스헤어] 일시 중단 (데이터 업데이트 중)');
      
      // ✅ 1. 워밍업 데이터를 포함한 전체 데이터 사용 (정확한 지표 계산을 위함)
      if (!maCalculationData || maCalculationData.length === 0) {
        console.warn('⚠️ [지표 재계산] maCalculationData가 없어 재계산 불가');
        setSharedHoverTime(previousHoverTime); // 복원
        return;
      }
      
      if (!candleData || candleData.length === 0) {
        console.warn('⚠️ [지표 재계산] candleData가 없어 재계산 불가');
        setSharedHoverTime(previousHoverTime); // 복원
        return;
      }

      console.log(`📊 [지표 재계산] 워밍업 포함 전체: ${maCalculationData.length}개, 표시용: ${candleData.length}개`);

      // ✅ 2. 백엔드 API 호출 - 워밍업 데이터 포함 전체 데이터로 지표 재계산
      console.log(`📊 [보조지표 데이터 재요청] symbol=${symbol}, interval=${timeInterval}, candles=${maCalculationData.length}개 (워밍업 포함)`);
      console.log(`📊 [변경된 지표 기간] williamsRPeriod=${currentSettings.williamsRPeriod}, rsiPeriod=${currentSettings.rsiPeriod}, macdFast=${currentSettings.macdFastPeriod}`);
      console.log(`📊 [백엔드 전송 데이터] indicatorSettings:`, {
        obvPeriod: currentSettings.obvPeriod,
        obvSignalPeriod: currentSettings.obvSignalPeriod,
        rsiPeriod: currentSettings.rsiPeriod,
        rsiSignalPeriod: currentSettings.rsiSignalPeriod,
        cciPeriod: currentSettings.cciPeriod,
        cciSignalPeriod: currentSettings.cciSignalPeriod,
      });
      
      const response = await axios.post(`${API_BASE_URL}/upbit/analyze`, {
        symbol,
        interval: timeInterval,
        candleData: maCalculationData, // ✅ 워밍업 데이터 포함 전체 데이터 사용
        // ✅ 변경된 지표 기간 설정 전달 (백엔드가 새로운 기간으로 계산하도록)
        indicatorSettings: {
          ...currentSettings,
          // ✅ 볼린저밴드 설정 추가
          bollingerPeriod: indicatorVisibilitySettings.bollingerPeriod,
          bollingerStdDev: indicatorVisibilitySettings.bollingerStdDev,
          // ✅ MA 기간 추가
          maPeriods: indicatorVisibilitySettings.maLines
            .filter(ma => ma.enabled)
            .map(ma => ma.period),
        },
      });

      if (response.data && response.data.length > 0) {
        console.log(`✅ [보조지표 데이터 재수신] ${response.data.length}개 (워밍업 포함)`);
        
        // ✅ 3. 표시용 200개에 해당하는 지표만 추출
        // maCalculationData는 [워밍업...] + [표시용 200개]로 구성
        // candleData는 [표시용 200개]만 포함
        const skipCount = maCalculationData.length - candleData.length;
        const displayIndicators = response.data.slice(skipCount);
        
        console.log(`📊 [지표 슬라이싱] 전체 ${response.data.length}개 중 앞 ${skipCount}개 스킵 → 표시용 ${displayIndicators.length}개 추출`);
        
        // ✅ 데이터 변경 전후 비교 (실제 값이 변경되었는지 확인)
        if (indicatorData.length > 0 && displayIndicators.length > 0) {
          const oldSample: any = indicatorData[100] || indicatorData[0];
          const newSample: any = displayIndicators[100] || displayIndicators[0];
          console.log('🔍 [데이터 변경 확인] 이전 값:', {
            williamsR: oldSample?.williamsR,
            rsi: oldSample?.rsi,
            macdLine: oldSample?.macdLine,
          });
          console.log('🔍 [데이터 변경 확인] 새로운 값:', {
            williamsR: newSample?.williamsR,
            rsi: newSample?.rsi,
            macdLine: newSample?.macdLine,
          });
          
          // 값이 실제로 변경되었는지 확인
          const changed = 
            oldSample?.williamsR !== newSample?.williamsR ||
            oldSample?.rsi !== newSample?.rsi ||
            oldSample?.macdLine !== newSample?.macdLine;
          
          if (changed) {
            console.log('✅ [데이터 변경 확인] 지표 값이 실제로 변경되었습니다!');
          } else {
            console.warn('⚠️ [데이터 변경 확인] 지표 값이 변경되지 않았습니다. 백엔드 확인 필요!');
          }
        }
        
        // ✅ 4. indicatorData만 업데이트 (candleData는 그대로 유지)
        // - 이렇게 하면 chartData useMemo가 자동으로 재계산됨
        // - IndicatorChart는 useEffect에서 데이터 변경을 감지하고
        // - 차트 인스턴스의 setData() 메서드만 호출하여 부드럽게 업데이트
        setIndicatorData(displayIndicators);
        
        // ✅ 5. 데이터 업데이트 완료 후 크로스헤어 동기화 재개
        // (약간의 지연 후 재개하여 차트가 안정화되도록 함)
        setTimeout(() => {
          console.log('🔓 [크로스헤어] 재개 (데이터 업데이트 완료)');
          // previousHoverTime을 복원하지 않고 null로 유지 (깔끔한 상태)
          // 사용자가 다시 마우스를 움직이면 자연스럽게 재설정됨
        }, 500);
        
        console.log('✅ [지표 재계산 완료] 차트가 자동으로 업데이트됩니다 (remount 없음, 워밍업 데이터로 정확한 계산)');
      } else {
        console.warn('⚠️ [지표 재계산] 응답 데이터가 없습니다');
        setSharedHoverTime(previousHoverTime); // 복원
      }
    } catch (error) {
      console.error('❌ [지표 재계산 실패]', error);
      // 에러 발생 시에도 크로스헤어를 null로 유지 (안전한 상태)
    }
  }, [maCalculationData, candleData, symbol, timeInterval, sharedHoverTime]);

  // ✅ 지표 설정(기간) 변경 감지 - 차트 remount 없이 데이터만 갱신
  const prevParamSettingsRef = useRef<typeof paramSettings | null>(null);
  
  useEffect(() => {
    // ✅ 최초 실행 시 현재 값으로 초기화 (비교 기준 설정)
    if (prevParamSettingsRef.current === null) {
      prevParamSettingsRef.current = paramSettings;
      console.log('📊 [지표 기간 감지] 초기화 완료:', {
        obvSignalPeriod: paramSettings.obvSignalPeriod,
        rsiPeriod: paramSettings.rsiPeriod,
        williamsRPeriod: paramSettings.williamsRPeriod,
      });
      return;
    }
    
    const prev = prevParamSettingsRef.current;
    const curr = paramSettings;
    
    console.log('🔍 [지표 기간 검사] paramSettings 업데이트됨:', {
      obvSignalPeriod: curr.obvSignalPeriod,
      rsiPeriod: curr.rsiPeriod,
      rsiSignalPeriod: curr.rsiSignalPeriod,
    });
    console.log('🔍 [지표 기간 검사] prev 값:', {
      obvSignalPeriod: prev.obvSignalPeriod,
      rsiPeriod: prev.rsiPeriod,
      rsiSignalPeriod: prev.rsiSignalPeriod,
    });
    
    // ✅ 초기 로드 중에는 스킵 (데이터가 아직 없음)
    if (!chartData || chartData.length === 0) {
      console.log('⏭️ [지표 기간 검사] 스킵 (데이터 없음)');
      return;
    }

    // 기간 관련 설정이 변경되었는지 확인
    const periodChanged = 
      prev.williamsRPeriod !== curr.williamsRPeriod ||
      prev.rsiPeriod !== curr.rsiPeriod ||
      prev.macdFastPeriod !== curr.macdFastPeriod ||
      prev.macdSlowPeriod !== curr.macdSlowPeriod ||
      prev.macdSignalPeriod !== curr.macdSignalPeriod ||
      prev.stochasticKPeriod !== curr.stochasticKPeriod ||
      prev.stochasticDPeriod !== curr.stochasticDPeriod ||
      prev.cciPeriod !== curr.cciPeriod ||
      prev.adxPeriod !== curr.adxPeriod ||
      prev.dmiPeriod !== curr.dmiPeriod ||  // DMI 기간 변경 감지
      prev.trixPeriod !== curr.trixPeriod ||
      prev.trixSignalPeriod !== curr.trixSignalPeriod ||
      prev.newPsychologicalPeriod !== curr.newPsychologicalPeriod ||
      prev.investPsychologicalPeriod !== curr.investPsychologicalPeriod ||
      prev.bwiPeriod !== curr.bwiPeriod ||
      prev.obvSignalPeriod !== curr.obvSignalPeriod ||  // OBV 신호선 기간 (obvPeriod는 사용 안 함)
      prev.volumeOscShortPeriod !== curr.volumeOscShortPeriod ||
      prev.volumeOscLongPeriod !== curr.volumeOscLongPeriod ||
      prev.vrPeriod !== curr.vrPeriod ||
      prev.disparityUltraShortPeriod !== curr.disparityUltraShortPeriod ||
      prev.disparityShortPeriod !== curr.disparityShortPeriod ||
      prev.disparityMidPeriod !== curr.disparityMidPeriod ||
      prev.disparityLongPeriod !== curr.disparityLongPeriod;

    console.log('🔍 [지표 기간 검사] periodChanged:', periodChanged, {
      obvSignalPeriodChanged: prev.obvSignalPeriod !== curr.obvSignalPeriod,
      rsiPeriodChanged: prev.rsiPeriod !== curr.rsiPeriod,
      rsiSignalPeriodChanged: prev.rsiSignalPeriod !== curr.rsiSignalPeriod,
    });

    if (periodChanged) {
      console.log('🔄 [지표 기간 변경 감지] 지표만 재계산 (화면/차트 유지)');
      console.log('이전 설정:', {
        williamsRPeriod: prev.williamsRPeriod,
        rsiPeriod: prev.rsiPeriod,
        macdFastPeriod: prev.macdFastPeriod,
        stochasticKPeriod: prev.stochasticKPeriod,
        cciPeriod: prev.cciPeriod,
        obvSignalPeriod: prev.obvSignalPeriod,
      });
      console.log('새로운 설정:', {
        williamsRPeriod: curr.williamsRPeriod,
        rsiPeriod: curr.rsiPeriod,
        macdFastPeriod: curr.macdFastPeriod,
        stochasticKPeriod: curr.stochasticKPeriod,
        cciPeriod: curr.cciPeriod,
        obvSignalPeriod: curr.obvSignalPeriod,
      });

      // ✅ 경량 재계산 함수 호출 (loadData 대신)
      // - loading 상태 변경 없음 → 화면 이동 없음
      // - candleData 그대로 → 캔들 차트 유지
      // - indicatorData만 업데이트 → 보조지표만 부드럽게 갱신
      // ⭐ 최신 paramSettings를 명시적으로 전달 (클로저 문제 해결)
      reloadIndicatorsOnly(curr);
    }

    // 현재 설정을 이전 설정으로 업데이트
    prevParamSettingsRef.current = paramSettings;
  }, [paramSettings, chartData, reloadIndicatorsOnly]);

  // ✅ 볼린저밴드 설정 변경 감지 - 기간/표준편차 변경 시 지표 재계산
  const prevBollingerSettingsRef = useRef<{ period: number; stdDev: number } | null>(null);
  
  useEffect(() => {
    console.log('🔍 [볼린저밴드 useEffect] 실행됨:', {
      period: indicatorVisibilitySettings.bollingerPeriod,
      stdDev: indicatorVisibilitySettings.bollingerStdDev,
      chartDataLength: chartData?.length,
      prevRef: prevBollingerSettingsRef.current,
    });
    
    // ✅ 최초 실행 시 현재 값으로 초기화
    if (prevBollingerSettingsRef.current === null) {
      prevBollingerSettingsRef.current = {
        period: indicatorVisibilitySettings.bollingerPeriod,
        stdDev: indicatorVisibilitySettings.bollingerStdDev,
      };
      console.log('📊 [볼린저밴드 설정 감지] 초기화 완료:', prevBollingerSettingsRef.current);
      return;
    }
    
    // ✅ 초기 로드 중에는 스킵
    if (!chartData || chartData.length === 0) {
      console.log('⏭️ [볼린저밴드 설정 검사] 스킵 (데이터 없음)');
      return;
    }
    
    const prev = prevBollingerSettingsRef.current;
    const curr = {
      period: indicatorVisibilitySettings.bollingerPeriod,
      stdDev: indicatorVisibilitySettings.bollingerStdDev,
    };
    
    const bollingerChanged = prev.period !== curr.period || prev.stdDev !== curr.stdDev;
    
    console.log('🔍 [볼린저밴드 설정 비교]:', {
      prev,
      curr,
      periodChanged: prev.period !== curr.period,
      stdDevChanged: prev.stdDev !== curr.stdDev,
      bollingerChanged,
    });
    
    if (bollingerChanged) {
      console.log('🔄 [볼린저밴드 설정 변경 감지] 지표 재계산 시작');
      console.log('이전 설정:', prev);
      console.log('새로운 설정:', curr);
      
      // 지표 재계산 (paramSettings는 그대로, 볼린저밴드 설정만 변경됨)
      reloadIndicatorsOnly();
    }
    
    // 현재 설정을 이전 설정으로 업데이트
    prevBollingerSettingsRef.current = curr;
  }, [indicatorVisibilitySettings.bollingerPeriod, indicatorVisibilitySettings.bollingerStdDev, chartData, reloadIndicatorsOnly]);

  // chartData 범위 확인 및 sharedViewRange 자동 업데이트 (미래 영역 포함/축소)
  // ⚠️ 업비트 스타일 수동 스크롤 모드: 이 자동 범위 조정 비활성화
  // 사용자가 좌/우 스크롤로 설정한 차트 위치를 항상 유지
  useEffect(() => {
    // ✅ 수동 스크롤 모드에서는 자동 범위 조정 완전히 비활성화
    // websocket 데이터 추가, 일목균형표 ON/OFF 등 모든 경우에 차트 위치 유지
    console.log(`⏸️ [chartData useEffect] 수동 스크롤 모드 - 자동 범위 조정 비활성화 (차트 위치 유지)`);
    
    // ✅ 데이터 길이는 추적하되 차트 범위는 건드리지 않음
    if (chartData.length > 0) {
      prevChartDataLengthRef.current = chartData.length;
    }
    
    // 원래 로직 전체 주석 처리
    // if (isIncrementalUpdateInProgressRef.current) {
    //   console.log(`⏸️ [chartData useEffect] 증분 업데이트 중 - 실행 건너뜀`);
    //   return;
    // }
    // 
    // if (chartData.length > 0 && !loading && !isLoadingHistory) {
    //   const prevLen = prevChartDataLengthRef.current;
    //   const lenChanged = prevLen !== chartData.length;
    //   prevChartDataLengthRef.current = chartData.length;
    //
    //   if (!lenChanged) {
    //     return;
    //   }
    //
    //   console.log(`📊 CandlestickTestPage chartData 전달: 첫번째=${chartData[0]?.date}, 마지막=${chartData[chartData.length - 1]?.date}, 총 ${chartData.length}개`);
    //
    //   if (chartData.length !== sharedViewRange.end) {
    //     console.log(`🔄 [chartData 크기 변경] sharedViewRange 업데이트: ${sharedViewRange.end} → ${chartData.length}`);
    //     setSharedViewRange(prev => ({ ...prev, end: chartData.length }));
    //     // ... 차트 범위 강제 업데이트 로직 ...
    //   }
    // }
  }, [chartData.length, loading, isLoadingHistory, sharedViewRange.end]);

  // ✅ 보조지표 활성화/비활성화 시 캔들차트의 현재 범위를 즉시 적용
  // ⚠️ Ichimoku 활성화 시에는 스킵 (initialVisibleRange가 이미 전체 범위를 전달함)
  useEffect(() => {
    if (!chartRef.current || !hasInitialData || chartData.length === 0) return;

    // Ichimoku 선행스팬이 활성화된 경우 이 로직 스킵
    const ichimokuEnabled = indicatorVisibilitySettings.ichimokuSenkouA || indicatorVisibilitySettings.ichimokuSenkouB;
    if (ichimokuEnabled) {
      console.log('⏸️ [보조지표 ON/OFF] Ichimoku 활성화 중 - 동기화 스킵 (initialVisibleRange 사용)');
      return;
    }

    // 300ms 후 범위 강제 동기화 (차트 마운트 및 데이터 로딩 대기)
    const timer = setTimeout(() => {
      try {
        const currentRange = chartRef.current?.getVisibleRange();
        if (!currentRange) {
          console.warn('[보조지표 동기화] 캔들차트 범위 없음');
          return;
        }

        const { from, to } = currentRange;
        console.log(`🔄 [보조지표 ON/OFF] 캔들차트 범위 강제 동기화: ${new Date(from * 1000).toISOString().slice(11, 19)} ~ ${new Date(to * 1000).toISOString().slice(11, 19)}`);

        // 모든 보조지표에 즉시 적용
        volumeChartRef.current?.setVisibleRange(from, to);
        macdChartRef.current?.setVisibleRange(from, to);
        rsiChartRef.current?.setVisibleRange(from, to);
        stochasticChartRef.current?.setVisibleRange(from, to);
        cciChartRef.current?.setVisibleRange(from, to);
        adxChartRef.current?.setVisibleRange(from, to);
        obvChartRef.current?.setVisibleRange(from, to);
        newPsychologicalChartRef.current?.setVisibleRange(from, to);
        investPsychologicalChartRef.current?.setVisibleRange(from, to);
        bwiChartRef.current?.setVisibleRange(from, to);
        dmiChartRef.current?.setVisibleRange(from, to);
        williamsRChartRef.current?.setVisibleRange(from, to);
        trixChartRef.current?.setVisibleRange(from, to);
        volumeOscChartRef.current?.setVisibleRange(from, to);
        vrChartRef.current?.setVisibleRange(from, to);
        disparityChartRef.current?.setVisibleRange(from, to);

        // 600ms 후 최종 재동기화 (데이터 로딩 후 자동 fitContent 방지)
        setTimeout(() => {
          volumeChartRef.current?.setVisibleRange(from, to);
          macdChartRef.current?.setVisibleRange(from, to);
          rsiChartRef.current?.setVisibleRange(from, to);
          stochasticChartRef.current?.setVisibleRange(from, to);
          cciChartRef.current?.setVisibleRange(from, to);
          adxChartRef.current?.setVisibleRange(from, to);
          obvChartRef.current?.setVisibleRange(from, to);
          newPsychologicalChartRef.current?.setVisibleRange(from, to);
          investPsychologicalChartRef.current?.setVisibleRange(from, to);
          bwiChartRef.current?.setVisibleRange(from, to);
          dmiChartRef.current?.setVisibleRange(from, to);
          williamsRChartRef.current?.setVisibleRange(from, to);
          trixChartRef.current?.setVisibleRange(from, to);
          volumeOscChartRef.current?.setVisibleRange(from, to);
          vrChartRef.current?.setVisibleRange(from, to);
          disparityChartRef.current?.setVisibleRange(from, to);
          console.log('🔄 [보조지표 ON/OFF] 최종 재동기화 완료');
        }, 600);
      } catch (e) {
        console.warn('[보조지표 동기화] 범위 적용 실패:', e);
      }
    }, 300);

    return () => clearTimeout(timer);
  }, [
    indicatorVisibilitySettings.volume,
    indicatorVisibilitySettings.macdLine,
    indicatorVisibilitySettings.macdHistogram,
    indicatorVisibilitySettings.rsi,
    indicatorVisibilitySettings.stochasticSlow,
    indicatorVisibilitySettings.stochasticK,
    indicatorVisibilitySettings.stochasticD,
    indicatorVisibilitySettings.cci,
    indicatorVisibilitySettings.adx,
    indicatorVisibilitySettings.plusDi,
    indicatorVisibilitySettings.minusDi,
    indicatorVisibilitySettings.obv,
    indicatorVisibilitySettings.newPsychological,
    indicatorVisibilitySettings.investPsychological,
    indicatorVisibilitySettings.bwi,
    indicatorVisibilitySettings.williamsR,
    indicatorVisibilitySettings.trix,
    indicatorVisibilitySettings.trixSignal,
    indicatorVisibilitySettings.volumeOsc,
    indicatorVisibilitySettings.vr,
    indicatorVisibilitySettings.disparity,
    indicatorVisibilitySettings.ichimokuSenkouA,  // ✅ Ichimoku 활성화 시 이 로직 스킵 위해 추가
    indicatorVisibilitySettings.ichimokuSenkouB,  // ✅ Ichimoku 활성화 시 이 로직 스킵 위해 추가
    hasInitialData,
    chartData.length,
  ]);

  // ✅ 초기 데이터 로드 후 차트 동기화 (크로스헤어 일치를 위해) - 정말로 처음 한 번만!
  useEffect(() => {
    // ✅ 조건 체크:
    // 1. 초기 데이터가 로드되었고
    // 2. 기간 변경 중이 아니며 (isPrependingDataRef)
    // 3. 아직 초기 동기화를 한 번도 하지 않았고 (isInitialLoadDoneRef)
    // 4. 로딩 중이 아닐 때
    if (
      hasInitialData &&
      chartData.length > 0 &&
      !loading &&
      !isInitialLoadDoneRef.current
    ) {
      // 초기 로드 완료 후 약간의 지연을 두고 모든 차트의 visible range 동기화
      const timer = setTimeout(() => {
        // ✅ chartData 기준으로 설정 (일목균형표 미래 영역 포함)
        const sortedChartData = [...chartData].sort((a, b) =>
          new Date(a.time || a.date).getTime() - new Date(b.time || b.date).getTime()
        );

        // ✅ 전체 chartData 범위로 설정 (미래 영역 포함)
        const startIdx = 0;
        const endIdx = sortedChartData.length;

        if (sortedChartData[startIdx] && sortedChartData[endIdx - 1]) {
          const fromTime = new Date(sortedChartData[startIdx].time || sortedChartData[startIdx].date).getTime() / 1000;
          const toTime = new Date(sortedChartData[endIdx - 1].time || sortedChartData[endIdx - 1].date).getTime() / 1000;

          console.log(`🎯 [초기 로드 - 한 번만] 차트 동기화 (미래 영역 포함): ${sortedChartData[startIdx].time || sortedChartData[startIdx].date} ~ ${sortedChartData[endIdx - 1].time || sortedChartData[endIdx - 1].date}`);

          // 모든 차트에 동일한 visible range 적용
          chartRef.current?.setVisibleRange(fromTime, toTime);
          volumeChartRef.current?.setVisibleRange(fromTime, toTime);
          macdChartRef.current?.setVisibleRange(fromTime, toTime);
          rsiChartRef.current?.setVisibleRange(fromTime, toTime);
          stochasticChartRef.current?.setVisibleRange(fromTime, toTime);
          cciChartRef.current?.setVisibleRange(fromTime, toTime);
          adxChartRef.current?.setVisibleRange(fromTime, toTime);
          obvChartRef.current?.setVisibleRange(fromTime, toTime);
          newPsychologicalChartRef.current?.setVisibleRange(fromTime, toTime);
          investPsychologicalChartRef.current?.setVisibleRange(fromTime, toTime);
          bwiChartRef.current?.setVisibleRange(fromTime, toTime);
          dmiChartRef.current?.setVisibleRange(fromTime, toTime);
          williamsRChartRef.current?.setVisibleRange(fromTime, toTime);
          trixChartRef.current?.setVisibleRange(fromTime, toTime);
          volumeOscChartRef.current?.setVisibleRange(fromTime, toTime);
          vrChartRef.current?.setVisibleRange(fromTime, toTime);
          disparityChartRef.current?.setVisibleRange(fromTime, toTime);

          // sharedViewRange도 업데이트
          setSharedViewRange({ start: startIdx, end: endIdx });

          // ✅ 초기 로드 완료 플래그 설정 (다시는 실행하지 않음)
          isInitialLoadDoneRef.current = true;
        }
      }, 500); // 500ms 지연 (차트가 완전히 렌더링될 때까지)

      return () => clearTimeout(timer);
    }
  }, [hasInitialData, chartData.length, loading]);

  // ✅ MA/일목균형표 설정 변경 감지 및 자동 데이터 로드
  const prevMaxMAPeriodRef = useRef<number>(0);
  const prevMaxIchimokuPeriodRef = useRef<number>(0);
  const prevMAPeriodsRef = useRef<number[]>([]); // ✅ MA 기간 목록 저장

  useEffect(() => {
    // 현재 활성화된 MA 중 최대 period 계산
    const enabledMAPeriods = indicatorVisibilitySettings.maLines
      ?.filter(ma => ma.enabled)
      .map(ma => ma.period) || [];
    const maxMAPeriod = enabledMAPeriods.length > 0 ? Math.max(...enabledMAPeriods) : 0;

    // 일목균형표 활성화 및 최대 period 계산
    const ichimokuEnabled = indicatorVisibilitySettings.ichimokuSenkouA || indicatorVisibilitySettings.ichimokuSenkouB;
    const maxIchimokuPeriod = ichimokuEnabled ? (indicatorVisibilitySettings.ichimokuSenkouBPeriod || 52) : 0;

    // 이전 최대 period와 비교
    const prevMaxMAPeriod = prevMaxMAPeriodRef.current;
    const prevMaxIchimokuPeriod = prevMaxIchimokuPeriodRef.current;

    // MA 또는 일목균형표 최대 period 계산
    const currentMaxPeriod = Math.max(maxMAPeriod, maxIchimokuPeriod);
    const prevMaxPeriod = Math.max(prevMaxMAPeriod, prevMaxIchimokuPeriod);

    // ✅ MA 기간 목록이 변경되었는지 확인
    const prevMAPeriods = prevMAPeriodsRef.current;
    const sortedCurrent = [...enabledMAPeriods].sort((a, b) => a - b);
    const sortedPrev = [...prevMAPeriods].sort((a, b) => a - b);
    const maPeriodsChanged = JSON.stringify(sortedCurrent) !== JSON.stringify(sortedPrev);

    console.log(`🔍 [MA 기간 체크] 이전: [${sortedPrev}], 현재: [${sortedCurrent}], 변경됨: ${maPeriodsChanged}`);

    // ✅ MA 기간 목록이 변경되었고, 데이터가 있으면 지표만 재계산
    // ⚠️ 첫 실행 시에도 동작하도록 prevMAPeriods.length 조건 제거
    if (maPeriodsChanged && candleData.length > 0 && sortedCurrent.length > 0) {
      console.log(`🔄 [MA 기간 변경 감지] 지표 데이터 재계산 시작 - 백엔드 API 호출`);
      
      // ✅ 이전 데이터를 초기화하지 않고 유지 (차트가 비어지는 문제 방지)
      // setAllIndicatorData([]);
      // allIndicatorDataRef.current = [];
      console.log(`🔄 [MA 기간 변경] 기존 데이터 유지하며 새 데이터 로드 중...`);
      
      // ✅ maCalculationData로 지표 재계산 (차트가 400개 전체 데이터를 사용하므로)
      (async () => {
        try {
          // 전체 maCalculationData(400개)로 지표 계산
          const calculatedIndicators = await loadIndicatorDataSync(maCalculationData, false);
          if (calculatedIndicators.length > 0) {
            // ✅ 1. 먼저 전체 400개 지표 데이터 업데이트 (차트 MA 렌더링용)
            setAllIndicatorData(calculatedIndicators);
            allIndicatorDataRef.current = calculatedIndicators;
            
            // ✅ 2. 마지막 200개만 추출 (표시용)
            const skipCount = maCalculationData.length - candleData.length;
            const displayIndicators = calculatedIndicators.slice(skipCount);
            setIndicatorData(displayIndicators);
            
            console.log(`✅ [MA 기간 변경] 지표 데이터 업데이트 완료: 전체 ${calculatedIndicators.length}개 (allIndicatorData) + 표시용 ${displayIndicators.length}개 (indicatorData)`);
          }
        } catch (err) {
          console.error('❌ [MA 기간 변경] 지표 재계산 실패:', err);
        }
      })();
    }

    // ✅ 현재 데이터가 부족한 경우 자동 재로드 (최대 period 증가 여부와 관계없이)
    if (currentMaxPeriod > 0) {
      const currentDataCount = maCalculationData.length > 0 ? maCalculationData.length : candleData.length;
      const requiredTotal = CHART_DISPLAY_COUNT + currentMaxPeriod;

      console.log(`🔄 [지표 설정 변경] MA: ${prevMaxMAPeriod} → ${maxMAPeriod}, 일목균형표: ${prevMaxIchimokuPeriod} → ${maxIchimokuPeriod}`);
      console.log(`🔄 [지표 설정 변경] 최대 period: ${prevMaxPeriod} → ${currentMaxPeriod}, 현재 데이터: ${currentDataCount}, 필요: ${requiredTotal}`);

      if (currentDataCount < requiredTotal) {
        console.log(`📊 [지표 설정 변경] 데이터 부족 - 자동 재로드 시작`);
        // ✅ loadData 호출 전에 loading 상태를 true로 설정하여 차트 데이터 유지
        setLoading(true);
        // requestAnimationFrame으로 다음 프레임에서 loadData 호출 (차트가 이전 데이터를 유지하도록 함)
        requestAnimationFrame(() => {
          loadData(false); // 현재 위치 유지하며 데이터만 추가 로드
        });
      } else {
        console.log(`✅ [지표 설정 변경] 데이터 충분 - 재로드 불필요`);
      }
    }

    // ✅ 현재 MA 기간 목록 저장
    prevMAPeriodsRef.current = enabledMAPeriods;
    // 현재 최대 period 저장
    prevMaxMAPeriodRef.current = maxMAPeriod;
    prevMaxIchimokuPeriodRef.current = maxIchimokuPeriod;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    maLinesVersion,  // ✅ MA 설정 변경 시 증가하는 버전 번호로 확실하게 감지
    indicatorVisibilitySettings.ichimokuSenkouA,
    indicatorVisibilitySettings.ichimokuSenkouB,
    indicatorVisibilitySettings.ichimokuTenkanPeriod,  // ✅ 전환선 기간 변경 감지
    indicatorVisibilitySettings.ichimokuKijunPeriod,   // ✅ 기준선 기간 변경 감지
    indicatorVisibilitySettings.ichimokuChikouPeriod,  // ✅ 후행스팬 기간 변경 감지
    indicatorVisibilitySettings.ichimokuSenkouAPeriod, // ✅ 선행스팬1 기간 변경 감지
    indicatorVisibilitySettings.ichimokuSenkouBPeriod,
    candleData.length,
    maCalculationData.length,
    loadData,
    setLoading
    // ✅ loadIndicatorDataSync, setIndicatorData는 dependency에서 제외 (안정성을 위해)
  ]);

  // ✅ 백테스트 완료 후 뷰 복원 (chartData가 재계산된 직후)
  const prevBacktestResultRef = useRef<BacktestResult | null>(null);
  const viewRangeToRestoreRef = useRef<{ start: number; end: number } | null>(null);

  useEffect(() => {
    // backtestResult가 null에서 non-null로 변경되거나, 
    // 또는 backtestResult의 tradeRecords가 변경된 경우
    const backtestChanged =
      (prevBacktestResultRef.current === null && backtestResult !== null) ||
      (prevBacktestResultRef.current !== null && backtestResult !== null &&
        prevBacktestResultRef.current.tradeRecords?.length !== backtestResult.tradeRecords?.length);

    if (backtestChanged && viewRangeToRestoreRef.current && candleData.length > 0) {
      const savedRange = viewRangeToRestoreRef.current;
      console.log('🔄 [useEffect] backtestResult 변경 감지 → 즉시 뷰 복원:', savedRange);

      // sharedViewRange 복원
      setSharedViewRange(savedRange);

      // 차트 라이브러리에도 즉시 visible range 설정
      setTimeout(() => {
        const sortedCandles = [...candleData].sort((a, b) =>
          new Date(a.time).getTime() - new Date(b.time).getTime()
        );

        const startIndex = Math.max(0, savedRange.start);
        const endIndex = Math.min(savedRange.end - 1, sortedCandles.length - 1);

        if (sortedCandles[startIndex] && sortedCandles[endIndex]) {
          const fromTime = new Date(sortedCandles[startIndex].time).getTime() / 1000;
          const toTime = new Date(sortedCandles[endIndex].time).getTime() / 1000;

          console.log('📊 [useEffect] 차트 visible range 강제 설정:', {
            fromTime: sortedCandles[startIndex].time,
            toTime: sortedCandles[endIndex].time,
          });

          // ✅ 즉시 visible range 설정 (재시도 로직 포함)
          const applyRestoreRange = (attempt: number = 1) => {
            const refs = {
              chart: chartRef.current,
              volume: volumeChartRef.current,
              macd: macdChartRef.current,
              rsi: rsiChartRef.current,
              stochastic: stochasticChartRef.current,
              cci: cciChartRef.current,
              adx: adxChartRef.current,
              obv: obvChartRef.current,
            };

            chartRef.current?.setVisibleRange(fromTime, toTime);
            volumeChartRef.current?.setVisibleRange(fromTime, toTime);
            macdChartRef.current?.setVisibleRange(fromTime, toTime);
            rsiChartRef.current?.setVisibleRange(fromTime, toTime);
            stochasticChartRef.current?.setVisibleRange(fromTime, toTime);
            cciChartRef.current?.setVisibleRange(fromTime, toTime);
            adxChartRef.current?.setVisibleRange(fromTime, toTime);
            obvChartRef.current?.setVisibleRange(fromTime, toTime);
            newPsychologicalChartRef.current?.setVisibleRange(fromTime, toTime);
            investPsychologicalChartRef.current?.setVisibleRange(fromTime, toTime);
            bwiChartRef.current?.setVisibleRange(fromTime, toTime);
            dmiChartRef.current?.setVisibleRange(fromTime, toTime);
            williamsRChartRef.current?.setVisibleRange(fromTime, toTime);
            trixChartRef.current?.setVisibleRange(fromTime, toTime);
            volumeOscChartRef.current?.setVisibleRange(fromTime, toTime);
            vrChartRef.current?.setVisibleRange(fromTime, toTime);
            disparityChartRef.current?.setVisibleRange(fromTime, toTime);

            const nullRefs = Object.entries(refs).filter(([_, ref]) => !ref);
            if (nullRefs.length > 0 && attempt < 3) {
              console.warn(`⚠️ [뷰 복원] Null refs 재시도 (${attempt}/3):`, nullRefs.map(([name]) => name));
              setTimeout(() => applyRestoreRange(attempt + 1), 100);
            }
          };

          applyRestoreRange(1);

          // DOM 렌더링 후 다시 시도
          requestAnimationFrame(() => {
            requestAnimationFrame(() => {
              applyRestoreRange(2);
            });
          });

          console.log('✅ [useEffect] 뷰 복원 완료');

          // 복원 완료 후 ref 초기화
          viewRangeToRestoreRef.current = null;

          // ✅ 뷰 복원 플래그 해제 (300ms 후 - 차트가 안정화될 때까지 대기)
          setTimeout(() => {
            isRestoringViewRef.current = false;
            console.log('🔓 뷰 복원 플래그 해제 (onVisibleRangeChange 재활성화)');
          }, 300);
        }
      }, 10);  // 50ms → 10ms로 단축
    }

    prevBacktestResultRef.current = backtestResult;
  }, [backtestResult, candleData]);

  // 전략 목록 로드
  useEffect(() => {
    const loadStrategies = async () => {
      try {
        const rules = await getStrategyRules();
        console.log('📊 전체 전략 목록:', rules);
        const enabledRules = rules.filter(r => r.enabled);
        console.log('📊 활성화된 전략:', enabledRules);
        console.log('📊 정배열 전략 (UPTREND/ALL):', enabledRules.filter(r => r.trendType === 'UPTREND' || r.trendType === 'ALL'));
        console.log('📊 역배열 전략 (DOWNTREND/ALL):', enabledRules.filter(r => r.trendType === 'DOWNTREND' || r.trendType === 'ALL'));
        setStrategyRules(enabledRules);
        
        // 디버깅용: window 객체에 노출
        (window as any).__strategyRulesCount = enabledRules.length;
        (window as any).__strategyRules = enabledRules;
        console.log('🔍 [DEBUG] 전략 Rules State 설정 완료:', enabledRules.length);
      } catch (err) {
        console.error('❌ 전략 목록 로드 실패:', err);
      }
    };
    loadStrategies();
  }, []);

  // 🤖 실시간 AI 분석 실행
  const executeRealTimeAnalysis = useCallback(async () => {
    if (!candleData || candleData.length === 0) {
      console.log('⚠️ 분석할 데이터가 없습니다.');
      return;
    }

    if (!realTimeAnalysisRef.current) {
      console.log('⚠️ 실시간 분석 패널 ref가 없습니다.');
      return;
    }

    console.log('🤖 실시간 AI 분석 시작:', symbol);
    realTimeAnalysisRef.current.startNewAnalysis();

    try {
      const response = await fetch('/api/agent/chat/stream', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: `${symbol} 종목을 분석해주세요`,
          symbol: symbol,
        }),
      });

      if (!response.ok) {
        throw new Error(`AI 분석 요청 실패: ${response.status}`);
      }

      const reader = response.body?.getReader();
      if (!reader) {
        throw new Error('스트리밍 응답을 받을 수 없습니다');
      }

      const decoder = new TextDecoder();
      let analysisText = '';
      let recommendation: 'strong_buy' | 'buy' | 'hold' | 'sell' | 'strong_sell' | null = null;

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value, { stream: true });
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6);
            if (data === '[DONE]') break;
            if (data.startsWith('{') && data.includes('error')) {
              try {
                const errorObj = JSON.parse(data);
                throw new Error(errorObj.error);
              } catch { /* ignore */ }
            }
            const content = data.replace(/\\n/g, '\n');
            analysisText += content;
            realTimeAnalysisRef.current.appendToLastAnalysis(content);

            if (analysisText.includes('적극 매수') || analysisText.includes('강력 매수') || analysisText.includes('강한 매수')) {
              recommendation = 'strong_buy';
            } else if (analysisText.includes('매수')) {
              recommendation = 'buy';
            } else if (analysisText.includes('적극 매도') || analysisText.includes('강력 매도') || analysisText.includes('강한 매도')) {
              recommendation = 'strong_sell';
            } else if (analysisText.includes('매도')) {
              recommendation = 'sell';
            } else if (analysisText.includes('관망') || analysisText.includes('보유') || analysisText.includes('중립')) {
              recommendation = 'hold';
            }
          }
        }
      }

      realTimeAnalysisRef.current.updateLastAnalysis(analysisText, recommendation);
      const now = new Date();
      setLastAnalysisTime(now);
      setNextAnalysisTime(calculateNextUpdateTime(now, timeInterval));
      console.log('✅ 실시간 AI 분석 완료:', recommendation);
    } catch (err) {
      console.error('❌ 실시간 AI 분석 오류:', err);
      if (realTimeAnalysisRef.current) {
        realTimeAnalysisRef.current.updateLastAnalysis(
          `⚠️ 분석 중 오류가 발생했습니다: ${err instanceof Error ? err.message : '알 수 없는 오류'}`,
          null
        );
      }
    }
  }, [candleData, symbol, timeInterval, calculateNextUpdateTime]);

  // ✅ 시간봉에 따른 갱신 주기 계산 (밀리초)
  const getRefreshInterval = useCallback((interval: string): number => {
    const match = interval.match(/^(\d+)([mhd]|w|M)$/);
    if (!match) return 60000; // 기본값: 1분
    
    const value = parseInt(match[1]);
    const unit = match[2];
    
    switch (unit) {
      case 'm': return value * 60 * 1000; // 분
      case 'h': return value * 60 * 60 * 1000; // 시간
      case 'd': return value * 24 * 60 * 60 * 1000; // 일
      case 'w': return value * 7 * 24 * 60 * 60 * 1000; // 주
      case 'M': return value * 30 * 24 * 60 * 60 * 1000; // 월 (약 30일)
      default: return 60000;
    }
  }, []);

  // 🔄 자동 데이터 갱신 (시간봉에 맞춘 주기)
  useEffect(() => {
    const shouldRun = autoRefreshEnabled || (realTimeAnalysisEnabled && realTimeAnalysisPanelOpen);

    if (!shouldRun) {
      if (autoRefreshIntervalRef.current) {
        clearInterval(autoRefreshIntervalRef.current);
        autoRefreshIntervalRef.current = null;
      }
      return;
    }

    if (loading) return;
    if (autoRefreshIntervalRef.current) return;

    // ✅ 시간봉에 맞춘 갱신 주기 계산 (현재 ref 값 사용)
    const currentInterval = timeIntervalRef.current;
    const refreshInterval = getRefreshInterval(currentInterval);
    const refreshMinutes = Math.round(refreshInterval / 60000);
    
    // ✅ 다음 interval 경계까지 남은 시간 계산 (interval 경계에 맞춰 갱신)
    const now = Date.now();
    const nextBoundary = Math.ceil(now / refreshInterval) * refreshInterval;
    const delayUntilBoundary = nextBoundary - now + 2000; // +2초 지연 (서버 데이터 준비 시간)
    
    console.log(`⏰ [자동 갱신] 시간봉 ${currentInterval}에 맞춰 ${refreshMinutes}분마다 갱신 시작 (첫 갱신까지 ${Math.round(delayUntilBoundary / 1000)}초)`);

    const refreshLogic = () => {
      // ✅ 이전 기간을 보고 있는지 확인 (ref 사용으로 즉시 반영)
      const isViewingHistoricalData = isViewingHistoricalDataRef.current;
      // ✅ 최신 timeInterval 값 참조
      const latestInterval = timeIntervalRef.current;

      console.log(`🔍 [자동 갱신 체크] interval=${latestInterval}, isViewingHistoricalData=${isViewingHistoricalData}, timeHistory.length=${timeHistory.length}, loadToTime=${loadToTime}`);

      if (isViewingHistoricalData) {
        console.log('⏸️ [자동 갱신] 이전 기간 보기 중 - 자동 갱신 건너뜀');
        return;
      }

      if (autoRefreshEnabled) {
        // ✅ 자동 새로고침: 워밍업 데이터 포함하여 정확한 보조지표 계산 (정확도 우선)
        console.log(`🔄 [자동 갱신] 최신 데이터 전체 업데이트 (워밍업 포함) - ${refreshMinutes}분 주기, interval=${latestInterval}`);
        loadData(false, null, false, false, false); // isIncrementalUpdate = false (정확도 우선)
      }

      if (realTimeAnalysisEnabled && realTimeAnalysisPanelOpen) {
        if (!autoRefreshEnabled) {
          // 실시간 분석 시에도 워밍업 포함하여 정확한 데이터 로드
          loadData(false, null, false, false, false); // isIncrementalUpdate = false (정확도 우선)
        }

        if (shouldUpdateNow(lastAnalysisTime, latestInterval)) {
          setTimeout(() => {
            executeRealTimeAnalysis();
          }, 1000);
        }
      }
    };

    // ✅ 첫 갱신을 interval 경계에 맞춰서 실행
    const firstTimeout = setTimeout(() => {
      refreshLogic(); // 경계 시점에 첫 갱신 실행
      
      // 이후 정기적으로 interval마다 갱신
      autoRefreshIntervalRef.current = setInterval(refreshLogic, refreshInterval);
    }, delayUntilBoundary);

    return () => {
      clearTimeout(firstTimeout);
      if (autoRefreshIntervalRef.current) {
        console.log(`⏰ [자동 갱신] 타이머 정리 (${refreshMinutes}분 주기)`);
        clearInterval(autoRefreshIntervalRef.current);
        autoRefreshIntervalRef.current = null;
      }
    };
  // ✅ timeInterval을 의존성에 포함하여 시간봉 변경 시 타이머 재시작
  // ⚠️ loadData는 제외 (timeIntervalRef로 최신 값 참조하므로 불필요)
  }, [autoRefreshEnabled, loading, realTimeAnalysisEnabled, realTimeAnalysisPanelOpen,
    executeRealTimeAnalysis, shouldUpdateNow, lastAnalysisTime, timeInterval, timeHistory.length, loadToTime, getRefreshInterval]);

  // 실시간 분석 패널이 열릴 때 다음 갱신 시간 초기화
  useEffect(() => {
    if (realTimeAnalysisPanelOpen && !nextAnalysisTime) {
      setNextAnalysisTime(calculateNextUpdateTime(null, timeInterval));
    }
  }, [realTimeAnalysisPanelOpen, nextAnalysisTime, timeInterval, calculateNextUpdateTime]);

  // 컴포넌트 언마운트 시 디바운스 타이머 정리
  useEffect(() => {
    return () => {
      if (hoverDebounceTimerRef.current) {
        clearTimeout(hoverDebounceTimerRef.current);
      }
    };
  }, []);

  // 투자실행 버튼 클릭 시 전략 분석 실행
  const handleExecuteStrategy = useCallback(async () => {
    console.log('🚀 [투자실행] 버튼 클릭됨');
    console.log('📊 [투자실행] 현재 상태:', {
      symbol,
      chartDataLength: chartData?.length || 0,
      trendStrategy,
      uptrendStrategyId,
      downtrendStrategyId,
      generalStrategyId,
    });

    if (!symbol || !chartData || chartData.length === 0) {
      console.warn('⚠️ [투자실행] symbol 또는 chartData가 없음');
      setBacktestResult(null);
      return;
    }

    // 추세전략에 따라 다른 검증 로직 적용
    if (trendStrategy === 'none') {
      // 추세전략 사용안함: 투자전략만 확인
      if (generalStrategyId === null) {
        console.warn('⚠️ [투자실행] 투자전략이 선택되지 않았습니다');
        alert('투자전략을 선택해주세요');
        setBacktestResult(null);
        return;
      }
    } else {
      // 추세전략 사용: 정배열/역배열 전략 확인
      if (uptrendStrategyId === null && downtrendStrategyId === null) {
        console.warn('⚠️ [투자실행] 전략이 선택되지 않았습니다');
        alert('정배열 전략 또는 역배열 전략을 선택해주세요');
        setBacktestResult(null);
        return;
      }
    }

    console.log('✅ [투자실행] 조건 통과, 백테스트 시작');

    // ✅ 백테스트 시작 시 기존 마커 즉시 초기화
    console.log('🧹 [투자실행] 기존 매수/매도 마커 초기화');
    setBacktestResult(null);

    // ✅ 백테스트 실행 전에 현재 뷰 범위 저장
    const savedViewRange = { ...sharedViewRange };
    const savedCandleDataLength = candleData.length;
    console.log('💾 백테스트 전 뷰 저장:', {
      viewRange: savedViewRange,
      candleDataLength: savedCandleDataLength,
    });

    // ref에도 저장 (useEffect에서 사용)
    viewRangeToRestoreRef.current = savedViewRange;

    // ✅ 뷰 복원 플래그 설정 (onVisibleRangeChange 콜백 무시)
    isRestoringViewRef.current = true;
    console.log('🔒 뷰 복원 플래그 설정 (onVisibleRangeChange 무시 시작)');

    setIsRunningBacktest(true);
    try {
      console.log('🔍 [디버깅] chartData 확인:', {
        chartDataLength: chartData?.length || 0,
        firstCandle: chartData[0],
        hasTime: !!chartData[0]?.time,
        hasTimestamp: !!chartData[0]?.timestamp,
        hasDate: !!chartData[0]?.date,
      });

      const startDateRaw = chartData[0]?.timestamp || chartData[0]?.date || '';
      const endDateRaw = chartData[chartData.length - 1]?.timestamp || chartData[chartData.length - 1]?.date || '';

      // ✅ 날짜 형식 정규화: 초(seconds)까지 포함하도록
      let startDate = startDateRaw;
      if (!startDate.includes('T')) {
        startDate = `${startDate}T00:00:00`;
      } else if (!startDate.match(/T\d{2}:\d{2}:\d{2}/)) {
        // "2025-12-26T01:00" -> "2025-12-26T01:00:00"
        startDate = `${startDate}:00`;
      }

      let endDate = endDateRaw;
      if (!endDate.includes('T')) {
        endDate = `${endDate}T23:59:59`;
      } else if (!endDate.match(/T\d{2}:\d{2}:\d{2}/)) {
        // "2025-12-26T01:00" -> "2025-12-26T01:00:00"
        endDate = `${endDate}:00`;
      }

      console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
      console.log('🎯 [투자실행] Frontend 설정값 확인');
      console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
      console.log('📊 종목 정보:', {
        symbol: symbol,
        timeInterval: timeInterval, // ✅ Frontend에 설정된 시간봉
        chartDataCount: chartData?.length || 0,
      });
      console.log('📅 백테스트 기간:', { startDate, endDate });
      console.log('📈 투자전략 설정:', {
        trendStrategy: trendStrategy,
        uptrendStrategyId: uptrendStrategyId,
        downtrendStrategyId: downtrendStrategyId,
        generalStrategyId: generalStrategyId,
      });
      console.log('📊 추세 판단 MA:', {
        trendShortMA: trendShortMA,
        trendMediumMA: trendMediumMA,
        trendLongMA: trendLongMA,
      });
      console.log('💰 거래 설정:', {
        buyAmount: buyAmount,
        sellAmount: sellAmount,
        totalBalance: totalBalance,
      });
      console.log('📊 보조지표 설정:', {
        macd: `(${paramSettings.macdFastPeriod}, ${paramSettings.macdSlowPeriod}, ${paramSettings.macdSignalPeriod})`,
        stochastic: `(K:${paramSettings.stochasticKPeriod}, D:${paramSettings.stochasticDPeriod}, Slowing:${paramSettings.stochasticSlowing})`,
        rsi: `(Period:${paramSettings.rsiPeriod}, Oversold:${paramSettings.rsiOversold}, Overbought:${paramSettings.rsiOverbought})`,
      });
      console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');

      // ✅ 백엔드가 Upbit API를 직접 호출하여 필요한 데이터를 수집
      console.log('📡 백엔드에서 Upbit API 호출하여 워밍업 데이터 포함 전체 데이터 수집');

      // ✅ 백테스트 전에 추가 워밍업 데이터 로드 (MA 계산용)
      console.log('📊 [백테스트 준비] 추가 워밍업 데이터 로드 시작 (MA 계산용)');
      let additionalWarmupData: CandleData[] = [];
      
      try {
        // 현재 차트의 첫 번째 캔들 이전 200개 데이터 요청
        const firstCandleTime = chartData[0].time || chartData[0].timestamp;
        const warmupParams = {
          market: symbol,
          timeframe: timeInterval,
          count: 200, // MA200까지 계산하기 위한 워밍업 데이터
          to: firstCandleTime,
        };
        
        console.log('📊 [워밍업 데이터] 요청:', warmupParams);
        const warmupResponse = await axios.get(`${API_BASE_URL}/pattern/candles`, {
          params: warmupParams,
        });
        
        const warmupCandleDataList = warmupResponse.data.data || [];
        const sortedWarmupCandles = [...warmupCandleDataList].sort((a: any, b: any) => {
          return new Date(a.time).getTime() - new Date(b.time).getTime();
        });
        
        additionalWarmupData = sortedWarmupCandles.map((candle: any) => ({
          time: candle.time,
          open: candle.open,
          high: candle.high,
          low: candle.low,
          close: candle.close,
          volume: candle.volume,
          isBuyPoint: false,
          isSellPoint: false,
        }));
        
        console.log('✅ [워밍업 데이터] 로드 완료:', additionalWarmupData.length, '개');
      } catch (warmupErr) {
        console.warn('⚠️ [워밍업 데이터] 로드 실패 (백테스트는 계속 진행):', warmupErr);
      }

      // 추세전략에 따라 다른 파라미터 전달
      const backtestParams = trendStrategy === 'none'
        ? {
          // 추세전략 사용안함: 투자전략만 사용 (정배열/역배열 구분 없음)
          symbol: symbol,
          startDate: startDate,
          endDate: endDate,
          initialCapital: totalBalance,
          positionSizeRatio: 1.0,
          commissionRate: 0.0005,
          strategyId: generalStrategyId || 0,
          uptrendStrategyId: generalStrategyId || undefined,
          downtrendStrategyId: generalStrategyId || undefined, // 동일한 전략 사용
          trendShortMA: trendShortMA,
          trendMediumMA: trendMediumMA,
          trendLongMA: trendLongMA,
          timeframe: timeInterval,
        }
        : {
          // 추세전략 사용: 정배열/역배열 전략 구분
          symbol: symbol,
          startDate: startDate,
          endDate: endDate,
          initialCapital: totalBalance,
          positionSizeRatio: 1.0,
          commissionRate: 0.0005,
          strategyId: uptrendStrategyId || downtrendStrategyId || 0,
          uptrendStrategyId: uptrendStrategyId || undefined,
          downtrendStrategyId: downtrendStrategyId || undefined,
          trendShortMA: trendShortMA,
          trendMediumMA: trendMediumMA,
          trendLongMA: trendLongMA,
          timeframe: timeInterval,
        };

      const result = await runBacktest({
        ...backtestParams,
        macdFastPeriod: paramSettings.macdFastPeriod,
        macdSlowPeriod: paramSettings.macdSlowPeriod,
        macdSignalPeriod: paramSettings.macdSignalPeriod,
        stochasticKPeriod: paramSettings.stochasticKPeriod,
        stochasticDPeriod: paramSettings.stochasticDPeriod,
        stochasticSlowing: paramSettings.stochasticSlowing,
        rsiPeriod: paramSettings.rsiPeriod,
        rsiOversold: paramSettings.rsiOversold,
        rsiOverbought: paramSettings.rsiOverbought,
        cciOversold: paramSettings.cciOversold || -100,
        cciMiddle: paramSettings.cciMiddle || 0,
        cciOverbought: paramSettings.cciOverbought || 100,
        buyAmount: buyAmount,
        sellAmount: sellAmount,
      });

      console.log('📊 백테스트 결과:', {
        candleDataCount: result.candleData?.length || 0,
        tradeRecordsCount: result.tradeRecords?.length || 0,
        buyPoints: result.candleData?.filter(c => c.isBuyPoint).length || 0,
        sellPoints: result.candleData?.filter(c => c.isSellPoint).length || 0,
        firstTrade: result.tradeRecords?.[0],
      });
      
      // 디버깅: 마커가 있는 캔들만 출력
      const candlesWithMarkers = result.candleData?.filter(c => c.isBuyPoint || c.isSellPoint) || [];
      console.log('🎯 [백테스트] 마커가 있는 캔들:', candlesWithMarkers.map(c => ({
        time: c.time,
        isBuyPoint: c.isBuyPoint,
        isSellPoint: c.isSellPoint,
      })));

      // ✅ 시간 정규화 함수: 초와 밀리초를 제거하여 YYYY-MM-DDTHH:mm 형태로 통일
      const normalizeTime = (time: string): string => {
        if (!time) return '';
        // "2026-02-23T15:00:00.000Z" → "2026-02-23T15:00"
        // "2026-02-23T15:00:00" → "2026-02-23T15:00"
        // "2026-02-23T15:00" → "2026-02-23T15:00"
        return time.substring(0, 16); // YYYY-MM-DDTHH:mm (16자)
      };
      
      // ✅ 백테스트 응답의 candleData와 indicatorData를 사용하여 워밍업 기간 마커 포함
      // 백엔드가 stateTrackingStartIndex부터 데이터를 반환하므로 워밍업 기간 마커도 포함됨
      if (result.candleData && result.candleData.length > 0) {
        console.log('✅ 백테스트 캔들 데이터 업데이트:', {
          이전_캔들수: candleData.length,
          새_캔들수: result.candleData.length,
          이전_범위: candleData.length > 0 ? `${candleData[0].time} ~ ${candleData[candleData.length-1].time}` : 'N/A',
          새_범위: `${result.candleData[0].time} ~ ${result.candleData[result.candleData.length-1].time}`,
        });
        
        // ✅ 기존 데이터를 유지하면서 마커 정보만 병합 (SMA 등 지표 데이터 보존)
        // - 백테스트 결과에는 마커 정보만 있고 SMA 등 기본 지표는 없음
        // - 시간을 기준으로 매칭하여 마커 정보만 추가
        
        // 1. 백테스트 결과를 Map으로 변환 (빠른 조회) - 시간 정규화 적용
        const backtestMarkerMap = new Map<string, { isBuyPoint?: boolean, isSellPoint?: boolean }>();
        result.candleData.forEach((candle, idx) => {
          const key = normalizeTime(candle.time); // ✅ 시간 정규화
          const candleWithMarker = candle as any;
          backtestMarkerMap.set(key, {
            isBuyPoint: candleWithMarker.isBuyPoint,
            isSellPoint: candleWithMarker.isSellPoint,
          });
          
          // 디버깅: 마커가 있는 캔들만 로그 출력
          if (candleWithMarker.isBuyPoint || candleWithMarker.isSellPoint) {
            console.log(`🎯 [마커 맵핑] 백테스트 결과 [${idx}]:`, {
              originalTime: candle.time,
              normalizedTime: key,
              isBuyPoint: candleWithMarker.isBuyPoint,
              isSellPoint: candleWithMarker.isSellPoint,
            });
          }
        });
        
        const backtestIndicatorMap = new Map<string, { isBuyPoint?: boolean, isSellPoint?: boolean }>();
        result.indicatorData?.forEach(indicator => {
          const key = normalizeTime(indicator.time); // ✅ 시간 정규화
          const indicatorWithMarker = indicator as any;
          backtestIndicatorMap.set(key, {
            isBuyPoint: indicatorWithMarker.isBuyPoint,
            isSellPoint: indicatorWithMarker.isSellPoint,
          });
        });
        
        // 2. 기존 차트 데이터에 백테스트 결과의 마커 정보만 병합
        // ✅ 차트 데이터를 교체하지 않고, 마커만 추가하여 차트가 바뀌지 않도록 함
        console.log('🔍 [마커 병합] 차트 데이터 샘플 (처음 3개):', candleData.slice(0, 3).map(c => ({
          time: c.time,
          timestamp: c.timestamp,
        })));
        console.log('🔍 [마커 병합] 백테스트 마커 Map 키 샘플 (처음 5개):', Array.from(backtestMarkerMap.keys()).slice(0, 5));
        
        let matchedCount = 0;
        const mergedCandleData = candleData.map((candle, idx) => {
          const key = normalizeTime(candle.time || candle.timestamp); // ✅ 시간 정규화
          const markers = backtestMarkerMap.get(key);
          
          if (markers && (markers.isBuyPoint || markers.isSellPoint)) {
            matchedCount++;
            console.log(`✅ [마커 매칭 성공] candleData[${idx}]:`, {
              chartTime: key,
              isBuyPoint: markers.isBuyPoint,
              isSellPoint: markers.isSellPoint,
            });
          }
          
          return {
            ...candle,
            isBuyPoint: markers?.isBuyPoint || false,
            isSellPoint: markers?.isSellPoint || false,
          };
        });
        
        console.log(`📊 [마커 병합 결과] 총 ${matchedCount}개 마커가 매칭됨`);
        
        // 3. 기존 indicatorData에 마커 정보 병합 (이전 마커 초기화 후 새 마커 적용)
        const mergedIndicatorData = indicatorData.map(indicator => {
          const key = normalizeTime(indicator.time); // ✅ 시간 정규화
          const markers = backtestIndicatorMap.get(key);
          // ✅ 모든 지표의 기존 마커를 false로 초기화한 후, 새 마커가 있으면 적용
          return {
            ...indicator,
            isBuyPoint: markers?.isBuyPoint || false,
            isSellPoint: markers?.isSellPoint || false,
          };
        });
        
        // 4. 병합된 데이터로 state와 ref 업데이트
        const mergedMaCalculationData = [...additionalWarmupData, ...mergedCandleData];
        maCalculationDataRef.current = mergedMaCalculationData;
        candleDataRef.current = mergedCandleData;
        indicatorDataRef.current = mergedIndicatorData;
        
        // ✅ state도 업데이트하여 chartData useMemo 재계산 트리거 (SMA 데이터 포함)
        // 디버깅: setCandleData 직전에 마커 상태 확인
        console.log('🔍 [setCandleData 직전] mergedCandleData 마커 상태:', {
          총_캔들수: mergedCandleData.length,
          매수_마커수: mergedCandleData.filter(c => c.isBuyPoint).length,
          매도_마커수: mergedCandleData.filter(c => c.isSellPoint).length,
          매수_마커_샘플: mergedCandleData.filter(c => c.isBuyPoint).slice(0, 3).map(c => ({
            time: c.time,
            isBuyPoint: c.isBuyPoint
          })),
          매도_마커_샘플: mergedCandleData.filter(c => c.isSellPoint).slice(0, 3).map(c => ({
            time: c.time,
            isSellPoint: c.isSellPoint
          })),
        });
        
        setCandleData(mergedCandleData);
        setIndicatorData(mergedIndicatorData);
        
        console.log('✅ [백테스트] state와 ref 모두 업데이트 (마커 표시 활성화, SMA 데이터 보존):', {
          워밍업_데이터: additionalWarmupData.length,
          백테스트_캔들: result.candleData.length,
          병합_캔들: mergedCandleData.length,
          MA계산_총데이터: mergedMaCalculationData.length,
          현재_candleData: candleData.length,
          현재_indicatorData: indicatorData.length,
          매수_마커: mergedCandleData.filter(c => c.isBuyPoint).length,
          매도_마커: mergedCandleData.filter(c => c.isSellPoint).length,
        });
      }
      
      console.log('✅ 백테스트 완료 (워밍업 기간 포함):', {
        backtestCandleCount: result.candleData?.length || 0,
        currentCandleCount: candleData.length,
        tradeRecords: result.tradeRecords?.length || 0,
        buyPoints: result.candleData?.filter(c => c.isBuyPoint).length || 0,
        sellPoints: result.candleData?.filter(c => c.isSellPoint).length || 0,
      });

      // ✅ 매수/매도 시그널이 0개인 경우 사용자에게 안내
      if (!result.tradeRecords || result.tradeRecords.length === 0) {
        console.warn('⚠️ 선택한 기간 동안 매수/매도 시그널이 발생하지 않았습니다.');
        
        // ✅ 시그널이 없으면 결과를 null로 설정 (마커 제거)
        setBacktestResult(null);
        
        alert(
          '선택한 기간 동안 매수/매도 시그널이 발생하지 않았습니다.\n\n' +
          '가능한 원인:\n' +
          '• 이동평균선 골든크로스/데드크로스가 발생하지 않음\n' +
          '• 전략 조건을 만족하는 시점이 없음\n' +
          '• 분석 기간이 짧거나 횡보 구간\n\n' +
          '💡 해결 방법:\n' +
          '• 더 긴 기간의 데이터 로드 (좌측/우측 화살표)\n' +
          '• 단기 MA 기간 사용 (예: MA5-MA20)\n' +
          '• 다른 전략 규칙 선택\n\n' +
          '✅ 워밍업 데이터가 자동으로 로드되었습니다.\n' +
          '백엔드 로그에서 상세한 분석 결과를 확인하세요.'
        );
        
        // ✅ 뷰 복원 플래그 해제 (시그널 없을 때는 뷰 복원 불필요)
        isRestoringViewRef.current = false;
        viewRangeToRestoreRef.current = null;
        console.log('🔓 [시그널 없음] 뷰 복원 플래그 해제');
      } else {
        console.log(`✅ 매수/매도 시그널 발견: 매수 ${result.candleData?.filter(c => c.isBuyPoint).length || 0}개, 매도 ${result.candleData?.filter(c => c.isSellPoint).length || 0}개`);
        
        // ✅ backtestResult 업데이트 (시그널이 있을 때만)
        // 뷰 복원은 useEffect에서 자동으로 처리됨
        console.log('🔄 백테스트 결과 업데이트 (뷰 복원은 useEffect에서 처리)');
        setBacktestResult(result);
      }
    } catch (err) {
      console.error('전략 분석 실패:', err);
      setBacktestResult(null);

      // ✅ 에러 발생 시에도 플래그 해제
      isRestoringViewRef.current = false;
      viewRangeToRestoreRef.current = null;
      console.log('🔓 [에러] 뷰 복원 플래그 해제');
    } finally {
      setIsRunningBacktest(false);
    }
  }, [symbol, chartData, trendStrategy, uptrendStrategyId, downtrendStrategyId, generalStrategyId, timeInterval, paramSettings, sharedViewRange, candleData, totalBalance, buyAmount, sellAmount]);

  // 시그널 업데이트 함수 (candleData와 indicatorData를 파라미터로 받아서 무한 루프 방지)
  const updateSignalsWithData = useCallback(async (candles: CandleData[], indicators: IndicatorData[]) => {
    if (!symbol || candles.length === 0) return;

    // 추세전략에 따라 다른 검증 로직
    if (trendStrategy === 'none') {
      if (!generalStrategyId) {
        console.log('⚠️ 투자전략이 선택되지 않음 - 시그널 분석 건너뜀');
        return;
      }
    } else {
      if (!uptrendStrategyId && !downtrendStrategyId) {
        console.log('⚠️ 전략이 선택되지 않음 - 시그널 분석 건너뜀');
        return;
      }
    }

    console.log(`📊 시그널 분석 시작: symbol=${symbol}, interval=${timeInterval}, candles=${candles.length}개, indicators=${indicators.length}개`);

    try {
      // ✅ candleData의 시간 범위 생성
      const candleTimeSet = new Set(candles.map(c => c.time));

      // ✅ indicatorData를 candleData의 시간 범위에 맞게 필터링
      const filteredIndicators = indicators.filter(ind => {
        const timeValue = ind.time || (ind as any).timestamp || (ind as any).date;
        return timeValue && candleTimeSet.has(timeValue);
      });

      console.log(`📊 indicatorData 필터링: ${indicators.length}개 → ${filteredIndicators.length}개 (candleData와 시간 일치)`);

      const request: SignalAnalysisRequest = {
        symbol,
        interval: timeInterval,
        candleData: candles.map(c => ({
          time: c.time,
          open: c.open,
          high: c.high,
          low: c.low,
          close: c.close,
          volume: c.volume,
          isBuyPoint: false,
          isSellPoint: false
        })),
        indicatorData: filteredIndicators.length > 0 ? filteredIndicators.map(ind => {
          const timeValue = ind.time || (ind as any).timestamp || (ind as any).date;
          return {
            time: timeValue,
            macdLine: ind.macdLine,
            macdSignal: ind.signalLine,
            macdHistogram: ind.histogram,
            stochasticK: ind.stochK,
            stochasticD: ind.stochD,
            rsi: ind.rsi,
            ema5: ind.ma10,
            ema10: ind.ma10,
            ema20: ind.ma20,
            ema60: ind.ma60,
            ema120: ind.ma200,
          };
        }) : undefined,
        uptrendStrategyId: trendStrategy === 'none' ? (generalStrategyId || undefined) : (uptrendStrategyId || undefined),
        downtrendStrategyId: trendStrategy === 'none' ? (generalStrategyId || undefined) : (downtrendStrategyId || undefined),
        trendShortMA: trendShortMA,
        trendMediumMA: trendMediumMA,
        trendLongMA: trendLongMA,
        macdFastPeriod: paramSettings.macdFastPeriod,
        macdSlowPeriod: paramSettings.macdSlowPeriod,
        macdSignalPeriod: paramSettings.macdSignalPeriod,
        stochasticKPeriod: paramSettings.stochasticKPeriod,
        stochasticDPeriod: paramSettings.stochasticDPeriod,
        stochasticSlowing: paramSettings.stochasticSlowing,
        rsiPeriod: paramSettings.rsiPeriod,
        rsiOversold: paramSettings.rsiOversold,
        rsiOverbought: paramSettings.rsiOverbought,
      };

      const response = await analyzeSignals(request);

      if (response.success && response.signals) {
        const appliedBuyCount = response.signals.filter(s => s.isBuyPoint).length;
        const appliedSellCount = response.signals.filter(s => s.isSellPoint).length;

        console.log(`✅ 시그널 분석 완료: 매수 ${appliedBuyCount}개, 매도 ${appliedSellCount}개`);

        const tradeRecords = response.signals
          .filter(s => s.isBuyPoint || s.isSellPoint)
          .map(s => ({
            timestamp: s.time,
            tradeType: s.isBuyPoint ? 'BUY' : 'SELL' as 'BUY' | 'SELL',
            strategyName: s.strategyName || '',
            trendStatus: s.trendStatus || '',
            matchedConditions: s.matchedConditions || [],
            price: 0,
            quantity: 0,
            totalAmount: 0,
            balance: 0,
            profit: 0,
            profitRate: 0,
          }));

        const tradeRecordsMap = new Map<string, 'BUY' | 'SELL'>();
        tradeRecords.forEach(trade => {
          let normalizedTimestamp = (trade.timestamp || '').replace(' ', 'T');
          const match = normalizedTimestamp.match(/(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2})/);
          normalizedTimestamp = match ? match[1].replace(' ', 'T') : normalizedTimestamp;

          if (normalizedTimestamp && (trade.tradeType === 'BUY' || trade.tradeType === 'SELL')) {
            tradeRecordsMap.set(normalizedTimestamp, trade.tradeType);
          }
        });

        setBacktestResult(prev => {
          if (prev) {
            return {
              ...prev,
              tradeRecords,
              tradeRecordsMap,
              buySignalCount: appliedBuyCount,
              sellSignalCount: appliedSellCount,
            };
          } else {
            return {
              symbol,
              interval: timeInterval,
              candleData: [],
              indicatorData: [],
              tradeRecords,
              tradeRecordsMap,
              buySignalCount: appliedBuyCount,
              sellSignalCount: appliedSellCount,
              profitRate: 0,
              totalProfit: 0,
              winRate: 0,
              maxDrawdown: 0,
            } as any;
          }
        });

        console.log(`🔄 backtestResult 업데이트 완료: tradeRecords ${tradeRecords.length}개, tradeRecordsMap ${tradeRecordsMap.size}개`);
      }
    } catch (error) {
      console.error('❌ 시그널 분석 실패:', error);
    }
  }, [symbol, timeInterval, trendStrategy, uptrendStrategyId, downtrendStrategyId, generalStrategyId, paramSettings]);

  // ✅ updateSignalsWithData를 ref로 관리하여 최신 함수 참조 보장
  const updateSignalsWithDataRef = useRef(updateSignalsWithData);
  useEffect(() => {
    updateSignalsWithDataRef.current = updateSignalsWithData;
  }, [updateSignalsWithData]);

  // ✅ 전략 변경 시 시그널 자동 갱신 (Debounce 적용)
  useEffect(() => {
    if (candleData.length === 0 || indicatorData.length === 0) return;

    const debounceTimer = setTimeout(() => {
      const hasStrategy = trendStrategy === 'none'
        ? generalStrategyId !== null
        : (uptrendStrategyId !== null || downtrendStrategyId !== null);

      if (hasStrategy) {
        console.log(`🔄 전략 변경 감지 - 시그널 자동 갱신 (debounced 500ms): trendStrategy=${trendStrategy}, uptrend=${uptrendStrategyId}, downtrend=${downtrendStrategyId}, general=${generalStrategyId}`);
        updateSignalsWithDataRef.current(candleData, indicatorData);
      } else {
        console.log('🔄 전략 없음 - 시그널 초기화');
        setBacktestResult(null);
      }
    }, 500);

    return () => clearTimeout(debounceTimer);
  }, [trendStrategy, uptrendStrategyId, downtrendStrategyId, generalStrategyId]); // ⚠️ candleData, indicatorData 제거 (무한 루프 방지)

  // ✅ 데이터 로드 완료 후 시그널 자동 갱신은 loadData 함수 내에서 처리됨
  // (이 useEffect는 loadData에서 직접 호출하므로 제거됨)

  // ✅ 캔들차트 리사이즈 핸들러 (초록색 버튼과 동일한 방식)
  const handleCandleChartResize = useCallback((newHeight: number) => {
    setCandleChartHeight(newHeight);
  }, []);

  // ✅ 캔들차트 리사이즈 시작
  const handleCandleChartResizeStart = useCallback(() => {
    setIsCandleChartResizing(true);
  }, []);

  // ✅ 캔들차트 리사이즈 종료
  const handleCandleChartResizeEnd = useCallback(() => {
    setIsCandleChartResizing(false);
  }, []);

  // ✅ 보조지표 리사이즈 핸들러
  const handleIndicatorPanelResize = useCallback((newHeight: number) => {
    setIndicatorPanelHeight(newHeight);
  }, []);

  // ✅ 보조지표 리사이즈 시작
  const handleIndicatorResizeStart = useCallback(() => {
    setIsIndicatorResizing(true);
  }, []);

  // ✅ 보조지표 리사이즈 종료
  const handleIndicatorResizeEnd = useCallback(() => {
    setIsIndicatorResizing(false);
  }, []);

  // ✅ 전체화면 토글 핸들러
  const handleToggleFullscreen = useCallback(() => {
    setIsChartFullscreen(prev => {
      const newFullscreen = !prev;

      if (newFullscreen) {
        // 전체화면 진입 시: 현재 뷰 범위 저장
        savedViewRangeRef.current = sharedViewRange;
        console.log('🖥️ 전체화면 진입 - 현재 뷰 범위 저장:', sharedViewRange);
      } else {
        // 전체화면 종료 시: 차트 리사이즈 및 전체 데이터 표시
        // requestAnimationFrame을 사용하여 브라우저 레이아웃 계산 완료 대기
        requestAnimationFrame(() => {
          requestAnimationFrame(() => {
            setTimeout(() => {
              console.log('🔄 전체화면 종료 - 차트 리사이즈 및 fitContent 시작');

              // 1단계: 차트 리사이즈 (DOM이 완전히 렌더링된 후)
              chartRef.current?.resize();
              volumeChartRef.current?.resize();
              macdChartRef.current?.resize();
              rsiChartRef.current?.resize();
              stochasticChartRef.current?.resize();
              cciChartRef.current?.resize();
              adxChartRef.current?.resize();
              obvChartRef.current?.resize();

              // 2단계: 전체 데이터 표시 (리사이즈 후 약간의 딜레이)
              setTimeout(() => {
                console.log('🔄 전체화면 종료 - 전체 데이터 표시 (fitContent)');

                // 모든 차트에 fitContent 적용하여 전체 데이터 표시
                chartRef.current?.fitContent();
                volumeChartRef.current?.fitContent();
                macdChartRef.current?.fitContent();
                rsiChartRef.current?.fitContent();
                stochasticChartRef.current?.fitContent();
                cciChartRef.current?.fitContent();
                adxChartRef.current?.fitContent();
                obvChartRef.current?.fitContent();

                // 플래그 및 저장된 범위 초기화
                isRestoringViewRangeRef.current = false;
                savedViewRangeRef.current = null;
                console.log('✅ 전체화면 종료 - 완료');
              }, 200);
            }, 150);
          });
        });
      }

      return newFullscreen;
    });
  }, [sharedViewRange]);

  // ✅ ESC 키로 전체화면 종료
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape' && isChartFullscreen) {
        handleToggleFullscreen(); // 토글 핸들러 호출하여 뷰 범위 복원 로직 실행
      }
    };

    if (isChartFullscreen) {
      document.addEventListener('keydown', handleKeyDown);
    }

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [isChartFullscreen, handleToggleFullscreen]);

  // ✅ 전체화면 상태 변경 감지 및 차트 리사이즈
  useEffect(() => {
    if (!isChartFullscreen) {
      // 전체화면에서 기본 화면으로 전환될 때만 실행
      // requestAnimationFrame을 사용하여 DOM 업데이트 완료 대기
      const rafId1 = requestAnimationFrame(() => {
        const rafId2 = requestAnimationFrame(() => {
          // 추가 안전성을 위해 약간의 딜레이 후 리사이즈 및 fitContent
          const timerId = setTimeout(() => {
            console.log('🔧 전체화면 종료 후 차트 리사이즈 및 fitContent (useEffect)');

            // 리사이즈
            chartRef.current?.resize();
            volumeChartRef.current?.resize();
            macdChartRef.current?.resize();
            rsiChartRef.current?.resize();
            stochasticChartRef.current?.resize();
            cciChartRef.current?.resize();
            adxChartRef.current?.resize();
            obvChartRef.current?.resize();

            // 전체 데이터 표시
            setTimeout(() => {
              chartRef.current?.fitContent();
              volumeChartRef.current?.fitContent();
              macdChartRef.current?.fitContent();
              rsiChartRef.current?.fitContent();
              stochasticChartRef.current?.fitContent();
              cciChartRef.current?.fitContent();
              adxChartRef.current?.fitContent();
              obvChartRef.current?.fitContent();
            }, 100);
          }, 50);

          return () => clearTimeout(timerId);
        });
      });
    }
  }, [isChartFullscreen]);

  // ✅ 툴팁 토글 핸들러
  const handleToggleTooltip = useCallback(() => {
    setTooltipEnabled(prev => !prev);
  }, []);

  // ✅ 차트 돋보기 (확대/축소) 핸들러
  const handleZoomIn = useCallback(() => {
    setZoomLevel(prev => Math.min(200, prev + 10));
  }, []);

  const handleZoomOut = useCallback(() => {
    setZoomLevel(prev => Math.max(50, prev - 10));
  }, []);

  const handleZoomReset = useCallback(() => {
    setZoomLevel(100);
  }, []);

  // ✅ 차트 이동 (좌우 스크롤) 핸들러
  // 시간 범위 대신 logical range(바 인덱스)를 사용해 무반응/깜빡임을 줄인다.
  const applyLogicalPan = useCallback((direction: 'left' | 'right') => {
    if (!chartRef.current || chartData.length === 0) return;

    try {
      const currentRange = chartRef.current.getVisibleLogicalRange();
      if (!currentRange) return;

      const { from, to } = currentRange;
      const rangeSize = Math.max(1, to - from);
      const moveAmount = Math.max(1, rangeSize * 0.1);
      const maxIndex = Math.max(0, chartData.length - 1);

      let newFrom = from;
      let newTo = to;
      if (direction === 'left') {
        newFrom = Math.max(0, from - moveAmount);
        newTo = newFrom + rangeSize;
      } else {
        newTo = Math.min(maxIndex, to + moveAmount);
        newFrom = Math.max(0, newTo - rangeSize);
      }

      // 실제 이동이 없으면 불필요한 setRange 호출 방지
      if (Math.abs(newFrom - from) < 0.0001 && Math.abs(newTo - to) < 0.0001) return;

      // 메인/보조 차트를 동일 프레임에 적용해 깜빡임 최소화
      requestAnimationFrame(() => {
        chartRef.current?.setVisibleLogicalRange(newFrom, newTo);
        [volumeChartRef, macdChartRef, rsiChartRef, stochasticChartRef,
         cciChartRef, adxChartRef, dmiChartRef, bwiChartRef,
         obvChartRef, trixChartRef, williamsRChartRef,
         vrChartRef, volumeOscChartRef, disparityChartRef,
         newPsychologicalChartRef, investPsychologicalChartRef
        ].forEach(ref => {
          ref.current?.setVisibleLogicalRange(newFrom, newTo);
        });
      });
    } catch (error) {
      console.error(`차트 ${direction === 'left' ? '왼쪽' : '오른쪽'} 이동 실패:`, error);
    }
  }, [chartData.length]);

  const handlePanLeft = useCallback(() => {
    applyLogicalPan('left');
  }, [applyLogicalPan]);

  const handlePanRight = useCallback(() => {
    applyLogicalPan('right');
  }, [applyLogicalPan]);

  // ✅ zoomLevel 변경 시 모든 차트에 동기화
  const lastZoomLevelRef = useRef<number>(100);
  const isZoomingRef = useRef<boolean>(false);
  const isSyncingVisibleRangeRef = useRef<boolean>(false); // ✅ 차트 스크롤 동기화 중 플래그

  useEffect(() => {
    if (!chartRef.current || chartData.length === 0) return;

    // 줌 레벨이 실제로 변경되었을 때만 실행
    if (lastZoomLevelRef.current === zoomLevel) return;

    // 이미 줌 처리 중이면 스킵
    if (isZoomingRef.current) return;

    try {
      isZoomingRef.current = true;

      // 현재 visible range 가져오기
      const visibleRange = chartRef.current.getVisibleRange();
      if (!visibleRange) {
        isZoomingRef.current = false;
        return;
      }

      const { from, to } = visibleRange;
      const currentRange = to - from;

      // 줌 레벨 변화량 계산
      const previousZoom = lastZoomLevelRef.current;
      const zoomRatio = zoomLevel / previousZoom;

      // 새로운 범위 계산 (줌 레벨이 증가하면 범위가 줄어듦)
      const newRange = currentRange / zoomRatio;

      // ✅ 업비트 스타일: 우측(최신 시간)을 고정하고 좌측만 확대/축소
      // 우측 끝점(to)은 고정, 좌측(from)만 조정
      let newTo = to;  // 우측 고정
      let newFrom = to - newRange;  // 좌측만 이동

      // 데이터 범위 제한 (차트 데이터 범위를 벗어나지 않도록)
      if (chartData.length > 0) {
        const sortedData = [...chartData].sort((a, b) =>
          new Date(a.time || a.date).getTime() - new Date(b.time || b.date).getTime()
        );
        const firstTime = new Date(sortedData[0].time || sortedData[0].date).getTime() / 1000;
        const lastTime = new Date(sortedData[sortedData.length - 1].time || sortedData[sortedData.length - 1].date).getTime() / 1000;

        newFrom = Math.max(firstTime, newFrom);
        newTo = Math.min(lastTime, newTo);
      }

      console.log(`🔍 [줌 컨트롤] ${previousZoom}% → ${zoomLevel}% (비율: ${zoomRatio.toFixed(2)})`);
      console.log(`🔍 [줌 컨트롤] 범위: ${new Date(from * 1000).toISOString().slice(0, 16)} ~ ${new Date(to * 1000).toISOString().slice(0, 16)} → ${new Date(newFrom * 1000).toISOString().slice(0, 16)} ~ ${new Date(newTo * 1000).toISOString().slice(0, 16)}`);

      // 모든 차트에 동시에 적용하는 함수
      const applyVisibleRangeToAllCharts = () => {
        chartRef.current?.setVisibleRange(newFrom, newTo);
        volumeChartRef.current?.setVisibleRange(newFrom, newTo);
        macdChartRef.current?.setVisibleRange(newFrom, newTo);
        rsiChartRef.current?.setVisibleRange(newFrom, newTo);
        stochasticChartRef.current?.setVisibleRange(newFrom, newTo);
        cciChartRef.current?.setVisibleRange(newFrom, newTo);
        adxChartRef.current?.setVisibleRange(newFrom, newTo);
        obvChartRef.current?.setVisibleRange(newFrom, newTo);
        newPsychologicalChartRef.current?.setVisibleRange(newFrom, newTo);
        investPsychologicalChartRef.current?.setVisibleRange(newFrom, newTo);
        bwiChartRef.current?.setVisibleRange(newFrom, newTo);
        dmiChartRef.current?.setVisibleRange(newFrom, newTo);
        williamsRChartRef.current?.setVisibleRange(newFrom, newTo);
        trixChartRef.current?.setVisibleRange(newFrom, newTo);
        volumeOscChartRef.current?.setVisibleRange(newFrom, newTo);
        vrChartRef.current?.setVisibleRange(newFrom, newTo);
        disparityChartRef.current?.setVisibleRange(newFrom, newTo);
      };

      // 즉시 적용
      applyVisibleRangeToAllCharts();

      // 다음 프레임에 한 번 더 적용하여 확실히 일치시킴
      requestAnimationFrame(() => {
        applyVisibleRangeToAllCharts();

        // 줌 처리 완료
        setTimeout(() => {
          isZoomingRef.current = false;
        }, 50);
      });

      // 마지막 줌 레벨 업데이트
      lastZoomLevelRef.current = zoomLevel;
    } catch (error) {
      console.error('[줌 컨트롤] 오류:', error);
      isZoomingRef.current = false;
    }
  }, [zoomLevel, chartData]);

  // ✅ 피보나치 타임존 클릭 핸들러 (툴바 표시)
  const handleFibonacciClick = useCallback((objectId: string, event: React.MouseEvent) => {
    const fib = fibonacciTimezones.find(f => f.id === objectId);
    if (fib) {
      console.log('🖱️ [피보나치 클릭] objectId:', objectId);
      setSelectedFibId(objectId);
      setFibToolbarPosition({
        top: event.clientY - 40,
        left: event.clientX,
      });
      setFibToolbarOpen(true);
    }
  }, [fibonacciTimezones]);

  // ✅ 피보나치 타임존 더블클릭 핸들러
  const handleFibonacciDoubleClick = useCallback((objectId: string) => {
    const fib = fibonacciTimezones.find(f => f.id === objectId);
    if (fib) {
      console.log('🖱️ [피보나치 더블클릭] objectId:', objectId);
      setEditingFibId(objectId);
      setFibDialogOpen(true);
      setFibToolbarOpen(false); // 툴바 닫기
      // useEffect가 설정을 로드할 것임
    }
  }, [fibonacciTimezones]);

  const handleFibonacciSave = useCallback(async (settings: FibonacciTimezoneSettings) => {
    console.log('💾 [피보나치 설정 저장] editingFibId:', editingFibId);
    console.log('💾 [피보나치 설정 저장] settings:', JSON.stringify(settings, null, 2));
    console.log('💾 [피보나치 설정 저장] baseline:', JSON.stringify(settings.baseline, null, 2));

    if (editingFibId) {
      // ✅ 개별 피보나치 객체의 설정만 업데이트 (기존 기준선 기준으로 갱신)
      setFibonacciTimezones(prev => {
        const updated = prev.map(fib =>
          fib.id === editingFibId ? { ...fib, settings } : fib
        );
        console.log('✅ [피보나치 설정 저장] 업데이트된 피보나치 객체:', updated.find(f => f.id === editingFibId));
        return updated;
      });

      // ✅ fibSettings 상태도 즉시 업데이트 (다이얼로그 재오픈 시 최신 설정 표시)
      setFibSettings(settings);

      console.log('✅ [피보나치 설정 저장] 개별 객체 업데이트 완료:', editingFibId);
      console.log('✅ [피보나치 설정 저장] 기존 피보나치가 유지되며 설정만 변경됨');

      // ✅ 개별 설정 변경 시 기본 설정도 함께 업데이트 (삭제 후 다시 그릴 때 같은 설정 사용)
      console.log('💾 [피보나치 기본 설정도 함께 업데이트] 삭제 후 다시 그릴 때도 동일한 설정 사용');
      setFibDefaultSettings(settings);
      setIsFibSettingsLoaded(true);

      // DB에 기본 설정 저장
      try {
        const deviceId = getDeviceId();
        const jsonString = JSON.stringify(settings);
        await settingsApi.saveGuestFibonacciSettings(deviceId, jsonString);
        console.log('✅ [피보나치 기본 설정] DB 저장 완료!');
      } catch (error) {
        console.error('❌ [피보나치 기본 설정] DB 저장 실패:', error);
      }

      // ✅ 설정 변경 후 drawing 상태 및 드래그 상태 초기화 및 crosshair로 전환
      setIsDrawingFib(false);
      setFibStartTime(null);
      setFibStartIndex(null);
      setDraggingFibId(null);
      setDragStartX(0);
      setDragStartFibTime(null);
      setDragStartMouseTime(null);
      setActiveTool('crosshair');
      console.log('✅ [피보나치 설정 저장] 모든 드래그 상태 초기화 및 activeTool을 crosshair로 변경');
    } else {
      // ✅ 기본 설정 편집 (툴바 설정 버튼 클릭 시)
      console.log('💾 [피보나치 기본 설정 저장 시작]');
      console.log('💾 [저장 전] 현재 fibDefaultSettings:', JSON.stringify(fibDefaultSettings, null, 2));

      // 먼저 상태 업데이트
      setFibDefaultSettings(settings);
      setIsFibSettingsLoaded(true);
      console.log('💾 [저장 후] 새로운 settings:', JSON.stringify(settings, null, 2));

      // DB에 저장
      try {
        const deviceId = getDeviceId();
        console.log('💾 [피보나치 설정 저장] deviceId:', deviceId);
        const jsonString = JSON.stringify(settings);
        console.log('💾 [피보나치 설정 저장] JSON 문자열:', jsonString);

        await settingsApi.saveGuestFibonacciSettings(deviceId, jsonString);
        console.log('✅ [피보나치 설정 저장] DB 저장 완료!');

        // 저장 확인을 위해 다시 로드
        const savedSettings = await settingsApi.getGuestFibonacciSettings(deviceId);
        console.log('✅ [피보나치 설정 저장] DB 저장 확인:', savedSettings);
      } catch (error) {
        console.error('❌ [피보나치 설정 저장] DB 저장 실패:', error);
        alert('피보나치 설정 저장에 실패했습니다. 콘솔을 확인해주세요.');
      }
    }

    setEditingFibId(null);
  }, [editingFibId, fibDefaultSettings]);

  // ✅ 피보나치 삭제 핸들러
  const handleFibonacciDelete = useCallback(() => {
    console.log('🗑️ [피보나치 삭제] editingFibId:', editingFibId);

    if (editingFibId) {
      setFibonacciTimezones(prev => prev.filter(fib => fib.id !== editingFibId));
      console.log('✅ [피보나치 삭제] 완료:', editingFibId);
      setEditingFibId(null);

      // ✅ 삭제 후 drawing 및 드래그 상태 초기화
      setIsDrawingFib(false);
      setFibStartTime(null);
      setFibStartIndex(null);
      setDraggingFibId(null);
      setDragStartX(null);
      setDragStartFibTime(null);
      setDragStartMouseTime(null);
      console.log('✅ [피보나치 삭제] drawing 및 드래그 상태 초기화');
    }
  }, [editingFibId]);

  // ✅ 피보나치 툴바 핸들러들
  const handleFibToolbarColorChange = useCallback((color: string) => {
    if (!selectedFibId) return;
    
    setFibonacciTimezones(prev => prev.map(fib => {
      if (fib.id === selectedFibId) {
        return {
          ...fib,
          settings: {
            ...fib.settings,
            baseline: { ...fib.settings.baseline, color },
            levels: fib.settings.levels.map(level => ({ ...level, color })),
          },
        };
      }
      return fib;
    }));
  }, [selectedFibId]);

  const handleFibToolbarLineWidthChange = useCallback((lineWidth: number) => {
    if (!selectedFibId) return;
    
    setFibonacciTimezones(prev => prev.map(fib => {
      if (fib.id === selectedFibId) {
        return {
          ...fib,
          settings: {
            ...fib.settings,
            baseline: { ...fib.settings.baseline, lineWidth },
            levels: fib.settings.levels.map(level => ({ ...level, lineWidth })),
          },
        };
      }
      return fib;
    }));
  }, [selectedFibId]);

  const handleFibToolbarLineStyleChange = useCallback((lineStyle: 'solid' | 'dashed' | 'dotted') => {
    if (!selectedFibId) return;
    
    setFibonacciTimezones(prev => prev.map(fib => {
      if (fib.id === selectedFibId) {
        return {
          ...fib,
          settings: {
            ...fib.settings,
            baseline: { ...fib.settings.baseline, lineStyle },
            levels: fib.settings.levels.map(level => ({ ...level, lineStyle })),
          },
        };
      }
      return fib;
    }));
  }, [selectedFibId]);

  const handleFibToolbarDelete = useCallback(() => {
    if (!selectedFibId) return;
    
    setFibonacciTimezones(prev => prev.filter(fib => fib.id !== selectedFibId));
    setFibToolbarOpen(false);
    setSelectedFibId(null);
    console.log('✅ [피보나치 툴바] 삭제 완료:', selectedFibId);
  }, [selectedFibId]);

  // ✅ 기간 측정 도구 더블클릭 핸들러
  const handleMeasureDoubleClick = useCallback((drawingId: string) => {
    console.log('🖱️ [기간 측정 더블클릭] drawingId:', drawingId);
    setEditingMeasureId(drawingId);
    setMeasureSettings(measureDefaultSettings);
    setMeasureDialogOpen(true);
  }, [measureDefaultSettings]);

  // ✅ 기간 측정 설정 저장 핸들러
  const handleMeasureSave = useCallback(async (settings: MeasureSettings) => {
    console.log('💾 [기간 측정 설정 저장] settings:', settings);

    // 개별 측정 도형 수정 중이면 해당 도형 업데이트
    if (editingMeasureId) {
      const updatedDrawings = drawings.map(d => {
        if (d.id === editingMeasureId && d.type === 'measure') {
          return {
            ...d,
            style: {
              ...d.style,
              color: settings.lineColor,
              lineStyle: settings.lineStyle,
              lineWidth: settings.lineWidth,
              fillColor: settings.backgroundColor,
              fillOpacity: settings.backgroundOpacity,
              fontSize: settings.labelSize,
            },
          };
        }
        return d;
      });
      updateDrawingsWithHistory(updatedDrawings);
    }

    // 기본 설정 업데이트
    setMeasureDefaultSettings(settings);
    setIsMeasureSettingsLoaded(true);

    // DB에 저장
    try {
      const deviceId = getDeviceId();
      const jsonString = JSON.stringify(settings);
      await settingsApi.saveGuestMeasureSettings(deviceId, jsonString);
      console.log('✅ [기간 측정 설정 저장] DB 저장 완료!');
    } catch (error) {
      console.error('❌ [기간 측정 설정 저장] DB 저장 실패:', error);
      alert('기간 측정 설정 저장에 실패했습니다.');
    }

    setEditingMeasureId(null);
  }, [editingMeasureId, drawings, updateDrawingsWithHistory]);

  // ✅ 기간 측정 삭제 핸들러 (MacdChart의 drawings에서 삭제하도록 전달)
  const handleMeasureDelete = useCallback(() => {
    console.log('🗑️ [기간 측정 삭제] editingMeasureId:', editingMeasureId);
    // MacdChart 내부의 drawings 상태를 직접 수정할 수 없으므로,
    // 이 기능은 MacdChart 내부에서 처리하거나 별도 prop으로 전달 필요
    // 현재는 설정 다이얼로그에서 삭제 버튼을 숨김 처리
    setEditingMeasureId(null);
  }, [editingMeasureId]);

  // ✅ 가장 가까운 캔들 인덱스와 시간 찾기
  const findNearestCandle = useCallback((clickTime: number): { index: number; time: number } | null => {
    if (candleData.length === 0) return null;

    let nearestIndex = 0;
    let nearestTime = clickTime;
    let minDiff = Infinity;

    for (let i = 0; i < candleData.length; i++) {
      const candleTime = new Date(candleData[i].time).getTime() / 1000;
      const diff = Math.abs(candleTime - clickTime);

      if (diff < minDiff) {
        minDiff = diff;
        nearestIndex = i;
        nearestTime = candleTime;
      }
    }

    console.log(`🎯 [가장 가까운 캔들] 클릭 시간: ${clickTime}, 캔들 인덱스: ${nearestIndex}, 캔들 시간: ${nearestTime}`);
    return { index: nearestIndex, time: nearestTime };
  }, [candleData]);

  // ✅ 피보나치 타임존 클릭 핸들러 (클릭-클릭 방식)
  const handleChartClick = useCallback((time: number) => {
    console.log(`🖱️ [handleChartClick] 호출됨 - time: ${time}, activeTool: ${activeTool}, isDrawingFib: ${isDrawingFib}`);

    if ((activeTool === 'fibonacci' || activeTool === 'fibonacciTimezone') && chartViewMode === 'tradingview') {
      if (!isDrawingFib) {
        // ✅ 이미 피보나치가 그려져 있으면 새로 그리기 방지
        if (fibonacciTimezones.length > 0) {
          console.warn(`⚠️ [피보나치] 이미 피보나치가 그려져 있습니다. 기존 피보나치를 삭제하거나 편집하세요.`);
          alert('이미 피보나치가 그려져 있습니다.\n기존 피보나치를 더블클릭하여 편집하거나 삭제 후 새로 그려주세요.');
          return;
        }

        // 첫 번째 클릭: 가장 가까운 캔들을 기준점으로 설정
        const nearestCandle = findNearestCandle(time);
        if (!nearestCandle) {
          console.error(`❌ [피보나치] 가까운 캔들을 찾을 수 없음`);
          return;
        }
        console.log(`✅ [피보나치] 첫 번째 클릭 - 기준점 인덱스: ${nearestCandle.index}, 시간: ${nearestCandle.time}`);
        setIsDrawingFib(true);
        setFibStartTime(nearestCandle.time);
        setFibStartIndex(nearestCandle.index);
      } else if (fibStartTime !== null && fibStartIndex !== null) {
        // 두 번째 클릭: 방향 판단 후 피보나치 타임존 생성
        const nearestCandle = findNearestCandle(time);
        if (!nearestCandle) {
          console.error(`❌ [피보나치] 가까운 캔들을 찾을 수 없음`);
          return;
        }

        console.log(`✅ [피보나치] 두 번째 클릭 - 지점 인덱스: ${nearestCandle.index}, 시간: ${nearestCandle.time}, 기준점: ${fibStartIndex}`);

        // 방향 결정 (1 = 오른쪽, -1 = 왼쪽)
        const direction = nearestCandle.index > fibStartIndex ? 1 : nearestCandle.index < fibStartIndex ? -1 : 0;

        // 같은 위치 클릭하면 무시
        if (direction === 0) {
          console.warn(`⚠️ [피보나치] 같은 위치를 클릭했습니다. 다른 위치를 클릭하세요.`);
          return;
        }

        console.log(`📏 [피보나치] 방향: ${direction > 0 ? '오른쪽 →' : '← 왼쪽'}`);
        console.log(`📋 [피보나치] 현재 fibDefaultSettings:`, JSON.stringify(fibDefaultSettings, null, 2));
        console.log(`📋 [피보나치] 설정 로드 완료 여부: ${isFibSettingsLoaded}`);
        console.log(`📋 [피보나치] 활성화된 레벨:`, fibDefaultSettings.levels.filter(l => l.enabled).map(l => l.value));

        const newFib: FibonacciTimezoneObject = {
          id: `fib-${Date.now()}`,
          startTime: fibStartTime,
          endTime: nearestCandle.time,
          baseInterval: direction, // ✅ 방향만 저장 (1 또는 -1)
          settings: {
            baseline: { ...fibDefaultSettings.baseline }, // ✅ 기준선 복사
            levels: fibDefaultSettings.levels.map(l => ({ ...l }))
          }, // ✅ 깊은 복사
        };

        setFibonacciTimezones(prev => [...prev, newFib]);
        setIsDrawingFib(false);
        setFibStartTime(null);
        setFibStartIndex(null);
        // ✅ 드로잉 완료 후 자동으로 십자선으로 전환 (One-shot 모드)
        setActiveTool('crosshair');
        console.log(`🎉 [피보나치] 타임존 생성 완료`, newFib);
        console.log(`🎉 [피보나치] 생성된 설정:`, newFib.settings);
      }
    }
  }, [activeTool, chartViewMode, isDrawingFib, fibStartTime, fibStartIndex, findNearestCandle, fibDefaultSettings, fibonacciTimezones]);

  // ✅ 피보나치 타임존 드래그 시작
  const handleFibonacciDragStart = useCallback((fibId: string, startX: number, startTime: number) => {
    console.log(`🖱️ [피보나치 드래그 시작] ID: ${fibId}, X: ${startX}, Time: ${startTime}`);
    setDraggingFibId(fibId);
    setDragStartX(startX);
    setDragStartFibTime(startTime); // ✅ 피보나치 객체의 원래 startTime 저장
    setDragStartMouseTime(null); // ✅ 첫 번째 마우스 무브에서 설정됨
  }, []);

  // ✅ 피보나치 타임존 드래그 종료
  const handleFibonacciDragEnd = useCallback(() => {
    console.log(`🖱️ [피보나치 드래그 종료]`);
    setDraggingFibId(null);
    setDragStartX(null);
    setDragStartFibTime(null);
    setDragStartMouseTime(null); // ✅ 드래그 시작 마우스 위치도 초기화
  }, []);

  // ✅ 마우스 다운 핸들러 (드래그는 오버레이에서 처리)
  const handleChartMouseDown = useCallback((time: number) => {
    // 피보나치 툴이 활성화되어 있지만 드로잉 중이 아니면 아무것도 하지 않음
    // (드래그는 FibonacciTimezoneOverlay에서 onDragStart로 처리됨)
  }, []);

  // ✅ 마우스 무브 핸들러 (드래그 중)
  const handleChartMouseMove = useCallback((time: number) => {
    if (!draggingFibId || dragStartFibTime === null) return;

    // ✅ 첫 번째 호출 시 dragStartMouseTime 설정 (마우스가 실제로 움직이기 시작한 지점)
    if (dragStartMouseTime === null) {
      setDragStartMouseTime(time);
      return;
    }

    const currentCandle = findNearestCandle(time);
    const dragStartCandle = findNearestCandle(dragStartMouseTime);

    if (!currentCandle || !dragStartCandle) return;

    // ✅ 드래그 시작 시 마우스 위치와 현재 마우스 위치의 캔들 인덱스 차이 계산
    const indexDiff = currentCandle.index - dragStartCandle.index;

    // ✅ 인덱스 차이가 없으면 업데이트하지 않음 (불필요한 렌더링 방지)
    if (indexDiff === 0) return;

    console.log(`🖱️ [드래그 중] 인덱스 차이: ${indexDiff}`);

    // ✅ 피보나치의 원래 startTime에서 indexDiff만큼 이동
    const originalStartCandle = findNearestCandle(dragStartFibTime);
    if (!originalStartCandle) return;

    const newStartIndex = originalStartCandle.index + indexDiff;

    // ✅ 기준점만 범위 체크 (일부 선이 범위를 벗어나도 드래그 계속 가능)
    if (newStartIndex < 0 || newStartIndex >= candleData.length) {
      console.log(`⚠️ [드래그] 범위 초과 - newStartIndex: ${newStartIndex}`);
      return;
    }

    const newStartTime = new Date(candleData[newStartIndex].time).getTime() / 1000;

    // ✅ 피보나치 업데이트 (startTime만 업데이트, endTime은 유지)
    setFibonacciTimezones(prev =>
      prev.map(fib => {
        if (fib.id !== draggingFibId) return fib;

        // ✅ 이미 같은 위치면 업데이트하지 않음
        if (fib.startTime === newStartTime) return fib;

        console.log(`📍 [드래그] 피보나치 이동: ${fib.startTime} → ${newStartTime}`);

        return {
          ...fib,
          startTime: newStartTime,
          // endTime은 그대로 유지 (방향 결정에만 사용되므로)
        };
      })
    );
  }, [draggingFibId, dragStartFibTime, dragStartMouseTime, findNearestCandle, candleData]);

  // ✅ 마우스 업 핸들러
  const handleChartMouseUp = useCallback(() => {
    if (draggingFibId) {
      handleFibonacciDragEnd();
    }
  }, [draggingFibId, handleFibonacciDragEnd]);

  // ✅ 드래그 중 전역 마우스 이벤트 리스너 추가 (차트 밖으로 마우스가 나가도 작동)
  useEffect(() => {
    if (!draggingFibId || !chartRef.current) return;

    const handleGlobalMouseMove = (e: MouseEvent) => {
      // 차트 컨테이너의 위치 가져오기
      const chartContainer = chartContainerRef.current;
      if (!chartContainer) return;

      const rect = chartContainer.getBoundingClientRect();
      const x = e.clientX - rect.left;

      // X 좌표를 시간으로 변환
      const timeScale = chartRef.current?.timeScale();
      if (!timeScale) return;

      const time = timeScale.coordinateToTime(x);
      if (time) {
        handleChartMouseMove(time as number);
      }
    };

    const handleGlobalMouseUp = () => {
      handleFibonacciDragEnd();
    };

    // 전역 이벤트 리스너 추가
    document.addEventListener('mousemove', handleGlobalMouseMove);
    document.addEventListener('mouseup', handleGlobalMouseUp);

    console.log('🖱️ [전역 이벤트] 드래그 중 - 전역 마우스 이벤트 리스너 추가됨');

    // 클린업: 이벤트 리스너 제거
    return () => {
      document.removeEventListener('mousemove', handleGlobalMouseMove);
      document.removeEventListener('mouseup', handleGlobalMouseUp);
      console.log('🖱️ [전역 이벤트] 드래그 종료 - 전역 마우스 이벤트 리스너 제거됨');
    };
  }, [draggingFibId, handleChartMouseMove, handleFibonacciDragEnd]);

  // ✅ 현재 표시 기간 라벨 생성 (현재 뷰 범위 기준)
  const getCurrentPeriodLabel = useCallback((): string => {
    if (!visibleDateRange.start || !visibleDateRange.end) return '-';

    const startDate = new Date(visibleDateRange.start);
    const endDate = new Date(visibleDateRange.end);

    // 날짜 포맷: M/D ~ M/D
    return `${startDate.getMonth() + 1}/${startDate.getDate()} ~ ${endDate.getMonth() + 1}/${endDate.getDate()}`;
  }, [visibleDateRange]);

  // ✅ 차트 마커 클릭 핸들러 (timestamp로 거래 기록 찾기)
  const handleMarkerClick = useCallback((timestamp: string, tradeType: 'BUY' | 'SELL') => {
    console.log('🖱️ 마커 클릭됨:', { timestamp, tradeType, hasBacktestResult: !!backtestResult, tradeRecordsCount: backtestResult?.tradeRecords?.length || 0 });

    if (!backtestResult?.tradeRecords) {
      console.warn('⚠️ backtestResult 또는 tradeRecords가 없습니다.');
      return;
    }

    // 🔍 전체 거래 기록 출력 (디버깅용)
    console.log('📋 전체 거래 기록 (17개):');
    console.table(backtestResult.tradeRecords.map(t => ({
      timestamp: t.timestamp,
      type: t.tradeType,
      price: t.price
    })));

    // timestamp 정규화 함수 (다양한 형식을 "YYYY-MM-DDTHH:mm" 형식으로 통일)
    const normalizeTimestamp = (ts: string) => {
      if (!ts) return '';
      // 공백을 T로 변환 (통일된 ISO 형식)
      let normalized = ts.replace(' ', 'T');
      // 초와 밀리초 제거 (예: "2025-12-20T18:00:00" -> "2025-12-20T18:00")
      const match = normalized.match(/(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2})/);
      return match ? match[1].replace(' ', 'T') : normalized;
    };

    const normalizedClickedTimestamp = normalizeTimestamp(timestamp);
    console.log('🔍 정규화된 클릭 timestamp:', normalizedClickedTimestamp);

    // timestamp와 tradeType으로 해당 거래 기록 찾기
    const trade = backtestResult.tradeRecords.find(t => {
      const normalizedTradeTimestamp = normalizeTimestamp(t.timestamp || '');
      const isMatch = normalizedTradeTimestamp === normalizedClickedTimestamp && t.tradeType === tradeType;

      if (isMatch) {
        console.log('✅ 매칭됨:', {
          tradeTimestamp: t.timestamp,
          normalizedTradeTimestamp,
          clickedTimestamp: timestamp,
          normalizedClickedTimestamp
        });
      }

      return isMatch;
    });

    if (trade) {
      console.log('✅ 거래 기록 찾음:', trade);
      setSelectedTrade(trade);
      setTradeDetailOpen(true);
    } else {
      console.log('⚠️ 거래 기록을 찾을 수 없습니다:', {
        clickedTimestamp: timestamp,
        normalizedClickedTimestamp,
        tradeType,
        샘플_거래기록: backtestResult.tradeRecords.slice(0, 3).map(t => ({
          original: t.timestamp,
          normalized: normalizeTimestamp(t.timestamp || ''),
          type: t.tradeType
        }))
      });
    }
  }, [backtestResult]);

  // ✅ 기간 이동 핸들러 (이전 시점)
  // ✅ 서브패널 순서 변경 핸들러
  const handleSubPanelReorder = useCallback((fromIndex: number, toIndex: number) => {
    console.log(`🔄 [handleSubPanelReorder] fromIndex: ${fromIndex}, toIndex: ${toIndex}`);
    
    setSubPanelsOrder(prev => {
      const newOrder = [...prev];
      const [removed] = newOrder.splice(fromIndex, 1);
      newOrder.splice(toIndex, 0, removed);
      
      console.log(`✅ [SubPanel Order] 이전: ${prev.join(', ')}`);
      console.log(`✅ [SubPanel Order] 이후: ${newOrder.join(', ')}`);
      
      return newOrder;
    });
  }, []);

  const handlePreviousPeriod = useCallback(async () => {
    console.log('🔘 [이전 기간 버튼] 클릭됨');
    
    // ✅ ref를 사용하여 최신 candleData 참조 (클로저 문제 방지)
    const currentCandleData = candleDataRef.current;
    console.log(`🔍 [이전 기간] candleDataRef.current 길이: ${currentCandleData.length}`);
    console.log(`🔍 [이전 기간] candleData state 길이: ${candleData.length}`);
    console.log(`🔍 [이전 기간] timeIntervalRef.current: ${timeIntervalRef.current}, timeInterval state: ${timeInterval}`);
    
    if (currentCandleData.length === 0) {
      console.log('❌ [이전 기간] candleData가 비어있어 종료');
      return;
    }

    // ✅ 현재 visible range 저장 (스케일 유지를 위해)
    if (chartRef.current) {
      const currentRange = chartRef.current.getVisibleRange();
      if (currentRange) {
        const rangeSize = currentRange.to - currentRange.from;
        savedViewRangeRef.current = {
          start: currentRange.from,
          end: currentRange.to,
          size: rangeSize
        };
        console.log(`💾 [이전 기간] 현재 visible range 저장: ${new Date(currentRange.from * 1000).toISOString()} ~ ${new Date(currentRange.to * 1000).toISOString()} (크기: ${rangeSize})`);
      }
    }

    // ✅ 현재 윈도우의 마지막 캔들 다음 시간을 히스토리에 저장
    const lastCandle = currentCandleData[currentCandleData.length - 1];
    // ✅ timeIntervalRef를 사용하여 최신 timeInterval 참조
    const currentTimeInterval = timeIntervalRef.current;
    const nextTime = getNextCandleTime(lastCandle.time, currentTimeInterval);

    console.log(`⬅️ [이전 기간] 현재 마지막 캔들: ${lastCandle.time}`);
    console.log(`⬅️ [이전 기간] 히스토리 저장: ${nextTime}`);
    console.log(`⬅️ [이전 기간] 현재 히스토리 스택 크기: ${timeHistoryRef.current.length}`);
    
    setTimeHistory(prev => {
      const newHistory = [...prev, nextTime];
      console.log(`⬅️ [이전 기간] 새 히스토리 스택 크기: ${newHistory.length}`);
      return newHistory;
    });

    // ✅ 현재 첫 캔들 시간으로 이전 200개 로드
    const firstCandle = currentCandleData[0];
    
    // ✅ 첫 캔들 시간에서 -1개 시간 단위를 빼서 이전 데이터 요청
    // (업비트 API는 to 시간을 포함하여 최신 200개를 반환하므로)
    const intervalForPrevious = timeIntervalRef.current;
    const firstCandleTime = firstCandle.time.includes('Z')
      ? firstCandle.time.split('.')[0].replace('Z', '')
      : firstCandle.time.split('.')[0];
    
    // Date 객체로 변환
    const firstCandleDate = new Date(firstCandleTime);
    
    // 시간봉에 따라 -1 단위 빼기
    if (intervalForPrevious.includes('m') || intervalForPrevious.match(/^\d+$/)) {
      // 분봉
      const minutes = parseInt(intervalForPrevious.match(/\d+/)?.[0] || '1');
      firstCandleDate.setMinutes(firstCandleDate.getMinutes() - minutes);
    } else if (intervalForPrevious.includes('h')) {
      // 시간봉
      const hours = parseInt(intervalForPrevious.match(/\d+/)?.[0] || '1');
      firstCandleDate.setHours(firstCandleDate.getHours() - hours);
    } else if (intervalForPrevious.includes('d')) {
      // 일봉
      firstCandleDate.setDate(firstCandleDate.getDate() - 1);
    } else if (intervalForPrevious.includes('w')) {
      // 주봉
      firstCandleDate.setDate(firstCandleDate.getDate() - 7);
    } else if (intervalForPrevious.includes('M')) {
      // 월봉
      firstCandleDate.setMonth(firstCandleDate.getMonth() - 1);
    }
    
    // ISO 형식으로 변환 (밀리초 제거)
    const toTimeStr = firstCandleDate.toISOString().split('.')[0];

    console.log(`⬅️ [이전 기간] 현재 첫 캔들: ${firstCandle.time}`);
    console.log(`⬅️ [이전 기간] -1 단위 적용된 시간: ${toTimeStr}`);
    console.log(`⬅️ [이전 기간] 새 ${CHART_DISPLAY_COUNT}개 로드 (기준시간: ${toTimeStr})`);

    // ✅ 백테스트 결과 초기화 (기간 이동 시 마커 제거)
    console.log('🧹 [이전 기간] 백테스트 결과 초기화 (마커 제거)');
    setBacktestResult(null);
    
    // ✅ 기간 이동 플래그 설정 (중간 렌더링 방지)
    setIsLoadingHistory(true);
    setLoadToTime(toTimeStr);
    isViewingHistoricalDataRef.current = true; // ✅ 이전 기간 보기 플래그 설정
    console.log('🔒 [이전 기간] 로딩 오버레이 표시 및 차트 업데이트 차단 (자동 갱신 비활성화)');

    // ✅ React가 state를 업데이트하고 로딩 오버레이를 표시할 시간 부여
    await new Promise(resolve => setTimeout(resolve, 50));

    // ✅ 이전 200개로 교체 (워밍업 포함, loadData 내에서 shouldSetFullRange 설정됨)
    // ✅ loadDataRef.current 사용으로 항상 최신 loadData 함수 호출 (클로저 문제 해결)
    if (loadDataRef.current) {
      await loadDataRef.current(false, toTimeStr, false, false);
    }
  }, [getNextCandleTime]);

  const handleNextPeriod = useCallback(async () => {
    console.log('🔘 [다음 기간 버튼] 클릭됨');
    
    // ✅ ref를 사용하여 최신 candleData와 timeHistory 참조 (클로저 문제 방지)
    const currentCandleData = candleDataRef.current;
    const currentTimeHistory = timeHistoryRef.current;
    
    console.log(`🔍 [다음 기간] candleDataRef.current 길이: ${currentCandleData.length}`);
    console.log(`🔍 [다음 기간] candleData state 길이: ${candleData.length}`);
    console.log(`🔍 [다음 기간] timeIntervalRef.current: ${timeIntervalRef.current}, timeInterval state: ${timeInterval}`);
    console.log(`🔍 [다음 기간] 현재 히스토리 스택 크기: ${currentTimeHistory.length}`);
    
    if (currentCandleData.length === 0) {
      console.log('❌ [다음 기간] candleData가 비어있어 종료');
      return;
    }

    // ✅ 현재 visible range 저장 (스케일 유지를 위해)
    if (chartRef.current) {
      const currentRange = chartRef.current.getVisibleRange();
      if (currentRange) {
        const rangeSize = currentRange.to - currentRange.from;
        savedViewRangeRef.current = {
          start: currentRange.from,
          end: currentRange.to,
          size: rangeSize
        };
        console.log(`💾 [다음 기간] 현재 visible range 저장: ${new Date(currentRange.from * 1000).toISOString()} ~ ${new Date(currentRange.to * 1000).toISOString()} (크기: ${rangeSize})`);
      }
    }

    // ✅ 백테스트 결과 초기화 (기간 이동 시 마커 제거)
    console.log('🧹 [다음 기간] 백테스트 결과 초기화 (마커 제거)');
    setBacktestResult(null);
    
    // ✅ 기간 이동 플래그 설정 (중간 렌더링 방지)
    setIsLoadingHistory(true);
    console.log('🔒 [다음 기간] 로딩 오버레이 표시 및 차트 업데이트 차단');

    // ✅ React가 state를 업데이트하고 로딩 오버레이를 표시할 시간 부여
    await new Promise(resolve => setTimeout(resolve, 50));

    if (currentTimeHistory.length === 0) {
      // 히스토리 없음 → 최신 200개로 이동
      console.log(`➡️ [다음 기간] 히스토리 없음, 최신 ${CHART_DISPLAY_COUNT}개로 이동`);
      setLoadToTime(null);
      isViewingHistoricalDataRef.current = false; // ✅ 최신 기간으로 복귀 (자동 갱신 활성화)
      // ✅ loadDataRef.current 사용으로 항상 최신 loadData 함수 호출
      if (loadDataRef.current) {
        await loadDataRef.current(false, null, false, false);
      }
    } else {
      // 히스토리에서 pop하여 이전 상태로 복귀
      const toTime = currentTimeHistory[currentTimeHistory.length - 1];

      console.log(`➡️ [다음 기간] 히스토리 복귀: ${toTime}`);
      console.log(`➡️ [다음 기간] 히스토리 스택 크기: ${currentTimeHistory.length} → ${currentTimeHistory.length - 1}`);

      setTimeHistory(prev => {
        const newHistory = prev.slice(0, -1);
        // ✅ 히스토리가 비어있으면 최신 기간으로 간주
        if (newHistory.length === 0) {
          isViewingHistoricalDataRef.current = false;
          console.log('✅ [다음 기간] 히스토리 비어있음 → 최신 기간 (자동 갱신 활성화)');
        }
        return newHistory;
      });
      
      setLoadToTime(toTime);
      // ✅ loadDataRef.current 사용으로 항상 최신 loadData 함수 호출
      if (loadDataRef.current) {
        await loadDataRef.current(false, toTime, false, false);
      }
    }
  }, []);

  // ✅ 전략 체크 팝업 핸들러
  const handleOpenStrategyCheck = useCallback(() => {
    setStrategyCheckPopupOpen(true);
  }, []);

  const handleCloseStrategyCheck = useCallback(() => {
    setStrategyCheckPopupOpen(false);
  }, []);

  // ✅ 차트 설정 팝업 핸들러
  const handleOpenChartSettings = useCallback(() => {
    setChartSettingsPopupOpen(true);
  }, []);

  const handleCloseChartSettings = useCallback(() => {
    setChartSettingsPopupOpen(false);
  }, []);

  // ✅ 설정 내보내기
  const handleExportConfig = useCallback(() => {
    const config: InvestmentAnalysisConfig = {
      version: 1,
      timeInterval,
      trendStrategy,
      generalStrategyId,
      uptrendStrategyId,
      downtrendStrategyId,
      indicatorSettings: indicatorVisibilitySettings,
      indicatorOrder: indicatorVisibilityOrder,
      exportedAt: new Date().toISOString(),
    };
    downloadConfigFile(config);
    setSettingsMenuAnchor(null);
    setSnackbar({ open: true, message: '설정 파일이 다운로드되었습니다.', severity: 'success' });
  }, [timeInterval, trendStrategy, generalStrategyId, uptrendStrategyId, downtrendStrategyId, indicatorVisibilitySettings, indicatorVisibilityOrder]);

  // ✅ 설정 가져오기
  const handleImportConfig = useCallback(() => {
    importConfigInputRef.current?.click();
    setSettingsMenuAnchor(null);
  }, []);

  const handleImportFileChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const file = e.target.files?.[0];
      if (!file) return;
      const reader = new FileReader();
      reader.onload = () => {
        try {
          const text = reader.result as string;
          const result = applyConfigFromFile(text);
          if (result.success) {
            const cfg = loadFromFile();
            if (cfg?.timeInterval) setTimeInterval(cfg.timeInterval);
            if (cfg?.trendStrategy) setTrendStrategy(cfg.trendStrategy as 'none' | 'aggressive' | 'balanced' | 'conservative');
            if (cfg?.generalStrategyId !== undefined) setGeneralStrategyId(cfg.generalStrategyId);
            if (cfg?.uptrendStrategyId !== undefined) setUptrendStrategyId(cfg.uptrendStrategyId);
            if (cfg?.downtrendStrategyId !== undefined) setDowntrendStrategyId(cfg.downtrendStrategyId);
            loadIndicatorSettings(!!localStorage.getItem('accessToken'));
            setSnackbar({ open: true, message: result.message, severity: 'success' });
          } else {
            setSnackbar({ open: true, message: result.message, severity: 'error' });
          }
        } catch {
          setSnackbar({ open: true, message: '설정 파일을 읽을 수 없습니다.', severity: 'error' });
        }
        (e.target as HTMLInputElement).value = '';
      };
      reader.onerror = () => {
        setSnackbar({ open: true, message: '파일을 읽는 중 오류가 발생했습니다.', severity: 'error' });
        (e.target as HTMLInputElement).value = '';
      };
      reader.readAsText(file);
    },
    [loadIndicatorSettings]
  );

  // 매매신호 클릭 핸들러
  const handleSignalRowClick = useCallback((row: any, simulation?: TradeSimulation) => {
    setSelectedSignal(row);
    setSelectedSimulation(simulation);
    setSignalDetailOpen(true);
  }, []);

  // ✅ 숫자를 한글 금액으로 변환하는 함수
  const formatNumberToKorean = useCallback((num: number): string => {
    if (num === 0) return '0원';

    const units = ['', '만', '억', '조'];
    const result: string[] = [];
    let tempNum = num;

    for (let i = 0; i < units.length && tempNum > 0; i++) {
      const part = tempNum % 10000;
      if (part > 0) {
        const thousands = Math.floor(part / 1000);
        const hundreds = Math.floor((part % 1000) / 100);
        const tens = Math.floor((part % 100) / 10);
        const ones = part % 10;

        let partStr = '';
        if (thousands > 0) partStr += `${thousands}천`;
        if (hundreds > 0) partStr += `${hundreds}백`;
        if (tens > 0) partStr += `${tens}십`;
        if (ones > 0) partStr += `${ones}`;

        result.unshift(partStr + units[i]);
      }
      tempNum = Math.floor(tempNum / 10000);
    }

    return result.join('') + '원';
  }, []);

  // ✅ 크로스헤어 동기화 핸들러
  const lastCrosshairSourceRef = useRef<string | null>(null);
  const lastCrosshairTimeRef = useRef<number | null>(null);
  const isSyncingCrosshairRef = useRef<boolean>(false);

  const syncCrosshairToOtherCharts = useCallback((time: Time | null, source: string) => {
    // 동기화 중이면 무시 (무한 루프 방지)
    if (isSyncingCrosshairRef.current) return;

    // 동일한 시간에 대한 중복 이벤트 무시 (떨림 방지)
    const timeNum = time ? (time as number) : null;

    // ✅ 같은 시간과 같은 소스의 조합이 반복되는 경우만 무시
    if (timeNum !== null &&
      lastCrosshairTimeRef.current === timeNum &&
      lastCrosshairSourceRef.current === source) {
      return;
    }

    // 동기화 시작
    isSyncingCrosshairRef.current = true;
    lastCrosshairSourceRef.current = source;
    lastCrosshairTimeRef.current = timeNum;

    console.log(`🎯 [크로스헤어 동기화] source: ${source}, time: ${timeNum ? new Date(timeNum * 1000).toISOString().slice(11, 19) : 'null'}`);

    // ✅ sharedHoverTime 상태만 업데이트 (externalHoverTime prop으로 전파)
    // 직접 setCrosshairPosition 호출하지 않음 (중복 방지)
    if (time) {
      const timeStr = new Date((time as number) * 1000).toISOString();
      setSharedHoverTime(timeStr);
    } else {
      setSharedHoverTime(null);
    }

    // 충분한 시간 후에 플래그 리셋 (다음 이벤트 허용)
    setTimeout(() => {
      isSyncingCrosshairRef.current = false;
    }, 16); // 1프레임 정도만 대기 (약 60fps 기준)
  }, []);

  // 드래그 앤 드롭 센서 설정
  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: {
        distance: 8, // 8px 이동 후 드래그 시작
      },
    }),
    useSensor(KeyboardSensor, {
      coordinateGetter: sortableKeyboardCoordinates,
    })
  );

  // 드래그 시작 핸들러
  const handleDragStart = useCallback((event: DragStartEvent) => {
    setActiveDragId(event.active.id as string);
  }, []);

  // 보조지표 순서 변경 핸들러
  const handleDragEnd = useCallback((event: DragEndEvent) => {
    const { active, over } = event;
    setActiveDragId(null);

    // 캔들차트 영역에 드롭된 경우
    if (over && over.id === 'candle-chart-drop-zone') {
      const indicatorType = active.id as string;
      // 보조지표가 활성화되어 있는지 확인
      const isActive = (() => {
        switch (indicatorType) {
          case 'volume': return indicatorVisibilitySettings.volume;
          case 'newPsychological': return indicatorVisibilitySettings.newPsychological || false;
          case 'investPsychological': return indicatorVisibilitySettings.investPsychological || false;
          case 'macd': return indicatorVisibilitySettings.macdLine;
          case 'cci': return indicatorVisibilitySettings.cci;
          case 'adx': return indicatorVisibilitySettings.adx;
          case 'bwi': return indicatorVisibilitySettings.bwi || false;
          case 'dmi': return indicatorVisibilitySettings.plusDi || false;
          case 'obv': return indicatorVisibilitySettings.obv;
          case 'rsi': return indicatorVisibilitySettings.rsi;
          case 'stochastic': return indicatorVisibilitySettings.stochasticK;
          case 'williamsR': return indicatorVisibilitySettings.williamsR || false;
          case 'trix': return indicatorVisibilitySettings.trix || false;
          case 'volumeOsc': return indicatorVisibilitySettings.volumeOsc || false;
          case 'vr': return indicatorVisibilitySettings.vr || false;
          case 'disparity': return indicatorVisibilitySettings.disparity || false;
          default: return false;
        }
      })();

      if (isActive) {
        setCandleChartOverlayIndicator(indicatorType);
      }
      return;
    }

    // 기존 보조지표 영역 내에서 순서 변경
    if (over && active.id !== over.id) {
      setIndicatorOrder((items) => {
        const oldIndex = items.indexOf(active.id as string);
        const newIndex = items.indexOf(over.id as string);
        return arrayMove(items, oldIndex, newIndex);
      });
    }
  }, [indicatorVisibilitySettings]);

  // SignalTable 데이터 메모이제이션
  const signalTableData = useMemo(() => {
    return chartData.slice(-120);
  }, [chartData]);

  // AI 채팅 UI 명령 핸들러
  const handleUICommand = useCallback((command: UICommand) => {
    switch (command.action) {
      case 'CHANGE_SYMBOL':
        if (command.params.symbol) setSymbol(command.params.symbol);
        break;
      case 'CHANGE_INTERVAL':
        if (command.params.interval) setTimeInterval(command.params.interval);
        break;
      case 'OPEN_CHART_DETAIL':
        setChartDetailType(command.params.chartType || 'candle');
        setChartDetailOpen(true);
        break;
      case 'CLOSE_CHART_DETAIL':
        setChartDetailOpen(false);
        break;
      case 'OPEN_AI_ANALYSIS':
        setAiTestPopupOpen(true);
        break;
      case 'REFRESH_DATA':
        // 수동 새로고침 시에도 현재 차트 범위 유지 (forceReset=false)
        loadData(false);
        break;
      case 'TOGGLE_FULLSCREEN':
        setIsFullscreen(prev => !prev);
        break;
      case 'ADD_CHART_MARKERS':
        // AI Agent가 분석한 골든크로스/데드크로스 등의 지점을 차트에 마커로 표시
        console.log('🎯 ADD_CHART_MARKERS 명령 수신, params:', command.params);
        
        if (command.params.positions && Array.isArray(command.params.positions)) {
          const markerType = command.params.markerType || 'unknown';
          const positions = command.params.positions;
          
          console.log('📍 AI Agent 마커 표시 시작:', {
            markerType,
            count: positions.length,
            positions_샘플: positions.slice(0, 3) // 처음 3개만 로그
          });
          
          // 백테스트 결과 형식으로 변환하여 저장
          // chartData의 useMemo가 자동으로 마커를 적용함
          const tradeRecords = positions.map((p: any) => {
            const mt = p.marker_type || markerType;
            // 모든 매수 신호 타입
            const isBuySignal = ['golden_cross', 'ma_golden', 'rsi_oversold', 'volume_spike', 
                    'pattern', 'support', 'stoch_golden', 'bollinger_bounce_buy',
                    'gap_up', 'triple_buy_signal'].includes(mt);
            
            return {
              timestamp: p.time,
              tradeType: isBuySignal ? 'BUY' : 'SELL',
              price: p.price,
              label: p.label,
              marker_type: mt
            };
          });
          
          console.log('📊 백테스트 결과로 저장:', tradeRecords.length, '개 거래');
          console.log('🕐 마커 시간 샘플:', positions.slice(0, 2).map(p => p.time));
          
          setBacktestResult(prev => {
            const newResult = {
              ...prev, // 기존 백테스트 결과 유지
              symbol: symbol,
              tradeRecords: tradeRecords
            } as any;
            
            console.log('✅ backtestResult 업데이트:', newResult);
            return newResult;
          });
          
          const buyCount = tradeRecords.filter(t => t.tradeType === 'BUY').length;
          const sellCount = tradeRecords.filter(t => t.tradeType === 'SELL').length;
          
          console.log(`✅ 마커 저장 완료: 매수 ${buyCount}개, 매도 ${sellCount}개`);
          console.log(`✅ chartData useMemo가 다음 렌더링에서 마커를 차트에 적용합니다.`);
          
          // 📍 마커가 현재 차트 범위 밖에 있는지 확인하고 자동 스크롤
          if (positions.length > 0 && chartData.length > 0) {
            const firstMarkerTime = new Date(positions[0].time).getTime();
            const lastMarkerTime = new Date(positions[positions.length - 1].time).getTime();
            const chartFirstTime = new Date(chartData[0].time).getTime();
            const chartLastTime = new Date(chartData[chartData.length - 1].time).getTime();
            
            console.log('🔍 [마커 범위 체크]', {
              첫_마커: positions[0].time,
              마지막_마커: positions[positions.length - 1].time,
              차트_시작: chartData[0].time,
              차트_끝: chartData[chartData.length - 1].time
            });
            
            // 마커가 차트 범위 밖에 있으면 해당 시점으로 데이터 로드
            if (lastMarkerTime < chartFirstTime || firstMarkerTime > chartLastTime) {
              console.warn('⚠️ [마커 위치] 마커가 현재 차트 범위 밖에 있습니다!');
              console.log('📍 [자동 이동] 마커가 있는 시점으로 차트를 이동합니다...');
              
              // 마커 범위의 중간 지점으로 이동
              const middleMarkerTime = new Date((firstMarkerTime + lastMarkerTime) / 2).toISOString();
              console.log('🎯 [이동 목표 시간]', middleMarkerTime);
              
              // 해당 시점의 데이터 로드 (forceReset=true, toTime=middleMarkerTime)
              loadData(true, middleMarkerTime);
            } else {
              console.log('✅ [마커 위치] 마커가 현재 차트 범위 내에 있습니다.');
            }
          }
        } else {
          console.warn('⚠️ positions가 없거나 배열이 아님:', command.params);
        }
        break;
      // 페이지 라우팅
      case 'NAVIGATE':
        if (command.params.path) {
          console.log('🚀 페이지 이동:', command.params.path);
          window.location.href = command.params.path;
        }
        break;
      
      // 종목 검색 및 선택
      case 'SEARCH_AND_SELECT_SYMBOL':
        if (command.params.symbol) {
          console.log('🔍 종목 검색 및 선택:', command.params.symbol, command.params.korean_name);
          setSymbol(command.params.symbol);
          // 데이터 새로고침
          loadData(true);
        }
        break;
      
      // 지표 ON/OFF
      case 'TOGGLE_INDICATOR':
        if (command.params.indicator) {
          const indicator = command.params.indicator;
          const enabled = command.params.enabled !== false; // 기본값 true
          console.log('📊 지표 토글:', indicator, enabled ? 'ON' : 'OFF');
          
          // Context의 updateSettings 사용
          updateIndicatorSettings({
            [indicator.toLowerCase()]: enabled
          });
        }
        break;
      
      // 이동평균선 ON/OFF
      case 'TOGGLE_MOVING_AVERAGE':
        if (command.params.period !== undefined) {
          const period = command.params.period;
          const enabled = command.params.enabled !== false;
          console.log('📈 이동평균선 토글:', period + '일선', enabled ? '표시' : '숨기기');
          
          // maLines 배열 업데이트
          const currentMaLines = indicatorVisibilitySettings.maLines || [];
          const existingIndex = currentMaLines.findIndex(ma => ma.period === period);
          
          if (existingIndex !== -1) {
            // 기존 항목의 enabled 상태만 변경
            const updatedMaLines = [...currentMaLines];
            updatedMaLines[existingIndex] = { ...updatedMaLines[existingIndex], enabled };
            
            updateIndicatorSettings({
              maLines: updatedMaLines
            });
          } else if (enabled) {
            // 새로운 항목 추가 (켜는 경우에만) - 전체 속성 포함
            const newId = `MA${currentMaLines.length + 1}`;
            const defaultColors = ['#000000', '#00E676', '#2962FF', '#00E5FF', '#FFEB3B', '#9E9E9E', '#2E7D32', '#7B1FA2'];
            const colorIndex = currentMaLines.length % defaultColors.length;
            
            const newMALine = {
              id: newId,
              enabled: true,
              period: period,
              color: defaultColors[colorIndex],
              width: 1,
              type: 'SMA' as const
            };
            
            updateIndicatorSettings({
              maLines: [...currentMaLines, newMALine]
            });
          }
          // 없는 항목을 끄려고 하는 경우 - 아무것도 하지 않음 (경고만 로그)
          else {
            console.warn(`⚠️ ${period}일선이 maLines에 없습니다. 추가하려면 enabled: true로 호출하세요.`);
          }
        }
        break;
      
      // 지표 설정 열기
      case 'OPEN_INDICATOR_SETTINGS':
        console.log('⚙️ 지표 설정 열기:', command.params.indicator);
        // 지표 설정 다이얼로그 열기 (구현 필요)
        break;
      
      // 차트 모드 변경
      case 'CHANGE_CHART_MODE':
        if (command.params.mode) {
          console.log('🎨 차트 모드 변경:', command.params.mode);
          setChartMode(command.params.mode);
        }
        break;
      
      // 차트 줌
      case 'ZOOM_CHART':
        if (command.params.direction && chartRef.current) {
          console.log('🔍 차트 줌:', command.params.direction);
          const direction = command.params.direction;
          
          const currentRange = chartRef.current.getVisibleLogicalRange();
          if (currentRange) {
            const { from, to } = currentRange;
            const center = (from + to) / 2;
            const halfRange = (to - from) / 2;
            
            let newHalfRange = halfRange;
            if (direction === 'in') {
              newHalfRange = halfRange * 0.7; // 30% 확대
            } else if (direction === 'out') {
              newHalfRange = halfRange * 1.3; // 30% 축소
            } else if (direction === 'reset') {
              chartRef.current.fitContent();
              break;
            }
            
            chartRef.current.setVisibleLogicalRange(
              Math.max(0, center - newHalfRange),
              Math.min(chartData.length - 1, center + newHalfRange)
            );
          }
        }
        break;
      
      // 차트 스크롤
      case 'SCROLL_CHART':
        if (command.params.direction && command.params.amount && chartRef.current) {
          console.log('⬅️➡️ 차트 스크롤:', command.params.direction, command.params.amount);
          const direction = command.params.direction;
          const amount = command.params.amount;
          
          const currentRange = chartRef.current.getVisibleLogicalRange();
          if (currentRange) {
            let { from, to } = currentRange;
            const rangeSize = to - from;
            
            if (direction === 'left') {
              from = Math.max(0, from - amount);
              to = from + rangeSize;
            } else if (direction === 'right') {
              to = Math.min(chartData.length - 1, to + amount);
              from = to - rangeSize;
            }
            
            chartRef.current.setVisibleLogicalRange(from, to);
          }
        }
        break;
      
      // 기간 표시
      case 'SHOW_PERIOD':
        console.log('📅 기간 표시:', command.params.period);
        // 기간에 따른 데이터 로드 (구현 필요)
        break;
      
      // 보조지표 순서 변경
      case 'REORDER_INDICATOR':
        if (command.params.indicator && command.params.position !== undefined) {
          const indicator = command.params.indicator.toLowerCase();
          const position = command.params.position;
          
          console.log('🔀 보조지표 순서 변경:', indicator, '→', position);
          
          setIndicatorOrder(prev => {
            const currentIndex = prev.indexOf(indicator);
            if (currentIndex === -1) {
              console.warn(`⚠️ 지표 ${indicator}를 찾을 수 없습니다.`);
              return prev;
            }
            
            // 현재 항목 제거
            const newOrder = prev.filter((_, idx) => idx !== currentIndex);
            
            // 새 위치에 삽입
            let targetIndex = 0;
            if (position === 'top') {
              targetIndex = 0;
            } else if (position === 'bottom') {
              targetIndex = newOrder.length;
            } else if (typeof position === 'number') {
              targetIndex = Math.max(0, Math.min(position - 1, newOrder.length));
            }
            
            newOrder.splice(targetIndex, 0, indicator);
            console.log('✅ 새 순서:', newOrder);
            return newOrder;
          });
        }
        break;
      
      // 보조지표 내부 선/기준선 ON/OFF
      case 'TOGGLE_INDICATOR_LINE':
        if (command.params.indicator && command.params.lineType) {
          const indicator = command.params.indicator.toLowerCase();
          const lineType = command.params.lineType;
          const enabled = command.params.enabled !== false;
          
          console.log('📏 보조지표 라인 토글:', indicator, lineType, enabled ? 'ON' : 'OFF');
          
          // indicatorVisibilitySettings에서 해당하는 속성 업데이트
          const settingsMap: Record<string, string> = {
            // Stochastic
            'stochastic_overbought': 'stochasticOverboughtLine',
            'stochastic_oversold': 'stochasticOversoldLine',
            'stochastic_k_line': 'stochasticK',
            'stochastic_d_line': 'stochasticD',
            // MACD
            'macd_macd_line': 'macdLine',
            'macd_signal_line': 'macdSignal',
            'macd_histogram': 'macdHistogram',
            // RSI
            'rsi_overbought': 'rsiOverboughtLine',
            'rsi_oversold': 'rsiOversoldLine',
            // CCI
            'cci_overbought': 'cciOverboughtLine',
            'cci_oversold': 'cciOversoldLine',
          };
          
          const settingKey = settingsMap[`${indicator}_${lineType}`];
          if (settingKey) {
            updateIndicatorSettings({
              [settingKey]: enabled
            });
            console.log(`✅ ${settingKey} = ${enabled}`);
          } else {
            console.warn(`⚠️ 설정 키를 찾을 수 없습니다: ${indicator}_${lineType}`);
          }
        }
        break;
      
      default:
        console.warn('Unknown UI command:', command.action);
    }
  }, [loadData, symbol, setBacktestResult, chartData, chartRef, updateIndicatorSettings, indicatorVisibilitySettings, setIndicatorOrder]);

  // AI 분석 요청 함수
  const handleAiTest = useCallback(async () => {
    if (!chartData || chartData.length === 0) {
      setAiTestError('차트 데이터가 없습니다.');
      setAiTestPopupOpen(true);
      return;
    }

    if (aiTestLoading) return;

    setAiTestLoading(true);
    setAiTestError(null);
    setAiTestResult('');
    setAiTestPopupOpen(true);

    try {
      const recentData = chartData.slice(-60);
      const latestCandle = recentData[recentData.length - 1] as any;
      const prevCandle = recentData[recentData.length - 2] as any;

      const priceChange1d = prevCandle?.close
        ? ((latestCandle?.close - prevCandle?.close) / prevCandle?.close * 100).toFixed(2) : 'N/A';

      const sma5 = latestCandle?.sma5 || 0;
      const sma20 = latestCandle?.sma20 || 0;
      const sma60 = latestCandle?.sma60 || 0;
      const sma120 = latestCandle?.sma120 || 0;
      const currentPrice = latestCandle?.close || 0;

      const macdLine = latestCandle?.macdLine || 0;
      const signalLine = latestCandle?.signalLine || 0;
      const rsi = latestCandle?.rsi || 0;
      const stochK = latestCandle?.stochasticK || 0;
      const stochD = latestCandle?.stochasticD || 0;

      const prompt = `/no_think
당신은 암호화폐 기술적 분석 전문가입니다.

## 종목: ${symbol} (${timeInterval})
현재가: ${currentPrice?.toLocaleString()}원

## 📈 가격 정보
- 변동률: ${priceChange1d}%

## 📉 이동평균선
- SMA5: ${sma5?.toLocaleString()} | SMA20: ${sma20?.toLocaleString()} | SMA60: ${sma60?.toLocaleString()} | SMA120: ${sma120?.toLocaleString()}

## 📊 MACD
- MACD: ${macdLine?.toFixed(2)} | Signal: ${signalLine?.toFixed(2)}

## 📊 RSI
- RSI: ${rsi?.toFixed(1)}

## 📊 Stochastic
- %K: ${stochK?.toFixed(1)} | %D: ${stochD?.toFixed(1)}

## 🎯 분석 요청
위의 지표를 종합하여 다음 형식으로 답변하세요:

**[최종판단]** 매수 / 매도 / 관망
**[확신도]** 상(70%+) / 중(50-70%) / 하(50%-)
**[핵심근거]** (가장 중요한 2-3개 시그널 언급)
**[매수시]** 진입가: ___원 | 목표가: ___원 | 손절가: ___원
**[매도시]** 청산가: ___원 | 재진입 고려가: ___원
**[리스크]** (주의해야 할 점 1-2줄)

한국어로 간결하게 답변.`;

      setAiTestResult('AI 분석 중...');

      const response = await fetch('/api/chatgpt/analyze', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ prompt }),
      });

      if (!response.ok) {
        throw new Error(`AI 서버 응답 오류: ${response.status}`);
      }

      const result = await response.json();

      if (result.success && result.response) {
        setAiTestResult(result.response);
      } else {
        throw new Error(result.error || 'AI 분석 실패');
      }
    } catch (err: any) {
      setAiTestError(err.message || 'AI 서버 연결 실패');
    } finally {
      setAiTestLoading(false);
    }
  }, [chartData, symbol, timeInterval, aiTestLoading]);

  // 종목 변경 핸들러
  const handleCryptoNameChange = useCallback((selectedSymbol: string, cryptoInfo?: UpbitMarket) => {
    if (selectedSymbol !== symbol) {
      setSymbol(selectedSymbol);
      // ✅ 종목 변경 시 실시간 가격 초기화
      setRealtimePrice(null);
    }
  }, [symbol]);

  // ESC 키로 전체화면 종료
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape' && isFullscreen) {
        setIsFullscreen(false);
      }
    };

    if (isFullscreen) {
      document.addEventListener('keydown', handleKeyDown);
    }

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [isFullscreen]);

  return (
    <Box sx={{
      p: isChartFullscreen ? 0 : 3,
      height: isChartFullscreen ? '100vh' : 'auto',
      overflow: isChartFullscreen ? 'hidden' : 'auto',
      position: isChartFullscreen ? 'fixed' : 'relative',
      top: isChartFullscreen ? 0 : 'auto',
      left: isChartFullscreen ? 0 : 'auto',
      right: isChartFullscreen ? 0 : 'auto',
      zIndex: isChartFullscreen ? 1300 : 'auto',
      bgcolor: isChartFullscreen ? 'background.default' : 'transparent',
    }}>
      {/* ✅ 툴바 카드: 종목 선택 + 현재가/신호 + 버튼들 - 전체화면일 때 숨김 */}
      {!isChartFullscreen && (
      <Paper elevation={3} sx={{
        position: 'sticky',
        top: 0,
        zIndex: 10,
        p: isMobile ? 1.5 : 2,
        mb: 3,
        borderRadius: 1,
      }}>
        {/* 첫번째 라인: 종목 선택 + 현재가/신호 + 컨트롤 버튼들 */}
        <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 2 }}>
          {/* 좌측: 암호화폐 선택 콤보박스 + 현재가/현재신호 */}
          <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}>
            <Box sx={{ width: isMobile ? '100%' : 320, minWidth: 250 }}>
              <CryptoNameComboBox
                value={symbol}
                onChange={handleCryptoNameChange}
                disabled={loading}
                label=""
                placeholder="암호화폐 선택"
                size="small"
              />
            </Box>

            {/* 현재가 */}
            <Typography variant="body1" color="text.secondary" sx={{ whiteSpace: 'nowrap' }}>
              현재가: {loading && !hasInitialData ? (
                <Skeleton variant="text" width={100} sx={{ display: 'inline-block', bgcolor: 'rgba(255,255,255,0.1)' }} />
              ) : (
                <strong style={{ color: '#22c55e' }}>{statistics?.currentPrice?.toLocaleString() || '-'}원</strong>
              )}
            </Typography>

            {/* 현재 신호 */}
            {loading && !hasInitialData ? (
              <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
                <CircularProgress size={14} />
                <Typography variant="caption" color="text.secondary">분석 중...</Typography>
              </Box>
            ) : statistics && (
              <Typography
                variant="body2"
                sx={{
                  px: 1.5,
                  py: 0.25,
                  borderRadius: 1,
                  border: 1,
                  fontSize: '0.85rem',
                  borderColor: statistics.currentSignal === 'BUY' ? '#22c55e' :
                    statistics.currentSignal === 'SELL' ? '#ef4444' : '#6366f1',
                  backgroundColor: statistics.currentSignal === 'BUY' ? 'rgba(34, 197, 94, 0.15)' :
                    statistics.currentSignal === 'SELL' ? 'rgba(239, 68, 68, 0.15)' : 'rgba(99, 102, 241, 0.15)',
                  color: statistics.currentSignal === 'BUY' ? '#22c55e' :
                    statistics.currentSignal === 'SELL' ? '#ef4444' : '#6366f1',
                  fontWeight: 500,
                  whiteSpace: 'nowrap',
                }}
              >
                {statistics.currentSignal === 'BUY' ? '📈 매수' :
                  statistics.currentSignal === 'SELL' ? '📉 매도' : '⏸ 보유'}
              </Typography>
            )}
          </Box>

          {/* 우측: 컨트롤 버튼들 */}
          <Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap' }}>
            {/* 알림 ON/OFF 토글 */}
            <MuiTooltip
              title={alertEnabled ? `${symbol} 알림이 활성화되어 있습니다 (클릭하면 OFF)` : `클릭하면 ${symbol} 알림 활성화`}
              arrow
            >
              <Button
                variant={alertEnabled ? "contained" : "outlined"}
                size="small"
                startIcon={<Timeline />}
                onClick={handleToggleAlert}
                disabled={loadingAlert}
                color={alertEnabled ? "success" : "inherit"}
                sx={{
                  minWidth: '110px',
                  animation: alertEnabled ? 'pulse 2s infinite' : 'none',
                  '@keyframes pulse': {
                    '0%, 100%': { opacity: 1 },
                    '50%': { opacity: 0.6 }
                  }
                }}
              >
                {loadingAlert ? '처리중...' : alertEnabled ? '알림 ON' : '알림 OFF'}
              </Button>
            </MuiTooltip>

            {/* 새로고침 버튼 */}
            <MuiTooltip title="차트 데이터 새로고침">
              <IconButton
                onClick={() => loadData(false)}
                disabled={loading}
                size="small"
                color="default"
              >
                <Refresh />
              </IconButton>
            </MuiTooltip>

            {/* ✅ 툴팁 on/off */}
            <MuiTooltip title={tooltipEnabled ? "툴팁 끄기" : "툴팁 켜기"}>
              <IconButton
                onClick={handleToggleTooltip}
                size="small"
                color={tooltipEnabled ? "primary" : "default"}
              >
                <Info />
              </IconButton>
            </MuiTooltip>

            {/* ✅ 차트 이동 컨트롤 (좌우 스크롤) */}
            <Box sx={{
              display: 'flex',
              gap: 0.5,
              alignItems: 'center',
              borderRadius: 1,
              border: isDark ? '1px solid rgba(255,255,255,0.2)' : '1px solid rgba(0,0,0,0.15)',
              backgroundColor: isDark ? 'rgba(30, 41, 59, 0.85)' : 'rgba(255, 255, 255, 0.95)',
              px: 0.5
            }}>
              <MuiTooltip title="왼쪽으로 이동">
                <IconButton onClick={handlePanLeft} size="small">
                  <NavigateBefore fontSize="small" />
                </IconButton>
              </MuiTooltip>
              <MuiTooltip title="오른쪽으로 이동">
                <IconButton onClick={handlePanRight} size="small">
                  <NavigateNext fontSize="small" />
                </IconButton>
              </MuiTooltip>
            </Box>

            {/* ✅ 차트 돋보기 (확대/축소) */}
            <Box sx={{
              display: 'flex',
              gap: 0.5,
              alignItems: 'center',
              borderRadius: 1,
              border: isDark ? '1px solid rgba(255,255,255,0.2)' : '1px solid rgba(0,0,0,0.15)',
              backgroundColor: isDark ? 'rgba(30, 41, 59, 0.85)' : 'rgba(255, 255, 255, 0.95)',
              px: 0.5
            }}>
              <MuiTooltip title="축소">
                <IconButton onClick={handleZoomOut} size="small" disabled={zoomLevel <= 50}>
                  <ZoomOut fontSize="small" />
                </IconButton>
              </MuiTooltip>
              <Typography variant="caption" sx={{ minWidth: 45, textAlign: 'center', fontSize: '0.75rem' }}>
                {zoomLevel}%
              </Typography>
              <MuiTooltip title="확대">
                <IconButton onClick={handleZoomIn} size="small" disabled={zoomLevel >= 200}>
                  <ZoomIn fontSize="small" />
                </IconButton>
              </MuiTooltip>
              <MuiTooltip title="초기화">
                <IconButton onClick={handleZoomReset} size="small">
                  <RestartAlt fontSize="small" />
                </IconButton>
              </MuiTooltip>
            </Box>

            {/* ✅ 기간 이동 컨트롤 + 현재 기간 표시 */}
            <Box sx={{
              display: 'flex',
              gap: 0.5,
              alignItems: 'center',
              borderRadius: 1,
              border: isDark ? '1px solid rgba(255,255,255,0.2)' : '1px solid rgba(0,0,0,0.15)',
              backgroundColor: isDark ? 'rgba(30, 41, 59, 0.85)' : 'rgba(255, 255, 255, 0.95)',
              px: 0.5
            }}>
              <MuiTooltip title="이전 기간">
                <IconButton
                  onClick={handlePreviousPeriod}
                  size="small"
                  disabled={loading}
                >
                  <NavigateBefore />
                </IconButton>
              </MuiTooltip>
              <Typography
                variant="caption"
                sx={{
                  minWidth: 85,
                  textAlign: 'center',
                  fontSize: '0.75rem',
                  color: 'text.secondary',
                  whiteSpace: 'nowrap'
                }}
              >
                {getCurrentPeriodLabel()}
              </Typography>
              <MuiTooltip title="다음 기간">
                <IconButton
                  onClick={handleNextPeriod}
                  size="small"
                  disabled={loading || timeHistory.length === 0}
                >
                  <NavigateNext />
                </IconButton>
              </MuiTooltip>
            </Box>

            {/* ✅ 전략 체크 */}
            <MuiTooltip title="전략 조건 확인">
              <IconButton
                onClick={handleOpenStrategyCheck}
                color={strategyCheckPopupOpen ? "primary" : "info"}
                size="small"
              >
                <CheckCircle />
              </IconButton>
            </MuiTooltip>

            {/* 차트 설정 (보조지표 설정 + 내보내기/가져오기) */}
            <MuiTooltip title="설정">
              <IconButton onClick={(e) => setSettingsMenuAnchor(e.currentTarget)} size="small">
                <SettingsIcon />
              </IconButton>
            </MuiTooltip>
            <Menu
              anchorEl={settingsMenuAnchor}
              open={Boolean(settingsMenuAnchor)}
              onClose={() => setSettingsMenuAnchor(null)}
              anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
              transformOrigin={{ vertical: 'top', horizontal: 'right' }}
            >
              <MenuItem onClick={() => { setSettingsDialogOpen(true); setSettingsMenuAnchor(null); }}>
                <ListItemIcon><SettingsIcon fontSize="small" /></ListItemIcon>
                <ListItemText>보조지표 설정</ListItemText>
              </MenuItem>
              <Divider />
              <MenuItem onClick={handleExportConfig}>
                <ListItemIcon><FileDownloadIcon fontSize="small" /></ListItemIcon>
                <ListItemText>설정 내보내기</ListItemText>
              </MenuItem>
              <MenuItem onClick={handleImportConfig}>
                <ListItemIcon><FileUploadIcon fontSize="small" /></ListItemIcon>
                <ListItemText>설정 가져오기</ListItemText>
              </MenuItem>
            </Menu>
            <input
              type="file"
              ref={importConfigInputRef}
              accept=".json,application/json"
              style={{ display: 'none' }}
              onChange={handleImportFileChange}
            />
          </Box>
        </Box>


        {/* 두번째 라인: 전략 + 시간봉 + 매수/매도 + 투자실행 */}
        <Box sx={{ display: 'flex', gap: 2, alignItems: 'center', flexWrap: 'wrap', mt: 2 }}>
          {/* 추세 판단 전략 설정 - 최우선 배치 */}
          <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
            <Typography variant="body2" color="text.secondary" sx={{ whiteSpace: 'nowrap' }}>
              추세전략:
            </Typography>
            <FormControl size="small" sx={{ minWidth: 140 }}>
              <Select
                value={trendStrategy}
                onChange={(e) => setTrendStrategy(e.target.value as TrendStrategy)}
                sx={{ fontSize: '0.75rem', '& .MuiSelect-select': { py: 0.5 } }}
              >
                <MenuItem value="none">사용안함</MenuItem>
                <MenuItem value="aggressive">공격적(5/10/20)</MenuItem>
                <MenuItem value="balanced">균형(10/20/60)</MenuItem>
                <MenuItem value="conservative">보수적(20/60/120)</MenuItem>
              </Select>
            </FormControl>
          </Box>

          {/* 추세전략이 사용안함일 경우: 투자전략 선택 */}
          {trendStrategy === 'none' ? (
            <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
              <Typography variant="body2" color="text.secondary" sx={{ whiteSpace: 'nowrap' }}>
                투자전략:
              </Typography>
              <FormControl size="small" sx={{ minWidth: 130 }}>
                <Select
                  value={generalStrategyId ?? ''}
                  onChange={(e) => setGeneralStrategyId(e.target.value === '' ? null : Number(e.target.value))}
                  displayEmpty
                  sx={{ fontSize: '0.85rem', '& .MuiSelect-select': { py: 0.5 } }}
                >
                  <MenuItem value="">선택안함</MenuItem>
                  {strategyRules.map(rule => (
                    <MenuItem key={rule.id} value={rule.id}>{rule.name}</MenuItem>
                  ))}
                </Select>
              </FormControl>
            </Box>
          ) : (
            <>
              {/* 정배열 전략 */}
              <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
                <Typography variant="body2" color="text.secondary" sx={{ whiteSpace: 'nowrap' }}>
                  정배열:
                </Typography>
                <FormControl size="small" sx={{ minWidth: 130 }}>
                  <Select
                    value={uptrendStrategyId ?? ''}
                    onChange={(e) => setUptrendStrategyId(e.target.value === '' ? null : Number(e.target.value))}
                    displayEmpty
                    sx={{ fontSize: '0.85rem', '& .MuiSelect-select': { py: 0.5 } }}
                  >
                    <MenuItem value="">선택안함</MenuItem>
                    {strategyRules.filter(r => r.trendType === 'UPTREND' || r.trendType === 'ALL').map(rule => (
                      <MenuItem key={rule.id} value={rule.id}>{rule.name}</MenuItem>
                    ))}
                  </Select>
                </FormControl>
              </Box>

              {/* 역배열 전략 */}
              <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
                <Typography variant="body2" color="text.secondary" sx={{ whiteSpace: 'nowrap' }}>
                  역배열:
                </Typography>
                <FormControl size="small" sx={{ minWidth: 130 }}>
                  <Select
                    value={downtrendStrategyId ?? ''}
                    onChange={(e) => setDowntrendStrategyId(e.target.value === '' ? null : Number(e.target.value))}
                    displayEmpty
                    sx={{ fontSize: '0.85rem', '& .MuiSelect-select': { py: 0.5 } }}
                  >
                    <MenuItem value="">선택안함</MenuItem>
                    {strategyRules.filter(r => r.trendType === 'DOWNTREND' || r.trendType === 'ALL').map(rule => (
                      <MenuItem key={rule.id} value={rule.id}>{rule.name}</MenuItem>
                    ))}
                  </Select>
                </FormControl>
              </Box>
            </>
          )}

          {/* 시간봉 선택 */}
          <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
            <Typography variant="body2" color="text.secondary" sx={{ whiteSpace: 'nowrap' }}>
              시간봉:
            </Typography>
            <FormControl size="small" sx={{ minWidth: 120 }}>
              <Select
                value={timeInterval}
                onChange={(e) => setTimeInterval(e.target.value)}
                sx={{ fontSize: '0.85rem', '& .MuiSelect-select': { py: 0.5 } }}
              >
                <MenuItem value="1m">1분봉</MenuItem>
                <MenuItem value="3m">3분봉</MenuItem>
                <MenuItem value="5m">5분봉</MenuItem>
                <MenuItem value="10m">10분봉</MenuItem>
                <MenuItem value="15m">15분봉</MenuItem>
                <MenuItem value="30m">30분봉</MenuItem>
                <MenuItem value="1h">1시간봉</MenuItem>
                <MenuItem value="4h">4시간봉</MenuItem>
                <MenuItem value="1d">일봉</MenuItem>
                <MenuItem value="1w">주봉</MenuItem>
              </Select>
            </FormControl>
          </Box>

          {/* 매수/매도 설정 */}
          <Box sx={{
            display: 'flex',
            alignItems: 'center',
            gap: 1,
            px: 1.5,
            py: 0.5,
            border: isDark ? '1px solid rgba(255,255,255,0.2)' : '1px solid rgba(0,0,0,0.15)',
            borderRadius: 1,
            backgroundColor: isDark ? 'rgba(30, 35, 45, 0.5)' : 'rgba(255, 255, 255, 0.95)'
          }}>
            {/* 매수/매도 스위치 */}
            <ToggleButtonGroup
              value={tradeMode}
              exclusive
              onChange={(e, newMode) => newMode && setTradeMode(newMode)}
              size="small"
              sx={{ height: 28 }}
            >
              <ToggleButton
                value="buy"
                sx={{
                  px: 1.5,
                  py: 0.25,
                  fontSize: '0.75rem',
                  '&.Mui-selected': {
                    backgroundColor: 'primary.main',
                    color: '#fff',
                    '&:hover': { backgroundColor: 'primary.dark' }
                  }
                }}
              >
                매수
              </ToggleButton>
              <ToggleButton
                value="sell"
                sx={{
                  px: 1.5,
                  py: 0.25,
                  fontSize: '0.75rem',
                  '&.Mui-selected': {
                    backgroundColor: 'error.main',
                    color: '#fff',
                    '&:hover': { backgroundColor: 'error.dark' }
                  }
                }}
              >
                매도
              </ToggleButton>
            </ToggleButtonGroup>

            {/* 금액/가능 선택 */}
            <FormControl size="small" sx={{ minWidth: 70 }}>
              <Select
                value={amountType}
                onChange={(e) => setAmountType(e.target.value as 'amount' | 'available')}
                sx={{ fontSize: '0.75rem', '& .MuiSelect-select': { py: 0.25, px: 0.75 } }}
              >
                <MenuItem value="amount" sx={{ fontSize: '0.75rem' }}>금액</MenuItem>
                <MenuItem value="available" sx={{ fontSize: '0.75rem' }}>가능</MenuItem>
              </Select>
            </FormControl>

            {/* 금액 입력 */}
            <MuiTooltip
              title={formatNumberToKorean(tradeMode === 'buy' ? buyAmount : sellAmount)}
              placement="top"
              arrow
            >
              <TextField
                size="small"
                type="text"
                value={tradeMode === 'buy' ? buyAmount.toLocaleString('ko-KR') : sellAmount.toLocaleString('ko-KR')}
                onChange={(e) => {
                  const num = parseInt(e.target.value.replace(/,/g, '')) || 0;
                  if (tradeMode === 'buy') {
                    setBuyAmount(num);
                  } else {
                    setSellAmount(num);
                  }
                }}
                sx={{
                  width: 120,
                  '& .MuiInputBase-input': { fontSize: '0.75rem', py: 0.25, px: 0.75, textAlign: 'right' }
                }}
                InputProps={{
                  endAdornment: <Typography variant="caption" sx={{ fontSize: '0.7rem', ml: 0.5 }}>KRW</Typography>
                }}
              />
            </MuiTooltip>

            {/* 금액 조정 버튼 */}
            <Box sx={{ display: 'flex', gap: 0.25 }}>
              {amountType === 'amount' ? (
                <>
                  <Button
                    size="small"
                    variant="outlined"
                    onClick={() => {
                      if (tradeMode === 'buy') {
                        setBuyAmount(prev => prev + 10000000);
                      } else {
                        setSellAmount(prev => prev + 10000000);
                      }
                    }}
                    sx={{
                      minWidth: 'auto',
                      px: 0.5,
                      py: 0.25,
                      fontSize: '0.65rem',
                      borderColor: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      color: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      '&:hover': {
                        borderColor: tradeMode === 'buy'
                          ? (isDark ? '#42a5f5' : '#1565c0')
                          : (isDark ? '#ef5350' : '#c62828'),
                        bgcolor: tradeMode === 'buy'
                          ? (isDark ? 'rgba(33, 150, 243, 0.08)' : 'rgba(25, 118, 210, 0.08)')
                          : (isDark ? 'rgba(244, 67, 54, 0.08)' : 'rgba(211, 47, 47, 0.08)')
                      }
                    }}
                  >
                    +1000만
                  </Button>
                  <Button
                    size="small"
                    variant="outlined"
                    onClick={() => {
                      if (tradeMode === 'buy') {
                        setBuyAmount(prev => prev + 1000000);
                      } else {
                        setSellAmount(prev => prev + 1000000);
                      }
                    }}
                    sx={{
                      minWidth: 'auto',
                      px: 0.5,
                      py: 0.25,
                      fontSize: '0.65rem',
                      borderColor: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      color: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      '&:hover': {
                        borderColor: tradeMode === 'buy'
                          ? (isDark ? '#42a5f5' : '#1565c0')
                          : (isDark ? '#ef5350' : '#c62828'),
                        bgcolor: tradeMode === 'buy'
                          ? (isDark ? 'rgba(33, 150, 243, 0.08)' : 'rgba(25, 118, 210, 0.08)')
                          : (isDark ? 'rgba(244, 67, 54, 0.08)' : 'rgba(211, 47, 47, 0.08)')
                      }
                    }}
                  >
                    +100만
                  </Button>
                  <Button
                    size="small"
                    variant="outlined"
                    onClick={() => {
                      if (tradeMode === 'buy') {
                        setBuyAmount(prev => prev + 100000);
                      } else {
                        setSellAmount(prev => prev + 100000);
                      }
                    }}
                    sx={{
                      minWidth: 'auto',
                      px: 0.5,
                      py: 0.25,
                      fontSize: '0.65rem',
                      borderColor: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      color: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      '&:hover': {
                        borderColor: tradeMode === 'buy'
                          ? (isDark ? '#42a5f5' : '#1565c0')
                          : (isDark ? '#ef5350' : '#c62828'),
                        bgcolor: tradeMode === 'buy'
                          ? (isDark ? 'rgba(33, 150, 243, 0.08)' : 'rgba(25, 118, 210, 0.08)')
                          : (isDark ? 'rgba(244, 67, 54, 0.08)' : 'rgba(211, 47, 47, 0.08)')
                      }
                    }}
                  >
                    +10만
                  </Button>
                </>
              ) : (
                <>
                  <Button
                    size="small"
                    variant="outlined"
                    onClick={() => {
                      const amount = Math.floor(totalBalance * 0.1);
                      if (tradeMode === 'buy') {
                        setBuyAmount(amount);
                      } else {
                        setSellAmount(amount);
                      }
                    }}
                    sx={{
                      minWidth: 'auto',
                      px: 0.5,
                      py: 0.25,
                      fontSize: '0.65rem',
                      borderColor: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      color: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      '&:hover': {
                        borderColor: tradeMode === 'buy'
                          ? (isDark ? '#42a5f5' : '#1565c0')
                          : (isDark ? '#ef5350' : '#c62828'),
                        bgcolor: tradeMode === 'buy'
                          ? (isDark ? 'rgba(33, 150, 243, 0.08)' : 'rgba(25, 118, 210, 0.08)')
                          : (isDark ? 'rgba(244, 67, 54, 0.08)' : 'rgba(211, 47, 47, 0.08)')
                      }
                    }}
                  >
                    10%
                  </Button>
                  <Button
                    size="small"
                    variant="outlined"
                    onClick={() => {
                      const amount = Math.floor(totalBalance * 0.25);
                      if (tradeMode === 'buy') {
                        setBuyAmount(amount);
                      } else {
                        setSellAmount(amount);
                      }
                    }}
                    sx={{
                      minWidth: 'auto',
                      px: 0.5,
                      py: 0.25,
                      fontSize: '0.65rem',
                      borderColor: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      color: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      '&:hover': {
                        borderColor: tradeMode === 'buy'
                          ? (isDark ? '#42a5f5' : '#1565c0')
                          : (isDark ? '#ef5350' : '#c62828'),
                        bgcolor: tradeMode === 'buy'
                          ? (isDark ? 'rgba(33, 150, 243, 0.08)' : 'rgba(25, 118, 210, 0.08)')
                          : (isDark ? 'rgba(244, 67, 54, 0.08)' : 'rgba(211, 47, 47, 0.08)')
                      }
                    }}
                  >
                    25%
                  </Button>
                  <Button
                    size="small"
                    variant="outlined"
                    onClick={() => {
                      const amount = Math.floor(totalBalance * 0.5);
                      if (tradeMode === 'buy') {
                        setBuyAmount(amount);
                      } else {
                        setSellAmount(amount);
                      }
                    }}
                    sx={{
                      minWidth: 'auto',
                      px: 0.5,
                      py: 0.25,
                      fontSize: '0.65rem',
                      borderColor: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      color: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      '&:hover': {
                        borderColor: tradeMode === 'buy'
                          ? (isDark ? '#42a5f5' : '#1565c0')
                          : (isDark ? '#ef5350' : '#c62828'),
                        bgcolor: tradeMode === 'buy'
                          ? (isDark ? 'rgba(33, 150, 243, 0.08)' : 'rgba(25, 118, 210, 0.08)')
                          : (isDark ? 'rgba(244, 67, 54, 0.08)' : 'rgba(211, 47, 47, 0.08)')
                      }
                    }}
                  >
                    50%
                  </Button>
                  <Button
                    size="small"
                    variant="outlined"
                    onClick={() => {
                      if (tradeMode === 'buy') {
                        setBuyAmount(totalBalance);
                      } else {
                        setSellAmount(totalBalance);
                      }
                    }}
                    sx={{
                      minWidth: 'auto',
                      px: 0.5,
                      py: 0.25,
                      fontSize: '0.65rem',
                      borderColor: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      color: tradeMode === 'buy'
                        ? (isDark ? '#2196F3' : '#1976d2')
                        : (isDark ? '#f44336' : '#d32f2f'),
                      '&:hover': {
                        borderColor: tradeMode === 'buy'
                          ? (isDark ? '#42a5f5' : '#1565c0')
                          : (isDark ? '#ef5350' : '#c62828'),
                        bgcolor: tradeMode === 'buy'
                          ? (isDark ? 'rgba(33, 150, 243, 0.08)' : 'rgba(25, 118, 210, 0.08)')
                          : (isDark ? 'rgba(244, 67, 54, 0.08)' : 'rgba(211, 47, 47, 0.08)')
                      }
                    }}
                  >
                    최대
                  </Button>
                </>
              )}
            </Box>

            {/* 보유금액 설정 */}
            <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, ml: 1, pl: 1, borderLeft: '1px solid rgba(255,255,255,0.2)' }}>
              <Typography variant="caption" color="text.secondary" sx={{ whiteSpace: 'nowrap', fontSize: '0.7rem' }}>
                보유
              </Typography>
              <MuiTooltip
                title={formatNumberToKorean(parseInt(tempTotalBalance.replace(/,/g, '')) || 0)}
                placement="top"
                arrow
              >
                <TextField
                  size="small"
                  type="text"
                  value={parseInt(tempTotalBalance.replace(/,/g, '')).toLocaleString('ko-KR')}
                  onChange={(e) => setTempTotalBalance(e.target.value.replace(/,/g, ''))}
                  sx={{
                    width: 120,
                    '& .MuiInputBase-input': { fontSize: '0.7rem', py: 0.25, px: 0.75, textAlign: 'right' }
                  }}
                  InputProps={{
                    endAdornment: <Typography variant="caption" sx={{ fontSize: '0.65rem' }}>KRW</Typography>
                  }}
                />
              </MuiTooltip>
              <Button
                size="small"
                variant="contained"
                onClick={() => {
                  const num = parseInt(tempTotalBalance.replace(/,/g, '')) || 0;
                  setTotalBalance(num);
                }}
                sx={{ minWidth: 'auto', px: 0.75, py: 0.25, fontSize: '0.65rem' }}
              >
                적용
              </Button>
            </Box>
          </Box>

          {/* 투자실행 버튼 */}
          <Button
            variant="outlined"
            size="small"
            onClick={handleExecuteStrategy}
            disabled={isRunningBacktest}
            startIcon={isRunningBacktest ? <CircularProgress size={14} color="inherit" /> : <TrendingUp />}
          >
            {isRunningBacktest ? '분석중...' : '투자실행'}
          </Button>

          {/* 투자실행 결과 표시 */}
          {backtestResult && !isRunningBacktest && (
            <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, ml: 2 }}>
              <Typography variant="body2" sx={{ color: 'text.secondary' }}>
                매수: <span style={{ fontWeight: 600, color: '#1976d2' }}>{backtestResult.buyCount ?? 0}건</span>
              </Typography>
              <Typography variant="body2" sx={{ color: 'text.secondary' }}>
                매도: <span style={{ fontWeight: 600, color: '#d32f2f' }}>{backtestResult.sellCount ?? 0}건</span>
              </Typography>
              <Typography 
                variant="body2" 
                sx={{ 
                  fontWeight: 700,
                  color: (backtestResult.returnRate ?? 0) >= 0 ? '#2e7d32' : '#d32f2f'
                }}
              >
                수익률: {(backtestResult.returnRate ?? 0) >= 0 ? '+' : ''}{(backtestResult.returnRate ?? 0).toFixed(2)}%
              </Typography>
            </Box>
          )}
        </Box>
      </Paper>
      )}

      {/* 에러 표시 - 전체화면 모드가 아닐 때만 표시 */}
      {error && !isFullscreen && (
        <Alert severity="error" sx={{ mb: 3 }}>
          {error}
        </Alert>
      )}

      {/* 메인 차트 영역 */}
      <DndContext
        sensors={sensors}
        collisionDetection={closestCenter}
        onDragStart={handleDragStart}
        onDragEnd={handleDragEnd}
      >
        <Paper sx={{
          p: isChartFullscreen ? 1 : 2,
          height: isChartFullscreen ? '100vh' : 'auto',
          mb: isChartFullscreen ? 0 : 3,
          overflow: isChartFullscreen ? 'hidden' : 'visible',
          display: 'flex',
          flexDirection: 'column',
        }}>
          {loading && !hasInitialData ? (
            <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: 600 }}>
              <CircularProgress />
            </Box>
          ) : candleData.length > 0 ? (
            <Box sx={{
              height: isChartFullscreen ? undefined : 'auto',
              flex: isChartFullscreen ? 1 : 'none',
              minHeight: isChartFullscreen ? 0 : 'auto',
              overflow: 'visible',
              display: 'flex',
              flexDirection: 'column',
            }}>
              {/* 차트 툴바 및 모드 선택 - 전체화면 모드에서도 표시 */}
              <Box sx={{
                mb: 2,
                display: 'flex',
                justifyContent: 'space-between',
                alignItems: 'center',
                gap: 2,
              }}>
                  {/* 왼쪽: 보조지표 선택 드롭다운 + 이동평균선 + 일목균형표 */}
                  <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
                    <MuiTooltip title={`보조지표 (${(() => {
                      // 업비트 표준 16개 보조지표 카운트 (그룹화된 지표는 1개로 카운트)
                      let count = 0;
                      // 단일 키 지표들
                      if (indicatorVisibilitySettings.volume) count++;
                      if (indicatorVisibilitySettings.newPsychological) count++;
                      if (indicatorVisibilitySettings.investPsychological) count++;
                      if (indicatorVisibilitySettings.cci) count++;
                      if (indicatorVisibilitySettings.adx) count++;
                      if (indicatorVisibilitySettings.bwi) count++;
                      if (indicatorVisibilitySettings.obv) count++;
                      if (indicatorVisibilitySettings.rsi) count++;
                      if (indicatorVisibilitySettings.williamsR) count++;
                      if (indicatorVisibilitySettings.volumeOsc) count++;
                      if (indicatorVisibilitySettings.vr) count++;
                      if (indicatorVisibilitySettings.disparity) count++;
                      // MACD (macdLine 또는 macdHistogram 중 하나라도 true면 1개)
                      if (indicatorVisibilitySettings.macdLine || indicatorVisibilitySettings.macdHistogram) count++;
                      // Stochastic (stochasticSlow, stochasticK, stochasticD 중 하나라도 true면 1개)
                      if (indicatorVisibilitySettings.stochasticSlow || indicatorVisibilitySettings.stochasticK || indicatorVisibilitySettings.stochasticD) count++;
                      // DMI (plusDi 또는 minusDi 중 하나라도 true면 1개)
                      if (indicatorVisibilitySettings.plusDi || indicatorVisibilitySettings.minusDi) count++;
                      // TRIX (trix 또는 trixSignal 중 하나라도 true면 1개)
                      if (indicatorVisibilitySettings.trix || indicatorVisibilitySettings.trixSignal) count++;
                      return count;
                    })()}개 선택)`} arrow>
                      <Button
                        variant="outlined"
                        size="small"
                        onClick={(e) => setIndicatorMenuAnchor(e.currentTarget)}
                        startIcon={<BarChartIcon sx={{ fontSize: 18 }} />}
                        endIcon={<KeyboardArrowDown />}
                        sx={{
                          fontSize: '12px',
                          textTransform: 'none',
                          borderColor: 'divider',
                          px: 1.5,
                          py: 0.5,
                        }}
                      >
                        보조지표({(() => {
                          // 업비트 표준 16개 보조지표 카운트 (그룹화된 지표는 1개로 카운트)
                          let count = 0;
                          const enabledIndicators: string[] = [];
                          
                          // 단일 키 지표들
                          if (indicatorVisibilitySettings.volume) { count++; enabledIndicators.push('volume'); }
                          if (indicatorVisibilitySettings.newPsychological) { count++; enabledIndicators.push('newPsychological'); }
                          if (indicatorVisibilitySettings.investPsychological) { count++; enabledIndicators.push('investPsychological'); }
                          if (indicatorVisibilitySettings.cci) { count++; enabledIndicators.push('cci'); }
                          if (indicatorVisibilitySettings.adx) { count++; enabledIndicators.push('adx'); }
                          if (indicatorVisibilitySettings.bwi) { count++; enabledIndicators.push('bwi'); }
                          if (indicatorVisibilitySettings.obv) { count++; enabledIndicators.push('obv'); }
                          if (indicatorVisibilitySettings.rsi) { count++; enabledIndicators.push('rsi'); }
                          if (indicatorVisibilitySettings.williamsR) { count++; enabledIndicators.push('williamsR'); }
                          if (indicatorVisibilitySettings.volumeOsc) { count++; enabledIndicators.push('volumeOsc'); }
                          if (indicatorVisibilitySettings.vr) { count++; enabledIndicators.push('vr'); }
                          if (indicatorVisibilitySettings.disparity) { count++; enabledIndicators.push('disparity'); }
                          // MACD (macdLine 또는 macdHistogram 중 하나라도 true면 1개)
                          if (indicatorVisibilitySettings.macdLine || indicatorVisibilitySettings.macdHistogram) { 
                            count++; 
                            enabledIndicators.push(`MACD(line:${indicatorVisibilitySettings.macdLine}, hist:${indicatorVisibilitySettings.macdHistogram})`); 
                          }
                          // Stochastic (stochasticSlow, stochasticK, stochasticD 중 하나라도 true면 1개)
                          if (indicatorVisibilitySettings.stochasticSlow || indicatorVisibilitySettings.stochasticK || indicatorVisibilitySettings.stochasticD) { 
                            count++; 
                            enabledIndicators.push(`Stochastic(slow:${indicatorVisibilitySettings.stochasticSlow}, K:${indicatorVisibilitySettings.stochasticK}, D:${indicatorVisibilitySettings.stochasticD})`); 
                          }
                          // DMI (plusDi 또는 minusDi 중 하나라도 true면 1개)
                          if (indicatorVisibilitySettings.plusDi || indicatorVisibilitySettings.minusDi) { 
                            count++; 
                            enabledIndicators.push(`DMI(+DI:${indicatorVisibilitySettings.plusDi}, -DI:${indicatorVisibilitySettings.minusDi})`); 
                          }
                          // TRIX (trix 또는 trixSignal 중 하나라도 true면 1개)
                          if (indicatorVisibilitySettings.trix || indicatorVisibilitySettings.trixSignal) { 
                            count++; 
                            enabledIndicators.push(`TRIX(trix:${indicatorVisibilitySettings.trix}, signal:${indicatorVisibilitySettings.trixSignal})`); 
                          }
                          
                          console.log('🔍 활성화된 보조지표:', enabledIndicators);
                          return count;
                        })()})
                      </Button>
                    </MuiTooltip>

                    {/* 보조지표 선택 메뉴 */}
                    <Menu
                      anchorEl={indicatorMenuAnchor}
                      open={Boolean(indicatorMenuAnchor)}
                      onClose={() => setIndicatorMenuAnchor(null)}
                      anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
                      transformOrigin={{ vertical: 'top', horizontal: 'left' }}
                      slotProps={{
                        paper: {
                          sx: {
                            maxHeight: 500,
                            width: 280,
                            overflowY: 'auto',
                          }
                        }
                      }}
                    >
                      {/* 거래량 */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ volume: !indicatorVisibilitySettings.volume });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.volume}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="거래량" />
                      </MenuItem>

                      <Divider sx={{ my: 0.5 }} />

                      {/* 신심리도 */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ newPsychological: !indicatorVisibilitySettings.newPsychological });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.newPsychological || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="신심리도" />
                      </MenuItem>

                      {/* 투자심리도 */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ investPsychological: !indicatorVisibilitySettings.investPsychological });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.investPsychological || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="투자심리도" />
                      </MenuItem>

                      {/* MACD */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ macdLine: !indicatorVisibilitySettings.macdLine, macdHistogram: !indicatorVisibilitySettings.macdLine });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.macdLine || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="MACD" />
                      </MenuItem>

                      {/* CCI */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ cci: !indicatorVisibilitySettings.cci });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.cci || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="CCI" />
                      </MenuItem>

                      {/* ADX */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ adx: !indicatorVisibilitySettings.adx });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.adx || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="ADX" />
                      </MenuItem>

                      {/* 볼린저밴드 %B (BWI) */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ bwi: !indicatorVisibilitySettings.bwi });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.bwi || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="볼린저밴드 %B" />
                      </MenuItem>

                      {/* DMI */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ plusDi: !indicatorVisibilitySettings.plusDi, minusDi: !indicatorVisibilitySettings.plusDi });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.plusDi || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="DMI" />
                      </MenuItem>

                      {/* OBV */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ obv: !indicatorVisibilitySettings.obv });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.obv || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="OBV" />
                      </MenuItem>

                      {/* RSI */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ rsi: !indicatorVisibilitySettings.rsi });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.rsi || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="RSI" />
                      </MenuItem>

                      {/* Stochastic Slow */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ stochasticK: !indicatorVisibilitySettings.stochasticK, stochasticD: !indicatorVisibilitySettings.stochasticK, stochasticSlow: !indicatorVisibilitySettings.stochasticK });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.stochasticK || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="Stochastic Slow" />
                      </MenuItem>

                      {/* Williams %R */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ williamsR: !indicatorVisibilitySettings.williamsR });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.williamsR || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="Williams %R" />
                      </MenuItem>

                      {/* TRIX */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ trix: !indicatorVisibilitySettings.trix, trixSignal: !indicatorVisibilitySettings.trix });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.trix || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="TRIX" />
                      </MenuItem>

                      {/* Volume OSC */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ volumeOsc: !indicatorVisibilitySettings.volumeOsc });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.volumeOsc || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="Volume OSC" />
                      </MenuItem>

                      {/* VR */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ vr: !indicatorVisibilitySettings.vr });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.vr || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="VR" />
                      </MenuItem>

                      {/* 이격도 */}
                      <MenuItem
                        onClick={() => {
                          updateIndicatorSettings({ disparity: !indicatorVisibilitySettings.disparity });
                        }}
                      >
                        <ListItemIcon>
                          <Checkbox
                            edge="start"
                            checked={indicatorVisibilitySettings.disparity || false}
                            tabIndex={-1}
                            disableRipple
                            size="small"
                          />
                        </ListItemIcon>
                        <ListItemText primary="이격도" />
                      </MenuItem>

                      <Divider sx={{ my: 0.5 }} />
                      
                      {/* 보조지표 설정 버튼 */}
                      <MenuItem 
                        onClick={() => {
                          setIndicatorMenuAnchor(null);
                          setIndicatorSettingsDialogOpen(true);
                        }} 
                        sx={{ py: 1 }}
                      >
                        <ListItemIcon>
                          <SettingsIcon fontSize="small" />
                        </ListItemIcon>
                        <ListItemText 
                          primary="보조지표 설정..." 
                          primaryTypographyProps={{ fontSize: '13px', fontWeight: 'bold' }} 
                        />
                      </MenuItem>
                    </Menu>

                    {/* 이동평균선 선택 */}
                    <MuiTooltip title={`이동평균선 (${indicatorVisibilitySettings.maLines?.filter((ma: any) => ma.enabled).length || 0}개 활성화)`} arrow>
                      <Button
                        variant="outlined"
                        size="small"
                        onClick={(e) => setMaMenuAnchor(e.currentTarget)}
                        startIcon={<ShowChart sx={{ fontSize: 18 }} />}
                        endIcon={<KeyboardArrowDown />}
                        sx={{
                          fontSize: '12px',
                          textTransform: 'none',
                          borderColor: 'divider',
                          px: 1,
                          py: 0.5,
                        }}
                      >
                        MA({indicatorVisibilitySettings.maLines?.filter((ma: any) => ma.enabled).length || 0})
                      </Button>
                    </MuiTooltip>

                    {/* 일목균형표 선택 */}
                    <MuiTooltip title={`일목균형표 (${[indicatorVisibilitySettings.ichimokuTenkan, indicatorVisibilitySettings.ichimokuKijun, indicatorVisibilitySettings.ichimokuChikou, indicatorVisibilitySettings.ichimokuSenkouA, indicatorVisibilitySettings.ichimokuSenkouB].filter(Boolean).length}개 선택)`} arrow>
                      <Button
                        variant="outlined"
                        size="small"
                        onClick={(e) => setIchimokuMenuAnchor(e.currentTarget)}
                        startIcon={<Timeline sx={{ fontSize: 18 }} />}
                        endIcon={<KeyboardArrowDown />}
                        sx={{
                          fontSize: '12px',
                          textTransform: 'none',
                          borderColor: 'divider',
                          px: 1,
                          py: 0.5,
                        }}
                      >
                        일목({[indicatorVisibilitySettings.ichimokuTenkan, indicatorVisibilitySettings.ichimokuKijun, indicatorVisibilitySettings.ichimokuChikou, indicatorVisibilitySettings.ichimokuSenkouA, indicatorVisibilitySettings.ichimokuSenkouB].filter(Boolean).length})
                      </Button>
                    </MuiTooltip>

                    {/* 볼린저 밴드 선택 */}
                    <MuiTooltip title={`볼린저 밴드 (${[indicatorVisibilitySettings.bollingerUpper, indicatorVisibilitySettings.bollingerMiddle, indicatorVisibilitySettings.bollingerLower].filter(Boolean).length}개 선택)`} arrow>
                      <Button
                        variant="outlined"
                        size="small"
                        onClick={(e) => setBollingerMenuAnchor(e.currentTarget)}
                        startIcon={<ShowChart sx={{ fontSize: 18 }} />}
                        endIcon={<KeyboardArrowDown />}
                        sx={{
                          fontSize: '12px',
                          textTransform: 'none',
                          borderColor: 'divider',
                          px: 1,
                          py: 0.5,
                        }}
                      >
                        볼린저({[indicatorVisibilitySettings.bollingerUpper, indicatorVisibilitySettings.bollingerMiddle, indicatorVisibilitySettings.bollingerLower].filter(Boolean).length})
                      </Button>
                    </MuiTooltip>

                    {/* ✅ 실시간: 이미지1=미수신(아웃라인만) / 이미지2=수신(솔리드+네온 글로우) */}
                    <Box sx={{ position: 'relative', display: 'inline-flex', alignItems: 'center', ml: 0.5 }}>
                      {realtimeUpdateEnabled && wsStatus === 'connected' && realtimeDataReceivedLook && realtimeGlowTick > 0 && (
                        <Box
                          key={realtimeGlowTick}
                          sx={{
                            position: 'absolute',
                            inset: -8,
                            borderRadius: 999,
                            pointerEvents: 'none',
                            zIndex: 0,
                            border: 'none',
                            bgcolor: 'transparent',
                            animation: `${realtimeOutlineBlink} 0.55s ease-out forwards`,
                          }}
                        />
                      )}
                      <Box
                        sx={{
                          position: 'relative',
                          zIndex: 1,
                          borderRadius: 999,
                          border: '1px solid',
                          overflow: 'hidden',
                          ...(realtimeUpdateEnabled && wsStatus === 'connected'
                            ? {
                                borderColor:
                                  realtimeDataReceivedLook ? realtimeCapsuleBorderLive : realtimeCapsuleBorderIdle,
                                boxShadow: 'none',
                              }
                            : {
                                borderColor: isDark ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.12)',
                                boxShadow: 'none',
                              }),
                        }}
                      >
                        {realtimeUpdateEnabled && wsStatus === 'connected' && realtimeDataReceivedLook && (
                          <Box
                            sx={{
                              position: 'absolute',
                              inset: 0,
                              borderRadius: 999,
                              bgcolor: realtimeCapsuleBg,
                              zIndex: 0,
                            }}
                          />
                        )}
                        <Box
                          sx={{
                            position: 'relative',
                            zIndex: 2,
                            display: 'flex',
                            alignItems: 'center',
                            gap: 0.75,
                            px: 0.85,
                            py: 0.4,
                            bgcolor:
                              realtimeUpdateEnabled && wsStatus === 'connected'
                                ? 'transparent'
                                : isDark
                                  ? 'rgba(15, 17, 22, 0.96)'
                                  : 'rgba(250, 252, 248, 0.98)',
                          }}
                        >
                        <Chip
                          icon={wsStatus === 'connected' ? <CheckCircle /> : undefined}
                          label={
                            wsStatus === 'connected' ? '실시간 연결됨' :
                            wsStatus === 'connecting' ? '연결 중...' :
                            wsStatus === 'error' ? '연결 오류' :
                            '연결 끊김'
                          }
                          size="small"
                          variant={
                            realtimeUpdateEnabled && wsStatus === 'connected' && !realtimeDataReceivedLook
                              ? 'outlined'
                              : 'filled'
                          }
                          color={
                            wsStatus === 'connecting' ? 'warning' :
                            wsStatus === 'error' ? 'error' :
                            'default'
                          }
                          sx={{
                            ...(wsStatus === 'connected' && realtimeUpdateEnabled && !realtimeDataReceivedLook
                              ? {
                                  bgcolor: 'transparent',
                                  border: '1px solid rgba(140, 210, 120, 0.9)',
                                  borderColor: 'rgba(140, 210, 120, 0.9)',
                                  color: '#ffffff',
                                  '& .MuiChip-icon': {
                                    color: '#a5d6a7',
                                  },
                                }
                              : wsStatus === 'connected'
                              ? {
                                  bgcolor: realtimeGreenSolid,
                                  border: 'none',
                                }
                              : {
                                  bgcolor:
                                    wsStatus === 'connecting' ? 'rgba(237, 108, 2, 0.9)' :
                                    wsStatus === 'error' ? 'rgba(211, 47, 47, 0.9)' :
                                    'rgba(97, 97, 97, 0.9)',
                                }),
                            color: '#ffffff',
                            fontWeight: 600,
                            fontSize: '0.75rem',
                            height: 24,
                            '& .MuiChip-label': { px: 0.75 },
                          }}
                        />
                        {realtimeUpdateEnabled && wsStatus === 'connected' && (
                          <Chip
                            icon={
                              <Schedule
                                sx={{
                                  fontSize: '16px !important',
                                  color:
                                    realtimeDataReceivedLook ? '#ffffff' : realtimeBlueSolid,
                                }}
                              />
                            }
                            label={`${realtimeUpdateInterval / 1000}초 갱신`}
                            size="small"
                            variant={realtimeDataReceivedLook ? 'filled' : 'outlined'}
                            sx={{
                              ...(realtimeDataReceivedLook
                                ? {
                                    bgcolor: realtimeBlueSolid,
                                    color: '#ffffff',
                                    border: '1px solid rgba(255, 255, 255, 0.95)',
                                  }
                                : {
                                    bgcolor: 'transparent',
                                    color: '#ffffff',
                                    border: '1px solid rgba(255, 255, 255, 0.92)',
                                    borderColor: 'rgba(69, 137, 225, 0.95)',
                                  }),
                              fontWeight: 600,
                              fontSize: '0.7rem',
                              height: 24,
                              '& .MuiChip-label': { px: 0.75 },
                              '& .MuiChip-icon': {
                                color: realtimeDataReceivedLook ? '#ffffff' : realtimeBlueSolid,
                              },
                            }}
                          />
                        )}
                        {!realtimeUpdateEnabled && (
                          <Chip
                            label="수동 갱신"
                            size="small"
                            variant="outlined"
                            sx={{
                              bgcolor: 'rgba(120, 120, 120, 0.92)',
                              color: '#ffffff',
                              borderColor: 'rgba(255,255,255,0.85)',
                              fontSize: '0.7rem',
                              height: 24,
                            }}
                          />
                        )}
                        </Box>
                      </Box>
                    </Box>
                  </Box>

                  {/* 중앙: 차트 툴바 (트레이딩뷰 모드일 때만) */}
                  <Box sx={{ display: 'flex', alignItems: 'center', flex: 1, gap: 1 }}>
                    {chartViewMode === 'tradingview' && (
                      <>
                        <ChartToolbar
                          activeTool={activeTool || 'crosshair'}
                          onToolChange={(tool) => {
                            console.log(`🔧 [툴바] 툴 변경: ${tool}`);
                            setActiveTool(tool);
                          }}
                          onUndo={handleUndo}
                          onRedo={handleRedo}
                          onZoomIn={() => {
                            if (chartRef.current) {
                              const currentRange = chartRef.current.getVisibleLogicalRange();
                              if (currentRange) {
                                const { from, to } = currentRange;
                                const center = (from + to) / 2;
                                const halfRange = (to - from) / 2;
                                chartRef.current.setVisibleLogicalRange(center - halfRange * 0.8, center + halfRange * 0.8);
                              }
                            }
                          }}
                          onZoomOut={() => {
                            if (chartRef.current) {
                              const currentRange = chartRef.current.getVisibleLogicalRange();
                              if (currentRange) {
                                const { from, to } = currentRange;
                                const center = (from + to) / 2;
                                const halfRange = (to - from) / 2;
                                chartRef.current.setVisibleLogicalRange(center - halfRange * 1.25, center + halfRange * 1.25);
                              }
                            }
                          }}
                          onClearAll={() => {
                            setFibonacciTimezones([]);
                            updateDrawingsWithHistory([]);
                          }}
                        />
                        {/* 피보나치 툴 선택 시 설정 버튼 표시 */}
                        {(activeTool === 'fibonacci' || activeTool === 'fibonacciTimezone') && (
                          <IconButton
                            size="small"
                            onClick={() => {
                              console.log('⚙️ [피보나치 설정 버튼] 클릭 - 기본 설정 편집 모드');
                              setEditingFibId(null); // 기본 설정 편집
                              setFibDialogOpen(true);
                            }}
                            sx={{
                              backgroundColor: 'rgba(255, 255, 255, 0.1)',
                              '&:hover': {
                                backgroundColor: 'rgba(255, 255, 255, 0.2)',
                              },
                            }}
                          >
                            <SettingsIcon fontSize="small" />
                          </IconButton>
                        )}
                      </>
                    )}
                  </Box>

                  {/* 오른쪽: 차트 모드 선택 버튼 — @dnd-kit PointerSensor가 클릭을 가로채지 않도록 캡처 단계에서 차단 */}
                  <Box
                    onPointerDownCapture={(e) => e.stopPropagation()}
                    sx={{
                      display: 'flex',
                      alignItems: 'center',
                      gap: 0.5,
                      backgroundColor: isDark ? 'rgba(30, 41, 59, 0.85)' : 'rgba(255, 255, 255, 0.95)',
                      borderRadius: 1,
                      padding: '4px',
                      border: isDark ? 'none' : '1px solid rgba(0,0,0,0.15)',
                    }}
                  >
                    {/* ✅ 전체화면 버튼 */}
                    <MuiTooltip title={isChartFullscreen ? "전체화면 종료 (ESC)" : "전체화면"}>
                      <IconButton
                        size="small"
                        onClick={handleToggleFullscreen}
                        sx={{
                          width: 28,
                          height: 28,
                          bgcolor: isChartFullscreen ? 'primary.main' : 'transparent',
                          color: isChartFullscreen ? 'white' : (isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)'),
                          '&:hover': {
                            bgcolor: isChartFullscreen ? 'primary.dark' : (isDark ? 'rgba(71, 85, 105, 0.5)' : 'rgba(0, 0, 0, 0.08)'),
                          },
                        }}
                      >
                        {isChartFullscreen ? <FullscreenExit sx={{ fontSize: '18px' }} /> : <Fullscreen sx={{ fontSize: '18px' }} />}
                      </IconButton>
                    </MuiTooltip>

                    <MuiTooltip title="실시간 차트 값 (캔들·지표 숫자)" disableInteractive>
                      <IconButton
                        size="small"
                        onPointerDown={(e) => e.stopPropagation()}
                        onClick={(e) => {
                          e.stopPropagation();
                          const el = e.currentTarget;
                          setChartValuesAnchorEl((prev) => (prev === el ? null : el));
                        }}
                        sx={{
                          width: 28,
                          height: 28,
                          bgcolor: chartValuesAnchorEl ? 'secondary.main' : 'transparent',
                          color: chartValuesAnchorEl ? 'white' : (isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)'),
                          '&:hover': {
                            bgcolor: chartValuesAnchorEl ? 'secondary.dark' : (isDark ? 'rgba(71, 85, 105, 0.5)' : 'rgba(0, 0, 0, 0.08)'),
                          },
                        }}
                      >
                        <TableRows sx={{ fontSize: '18px' }} />
                      </IconButton>
                    </MuiTooltip>

                    {/* 돋보기 버튼 */}
                    <MuiTooltip title={activeTool === 'magnifier' ? "돋보기 끄기" : "돋보기 켜기"}>
                      <IconButton
                        size="small"
                        onClick={() => {
                          console.log(`🔍 [돋보기] 토글: ${activeTool === 'magnifier' ? 'OFF' : 'ON'}`);
                          setActiveTool(activeTool === 'magnifier' ? 'crosshair' : 'magnifier');
                        }}
                        sx={{
                          width: 28,
                          height: 28,
                          bgcolor: activeTool === 'magnifier' ? 'primary.main' : 'transparent',
                          color: activeTool === 'magnifier' ? 'white' : (isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)'),
                          '&:hover': {
                            bgcolor: activeTool === 'magnifier' ? 'primary.dark' : (isDark ? 'rgba(71, 85, 105, 0.5)' : 'rgba(0, 0, 0, 0.08)'),
                          },
                        }}
                      >
                        <SearchIcon sx={{ fontSize: '18px' }} />
                      </IconButton>
                    </MuiTooltip>

                    {/* 실시간 갱신 토글 버튼 */}
                    <MuiTooltip title={realtimeUpdateEnabled ? `실시간 갱신 ON (${realtimeUpdateInterval/1000}초마다)` : "실시간 갱신 끄기"}>
                      <IconButton
                        size="small"
                        onClick={(e) => {
                          if (realtimeUpdateEnabled) {
                            // 켜져있으면 메뉴 열기
                            setRealtimeIntervalMenuAnchor(e.currentTarget);
                          } else {
                            // 꺼져있으면 켜기
                            setRealtimeUpdateEnabled(true);
                            localStorage.setItem('realtimeUpdateEnabled', 'true');
                            console.log(`🔄 [실시간 갱신] ON (${realtimeUpdateInterval/1000}초)`);
                          }
                        }}
                        onContextMenu={(e) => {
                          e.preventDefault();
                          setRealtimeIntervalMenuAnchor(e.currentTarget);
                        }}
                        sx={{
                          width: 28,
                          height: 28,
                          bgcolor: realtimeUpdateEnabled ? 'success.main' : 'transparent',
                          color: realtimeUpdateEnabled ? 'white' : (isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)'),
                          '&:hover': {
                            bgcolor: realtimeUpdateEnabled ? 'success.dark' : (isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.04)'),
                          },
                        }}
                      >
                        <TrendingUp sx={{ fontSize: 20 }} />
                      </IconButton>
                    </MuiTooltip>

                    {/* 봉 완성 시간 표시 버튼 */}
                    <MuiTooltip title={candleTimeRemainingEnabled ? "봉 완성 시간 숨기기" : "봉 완성 시간 표시"}>
                      <IconButton
                        size="small"
                        onClick={() => {
                          console.log(`⏰ [봉 완성 시간] 토글: ${candleTimeRemainingEnabled ? 'OFF' : 'ON'}`);
                          setCandleTimeRemainingEnabled(!candleTimeRemainingEnabled);
                        }}
                        sx={{
                          width: 28,
                          height: 28,
                          bgcolor: candleTimeRemainingEnabled ? 'primary.main' : 'transparent',
                          color: candleTimeRemainingEnabled ? 'white' : (isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)'),
                          '&:hover': {
                            bgcolor: candleTimeRemainingEnabled ? 'primary.dark' : (isDark ? 'rgba(71, 85, 105, 0.5)' : 'rgba(0, 0, 0, 0.08)'),
                          },
                        }}
                      >
                        <Schedule sx={{ fontSize: '18px' }} />
                      </IconButton>
                    </MuiTooltip>

                    <Divider orientation="vertical" flexItem sx={{ mx: 0.5, my: 0.5 }} />

                    <Button
                      size="small"
                      variant={chartViewMode === 'basic' ? 'contained' : 'text'}
                      onClick={() => {
                        console.log(`📱 [차트 모드 변경] 기본차트로 전환`);
                        setChartViewMode('basic');
                      }}
                      sx={{
                        minWidth: 'auto',
                        px: 1.5,
                        py: 0.5,
                        fontSize: '0.75rem',
                        color: chartViewMode === 'basic'
                          ? 'white'
                          : (isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)'),
                        backgroundColor: chartViewMode === 'basic'
                          ? (isDark ? 'rgba(59, 130, 246, 0.9)' : '#1976d2')
                          : 'transparent',
                        '&:hover': {
                          backgroundColor: chartViewMode === 'basic'
                            ? (isDark ? 'rgba(59, 130, 246, 1)' : '#1565c0')
                            : (isDark ? 'rgba(71, 85, 105, 0.5)' : 'rgba(0, 0, 0, 0.08)'),
                        },
                      }}
                    >
                      기본차트
                    </Button>
                    <Button
                      size="small"
                      variant={chartViewMode === 'tradingview' ? 'contained' : 'text'}
                      onClick={() => {
                        console.log(`📱 [차트 모드 변경] 트레이딩뷰로 전환`);
                        setChartViewMode('tradingview');
                      }}
                      sx={{
                        minWidth: 'auto',
                        px: 1.5,
                        py: 0.5,
                        fontSize: '0.75rem',
                        color: chartViewMode === 'tradingview'
                          ? 'white'
                          : (isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)'),
                        backgroundColor: chartViewMode === 'tradingview'
                          ? (isDark ? 'rgba(59, 130, 246, 0.9)' : '#1976d2')
                          : 'transparent',
                        '&:hover': {
                          backgroundColor: chartViewMode === 'tradingview'
                            ? (isDark ? 'rgba(59, 130, 246, 1)' : '#1565c0')
                            : (isDark ? 'rgba(71, 85, 105, 0.5)' : 'rgba(0, 0, 0, 0.08)'),
                        },
                      }}
                    >
                      트레이딩뷰
                    </Button>
                  </Box>
                </Box>

              {/* 메인 캔들차트 */}
              <Box
                ref={chartContainerRef}
                sx={{
                  mb: 1,
                  position: 'relative',
                  flex: isChartFullscreen ? 1 : 'initial',
                  minHeight: isChartFullscreen ? 0 : 'auto',
                }}
              >

                {/* ✅ 기간 이동 중 로딩 오버레이 */}
                {isLoadingHistory && (
                  <Box
                    sx={{
                      position: 'absolute',
                      top: 0,
                      left: 0,
                      right: 0,
                      bottom: 0,
                      backgroundColor: 'rgba(0, 0, 0, 0.7)',
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center',
                      zIndex: 9999,
                      borderRadius: 1,
                    }}
                  >
                    <Box sx={{ textAlign: 'center' }}>
                      <CircularProgress size={40} />
                      <Typography variant="body2" sx={{ mt: 2, color: 'white' }}>
                        차트 데이터 로딩 중...
                      </Typography>
                    </Box>
                  </Box>
                )}
                  {/* ✅ 로딩 중에도 기존 차트 유지 (로딩 오버레이만 표시) */}
                <CandleChartDropZone>
                  {/* 캔들차트 영역 (리사이즈 핸들 위치 제어용) */}
                  <Box sx={{ position: 'relative' }}>
                    {/* ✅ 봉 완성 시간 표시 */}
                    {candleTimeRemainingEnabled && candleData.length > 0 && (
                      <CandleTimeRemaining
                        lastCandleTime={candleData[candleData.length - 1].time}
                        interval={timeInterval}
                        visible={candleTimeRemainingEnabled}
                        chartWidth={chartContainerWidth}
                      />
                    )}
                    
                    <CandlestickChart
                    ref={chartRef}
                    data={chartData}
                    indicatorData={indicatorData}
                    maCalculationData={maCalculationData}
                    allIndicatorData={allIndicatorData}
                    height={isChartFullscreen && fullscreenHeights ? fullscreenHeights.candle : (isChartFullscreen ? 350 : candleChartHeight)}
                    isResizing={isCandleChartResizing}
                    showIndicatorOverlay={false}
                    hideNativeCrosshair={false}
                    hideTimeScale={!!candleChartOverlayIndicator}
                    showTooltip={tooltipEnabled}
                    onMarkerClick={handleMarkerClick}
                    trendShortMA={trendShortMA}
                    trendMediumMA={trendMediumMA}
                    trendLongMA={trendLongMA}
                    hideTrendAlignmentBar
                    onSubPanelSettingsClick={(panelType, title) => {
                      // 서브패널 타입을 IndicatorType으로 매핑
                      const typeMap: Record<string, IndicatorType> = {
                        volume: 'volume',
                        macd: 'macd',
                        rsi: 'rsi',
                        stochastic: 'stochastic',
                        cci: 'cci',
                        adx: 'adx',
                        obv: 'obv',
                        psychological: 'newPsychological',  // ✅ 이전 호환성
                        newPsychological: 'newPsychological',  // ✅ 신심리도
                        investPsychological: 'investPsychological',  // ✅ 투자심리도
                        bwi: 'bwi',
                        dmi: 'dmi',
                        williamsR: 'williamsR',
                        trix: 'trix',
                        volumeOsc: 'volumeOsc',
                        vr: 'vr',
                        vr2: 'vr2',  // ✅ VR2 추가
                        disparity: 'disparity',
                      };
                      
                      const indicatorType = typeMap[panelType];
                      if (indicatorType) {
                        setSelectedIndicator(indicatorType);
                        setSettingsPopupOpen(true);
                      }
                    }}
                    onIndicatorDelete={(panelType) => {
                      // ✅ 보조지표 삭제(off 설정)
                      const settingsMap: Record<string, Partial<IndicatorVisibilitySettings>> = {
                        volume: { volume: false },
                        macd: { macdLine: false, macdHistogram: false },
                        rsi: { rsi: false },
                        stochastic: { stochasticK: false, stochasticD: false, stochasticSlow: false },
                        cci: { cci: false },
                        adx: { adx: false },
                        obv: { obv: false },
                        newPsychological: { newPsychological: false },
                        investPsychological: { investPsychological: false },
                        bwi: { bwi: false },
                        dmi: { plusDi: false, minusDi: false },
                        williamsR: { williamsR: false },
                        trix: { trix: false, trixSignal: false },
                        volumeOsc: { volumeOsc: false },
                        vr: { vr: false },
                        disparity: { disparity: false },
                      };
                      
                      const settingsToUpdate = settingsMap[panelType];
                      if (settingsToUpdate) {
                        updateIndicatorSettings(settingsToUpdate);
                      }
                    }}
                    indicatorSettings={paramSettings}
                    colorSettings={colorSettings}
                    onSubPanelReorder={handleSubPanelReorder}
                    subPanels={subPanelsOrder.map(type => {
                      const enabledMap: Record<string, boolean> = {
                        volume: indicatorVisibilitySettings.volume,
                        macd: indicatorVisibilitySettings.macdLine,
                        rsi: indicatorVisibilitySettings.rsi,
                        stochastic: indicatorVisibilitySettings.stochasticK || indicatorVisibilitySettings.stochasticD || indicatorVisibilitySettings.stochasticSlow,
                        cci: indicatorVisibilitySettings.cci,
                        adx: indicatorVisibilitySettings.adx,
                        obv: indicatorVisibilitySettings.obv,
                        newPsychological: indicatorVisibilitySettings.newPsychological,
                        investPsychological: indicatorVisibilitySettings.investPsychological,
                        bwi: indicatorVisibilitySettings.bwi,
                        dmi: indicatorVisibilitySettings.plusDi || indicatorVisibilitySettings.minusDi,
                        williamsR: indicatorVisibilitySettings.williamsR,
                        trix: indicatorVisibilitySettings.trix,
                        volumeOsc: indicatorVisibilitySettings.volumeOsc,
                        vr: indicatorVisibilitySettings.vr,
                        disparity: indicatorVisibilitySettings.disparity,
                      };
                      const panelHeight = isChartFullscreen && fullscreenHeights ? fullscreenHeights.indicator : indicatorPanelHeight;
                      return {
                        type: type as any,
                        height: panelHeight,
                        enabled: enabledMap[type] || false,
                      };
                    })}
                    colorVersion={colorVersion}
                    settingsVersion={settingsVersion}
                    onCrosshairMove={(time) => {
                      syncCrosshairToOtherCharts(time, 'candlestick');
                      // 캔들차트 하단 보조지표도 직접 업데이트
                      if (candleChartOverlayIndicator && time) {
                        const chartRefMap: Record<string, React.RefObject<IndicatorChartHandle>> = {
                          volume: volumeChartRef,
                          newPsychological: newPsychologicalChartRef,
                          investPsychological: investPsychologicalChartRef,
                          macd: macdChartRef,
                          cci: cciChartRef,
                          adx: adxChartRef,
                          bwi: bwiChartRef,
                          dmi: dmiChartRef,
                          obv: obvChartRef,
                          rsi: rsiChartRef,
                          stochastic: stochasticChartRef,
                          williamsR: williamsRChartRef,
                          trix: trixChartRef,
                          volumeOsc: volumeOscChartRef,
                          vr: vrChartRef,
                          disparity: disparityChartRef,
                        };
                        const overlayChartRef = chartRefMap[candleChartOverlayIndicator];
                        if (overlayChartRef?.current?.setCrosshairPosition) {
                          overlayChartRef.current.setCrosshairPosition(time);
                        }
                      }
                    }}
                    activeTool={(activeTool === 'fibonacci' || activeTool === 'fibonacciTimezone' || activeTool === 'fibonacciFan' || activeTool === 'fibonacciCircle' || activeTool === 'fibonacciWedge') ? 'crosshair' : activeTool}
                    onChartClick={handleChartClick}
                    onChartMouseDown={handleChartMouseDown}
                    onChartMouseMove={handleChartMouseMove}
                    onChartMouseUp={handleChartMouseUp}
                    chartViewMode={chartViewMode}
                    maLines={memoizedMaLines}
                    ichimokuSettings={memoizedIchimokuSettings}
                    ichimokuColors={memoizedIchimokuColors}
                    onVisibleRangeChange={(from, to) => {
                      // ✅ 전체화면 전환 중 뷰 범위 복원 시에는 처리 생략
                      if (isRestoringViewRangeRef.current) {
                        console.log('⏸️ 뷰 범위 복원 중 - onVisibleRangeChange 생략');
                        return;
                      }
                      
                      // ✅ 브라우저 resize 중에는 처리 생략 (개발자 도구 토글, 창 크기 조절 등)
                      if (isResizingRef.current) {
                        console.log('⏸️ 브라우저 Resize 중 - onVisibleRangeChange 생략');
                        return;
                      }

                      // ✅ 백테스트 후 뷰 복원 중에는 모든 자동 뷰 범위 조정 무시
                      // 보조지표 차트는 useEffect에서 명시적으로 동기화됨
                      if (isRestoringViewRef.current) {
                        console.log('⏸️ 백테스트 후 뷰 복원 중 - onVisibleRangeChange 완전히 무시 (useEffect에서 동기화)');
                        return;
                      }

                      // ✅ 줌 처리 중에는 동기화 생략 (useEffect에서 이미 처리됨)
                      if (isZoomingRef.current) {
                        console.log('⏸️ [onVisibleRangeChange] 줌 컨트롤 처리 중 - 동기화 생략');
                        return;
                      }

                      // ✅ 이미 동기화 중이면 무한 루프 방지
                      if (isSyncingVisibleRangeRef.current) {
                        return;
                      }

                      // ✅ 동기화 시작
                      isSyncingVisibleRangeRef.current = true;

                      // ✅ 캔들차트에서 logical range 가져오기 (스케일 유지)
                      const logicalRange = chartRef.current?.getVisibleLogicalRange();
                      
                      // 캔들차트의 visible range를 보조지표 차트와 동기화
                      // ✅ requestAnimationFrame으로 묶어서 한 번에 처리 (깜빡임 방지)
                      requestAnimationFrame(() => {
                        if (logicalRange) {
                          // ✅ logical range 사용 (스케일 유지)
                          volumeChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          macdChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          rsiChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          stochasticChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          cciChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          adxChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          obvChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          newPsychologicalChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          investPsychologicalChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          bwiChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          dmiChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          williamsRChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          trixChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          volumeOscChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          vrChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          disparityChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          // 캔들차트 하단에 추가된 보조지표도 동기화
                          if (candleChartOverlayIndicator) {
                            const chartRefMap: Record<string, React.RefObject<IndicatorChartHandle>> = {
                              volume: volumeChartRef,
                              newPsychological: newPsychologicalChartRef,
                              investPsychological: investPsychologicalChartRef,
                              macd: macdChartRef,
                              cci: cciChartRef,
                              adx: adxChartRef,
                              bwi: bwiChartRef,
                              dmi: dmiChartRef,
                              obv: obvChartRef,
                              rsi: rsiChartRef,
                              stochastic: stochasticChartRef,
                              williamsR: williamsRChartRef,
                              trix: trixChartRef,
                              volumeOsc: volumeOscChartRef,
                              vr: vrChartRef,
                              disparity: disparityChartRef,
                            };
                            chartRefMap[candleChartOverlayIndicator]?.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                          }
                        }
                      });

                      // ✅ chartData 사용 (candleData가 아니라!) - 일목균형표 미래 영역 포함
                      if (chartData.length > 0) {
                        console.log(`🔍 [onVisibleRangeChange] chartData 사용: ${chartData.length}개 (첫=${chartData[0]?.time || chartData[0]?.date}, 끝=${chartData[chartData.length - 1]?.time || chartData[chartData.length - 1]?.date})`);

                        const fromMs = from * 1000;
                        const toMs = to * 1000;

                        // chartData를 시간순으로 정렬 (미래 영역 포함)
                        const sortedCandleData = [...chartData].sort((a, b) => {
                          return new Date(a.time || a.date).getTime() - new Date(b.time || b.date).getTime();
                        });

                        console.log(`🔍 [onVisibleRangeChange] sortedCandleData: ${sortedCandleData.length}개 (첫=${sortedCandleData[0]?.time || sortedCandleData[0]?.date}, 끝=${sortedCandleData[sortedCandleData.length - 1]?.time || sortedCandleData[sortedCandleData.length - 1]?.date})`);

                        // ★ 시작 인덱스 찾기: from 시간 이전의 마지막 데이터부터 시작
                        // 이렇게 해야 캔들차트의 visible range와 정확히 일치
                        let startIndex = -1;
                        for (let i = 0; i < sortedCandleData.length; i++) {
                          const candleTime = new Date(sortedCandleData[i].time || sortedCandleData[i].date).getTime();
                          if (candleTime >= fromMs) {
                            // from 시간 이상인 첫 번째 데이터 발견
                            // 이전 데이터가 있고 from 시간에 더 가까우면 그것 사용
                            if (i > 0) {
                              const prevTime = new Date(sortedCandleData[i - 1].time || sortedCandleData[i - 1].date).getTime();
                              // from 시간이 이전 데이터와 현재 데이터 사이에 있으면 이전 데이터 사용
                              if (fromMs - prevTime < candleTime - fromMs) {
                                startIndex = i - 1;
                              } else {
                                startIndex = i;
                              }
                            } else {
                              startIndex = i;
                            }
                            break;
                          }
                        }
                        if (startIndex === -1) startIndex = 0;

                        // 끝 인덱스 찾기: to 시간 이후의 첫 번째 데이터까지 포함
                        let endIndex = sortedCandleData.length;
                        for (let i = sortedCandleData.length - 1; i >= 0; i--) {
                          const candleTime = new Date(sortedCandleData[i].time || sortedCandleData[i].date).getTime();
                          if (candleTime <= toMs) {
                            // to 시간 이하인 마지막 데이터 발견
                            // 다음 데이터가 있고 to 시간에 더 가까우면 그것 사용
                            if (i < sortedCandleData.length - 1) {
                              const nextTime = new Date(sortedCandleData[i + 1].time || sortedCandleData[i + 1].date).getTime();
                              if (nextTime - toMs < toMs - candleTime) {
                                endIndex = i + 2; // i+1까지 포함하므로 i+2
                              } else {
                                endIndex = i + 1;
                              }
                            } else {
                              endIndex = i + 1;
                            }
                            break;
                          }
                        }

                        // 최소 범위 보장
                        if (endIndex - startIndex < 10) {
                          endIndex = Math.min(sortedCandleData.length, startIndex + 10);
                        }

                        console.log(`🔍 [onVisibleRangeChange] 계산된 인덱스: start=${startIndex}, end=${endIndex}, 총=${sortedCandleData.length}개`);
                        console.log(`🔍 [onVisibleRangeChange] 시간 범위: ${sortedCandleData[startIndex]?.time || sortedCandleData[startIndex]?.date} ~ ${sortedCandleData[Math.min(endIndex - 1, sortedCandleData.length - 1)]?.time || sortedCandleData[Math.min(endIndex - 1, sortedCandleData.length - 1)]?.date}`);

                        // ✅ sharedViewRange 자동 업데이트 비활성화 (업비트 스타일 수동 스크롤 허용)
                        // 보조지표 동기화는 이미 setVisibleLogicalRange로 처리됨 (9903-9945줄)
                        // if (sharedViewRange.start !== startIndex || sharedViewRange.end !== endIndex) {
                        //   setSharedViewRange({ start: startIndex, end: endIndex });
                        // }

                        // viewRange 변경 시 현재 호버 시간이 새 범위에 포함되는지 확인
                        // 포함되지 않으면 리셋하여 크로스헤어 동기화 문제 방지
                        if (sharedHoverTime) {
                          const hoverMs = new Date(sharedHoverTime).getTime();
                          if (hoverMs < fromMs || hoverMs > toMs) {
                            setSharedHoverTime(null);
                          }
                        }
                      }

                      // ✅ 동기화 완료 (빠른 해제로 깜빡임 방지)
                      setTimeout(() => {
                        isSyncingVisibleRangeRef.current = false;
                      }, 10);
                    }}
                    externalHoverTime={sharedHoverTime}
                    onZoomChange={(fromTime, toTime) => {
                      // 줌 컨트롤러 변경 시 모든 보조지표 차트 동기화
                      console.log(`🔍 [줌 컨트롤러] 보조지표 차트 동기화: ${fromTime} ~ ${toTime}`);
                      volumeChartRef.current?.setVisibleRange(fromTime, toTime);
                      macdChartRef.current?.setVisibleRange(fromTime, toTime);
                      rsiChartRef.current?.setVisibleRange(fromTime, toTime);
                      stochasticChartRef.current?.setVisibleRange(fromTime, toTime);
                      cciChartRef.current?.setVisibleRange(fromTime, toTime);
                      adxChartRef.current?.setVisibleRange(fromTime, toTime);
                      obvChartRef.current?.setVisibleRange(fromTime, toTime);
                      newPsychologicalChartRef.current?.setVisibleRange(fromTime, toTime);
                      investPsychologicalChartRef.current?.setVisibleRange(fromTime, toTime);
                      bwiChartRef.current?.setVisibleRange(fromTime, toTime);
                      dmiChartRef.current?.setVisibleRange(fromTime, toTime);
                      williamsRChartRef.current?.setVisibleRange(fromTime, toTime);
                      trixChartRef.current?.setVisibleRange(fromTime, toTime);
                      volumeOscChartRef.current?.setVisibleRange(fromTime, toTime);
                      vrChartRef.current?.setVisibleRange(fromTime, toTime);
                      disparityChartRef.current?.setVisibleRange(fromTime, toTime);
                      // 캔들차트 하단에 추가된 보조지표도 동기화
                      if (candleChartOverlayIndicator) {
                        const chartRefMap: Record<string, React.RefObject<IndicatorChartHandle>> = {
                          volume: volumeChartRef,
                          newPsychological: newPsychologicalChartRef,
                          investPsychological: investPsychologicalChartRef,
                          macd: macdChartRef,
                          cci: cciChartRef,
                          adx: adxChartRef,
                          bwi: bwiChartRef,
                          dmi: dmiChartRef,
                          obv: obvChartRef,
                          rsi: rsiChartRef,
                          stochastic: stochasticChartRef,
                          williamsR: williamsRChartRef,
                          trix: trixChartRef,
                          volumeOsc: volumeOscChartRef,
                          vr: vrChartRef,
                          disparity: disparityChartRef,
                        };
                        chartRefMap[candleChartOverlayIndicator]?.current?.setVisibleRange(fromTime, toTime);
                      }
                    }}
                  />

                  {/* 캔들차트 리사이즈 핸들 — 캔들 영역 하단 (absolute) */}
                  <Box
                    onMouseDown={(e) => {
                      e.preventDefault();
                      e.stopPropagation();
                      handleCandleChartResizeStart();

                      const startY = e.clientY;
                      const startHeight = (isChartFullscreen && fullscreenHeights) ? fullscreenHeights.candle : candleChartHeight;

                      const handleMouseMove = (moveEvent: MouseEvent) => {
                        const deltaY = moveEvent.clientY - startY;
                        const newHeight = Math.max(200, Math.min(800, startHeight + deltaY));
                        if (isChartFullscreen && fullscreenChartAreaHeight > 0) {
                          const RESIZE_HANDLE = 8;
                          const available = fullscreenChartAreaHeight - TREND_BAR_HEIGHT - RESIZE_HANDLE;
                          setFullscreenCandleRatio(Math.max(0.3, Math.min(0.9, newHeight / available)));
                        }
                        handleCandleChartResize(newHeight);
                      };

                      const handleMouseUp = () => {
                        handleCandleChartResizeEnd();
                        document.removeEventListener('mousemove', handleMouseMove);
                        document.removeEventListener('mouseup', handleMouseUp);
                      };

                      document.addEventListener('mousemove', handleMouseMove);
                      document.addEventListener('mouseup', handleMouseUp);
                    }}
                    sx={{
                      position: 'absolute',
                      top: ((isChartFullscreen && fullscreenHeights) ? fullscreenHeights.candle : (isChartFullscreen ? 350 : candleChartHeight)) + TREND_BAR_HEIGHT,
                      left: 0,
                      right: 0,
                      width: '100%',
                      height: 8,
                      cursor: 'ns-resize',
                      bgcolor: 'transparent',
                      '&:hover': {
                        '& .resize-handle-icon': {
                          opacity: 1,
                        },
                      },
                      transition: 'background-color 0.2s',
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center',
                      zIndex: 200,
                      pointerEvents: 'auto',
                      userSelect: 'none', // 텍스트 선택 방지
                    }}
                  >
                    {/* 드래그 핸들 아이콘 */}
                    <Box
                      className="resize-handle-icon"
                      sx={{
                        display: 'flex',
                        alignItems: 'center',
                        justifyContent: 'center',
                        bgcolor: isCandleChartResizing ? 'primary.main' : 'rgba(144, 202, 249, 0.8)',
                        borderRadius: '12px',
                        px: 1.5,
                        py: 0.5,
                        opacity: isCandleChartResizing ? 1 : 0.6,
                        transition: 'opacity 0.2s, background-color 0.2s',
                        boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
                        pointerEvents: 'none', // 드래그 방지
                        userSelect: 'none', // 텍스트 선택 방지
                        '&:hover': {
                          bgcolor: isCandleChartResizing ? 'primary.dark' : 'primary.main',
                        },
                      }}
                    >
                      <DragIndicator
                        sx={{
                          fontSize: 16,
                          color: 'white',
                          transform: 'rotate(90deg)',
                          pointerEvents: 'none', // 드래그 방지
                        }}
                      />
                    </Box>
                  </Box>

                  {/* 보조지표 리사이즈 핸들 - 보조지표 영역 하단 중앙 */}
                  {(() => {
                    // 활성화된 보조지표 개수 계산
                    const enabledIndicatorsCount = [
                      indicatorVisibilitySettings.volume,
                      indicatorVisibilitySettings.macdLine || indicatorVisibilitySettings.macdHistogram,
                      indicatorVisibilitySettings.rsi,
                      indicatorVisibilitySettings.stochasticK || indicatorVisibilitySettings.stochasticD || indicatorVisibilitySettings.stochasticSlow,
                      indicatorVisibilitySettings.cci,
                      indicatorVisibilitySettings.adx,
                      indicatorVisibilitySettings.obv,
                      indicatorVisibilitySettings.newPsychological || indicatorVisibilitySettings.investPsychological,
                      indicatorVisibilitySettings.bwi,
                      indicatorVisibilitySettings.plusDi || indicatorVisibilitySettings.minusDi,
                      indicatorVisibilitySettings.williamsR,
                      indicatorVisibilitySettings.trix,
                      indicatorVisibilitySettings.volumeOsc,
                      indicatorVisibilitySettings.vr,
                      indicatorVisibilitySettings.disparity,
                    ].filter(Boolean).length;

                    if (enabledIndicatorsCount === 0) return null;

                    const actualCandleHeight = (isChartFullscreen && fullscreenHeights) ? fullscreenHeights.candle : (isChartFullscreen ? 350 : candleChartHeight);
                    const actualIndicatorHeight = (isChartFullscreen && fullscreenHeights) ? fullscreenHeights.indicator : indicatorPanelHeight;
                    const totalIndicatorHeight = enabledIndicatorsCount * actualIndicatorHeight;
                    const topPosition = actualCandleHeight + TREND_BAR_HEIGHT + totalIndicatorHeight;

                    return (
                      <Box
                        onMouseDown={(e) => {
                          e.preventDefault();
                          e.stopPropagation();
                          handleIndicatorResizeStart();

                          const startY = e.clientY;
                          const startHeight = actualIndicatorHeight;

                          const handleMouseMove = (moveEvent: MouseEvent) => {
                            const deltaY = moveEvent.clientY - startY;
                            const newHeight = Math.max(80, Math.min(300, startHeight + deltaY));
                            if (isChartFullscreen && fullscreenChartAreaHeight > 0) {
                              const RESIZE_HANDLE = 8;
                              const available = fullscreenChartAreaHeight - TREND_BAR_HEIGHT - RESIZE_HANDLE;
                              const newCandle = available - enabledIndicatorsCount * newHeight;
                              setFullscreenCandleRatio(Math.max(0.3, Math.min(0.9, newCandle / available)));
                            }
                            handleIndicatorPanelResize(newHeight);
                          };

                          const handleMouseUp = () => {
                            handleIndicatorResizeEnd();
                            document.removeEventListener('mousemove', handleMouseMove);
                            document.removeEventListener('mouseup', handleMouseUp);
                          };

                          document.addEventListener('mousemove', handleMouseMove);
                          document.addEventListener('mouseup', handleMouseUp);
                        }}
                        sx={{
                          position: 'absolute',
                          top: topPosition,
                          left: 0,
                          right: 0,
                          width: '100%',
                          height: 8,
                          cursor: 'ns-resize',
                          bgcolor: 'transparent',
                          '&:hover': {
                            '& .resize-handle-icon': {
                              opacity: 1,
                            },
                          },
                          transition: 'background-color 0.2s',
                          display: 'flex',
                          alignItems: 'center',
                          justifyContent: 'center',
                          zIndex: 200,
                          pointerEvents: 'auto',
                          userSelect: 'none', // 텍스트 선택 방지
                        }}
                      >
                        {/* 드래그 핸들 아이콘 */}
                        <Box
                          className="resize-handle-icon"
                          sx={{
                            display: 'flex',
                            alignItems: 'center',
                            justifyContent: 'center',
                            bgcolor: isIndicatorResizing ? 'success.main' : 'rgba(76, 175, 80, 0.8)',
                            borderRadius: '12px',
                            px: 1.5,
                            py: 0.5,
                            opacity: isIndicatorResizing ? 1 : 0.6,
                            transition: 'opacity 0.2s, background-color 0.2s',
                            boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
                            pointerEvents: 'none', // 드래그 방지
                            userSelect: 'none', // 텍스트 선택 방지
                            '&:hover': {
                              bgcolor: isIndicatorResizing ? 'success.dark' : 'success.main',
                            },
                          }}
                        >
                          <DragIndicator
                            sx={{
                              fontSize: 16,
                              color: 'white',
                              transform: 'rotate(90deg)',
                              pointerEvents: 'none', // 드래그 방지
                            }}
                          />
                        </Box>
                      </Box>
                    );
                  })()}

                  {/* ✅ 피보나치 타임존 오버레이 */}
                  {chartViewMode === 'tradingview' && candleData.length > 0 && (() => {
                    // 활성화된 보조지표 개수 계산
                    const enabledIndicatorsCount = [
                      indicatorVisibilitySettings.volume,
                      indicatorVisibilitySettings.macdLine || indicatorVisibilitySettings.macdHistogram,
                      indicatorVisibilitySettings.rsi,
                      indicatorVisibilitySettings.stochasticK || indicatorVisibilitySettings.stochasticD || indicatorVisibilitySettings.stochasticSlow,
                      indicatorVisibilitySettings.cci,
                      indicatorVisibilitySettings.adx,
                      indicatorVisibilitySettings.obv,
                      indicatorVisibilitySettings.newPsychological || indicatorVisibilitySettings.investPsychological,
                      indicatorVisibilitySettings.bwi,
                      indicatorVisibilitySettings.plusDi || indicatorVisibilitySettings.minusDi,
                      indicatorVisibilitySettings.williamsR,
                      indicatorVisibilitySettings.trix,
                      indicatorVisibilitySettings.volumeOsc,
                      indicatorVisibilitySettings.vr,
                      indicatorVisibilitySettings.disparity,
                    ].filter(Boolean).length;

                    const candleHeight = (isChartFullscreen && fullscreenHeights) ? fullscreenHeights.candle : (isChartFullscreen ? 350 : candleChartHeight);
                    const subPanelHeight = (isChartFullscreen && fullscreenHeights) ? fullscreenHeights.indicator : indicatorPanelHeight;
                    const totalHeight = candleHeight + TREND_BAR_HEIGHT + (enabledIndicatorsCount * subPanelHeight);

                    return (
                      <FibonacciTimezoneOverlay
                        objects={fibonacciTimezones}
                        chartWidth={chartContainerWidth}
                        chartHeight={candleHeight}
                        totalChartHeight={totalHeight}
                        timeScale={{
                        from: sharedViewRange && candleData[sharedViewRange.start]
                          ? new Date(candleData[sharedViewRange.start].time).getTime() / 1000
                          : new Date(candleData[0].time).getTime() / 1000,
                        to: sharedViewRange && candleData[Math.min(sharedViewRange.end - 1, candleData.length - 1)]
                          ? new Date(candleData[Math.min(sharedViewRange.end - 1, candleData.length - 1)].time).getTime() / 1000
                          : new Date(candleData[candleData.length - 1].time).getTime() / 1000,
                      }}
                      candleData={candleData}
                      timeToX={(time: number) => {
                        if (!chartRef.current) {
                          console.log('⚠️ [timeToX] chartRef.current가 없음');
                          return null;
                        }

                        // ✅ 먼저 lightweight-charts의 정확한 좌표 사용 시도
                        const coordinate = chartRef.current.timeScale().timeToCoordinate(time);
                        if (coordinate !== null) {
                          // console.log(`✅ [timeToX] 정확한 좌표: time=${time}, x=${coordinate}`);
                          return coordinate;
                        }

                        console.log(`⚠️ [timeToX] timeToCoordinate가 null 반환, 선형 보간 시도: time=${time}`);

                        // ✅ null이면 선형 보간으로 계산 (가시 영역 밖의 시간도 처리)
                        if (candleData.length < 2) {
                          console.log(`❌ [timeToX] candleData 부족: ${candleData.length}`);
                          return null;
                        }

                        if (!sharedViewRange) {
                          console.log(`❌ [timeToX] sharedViewRange가 없음`);
                          return null;
                        }

                        const startIdx = Math.max(0, sharedViewRange.start);
                        const endIdx = Math.min(candleData.length - 1, sharedViewRange.end - 1);

                        console.log(`🔍 [timeToX] startIdx: ${startIdx}, endIdx: ${endIdx}, candleData.length: ${candleData.length}`);

                        if (startIdx >= candleData.length || endIdx < 0) {
                          console.log(`❌ [timeToX] 인덱스 범위 오류`);
                          return null;
                        }

                        const fromTime = new Date(candleData[startIdx].time).getTime() / 1000;
                        const toTime = new Date(candleData[endIdx].time).getTime() / 1000;
                        const timeRange = toTime - fromTime;

                        console.log(`🔍 [timeToX] fromTime: ${fromTime}, toTime: ${toTime}, timeRange: ${timeRange}`);

                        if (timeRange <= 0) {
                          console.log(`❌ [timeToX] timeRange가 0 이하: ${timeRange}`);
                          return null;
                        }

                        // 선형 보간: x = (time - from) / (to - from) * chartWidth
                        const normalizedTime = (time - fromTime) / timeRange;
                        const x = normalizedTime * chartContainerWidth;

                        console.log(`📐 [선형 보간] time: ${time}, x: ${x}, chartWidth: ${chartContainerWidth}, normalized: ${normalizedTime}`);

                        return x;
                      }}
                      onClick={handleFibonacciClick}
                      onDoubleClick={handleFibonacciDoubleClick}
                      onDragStart={handleFibonacciDragStart}
                      isDrawing={isDrawingFib}
                      drawingStartTime={fibStartTime}
                      />
                    );
                  })()}

                  {/* 새로운 피보나치 툴 오버레이 */}
                  {(() => {
                        const enabledIndicatorsCount = [
                          indicatorVisibilitySettings.volume,
                          indicatorVisibilitySettings.macdLine || indicatorVisibilitySettings.macdHistogram,
                          indicatorVisibilitySettings.rsi,
                          indicatorVisibilitySettings.stochasticK || indicatorVisibilitySettings.stochasticD || indicatorVisibilitySettings.stochasticSlow,
                          indicatorVisibilitySettings.cci,
                          indicatorVisibilitySettings.adx,
                          indicatorVisibilitySettings.obv,
                          indicatorVisibilitySettings.newPsychological || indicatorVisibilitySettings.investPsychological,
                          indicatorVisibilitySettings.bwi,
                          indicatorVisibilitySettings.plusDi || indicatorVisibilitySettings.minusDi,
                        ].filter(Boolean).length;
                        
                        const candleHeight = (isChartFullscreen && fullscreenHeights) ? fullscreenHeights.candle : (isChartFullscreen ? 350 : candleChartHeight);
                        const subPanelHeight = (isChartFullscreen && fullscreenHeights) ? fullscreenHeights.indicator : indicatorPanelHeight;
                        const totalHeight = candleHeight + TREND_BAR_HEIGHT + (enabledIndicatorsCount * subPanelHeight);

                        const visibleCandles = sharedViewRange 
                          ? candleData.slice(sharedViewRange.start, sharedViewRange.end)
                          : candleData;
                        const minPrice = Math.min(...visibleCandles.map(c => c.low));
                        const maxPrice = Math.max(...visibleCandles.map(c => c.high));

                        return (
                          <>
                            <FibonacciFan
                              objects={fibonacciFans}
                              chartWidth={chartContainerWidth}
                              chartHeight={candleHeight}
                              totalChartHeight={totalHeight}
                              priceScale={{
                                from: minPrice,
                                to: maxPrice,
                              }}
                              timeScale={{
                                from: sharedViewRange && candleData[sharedViewRange.start]
                                  ? new Date(candleData[sharedViewRange.start].time).getTime() / 1000
                                  : new Date(candleData[0].time).getTime() / 1000,
                                to: sharedViewRange && candleData[Math.min(sharedViewRange.end - 1, candleData.length - 1)]
                                  ? new Date(candleData[Math.min(sharedViewRange.end - 1, candleData.length - 1)].time).getTime() / 1000
                                  : new Date(candleData[candleData.length - 1].time).getTime() / 1000,
                              }}
                              candleData={candleData}
                              timeToX={(time: number) => {
                                if (!chartRef.current) return null;
                                const coordinate = chartRef.current.timeScale().timeToCoordinate(time);
                                return coordinate !== null ? coordinate : null;
                              }}
                              priceToY={(price: number) => {
                                if (!chartRef.current) return null;
                                const coordinate = chartRef.current.priceScale().priceToCoordinate(price);
                                return coordinate !== null ? coordinate : null;
                              }}
                            />

                            <FibonacciCircle
                              objects={fibonacciCircles}
                              chartWidth={chartContainerWidth}
                              chartHeight={candleHeight}
                              totalChartHeight={totalHeight}
                              timeToX={(time: number) => {
                                if (!chartRef.current) return null;
                                const coordinate = chartRef.current.timeScale().timeToCoordinate(time);
                                return coordinate !== null ? coordinate : null;
                              }}
                              priceToY={(price: number) => {
                                if (!chartRef.current) return null;
                                const coordinate = chartRef.current.priceScale().priceToCoordinate(price);
                                return coordinate !== null ? coordinate : null;
                              }}
                            />

                            <FibonacciWedge
                              objects={fibonacciWedges}
                              chartWidth={chartContainerWidth}
                              chartHeight={candleHeight}
                              totalChartHeight={totalHeight}
                              timeToX={(time: number) => {
                                if (!chartRef.current) return null;
                                const coordinate = chartRef.current.timeScale().timeToCoordinate(time);
                                return coordinate !== null ? coordinate : null;
                              }}
                              priceToY={(price: number) => {
                                if (!chartRef.current) return null;
                                const coordinate = chartRef.current.priceScale().priceToCoordinate(price);
                                return coordinate !== null ? coordinate : null;
                              }}
                            />
                          </>
                        );
                      })()}

                  {/* ✅ Drawing Tools Overlay */}
                  {chartViewMode === 'tradingview' && (() => {
                    // ✅ 전체 차트 높이 계산 (수직선 확장 옵션용)
                    const enabledIndicatorsCount = [
                      indicatorVisibilitySettings.volume,
                      indicatorVisibilitySettings.macdLine || indicatorVisibilitySettings.macdHistogram,
                      indicatorVisibilitySettings.rsi,
                      indicatorVisibilitySettings.stochasticK || indicatorVisibilitySettings.stochasticD || indicatorVisibilitySettings.stochasticSlow,
                      indicatorVisibilitySettings.cci,
                      indicatorVisibilitySettings.adx,
                      indicatorVisibilitySettings.obv,
                      indicatorVisibilitySettings.newPsychological || indicatorVisibilitySettings.investPsychological,
                      indicatorVisibilitySettings.bwi,
                      indicatorVisibilitySettings.plusDi || indicatorVisibilitySettings.minusDi,
                      indicatorVisibilitySettings.williamsR,
                      indicatorVisibilitySettings.trix,
                      indicatorVisibilitySettings.volumeOsc,
                      indicatorVisibilitySettings.vr,
                      indicatorVisibilitySettings.disparity,
                    ].filter(Boolean).length;

                    const subPanelHeight = indicatorPanelHeight;
                    const candleHeight = isChartFullscreen ? 350 : candleChartHeight;
                    const totalHeight = candleHeight + TREND_BAR_HEIGHT + (enabledIndicatorsCount * subPanelHeight);

                    return (
                      <ChartDrawingOverlay
                        width={chartContainerWidth}
                        height={candleHeight}
                        totalChartHeight={totalHeight}
                        activeTool={(activeTool === 'fibonacci' || activeTool === 'fibonacciTimezone') ? null : activeTool}
                        drawings={drawings}
                        onUpdateDrawings={updateDrawingsWithHistory}
                      timeToCoordinate={timeToCoordinate}
                      coordinateToTime={coordinateToTime}
                      priceToCoordinate={priceToCoordinate}
                      coordinateToPrice={coordinateToPrice}
                      onDrawingComplete={(drawing) => {
                        console.log('✅ [드로잉 완료] 십자선으로 전환', drawing);
                        setActiveTool('crosshair'); // ✅ One-shot 모드: 드로잉 완료 후 십자선으로 전환
                      }}
                      onDrawingClick={(drawing) => {
                        console.log('🖱️ [Drawing Click]', drawing);
                        
                        // ✅ 수직선일 경우 전용 다이얼로그 열기
                        if (drawing.type === 'verticalLine') {
                          setEditingVerticalLine(drawing);
                          setVerticalLineSettings({
                            showLine: true,
                            extended: drawing.extended || false,
                            showLabel: drawing.showLabel !== false,
                            customText: drawing.customText || '',
                            color: drawing.style?.color || '#2962ff',
                            lineStyle: drawing.style?.lineStyle || 'solid',
                            lineWidth: drawing.style?.lineWidth || 2,
                            opacity: 100,
                          });
                          setVerticalLineDialogOpen(true);
                        } else if (drawing.type === 'measure') {
                          setEditingMeasureId(drawing.id);
                          setMeasureSettings({
                            lineColor: drawing.style?.color || '#2962ff',
                            lineStyle: drawing.style?.lineStyle || 'solid',
                            lineWidth: drawing.style?.lineWidth || 2,
                            backgroundColor: drawing.style?.fillColor || '#2962ff',
                            backgroundEnabled: true,
                            backgroundOpacity: drawing.style?.fillOpacity || 0.15,
                            extendTop: false,
                            extendBottom: false,
                            labelColor: drawing.style?.color || '#2962ff',
                            labelSize: drawing.style?.fontSize || 12,
                            labelBackgroundEnabled: true,
                          });
                          setMeasureDialogOpen(true);
                        } else if (drawing.type === 'fibonacciFan' || drawing.type === 'fibonacciCircle' || drawing.type === 'fibonacciWedge') {
                          // ✅ 피보나치 도구 전용 다이얼로그 열기
                          setEditingFibonacci(drawing);
                          setFibonacciSettingsOpen(true);
                        } else {
                          setEditingDrawing(drawing);
                          setDrawingSettingsOpen(true);
                        }
                      }}
                      onDrawingDoubleClick={(drawing) => {
                        console.log('🖱️ [Drawing Double Click]', drawing);
                        
                        // ✅ 수직선일 경우 전용 다이얼로그 열기
                        if (drawing.type === 'verticalLine') {
                          setEditingVerticalLine(drawing);
                          setVerticalLineSettings({
                            showLine: true,
                            extended: drawing.extended || false,
                            showLabel: drawing.showLabel !== false,
                            customText: drawing.customText || '',
                            color: drawing.style?.color || '#2962ff',
                            lineStyle: drawing.style?.lineStyle || 'solid',
                            lineWidth: drawing.style?.lineWidth || 2,
                            opacity: 100,
                          });
                          setVerticalLineDialogOpen(true);
                        } else if (drawing.type === 'measure') {
                          setEditingMeasureId(drawing.id);
                          setMeasureSettings({
                            lineColor: drawing.style?.color || '#2962ff',
                            lineStyle: drawing.style?.lineStyle || 'solid',
                            lineWidth: drawing.style?.lineWidth || 2,
                            backgroundColor: drawing.style?.fillColor || '#2962ff',
                            backgroundEnabled: true,
                            backgroundOpacity: drawing.style?.fillOpacity || 0.15,
                            extendTop: false,
                            extendBottom: false,
                            labelColor: drawing.style?.color || '#2962ff',
                            labelSize: drawing.style?.fontSize || 12,
                            labelBackgroundEnabled: true,
                          });
                          setMeasureDialogOpen(true);
                        } else if (drawing.type === 'fibonacciFan' || drawing.type === 'fibonacciCircle' || drawing.type === 'fibonacciWedge') {
                          // ✅ 피보나치 도구 전용 다이얼로그 열기
                          setEditingFibonacci(drawing);
                          setFibonacciSettingsOpen(true);
                        } else {
                          setEditingDrawing(drawing);
                          setDrawingSettingsOpen(true);
                        }
                      }}
                      onDrawingDragStart={handleDrawingDragStart}
                      />
                    );
                  })()}

                  {/* ✅ Drawing Settings Dialog */}
                  <DrawingSettingsDialog
                    open={drawingSettingsOpen}
                    onClose={() => {
                      setDrawingSettingsOpen(false);
                      setEditingDrawing(null);
                    }}
                    drawing={editingDrawing}
                    onSave={(updatedDrawing) => {
                      updateDrawingsWithHistory(drawings.map(d => d.id === updatedDrawing.id ? updatedDrawing : d));
                      console.log('💾 [Drawing Updated]', updatedDrawing);
                    }}
                    onDelete={(drawingId) => {
                      updateDrawingsWithHistory(drawings.filter(d => d.id !== drawingId));
                      console.log('🗑️ [Drawing Deleted]', drawingId);
                    }}
                  />

                  {/* ✅ 피보나치 설정 다이얼로그 */}
                  <FibonacciDrawingSettingsDialog
                    open={fibonacciSettingsOpen}
                    onClose={() => {
                      setFibonacciSettingsOpen(false);
                      setEditingFibonacci(null);
                    }}
                    drawing={editingFibonacci}
                    onSave={(updatedDrawing) => {
                      updateDrawingsWithHistory(drawings.map(d => d.id === updatedDrawing.id ? updatedDrawing : d));
                      console.log('💾 [Fibonacci Drawing Updated]', updatedDrawing);
                    }}
                    onDelete={(drawingId) => {
                      updateDrawingsWithHistory(drawings.filter(d => d.id !== drawingId));
                      console.log('🗑️ [Fibonacci Drawing Deleted]', drawingId);
                    }}
                  />

                  {/* ✅ 수직선 설정 다이얼로그 */}
                  <VerticalLineDialog
                    open={verticalLineDialogOpen}
                    onClose={() => {
                      setVerticalLineDialogOpen(false);
                      setEditingVerticalLine(null);
                    }}
                    settings={verticalLineSettings}
                    onSave={handleVerticalLineSave}
                    onDelete={handleVerticalLineDelete}
                  />
                </Box>

                  {/* 캔들차트 하단에 추가된 보조지표 */}
                  {candleChartOverlayIndicator && (
                    <Box
                      sx={{
                        position: 'relative',
                        mt: 1,
                        height: 150,
                        border: '1px solid',
                        borderColor: 'divider',
                        borderRadius: 1,
                        bgcolor: 'background.paper',
                        overflow: 'hidden',
                      }}
                    >
                      {/* x 버튼 - 좌측 상단 */}
                      <IconButton
                        size="small"
                        onClick={() => setCandleChartOverlayIndicator(null)}
                        sx={{
                          position: 'absolute',
                          top: 4,
                          left: 4,
                          zIndex: 20,
                          bgcolor: 'rgba(0, 0, 0, 0.5)',
                          color: 'white',
                          '&:hover': {
                            bgcolor: 'rgba(0, 0, 0, 0.7)',
                          },
                          width: 24,
                          height: 24,
                        }}
                      >
                        <CloseIcon sx={{ fontSize: 16 }} />
                      </IconButton>

                      {/* 보조지표 차트 */}
                      {(() => {
                        const chartRefMap: Record<string, React.RefObject<IndicatorChartHandle>> = {
                          volume: volumeChartRef,
                          newPsychological: newPsychologicalChartRef,
                          investPsychological: investPsychologicalChartRef,
                          macd: macdChartRef,
                          cci: cciChartRef,
                          adx: adxChartRef,
                          bwi: bwiChartRef,
                          dmi: dmiChartRef,
                          obv: obvChartRef,
                          rsi: rsiChartRef,
                          stochastic: stochasticChartRef,
                          williamsR: williamsRChartRef,
                          trix: trixChartRef,
                          volumeOsc: volumeOscChartRef,
                          vr: vrChartRef,
                          disparity: disparityChartRef,
                        };

                        const titleMap: Record<string, string> = {
                          volume: '거래량',
                          newPsychological: '신심리도',
                          investPsychological: '투자심리도',
                          macd: 'MACD',
                          cci: 'CCI',
                          adx: 'ADX',
                          bwi: 'BWI',
                          dmi: 'DMI',
                          obv: 'OBV',
                          rsi: 'RSI',
                          stochastic: 'Stochastic Slow',
                          williamsR: 'Williams %R',
                          trix: 'TRIX',
                          volumeOsc: 'VolumeOSC',
                          vr: 'VR',
                          disparity: '이격도',
                        };

                        const handleCrosshairMove = (time: any) => {
                          if (time) {
                            const timestamp = (time as number) * 1000;
                            const matchingCandle = candleData.find(c => {
                              const candleTime = new Date(c.time).getTime();
                              return Math.abs(candleTime - timestamp) < 60000;
                            });
                            setSharedHoverTime(matchingCandle ? matchingCandle.time : null);
                          } else {
                            setSharedHoverTime(null);
                          }
                        };

                        const handleIndicatorVisibleRangeChange = (from: number, to: number) => {
                          // ✅ 오버레이 보조지표 스크롤 → 캔들차트 및 다른 모든 보조지표 동기화
                          if (isZoomingRef.current || isRestoringViewRef.current || isRestoringViewRangeRef.current || isSyncingVisibleRangeRef.current) {
                            return;
                          }

                          // ✅ 동기화 시작
                          isSyncingVisibleRangeRef.current = true;

                          // ✅ requestAnimationFrame으로 묶어서 한 번에 처리 (깜빡임 방지)
                          requestAnimationFrame(() => {
                            try {
                              // ✅ 오버레이 보조지표에서 logical range 가져오기
                              const overlayChart = chartRefMap[candleChartOverlayIndicator]?.current;
                              const logicalRange = overlayChart?.getVisibleLogicalRange?.();
                              
                              if (logicalRange) {
                                // ✅ logical range로 동기화 (스케일 유지)
                                if (chartRef.current && chartRef.current.setVisibleLogicalRange) {
                                  chartRef.current.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                }

                                // 모든 보조지표 차트 동기화
                                volumeChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                macdChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                rsiChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                stochasticChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                cciChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                adxChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                obvChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                newPsychologicalChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                investPsychologicalChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                bwiChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                dmiChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                williamsRChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                trixChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                volumeOscChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                vrChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                                disparityChartRef.current?.setVisibleLogicalRange(logicalRange.from, logicalRange.to);
                              }
                            } catch (e) {
                              console.error('❌ 오버레이 보조지표 스크롤 동기화 실패:', e);
                            } finally {
                              // ✅ 동기화 완료 (빠른 해제로 깜빡임 방지)
                              setTimeout(() => {
                                isSyncingVisibleRangeRef.current = false;
                              }, 10);
                            }
                          });
                        };

                        return (
                          <IndicatorChart
                            ref={chartRefMap[candleChartOverlayIndicator]}
                            type={candleChartOverlayIndicator as any}
                            data={indicatorChartData}
                            height={150}
                            hideTitle={true}
                            externalHoverTime={sharedHoverTime}
                            hideNativeCrosshair={false}
                            hideTimeScale={false}
                            colorVersion={colorVersion}
                            settingsVersion={settingsVersion}
                            ichimokuEnabled={indicatorVisibilitySettings.ichimokuSenkouA || indicatorVisibilitySettings.ichimokuSenkouB}
                            onCrosshairMove={(time) => {
                              syncCrosshairToOtherCharts(time, candleChartOverlayIndicator);
                              // 캔들차트에도 직접 전달
                              if (chartRef.current && time) {
                                try {
                                  chartRef.current.setCrosshairPosition(time);
                                } catch (e) {
                                  // setCrosshairPosition이 없을 수 있음
                                }
                              }
                            }}
                            onVisibleRangeChange={handleIndicatorVisibleRangeChange}
                            initialVisibleRange={initialVisibleRange || undefined}
                          />
                        );
                      })()}
                    </Box>
                  )}
                </CandleChartDropZone>
              </Box>

              {/* ✅ 기존 보조지표 차트 영역 제거됨 - 캔들차트 통합 보조지표만 사용 */}
            </Box>
          ) : (
            <Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 20 }}>
              데이터가 없습니다.
            </Typography>
          )}
          {/* ✅ 기존 보조지표 제거로 DragOverlay 단순화 */}
          <DragOverlay>{null}</DragOverlay>
        </Paper>
      </DndContext>


      {/* 하단 고정 매매 신호 패널 */}
      {!isFullscreen && hasInitialData && chartData.length > 0 && (
        <Box
          sx={{
            position: 'fixed',
            bottom: 0,
            left: 0,
            right: 0,
            zIndex: 1200,
            transform: signalPanelOpen ? 'translateY(0)' : 'translateY(calc(100% - 36px))',
            transition: 'transform 0.3s ease-in-out',
            boxShadow: '0 -4px 20px rgba(0, 0, 0, 0.15)',
          }}
        >
          <Paper 
            elevation={8} 
            sx={{ 
              borderRadius: '16px 16px 0 0',
              bgcolor: theme.palette.mode === 'dark' ? '#1a1b2e' : '#fff',
              maxHeight: '50vh',
              display: 'flex',
              flexDirection: 'column',
            }}
          >
            {/* 패널 헤더 - 펼침/닫힘 버튼 */}
            <Box
              sx={{
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                position: 'relative',
                p: 0.5,
                borderBottom: signalPanelOpen ? `1px solid ${theme.palette.divider}` : 'none',
                bgcolor: theme.palette.mode === 'dark' ? '#252641' : '#f5f5f5',
                borderRadius: '16px 16px 0 0',
                cursor: 'pointer',
                '&:hover': {
                  bgcolor: theme.palette.mode === 'dark' ? '#2d2e4a' : '#eeeeee',
                },
              }}
              onClick={() => setSignalPanelOpen(!signalPanelOpen)}
            >
              {/* 중앙 버튼 */}
              <IconButton 
                size="small"
                sx={{
                  transform: signalPanelOpen ? 'rotate(0deg)' : 'rotate(180deg)',
                  transition: 'transform 0.3s',
                  p: 0.5,
                }}
              >
                <Typography sx={{ fontSize: '18px' }}>
                  ▼
                </Typography>
              </IconButton>
              
              {/* 타이틀 및 정보 - 우측 배치 */}
              <Box 
                sx={{ 
                  position: 'absolute',
                  right: 16,
                  display: 'flex', 
                  alignItems: 'center', 
                  gap: 0.5,
                }}
              >
                <Typography variant="caption" sx={{ fontSize: '12px', fontWeight: 500 }}>
                  📋 매매 신호 상세
                </Typography>
                {loading && <Chip label="갱신 중" size="small" color="primary" sx={{ height: 18, fontSize: '11px' }} />}
                <Chip 
                  label={`${signalTableData.length}건`} 
                  size="small" 
                  color="default"
                  sx={{ fontWeight: 600, height: 18, fontSize: '11px' }}
                />
              </Box>
            </Box>

            {/* 패널 내용 */}
            {signalPanelOpen && (
              <Box 
                sx={{ 
                  p: 2,
                  overflowY: 'auto',
                  maxHeight: 'calc(50vh - 36px)',
                  opacity: loading ? 0.7 : 1, 
                  transition: 'opacity 0.3s',
                  position: 'relative',
                }}
              >
                {loading && (
                  <Box sx={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 1 }}>
                    <LinearProgress />
                  </Box>
                )}
                <MemoizedSignalTable
                  data={signalTableData as any[]}
                  interval={timeInterval}
                  onRowClick={handleSignalRowClick}
                />
              </Box>
            )}
          </Paper>
        </Box>
      )}

      {/* 차트 설정 다이얼로그 */}
      <ChartSettingsDialog
        open={settingsDialogOpen}
        onClose={() => setSettingsDialogOpen(false)}
      />

      {/* AI 테스트 결과 팝업 */}
      <Dialog
        open={aiTestPopupOpen}
        onClose={() => setAiTestPopupOpen(false)}
        maxWidth="md"
        fullWidth
        hideBackdrop={true}
        disableScrollLock={true}
        PaperComponent={DraggablePaper}
        sx={{
          pointerEvents: 'none',
          '& .MuiDialog-container': {
            pointerEvents: 'none',
            alignItems: 'flex-start',
          },
        }}
        PaperProps={{
          sx: {
            bgcolor: theme.palette.mode === 'dark' ? '#1a1a2e' : '#fff',
            borderRadius: 2,
            maxHeight: '80vh',
            pointerEvents: 'auto',
            boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
            border: theme.palette.mode === 'dark' ? '2px solid rgba(80, 100, 160, 0.5)' : '1px solid #ddd',
          }
        }}
      >
        <DialogTitle sx={{
          display: 'flex',
          alignItems: 'center',
          gap: 1,
          bgcolor: theme.palette.mode === 'dark' ? '#2d2d44' : '#f5f5f5',
          borderBottom: 1,
          borderColor: 'divider',
        }}>
          <Psychology sx={{ color: '#9c27b0' }} />
          <Typography variant="h6" sx={{ flexGrow: 1 }}>
            AI 차트 분석 결과
          </Typography>
          <Chip
            label={`${symbol || 'Unknown'}`}
            size="small"
            sx={{ bgcolor: 'rgba(156, 39, 176, 0.2)', color: '#9c27b0' }}
          />
          <IconButton onClick={() => setAiTestPopupOpen(false)} size="small">
            <CloseIcon />
          </IconButton>
        </DialogTitle>
        <DialogContent sx={{ p: 2, overflowY: 'auto', maxHeight: 'calc(80vh - 140px)' }}>
          {aiTestLoading ? (
            <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', py: 6, gap: 2 }}>
              <CircularProgress size={48} sx={{ color: '#9c27b0' }} />
              <Typography variant="body1" color="text.secondary">
                AI가 차트를 분석하고 있습니다...
              </Typography>
            </Box>
          ) : aiTestError ? (
            <Box sx={{ p: 3, bgcolor: 'rgba(239, 68, 68, 0.1)', borderRadius: 2, border: '1px solid rgba(239, 68, 68, 0.3)' }}>
              <Typography variant="body1" color="error" fontWeight="bold" gutterBottom>
                분석 오류
              </Typography>
              <Typography variant="body2" color="error">
                {aiTestError}
              </Typography>
            </Box>
          ) : (
            <Box sx={{
              p: 2,
              bgcolor: theme.palette.mode === 'dark' ? 'rgba(156, 39, 176, 0.1)' : 'rgba(156, 39, 176, 0.05)',
              borderRadius: 2,
              border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(156, 39, 176, 0.3)' : 'rgba(156, 39, 176, 0.2)'}`,
            }}>
              <Typography variant="body1" sx={{ whiteSpace: 'pre-wrap', lineHeight: 1.8, color: theme.palette.mode === 'dark' ? '#e0e0e0' : '#333' }}>
                {aiTestResult || '분석 결과가 없습니다.'}
              </Typography>
            </Box>
          )}
        </DialogContent>
        <DialogActions sx={{ p: 2, bgcolor: theme.palette.mode === 'dark' ? '#2d2d44' : '#f5f5f5', borderTop: 1, borderColor: 'divider' }}>
          <Button onClick={handleAiTest} disabled={aiTestLoading} startIcon={<Psychology />} variant="outlined" sx={{ borderColor: '#9c27b0', color: '#9c27b0' }}>
            다시 분석
          </Button>
          <Button onClick={() => setAiTestPopupOpen(false)} variant="contained" sx={{ bgcolor: '#9c27b0', '&:hover': { bgcolor: '#7b1fa2' } }}>
            닫기
          </Button>
        </DialogActions>
      </Dialog>

      {/* 차트 상세보기 팝업 */}
      <ChartDetailPopup
        open={chartDetailOpen}
        onClose={() => setChartDetailOpen(false)}
        data={chartData as any[]}
        centerIndex={chartDetailIndex}
        chartType={chartDetailType}
        signal={chartDetailSignal}
        interval={timeInterval}
        range={5}
      />

      {/* 보조지표 설정 팝업 - 업비트와 동일한 컴포넌트 사용 */}
      <IndicatorSettingsPopup
        open={settingsPopupOpen}
        onClose={() => setSettingsPopupOpen(false)}
        indicatorType={selectedIndicator}
      />

      {/* MA 설정 다이얼로그 */}
      <MASettingsDialog
        open={maSettingsOpen}
        onClose={() => setMaSettingsOpen(false)}
      />

      {/* Ichimoku 설정 다이얼로그 */}
      <IchimokuSettingsDialog
        open={ichimokuSettingsOpen}
        onClose={() => setIchimokuSettingsOpen(false)}
      />

      {/* Bollinger Bands 설정 다이얼로그 */}
      <BollingerSettingsDialog
        open={bollingerSettingsOpen}
        onClose={() => setBollingerSettingsOpen(false)}
      />

      {/* 보조지표 설정 다이얼로그 */}
      <IndicatorParamsSettingsDialog
        open={indicatorSettingsDialogOpen}
        onClose={() => setIndicatorSettingsDialogOpen(false)}
      />

      {/* ✅ 피보나치 타임존 설정 다이얼로그 */}
      <FibonacciTimezoneDialog
        open={fibDialogOpen}
        onClose={() => {
          console.log('🚪 [피보나치 설정 대화상자] 닫기');
          setFibDialogOpen(false);
          setEditingFibId(null);
          // ✅ drawing 상태 초기화 및 crosshair로 전환 (기존 피보나치 유지)
          setIsDrawingFib(false);
          setFibStartTime(null);
          setFibStartIndex(null);
          if (activeTool === 'fibonacci' || activeTool === 'fibonacciTimezone') {
            setActiveTool('crosshair');
            console.log('✅ [피보나치 설정 대화상자] activeTool을 crosshair로 변경');
          }
        }}
        settings={fibSettings}
        onSave={handleFibonacciSave}
        onDelete={handleFibonacciDelete}
        isEditingIndividual={editingFibId !== null}
      />

      {/* ✅ 피보나치 툴바 */}
      {selectedFibId && (() => {
        const selectedFib = fibonacciTimezones.find(f => f.id === selectedFibId);
        return selectedFib ? (
          <FibonacciToolbar
            open={fibToolbarOpen}
            anchorPosition={fibToolbarPosition}
            settings={selectedFib.settings}
            onClose={() => {
              setFibToolbarOpen(false);
              setSelectedFibId(null);
            }}
            onColorChange={handleFibToolbarColorChange}
            onLineWidthChange={handleFibToolbarLineWidthChange}
            onLineStyleChange={handleFibToolbarLineStyleChange}
            onSettingsClick={() => {
              setEditingFibId(selectedFibId);
              setFibDialogOpen(true);
              setFibToolbarOpen(false);
            }}
            onDelete={handleFibToolbarDelete}
            onVisibilityToggle={() => setIsFibVisible(!isFibVisible)}
            isVisible={isFibVisible}
          />
        ) : null;
      })()}

      {/* ✅ 기간 측정 도구 설정 다이얼로그 */}
      <MeasureSettingsDialog
        open={measureDialogOpen}
        onClose={() => {
          console.log('🚪 [기간 측정 설정 대화상자] 닫기');
          setMeasureDialogOpen(false);
          setEditingMeasureId(null);
        }}
        settings={measureSettings}
        onSave={handleMeasureSave}
        onDelete={editingMeasureId ? () => {
          updateDrawingsWithHistory(drawings.filter(d => d.id !== editingMeasureId));
          setMeasureDialogOpen(false);
          setEditingMeasureId(null);
          console.log('🗑️ [기간 측정 삭제]', editingMeasureId);
        } : undefined}
        isEditingIndividual={editingMeasureId !== null}
      />

      {/* MA 메뉴 */}
      <Menu
        anchorEl={maMenuAnchor}
        open={Boolean(maMenuAnchor)}
        onClose={() => setMaMenuAnchor(null)}
        anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
        transformOrigin={{ vertical: 'top', horizontal: 'left' }}
        slotProps={{
          paper: {
            sx: {
              maxHeight: 400,
              overflowY: 'auto',
            }
          }
        }}
      >
        {/* maLines 기반 메뉴 */}
        {indicatorVisibilitySettings.maLines?.map((ma: any, index: number) => (
          <MenuItem
            key={ma.id}
            onClick={() => {
              const newLines = [...indicatorVisibilitySettings.maLines];
              newLines[index] = { ...newLines[index], enabled: !newLines[index].enabled };
              updateIndicatorSettings({ maLines: newLines });
            }}
            sx={{ py: 0.5 }}
          >
            <Checkbox
              checked={ma.enabled}
              size="small"
              sx={{
                p: 0.5,
                color: ma.color,
                '&.Mui-checked': { color: ma.color }
              }}
            />
            <ListItemText
              primary={`${ma.id} (${ma.period})`}
              primaryTypographyProps={{ fontSize: '13px' }}
            />
            <Box
              sx={{
                width: 16,
                height: ma.width * 2,
                backgroundColor: ma.color,
                borderRadius: 0.5,
                ml: 1
              }}
            />
          </MenuItem>
        ))}
        <Divider sx={{ my: 0.5 }} />
        <MenuItem
          onClick={() => {
            setMaMenuAnchor(null);
            setMaSettingsOpen(true);
          }}
          sx={{ py: 1 }}
        >
          <ListItemText
            primary="⚙️ 이동평균선 설정..."
            primaryTypographyProps={{ fontSize: '13px', fontWeight: 'bold' }}
          />
        </MenuItem>
      </Menu>

      {/* Ichimoku 메뉴 */}
      <Menu
        anchorEl={ichimokuMenuAnchor}
        open={Boolean(ichimokuMenuAnchor)}
        onClose={() => setIchimokuMenuAnchor(null)}
        PaperProps={{ sx: { minWidth: 150 } }}
        slotProps={{
          paper: {
            sx: {
              maxHeight: 400,
              overflowY: 'auto',
            }
          }
        }}
      >
        <MenuItem onClick={(e) => { e.stopPropagation(); updateIndicatorSettings({ ichimokuTenkan: !indicatorVisibilitySettings.ichimokuTenkan }); }} sx={{ py: 0.5 }}>
          <Checkbox
            checked={indicatorVisibilitySettings.ichimokuTenkan}
            size="small"
            onClick={(e) => {
              e.stopPropagation();
              updateIndicatorSettings({ ichimokuTenkan: !indicatorVisibilitySettings.ichimokuTenkan });
            }}
            sx={{ p: 0.5, color: colorSettings.ichimokuTenkan, '&.Mui-checked': { color: colorSettings.ichimokuTenkan } }}
          />
          <ListItemText primary="전환선 (9)" primaryTypographyProps={{ fontSize: '13px' }} />
          <Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: colorSettings.ichimokuTenkan, ml: 1 }} />
        </MenuItem>
        <MenuItem onClick={(e) => { e.stopPropagation(); updateIndicatorSettings({ ichimokuKijun: !indicatorVisibilitySettings.ichimokuKijun }); }} sx={{ py: 0.5 }}>
          <Checkbox
            checked={indicatorVisibilitySettings.ichimokuKijun}
            size="small"
            onClick={(e) => {
              e.stopPropagation();
              updateIndicatorSettings({ ichimokuKijun: !indicatorVisibilitySettings.ichimokuKijun });
            }}
            sx={{ p: 0.5, color: colorSettings.ichimokuKijun, '&.Mui-checked': { color: colorSettings.ichimokuKijun } }}
          />
          <ListItemText primary="기준선 (26)" primaryTypographyProps={{ fontSize: '13px' }} />
          <Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: colorSettings.ichimokuKijun, ml: 1 }} />
        </MenuItem>
        <MenuItem onClick={(e) => { e.stopPropagation(); updateIndicatorSettings({ ichimokuChikou: !indicatorVisibilitySettings.ichimokuChikou }); }} sx={{ py: 0.5 }}>
          <Checkbox
            checked={indicatorVisibilitySettings.ichimokuChikou}
            size="small"
            onClick={(e) => {
              e.stopPropagation();
              updateIndicatorSettings({ ichimokuChikou: !indicatorVisibilitySettings.ichimokuChikou });
            }}
            sx={{ p: 0.5, color: colorSettings.ichimokuChikou, '&.Mui-checked': { color: colorSettings.ichimokuChikou } }}
          />
          <ListItemText primary="후행스팬 (26)" primaryTypographyProps={{ fontSize: '13px' }} />
          <Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: colorSettings.ichimokuChikou, ml: 1 }} />
        </MenuItem>
        <MenuItem onClick={(e) => {
          e.stopPropagation();
          console.log('✅ [선행스팬1 MenuItem 클릭] 현재:', indicatorVisibilitySettings.ichimokuSenkouA, '→ 변경:', !indicatorVisibilitySettings.ichimokuSenkouA);
          updateIndicatorSettings({ ichimokuSenkouA: !indicatorVisibilitySettings.ichimokuSenkouA });
        }} sx={{ py: 0.5 }}>
          <Checkbox
            checked={indicatorVisibilitySettings.ichimokuSenkouA}
            size="small"
            onClick={(e) => {
              e.stopPropagation();
              console.log('✅ [선행스팬1 Checkbox 클릭] 현재:', indicatorVisibilitySettings.ichimokuSenkouA, '→ 변경:', !indicatorVisibilitySettings.ichimokuSenkouA);
              updateIndicatorSettings({ ichimokuSenkouA: !indicatorVisibilitySettings.ichimokuSenkouA });
            }}
            sx={{ p: 0.5, color: colorSettings.ichimokuSenkouA, '&.Mui-checked': { color: colorSettings.ichimokuSenkouA } }}
          />
          <ListItemText primary="선행스팬1 (26)" primaryTypographyProps={{ fontSize: '13px' }} />
          <Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: colorSettings.ichimokuSenkouA, ml: 1 }} />
        </MenuItem>
        <MenuItem onClick={(e) => {
          e.stopPropagation();
          console.log('✅ [선행스팬2 MenuItem 클릭] 현재:', indicatorVisibilitySettings.ichimokuSenkouB, '→ 변경:', !indicatorVisibilitySettings.ichimokuSenkouB);
          updateIndicatorSettings({ ichimokuSenkouB: !indicatorVisibilitySettings.ichimokuSenkouB });
        }} sx={{ py: 0.5 }}>
          <Checkbox
            checked={indicatorVisibilitySettings.ichimokuSenkouB}
            size="small"
            onClick={(e) => {
              e.stopPropagation();
              console.log('✅ [선행스팬2 Checkbox 클릭] 현재:', indicatorVisibilitySettings.ichimokuSenkouB, '→ 변경:', !indicatorVisibilitySettings.ichimokuSenkouB);
              updateIndicatorSettings({ ichimokuSenkouB: !indicatorVisibilitySettings.ichimokuSenkouB });
            }}
            sx={{ p: 0.5, color: colorSettings.ichimokuSenkouB, '&.Mui-checked': { color: colorSettings.ichimokuSenkouB } }}
          />
          <ListItemText primary="선행스팬2 (52)" primaryTypographyProps={{ fontSize: '13px' }} />
          <Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: colorSettings.ichimokuSenkouB, ml: 1 }} />
        </MenuItem>
        <Divider sx={{ my: 0.5 }} />
        <MenuItem
          onClick={() => {
            setIchimokuMenuAnchor(null);
            setIchimokuSettingsOpen(true);
          }}
          sx={{ py: 1 }}
        >
          <ListItemText
            primary="⚙️ 일목균형표 설정..."
            primaryTypographyProps={{ fontSize: '13px', fontWeight: 'bold' }}
          />
        </MenuItem>
      </Menu>

      {/* Bollinger Bands 메뉴 */}
      <Menu
        anchorEl={bollingerMenuAnchor}
        open={Boolean(bollingerMenuAnchor)}
        onClose={() => setBollingerMenuAnchor(null)}
        PaperProps={{ sx: { minWidth: 150 } }}
      >
        <MenuItem onClick={(e) => { e.stopPropagation(); updateIndicatorSettings({ bollingerUpper: !indicatorVisibilitySettings.bollingerUpper }); }} sx={{ py: 0.5 }}>
          <Checkbox
            checked={indicatorVisibilitySettings.bollingerUpper}
            size="small"
            onClick={(e) => {
              e.stopPropagation();
              updateIndicatorSettings({ bollingerUpper: !indicatorVisibilitySettings.bollingerUpper });
            }}
            sx={{ p: 0.5, color: colorSettings.bollingerUpperColor, '&.Mui-checked': { color: colorSettings.bollingerUpperColor } }}
          />
          <ListItemText primary="상한선 (Upper)" primaryTypographyProps={{ fontSize: '13px' }} />
          <Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: colorSettings.bollingerUpperColor, ml: 1 }} />
        </MenuItem>
        <MenuItem onClick={(e) => { e.stopPropagation(); updateIndicatorSettings({ bollingerMiddle: !indicatorVisibilitySettings.bollingerMiddle }); }} sx={{ py: 0.5 }}>
          <Checkbox
            checked={indicatorVisibilitySettings.bollingerMiddle}
            size="small"
            onClick={(e) => {
              e.stopPropagation();
              updateIndicatorSettings({ bollingerMiddle: !indicatorVisibilitySettings.bollingerMiddle });
            }}
            sx={{ p: 0.5, color: colorSettings.bollingerMiddleColor, '&.Mui-checked': { color: colorSettings.bollingerMiddleColor } }}
          />
          <ListItemText primary="중간선 (Middle)" primaryTypographyProps={{ fontSize: '13px' }} />
          <Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: colorSettings.bollingerMiddleColor, ml: 1 }} />
        </MenuItem>
        <MenuItem onClick={(e) => { e.stopPropagation(); updateIndicatorSettings({ bollingerLower: !indicatorVisibilitySettings.bollingerLower }); }} sx={{ py: 0.5 }}>
          <Checkbox
            checked={indicatorVisibilitySettings.bollingerLower}
            size="small"
            onClick={(e) => {
              e.stopPropagation();
              updateIndicatorSettings({ bollingerLower: !indicatorVisibilitySettings.bollingerLower });
            }}
            sx={{ p: 0.5, color: colorSettings.bollingerLowerColor, '&.Mui-checked': { color: colorSettings.bollingerLowerColor } }}
          />
          <ListItemText primary="하한선 (Lower)" primaryTypographyProps={{ fontSize: '13px' }} />
          <Box sx={{ width: 12, height: 12, borderRadius: '50%', bgcolor: colorSettings.bollingerLowerColor, ml: 1 }} />
        </MenuItem>
        <Divider sx={{ my: 0.5 }} />
        <MenuItem
          onClick={() => {
            setBollingerMenuAnchor(null);
            setBollingerSettingsOpen(true);
          }}
          sx={{ py: 1 }}
        >
          <ListItemText
            primary="⚙️ 볼린저 밴드 설정..."
            primaryTypographyProps={{ fontSize: '13px', fontWeight: 'bold' }}
          />
        </MenuItem>
      </Menu>

      {/* ✅ 전략 체크 팝업 */}
      <Dialog
        open={strategyCheckPopupOpen}
        onClose={handleCloseStrategyCheck}
        maxWidth="md"
        fullWidth
        hideBackdrop={true}
        disableScrollLock={true}
        PaperComponent={DraggablePaper}
        sx={{
          pointerEvents: 'none',
          '& .MuiDialog-container': {
            pointerEvents: 'none',
            alignItems: 'flex-start',
          },
        }}
        PaperProps={{
          sx: {
            pointerEvents: 'auto',
            boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
            border: theme.palette.mode === 'dark' ? '2px solid rgba(80, 100, 160, 0.5)' : '1px solid #ddd',
          }
        }}
      >
        <DialogTitle>
          <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <Typography variant="h6">전략 조건 확인</Typography>
            <IconButton onClick={handleCloseStrategyCheck} size="small">
              <CloseIcon />
            </IconButton>
          </Box>
        </DialogTitle>
        <DialogContent dividers>
          {!uptrendStrategyId && !downtrendStrategyId ? (
            <Alert severity="info">선택된 전략이 없습니다. 정배열 또는 역배열 전략을 선택해주세요.</Alert>
          ) : (
            <Box>
              {uptrendStrategyId && (
                <Box sx={{ mb: 3 }}>
                  <Typography variant="subtitle1" sx={{ fontWeight: 'bold', mb: 1, color: 'success.main' }}>
                    🟢 정배열 전략
                  </Typography>
                  {strategyRules.find(r => r.id === uptrendStrategyId) ? (
                    <Box sx={{ pl: 2 }}>
                      <Typography variant="body2" sx={{ mb: 1 }}>
                        <strong>전략명:</strong> {strategyRules.find(r => r.id === uptrendStrategyId)?.name}
                      </Typography>
                      <Typography variant="body2" sx={{ mb: 1 }}>
                        <strong>설명:</strong> {strategyRules.find(r => r.id === uptrendStrategyId)?.description || '설명 없음'}
                      </Typography>
                      <Typography variant="body2" sx={{ mb: 1 }}>
                        <strong>사용 중:</strong> {strategyRules.find(r => r.id === uptrendStrategyId)?.enabled ? '✅ 활성화' : '❌ 비활성화'}
                      </Typography>
                      <Typography variant="body2" sx={{ mb: 1 }}>
                        <strong>전략 타입:</strong> {strategyRules.find(r => r.id === uptrendStrategyId)?.trendType || 'N/A'}
                      </Typography>
                    </Box>
                  ) : (
                    <Typography variant="body2" color="error">전략 정보를 찾을 수 없습니다.</Typography>
                  )}
                </Box>
              )}

              {downtrendStrategyId && (
                <Box>
                  <Typography variant="subtitle1" sx={{ fontWeight: 'bold', mb: 1, color: 'error.main' }}>
                    🔴 역배열 전략
                  </Typography>
                  {strategyRules.find(r => r.id === downtrendStrategyId) ? (
                    <Box sx={{ pl: 2 }}>
                      <Typography variant="body2" sx={{ mb: 1 }}>
                        <strong>전략명:</strong> {strategyRules.find(r => r.id === downtrendStrategyId)?.name}
                      </Typography>
                      <Typography variant="body2" sx={{ mb: 1 }}>
                        <strong>설명:</strong> {strategyRules.find(r => r.id === downtrendStrategyId)?.description || '설명 없음'}
                      </Typography>
                      <Typography variant="body2" sx={{ mb: 1 }}>
                        <strong>사용 중:</strong> {strategyRules.find(r => r.id === downtrendStrategyId)?.enabled ? '✅ 활성화' : '❌ 비활성화'}
                      </Typography>
                      <Typography variant="body2" sx={{ mb: 1 }}>
                        <strong>전략 타입:</strong> {strategyRules.find(r => r.id === downtrendStrategyId)?.trendType || 'N/A'}
                      </Typography>
                    </Box>
                  ) : (
                    <Typography variant="body2" color="error">전략 정보를 찾을 수 없습니다.</Typography>
                  )}
                </Box>
              )}

              {backtestResult && (
                <Box sx={{ mt: 3, p: 2, bgcolor: 'action.hover', borderRadius: 1 }}>
                  <Typography variant="subtitle2" sx={{ fontWeight: 'bold', mb: 1 }}>
                    📊 시그널 통계
                  </Typography>
                  <Grid container spacing={2}>
                    <Grid item xs={6}>
                      <Typography variant="body2">
                        <strong>매수 시그널:</strong> {backtestResult.buyCount || 0}개
                      </Typography>
                    </Grid>
                    <Grid item xs={6}>
                      <Typography variant="body2">
                        <strong>매도 시그널:</strong> {backtestResult.sellCount || 0}개
                      </Typography>
                    </Grid>
                    <Grid item xs={6}>
                      <Typography variant="body2">
                        <strong>총 거래:</strong> {backtestResult.totalTrades || 0}회
                      </Typography>
                    </Grid>
                  </Grid>
                </Box>
              )}
            </Box>
          )}
        </DialogContent>
        <DialogActions>
          <Button onClick={handleCloseStrategyCheck} variant="outlined">
            닫기
          </Button>
        </DialogActions>
      </Dialog>

      {/* 종목 선택 패널 (좌측 하단 FAB) */}
      <CryptoListPanel
        currentSymbol={symbol}
        onSelectSymbol={(newSymbol) => {
          setSymbol(newSymbol);
        }}
      />

      {/* 실시간 AI 분석 버튼 */}
      {!realTimeAnalysisPanelOpen && (
        <MuiTooltip title="실시간 AI 분석" placement="left">
          <IconButton
            onClick={() => {
              setRealTimeAnalysisPanelOpen(true);
              setRealTimeAnalysisEnabled(true);
            }}
            sx={{
              position: 'fixed',
              bottom: 128,
              right: 24,
              zIndex: 1200,
              bgcolor: realTimeAnalysisEnabled ? 'success.main' : 'background.paper',
              color: realTimeAnalysisEnabled ? 'white' : 'text.secondary',
              boxShadow: 3,
              animation: realTimeAnalysisEnabled ? 'pulse 2s infinite' : 'none',
              '@keyframes pulse': {
                '0%, 100%': { opacity: 1 },
                '50%': { opacity: 0.7 }
              },
              '&:hover': {
                bgcolor: realTimeAnalysisEnabled ? 'success.dark' : 'action.hover',
                transform: 'scale(1.1)',
              },
              transition: 'transform 0.2s',
            }}
          >
            <AnalyticsIcon sx={{ fontSize: 28 }} />
          </IconButton>
        </MuiTooltip>
      )}

      {/* AI 채팅 버튼 */}
      {!aiChatPanelOpen && (
        <MuiTooltip title="AI 채팅" placement="left">
          <IconButton
            onClick={() => setAiChatPanelOpen(true)}
            sx={{
              position: 'fixed',
              bottom: 64,
              right: 24,
              zIndex: 1200,
              color: 'text.secondary',
              '&:hover': {
                color: 'primary.main',
                transform: 'scale(1.1)',
                transition: 'transform 0.2s ease',
                bgcolor: 'transparent',
              },
            }}
          >
            <AIChatIcon sx={{ fontSize: 28 }} />
          </IconButton>
        </MuiTooltip>
      )}

      {/* AI 채팅 패널 */}
      <AIChatPanel
        open={aiChatPanelOpen}
        onClose={() => setAiChatPanelOpen(false)}
        symbol={symbol}
        timeInterval={timeInterval}
        onUICommand={handleUICommand}
        dateRangeStart={visibleDateRange.start || undefined}
        dateRangeEnd={visibleDateRange.end || undefined}
      />

      {/* 실시간 AI 분석 패널 */}
      <RealTimeAnalysisPanel
        ref={realTimeAnalysisRef}
        open={realTimeAnalysisPanelOpen}
        onClose={() => {
          setRealTimeAnalysisPanelOpen(false);
          setRealTimeAnalysisEnabled(false);
        }}
        symbol={symbol}
        currentPrice={statistics?.currentPrice || null}
        onAnalyze={executeRealTimeAnalysis}
        autoAnalyze={realTimeAnalysisEnabled}
        nextUpdateTime={nextAnalysisTime}
        timeInterval={timeInterval}
      />

      {/* 실시간 호가 Drawer (좌측) - AppBar 아래에서 시작 */}
      <Drawer
        anchor="left"
        open={orderbookPanelOpen}
        onClose={() => setOrderbookPanelOpen(false)}
        variant="temporary"
        ModalProps={{
          keepMounted: false,
          sx: {
            top: '64px',
            '& .MuiBackdrop-root': { top: '64px' },
          },
        }}
        PaperProps={{
          style: {
            width: 360,
            minWidth: 360,
            maxWidth: 360,
            overflowX: 'hidden',
          },
        }}
        sx={{
          '& .MuiDrawer-paper': {
            width: 360,
            minWidth: 360,
            maxWidth: 360,
            boxSizing: 'border-box',
            top: '64px',
            height: 'calc(100vh - 64px)',
            bgcolor: 'background.paper',
            color: 'text.primary',
            borderRight: 1,
            borderColor: 'divider',
            overflow: 'hidden',
            display: 'flex',
            flexDirection: 'column',
          },
        }}
      >
        {/* 드로어 헤더 */}
        <Box sx={{
          px: 2,
          py: 1.5,
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'space-between',
          bgcolor: 'background.default',
          borderBottom: 1,
          borderColor: 'divider',
          flexShrink: 0,
        }}>
          <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
            <BarChartIcon sx={{ color: 'primary.main', fontSize: 20 }} />
            <Typography variant="subtitle1" fontWeight={700}>실시간 호가</Typography>
          </Box>
          <IconButton size="small" onClick={() => setOrderbookPanelOpen(false)}>
            <ChevronRightIcon sx={{ transform: 'rotate(180deg)' }} />
          </IconButton>
        </Box>
        {/* 종목 목록 */}
        <Box sx={{ flex: 1, overflow: 'hidden' }}>
          <MarketList
            tickers={marketTickers}
            selectedMarket={symbol}
            onSelectMarket={(market) => {
              setSymbol(market);
              localStorage.setItem(STORAGE_KEYS.SYMBOL, market);
              setOrderbookPanelOpen(false);
            }}
            wsStatus={marketWsStatus}
          />
        </Box>
      </Drawer>

      {/* 실시간 호가 패널 숨기기 버튼 (패널 열린 상태 - 패널 우측 중간) */}
      {orderbookPanelOpen && (
        <MuiTooltip title="패널 닫기" placement="right">
          <IconButton
            onClick={() => setOrderbookPanelOpen(false)}
            sx={{
              position: 'fixed',
              left: 368,
              top: '50%',
              transform: 'translateY(-50%)',
              zIndex: 1400,
              bgcolor: '#1a1f2e',
              color: '#26a69a',
              width: 24,
              height: 48,
              borderRadius: '0 6px 6px 0',
              '&:hover': {
                bgcolor: '#252a3a',
                color: '#fff',
              },
              boxShadow: '2px 0 8px rgba(0,0,0,0.4)',
            }}
          >
            <ChevronRightIcon sx={{ fontSize: 18, transform: 'rotate(180deg)' }} />
          </IconButton>
        </MuiTooltip>
      )}

      {/* 실시간 호가 패널 토글 버튼 (패널 닫힌 상태) */}
      {!orderbookPanelOpen && (
        <MuiTooltip title="실시간 호가" placement="right">
          <IconButton
            onClick={() => setOrderbookPanelOpen(true)}
            sx={{
              position: 'fixed',
              left: 8,
              top: '50%',
              transform: 'translateY(-50%)',
              zIndex: 1100,
              bgcolor: '#1a1f2e',
              color: '#26a69a',
              '&:hover': {
                bgcolor: '#252a3a',
                transform: 'translateY(-50%) scale(1.1)',
                transition: 'transform 0.2s ease',
              },
              boxShadow: '0 4px 12px rgba(0,0,0,0.4)',
            }}
          >
            <BarChartIcon />
          </IconButton>
        </MuiTooltip>
      )}

      {/* 거래 상세 정보 다이얼로그 */}
      <TradeSignalDetailDialog
        open={tradeDetailOpen}
        trade={selectedTrade}
        backtestResult={backtestResult}
        onClose={() => setTradeDetailOpen(false)}
      />

      {/* 실시간 갱신 간격 설정 메뉴 */}
      <Menu
        anchorEl={realtimeIntervalMenuAnchor}
        open={Boolean(realtimeIntervalMenuAnchor)}
        onClose={() => setRealtimeIntervalMenuAnchor(null)}
      >
        <MenuItem disabled>
          <ListItemText primary="차트 갱신 간격" />
        </MenuItem>
        <Divider />
        {[
          { value: 1000, label: '1초' },
          { value: 2000, label: '2초' },
          { value: 3000, label: '3초' },
          { value: 5000, label: '5초' },
          { value: 10000, label: '10초' },
          { value: 30000, label: '30초' },
        ].map((option) => (
          <MenuItem
            key={option.value}
            selected={realtimeUpdateInterval === option.value}
            onClick={() => {
              setRealtimeUpdateInterval(option.value);
              localStorage.setItem('realtimeUpdateInterval', String(option.value));
              setRealtimeIntervalMenuAnchor(null);
              console.log(`⏱️ [갱신 간격] 변경: ${option.label}`);
            }}
          >
            <ListItemIcon>
              {realtimeUpdateInterval === option.value && <CheckCircle fontSize="small" />}
            </ListItemIcon>
            <ListItemText primary={option.label} />
          </MenuItem>
        ))}
        <Divider />
        <MenuItem
          onClick={() => {
            setRealtimeUpdateEnabled(false);
            localStorage.setItem('realtimeUpdateEnabled', 'false');
            setRealtimeIntervalMenuAnchor(null);
            console.log(`🔄 [실시간 갱신] OFF`);
          }}
        >
          <ListItemText primary="실시간 갱신 끄기" />
        </MenuItem>
      </Menu>

      {/* 워밍업 데이터 로딩 프로그레스 */}
      <Backdrop
        open={isRunningBacktest}
        sx={{
          color: '#fff',
          zIndex: (theme) => theme.zIndex.drawer + 1000,
          backdropFilter: 'blur(4px)',
          backgroundColor: 'rgba(0, 0, 0, 0.7)',
        }}
      >
        <Box
          sx={{
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            gap: 3,
            p: 4,
            bgcolor: 'rgba(0, 0, 0, 0.8)',
            borderRadius: 2,
            border: '1px solid rgba(255, 255, 255, 0.1)',
          }}
        >
          <CircularProgress size={60} thickness={4} sx={{ color: '#4caf50' }} />
          <Box sx={{ textAlign: 'center' }}>
            <Typography variant="h6" sx={{ mb: 1, fontWeight: 600, color: '#4caf50' }}>
              투자 전략 분석 중
            </Typography>
            <Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)', mb: 1 }}>
              워밍업 데이터 로딩 및 MA 계산 중...
            </Typography>
            <Typography variant="caption" sx={{ color: 'rgba(255, 255, 255, 0.5)' }}>
              정확한 골든크로스/데드크로스 판별을 위해<br />
              충분한 데이터를 준비하고 있습니다
            </Typography>
          </Box>
        </Box>
      </Backdrop>

      <RealtimeChartValuesPopup
        anchorEl={chartValuesAnchorEl}
        onClose={() => setChartValuesAnchorEl(null)}
        isDark={isDark}
        symbol={symbol}
        timeInterval={timeInterval}
        candleData={candleData}
        maCalculationData={maCalculationData}
        allIndicatorData={allIndicatorData}
        indicatorData={indicatorData}
        maLines={memoizedMaLines}
        indicatorVisibility={indicatorVisibilitySettings}
        ichimokuSettings={memoizedIchimokuSettings}
        subPanelsOrder={subPanelsOrder}
        realtimePrice={realtimePrice}
        realtimeReceiving={realtimeUpdateEnabled && wsStatus === 'connected'}
      />

      <Snackbar
        open={snackbar.open}
        autoHideDuration={3000}
        onClose={() => setSnackbar(prev => ({ ...prev, open: false }))}
        anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
      >
        <Alert severity={snackbar.severity} onClose={() => setSnackbar(prev => ({ ...prev, open: false }))}>
          {snackbar.message}
        </Alert>
      </Snackbar>

      <IndicatorGaugePanel
        open={gaugePanelOpen}
        onToggle={() => setGaugePanelOpen(prev => !prev)}
        indicatorData={indicatorData.length > 0 ? indicatorData[indicatorData.length - 1] : null}
        isDark={isDark}
      />
    </Box>
  );
};

export default CandlestickTestPage;
