Initial commit: Django quantitative strategy executor

- Django 5.2.7 project with Python 3.13+
- Quant strategy management system with version control
- Strategy implementations using registry pattern
- API endpoints for strategy listing and execution
- Sample strategy implementations (MovingAverage, RSI, BollingerBand)
- Async strategy execution with status tracking

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-04 13:50:46 +09:00
commit 01403c7df4
22 changed files with 1031 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(python manage.py:*)",
"Bash(git init:*)",
"Bash(git add:*)"
],
"deny": [],
"ask": []
}
}

87
.gitignore vendored Normal file
View File

@@ -0,0 +1,87 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Virtual environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Django
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
media/
staticfiles/
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# UV
uv.lock

66
CLAUDE.md Normal file
View File

@@ -0,0 +1,66 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a Django 5.2.7 project named "executor" using Python 3.13+ with a minimal configuration. It's a fresh Django project with only the default Django applications installed and no custom apps yet.
## Development Commands
### Environment Setup
```bash
# Activate virtual environment (if using .venv)
source .venv/bin/activate
# Install dependencies
uv sync
```
### Django Commands
```bash
# Run development server
python manage.py runserver
# Database migrations
python manage.py makemigrations
python manage.py migrate
# Create superuser
python manage.py createsuperuser
# Django shell
python manage.py shell
# Run tests
python manage.py test
# Collect static files
python manage.py collectstatic
```
## Project Structure
- **executor/**: Main Django project directory containing settings, URLs, and WSGI/ASGI configuration
- **templates/**: Global template directory (currently empty)
- **db.sqlite3**: SQLite database file
- **manage.py**: Django management script
- **pyproject.toml**: Project dependencies managed by uv
- **uv.lock**: Dependency lock file
## Architecture Notes
- Uses SQLite as the default database
- Templates directory is configured at the project root level
- Standard Django project layout with no custom applications yet
- Secret key is currently set to Django's insecure default (should be changed for production)
- Debug mode is enabled (development configuration)
## Development Workflow
This appears to be a new Django project. When adding new functionality:
1. Create Django apps using `python manage.py startapp <app_name>`
2. Add new apps to `INSTALLED_APPS` in `executor/settings.py`
3. Create models, views, and URL patterns within the apps
4. Run migrations after model changes
5. Add templates to either app-specific template directories or the global `templates/` directory

0
executor/__init__.py Normal file
View File

16
executor/asgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
ASGI config for executor project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'executor.settings')
application = get_asgi_application()

124
executor/settings.py Normal file
View File

@@ -0,0 +1,124 @@
"""
Django settings for executor project.
Generated by 'django-admin startproject' using Django 5.2.7.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-ma+k#^%b4oe-(bm7y=kswcbbm-i+d=#fft7t_tl0sk0$_4asop'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'strategies',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'executor.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'executor.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

23
executor/urls.py Normal file
View File

@@ -0,0 +1,23 @@
"""
URL configuration for executor project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('strategies.urls')),
]

16
executor/wsgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
WSGI config for executor project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'executor.settings')
application = get_wsgi_application()

22
manage.py Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'executor.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

8
pyproject.toml Normal file
View File

@@ -0,0 +1,8 @@
[project]
name = "executor"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.13"
dependencies = [
"django>=5.2.7",
]

0
strategies/__init__.py Normal file
View File

3
strategies/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
strategies/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class StrategiesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'strategies'

110
strategies/base.py Normal file
View File

@@ -0,0 +1,110 @@
from abc import ABC, abstractmethod
from typing import Dict, Any
import json
class BaseQuantStrategy(ABC):
"""
퀀트 전략의 기본 클래스
모든 전략 구현체는 이 클래스를 상속받아야 합니다.
"""
@property
@abstractmethod
def name(self) -> str:
"""전략 이름"""
pass
@property
@abstractmethod
def description(self) -> str:
"""전략 설명"""
pass
@property
@abstractmethod
def version(self) -> str:
"""전략 버전"""
pass
@property
def default_parameters(self) -> Dict[str, Any]:
"""기본 파라미터"""
return {}
@abstractmethod
def execute(self, parameters: Dict[str, Any] = None) -> Dict[str, Any]:
"""
전략을 실행합니다.
Args:
parameters: 실행 파라미터
Returns:
실행 결과
"""
pass
def validate_parameters(self, parameters: Dict[str, Any]) -> bool:
"""
파라미터 유효성 검사
Args:
parameters: 검사할 파라미터
Returns:
유효성 검사 결과
"""
return True
class StrategyRegistry:
"""전략 구현체 레지스트리"""
_strategies = {}
@classmethod
def register(cls, strategy_class: BaseQuantStrategy):
"""전략 구현체를 레지스트리에 등록"""
strategy_instance = strategy_class()
key = f"{strategy_instance.name}:{strategy_instance.version}"
cls._strategies[key] = strategy_class
return strategy_class
@classmethod
def get_strategy(cls, name: str, version: str) -> BaseQuantStrategy:
"""전략 구현체를 가져옵니다"""
key = f"{name}:{version}"
strategy_class = cls._strategies.get(key)
if strategy_class:
return strategy_class()
raise ValueError(f"Strategy {key} not found in registry")
@classmethod
def list_strategies(cls) -> Dict[str, Dict[str, Any]]:
"""등록된 모든 전략 목록을 반환합니다"""
strategies = {}
for key, strategy_class in cls._strategies.items():
strategy_instance = strategy_class()
name = strategy_instance.name
if name not in strategies:
strategies[name] = {
'name': name,
'description': strategy_instance.description,
'versions': []
}
strategies[name]['versions'].append({
'version': strategy_instance.version,
'default_parameters': strategy_instance.default_parameters
})
return strategies
@classmethod
def get_strategy_key(cls, name: str, version: str) -> str:
"""전략 키를 생성합니다"""
return f"{name}:{version}"
def strategy(cls):
"""전략 구현체를 자동으로 레지스트리에 등록하는 데코레이터"""
return StrategyRegistry.register(cls)

View File

@@ -0,0 +1,202 @@
from typing import Dict, Any
import time
import random
import math
from .base import BaseQuantStrategy, strategy
@strategy
class MovingAverageCrossover(BaseQuantStrategy):
"""이동평균선 교차 전략"""
@property
def name(self) -> str:
return "MovingAverageCrossover"
@property
def description(self) -> str:
return "단기 이동평균선이 장기 이동평균선을 상향 돌파할 때 매수, 하향 돌파할 때 매도하는 전략"
@property
def version(self) -> str:
return "1.0.0"
@property
def default_parameters(self) -> Dict[str, Any]:
return {
"short_window": 20,
"long_window": 50,
"initial_capital": 100000,
"position_size": 0.1
}
def validate_parameters(self, parameters: Dict[str, Any]) -> bool:
required_params = ["short_window", "long_window", "initial_capital"]
for param in required_params:
if param not in parameters:
return False
if parameters["short_window"] >= parameters["long_window"]:
return False
return True
def execute(self, parameters: Dict[str, Any] = None) -> Dict[str, Any]:
if parameters is None:
parameters = self.default_parameters
if not self.validate_parameters(parameters):
raise ValueError("Invalid parameters")
# 시뮬레이션 실행
time.sleep(1) # 실행 시간 시뮬레이션
# 모의 결과 생성
profit_rate = random.uniform(-0.15, 0.25)
trades_count = random.randint(15, 60)
win_rate = random.uniform(0.45, 0.75)
return {
"strategy": self.name,
"version": self.version,
"profit_loss": round(parameters["initial_capital"] * profit_rate, 2),
"profit_rate": round(profit_rate * 100, 2),
"trades_executed": trades_count,
"win_rate": round(win_rate, 3),
"execution_time": "1.2s",
"parameters_used": parameters,
"final_capital": round(parameters["initial_capital"] * (1 + profit_rate), 2)
}
@strategy
class RSIMeanReversion(BaseQuantStrategy):
"""RSI 평균회귀 전략"""
@property
def name(self) -> str:
return "RSIMeanReversion"
@property
def description(self) -> str:
return "RSI 지표를 이용한 평균회귀 전략. RSI가 과매수/과매도 구간에서 반대 방향으로 거래"
@property
def version(self) -> str:
return "1.0.0"
@property
def default_parameters(self) -> Dict[str, Any]:
return {
"rsi_period": 14,
"oversold_threshold": 30,
"overbought_threshold": 70,
"initial_capital": 100000,
"position_size": 0.05
}
def validate_parameters(self, parameters: Dict[str, Any]) -> bool:
required_params = ["rsi_period", "oversold_threshold", "overbought_threshold", "initial_capital"]
for param in required_params:
if param not in parameters:
return False
if not (0 < parameters["oversold_threshold"] < parameters["overbought_threshold"] < 100):
return False
return True
def execute(self, parameters: Dict[str, Any] = None) -> Dict[str, Any]:
if parameters is None:
parameters = self.default_parameters
if not self.validate_parameters(parameters):
raise ValueError("Invalid parameters")
# 시뮬레이션 실행
time.sleep(1.5) # 실행 시간 시뮬레이션
# 모의 결과 생성
profit_rate = random.uniform(-0.10, 0.18)
trades_count = random.randint(25, 80)
win_rate = random.uniform(0.40, 0.65)
return {
"strategy": self.name,
"version": self.version,
"profit_loss": round(parameters["initial_capital"] * profit_rate, 2),
"profit_rate": round(profit_rate * 100, 2),
"trades_executed": trades_count,
"win_rate": round(win_rate, 3),
"execution_time": "1.5s",
"parameters_used": parameters,
"final_capital": round(parameters["initial_capital"] * (1 + profit_rate), 2),
"max_drawdown": round(random.uniform(0.05, 0.20), 3)
}
@strategy
class BollingerBandBreakout(BaseQuantStrategy):
"""볼린저 밴드 돌파 전략"""
@property
def name(self) -> str:
return "BollingerBandBreakout"
@property
def description(self) -> str:
return "볼린저 밴드 상한선 돌파시 매수, 하한선 돌파시 매도하는 돌파 전략"
@property
def version(self) -> str:
return "2.0.0"
@property
def default_parameters(self) -> Dict[str, Any]:
return {
"period": 20,
"std_dev": 2.0,
"initial_capital": 100000,
"position_size": 0.08,
"stop_loss": 0.05
}
def validate_parameters(self, parameters: Dict[str, Any]) -> bool:
required_params = ["period", "std_dev", "initial_capital"]
for param in required_params:
if param not in parameters:
return False
if parameters["std_dev"] <= 0 or parameters["period"] <= 0:
return False
return True
def execute(self, parameters: Dict[str, Any] = None) -> Dict[str, Any]:
if parameters is None:
parameters = self.default_parameters
if not self.validate_parameters(parameters):
raise ValueError("Invalid parameters")
# 시뮬레이션 실행
time.sleep(2) # 실행 시간 시뮬레이션
# 모의 결과 생성
profit_rate = random.uniform(-0.20, 0.30)
trades_count = random.randint(10, 40)
win_rate = random.uniform(0.35, 0.70)
return {
"strategy": self.name,
"version": self.version,
"profit_loss": round(parameters["initial_capital"] * profit_rate, 2),
"profit_rate": round(profit_rate * 100, 2),
"trades_executed": trades_count,
"win_rate": round(win_rate, 3),
"execution_time": "2.0s",
"parameters_used": parameters,
"final_capital": round(parameters["initial_capital"] * (1 + profit_rate), 2),
"sharpe_ratio": round(random.uniform(0.5, 2.5), 2),
"volatility": round(random.uniform(0.15, 0.35), 3)
}

View File

@@ -0,0 +1,61 @@
# Generated by Django 5.2.7 on 2025-10-04 04:39
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='QuantStrategy',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True)),
('description', models.TextField(blank=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('is_active', models.BooleanField(default=True)),
],
options={
'verbose_name_plural': 'Quant Strategies',
},
),
migrations.CreateModel(
name='StrategyVersion',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('version', models.CharField(max_length=20)),
('code', models.TextField()),
('parameters', models.JSONField(blank=True, default=dict)),
('created_at', models.DateTimeField(auto_now_add=True)),
('is_current', models.BooleanField(default=False)),
('strategy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='strategies.quantstrategy')),
],
options={
'ordering': ['-created_at'],
'unique_together': {('strategy', 'version')},
},
),
migrations.CreateModel(
name='StrategyExecution',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('execution_parameters', models.JSONField(blank=True, default=dict)),
('status', models.CharField(choices=[('pending', 'Pending'), ('running', 'Running'), ('completed', 'Completed'), ('failed', 'Failed')], default='pending', max_length=20)),
('started_at', models.DateTimeField(auto_now_add=True)),
('completed_at', models.DateTimeField(blank=True, null=True)),
('result', models.JSONField(blank=True, null=True)),
('error_message', models.TextField(blank=True)),
('strategy_version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='strategies.strategyversion')),
],
options={
'ordering': ['-started_at'],
},
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.2.7 on 2025-10-04 04:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('strategies', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='strategyversion',
name='code',
),
migrations.AddField(
model_name='strategyversion',
name='implementation_key',
field=models.CharField(default='', help_text='전략 구현체의 레지스트리 키', max_length=200),
),
]

View File

81
strategies/models.py Normal file
View File

@@ -0,0 +1,81 @@
from django.db import models
from django.core.exceptions import ValidationError
import json
from .base import StrategyRegistry
class QuantStrategy(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Quant Strategies"
class StrategyVersion(models.Model):
strategy = models.ForeignKey(QuantStrategy, on_delete=models.CASCADE, related_name='versions')
version = models.CharField(max_length=20)
implementation_key = models.CharField(max_length=200, default="", help_text="전략 구현체의 레지스트리 키")
parameters = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
is_current = models.BooleanField(default=False)
def clean(self):
if self.is_current:
StrategyVersion.objects.filter(
strategy=self.strategy,
is_current=True
).exclude(pk=self.pk).update(is_current=False)
if not self.implementation_key:
self.implementation_key = StrategyRegistry.get_strategy_key(
self.strategy.name, self.version
)
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
def get_strategy_implementation(self):
"""전략 구현체 인스턴스를 반환합니다"""
try:
name, version = self.implementation_key.split(':')
return StrategyRegistry.get_strategy(name, version)
except ValueError:
raise ValidationError(f"Invalid implementation key: {self.implementation_key}")
def __str__(self):
return f"{self.strategy.name} v{self.version}"
class Meta:
unique_together = ['strategy', 'version']
ordering = ['-created_at']
class StrategyExecution(models.Model):
EXECUTION_STATUS = [
('pending', 'Pending'),
('running', 'Running'),
('completed', 'Completed'),
('failed', 'Failed'),
]
strategy_version = models.ForeignKey(StrategyVersion, on_delete=models.CASCADE)
execution_parameters = models.JSONField(default=dict, blank=True)
status = models.CharField(max_length=20, choices=EXECUTION_STATUS, default='pending')
started_at = models.DateTimeField(auto_now_add=True)
completed_at = models.DateTimeField(null=True, blank=True)
result = models.JSONField(null=True, blank=True)
error_message = models.TextField(blank=True)
def __str__(self):
return f"Execution of {self.strategy_version} - {self.status}"
class Meta:
ordering = ['-started_at']

3
strategies/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
strategies/urls.py Normal file
View File

@@ -0,0 +1,9 @@
from django.urls import path
from . import views
urlpatterns = [
path('strategies/', views.list_strategies, name='list_strategies'),
path('strategies/execute/', views.execute_strategy, name='execute_strategy'),
path('strategies/implementations/', views.list_available_implementations, name='list_available_implementations'),
path('executions/<int:execution_id>/', views.execution_status, name='execution_status'),
]

161
strategies/views.py Normal file
View File

@@ -0,0 +1,161 @@
from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.utils import timezone
import json
import threading
import time
from .models import QuantStrategy, StrategyVersion, StrategyExecution
from .base import StrategyRegistry
from . import implementations # 구현체들을 로드하여 레지스트리에 등록
@require_http_methods(["GET"])
def list_strategies(request):
# 레지스트리에서 사용 가능한 전략들을 가져옴
available_strategies = StrategyRegistry.list_strategies()
# DB에서 관리되는 전략들을 가져옴
strategies = QuantStrategy.objects.filter(is_active=True).prefetch_related('versions')
strategy_list = []
for strategy in strategies:
current_version = strategy.versions.filter(is_current=True).first()
strategy_data = {
'id': strategy.id,
'name': strategy.name,
'description': strategy.description,
'created_at': strategy.created_at.isoformat(),
'current_version': {
'id': current_version.id,
'version': current_version.version,
'implementation_key': current_version.implementation_key,
'parameters': current_version.parameters,
'created_at': current_version.created_at.isoformat()
} if current_version else None,
'total_versions': strategy.versions.count(),
'available_implementations': available_strategies.get(strategy.name, {}).get('versions', [])
}
strategy_list.append(strategy_data)
return JsonResponse({
'strategies': strategy_list,
'total_count': len(strategy_list),
'available_implementations': available_strategies
})
@csrf_exempt
@require_http_methods(["POST"])
def execute_strategy(request):
try:
data = json.loads(request.body)
strategy_name = data.get('strategy_name')
version = data.get('version')
execution_parameters = data.get('parameters', {})
if not strategy_name:
return JsonResponse({
'error': 'strategy_name 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 = StrategyExecution.objects.create(
strategy_version=strategy_version,
execution_parameters=execution_parameters,
status='pending'
)
def run_strategy():
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 = strategy_impl.execute(merged_parameters)
execution.status = 'completed'
execution.result = result
execution.completed_at = timezone.now()
execution.save()
except Exception as e:
execution.status = 'failed'
execution.error_message = str(e)
execution.completed_at = timezone.now()
execution.save()
thread = threading.Thread(target=run_strategy)
thread.start()
return JsonResponse({
'execution_id': execution.id,
'status': 'pending',
'message': 'Strategy execution started'
})
except json.JSONDecodeError:
return JsonResponse({
'error': 'Invalid JSON in request body'
}, status=400)
except Exception as e:
return JsonResponse({
'error': str(e)
}, status=500)
@require_http_methods(["GET"])
def execution_status(request, execution_id):
execution = get_object_or_404(StrategyExecution, id=execution_id)
response_data = {
'execution_id': execution.id,
'strategy': execution.strategy_version.strategy.name,
'version': execution.strategy_version.version,
'status': execution.status,
'started_at': execution.started_at.isoformat(),
'execution_parameters': execution.execution_parameters
}
if execution.completed_at:
response_data['completed_at'] = execution.completed_at.isoformat()
if execution.status == 'completed' and execution.result:
response_data['result'] = execution.result
if execution.status == 'failed' and execution.error_message:
response_data['error_message'] = execution.error_message
return JsonResponse(response_data)
@require_http_methods(["GET"])
def list_available_implementations(request):
"""레지스트리에 등록된 모든 전략 구현체 목록을 반환"""
available_strategies = StrategyRegistry.list_strategies()
return JsonResponse({
'available_implementations': available_strategies
})