209 lines
6.2 KiB
TypeScript
209 lines
6.2 KiB
TypeScript
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;
|