본문 바로가기

코딩 갤러리/데이터분석

[파이썬] MACD 크로스 매매기법 코스피200 적용 결과

728x90
반응형

이전에 MACD 크로스 매매기법으로 개별 종목을 백테스팅하여 수익을 확인해보는 글을 작성해보았는데, 이번에는 조금더 거시적으로 코스피200 종목 모두에 대해 백테스팅을 적용해보며, 코스피200 시장에 MACD 크로스 매매기법을 적용할 수 있는 지 알아보았다.

import pandas as pd
import pandas_datareader as web
import datetime
import matplotlib.pyplot as plt
import numpy as np

먼저 위의 패키지들을 사용했다.

kospi200df = pd.read_excel('kospi200_code.xlsx', engine='openpyxl', usecols=[1])
def add_code(x):
    x = str(x)
    return '0'*(6-len(x))+x+'.KS'
kospi200 = kospi200df['종목코드'].apply(add_code)

코스피200 엑셀 파일은 기업공시채널KIND에서 가져왔다(xls파일이어서 xlsx파일로 변경 저장함. KRX정보데이터시스템에서도 리스트를 다운 받을 수 있는데, 이 경우에는 KIND와 데이터 순서가 다르기 때문에 알맞게 코드를 변경해야함).

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 값들을 구해주는 함수를 만들었다. 이번의 경우에는 (12, 26, 9) 값으로 설정했다.

all_profit = []

def fuction(df):
    Buy, Sell = [], []
    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 buy_point[-1] == len(df):
        del buy_point[-1]
    if sell_point[-1] == len(df):
        del sell_point[-1]
    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]
    profits = []
    for i in range(len(sell_price)):
        profits.append((sell_price[i] - buy_price[i]) / buy_price[i])
    asset = 100
    for i in profits:
        asset = asset + (asset * i)
    profit = ((asset-100)/100)*100
    all_profit.append(profit)

그 다음 매수매도 시점을 찾고 수익률을 계산하는 함수를 만들어보았다. 매수매도 시점을 찾는 것에 대한 자세한 글은 'MACD 매매기법 백테스팅하고 그래프로 만들기'에 조금 더 자세히 나와있다. 일단 수익률을 저장하기 위해 all_profit을 빈 리스트로 만들어주고, 함수 내에서도 매수와 매도 시점을 각각 저장해주는 Buy와 Sell이라는 리스트를 만들었다.

이후 for문을 통해 26일 평균값이 나온 이후부터 매수와 매도 시점을 찾는 코드를 작성했다. 그리고 실제 매수매도는 신호가 나온 다음날 시초가를 기준으로 두며, 이를 위해 Buy와 Sell 리스트의 값들에 각각 1을 더하여 buy_point와 sell_point에 넣어주었다.

그리고 그 이후의 if문 4개 중 앞 2개는 buy_point와 sell_point의 마지막 값이 데이터를 초과하였을 경우(즉, 12월 31일이 마지막인데 다음 영업일의 데이터가 필요한 경우)를 제외하기 위한 if문이다. 그리고 그 다음 두 if문은 매수시점보다 매도시점이 먼저 나타나거나, 마지막이 매수시점으로 끝날 경우를 제외하기 위한 코드이다.

그 다음 각각의 시점의 시초가 데이터를 buy_price와 sell_price 두 리스트에 넣어준 뒤, 빈 리스트인 profits을 만들어 주고 수익률을 계산하고 proftis에 넣어주었다.

index_number = []

def start_anlaysis(z, startday, endday):
    for i, code in enumerate(z):
        try:
            df = web.get_data_yahoo(code, startday, endday)
            macd(df)
            if len(df) > 99:
                fuction(df)
                index_number.append(i)
                print(i, '번 째', '종목코드', code, '분석완료')
                time.sleep(0.5)
            else:
                print(i, '번 째', '종목코드', code, '일수부족')
        except Exception as ex:
            print(i, '번 째', '종목코드', code, 'Ex:', ex, '분석실패')
    positive_profit = [i for i in all_profit if i > 0]
    negative_profit = [i for i in all_profit if i < 0]
    zero_profit = [i for i in all_profit if i == 0]
    print('''
    ================================
    MACD 크로스 매매기법 결과입니다.
    평 균 수 익 률: {}%
    최 대 수 익 률: {}%
    최대 수익 종목: {}
    최 소 수 익 률: {}%
    최소 수익 종목: {}
    수익이 난 종목: {}개
    손실이 난 종목: {}개
    수익률 0% 종목: {}개
    전 체 종 목 수: {}개
    전 체 분 석 수: {}개
    ================================'''
          .format(sum(all_profit) / len(all_profit), max(all_profit), kospi200.iloc[index_number[np.argmax(all_profit)]], min(all_profit),
              kospi200.iloc[index_number[np.argmin(all_profit)]],len(positive_profit), len(negative_profit), len(zero_profit),
              len(kospi200), len(all_profit)))

그 다음 최종적으로 코스피200의 종목들을 모두 분석해 수익률을 구하고, 마지막 결과를 알려주도록 실행하는 함수를 만들었다. 일단 개인적으로 거래일수가 100일 이하인 종목들은 분석에서 제외하였다. 그리고 에러가 발생하는 경우가 있어서 try excpet문으로 예외를 그냥 무시하도록 하였다. index_number의 경우에는 마지막 결과를 알려줄 때의 계산을 위해 만들었다.

start_anlaysis(kospi200, '2011-01-01', '2021-12-31')

그리고 이제 이 모든 코드를 통해 코스피200 종목의 2011년 1월 1일부터 2021년 12월 31일까지의 데이터를 통해 MACD 크로스 매매기법을 적용해보겠다. 이를 실행해보면...

0 번 째 종목코드 373220.KS Ex: 'Date' 분석실패
1 번 째 종목코드 402340.KS 일수부족
2 번 째 종목코드 377300.KS 일수부족
3 번 째 종목코드 329180.KS 일수부족
4 번 째 종목코드 259960.KS 일수부족
5 번 째 종목코드 178920.KS 분석완료
(중략)
196 번 째 종목코드 000240.KS 분석완료
197 번 째 종목코드 003490.KS 분석완료
198 번 째 종목코드 000100.KS 분석완료
199 번 째 종목코드 000120.KS 분석완료

    ================================
    MACD 크로스 매매기법 결과입니다.
    평 균 수 익 률: 52.8466580003899%
    최 대 수 익 률: 1380.098836506827%
    최대 수익 종목: 010780.KS
    최 소 수 익 률: -87.44136620469493%
    최소 수익 종목: 139480.KS
    수익이 난 종목: 88개
    손실이 난 종목: 104개
    수익률 0% 종목: 0개
    전 체 종 목 수: 200개
    전 체 분 석 수: 192개
    ================================

잘 실행된다! 아마 문제 없는 것 같다(제발).

그리고 사실 저 결과가 나오고 나서 아래와 같은 오류들이 떴는데... 이게 왜 생긴지 아무리 찾아봐도 모르겠다...ㅠㅠ

C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 53356 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 47196 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 49828 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 47588 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 44592 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 48277 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 44208 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 44284 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 49688 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 51061 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 44396 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 44036 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 48324 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 51333 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:214: RuntimeWarning: Glyph 47785 missing from current font.
  font.set_text(s, 0.0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 49688 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 51061 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 44396 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 44036 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 48324 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 51333 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 47785 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 53356 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 47196 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 49828 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 47588 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 44592 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 48277 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 44208 missing from current font.
  font.set_text(s, 0, flags=flags)
C:\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:183: RuntimeWarning: Glyph 44284 missing from current font.
  font.set_text(s, 0, flags=flags)

Process finished with exit code 0

얘네들 도대체 뭐지? 왜 뜨는 걸까?

위의 코드들을 하나로 묶은 것은 아래와 같다.

import pandas as pd
import pandas_datareader as web
import datetime
import matplotlib.pyplot as plt
import numpy as np

# 코스피 200 종목코드 엑셀에서 가져오기
kospi200df = pd.read_excel('kospi200_code.xlsx', engine='openpyxl', usecols=[1])
def add_code(x):
    x = str(x)
    return '0'*(6-len(x))+x+'.KS'
kospi200 = kospi200df['종목코드'].apply(add_code)

# 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()

all_profit = []

def fuction(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]
    # buy_point와 sell_point가 데이터를 초과하였을 때(즉, 12월 31일이 마지막인데 1월 1일 데이터가 필요할 경우) 에러가 발생하기때문에 이를 삭제해야함
    if buy_point[-1] == len(df):
        del buy_point[-1]
    if sell_point[-1] == len(df):
        del sell_point[-1]
    # 매수보다 매도가 빠르거나 마지막에 매도로 끝나지 않고 매수로 끝날 시 그 부분 삭제
    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]
    # 수익률 계산
    profits = []
    for i in range(len(sell_price)):
        profits.append((sell_price[i] - buy_price[i]) / buy_price[i])
    asset = 100
    for i in profits:
        asset = asset + (asset * i)
    profit = ((asset-100)/100)*100
    all_profit.append(profit)

index_number = []

# 구하기
def start_anlaysis(z, startday, endday):
    for i, code in enumerate(z):
        try:
            df = web.get_data_yahoo(code, startday, endday)
            macd(df)
            if len(df) > 99:
                fuction(df)
                index_number.append(i)
                print(i, '번 째', '종목코드', code, '분석완료')
                time.sleep(0.5)
            else:
                print(i, '번 째', '종목코드', code, '일수부족')
        except Exception as ex:
            print(i, '번 째', '종목코드', code, 'Ex:', ex, '분석실패')
    positive_profit = [i for i in all_profit if i > 0]
    negative_profit = [i for i in all_profit if i < 0]
    zero_profit = [i for i in all_profit if i == 0]
    print('''
    ================================
    MACD 크로스 매매기법 결과입니다.
    평 균 수 익 률: {}%
    최 대 수 익 률: {}%
    최대 수익 종목: {}
    최 소 수 익 률: {}%
    최소 수익 종목: {}
    수익이 난 종목: {}개
    손실이 난 종목: {}개
    수익률 0% 종목: {}개
    전 체 종 목 수: {}개
    전 체 분 석 수: {}개
    ================================'''
          .format(sum(all_profit) / len(all_profit), max(all_profit), kospi200.iloc[index_number[np.argmax(all_profit)]], min(all_profit),
              kospi200.iloc[index_number[np.argmin(all_profit)]],len(positive_profit), len(negative_profit), len(zero_profit),
              len(kospi200), len(all_profit)))

start_anlaysis(kospi200, '2011-01-01', '2021-12-31')
728x90
반응형