ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 파이썬으로 이항분포 옵션 평가(CRR) 모델 만들기
    파이썬 2020. 3. 29. 11:34

    저는 공인회계사이고 프로그래밍 전문가는 아닙니다. 우연한 기회에 파이썬을 접하게 되었고, 엑셀로 구현하기 어려운 이항분포 옵션 평가 모델을 파이썬으로 만들 수 있지 않을까 하여, 수개월 가량 각종 서적과 유튜브 등을 뒤져가면서 공부한 끝에 최근에는 원하던 모델을 파이썬으로 구현할 수 있게 되었습니다. 익숙해지고 나니 엑셀보다 훨씬 실행속도가 빠르고 복잡한 조건들을 구현할 수 있어서, 실제로 옵션 평가 업무 수행시에 활용하고 있습니다. 또한 파이썬을 활용하여 대량 데이터 분석이나 웹 크롤링도 가능하다는 것을 알게 되었고 이것저것 재밌게 배워가며 업무에 활용하려고 노력하고 있습니다.

     

    블로그를 한번 만들어보고 싶다는 생각을 하고 있었는데, 파이썬을 활용한 재무분석이 좋은 주제가 될 수 있지 않을까 하여 첫 글을 시작하게 되었습니다. 첫번째 주제로는 저를 파이썬의 길로 들어서게 한 이항분포 옵션 평가 모델로 하기로 하였습니다. 들어가기에 앞서 미리 변명을 좀 드리자면 저는 프로그래밍 전문가도 아니며 금융공학 전문가도 아니기 때문에 해당 분야 전문가께서 보시면 많이 부족한 부분이 있을 것 같기도 합니다. 코드가 너무 비효율적이거나 금융공학 이론적인 측면에서 오류가 있다면 지적하여 주시면 감사하겠습니다.

     

    일단 코드의 가장 첫번째 부분입니다. 코딩 환경은 주피터 노트북입니다.

    In [1] 부분은 지수함수와 제곱근 등을 활용하기 위하여 math 모듈을 임포트한 것입니다.

     

    In [2] 부분은 옵션의 기초 조건들을 가정한 것입니다. 기본적으로 콜옵션을 가정하였습니다.

    S0는 현재주가를 의미하며,

    V는 주가의 기간별 변동성을 의미하고,

    T는 이항분포가 발생하는 전체 기간을 의미합니다.

    dt는 이항분포가 1회 발생하는 단위기간을 의미하여 계산상 편의를 위하여 1로 하였습니다.

    만약에 연간주가변동성을 기준으로 이항분포를 만드는데,

    주가의 상승이나 하락이 월에 1회 발생함을 가정하였다면,

    dt = 1 /12 = 0.8333... 이 됩니다.

    Rf는 무위험이자율을 의미하며, 이산복리를 가정합니다.

    K는 옵션의 행사가격을 의미합니다. 본 사례의 평가대상 옵션은 현재주가가 100이고 행사가격도 100인 콜옵션입니다.

    실제 업무시에는 옵션만 별도로 평가하는 경우보다는 옵션과 회사채가 붙어있는 전환사채나 상환전환우선주(RCPS)를 평가하는 경우가 더 빈번합니다. 전환사채는 전환권과 회사채가 함께 붙어있어서, 전환권을 행사하면 회사채 현금흐름을 포기해야 하기 때문에 전환시 주식가치가 행사가격보다 큰 경우에도 회사채의 현재가치가 전환시 주식가치보다 더 높다면 행사되지 않습니다. 실제 발생 가능한 경우는 예를 들어 YTM 6%로 5년만기 전환사채를 발행하였는데, 만기가 가까워진 시점에 주가가 행사가격(중간에 리픽싱이 없었다면 일반적으로 전환사채 액면과 동일함)과 유사한 수준이라면 옵션을 행사하지 않고 회사채 상환을 기다릴 것입니다. 여튼 본 사례에서는 분리된 콜옵션을 가정하였기 때문에 행사가격만 고려하는 것으로 하였습니다.

     

    In [3] 부분은 기본 가정으로 주어진 변동성과 단위기간을 활용하여 주가상승배수인 u와 주가하락배수인 d 및 위험중립확률인 P를 계산한 것입니다. 대부분 알고 계신 내용이겠지만 혹시나 하여 말씀을 드리면 u는 주가상승률이 아니라 주가상승시 배수입니다. 30% 상승하면 u = 1+ 30% = 1.3이 되는 것입니다. 위험중립확률은 주가상승과 주가하락이 확률적으로 발생한다고 가정할 때 주가가 상승할 확률을 의미하며, 위험중립이라는 말이 붙은 이유는 P의 확률로 주가가 상승하는 경우와 나머지 확률로 주가가 하락하는 경우의 기대값을 무위험이자율로 할인하게되면 현재 주가와 동일한 값이 산출되기 때문입니다. 식으로 표현하면 S0 = [P * u * S0 + (1 - P) * d * S0] * e ^ (-Rf) 등식을 충족하는 P 값이라고 할 수 있습니다.

     

    다음 단계는 코드의 두번째 부분인 주가트리를 생성하는 부분입니다.

     

    In [4]는 시점별 노드별 주가를 입력할 수 있도록 (T + 1) * (T + 1) 크기의 매트릭스를 모든 값을 0으로 하여 생성하는 것입니다. 코드를 순서대로 읽어가면 np.zeros((T +1) * (T + 1)).reshape(T + 1, T + 1)은 36개(6 * 6)의 0을 만들어서 6 곱하기 6의 형태로 행렬을 만들라는 것입니다. S_tree = pd.DataFrame()은 괄호 안에서 만들어진 행렬을 pandas의 데이터프레임 형식으로 S_tree라는 변수에 저장하라는 의미입니다. 개인적으로는 이 코드를 발굴하기까지가 가장 힘들었습니다. 엑셀에서는 현재 주가로부터 상승시에는 u를 곱하고 하락시에는 d를 곱해서 순서대로 표를 만들어주면 되는데, 이거를 파이썬에 구현하려고 하니까 도대체 어떻게 그런 테이블을 만들어낼 수 있는지부터 전혀 감이 안잡혔습니다. 다행히 어느 외국 유튜버께서 이 과정을 numpy로 만들어 놓으신걸 보고 제가 pandas로 변경시켜서 활용하고 있습니다. numpy는 행과 열의 번호가 보이지 않아서 T가 큰 값일 때 눈으로 따라가기가 어려운 단점이 있습니다. 아마 pandas에 익숙하신 분에게는 쉬운 내용일 수도 있는데, 저한테는 파이썬에서 이렇게 테이블을 만들 수도 있다는 사실과 테이블의 데이터를 입력하기 위해 반복문을 사용할 수 있다는 점 등을 알아내기까지 시간이 많이 걸렸던 것 같습니다. 오히려 이 다음부터는 쉽게 해결하였던 것 같습니다. 

    In [5]는 현재 주가 100부터 주가상승배수와 주가하락배수를 시점과 노드에 맞게 곱하여 주가 트리를 생성하는 것입니다. In [4]에서 모든 값이 0인 트리를 먼저 만들고 각 칸에다가 새롭게 계산한 값을 입력하여 주는 것입니다. S_tree.loc[node, time]은 S_tree의 node행과 time열의 위치의 데이터를 의미합니다. 위에서 모두 0으로 생성하였기 때문에 새로운 값이 입력되기 전까지는 0으로 되어있을 것입니다. S0 * m.pow(u, (time - node)) * m.pow(d, node)는 S0 값에다가 노드 수만큼 주가하락배수를 제곱하고, (time - node) 수만큼 주가상승배수를 제곱하라는 식입니다. 다른 방식으로 구현할 수도 있겠지만 일단 저는 가장 윗줄이 처음부터 끝까지 상승만 발생하는 경우가 되도록 행렬을 만들었으므로 node가 0일 때는 time 수만큼 u를 제곱하게 되고, node가 1일 때에는 상승은 (time - 1)회 발생하게 되고, 하락은 1회 발생하게됩니다. 같은 방식으로 node를 늘려나가면 제일 아래 값은 5회 하락이 발생할 것이고 상승은 0회 발생할 것입니다. time >= node 조건은 time 수보다 node가 많을 수 없기 때문입니다. 시점 1에 하락이 2회 발생할 수는 없는 것이니까요.

     

    코드의 세번째 부분은 시점별 노드별 옵션의 기대값을 구하고 각 기대값을 무위험이자율로 할인하여 옵션의 현재가치를 구하는 것입니다.

     

    In [6]은 옵션 행사시 기대값 트리를 생성하는 것입니다. 마찬가지로 모두 0으로 구성된 행렬을 먼저 만들고, 트리의 node 행과 time 열에 max(주가 - 행사가격, 0)의 값을 입력하였습니다.

     

    In [7]은 만기 시점의 옵션 행사시 기대값을 옵션 트리에 입력하였습니다.

     

    In [8]은 최종목표인 옵션 트리를 완성하는 것인데, 만기시의 행사 기대값을 위험중립확률과 무위험이자율로 직전 시점으로 할인하는 것입니다. 앞서의 반복문과 다른 점은 range(T -1, -1, -1) 부분인데, T - 1에서 출발해서 0까지(두번째 -1은 파이썬 문법상 -1번째 직전까지를 의미하므로, 결국 0까지를 의미합니다, 다른 부분에서 T + 1이 T까지를 의미하는 것과 같습니다.), 역순(세번째 -1)으로 반복하라는 의미입니다. 이 때 만약에 t + 1 시점의 옵션 가치를 할인한 현재가치보다 t 시점의 옵션 행사시 기대값이 더 크다면 행사시 기대값을 해당 time, node의 콜옵션 가치로 합니다. 산식은 max(미래 옵션가치의 현재가치, 옵션 행사시 기대값)으로 설정하였는데 결과물은 만기시 옵션 가치를 현재시점까지 단순히 할인한 것과 동일한 트리가 생성되었습니다. 우연히 그런것인지 원래 그런것인지는 모르겠습니다. 수학적으로 왜 그런지 증명할 수 있으면 좋겠지만 그 정도 능력이 안되어서 아쉽습니다. 여기까지 하면 모두 완료된 것이고, 본 사례의 콜옵션 가치는 37로 산정되었습니다. 아래 링크는 위 코드를 colab에서 작성한 링크입니다.

     

    https://colab.research.google.com/drive/1yap9HfNlKQ0qmMhO6mwM_w7lJyJJyTeB

     

    동일한 가정으로 블랙숄즈 모형(https://pythoncpa.tistory.com/4)으로 옵션가치를 구해 보았습니다. 결론적으로 이항모형의 단위기간을 매우 작은 기간으로 줄이면 블랙숄즈모형과 점점 값이 근접해가는 것을 확인할 수 있었습니다.

     

    결과물을 엑셀로 저장하려면 아래의 코드를 입력하고 실행하면 됩니다.

    In [9]를 실행하면 위에서 생성한 주가 트리, 옵션 행사시 기대값 트리, 옵션 트리의 결과값이 해당 코드가 있는 폴더에 CRR_option_pricing.xlsx 이라는 이름의 엑셀파일에 각 S_tree, C_K_tree, C_tree 시트에 생성됩니다.

     

    엑셀 결과물을 역으로 검증해 보았는데 동일한 결과가 나옵니다. 다행입니다. 이 정도 모델을 만드는 것은 사실 엑셀이 훨씬 쉽습니다. 그런데 기간이 조금만 더 길어지면 예를 들어 10년인데 월별로 상승 또는 하락을 가정하면 120 * 120 / 2 이므로 7,200개 셀이 할인율과 위험중립확률이 고려된 식을 계산하여야 해서 실행 속도가 엄청 느려집니다. 금리 자체도 본 사례는 단순화를 위하여 단일의 금리를 적용하였는데 실제 업무시에는 기간별 선도금리를 추정하여 각 기간별 할인율을 달리 적용하게 됩니다. 어떠한 모델은 엑셀 1개 셀에 있는 값을 지분가치 부분과 채권가치 부분으로 구분하여 서로 다른 할인율로 할인해주기도 하는데, 그런 경우에는 엑셀로 모델링 하기가 어려운 것 같습니다. 이러저러한 이유로 이항분포 옵션 모델링 목적으로는 파이썬이 엑셀보다 우월한 것 같습니다.

     

    단순한 내용을 설명하는 데도 생각보다 시간이 많이 걸리는 것 같습니다. 그래도 기회가 될 때마다 포스팅을 해 보려고 합니다. 주제는 일단은 파이썬을 재무나 회계업무에 활용하는 방법이 될 것 같습니다.

     

    긴 글 읽어주셔서 감사합니다.

    댓글

Designed by Tistory.