금융퀀트/자산평가&프로그램매매

이자율스왑 평가하기(python)3: 3개월 단위 금리표 만들기

문송한투자자 2024. 10. 30. 01:30
반응형

3개월 단위 스왑금리 산출 방법

이자율스왑 평가하기(python)2: 금리 커브 일자 구하기 에서 커브의 각 node에 해당하는 날짜를 구해보았다. KRX에서는 이자교환 주기를 3개월로 가정하기 때문에 3개월 단위의 이자율을 나타내는 데이터를 먼저 만들어서 그 데이터를 이용한 Bootstrapping을 통해서 무이표금리를 산출한다.

구분 1일 3개월 1년 2년 3년 4년 5년 ...
금리 3.55 3.48 3.22 3.00 2.905 2.8625 2.845 ...

표 1: 원화이자율스왑 평가 커브(2024-08-02 기준, 1년 이상은 BID-OFFER의 평균인 MID 금리)

KRX에서는 아래 그림 1의 자료처럼 주어진 데이터에서 선형보간법을 사용해서 비기준만기(3개월 단위) 스왑 금리 커브를 만든다. 선형보간은 기간의 비례식을 이용해서 금리와 금리 사이의 값을 찾는 것인데 선형보간법 연습 참조하면 된다.

그림1: KRX의 금리커브 산출(3개월 단위 데이터 만들기)

3개월 단위 스왑금리 생성: config_curve_by_unit, linear_interpolate, get_timeinterval_a365f

표 1의 데이터 구조를 보면 1년 이내는 3개월 데이터가 있어서 6개월 9개월 의 금리만 선형보간으로 만들면 되고, 1년 이상 데이터는 년 단위로 되어 있어서 3, 6, 9 개월 데이터를 각각 선형보간으로 구해줘야 한다는 것을 알 수 있다. 그리고 선형보간을 통한 각각의 데이터 산출 작업은 linear_interpolate이라는 함수를 통해서 수행할 수 있다. 이를 그림 2와 같이 나타낼 수 있다. 

그림2: 3개월 단위 스왑금리 구하기

구간의 일수를 구하는 함수 구현: get_timeinterval_a365f

node 사이의 3개월 단위의 금리를 만들기 위해서는 선형보간이 필수적인데, 이 선형보간을 위해서 필수적인 것이 구간의 일수를 구하는 것이다. 선형보간은 각 구간의 일수에 따라서 값이 달라지기 때문이다. 시작일과 종료일이라는 변수를 넣어서 그 사이의 일 수를 구하는 함수는 아래와 같이 구현할 수 있는데, 이때 시작일과 종료일은 이자율스왑 평가하기(python)2: 금리 커브 일자 구하기에서 구현한 get_modf_eom_date 함수를 사용해서 Modified Following 규칙과 End of Month 규칙을 적용한 날짜로 변환하는 과정을 거친다.

# 라이브러리 사용
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
import pandas as pd
import re

# modified following 과 end of month 규칙이 적용된 날짜를 구하는 함수
def get_modf_eom_date(date, holidays):
    origin_month = date.month
    while date.weekday() >= 5 or date in holidays:
        date += timedelta(days=1)
    if date.month != origin_month:
        end_of_month = datetime(date.year, origin_month + 1, 1) - timedelta(days=1)
        while end_of_month.weekday() >= 5 or end_of_month in holidays:
            end_of_month -= timedelta(days=1)
        date = end_of_month
    return date

# Actual 365 날짜 규칙을 바탕으로 시작일과 종료일의 날짜를 계산하는 함수
def get_timeinterval_a365f(startdt, enddt):
    startdt = datetime.strptime(startdt, '%Y-%m-%d')
    enddt = datetime.strptime(enddt, '%Y-%m-%d')
    holidays = holidays
    holidays = [datetime.strptime(holiday, '%Y-%m-%d') for holiday in holidays]
    startdt = get_modf_eom_date(startdt)
    enddt = get_modf_eom_date(enddt)
    actdays = (enddt - startdt).days
    return actdays

선형보간 함수 구현: linear_interpolate

선형보간법 연습에서 알아본 것처럼 "시작일과 target일자의 구간 / 시작일과 종료일의 구간" 비율을 바탕으로 target일자의 금리를 알 수 있으며, 코드는 아래와 같이 구현할 수 있다. 코드를 보면 위의 get_timeinterval_a365f 함수를 이용해서 시작일과 종료일의 일수를 구하고, 같은 함수를 사용해서 시작일과 목표일의 일수를 구하는 것을 볼 수 있다. 그 뒤 해당 금리구간의 크기를 intdiff 변수에 저장한다. 마지막으로 시작일의 금리인 intdiff 변수에 "시작일과 target일자의 구간 / 시작일과 종료일의 구간" 비율을 곱한 뒤 구간의 시작금리인 strate과 합해주면 선형보간된 금리인 targetrate을 얻을 수 있다.

def linear_interpolate(stdate, eddate, strate, edrate, targetdate):
    tottimediff = get_timeinterval_a365f(stdate, eddate)
    timediff = get_timeinterval_a365f(stdate, targetdate)
    intdiff = edrate - strate
    targetrate = strate + timediff / tottimediff * intdiff
    return targetrate

3개월 단위 스왑커브 만드는 함수: config_curve_by_unit

get_timeinterval_a365f 함수와 linear_interpolate 함수를 모두 완성했다면, 위 그림 2와 같은 구조의 config_curve_by_unit 함수를 만들 수 있다. 위 표 1의 raw data 가 input 값으로 넣는다면, node 가 1년 미만(raw data에서 1년 미만인 경우는 3개월이다.) 일 경우 그림 2의 1년 미만인 경우의 처리 로직을 거치도록 구성하고, 그 외의 경우 node가 1년 이상일 경우의 처리 로직을 거치도록 구성하면 된다. raw data에서 비어있는 3개월 단위의 데이터를 모은 add_df라는 변수를 만들어서 기존의 데이터인 df와 합친 뒤 nodedate 기준으로 정렬하면, df_sorted_reset이라는 3개월 단위의 금리표가 만들어진다.

def config_curve_by_unit(df: pd.DataFrame):
        add_df = []
        basedt = datetime.strftime(basedt, "%Y-%m-%d")
        for i in df.index:
            if df.at[i, "node"] == "3M":
                targetdate1 = get_adj_date(basedt, "6M")
                targetdate2 = get_adj_date(basedt, "9M")
                stdate = df[df["node"] == "3M"]["nodedate"].iloc[0]
                eddate = df[df["node"] == "1Y"]["nodedate"].iloc[0]
                strate = df[df["node"] == "3M"]["intrate"].iloc[0]
                edrate = df[df["node"] == "1Y"]["intrate"].iloc[0]
                add_rate1 = linear_interpolate(stdate, eddate, strate, edrate, targetdate1)
                add_row1 = [basedt, "6M", targetdate1, add_rate1]
                add_df.append(add_row1)
                add_rate2 = linear_interpolate(stdate, eddate, strate, edrate, targetdate2)
                add_row2 = [basedt, "9M", targetdate2, add_rate2]
                add_df.append(add_row2)
            elif df.at[i, "node"][-1] == "Y" and i != df.index[-1]:
                num_of_years = int(extract_numbers(df.at[i, "node"]))
                month1 = str(num_of_years * 12 + 3) + "M"
                month2 = str(num_of_years * 12 + 6) + "M"
                month3 = str(num_of_years * 12 + 9) + "M" 
                targetdate1 = get_adj_date(basedt, month1)
                targetdate2 = get_adj_date(basedt, month2)
                targetdate3 = get_adj_date(basedt, month3)
                stdate = df.at[i, "nodedate"]
                eddate = df.at[i+1, "nodedate"]
                strate = df.at[i, "intrate"]
                edrate = df.at[i+1, "intrate"]
                add_rate1 = linear_interpolate(stdate, eddate, strate, edrate, targetdate1)
                add_row1 = [basedt, month1, targetdate1, add_rate1]
                add_df.append(add_row1)
                add_rate2 = linear_interpolate(stdate, eddate, strate, edrate, targetdate2)
                add_row2 = [basedt, month2, targetdate2, add_rate2]
                add_df.append(add_row2)
                add_rate3 = linear_interpolate(stdate, eddate, strate, edrate, targetdate3)
                add_row3 = [basedt, month3, targetdate3, add_rate3]
                add_df.append(add_row3)
        add_df = pd.DataFrame(add_df, columns=["basedt", "node", "nodedate", "intrate"])
        df = pd.concat([df, add_df])
        df_sorted = df.sort_values(by="nodedate", ascending=True)
        df_sorted_reset = df_sorted.reset_index(drop=True)
        return df_sorted_reset
반응형