알림 전체닫기 오류 수정

This commit is contained in:
Macbook
2026-06-04 13:00:59 +09:00
parent 5740a9a59d
commit 128b589369
16 changed files with 513 additions and 126 deletions
+264 -48
View File
@@ -143,6 +143,10 @@ interface SettingsPageProps {
onPaperAutoTradeEnabled?: (v: boolean) => void;
paperAutoTradeBudgetPct?: number;
onPaperAutoTradeBudgetPct?: (v: number) => void;
paperOrderFillMode?: 'LIMIT_ONLY' | 'NEXT_TICK' | 'AUTO_CANCEL';
onPaperOrderFillMode?: (v: 'LIMIT_ONLY' | 'NEXT_TICK' | 'AUTO_CANCEL') => void;
paperOrderAutoCancelPct?: number;
onPaperOrderAutoCancelPct?: (v: number) => void;
virtualTargetMaxCount?: number;
onVirtualTargetMaxCount?: (v: number) => void;
onPaperAccountReset?: () => void;
@@ -250,6 +254,15 @@ const IcVirtual = () => (
<path d="M10 11h2"/>
</svg>
);
const IcTrading = () => (
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="5" width="16" height="12" rx="2"/>
<line x1="7" y1="9" x2="15" y2="9"/>
<line x1="7" y1="12" x2="12" y2="12"/>
<path d="M16 2l2 2-2 2"/>
<path d="M6 20l-2-2 2-2"/>
</svg>
);
const IcLive = () => (
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="11" cy="11" r="9"/>
@@ -287,8 +300,7 @@ const ALL_CATEGORIES: Category[] = [
{ id: 'indicators', label: '보조지표 설정', icon: <IcIndicators />, desc: '지표 추가 Main 탭 보조지표 기본 파라미터·색상·기준선' },
{ id: 'backtest', label: '백테스팅', icon: <IcBacktest />, desc: '백테스팅 실행 옵션, 자본·비용·리스크 관리 설정' },
{ id: 'strategy', label: '전략 설정', icon: <IcStrategy />, desc: '투자 전략 기본 파라미터 및 조건 설정' },
{ id: 'paper', label: '모의투자', icon: <IcPaper />, desc: '모의투자 활성화, 자동매매, 초기 자본·수수료·슬리피지, 계좌 초기화' },
{ id: 'virtual', label: '가상매매', icon: <IcVirtual />, desc: '가상매매 화면 투자대상 목록 한도 등 가상매매 전용 설정' },
{ id: 'trading', label: '매매설정', icon: <IcTrading />, desc: '모의투자·가상매매 통합 설정 — 자동매매, 미체결 주문 처리, 계좌·비용 등' },
{ id: 'live', label: '실거래', icon: <IcLive />, desc: '매매 운영 모드, 업비트 API 키, 실거래 자동매매' },
{ id: 'trend-search', label: '추세검색', icon: <IcTrendSearch />, desc: '상승추세 배점, 합격 점수, 결과 수, 자동갱신·투자대상 자동추가' },
{ id: 'alert', label: '알림 설정', icon: <IcAlert />, desc: '가격 알림, 신호 알림, 알림 방식 설정' },
@@ -474,29 +486,226 @@ const PaperTradingSettingsPanel: React.FC<PaperTradingSettingsPanelProps> = ({
</>
);
const VirtualTradingSettingsPanel: React.FC<{
// ── 통합 매매설정 패널 ────────────────────────────────────────────────────────
interface TradingSettingsPanelProps {
paperTradingEnabled?: boolean;
onPaperTradingEnabled?: (v: boolean) => void;
paperInitialCapital?: number;
onPaperInitialCapital?: (v: number) => void;
paperFeeRatePct?: number;
onPaperFeeRatePct?: (v: number) => void;
paperSlippagePct?: number;
onPaperSlippagePct?: (v: number) => void;
paperMinOrderKrw?: number;
onPaperMinOrderKrw?: (v: number) => void;
paperAutoTradeEnabled?: boolean;
onPaperAutoTradeEnabled?: (v: boolean) => void;
paperAutoTradeBudgetPct?: number;
onPaperAutoTradeBudgetPct?: (v: number) => void;
paperOrderFillMode?: 'LIMIT_ONLY' | 'NEXT_TICK' | 'AUTO_CANCEL';
onPaperOrderFillMode?: (v: 'LIMIT_ONLY' | 'NEXT_TICK' | 'AUTO_CANCEL') => void;
paperOrderAutoCancelPct?: number;
onPaperOrderAutoCancelPct?: (v: number) => void;
virtualTargetMaxCount?: number;
onVirtualTargetMaxCount?: (v: number) => void;
}> = ({ virtualTargetMaxCount = 20, onVirtualTargetMaxCount }) => (
<SettingSection title="가상매매">
<SettingRow
label="투자대상 목록 최대 개수"
desc="가상매매 화면·추세검색에서 등록할 수 있는 투자대상 종목 수 상한입니다."
>
<input
type="number"
className="stg-input stg-input--num"
value={virtualTargetMaxCount}
min={1}
max={100}
step={1}
onChange={e => onVirtualTargetMaxCount?.(Number(e.target.value))}
/>
</SettingRow>
<p className="stg-hint">
·· .
</p>
</SettingSection>
onPaperAccountReset?: () => void;
}
const TradingSettingsPanel: React.FC<TradingSettingsPanelProps> = ({
paperTradingEnabled = true,
onPaperTradingEnabled,
paperInitialCapital = 10_000_000,
onPaperInitialCapital,
paperFeeRatePct = 0.05,
onPaperFeeRatePct,
paperSlippagePct = 0,
onPaperSlippagePct,
paperMinOrderKrw = 5000,
onPaperMinOrderKrw,
paperAutoTradeEnabled = false,
onPaperAutoTradeEnabled,
paperAutoTradeBudgetPct = 95,
onPaperAutoTradeBudgetPct,
paperOrderFillMode = 'LIMIT_ONLY',
onPaperOrderFillMode,
paperOrderAutoCancelPct = 1.0,
onPaperOrderAutoCancelPct,
virtualTargetMaxCount = 20,
onVirtualTargetMaxCount,
onPaperAccountReset,
}) => (
<>
{/* ── 모의투자 ────────────────────────────────────────── */}
<SettingSection title="모의투자">
<SettingRow
label="모의투자 활성화"
desc="켜면 모의투자 화면·실시간 차트 매매 패널·전략 Match 자동매매가 모의 계좌로 체결됩니다."
>
<label className="stg-toggle">
<input type="checkbox" checked={paperTradingEnabled} onChange={e => onPaperTradingEnabled?.(e.target.checked)} />
<span className="stg-toggle-slider" />
</label>
</SettingRow>
</SettingSection>
{/* ── 자동매매 ─────────────────────────────────────────── */}
<SettingSection title="자동매매">
<SettingRow
label="자동매매"
desc="Match 충족 시 모의 계좌로 자동 매수·매도합니다. 모의투자·가상매매 화면 타이틀바에서도 변경할 수 있습니다."
>
<label className="stg-toggle">
<input type="checkbox" checked={paperAutoTradeEnabled} onChange={e => onPaperAutoTradeEnabled?.(e.target.checked)} />
<span className="stg-toggle-slider" />
</label>
</SettingRow>
<SettingRow
label="자동 매수 예산 (%)"
desc="Match 매수 시 주문가능 현금 중 사용 비율. Match 매도는 해당 종목 보유 수량 전량 매도."
>
<input
type="number"
className="stg-input stg-input--num"
value={paperAutoTradeBudgetPct}
min={1} max={100} step={1}
disabled={!paperAutoTradeEnabled}
onChange={e => onPaperAutoTradeBudgetPct?.(Number(e.target.value))}
/>
</SettingRow>
</SettingSection>
{/* ── 미체결 주문 처리 ─────────────────────────────────── */}
<SettingSection title="미체결 주문 처리">
<SettingRow
label="체결 모드"
desc="수동·자동매매 시 생성된 미체결 주문이 시세 변동 시 어떻게 처리될지 결정합니다."
className="stg-row--block"
>
<div className="stg-radio-row">
<label className="stg-radio-option stg-radio-option--cyan">
<input
type="radio"
name="stg-fill-mode"
value="LIMIT_ONLY"
checked={paperOrderFillMode === 'LIMIT_ONLY'}
onChange={() => onPaperOrderFillMode?.('LIMIT_ONLY')}
/>
<span className="stg-radio-option-text">
<strong>A. ()</strong>
<span>매수: 시세 / 매도: 시세 . </span>
</span>
</label>
<label className="stg-radio-option stg-radio-option--blue">
<input
type="radio"
name="stg-fill-mode"
value="NEXT_TICK"
checked={paperOrderFillMode === 'NEXT_TICK'}
onChange={() => onPaperOrderFillMode?.('NEXT_TICK')}
/>
<span className="stg-radio-option-text">
<strong>B. </strong>
<span> ( )</span>
</span>
</label>
<label className="stg-radio-option stg-radio-option--orange">
<input
type="radio"
name="stg-fill-mode"
value="AUTO_CANCEL"
checked={paperOrderFillMode === 'AUTO_CANCEL'}
onChange={() => onPaperOrderFillMode?.('AUTO_CANCEL')}
/>
<span className="stg-radio-option-text">
<strong>C. </strong>
<span> % . </span>
</span>
</label>
</div>
</SettingRow>
{paperOrderFillMode === 'AUTO_CANCEL' && (
<SettingRow label="자동 취소 허용 이탈폭 (%)" desc="주문가 대비 이 % 이상 가격이 벗어나면 미체결 주문이 자동 취소됩니다.">
<input
type="number"
className="stg-input stg-input--num"
value={paperOrderAutoCancelPct}
min={0.1} max={50} step={0.1}
onChange={e => onPaperOrderAutoCancelPct?.(Number(e.target.value))}
/>
<span className="stg-unit">%</span>
</SettingRow>
)}
</SettingSection>
{/* ── 계좌·비용 ─────────────────────────────────────────── */}
<SettingSection title="계좌·비용">
<SettingRow label="초기 자본 (KRW)" desc="계좌 초기화 시 적용되는 시작 현금입니다.">
<input
type="number"
className="stg-input stg-input--num"
value={paperInitialCapital}
min={100000} step={100000}
onChange={e => onPaperInitialCapital?.(Number(e.target.value))}
/>
</SettingRow>
<SettingRow label="수수료율 (%)" desc="체결 금액 기준 수수료 (업비트 KRW 마켓 기본 0.05% 참고).">
<input
type="number"
className="stg-input stg-input--num"
value={paperFeeRatePct}
min={0} max={1} step={0.01}
onChange={e => onPaperFeeRatePct?.(Number(e.target.value))}
/>
</SettingRow>
<SettingRow label="슬리피지 (%)" desc="매수는 가격 상승, 매도는 가격 하락으로 체결가를 조정합니다.">
<input
type="number"
className="stg-input stg-input--num"
value={paperSlippagePct}
min={0} max={5} step={0.01}
onChange={e => onPaperSlippagePct?.(Number(e.target.value))}
/>
</SettingRow>
<SettingRow label="최소 주문 금액 (KRW)" desc="이 금액 미만 주문은 거부됩니다.">
<input
type="number"
className="stg-input stg-input--num"
value={paperMinOrderKrw}
min={1000} step={1000}
onChange={e => onPaperMinOrderKrw?.(Number(e.target.value))}
/>
</SettingRow>
</SettingSection>
{/* ── 가상매매 ─────────────────────────────────────────── */}
<SettingSection title="가상매매">
<SettingRow
label="투자대상 목록 최대 개수"
desc="가상매매 화면·추세검색에서 등록할 수 있는 투자대상 종목 수 상한입니다."
>
<input
type="number"
className="stg-input stg-input--num"
value={virtualTargetMaxCount}
min={1} max={100} step={1}
onChange={e => onVirtualTargetMaxCount?.(Number(e.target.value))}
/>
</SettingRow>
</SettingSection>
{/* ── 계좌 관리 ─────────────────────────────────────────── */}
<SettingSection title="계좌 관리">
<SettingRow
label="계좌 초기화"
desc="보유 종목·체결·원장·투자금 한도·미체결 주문을 삭제하고 초기 자본으로 현금을 되돌립니다."
>
<button type="button" className="stg-btn stg-btn--danger" onClick={onPaperAccountReset}>
</button>
</SettingRow>
</SettingSection>
</>
);
const LiveTradingSettingsPanel: React.FC<{
@@ -1764,6 +1973,10 @@ const SettingsPage: React.FC<SettingsPageProps> = ({
onPaperAutoTradeEnabled,
paperAutoTradeBudgetPct = 95,
onPaperAutoTradeBudgetPct,
paperOrderFillMode = 'LIMIT_ONLY',
onPaperOrderFillMode,
paperOrderAutoCancelPct = 1.0,
onPaperOrderAutoCancelPct,
virtualTargetMaxCount = 20,
onVirtualTargetMaxCount,
onPaperAccountReset,
@@ -1913,31 +2126,34 @@ const SettingsPage: React.FC<SettingsPageProps> = ({
<AdminSettingsPanel />
</AdminPasswordGate>
);
case 'paper': return (
<PaperTradingSettingsPanel
paperTradingEnabled={paperTradingEnabled}
onPaperTradingEnabled={onPaperTradingEnabled}
paperInitialCapital={paperInitialCapital}
onPaperInitialCapital={onPaperInitialCapital}
paperFeeRatePct={paperFeeRatePct}
onPaperFeeRatePct={onPaperFeeRatePct}
paperSlippagePct={paperSlippagePct}
onPaperSlippagePct={onPaperSlippagePct}
paperMinOrderKrw={paperMinOrderKrw}
onPaperMinOrderKrw={onPaperMinOrderKrw}
paperAutoTradeEnabled={paperAutoTradeEnabled}
onPaperAutoTradeEnabled={onPaperAutoTradeEnabled}
paperAutoTradeBudgetPct={paperAutoTradeBudgetPct}
onPaperAutoTradeBudgetPct={onPaperAutoTradeBudgetPct}
onPaperAccountReset={onPaperAccountReset}
/>
);
case 'virtual': return (
<VirtualTradingSettingsPanel
virtualTargetMaxCount={virtualTargetMaxCount}
onVirtualTargetMaxCount={onVirtualTargetMaxCount}
/>
);
case 'trading':
case 'paper':
case 'virtual':
return (
<TradingSettingsPanel
paperTradingEnabled={paperTradingEnabled}
onPaperTradingEnabled={onPaperTradingEnabled}
paperInitialCapital={paperInitialCapital}
onPaperInitialCapital={onPaperInitialCapital}
paperFeeRatePct={paperFeeRatePct}
onPaperFeeRatePct={onPaperFeeRatePct}
paperSlippagePct={paperSlippagePct}
onPaperSlippagePct={onPaperSlippagePct}
paperMinOrderKrw={paperMinOrderKrw}
onPaperMinOrderKrw={onPaperMinOrderKrw}
paperAutoTradeEnabled={paperAutoTradeEnabled}
onPaperAutoTradeEnabled={onPaperAutoTradeEnabled}
paperAutoTradeBudgetPct={paperAutoTradeBudgetPct}
onPaperAutoTradeBudgetPct={onPaperAutoTradeBudgetPct}
paperOrderFillMode={paperOrderFillMode}
onPaperOrderFillMode={onPaperOrderFillMode}
paperOrderAutoCancelPct={paperOrderAutoCancelPct}
onPaperOrderAutoCancelPct={onPaperOrderAutoCancelPct}
virtualTargetMaxCount={virtualTargetMaxCount}
onVirtualTargetMaxCount={onVirtualTargetMaxCount}
onPaperAccountReset={onPaperAccountReset}
/>
);
case 'live': return (
<LiveTradingSettingsPanel
tradingMode={tradingMode}
@@ -85,6 +85,30 @@ const PaperLeftSettingsTab: React.FC<Props> = ({
onChange={e => patch({ paperAutoTradeBudgetPct: Number(e.target.value) })}
/>
</SettingsFieldRow>
<SettingsFieldRow label="미체결 주문 처리">
<select
className="vtd-trade-meta-value ptd-settings-input ptd-settings-select"
value={defaults.paperOrderFillMode}
onChange={e => patch({ paperOrderFillMode: e.target.value as 'LIMIT_ONLY' | 'NEXT_TICK' | 'AUTO_CANCEL' })}
>
<option value="LIMIT_ONLY">A. ( )</option>
<option value="NEXT_TICK">B. ( )</option>
<option value="AUTO_CANCEL">C. </option>
</select>
</SettingsFieldRow>
{defaults.paperOrderFillMode === 'AUTO_CANCEL' && (
<SettingsFieldRow label="허용 이탈폭 (%)">
<input
type="number"
className="vtd-trade-meta-value ptd-settings-input"
min={0.1}
max={50}
step={0.1}
value={defaults.paperOrderAutoCancelPct}
onChange={e => patch({ paperOrderAutoCancelPct: Number(e.target.value) })}
/>
</SettingsFieldRow>
)}
</div>
</div>
</div>