/**
* 설정 화면 — 백테스팅 설정 패널 (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 }) => (
);
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;