-
파이썬으로 현물이자율(Spot Rate)과 선도이자율(Forward Rate) 구하기파이썬 2020. 4. 9. 18:38
제일 처음 포스팅한 글이 이항분포 모형이 되다보니 관련 글을 계속 쓰게 되네요. 앞에 글에서 언급하였던 내용을 복기하자면 이항분포의 단위기간을 매우 작은 크기로 줄이게 되면 이항모형과 블랙숄즈모형은 거의 유사한 값으로 도출이 됩니다. 따라서 이항분포 모형과 블랙숄즈 모형은 계산 과정은 다르지만 결국에는 같은 결과를 낼 수 있는 유사한 도구라는 것이고, 블랙숄즈 모형은 만기이전 행사를 구현할 수 없지만 이항모형은 만기이전 행사를 구현할 수도 있고, 옵션과 회사채가 혼합된 상품이나 기본적인 전환옵션에 추가적인 조건이 부여된 효과도 구현해낼 수 있기 때문에 더 우월한 모델이지 않을까라고 조심스럽게 생각해봅니다.
사설이 길었습니다. 표제의 본론으로 들어가면 CRR 이항모형은 만기시점(t)의 옵션가격으로부터 특정 이자율로 할인하여 직전시점(t-1)의 현재가치와 동일 시점의 행사가격 중 큰 값을 취하는 과정을 반복하여 옵션의 현재가치를 도출해 냅니다. 이 경우에 적용되는 이자율은 선도이자율로서 시장에서 관측되지 않는 이자율입니다. 현금흐름을 할인할 때 쓰는 이자율은 크게 3가지가 있는데 각각의 개념은 다음과 같습니다. 다 알고 계시는 내용일텐데 그냥 한번 되새겨 보겠습니다.
만기수익률(YTM, Yield To Maturity) 채권을 만기까지 보유하였을 경우의 현금흐름과 현재 채권가치를 일치시키는 이자율, 쿠폰이 없으면 현물이자율과 동일함 현물이자율(SR, Spot Rate, zero-coupon discount rate) 무이표채권(중간에 이자를 안주는 채권, 무이표채권, zero-coupon bond)의 만기현금흐름과 현재 채권가치를 일치시키는 이자율, 현재시점(t=0)로부터 미래의 특정시점(t=n)까지 기하평균이자율 선도이자율(FR, Forward Rate) 특정 기간에 적용되는 이자율, 현재부터 2년뒤까지의 현물이자율을 SR2라고 하고 현재부터 1년 뒤까지의 이자율을 FR(0,1), 1년뒤부터 2년 뒤까지의 이자율을 FR(1,2)라고 하면 (1+SR2)^2 = (1 + FR(0,1)) * (1 + FR(1,2))의 식이 성립합니다. 말로 써 놓으니까 더 복잡한 것 같습니다. 어쨌거나 선도이자율은 어디서 관측되지 않고 시장에서 관측되는 YTM을 가지고 추정하여야 합니다. 이렇게 추정하는 것을 Bootstrapping(부트스트래핑)이라고 한다고 합니다. 통계학에서 나온 용어 같은데 위키피디아 설명이 더 예술입니다. 부트스트랩(bootstrap) 또는 부트스트래핑(bootstrapping)은 "현재 상황에서 어떻게든 한다"는 뜻(https://ko.wikipedia.org/wiki/%EB%B6%80%ED%8A%B8%EC%8A%A4%ED%8A%B8%EB%9E%A9)이다. 아주 그냥 핵심적인 내용 같습니다. 관측이 안되는데 이론상으로 기간별 이자율을 구해야 하기 때문에 있는 자료 가지고 어떻게든 한다는 뜻이 아닐까 싶습니다.
여튼간에 선도이자율의 개념상 현물이자율로부터 추출하여야 하는데, 국내에서 발행되는 채권들은 대부분 이표채이기 때문에 채권 수익률은 YTM 기준으로 공시가 됩니다. 따라서 YTM 기준의 수익률로부터 SR을 추정하고 SR로부터 FR을 추정해야 하는 것이지요.
일단 2019년 12월 31일 기준으로 금융투자협회 채권정보센터에서*(http://www.kofiabond.or.kr/index.html) 조회된 금리를 보겠습니다. 옵션 평가를 위하여 선도이자율을 구하는 것이기 때문에 국고채 금리를 가져왔습니다. 이항모형에서 무위험이자율을 쓰는 이유는 무위험이자율로 할인하였을 때 주가가 상승하거나 하락하거나 무차별한 현재가치를 만들어주는 위험중립확률을 사용하였기 때문입니다. 저도 처음에 별 생각없을 때는 주가라서 회사의 리스크가 반영된 뭔가를 써야하는게 아닌가 했었습니다.
일단 위에 내용을 엑셀파일로 원하는 폴더에 저장하고 해당 폴더에 주피터노트북으로 아무제목.ipynb 파일을 만들어보겠습니다. 위 사이트에서 제공하는 엑셀파일을 다운로드 받아서 rf_ytm.xlsx 이름으로 저장한 아래와 같이 엑셀상에서 데이터를 조금 수정한 이후에 같은 이름으로 다시 저장하였습니다.
시점으로 월로 변경한 만기별 국고채의 YTM 입니다.
이 다음부터는 주피터 노트북에서 진행합니다.
In [1]은 pandas와 numpy를 임포트한 것입니다.
In [2]는 앞서 엑셀로 저장한 파일을 읽어 들여서 YTM이라는 이름의 DataFrame으로 저장하고, 엑셀 자료상 %가 생략되어 있어서 YTM에 나누기 100을 해 준 것입니다.
In [3]은 10년 기간에 YTM을 입력할 수 있도록 BSTR(BootSTRapping)이라는 이름으로 DataFrame을 만든 것입니다.
최초에는 3개월 단위로 하려고 했었는데, 작성하다보니 코드가 너무 복잡해져서 핵심내용을 전달하지 못 하는 것 같아서, 연단위로 10년간의 데이터를 만드는 것으로 수정하였습니다.
In [4]는 새로 만든 BSTR 데이터 프레임에 연도별 YTM을 기존 YTM 데이터 프레임으로부터 가져온 것입니다. YTM 데이터 프레임의 Month / 12가 BSTR 데이터 프레임의 Year와 같은 경우에 YTM 데이터 프레임의 YTM 값을 BSTR 데이터 프레임의 YTM 값으로 입력하였습니다. Out[4]를 확인하시면 6년, 8년, 9년 만기의 YTM이 공시되지 않으므로 최초에 설정한 값인 0으로 되어있는 것을 확인할 수 있습니다.
In [5]는 YTM이 시장에서 관측되지 않은 만기에 대한 YTM을 보간법으로 추정하여 입력한 것입니다. 6년차의 경우 7년차와 5년차 YTM의 차이를 2로 나눈 다음에 1년에 해당하는 금리를 5년차 YTM에 더하여 6년차 YTM을 만든 것이고, 8년차의 경우 10년차와 7년차 YTM의 차이를 3으로 나눈 다음에 1년에 해당하는 금리를 7년차 YTM에 더하여 8년차 YTM을 산출하였으며, 9년차 YTM은 직전에 만든 8년차 YTM과 10년차 YTM의 차이를 이용하여 산정한 것입니다. 코드 작성 측면에서 중요한 것은 for 반복문과 while 반복문을 함께 사용한 것인데, 첫번째 라인의 for i in BSTR.index 코드는 BSTR 데이터 프레임의 index에 있는 i에 대해서 라는 의미로서 0부터 9까지 index에 대해서 적용될 것입니다. 0에서 9까지라는 것을 이미 알고 있기 때문에 for i in range(10)이라고 해도 같은 결과가 나올 것 입니다. 두번째 라인의 BSTR.loc[i, 'YTM'] == 0 코드는 인덱스가 i인 행의 YTM 칼럼에 있는 값이 0이 참이라면 이라는 의미로서 인덱스가 0부터 시작하므로, 인덱스가 i인 행은 실제로 (i+1)행입니다. 결국 해당 연도의 YTM이 아직 0으로 입력된 행에 대해서 그 아래의 구문을 실행하겠다는 의미입니다. 세번째 라인의 j = 1 코드는 그 다음 while 반복문에서 j가 등장하기 때문에 먼저 변수 j를 선언한 것입니다. 먼저 선언하지 않으면 에러가 납니다. 네번째 라인의 while BSTR.loc[i+j, 'YTM'] == 0: 다섯번재 라인의 j = j+1, 여섯번째 라인의 if BSTR.loc[i+j, 'YTM'] > 0: 일곱번째 라인의 break까지는 인덱스를 하나씩 더해가면서 YTM의 값이 0이 아닐 때까지 가서 멈추라는 의미이며, 여기서 for 반복문이 아니라 while 반복문을 사용한 이유는 몇번 반복을 하여야 반복문이 종료되는지 사전에 모르기 때문에 j를 1씩 늘려가면서 YTM이 0보다 큰 인덱스가 되는 시점에서 반복문을 종료하게 한 것입니다. 이 코드에서 두 반복문의 차이는 일단 for 반복문은 반복의 횟수가 사전에 정해져 있다는 것이고 while 반복문은 반복의 횟수가 사전에 정해져있지 않다는 것입니다. 여덟번째 라인의 BSTR.loc[i, 'YTM'] = BSTR.loc[i-1, 'YTM'] + (BSTR.loc[i+j, 'YTM'] - BSTR.loc[i-1, 'YTM'])/(j+1) 코드는 인덱스가 i+j인 행에서 YTM이 0보다 큰 값이므로, j는 값이 정해져 있습니다. i가 7이라고 하면(8행) YTM이 0 이므로 i+j가 9가 되었을 때(10행)에서 YTM이 값을 가지고 있으므로, j는 2가 되는 것이고, 위 코드를 앞에서부터 i에 7을 입력하여 읽어보면, BSTR.loc[7, 'YTM']은 8년 만기 YTM의 값이라는 의미이므로, 8년 만기 YTM 값 = 7년 만기 YTM 값 + (10년 만기 YTM 값 - 7년 만기 YTM 값) / 3년 이라는 의미입니다. 파이썬 코드를 보여주는 목적으로 파이썬으로 작성하기는 하였는데, 데이터가 엄청 많아지기 전까지는 엑셀이 편할 것 같습니다. 이렇게 복잡한 과정을 거쳐서 BSTR을 출력하여 보면 이제 모든 행에서 YTM값이 채워진 것을 확인할 수 있습니다. 공시된 YTM의 사이의 기간들에 대해서는 등간격으로 YTM이 계산된 것을 확인할 수 있습니다.
In [6]은 BSTR에 현물이자율(SR, Spot Rate)을 구하기 위한 준비를 하는 코드입니다. YTM은 채권 원리금의 현금흐름을 채권의 현재가치와 일치시키는 이자율이므로, 채권의 쿠폰금리과 YTM과 같다고 하면, 해당 채권 원리금의 현금흐름을 YTM으로 할인한 현재가치는 해당 채권의 액면금액과 동일할 것입니다. 계산을 좀 편하게 하기 위해서 채권의 액면은 1이라고 가정합니다. 1년만기의 YTM은 1년만기의 SR과 동일하기 때문에 YTM 값을 SR에 입력하여 줍니다. C는 쿠폰(Coupon)을 의미합니다. 채권의 액면금액이 1이므로 쿠폰의 값은 YTM과 동일합니다. DFPVC는 만기원리금 직전까지의 쿠폰의 현가를 계산하기 위한 현가계수입니다(Discount Factor for Present Value of Coupon). LP는 만기원리금(Last Payment)을 의미합니다. PVC는 쿠폰의 현재가치(Present Value of Coupon)이며, PVLP는 만기원리금의 현재가치(Present Value of Last Payment)입니다. 1년차의 경우 만기원리금 이전에 쿠폰의 지급이 없으므로 PVC는 0이 되는 것이며 PVLP가 1이 되는 것입니다.
In [7]은 각 만기별 SR을 계산하는 표로서 가장 중요한 표입니다. 설명의 편의를 위하여 만기 t인 SR은 SRt로 표시하도록 하겠습니다. 표를 설명하기에 앞서 SR2를 구하는 방식을 말씀을 드리면 SR2는 1년차 쿠폰 / (1+SR1) + 만기 원리금 / (1+SR2)^2 = 1을 충족시키는 이자율입니다. 같은 방식으로 SR3을 구하면 SR3은 1년차 쿠폰 / (1+SR1) + 2년차 쿠폰 / (1+SR2)^2 + 만기 원리금 / (1+SR3)^3 = 1을 충족시키는 이자율입니다. SR1을 알기 때문에 SR2를 구할 수 있고, SR1과 SR2를 구한 이후에는 SR3를 구할 수 있습니다. 결구 위식을 일반화 시키면 SRt는 1년차 쿠폰 / (1+SR1) + 2년차 쿠폰 / (1+SR2)^2 + 3년차 쿠폰 / (1+SR3)^3 + ... + (t-1)년차 쿠폰 / (1+SR(t-1))^(t-1) + 만기 원리금 / (1+SRt)^t 와 같이 표현할 수 있습니다. 또한 액면을 1로 가정하였으므로 쿠폰은 (액면 * YTM = 1 * YTM = YTM)이 됩니다.
첫번째 라인의 for i in BSTR.index 코드는 앞서와 마찬가지로 BSTR의 인덱스에 대해서, 2번째 라인의 i >= 1 코드는 이미 인덱스가 0인 행에 대해서는 값을 다 입력하였으므로 인덱스가 1인 행부터 시작하겠다는 의미이며, 세번째 라인의 BSTR.loc[i, 'DFPVC'] = BSTR.loc[i-1, 'DFPVC'] + (1/(1+BSTR.loc[i-1, 'SR']))**BSTR.loc[i-1, 'Year'] 코드는 인덱스가 i인 쿠폰의 현가계수는 인덱스가 i-1인 쿠폰 현가계수 더하기 인덱스가 i-1인 SR을 인덱스가 i-1인 Year 기간동안 할인한 값을 합한 값입니다. 기호로 써 놓으니 어렵게 느껴지실 수 있는데 i = 2를 가정한다면 인덱스가 2인 쿠폰의 현가계수(3년 만기 채권의 1년차와 2년차 쿠폰의 현재가치를 구할 때 현가계수)는 2년 만기 채권에 적용한 DFPVC(만기원리금 직전의 쿠폰은 1년차 쿠폰이므로 1/(1+SR1)으로 계산됨) 더하기 2년차 쿠폰에 적용되는 현가계수(1/(1+SR2)^2)입니다. 풀어쓰면 3년차 DFPVC = 1/(1+SR1) + 1/(1+SR2)^2 가 되는 것입니다. 말로 설명을 하려니 너무 어려운 것 같습니다. 네번째 라인의 BSTR.loc[i, 'PVC'] = BSTR.loc[i, 'C'] * BSTR.loc[i, 'DFPVC'] 코드는 인덱스 i인 행의 쿠폰 현재가치를 구한 것입니다. 쿠폰에 현가계수를 곱한 값입니다. 다섯번째 라인의 BSTR.loc[i, 'PVLP'] = 1 - BSTR.loc[i, 'PVC'] 코드는 채권 원리금의 현재가치가 1이어야 하므로 만기원리금의 현재가치는 1 - 쿠폰의 현재가치 이라는 의미입니다. 다섯번째 라인은 인덱스가 앞서 구한 PVLP와 LP 그리고 만기를 활용하여 해당 만기의 SR을 구한 것입니다. 그리고 결과를 확인합니다. 뭔가 그럴싸하게 나왔습니다. 이제 현물이자율까지 구했고, 다음단계에서 선도이자율을 구해보도록 하겠습니다.
In [8]은 1년차 SR을 1년차 FR에 입력한 것입니다. 만기가 t인 현물이자율은 SRt로 표시하였으므로 (t-1)에서 t기간의 FR은 FR(t-1, t)와 같이 표시하도록 하겠습니다. 첫번째 라인의 BSTR.insert(3, 'FR', 0)은 인덱스 3의 칼럼에 FR이라는 이름의 칼럼을 추가하고 값을 0으로 입력하라는 의미입니다. 두번째 라인은 첫번째 행의 FR에 첫번째 행의 SR을 입력하라는 것입니다.
In [9]는 2년차부터 10년차까지의 선도이자율을 구하는 코드입니다. 일단 1년차를 입력하였으므로 2년차부터 계산을 합니다. 세번째 라인의 코드는 인덱스가 i인 행의 FR은 인덱스가 i인 행의 SR과 Year를 활용한 종가를 인덱스가 i-1인 행의 SR과 Year를 활용한 종가로 나눈 값에서 1을 뺀 값이라는 의미입니다. 2년차의 선도이자율을 계산할 경우 (1+FR(0,1))*(1+FR(1,2)) = (1+SR2)^2 입니다. 따라서 FR(1,2) = ((1+SR2)^2)/(1+SR1) - 1 입니다(SR1과 FR(0,1)은 동일하므로). 3년차에 적용을 해보면 (1+FR(0,1))*(1+FR(1,2))*(1+FR(2,3)) = (1+SR3)^3 인데 좌측의 식을 (1+SR2)^2*(1+FR(2,3))
으로 고쳐 쓸 수 있습니다. 이렇게 되면 FR(2,3) = ((1+SR3)^3)/(1+SR2)^2) - 1 이 됩니다.
아래는 SR과 FR이 제대로 구해졌는지 검증하기 위한 식입니다. 엑셀로 검증하셔도 됩니다.
이렇게 고생해서 산정한 선도이자율은 이항모형에서 특정 시점의 옵션가치를 직전 시점의 옵션가치로 할인할 때마다 각 기간의 할인율로 사용하게 됩니다. 각 기간에 적용하는 이자율이 기간마다 다르므로 기간마다 위험중립확률(P)도 다를 것입니다. 이론상 이항모형은 심플한데 실제 이항모형은 손이 많이 갑니다. 그래도 블랙숄즈 모형에서는 기간마다 이렇게 다른 이자율을 적용하는 것이 용이하지 않을텐데 이항모형에서는 이러한 조건 변동도 반영할 수 있기 때문에 가치평가 실무에서 더 활용도가 높은 것 같습니다.
위의 부트스트래핑 작업은 당연히 엑셀로도 가능하며, 기간이 매우 길지 않은 이상 엑셀로 작성하는 것이 더 용이할 것 같습니다. 파이썬을 공부하는 목적도 있기 때문에 파이썬으로 끝까지 구현해 보았습니다. 저도 아직까지 엑셀이 파이썬이나 판다스보다 훨씬 익숙하지만 파이썬을 공부하면서 몇몇 가능성을 발견하였고, 그러한 부분들에서 흥미를 느끼고 공부하고 있습니다.
긴 글 읽어주셔서 감사합니다.
'파이썬' 카테고리의 다른 글
과연 이항모형은 아메리칸 옵션을 구현할 수 있는가(feat. CRR 논문) (5) 2021.05.15 파이썬으로 금리파생상품(조기상환권, 수의상환권, Put Option, Call Option) 모델 만들기(fsolve로 Black-Derman-Toy 모델 구현)(Part 1) (5) 2020.10.22 파이썬으로 목표값(또는 최적해) 찾기(Goal Seek) (4) 2020.04.22 파이썬으로 블랙숄즈 옵션 평가 모델 만들기 (0) 2020.04.02 파이썬으로 이항분포 옵션 평가(CRR) 모델 만들기 (15) 2020.03.29