채권가격계산 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

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

Extjs 하드코딩으로 연습하기 – 3

main이 되는 Frame 화면의 메뉴 부분을 함수로 만드는 부분은 사실 효용성이 크지 않을 수 있다. 왜냐하면 frame은 한 사이트 내에서 대부분 하나 정도만 차지하고 tree menu와 같이 반복적인 부분은 그렇게 많지 않기 때문이다.
사이트의 내용이 정보 조회가 많은 금융 사이트인 경우 조회되는 대부분의 내용은 grid, chart 등으로 화면에 표시한다. 이 때 grid와 chart를 하나씩 개발하는 경우 많은 인력과 시간이 소요되며 유사한 grid를 여러 페이지에 걸쳐서 복사&붙여넣기(Copy&Pate)하는 것 또한 쉬운 일은 아니다.
이런 경우 역시 php로 공통함수를 개발함으로써 복잡한 문제를 일정부분 쉽게 해결할 수 있다.

먼저 common.php란 공통 파일을 하나 만든다. grid.php나 chart.php로 분리해서 따로따로 만들어도 상관없다. 어짜피 화면에는 호출한 함수 부분만 포함되기 때문이다.

common.php

<?php

// grid를 화면에 뿌려주는 함수
// gridName : Unique한 gridName
// dataURL : ajax로 데이터를 호출하기 위한 URL 주소(실제 데이터는 당연히 ajax로 받아와야 한다)
// header1 : grid의 header(컬럼명) 1은 가장 윗줄
// header2 : grid의 두번째 header(컬럼명), 첫번째와 두번째가 동일하면 하나의 cell로 merge된다. header2가 null이면 두번째 컬럼은 보이지 않는다.
// columnWidth : 배열형태로 받는 각 컬럼의 넓이
// datafield : 서버로부터 수신해서 첫번째 컬럼별부터 보여줄 field명 (json형태로 데이터를 받을 것이므로 field명을 반드시 써야한다.)
// align : 배열형태로 받는 각 컬럼별 정렬 방식(left, center, right)
// targetid : grid를 화면에 위치하기 위한 element id. 해당 element에 overriding한다.
// gridHeight : grid 전체의 높이
// autoload : 처음 grid생성시 데이터를 loading할 것인지 여부. string형태.
function GridType1($gridName, $dataURL, $header1, $header2, $columnWidth, $datafield, $align, $targetid, $gridHeight, $autoload="false")
{
?>
		Ext.onReady(function(){
		    Ext.define('<?=$gridName?>',{
		        extend: 'Ext.data.Model',
		        proxy: {
		            type: 'ajax',
		            reader: 'json'
		        },
		        fields: [ <?
		        $arr_count = count($datafield);
		       for ( $i = 0; $i < $arr_count; $i++ ) {
		        	echo "'". $datafield[$i] . "'";
		        	if ( $i < $arr_count - 1) echo ","; 		        }  ?> ]
		    });

		    // create the Data Store
		    var store = Ext.create('Ext.data.Store', {
		        model: '<?=$gridName?>',
		        autoLoad: <?=$autoload?>,
		        proxy: {
		            type: 'ajax',
		            url: '<?=$dataURL?>',
		            reader: {
		                type: 'json'
		            }
		        }
		    });
		    // create the grid
		    Ext.create('Ext.grid.Panel', {
		    	id: '<?=$gridName?>',
		        store: store,
		        columnLines: true, // check....  
		        font: '10px',
		        cls: 'grid-row-span',
		        columns: [ <?
		        	if ( count($header1) == count($header2) ) {
		        		$header_count = count($header1);
		        		for ( $i = 0; $i < $header_count; $i++ ) { 		        			if ( strcmp($header1[$i], $header2[$i] ) != 0 || 		        				($i > 0 && strcmp($header1[$i-1], $header1[$i] ) == 0) || 
		        				($i+1 < $header_count && strcmp($header1[$i+1], $header1[$i] ) == 0 )) { 		        				echo "{text: '{$header1[$i]}',\n"; 		        				echo "	columns: [\n"; 		        				while ( $header_count > $i && strcmp($header1[$i+1], $header1[$i]) == 0) {
		        					echo "{text: '{$header2[$i]}', style:'text-align:center', align:'".$align[$i]."', width:{$columnWidth[$i]}, minWidth:{$columnWidth[$i]}, autoSizeColumn: true, dataIndex:'{$datafield[$i]}'}";
		        					if ( $header_count > $i+1 && strcmp($header1[$i+1], $header1[$i]) == 0 ) 
		        						echo ",";
		        					$i++;
		        				}
	        					echo "{text: '{$header2[$i]}', style:'text-align:center', align:'".$align[$i]."', width:{$columnWidth[$i]}, minWidth:{$columnWidth[$i]}, autoSizeColumn: true, dataIndex:'{$datafield[$i]}'}";
		        				echo "]\n";
		        				echo "}\n";
		        			}
		        			else {
			        			echo "{text: '{$header1[$i]}', style:'text-align:center', align:'".$align[$i]."', width:{$columnWidth[$i]}, minWidth:{$columnWidth[$i]}, autoSizeColumn: true, dataIndex:'{$datafield[$i]}'}";
			        		}
		        			if ( $i < $header_count - 1 ) echo ",\n";
		        		}
		        	}
		        	else if (count($header2) == 0 ) {
		        		$header_count = count($header1);
		        		for ( $i = 0; $i < $header_count; $i++ ) {
	        				echo "{text: '{$header1[$i]}', style:'text-align:center', align:'".$align[$i]."', width:{$columnWidth[$i]}, minWidth:{$columnWidth[$i]}, autoSizeColumn: true, dataIndex:'{$datafield[$i]}'}";
		        			if ( $i < $header_count - 1 ) echo ",\n"; 		        		} 		        	} 		        ?>
		        ],
		        renderTo:'',
		        height: ,
		        layout: 'fit',
		        viewConfig: {  /* check... */
		            stripeRows: true,
			        listeners: {
			            refresh: function(dataview) {
			                Ext.each(dataview.panel.columns, function(column) {
			                    if (column.autoSizeColumn === true)
			                        column.autoSize();
			                })
			            }
			        }
		        }
		    });
		});
<? 	} 
?>

실제로 grid는 표현하는 데이터에 따라서 약간씩 모양이 달라질 수 있으므로 하나의 grid만 사용할 수는 없다. 따라서 gridType1, 2, 3 등으로 서로 다른 이름을 주었다.

이제 이렇게 공통으로 정의된 library를 화면 파일에서 호출만 하면 된다.

SGAMS_1230_SUB.php
<?
 	include 'AMS_top.php';
 	include '../lib/mssql.php';
 	include '../lib/common.php';
         $conn_ms = ms_dbconn(); 
?>
<script type="text/javascript">
<?
 	$header1 = array('일자', '운용규모', '운용규모','운용규모', '기간수익률', '기간수익률'); 	$header2 = array('일자', '매수', '매도','평가금액', '수익률', '누적수익률');
 	$columnWidth = array(150, 150, 150, 150, 150, 150 );
 	$datafield = array('field0', 'field1', 'field2', 'field3', 'field4', 'field5');
 	$align = array('center', 'right', 'right', 'right', 'right', 'right');
	$dataUrl1 = "reply.php?peGrp=A&FromDate=2013-01-04";
        // Grid함수를 호출하는 부분
 	GridType1('Grid2', $dataUrl1, $header1, $header2, $columnWidth, $datafield, $align, 'grid-main2', 200); 
?>

function onClick();
...

이 grid는 가로 또는 세로로 헤더 내에 동일한 텍스트가 있으면 merge가되는 구조로 되어 있어서 header가 2개의 array로 정의되어 있다. 그리고 각 column width도 array로 입력 받도록 하였으며, 각 셀별 정렬기준인 align도 array로 입력 받는다.
여기서 datafield array가 있는데 이는 각 grid의 실제 내용을 ajax로 조회해서 조회 결과를 grid내 column에 뿌릴 때 column들과 ajax의 json 구조와 일치시키기 위한 부분이다. 즉, server side에서 조회된 결과가 json으로 화면에 뿌려지는데 이 때 조회 결과의 json header와 각 column을 매치시키기 위해 사용한다.
이 때 서버측(ajax에 응답하는 서버)

client                                                           server

presentation layer : grid(column1, column2, column3...)
data layer : data model(field1, field2, field3...)                 dbms(field1, field2, field3...)
data : ajax(json: field1, field2, field3...)  <-----------------> reply.php(json : field1, field2, ...)
reply.php 결과
[{"id":"RISKA001","field1":"전체자금(잡비포함)","field2":"N","field3":"2010-01-01","field4":"2013-12-31","field5":"","field6":"","field7":0,"field8":""},
{"id":"RISKA002","field1":"전체자금(잡비제외)","field2":"N","field3":"2010-01-01","field4":"2013-12-31","field5":"","field6":"","field7":0,"field8",""},
{"id":"RISKA003","field1":"수입","field2":"N","field3":"2010-01-01","field4":"2013-12-31","field5":"","field6":"","field7":0,"field8":""},
{"id":"RISKA004","field1":"매출","field2":"N","field3":"2010-01-01","field4":"2013-12-

서버측에서 데이터를 보내주는 프로그램은 데이터를 json으로 보내준다.
즉 서버 -> json -> 전송 -> web brower -> json -> ajax 엔진 -> javascript -> grid 의 흐름을 보인다.
json은 php에 구현되어 있는 기본 기능이며 string으로 데이터를 보낼 수 있는 경량화된 좋은 프로토콜이다. 단, 숫자와 문자열에 대한 구분을 혼동하지 않도록 해야 한다.
개념이 조금 어려울 수도 있는데 직접 구현해서 실행한 다음 화면에서 소스보기를 선택해서 보면 grid의 field array 파라미터가 화면에 어떻게 매핑되는 지 쉽게 확인할 수 있다.

ExtJs 하드코딩으로 연습하기 – 2

첫번째 복합 frame으로 이루어진(feed sample을 응용한) 샘플을 만들어서 실행까지는 어떻게든 해 볼 수 있지만 extjs를 이용한 모든 코딩이 이렇게 하드코딩으로만 이루어진다면 복잡한 디버깅과 끝없이 반복되는 bracket([,{,},]) 들 때문에 더이상 쳐다보고 싶지도 않게 된다.

아무래도 코드를 정리하고 모듈로 쪼개는 것이 좋겠다. 모듈로 쪼개기 위해서는 아무래도 기능적으로나 형태적으로 반복되는 부분을 잘 구분하고 전체적인 형태를 함수 호출 형태로 구성하는 것이 가장 쉬운 방법이다. 그러나 지나친 함수화는 자칫 파편화를 일으켜 오히려 전체적인 흐름을 방해할 수도 있으므로 간단한 수준의 함수화를 진행하도록 하자.

ExtJs는 화면에서 실행되는 client side script임을 여러차례 강조했는데 화면이 이미 script 언어이므로 이를 다시 모듈화하여 함수로 묶는 부분은 그렇게 큰 효용성이 없다. 물론 단순 반복적인 작업은 당연히 함수로 구현하는 것이 좋으며 소스의 크기가 작으면 로딩 속도와 실행속도에서 약간의 이득이 있겠지만 개발자의 입장에서는 아주 미세한 로딩 속도보다는 개발과 디버깅의 편의성이 훨씬 중요하다.
그래서, Server side script에서 화면 부분을 모듈로 정리하기로 했다.

우선 앞에서 설명한 예제를 php 언어로 재작성하였다.

index.php
<?php
// session 처리와 login 처리 
...
// db를 위한 부분
     include 'lib/mysql.php';
     include 'lib/check_user.php';
//
?>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=7"> <title>F-square</title> 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
<meta http-equiv="expires" content="0" /> 
<meta http-equiv="Cache-control" content="no-cache" /> 
<meta http-equiv="Cache-control" content="max-age=0" /> 
<meta http-equiv="pragma" content="no-cache" />
<!-- 적용된 테마 외에 추가적인 style정의를 모아 놓은 sheet -->
<link rel="stylesheet" type="text/css" href="./design/css/menu_blue.css" media="screen" />

<!-- extjs 핵심 library -->
<script type="text/javascript" src="./js/include-ext.js"></script> 

<script type="text/javascript" src="./js/options-toolbar.js"></script>

<script type="text/javascript" src="./js/lib.js"></script>
<?
      include 'js/tree-menu.php';
      $conn_db = mysql_connect();
?>

<style type="text/css">
// style, icon class등 추가
</style>

Ext.onReady(function() {
    Ext.QuickTips.init();
    var viewport = Ext.create('Ext.Viewport', {
    id: 'border-example',
    layout: 'border',
    items: [
        // create instance immediately
        Ext.create('Ext.Component', {
            region: 'north',      // 화면의 북쪽(윗쪽)에 위치
            height: 58, // give north and south regions a height 
            autoEl: {
                tag: 'div'
            }
        }), {

            // lazily created panel (xtype:'panel' is default)
            region: 'south',  // 화면의 남쪽(아랫쪽)에 위치하는 panel
            contentEl: 'south', // div id='south'라고 하는 content를 overriding함
            split: true,  // 크기조정이 가능한 split
            height: 70,
            minSize: 100,
            maxSize: 200,
            collapsible: true,
            collapsed: true,
            title: 'Ticker',
            margins: '0 0 0 0'
        },

        {
            region: 'west',            // 화면의 서쪽(왼쪽)에 위치
            stateId: 'navigation-panel',
            id: 'west-panel', // see Ext.getCmp() below
            title: 'Menu',
            split: true,  // 크기조정이 가능한 split
            width: 200,
            minWidth: 175,
            maxWidth: 400,
            collapsible: true,  // 닫힘 기능이 가능
            animCollapse: true,  // 닫힐때 애니메이션 적용
            margins: '0 0 0 5',
            layout: 'accordion',  // accordion layout으로 탭들이 존재
            items: [
<?
      drawTreeMenu('favorit', $conn_db);
?>
                     ,
<?
      drawTreeMenu('main_menu', $conn_db);
?>
                    , 
<?
      drawTreeMenu('config', $conn_db);
?>

            ]
        },

        Ext.create('Ext.tab.Panel', {   // 화면의 가운데 tab panel생성 (mainpanel이라고 명명)
            region: 'center', // a center region is ALWAYS required for border layout
            deferredRender: false,
            bodyStyle: 'background-size:contain; background-image:url(http://www.nyewall.com/images/2013/07/background-flowers-sky-wallpaper-blue-nice-wallpapers.jpg);',
            id: 'MainPanel',
            activeTab: 0 // first tab initially active
        })]
    });

    // get a reference to the HTML element with id "hideit" and add a click listener to it
    Ext.get("hide-it").on('click', function(){
        var w = Ext.getCmp('west-panel');
        w.collapsed ? w.expand() : w.collapse();
    });
});
</script>

<body bgcolor="#F0F0FF" /* onload=reqeustFullScreen();*/>
<table width=100% border=0 cellpadding=0 cellspacing=0 >
<tr><td align=left width=145 class="logo">AMS</td><td align=left class="logo"></td></tr>
<tr><td align=left width=145 class="logo_under">&nbsp; Management System</td><td class="logo_under">- AMS Consulting</td></tr>
</table>

<!-- use class="x-hide-display" to prevent a brief flicker of the content -->
<div id="west" class="x-hide-display">
<p>west panel</p>
</div>
<div id="center2" class="x-hide-display">
<a id="hide-it" href="#"> </div>
<div id="center1" class="x-hide-display">
</div>
</div>
<div id="south" class="x-hide-display">
<p>정상상태</p>
</div>
<br/>

이제 index.php가 조금 간단하게 줄어들었다. 추가로 수정해야 하는 부분은 drawTreeMenu를 구성부분이다. php의 장점 중 하나가 함수 내에 html 텍스트를 임의로 삽입할 수 있고 문법 체크가 덜 엄격하다는 부분으로 비슷한 기능으로 비교대상이 되곤 하는 jsp로 구성하는 경우와는 약간 차이가 난다(jsp로도 구성은 가능하나 일부 변경작업이 필요하다).

js/tree-menu.php
<?php
function drawTreeMenu( $title, $dbconn )
{
     // 보안을 위해 추가적인 session check와
     // theme, cookie 등을 체크

     if ( $title == 'main_menu') {
        $str_id = 'Fund-manager';
        $icon   = 'info';
        $cond_clause = 'user_level <= menu_level ';
    }
    else if ( $title == 'favorit' ) {
        $str_id = 'favorit';
        $icon   = 'nav';
        $cond_clause = 'favorit = true';
    }
?>
       {
        title: '즐겨찾기',
        xtyle: 'treepanel',
        id: '<=$str_id>',
        iconCls: '<=$icon>',
        margins: '2 2 0 2',
        autoScroll: true,
        ...
        root:{ text: "Root Node",
              children: [
<?
                 // 메뉴를 조회하기 위한 query문
                 $sql = "SELECT menu_title, menu_num, leaf ";
                 $sql.= "FROM menu_table ";
                 $sql.= "WHERE use_yn = 'Y'  ";
                 $sql.= $cond_clause;
                 ...
                 menu를 화면에 출력하는 부분
?> 
              ]
          },
          listeners: {
                itemclick: function(s,d) {
                 ...
          }
       }
<?
}
?>

php로 extjs의 복잡함을 줄이기는 역부족이다. 그러나, Style sheet를 잘 정의하고 반복적인 컴포넌트 호출 (chart, grid 등)부분을 function으로 분리하기만 해도 디버그와 화면 개발이 훨씬 쉬워진다.

ExtJs 하드코딩으로 연습하기 – 1

Extjs를 설치했다면 이제 sample등을 잘 활용하여 사용하기만 하면 된다.
ExtJs를 사용하기 위해서는 ExtJs의 기본 구조를 잘 이해해야 한다.
ExtJs는 Client Side Script인 javascript로 이루어진 Web 개발 library이다. 이 library가 공통으로 화면에 로딩되고 그 상태에서 사용자가 작성한 화면 script가 실행되는 형식이다. Sencha에서 제안하는 또 다른 방법은 extjs를 활용하여 화면 구성에 필요한 새로운 library를 생성하여 이를 배포하는 방식이다. script프로그램의 특성상 화면의 로딩 속도가 빨라야 하고 화면에 표시되는 부분(View)와 데이터를 처리하는 부분(Model)을 분리하여 코드의 재가용성도 높일수 있는 방법이다(MVC Model).
그러나 소규모의 개발에서는 오히려 사용이 어렵고 코드의 재가용성에 대한 요구가 높지 않은 경우 불필요한 작업까지 포함될 수 있고, 소스가 짧은 경우 로딩속도가(script언어의 특성상 개발시 포함된 space와 tab 문자들이 모두 소스에 포함되어 있어 파일 크기가 커진다) 크게 차이나지 않기 때문에 간단히 script-engine + script 방식으로 구성해도 충분한 것 같다.
사실 대부분의 web browser는 javascript(또는 vbscript)를 분석(compile)하고 실행하기 위한 엔진(gecko와 같은 레이아웃 엔진내에)을 탑재하고 있다. 여기에 낮은 레벨의 javascript와 style-sheet를 결합하여 매크로처럼 화면 구성용 함수들을 만들어 놓은 library가 ExtJs이므로 이를 그냥 가져다가 쉽게 사용하기만 하면 되니, 복잡하게 새로운 개발이나 대단위 프로젝트는 생각하지 않기로 했다.
대신 화면 개발시 유사한 화면에 반복적으로 포함되는 코딩을 조금 더 쉽게 처리하기 위한 방법을 고민하다 보니 오히려 Server side의 library가 더 적합하다는 생각을 하게 되었다.

우선 무작정 하드코딩으로 ExtJs를 이용한 page를 개발하기 위해서는 extjs library를 서버에 설치하고 이를 각 page가 로딩될 때 같이 로딩될 수 있도록 한다.

<meta http-equiv="X-UA-Compatible" content="IE=7"> <title>F-square</title> 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
<meta http-equiv="expires" content="0" /> 
<meta http-equiv="Cache-control" content="no-cache" /> 
<meta http-equiv="Cache-control" content="max-age=0" /> 
<meta http-equiv="pragma" content="no-cache" />
<!-- 적용된 테마 외에 추가적인 style정의를 모아 놓은 sheet -->
<link rel="stylesheet" type="text/css" href="./design/css/menu_blue.css" media="screen" />

<!-- extjs 핵심 library -->
<script type="text/javascript" src="./js/include-ext.js"></script> 

<script type="text/javascript" src="./js/options-toolbar.js"></script>

<script type="text/javascript" src="./js/lib.js"></script>

디렉토리 구조는 각자가 설치하기 나름이므로 설치된 경로를 그대로 써 주면되겠다.
먼저 첫번째에 meta로 잔뜩 나오는 부분은 주로 web browser의 cache를 지워주기 위한 부분이다. 각 페이지 내용을 web browser가 cache(PC상에 임시저장)하고 있으면 개발 작업시 변경부분이 적용되지 않아 혼란을 줄 수 있다. 따라서 가급적 cache는 지워버리도록 하는 것이 좋다. 반대의 경우 static한 내용을 반복적으로 보여줘야 할 때는 cache되어 있는 것이 속도 면에서는 훨씬 유리하다.

이제 본격적으로 코딩을 시작해 보자.
ExtJs설치시 포함되어 있는 예제 파일을 하나씩 소스에 추가해서 실행시켜 보면서 그 구조를 파악하면 훨씬 쉽게 이해할 수 있다.

여기서는 FeedViewer Demo와 Tree, Tab을 이용하여 화면을 구성하고 내부 화면은 iframe으로 간단히 정보조회 화면을 구성하도록 하였다.
편집기는 Text Editor와 vi를 혼용하였고, 테스트는 Web brower로 직접 화면을 보면서 수정을 하였다(Subscription가입을 하지 않아 UI-Designer를 구하지는 못했다).

<script type="text/javascript">

Ext.onReady(function() {
    Ext.QuickTips.init();
    var viewport = Ext.create('Ext.Viewport', {
    id: 'border-example',
    layout: 'border',
    items: [
        // create instance immediately
        Ext.create('Ext.Component', {
            region: 'north',      // 화면의 북쪽(윗쪽)에 위치
            height: 58, // give north and south regions a height 
            autoEl: {
                tag: 'div'
            }
        }), {

            // lazily created panel (xtype:'panel' is default)
            region: 'south',  // 화면의 남쪽(아랫쪽)에 위치하는 panel
            contentEl: 'south', // div id='south'라고 하는 content를 overriding함
            split: true,  // 크기조정이 가능한 split
            height: 70,
            minSize: 100,
            maxSize: 200,
            collapsible: true,
            collapsed: true,
            title: 'Ticker',
            margins: '0 0 0 0'
        },

        {
            region: 'west',            // 화면의 서쪽(왼쪽)에 위치
            stateId: 'navigation-panel',
            id: 'west-panel', // see Ext.getCmp() below
            title: 'Menu',
            split: true,  // 크기조정이 가능한 split
            width: 200,
            minWidth: 175,
            maxWidth: 400,
            collapsible: true,  // 닫힘 기능이 가능
            animCollapse: true,  // 닫힐때 애니메이션 적용
            margins: '0 0 0 5',
            layout: 'accordion',  // accordion layout으로 탭들이 존재
            items: [{             // accordion layout으로 존재하는 탭의 아이템들
                    title: '즐겨찾기',
                    xtype: 'treepanel',             //  tree panel object 생성
                    id: 'favorit',
                    iconCls: 'nav',                 // style로 정의된 'nav' icon
                    margins: '2 2 0 2',
                    autoScroll: true,
                    rootVisible: false,
                    root: { text: "Root Node",      // tree의 node의 정의
                        children:[
                            {text:'메뉴1', id: 'PMS_0001', leaf:true},   // 각각의 메뉴
                            {text:'메뉴100', id: 'PMS_0100', leaf:true}
                        ]
                    },

                    listeners: {
                        itemclick : function(s,d){                // tree menu를 click 했을때의 action
                            if ( d.data.leaf ) {
                                var scr_id = "S" + d.data.id;
                                var scr_title = d.data.text;
                                var scr_filename = "PMS/SG" + d.data.id.trim() + "_SUB.php?theme=neptune";    // PMS디렉토리 내에있는 php파일명 생성
                                var tab = Ext.getCmp( scr_id );
                                var mainPanel = Ext.getCmp('MainPanel');
                                if ( tab != null ) {
                                    mainPanel.setActiveTab(tab);  // 만약 이미 화면이 열려있으면 해당 화면을 activate시킴
                                }
                                else {
                                    var tab = new Ext.Panel({      // 새로운 창에 화면을 open
                                        id: scr_id,
                                        title: scr_title,
                                        closable:true,
                                        autoScroll: false,
                                        border:false,
                                        layout:'fit',
                                        frame: false,
                                        html: '<iframe src="' + scr_filename + '" frameborder="no" allowtransparency="y" vspace="0" hspace="0" width="100%" height="100%"></iframe>'  // iframe으로 tab 내에서 화면을 open함
                                    });

                                    mainPanel.add(tab);
                                    mainPanel.setActiveTab(tab);  // tab을 add하고 activate함
                                }
                            } // if ( d.data.leaf ) 의 끝
                        } // itemclick : function(s,d)의 끝
                    } // listener의 끝
                    }, {
                        title: '자금관리',
                        xtype: 'treepanel',
                        id: 'Fund-manager',
                        iconCls: 'info',
                        margins: '2 2 0 2',
                        autoScroll: true,
                        rootVisible: false,
                        root: { text: "Root Node",
                            children:[
                                { text: '<b>메뉴1</b>', id: 'PMS_0001', leaf:true},
                                { text: '<b>메뉴2</b>', id: 'PMS_0002', leaf:true},
                                { text: '<b>메뉴100</b>', id: 'PMS_0100', leaf:true},
                                { text: '<b>메뉴200</b>', id: 'PMS_0200', leaf:true}
                            ]
                        },

                        listeners: {
                        itemclick : function(s,d){
                            if ( d.data.leaf ) {
                                var scr_id = "S" + d.data.id;
                                var scr_title = d.data.text;
                                var scr_filename = "PMS/SG" + d.data.id.trim() + "_SUB.php?theme=neptune";
                                var tab = Ext.getCmp( scr_id );
                                var mainPanel = Ext.getCmp('MainPanel');
                                if ( tab != null ) {
                                    mainPanel.setActiveTab(tab);
                                }
                                else {
                                    var tab = new Ext.Panel({
                                        id: scr_id,
                                        title: scr_title,
                                        closable:true,
                                        autoScroll: false,
                                        border:false,
                                        layout:'fit',
                                        frame: false,
                                        html: '<iframe src="' + scr_filename + '" frameborder="no" allowtransparency="y" vspace="0" hspace="0" width="100%" height="100%"></iframe>'
                                    });

                                    mainPanel.add(tab);
                                    mainPanel.setActiveTab(tab);
                                }
                            } // if ( d.data.leaf ) 의 끝
                        } // itemclick : function(s,d)의 끝
                    } // listener의 끝
                    }, {
                        title: '환경설정',
                        xtype: 'treepanel',
                        id: 'tree-config',
                        iconCls: 'settings',
                        margins: '2 2 0 2',
                        autoScroll: true,
                        rootVisible: false,
                        root: { text: "Root Node",
                            children:[
                                {text:'즐겨찾기 설정', id: 'CFG_1000', leaf:true},
                                {text:'화면 설정', id: 'CFG_2000', leaf:true}
                            ]
                        },

                        listeners: {
                            itemclick : function(s,d){
                                if ( d.data.leaf ) {
                                    var scr_id = "S" + d.data.id;
                                    var scr_title = d.data.text;
                                    var scr_filename = "CFG/S" + d.data.id.trim() + ".php?theme=neptune";
                                    var tab = Ext.getCmp( scr_id );
                                    var mainPanel = Ext.getCmp('MainPanel');
                                    if ( tab != null ) {
                                        mainPanel.setActiveTab(tab);
                                }
                                else {
                                    var tab = new Ext.Panel({
                                        id: scr_id,
                                        title: scr_title,
                                        closable:true,
                                        autoScroll: false,
                                        border:false,
                                        layout:'fit',
                                        frame: false,
                                        html: '<iframe src="' + scr_filename + '" frameborder="no" allowtransparency="y" vspace="0" hspace="0" width="100%" height="100%"></iframe>'
                                    });

                                    mainPanel.add(tab);
                                    mainPanel.setActiveTab(tab);
                                }
                            }
                        }
                    }
                }
            ]
        },

        Ext.create('Ext.tab.Panel', {   // 화면의 가운데 tab panel생성 (mainpanel이라고 명명)
            region: 'center', // a center region is ALWAYS required for border layout
            deferredRender: false,
            bodyStyle: 'background-size:contain; background-image:url(http://www.nyewall.com/images/2013/07/background-flowers-sky-wallpaper-blue-nice-wallpapers.jpg);',
            id: 'MainPanel',
            activeTab: 0 // first tab initially active
        })]
    });

    // get a reference to the HTML element with id "hideit" and add a click listener to it
    Ext.get("hide-it").on('click', function(){
        var w = Ext.getCmp('west-panel');
        w.collapsed ? w.expand() : w.collapse();
    });
});
</script>

<body bgcolor="#F0F0FF" /* onload=reqeustFullScreen();*/>
<table width=100% border=0 cellpadding=0 cellspacing=0 >
<tr><td align=left width=145 class="logo">AMS</td><td align=left class="logo"></td></tr>
<tr><td align=left width=145 class="logo_under">&nbsp; Management System</td><td class="logo_under">- AMS Consulting</td></tr>
</table>

<!-- use class="x-hide-display" to prevent a brief flicker of the content -->
<div id="west" class="x-hide-display">
<p>west panel</p>
</div>
<div id="center2" class="x-hide-display">
<a id="hide-it" href="#"> </div>
<div id="center1" class="x-hide-display">
</div>
</div>
<div id="south" class="x-hide-display">
<p>정상상태</p>
</div>
<br/>

이제 해당 library와 style sheet가 적절한 경로에 설치만 되어 있다면 첫번째 메인 화면이 정상적으로 뜰 것이다.
fundratings
이제 메인화면을 생성했고 앞으로 해야할 파일은 각 메뉴를 클릭했을 때 iframe상에 뜰 각각의 화면 파일을 생성하는 일만 남았다.
그런데 메인화면을 자세히 보면 반복적인 부분들이 보일것이다. 즉, tree menu가 즐겨찾기, 화면메뉴, 환경설정 등과 같이 동일한 형태의 자료구조로 반복되는 것을 볼 수 있는데 이러한 부분을 모두 Hard coding으로 fix하지 말고 서버측에서 동적으로 DB를 조회하여 생성하면 반복작업이 훨씬 줄어들 것이다.

index.php
<header>
...
<script type="text/javascript">

...
<?         include 'lib/db.php';
         // db 조회시작
         ....
         // 조회 결과를 출력
         ...
         while ( $rs = mssql.next() ) {                   if ($row_cnt > 0 ) echo ",\n";
                  echo "{ text : '" . $rs.getString(1) . "', id : " . $rs.getString(2) . ", leaf : " . $rs.getString(5) . "}";
                  $row_cnt ++;
      }

?>
     }
   ]

채권 가격 계산 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)를 적용해야 한다.