투자/퀀트

파이썬, 구글 코랩 / 비트코인과 금 가격, 주가와의 상관관계 산출

KTCF 2023. 2. 8. 08:00

 

비트코인이 가진 성격에 대해 다양한 의견이 존재한다. 결제 수단으로써의 비트코인, 디지털 금과 같은 가치저장 수단, 기술주와 같은 위험 자산 등등...

이번 포스팅에서는 비트코인의 가격과 금 가격, 주가와의 상관관계를 통해 비트코인의 자산으로써의 성격을 알아보고자 한다.

 

※ 사전 준비

야후 파이낸스(yfinance) install 및 import 완료

판다스(pandas) import 완료

 

btc = yf.download('BTC-USD', period = '2y', auto_adjust = True)

yf.download()

비트코인 가격, 금(선물) 가격, 나스닥 지수, S&P500 지수 데이터 2년치를 추출했다.

배당, 액면분할 등을 고려하기 위해 수정주가로 산출했다. (auto_adjust = True)

 

※ 야후 파이낸스에서 데이터를 가져오는 내용을 자세히 보고 싶다면, 이 포스팅을 참고

(2021.07.10 - [Quant] - 파이썬, 구글 코랩 / 야후 파이낸스로 주가 데이터 추출)

# 비트코인, 금, 나스닥, S&P500 데이터 추출
btc = yf.download('BTC-USD', period = '2y', auto_adjust = True)
gold = yf.download('GC=F', period = '2y', auto_adjust = True)
nasdaq = yf.download('^IXIC', period = '2y', auto_adjust = True)
snp500 = yf.download('^GSPC', period = '2y', auto_adjust = True)

 

비트코인, 금 데이터

비트코인과 금 데이터는 위와 같다.

비트코인은 731개 행, 금은 505행의 데이터가 존재한다.

비트코인은 24/7 계속 거래되기 때문에 데이터의 양이 더 많다.

나스닥, snp500 데이터

나스닥, S&P500 데이터는 위와 같다.

둘 다 504개 행의 데이터가 존재한다.

금 선물 데이터보다 1개 더 적은데, 이유는 잘 모르겠다. (선물거래소보다 증권거래소 휴장일이 하루 더 많은 듯...)

 

btc = pd.DataFrame(btc['Close'])

종가 데이터 추출

기존 데이터프레임에서 종가 데이터만 추출한다.

코드를 설명하자면, btc라는 데이터프레임의 ['Close']라는 행을 가져오고,

이를 다시 데이터프레임으로 만들어 btc에 할당하라는 뜻이다.

# 비트코인, 금, 나스닥, S&P 500 종가 데이터 추출
btc = pd.DataFrame(btc['Close'])
gold = pd.DataFrame(gold['Close'])
nasdaq = pd.DataFrame(nasdaq['Close'])
snp500 = pd.DataFrame(snp500['Close'])

종가 데이터 추출

위와 같이 종가 데이터만 남았다.

금, 나스닥, S&P500 모두 동일하다

 

btc.reset_index(inplace = True)

인덱스 재설정

데이터를 보면 인덱스가 날짜로 되어 있다.

개별 자산별로 날짜 형식이 동일하면 좋겠지만, 다른 관계로 년월일만 남기고 나머지는 제거해줄 생각이다.

이를 위해, 날짜로 설정된 인덱스를 재설정한다.

# 인덱스 재설정
btc.reset_index(inplace = True)
gold.reset_index(inplace = True)
nasdaq.reset_index(inplace = True)
snp500.reset_index(inplace = True)

인덱스 재설정

위와 같이 날짜에 설정되어 있던 인덱스가 해제되었고, 새로운 인덱스가 만들어졌다.

 

btc['Date'] = btc['Date'].astype('str')
btc['Date'] = btc['Date'].str.slice(0, 10)

btc['Date'] = btc['Date'].astype('str') \n btc['Date'] = btc['Date'].str.slice(0, 10)

날짜 데이터를 년월일(yyyy-mm-dd)만 남기기 위해 문자열(String) 타입으로 전환한다.

astype은 데이터의 형식을 전환하기 위해 사용하는 메소드이다.

 

그리고 slice(start, end)는 왼쪽부터 시작 인덱스(start)부터 끝 인덱스(end)까지 남기는 것을 뜻한다.

단, 끝 인덱스(end)는 포함하지 않는다.

예를 들어, tistory라는 글자가 있을 때 sto만 남기고 싶다면, slice(2, 5)가 되는 것이다.

다시 돌아가서, yyyy-mm-dd(- 포함)의 글자 수가 10개이기 때문에, 앞에서 10번째 글자까지만 남긴다.

# 년월일만 남기기
# 스트링으로 전환
btc['Date'] = btc['Date'].astype('str')
gold['Date'] = gold['Date'].astype('str')
nasdaq['Date'] = nasdaq['Date'].astype('str')
snp500['Date'] = snp500['Date'].astype('str')

# 앞의 10글자만 남기기
btc['Date'] = btc['Date'].str.slice(0, 10)
gold['Date'] = gold['Date'].str.slice(0, 10)
nasdaq['Date'] = nasdaq['Date'].str.slice(0, 10)
snp500['Date'] = snp500['Date'].str.slice(0, 10)

년월일(yyyy-mm-dd)만 남았다. 나스닥, s&p500도 마찬가지

btc.rename(columns = {'Close':"btc_price"}, inplace = True)
df = pd.merge(btc, gold, on = 'Date')

btc.rename(columns = {'Close':"btc_price"}, inplace = True) \n df = pd.merge(btc, gold, on = 'Date')

4개의 데이터프레임으로 되어 있는 데이터들을 하나의 데이터프레임으로 모아준다.

그 전에, 개별 데이터의 행 이름이 'Close'로 동일하기 때문에, 구분을 위해 행 이름을 변경해준다.

행 이름을 변경한 후, merge() 메소드를 통해 각각의 데이터프레임을 병합한다.

기준은 날짜로 지정하였고, 이 경우 날짜가 동일한 데이터는 병합되고, 동일한 날짜가 없는 데이터는 버려진다. 

# 하나의 데이터프레임으로 합침
# 행 이름 변경
btc.rename(columns = {'Close':"btc_price"}, inplace = True)
gold.rename(columns = {'Close':"gold_price"}, inplace = True)
nasdaq.rename(columns = {'Close':"nasdaq_price"}, inplace = True)
snp500.rename(columns = {'Close':"snp500_price"}, inplace = True)

# 각각의 데이터프레임 병합
df = pd.merge(btc, gold, on = 'Date')
df = pd.merge(df, nasdaq, on = 'Date')
df = pd.merge(df, snp500, on = 'Date')

위의 코드를 실행한 결과는 아래와 같다.

행의 개수가 기존 4개 데이터프레임의 최소값인 504개로 줄었으며, 개별 종가데이터가 하나의 데이터프레임으로 합쳐졌다.

데이터프레임 병합

df['rolling_30_btc_gold'] = df['btc_price'].rolling(30).corr(df['gold_price'])

df['rolling_30_btc_gold'] = df['btc_price'].rolling(30).corr(df['gold_price'])

다음은 상관계수를 구할 차례이다.

판다스에서는 rolling()과 corr()메소드를 가지고 있기 때문에, 데이터프레임에서 바로 구할 수 있다.

rolling(window)은 이동평균, 이동 상관계수 등을 구할 때 자주 사용하는 메소드로, 숫자에 해당하는 행만큼을 한 구역(window)로 묶고 평균, 상관관계 등을 계산할 수 있게끔 해준다.

예를 들어, window = 5라 하면 아래의 빨간 사각형(?)이 설정되는 것이다.

그러므로, rolling(30).corr()은 30개 행의 구역의 상관계수를 구하라는 뜻이 된다.

rolling(window)

# 상관계수 구하기
df['rolling_30_btc_gold'] = df['btc_price'].rolling(30).corr(df['gold_price'])
df['rolling_30_btc_nasdaq'] = df['btc_price'].rolling(30).corr(df['nasdaq_price'])
df['rolling_30_btc_snp500'] = df['btc_price'].rolling(30).corr(df['snp500_price'])

위의 코드를 실행한 결과는 아래와 같다. 

비트코인과 금, 나스닥, s&p500의 30일 이동 상관계수 결과

보기 쉽게, 그래프로 그리면 아래와 같다.

최신 데이터를 보면, 각각의 상관계수가 0.9에 육박하는 것을 볼 수 있는데,

이를 해석하면 "최근에는 비트코인, 금, 나스닥, s&p500의 가격이 대체로 같이 움직인다"는 뜻이다.

인플레이션 추세가 조금 꺾이고, 연준의 긴축 기조 전환에 대한 기대감으로 대부분의 자산 가격이 상승하고 있는 최근 동향을 반영해주는 결과인 듯하다.

비트코인과 금, 나스닥, s&p500의 30일 이동 상관계수 그래프

전체 코드

더보기
!pip install yfinance
import yfinance as yf
import pandas as pd

# 비트코인, 금, 나스닥, S&P500 데이터 추출
btc = yf.download('BTC-USD', period = '2y', auto_adjust = True)
gold = yf.download('GC=F', period = '2y', auto_adjust = True)
nasdaq = yf.download('^IXIC', period = '2y', auto_adjust = True)
snp500 = yf.download('^GSPC', period = '2y', auto_adjust = True)

# 비트코인, 금, 나스닥, S&P 500 종가 데이터 추출
btc = pd.DataFrame(btc['Close'])
gold = pd.DataFrame(gold['Close'])
nasdaq = pd.DataFrame(nasdaq['Close'])
snp500 = pd.DataFrame(snp500['Close'])

# 인덱스 재설정
btc.reset_index(inplace = True)
gold.reset_index(inplace = True)
nasdaq.reset_index(inplace = True)
snp500.reset_index(inplace = True)

# 년월일만 남기기
# 스트링으로 전환
btc['Date'] = btc['Date'].astype('str')
gold['Date'] = gold['Date'].astype('str')
nasdaq['Date'] = nasdaq['Date'].astype('str')
snp500['Date'] = snp500['Date'].astype('str')

# 앞의 10글자만 남기기
btc['Date'] = btc['Date'].str.slice(0, 10)
gold['Date'] = gold['Date'].str.slice(0, 10)
nasdaq['Date'] = nasdaq['Date'].str.slice(0, 10)
snp500['Date'] = snp500['Date'].str.slice(0, 10)

# 하나의 데이터프레임으로 합침
# 행 이름 변경
btc.rename(columns = {'Close':"btc_price"}, inplace = True)
gold.rename(columns = {'Close':"gold_price"}, inplace = True)
nasdaq.rename(columns = {'Close':"nasdaq_price"}, inplace = True)
snp500.rename(columns = {'Close':"snp500_price"}, inplace = True)

# 각각의 데이터프레임 병합
df = pd.merge(btc, gold, on = 'Date')
df = pd.merge(df, nasdaq, on = 'Date')
df = pd.merge(df, snp500, on = 'Date')

# 상관계수 구하기
df['rolling_30_btc_gold'] = df['btc_price'].rolling(30).corr(df['gold_price'])
df['rolling_30_btc_nasdaq'] = df['btc_price'].rolling(30).corr(df['nasdaq_price'])
df['rolling_30_btc_snp500'] = df['btc_price'].rolling(30).corr(df['snp500_price'])

# 시각화
import matplotlib.pyplot as plt
df.plot(x = 'Date', y=['rolling_30_btc_gold', 'rolling_30_btc_nasdaq', 'rolling_30_btc_snp500'])