투자/생각

[Python] Bootstrapping VaR

khc9914 2024. 2. 23. 12:26
from selenium.webdriver.common.by import By
from selenium import webdriver
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from datetime import datetime, date, timedelta
import scipy.stats as stats
from scipy.stats import multivariate_normal
from scipy.stats import norm,beta,cauchy,expon,rayleigh,uniform,multivariate_t,t,kendalltau,rankdata
import statsmodels.api as sm
import FinanceDataReader as fdr
from matplotlib.patches import Ellipse
import matplotlib.transforms as transforms
import math
from mpl_toolkits.mplot3d import Axes3D
from pycopula.copula import ArchimedeanCopula
from pycopula.copula import GaussianCopula
import seaborn as sns
sns.set_theme(style='whitegrid')

필요한 기능 로드


#현재 USD/KRW 환율 로드
USDKRW=fdr.DataReader('USD/KRW')['Close']
USDKRW=USDKRW.iloc[len(USDKRW)-1]

#웹 페이지 열기
driver=webdriver.Chrome()
driver.get('게시한 구글 스프레드 시트 주소')
driver.find_element(By.XPATH,'//*[@id="sheet-button-1233126621"]/a').click() #탭 클릭

#내 포트폴리오 정보 크롤링
Data=pd.DataFrame() #빈 DataFrame 생성
for i in range(3,20):
    Data.loc[i-3,'종목명']=driver.find_element(By.XPATH,f'//*[@id="1233126621"]/div/table/tbody/tr[{i}]/td[1]').text
    Data.loc[i-3,'통화']=driver.find_element(By.XPATH,f'//*[@id="1233126621"]/div/table/tbody/tr[{i}]/td[3]').text
    Data.loc[i-3,'티커']=driver.find_element(By.XPATH,f'//*[@id="1233126621"]/div/table/tbody/tr[{i}]/td[4]').text
    Data.loc[i-3,'잔고주식수']=driver.find_element(By.XPATH,f'//*[@id="1233126621"]/div/table/tbody/tr[{i}]/td[7]').text
    Data.loc[i-3,'잔고평가액']=driver.find_element(By.XPATH,f'//*[@id="1233126621"]/div/table/tbody/tr[{i}]/td[11]').text

Data['잔고평가액']=Data['잔고평가액'].str.replace(",","").astype('float') #잔고평가액 쉼표 없애고 실수로 변환
Data.loc[Data['통화']=='USD','잔고평가액']=Data['잔고평가액']*USDKRW # USD는 환율 곱해주어 KRW로 환산

Data=Data.loc[Data["잔고평가액"]>0,:] #'잔고평가액' > 0 인 행만 추출

웹 크롤링으로 보유하고 있는 종목의 '종목명 / 통화 / 티커 / 잔고주식수 / 잔고평가액'을 가져온다.

USD로 표시된 미국 ETF의 경우 현재 환율을 곱해주어 KRW로 환산

잔고평가액이 0을 초과하는 (현재 투자하고 있는) 종목만 선택

'Data'


years=3
endDate=datetime.now()
startDate=endDate-timedelta(365*years)

tickers=Data['티커'].to_list()

returns=pd.DataFrame()
for i in tickers:
    try:
        returns[i]=fdr.DataReader(i,start=startDate,end=endDate)['Adj Close']
    except:
        returns[i]=fdr.DataReader(i,start=startDate,end=endDate)['Close']

returns=returns.dropna(axis=0).pct_change().dropna()

수익률을 가져올 기간을 설정해 주고, FinanceDataReader를 통해 주가 크롤링 및 수익률로 변환.

한국 주식의 경우 'Adj Close'가 없는 경우가 있어 오류가 발생하면 'Close'를 가져오도록 함.

'Returns'


mc_sims=1000 #시뮬레이션 횟수
T=90 #Days
VaR_Percentile=95
portfolio_sim_result=pd.DataFrame()

for x in range(0,mc_sims):
    simulated_returns=returns.sample(T)+1 #3년 동안의 수익률 중 T일 개의 수익률 선택
    simulated_returns=np.insert(simulated_returns,0,np.repeat(1.0,len(Data)),axis=0) #첫 행에 [1 1 1 1 1] 추가
    portfolio_sim=pd.DataFrame()
    for i in range(0,len(Data)):
        portfolio_sim[tickers[i]]=np.cumprod(simulated_returns[:,i])*Data.iloc[i,4] #'T' Days의 누적 수익률 * 기초 투자 금액
    
    portfolio_sim_result[x]=np.sum(portfolio_sim,axis=1)/10000

수익률 선택 예시

위의 방식으로 한 행이 랜덤으로 선택되면 해당 행의 수익률 전체를 각 종목에 적용.

(각 종목마다 따로 선택하면 상관성이 반영되지 않음.)

이런 방식으로 랜덤으로 90개의 수익률 선택.

'simulated_returns'

각 종목의 가치 변화를 시뮬레이션하고, 이를 가로 방향으로 합하면 전체 포트폴리오 가치의 변화가 된다.

이 계산을 시뮬레이션 횟수 (1000번) 만큼 반복하면

90개의 행 (90일 가치 변화), 1000개의 열 (시뮬레이션 횟수)를 지닌 Dataframe이 생성된다.


plt.figure(figsize=(8,4))
plt.plot(portfolio_sim_result,linewidth=1)
plt.ylabel('Portfolio (10 Thousands)')
plt.yscale
plt.xlabel('Days')
plt.title('MC Simulation for Portfolio')

1000개 시뮬레이션 추이


portfolio_sim_result_lastvalues=portfolio_sim_result.iloc[len(portfolio_sim_result)-1,:]
VaR=np.percentile(portfolio_sim_result_lastvalues,100-VaR_Percentile)

plt.figure(figsize=(8,4))
sns.kdeplot(portfolio_sim_result_lastvalues,fill=True)
plt.xlim(750,3000)
plt.ylabel('Density')
plt.xlabel('Value (10 Thousands)')
plt.title('Distribution of Portfolio Value')
plt.axvline(x=Data['잔고평가액'].sum()/10000,color='k',linestyle='dashed',label=f'Initial Value {Data['잔고평가액'].sum()/10000:.0f}',linewidth=1.5)
plt.axvline(x=VaR,color='r',linestyle='dashed',label=f'{T} Days {VaR_Percentile}% VaR {VaR:.0f} ({VaR-Data['잔고평가액'].sum()/10000:.0f})',linewidth=1.5)
plt.legend()

90일 결과의 분포