/** * 설정 화면 — 백테스팅 설정 패널 (2열 카드 레이아웃) */ import React, { useState, useEffect, useCallback } from 'react'; import { type BacktestSettingsDto, DEFAULT_BACKTEST_SETTINGS, loadBacktestSettings, saveBacktestSettings, } from '../utils/backendApi'; import { ANALYSIS_METHOD_OPTIONS } from '../utils/analysisMethodLabels'; import { TRADE_EXECUTION_MODE_OPTIONS } from '../utils/tradeExecutionMode'; // ── 백테스팅 전용 UI 컴포넌트 ─────────────────────────────────────────────── const BtSection: React.FC<{ title: string; children: React.ReactNode }> = ({ title, children }) => (

{title}

{children}
); const BtGrid: React.FC<{ children: React.ReactNode }> = ({ children }) => (
{children}
); const BtField: React.FC<{ label: string; desc?: string; full?: boolean; children: React.ReactNode; }> = ({ label, desc, full, children }) => (
{label} {desc && {desc}}
{children}
); export interface BacktestSettingsPanelProps { btAutoPopup?: boolean; onBtAutoPopup?: (v: boolean) => void; btShowPrice?: boolean; onBtShowPrice?: (v: boolean) => void; } export const BacktestSettingsPanel: React.FC = ({ btAutoPopup = true, onBtAutoPopup, btShowPrice = true, onBtShowPrice, }) => { const [cfg, setCfg] = useState(DEFAULT_BACKTEST_SETTINGS); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); useEffect(() => { loadBacktestSettings().then(s => { if (s) setCfg(s); }); }, []); const update = useCallback(async (patch: Partial) => { const next = { ...cfg, ...patch }; setCfg(next); setSaving(true); setSaved(false); try { await saveBacktestSettings(next); setSaved(true); setTimeout(() => setSaved(false), 1500); } finally { setSaving(false); } }, [cfg]); const field = (key: K, value: BacktestSettingsDto[K]) => update({ [key]: value } as Partial); return (
{(saving || saved) && (
{saving ? '저장 중…' : '✓ 저장됨'}
)}

{(cfg.tradeExecutionMode ?? 'BACKTEST_ENGINE') === 'SCAN_SIGNALS' ? TRADE_EXECUTION_MODE_OPTIONS[1].desc : TRADE_EXECUTION_MODE_OPTIONS[0].desc}

{(cfg.tradeExecutionMode ?? 'BACKTEST_ENGINE') === 'SCAN_SIGNALS' && (

조건 스캔 모드: 슬리피지·손절/익절·트레일링스탑·진입/청산가(NEXT_OPEN) 설정은 시그널 생성에 적용되지 않습니다. 수수료만 시뮬레이션에 반영됩니다.

)}
field('initialCapital', Number(e.target.value))} /> field('tradeSizeValue', Number(e.target.value))} /> {cfg.commissionType === 'LINEAR' ? ( field('commissionRate', Number(e.target.value) / 100)} /> ) : (
)} field('slippageRate', Number(e.target.value) / 100)} /> {cfg.stopLossEnabled ? ( field('stopLossPct', Number(e.target.value))} /> ) :
} {cfg.takeProfitEnabled ? ( field('takeProfitPct', Number(e.target.value))} /> ) :
} {cfg.trailingStopEnabled ? ( field('trailingStopPct', Number(e.target.value))} /> ) :
} field('reentryWaitBars', Number(e.target.value))} /> field('maxOpenTrades', Number(e.target.value))} /> {cfg.partialExitEnabled ? ( field('partialExitPct', Number(e.target.value))} /> ) :
}
{cfg.positionMode === 'SIGNAL_ONLY' && (
Signal-Only 모드: 매도 시그널이 이전 매수 없이도 발생합니다
)}
); }; export default BacktestSettingsPanel;