본문 바로가기
금융공학

그릭을 수치 해석적인 방법으로 해결하기

by hustler78 2023. 6. 23.
728x90
반응형

 

이번 글은 

2023.04.27 - [금융공학] - 델타, 감마, 스피드! 콜옵션의 가격 변화를 쫓아가보자 #1

 

델타, 감마, 스피드! 콜옵션의 가격 변화를 쫓아가보자 #1

이 글은 테일러 전개 : 파생상품 헤지의 준비 이론 테일러 전개 : 파생상품 헤지의 준비 이론 이 글은 예전글인 2022.05.19 - [수학의 재미/아름다운 이론] - 테일러 전개 #1 테일러 전개 #1 무한번 미

sine-qua-none.tistory.com

와 관련이 있습니다. 

 

파생상품의 민감도(Greeks) 들은 파생상품의 가격 공식을 미분하여 얻어집니다.  예컨대, 시점 $t$, 기초자산 $S$에서의 가격이 $f(t, S)$로 주어지는 파생상품의 델타, 감마, 스피드는 각각

 

$$ \Delta = \frac{\partial f}{\partial S}~,~ \Gamma = \frac{\partial^2 f}{\partial S^2}~,~ {\rm{Speed}} = \frac{\partial^3 f}{\partial S^3} \tag{1}$$
으로 정의되죠. 하지만, 이렇게 구하는 방법에는 몇 가지 애로사항이 있습니다.

 

 

○ 파생상품의 가격 $f(t,S)$가 공식으로 주어지지 않는 다면 어쩔 것이냐?

우리가 앞서 알아본 콜옵션/풋옵션은 상품 가격이 공식으로 주어집니다. 이른바 Black Scholes Formula 이죠. 그런데 대부분의 상품에 가격 공식이 있는 것이 아닙니다. 예컨대, ELS ([금융공학] - 1star 스텝다운 ELS의 계산(시뮬레이션) 참고)의 경우만 하더라도 공식이 없어서, MC나 FDM 또는 이항트리(binomial tree)를 이용하여 구하는 실정입니다. 공식이 주어지지 않으면 식(1)을 쓸 수 조차 없겠죠.

 

○ 미분은 틀리기 마련이다?!

식 (1)을 쓰기 위해서는 정확한 미분 실력이 있어야 합니다. 공식 자체가 복잡해지면, 한 번 미분하는 것(델타)도 어렵습니다. 하물며, 감마, 스피드는 오죽할까요? 꾸역꾸역 미분, 2차 도함수, 3차 도함수들을 구해보더라도 이것이 맞는지 검증이 필요합니다.

 

 

이번 글에서는 다른 방법으로 그릭들을 구하는 방법을 소개합니다.

 

 

 

수치해석적 미분

 시점 $t$, 기초자산 $S$에서 어떤 파생상품의 가격을 $f(t,S)$ 라 합시다. 이 함수 $f$는 공식이 없어도 되고, 미분 가능한지 안 한 지 따질 수도 없다고 합시다(사실은 2번까지는 미분가능하다는 것이 금융공학의 전제조건이긴 합니다. 암묵적으로 $f$는 2번까지는 미분 가능합니다.

이 때, 수치해석적인 방법으로 1차, 2차, 3차 도함수를 구할 수 있습니다(각각 델타, 감마, 스피드에 해당되겠죠.)

 

이번 설명에서 관심 있는 변수는 $S$이므로 $t$는 고정되어 있다고 생각하여 $f(t, S) := f(S)$로 한 변수인 것처럼 생각하겠습니다.

 

1차 도함수

$$\frac{\partial f}{\partial S} \approx \frac{f(S+h/2)-f(S-h/2)}{h}\tag{2}$$

입니다. 위 식에서 $h$는 (아주) 작은 양수입니다. 이렇게 정의하는 방법을 중앙차분법(central difference)라 합니다.  다른 방법으로,


$$\frac{\partial f}{\partial S} \approx \frac{f(S+h)-f(S)}{h}\tag{3}$$

$$\frac{\partial f}{\partial S} \approx \frac{f(S-h)-f(S)}{-h}\tag{4}$$

 

로 정의하는 경우도 많습니다. 식(3)을 전방 차분 (forward difference), 식(4)을 후방 차분(backward difference)라고 합니다. 무슨 근사치를 써도 상관은 없으나, 보통 식(2)의 중앙 차분을 많이 사용합니다.

 

식(2), (3), (4) 공히 $h$가 무한히 작아지게 된다면, 실제로 1차 도함수로 가죠. 고등학교 때 배웠던 미분의 정의입니다. 

 

 

2차 도함수

2차 도함수는 어떻게 수치해석적 근사식을 만들 수 있을까요? 

$$
\begin{align}
f''(S) & \approx \frac { f'(S+h/2)-f'(S-h/2) }{h} \\
&\approx \frac{ \frac{f(S+h/2+h/2)-f(S+h/2-h/2)}{h} -\frac{f(S-h/2+h/2)-f(S-h/2-h/2}{h}}{h}\\
&\approx \frac{f(S+h)-2f(S)+f(S-h)}{h^2}\tag{5}
\end{align}
$$

첫 번째 줄은 식(2)을 적용한 결과입니다. 두 번째 줄은 $f'(S+h/2)$와 $f'(S-h/2)$에 다시 한번 식(2)를 적용한 결과입니다.

 

 

3차 도함수

3차 도함수의 근사식은 아래처럼 구합니다.

$$
\begin{align}
f'''(S) & \approx \frac { f''(S+h/2)-f''(S-h/2) }{h} \\
       &\approx \Bigg(\frac{f(S+h/2+h/2)-2f(S+h/2)+f(S+h/2-h/2)}{(h/2)^2} \\
        & ~~~~~~~~~~-\frac{f(S-h/2+h/2)-2f(S-h/2)+f(S-h/2-h/2}{(h/2)^2}\Bigg)\Big/ h\\
      &\approx \frac{f(x+h)-2f(x+h/2)+2f(x-h/2)-f(x-h)}{h^3/4}\tag{6}
\end{align}
$$

 

이제 식(2) , (5), (6)을 주목해 주시기 바랍니다. 이것들이 각각 델타, 감마, 스피드가 되니까요!

그럼 이렇게 수치해석적으로 구한 그릭들이  여기에 기재한 콜옵션의 공식 그릭과 비슷한지 아닌지 코딩을 통하여 알아보겠습니다.

 

 

Python Code:  Greeks들을 수치해석적으로 구하기

 

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

def CallOptionBS(S, K, T, r, q, sigma):
	# 전의 글들을 참고하기 바랍니다.



# -----------------------------------------------------
# ---- CallOptionBS_NumericalGreeks 함수 설명 ----------
# delta, gamma, speed를 수치해석적으로 구하는 함수
# call option value 는 Black Scholes Formula를 그대로 씀
# -----------------------------------------------------
def CallOptionBS_NumericalGreeks(S, K, T, r, q, sigma):
    Ncdf = norm.cdf
    npdf = norm.pdf

    ds = 0.01 * S   # 본문의 h로 표시한 것(h=dS), S의 크기에 1%를 h로 설정

    val = CallOptionBS(S, K, T, r, q, sigma)[0]
    val_up = CallOptionBS(S + ds / 2, K, T, r, q, sigma)[0]   # h/2 bumping up
    val_up2 = CallOptionBS(S + ds, K, T, r, q, sigma)[0]      # h   bumping up
    val_down = CallOptionBS(S - ds / 2, K, T, r, q, sigma)[0] # h/2 bumping down
    val_down2 = CallOptionBS(S - ds, K, T, r, q, sigma)[0]    # h   bumping down
    
    # 본문의 식(2), (5), (6)을 코딩한 결과
    delta = (val_up - val_down) / (ds)     
    gamma = (val_up2 - 2 * val + val_down2) / (ds ** 2)
    speed = (val_up2 - 2 * val_up + 2 * val_down - val_down2) / (ds ** 3 / 4)

    # 0. value
    # 1. delta
    # 2. gamma
    # 3. speed
    return val, delta, gamma, speed   # value, delta, gamma, speed 를 return


def analysis_call_option_numerical_greeks():
    s0 = 100
    strike = 100
    maturity = 1
    rfr = 0.03
    div = 0
    vol = 0.3

    spot_array = np.arange(50, 150 + 1, 1)  # 기초자산은 50~ 150까지 1간격으로 array 만듬
    delta_list = []
    gamma_list = []
    speed_list = []
    for s in spot_array:
        res_analytic = CallOptionBS(s, strike, maturity, rfr, div, vol)   # 공식으로 구한 greeks
        res_numerical = CallOptionBS_NumericalGreeks(s, strike, maturity, rfr, div, vol)
                                                                          # 수치해석적 greeks
        delta_list.append([res_analytic[1], res_numerical[1]])    # [공식 델타, 수치해석 델타] list
        gamma_list.append([res_analytic[2], res_numerical[2]])    # [공식 감마, 수치해석 감마] list
        speed_list.append([res_analytic[3], res_numerical[3]])    # [공식 speed, 수치해석 speed] list

    delta_array = np.array(delta_list)  #list의 array화 (밑에 slicing을 위하여)
    gamma_array = np.array(gamma_list)
    speed_array = np.array(speed_list)

    from matplotlib import gridspec
    fig = plt.figure(figsize=(10, 30))
    gs = gridspec.GridSpec(nrows=3, ncols=1, height_ratios=[1, 1, 1])
    ax = [fig.add_subplot(gs[0]), fig.add_subplot(gs[1]), fig.add_subplot(gs[2])]
    
    # 기초자산 가격에 따른 공식 델타와, 수치해석 델타를 그래프에 같이 표시
    ax[0].plot(spot_array, delta_array[:, 0], color='lightgray', linewidth=5, label='delta_closedform')
    ax[0].plot(spot_array, delta_array[:, 1], color='blue', linewidth=2, linestyle='--'
               , label='delta numerical')
    ax[0].legend()
    
    # 공식 델타와 수치해석 델타 array의 원소중 차이가 가장 큰 것을 출력
    print('Maximum difference between closed form delta and numerical delta : {:.10f}'.format(
        np.max(np.abs(delta_array[:, 1] - delta_array[:, 0]))))

    # 기초자산 가격에 따른 공식 감마와, 수치해석 감마를 그래프에 같이 표시
    ax[1].plot(spot_array, gamma_array[:, 0], color='lightgray', linewidth=5, label='gamma_closedform')
    ax[1].plot(spot_array, gamma_array[:, 1], color='tomato', linewidth=2, linestyle='--', label='gamma numerical')
    ax[1].legend()
    
    # 공식 감마와 수치해석 감마 array의 원소중 차이가 가장 큰 것을 출력
    print('Maximum difference between closed form gamma and numerical gamma : {:.10f}'.format(
        np.max(np.abs(gamma_array[:, 1] - gamma_array[:, 0]))))

    # 기초자산 가격에 따른 공식 스피드와, 수치해석 스피드를 그래프에 같이 표시
    ax[2].plot(spot_array, speed_array[:, 0], color='lightgray', linewidth=5, label='speed_closedform')
    ax[2].plot(spot_array, speed_array[:, 1], color='green', linewidth=2, linestyle='--', label='speed numerical')
    ax[2].legend()
    
    # 공식 스피드와 수치해석 스피드 array의 원소중 차이가 가장 큰 것을 출력
    print('Maximum difference between closed form speed and numerical speed : {:.10f}'.format(
        np.max(np.abs(speed_array[:, 1] - speed_array[:, 0]))))

    plt.show()

if __name__ == '__main__':
    analysis_call_option_numerical_greeks()

 

 

 

결과를 보겠습니다.

Maximum difference between closed form delta and numerical delta : 0.0000148013
Maximum difference between closed form gamma and numerical gamma : 0.0000014086
Maximum difference between closed form speed and numerical speed : 0.0000000613

기초자산 가격이 50에서 150까지 각각 델타, 감마, 스피드를 수치해석적으로 구하여, 이를 공식과 비교하여 차이가 가장 큰 값을 계산해 본 결과입니다.  공식과 수치해석 결과가 거의 차이가 나지 않는다는 것을 알 수 있습니다.  이는 아래 그래프를 봐도 알 수 있습니다. 델타, 감마, 스피드 각각 공식과 수치해석적 방법 두 그래프가 완전히 겹치는 모습이죠.

 

 

이제 이 분석을 통하여 우리는 두 가지 정보를 얻을 수 있습니다.

 

○ 수치해석적 방법과 공식(미분)이 거의 차이가 없으니 우리가 미분을 실수 없이 잘했었구나. 그거 그대로 믿고 
   사용해도 되겠다!

○ 굳이 델타, 감마 등을 미분해서 구할 필요가 있나? 계산 실수할 수도 있는데.. 그냥 수치 해석적 방법을 써야겠다!

 

파생상품의 가격 공식이 공식으로만 끝나는 경우는 없습니다. 이 공식을 프로그래밍하여 파생상품의 움직임을 분석하고 운용하는 게 목적이죠. 어차피 프로그래밍을 할 것이라면,  이렇게 수치해석적인 방법론이, 연습장과 연필로 푼 미분 공식값과 일치하는지 따져보는 일은 충분히 가치 있는 일이 될 것입니다.

또한, 공식이 없는 파생상품들을 위해 그릭들을 수치해석적으로 구하는 방법을 알아두면 큰 도움이 되겠죠.

 

728x90
반응형

댓글