니콜라스 다바스 박스이론을 활용한 트레이딩 전략(Nicholas Darvas’ Box Theory Trading Strategy)

니콜라스 다바스 박스이론의 개념과 유래

니콜라스 다바스 박스이론은 1950년대에 헝가리 출신의 무용가이자 투자자인 니콜라스 다바스(Nicholas Darvas)가 개발한 기술적 분석 방법이다. 다바스는 자신의 투자 경험을 바탕으로 이 이론을 고안하였으며, 1960년에 출간된 그의 저서 “How I Made $2,000,000 in the Stock Market”에서 박스이론을 자세히 설명하였다.

박스이론의 핵심은 주가 차트에서 일정 기간 동안의 가격 범위를 ‘박스’로 정의하고, 이 박스의 상단과 하단을 돌파할 때 매매 신호로 활용하는 것이다. 다바스는 이를 통해 주가의 추세를 파악하고, 매매 타이밍을 결정하였다.

박스의 정의와 새로운 박스의 형성

다바스 박스이론에서 박스는 다음과 같이 정의된다:

  1. 박스의 상단은 일정 기간 동안의 최고가로 정의한다.
  2. 박스의 하단은 일정 기간 동안의 최저가로 정의한다.

박스가 형성되면, 주가가 박스의 상단이나 하단을 돌파할 때까지 해당 박스는 유효하다. 주가가 박스의 상단을 돌파하면 새로운 박스의 하단이 형성되고, 주가가 박스의 하단을 돌파하면 새로운 박스의 상단이 형성된다.

새로운 박스의 하단을 정의하는 방법은 다음과 같다:

  1. 주가가 기존 박스의 상단을 돌파한 후, 이전 박스의 상단과 현재 주가 사이에서 형성되는 가장 낮은 저점을 새로운 박스의 하단으로 정의한다.
  2. 만약 주가가 새로운 박스의 하단을 다시 하향 돌파한다면, 위 과정을 반복하여 새로운 박스의 하단을 재정의한다.

다바스는 이러한 박스 형성 과정을 통해 주가의 추세를 파악하고, 매매 기회를 포착하였다.

니콜라스 다바스 박스이론을 활용한 매매 전략

니콜라스 다바스 박스이론을 활용한 매매 전략은 다음과 같이 구성할 수 있다:

  1. 박스 돌파 전략:
  • 주가가 박스의 상단을 돌파할 때 매수하고, 박스의 하단을 돌파할 때 매도한다.
  • 이는 강한 상승 추세나 하락 추세를 따르는 전략이다.
  1. 박스 형성 전략:
  • 주가가 새로운 박스를 형성할 때, 박스의 하단에서 매수하고 상단에서 매도한다.
  • 이는 박스 내에서 주가의 움직임을 활용하는 전략이다.
  1. 박스 확장 전략:
  • 주가가 기존 박스의 상단을 돌파하여 새로운 박스를 형성할 때, 이전 박스의 상단에서 매수한다.
  • 주가가 기존 박스의 하단을 돌파하여 새로운 박스를 형성할 때, 이전 박스의 하단에서 매도한다.
  • 이는 추세 전환 시점을 포착하여 매매하는 전략이다.

다바스 박스이론은 주가의 추세와 변동성을 파악하는 데 유용하지만, 실제 트레이딩에서는 위험 관리와 포지션 크기 조절 등을 함께 고려해야 한다. 또한 거래 비용과 시장의 변동성이 크게 영향을 미칠 수 있으므로, 이에 대한 고려도 필요하다.

파이썬을 활용한 니콜라스 다바스 박스이론 백테스팅

파이썬을 사용하여 니콜라스 다바스 박스이론 기반의 트레이딩 전략을 백테스팅해보자. 다음 라이브러리를 활용할 것이다:

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

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

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

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

다음으로 박스를 정의하고 매매 신호를 생성하는 코드를 작성해보자.

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 define_box(data, lookback):
    box_high = data['High'].rolling(lookback).max()
    box_low = data['Low'].rolling(lookback).min()
    return box_high, box_low

# 새로운 박스 하단 정의 함수
def define_new_box_bottom(data, box_high, box_low):
    new_box_bottom = data[data['Close'] > box_high].iloc[0]['Low']
    return new_box_bottom

# 매매 신호 생성 함수
def get_signals(data, box_high, box_low):
    signals = pd.DataFrame(index=data.index)
    signals['signal'] = 0

    for i in range(1, len(data)):
        if data['Close'][i] > box_high[i-1]:
            signals['signal'][i] = 1
        elif data['Close'][i] < box_low[i-1]:
            signals['signal'][i] = -1
        else:
            signals['signal'][i] = signals['signal'][i-1]

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

# 박스 정의 (20일 기준)
box_high, box_low = define_box(data, lookback=20)

# 매매 신호 생성
signals = get_signals(data, box_high, box_low)

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

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

portfolio['holdings'] = (positions.multiply(data['Adj Close'], axis=0)).sum(axis=1)
portfolio['cash'] = initial_capital - (pos_diff.multiply(data['Adj 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) = plt.subplots(2, 1, figsize=(15, 10), gridspec_kw={'height_ratios': [3, 1]})

ax1.plot(data['Close'], label='Price')
ax1.plot(box_high, label='Box High')
ax1.plot(box_low, label='Box Low')
ax1.legend(loc='upper left')
ax1.set_ylabel('Price')
ax1.set_title('Nicholas Darvas Box Theory')

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

plt.tight_layout()
plt.show()

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

define_box 함수는 주어진 데이터와 룩백 기간을 바탕으로 박스의 상단과 하단을 정의한다. 여기서는 20일을 기준으로 박스를 정의하였다.

define_new_box_bottom 함수는 주가가 기존 박스의 상단을 돌파했을 때, 새로운 박스의 하단을 정의한다. 이전 박스의 상단과 현재 주가 사이에서 형성되는 가장 낮은 저점을 새로운 박스의 하단으로 정의한다.

get_signals 함수는 정의된 박스를 기준으로 매매 신호를 생성한다. 주가가 박스의 상단을 돌파하면 매수 신호(1)를, 하단을 돌파하면 매도 신호(-1)를 생성한다.

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

matplotlib을 사용하여 주가, 박스의 상단과 하단, 포트폴리오 가치를 시각화한다.

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

Leave a Reply

error: Content is protected !!