1706 lines
69 KiB
TypeScript
1706 lines
69 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
||
import {
|
||
Box,
|
||
TextField,
|
||
Grid,
|
||
Typography,
|
||
Button,
|
||
Alert,
|
||
Divider,
|
||
Accordion,
|
||
AccordionSummary,
|
||
AccordionDetails,
|
||
Dialog,
|
||
DialogTitle,
|
||
DialogContent,
|
||
DialogActions,
|
||
Chip,
|
||
IconButton,
|
||
Tooltip,
|
||
CircularProgress,
|
||
Switch,
|
||
FormControlLabel,
|
||
FormControl,
|
||
Select,
|
||
MenuItem,
|
||
InputLabel,
|
||
} from '@mui/material';
|
||
import { RestartAlt, ExpandMore, CheckCircle, Error, Palette, Add, Delete, Star, StarBorder, HelpOutline, Close } from '@mui/icons-material';
|
||
import { useChartColors, ChartColorSettings } from '../../contexts/ChartColorContext';
|
||
import {
|
||
useIndicatorSettings,
|
||
IndicatorSettings,
|
||
DEFAULT_INDICATOR_SETTINGS,
|
||
} from '../../contexts/IndicatorSettingsContext';
|
||
import {
|
||
getIndicatorPeriods,
|
||
addPeriod,
|
||
updatePeriod,
|
||
deletePeriod,
|
||
invalidatePeriodsCache,
|
||
IndicatorPeriodsResponse,
|
||
PeriodInfo,
|
||
} from '../../services/indicatorPeriodApi';
|
||
|
||
// 타입과 기본값을 Context에서 재내보내기 (하위 호환성 유지)
|
||
export type { IndicatorSettings } from '../../contexts/IndicatorSettingsContext';
|
||
export {
|
||
DEFAULT_INDICATOR_SETTINGS,
|
||
loadIndicatorSettingsFromStorage as loadIndicatorSettings,
|
||
saveIndicatorSettingsToStorage as saveIndicatorSettings,
|
||
} from '../../contexts/IndicatorSettingsContext';
|
||
|
||
// 기간 관리 컴포넌트
|
||
interface PeriodManagerProps {
|
||
indicatorType: string;
|
||
label: string;
|
||
}
|
||
|
||
const PeriodManager: React.FC<PeriodManagerProps> = ({ indicatorType, label }) => {
|
||
const [periods, setPeriods] = useState<IndicatorPeriodsResponse | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [newPeriod, setNewPeriod] = useState<string>('');
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [success, setSuccess] = useState<string | null>(null);
|
||
|
||
const loadPeriods = useCallback(async () => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const data = await getIndicatorPeriods(indicatorType);
|
||
setPeriods(data);
|
||
} catch (err: any) {
|
||
console.error('기간 로드 실패:', err);
|
||
// API 오류 시 빈 응답으로 처리 (데이터 추가 가능하도록)
|
||
setPeriods({
|
||
indicatorType,
|
||
indicatorDisplayName: label,
|
||
periods: [],
|
||
defaultPeriod: null,
|
||
});
|
||
setError(`${label} 기간 데이터를 불러올 수 없습니다. 새 기간을 추가해주세요.`);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [indicatorType, label]);
|
||
|
||
useEffect(() => {
|
||
loadPeriods();
|
||
}, [loadPeriods]);
|
||
|
||
const handleAddPeriod = async () => {
|
||
const periodNum = parseInt(newPeriod);
|
||
if (!periodNum || periodNum < 1) return;
|
||
|
||
try {
|
||
setError(null);
|
||
await addPeriod({
|
||
indicatorType,
|
||
period: periodNum,
|
||
isDefault: (periods?.periods.length || 0) === 0, // 첫 번째면 기본값
|
||
enabled: true,
|
||
});
|
||
invalidatePeriodsCache();
|
||
setNewPeriod('');
|
||
setSuccess(`${periodNum}일 기간이 추가되었습니다.`);
|
||
setTimeout(() => setSuccess(null), 2000);
|
||
loadPeriods();
|
||
} catch (err: any) {
|
||
const message = err.response?.data?.message || err.response?.data || '추가 실패';
|
||
setError(typeof message === 'string' ? message : JSON.stringify(message));
|
||
}
|
||
};
|
||
|
||
const handleDeletePeriod = async (id: number, period: number) => {
|
||
if (!window.confirm(`${period}일 기간을 삭제하시겠습니까?`)) return;
|
||
|
||
try {
|
||
setError(null);
|
||
await deletePeriod(id);
|
||
invalidatePeriodsCache();
|
||
setSuccess('삭제되었습니다.');
|
||
setTimeout(() => setSuccess(null), 2000);
|
||
loadPeriods();
|
||
} catch (err: any) {
|
||
setError('삭제 실패: ' + (err.response?.data?.message || err.message));
|
||
}
|
||
};
|
||
|
||
const handleSetDefault = async (id: number, period: number) => {
|
||
try {
|
||
setError(null);
|
||
await updatePeriod(id, { isDefault: true });
|
||
invalidatePeriodsCache();
|
||
setSuccess(`${period}일이 기본값으로 설정되었습니다.`);
|
||
setTimeout(() => setSuccess(null), 2000);
|
||
loadPeriods();
|
||
} catch (err: any) {
|
||
setError('기본값 설정 실패: ' + (err.response?.data?.message || err.message));
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, py: 1 }}>
|
||
<CircularProgress size={16} />
|
||
<Typography variant="body2" color="text.secondary">기간 로드 중...</Typography>
|
||
</Box>
|
||
);
|
||
}
|
||
|
||
const hasPeriods = periods && periods.periods && periods.periods.length > 0;
|
||
|
||
return (
|
||
<Box sx={{ mt: 2, p: 2, bgcolor: 'action.hover', borderRadius: 1 }}>
|
||
<Typography variant="subtitle2" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
📊 전략 규칙용 기간 관리 ({label})
|
||
</Typography>
|
||
|
||
{error && (
|
||
<Alert severity="warning" sx={{ mb: 1, py: 0 }} onClose={() => setError(null)}>
|
||
{error}
|
||
</Alert>
|
||
)}
|
||
|
||
{success && (
|
||
<Alert severity="success" sx={{ mb: 1, py: 0 }} onClose={() => setSuccess(null)}>
|
||
{success}
|
||
</Alert>
|
||
)}
|
||
|
||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 2, minHeight: 32, alignItems: 'center' }}>
|
||
{hasPeriods ? (
|
||
periods.periods.map((p: PeriodInfo) => (
|
||
<Chip
|
||
key={p.id}
|
||
label={`${p.period}일`}
|
||
color={p.isDefault ? 'primary' : 'default'}
|
||
variant={p.enabled ? 'filled' : 'outlined'}
|
||
size="small"
|
||
icon={p.isDefault ? <Star fontSize="small" /> : undefined}
|
||
onDelete={() => handleDeletePeriod(p.id, p.period)}
|
||
onClick={() => !p.isDefault && handleSetDefault(p.id, p.period)}
|
||
sx={{
|
||
cursor: p.isDefault ? 'default' : 'pointer',
|
||
'&:hover': { opacity: p.isDefault ? 1 : 0.8 }
|
||
}}
|
||
/>
|
||
))
|
||
) : (
|
||
<Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>
|
||
등록된 기간이 없습니다. 아래에서 기간을 추가해주세요.
|
||
</Typography>
|
||
)}
|
||
</Box>
|
||
|
||
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap' }}>
|
||
<TextField
|
||
size="small"
|
||
type="number"
|
||
placeholder="기간 (일)"
|
||
value={newPeriod}
|
||
onChange={(e) => setNewPeriod(e.target.value)}
|
||
onKeyPress={(e) => e.key === 'Enter' && handleAddPeriod()}
|
||
sx={{ width: 100 }}
|
||
inputProps={{ min: 1, max: 500 }}
|
||
/>
|
||
<Button
|
||
size="small"
|
||
variant="contained"
|
||
color="primary"
|
||
startIcon={<Add />}
|
||
onClick={handleAddPeriod}
|
||
disabled={!newPeriod || parseInt(newPeriod) < 1}
|
||
>
|
||
기간 추가
|
||
</Button>
|
||
<Typography variant="caption" color="text.secondary" sx={{ ml: 1 }}>
|
||
💡 칩 클릭: 기본값 설정 | ⭐ 기본값 | ✕ 삭제
|
||
</Typography>
|
||
</Box>
|
||
|
||
{!hasPeriods && (
|
||
<Alert severity="info" sx={{ mt: 2, py: 0.5 }}>
|
||
<Typography variant="caption">
|
||
전략 규칙 생성 시 선택할 수 있는 기간을 추가해주세요.
|
||
</Typography>
|
||
</Alert>
|
||
)}
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
// 색상 선택 컴포넌트
|
||
const ColorPicker: React.FC<{
|
||
label: string;
|
||
value: string;
|
||
onChange: (color: string) => void;
|
||
}> = ({ label, value, onChange }) => (
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
|
||
<Box
|
||
sx={{
|
||
width: 32,
|
||
height: 32,
|
||
borderRadius: 1,
|
||
bgcolor: value,
|
||
border: '2px solid',
|
||
borderColor: 'divider',
|
||
cursor: 'pointer',
|
||
position: 'relative',
|
||
overflow: 'hidden',
|
||
}}
|
||
>
|
||
<input
|
||
type="color"
|
||
value={value}
|
||
onChange={(e) => onChange(e.target.value)}
|
||
style={{
|
||
position: 'absolute',
|
||
top: 0,
|
||
left: 0,
|
||
width: '100%',
|
||
height: '100%',
|
||
opacity: 0,
|
||
cursor: 'pointer',
|
||
}}
|
||
/>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ minWidth: 100 }}>{label}</Typography>
|
||
<TextField
|
||
size="small"
|
||
value={value}
|
||
onChange={(e) => onChange(e.target.value)}
|
||
sx={{ width: 100 }}
|
||
inputProps={{ style: { fontSize: 12 } }}
|
||
/>
|
||
</Box>
|
||
);
|
||
|
||
interface IndicatorSettingsTabProps {
|
||
onSave?: (settings: IndicatorSettings) => void;
|
||
}
|
||
|
||
const IndicatorSettingsTab: React.FC<IndicatorSettingsTabProps> = ({ onSave }) => {
|
||
const { indicatorSettings, saveIndicatorSettings, resetIndicatorSettings } = useIndicatorSettings();
|
||
const [settings, setSettings] = useState<IndicatorSettings>(indicatorSettings);
|
||
|
||
const { colors, updateColors } = useChartColors();
|
||
const [localColors, setLocalColors] = useState<Partial<ChartColorSettings>>({
|
||
macdLine: colors.macdLine,
|
||
signalLine: colors.signalLine,
|
||
histogramPositive: colors.histogramPositive,
|
||
histogramNegative: colors.histogramNegative,
|
||
stochasticK: colors.stochasticK,
|
||
stochasticD: colors.stochasticD,
|
||
});
|
||
|
||
const [resultDialogOpen, setResultDialogOpen] = useState(false);
|
||
const [saveResult, setSaveResult] = useState<{ success: boolean; message: string }>({ success: false, message: '' });
|
||
const [macdHelpOpen, setMacdHelpOpen] = useState(false);
|
||
|
||
// Context의 설정값이 변경되면 로컬 상태도 동기화
|
||
useEffect(() => {
|
||
setSettings(indicatorSettings);
|
||
}, [indicatorSettings]);
|
||
|
||
// 설정 변경 시 즉시 Context에 반영 (실시간 차트 업데이트)
|
||
const handleSettingChange = (newSettings: Partial<IndicatorSettings>) => {
|
||
const updated = { ...settings, ...newSettings };
|
||
setSettings(updated);
|
||
// 즉시 Context에 반영하여 차트에 실시간 적용
|
||
saveIndicatorSettings(updated);
|
||
};
|
||
|
||
const handleSave = () => {
|
||
try {
|
||
saveIndicatorSettings(settings);
|
||
if (onSave) {
|
||
onSave(settings);
|
||
}
|
||
updateColors(localColors);
|
||
|
||
setSaveResult({
|
||
success: true,
|
||
message: `보조지표 설정이 저장되었습니다.\n\n` +
|
||
`신심리도: (${settings.newPsychologicalPeriod})\n` +
|
||
`투자심리도: (${settings.investPsychologicalPeriod})\n` +
|
||
`MACD: (${settings.macdFastPeriod}, ${settings.macdSlowPeriod}, ${settings.macdSignalPeriod})\n` +
|
||
`CCI: (${settings.cciPeriod}, ${settings.cciSignalPeriod})\n` +
|
||
`ADX: (${settings.adxPeriod})\n` +
|
||
`BWI: (${settings.bwiPeriod})\n` +
|
||
`DMI: (${settings.dmiPeriod})\n` +
|
||
`OBV: (${settings.obvPeriod})\n` +
|
||
`RSI: (${settings.rsiPeriod})\n` +
|
||
`Stochastic Slow: (${settings.stochasticKPeriod}, ${settings.stochasticDPeriod}, ${settings.stochasticSlowing})\n` +
|
||
`Williams %R: (${settings.williamsRPeriod})\n` +
|
||
`TRIX: (${settings.trixPeriod}, ${settings.trixSignalPeriod})\n` +
|
||
`VolumeOSC: (${settings.volumeOscShortPeriod}, ${settings.volumeOscLongPeriod})\n` +
|
||
`VR: (${settings.vrPeriod})\n` +
|
||
`이격도: (초단기:${settings.disparityUltraShortPeriod}, 단기:${settings.disparityShortPeriod}, 중기:${settings.disparityMidPeriod}, 장기:${settings.disparityLongPeriod})`
|
||
});
|
||
setResultDialogOpen(true);
|
||
} catch (error) {
|
||
setSaveResult({ success: false, message: `저장 중 오류가 발생했습니다: ${error}` });
|
||
setResultDialogOpen(true);
|
||
}
|
||
};
|
||
|
||
const handleResetAll = () => {
|
||
if (window.confirm('모든 보조지표 설정을 기본값으로 초기화하시겠습니까?')) {
|
||
resetIndicatorSettings();
|
||
setSettings(DEFAULT_INDICATOR_SETTINGS);
|
||
if (onSave) onSave(DEFAULT_INDICATOR_SETTINGS);
|
||
setSaveResult({
|
||
success: true,
|
||
message: `모든 보조지표 설정이 기본값으로 초기화되었습니다.`
|
||
});
|
||
setResultDialogOpen(true);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Box>
|
||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||
<Typography variant="h6">보조지표 파라미터 설정 (15가지)</Typography>
|
||
<Button
|
||
variant="outlined"
|
||
size="small"
|
||
startIcon={<RestartAlt />}
|
||
onClick={handleResetAll}
|
||
color="secondary"
|
||
>
|
||
전체 초기화
|
||
</Button>
|
||
</Box>
|
||
|
||
<Alert severity="info" sx={{ mb: 3 }}>
|
||
<Typography variant="body2">
|
||
차트에서 사용되는 15가지 보조지표의 파라미터를 설정합니다. 변경된 설정은 <strong>즉시 차트에 반영</strong>됩니다.
|
||
</Typography>
|
||
</Alert>
|
||
|
||
{/* 1. 신심리도 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
1️⃣ 신심리도 (New Psychological Line)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="기간"
|
||
type="number"
|
||
value={settings.newPsychologicalPeriod}
|
||
onChange={(e) => handleSettingChange({ newPsychologicalPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 5"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="침체 기준"
|
||
type="number"
|
||
value={settings.newPsychologicalOversold}
|
||
onChange={(e) => handleSettingChange({ newPsychologicalOversold: Number(e.target.value) })}
|
||
inputProps={{ min: -100, max: 100 }}
|
||
helperText="기본값: -50"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="중간"
|
||
type="number"
|
||
value={settings.newPsychologicalMiddle}
|
||
onChange={(e) => handleSettingChange({ newPsychologicalMiddle: Number(e.target.value) })}
|
||
inputProps={{ min: -100, max: 100 }}
|
||
helperText="기본값: 0"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="과열 기준"
|
||
type="number"
|
||
value={settings.newPsychologicalOverbought}
|
||
onChange={(e) => handleSettingChange({ newPsychologicalOverbought: Number(e.target.value) })}
|
||
inputProps={{ min: -100, max: 100 }}
|
||
helperText="기본값: 50"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
신심리도는 시장 참여자들의 심리 상태를 측정하는 지표입니다.
|
||
상승일수 비율로 과매수/과매도를 판단합니다.
|
||
</Typography>
|
||
</Alert>
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 2. 투자심리도 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
2️⃣ 투자심리도 (Investor Psychological Line)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="기간"
|
||
type="number"
|
||
value={settings.investPsychologicalPeriod}
|
||
onChange={(e) => handleSettingChange({ investPsychologicalPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 10"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
투자심리도는 일정 기간 동안의 상승일 비율을 백분율로 표시합니다.
|
||
75% 이상은 과매수, 25% 이하는 과매도로 판단합니다.
|
||
</Typography>
|
||
</Alert>
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 3. MACD 설정 */}
|
||
<Accordion defaultExpanded sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flex: 1 }}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
3️⃣ MACD (Moving Average Convergence Divergence)
|
||
</Typography>
|
||
<IconButton
|
||
size="small"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
setMacdHelpOpen(true);
|
||
}}
|
||
sx={{
|
||
color: 'info.main',
|
||
'&:hover': { bgcolor: 'info.light', color: 'info.dark' }
|
||
}}
|
||
>
|
||
<HelpOutline fontSize="small" />
|
||
</IconButton>
|
||
</Box>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="Fast EMA 기간"
|
||
type="number"
|
||
value={settings.macdFastPeriod}
|
||
onChange={(e) => handleSettingChange({ macdFastPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 12"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="Slow EMA 기간"
|
||
type="number"
|
||
value={settings.macdSlowPeriod}
|
||
onChange={(e) => handleSettingChange({ macdSlowPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 100 }}
|
||
helperText="기본값: 26"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="Signal 기간"
|
||
type="number"
|
||
value={settings.macdSignalPeriod}
|
||
onChange={(e) => handleSettingChange({ macdSignalPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 9"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
<Divider sx={{ my: 3 }} />
|
||
|
||
<Box sx={{ mt: 2 }}>
|
||
<Typography variant="subtitle2" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
<Palette fontSize="small" /> 라인 색상 설정
|
||
</Typography>
|
||
<Grid container spacing={2} sx={{ mt: 1 }}>
|
||
<Grid item xs={12} sm={6} md={3}>
|
||
<ColorPicker
|
||
label="MACD 라인"
|
||
value={localColors.macdLine || '#f44336'}
|
||
onChange={(color) => setLocalColors({ ...localColors, macdLine: color })}
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} sm={6} md={3}>
|
||
<ColorPicker
|
||
label="Signal 라인"
|
||
value={localColors.signalLine || '#ff9800'}
|
||
onChange={(color) => setLocalColors({ ...localColors, signalLine: color })}
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} sm={6} md={3}>
|
||
<ColorPicker
|
||
label="히스토그램 (+)"
|
||
value={localColors.histogramPositive || '#4caf50'}
|
||
onChange={(color) => setLocalColors({ ...localColors, histogramPositive: color })}
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} sm={6} md={3}>
|
||
<ColorPicker
|
||
label="히스토그램 (-)"
|
||
value={localColors.histogramNegative || '#f44336'}
|
||
onChange={(color) => setLocalColors({ ...localColors, histogramNegative: color })}
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
</Box>
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 4. CCI 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
4️⃣ CCI (Commodity Channel Index)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="CCI 기간"
|
||
type="number"
|
||
value={settings.cciPeriod}
|
||
onChange={(e) => handleSettingChange({ cciPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 5, max: 50 }}
|
||
helperText="기본값: 13"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="Signal 기간"
|
||
type="number"
|
||
value={settings.cciSignalPeriod}
|
||
onChange={(e) => handleSettingChange({ cciSignalPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 10"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="과매도 기준"
|
||
type="number"
|
||
value={settings.cciOversold}
|
||
onChange={(e) => handleSettingChange({ cciOversold: Number(e.target.value) })}
|
||
inputProps={{ min: -300, max: -50 }}
|
||
helperText="기본값: -100"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="중앙값"
|
||
type="number"
|
||
value={settings.cciMiddle}
|
||
onChange={(e) => handleSettingChange({ cciMiddle: Number(e.target.value) })}
|
||
inputProps={{ min: -50, max: 50 }}
|
||
helperText="기본값: 0"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="과매수 기준"
|
||
type="number"
|
||
value={settings.cciOverbought}
|
||
onChange={(e) => handleSettingChange({ cciOverbought: Number(e.target.value) })}
|
||
inputProps={{ min: 50, max: 300 }}
|
||
helperText="기본값: 100"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<PeriodManager indicatorType="CCI" label="CCI" />
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 5. ADX 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
5️⃣ ADX (Average Directional Index)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="ADX 기간"
|
||
type="number"
|
||
value={settings.adxPeriod}
|
||
onChange={(e) => handleSettingChange({ adxPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 5, max: 50 }}
|
||
helperText="기본값: 11"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="강한 추세 기준"
|
||
type="number"
|
||
value={settings.adxStrongThreshold}
|
||
onChange={(e) => handleSettingChange({ adxStrongThreshold: Number(e.target.value) })}
|
||
inputProps={{ min: 15, max: 50 }}
|
||
helperText="기본값: 25"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
ADX가 강한 추세 기준값 이상일 때 추세가 강하다고 판단합니다.
|
||
</Typography>
|
||
</Alert>
|
||
<PeriodManager indicatorType="ADX" label="ADX" />
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 6. BWI 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
6️⃣ BWI (Bollinger Band Width Index)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="기간"
|
||
type="number"
|
||
value={settings.bwiPeriod}
|
||
onChange={(e) => handleSettingChange({ bwiPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 5, max: 50 }}
|
||
helperText="기본값: 20"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
BWI는 볼린저 밴드의 폭을 측정하여 변동성을 나타내는 지표입니다.
|
||
값이 클수록 변동성이 높고, 값이 작을수록 변동성이 낮습니다.
|
||
</Typography>
|
||
</Alert>
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 7. DMI 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
7️⃣ DMI (Directional Movement Index)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="DMI 기간"
|
||
type="number"
|
||
value={settings.dmiPeriod}
|
||
onChange={(e) => handleSettingChange({ dmiPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 5, max: 50 }}
|
||
helperText="기본값: 14"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
DMI는 +DI와 -DI로 구성되며 추세의 방향을 나타냅니다.
|
||
+DI {'>'} -DI 이면 상승 추세, -DI {'>'} +DI 이면 하락 추세입니다.
|
||
</Typography>
|
||
</Alert>
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 8. OBV 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
8️⃣ OBV (On Balance Volume)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="이동평균 기간"
|
||
type="number"
|
||
value={settings.obvPeriod}
|
||
onChange={(e) => handleSettingChange({ obvPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 9"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
OBV는 거래량의 누적치로 가격 움직임의 선행 지표입니다.
|
||
가격이 상승하면 거래량을 더하고, 하락하면 거래량을 뺍니다.
|
||
</Typography>
|
||
</Alert>
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 9. RSI 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
9️⃣ RSI (Relative Strength Index)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="RSI 기간"
|
||
type="number"
|
||
value={settings.rsiPeriod}
|
||
onChange={(e) => handleSettingChange({ rsiPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 9"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="과매도 기준"
|
||
type="number"
|
||
value={settings.rsiOversold}
|
||
onChange={(e) => handleSettingChange({ rsiOversold: Number(e.target.value) })}
|
||
inputProps={{ min: 0, max: 100 }}
|
||
helperText="기본값: 30 (매수 신호)"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="중앙값"
|
||
type="number"
|
||
value={settings.rsiMiddle}
|
||
onChange={(e) => handleSettingChange({ rsiMiddle: Number(e.target.value) })}
|
||
inputProps={{ min: 0, max: 100 }}
|
||
helperText="기본값: 50"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="과매수 기준"
|
||
type="number"
|
||
value={settings.rsiOverbought}
|
||
onChange={(e) => handleSettingChange({ rsiOverbought: Number(e.target.value) })}
|
||
inputProps={{ min: 0, max: 100 }}
|
||
helperText="기본값: 70 (매도 신호)"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<PeriodManager indicatorType="RSI" label="RSI" />
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 10. Stochastic Slow 설정 */}
|
||
<Accordion defaultExpanded sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
🔟 Stochastic Slow
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Typography variant="subtitle2" gutterBottom sx={{ mb: 2, color: 'text.secondary' }}>
|
||
기간 설정
|
||
</Typography>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="기간"
|
||
type="number"
|
||
value={settings.stochasticKPeriod}
|
||
onChange={(e) => handleSettingChange({ stochasticKPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 12"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="%K"
|
||
type="number"
|
||
value={settings.stochasticSlowing}
|
||
onChange={(e) => handleSettingChange({ stochasticSlowing: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 5"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="%D"
|
||
type="number"
|
||
value={settings.stochasticDPeriod}
|
||
onChange={(e) => handleSettingChange({ stochasticDPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 5"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
<Divider sx={{ my: 3 }} />
|
||
|
||
<Typography variant="subtitle2" gutterBottom sx={{ mb: 2, color: 'text.secondary' }}>
|
||
매매 기준값 설정
|
||
</Typography>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="과매도 기준"
|
||
type="number"
|
||
value={settings.stochasticOversold}
|
||
onChange={(e) => handleSettingChange({ stochasticOversold: Number(e.target.value) })}
|
||
inputProps={{ min: 5, max: 40 }}
|
||
helperText="기본값: 20 (매수 신호)"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="중앙값 기준"
|
||
type="number"
|
||
value={settings.stochasticNeutral}
|
||
onChange={(e) => handleSettingChange({ stochasticNeutral: Number(e.target.value) })}
|
||
inputProps={{ min: 40, max: 60 }}
|
||
helperText="기본값: 50 (중립 구간)"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="과매수 기준"
|
||
type="number"
|
||
value={settings.stochasticOverbought}
|
||
onChange={(e) => handleSettingChange({ stochasticOverbought: Number(e.target.value) })}
|
||
inputProps={{ min: 60, max: 95 }}
|
||
helperText="기본값: 80 (매도 신호)"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
<Divider sx={{ my: 3 }} />
|
||
|
||
<Box sx={{ mt: 2 }}>
|
||
<Typography variant="subtitle2" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
<Palette fontSize="small" /> 라인 색상 설정
|
||
</Typography>
|
||
<Grid container spacing={2} sx={{ mt: 1 }}>
|
||
<Grid item xs={12} sm={6}>
|
||
<ColorPicker
|
||
label="%K 라인"
|
||
value={localColors.stochasticK || '#2196f3'}
|
||
onChange={(color) => setLocalColors({ ...localColors, stochasticK: color })}
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} sm={6}>
|
||
<ColorPicker
|
||
label="%D 라인"
|
||
value={localColors.stochasticD || '#ff5722'}
|
||
onChange={(color) => setLocalColors({ ...localColors, stochasticD: color })}
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
</Box>
|
||
|
||
<Box sx={{ mt: 3 }}>
|
||
<Typography variant="subtitle2" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
<Palette fontSize="small" /> 기준선 색상 설정
|
||
</Typography>
|
||
<Grid container spacing={2} sx={{ mt: 1 }}>
|
||
<Grid item xs={12} sm={4}>
|
||
<ColorPicker
|
||
label="과매도 기준선"
|
||
value={localColors.stochasticOversoldColor || '#4caf50'}
|
||
onChange={(color) => setLocalColors({ ...localColors, stochasticOversoldColor: color })}
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} sm={4}>
|
||
<ColorPicker
|
||
label="중앙값 기준선"
|
||
value={localColors.stochasticNeutralColor || '#999999'}
|
||
onChange={(color) => setLocalColors({ ...localColors, stochasticNeutralColor: color })}
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} sm={4}>
|
||
<ColorPicker
|
||
label="과매수 기준선"
|
||
value={localColors.stochasticOverboughtColor || '#ef5350'}
|
||
onChange={(color) => setLocalColors({ ...localColors, stochasticOverboughtColor: color })}
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
</Box>
|
||
|
||
<PeriodManager indicatorType="STOCHASTIC" label="Stochastic" />
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 11. Williams %R 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
1️⃣1️⃣ Williams %R
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="기간"
|
||
type="number"
|
||
value={settings.williamsRPeriod}
|
||
onChange={(e) => handleSettingChange({ williamsRPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 11"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="침체 기준"
|
||
type="number"
|
||
value={settings.williamsROversold}
|
||
onChange={(e) => handleSettingChange({ williamsROversold: Number(e.target.value) })}
|
||
inputProps={{ min: -100, max: 0 }}
|
||
helperText="기본값: -80"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="중간"
|
||
type="number"
|
||
value={settings.williamsRMiddle}
|
||
onChange={(e) => handleSettingChange({ williamsRMiddle: Number(e.target.value) })}
|
||
inputProps={{ min: -100, max: 0 }}
|
||
helperText="기본값: -50"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="과열 기준"
|
||
type="number"
|
||
value={settings.williamsROverbought}
|
||
onChange={(e) => handleSettingChange({ williamsROverbought: Number(e.target.value) })}
|
||
inputProps={{ min: -100, max: 0 }}
|
||
helperText="기본값: -20"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
{/* 기준선 색상 설정 */}
|
||
<Typography variant="subtitle2" sx={{ mt: 3, mb: 2, fontWeight: 'bold' }}>
|
||
기준선 스타일 설정
|
||
</Typography>
|
||
<Grid container spacing={2}>
|
||
{/* 침체선 */}
|
||
<Grid item xs={12} md={4}>
|
||
<Box sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 1 }}>
|
||
<Typography variant="body2" sx={{ mb: 1, fontWeight: 'bold' }}>침체선 (-80)</Typography>
|
||
<Grid container spacing={1}>
|
||
<Grid item xs={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="색상"
|
||
type="color"
|
||
value={colors.williamsROversoldColor}
|
||
onChange={(e) => updateColors({ williamsROversoldColor: e.target.value })}
|
||
size="small"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="굵기"
|
||
type="number"
|
||
value={colors.williamsROversoldWidth}
|
||
onChange={(e) => updateColors({ williamsROversoldWidth: Number(e.target.value) })}
|
||
inputProps={{ min: 0.5, max: 5, step: 0.5 }}
|
||
size="small"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
</Box>
|
||
</Grid>
|
||
|
||
{/* 중간선 */}
|
||
<Grid item xs={12} md={4}>
|
||
<Box sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 1 }}>
|
||
<Typography variant="body2" sx={{ mb: 1, fontWeight: 'bold' }}>중간선 (-50)</Typography>
|
||
<Grid container spacing={1}>
|
||
<Grid item xs={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="색상"
|
||
type="color"
|
||
value={colors.williamsRMiddleColor}
|
||
onChange={(e) => updateColors({ williamsRMiddleColor: e.target.value })}
|
||
size="small"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="굵기"
|
||
type="number"
|
||
value={colors.williamsRMiddleWidth}
|
||
onChange={(e) => updateColors({ williamsRMiddleWidth: Number(e.target.value) })}
|
||
inputProps={{ min: 0.5, max: 5, step: 0.5 }}
|
||
size="small"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
</Box>
|
||
</Grid>
|
||
|
||
{/* 과열선 */}
|
||
<Grid item xs={12} md={4}>
|
||
<Box sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 1 }}>
|
||
<Typography variant="body2" sx={{ mb: 1, fontWeight: 'bold' }}>과열선 (-20)</Typography>
|
||
<Grid container spacing={1}>
|
||
<Grid item xs={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="색상"
|
||
type="color"
|
||
value={colors.williamsROverboughtColor}
|
||
onChange={(e) => updateColors({ williamsROverboughtColor: e.target.value })}
|
||
size="small"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="굵기"
|
||
type="number"
|
||
value={colors.williamsROverboughtWidth}
|
||
onChange={(e) => updateColors({ williamsROverboughtWidth: Number(e.target.value) })}
|
||
inputProps={{ min: 0.5, max: 5, step: 0.5 }}
|
||
size="small"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
</Box>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
Williams %R은 0에서 -100 사이의 값을 가집니다.
|
||
-80 이하는 침체, -20 이상은 과열로 판단합니다.
|
||
</Typography>
|
||
</Alert>
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 12. TRIX 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
1️⃣2️⃣ TRIX (Triple Exponential Moving Average)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="TRIX 기간"
|
||
type="number"
|
||
value={settings.trixPeriod}
|
||
onChange={(e) => handleSettingChange({ trixPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 5, max: 50 }}
|
||
helperText="기본값: 12"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="Signal 기간"
|
||
type="number"
|
||
value={settings.trixSignalPeriod}
|
||
onChange={(e) => handleSettingChange({ trixSignalPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 20 }}
|
||
helperText="기본값: 9"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="과매도 기준"
|
||
type="number"
|
||
value={settings.trixOversold}
|
||
onChange={(e) => handleSettingChange({ trixOversold: Number(e.target.value) })}
|
||
inputProps={{ min: -2, max: -0.1, step: 0.1 }}
|
||
helperText="기본값: -0.5"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="중앙값"
|
||
type="number"
|
||
value={settings.trixMiddle}
|
||
onChange={(e) => handleSettingChange({ trixMiddle: Number(e.target.value) })}
|
||
inputProps={{ min: -0.5, max: 0.5, step: 0.1 }}
|
||
helperText="기본값: 0"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="과매수 기준"
|
||
type="number"
|
||
value={settings.trixOverbought}
|
||
onChange={(e) => handleSettingChange({ trixOverbought: Number(e.target.value) })}
|
||
inputProps={{ min: 0.1, max: 2, step: 0.1 }}
|
||
helperText="기본값: 0.5"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
TRIX는 EMA의 EMA의 EMA를 사용하여 노이즈를 필터링한 모멘텀 지표입니다.
|
||
0선 상향 돌파 시 매수, 하향 돌파 시 매도 신호로 해석합니다.
|
||
</Typography>
|
||
</Alert>
|
||
<PeriodManager indicatorType="TRIX" label="TRIX" />
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 13. VolumeOSC 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
1️⃣3️⃣ VolumeOSC (Volume Oscillator)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="단기 기간"
|
||
type="number"
|
||
value={settings.volumeOscShortPeriod}
|
||
onChange={(e) => handleSettingChange({ volumeOscShortPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 50 }}
|
||
helperText="기본값: 10"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="장기 기간"
|
||
type="number"
|
||
value={settings.volumeOscLongPeriod}
|
||
onChange={(e) => handleSettingChange({ volumeOscLongPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 10, max: 100 }}
|
||
helperText="기본값: 20"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
VolumeOSC는 단기 거래량 이동평균과 장기 거래량 이동평균의 차이를 백분율로 나타냅니다.
|
||
양수면 거래량 증가 추세, 음수면 거래량 감소 추세입니다.
|
||
</Typography>
|
||
</Alert>
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 14. VR 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
1️⃣4️⃣ VR (Volume Ratio)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Grid container spacing={3}>
|
||
<Grid item xs={12} md={6}>
|
||
<TextField
|
||
fullWidth
|
||
label="기간"
|
||
type="number"
|
||
value={settings.vrPeriod}
|
||
onChange={(e) => handleSettingChange({ vrPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 5, max: 50 }}
|
||
helperText="기본값: 10"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
VR은 상승일 거래량과 하락일 거래량의 비율입니다.
|
||
일반적으로 70% 이하는 과매도, 250% 이상은 과매수로 판단합니다.
|
||
</Typography>
|
||
</Alert>
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 15. 이격도 설정 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
1️⃣5️⃣ 이격도 (Disparity Index)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
{/* 각 선 활성화 스위치 */}
|
||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 'bold' }}>
|
||
선 표시 설정
|
||
</Typography>
|
||
<Grid container spacing={2} sx={{ mb: 3 }}>
|
||
<Grid item xs={6} md={3}>
|
||
<FormControlLabel
|
||
control={
|
||
<Switch
|
||
checked={settings.disparityUltraShortEnabled ?? true}
|
||
onChange={(e) => handleSettingChange({ disparityUltraShortEnabled: e.target.checked })}
|
||
/>
|
||
}
|
||
label="초단기 (5일)"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={6} md={3}>
|
||
<FormControlLabel
|
||
control={
|
||
<Switch
|
||
checked={settings.disparityShortEnabled ?? true}
|
||
onChange={(e) => handleSettingChange({ disparityShortEnabled: e.target.checked })}
|
||
/>
|
||
}
|
||
label="단기 (20일)"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={6} md={3}>
|
||
<FormControlLabel
|
||
control={
|
||
<Switch
|
||
checked={settings.disparityMidEnabled ?? true}
|
||
onChange={(e) => handleSettingChange({ disparityMidEnabled: e.target.checked })}
|
||
/>
|
||
}
|
||
label="중기 (60일)"
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={6} md={3}>
|
||
<FormControlLabel
|
||
control={
|
||
<Switch
|
||
checked={settings.disparityLongEnabled ?? true}
|
||
onChange={(e) => handleSettingChange({ disparityLongEnabled: e.target.checked })}
|
||
/>
|
||
}
|
||
label="장기 (120일)"
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
<Divider sx={{ my: 2 }} />
|
||
|
||
{/* 기간 설정 */}
|
||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 'bold' }}>
|
||
기간 설정
|
||
</Typography>
|
||
<Grid container spacing={3}>
|
||
{/* 초단기 (5일) */}
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="초단기 기간"
|
||
type="number"
|
||
value={settings.disparityUltraShortPeriod}
|
||
onChange={(e) => handleSettingChange({ disparityUltraShortPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 1, max: 20 }}
|
||
helperText="기본값: 5일"
|
||
disabled={!settings.disparityUltraShortEnabled}
|
||
/>
|
||
</Grid>
|
||
{/* 단기 (20일) */}
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="단기 기간"
|
||
type="number"
|
||
value={settings.disparityShortPeriod}
|
||
onChange={(e) => handleSettingChange({ disparityShortPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 10, max: 50 }}
|
||
helperText="기본값: 20일"
|
||
disabled={!settings.disparityShortEnabled}
|
||
/>
|
||
</Grid>
|
||
{/* 중기 (60일) */}
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="중기 기간"
|
||
type="number"
|
||
value={settings.disparityMidPeriod}
|
||
onChange={(e) => handleSettingChange({ disparityMidPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 30, max: 100 }}
|
||
helperText="기본값: 60일"
|
||
disabled={!settings.disparityMidEnabled}
|
||
/>
|
||
</Grid>
|
||
{/* 장기 (120일) */}
|
||
<Grid item xs={12} md={3}>
|
||
<TextField
|
||
fullWidth
|
||
label="장기 기간"
|
||
type="number"
|
||
value={settings.disparityLongPeriod}
|
||
onChange={(e) => handleSettingChange({ disparityLongPeriod: Number(e.target.value) })}
|
||
inputProps={{ min: 60, max: 200 }}
|
||
helperText="기본값: 120일"
|
||
disabled={!settings.disparityLongEnabled}
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
<Alert severity="info" sx={{ mt: 2 }}>
|
||
<Typography variant="body2">
|
||
이격도는 현재 주가와 이동평균선 사이의 괴리율을 나타내는 지표입니다.
|
||
100을 기준으로 100 이상이면 주가가 이동평균선보다 위에, 100 이하면 아래에 있습니다.
|
||
초단기(5일), 단기(20일), 중기(60일), 장기(120일) 4개의 이격도를 동시에 표시합니다.
|
||
</Typography>
|
||
</Alert>
|
||
|
||
{/* 기준선 설정 */}
|
||
<Typography variant="subtitle2" sx={{ mt: 3, mb: 2, fontWeight: 'bold' }}>
|
||
기준선 스타일 설정
|
||
</Typography>
|
||
<Grid container spacing={2}>
|
||
<Grid item xs={12} md={12}>
|
||
<Box sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 1 }}>
|
||
<FormControlLabel
|
||
control={
|
||
<Switch
|
||
checked={settings.disparityBaseLineEnabled ?? true}
|
||
onChange={(e) => handleSettingChange({ disparityBaseLineEnabled: e.target.checked })}
|
||
/>
|
||
}
|
||
label="기준선 표시 (100)"
|
||
sx={{ mb: 2 }}
|
||
/>
|
||
<Grid container spacing={2}>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="색상"
|
||
type="color"
|
||
value={colors.disparityBaseLineColor}
|
||
onChange={(e) => updateColors({ disparityBaseLineColor: e.target.value })}
|
||
size="small"
|
||
disabled={!settings.disparityBaseLineEnabled}
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<TextField
|
||
fullWidth
|
||
label="굵기"
|
||
type="number"
|
||
value={colors.disparityBaseLineWidth}
|
||
onChange={(e) => updateColors({ disparityBaseLineWidth: Number(e.target.value) })}
|
||
inputProps={{ min: 0.5, max: 5, step: 0.5 }}
|
||
size="small"
|
||
disabled={!settings.disparityBaseLineEnabled}
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} md={4}>
|
||
<FormControl fullWidth size="small" disabled={!settings.disparityBaseLineEnabled}>
|
||
<InputLabel>선 스타일</InputLabel>
|
||
<Select
|
||
value={colors.disparityBaseLineStyle}
|
||
label="선 스타일"
|
||
onChange={(e) => updateColors({ disparityBaseLineStyle: e.target.value as 'solid' | 'dashed' | 'dotted' })}
|
||
>
|
||
<MenuItem value="solid">실선</MenuItem>
|
||
<MenuItem value="dashed">파선</MenuItem>
|
||
<MenuItem value="dotted">점선</MenuItem>
|
||
</Select>
|
||
</FormControl>
|
||
</Grid>
|
||
</Grid>
|
||
</Box>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
<PeriodManager indicatorType="DISPARITY" label="이격도" />
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
{/* 이동평균 기간 관리 섹션 */}
|
||
<Accordion defaultExpanded={false} sx={{ mb: 2 }}>
|
||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
|
||
📊 이동평균 기간 관리 (전략 규칙용)
|
||
</Typography>
|
||
</AccordionSummary>
|
||
<AccordionDetails>
|
||
<Alert severity="info" sx={{ mb: 2 }}>
|
||
<Typography variant="body2">
|
||
전략 규칙 생성 시 선택 가능한 이동평균 기간을 관리합니다.
|
||
</Typography>
|
||
</Alert>
|
||
|
||
<Grid container spacing={2}>
|
||
<Grid item xs={12} md={6}>
|
||
<PeriodManager indicatorType="MA" label="단순이동평균 (SMA)" />
|
||
</Grid>
|
||
<Grid item xs={12} md={6}>
|
||
<PeriodManager indicatorType="EMA" label="지수이동평균 (EMA)" />
|
||
</Grid>
|
||
</Grid>
|
||
|
||
<Box sx={{ mt: 2 }}>
|
||
<PeriodManager indicatorType="VOLUME_MA" label="거래량 이동평균" />
|
||
</Box>
|
||
</AccordionDetails>
|
||
</Accordion>
|
||
|
||
<Divider sx={{ my: 3 }} />
|
||
|
||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
|
||
<Button variant="contained" onClick={handleSave} size="large">
|
||
저장
|
||
</Button>
|
||
</Box>
|
||
|
||
{/* 저장 결과 팝업 */}
|
||
<Dialog open={resultDialogOpen} onClose={() => setResultDialogOpen(false)} maxWidth="sm" fullWidth>
|
||
<DialogTitle sx={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
bgcolor: '#1976d2',
|
||
color: '#fff',
|
||
py: 1.2,
|
||
}}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
{saveResult.success ? <CheckCircle sx={{ color: '#fff' }} /> : <Error sx={{ color: '#fff' }} />}
|
||
<Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px', color: '#fff' }}>
|
||
{saveResult.success ? '저장 완료' : '저장 실패'}
|
||
</Typography>
|
||
</Box>
|
||
<IconButton onClick={() => setResultDialogOpen(false)} size="small" sx={{ color: '#fff' }}>
|
||
<Close />
|
||
</IconButton>
|
||
</DialogTitle>
|
||
<DialogContent sx={{ mt: 2 }}>
|
||
<Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>
|
||
{saveResult.message}
|
||
</Typography>
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<Button onClick={() => setResultDialogOpen(false)} variant="contained" autoFocus>
|
||
확인
|
||
</Button>
|
||
</DialogActions>
|
||
</Dialog>
|
||
|
||
{/* MACD 상세 설명 다이얼로그 */}
|
||
<Dialog
|
||
open={macdHelpOpen}
|
||
onClose={() => setMacdHelpOpen(false)}
|
||
maxWidth="md"
|
||
fullWidth
|
||
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 }}>
|
||
<HelpOutline />
|
||
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||
MACD 상세 설명
|
||
</Typography>
|
||
</Box>
|
||
<IconButton onClick={() => setMacdHelpOpen(false)} size="small" sx={{ color: 'white' }}>
|
||
<ExpandMore sx={{ transform: 'rotate(180deg)' }} />
|
||
</IconButton>
|
||
</DialogTitle>
|
||
|
||
<DialogContent sx={{ pt: 3, pb: 3, maxHeight: '65vh', overflowY: 'auto' }}>
|
||
<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>
|
||
</DialogContent>
|
||
|
||
<DialogActions sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider' }}>
|
||
<Button onClick={() => setMacdHelpOpen(false)} variant="contained" color="primary" fullWidth>
|
||
확인
|
||
</Button>
|
||
</DialogActions>
|
||
</Dialog>
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
export default IndicatorSettingsTab;
|