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:
2026-02-08 13:54:05 +09:00
parent 3be9d8eeba
commit b64a76a8b9
6 changed files with 605 additions and 39 deletions

View File

@@ -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)