골든 크로스 데드 크로스 - goldeun keuloseu dedeu keuloseu

위의 차트 왼쪽 상단에 보면, "5, 20, 60, 120"으로 적힌 숫자가 보이고, 각 숫자별로 색깔도 표시되어 있다. 초록색선이 5일 이동평균선(이평선)이고, 5일간의 주가평균을 나타내는 선이므로 곡선의 기울기가 급변한다. 반대로 보라색선이 120일 이평선이고, 120간의 주가평균을 나타내는 선이므로 곡선의 기울기가 완만하다.

그리고 물론 이평선 지표만 맹신하면 안되지만, 보통 주가는 이평선 쪽으로 회귀한다고 한다. 그래서 네이버 금융에서는 이평선과 현재 주가의 이격도가 큰 종목들을 별도로 정리해서 보여주는 페이지도 있다.


이동평균선을 그려나가다 보면 단기선이 장기선을 뚫고 오르거나 내리거나 해서 서로 교차하게 된다. 골든크로스와 데드크로스는 이평선들이 교차하는 지점을 지칭한다.

골든 크로스 데드 크로스 - goldeun keuloseu dedeu keuloseu
골든크로스 샘플 (출처 : 네이버 금융)

위의 그림을 보면 5일 이동평균선이 60일 이동평균선을 교차하여 상승하는 부분을 확인할 수 있다. 이렇게 단기 이평선이 장기이평선을 교차하여 상승하는 교차지점을 "골든크로스"라고 부른다. 골든크로스가 생겼다는 것은 최근 5일 간의 투자심리가 이전 60일간의 투자심리보다 좋아지면서 향후 주가가 상승할 가능성이 높다는 것을 의미한다.

일반적으로 골든크로스가 나타나면, 향후 주가가 상승할 것이라는 신호로 해석된다. 물론, 골든크로스만으로 향후 주가가가 상승하는 방향으로 전환되었다고 판단하기 어렵다. 골든크로스 발생 시 거래량이 많거나 단기이평선이 60일 이평선을 뚫고 120일 이평선까지 뚫고 상승해야 추세적으로 상승으로 전환했다고 해석한다.


골든 크로스 데드 크로스 - goldeun keuloseu dedeu keuloseu
데드크로스 샘플

주가가 상승추세에 있는 경우, 이평선들은 단기이평선이 장기이평선 위에 위치하는 형태를 나타낸다. 조금 더 풀어설명하면, 5일 이평선이 가장위에 그 아래에 20일, 60일, 120일 순으로 위치한 것을 말한다. 이러한 형태를 "정배열"이라고 한다. 데드크로스는 단기 이평선이 중장기 이평선을 뚫고 아래로 내려가는 것을 의미한다. 정배열 형태로움직이던 이평선들이 역배열상태로 진입하기 위한 초기신호라고도 해석한다. 

데드크로스 발생을 주식의 매도 신호로 사용되고 있다. 그렇지만 데드크로스 발생 후 다시 주가가 상승하는 경우들도 있다. 위의 예시 사진에서도 첫번째 데드크로스 발생 후 주가가 오히려 상승하였다. 이처럼 이동평균선, 골든크로스, 데드크로스 등의 지표들은 주식 매매 시 "보조지표"로만 활용해야 한다. 골든크로스, 데드크로스를 절대적인 지표로 활용하는 것은 바람직하지 못하다고 생각한다. 

골든크로스는 단기 이동 평균선이 올라가고 장기 이동 평균선이 내려가면서 두 이평선이 교차하는 것으로, 최근 주가가 과거보다 상승하고 있다는 신호로 알려져 있다. 반대로, 데드크로스는 장기 이동 평균선이 올라가고 단기 이동 평균선이 내려가면서 두 이평선이 교차하는 것으로, 최근 주가가 과거보다 하락하고 있다는 신호로 알려져있다.

이동평균선 혹은 이평선은 특정 기간동안의 평균 주가를 의미하며, 5일, 10일 이평선을 보통 단기 이동 평균선, 60일, 120일 이평선을 중장기 이동 평균선이라 한다. 

시계열 개념이 친숙하다면, moving average와 같은 의미라고 받아들이면 된다.

아래 이미지에서 보는 것처럼, 골든크로스와 데드크로스 모두 빨간색 단기이평선과 초록색 장기이평선이 만나는 점인데, 골든크로스는 단기이평선이 올라가면서 만나는 점이고, 데드크로스는 단기이평선이 내려가면서 만나는 점이다.

골든 크로스 데드 크로스 - goldeun keuloseu dedeu keuloseu

실험 내용

이번 실험에서는 골든크로스 지점에서 매수를 하고, 데드크로스 지점에서 매도를 하는 것이 정말로 효과적인 투자 전략인지를 검증하고자 한다.

구체적인 내용은 다음과 같다.

  • 골든크로스 지점에서 매수하고 바로 이어지는 데드크로스 지점에서 매도했을 때의 기대 수익 계산
  • 골든크로스 지점에서 매수하고 1, 3, 6, 12개월 후 매도했을 때의 기대 수익 계산
  • 데드크로스 지점에서 매수하고 1, 3, 6, 12개월 후 매도했을 때의 기대 수익 계산

데이터 준비 및 전처리

그러면 곧바로 파이썬 코드로 가보자.

먼저 필요한 모듈을 불러온다.

# 모듈 불러오기
import pandas as pd
import os
import warnings
import numpy as np
import itertools

warnings.filterwarnings("ignore")

그리고 주가 데이터가 있는 경로를 설정하고, 데이터를 담을 사전을 정의한다.

path = "../../QUANT_DATA/201609~202108/주가/일"
data_dict = dict()

데이터를 모두 불러온 뒤에 전처리를 하면 불필요한 단계가 포함될 수 있어서, 데이터를 불러오면서 전처리를 하고, 전처리한 데이터를 data_dict에 저장할 것이다. 

전처리 및 검증을 위해 필요한 함수들을 하나하나 정의하자.

먼저, 주가 데이터(data)에서 날짜(date)를 입력했을 때, 해당 날짜의 주가 혹은 해당 날짜와 가장 가까운 날짜의 주가를 가져오는 함수를 정의한다.

def get_nearest_close_price(data, date):
    # data에서 날짜가 date일 때의 종가 가져오기
    # 단, date일 때 장이 열리지 않았다면, 가장 최근에 열린 장의 데이터를 사용
    # 예: 2021년 10월 2일 가격이 없으면 10월 1일 가격을 가져옴
    while True:
        if date in data.index:
            return data.loc[date, '종가']
            break
        else:
            date = date - pd.to_timedelta(1, unit = "D")

그리고 골든크로스인지 데드크로스인지를 나타내는 함수를 정의한다.

두 함수 모두 단기이평선(short_term_MA)와 장기이평선(long_term_MA)를 입력으로 받는다.

그리고 i번째 시점에서 단기이평선이 장기이평선 위에 있는데, 직전 시점에서 장기이평선이 단기이평선 위에 있다면 골든 크로스라고 판단하고, 그 반대의 경우에는 데드크로스로 판단한다.

두 함수의 리턴값은 해당 시점에서 골든크로스(데드크로스)면 True를, 그렇지 않으면 False를 반환하는 부울 배열이다. 

def goldencross(short_term_MA, long_term_MA):
    result = [False]
    for i in range(1, len(short_term_MA)):
        if (short_term_MA.iloc[i] >= long_term_MA.iloc[i]) and (short_term_MA.iloc[i-1] < long_term_MA.iloc[i-1]):
            result.append(True)
        else:
            result.append(False)
    
    return result
def deadcross(short_term_MA, long_term_MA):
    result = [False]
    for i in range(1, len(short_term_MA)):
        if (short_term_MA.iloc[i] <= long_term_MA.iloc[i]) and (short_term_MA.iloc[i-1] > long_term_MA.iloc[i-1]):
            result.append(True)
        else:
            result.append(False)
    
    return result

이제 주가 데이터를 전처리하는 함수를 만들자.

코드가 길지만, 내용은 단순하다.

먼저, 날짜라는 컬럼에 YYYYMMDD 형식으로 날짜가 정의되어 있는데, 숫자로 인식되어 있다.

이를 문자로 치환하여 YYYY, MM, DD를 가져오고, 날짜를 datetime으로 정의한 뒤, 날짜를 기준으로 오름차순 정렬하고, 날짜를 인덱스로 설정한다.

그리고 Series의 rolling 메서드를 사용하여 5일, 20일, 60일, 120일 이동 평균을 구한다.

이동평균을 구하는 과정에서 앞 부분에 결측이 생기므로, 결측이 있는 부분을 제거한다.

결측을 제거하고 데이터가 남아있다면, 앞서 정의했던 goldencross, deadcross 함수를 이용하여 골든크로스와 데드크로스 여부를 나타내는 컬럼을 만든다.

사실 골든크로스와 데드크로스의 정의에서 장기와 단기 이평선이 정확히 어떤 이평선인지를 정의하고 있지 않아, 4가지 경우의 수를 모두 고려한다. 

그리고 크로스가 포함된 컬럼이 모두 False라면 합이 0일테므로, 이 것을 활용해서 크로스가 없는 데이터라면 None을 리턴한다. 

def data_preprocessing(data):
    YYYY = data["날짜"].astype(str).str[:4]
    MM = data["날짜"].astype(str).str[4:6]
    DD = data["날짜"].astype(str).str[6:8]
    data["날짜"] = pd.to_datetime(YYYY + "/" + MM + "/" + DD)
    data.sort_values("날짜", inplace = True) # 날짜를 오름차순으로 정렬
    data.set_index("날짜", inplace = True) # 날짜를 인덱스로 설정
    
    data['5일이동평균'] = data['종가'].rolling(5).mean()
    data['20일이동평균'] = data['종가'].rolling(20).mean()
    data['60일이동평균'] = data['종가'].rolling(60).mean()
    data['120일이동평균'] = data['종가'].rolling(120).mean()
    data.dropna(inplace = True)
    
    if len(data) > 0:
        data['5-60_골든크로스'] = goldencross(data['5일이동평균'], data['60일이동평균'])
        data['5-120_골든크로스'] = goldencross(data['5일이동평균'], data['120일이동평균'])
        data['20-60_골든크로스'] = goldencross(data['20일이동평균'], data['60일이동평균'])
        data['20-120_골든크로스'] = goldencross(data['20일이동평균'], data['120일이동평균'])

        data['5-60_데드크로스'] = deadcross(data['5일이동평균'], data['60일이동평균'])
        data['5-120_데드크로스'] = deadcross(data['5일이동평균'], data['120일이동평균'])
        data['20-60_데드크로스'] = deadcross(data['20일이동평균'], data['60일이동평균'])
        data['20-120_데드크로스'] = deadcross(data['20일이동평균'], data['120일이동평균'])
        
        cross_cols = [col for col in data.columns if '크로스' in col]
        if data[cross_cols].sum().sum() > 0: # 크로스가 하나라도 있어야 함
            return data[cross_cols + ['종가']]
        else:
            return None
    else:
        return None

이제 코스피와 코스닥 데이터를 가져오면서 전처리를 한 뒤, data_dict에 추가한다.

# 데이터 불러오기 및 정제
for market in ["KOSPI", "KOSDAQ"]:
    for file_name in os.listdir(path + "/{}".format(market)):
        df = pd.read_csv(path + "/{}/".format(market) + file_name, encoding = "cp949")
        preprocessed_data = data_preprocessing(df)
        if type(preprocessed_data) != type(None):
            data_dict[file_name.split('.')[0]] = preprocessed_data

골든크로스 지점에서 매수, 데드크로스 지점에서 매도 시 기대 수익 계산

먼저 수익률 목록을 담을 사전을 정의한다.

# 수익률 목록 사전 초기화
profit_list_dict = dict()

그리고 코드 작성의 편의를 위해, 골든크로스 관련 컬럼과 데드크로스 관련 컬럼을 정의한다.

# 컬럼 설정
golden_cross_col_list = ['5-60_골든크로스', '5-120_골든크로스', '20-60_골든크로스', '20-120_골든크로스']
dead_cross_col_list = ['5-60_데드크로스', '5-120_데드크로스', '20-60_데드크로스', '20-120_데드크로스']

이제 모든 (golden_cross_col, dead_cross_col)을 기준으로 매매했을 때의 수익을 계산한다.

itertools.product를 이용하여 먼저 모든 조합을 순회하면서, profit_list를 빈 리스트로 초기화한다.

그 다음으로 해당 컬럼들에 True인 값이 하나라도 있으면, 골든크로스 지점과 데드크로스 지점을 탐색하여 각각 buying_time과 selling_time에 저장한다.

이제 buying_time에 있는 값을 bt로 순회하면서, 이 시점에서의 종가를 buying_price에 정의하고, bt보다 큰 selling_time에 있는 값 가운데 가장 작은 값을 매도 시점으로 정한다.

만약, bt 이후에 데드크로스가 없으면, 데이터가 끝나는 시점에 매도한다.

이 결과를 profit_list에 추가한 뒤, 골든크로스컬럼과 데드크로스 컬럼을 key로 하는 profit_list_dict에 profit_list를 저장한다. 

for golden_cross_col, dead_cross_col in itertools.product(golden_cross_col_list, dead_cross_col_list):
    # 수익률 목록 초기화
    profit_list = []
    for key in data_dict.keys():
        data = data_dict[key]
        if data[[golden_cross_col, dead_cross_col]].sum().sum() > 0:
            # 데이터를 순회하면서, 데드크로스와 골든크로스가 존재하는 데이터에 대해서만 아래 작업을 수행

            # 골든크로스 발생 시점을 모두 buying_time에 정의
            # 데드크로스 발생 시점을 모두 selling_time에 정의
            buying_time = data.loc[data[golden_cross_col]].index
            selling_time = data.loc[data[dead_cross_col]].index

            # 모든 골든크로스 발생 시점을 순회하면서
            for bt in buying_time:
                buying_price = data.loc[bt, '종가']
                if sum(selling_time > bt) > 0: # bt보다 이후에 발생한 데드크로스에서 판매
                    st = selling_time[selling_time > bt].min()
                    selling_price = data.loc[st, '종가']
                else: # 만약 이후에 발생한 데드크로스가 없으면, 끝까지 존버하다 판매
                    selling_price = data['종가'].iloc[0]
                profit = ((selling_price - buying_price) / buying_price) * 100
                profit_list.append(profit)
    
    profit_list_dict[golden_cross_col, dead_cross_col] = profit_list

이제 코드의 결과를 확인하자.

profit_list_dict에 있는 모든 요소에 describe 메서드를 이용하여 통계량을 낸 뒤, 이를 result라는 사전에 추가하고, DataFrame으로 변환하여 출력한다.

path = "../../QUANT_DATA/201609~202108/주가/일"
data_dict = dict()
0

파이썬에서 출력하니 보기가 힘들어서, 엑셀로 정리하였다.

모두 단위가 %임을 생각해서 해석해보도록 하자.

먼저, 최소값은 -95%인 경우가 있는데, 아마도 데드크로스가 발생하지 않고 버티기만 하다가 거의 상장폐지에 가까웠던 종목이 있던게 아닌가라고 추측할 수 있다.

그런데 어떤 골든크로스를 사용하던, 어떤 데드크로스를 사용하던, 50% 이상은 손실을 보게된다.

그러나 평균 이익은 양수로, 손실을 보는 사람은 많지만, 소수의 사람이 이익을 더 많이 보는 전략이라고 볼 수 있다.

전반적으로 따라할만한 전략은 아닌 듯 하다. 

골든 크로스 데드 크로스 - goldeun keuloseu dedeu keuloseu

골든크로스 지점에서 매수하고 1, 3, 6, 12개월 후 매도했을 때의 기대 수익 계산

수익률 목록을 초기화하고, 골든크로스 관련 컬럼을 정의한다.

# 수익률 목록 사전 초기화
profit_list_dict = dict()
path = "../../QUANT_DATA/201609~202108/주가/일"
data_dict = dict()
2

이제 각 컬럼을 순회하면서, 골든크로스 지점에서의 가격과 (bt), 1개월, 3개월, 6개월, 1년후의 가격을 비교하여, 수익률을 계산한다.

코드가 굉장히 길지만, 결국 핵심은 골든크로스가 발생한 지점을 buying_time에 정의하고, buying_time에 있는 요소에서 특정 기간 이후에서 시점이 데이터에 포함되어 있는지에 따라 이익을 계산하는 것이다.

그리고 계산한 결과를 기간 차이에 따라, 대응되는 리스트에 추가한다.

path = "../../QUANT_DATA/201609~202108/주가/일"
data_dict = dict()
3

실험 결과는 이전 방법과 동일하게 정리한다.

path = "../../QUANT_DATA/201609~202108/주가/일"
data_dict = dict()
0

의외의 결과가 몇 개 보이니 해석해보도록 하자.

먼저, 골든크로스 지점에 종목을 구매할 것이면 1년이 아니라, 6개월을 보유하는 것이 가장 큰 이익이 됨을 확인했다.

그리고 중위수가 0에 가까웠는데, 딱 절반은 손실을 보고 절반은 이익을 보는 구조라는 것을 알 수 있다.

골든 크로스 데드 크로스 - goldeun keuloseu dedeu keuloseu

그렇다하더라도 골든크로스 지점에서 구매하는 것은 추천할만한 전략은 아닌 것으로 보인다.

데드크로스 지점에서 매수하고 1, 3, 6, 12개월 후 매도했을 때의 기대 수익 계산

수익률 목록을 초기화하고, 데드크로스 관련 컬럼을 정의한다.

컬럼이 데드크로스와 관련된 것만 빼면, 위의 실험 과정과 완전히 동일하다.

# 수익률 목록 사전 초기화
profit_list_dict = dict()
path = "../../QUANT_DATA/201609~202108/주가/일"
data_dict = dict()
6
path = "../../QUANT_DATA/201609~202108/주가/일"
data_dict = dict()
7

마찬가지로 실험 결과를 확인하자.

path = "../../QUANT_DATA/201609~202108/주가/일"
data_dict = dict()
0

굉장히 당황스러운 결과인데, 사실 데드크로스에서 매수하는 전략은 거의 누구도 하지 않는다.

그런데 데드크로스에서 매수하는거와 골든크로스에서 매수하는것에 큰 차이가 없었고, 심지어는 골든크로스에서 구매해서 데드크로스에서 파는 것보다 데드크로스에서 사서 그냥 묵히는 것이 더 큰 이익이 나는 것을 확인했다.