6ce128f576
Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
236 lines
8.6 KiB
Markdown
236 lines
8.6 KiB
Markdown
# QuantDinger Python 전략 개발 가이드
|
|
|
|
이 가이드는 QuantDinger 플랫폼에서 Python을 사용하여 거래 전략을 개발하는 방법을 자세히 설명합니다. QuantDinger는 데이터 액세스, 지표 계산 및 신호 생성을 지원하는 유연한 실행 환경을 제공합니다.
|
|
|
|
## 1. 개요
|
|
|
|
QuantDinger의 전략은 **신호 제공자(Signal Provider)** 모드를 기반으로 작동합니다. 시스템은 귀하의 Python 스크립트를 실행하며, 이 스크립트는 시장 데이터(DataFrame)를 처리하고 거래 신호를 출력합니다.
|
|
|
|
실행 흐름은 다음과 같습니다:
|
|
1. **입력**: 시스템은 OHLCV 데이터를 포함하는 `df`(Pandas DataFrame)를 스크립트 환경에 주입합니다.
|
|
2. **처리**: Python(`pandas`, `numpy`)을 사용하여 지표를 계산하고 `buy`/`sell` 로직을 정의합니다.
|
|
3. **출력**: 플롯 데이터와 신호를 포함하는 특정 `output` 딕셔너리를 구성합니다.
|
|
|
|
---
|
|
|
|
## 2. 환경 및 데이터
|
|
|
|
스크립트는 샌드박스 처리된 Python 환경에서 실행됩니다.
|
|
|
|
### 2.1 사전 가져오기된 라이브러리
|
|
다음 라이브러리는 기본적으로 사용할 수 있습니다(`import`할 **필요 없음**):
|
|
* `pd` (pandas)
|
|
* `np` (numpy)
|
|
|
|
### 2.2 입력 데이터 (`df`)
|
|
`df`라는 이름의 Pandas DataFrame 변수가 전역 범위에 자동으로 존재합니다. 여기에는 선택한 심볼 및 시간대의 과거 시장 데이터가 포함되어 있습니다.
|
|
|
|
**열 (Columns):**
|
|
* `time`: 타임스탬프 (datetime 또는 int, 컨텍스트에 따라 다름)
|
|
* `open`: 시가 (float)
|
|
* `high`: 고가 (float)
|
|
* `low`: 저가 (float)
|
|
* `close`: 종가 (float)
|
|
* `volume`: 거래량 (float)
|
|
|
|
**예시:**
|
|
```python
|
|
# 종가 시리즈 가져오기
|
|
closes = df['close']
|
|
|
|
# 단순 이동 평균(SMA) 계산
|
|
sma_20 = df['close'].rolling(20).mean()
|
|
```
|
|
|
|
---
|
|
|
|
## 3. 전략 개발
|
|
|
|
표준 전략 스크립트는 세 부분으로 구성됩니다:
|
|
1. **지표 계산**: 기술적 지표를 계산합니다.
|
|
2. **신호 생성**: 매수 및 매도 신호 로직을 정의합니다.
|
|
3. **출력 구성**: 차트 표시 및 실행 엔진을 위한 결과를 포맷팅합니다.
|
|
|
|
### 3.1 지표 계산
|
|
표준 Pandas 연산을 사용하여 지표를 계산할 수 있습니다.
|
|
|
|
```python
|
|
# 예시: MACD 계산
|
|
short_window = 12
|
|
long_window = 26
|
|
signal_window = 9
|
|
|
|
ema12 = df['close'].ewm(span=short_window, adjust=False).mean()
|
|
ema26 = df['close'].ewm(span=long_window, adjust=False).mean()
|
|
macd = ema12 - ema26
|
|
signal_line = macd.ewm(span=signal_window, adjust=False).mean()
|
|
```
|
|
|
|
### 3.2 신호 생성 (중요)
|
|
|
|
`df` 내에(또는 독립 변수로) `buy`와 `sell`이라는 이름의 두 개의 불리언(Boolean) Series를 **반드시 생성해야 합니다**.
|
|
|
|
* `True`는 신호 트리거를 나타냅니다.
|
|
* `False`는 신호 없음을 나타냅니다.
|
|
|
|
**중요: 엣지 트리거(Edge Triggering)**
|
|
연속된 캔들에서 반복적으로 신호가 발생하는 것을 방지하기 위해(백엔드 설정에 따라 중복 주문으로 이어질 수 있음), **엣지 트리거** 신호(조건이 참이 되는 순간에만 신호 발생)를 사용하는 것이 모범 사례입니다.
|
|
|
|
```python
|
|
# 조건: 종가가 SMA 20 상향 돌파
|
|
condition_buy = (df['close'] > sma_20) & (df['close'].shift(1) <= sma_20.shift(1))
|
|
|
|
# 조건: 종가가 SMA 20 하향 돌파
|
|
condition_sell = (df['close'] < sma_20) & (df['close'].shift(1) >= sma_20.shift(1))
|
|
|
|
# df에 할당 (백테스팅에 필수)
|
|
df['buy'] = condition_buy.fillna(False)
|
|
df['sell'] = condition_sell.fillna(False)
|
|
```
|
|
|
|
**신호 유형에 대한 참고:**
|
|
* QuantDinger는 전략 구성(롱 전용, 숏 전용 또는 양방향)에 따라 신호를 정규화합니다.
|
|
* 스크립트는 단순히 "buy"(강세 의도) 또는 "sell"(약세 의도)을 출력하면 됩니다. 백엔드가 진입/청산 로직을 처리합니다.
|
|
|
|
### 3.3 시각적 마커
|
|
차트에 표시하기 위해 일반적으로 신호 아이콘을 캔들 위나 아래에 배치합니다.
|
|
|
|
```python
|
|
# 매수 마커를 저가보다 0.5% 아래에 배치
|
|
buy_marks = [
|
|
df['low'].iloc[i] * 0.995 if df['buy'].iloc[i] else None
|
|
for i in range(len(df))
|
|
]
|
|
|
|
# 매도 마커를 고가보다 0.5% 위에 배치
|
|
sell_marks = [
|
|
df['high'].iloc[i] * 1.005 if df['sell'].iloc[i] else None
|
|
for i in range(len(df))
|
|
]
|
|
```
|
|
|
|
### 3.4 `output` 변수 (필수)
|
|
마지막 단계는 `output` 변수에 딕셔너리를 할당하는 것입니다. 이는 프론트엔드에 무엇을 그릴지, 백엔드에 신호가 어디에 있는지를 알려줍니다.
|
|
|
|
**구조:**
|
|
```python
|
|
output = {
|
|
"name": "내 전략 이름",
|
|
"plots": [ ... ], # 그릴 라인/지표 목록
|
|
"signals": [ ... ] # 신호 마커 목록
|
|
}
|
|
```
|
|
|
|
**Plots Schema (플롯 구성):**
|
|
* `name`: 범례 이름 (예: "SMA 20")
|
|
* `data`: 값 목록 (`df` 길이와 일치해야 함). `.tolist()`를 사용하여 변환.
|
|
* `color`: 16진수 색상 문자열 (예: "#ff0000").
|
|
* `overlay`: `True`는 메인 차트(가격) 위에 그리기, `False`는 보조 차트(RSI/MACD 등)에 그리기.
|
|
|
|
**Signals Schema (신호 구성):**
|
|
* `type`: "buy" 또는 "sell"이어야 합니다.
|
|
* `text`: 아이콘에 표시할 텍스트 (예: "B", "S").
|
|
* `data`: 값 목록 (가격 위치). 신호 없는 곳은 `None`.
|
|
* `color`: 아이콘 색상.
|
|
|
|
---
|
|
|
|
## 4. 전체 예제: 이중 이동 평균 교차 (Dual SMA)
|
|
|
|
다음은 SMA(10)이 SMA(30)을 상향 돌파할 때 매수하고, 하향 돌파할 때 매도하는 전체 복사 가능한 전략 예제입니다.
|
|
|
|
```python
|
|
# 1. 지표 계산
|
|
# -----------------------
|
|
# 단기 및 장기 SMA 계산
|
|
sma_short = df['close'].rolling(10).mean()
|
|
sma_long = df['close'].rolling(30).mean()
|
|
|
|
# 2. 신호 로직
|
|
# -----------------------
|
|
# 매수: 단기 SMA가 장기 SMA 상향 돌파
|
|
raw_buy = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1))
|
|
|
|
# 매도: 단기 SMA가 장기 SMA 하향 돌파
|
|
raw_sell = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1))
|
|
|
|
# NaN 정리 및 불리언 타입 보장
|
|
buy = raw_buy.fillna(False)
|
|
sell = raw_sell.fillna(False)
|
|
|
|
# df 열에 할당 (백엔드 실행의 핵심)
|
|
df['buy'] = buy
|
|
df['sell'] = sell
|
|
|
|
# 3. 시각적 포맷팅
|
|
# -----------------------
|
|
# 마커 위치 계산
|
|
buy_marks = [
|
|
df['low'].iloc[i] * 0.995 if buy.iloc[i] else None
|
|
for i in range(len(df))
|
|
]
|
|
|
|
sell_marks = [
|
|
df['high'].iloc[i] * 1.005 if sell.iloc[i] else None
|
|
for i in range(len(df))
|
|
]
|
|
|
|
# 4. 최종 출력
|
|
# -----------------------
|
|
output = {
|
|
'name': 'Dual SMA Strategy',
|
|
'plots': [
|
|
{
|
|
'name': 'SMA 10',
|
|
'data': sma_short.fillna(0).tolist(),
|
|
'color': '#1890ff',
|
|
'overlay': True
|
|
},
|
|
{
|
|
'name': 'SMA 30',
|
|
'data': sma_long.fillna(0).tolist(),
|
|
'color': '#faad14',
|
|
'overlay': True
|
|
}
|
|
],
|
|
'signals': [
|
|
{
|
|
'type': 'buy',
|
|
'text': 'B',
|
|
'data': buy_marks,
|
|
'color': '#00E676'
|
|
},
|
|
{
|
|
'type': 'sell',
|
|
'text': 'S',
|
|
'data': sell_marks,
|
|
'color': '#FF5252'
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## 5. 모범 사례 및 문제 해결
|
|
|
|
### 5.1 NaN 처리
|
|
롤링 계산(`rolling(14)` 등)은 데이터 시작 부분에 `NaN` 값을 생성합니다.
|
|
* **규칙**: 신호를 생성하기 전에 항상 `NaN`을 처리하십시오.
|
|
* **수정**: 상황에 따라 `.fillna(0)` 또는 `.fillna(False)`를 사용하십시오.
|
|
|
|
### 5.2 미래 참조 편향 (Look-ahead Bias)
|
|
시스템은 캔들의 **종가**에서 발생한 신호를 기반으로 거래를 실행합니다.
|
|
* 백테스트 엔진은 일반적으로 **다음 캔들의 시가**에서 주문을 실행합니다.
|
|
* 신호 로직은 `close`(현재 완료된 캔들) 또는 `shift(1)`(이전 캔들)에 의존해야 합니다. `shift(-1)`은 절대 사용하지 마십시오.
|
|
|
|
### 5.3 성능
|
|
계산 로직에서 DataFrame 행을 반복(`for i in range(len(df)): ...`)하는 것을 피하십시오. 매우 느립니다.
|
|
* **나쁨**: 루프를 사용하여 SMA 계산.
|
|
* **좋음**: `df['close'].rolling(...)` 사용.
|
|
* **예외**: `buy_marks`/`sell_marks` 리스트 구성에는 일반적으로 리스트 컴프리헨션이 필요하며, 이는 시각적 출력에만 사용되므로 허용됩니다.
|
|
|
|
### 5.4 디버깅
|
|
일부 실행 모드에서는 `print()` 출력을 쉽게 볼 수 없으므로, 전략 로드에 실패하면 백엔드 로그(`backend_api_python/logs/app.log`)를 확인하십시오.
|
|
* 일반적인 오류: `KeyError` (잘못된 열 이름).
|
|
* 일반적인 오류: `ValueError` (배열 길이 불일치). `plots`의 데이터 길이가 `df`와 일치하는지 확인하십시오.
|
|
|