본문 바로가기
금융공학

배리어옵션 -UOC 옵션 #2 함축적 FDM

by hustler78 2022. 8. 25.
728x90
반응형

이번 글은 지난 글

2022.08.24 - [금융공학] - 배리어옵션 -UOC 옵션 #1 수학공식(Closed form)

 

배리어옵션 -UOC 옵션 #1 수학공식(Closed form)

저번 글까지 파생상품의 대표주자 옵션의 프리미엄을 다양한 방법으로 구해 봤습니다. 참고로 옵션 가격 계산의 마지막 글은 2022.08.22 - [금융공학] - 옵션 #8. 옵션 프리미엄 구하기 실습: Binomial T

sine-qua-none.tistory.com

에 이어 UOC옵션의 가격을 구하는 다른 방법을 알아보겠습니다. 이 글의 주제는 유한차분법(Finite Difference Merhod, FDM)입니다. 

 

 

 

UOC옵션 복습

Up and Out Call option은 다음과 같은 구조를 가진 파생상품입니다.

 

콜옵션과 얼개가 비슷하지만, 어떤 배리어(barrier = 120)가 있어서 만기전까지 주가가 한번이라도 배리어를 상회하게 되면 옵션은 종료되고 소정의 Rebate를 챙겨주는 상품입니다. 한 번도 배리어를 치지 않으면 콜옵션의 페이오프를 따라가게 되죠. 

 

이 상품을 FDM으로 구현해 보고자 합니다. 가장 키포인트는

주가가 배리어를 상회하는 상황을 FDM 격자에서 어떻게 다루면 될까?

하는 점입니다. 먼저 글에서 다루었던 FDM 링크로 복습을 갈음하겠습니다. 

 

 

 

FDM 복습(함축적 방법)

FDM은 크게 명시적(explicit) 방법과 함축적(implicit)방법이 있습니다. 명시적 방법은 계산 알고리즘이 간단하고 빠른 장점이 있지만, 수렴성 문제로 인해 값이 제대로 나오지 않는 경우가 허다했죠. 반면 함축적 방법은 행렬 방정식을 푸는 등 다소 번거로운 작업이 있으나 수렴성 하나만큼은 확실히 보장되는 방법입니다. 따라서 이 글에서는 함축적 방법에 대해서만 다루도록 할게요.

 

함축적 FDM은

2022.08.01 - [수학의 재미/아름다운 이론] - FDM #7, Heat Equation의 풀이(3)

 

FDM #7, Heat Equation의 풀이(3)

이 글은 2022.08.01 - [수학의 재미/아름다운 이론] - FDM #6, Heat Equation의 풀이(2) FDM #6, Heat Equation의 풀이(2) 이 글은 2022.07.30 - [수학의 재미/아름다운 이론] - FDM #5, Heat Equation의 풀이(1)..

sine-qua-none.tistory.com

을 참고하면 되고, 이를 이용하여 콜옵션의 프리미엄을 구하는 예제는

2022.08.19 - [금융공학] - 옵션 #5. 옵션 프리미엄 구하기 실습: 함축적 FDM

 

옵션 #5. 옵션 프리미엄 구하기 실습: 함축적 FDM

지난 글 2022.08.19 - [금융공학] - 옵션 #4. 옵션 프리미엄 구하기 실습: 명시적 FDM 옵션 #4. 옵션 프리미엄 구하기 실습: 명시적 FDM 저번 글 2022.08.18 - [금융공학] - 옵션 #3. 옵션 프리미엄 구하기(FDM)..

sine-qua-none.tistory.com

을 보면 됩니다. 

 

 

배리어를 상회하면 옵션 기능이 아웃되고 Rebate를 주는 상황은 어떻게 구현하면 될까요? 다음의 세 그림을 보시죠.

 

$t_{n-1}$ 시점의 격자값들과 함축적 방법을 이용하여 $t_n$ 시점의 격자들을 계산한다.

점화식(행렬방정식)을 사용하는 모습

 

$t_n$ 시점의 모든 격자값을 얻어낸다.

$t_n$시점의 계산된 격자값들(빨간색 점)

 

배리어 위의 값들은 Rebate로 대체

 

 

함축적 방법에 의해 얻어졌던 빨간색 점의 값들 중 배리어를 상회하는 격자들은 강제적으로 Rebate 값으로 바꿔줍니다. 그러면 Up and Out의 효과를 얻을 수 있게 됩니다.

 

 

 

 

Python Code

 

UOC 를 FDM으로 계산하여 시점 축과 기초자산 축 그리고 격자 값들을 반환하는 함수와 그걸 이용하여 UOC값을 분석해보는 코드를 작성했습니다

 

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
import time

N = norm.cdf

def UOC_price_FDM_implicit(s0, strike, maturity, upBarrier, rfr, vol, div, rebate):
    Nsize, Jsize = 250, 1000
    # s variable setting
    s_min, s_max = 0, 5 * s0
    s_seq = np.linspace(s_min, s_max, Jsize + 1)
    h = s_seq[1] - s_seq[0]

    # time variable setting
    t_min, t_max = 0, maturity
    t_seq = np.linspace(t_min, t_max, Nsize + 1)
    k = t_seq[1] - t_seq[0]

    # solution grid u setting
    u = np.empty((Nsize + 1, Jsize + 1))

    # initial_condition
    u[0, :] = np.array([max(s - strike, 0) for s in s_seq])
    u[0, s_seq > upBarrier] = rebate
    # recursive formula
    for n in range(Nsize):
        # make tridiagonal matrix
        diag = np.array([1 / k + (vol * x / h) ** 2 + rfr for x in s_seq[1:Jsize]])
        under = np.array([(rfr - div) * x / 2 / h - 1 / 2 * (vol * x / h) ** 2 for x in s_seq[1:Jsize]])
        over = np.array([-(rfr - div) * x / 2 / h - 1 / 2 * (vol * x / h) ** 2 for x in s_seq[1:Jsize]])

        diag[0] = 2 * under[0] + diag[0]
        over[0] = - under[0] + over[0]

        diag[-1] = diag[-1] + 2 * over[-1]
        under[-1] = under[-1] - over[-1]

        # solve tridiagonal matrix
        known = u[n, 1: Jsize] / k
        unknown = thomas(under, diag, over, known)
        u[n + 1, 1:Jsize] = unknown
        u[n + 1, s_seq >= upBarrier] = rebate
        # set boundary condition
        u[n + 1, 0] = 2 * u[n + 1, 1] - u[n + 1, 2]
        u[n + 1, -1] = 2 * u[n + 1, -2] - u[n + 1, -3]

    return u, s_seq, t_seq


def thomas(a, b, c, d):
    """ A is the tridiagnonal coefficient matrix and d is the RHS matrix"""
    """
    a is lower diagonal a2,a3,..,a_N, meaning
    b is diagonal b1,b2,b3,..,b_N meaning
    c is upper diagonal c1,c2,c3,.. c_{N-1} meaning
    """
    N = len(a)
    cp = np.zeros(N, dtype='float64')  # store tranformed c or c'
    dp = np.zeros(N, dtype='float64')  # store transformed d or d'
    X = np.zeros(N, dtype='float64')  # store unknown coefficients

    # Perform Forward Sweep
    # Equation 1 indexed as 0 in python
    cp[0] = c[0] / b[0]
    dp[0] = d[0] / b[0]
    # Equation 2, ..., N (indexed 1 - N-1 in Python)
    for i in np.arange(1, (N), 1):
        dnum = b[i] - a[i] * cp[i - 1]
        cp[i] = c[i] / dnum
        dp[i] = (d[i] - a[i] * dp[i - 1]) / dnum

    # Perform Back Substitution
    X[(N - 1)] = dp[N - 1]  # Obtain last xn

    for i in np.arange((N - 2), -1, -1):  # use x[i+1] to obtain x[i]
        X[i] = (dp[i]) - (cp[i]) * (X[i + 1])

    return (X)

def calculate_UOC():
    s0 = 100
    strike = 100
    maturity = 1
    upBarrier = 120
    rfr = 0.02
    vol = 0.2
    div = 0.01
    rebate = 3

    sSeries = np.linspace(s0 / 2, upBarrier, 101)

    uoc_value_fdm, sGrid, tGrid = UOC_price_FDM_implicit(s0, strike, maturity, upBarrier, rfr, vol, div, rebate)

    result_FDM = np.interp(sSeries, sGrid, uoc_value_fdm[-1, :])
    # upBarrier = upBarrier*np.exp(0.5826*vol*np.sqrt(maturity/250))
    result_closed_fom = np.array(
        [UOcall_ClosedForm(spot, strike, maturity, upBarrier, rfr, vol, div, rebate) for spot in sSeries])

    plt.plot(sSeries, result_FDM, label='FDM value')
    plt.plot(sSeries, result_closed_fom, label='Closed Form Value')
    plt.legend()
    plt.show()

    print('UOC_value_FDM: {}'.format(np.interp(s0, sGrid, uoc_value_fdm[-1, :])))
    print('UOC_value_ClosedForm: {}'.format(UOcall_ClosedForm(s0, strike, maturity, upBarrier, rfr, vol, div, rebate)))


if __name__ == '__main__':
    calculate_UOC()

 

간략 분석을 해보도록 하겠습니다. 오리지날 콜옵션 할 때(여기)와 유사한 부분들은 생략토록 하겠습니다

 

def UOC_price_FDM_implicit(s0, strike, maturity, upBarrier, rfr, vol, div, rebate):
    # s0 : 현재가
    # strike : 행사가
    # maturity: 만기
    # upBarrier : 배리어
    # rfr, vol, div : 각각 무위험이자율, 기초자산 변동성, 기초자산 연속배당률
    # rebate : 옵션 out시 지급되는 쿠폰

    return u, s_seq, t_seq	# 격자값과 기초자산 배열, 시점 배열을 output으로 함

 


    # initial_condition
    u[0, :] = np.array([max(s - strike, 0) for s in s_seq]) 	# 콜옵션 페이오프를 우선 대입
    u[0, s_seq > upBarrier] = rebate	# 주가가 배리어이상인 격자의 값을 rebate로 치환 (중요!!)

 


        # solve tridiagonal matrix
        known = u[n, 1: Jsize] / k
        unknown = thomas(under, diag, over, known)	#Thomas algorithm으로 함축적 방법론을 해결
        u[n + 1, 1:Jsize] = unknown		# 행렬방정식에서 얻어진 해를 n+1 시점에 대입
        u[n + 1, s_seq >= upBarrier] = rebate	# 다시 Barrier 상회하는 격자의 값은 rebate로 치환(중요!!)

 


def calculate_UOC():
    s0 = 100
    strike = 100
    maturity = 1
    upBarrier = 120
    rfr = 0.02
    vol = 0.2
    div = 0.01
    rebate = 3
    # 파라미터 세팅은 위와 같이 함

    sSeries = np.linspace(s0 / 2, upBarrier, 101)	# 현재가의 절반부터 barrier까지 주가를 100등분하여 주가 벡터 만듬

    # implicit FDM으로 가격 격자 구함
    uoc_value_fdm, sGrid, tGrid = UOC_price_FDM_implicit(s0, strike, maturity, upBarrier, rfr, vol, div, rebate)
	# sGrid : 격자생성시 해당 기초자산 주가
    # tGrid : 격자생성시 시점 격자
    # uoc_value_fdm : 격자의 값    
    
    result_FDM = np.interp(sSeries, sGrid, uoc_value_fdm[-1, :])	#아래 따로 설명
    # upBarrier = upBarrier*np.exp(0.5826*vol*np.sqrt(maturity/250))
    result_closed_fom = np.array(
        [UOcall_ClosedForm(spot, strike, maturity, upBarrier, rfr, vol, div, rebate) for spot in sSeries])
	# plotting을 위해 만든 sSeries의 원소들 각각에 대해 closed form value를 구함
    
    
    plt.plot(sSeries, result_FDM, label='FDM value')	#FDM 결과
    plt.plot(sSeries, result_closed_fom, label='Closed Form Value')	#closed form 결과
    plt.legend()
    plt.show()

    print('UOC_value_FDM: {}'.format(np.interp(s0, sGrid, uoc_value_fdm[-1, :])))	#아래 설명
    print('UOC_value_ClosedForm: {}'.format(UOcall_ClosedForm(s0, strike, maturity, upBarrier, rfr, vol, div, rebate)))

○  UOC_price_FDM_implicit에서 반환받은 sGrid와,  그래프를 그리기 위한 sSeries 가 값이 다릅니다. 즉, sSeries의 원소들이 sGrid 안에 포함되어 있지 않다는 뜻이겠죠. 따라서 선형보간법(linear interpolation)을 써서 해결합니다.

 

선형보간(linear interpolation) 기법이란?

그림과 같이, 어떤 함수가 $s_1$일 때 값이 $u_1$, $s_2$일 때 값이 $u_2$인 상황에서 $s$일 때의 값을 유추하는 문제입니다. $s_1$과 $s_2$사이에는 함숫값이 주어져 있지 않으므로 그 값을 유추할 수밖에 없는데요. 유추의 방법을 아래 그림처럼 선형으로 이어서 하는 것입니다.

점 $s$에서의 함숫값은 어떻게 될까요? 우선, $u_1$과 $u_2$를 잇는 직선의 방정식은

$$ y= \frac{u_2-u_1}{s_2}{s_1} (x-s_1)+u_1$$

이므로 여기에 $x=s$를 대입하여 보간된 값

$$ \frac{u_2-u_1}{s_2}{s_1} (s-s_1)+u_1$$

를 얻는 것이죠. 이 값을 파이썬에서는

numpy.interp(s, [s1,s2], [u1,u2])

라는 형식으로 구하게 되는 것입니다. 만일 s가 배열이면 s 각각의 원소에 대해 보간된 값을 배열로 반환합니다. 따라서 다음의 해석이 가능하죠.

 

    result_FDM = np.interp(sSeries, sGrid, uoc_value_fdm[-1, :])	
    
    # x좌표 배열이 sGrid 이고, y좌표 배열이 uoc_value_fdm[-1,:] 일 때, 
    # sSeries 각각의 원소에 대해 보간값을 구해 반환하라
    # uoc_value_fdm[-1,:] 은 시점 index가 마지막일 때의 uoc 값들, 즉 현재시점에서의 uoc 값들로 이루어진 배열
    
    np.interp(s0, sGrid, uoc_value_fdm[-1, :])
    # s0에 대한 보간값을 반환하라.

 

 

코딩 결과

그럼 결과를 살펴볼까요?

UOC_value_FDM: 2.223138681295252
UOC_value_ClosedForm: 2.1397093466460846

FDM 으로 구한 값과 수식에 의한 값이 유사합니다. FDM 방법론이 잘 구현된 것 같습니다. 

 

그래프를 살펴보겠습니다.

 

조금 차이가 나네요. 

 

이 차이는 FDM의 이산화 과정에 있습니다. FDM을 풀 때, 시점이 이산화 됐었죠. 기초자산 주가의 관찰을 유한개의 시점에 대해서만 관찰을 합니다. 실제 주식 시장으로 따지자면 예컨대 15:30에 결정되는 종가가 배리어를 넘냐 안 넘냐만 따지는 것입니다. 

반면 수식은 GBM 주가모델을 가정하고 Feyman-Kac formula를 써서 구하게 되죠. GBM 모델이 연속 확률 과정을 가정하므로 이렇게 푸는 방법은 실시간 주가 움직임을 모두 관찰하여 반영된 값을 얻는 것입니다.

 

여러 학자의 연구에 따르면, 이 간극을 없앨 좋은 조정방법이 있습니다. 다음 글에서 다뤄보도록 하겠습니다.

 

 

 

 

 

728x90
반응형

댓글