feat: 전략 백테스팅 기능 추가
- Introduced a `backtest_strategy` endpoint to enable strategy backtesting with user-specified parameters. - Implemented a generic backtesting engine allowing rebalancing, equity curve tracking, and performance metric calculations. - Added `BacktestMixin` for strategies to support backtesting-related operations. - Extended BAA strategy to support backtesting with ticker data download and portfolio simulation. - Updated `urls.py` to include the new backtesting endpoint. - Enhanced logging and error handling throughout the backtesting process.
This commit is contained in:
@@ -11,6 +11,7 @@ import logging
|
||||
|
||||
from .models import QuantStrategy, StrategyVersion, StrategyExecution
|
||||
from .base import StrategyRegistry
|
||||
from .services.backtest import run_backtest
|
||||
from . import implementations # 구현체들을 로드하여 레지스트리에 등록
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -239,3 +240,106 @@ def list_available_implementations(request):
|
||||
return JsonResponse({
|
||||
'available_implementations': available_strategies
|
||||
})
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(["POST"])
|
||||
def backtest_strategy(request):
|
||||
"""전략 백테스트 실행"""
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
strategy_name = data.get('strategy_name')
|
||||
version = data.get('version')
|
||||
execution_parameters = data.get('parameters', {})
|
||||
callback_url = data.get('callback_url')
|
||||
|
||||
if not strategy_name:
|
||||
return JsonResponse({
|
||||
'error': 'strategy_name is required'
|
||||
}, status=400)
|
||||
|
||||
# 백테스트 필수 파라미터 검증
|
||||
if 'backtest_start_date' not in execution_parameters:
|
||||
return JsonResponse({
|
||||
'error': 'parameters.backtest_start_date is required'
|
||||
}, status=400)
|
||||
|
||||
if 'backtest_end_date' not in execution_parameters:
|
||||
return JsonResponse({
|
||||
'error': 'parameters.backtest_end_date is required'
|
||||
}, status=400)
|
||||
|
||||
strategy = get_object_or_404(QuantStrategy, name=strategy_name, is_active=True)
|
||||
|
||||
if version:
|
||||
strategy_version = get_object_or_404(
|
||||
StrategyVersion,
|
||||
strategy=strategy,
|
||||
version=version
|
||||
)
|
||||
else:
|
||||
strategy_version = strategy.versions.filter(is_current=True).first()
|
||||
if not strategy_version:
|
||||
return JsonResponse({
|
||||
'error': 'No current version found for this strategy'
|
||||
}, status=404)
|
||||
|
||||
# execution_mode를 backtest로 표시
|
||||
backtest_params = execution_parameters.copy()
|
||||
backtest_params['execution_mode'] = 'backtest'
|
||||
|
||||
execution = StrategyExecution.objects.create(
|
||||
strategy_version=strategy_version,
|
||||
execution_parameters=backtest_params,
|
||||
status='pending',
|
||||
callback_url=callback_url
|
||||
)
|
||||
|
||||
def run_backtest_task():
|
||||
try:
|
||||
execution.status = 'running'
|
||||
execution.save()
|
||||
|
||||
strategy_impl = strategy_version.get_strategy_implementation()
|
||||
|
||||
# 기본 파라미터와 실행 파라미터를 병합
|
||||
merged_parameters = strategy_impl.default_parameters.copy()
|
||||
merged_parameters.update(execution_parameters)
|
||||
|
||||
# 백테스트 실행
|
||||
result = run_backtest(strategy_impl, merged_parameters)
|
||||
|
||||
execution.status = 'completed'
|
||||
execution.result = result
|
||||
execution.completed_at = timezone.now()
|
||||
execution.save()
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f'Backtest failed for execution {execution.id}')
|
||||
execution.status = 'failed'
|
||||
execution.error_message = str(e)
|
||||
execution.completed_at = timezone.now()
|
||||
execution.save()
|
||||
|
||||
finally:
|
||||
if execution.callback_url:
|
||||
send_callback(execution)
|
||||
|
||||
thread = threading.Thread(target=run_backtest_task)
|
||||
thread.start()
|
||||
|
||||
return JsonResponse({
|
||||
'execution_id': execution.id,
|
||||
'status': 'pending',
|
||||
'message': 'Backtest execution started',
|
||||
'callback_url': callback_url
|
||||
})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({
|
||||
'error': 'Invalid JSON in request body'
|
||||
}, status=400)
|
||||
except Exception as e:
|
||||
return JsonResponse({
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
Reference in New Issue
Block a user