MFI 지표의 개념과 유래
MFI(Money Flow Index)는 가격과 거래량을 함께 고려하여 시장의 자금 흐름을 측정하는 모멘텀 지표이다. MFI는 1970년대 후반 Gene Quong과 Avrum Soudack이 개발하였으며, RSI(Relative Strength Index)와 유사한 개념으로 사용된다.
MFI는 일정 기간 동안의 가격 변동과 거래량을 바탕으로 자금 흐름을 계산한다. 이를 통해 시장에 유입되는 자금과 유출되는 자금의 강도를 파악할 수 있다. MFI의 값은 0에서 100 사이로 나타내며, 일반적으로 80 이상은 과매수 구간, 20 이하는 과매도 구간으로 해석한다.
MFI 계산 방법
MFI는 다음과 같은 단계를 거쳐 계산된다:
- Money Flow(MF) 계산:
- Typical Price = (고가 + 저가 + 종가) / 3
- Raw Money Flow = Typical Price × 거래량
- 가격이 전일 대비 상승한 경우 Positive Money Flow(PMF), 하락한 경우 Negative Money Flow(NMF)로 분류
- Money Flow Ratio(MFR) 계산:
- Money Flow Ratio = (14일 동안의 PMF 합) / (14일 동안의 NMF 합)
- MFI 계산:
- MFI = 100 – (100 / (1 + Money Flow Ratio))
MFI를 활용한 매매 전략
MFI는 다음과 같은 매매 전략에 활용될 수 있다:
- 과매수/과매도 구간 활용:
- MFI가 80 이상의 과매수 구간에 진입하면 매도 신호로 해석하고, 20 이하의 과매도 구간에 진입하면 매수 신호로 해석한다.
- 과매수/과매도 구간에서 반전 신호가 나타날 때 매매를 고려할 수 있다.
- 다이버전스 전략:
- 가격은 신고가를 경신하는데 MFI는 신고가를 경신하지 못하는 베어시 다이버전스가 나타나면 매도 신호로 해석한다.
- 가격은 신저가를 경신하는데 MFI는 신저가를 경신하지 못하는 불리시 다이버전스가 나타나면 매수 신호로 해석한다.
- 추세 추종 전략:
- MFI가 50 이상에서 상승 추세를 보이면 매수 신호, 50 이하에서 하락 추세를 보이면 매도 신호로 해석한다.
- MFI의 추세 방향과 가격 추세의 방향이 일치할 때 매매 신호의 신뢰도가 높아진다.
실제 트레이딩에서는 MFI와 함께 다른 기술적 지표나 가격 액션을 함께 활용하여 매매 신호의 정확성을 높이는 것이 효과적이다.
파이썬을 활용한 MFI 백테스팅
이제 파이썬을 사용하여 MFI 기반의 트레이딩 전략을 백테스팅해보자. 다음 라이브러리를 활용할 것이다:
pandas
: 데이터 조작 및 분석numpy
: 수치 계산matplotlib
: 데이터 시각화FinanceDataReader
: 주가 데이터 수집
먼저 가상환경을 활성화하고 필요한 라이브러리를 설치한다.
source venv/bin/activate
pip install pandas numpy matplotlib FinanceDataReader
FinanceDataReader를 설치하면 종속 모듈도 함께 설치된다.
다음으로 MFI를 계산하고 매매 신호를 생성하는 코드를 작성해보자.
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)
# MFI 계산 함수
def calculate_mfi(data, period=14):
typical_price = (data['High'] + data['Low'] + data['Close']) / 3
money_flow = typical_price * data['Volume']
positive_flow = []
negative_flow = []
for i in range(1, len(typical_price)):
if typical_price[i] > typical_price[i-1]:
positive_flow.append(money_flow[i-1])
negative_flow.append(0)
elif typical_price[i] < typical_price[i-1]:
negative_flow.append(money_flow[i-1])
positive_flow.append(0)
else:
positive_flow.append(0)
negative_flow.append(0)
positive_mf = pd.Series(positive_flow)
negative_mf = pd.Series(negative_flow)
mfi = 100 - (100 / (1 + (positive_mf.rolling(window=period).sum() / negative_mf.rolling(window=period).sum())))
return mfi
# 매매 신호 생성 함수
def get_signals(data, mfi):
signals = pd.DataFrame(index=data.index)
signals['signal'] = 0
signals['signal'][mfi > 80] = -1
signals['signal'][mfi < 20] = 1
signals['positions'] = signals['signal'].diff()
return signals
# MFI 계산
mfi = calculate_mfi(data)
# 매매 신호 생성
signals = get_signals(data, mfi)
# 백테스팅
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, ax3) = plt.subplots(3, 1, figsize=(15, 15), gridspec_kw={'height_ratios': [3, 1, 1]})
ax1.plot(data['Close'], label='Close Price')
ax1.set_title('Price Chart')
ax1.legend(loc='upper left')
ax1.set_ylabel('Price')
ax2.plot(mfi, label='MFI')
ax2.axhline(80, color='r', linestyle='--', label='Overbought')
ax2.axhline(20, color='g', linestyle='--', label='Oversold')
ax2.set_title('MFI')
ax2.legend(loc='upper left')
ax2.set_ylabel('MFI')
ax3.plot(portfolio['total'], label='Portfolio Value')
ax3.set_title('Portfolio Performance')
ax3.legend(loc='upper left')
ax3.set_ylabel('Portfolio Value')
plt.tight_layout()
plt.show()
위 코드에서는 FinanceDataReader를 사용하여 VOO ETF의 전체 기간 주가 데이터를 가져온다. start_date
를 가능한 한 오래전 날짜로 설정하여 모든 데이터를 가져올 수 있도록 한다.
calculate_mfi
함수는 주어진 데이터를 바탕으로 MFI를 계산한다. 먼저 Typical Price를 계산하고, 이를 거래량과 곱하여 Money Flow를 구한다. 그 다음 가격 변동에 따라 Positive Money Flow와 Negative Money Flow로 구분하고, 이를 바탕으로 MFI를 계산한다.
get_signals
함수는 계산된 MFI를 기준으로 매매 신호를 생성한다. MFI가 80 이상일 때 매도 신호(-1), 20 이하일 때 매수 신호(1)를 생성한다.
백테스팅을 위해 초기 자본금을 10,000달러로 설정하고, 생성된 매매 신호를 바탕으로 포트폴리오 가치를 계산한다. 최종적으로 총 수익률과 포트폴리오 가치를 출력한다.
matplotlib
을 사용하여 주가, MFI, 포트폴리오 가치를 시각화한다. 과매수/과매도 기준선인 80과 20도 함께 표시한다.
코드 실행 중 발생할 수 있는 오류를 처리하기 위해 try-except
문을 사용하였다. 데이터를 가져오는 과정에서 오류가 발생하면 에러 메시지를 출력하고 프로그램을 종료한다.