이 가이드는 QuantDinger 플랫폼에서 Python을 사용하여 거래 전략을 개발하는 방법을 자세히 설명합니다. QuantDinger는 데이터 액세스, 지표 계산 및 신호 생성을 지원하는 유연한 실행 환경을 제공합니다.
QuantDinger의 전략은 신호 제공자(Signal Provider) 모드를 기반으로 작동합니다. 시스템은 귀하의 Python 스크립트를 실행하며, 이 스크립트는 시장 데이터(DataFrame)를 처리하고 거래 신호를 출력합니다.
실행 흐름은 다음과 같습니다:
- 입력: 시스템은 OHLCV 데이터를 포함하는
df(Pandas DataFrame)를 스크립트 환경에 주입합니다. - 처리: Python(
pandas,numpy)을 사용하여 지표를 계산하고buy/sell로직을 정의합니다. - 출력: 플롯 데이터와 신호를 포함하는 특정
output딕셔너리를 구성합니다.
스크립트는 샌드박스 처리된 Python 환경에서 실행됩니다.
다음 라이브러리는 기본적으로 사용할 수 있습니다(import할 필요 없음):
pd(pandas)np(numpy)
df라는 이름의 Pandas DataFrame 변수가 전역 범위에 자동으로 존재합니다. 여기에는 선택한 심볼 및 시간대의 과거 시장 데이터가 포함되어 있습니다.
열 (Columns):
time: 타임스탬프 (datetime 또는 int, 컨텍스트에 따라 다름)open: 시가 (float)high: 고가 (float)low: 저가 (float)close: 종가 (float)volume: 거래량 (float)
예시:
# 종가 시리즈 가져오기
closes = df['close']
# 단순 이동 평균(SMA) 계산
sma_20 = df['close'].rolling(20).mean()표준 전략 스크립트는 세 부분으로 구성됩니다:
- 지표 계산: 기술적 지표를 계산합니다.
- 신호 생성: 매수 및 매도 신호 로직을 정의합니다.
- 출력 구성: 차트 표시 및 실행 엔진을 위한 결과를 포맷팅합니다.
표준 Pandas 연산을 사용하여 지표를 계산할 수 있습니다.
# 예시: 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()df 내에(또는 독립 변수로) buy와 sell이라는 이름의 두 개의 불리언(Boolean) Series를 반드시 생성해야 합니다.
True는 신호 트리거를 나타냅니다.False는 신호 없음을 나타냅니다.
중요: 엣지 트리거(Edge Triggering) 연속된 캔들에서 반복적으로 신호가 발생하는 것을 방지하기 위해(백엔드 설정에 따라 중복 주문으로 이어질 수 있음), 엣지 트리거 신호(조건이 참이 되는 순간에만 신호 발생)를 사용하는 것이 모범 사례입니다.
# 조건: 종가가 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"(약세 의도)을 출력하면 됩니다. 백엔드가 진입/청산 로직을 처리합니다.
차트에 표시하기 위해 일반적으로 신호 아이콘을 캔들 위나 아래에 배치합니다.
# 매수 마커를 저가보다 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))
]마지막 단계는 output 변수에 딕셔너리를 할당하는 것입니다. 이는 프론트엔드에 무엇을 그릴지, 백엔드에 신호가 어디에 있는지를 알려줍니다.
구조:
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: 아이콘 색상.
다음은 SMA(10)이 SMA(30)을 상향 돌파할 때 매수하고, 하향 돌파할 때 매도하는 전체 복사 가능한 전략 예제입니다.
# 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'
}
]
}롤링 계산(rolling(14) 등)은 데이터 시작 부분에 NaN 값을 생성합니다.
- 규칙: 신호를 생성하기 전에 항상
NaN을 처리하십시오. - 수정: 상황에 따라
.fillna(0)또는.fillna(False)를 사용하십시오.
시스템은 캔들의 종가에서 발생한 신호를 기반으로 거래를 실행합니다.
- 백테스트 엔진은 일반적으로 다음 캔들의 시가에서 주문을 실행합니다.
- 신호 로직은
close(현재 완료된 캔들) 또는shift(1)(이전 캔들)에 의존해야 합니다.shift(-1)은 절대 사용하지 마십시오.
계산 로직에서 DataFrame 행을 반복(for i in range(len(df)): ...)하는 것을 피하십시오. 매우 느립니다.
- 나쁨: 루프를 사용하여 SMA 계산.
- 좋음:
df['close'].rolling(...)사용. - 예외:
buy_marks/sell_marks리스트 구성에는 일반적으로 리스트 컴프리헨션이 필요하며, 이는 시각적 출력에만 사용되므로 허용됩니다.
일부 실행 모드에서는 print() 출력을 쉽게 볼 수 없으므로, 전략 로드에 실패하면 백엔드 로그(backend_api_python/logs/app.log)를 확인하십시오.
- 일반적인 오류:
KeyError(잘못된 열 이름). - 일반적인 오류:
ValueError(배열 길이 불일치).plots의 데이터 길이가df와 일치하는지 확인하십시오.