볼린저 밴드의 개념과 유래
볼린저 밴드(Bollinger Bands)는 주가의 변동성을 기반으로 상한선과 하한선을 제공하는 기술적 분석 지표이다. 1980년대 초반, 존 볼린저(John Bollinger)가 개발한 이 지표는 주가의 표준편차를 이용하여 밴드를 구성한다.
볼린저 밴드는 중심선(일반적으로 20일 이동평균)과 상한선 및 하한선으로 이루어진다. 상한선은 중심선에 표준편차의 일정 배수(일반적으로 2배)를 더한 값이며, 하한선은 중심선에서 표준편차의 일정 배수를 뺀 값이다. 이렇게 계산된 밴드는 주가의 변동성이 커질수록 넓어지고, 변동성이 작아질수록 좁아지는 특징을 가진다.
표준편차의 개념
볼린저 밴드를 이해하기 위해서는 표준편차의 개념을 알아야 한다. 표준편차는 데이터의 변동성을 측정하는 통계적 지표로, 데이터가 평균에서 얼마나 떨어져 있는지를 나타낸다. 표준편차가 클수록 데이터의 변동성이 크다는 것을 의미한다.
주가의 경우, 표준편차가 크다는 것은 가격 변동의 폭이 크다는 것을 나타낸다. 반대로 표준편차가 작으면 주가의 변동성이 작다는 것을 의미한다. 이러한 특성을 활용하여 볼린저 밴드는 주가의 변동성을 시각적으로 표현한다.
표준편차 계산 예시
예를 들어, 어떤 주식의 지난 5일간 종가가 다음과 같다고 가정해보자:
날짜 | 종가 |
1일차 | 100 |
2일차 | 110 |
3일차 | 90 |
4일차 | 120 |
5일차 | 100 |
이 주식의 5일 평균 종가는 (100 + 110 + 90 + 120 + 100) / 5 = 104 이다.
각 종가와 평균의 차이는 다음과 같다:
날짜 | 종가 | 편차 | 편차의 제곱 |
1일차 | 100 | -4 | 16 |
2일차 | 110 | 6 | 36 |
3일차 | 90 | -14 | 196 |
4일차 | 120 | 16 | 256 |
5일차 | 100 | -4 | 16 |
편차의 제곱 합은 16 + 36 + 196 + 256 + 16 = 520 이다.
분산은 편차 제곱 합을 데이터 개수 – 1로 나눈 값이므로, 520 / (5 – 1) = 130 이다.
따라서 표준편차는 분산의 제곱근인 √130 = 11.40이 된다.
표준편차와 정규분포의 관계
정규분포(normal distribution)는 종 모양의 대칭적인 형태를 가진 연속 확률 분포이다. 정규분포는 평균(μ)과 표준편차(σ)에 의해 결정되며, 이를 표기할 때는 N(μ, σ²)와 같이 나타낸다.
표준편차는 정규분포에서 데이터의 분산 정도를 나타내는 척도로 사용된다. 표준편차가 작을수록 데이터가 평균 근처에 밀집해 있고, 표준편차가 클수록 데이터가 평균에서 멀리 퍼져 있음을 의미한다.
정규분포의 특징 중 하나는 평균을 중심으로 특정 표준편차 범위 내에 데이터가 분포하는 비율이 일정하다는 것이다. 이를 68-95-99.7 법칙이라고 한다.
- 평균으로부터 ±1 표준편차 이내에는 약 68%의 데이터가 분포한다.
- 평균으로부터 ±2 표준편차 이내에는 약 95%의 데이터가 분포한다.
- 평균으로부터 ±3 표준편차 이내에는 약 99.7%의 데이터가 분포한다.
표준편차 1, 2, 3, 4에 따른 정규분포의 예시
- 표준편차가 1인 경우: 데이터의 약 68%가 -1에서 1 사이에 분포하고, 약 95%가 -2에서 2 사이에, 99.7%는 -3에서 3 사이에 분포한다.
- 표준편차가 2인 경우: 데이터의 약 68%가 -2에서 2 사이에 분포하고, 약 95%가 -4에서 4 사이에, 99.7%는 -6에서 6 사이에 분포한다.
- 표준편차가 3, 4인 경우: 위와 같은 원리로 데이터가 분포한다.
이처럼 표준편차는 정규분포에서 데이터의 분산 정도를 결정하는 중요한 요소이다. 주가 데이터에 볼린저 밴드를 적용할 때, 일반적으로 표준편차 2를 사용하여 상한선과 하한선을 설정한다. 이는 주가가 상한선과 하한선 사이에 위치할 확률이 약 95%임을 의미한다. 따라서 주가가 밴드를 벗어날 경우, 통계적으로 유의미한 변동성 확대로 해석할 수 있다.
이러한 계산 과정을 통해 얻은 표준편차 값이 볼린저 밴드의 상한선과 하한선을 결정하는 데 사용된다. 주가의 변동성이 클수록 표준편차가 커지고, 이에 따라 볼린저 밴드의 폭도 넓어지게 된다.
볼린저 밴드는 이러한 표준편차의 특성을 활용하여 주가의 변동성을 직관적으로 표현함으로써 투자자들이 시장의 상황을 파악하고 적절한 투자 결정을 내리는 데 도움을 준다.
볼린저 밴드를 활용한 매매 전략
볼린저 밴드를 활용한 대표적인 매매 전략은 다음과 같다:
- 반전 매매 전략: 주가가 하한선에 접근하면 매수하고, 상한선에 접근하면 매도하는 전략이다. 이는 주가가 밴드의 극단으로 이동했을 때 반전할 가능성이 높다는 가정에 기반한다.
- 돌파 매매 전략: 주가가 상한선을 돌파하면 매수하고, 하한선을 돌파하면 매도하는 전략이다. 이는 강한 상승 또는 하락 추세를 활용하는 방식이다.
- 추세 추종 전략: 주가가 볼린저 밴드 내에서 상승 추세를 보일 때 매수하고, 하락 추세를 보일 때 매도하는 전략이다. 이 경우 중심선의 방향성을 추세 판단에 활용할 수 있다.
실제 트레이딩에서는 볼린저 밴드와 함께 다른 기술적 지표나 차트 패턴 등을 종합적으로 분석하여 매매 결정을 내리는 것이 효과적이다. 또한, 각 전략에 적합한 진입 및 청산 기준을 설정하고, 포지션 크기 조절과 리스크 관리에도 주의를 기울여야 한다.
파이썬을 활용한 볼린저 밴드 백테스팅
이제 파이썬을 사용하여 볼린저 밴드 기반의 트레이딩 전략을 백테스팅해보자. 다음 라이브러리를 활용할 것이다:
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 get_bollinger_bands(data, window=20, num_std=2):
rolling_mean = data['Close'].rolling(window=window).mean()
rolling_std = data['Close'].rolling(window=window).std()
upper_band = rolling_mean + (rolling_std * num_std)
lower_band = rolling_mean - (rolling_std * num_std)
return rolling_mean, upper_band, lower_band
# 매매 신호 생성 함수
def get_signals(data, rolling_mean, upper_band, lower_band):
signals = pd.DataFrame(index=data.index)
signals['signal'] = 0
signals['signal'][data['Close'] > upper_band] = -1
signals['signal'][data['Close'] < lower_band] = 1
signals['positions'] = signals['signal'].diff()
return signals
# 볼린저 밴드 계산
rolling_mean, upper_band, lower_band = get_bollinger_bands(data)
# 매매 신호 생성
signals = get_signals(data, rolling_mean, upper_band, lower_band)
# 백테스팅
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='Close Price')
ax1.plot(rolling_mean, label='Rolling Mean')
ax1.plot(upper_band, 'g--', label='Upper Band')
ax1.plot(lower_band, 'r--', label='Lower Band')
ax1.legend(loc='upper left')
ax1.set_title(f'{symbol} Bollinger Bands')
ax2.plot(portfolio['total'], label='Portfolio Value')
ax2.legend(loc='upper left')
ax2.set_title('Portfolio Performance')
plt.tight_layout()
plt.show()
위 코드에서는 FinanceDataReader를 사용하여 VOO ETF의 전체 기간 주가 데이터를 가져온다. start_date
를 가능한 한 오래전 날짜로 설정하여 모든 데이터를 가져올 수 있도록 한다.
get_bollinger_bands
함수는 주가 데이터와 윈도우 크기, 표준편차 배수를 입력받아 볼린저 밴드의 중심선, 상한선, 하한선을 계산한다.
get_signals
함수는 주가가 상한선을 돌파할 때 매도 신호(-1), 하한선을 돌파할 때 매수 신호(1)를 생성한다.
백테스팅을 위해 초기 자본금을 10,000달러로 설정하고, 생성된 매매 신호를 바탕으로 포트폴리오 가치를 계산한다. 최종적으로 총 수익률과 포트폴리오 가치를 출력한다.
matplotlib
을 사용하여 주가, 볼린저 밴드, 포트폴리오 가치를 시각화한다.
코드 실행 중 발생할 수 있는 오류를 처리하기 위해 try-except
문을 사용하였다. 데이터를 가져오는 과정에서 오류가 발생하면 에러 메시지를 출력하고 프로그램을 종료한다.