지난 글에서 FinanceDataReader 모듈을 이용하여 주가를 가져와서 그래프로 나타내어 보았습니다. 주가는 거래 가격(PR; Price Return)과 배당 재투자 가격(TR; Total Return)으로 얻을 수 있었습니다. 그리 길지 않은 코드로도 그럴듯한 그래프를 만들 수 있었습니다. 지난 글: [파이썬 분석 2] PR(배당 미고려)과 TR(배당 재투자) 주가 흐름을 그래프로 그려 보자 (FinanceDataReader 모듈 사용)
투자에 참고하기 위해 여러 자산을 비교 분석하기 위해서는 정규화(normalization)를 해야 합니다. TR과 누적 수익률이 대표적인 정규화 방식입니다. TR은 배당이 많은 자산이 불리해 보이는 착시를 제거해 주고, 누적 수익률은 자산의 거래 통화나 단위가 다른 경우에도 동일한 조건으로 비교할 수 있게 변환해 줍니다.
이 글에서는 TR 주가 흐름을 이용하여 누적 수익률 그래프를 그리는 방법을 소개합니다.
주의: 이 글은 특정 상품 또는 특정 전략에 대한 추천의 의도가 없습니다. 이 글에서 제시하는 수치는 과거에 그랬다는 기록이지, 앞으로도 그럴 거라는 예상이 아닙니다. 분석 대상, 기간, 방법에 따라 전혀 다른 결과가 나올 수 있습니다. 데이터 수집, 가공, 해석 단계에서 의도하지 않은 오류가 있을 수 있습니다. 일부 설명은 편의상 현재형으로 기술하지만, 데이터 분석에 대한 설명은 모두 과거형으로 이해해야 합니다.
복수 주가 데이터로 그래프 그리기
다음은 SPY와 QQQ 두 ETF의 주가를 가져와서 그래프로 그리는 코드입니다.
import matplotlib as mpl
mpl.rcParams.update({"axes.grid" : True})
df = fdr.DataReader('SPY, QQQ')
df.plot()
하나씩 살펴봅니다.
import matplotlib as mpl
mpl.rcParams.update({"axes.grid" : True})
그래프에 격자에 항상 표시하고 싶기에, matplotlib 모듈의 기본값을 설정했습니다. 이렇게 한 번 설정해 두면, 이후 그리는 그래프에 기본으로 적용됩니다.
df = fdr.DataReader('SPY, QQQ')
df.plot()
SPY와 QQQ의 수정 종가를 불러와서 그래프로 그립니다. FinanceDataReader의 경우 복수 종목에 대한 주가를 얻고자 한다면, 이처럼 콤마(,)로 구분된 문자열을 입력하면 됩니다. 종목 하나만 지정하면 시가, 고가, 저가, 고가, 거래량과 수정 주가까지 함께 가져오지만, 복수 종목을 지정하면 수정 주가 하나만 가져옵니다.
아래는 가져온 주가 데이터로 그린 그래프입니다. 격자가 기본으로 들어갔습니다.
공통 기간에 대해 수익률 그래프 그리기
두 ETF의 상장 후 주가 흐름이 그래프로 표현되었지만, 시각적으로 몇 가지 문제가 있습니다. 먼저 두 종목의 상장 시기가 다릅니다. 세로축은 수익률이 아닌 가격으로 표시되어 있어 상대적인 비교가 어렵습니다. 또한, 산술 스케일이기에 주가의 장기 변화가 과장되어 있습니다.
이러한 문제를 해결하기 위해 데이터를 살펴보겠습니다.
df
df를 출력해서 보면 다음과 같은 결과를 보여줍니다.
SPY가 QQQ보다 먼저 상장되었기에 테이블 앞부분에는 SPY 칼럼에는 값이 있지만, QQQ 칼럼에는 NaN이라고 채워져 있습니다. DataFrame은 값이 비어있으면 NaN으로 표시합니다. 이 행들을 모두 지우는 명령어가 dropna()입니다.
아래는 공통 상장 기간에 해당되는 데이터만 뽑아서, 가격 대신 자산비로 바꾸어 그래프로 그리는 코드입니다
df2 = df.dropna()
df2 = df2 / df2.iloc[0]
df2.plot()
첫 번째 줄의 df.dropna()로 어느 한쪽에만 데이터가 존재하는 열을 지우고 df2에 넣습니다.
두 번째 줄에 사용한 df2.iloc[0]는 첫 번째 행을 말합니다. 구글 시트나 마이크로소프트 엑셀처럼 DataFrame은 배열 연산(array operation)을 지원합니다. df2의 모든 데이터에 대해 첫 번째 행의 값으로 나누라는 의미입니다. 첫 행의 값을 1로 한 정규화된 결과가 됩니다.
그래프로 그리면 다음과 같습니다.
자산비 대신 수익률로 그래프를 그리고 싶다면, 자산비에서 1을 빼서 수익률로 변환하면 됩니다.
from matplotlib.ticker import PercentFormatter
(df2 - 1).plot()
plt.gca().yaxis.set_major_formatter(PercentFormatter(xmax = 1))
from matplotlib.ticker import PercentFormatter는 matplotlib 아래에 있는 ticker 모듈에서 PercentFormatter 함수를 쓰겠다는 지시입니다. 이렇게 쓰는 이유는 앞에 모률명을 붙여 mpl.ticker.PercentFormatter와 같이 길게 쓰고 싶지 않기 때문입니다.
(df2 - 1).plot()은 자산비에서 1을 빼서 수익률을 변환하여 그래프를 그리는 명령입니다.
마지막 plt... 줄은 y축을 퍼센트로 표시하라는 지시입니다. xmax = 1로 지정한 것은 1을 100%로 해석하라는 뜻입니다. 기본은 퍼센트로 되어 있다고 가정하기에 1이 1%로 표시됩니다.
다음과 같은 그래프가 만들어집니다.
이제 보기 훨씬 좋아졌지만, 이 그래프는 세로축이 선형이기에 수익률에 대한 착시가 발생할 수 있습니다. 장기 투자의 누적 수익률 또는 누적 자산비는 로그 스케일(log scale)로 표현하는 것이 적절합니다.
로그 자산비 그래프 그리기
로그는 마이너스에 대해 정의가 되어 있지 않습니다. log(0) = -∞이기 때문입니다. 이 때문에 대개의 투자자에게 익숙한 수익률보다는 자산비로 데이터를 가공하여 처리하면서, 최종 결과만 수익률로 변환해서 표시하는 것이 편리합니다.
자산비 그래프는 다음과 같이 로그 스케일로 바꿀 수 있습니다.
df2.plot()
plt.yscale('log', base = 1.1)
자산비로 그래프로 그리고, plt.yscale()로 로그 스케일로 변환했습니다. base(지수의 밑수)가 1.1이기에 1.1의 몇 승인지로 눈금이 표현됩니다.
그래프를 보면 1.1³과 1.1⁸에 눈금이 그려져 있습니다. 지수 차이는 8 - 3 = 5입니다. 두 눈금 사이에는 복리 수익률 10%의 다섯 번에 해당되는 수익률 차이가 있다는 의미입니다.
로그 자산비 그래프에 수익률 눈금 달기
그래프는 로그 스케일로 변환되었지만, x축의 값이 지수 형태로 표현되기에 직관적이지 않습니다. 보다 익숙한 산술 수익률로 표시하는 것이 그래프를 해석하기에 편리할 수 있습니다.
다음은 그래프는 로그 스케일로 나타내고, 축의 값은 수익률로 표현하는 예입니다.
import numpy as np
df2.plot()
plt.yscale('log', base = 1.1)
yticks = np.array([-0.6, -0.4, -0.2, 0, 1, 2, 5, 10, 20])
plt.yticks(ticks = yticks + 1,
labels = [f'{round(tick * 100)}%' for tick in yticks])
plt.show()
첫 줄의 numpy는 수치 관련 함수를 제공하는 모듈입니다.
df2.plot()으로 자산비 그래프를 그리고, 이어서 로그 스케일로 그래프를 설정합니다.
y축에 표현할 수익률 목록을 yticks에 설정했습니다. np.array()는 데이터를 배열(행렬 포함)로 만드는 함수입니다. 배열은 배열 연산을 지원하기에 편리합니다.
plt.yticks()로 ticks와 labels 두 가지를 설정합니다. ticks는 눈금의 위치를, labels는 눈금에 붙이는 이름을 지정합니다. yticks 변수에 든 값은 수익률이기에 배열의 원소(element) 각각에 1을 더한 자산비로 눈금 위치를 지정합니다. 눈금값은 수익률로 나타내고 싶기 때문에 각각의 눈금 위치에 해당하는 적절한 수익률로 바꾸었습니다.
자주 사용되는 변환 방법이기에 간단하게 설명합니다.
[f'{round(tick * 100)}%' for tick in yticks]
[func(x) for x in array] 형식입니다. array라는 배열에 든 값 x 하나하나에 대해 어떤 func()을 적용하고, 그 결과를 배열로 만드는 것입니다. 여기서는 f'{found(tick * 100)%'를 지정했습니다. 눈금 위치에 100을 곱하고, 반올림을 한 후에 퍼센트(%) 기호를 붙이라는 뜻입니다. f는 f-string 포맷터라고 불립니다. 문자열 안에 {}가 발견되면, 연산을 수행합니다.
실행하면 다음과 같은 결과가 나옵니다. 제가 자주 사용하는 형태입니다.
이 그래프 형식은 상당히 유용합니다. 로그 스케일이기에 자산의 장기 누적 수익률 변화를 쉽게 파악할 수 있으면서, 수치는 익숙한 수익률로 표시되기 때문입니다.
구글 시트나 마이크로소프트 엑셀로 이와 같은 그래프를 그리려면 기본적인 기능으로는 쉽지 않을 것입니다. 로그 스케일로 지정하면, 설정할 수 있는 항목이 대폭 줄어들기 때문입니다. 제가 몇 번 시도해 보았는데 마음에 드는 결과를 얻을 수 없었습니다.
정리하며
자산의 누적 수익률을 흐름을 비교하기 위해 누적 자산비를 로그 스케일로 나타내면서, 눈금은 누적 수익률로 표시하는 방법을 살펴보았습니다. 간단해 보이는 이 표현 형식 하나만으로도 파이썬을 사용할 충분한 이유가 될 정도로 다른 툴로는 쉽게 표현하기 어려운 그래프 형식입니다.
이어지는 글에서는 주가 데이터에서 기초 통계량을 추출하여 확률 분포를 그리는 방법을 살펴봅니다.
참고: 연재와 관련한 질문은 댓글로 남겨주시기 바랍니다. 답변을 드리거나 이후 연재에서 다룰 수 있도록 노력하겠습니다.
참고 서적: 왜 위험한 주식에 투자하라는 걸까? - 장기 투자와 분산 투자에 대한 통계학적 시각
이어지는 글: [파이썬 분석 4] 수익률의 기초 통계량을 추출해 보고 확률 분포를 그려보자
연재 목록: 자산 배분 분석 방법 책 소개, 연재글 및 사례 모음 [목록]
함께 읽으면 좋은 글 (최신 글)
- [파이썬 분석 2] PR(배당 미고려)과 TR(배당 재투자) 주가 흐름을 그래프로 그려 보자 (FinanceDataReader 모듈 사용)
- [파이썬 분석 1] 주가 흐름을 그래프로 그려 보자 (구글 코랩을 써 보자! 인공지능 너도 실수하는구나?)
- [중급 14] 레버리지 ETF의 성과는 왜 좋았을까? (민감한 레버리지님과 기준 금리)
- [중급 13] 레버리지 배율은 무한정 높여도 될까? (복리 수익률을 평균-분산 그래프에 나타내 보자)
- [중급 12] 레버리지는 얼마나 위험할까? (레버리지의 위험을 평균-분산 그래프에 좀 더 현실적으로 표현해 보자)
함께 읽으면 좋은 글 (인기 글)
'주식투자' 카테고리의 다른 글
[파이썬 분석 7] 두 가지 자산을 혼합해 보고, 수익률 분포의 변화를 살펴보자 (0) | 2025.04.14 |
---|---|
[파이썬 분석 6] 산점도(scatter plot)에 자산의 특성을 나타내고, 예금과 혼합 효과도 표현해 보자 (0) | 2025.04.13 |
[파이썬 분석 5] 환율을 적용해 보고 어떤 변화가 발생했는지 살펴보자 (0) | 2025.04.13 |
[파이썬 분석 4] 수익률의 기초 통계량을 추출해 보고 확률 분포를 그려보자 (0) | 2025.04.12 |
[파이썬 분석 2] PR(배당 미고려)과 TR(배당 재투자) 주가 흐름을 그래프로 그려 보자 (FinanceDataReader 모듈 사용) (0) | 2025.04.12 |
[파이썬 분석 1] 주가 흐름을 그래프로 그려 보자 (구글 코랩을 써 보자! 인공지능 너도 실수하는구나?) (1) | 2025.04.11 |
[중급 14] 레버리지 ETF의 성과는 왜 좋았을까? (민감한 레버리지님과 기준 금리) (0) | 2025.04.11 |
[중급 13] 레버리지 배율은 무한정 높여도 될까? (복리 수익률을 평균-분산 그래프에 나타내 보자) (0) | 2025.04.10 |