카테고리 보관물: Asset Pricing

채권가격계산 library 개발-4

채권 가격 계산 라이브러 개발을 위해 일반 채권 중 가장 기본이 되는 할인채와 이표채에 대한 설명을 한데 이어 단리채와 복리채에 대한 설명을 하고자 한다.

단리채는 매년 채권의 표면 이자율로 계산한 이자를 누적하여 만기에 일시 상환하는 채권이다. 즉 매년 이자 얼마 ( 원금 X 표면이자율 )를 계산해서 만기에는 “원금 + 그동안의 누적이자” 로 지급한다.
복리채는 채권의 이자를 복리로 계산해서 만기일에 일시 상환하는 채권이다. 즉, 채권의 원금을 지급하는 만기에 이자까지 한꺼번에 지급하는데 이 때 첫 해부터 지급되는 이자가 복리로 계속 쌓인다는 조건이다. ( 복리라 함은 이자에도 이자가 붙는 개념 )

복리채의 로직은 크게 어려울 것이 없다. 단리채와 동일한데 이자를 누적해서 그 누적된 이자에도 이자 계산을 해서 다시 누적해 주어야 한다.

/*-----------------------------------------------------------------------------
 * fnSBCompoundBond : 복리채에 대한 가격 계산 함수
 *		Spot/YTM 모두 사용가능
 *		YTM의 갯수를 1개만 지정하는 경우 해당 YTM을 사용할 수 있으며,
 *		YTM의 갯수가 여러개인 경우 보간 하여 해당 YTM 사용
 *
 * total_coupon : total_year + days from issue to first coupon day
 *
 *---------------------------------------------------------------------------*/

UINT fnSBCompoundBond( DATETYPE dtPriceDay,     /* 계산일                           */
                       BONDINFO *pBondInfo,   /* Bond Info Structure              */
                       CURVE  *pCurve,        /* ytm/sport curve                  */
                       BONDPRICE *pBondPrice, /* Price Info Structure             */
                       INT    *error_code)
{
	DATETYPE	nextCouponDate;
	DATETYPE	prevCouponDate;
	UINT 		nTotalCoupon;
	UINT 		nResidueYear;
	UINT		nDaysToMaturity;
	DOUBLE		nBondPrice;
	DOUBLE		nDuration;
	DOUBLE		nMD;
	DOUBLE		nConvexity;
	DOUBLE		nFace;
	DOUBLE		nDiscountToday;
	DOUBLE		nIntRatio;
	UINT		nNextCouponRemDay;
	UINT		nCouponTermDay;

	nDuration  = 0.0;
	nMD        = 0.0;
	nConvexity = 0.0;

	nFace = 10000.0;
	nIntRatio  = pBondInfo->nIntMonth / 12.0;

	/*------------------------------------------------------
	 * cashflow = face * 만기상환율 + coupon / 횟수 * ( rem / term + N - 1 )
	 *-----------------------------------------------------*/

	nResidueYear = 0;

	prevCouponDate = pBondInfo->dtDueDay;
        while ( fnDatetoInt( prevCouponDate ) >= fnDatetoInt( dtPriceDay ) ) {
		nextCouponDate = prevCouponDate;
		prevCouponDate = fnAddDateInt( prevCouponDate, YEAR, -1 );
		nResidueYear++;
	}

	if ( nResidueYear > 0 ) nResidueYear--;
	nNextCouponRemDay = fnCountDate( dtPriceDay, nextCouponDate, DAY );
	nCouponTermDay    = fnCountDate( prevCouponDate, nextCouponDate, DAY ) ;
	nDiscountToday = (DOUBLE) nNextCouponRemDay / (DOUBLE) nCouponTermDay;

	nTotalCoupon = fnCountDate( pBondInfo->dtIssueDay, pBondInfo->dtDueDay, MONTH) / pBondInfo->nIntMonth;

	nBondPrice = 0.0;

	nDaysToMaturity = fnCountDate( dtPriceDay, pBondInfo->dtDueDay, DAY );

        /* 해당 수익률이 하나만 입력되는 경우 하나의 YTM을 이용 */
	if ( pCurve->nYieldCount == 1 )
	{
		pBondPrice->nYtm = pCurve->nYield[ 0 ];
	}
	else
	{
		/* 여러개의 YTM이 입력되는 경우 해당 잔존만기의 수익률을 선형보간으로 가져옴  */
		pBondPrice->nYtm = fnGetRate( pCurve, nDaysToMaturity , LINEAR );
	}
        /* Spot 커브에 대한 부분은 생략 */
	nBondPrice = nFace * pBondInfo->nReturnRate / 100.0 + nFace * pow( 1 + pBondInfo->nCouponRate * nIntRatio, nTotalCoupon ) - nFace;
	nBondPrice = floor(nBondPrice) / ( (1 + pBondPrice->nYtm * nDiscountToday ) * pow( 1.0 + pBondPrice->nYtm, nResidueYear  ) ); 

	nDuration = nDaysToMaturity / 365.0;
	nMD = Math_Round( nDuration / ( 1 + pBondPrice->nYtm ), 5 );
	nConvexity = Math_Round( ( Math_Round( nDuration, 6 ) * ( Math_Round( nDuration, 6 ) + 1 ) ) / pow( 1.0 + pBondPrice->nYtm * pBondInfo->nIntMonth /12.0, 2 ), 5 );

	pBondPrice->nPrice = nBondPrice;
	pBondPrice->nDuration = Math_Round( nDuration, 5 );
	pBondPrice->nMD = nMD;
	pBondPrice->nConvexity = nConvexity;

	return SUCCESS;
}

 

 
단리채는 채권 가격 계산 부분이 다음과 같이 변경되면 된다.


	nBondPrice = floor( nFace * ( pBondInfo->nReturnRate / 100.0 + pBondInfo->nCouponRate / nCouponPerYear * (nTotalYear * nCouponPerYear + nRemDay / nLastCouponTermDay ) ) );
	nBondPrice = nBondPrice / ( pow( 1.0 + pBondPrice->nYtm, nResidueYear ) * ( 1.0 + pBondPrice->nYtm * nDiscountToday ));

 

 

 

채권가격계산 library 개발-3

이표채에 이어 채권의 기본이 되는 할인채에 대해 살펴 보겠다. 할인채의 수익률은 그 자체로 spot rate이므로 하나의 수익률로 하나의 cashflow를 할인하여 채권 가격을 산출하는 간단한 로직이다.

만기 상환만 존재하는 할인채
할인채는 만기에 원금을 상환하기로 하고 표면금리로 할인된 현재 가치로 채권이 발행된다. 예를 들어 3년 후(만기)에 10,000원을 받기로 하고 현재 할인된 9,700원으로 채권을 매입하는 것이다. 이 때, 300원이 채권의 이자가 되고 원금은 10,000원이며 300원을 3년간 나눈 것이 할인율, 즉 수익률이 되겠다.
이를 함수로 만든것이 다음과 같다.

/*-----------------------------------------------------------------------------  
 * fnSBDiscountBond : 할인채에 대한 가격 계산 함수
 *		Spot/YTM 모두 사용가능
 *		YTM의 갯수를 1개만 지정하는 경우 해당 YTM을 사용할 수 있으며,
 *		YTM의 갯수가 여러개인 경우 보간 하여 해당 YTM 사용
 *
 * 미결사항 :
 * 
 *---------------------------------------------------------------------------*/
UINT fnSBDiscountBond( DATETYPE dtPriceDay,     /* 계산일              */
                       BONDINFO *pBondInfo,   /* Bond Info Structure   */
                       CURVE  *pCurve,        /* ytm/sport curve      */
                       BONDPRICE *pBondPrice, /* Price Info Structure  */
                       INT    *error_code)
{
	DATETYPE	nextCouponDate;
	DATETYPE	prevCouponDate;
	UINT 		nTotalYear;
	UINT		nDaysToMaturity;
	DOUBLE		nBondPrice;
	DOUBLE		nDuration;
	DOUBLE		nMD;
	DOUBLE		nConvexity;
	DOUBLE		nFace;
	DOUBLE		nDiscountToday;
	UINT		nNextCouponRemDay;
	UINT		nCouponTermDay;

...
중략
...

	nDuration  = 0.0;
	nMD        = 0.0;
	nConvexity = 0.0;

	nFace = 10000.0;

	// 상환일 기준 이자지급채권과 동일하게 날짜 계산
	prevCouponDate = pBondInfo->dtDueDay;
	nTotalYear = 0.0;
	while ( fnDatetoInt( prevCouponDate ) >= fnDatetoInt( dtPriceDay ) ) {
		nextCouponDate = prevCouponDate;
		prevCouponDate = fnAddDateInt( prevCouponDate, YEAR, -1 );
		nTotalYear++;
	}

	nBondPrice = 0.0;
	nNextCouponRemDay = fnCountDate( dtPriceDay, nextCouponDate, DAY );
	nCouponTermDay    = fnCountDate( prevCouponDate, nextCouponDate, DAY ) ;
	nDiscountToday = (DOUBLE) nNextCouponRemDay / (DOUBLE) nCouponTermDay;

	/*----------------------------------------------------------------------
	 * YTM : yield가 하나일경우 그냥 사용, 여러개일경우 linear/spline 보간
	 *---------------------------------------------------------------------*/

	nDaysToMaturity = fnCountDate( dtPriceDay, pBondInfo->dtDueDay, DAY );

	if ( pCurve->nYieldCount == 1 )
	{
		pBondPrice->nYtm = pCurve->nYield[ 0 ];
	}
	else
	{
		/* 해당 잔존만기의 수익률을 선형보간 */
		pBondPrice->nYtm = fnGetRate( pCurve, nDaysToMaturity , LINEAR );
	}

        // 채권 가격을 계산하는 부분
	nBondPrice = nFace / pow( 1.0 + pBondPrice->nYtm, nTotalYear - 1 );
	nBondPrice /= ( 1.0 + pBondPrice->nYtm * nDiscountToday );

	nDuration = (DOUBLE) nDaysToMaturity / 365.0;
	nMD = Math_Round( nDuration / ( 1 + pBondPrice->nYtm ), 5 );
	nConvexity = Math_Round( nDuration, 6 )* ( Math_Round( nDuration, 6) + 1 );
	nConvexity = Math_Round( nDuration / pow( 1.0 + pBondPrice->nYtm * pBondInfo->nIntMonth / 12.0, 2 ), 5 );

nDuration, 6) + 1 ) / pow( 1.0 + pBondPrice->nYtm * pBondInfo->nIntMonth / 12.0, 2 ), 5 );

	pBondPrice->nPrice = nBondPrice;
	pBondPrice->nDuration = Math_Round( nDuration, 5 );
	pBondPrice->nMD = nMD;
	pBondPrice->nConvexity = nConvexity;

	return SUCCESS;
}

사실 별로 어려울 것도 없이 간단한 계산이다. 이표채의 여러 cashflow중 만기에 해당하는 것 한 건만 계산하면 된다.

위에서 얘기한 바와 같이 할인채는 그 자체의 수익률이 spot rate이므로 시장에서 관찰된 spot을 그대로 사용해서 채권 가격을 계산하면 되며 spot이 아닌 공시 수익률과 같은 ytm으로 수익률을 입력하는 경우 사전적으로 bootstrapping 방법 등을 통해 spot rate을 산출하면 된다.

 

 

채권가격계산 library 개발-2

얼마전 채권평가사 중 한 곳에서 c++을 이용하여 엔진을 개발했다고 한다. 기존의 c엔진보다 속도나 유연성, 확장성을 높이기 위해서라고 그 이유를 밝혔었는데 언어의 확장성은 사실 중요한 요소이긴 하다. 특히 금융상품들을 분석하다보면 비슷한 패턴의 속성(예를들면 cashflow를 이용한 할인법이나 이자율 추정 방법 등)들이 반복되므로 c++언어 자체의 상속성과 캡슐화 등이 유용할 수 있다. 특히 이미 잘 짜여진 c++의 템플릿을 통한 연산속도의 향상은 반복계산시 어느정도 성능차이를 불러올 수도 있다.
그러나, 언어 자체의 차이도 중요하지만 언어를 잘 사용하는 것도 아주 중요한 요소 중 하나이다. c++와 c 둘 중에서 어느 쪽에 더 낫다라고 말하기는 애매한 상황이라서 아무래도 개발이 쉬운 c를 선택했다(언젠가 java로도 포팅해 볼 예정이다).

채권 가격 계산을 위해 제일 먼저 덤벼든 부분은 일반채권(SB : straight bond)이다. 일반 채권 중에서도 이표채(Coupon Bond)를 먼저 소개한다. 여기서는 채권의 기본인 가격 계산과 duration, convexity를 함께 계산할 수 있도록 작성을 했다. 채권수익률은 ytm 하나를 사용할 수도 있고 spot rate를 사용할 수도 있다.

가격 계산에서의 YTM과 SPOT
YTM(Yield to Maturity)은 일반적으로 만기까지의 수익률로 이해할 수 있지만 가격 계산적인 측면에서 볼 때는 만기에 따라 서로 다른 spot rate을 다 적용안 것과 동일한 역할을 하는 하나의 수익률이다.
즉, 1M -> 3.45, 3M -> 3.47, 6M -> 3.49, 9M -> 3.50와 같이 서로 다른 spot rate(할인율)을 적용해서 계산해야 하는데 그냥 3.48로 계산했을 때 동일한 결과가 나오게 해 주는 수익률이 YTM이다.

다음은 아직 미결사항이 조금 남아 있는 소스이다. 선매출과 경과이자에 대한 계산은 포함하지 않는다.

/*-----------------------------------------------------------------------------
 * bond_sb.c
 * 			: SB ( Straight Bond )를 계산하기 위한 함수
 * 			가격계산시 duration, convexity를 계산함
 *
 *---------------------------------------------------------------------------*/
#ifndef _BOND_SB_C_
#define _BOND_SB_C_

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "define.h"
#include "util.h"
#include "bond_lib.h"
#include "math_util.h"
#include "date_lib.h"

/*-----------------------------------------------------------------------------
 * fnSBCouponBond : 이표채에 대한 가격 계산 함수
 *		Spot/YTM 모두 사용가능
 *		YTM의 갯수를 1개만 지정하는 경우 해당 YTM을 사용할 수 있으며,
 *		YTM의 갯수가 여러개인 경우 보간 하여 해당 YTM 사용
 *
 * 미결사항 :
 *---------------------------------------------------------------------------*/
UINT fnSBCouponBond( DATETYPE  dtPriceDay,     /* 계산일                           */
                     BONDINFO  *pBondInfo,   /* Bond Info Structure              */
                     CURVE     *pCurve,      /* ytm/sport curve                  */
                     BONDPRICE *pBondPrice,  /* Price Info Structure             */
                     INT       *error_code )
{
	DATETYPE	nextCouponDate;
	DATETYPE	prevCouponDate;
	DATETYPE	nextDate;
	DATETYPE	prevDate;
	DATETYPE	dtBaseDate;
	CASHFACTOR	*pCashFactor;
	UINT 		nTotalCoupon;
	DOUBLE		nCashIn;
	DOUBLE		nDuration;
	DOUBLE		nMD;
	DOUBLE		nConvexity;
        DOUBLE          nBondPrice;
	DOUBLE		nInterest;
	DOUBLE		nFace;
	DOUBLE		nDiscountToday;
        UINT            nIntPayType;
	UINT		nCouponPerYear;
	UINT		nNextCouponRemDay;
	UINT		nCouponTermDay;
	int		idx;
	int		nCouponIdx;
...
중략
...
//초기화
	nFace = 10000.0;
	nCouponPerYear = 12.0 / pBondInfo->nIntMonth;
	nInterest = nFace * pBondInfo->nCouponRate / nCouponPerYear;
	nFace = nFace * pBondInfo->nReturnRate / 100.0;
	nTotalCoupon = fnCountDate( dtPriceDay, pBondInfo->dtDueDay, MONTH ) / pBondInfo->nIntMonth + 3;
...
// 만기가 지나거나 발행정보가 잘못되었는지 체크하고 잘못되었을땐 리턴하도록 하는 부분 포함
...
//
// 이자를 지급하는 기준이 발행일 말일 기준인지, 만기일 말일 기준인지, 발행일 기준인지, 만기일 기준인지를 판단한다.
	pCashFactor = (CASHFACTOR*)calloc( nTotalCoupon, sizeof( CASHFACTOR ) );

	nIntPayType = pBondInfo->nIntPayType;

	if ( nIntPayType == ENGINE_SELECT ) {
		if ( fnIsDate( pBondInfo->dtFirstIntDay ) && fnIsLastDay( pBondInfo->dtFirstIntDay ) && pBondInfo->bEndOfMonth )
			nIntPayType = ISSUE_MONTH_LAST_DAY;
		else if ( fnIsDate( pBondInfo->dtFirstIntDay ) && 
			fnDatetoInt( pBondInfo->dtFirstIntDay ) == fnDatetoInt( fnAddDateInt( pBondInfo->dtIssueDay, MONTH, pBondInfo->nIntMonth ) ) )
			nIntPayType = ISSUE_DAY;
		else if ( fnIsDate( pBondInfo->dtFirstIntDay ) )
			nIntPayType = IRREGULAR_FIRST;
		else if ( fnIsDate( pBondInfo->dtLastIntDay ) && fnIsLastDay( pBondInfo->dtLastIntDay ) )
			nIntPayType = MATURE_MONTH_LAST_DAY;
		else if ( fnIsDate( pBondInfo->dtLastIntDay ) &&
			fnDatetoInt( pBondInfo->dtLastIntDay ) == fnDatetoInt( fnAddDateInt( pBondInfo->dtLastIntDay, MONTH, -pBondInfo->nIntMonth ) ) )
			nIntPayType = MATURITY_DAY;
		else if ( !fnIsDate( pBondInfo->dtFirstIntDay ) && !fnIsDate( pBondInfo->dtLastIntDay ) ) {
			if ( fnIsLastDay( pBondInfo->dtIssueDay ) && pBondInfo->bEndOfMonth )
				nIntPayType = ISSUE_MONTH_LAST_DAY;
			else
				nIntPayType = ISSUE_DAY;
		}
		else if ( fnIsDate( pBondInfo->dtLastIntDay ) )
			nIntPayType = IRREGULAR_LAST;
	}

	/* 발행일 기준 이자지급 채권							*/
	if ( nIntPayType == ISSUE_DAY || nIntPayType == IRREGULAR_FIRST ) {
		if ( nIntPayType == ISSUE_DAY )
                         // 가격 계산일이 발행일보다 이전인 경우(발행도 안된 채권을 사는 것이다!)
			if ( fnDatetoInt( pBondInfo->dtIssueDay ) > fnDatetoInt( dtPriceDay ) ) {
				prevCouponDate = dtPriceDay;
                                // 다음 이자지급일은 발행일에 이자지급 주기를 그냥 더한다.
				nextCouponDate = fnAddDateInt( pBondInfo->dtIssueDay, MONTH, pBondInfo->nIntMonth );
			}
                        // 이미 발행된 채권이라면 다음 이자지급일은 발행일을 기점으로 더해나가야 한다.
                        // (단 prevCouponDate == nextCoupondate를 해 놓고 아래에서 loop 돌면서 찾는다.)
			else {
				nextCouponDate = pBondInfo->dtIssueDay;
				prevCouponDate = nextCouponDate;
			}
		else {
			nextCouponDate = pBondInfo->dtFirstIntDay;
			prevCouponDate = nextCouponDate;
		}

		dtBaseDate = nextCouponDate;
                // 가격 계산일이 다음 이자지급일 직전 구간에 오도록 날짜를 더해간다
                // 사실 간단한 로직으로 한번에 구할 수도 있다.( 발행일 + floor((가격계산일 - 발행일 ) / 이자지급주기 ) * 이자지급주기 ) 그러나 프로그램을 Copy and paste 쉽게 하기 위해 풀어썼다. ^^;;
		while ( fnDatetoInt( nextCouponDate ) < fnDatetoInt( dtPriceDay ) ) { 			prevCouponDate = nextCouponDate; 			nextCouponDate = fnAddDateInt( dtBaseDate, MONTH, pBondInfo->nIntMonth * nCouponIdx++ );
		}
	}

// 다음부터는 발행일 말일이든, 만기일 말일이든, 로직이 큰 차이는 없고 날짜만 체크하면 된다.
...
중략
...
//
	/*-------------------------------------------------------------------------
	 * YTM / SPOT에 따른 cashflow 할인 계산 ->가격계산
	 *-----------------------------------------------------------------------*/
	if ( pCurve->nCurveType == YTM ) 
	{
		pBondPrice->nYtm = 0.0;

		if ( pCurve->nYieldCount == 1 )
		{
			pBondPrice->nYtm = pCurve->nYield[ 0 ];
		}
		else
		{
			/* 해당 잔존만기의 수익률을 선형보간으로 가져옴  */
			UINT	nRemDay;

			nRemDay = pCashFactor[ nTotalCoupon - 1 ].nRemDay;
			pBondPrice->nYtm = fnGetRate( pCurve, nRemDay, LINEAR );
		}

		idx = 0;
		nextDate = nextCouponDate;
		prevDate = prevCouponDate;
                // 날짜를 loop 돌면서 discount factor를 구해주기 시작한다.
                // discount factor란 각 이자지급일의 1원이 현재 가치로 얼마가 될 것인지 할인비율을 계산한 값이다.
		while ( fnDatetoInt( nextDate ) <= fnDatetoInt( pBondInfo->dtDueDay ) )
		{
			pCashFactor[ idx ].nRemDay = fnCountDate( dtPriceDay, nextDate, DAY );
			pCashFactor[ idx ].nCash = nInterest;
			pCashFactor[ idx ].nDF = 1 / pow( 1.0 + pBondPrice->nYtm / nCouponPerYear, idx );
			prevDate = nextDate;
			if ( nIntPayType == MATURE_MONTH_LAST_DAY || nIntPayType == ISSUE_MONTH_LAST_DAY )
				nextDate = fnSetLastDay( fnAddDateInt( dtBaseDate, MONTH, pBondInfo->nIntMonth * nCouponIdx ) );
			else
				nextDate = fnAddDateInt( dtBaseDate, MONTH, pBondInfo->nIntMonth * nCouponIdx );
			idx++;
			nCouponIdx++;
		}
			pCashFactor[ idx ].nRemDay = fnCountDate( dtPriceDay, pBondInfo->dtDueDay, DAY );
			pCashFactor[ idx ].nCash = nInterest;
			pCashFactor[ idx ].nCash = nInterest * ( fnDatetoInt( pBondInfo->dtDueDay ) - fnDatetoInt( prevDate ) ) / ( fnDatetoInt( nextDate ) - fnDatetoInt( prevDate ) );
			pCashFactor[ idx ].nCash += nFace;
			if ( idx > 0 )
				pCashFactor[ idx ].nDF = 1.0 / ( pow( 1.0 + pBondPrice->nYtm / nCouponPerYear, idx - 1) ) ;
			else 
				pCashFactor[ idx ].nDF = 1.0;

			pCashFactor[ idx ].nDF /= ( 1.0 + pBondPrice->nYtm / nCouponPerYear * (DOUBLE)( fnDatetoInt( pBondInfo->dtDueDay ) - fnDatetoInt( prevDate ) ) / (DOUBLE)( fnDatetoInt( nextDate ) - fnDatetoInt( prevDate ) ) );
			idx++;
		}
		else pCashFactor[ idx - 1].nCash += nFace;

		nTotalCoupon = idx;

                // 채권가격을 계산한다.
                // 각 이자지급일의 discount factor와 이자 또는 원금을 곱해서 현재가치에 누적해서 더한다.
		nBondPrice = 0.0;
		nNextCouponRemDay = fnCountDate( dtPriceDay, nextCouponDate, DAY );
		nCouponTermDay    = fnCountDate( prevCouponDate, nextCouponDate, DAY ) ;
		nDiscountToday = (DOUBLE) nNextCouponRemDay / (DOUBLE) nCouponTermDay;

		for ( idx = 0; idx <  nTotalCoupon; idx++ ) 		{ 			if ( pCashFactor[ idx ].nRemDay == 0 ) continue;                         // 각 이자의 현재가치(할인금액)는 다음 이자 지급일까지의 할인율을 곱한다 			nCashIn = pCashFactor[ idx ].nCash * pCashFactor[ idx ].nDF;                        // 다음 이자지급일까지 할인된 금액을 다시 현재까지로 할인한다. 두개의 산식을 합해서 하나로 해도 무방하다. 			nCashIn /= ( 1.0 + ( pBondPrice->nYtm / nCouponPerYear * nDiscountToday ) );
                        // 듀레이션을 같이 곱한다.
			nDuration += ( nCashIn * pCashFactor[ idx ].nRemDay / 365.0 ) ;
			nConvexity += ( nCashIn * pow( pCashFactor[ idx ].nRemDay/ 365.0, 2 ) * pCashFactor[ idx ].nRemDay / 365.0 );
			nBondPrice += nCashIn;
		}
                // 듀레이션과 컨벡서티도 계산한다.
		nDuration /= nBondPrice;
		nMD = Math_Round( nDuration / ( 1 + pBondPrice->nYtm * pBondInfo->nIntMonth / 12.0 ), 5 );
		nConvexity = Math_Round( nConvexity / ( pow( 1 + pBondPrice->nYtm * pBondInfo->nIntMonth / 12.0, 2 ) * nBondPrice ), 5 ) ;
	}

// 만약 YTM이 아니라면 spot rate를 선형보간해서 해당 날짜의 할인율을 구해서 discount factor로 사용한다.
        else {
...
        }
// 가격과 듀레이션, 컨벡서티를 리턴한다.

	pBondPrice->nPrice = nBondPrice;
	pBondPrice->nDuration = Math_Round( nDuration, 5 );
	pBondPrice->nMD = nMD;
	pBondPrice->nConvexity = nConvexity;

        free(pCashFactor);

        return SUCCESS;
}

고민하지 않고 로직 구현에만 신경을 써서 작성한 코드라 표현이 거칠고 정교화되지 않은 부분이 있겠지만 전체적인 로직 이해에는 큰 무리가 없을 것 같다.
가격 계산 로직의 핵심은 각 이자지급일을 정확히 계산하는 것과 해당일의 discount factor, 즉 할인률을 계산해서 지급하는 이자 및 원금(cashflow)을 현재가치로 계산하는 것이다.

df

그리고 이미 설명한 바와 같이 날짜 계산 함수들이 필수이다.

채권 가격 계산 library 개발 – 1

채권 가격 계산을 위해 가장 기본적으로 고려해야 하는 것이 날짜 계산 방법이다. 날짜가 중요한 이유는 채권 가격을 계산하기 위해서 가장 기본이 되는 이슈가 Cashflow, 즉 현금흐름이기 때문이다. 현금흐름이란 채권과 관련한 대금의 지급(채권가격을 사기 위하여 현금을 지불)과 이자의 수취(이자를 받음), 시간이 흐른 후에 원금 수취(채권 대금을 다시 돌려받음)하는 모든 일정 자체를 말한다.
현금을 하루 일찍 받느냐 늦게 받느냐에 따라 하루의 이자의 차이가 발생하고 이 차이가 현재 채권의 가격에 반영되므로 날짜 계산은 신속하고 정확한 계산이 필수이다.

보통 금융기관에서의 날짜 계산은 영업일 관리 등의 이유로 DBMS상에서 별도의 Calendar를 관리하며 DBMS 내에서의 함수나 연산을 통해 이루어지는 경우가 대부분이나 채권 가격 계산을 위해서는 최근래의 이자 지급일과 만기 상환일만 영업일 체크를 직접해야한다(속도를 위하여). 사실 정확한 채권 가격 계산을 위해서는 각 이자지급일의 영업일을 모두 점검하고 실제 현금지급일로 변환을 하여 계산하는 것이 가장 정확하겠으나 채권가격 계산시 관행적으로 적용하는 방식에서는 통상적으로 이자 지급일의 영업일 여부는 무시하고 계산하므로 단순 날짜 계산만 구현하면 된다.
만기 전 이자 지급일이 휴일이면 휴일의 이자를 더해서 휴일이 지난 다음에 지급하므로 미미하게나마 가치 차이가 발생할 수 있으나 만기 이전에 받는 이자의 경우 결국 전체 현금흐름상 처음부터 받기로 예정되어 있는 현금을 하루 전 후로 받는 것이므로 그 차이가 크지 않다고 생각하기 때문이다. 그러나 최근래의 이자 지급일이 휴일인 경우와 만기가 휴일인 경우는 채권 가격 자체에 영향을 미치는 요인이 될 수 있으므로 채권 가격에서 차이가 난다. (이는 채권 가격 계산 로직을 설명할 때 더 자세히 설명하도록 한다)

일자의 영업일에 따른 미세한 차이 외에도 채권 발행 조건 자체가 날짜계산을 반드시 필요로 하는 경우도 존재한다.
예를 들어 채권의 이자가 달의 말일(해당월의 마지막날)에 지급하기로 했다면 매달 말일은 달에 따라 28일, 29일, 30일, 31일 등 다양한 경우가 포함되어 있으므로 날짜 계산에 따라 채권 가격이 달라질 수 있다. 이러한 빈번한 날짜 계산을 DBMS에서 실행한다면 DBMS에 의존적이 될 것이며 유연하게 구성한다고 해도 빈번한 IO발생으로 속도저하가 발생할 수 있다.
따라서, 채권 가격 계산을 위한 날짜 계산을 위한 Calendar 계산 로직은 DBMS와는 별도로 채권 가격계산 library 내에 포함되어야 한다.
날짜 계산을 위한 기본 함수를 만드는 방법으로는

1. 날짜를 시간으로 환산하여 연산을 실시하고 다시 날짜로 환산하는 방법
2. 날짜 구조체와 날짜 연산 함수를 별도로 만들어 사용하는 방법
3. 언어에서 제공하는 날짜 관련 함수를 그대로 사용하는 방법
등으로 나눌 수 있다.

어떤 방법을 사용해도 큰 차이는 없으나 일단 2번 방법을 사용해서 구현했다. 이유는 C 언어로 구현하기에는 날짜 관련 함수가 너무 미약하고, 말일(해당월의 마지막날짜), 1일 등을 표현하기 위해 time, date structure를 계속 변환하는 것은 번거로운 편이기 때문이다.

별도의 구조체를 사용하기로 했기 때문에 구조체의 초기화(0으로 reset, year/month/day to structure), 변환(string to structure, structure to string, set to lastday),
계산(term to integer, add two structure, add integer to structure, interval as year/month/day), 체크(isLastday, isDate) 등의 함수들이 필요하다.

간단한 함수 작성을 위해 (연산 속도에는 불리하지만) 각 함수 계산시 정상적인 날짜 형식은 마지막에 체크한다. 즉, 계산 결과가 0월 13일이면 전년 12월 13일로 변환하고, 2월 30일이면 3월 1일 또는 3월 2일이 되도록 보정해주는 연산을 한다(adjust).
이 때 연산의 편의를 위해 0월은 31일, 2월은 28일까지 있도록 하였다. 윤년이 있는 경우 2월 말일을 계산하거나 2월 29일 발행인 채권의 경우 등과 같이 2월 29일이 필요한 경우 2월 말일로 변환하는 함수(set to lastday)를 적용해야 한다.

채권의 가격 계산 – 2

채권 가격을 계산하기 위해서는 채권의 발행정보를 이용하여 현금 흐름을 추정/계산해서 각 현금흐름을 현재가치로 할인해야 한다. 채권의 현금 흐름 형태는 채권의 종류에 따라 다르게 나타난다. 국내에서 발행되고 유통되는 채권의 종류는 채권의 현금 흐름(이자지급 방법과 시기)에 따라 할인채, 단리채, 복리채, 이표채로 구분할 수 있으며 원금 상환 방법에 따라 분할상환채권과 만기일시상환채권으로 구분할 수 있으며 이자지급 방법이 중간에 변경되는 경우도 있다. 일반적인 국내채권은 할인채, 단리채, 복리채, 이표채 정도만 이해하면 된다.
채권의 가격계산에서 또한 중요한 요소는 이자금액의 결정인데 이자는 일반적인 채권의 경우 발행시 매 이자지급 시기에 지급될 이자지급액과 그 상환 금액이 확정되어 있다. 그러나 일반적이지 않은 채권의 경우 이자지급액이나 상환 금액이 바뀌는 경우도 있다. 변동금리부 채권(FRN)의 경우 조건에 따라 이자지급액이 다른 금리들의 변동에 따라 변한다.

1. 할인채 : 할인채는 만기시에 채권 원금을 지급하고 발행시에는 그 원금을 할인해서 발행한다. 예를 들어 3년만기 이표율 3%인 채권은 발행시에 원금을 3%로 3년간 할인한 금액으로 발행된다. 할인채는 별도로 이자를 따로 지급하지않는다.
2. 단리채 : 채권 이자가 일년에 한번 발생하는 채권으로 실제 이자는 만기시에 한꺼번에 지급하는 채권이다.
3. 복리채 : 복리채는 채권에 의해 발생한 이자에 계속 이자가 붙는 채권이다. 예를 들어 3% 이자율로 6개월마다 이자가 발생하는 경우 6개월째 이자가 발생하고 또 6개월이 지나면 새로운 이자가 발생하고 6개월째 발생한 이자에도 역시 이자가 발생한다.
4. 이표채 : 이표채는 이자지급 시기마다 발생하는 이자를 실제로 채권 보유자에게 지급하는 채권이다.

이자지급 시기에 대한 이해와 함께 이자와 원금이 얼마인지도 알아야 한다. 이자는 채권발생시 정해진 이표율로 계산하는데 이표율이 아닌 이자율 조건에 따라 변하는 경우 그 이자율을 추정하거나 계산하여 그 결과(이자율,이표율)로 이자를 계산한다.
FRN의 경우 국고채 1년 금리와 CD91일물 금리의 차이 또는 CD91일물 금리의 3개월 평균 등으로 이자율이 정해지는 경우도 있다. 이자율이 일정하지 않은 경우 그 조건에 따라 미래의 이자율을 추정하여 현금 흐름을 계산하여야 한다.