452 lines
10 KiB
Markdown
452 lines
10 KiB
Markdown
# 콜백 API 가이드
|
|
|
|
## 📋 개요
|
|
|
|
전략 실행 시 `callback_url` 파라미터를 지정하면, 전략 실행이 완료된 후 해당 URL로 결과를 POST 메서드로 자동 전송합니다.
|
|
|
|
---
|
|
|
|
## 🚀 사용 방법
|
|
|
|
### 1. 기본 사용 예제
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8000/strategies/execute/ \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"strategy_name": "BoldAssetAllocation",
|
|
"parameters": {
|
|
"variant": "BAA-G4",
|
|
"initial_capital": 100000
|
|
},
|
|
"callback_url": "https://your-server.com/webhook/strategy-result"
|
|
}'
|
|
```
|
|
|
|
**응답:**
|
|
```json
|
|
{
|
|
"execution_id": 123,
|
|
"status": "pending",
|
|
"message": "Strategy execution started",
|
|
"callback_url": "https://your-server.com/webhook/strategy-result"
|
|
}
|
|
```
|
|
|
|
### 2. 콜백 URL 없이 실행 (기존 방식)
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8000/strategies/execute/ \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"strategy_name": "MovingAverageCrossover",
|
|
"parameters": {
|
|
"short_window": 20,
|
|
"long_window": 50
|
|
}
|
|
}'
|
|
```
|
|
|
|
---
|
|
|
|
## 📨 콜백 페이로드
|
|
|
|
전략 실행이 완료되면 다음과 같은 JSON 데이터가 `callback_url`로 POST 요청됩니다.
|
|
|
|
### 성공 시 (status: completed)
|
|
|
|
```json
|
|
{
|
|
"execution_id": 123,
|
|
"strategy": "BoldAssetAllocation",
|
|
"version": "1.0.0",
|
|
"status": "completed",
|
|
"started_at": "2025-10-04T12:00:00.000Z",
|
|
"completed_at": "2025-10-04T12:00:15.000Z",
|
|
"execution_parameters": {
|
|
"variant": "BAA-G4",
|
|
"initial_capital": 100000,
|
|
"use_real_data": false
|
|
},
|
|
"result": {
|
|
"strategy": "BoldAssetAllocation",
|
|
"variant": "BAA-G4",
|
|
"portfolio": {
|
|
"offensive_assets": ["VTI", "VEA"],
|
|
"weights": {
|
|
"VTI": 0.5,
|
|
"VEA": 0.5
|
|
}
|
|
},
|
|
"final_value": 125000,
|
|
"return": 0.25
|
|
}
|
|
}
|
|
```
|
|
|
|
### 실패 시 (status: failed)
|
|
|
|
```json
|
|
{
|
|
"execution_id": 124,
|
|
"strategy": "BoldAssetAllocation",
|
|
"version": "1.0.0",
|
|
"status": "failed",
|
|
"started_at": "2025-10-04T12:05:00.000Z",
|
|
"completed_at": "2025-10-04T12:05:02.000Z",
|
|
"execution_parameters": {
|
|
"variant": "INVALID"
|
|
},
|
|
"error_message": "Unknown variant: INVALID. Available variants: BAA-G12, BAA-G4, ..."
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔍 실행 상태 확인
|
|
|
|
콜백 전송 정보를 포함한 실행 상태 조회:
|
|
|
|
```bash
|
|
curl http://localhost:8000/executions/123/
|
|
```
|
|
|
|
**응답:**
|
|
```json
|
|
{
|
|
"execution_id": 123,
|
|
"strategy": "BoldAssetAllocation",
|
|
"version": "1.0.0",
|
|
"status": "completed",
|
|
"started_at": "2025-10-04T12:00:00.000Z",
|
|
"completed_at": "2025-10-04T12:00:15.000Z",
|
|
"execution_parameters": {...},
|
|
"result": {...},
|
|
"callback": {
|
|
"url": "https://your-server.com/webhook/strategy-result",
|
|
"sent": true,
|
|
"sent_at": "2025-10-04T12:00:15.500Z",
|
|
"response": {
|
|
"status_code": 200,
|
|
"response_text": "{\"status\": \"received\"}",
|
|
"headers": {
|
|
"Content-Type": "application/json"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🛠️ 콜백 서버 구현 예제
|
|
|
|
### Python (Flask)
|
|
|
|
```python
|
|
from flask import Flask, request, jsonify
|
|
|
|
app = Flask(__name__)
|
|
|
|
@app.route('/webhook/strategy-result', methods=['POST'])
|
|
def strategy_callback():
|
|
data = request.json
|
|
|
|
execution_id = data['execution_id']
|
|
status = data['status']
|
|
|
|
if status == 'completed':
|
|
result = data['result']
|
|
print(f"전략 실행 완료 (ID: {execution_id})")
|
|
print(f"최종 수익률: {result.get('return', 0) * 100:.2f}%")
|
|
|
|
# 결과를 데이터베이스에 저장하거나 다른 처리
|
|
# save_to_database(data)
|
|
|
|
elif status == 'failed':
|
|
error_message = data['error_message']
|
|
print(f"전략 실행 실패 (ID: {execution_id}): {error_message}")
|
|
|
|
# 에러 알림 전송
|
|
# send_error_notification(data)
|
|
|
|
return jsonify({'status': 'received'}), 200
|
|
|
|
if __name__ == '__main__':
|
|
app.run(port=8888)
|
|
```
|
|
|
|
### Node.js (Express)
|
|
|
|
```javascript
|
|
const express = require('express');
|
|
const app = express();
|
|
|
|
app.use(express.json());
|
|
|
|
app.post('/webhook/strategy-result', (req, res) => {
|
|
const { execution_id, status, result, error_message } = req.body;
|
|
|
|
if (status === 'completed') {
|
|
console.log(`전략 실행 완료 (ID: ${execution_id})`);
|
|
console.log(`최종 수익률: ${(result.return * 100).toFixed(2)}%`);
|
|
|
|
// 결과 처리
|
|
// saveToDatabase(req.body);
|
|
|
|
} else if (status === 'failed') {
|
|
console.error(`전략 실행 실패 (ID: ${execution_id}): ${error_message}`);
|
|
|
|
// 에러 처리
|
|
// sendErrorNotification(req.body);
|
|
}
|
|
|
|
res.json({ status: 'received' });
|
|
});
|
|
|
|
app.listen(8888, () => {
|
|
console.log('콜백 서버 시작: http://localhost:8888');
|
|
});
|
|
```
|
|
|
|
### Django View
|
|
|
|
```python
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from django.http import JsonResponse
|
|
import json
|
|
|
|
@csrf_exempt
|
|
def strategy_callback(request):
|
|
if request.method == 'POST':
|
|
data = json.loads(request.body)
|
|
|
|
execution_id = data['execution_id']
|
|
status = data['status']
|
|
|
|
if status == 'completed':
|
|
# 결과 처리
|
|
result = data['result']
|
|
print(f"전략 실행 완료: {result}")
|
|
|
|
elif status == 'failed':
|
|
# 에러 처리
|
|
error = data['error_message']
|
|
print(f"전략 실행 실패: {error}")
|
|
|
|
return JsonResponse({'status': 'received'})
|
|
|
|
return JsonResponse({'error': 'Method not allowed'}, status=405)
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 테스트 방법
|
|
|
|
### 1. 자동 테스트 스크립트 사용
|
|
|
|
```bash
|
|
python test_callback.py
|
|
```
|
|
|
|
이 스크립트는:
|
|
- 로컬에 콜백 수신 서버를 자동으로 시작
|
|
- 전략 실행 요청 (콜백 URL 포함)
|
|
- 콜백 수신 및 결과 출력
|
|
|
|
### 2. 수동 테스트
|
|
|
|
**터미널 1 - 콜백 서버:**
|
|
```bash
|
|
python -m http.server 8888
|
|
```
|
|
|
|
**터미널 2 - 전략 실행:**
|
|
```bash
|
|
curl -X POST http://localhost:8000/strategies/execute/ \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"strategy_name": "MovingAverageCrossover",
|
|
"parameters": {
|
|
"short_window": 10,
|
|
"long_window": 30
|
|
},
|
|
"callback_url": "http://localhost:8888/callback"
|
|
}'
|
|
```
|
|
|
|
### 3. 외부 테스트 도구
|
|
|
|
**webhook.site 사용:**
|
|
1. https://webhook.site 방문
|
|
2. 생성된 고유 URL 복사
|
|
3. 해당 URL을 `callback_url`로 사용
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8000/strategies/execute/ \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"strategy_name": "BoldAssetAllocation",
|
|
"parameters": {"variant": "BAA-G4"},
|
|
"callback_url": "https://webhook.site/your-unique-id"
|
|
}'
|
|
```
|
|
|
|
4. webhook.site에서 실시간으로 수신된 데이터 확인
|
|
|
|
---
|
|
|
|
## ⚙️ 설정 및 동작
|
|
|
|
### 콜백 타임아웃
|
|
|
|
콜백 POST 요청은 **10초 타임아웃**이 설정되어 있습니다.
|
|
|
|
### 재시도 정책
|
|
|
|
현재 콜백은 **1회만 시도**합니다. 실패 시 재시도하지 않습니다.
|
|
|
|
실패 시 `callback_response`에 에러 정보가 저장됩니다:
|
|
|
|
```json
|
|
{
|
|
"callback": {
|
|
"url": "https://unavailable.com/webhook",
|
|
"sent": false,
|
|
"sent_at": null,
|
|
"response": {
|
|
"error": "Connection refused"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 보안 고려사항
|
|
|
|
1. **HTTPS 사용 권장**: 프로덕션 환경에서는 HTTPS 콜백 URL 사용
|
|
2. **서명 검증**: 콜백 요청의 진위 확인을 위해 서명 메커니즘 고려
|
|
3. **IP 화이트리스트**: 알려진 IP에서만 콜백 수신
|
|
4. **Rate Limiting**: 과도한 요청 방지
|
|
|
|
---
|
|
|
|
## 📊 활용 사례
|
|
|
|
### 1. Slack 알림
|
|
|
|
```python
|
|
import requests
|
|
|
|
@app.route('/webhook/strategy-result', methods=['POST'])
|
|
def strategy_callback():
|
|
data = request.json
|
|
|
|
if data['status'] == 'completed':
|
|
result = data['result']
|
|
message = f"✅ 전략 실행 완료\n" \
|
|
f"전략: {data['strategy']}\n" \
|
|
f"수익률: {result.get('return', 0) * 100:.2f}%"
|
|
else:
|
|
message = f"❌ 전략 실행 실패\n" \
|
|
f"오류: {data['error_message']}"
|
|
|
|
# Slack Webhook
|
|
requests.post(
|
|
'https://hooks.slack.com/services/YOUR/WEBHOOK/URL',
|
|
json={'text': message}
|
|
)
|
|
|
|
return jsonify({'status': 'ok'})
|
|
```
|
|
|
|
### 2. 이메일 전송
|
|
|
|
```python
|
|
from django.core.mail import send_mail
|
|
|
|
@csrf_exempt
|
|
def strategy_callback(request):
|
|
data = json.loads(request.body)
|
|
|
|
if data['status'] == 'completed':
|
|
subject = f"전략 실행 완료: {data['strategy']}"
|
|
message = f"수익률: {data['result']['return'] * 100:.2f}%"
|
|
|
|
send_mail(
|
|
subject,
|
|
message,
|
|
'noreply@yoursite.com',
|
|
['admin@yoursite.com'],
|
|
)
|
|
|
|
return JsonResponse({'status': 'sent'})
|
|
```
|
|
|
|
### 3. 데이터베이스 저장
|
|
|
|
```python
|
|
@csrf_exempt
|
|
def strategy_callback(request):
|
|
data = json.loads(request.body)
|
|
|
|
StrategyResult.objects.create(
|
|
execution_id=data['execution_id'],
|
|
strategy_name=data['strategy'],
|
|
status=data['status'],
|
|
result=data.get('result'),
|
|
error=data.get('error_message'),
|
|
completed_at=data.get('completed_at')
|
|
)
|
|
|
|
return JsonResponse({'status': 'saved'})
|
|
```
|
|
|
|
---
|
|
|
|
## 🐛 문제 해결
|
|
|
|
### 콜백이 전송되지 않음
|
|
|
|
**확인사항:**
|
|
1. `callback_url`이 올바른 형식인지 확인
|
|
2. 콜백 서버가 실행 중인지 확인
|
|
3. 방화벽/보안 그룹 설정 확인
|
|
4. 실행 상태 API에서 `callback.response` 확인
|
|
|
|
```bash
|
|
curl http://localhost:8000/executions/{ID}/ | python -m json.tool
|
|
```
|
|
|
|
### 타임아웃 발생
|
|
|
|
콜백 서버의 응답 시간이 10초를 초과하는 경우:
|
|
|
|
**해결책:**
|
|
- 콜백 핸들러에서 빠르게 200 응답 후 백그라운드 처리
|
|
- 타임아웃 값 조정 필요 시 `views.py`의 `timeout=10` 수정
|
|
|
|
### localhost 콜백이 작동하지 않음
|
|
|
|
Docker 컨테이너 내부에서 실행 중인 경우:
|
|
|
|
**해결책:**
|
|
- `localhost` 대신 호스트 IP 사용
|
|
- Docker: `host.docker.internal` 사용 (Mac/Windows)
|
|
- ngrok 등으로 로컬 서버 외부 노출
|
|
|
|
---
|
|
|
|
## 📚 참고
|
|
|
|
- **API 전체 가이드**: `API_USAGE_GUIDE.md`
|
|
- **BAA 전략 설명**: `BAA_STRATEGY_README.md`
|
|
- **배포 가이드**: `DEPLOYMENT_GUIDE.md`
|
|
|
|
---
|
|
|
|
## 🔄 업데이트 로그
|
|
|
|
| 날짜 | 변경 내용 |
|
|
|------|-----------|
|
|
| 2025-10-04 | 콜백 기능 최초 구현 |
|