penti/frontend/src/components/backtest/BacktestForm.tsx

209 lines
6.2 KiB
TypeScript
Raw Normal View History

2026-01-31 23:30:51 +09:00
import React, { useState, useEffect } from 'react';
import { backtestAPI } from '../../api/client';
interface Strategy {
name: string;
description: string;
}
interface BacktestFormProps {
onSuccess: (result: any) => void;
}
const BacktestForm: React.FC<BacktestFormProps> = ({ onSuccess }) => {
const [strategies, setStrategies] = useState<Strategy[]>([]);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
name: '',
strategy_name: 'multi_factor',
start_date: '2020-01-01',
end_date: '2023-12-31',
initial_capital: 10000000,
commission_rate: 0.0015,
rebalance_frequency: 'monthly',
count: 20,
});
useEffect(() => {
loadStrategies();
}, []);
const loadStrategies = async () => {
try {
const response = await backtestAPI.strategies();
setStrategies(response.data.strategies);
} catch (error) {
console.error('전략 목록 로드 오류:', error);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const config = {
name: formData.name,
strategy_name: formData.strategy_name,
start_date: formData.start_date,
end_date: formData.end_date,
initial_capital: formData.initial_capital,
commission_rate: formData.commission_rate,
rebalance_frequency: formData.rebalance_frequency,
strategy_config: {
count: formData.count,
},
};
const response = await backtestAPI.run(config);
onSuccess(response.data);
} catch (error: any) {
alert(`백테스트 실행 오류: ${error.response?.data?.detail || error.message}`);
} finally {
setLoading(false);
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value,
}));
};
return (
<form onSubmit={handleSubmit} className="bg-white shadow rounded-lg p-6 space-y-6">
<h2 className="text-2xl font-bold"> </h2>
{/* 백테스트 이름 */}
<div>
<label className="block text-sm font-medium text-gray-700">
</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
placeholder="예: Multi-Factor 2020-2023"
/>
</div>
{/* 전략 선택 */}
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<select
name="strategy_name"
value={formData.strategy_name}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
>
{strategies.map(strategy => (
<option key={strategy.name} value={strategy.name}>
{strategy.name} - {strategy.description}
</option>
))}
</select>
</div>
{/* 기간 설정 */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700">
</label>
<input
type="date"
name="start_date"
value={formData.start_date}
onChange={handleChange}
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
</label>
<input
type="date"
name="end_date"
value={formData.end_date}
onChange={handleChange}
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
</div>
{/* 초기 자본 */}
<div>
<label className="block text-sm font-medium text-gray-700">
()
</label>
<input
type="number"
name="initial_capital"
value={formData.initial_capital}
onChange={handleChange}
required
step="1000000"
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
{/* 리밸런싱 주기 */}
<div>
<label className="block text-sm font-medium text-gray-700">
</label>
<select
name="rebalance_frequency"
value={formData.rebalance_frequency}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
>
<option value="monthly"></option>
<option value="quarterly"></option>
<option value="yearly"></option>
</select>
</div>
{/* 종목 수 */}
<div>
<label className="block text-sm font-medium text-gray-700">
</label>
<input
type="number"
name="count"
value={formData.count}
onChange={handleChange}
required
min="1"
max="100"
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
{/* 제출 버튼 */}
<button
type="submit"
disabled={loading}
className={`w-full py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white ${
loading
? 'bg-gray-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500'
}`}
>
{loading ? '실행 중...' : '백테스트 실행'}
</button>
</form>
);
};
export default BacktestForm;