McClellan Oscillator를 활용한 트레이딩 전략(McClellan Oscillator Trading Strategy)

McClellan Oscillator는 Sherman and Marian McClellan에 의해 개발된 시장 너비 지표로, 주가 지수의 단기 및 장기 추세를 파악하는 데 사용된다. 이 지표는 NYSE의 상승 종목 수와 하락 종목 수의 차이를 이동평균한 값으로 계산되며, 시장의 강도와 방향성을 나타낸다.

McClellan Oscillator의 계산 과정은 다음과 같다:

  1. NYSE의 상승 종목 수(Advances)와 하락 종목 수(Declines)를 구한다.
  2. A/D 라인 = Advances – Declines
  3. McClellan Oscillator = 19일 지수이동평균(EMA) – 39일 EMA

McClellan Oscillator는 일반적으로 +100에서 -100 사이의 값을 가진다. 양수일 때는 단기 상승 추세를, 음수일 때는 단기 하락 추세를 나타낸다. 또한 지표의 변화 속도와 방향 전환점을 관찰하면 시장의 전환점을 포착할 수 있다.

예를 들어, McClellan Oscillator가 음수에서 양수로 전환되면 상승 추세로의 전환을 시사하고, 양수에서 음수로 전환되면 하락 추세로의 전환을 의미할 수 있다. 지표가 극단적인 수준(예: +100 이상 또는 -100 이하)에 도달하면 추세의 지속 가능성이 낮아지므로, 반전 가능성을 고려해야 한다.

McClellan Oscillator를 활용한 매매 전략은 다음과 같이 구성할 수 있다:

  1. 추세 추종 전략: McClellan Oscillator가 양수일 때 매수하고, 음수일 때 매도한다.
  2. 지표 크로스오버 전략: McClellan Oscillator와 시그널 라인(예: 9일 EMA)의 크로스오버를 활용하여, Golden Cross 발생 시 매수, Dead Cross 발생 시 매도한다.
  3. 극단값 반전 전략: McClellan Oscillator가 극단적인 수준에 도달했을 때 반대 방향으로 매매한다. 예를 들어, 지표가 +100 이상일 때 매도하고, -100 이하일 때 매수한다.

파이썬을 활용하여 McClellan Oscillator 기반의 트레이딩 전략을 백테스팅해보자. 필요한 라이브러리는 다음과 같다:

  • pandas: 데이터 조작 및 분석
  • numpy: 수치 계산
  • matplotlib: 데이터 시각화
  • FinanceDataReader: 주가 데이터 수집

가상환경을 활성화하고 필요한 라이브러리를 설치한다.

source venv/bin/activate
pip install pandas numpy matplotlib FinanceDataReader

FinanceDataReader를 설치하면 종속 모듈도 함께 설치된다.

이제 McClellan Oscillator를 계산하고 매매 신호를 생성하는 코드를 작성해보자.

import FinanceDataReader as fdr
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 데이터 가져오기
symbol = "VOO"
start_date = "1900-01-01"
end_date = "2023-05-01"

try:
    data = fdr.DataReader(symbol, start_date, end_date)
except Exception as e:
    print(f"Error occurred while fetching data: {e}")
    exit(1)

# 상승/하락 종목 수 계산 함수
def get_advances_declines(data):
    advances = (data['Close'] > data['Open']).astype(int)
    declines = (data['Close'] < data['Open']).astype(int)
    return advances, declines

# McClellan Oscillator 계산 함수
def calculate_mcclellan_oscillator(advances, declines, fast_period=19, slow_period=39):
    ad_line = advances - declines
    fast_ema = ad_line.ewm(span=fast_period, adjust=False).mean()
    slow_ema = ad_line.ewm(span=slow_period, adjust=False).mean()
    mcclellan_oscillator = fast_ema - slow_ema
    return mcclellan_oscillator

# 매매 신호 생성 함수
def get_signals(mcclellan_oscillator, upper_threshold=100, lower_threshold=-100):
    signals = pd.DataFrame(index=mcclellan_oscillator.index)
    signals['mcclellan_oscillator'] = mcclellan_oscillator
    signals['signal'] = 0

    signals['signal'][mcclellan_oscillator > upper_threshold] = -1
    signals['signal'][mcclellan_oscillator < lower_threshold] = 1

    signals['positions'] = signals['signal'].diff()
    return signals

# 상승/하락 종목 수 계산
advances, declines = get_advances_declines(data)

# McClellan Oscillator 계산
mcclellan_oscillator = calculate_mcclellan_oscillator(advances, declines)

# 매매 신호 생성
signals = get_signals(mcclellan_oscillator)

# 백테스팅
initial_capital = 10000
positions = pd.DataFrame(index=signals.index).fillna(0.0)
positions[symbol] = 100 * signals['signal'].fillna(0)

portfolio = positions.multiply(data['Close'], axis=0)
pos_diff = positions.diff()

portfolio['holdings'] = (positions.multiply(data['Close'], axis=0)).sum(axis=1)
portfolio['cash'] = initial_capital - (pos_diff.multiply(data['Close'], axis=0)).sum(axis=1).cumsum()

portfolio['total'] = portfolio['cash'] + portfolio['holdings']
portfolio['returns'] = portfolio['total'].pct_change()

# 결과 출력
print(f"Final portfolio value: ${portfolio['total'][-1]:,.2f}")
print(f"Total returns: {100 * (portfolio['total'][-1] / initial_capital - 1):.2f}%")

# 시각화
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(15, 15), gridspec_kw={'height_ratios': [2, 1, 1]})

ax1.plot(data['Close'], label='Price')
ax1.set_ylabel('Price')
ax1.set_title('Price Chart')
ax1.legend(loc='upper left')

ax2.plot(mcclellan_oscillator, label='McClellan Oscillator')
ax2.axhline(y=100, color='r', linestyle='--', label='Overbought')
ax2.axhline(y=-100, color='g', linestyle='--', label='Oversold')
ax2.set_ylabel('McClellan Oscillator')
ax2.legend(loc='upper left')

ax3.plot(portfolio['total'], label='Portfolio Value')
ax3.set_ylabel('Portfolio Value')
ax3.set_title('Portfolio Performance')
ax3.legend(loc='upper left')

plt.tight_layout()
plt.show()

위 코드에서는 FinanceDataReader를 사용하여 VOO ETF의 전체 기간 주가 데이터를 가져온다. start_date를 가능한 한 오래전 날짜로 설정하여 모든 데이터를 가져올 수 있도록 한다.

get_advances_declines 함수는 일별 상승 종목 수와 하락 종목 수를 계산한다. calculate_mcclellan_oscillator 함수는 상승 종목 수와 하락 종목 수를 이용하여 McClellan Oscillator를 계산한다.

get_signals 함수는 McClellan Oscillator를 기준으로 매매 신호를 생성한다. 지표가 상단 임계값(기본값: +100)을 초과할 때 매도 신호(-1)를, 하단 임계값(기본값: -100)을 하회할 때 매수 신호(1)를 생성한다.

백테스팅을 위해 초기 자본금을 10,000달러로 설정하고, 생성된 매매 신호를 바탕으로 포트폴리오 가치를 계산한다. 최종적으로 총 수익률과 포트폴리오 가치를 출력한다.

matplotlib을 사용하여 주가, McClellan Oscillator, 포트폴리오 가치를 시각화한다. McClellan Oscillator 차트에는 과매수(+100)와 과매도(-100) 기준선을 표시하여 매매 신호 발생 구간을 나타낸다.

코드 실행 중 발생할 수 있는 오류를 처리하기 위해 try-except 문을 사용하였다. 데이터를 가져오는 과정에서 오류가 발생하면 에러 메시지를 출력하고 프로그램을 종료한다.

Leave a Reply

error: Content is protected !!