2165 lines
120 KiB
TypeScript
2165 lines
120 KiB
TypeScript
import React, { useState, useEffect, useRef } from 'react';
|
||
import {
|
||
Dialog,
|
||
DialogTitle,
|
||
DialogContent,
|
||
DialogActions,
|
||
Button,
|
||
TextField,
|
||
Typography,
|
||
Box,
|
||
Slider,
|
||
Divider,
|
||
useTheme,
|
||
useMediaQuery,
|
||
IconButton,
|
||
Grid,
|
||
Select,
|
||
MenuItem,
|
||
FormControl,
|
||
InputLabel,
|
||
Switch,
|
||
Paper,
|
||
PaperProps,
|
||
Autocomplete,
|
||
} from '@mui/material';
|
||
import Draggable from 'react-draggable';
|
||
import {
|
||
Close as CloseIcon,
|
||
Settings as SettingsIcon,
|
||
Add as AddIcon,
|
||
Delete as DeleteIcon,
|
||
HelpOutline as HelpOutlineIcon,
|
||
} from '@mui/icons-material';
|
||
import { useChartColors, ChartColorSettings } from '../contexts/ChartColorContext';
|
||
import { useIndicatorSettings, IndicatorSettings } from '../contexts/IndicatorSettingsContext';
|
||
|
||
// 보조지표 타입 정의
|
||
export type IndicatorType =
|
||
| 'volume'
|
||
| 'macd'
|
||
| 'stochastic'
|
||
| 'rsi'
|
||
| 'momentum'
|
||
| 'adx'
|
||
| 'dmi'
|
||
| 'cci'
|
||
| 'trix'
|
||
| 'disparity'
|
||
| 'newPsychological'
|
||
| 'investPsychological'
|
||
| 'williamsR'
|
||
| 'bwi'
|
||
| 'obv'
|
||
| 'volumeOsc'
|
||
| 'vr'
|
||
| 'vr2'
|
||
| 'dmi'
|
||
| 'ma'
|
||
| 'ichimoku';
|
||
|
||
interface IndicatorSettingsPopupProps {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
indicatorType: IndicatorType | null;
|
||
}
|
||
|
||
// 보조지표별 정보
|
||
const indicatorInfo: Record<IndicatorType, { name: string; description: string }> = {
|
||
volume: { name: '거래량', description: '매수/매도 거래량을 막대 그래프로 표시' },
|
||
macd: { name: 'MACD', description: 'Moving Average Convergence Divergence - 이동평균 수렴·발산 지표' },
|
||
stochastic: { name: 'Stochastic Slow', description: '현재 가격이 일정 기간의 가격 범위에서 어디에 위치하는지 표시' },
|
||
rsi: { name: 'RSI', description: 'Relative Strength Index - 상대강도지수' },
|
||
momentum: { name: 'Momentum', description: 'Momentum - 현재 가격과 n기간 전 가격의 차이를 나타내는 모멘텀 지표' },
|
||
adx: { name: 'ADX', description: 'Average Directional Index - 추세 강도 지표' },
|
||
dmi: { name: 'DMI', description: 'Directional Movement Index - 방향성 지표 (ADX + +DI + -DI)' },
|
||
cci: { name: 'CCI', description: 'Commodity Channel Index - 상품 채널 지수' },
|
||
williamsR: { name: 'Williams %R', description: '과매수/과매도 상태를 나타내는 모멘텀 지표' },
|
||
obv: { name: 'OBV', description: 'On Balance Volume - 거래량 누적 지표' },
|
||
trix: { name: 'TRIX', description: 'Triple Exponential Average - 삼중지수이동평균' },
|
||
newPsychological: { name: '신심리도', description: '일정 기간 동안 상승일 비율' },
|
||
investPsychological: { name: '투자심리도', description: '투자자들의 심리 상태를 나타내는 지표' },
|
||
disparity: { name: '이격도', description: '현재 가격과 이동평균선 간의 괴리 정도' },
|
||
bwi: { name: 'BWI', description: 'Bollinger Band Width Index - 볼린저밴드 %B' },
|
||
volumeOsc: { name: 'VolumeOSC', description: 'Volume Oscillator - 거래량 오실레이터' },
|
||
vr: { name: 'VR', description: 'Volume Ratio - 거래량 비율' },
|
||
vr2: { name: 'VR2', description: 'Volume Ratio (추가) - 거래량 비율 (별도 설정)' },
|
||
ma: { name: '이동평균선', description: 'Moving Average - 일정 기간 가격의 평균을 선으로 표시' },
|
||
ichimoku: { name: '일목균형표', description: 'Ichimoku Cloud - 추세, 지지/저항, 모멘텀을 한눈에 표시' },
|
||
};
|
||
|
||
// 보조지표별 상세 설명
|
||
const getIndicatorDetailedHelp = (type: IndicatorType): React.ReactNode => {
|
||
switch (type) {
|
||
case 'macd':
|
||
return (
|
||
<Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>1️⃣ MACD란?</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
MACD는 두 개의 이동평균선의 차이를 이용해
|
||
</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2">✔ 추세 방향</Typography>
|
||
<Typography variant="body2">✔ 매수·매도 시점</Typography>
|
||
<Typography variant="body2">✔ 힘의 강도(모멘텀)</Typography>
|
||
</Box>
|
||
<Typography variant="body2" paragraph>
|
||
를 판단하는 지표입니다.
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main', mb: 3 }}>
|
||
"주가의 흐름이 강해지고 있는지, 약해지고 있는지"를 보여줍니다.
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>2️⃣ MACD의 구성 요소 (3가지)</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>① MACD 선</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2 }}>
|
||
• 12일 지수이동평균(EMA) – 26일 EMA<br />
|
||
• 단기 추세와 중기 추세의 차이
|
||
</Typography>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>② 시그널 선</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2 }}>
|
||
• MACD 선의 9일 EMA<br />
|
||
• 매매 신호를 보조하는 기준선
|
||
</Typography>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>③ 히스토그램</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2 }}>
|
||
• MACD 선 – 시그널 선<br />
|
||
• 두 선의 간격을 막대그래프로 표시<br />
|
||
• 힘이 커지는지 / 약해지는지 시각적으로 보여줌
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>3️⃣ MACD 해석 방법 (핵심)</Typography>
|
||
<Box sx={{ mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 1. 골든크로스 / 데드크로스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>가장 많이 쓰이는 신호입니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', fontWeight: 600 }}>✔ 매수 신호</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>
|
||
• MACD 선이 시그널 선을 아래에서 위로 돌파<br />
|
||
• 상승 모멘텀 시작 가능성
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', fontWeight: 600 }}>✔ 매도 신호</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2 }}>
|
||
• MACD 선이 시그널 선을 위에서 아래로 이탈<br />
|
||
• 하락 모멘텀 시작 가능성
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 2. 0선 기준 해석</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>MACD가 0선 위/아래에 있는지도 중요합니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2">• 0선 위 → 상승 추세 우위</Typography>
|
||
<Typography variant="body2">• 0선 아래 → 하락 추세 우위</Typography>
|
||
<Typography variant="body2" sx={{ mt: 1, color: 'warning.main' }}>
|
||
📌 0선 위에서 골든크로스 = 신뢰도 ↑<br />
|
||
📌 0선 아래 데드크로스 = 하락 지속 가능성 ↑
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 3. 히스토그램 변화</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>히스토그램은 추세의 힘을 보여줍니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2">• 막대가 점점 커짐 → 추세 강화</Typography>
|
||
<Typography variant="body2">• 막대가 줄어듦 → 추세 약화</Typography>
|
||
<Typography variant="body2">• 방향 전환 → 추세 전환 가능성</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 4. 다이버전스 (중요)</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>주가와 MACD의 방향이 다를 때 나타납니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', fontWeight: 600 }}>✔ 강세 다이버전스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>
|
||
• 주가는 하락하는데 MACD는 저점 상승<br />
|
||
• → 반등 가능성
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', fontWeight: 600 }}>✔ 약세 다이버전스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2 }}>
|
||
• 주가는 상승하는데 MACD는 고점 하락<br />
|
||
• → 조정 가능성
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>4️⃣ 실전 활용 팁</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ MACD는 추세장에서 강력</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem' }}>횡보장에서는 잦은 헛신호 발생</Typography>
|
||
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 다른 지표와 함께 사용</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem' }}>RSI, 이동평균선, 거래량과 병행 추천</Typography>
|
||
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 시간봉에 따라 성격이 다름</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• 일봉 → 중기 추세<br />
|
||
• 분봉 → 단기 매매 (노이즈 많음)
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>5️⃣ MACD의 한계 (중요)</Typography>
|
||
<Box sx={{ pl: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'error.main', mb: 1 }}>❌ 후행성 지표</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>이동평균 기반 → 신호가 늦을 수 있음</Typography>
|
||
|
||
<Typography variant="body2" sx={{ color: 'error.main', mb: 1 }}>❌ 단독 사용은 위험</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>반드시 차트 패턴·지지저항·거래량과 함께 확인</Typography>
|
||
</Box>
|
||
</Box>
|
||
);
|
||
|
||
case 'rsi':
|
||
return (
|
||
<Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>1️⃣ RSI란?</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
RSI는 주가의 상승·하락 강도를 수치화한 모멘텀 지표입니다.
|
||
</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2">• 값의 범위: 0 ~ 100</Typography>
|
||
<Typography variant="body2">• "최근 일정 기간 동안 매수세와 매도세 중 누가 더 강한가"를 보여줌</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'warning.main', mb: 3 }}>
|
||
📌 과매수·과매도 판단에 가장 많이 쓰이는 지표 중 하나
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>2️⃣ RSI 기본 설정</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 기간: 14 (표준)</Typography>
|
||
<Typography variant="body2">• 거의 모든 HTS/MTS에서 기본값</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>3️⃣ 핵심 해석 방법</Typography>
|
||
<Box sx={{ mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ① 과매수 / 과매도 구간</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>가장 기본적인 사용법입니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• RSI ≥ 70 → 과매수 (조정 가능성)</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• RSI ≤ 30 → 과매도 (반등 가능성)</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', mb: 1 }}>⚠️ 수치 도달 즉시 매매 ❌</Typography>
|
||
<Typography variant="body2" sx={{ color: 'success.main' }}>✔ 꺾임(되돌림)을 확인 후 대응</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ② 50선 기준 추세 판단</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 50 이상 → 상승 추세 우위</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 50 이하 → 하락 추세 우위</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>📌 추세 확인용으로 매우 유용</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ③ 다이버전스 (중요)</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', fontWeight: 600, mb: 1 }}>✔ 강세 다이버전스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem', mb: 1 }}>
|
||
• 주가: 저점 낮아짐<br />
|
||
• RSI: 저점 높아짐<br />
|
||
→ 반등 가능성 ↑
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', fontWeight: 600, mb: 1 }}>✔ 약세 다이버전스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• 주가: 고점 높아짐<br />
|
||
• RSI: 고점 낮아짐<br />
|
||
→ 하락 가능성 ↑
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ④ RSI 추세선 활용</Typography>
|
||
<Box sx={{ pl: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• RSI에도 지지·저항선을 그릴 수 있음</Typography>
|
||
<Typography variant="body2">• RSI 추세선 이탈 = 주가 방향 전환 신호</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>4️⃣ 실전 활용 예시</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>📌 매수 타점 예</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>
|
||
• RSI 30 이하 진입<br />
|
||
• 바닥 다지고 30 상향 돌파<br />
|
||
• 거래량 증가 확인 → 분할 매수
|
||
</Typography>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>📌 매도 타점 예</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• RSI 70 이상 진입<br />
|
||
• 고점 형성 후 70 하향 이탈<br />
|
||
• 분할 매도
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>5️⃣ 매매 스타일별 RSI 설정</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>스타일</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>RSI 설정</Typography>
|
||
<Typography variant="body2">단타</Typography>
|
||
<Typography variant="body2">7 ~ 9</Typography>
|
||
<Typography variant="body2">스윙</Typography>
|
||
<Typography variant="body2">14</Typography>
|
||
<Typography variant="body2">중·장기</Typography>
|
||
<Typography variant="body2">21</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'warning.main', fontSize: '0.85rem', mb: 1 }}>📌 기간 ↓ → 신호 빠름 (노이즈 ↑)</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main', fontSize: '0.85rem' }}>📌 기간 ↑ → 안정적 (신호 느림)</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>6️⃣ RSI의 장점과 한계</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>✔ 장점</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>
|
||
• 직관적<br />
|
||
• 과열 구간 파악 쉬움<br />
|
||
• 대부분의 시장에 적용 가능
|
||
</Typography>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>❌ 한계</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>
|
||
• 강한 추세장에서는 신호 왜곡<br />
|
||
• 단독 사용 시 헛신호
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'info.main' }}>👉 MACD·이동평균·거래량과 병행 필수</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>한 줄 요약</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main' }}>
|
||
RSI는 매수·매도 세력의 힘을 보여주는 지표로, 30·70 기준과 다이버전스가 핵심 포인트입니다.
|
||
</Typography>
|
||
</Box>
|
||
);
|
||
|
||
case 'stochastic':
|
||
return (
|
||
<Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>1️⃣ Stochastic Slow란?</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
스토캐스틱은
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main', mb: 2 }}>
|
||
"현재 가격이 최근 일정 기간의 고가–저가 범위 중 어디에 위치하는가"
|
||
</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
를 보여주는 지표입니다.
|
||
</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2">• 가격이 고가 근처 → 매수 과열 가능</Typography>
|
||
<Typography variant="body2">• 가격이 저가 근처 → 매도 과열 가능</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'warning.main', mb: 3 }}>
|
||
📌 MACD가 추세 지표라면, Stochastic은 속도·과열 지표입니다.
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>2️⃣ Stochastic Slow의 구성요소</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 %K (느린 K)</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2 }}>
|
||
• 가격의 위치를 나타내는 주선<br />
|
||
• Fast Stochastic의 %K를 이동평균 처리한 값
|
||
</Typography>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 %D</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2 }}>
|
||
• %K의 이동평균 (신호선 역할)
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'info.main' }}>
|
||
👉 Slow Stochastic은 %K와 %D 두 개 선으로 해석합니다.
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>3️⃣ 계산 방식 (개념적으로)</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>기본 설정 기준:</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>(14, 3, 3) 이 가장 보편적</Typography>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>의미:</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>
|
||
• 14 → 기준 기간<br />
|
||
• 3 → %K를 느리게 만드는 이동평균<br />
|
||
• 3 → %D 이동평균
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem' }}>
|
||
※ 실제 계산은 자동이므로 원리만 이해하면 충분합니다.
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>4️⃣ 핵심 해석 방법</Typography>
|
||
<Box sx={{ mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔸 1. 과매수 / 과매도 구간</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>가장 기본적인 사용법입니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 80 이상 → 과매수 구간</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 20 이하 → 과매도 구간</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>
|
||
⚠️ 바로 매도·매수하는 게 아니라 전환 신호를 기다리는 것이 중요
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔸 2. 골든크로스 / 데드크로스</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', fontWeight: 600, mb: 1 }}>✔ 매수 신호</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem', mb: 1 }}>
|
||
• 20 이하 과매도 구간에서<br />
|
||
• %K가 %D를 아래에서 위로 돌파
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', fontWeight: 600, mb: 1 }}>✔ 매도 신호</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem', mb: 1 }}>
|
||
• 80 이상 과매수 구간에서<br />
|
||
• %K가 %D를 위에서 아래로 이탈
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>📌 구간 밖 크로스는 신뢰도 낮음</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔸 3. 다이버전스 (중요)</Typography>
|
||
<Box sx={{ pl: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', fontWeight: 600, mb: 1 }}>✔ 강세 다이버전스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem', mb: 1 }}>
|
||
• 주가는 저점 갱신<br />
|
||
• Stochastic은 저점 상승<br />
|
||
→ 반등 가능성
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', fontWeight: 600, mb: 1 }}>✔ 약세 다이버전스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• 주가는 고점 갱신<br />
|
||
• Stochastic은 고점 하락<br />
|
||
→ 조정 가능성
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>5️⃣ Fast vs Slow 차이</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 1, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>구분</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>Fast</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>Slow</Typography>
|
||
<Typography variant="body2">반응 속도</Typography>
|
||
<Typography variant="body2">매우 빠름</Typography>
|
||
<Typography variant="body2">느림</Typography>
|
||
<Typography variant="body2">노이즈</Typography>
|
||
<Typography variant="body2">많음</Typography>
|
||
<Typography variant="body2">적음</Typography>
|
||
<Typography variant="body2">실전 활용</Typography>
|
||
<Typography variant="body2">제한적</Typography>
|
||
<Typography variant="body2">가장 많이 사용</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'info.main' }}>
|
||
👉 실전에서는 Slow Stochastic을 기본으로 사용
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>6️⃣ 실전 활용 팁</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 횡보장·조정장에 강함</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem', color: 'error.main' }}>
|
||
❌ 강한 상승장에서는 과매수 상태가 오래 지속될 수 있음
|
||
</Typography>
|
||
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ MACD, 이동평균과 병행 추천</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem' }}>
|
||
• MACD로 추세 확인<br />
|
||
• Stochastic으로 진입 타이밍
|
||
</Typography>
|
||
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 분봉에서는 설정값 늘리면 안정적</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>7️⃣ 대표적인 설정값</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 기본: (14, 3, 3)</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 단타: (9, 3, 3)</Typography>
|
||
<Typography variant="body2">• 보수적: (21, 5, 5)</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>한 줄 요약</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main' }}>
|
||
Stochastic Slow는 가격의 과열 정도를 보여주는 지표로, 20·80 구간에서의 골든/데드크로스가 핵심 신호입니다.
|
||
</Typography>
|
||
</Box>
|
||
);
|
||
|
||
case 'volume':
|
||
return (
|
||
<Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>1️⃣ 거래량이란?</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
거래량은 일정 기간 동안 거래된 주식의 수량을 나타내며, 주가 변동의 신뢰도를 확인하는 핵심 지표입니다.
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main', mb: 3 }}>
|
||
"거래량이 많다 = 많은 사람들이 참여했다 = 신뢰도 높은 움직임"
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>2️⃣ 거래량 해석 방법</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 가격과 거래량의 관계</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main' }}>✔ 상승 + 거래량 증가 → 강한 상승 (신뢰도 높음)</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>✔ 상승 + 거래량 감소 → 약한 상승 (조정 가능성)</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main' }}>✔ 하락 + 거래량 증가 → 강한 하락 (추가 하락 가능성)</Typography>
|
||
<Typography variant="body2" sx={{ color: 'info.main' }}>✔ 하락 + 거래량 감소 → 약한 하락 (바닥 근접)</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 거래량 패턴</Typography>
|
||
<Box sx={{ pl: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 거래량 급증 → 추세 전환 신호</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 거래량 감소 → 추세 약화 또는 횡보 가능성</Typography>
|
||
<Typography variant="body2">• 거래량 평균 이상 → 신뢰할 수 있는 움직임</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>3️⃣ 실전 활용 팁</Typography>
|
||
<Box sx={{ pl: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 모든 매매 판단의 기본</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 돌파 구간에서 거래량 확인 필수</Typography>
|
||
<Typography variant="body2">✅ 이동평균선과 함께 보면 효과적</Typography>
|
||
</Box>
|
||
</Box>
|
||
);
|
||
|
||
case 'cci':
|
||
return (
|
||
<Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>1️⃣ CCI란?</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
CCI는
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main', mb: 2 }}>
|
||
"현재 가격이 최근 평균 가격보다 비싸게 거래되는지, 싸게 거래되는지"
|
||
</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
를 수치로 나타낸 지표입니다.
|
||
</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2">• + 방향 → 평균보다 강함</Typography>
|
||
<Typography variant="body2">• – 방향 → 평균보다 약함</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'warning.main', mb: 3 }}>
|
||
📌 RSI·Stochastic과 달리 기준이 고정(0선)이고, 과매수·과매도 범위가 유연한 것이 특징
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>2️⃣ CCI 기본 설정</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 기간: 14 또는 20 (가장 많이 사용)</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 기준선: 0선</Typography>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mt: 2, mb: 1 }}>일반적인 과열 기준:</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>• +100 이상 → 과매수</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2 }}>• –100 이하 → 과매도</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>3️⃣ CCI 계산 개념 (이해만 하면 충분)</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
CCI는 아래 개념으로 계산됩니다.
|
||
</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• TP(대표가격) = (고가 + 저가 + 종가) ÷ 3</Typography>
|
||
<Typography variant="body2">• TP가 평균(TP의 이동평균)에서 얼마나 떨어져 있는지를 수치화</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'info.main', mb: 3 }}>
|
||
👉 복잡해 보이지만 자동 계산되므로 해석이 핵심입니다.
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>4️⃣ 핵심 해석 방법</Typography>
|
||
<Box sx={{ mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ① 0선 돌파 (추세 판단)</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• CCI > 0 → 상승 모멘텀 우위</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• CCI < 0 → 하락 모멘텀 우위</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>📌 0선 돌파는 추세 시작 신호로 자주 활용</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ② 과매수 / 과매도</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• +100 이상 → 과매수 구간</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• –100 이하 → 과매도 구간</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', mb: 1 }}>⚠️ 도달 즉시 매매 ❌</Typography>
|
||
<Typography variant="body2" sx={{ color: 'success.main' }}>✔ 되돌림(재진입)을 기다리는 게 중요</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ③ 되돌림 매매 (가장 많이 사용)</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', fontWeight: 600, mb: 1 }}>✔ 매수 신호</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem' }}>
|
||
• CCI가 –100 이하 진입<br />
|
||
• 다시 –100 위로 회복
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', fontWeight: 600, mb: 1 }}>✔ 매도 신호</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem' }}>
|
||
• CCI가 +100 이상 진입<br />
|
||
• 다시 +100 아래로 이탈
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>📌 단기 매매에서 특히 효과적</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ④ 다이버전스</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', fontWeight: 600, mb: 1 }}>✔ 강세 다이버전스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem' }}>
|
||
• 주가 저점 하락<br />
|
||
• CCI 저점 상승<br />
|
||
→ 반등 가능성
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', fontWeight: 600, mb: 1 }}>✔ 약세 다이버전스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• 주가 고점 상승<br />
|
||
• CCI 고점 하락<br />
|
||
→ 하락 가능성
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>5️⃣ RSI·Stochastic과 차이점</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: 1, mb: 1 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>기준</Typography>
|
||
<Typography variant="body2">CCI: 0선 / RSI: 30·70 / Stochastic: 20·80</Typography>
|
||
</Box>
|
||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: 1, mb: 1 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>범위</Typography>
|
||
<Typography variant="body2">CCI: 제한 없음 / RSI: 0~100 / Stochastic: 0~100</Typography>
|
||
</Box>
|
||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: 1, mb: 1 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>성격</Typography>
|
||
<Typography variant="body2">CCI: 변동성 민감 / RSI: 힘의 균형 / Stochastic: 가격 위치</Typography>
|
||
</Box>
|
||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: 1, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>단타 활용</Typography>
|
||
<Typography variant="body2">CCI: ★★★★☆ / RSI: ★★★ / Stochastic: ★★★★</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'info.main' }}>👉 CCI는 짧은 파동 포착에 강함</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>6️⃣ 실전 활용 팁</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 횡보·조정장에서 강력</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem', color: 'error.main' }}>❌ 강한 추세장에서는 과열 지속 가능</Typography>
|
||
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 분봉에서는 기간 늘리면 안정적</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem' }}>예: 20 → 30</Typography>
|
||
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ MACD·이동평균과 병행 추천</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• 추세는 MACD<br />
|
||
• 타점은 CCI
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>7️⃣ 대표적인 설정값</Typography>
|
||
<Box sx={{ pl: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 기본: CCI(20)</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 단타: CCI(9~14)</Typography>
|
||
<Typography variant="body2">• 보수적: CCI(30)</Typography>
|
||
</Box>
|
||
</Box>
|
||
);
|
||
|
||
case 'adx':
|
||
return (
|
||
<Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>1️⃣ ADX란?</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
ADX(Average Directional Index)는 추세의 강도를 측정하는 지표입니다. 상승인지 하락인지가 아니라 "추세가 얼마나 강한지"만 보여줍니다.
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>2️⃣ ADX 해석 방법</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• ADX 25 미만 → 약한 추세 (횡보)</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• ADX 25~50 → 강한 추세</Typography>
|
||
<Typography variant="body2">• ADX 50 이상 → 매우 강한 추세</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>3️⃣ 실전 활용 팁</Typography>
|
||
<Box sx={{ pl: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 추세 추종 전략 사용 여부 결정</Typography>
|
||
<Typography variant="body2">✅ +DI, -DI와 함께 보면 방향까지 확인 가능</Typography>
|
||
</Box>
|
||
</Box>
|
||
);
|
||
|
||
case 'trix':
|
||
return (
|
||
<Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>TRIX (Triple Exponential Average Oscillator)</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
📈 추세의 방향과 전환을 비교적 깔끔하게 보여주는 모멘텀 보조지표입니다.
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'primary.main', mb: 3 }}>
|
||
이동평균의 노이즈를 크게 줄인 것이 가장 큰 특징입니다.
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>1️⃣ TRIX란?</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
TRIX는
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600, mb: 2 }}>
|
||
종가에 3중 지수이동평균(Triple EMA)을 적용한 뒤, 그 변화율(기울기)을 지표로 만든 것입니다.
|
||
</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
즉,
|
||
</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2">• 단순 이동평균보다 훨씬 부드럽고</Typography>
|
||
<Typography variant="body2">• 잔파동(노이즈)을 많이 제거한</Typography>
|
||
<Typography variant="body2">• 추세 중심 지표입니다.</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'warning.main', mb: 3 }}>
|
||
📌 단타보다는 스윙·중기 매매에 적합
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>2️⃣ TRIX의 구성 요소</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 TRIX 선</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2 }}>
|
||
• 3중 EMA의 변화율<br />
|
||
• 추세의 방향과 속도를 나타냄
|
||
</Typography>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 시그널 선 (선택)</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2 }}>
|
||
• TRIX의 이동평균<br />
|
||
• 매매 신호 보조
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>3️⃣ 기본 설정값</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• TRIX 기간: 14 (또는 15)</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 시그널: 9</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>
|
||
📌 RSI·MACD보다 신호가 느린 대신 신뢰도가 높음
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>4️⃣ 핵심 해석 방법</Typography>
|
||
<Box sx={{ mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔸 ① 0선 기준 해석 (가장 중요)</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• TRIX > 0 → 상승 추세</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• TRIX < 0 → 하락 추세</Typography>
|
||
<Typography variant="body2" sx={{ color: 'success.main' }}>✔ 0선 돌파 = 추세 전환 신호</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔸 ② 골든크로스 / 데드크로스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem' }}>(시그널 선 사용 시)</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', fontWeight: 600, mb: 1 }}>✔ 매수 신호</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem', mb: 1 }}>
|
||
• TRIX가 시그널을 아래에서 위로 돌파<br />
|
||
• 0선 근처에서 발생 시 신뢰도 ↑
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', fontWeight: 600, mb: 1 }}>✔ 매도 신호</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• TRIX가 시그널을 위에서 아래로 이탈
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔸 ③ 기울기(각도) 해석</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 기울기 가팔라짐 → 추세 가속</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 기울기 둔화 → 추세 약화</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>
|
||
📌 히스토그램처럼 힘의 변화를 직관적으로 파악 가능
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔸 ④ 다이버전스</Typography>
|
||
<Box sx={{ pl: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', fontWeight: 600, mb: 1 }}>✔ 강세 다이버전스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem', mb: 1 }}>
|
||
• 주가 저점 하락<br />
|
||
• TRIX 저점 상승<br />
|
||
→ 반등 가능성
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', fontWeight: 600, mb: 1 }}>✔ 약세 다이버전스</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• 주가 고점 상승<br />
|
||
• TRIX 고점 하락<br />
|
||
→ 하락 가능성
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>5️⃣ MACD와의 차이</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 1, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>구분</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>TRIX</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>MACD</Typography>
|
||
<Typography variant="body2">이동평균</Typography>
|
||
<Typography variant="body2">3중 EMA</Typography>
|
||
<Typography variant="body2">2중 EMA</Typography>
|
||
<Typography variant="body2">반응 속도</Typography>
|
||
<Typography variant="body2">느림</Typography>
|
||
<Typography variant="body2">빠름</Typography>
|
||
<Typography variant="body2">노이즈</Typography>
|
||
<Typography variant="body2">매우 적음</Typography>
|
||
<Typography variant="body2">중간</Typography>
|
||
<Typography variant="body2">적합 매매</Typography>
|
||
<Typography variant="body2">스윙·중기</Typography>
|
||
<Typography variant="body2">단타~중기</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'info.main' }}>
|
||
👉 TRIX = 깔끔한 추세 판단용
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>6️⃣ 실전 활용 팁</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 횡보장보다는 추세장에서 강력</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem', color: 'error.main' }}>
|
||
❌ 짧은 분봉에서는 신호 지연
|
||
</Typography>
|
||
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 이동평균선과 궁합 좋음</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1, fontSize: '0.85rem' }}>
|
||
• MA 정배열 + TRIX 0선 상방 = 상승 신뢰도 ↑
|
||
</Typography>
|
||
|
||
<Typography variant="body2" sx={{ mb: 1 }}>✅ 단타에서는 기간 줄여 사용 가능</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• 예: TRIX(7, 5)
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>7️⃣ TRIX의 한계</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 후행성 강함</Typography>
|
||
<Typography variant="body2" sx={{ mb: 2 }}>• 급등락 초기 신호 포착 어려움</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main', fontSize: '0.85rem', mb: 1 }}>
|
||
📌 진입 타점은 RSI·CCI,
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main', fontSize: '0.85rem' }}>
|
||
📌 방향 확인은 TRIX로 분리해서 쓰면 효과적
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>한 줄 요약</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main' }}>
|
||
TRIX는 3중 EMA 기반의 추세 지표로, 0선 돌파와 기울기 변화가 핵심 포인트입니다.
|
||
</Typography>
|
||
</Box>
|
||
);
|
||
|
||
case 'disparity':
|
||
return (
|
||
<Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>이격도(乖離度, Disparity Index)</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
📊 현재 주가가 이동평균선에서 얼마나 떨어져 있는지를 %로 나타낸 아주 직관적인 과열·과매도 보조지표입니다.
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>1️⃣ 이격도란?</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
이격도는 아래 질문에 답하는 지표입니다.
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main', mb: 2 }}>
|
||
"지금 주가는 평균 가격(이동평균)보다 얼마나 위(과열) 또는 아래(과매도)에 있는가?"
|
||
</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2">• 100% → 이동평균과 동일</Typography>
|
||
<Typography variant="body2">• 100% 초과 → 평균보다 비쌈</Typography>
|
||
<Typography variant="body2">• 100% 미만 → 평균보다 쌈</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>2️⃣ 이격도 계산식</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>개념만 이해하시면 충분합니다.</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600, mb: 2 }}>
|
||
이격도(%) = (현재가 ÷ 이동평균가) × 100
|
||
</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>예)</Typography>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem' }}>• 현재가 105, 20일선 100 → 이격도 105%</Typography>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem' }}>• 현재가 95, 20일선 100 → 이격도 95%</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>3️⃣ 기준 이동평균선 선택</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>이격도는 어떤 이동평균을 쓰느냐가 매우 중요합니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>이동평균</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>활용</Typography>
|
||
<Typography variant="body2">5일</Typography>
|
||
<Typography variant="body2">초단기 과열</Typography>
|
||
<Typography variant="body2">20일</Typography>
|
||
<Typography variant="body2">단기 매매(가장 많이 사용)</Typography>
|
||
<Typography variant="body2">60일</Typography>
|
||
<Typography variant="body2">중기 추세</Typography>
|
||
<Typography variant="body2">120일</Typography>
|
||
<Typography variant="body2">장기 과열</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>📌 20일 이격도가 가장 보편적</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>4️⃣ 핵심 해석 방법</Typography>
|
||
<Box sx={{ mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ① 기준값 (과열/과매도)</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>종목·시장마다 다르지만 일반적인 기준은 다음과 같습니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ color: 'error.main', fontWeight: 600, mb: 1 }}>📈 상승 과열</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem', mb: 1 }}>
|
||
• 이격도 105~110% 이상<br />
|
||
→ 단기 과열, 조정 가능성
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'success.main', fontWeight: 600, mb: 1 }}>📉 하락 과매도</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem', mb: 1 }}>
|
||
• 이격도 90~95% 이하<br />
|
||
→ 과매도, 반등 가능성
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main', fontWeight: 600, mb: 1 }}>⚠️ 기준값은 고정이 아님</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• 변동성 큰 종목 → 기준 확대<br />
|
||
• 우량주 → 기준 축소
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ② 평균 회귀 (Mean Reversion)</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>이격도의 핵심 개념입니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem', mb: 1 }}>
|
||
• 주가는 결국 이동평균으로 되돌아가려는 성질이 있음
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem', mb: 1 }}>
|
||
• 이격 ↑↑ → 조정 후 평균 회귀
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem' }}>
|
||
• 이격 ↓↓ → 반등 후 평균 회귀
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ③ 추세와 함께 사용</Typography>
|
||
<Box sx={{ pl: 2 }}>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem', mb: 1 }}>
|
||
• 상승 추세 + 이격 과열 → 눌림목 대기
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem', mb: 1 }}>
|
||
• 하락 추세 + 과매도 → 기술적 반등 주의
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>📌 추세 무시하고 역매매 ❌</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>5️⃣ 실전 활용 예시</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>📌 매수 타점</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>
|
||
• 상승 추세 유지<br />
|
||
• 조정으로 이격도 95% 내외<br />
|
||
• 지지 확인 후 분할 매수
|
||
</Typography>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>📌 매도 타점</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• 급등 후 이격도 110% 이상<br />
|
||
• 거래량 감소<br />
|
||
• 분할 매도
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>6️⃣ RSI·CCI와 차이점</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 1, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>구분</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>이격도</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>RSI</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>CCI</Typography>
|
||
<Typography variant="body2">기준</Typography>
|
||
<Typography variant="body2">이동평균</Typography>
|
||
<Typography variant="body2">30·70</Typography>
|
||
<Typography variant="body2">±100</Typography>
|
||
<Typography variant="body2">직관성</Typography>
|
||
<Typography variant="body2">매우 높음</Typography>
|
||
<Typography variant="body2">중간</Typography>
|
||
<Typography variant="body2">중간</Typography>
|
||
<Typography variant="body2">추세 판단</Typography>
|
||
<Typography variant="body2">약함</Typography>
|
||
<Typography variant="body2">보통</Typography>
|
||
<Typography variant="body2">보통</Typography>
|
||
<Typography variant="body2">평균회귀</Typography>
|
||
<Typography variant="body2">★★★★★</Typography>
|
||
<Typography variant="body2">★★★</Typography>
|
||
<Typography variant="body2">★★★</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'info.main' }}>
|
||
👉 이격도 = 평균에서 얼마나 벗어났는지 한눈에 파악
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>7️⃣ 이격도의 장점과 한계</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>✔ 장점</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>
|
||
• 계산·해석 쉬움<br />
|
||
• 과열/과매도 직관적<br />
|
||
• 초보자도 사용 가능
|
||
</Typography>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>❌ 한계</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>
|
||
• 강한 추세장에서는 과열 지속<br />
|
||
• 단독 사용 시 역추세 매매 위험
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>
|
||
📌 이동평균·거래량·추세 지표와 병행 필수
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>한 줄 요약</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main' }}>
|
||
이격도는 주가가 이동평균에서 얼마나 떨어졌는지를 %로 보여주는 지표로, 평균 회귀 관점에서 과열·과매도를 판단합니다.
|
||
</Typography>
|
||
</Box>
|
||
);
|
||
|
||
case 'newPsychological':
|
||
return (
|
||
<Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>신심리도(心理度, Psychological Line)</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
📊 투자자들의 '심리 상태'를 수치로 표현한 주식 보조지표입니다.
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main', mb: 3 }}>
|
||
"요즘 시장이 너무 낙관적인가, 너무 비관적인가?"를 판단할 때 사용합니다.
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>1️⃣ 신심리도란?</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
신심리도는 일정 기간 동안
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main', mb: 2 }}>
|
||
주가가 오른 날의 비율
|
||
</Typography>
|
||
<Typography variant="body2" paragraph>
|
||
을 계산한 지표입니다.
|
||
</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 오르는 날이 많을수록 → 낙관(과열)</Typography>
|
||
<Typography variant="body2">• 내리는 날이 많을수록 → 비관(침체)</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'warning.main', mb: 3 }}>
|
||
📌 가격 크기보다 '올랐는지/내렸는지'만 본다는 게 특징입니다.
|
||
</Typography>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>2️⃣ 신심리도 계산 방식 (개념)</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main', mb: 2 }}>
|
||
신심리도 = (상승한 날 수 ÷ 전체 기간) × 100
|
||
</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ fontSize: '0.9rem', mb: 1 }}>예)</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem', mb: 1 }}>
|
||
• 12일 중 9일 상승 → 신심리도 = 75
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
• 12일 중 3일 상승 → 신심리도 = 25
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>3️⃣ 기본 설정</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 기간: 12일 (가장 많이 사용)</Typography>
|
||
<Typography variant="body2">• 범위: 0 ~ 100</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>4️⃣ 핵심 해석 방법</Typography>
|
||
<Box sx={{ mb: 3 }}>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ① 과매수 / 과매도 구간</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>가장 중요합니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 75 이상 → 과매수 (낙관 과도)</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 25 이하 → 과매도 (비관 과도)</Typography>
|
||
<Typography variant="body2" sx={{ color: 'error.main', mb: 1 }}>⚠️ 도달 즉시 매매 ❌</Typography>
|
||
<Typography variant="body2" sx={{ color: 'success.main' }}>✔ 되돌림(구간 이탈)을 확인</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ② 평균 회귀 관점</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 1 }}>신심리도는 역추세 지표 성격이 강합니다.</Typography>
|
||
<Box sx={{ pl: 2, mb: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 과도한 낙관 → 조정 가능성</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 과도한 비관 → 반등 가능성</Typography>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>📌 횡보장·조정장에서 효과적</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>🔹 ③ 추세 확인 보조</Typography>
|
||
<Box sx={{ pl: 2 }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 50 이상 → 상승 심리 우위</Typography>
|
||
<Typography variant="body2">• 50 이하 → 하락 심리 우위</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>5️⃣ RSI·이격도와의 차이</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>구분 | 신심리도 | RSI | 이격도</Typography>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem', mb: 1 }}>• 기준: 상승일 비율 | 상승·하락 강도 | 평균 대비 거리</Typography>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem', mb: 1 }}>• 가격 크기 반영: ❌ | ⭕ | ⭕</Typography>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem', mb: 1 }}>• 성격: 심리 | 모멘텀 | 평균회귀</Typography>
|
||
<Typography variant="body2" sx={{ fontSize: '0.85rem', mb: 2 }}>• 역추세: ★★★★☆ | ★★★ | ★★★★★</Typography>
|
||
<Typography variant="body2" sx={{ color: 'info.main' }}>
|
||
👉 신심리도는 "분위기 과열"을 보는 지표
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>6️⃣ 실전 활용 팁</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', mb: 1 }}>✅ 단독 사용보다는 보조용</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>RSI, CCI와 함께 쓰면 신뢰도 ↑</Typography>
|
||
|
||
<Typography variant="body2" sx={{ color: 'success.main', mb: 1 }}>✅ 강한 추세장에서는 신호 무력</Typography>
|
||
<Typography variant="body2" sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>상승장에서는 75 이상 유지 가능</Typography>
|
||
|
||
<Typography variant="body2" sx={{ color: 'success.main', mb: 1 }}>✅ 기간 조절</Typography>
|
||
<Box sx={{ pl: 2, fontSize: '0.85rem' }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 단타: 9</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 기본: 12</Typography>
|
||
<Typography variant="body2">• 보수적: 20</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>7️⃣ 신심리도의 장점과 한계</Typography>
|
||
<Box sx={{ pl: 2, mb: 3 }}>
|
||
<Typography variant="body2" sx={{ color: 'success.main', mb: 1 }}>✔ 장점</Typography>
|
||
<Box sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 계산·해석 매우 쉬움</Typography>
|
||
<Typography variant="body2">• 투자심리 파악에 직관적</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="body2" sx={{ color: 'error.main', mb: 1 }}>❌ 한계</Typography>
|
||
<Box sx={{ pl: 2, mb: 2, fontSize: '0.85rem' }}>
|
||
<Typography variant="body2" sx={{ mb: 1 }}>• 가격 변동폭 반영 안 함</Typography>
|
||
<Typography variant="body2">• 추세 추종 능력 약함</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ color: 'warning.main' }}>
|
||
📌 반드시 추세 지표와 병행
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>한 줄 요약</Typography>
|
||
<Typography variant="body2" sx={{ fontStyle: 'italic', color: 'primary.main' }}>
|
||
신심리도는 상승한 날의 비율로 투자자 심리를 수치화한 지표로, 25·75 구간 이탈이 핵심 매매 신호입니다.
|
||
</Typography>
|
||
</Box>
|
||
);
|
||
|
||
default:
|
||
return (
|
||
<Box>
|
||
<Typography variant="body2" color="text.secondary">
|
||
{indicatorInfo[type]?.name} 지표에 대한 상세 설명은 준비 중입니다.
|
||
</Typography>
|
||
</Box>
|
||
);
|
||
}
|
||
};
|
||
|
||
const IndicatorSettingsPopup: React.FC<IndicatorSettingsPopupProps> = ({
|
||
open,
|
||
onClose,
|
||
indicatorType,
|
||
}) => {
|
||
const theme = useTheme();
|
||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||
const { colors, updateColors } = useChartColors();
|
||
const {
|
||
indicatorSettings,
|
||
updateIndicatorSettings,
|
||
saveIndicatorSettings,
|
||
} = useIndicatorSettings();
|
||
|
||
// 로컬 상태
|
||
const [localColors, setLocalColors] = useState<Partial<ChartColorSettings>>({});
|
||
const [localSettings, setLocalSettings] = useState<Partial<IndicatorSettings>>({});
|
||
const [showHelpDialog, setShowHelpDialog] = useState(false);
|
||
|
||
// ✅ 색상 input ref 추적 (리렌더링 방지를 위해)
|
||
const colorInputRefsRef = useRef<Map<string, HTMLInputElement>>(new Map());
|
||
|
||
// 팝업 위치 상태 (드래그 시 유지)
|
||
const [dialogPosition, setDialogPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 });
|
||
|
||
// 다이얼로그가 열릴 때 현재 값으로 초기화 (open/indicatorType 변경 시에만)
|
||
useEffect(() => {
|
||
if (open && indicatorType) {
|
||
setLocalColors({ ...colors });
|
||
setLocalSettings({ ...indicatorSettings });
|
||
}
|
||
|
||
// 다이얼로그가 닫힐 때 위치 초기화
|
||
if (!open) {
|
||
setDialogPosition({ x: 0, y: 0 });
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [open, indicatorType]);
|
||
|
||
if (!indicatorType) return null;
|
||
|
||
const info = indicatorInfo[indicatorType];
|
||
|
||
const handleColorChange = (key: keyof ChartColorSettings, value: string | number) => {
|
||
console.log(`🎨 [IndicatorSettingsPopup] handleColorChange - key: ${key}, value: ${value}`);
|
||
|
||
// ✅ 로컬 상태만 업데이트 (적용 버튼 클릭 시 Context에 반영)
|
||
setLocalColors(prev => {
|
||
const newColors = { ...prev, [key]: value };
|
||
console.log(`✅ [IndicatorSettingsPopup] localColors 업데이트:`, newColors);
|
||
return newColors;
|
||
});
|
||
};
|
||
|
||
const handleSettingChange = (key: keyof IndicatorSettings, value: number | boolean) => {
|
||
// 로컬 상태만 업데이트 (적용 버튼 클릭 시 Context에 반영)
|
||
setLocalSettings(prev => ({ ...prev, [key]: value }));
|
||
};
|
||
|
||
const handleSave = () => {
|
||
// 모든 색상 input의 현재 값을 읽어서 localColors에 반영
|
||
const finalColors: Partial<ChartColorSettings> = { ...localColors };
|
||
colorInputRefsRef.current.forEach((input, key) => {
|
||
if (input && input.value) {
|
||
(finalColors as any)[key] = input.value;
|
||
}
|
||
});
|
||
|
||
// 색상 업데이트
|
||
updateColors({ ...colors, ...finalColors });
|
||
|
||
// 설정값이 실제로 변경되었는지 확인
|
||
const settingsChanged = Object.keys(localSettings).some((key) => {
|
||
const localValue = (localSettings as any)[key];
|
||
const currentValue = (indicatorSettings as any)[key];
|
||
return localValue !== currentValue;
|
||
});
|
||
|
||
// 설정값이 변경된 경우 Context 업데이트 (settingsVersion 증가) 및 DB 저장
|
||
if (settingsChanged) {
|
||
console.log('🔄 [IndicatorSettingsPopup] 설정 변경 감지 - updateIndicatorSettings 호출:', localSettings);
|
||
// ✅ updateIndicatorSettings 호출 (settingsVersion 증가 → 차트 재계산 트리거)
|
||
updateIndicatorSettings(localSettings);
|
||
// ✅ DB 저장
|
||
saveIndicatorSettings({ ...indicatorSettings, ...localSettings });
|
||
}
|
||
|
||
onClose();
|
||
};
|
||
|
||
// 파라미터 테이블 컴포넌트
|
||
const ParamTable = ({
|
||
params,
|
||
}: {
|
||
params: { label: string; paramKey: keyof IndicatorSettings; min?: number; max?: number }[];
|
||
}) => {
|
||
// min/max 값을 기준으로 선택 옵션 생성 함수
|
||
const generateOptions = (min: number, max: number, step: number = 1) => {
|
||
const options: number[] = [];
|
||
for (let i = min; i <= max; i += step) {
|
||
options.push(i);
|
||
}
|
||
return options;
|
||
};
|
||
|
||
return (
|
||
<Box sx={{ mb: 2 }}>
|
||
<Typography variant="caption" fontWeight="bold" color="text.secondary" sx={{ display: 'block', mb: 1 }}>
|
||
📊 파라미터
|
||
</Typography>
|
||
{/* 헤더 행 */}
|
||
<Box sx={{ p: 1, mb: 0.5, bgcolor: 'primary.main', borderRadius: 1, opacity: 0.85 }}>
|
||
<Grid container spacing={1} alignItems="center">
|
||
{params.map((param) => (
|
||
<Grid item xs={12 / params.length} key={`header-${param.paramKey}`}>
|
||
<Typography variant="caption" fontWeight="bold" color="white" textAlign="center" display="block">
|
||
{param.label}
|
||
</Typography>
|
||
</Grid>
|
||
))}
|
||
</Grid>
|
||
</Box>
|
||
{/* 값 행 - 드롭다운으로 변경 */}
|
||
<Box sx={{ p: 1, bgcolor: 'background.paper', borderRadius: 1, border: '1px solid', borderColor: 'divider' }}>
|
||
<Grid container spacing={1} alignItems="center">
|
||
{params.map((param) => {
|
||
const min = param.min ?? 1;
|
||
const max = param.max ?? 100;
|
||
const currentValue = Number(localSettings[param.paramKey] ?? indicatorSettings[param.paramKey] ?? min);
|
||
const options = generateOptions(min, max);
|
||
|
||
return (
|
||
<Grid item xs={12 / params.length} key={param.paramKey}>
|
||
<Select
|
||
size="small"
|
||
fullWidth
|
||
value={currentValue}
|
||
onChange={(e) => {
|
||
const newValue = Number(e.target.value);
|
||
handleSettingChange(param.paramKey, newValue);
|
||
console.log(`🔄 [ParamTable] ${param.label} - 선택: ${newValue}`);
|
||
}}
|
||
sx={{
|
||
fontSize: '12px',
|
||
height: 32,
|
||
'& .MuiSelect-select': {
|
||
py: 0.5,
|
||
textAlign: 'center'
|
||
},
|
||
'& .MuiOutlinedInput-root': { bgcolor: 'background.paper' }
|
||
}}
|
||
>
|
||
{options.map((value) => (
|
||
<MenuItem key={value} value={value} sx={{ fontSize: '12px' }}>
|
||
{value}
|
||
</MenuItem>
|
||
))}
|
||
</Select>
|
||
</Grid>
|
||
);
|
||
})}
|
||
</Grid>
|
||
</Box>
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
// 색상 및 굵기 테이블 컴포넌트
|
||
const ColorTable = ({
|
||
configs,
|
||
}: {
|
||
configs: {
|
||
label: string;
|
||
colorKey?: keyof ChartColorSettings;
|
||
widthKey?: keyof ChartColorSettings;
|
||
lineStyleKey?: keyof ChartColorSettings;
|
||
valueKey?: keyof IndicatorSettings;
|
||
valueMin?: number;
|
||
valueMax?: number;
|
||
valueStep?: number;
|
||
enableKey?: keyof IndicatorSettings;
|
||
disableToggle?: boolean;
|
||
}[];
|
||
}) => {
|
||
const hasValueColumn = configs.some(cfg => cfg.valueKey !== undefined);
|
||
const hasEnableColumn = configs.some(cfg => cfg.enableKey !== undefined);
|
||
const hasWidthColumn = configs.some(cfg => cfg.widthKey !== undefined);
|
||
const hasLineStyleColumn = configs.some(cfg => cfg.lineStyleKey !== undefined);
|
||
|
||
// 기준값 입력 필드의 임시 값 관리 (입력 중 포커스 유지를 위한 로컬 상태)
|
||
const [inputValues, setInputValues] = React.useState<Record<string, string>>({});
|
||
|
||
// 타이틀 텍스트 생성
|
||
const getTitleText = () => {
|
||
const parts = ['🎨 색상'];
|
||
if (hasWidthColumn) parts.push('선 굵기');
|
||
if (hasLineStyleColumn) parts.push('선 유형');
|
||
return parts.join(', ');
|
||
};
|
||
|
||
return (
|
||
<Box sx={{ mb: 0.5 }}>
|
||
<Typography variant="caption" fontWeight="bold" color="text.secondary" sx={{ display: 'block', mb: 1, fontSize: '12px' }}>
|
||
{getTitleText()}
|
||
</Typography>
|
||
{/* 헤더 행 */}
|
||
<Box sx={{
|
||
p: 1,
|
||
mb: 0.5,
|
||
bgcolor: (theme) => theme.palette.mode === 'dark' ? '#616161' : '#9e9e9e',
|
||
borderRadius: 1,
|
||
}}>
|
||
<Grid container spacing={1.5} alignItems="center">
|
||
{hasEnableColumn && (
|
||
<Grid item xs={hasValueColumn ? 0.8 : 1}>
|
||
<Typography variant="caption" fontWeight="bold" color="white">ON/OFF</Typography>
|
||
</Grid>
|
||
)}
|
||
<Grid item xs={hasEnableColumn ? (hasValueColumn ? 1.5 : 2) : (hasValueColumn ? 1.8 : (!hasWidthColumn && !hasLineStyleColumn ? 4 : 2.5))}>
|
||
<Typography variant="caption" fontWeight="bold" color="white">항목</Typography>
|
||
</Grid>
|
||
{hasValueColumn && (
|
||
<Grid item xs={1.5}>
|
||
<Typography variant="caption" fontWeight="bold" color="white">기준값</Typography>
|
||
</Grid>
|
||
)}
|
||
<Grid item xs={hasEnableColumn ? (hasValueColumn ? 1 : 1.5) : (hasValueColumn ? 1.2 : (!hasWidthColumn && !hasLineStyleColumn ? 4 : 1.5))}>
|
||
<Typography variant="caption" fontWeight="bold" color="white">색상</Typography>
|
||
</Grid>
|
||
{hasWidthColumn && (
|
||
<Grid item xs={hasEnableColumn ? (hasValueColumn ? 2.5 : 2.5) : (hasValueColumn ? 3 : 3)}>
|
||
<Typography variant="caption" fontWeight="bold" color="white">굵기</Typography>
|
||
</Grid>
|
||
)}
|
||
{hasLineStyleColumn && (
|
||
<Grid item xs={hasEnableColumn ? (hasValueColumn ? 2 : 2.5) : (hasValueColumn ? 2.5 : 2.5)}>
|
||
<Typography variant="caption" fontWeight="bold" color="white">선 유형</Typography>
|
||
</Grid>
|
||
)}
|
||
<Grid item xs={hasEnableColumn ? (hasValueColumn ? (hasWidthColumn && hasLineStyleColumn ? 2.7 : 4) : (hasWidthColumn && hasLineStyleColumn ? 2.5 : 4)) : (hasValueColumn ? (hasWidthColumn && hasLineStyleColumn ? 3.5 : 4) : (hasWidthColumn && hasLineStyleColumn ? 2.5 : 4))}>
|
||
<Typography variant="caption" fontWeight="bold" color="white">미리보기</Typography>
|
||
</Grid>
|
||
</Grid>
|
||
</Box>
|
||
{/* 각 색상 설정 행 */}
|
||
{configs.map((cfg, index) => {
|
||
const colorValue = cfg.colorKey ? String(localColors[cfg.colorKey] || colors[cfg.colorKey] || '#666666') : '#666666';
|
||
const widthValue = cfg.widthKey ? Number(localColors[cfg.widthKey] ?? colors[cfg.widthKey] ?? 1.5) : 1.5;
|
||
const lineStyleValue = cfg.lineStyleKey ? String(localColors[cfg.lineStyleKey] ?? colors[cfg.lineStyleKey] ?? 'solid') : 'solid';
|
||
const paramValue = cfg.valueKey ? Number(localSettings[cfg.valueKey] ?? indicatorSettings[cfg.valueKey] ?? 0) : 0;
|
||
const enabledValue = cfg.enableKey ? Boolean(localSettings[cfg.enableKey] ?? indicatorSettings[cfg.enableKey] ?? false) : true;
|
||
|
||
return (
|
||
<Box key={cfg.colorKey || `param-${index}`} sx={{ p: 1, mb: 0.5, bgcolor: 'background.paper', borderRadius: 1, border: '1px solid', borderColor: 'divider' }}>
|
||
<Grid container spacing={1} alignItems="center">
|
||
{/* ON/OFF 토글 */}
|
||
{hasEnableColumn && (
|
||
<Grid item xs={hasValueColumn ? 0.8 : 1} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||
{cfg.enableKey ? (
|
||
<Switch
|
||
checked={enabledValue}
|
||
onChange={(e) => handleSettingChange(cfg.enableKey!, e.target.checked)}
|
||
disabled={cfg.disableToggle === true}
|
||
size="small"
|
||
/>
|
||
) : (
|
||
<Typography variant="caption" color="text.disabled">-</Typography>
|
||
)}
|
||
</Grid>
|
||
)}
|
||
{/* 항목 이름 */}
|
||
<Grid item xs={hasEnableColumn ? (hasValueColumn ? 1.5 : 2) : (hasValueColumn ? 1.8 : 2.5)} sx={{ display: 'flex', alignItems: 'center' }}>
|
||
<Typography variant="body2" sx={{ fontSize: '12px', fontWeight: 'medium' }}>
|
||
{cfg.label}
|
||
</Typography>
|
||
</Grid>
|
||
{/* 기준값 선택 (valueKey가 있을 때만 표시) */}
|
||
{hasValueColumn && (
|
||
<Grid item xs={1.5} sx={{ display: 'flex', alignItems: 'center' }}>
|
||
{cfg.valueKey ? (
|
||
<Autocomplete
|
||
id={`input-${cfg.valueKey}`}
|
||
value={paramValue}
|
||
onChange={(event, newValue) => {
|
||
// 드롭다운에서 선택했을 때
|
||
if (newValue !== null) {
|
||
const numValue = typeof newValue === 'number' ? newValue : Number(newValue);
|
||
if (!isNaN(numValue)) {
|
||
const min = cfg.valueMin ?? 0;
|
||
const max = cfg.valueMax ?? 100;
|
||
|
||
// 기준값(Period, Length 등)은 양수만 허용
|
||
const isPeriodValue = cfg.valueKey && (
|
||
cfg.valueKey.toLowerCase().includes('period') ||
|
||
cfg.valueKey.toLowerCase().includes('length') ||
|
||
cfg.valueKey.toLowerCase().includes('slowing')
|
||
);
|
||
|
||
if (numValue >= min && numValue <= max) {
|
||
// 기준값인 경우 양수 확인
|
||
if (isPeriodValue && numValue < 0) {
|
||
return;
|
||
}
|
||
handleSettingChange(cfg.valueKey!, numValue);
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: String(numValue) }));
|
||
console.log(`🔄 [IndicatorSettingsPopup] ${cfg.label} - 선택: ${numValue}`);
|
||
}
|
||
}
|
||
}
|
||
}}
|
||
inputValue={inputValues[cfg.valueKey] ?? String(paramValue)}
|
||
onInputChange={(event, newInputValue, reason) => {
|
||
// 입력값이 변경될 때는 로컬 상태만 업데이트 (필터링을 위해)
|
||
if (reason === 'input') {
|
||
// 기준값(Period, Length 등)은 양수만, 기준선(Overbought, Oversold 등)은 음수도 허용
|
||
const isPeriodValue = cfg.valueKey && (
|
||
cfg.valueKey.toLowerCase().includes('period') ||
|
||
cfg.valueKey.toLowerCase().includes('length') ||
|
||
cfg.valueKey.toLowerCase().includes('slowing')
|
||
);
|
||
const numberPattern = isPeriodValue ? /^\d*\.?\d*$/ : /^-?\d*\.?\d*$/;
|
||
|
||
// 빈 문자열이거나 숫자만 허용
|
||
if (newInputValue === '' || numberPattern.test(newInputValue)) {
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: newInputValue }));
|
||
}
|
||
} else if (reason === 'reset') {
|
||
// 리셋 시에는 현재 paramValue로 복원
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: String(paramValue) }));
|
||
} else if (reason === 'clear') {
|
||
// 클리어 시 빈 문자열
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: '' }));
|
||
}
|
||
}}
|
||
options={(() => {
|
||
const min = cfg.valueMin ?? 0;
|
||
const max = cfg.valueMax ?? 100;
|
||
const step = cfg.valueStep ?? 1;
|
||
const values: number[] = [];
|
||
for (let i = min; i <= max; i += step) {
|
||
values.push(i);
|
||
}
|
||
return values;
|
||
})()}
|
||
getOptionLabel={(option) => String(option)}
|
||
filterOptions={(options, state) => {
|
||
// 입력값으로 필터링
|
||
const inputValue = state.inputValue.toLowerCase();
|
||
if (!inputValue) return options;
|
||
return options.filter(option =>
|
||
String(option).toLowerCase().includes(inputValue)
|
||
);
|
||
}}
|
||
freeSolo
|
||
size="small"
|
||
fullWidth
|
||
disableClearable
|
||
renderInput={(params) => (
|
||
<TextField
|
||
{...params}
|
||
inputProps={{
|
||
...params.inputProps,
|
||
style: { textAlign: 'center', fontSize: '12px' }
|
||
}}
|
||
onBlur={(e) => {
|
||
// 포커스 아웃 시 실제 값 업데이트
|
||
const inputStr = e.target.value.trim();
|
||
if (inputStr === '') {
|
||
// 빈 값이면 원래 값으로 복원
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: String(paramValue) }));
|
||
return;
|
||
}
|
||
|
||
const numValue = Number(inputStr);
|
||
const min = cfg.valueMin ?? 0;
|
||
const max = cfg.valueMax ?? 100;
|
||
|
||
// 기준값(Period, Length 등)은 양수만 허용
|
||
const isPeriodValue = cfg.valueKey && (
|
||
cfg.valueKey.toLowerCase().includes('period') ||
|
||
cfg.valueKey.toLowerCase().includes('length') ||
|
||
cfg.valueKey.toLowerCase().includes('slowing')
|
||
);
|
||
|
||
if (!isNaN(numValue) && numValue >= min && numValue <= max) {
|
||
// 기준값인 경우 양수 확인
|
||
if (isPeriodValue && numValue < 0) {
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: String(paramValue) }));
|
||
return;
|
||
}
|
||
handleSettingChange(cfg.valueKey!, numValue);
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: String(numValue) }));
|
||
console.log(`🔄 [IndicatorSettingsPopup] ${cfg.label} - 입력 완료: ${numValue}`);
|
||
} else {
|
||
// 유효하지 않은 값이면 원래 값으로 복원
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: String(paramValue) }));
|
||
}
|
||
}}
|
||
onKeyDown={(e) => {
|
||
// 엔터 키 입력 시 실제 값 업데이트
|
||
if (e.key === 'Enter') {
|
||
const inputStr = (e.target as HTMLInputElement).value.trim();
|
||
if (inputStr === '') {
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: String(paramValue) }));
|
||
(e.target as HTMLInputElement).blur();
|
||
return;
|
||
}
|
||
|
||
const numValue = Number(inputStr);
|
||
const min = cfg.valueMin ?? 0;
|
||
const max = cfg.valueMax ?? 100;
|
||
|
||
// 기준값(Period, Length 등)은 양수만 허용
|
||
const isPeriodValue = cfg.valueKey && (
|
||
cfg.valueKey.toLowerCase().includes('period') ||
|
||
cfg.valueKey.toLowerCase().includes('length') ||
|
||
cfg.valueKey.toLowerCase().includes('slowing')
|
||
);
|
||
|
||
if (!isNaN(numValue) && numValue >= min && numValue <= max) {
|
||
// 기준값인 경우 양수 확인
|
||
if (isPeriodValue && numValue < 0) {
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: String(paramValue) }));
|
||
(e.target as HTMLInputElement).blur();
|
||
return;
|
||
}
|
||
handleSettingChange(cfg.valueKey!, numValue);
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: String(numValue) }));
|
||
console.log(`🔄 [IndicatorSettingsPopup] ${cfg.label} - 엔터 입력: ${numValue}`);
|
||
(e.target as HTMLInputElement).blur(); // 포커스 해제
|
||
} else {
|
||
// 유효하지 않은 값이면 원래 값으로 복원
|
||
setInputValues(prev => ({ ...prev, [cfg.valueKey!]: String(paramValue) }));
|
||
}
|
||
}
|
||
}}
|
||
sx={{
|
||
'& .MuiInputBase-root': {
|
||
height: 32,
|
||
fontSize: '12px'
|
||
},
|
||
'& .MuiInputBase-input': {
|
||
py: 0.5
|
||
}
|
||
}}
|
||
/>
|
||
)}
|
||
ListboxProps={{
|
||
style: { maxHeight: '200px', fontSize: '12px' }
|
||
}}
|
||
sx={{
|
||
'& .MuiAutocomplete-option': {
|
||
fontSize: '12px',
|
||
minHeight: '32px'
|
||
}
|
||
}}
|
||
/>
|
||
) : (
|
||
<Typography variant="caption" color="text.disabled">-</Typography>
|
||
)}
|
||
</Grid>
|
||
)}
|
||
{/* 색상 선택 */}
|
||
<Grid item xs={hasEnableColumn ? (hasValueColumn ? 1 : 1.5) : (hasValueColumn ? 1.2 : (!hasWidthColumn && !hasLineStyleColumn ? 4 : 1.5))} sx={{ display: 'flex', alignItems: 'center' }}>
|
||
{cfg.colorKey ? (
|
||
<Box sx={{ position: 'relative', display: 'inline-block' }}>
|
||
<Box
|
||
sx={{
|
||
width: 28,
|
||
height: 28,
|
||
borderRadius: 1,
|
||
bgcolor: colorValue,
|
||
border: '1px solid',
|
||
borderColor: 'divider',
|
||
cursor: 'pointer',
|
||
'&:hover': { boxShadow: '0 0 0 2px rgba(25, 118, 210, 0.5)' },
|
||
}}
|
||
/>
|
||
<input
|
||
type="color"
|
||
defaultValue={colorValue.startsWith('#') ? colorValue : '#666666'}
|
||
ref={(el) => {
|
||
if (el) {
|
||
colorInputRefsRef.current.set(cfg.colorKey!, el);
|
||
}
|
||
}}
|
||
onInput={(e) => {
|
||
// ✅ 색상 선택 중: 미리보기만 업데이트 (리렌더링 방지)
|
||
const target = e.target as HTMLInputElement;
|
||
const box = target.previousElementSibling as HTMLElement;
|
||
if (box) {
|
||
box.style.backgroundColor = target.value;
|
||
}
|
||
}}
|
||
style={{ position: 'absolute', top: 0, left: 0, width: 28, height: 28, opacity: 0, cursor: 'pointer' }}
|
||
/>
|
||
</Box>
|
||
) : (
|
||
<Typography variant="caption" color="text.disabled">-</Typography>
|
||
)}
|
||
</Grid>
|
||
{/* 선 굵기 슬라이더 */}
|
||
{hasWidthColumn && (
|
||
<Grid item xs={hasEnableColumn ? (hasValueColumn ? 2.5 : 2.5) : (hasValueColumn ? 3 : 3)} sx={{ display: 'flex', alignItems: 'center' }}>
|
||
{cfg.widthKey ? (
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: '100%' }}>
|
||
<Slider
|
||
value={widthValue}
|
||
onChange={(_, value) => handleColorChange(cfg.widthKey!, value as number)}
|
||
min={0.5}
|
||
max={5}
|
||
step={0.5}
|
||
size="small"
|
||
sx={{ flex: 1 }}
|
||
/>
|
||
<Typography variant="caption" sx={{ minWidth: 30, textAlign: 'right', fontSize: '11px' }}>
|
||
{widthValue}px
|
||
</Typography>
|
||
</Box>
|
||
) : (
|
||
<Typography variant="caption" color="text.disabled">-</Typography>
|
||
)}
|
||
</Grid>
|
||
)}
|
||
{/* 선 유형 선택 */}
|
||
{hasLineStyleColumn && (
|
||
<Grid item xs={hasEnableColumn ? (hasValueColumn ? 2 : 2.5) : (hasValueColumn ? 2.5 : 2.5)} sx={{ display: 'flex', alignItems: 'center' }}>
|
||
{cfg.lineStyleKey ? (
|
||
<Select
|
||
value={lineStyleValue}
|
||
onChange={(e) => handleColorChange(cfg.lineStyleKey!, e.target.value)}
|
||
size="small"
|
||
fullWidth
|
||
sx={{
|
||
fontSize: '11px',
|
||
height: 28,
|
||
'& .MuiSelect-select': { py: 0.5 }
|
||
}}
|
||
>
|
||
<MenuItem value="solid" sx={{ fontSize: '11px' }}>━━ 실선</MenuItem>
|
||
<MenuItem value="dashed" sx={{ fontSize: '11px' }}>╍╍ 대시</MenuItem>
|
||
<MenuItem value="dotted" sx={{ fontSize: '11px' }}>··· 점선</MenuItem>
|
||
</Select>
|
||
) : (
|
||
<Typography variant="caption" color="text.disabled">-</Typography>
|
||
)}
|
||
</Grid>
|
||
)}
|
||
{/* 미리보기 */}
|
||
<Grid item xs={hasEnableColumn ? (hasValueColumn ? (hasWidthColumn && hasLineStyleColumn ? 2.7 : 4) : (hasWidthColumn && hasLineStyleColumn ? 2.5 : 4)) : (hasValueColumn ? (hasWidthColumn && hasLineStyleColumn ? 3.5 : 4) : (hasWidthColumn && hasLineStyleColumn ? 2.5 : 4))} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||
{cfg.colorKey ? (
|
||
<svg width="100" height="20" style={{ display: 'block' }}>
|
||
<line
|
||
x1="0"
|
||
y1="10"
|
||
x2="100"
|
||
y2="10"
|
||
stroke={colorValue}
|
||
strokeWidth={widthValue}
|
||
strokeDasharray={
|
||
lineStyleValue === 'dashed' ? '5,5' :
|
||
lineStyleValue === 'dotted' ? '2,2' : '0'
|
||
}
|
||
/>
|
||
</svg>
|
||
) : (
|
||
<Typography variant="caption" color="text.disabled">-</Typography>
|
||
)}
|
||
</Grid>
|
||
</Grid>
|
||
</Box>
|
||
);
|
||
})}
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
// 보조지표별 설정 렌더링
|
||
const renderIndicatorSettings = () => {
|
||
switch (indicatorType) {
|
||
case 'volume':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: '상승', colorKey: 'volumeRising', enableKey: 'volumeRisingEnabled' },
|
||
{ label: '하락', colorKey: 'volumeFalling', enableKey: 'volumeFallingEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'macd':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'MACD', colorKey: 'macdLine', widthKey: 'macdLineWidth', lineStyleKey: 'macdLineStyle', valueKey: 'macdFastPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'macdEnabled' },
|
||
{ label: '장기 EMA', colorKey: 'slowEmaColor', widthKey: 'slowEmaWidth', lineStyleKey: 'slowEmaLineStyle', valueKey: 'macdSlowPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'macdEnabled' },
|
||
{ label: '시그널', colorKey: 'signalLine', widthKey: 'signalLineWidth', lineStyleKey: 'signalLineStyle', valueKey: 'macdSignalPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'macdEnabled' },
|
||
{ label: '0선', colorKey: 'macdZeroLineColor', widthKey: 'macdZeroLineWidth', lineStyleKey: 'macdZeroLineStyle', enableKey: 'macdZeroLineEnabled' },
|
||
{ label: '히스토그램(상승)', colorKey: 'histogramPositive', enableKey: 'histogramPositiveEnabled' },
|
||
{ label: '히스토그램(하락)', colorKey: 'histogramNegative', enableKey: 'histogramNegativeEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'stochastic':
|
||
return (
|
||
<>
|
||
<Box sx={{ mb: 2, p: 2, bgcolor: 'info.main', color: 'white', borderRadius: 1 }}>
|
||
<Typography variant="caption" sx={{ fontWeight: 'bold', display: 'block', mb: 0.5 }}>
|
||
ℹ️ Stochastic Slow 설명
|
||
</Typography>
|
||
<Typography variant="caption" sx={{ fontSize: '11px', display: 'block' }}>
|
||
• 기간: Raw Stochastic %K 계산 기간 (업비트 기본값: 12일)
|
||
</Typography>
|
||
<Typography variant="caption" sx={{ fontSize: '11px', display: 'block' }}>
|
||
• %K: Slow %K의 Smoothing 기간 (업비트 기본값: 5일)
|
||
</Typography>
|
||
<Typography variant="caption" sx={{ fontSize: '11px', display: 'block' }}>
|
||
• %D: Slow %D (시그널선) 계산 기간 (업비트 기본값: 5일)
|
||
</Typography>
|
||
</Box>
|
||
<ColorTable configs={[
|
||
{ label: '기간', colorKey: 'stochasticK', widthKey: 'stochasticKWidth', lineStyleKey: 'stochasticKLineStyle', valueKey: 'stochasticKPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'stochasticEnabled' },
|
||
{ label: '%K', colorKey: 'stochasticK', widthKey: 'stochasticKWidth', lineStyleKey: 'stochasticKLineStyle', valueKey: 'stochasticSlowing', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'stochasticEnabled' },
|
||
{ label: '%D', colorKey: 'stochasticD', widthKey: 'stochasticDWidth', lineStyleKey: 'stochasticDLineStyle', valueKey: 'stochasticDPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'stochasticEnabled' },
|
||
{ label: '과매도 기준선', colorKey: 'stochasticOversoldColor', widthKey: 'stochasticOversoldWidth', lineStyleKey: 'stochasticOversoldLineStyle', valueKey: 'stochasticOversold', valueMin: 0, valueMax: 50, valueStep: 1, enableKey: 'stochasticOversoldEnabled' },
|
||
{ label: '중앙값 기준선', colorKey: 'stochasticNeutralColor', widthKey: 'stochasticNeutralWidth', lineStyleKey: 'stochasticNeutralLineStyle', valueKey: 'stochasticNeutral', valueMin: 40, valueMax: 60, valueStep: 1, enableKey: 'stochasticNeutralEnabled' },
|
||
{ label: '과매수 기준선', colorKey: 'stochasticOverboughtColor', widthKey: 'stochasticOverboughtWidth', lineStyleKey: 'stochasticOverboughtLineStyle', valueKey: 'stochasticOverbought', valueMin: 50, valueMax: 100, valueStep: 1, enableKey: 'stochasticOverboughtEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'rsi':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'RSI', colorKey: 'rsiColor', widthKey: 'rsiWidth', lineStyleKey: 'rsiLineStyle', valueKey: 'rsiPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'rsiEnabled' },
|
||
{ label: '신호', colorKey: 'rsiSignalColor', widthKey: 'rsiSignalWidth', lineStyleKey: 'rsiSignalLineStyle', valueKey: 'rsiSignalPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'rsiSignalEnabled' },
|
||
{ label: '과매수', colorKey: 'rsiOverboughtColor', widthKey: 'rsiOverboughtWidth', lineStyleKey: 'rsiOverboughtLineStyle', valueKey: 'rsiOverbought', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'rsiOverboughtEnabled' },
|
||
{ label: '중앙값', colorKey: 'rsiMiddleColor', widthKey: 'rsiMiddleWidth', lineStyleKey: 'rsiMiddleLineStyle', valueKey: 'rsiMiddle', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'rsiMiddleEnabled' },
|
||
{ label: '과매도', colorKey: 'rsiOversoldColor', widthKey: 'rsiOversoldWidth', lineStyleKey: 'rsiOversoldLineStyle', valueKey: 'rsiOversold', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'rsiOversoldEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'adx':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'ADX', colorKey: 'adxColor', widthKey: 'adxWidth', lineStyleKey: 'adxLineStyle', valueKey: 'adxPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'adxEnabled' },
|
||
{ label: '+DI', colorKey: 'plusDiColor', widthKey: 'plusDiWidth', lineStyleKey: 'plusDiLineStyle', enableKey: 'plusDiEnabled' },
|
||
{ label: '-DI', colorKey: 'minusDiColor', widthKey: 'minusDiWidth', lineStyleKey: 'minusDiLineStyle', enableKey: 'minusDiEnabled' },
|
||
{ label: '강한 추세', colorKey: 'adxStrongColor', widthKey: 'adxStrongWidth', lineStyleKey: 'adxStrongLineStyle', valueKey: 'adxStrongThreshold', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'adxStrongEnabled' },
|
||
{ label: '약한 추세', colorKey: 'adxWeakColor', widthKey: 'adxWeakWidth', lineStyleKey: 'adxWeakLineStyle', valueKey: 'adxWeakThreshold', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'adxWeakEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'cci':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'CCI', colorKey: 'cciColor', widthKey: 'cciWidth', lineStyleKey: 'cciLineStyle', valueKey: 'cciPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'cciEnabled' },
|
||
{ label: '신호', colorKey: 'cciSignalColor', widthKey: 'cciSignalWidth', lineStyleKey: 'cciSignalLineStyle', valueKey: 'cciSignalPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'cciSignalEnabled' },
|
||
{ label: '과열', colorKey: 'cciOverboughtColor', widthKey: 'cciOverboughtWidth', lineStyleKey: 'cciOverboughtLineStyle', valueKey: 'cciOverbought', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'cciOverboughtEnabled' },
|
||
{ label: '중앙값', colorKey: 'cciMiddleColor', widthKey: 'cciMiddleWidth', lineStyleKey: 'cciMiddleLineStyle', valueKey: 'cciMiddle', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'cciMiddleEnabled' },
|
||
{ label: '침체', colorKey: 'cciOversoldColor', widthKey: 'cciOversoldWidth', lineStyleKey: 'cciOversoldLineStyle', valueKey: 'cciOversold', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'cciOversoldEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'trix':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'TRIX', colorKey: 'trixColor', widthKey: 'trixWidth', lineStyleKey: 'trixLineStyle', valueKey: 'trixPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'trixEnabled' },
|
||
{ label: '신호', colorKey: 'trixSignalColor', widthKey: 'trixSignalWidth', lineStyleKey: 'trixSignalLineStyle', valueKey: 'trixSignalPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'trixEnabled' },
|
||
{ label: '과매수', colorKey: 'trixOverboughtColor', widthKey: 'trixOverboughtWidth', lineStyleKey: 'trixOverboughtLineStyle', valueKey: 'trixOverbought', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'trixOverboughtEnabled' },
|
||
{ label: '중앙값', colorKey: 'trixMiddleColor', widthKey: 'trixMiddleWidth', lineStyleKey: 'trixMiddleLineStyle', valueKey: 'trixMiddle', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'trixMiddleEnabled' },
|
||
{ label: '과매도', colorKey: 'trixOversoldColor', widthKey: 'trixOversoldWidth', lineStyleKey: 'trixOversoldLineStyle', valueKey: 'trixOversold', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'trixOversoldEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'disparity':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: '초단기', colorKey: 'disparityUltraShortColor', widthKey: 'disparityUltraShortWidth', lineStyleKey: 'disparityUltraShortLineStyle', valueKey: 'disparityUltraShortPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'disparityUltraShortEnabled' },
|
||
{ label: '단기', colorKey: 'disparityShortColor', widthKey: 'disparityShortWidth', lineStyleKey: 'disparityShortLineStyle', valueKey: 'disparityShortPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'disparityShortEnabled' },
|
||
{ label: '중기', colorKey: 'disparityMidColor', widthKey: 'disparityMidWidth', lineStyleKey: 'disparityMidLineStyle', valueKey: 'disparityMidPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'disparityMidEnabled' },
|
||
{ label: '장기', colorKey: 'disparityLongColor', widthKey: 'disparityLongWidth', lineStyleKey: 'disparityLongLineStyle', valueKey: 'disparityLongPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'disparityLongEnabled' },
|
||
{ label: '100 기준선', colorKey: 'disparityBaseLineColor', widthKey: 'disparityBaseLineWidth', lineStyleKey: 'disparityBaseLineStyle', enableKey: 'disparityBaseLineEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'newPsychological':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: '신심리도', colorKey: 'newPsychologicalColor', widthKey: 'newPsychologicalWidth', lineStyleKey: 'newPsychologicalLineStyle', valueKey: 'newPsychologicalPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'newPsychologicalEnabled' },
|
||
{ label: '과열', colorKey: 'newPsychologicalOverboughtColor', widthKey: 'newPsychologicalOverboughtWidth', lineStyleKey: 'newPsychologicalOverboughtLineStyle', valueKey: 'newPsychologicalOverbought', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'newPsychologicalOverboughtEnabled' },
|
||
{ label: '중앙값', colorKey: 'newPsychologicalNeutralColor', widthKey: 'newPsychologicalNeutralWidth', lineStyleKey: 'newPsychologicalNeutralLineStyle', valueKey: 'newPsychologicalMiddle', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'newPsychologicalMiddleEnabled' },
|
||
{ label: '침체', colorKey: 'newPsychologicalOversoldColor', widthKey: 'newPsychologicalOversoldWidth', lineStyleKey: 'newPsychologicalOversoldLineStyle', valueKey: 'newPsychologicalOversold', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'newPsychologicalOversoldEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'investPsychological':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: '투자심리도', colorKey: 'investPsychologicalColor', widthKey: 'investPsychologicalWidth', lineStyleKey: 'investPsychologicalLineStyle', valueKey: 'investPsychologicalPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'investPsychologicalEnabled' },
|
||
{ label: '과열', colorKey: 'investPsychologicalOverboughtColor', widthKey: 'investPsychologicalOverboughtWidth', lineStyleKey: 'investPsychologicalOverboughtLineStyle', valueKey: 'investPsychologicalOverbought', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'investPsychologicalOverboughtEnabled' },
|
||
{ label: '중앙값', colorKey: 'investPsychologicalMiddleColor', widthKey: 'investPsychologicalMiddleWidth', lineStyleKey: 'investPsychologicalMiddleLineStyle', valueKey: 'investPsychologicalMiddle', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'investPsychologicalMiddleEnabled' },
|
||
{ label: '침체', colorKey: 'investPsychologicalOversoldColor', widthKey: 'investPsychologicalOversoldWidth', lineStyleKey: 'investPsychologicalOversoldLineStyle', valueKey: 'investPsychologicalOversold', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'investPsychologicalOversoldEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'williamsR':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'Williams%R', colorKey: 'williamsRColor', widthKey: 'williamsRWidth', lineStyleKey: 'williamsRLineStyle', valueKey: 'williamsRPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'williamsREnabled' },
|
||
{ label: '과열', colorKey: 'williamsROverboughtColor', widthKey: 'williamsROverboughtWidth', lineStyleKey: 'williamsROverboughtLineStyle', valueKey: 'williamsROverbought', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'williamsROverboughtEnabled' },
|
||
{ label: '중앙값', colorKey: 'williamsRMiddleColor', widthKey: 'williamsRMiddleWidth', lineStyleKey: 'williamsRMiddleLineStyle', valueKey: 'williamsRMiddle', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'williamsRMiddleEnabled' },
|
||
{ label: '침체', colorKey: 'williamsROversoldColor', widthKey: 'williamsROversoldWidth', lineStyleKey: 'williamsROversoldLineStyle', valueKey: 'williamsROversold', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'williamsROversoldEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'bwi':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'BWI', colorKey: 'bwiColor', widthKey: 'bwiWidth', lineStyleKey: 'bwiLineStyle', valueKey: 'bwiPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'bwiEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'obv':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'OBV', colorKey: 'obvColor', widthKey: 'obvWidth', lineStyleKey: 'obvLineStyle', enableKey: 'obvEnabled' },
|
||
{ label: '신호', colorKey: 'obvSignalColor', widthKey: 'obvSignalWidth', lineStyleKey: 'obvSignalLineStyle', valueKey: 'obvSignalPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'obvSignalEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'volumeOsc':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'VolumeOSC', colorKey: 'volumeOscColor', widthKey: 'volumeOscWidth', lineStyleKey: 'volumeOscLineStyle', enableKey: 'volumeOscEnabled' },
|
||
{ label: '단기', colorKey: 'volumeOscColor', widthKey: 'volumeOscWidth', lineStyleKey: 'volumeOscLineStyle', valueKey: 'volumeOscShortPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'volumeOscEnabled' },
|
||
{ label: '장기', colorKey: 'volumeOscColor', widthKey: 'volumeOscWidth', lineStyleKey: 'volumeOscLineStyle', valueKey: 'volumeOscLongPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'volumeOscEnabled' },
|
||
{ label: '0선', colorKey: 'volumeOscZeroLineColor', widthKey: 'volumeOscZeroLineWidth', lineStyleKey: 'volumeOscZeroLineStyle', enableKey: 'volumeOscZeroLineEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'vr':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'VR', colorKey: 'vrColor', widthKey: 'vrWidth', lineStyleKey: 'vrLineStyle', valueKey: 'vrPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'vrEnabled' },
|
||
{ label: 'VR2', colorKey: 'vr2Color', widthKey: 'vr2Width', lineStyleKey: 'vr2LineStyle', valueKey: 'vr2Period', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'vr2Enabled' },
|
||
{ label: '과열', colorKey: 'vrOverboughtColor', widthKey: 'vrOverboughtWidth', lineStyleKey: 'vrOverboughtLineStyle', valueKey: 'vrOverbought', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'vrOverboughtEnabled' },
|
||
{ label: '기준', colorKey: 'vrBaseLineColor', widthKey: 'vrBaseLineWidth', lineStyleKey: 'vrBaseLineStyle', valueKey: 'vrBaseLine', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'vrBaseLineEnabled' },
|
||
{ label: '침체', colorKey: 'vrOversoldColor', widthKey: 'vrOversoldWidth', lineStyleKey: 'vrOversoldLineStyle', valueKey: 'vrOversold', valueMin: -1000, valueMax: 1000, valueStep: 1, enableKey: 'vrOversoldEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'dmi':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: '기간', valueKey: 'dmiPeriod', valueMin: 1, valueMax: 500, valueStep: 1 },
|
||
{ label: 'DI+', colorKey: 'plusDiColor', widthKey: 'plusDiWidth', lineStyleKey: 'plusDiLineStyle', enableKey: 'plusDiEnabled' },
|
||
{ label: 'DI-', colorKey: 'minusDiColor', widthKey: 'minusDiWidth', lineStyleKey: 'minusDiLineStyle', enableKey: 'minusDiEnabled' },
|
||
{ label: '과열', colorKey: 'dmiOverboughtColor', widthKey: 'dmiOverboughtWidth', lineStyleKey: 'dmiOverboughtLineStyle', valueKey: 'dmiOverbought', valueMin: 0, valueMax: 100, valueStep: 1, enableKey: 'dmiOverboughtEnabled' },
|
||
{ label: '중간값', colorKey: 'dmiMiddleColor', widthKey: 'dmiMiddleWidth', lineStyleKey: 'dmiMiddleLineStyle', valueKey: 'dmiMiddle', valueMin: 0, valueMax: 100, valueStep: 1, enableKey: 'dmiMiddleEnabled' },
|
||
{ label: '침체', colorKey: 'dmiOversoldColor', widthKey: 'dmiOversoldWidth', lineStyleKey: 'dmiOversoldLineStyle', valueKey: 'dmiOversold', valueMin: 0, valueMax: 100, valueStep: 1, enableKey: 'dmiOversoldEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
case 'momentum':
|
||
return (
|
||
<>
|
||
<ColorTable configs={[
|
||
{ label: 'Momentum', colorKey: 'momentumColor', widthKey: 'momentumWidth', lineStyleKey: 'momentumLineStyle', valueKey: 'momentumPeriod', valueMin: 1, valueMax: 500, valueStep: 1, enableKey: 'momentumEnabled' },
|
||
{ label: '0선 기준선', colorKey: 'momentumZeroLineColor', widthKey: 'momentumZeroLineWidth', lineStyleKey: 'momentumZeroLineStyle', enableKey: 'momentumZeroLineEnabled' },
|
||
]} />
|
||
</>
|
||
);
|
||
|
||
default:
|
||
return <Typography>설정 항목이 없습니다.</Typography>;
|
||
}
|
||
};
|
||
|
||
// 드래그 가능한 Paper 컴포넌트
|
||
function DraggablePaper(props: PaperProps) {
|
||
return (
|
||
<Draggable
|
||
handle="#draggable-dialog-title"
|
||
cancel={'[class*="MuiDialogContent-root"], [class*="MuiIconButton-root"], [class*="MuiButton-root"]'}
|
||
position={dialogPosition}
|
||
onStop={(e, data) => {
|
||
setDialogPosition({ x: data.x, y: data.y });
|
||
}}
|
||
>
|
||
<Paper {...props} />
|
||
</Draggable>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<Dialog
|
||
open={open}
|
||
onClose={onClose}
|
||
maxWidth={false}
|
||
fullScreen={isMobile}
|
||
PaperComponent={isMobile ? Paper : DraggablePaper}
|
||
PaperProps={{
|
||
sx: {
|
||
bgcolor: 'background.paper',
|
||
backgroundImage: 'none',
|
||
width: isMobile ? '100%' : '900px',
|
||
minWidth: isMobile ? '100%' : '900px',
|
||
maxWidth: isMobile ? '100%' : '90vw',
|
||
minHeight: indicatorType === 'macd' ? '550px' : (indicatorType === 'cci' ? '450px' : '350px'),
|
||
maxHeight: '80vh',
|
||
resize: isMobile ? 'none' : 'both',
|
||
overflow: 'auto',
|
||
'&::after': isMobile ? {} : {
|
||
content: '""',
|
||
position: 'absolute',
|
||
right: 0,
|
||
bottom: 0,
|
||
width: '16px',
|
||
height: '16px',
|
||
background: 'linear-gradient(135deg, transparent 50%, rgba(0,0,0,0.2) 50%)',
|
||
cursor: 'nwse-resize',
|
||
pointerEvents: 'none',
|
||
},
|
||
},
|
||
}}
|
||
>
|
||
<DialogTitle
|
||
id="draggable-dialog-title"
|
||
sx={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
bgcolor: '#1976d2',
|
||
color: '#fff',
|
||
py: 1.2,
|
||
cursor: isMobile ? 'default' : 'move',
|
||
}}
|
||
>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
<SettingsIcon sx={{ color: '#fff' }} />
|
||
<Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px', color: '#fff' }}>
|
||
{info.name} 설정
|
||
</Typography>
|
||
<IconButton
|
||
onClick={() => setShowHelpDialog(true)}
|
||
size="small"
|
||
sx={{
|
||
ml: 0.5,
|
||
color: '#fff',
|
||
'&:hover': { bgcolor: 'rgba(255,255,255,0.1)' }
|
||
}}
|
||
>
|
||
<HelpOutlineIcon fontSize="small" />
|
||
</IconButton>
|
||
</Box>
|
||
<Box
|
||
sx={{ display: 'flex', alignItems: 'center', gap: 1 }}
|
||
onMouseDown={(e) => e.stopPropagation()}
|
||
>
|
||
<Button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
onClose();
|
||
}}
|
||
sx={{
|
||
color: '#fff',
|
||
borderColor: '#fff',
|
||
'&:hover': {
|
||
bgcolor: 'rgba(255,255,255,0.1)',
|
||
borderColor: '#fff'
|
||
},
|
||
minWidth: '70px',
|
||
fontSize: '13px',
|
||
fontWeight: 600,
|
||
}}
|
||
variant="outlined"
|
||
size="small"
|
||
>
|
||
취소
|
||
</Button>
|
||
<Button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
handleSave();
|
||
}}
|
||
sx={{
|
||
bgcolor: '#fff',
|
||
color: '#1976d2',
|
||
'&:hover': {
|
||
bgcolor: 'rgba(255,255,255,0.9)'
|
||
},
|
||
minWidth: '70px',
|
||
fontSize: '13px',
|
||
fontWeight: 600,
|
||
}}
|
||
variant="contained"
|
||
size="small"
|
||
>
|
||
적용
|
||
</Button>
|
||
</Box>
|
||
</DialogTitle>
|
||
|
||
<DialogContent sx={{
|
||
pt: 1.5,
|
||
pb: 2,
|
||
px: 2
|
||
}}>
|
||
{renderIndicatorSettings()}
|
||
</DialogContent>
|
||
|
||
{/* ✅ 하단 적용 버튼 (테스트용) */}
|
||
<DialogActions sx={{
|
||
px: 2,
|
||
pb: 2,
|
||
pt: 1,
|
||
justifyContent: 'flex-end',
|
||
borderTop: '1px solid',
|
||
borderColor: 'divider'
|
||
}}>
|
||
<Button
|
||
onClick={handleSave}
|
||
variant="contained"
|
||
color="primary"
|
||
size="medium"
|
||
sx={{
|
||
minWidth: '100px',
|
||
fontWeight: 600,
|
||
}}
|
||
>
|
||
적용
|
||
</Button>
|
||
</DialogActions>
|
||
</Dialog>
|
||
|
||
{/* 상세 설명 다이얼로그 */}
|
||
<Dialog
|
||
open={showHelpDialog}
|
||
onClose={() => setShowHelpDialog(false)}
|
||
maxWidth="md"
|
||
fullWidth
|
||
fullScreen={isMobile}
|
||
PaperProps={{
|
||
sx: {
|
||
bgcolor: 'background.paper',
|
||
backgroundImage: 'none',
|
||
},
|
||
}}
|
||
>
|
||
<DialogTitle sx={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
pb: 1,
|
||
borderBottom: '1px solid',
|
||
borderColor: 'divider',
|
||
bgcolor: 'primary.main',
|
||
color: 'white',
|
||
}}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
<HelpOutlineIcon />
|
||
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||
{info.name} 상세 설명
|
||
</Typography>
|
||
</Box>
|
||
<IconButton onClick={() => setShowHelpDialog(false)} size="small" sx={{ color: 'white' }}>
|
||
<CloseIcon />
|
||
</IconButton>
|
||
</DialogTitle>
|
||
|
||
<DialogContent sx={{ pt: 3, pb: 3, maxHeight: isMobile ? '65vh' : '55vh', overflowY: 'auto' }}>
|
||
{getIndicatorDetailedHelp(indicatorType)}
|
||
</DialogContent>
|
||
|
||
<DialogActions sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
|
||
<Button onClick={() => setShowHelpDialog(false)} variant="contained" color="primary" fullWidth={isMobile}>
|
||
확인
|
||
</Button>
|
||
</DialogActions>
|
||
</Dialog>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default IndicatorSettingsPopup;
|
||
|