본문 바로가기
금융공학

배리어옵션 - UOC 옵션 #4 MonteCarlo Simulation

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

 

이번 글 역시 UOC(Up and Out Call) 옵션의 가격을 구하는 방법에 대해 알아봅니다. 여태까지 

○ Closed Form으로 구하는 방법

○ Implicit FDM으로 구하는 방법

을 알아보았습니다. 저번 글은

2022.08.25 - [금융공학] - 배리어옵션 -UOC 옵션 #2 함축적 FDM

 

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

이번 글은 지난 글 2022.08.24 - [금융공학] - 배리어옵션 -UOC 옵션 #1 수학공식(Closed form) 배리어옵션 -UOC 옵션 #1 수학공식(Closed form) 저번 글까지 파생상품의 대표주자 옵션의 프리미엄을 다양한 방

sine-qua-none.tistory.com

을 참고하시면 됩니다. 오늘 이 글에서는 파생상품 가격 결정의 끝판왕 바로 MonteCarlo Simulation입니다. 짧게나마 복습을 해 보도록 하겠습니다.

 

 

MonteCarlo Simulation

일반적인 Call option의 계산을 MonteCarlo 시뮬레이션을 사용하는 예제각

2022.08.21 - [금융공학] - 옵션 #6. 옵션 프리미엄 구하기 실습: MonteCarlo Simulation

 

옵션 #6. 옵션 프리미엄 구하기 실습: MonteCarlo Simulation

이번 글은 2022.08.19 - [금융공학] - 옵션 #5. 옵션 프리미엄 구하기 실습: 함축적 FDM 옵션 #5. 옵션 프리미엄 구하기 실습: 함축적 FDM 지난 글 2022.08.19 - [금융공학] - 옵션 #4. 옵션 프리미엄 구하기 실

sine-qua-none.tistory.com

입니다. 

주가 프로세스를 GBM모형이라 가정합니다. 즉, 변동성 $\sigma$, 연속 배당률 $q$인 기초자산 $S_t$의 dynamics가

$$dS_t/S_t = (r-q)dt + \sigma dW_t\tag{1}$$ 만족한다고 할 때, UOC 옵션의 시점 $t$, 기초자산 $S_t$에서의 가치 $f(t,S_t)$는

$$ f(0,S_0) = \exp^{-rT} \mathbb{E}(f(T,S_T))$$

를 만족합니다.

 

그런데 일반적인 Call option과 UOC 옵션은 결정적인 차이 몇 개가 있습니다.

 

 

UOC의 만기는 일정하지 않다?

UOC 상품의 가장 큰 특징이 바로 "Up and Out"이죠. 기초자산이 어느 수준(배리어) 이상을 넘어가버리면 그 순간 해당 옵션은 종료되고 그 순간에 바로 리베이트(Rebate) 주고 끝내는 페이오프가 결정됩니다. 따라서 만기 역시 확률변수이죠

 

 

주가 생성 빈도

예전 글에서 다루길, 식 (1)을 이용하여 주가를 생성하는 방법은 두 가지 정도가 있다고 했습니다.

 

만기 시점 종가  생성

2022.06.19 - [금융공학] - GBM 주가패스 만들기 #1: 만기시점 주가만

 

GBM 주가패스 만들기 #1: 만기시점 주가만

이 글은 2022.06.07 - [금융공학] - 주식의 수학적 모델 #3 : GBM모델 주식의 수학적 모델 #3 : GBM모델 이 글은 2022.05.27 - [금융공학] - 주식의 수학적 모델 #1 주식의 수학적 모델 #1 이 글은 2022.05.25 - [..

sine-qua-none.tistory.com

을 복습해 보시기 바랍니다.

이 방법은 기본적인 콜옵션처럼 만기 때의 종가만으로 페이오프가 결정될 때 사용합니다.

 

 

에브리데이(관찰 포인트 시점)마다 주가 생성

관련 글은 

2022.06.19 - [금융공학] - GBM 주가패스 만들기 #2: EveryDay 주가까지!

 

GBM 주가패스 만들기 #2: EveryDay 주가까지!

이 글은 2022.06.19 - [금융공학] - GBM 주가패스 만들기 #1: 만기시점 주가만 GBM 주가패스 만들기 #1: 만기시점 주가만 이 글은 2022.06.07 - [금융공학] - 주식의 수학적 모델 #3 : GBM모델 주식의 수학적 모

sine-qua-none.tistory.com

에서 다룬 적이 있습니다.  매일매일의 종가를 생성할 필요가 있다는 뜻은 무언가 계속 조건을 만족하는지 관찰이 필요한 경우 겠죠? UOC 상품 같은 경우가 딱 그렇습니다. 주가가 만기시점까지 살아 움직이는 동안  배리어를 넘는지 아닌지를 판단해야 하기 때문이죠. 그래서 좀 번거롭더라도 매일매일의 주가를 생성해야 합니다. 다시 복습을 해보자면, 관찰시점을

$$ 0=t_0 < t_1 <\cdots <t_{N}=T$$

로 쪼갰을 때, $t_i$시점과 $t_{i+1}$시점의 주가 관계식은

$$ S_{t_{i+1}} = S_{t_i} \exp \left( (r-q-\textstyle{\frac12}\sigma^2)\Delta t + \sigma \sqrt{\Delta t} z\right) ,$$

$z$는 표준 정규분포를 따르는 랜덤 변수입니다. 물론 $i=0$일 때 $S_{t_0}$는 기초자산의 현재가이겠죠.

 

 

MonteCarlo Simulation의 Python Code

 

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

N = norm.cdf

def UOcall_MC(s0, strike, maturity, upBarrier, rfr, vol, div, rebate):
    nSimulation = 5000
    nsteps = int(maturity * 365)
    dt = 1 / 365

    drift = (rfr - div - 0.5 * vol ** 2) * dt
    diffusion = vol * np.sqrt(dt)
    df_mat = np.exp(-rfr * maturity)
    koutCnt = 0
    dfPayoff = []
    for i in range(nSimulation):
        s_process = np.zeros(nsteps + 1)
        s_process[0] = s0
        early_terminated = False

        for j in range(1, nsteps + 1):
            z = np.random.normal()
            s_process[j] = s_process[j - 1] * np.exp(drift + diffusion * z)
            if s_process[j] >= upBarrier:
                early_terminated = True
                koutCnt += 1
                df = np.exp(-rfr * j * dt)
                dfPayoff.append(df * rebate)
                break

        if early_terminated == False:
            payoff = np.max([s_process[-1] - strike, 0])
            dfPayoff.append(payoff * df_mat)
    result = np.mean(dfPayoff), koutCnt / nSimulation
    return result

def UOcall_ClosedForm(s0, strike, maturity, upBarrier, rfr, vol, div, rebate):

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

    start_time = time.time()
    uoc_value_mc, hitting_barrier_prob = UOcall_MC(s0, strike, maturity, upBarrier, rfr, vol, div, rebate)
    elapsed_time = time.time() - start_time
    print('UOV_value_MC : {}'.format(uoc_value_mc))
    print('Hitting Barrier Probability: {}'.format(hitting_barrier_prob))
    print('elapsed time : {}'.format(elapsed_time))
    print('UOC_value_ClosedForm: {}'.format(UOcall_ClosedForm(s0, strike, maturity, upBarrier, rfr, vol, div, rebate)))

if __name__ == '__main__':
    calculate_UOC()

 

이 글에서 가장 관심 있는 부분은 MoncteCarlo Simulation 코드입니다. 그 부분 위주로 살펴보도록 하죠.

 

 

def UOcall_MC(s0, strike, maturity, upBarrier, rfr, vol, div, rebate):
    nSimulation = 5000	# MC시뮬레이션 횟수
    nsteps = int(maturity * 365)	#1 년을 365번 관찰한다. 즉 매일매일
    dt = 1 / 365	#관찰시점의 간격은 매일, 즉 1/365

    drift = (rfr - div - 0.5 * vol ** 2) * dt	# 하루 간격의 drift항
    diffusion = vol * np.sqrt(dt)	# 하루 간격의 diffusion항
    df_mat = np.exp(-rfr * maturity)	# 만기 시점의 할인율
    koutCnt = 0		# 시뮬레이션 횟수 중, 주가가 배리어를 터치하여 up and OUT이 되는 횟수
    dfPayoff = []	# 할인된 페이오프 저장
    
    for i in range(nSimulation):
        s_process = np.zeros(nsteps + 1)	# 기초자산 움직임을 담을 배열 생성
        s_process[0] = s0			# 배열의 첫번째 원소는 현재가격
        early_terminated = False	# 옵션이 out됐는지를 알려줄 bool flag

        for j in range(1, nsteps + 1):	# 매일매일단위로 시점을 증가시키며
            z = np.random.normal()		# 표준정규분포 난수 발생
            s_process[j] = s_process[j - 1] * np.exp(drift + diffusion * z)	# 주가 패스 생성
            if s_process[j] >= upBarrier:	# 만일 j번째 시점의 주가가 배리어보다 커지면
                early_terminated = True		# 조기 종료 시그널을 True로 바꾸고
                koutCnt += 1				# Out 된 경우 횟수 1증가 시킴
                df = np.exp(-rfr * j * dt)	# j시점은 j dt 이므로 이걸로 현재까지 할인
                dfPayoff.append(df * rebate)	# out 되었으므로 페이오프는 rebate
                break	# 주가 생성을 stop 함

        if early_terminated == False:	# 만일 조기종료가 안됐으면, 즉 배리어 터치가 없었으면
            payoff = np.max([s_process[-1] - strike, 0])	# payoff는 기존 콜옵션처럼
            dfPayoff.append(payoff * df_mat)	# 이것 역시 할인페이오프 배열에 저장
    result = np.mean(dfPayoff), koutCnt / nSimulation
    		# 할인된 페이오프의 평균과,  up and Out 확률을 반환
    return result

○ MC는 계산 시간도 오래 걸릴뿐더러, Simulation 시 사용한 난수들 하에서 일관되게 발생한 다른 결과들을 계산하기 위해 가격뿐 아니라 관심 있는 숫자들을 같이 구하곤 합니다. 저는 up 하여 배리어를 터치 후 아웃되어 만기 시점 이전에 끝나는 상황의 빈도가 관심이 있어서 배리어를 터치할 확률을 

(배리어를 터치한 횟수) / (전체 시뮬레이션 횟수)

의 식으로 구했습니다.

 

○ 시뮬레이션 한번 한번마다, 즉 주가 패스 하나하나 마다 할인 팩터를 따로 구한 이유는 상황별로 만기가 다 다르기 때문입니다. 배리어를 터치 안 한 경우라면 만기가 $T$이겠지만,  배리어를 터치하면 그날이 바로 실제 만기가 되죠. 따라서 페이오프에 할인율을 먼저 곱해서 "할인된 페이오프" 개념으로 관리합니다.

 


    start_time = time.time()	# 현재 시각을 start_time 변수에 저장
    # MC 함수를 이용하여 UOC 가격계산
    uoc_value_mc, hitting_barrier_prob = UOcall_MC(s0, strike, maturity, upBarrier, rfr, vol, div, rebate)
    # 현재시각 time.time()에서 아까 저장해놨던 시각을 빼서 계산시간을 산출함
    elapsed_time = time.time() - start_time

○ MC는 아주 지저분하고 복잡한 페이오프라 할지라고 주가 패스만 구해 페이오프만 계산하고 이를 현재가치로 할인하여 평균하면 상품 가격을 구할 수 있는 아주 범용성 높은 방법론입니다. 시뮬레이션 횟수가 커질수록 정확도가 증가하는 성질 때문에 계산 횟수를 증가시켜 정교한 값을 얻고자 하죠. 하지만 시뮬레이션 횟수를 마냥 증가시킬 수도 없는 것이, 계산 속도 때문입니다. 따라서 계산의 정확성에 중점을 둘지, 아니면 계산 시간의 효율성에 중점을 둘 지에 따라 시뮬레이션 횟수가 정해지게 됩니다.

 

○ 이 예제의 시뮬레이션 횟수 5,000 번은 결코 큰 숫자가 아닙니다. 설명을 위해 Closed form 값과 크게 차이 나지 않으면서 계산도 비교적 빨리 되는 값을 정한 것입니다.

 

○위 예제에서는 python의 time.time() 함수를 이용해 시간 측정을 하였는데요. MC 방법뿐 아니라 결과를 도출하는 데 걸리는 시간을 측정하는 것은 의사 결정이나 효율적인 코딩에 대한 필요성을 느끼는 등에 꼭 필요한 작업인 것 같습니다.

 

그럼 이제 결과를 보겠습니다.

 

UOV_value_MC : 2.2018820377204213
Hitting Barrier Probability: 0.3322
elapsed time : 17.213398694992065
UOC_value_ClosedForm: 2.1397093466460846

 MC로 구한 가격과 Closed form 가격이 근사한 것을 볼 수 있습니다.  반면 계산시간은 오래 걸리는 편이죠. Closed form으로는 거의 실시간으로 계산됩니다.

배리어를 치는 확률은 1/3 정도 되네요. 배리어를 120, 변동성을 20으로 세팅하여 좀 더 높은 확률로 칠 줄 알았는데 아니네요.

 

 

다음 글에서는 UOC 옵션을 구하는 또 다른 방법인 Binomial Tree  방법을 소개하겠습니다.

 

 

 

 

 

 

728x90
반응형

댓글