On-Balance Volume(OBV) 을 이용한 매매전략

On-Balance Volume(OBV)의 개념과 유래

On-Balance Volume(OBV)는 Joe Granville이 1963년에 개발한 기술적 지표이다. OBV는 거래량과 가격의 변화를 결합하여 현재 추세의 강도를 측정한다. Granville은 거래량이 가격보다 선행한다는 가정 하에 OBV를 고안했다. 그는 거래량의 변화가 가격 변화를 예측할 수 있는 중요한 단서라고 믿었다.

OBV의 계산은 다음과 같다. 주가가 전일 대비 상승하면 해당일의 거래량을 누적하고, 주가가 전일 대비 하락하면 해당일의 거래량을 차감한다. 주가에 변화가 없으면 OBV에도 변화가 없다. 이렇게 계산된 OBV는 주가와 함께 차트에 그려져 분석에 활용된다.

OBV의 해석은 다음과 같다. OBV가 상승 추세를 보이면 매수세가 강한 것으로, 하락 추세를 보이면 매도세가 강한 것으로 판단한다. 특히 주가와 OBV의 움직임이 엇갈릴 때 추세 전환의 신호로 해석한다. 예를 들어, 주가는 새로운 고점을 갱신하는데 OBV가 이를 확인하지 않는다면(Bearish Divergence), 상승 추세의 약화로 해석할 수 있다.

On-Balance Volume을 활용한 매매 전략

OBV를 활용한 매매 전략의 핵심은 주가와 OBV 사이의 Divergence를 포착하는 것이다. 일반적인 매매 규칙은 다음과 같다.

  1. 주가가 새로운 고점을 형성하는데 OBV가 이를 확인하지 않으면(Bearish Divergence), 매도 신호로 해석한다.
  2. 주가가 새로운 저점을 형성하는데 OBV가 이를 확인하지 않으면(Bullish Divergence), 매수 신호로 해석한다.
  3. OBV가 중요한 지지선이나 저항선을 돌파할 때, 특히 주가가 아직 돌파하지 않았다면, 추세 전환의 신호로 해석할 수 있다.
  4. OBV의 변화율(Slope)도 중요한 단서가 될 수 있다. OBV의 상승 속도가 빨라지면 매수세 증가, 하락 속도가 빨라지면 매도세 증가로 해석할 수 있다.

그러나 OBV는 다른 기술적 지표와 마찬가지로 시장의 노이즈에 영향을 받을 수 있다. 따라서 OBV 단독으로 매매 신호를 판단하기보다는, 다른 지표나 차트 패턴과 함께 분석하는 것이 좋다. 또한 거래량이 적은 종목에서는 OBV의 신뢰성이 낮아질 수 있음에 유의해야 한다.

On-Balance Volume 매매 전략의 파이썬 코드 예시

다음은 Yahoo Finance에서 VOO ETF의 데이터를 가져와 OBV를 계산하고, 이를 기반으로 매매 신호를 생성하는 파이썬 코드 예시이다.

import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# VOO ETF 데이터 가져오기 (최대 기간)
data = yf.download('VOO', start='2010-09-07')

# OBV 계산 함수
def calc_obv(data):
    obv = [0]

    for i in range(1, len(data.Close)):
        if data.Close[i] > data.Close[i-1]:
            obv.append(obv[-1] + data.Volume[i])
        elif data.Close[i] < data.Close[i-1]:
            obv.append(obv[-1] - data.Volume[i])
        else:
            obv.append(obv[-1])

    return obv

# OBV 기반 매매 신호 생성 함수
def generate_signals(data, window):
    signals = pd.DataFrame(index=data.index)
    signals['Signal'] = 0

    signals['OBV'] = calc_obv(data)
    signals['OBV_EMA'] = signals['OBV'].ewm(span=window).mean()
    signals['OBV_Diff'] = signals['OBV'] - signals['OBV_EMA']

    signals['Price_EMA'] = data.Close.ewm(span=window).mean()
    signals['Price_Diff'] = data.Close - signals['Price_EMA']

    signals.loc[(signals['OBV_Diff'] > 0) & (signals['Price_Diff'] < 0), 'Signal'] = 1
    signals.loc[(signals['OBV_Diff'] < 0) & (signals['Price_Diff'] > 0), 'Signal'] = -1

    return signals

# 백테스팅 함수
def backtest(data, signals, initial_capital):
    positions = pd.DataFrame(index=signals.index).fillna(0.0)
    portfolio = pd.DataFrame(index=signals.index).fillna(0.0)

    positions['VOO'] = signals['Signal']

    portfolio['Positions'] = (positions.multiply(data['Adj Close'], axis=0))
    portfolio['Cash'] = initial_capital - (positions.diff().multiply(data['Adj Close'], axis=0)).cumsum()
    portfolio['Total'] = portfolio['Positions'] + portfolio['Cash']
    portfolio['Returns'] = portfolio['Total'].pct_change()

    return portfolio

# 매매 신호 생성
window = 20
signals = generate_signals(data, window)

# 백테스팅
initial_capital = 10000
portfolio = backtest(data, signals, initial_capital)

# 결과 출력
print(f'초기 자본: ${initial_capital:,.0f}')
print(f'최종 자산: ${portfolio["Total"][-1]:,.0f}')
print(f'누적 수익률: {portfolio["Returns"].cumsum()[-1]:.2%}')

# 차트 그리기
fig, axes = plt.subplots(2, 1, figsize=(12, 8), sharex=True)

axes[0].plot(data.Close)
axes[0].set_ylabel('Price')

axes[1].plot(signals.OBV, label='OBV')
axes[1].plot(signals.OBV_EMA, label='OBV EMA')
axes[1].legend()
axes[1].set_ylabel('OBV')

plt.show()

위 코드에서는 먼저 calc_obv 함수를 사용하여 OBV를 계산한다. 그리고 generate_signals 함수에서는 주가와 OBV의 Divergence를 포착하기 위해, 각각의 지수이동평균(Exponential Moving Average)을 계산하고 그 차이를 비교한다. 주가는 EMA보다 낮은데 OBV는 EMA보다 높으면 매수 신호(1), 그 반대면 매도 신호(-1)로 처리한다.

backtest 함수에서는 생성된 매매 신호에 따라 매매를 시뮬레이션하고, 포트폴리오의 가치를 추적한다. 마지막으로 주가 차트와 OBV 차트를 그려 시각적으로 확인할 수 있다.

이 코드는 OBV를 활용한 간단한 매매 전략의 예시일 뿐, 실제 트레이딩에는 더 많은 고려 사항이 필요하다. 매매 신호의 강도, 필터링, 리스크 관리 등이 추가되어야 하며, 모든 전략은 충분한 백테스팅과 최적화를 거쳐야 한다. 또한 과거의 성과가 미래의 수익을 보장하지는 않는다는 점도 명심해야 한다.

Leave a Reply

error: Content is protected !!