본문 바로가기
금융공학

1star 스텝다운 ELS의 계산(Binomial Tree) #2

by hustler78 2022. 12. 22.
728x90
반응형

이 글은

2022.12.21 - [금융공학] - 1star 스텝다운 ELS의 계산(Binomial Tree)

 

1star 스텝다운 ELS의 계산(Binomial Tree)

이번 글은 2022.12.20 - [금융공학] - 1star 스텝다운 ELS의 계산(FDM) #2 1star 스텝다운 ELS의 계산(FDM) #2 이 글은 2022.12.20 - [분류 전체보기] - 1star 스텝다운 ELS의 계산(FDM) 1star 스텝다운 ELS의 계산(FDM) 이

sine-qua-none.tistory.com

에서 계속됩니다.

 

1star 스텝다운 ELS의 가격을 이항트리모형을 이용해 구해보겠습니다. 구하는 이론적 배경은 이전 글에서 설명했으니 참고하시기 바랍니다.

 

 

python code

 

def OneDimELS_BinomialTree(objUnderlying: Underlying, objMarket: Market, redemption_schedule, coupon,
                           full_dummy, barrier, ki_barrier, n_iteration):
    vol = objUnderlying._volatility
    div = objUnderlying._dividend
    rfr = objMarket._rfr
    current_spot = objUnderlying._spot
    refprice = objUnderlying._refprice
    dt = 1 / 250
    maturity = redemption_schedule[-1]

    nSchedule = len(redemption_schedule)
    # time variable setting
    t_min, t_max = 0, maturity
    nNode = t_max - t_min

    u = np.exp(vol * np.sqrt(dt))
    d = 1 / u
    a = np.exp((rfr - div) * dt)
    p = (a - d) / (u - d)
    valueNode = np.zeros((nNode + 1, nNode + 1))
    ki_valueNode = valueNode
    nth_chance = nSchedule - 2  # 마지막 조기상환 index

    start_time = time.time()
    for n in range(nNode + 1)[::-1]:
        for i in range(n + 1):
            spotS = current_spot * u ** i * d ** (n - i)
            if n == nNode:
                payoff = 1 + coupon[-1] if spotS / refprice >= barrier[
                    -1] else 1 + full_dummy if spotS / refprice >= ki_barrier else spotS / refprice
                ki_payoff = 1 + coupon[-1] if spotS / refprice >= barrier[-1] else spotS / refprice
                valueNode[n, i], ki_valueNode[n, i] = payoff, ki_payoff
            else:
                up_val, ki_up_val = valueNode[n + 1, i + 1], ki_valueNode[n + 1, i + 1]
                down_val, ki_down_val = valueNode[n + 1, i], ki_valueNode[n + 1, i]
                df = np.exp(-rfr * dt)
                valueNode[n, i] = (p * up_val + (1 - p) * down_val) * df
                ki_valueNode[n, i] = (p * ki_up_val + (1 - p) * ki_down_val) * df
                if n == redemption_schedule[nth_chance]:
                    if spotS / refprice >= barrier[nth_chance]:
                        valueNode[n, i] = 1 + coupon[nth_chance]
                        ki_valueNode[n, i] = 1 + coupon[nth_chance]

            if spotS / refprice < ki_barrier:
                valueNode[n, i] = ki_valueNode[n, i]
        if n == redemption_schedule[nth_chance]:
            nth_chance -= 1
    cal_time = round(time.time() - start_time, 3)

    return valueNode[0, 0], cal_time, valueNode

 

 

코드 설명은 다음과 같습니다.

def OneDimELS_BinomialTree(objUnderlying: Underlying, objMarket: Market, redemption_schedule, coupon,
                           full_dummy, barrier, ki_barrier, n_iteration):

# class Underlying 과 class Market은 예전 글들에서 설명

1star 스텝다운 ELS의 계산(FDM) #2 등의 글을 참고하시면, Underlying과 Market class의 정의를 볼 수 있습니다.

 

 


    nSchedule = len(redemption_schedule)    # 상환시점 횟수 (3y6m의 경우 6번)
    # time variable setting
    t_min, t_max = 0, maturity              # 현재시점 0, 만기시점: 3년(=750일)
    nNode = t_max - t_min                   # time node는 0, 1, ..., 750 : nNode+1 개로 할 예정

    u = np.exp(vol * np.sqrt(dt))           # 이항모형에서 상승률
    d = 1 / u                               # 하락률, ud=1
    a = np.exp((rfr - div) * dt)         
    p = (a - d) / (u - d)                   # 상승확률
    valueNode = np.zeros((nNode + 1, nNode + 1))  # Node: timeNode는 nNode+1개, 각 시점 노드당
                                                  # 주식노드도 같은 갯수
    ki_valueNode = valueNode                # 낙인 쳤을 상황의 가격을 뜻하는 노드
    nth_chance = nSchedule - 2              # the last 조기상환시점 index 뜻함 : 4 (=6-2)

 

 


    for n in range(nNode + 1)[::-1]:      # time step을 backward로 진행시키며
        for i in range(n + 1):            # 해당 time step에서 가능한 주가의 경우에 대해
            spotS = current_spot * u ** i * d ** (n - i)
                                          # 주가 생성 s= s0 u^i d^(n-i)
            if n == nNode:                # 만일 만기시점이면,
                payoff = 1 + coupon[-1] if spotS / refprice >= barrier[
                    -1] else 1 + full_dummy if spotS / refprice >= ki_barrier else spotS / refprice
                                          # 낙인 안쳤을때의 3갈래의 페이오프
                ki_payoff = 1 + coupon[-1] if spotS / refprice >= barrier[-1] else spotS / refprice
                                          # 낙인 쳤을 때의 두 갈래 페이오프
                valueNode[n, i], ki_valueNode[n, i] = payoff, ki_payoff
                                          # 위 두 페이오프를 각각 노낙인트리, 낙인트리에 대입
            else:
                up_val, ki_up_val = valueNode[n + 1, i + 1], ki_valueNode[n + 1, i + 1]
                                          # 재귀식을 사용하기 위해 n+1번째 time node 값 추출
                down_val, ki_down_val = valueNode[n + 1, i], ki_valueNode[n + 1, i]
                                          # 재귀식을 사용하기 위해 n+1번째 time node 값 추출
                df = np.exp(-rfr * dt)    # 할인팩터
                valueNode[n, i] = (p * up_val + (1 - p) * down_val) * df
                                          # recursive formula
                ki_valueNode[n, i] = (p * ki_up_val + (1 - p) * ki_down_val) * df
                                          # recursive formula 
                if n == redemption_schedule[nth_chance]:
                                          # 조기상환 시점에 닿으면
                    if spotS / refprice >= barrier[nth_chance]:
                        valueNode[n, i] = 1 + coupon[nth_chance]
                        ki_valueNode[n, i] = 1 + coupon[nth_chance]
                                          # 낙인/노낙인 트리 모두 배리어이상의 주가에 대해서 쿠폰+원금으로 치환

            if spotS / refprice < ki_barrier:
                valueNode[n, i] = ki_valueNode[n, i]
                                          # 각 time step별 낙인배리어 이하에 대해서는 
                                          # 낙인 트리의 값으로 update
        if n == redemption_schedule[nth_chance]:
            nth_chance -= 1               # 모든 주가에 대해 실행 후, 조기상환 단계를 전단계로 갱신

 

 


    return valueNode[0, 0], cal_time, valueNode   # timestep =0, 기초자산 step =0 이 현재가이고 이것을 반환
                                                  # 계산 소요시간 및 트리값 모두 반환

 

 

 

결과 테스트

 

대상상품은 이번에도 아래의 구조로 하겠습니다.

 

 

def els_price_various_test():
    underlying_spot = Underlying(refprice=100, spotvalue=100, volatility=0.3, dividend=0)
    interest_rate = Market(0.03)
    redemption_schedule = np.array([1, 2, 3, 4, 5, 6]) * 125
    coupon = np.array([1, 2, 3, 4, 5, 6]) * 0.05
    full_dummy = coupon[-1]
    barrier = np.array([0.9, 0.9, 0.85, 0.85, 0.8, 0.8])
    ki_barrier = 0.6
    n_iteration = 10000

    els_price_bb, cal_time_bb, prob_bb = OneDimELS_MC_BB(underlying_spot, interest_rate, redemption_schedule, coupon,
                                                         full_dummy, barrier, ki_barrier, n_iteration)

    els_fdm, uu, cal_time_fdm, s_seq, t_seq = OneDimELS_FDM(underlying_spot, interest_rate, redemption_schedule, coupon,
                                                            full_dummy, barrier, ki_barrier, n_iteration)

    els_bt, cal_time_bt, vnode = OneDimELS_BinomialTree(underlying_spot, interest_rate, redemption_schedule, coupon,
                                                        full_dummy, barrier, ki_barrier, n_iteration)

    np.set_printoptions(suppress=True)

    print('ELS value(MC with BB): {:.3f}'.format(els_price_bb))
    print('elapsed time         : {}'.format(cal_time_bb))
    print('\n')
    print('ELS value(FDM)       : {:.3f}'.format(els_fdm))
    print('elapsed time         : {}'.format(cal_time_fdm))
    print('\n')
    print('ELS value(Binom.Tree) : {:.3f}'.format(els_bt))
    print('elapsed time         : {}'.format(cal_time_bt))

 

위 code는 Montecarlo Simulation, FDM , Binomial Tree의 값을 비교한 것입니다. 설명을 생략해도 될 정도로 단순한 코드입니다. 결과를 보시면,

 

ELS value(MC with BB): 0.988
elapsed time         : 1.979


ELS value(FDM)       : 0.984
elapsed time         : 13.492


ELS value(Binom.Tree) : 0.981
elapsed time         : 1.481

 

조금의 차이는 보이지만, 비슷하게 나옵니다. 

 

이제 여러 기초자산의 값에 대해서 얼마나 비슷한지 MC, FDM 방법과 비교해 보겠습니다.

 

 

def comparisonTest():
    underlying_spot = Underlying(refprice=100, spotvalue=100, volatility=0.3, dividend=0)
    interest_rate = Market(0.03)
    redemption_schedule = np.array([1, 2, 3, 4, 5, 6]) * 125
    coupon = np.array([1, 2, 3, 4, 5, 6]) * 0.05
    full_dummy = coupon[-1]
    barrier = np.array([0.9, 0.9, 0.85, 0.85, 0.8, 0.8])
    ki_barrier = 0.6
    n_iteration = 10000

    els_fdm, uGrid, cal_time_fdm, s_seq, t_seq = OneDimELS_FDM(underlying_spot, interest_rate, redemption_schedule,
                                                               coupon,
                                                               full_dummy, barrier, ki_barrier, n_iteration)

    mc_list = []
    fdm_list = []
    bt_list = []
    diff_fdm_bt_list = []
    x = []
    cal_time_mc = 0
    cal_time_bt = 0
    for i in range(20):
        curr_price = 50 + 5 * i
        underlying_spot = Underlying(refprice=100, spotvalue=curr_price, volatility=0.3, dividend=0)
        mc_value, ct, _ = OneDimELS_MC_BB(underlying_spot, interest_rate, redemption_schedule, coupon,
                                          full_dummy, barrier, ki_barrier, n_iteration)
        bt_value, ct_bt, vnode = OneDimELS_BinomialTree(underlying_spot, interest_rate, redemption_schedule, coupon,
                                                        full_dummy, barrier, ki_barrier, n_iteration)
        x.append(curr_price)
        mc_list.append(mc_value)
        fdm_value = np.interp(curr_price, s_seq, uGrid[0, :])
        fdm_list.append(fdm_value)
        bt_list.append(bt_value)
        diff_fdm_bt_list.append(bt_value - fdm_value)
        cal_time_mc += ct
        cal_time_bt += ct_bt
   
    print('Elapsed time: MC={},  FDM={}, Binom.Tree ={}'.format(cal_time_mc, cal_time_fdm, cal_time_bt))

    fig, ax1 = plt.subplots()  # subplots() 함수를 사용하여 ax설계
    ax1.plot(x, mc_list, c='r', marker='o', label='MC')
    ax1.plot(x, fdm_list, c='b', marker='x', label='FDM')
    ax1.plot(x, bt_list, c='c', marker='^', label='Binom.Tree')
    plt.xticks([50, 100, 150])
    ax1.legend()
    ax2 = ax1.twinx()  # 같은 축에다 그리기 위해 동일 ax1과 동일한 Axes를 ax2로 설계
    ax2.bar(x, diff_fdm_bt_list, color='royalblue', label='difference bt-fdm')
    ax2.legend()
    plt.show()

○ 기준가가 100인 기초자산을 50부터 5 단위씩 증가시켜 145까지 20개의 경우에 대해서 MC, FDM, Binomial Tree로 계산한 결과입니다.

 

○ 그 중, binomial tree방법과 FDM의 가격의 차이를 계산하여 그래프를 표현했습니다.

 

이전 글의 코드와 비슷하므로 자세한 설명은 생략합니다.

 

 

결과를 보겠습니다.

 

Binomial Tree 방법이 다른 방법과 비교해서 큰 차이점이 없네요. 또한 파란 bar chart는 binomial tree와 FDM 값의 차이인데 오른쪽 축의 값을 보시면 차이가 미미하다는 것을 알 수 있습니다. 

어느 정도 세 방법의 구현이 서로 서로 체크가 되네요.

 

 

728x90
반응형

댓글