본문 바로가기

코딩 갤러리/데이터분석

[파이썬] MACD 매매기법 백테스팅하고 그래프로 만들기

728x90
반응형

파이썬을 이용해 MACD 매매기법으로 수익을 낼 수 있는 지 백테스팅을 진행해보고, 이를 그래프로 나타내보도록 하겠습니다.

import pandas_datareader.data as web
import datetime
import matplotlib.pyplot as plt
from matplotlib import gridspec
from matplotlib import font_manager, rc

우선 필요한 패키지들은 위와 같습니다.

font_path = "C:/Windows/Fonts/NGULIM.TTF"
font = font_manager.FontProperties(fname=font_path).get_name()
rc('font', family=font)

이 코드는 그래프에 한국어를 표시하기 위해 필요한 코드입니다.

# 오늘 날짜 자동 설정
today = datetime.date.today()

# 삼성전자 주가 데이터 가져오기
df = web.get_data_yahoo('005930.KS', '2021-01-01', today)

# MACD 계산에 필요한 값들 데이터프레임에 넣기
def MACD(df):
    df['EMA12'] = df.Close.ewm(span=12).mean()
    df['EMA26'] = df.Close.ewm(span=26).mean()
    df['MACD'] = df.EMA12 - df.EMA26
    df['signal'] = df.MACD.ewm(span=9).mean()

# MACD 함수 실행
MACD(df)

먼저 2021년 1월 1일 이후부터 오늘(2022년 3월 22일)까지의 삼성전자 주가 데이터를 야후파이낸스로부터 가져오고 MACD의 값들을 모두 구해줍니다.

Buy, Sell = [], []

# 정확한 신호를 위해 26일 이평선이 나오고 나서부터 매수매도 시점 찾기
for i in range(26, len(df)):
    if df.MACD.iloc[i] > df.signal.iloc[i] and df.MACD.iloc[i - 1] < df.signal.iloc[i - 1]:
        Buy.append(i)
    elif df.MACD.iloc[i] < df.signal.iloc[i] and df.MACD.iloc[i - 1] > df.signal.iloc[i - 1]:
        Sell.append(i)
        
# 실제 매수매도는 신호가 나온 다음날에 하기떄문에 다음날로 지정
buy_point = [i + 1 for i in Buy]
sell_point = [i + 1 for i in Sell]

MACD 곡선이 Signal 곡선을 상향돌파하는 골든크로스가 발생하면 매수를, 반대로 하향돌파를 하는 데드크로스가 발생하면 매도를 하도록 설정합니다. 그리고 실제 매수매도는 신호가 발생한 다음날에 한다고 가정을 두고, 매매시점을 다음날로 설정하기 위한 코드를 입력합니다.

In[4]: buy_point
Out[4]: [30, 37, 47, 58, 100, 120, 127, 146, 166, 199, 274, 297]
In[5]: sell_point
Out[5]: [32, 44, 55, 73, 116, 124, 129, 154, 184, 248, 283]

buy_point와 sell_point를 설정을 하고 그 리스트를 살펴보면, 간혹 매수보다 매도가 먼저 발생하기도 하며(지금은 없음), 마지막 시점이 매수로 끝나는 경우(지금 보면 매도는 283번째로 끝이났지만, 297번째에 다시 매수가 들어감)가 있을 수 있고, 이는 계산을 방해하는 요소이기때문에 이러한 경우들을 없애는 코드를 입력해야합니다.

# 매수보다 매도가 빠르거나 마지막에 매도로 끝나지 않고 매수로 끝날 시 그 부분 삭제
if sell_point[0] < buy_point[0]:
    del sell_point[0]
if buy_point[-1] > sell_point[-1]:
    del buy_point[-1]

# 장이 열리자마자 시가에 매수매도 한다고 가정
buy_price = df.Open.iloc[buy_point]
sell_price = df.Open.iloc[sell_point]

위의 if 구문으로 매수보다 매도가 일찍 나타나거나, 마지막에 매도로 끝나지 않고 매수로 끝났을 경우 그 부분들을 삭제하도록 하는 코드를 넣습니다. 그리고 이후 매매는 시초가로 한다고 가정하고 코드를 입력합니다.

profit = []

for i in range(len(sell_price)):
    profit.append((sell_price[i] - buy_price[i]) / buy_price[i])

# 데이터프레임에 수익률 넣기
df['Profit'] = ''
df['Profit'].iloc[0] = 0
for i, point in enumerate(sell_point):
    df['Profit'].iloc[point] = profit[i]
df = df.replace('', 0)

# 데이터프레임에 자산 변동 넣기
df['Asset'] = ''
df['Asset'].iloc[0] = 100
for i in range(1, len(df)):
    if df['Profit'].iloc[i] == 0:
        df['Asset'].iloc[i] = df['Asset'].iloc[i-1]
    else:
        df['Asset'].iloc[i] = df['Asset'].iloc[i-1] + df['Asset'].iloc[i-1] * df['Profit'].iloc[i]

이제 매수를 하고 매도를 하였을 때, 모든 거래들이 각각 얼마의 수익률을 발생시키는지를 계산하여 prfit 리스트에 넣어두도록 하겠습니다. 그리고 구해진 수익률들을 주가 데이터프레임에 넣기위한 코드를 입력하고, 이후에 그래프로 자산이 어떻게 변동하는지를 표시하기 위해 필요한 데이터를 넣어주도록 합니다. 자산의 경우 기본값을 100으로 설정하고, 매매가 완료되어 수익률이 나타났을 때 바로바로 자산에 변화를 주도록 위해 for문을 이용해 코드를 입력했습니다.

In[7]: df
Out[7]: 
               High      Low     Open  ...       signal  Profit    Asset
Date                                   ...                              
2021-01-04  84400.0  80200.0  81000.0  ...     0.000000     0.0      100
2021-01-05  83900.0  81600.0  81600.0  ...    11.217949     0.0      100
2021-01-06  84500.0  82100.0  83300.0  ...    -4.440182     0.0      100
2021-01-07  84200.0  82700.0  82800.0  ...   -10.561981     0.0      100
2021-01-08  90000.0  83000.0  83300.0  ...    56.587899     0.0      100
             ...      ...      ...  ...          ...     ...      ...
2022-03-15  70200.0  70200.0  70200.0  ... -1117.572110     0.0  94.4238
2022-03-16  70500.0  69700.0  70200.0  ... -1144.658933     0.0  94.4238
2022-03-17  70400.0  70400.0  70400.0  ... -1157.058990     0.0  94.4238
2022-03-18  70900.0  70200.0  70600.0  ... -1152.180140     0.0  94.4238
2022-03-18  70900.0  70200.0  70600.0  ... -1134.100911     0.0  94.4238
[298 rows x 12 columns]

데이터프레임을 출력해보면 잘 들어가있는 것을 확인할 수 있습니다.

In[9]: print("최종 자산은", df['Asset'].iloc[-1], "입니다.")
Out[9]: 최종 자산은 94.42382515631816 입니다.
In[10]: print("최종 수익률은", df['Asset'].iloc[-1] - 100 / 100 * 100, "% 입니다.")
Out[10]: 최종 수익률은 -5.576174843681841 % 입니다.

print 함수를 이용해 최종 자산과 최종 수익률을 한번 계산해봅니다. 그러면 이제 주가와 MACD지표, 그리고 자산 변동을 하나의 그래프로 나타내보도록 하겠습니다.

728x90
반응형
# 그래프 만들기
fig = plt.figure()
spec = gridspec.GridSpec(ncols=1, nrows=3, height_ratios=[2, 1, 1])

# 주가 그래프에 매수매도시점 표시하기
ax0 = plt.subplot(spec[0])
plt.scatter(df.iloc[buy_point].index, df.iloc[buy_point].Close, marker='^', color='red')
plt.scatter(df.iloc[sell_point].index, df.iloc[sell_point].Close, marker='v', color='blue')
plt.plot(df.Close, label='삼성전자', color='k')
plt.legend()

# MACD 그래프 만들기
ax1 = plt.subplot(spec[1])
plt.plot(df.signal, label='signal', color='red')
plt.plot(df.MACD, label='MACD', color='green')
plt.legend()

# 자산 그래프 만들기
ax2 = plt.subplot(spec[2])
plt.plot(df.Asset, label='Asset', color='green')

plt.show()

위의 코드를 실행해보면 아래와 같은 그래프가 출력됩니다.

주가 그래프에 매수(빨간색 삼각형)와 매도(파란색 역삼각형)가 잘 표시되었고, MACD 지표 역시 잘 나온 것을 확인 할 수 있습니다. 그리고 자산 그래프 역시 매도가 발생하여 매매가 종료되었을 때, 그 수익률을 반영하여 움직이는 것을 확인할 수 있습니다.

3개 그래프 X축 공유시키고, 주가와 자산 그래프 마지막 값을 그래프에 나타내보도록 할려고 했는데, 아직 파이썬 초보라 그런지 못 하겠네요...ㅠㅠ 더 공부해야겠습니다. 그리고 왜 마지막 그래프는 라벨이 안 나오는걸까요? 그리고 오류로 뜨는 데, SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame 이거  어떻게 안 뜨게 하는거지...

아래는 전체 코드입니다.

import pandas_datareader.data as web
import datetime
import matplotlib.pyplot as plt
from matplotlib import gridspec
from matplotlib import font_manager, rc

# 한글 폰트 사용을 위해서 세팅
font_path = "C:/Windows/Fonts/NGULIM.TTF"
font = font_manager.FontProperties(fname=font_path).get_name()
rc('font', family=font)

# 오늘 날짜 자동 설정
today = datetime.date.today()

#데이터 가져오기
df = web.get_data_yahoo('005930.KS', '2021-01-01', today)

# MACD 계산에 필요한 값들 데이터프레임에 넣기
def MACD(df):
    df['EMA12'] = df.Close.ewm(span=12).mean()
    df['EMA26'] = df.Close.ewm(span=26).mean()
    df['MACD'] = df.EMA12 - df.EMA26
    df['signal'] = df.MACD.ewm(span=9).mean()

# MACD 함수 실행
MACD(df)

Buy, Sell = [], []

# 정확한 신호를 위해 26일 이평선이 나오고 나서부터 매수매도 시점 찾기
for i in range(26, len(df)):
    if df.MACD.iloc[i] > df.signal.iloc[i] and df.MACD.iloc[i - 1] < df.signal.iloc[i - 1]:
        Buy.append(i)
    elif df.MACD.iloc[i] < df.signal.iloc[i] and df.MACD.iloc[i - 1] > df.signal.iloc[i - 1]:
        Sell.append(i)

# 실제 매수매도는 신호가 나온 다음날에 하기떄문에 다음날로 지정
buy_point = [i + 1 for i in Buy]
sell_point = [i + 1 for i in Sell]

# 매수보다 매도가 빠르거나 마지막에 매도로 끝나지 않고 매수로 끝날 시 그 부분 삭제
if sell_point[0] < buy_point[0]:
    del sell_point[0]
if buy_point[-1] > sell_point[-1]:
    del buy_point[-1]

# 장이 열리자마자 시가에 매수매도 한다고 가정
buy_price = df.Open.iloc[buy_point]
sell_price = df.Open.iloc[sell_point]

profit = []

for i in range(len(sell_price)):
    profit.append((sell_price[i] - buy_price[i]) / buy_price[i])

# 데이터프레임에 수익률 넣기
df['Profit'] = ''
df['Profit'].iloc[0] = 0
for i, point in enumerate(sell_point):
    df['Profit'].iloc[point] = profit[i]
df = df.replace('', 0)

# 데이터프레임에 자산 변동 넣기
df['Asset'] = ''
df['Asset'].iloc[0] = 100
for i in range(1, len(df)):
    if df['Profit'].iloc[i] == 0:
        df['Asset'].iloc[i] = df['Asset'].iloc[i-1]
    else:
        df['Asset'].iloc[i] = df['Asset'].iloc[i-1] + df['Asset'].iloc[i-1] * df['Profit'].iloc[i]

print("최종 자산은", df['Asset'].iloc[-1], "입니다.")
print("최종 수익률은", df['Asset'].iloc[-1] - 100 / 100 * 100, "% 입니다.")

# 그래프 만들기
fig = plt.figure()
spec = gridspec.GridSpec(ncols=1, nrows=3, height_ratios=[2, 1, 1])

# 주가 그래프에 매수매도시점 표시하기
ax0 = plt.subplot(spec[0])
plt.scatter(df.iloc[buy_point].index, df.iloc[buy_point].Close, marker='^', color='red')
plt.scatter(df.iloc[sell_point].index, df.iloc[sell_point].Close, marker='v', color='blue')
plt.plot(df.Close, label='삼성전자', color='k')
plt.legend()

# MACD 그래프 만들기
ax1 = plt.subplot(spec[1])
plt.plot(df.signal, label='signal', color='red')
plt.plot(df.MACD, label='MACD', color='green')
plt.legend()

# 자산 그래프 만들기
ax2 = plt.subplot(spec[2])
plt.plot(df.Asset, label='Asset', color='green')

plt.show()
728x90
반응형