개념
ADX(평균 지향 이동 지수)는 웰스 와일더(Welles Wilder)가 1978년에 개발한 기술적 분석 지표이다. 이 지표는 주가 동향의 강도를 측정하여 추세의 강도를 판단하는 데 사용된다. ADX는 0에서 100 사이의 값으로 표현되며, 일반적으로 25 이상이면 강력한 추세, 25 이하이면 약한 추세 또는 범위 bound 상태로 해석한다. ADX는 다음 세 가지 지표로 구성된다.
- +DI (Positive Directional Index): 강력한 상승 추세를 나타내는 지표
- 계산식: +DI = (Smoothed +DM) / (ATR) * 100
- +DM (Positive Directional Movement) = 당일 고가 – 전일 고가 (단, +DM은 음수일 경우 0으로 처리)
- ATR (Average True Range) = TR의 n일 이동평균 (일반적으로 n=14)
- TR (True Range) = Max[(당일 고가 – 당일 저가), abs(당일 고가 – 전일 종가), abs(당일 저가 – 전일 종가)]
- 계산식: +DI = (Smoothed +DM) / (ATR) * 100
- -DI (Negative Directional Index): 강력한 하락 추세를 나타내는 지표
- 계산식: -DI = (Smoothed -DM) / (ATR) * 100
- -DM (Negative Directional Movement) = 전일 저가 – 당일 저가 (단, -DM은 음수일 경우 0으로 처리)
- 계산식: -DI = (Smoothed -DM) / (ATR) * 100
- ADX: +DI와 -DI의 이동평균 값으로, 추세의 강도를 측정한다.
- 계산식: ADX = Smoothed(+DI – -DI) / (+DI + -DI) * 100의 n일 이동평균 (일반적으로 n=14) ADX의 핵심 아이디어는 +DI와 -DI의 값을 비교하여 상승 추세와 하락 추세를 구분하고, ADX 값을 통해 그 추세의 강도를 판단하는 것이다. 이를 활용하여 추세 방향과 추세 전환 시점을 파악할 수 있다.
매매전략
ADX 지표를 활용한 대표적인 매매전략은 다음과 같다.
- ADX가 25 이상일 때 매매: ADX가 25 이상이면 강력한 추세가 형성되었다고 판단하여, +DI와 -DI의 값을 비교하여 매수/매도 시그널을 생성한다.
- +DI와 -DI 교차 시점 활용: +DI와 -DI가 교차하면 추세 전환 시점으로 보고 반대 포지션을 취한다.
- ADX 하락 시 보유 포지션 청산: ADX가 하락하기 시작하면 추세가 약화되는 것으로 보고 보유 포지션을 청산한다.
파이썬 코드 예시
아래는 야후파이낸스에서 VOO ETF 데이터를 가져와 ADX 지표와 매매전략을 파이썬으로 구현한 예시 코드이다.
import yfinance as yf
import pandas as pd
def adx(df, window=14):
df['TrueRange'] = df['High'].combine(df['Close'].shift(), max) - df['Low'].combine(df['Close'].shift(), min)
df['PDM'] = (df['High'] - df['High'].shift()).apply(lambda x: x if x > 0 else 0)
df['NDM'] = (df['Low'].shift() - df['Low']).apply(lambda x: x if x > 0 else 0)
df['PDM_Smooth'] = df['PDM'].ewm(alpha=1/window).mean()
df['NDM_Smooth'] = df['NDM'].ewm(alpha=1/window).mean()
df['TR_Smooth'] = df['TrueRange'].ewm(alpha=1/window).mean()
df['+DI'] = 100 * (df['PDM_Smooth'] / df['TR_Smooth'])
df['-DI'] = 100 * (df['NDM_Smooth'] / df['TR_Smooth'])
df['ADX'] = df['+DI'].ewm(alpha=1/window).mean() - df['-DI'].ewm(alpha=1/window).mean()
return df['+DI'], df['-DI'], df['ADX']
def adx_strategy(df, pdi, ndi, adx):
buy_signals = (pdi > ndi) & (adx > 25)
sell_signals = (pdi < ndi) & (adx > 25)
df['Signal'] = 0.0
df['Signal'][buy_signals] = 1.0
df['Signal'][sell_signals] = -1.0
return df
# 데이터 로드
voo = yf.Ticker("VOO")
data = voo.history(period="max")
# ADX 계산
pdi, ndi, adx = adx(data)
# 매매 시그널 생성
trading_signals = adx_strategy(data, pdi, ndi, adx)
# 백테스트
trading_signals['Returns'] = trading_signals['Signal'].shift(1) * data['Close'].pct_change()
trading_signals['Strategy'] = trading_signals['Returns'].cumsum().apply(np.exp)
trading_signals['Buy_and_Hold'] = data['Close'] / data['Close'].iloc[0]
# 결과 시각화
trading_signals[['Strategy', 'Buy_and_Hold']].plot()
이 코드에서는 먼저 yfinance
라이브러리를 사용하여 VOO ETF의 역사적 데이터를 불러온다. 그 후 adx
함수를 통해 +DI, -DI, ADX 값을 계산하고, adx_strategy
함수에서 구매 신호와 판매 신호를 생성한다.
매매 시그널을 활용하여 백테스트를 수행한다. 각 거래일의 수익률을 계산하고, 전략 수익률과 Buy & Hold 수익률을 비교할 수 있다. 마지막으로 결과를 시각화한다.
이 코드를 통해 ADX 매매전략의 성과를 평가하고, 실제 거래에 적용할 수 있다.