ExtJs – Server에서 Tree구조 데이터를 JSON 데이터로 전송

ExtJs의 Tree Panel구조를 이용하여 메뉴나 선택항목을 구성하는 경우 데이터를 Ajax 등을 통해 Data Model로 내려받아 Panel에 뿌려야 한다(그렇지 않으면 Tree를 갱신하기도 어려워지고 공통모듈을 사용하기 어려워 진다).
이 때 서버쪽에서 Tree구조의 json 데이터를 내려주기 위해서는 결국 서버쪽에서 json데이터를 만들기 전 먼저 tree 구조로 데이터를 재정렬해서 json데이터 형태로 전환해야 한다.

많은 검색 끝에 찾은 팁은 다음과 같다.

데이터 조회(php)

...
db조회
...
    $data = array();
    $i = 0;
    while ( $row = dbnext($result) ) { // 쿼리 결과를 loop 돌면서 배열에 채우는 부분
          $data[$i] = array();
          $data[$i]['id'] = $row[0];    // 정렬을 위한 키값을 읽음. 이를 id라고 하는 field에 저장
          $data[$i]['parent_id] = $row[4];  // 정렬을 위해 부모(parent) 키값을 읽음
          $data[$i]['leaf'] = true;         // 모든 노드를 잠정적으로 leaf로 저장
          for ( $idx = 1; $idx <= 3; $idx++ ) { // 조회한 field값을 배열에 입력 받음 
              $field_name = "field" . $idx;
              $data[$i][$field_name] = $row[$idx];
          }
          $i++;
    }
...
db close

정렬하는 부분(php)

         $itemsByReference = array();
         foreach($data as $key => &$item) {
               $itemsByReference[$item['id']] = &apm;$item;
         }

         foreach($data as $key => &$item)
               if($item['parent_id'] && isset($itemsByRefence[$item['parent_id']])) {
                    $itemsByReference[$item['parent_id']['children'][] = &$item;
                    $itemsByReference[$item[parent_id']['expanded'] = true;
                    unset($itemsByReference[$item['parent_id']]['leaf']);
               }

         foreach($data as $key => &$item) {
               if($item['parent_id'] && isset($itemsByReference[$item['parent_id']]))
                    unset($data[$key]);
         }

         if ($i > 0) {
             print json_encode($data);
         }

php에서 지원하는 json과 배열의 call by reference가 jsp에서는 지원되지 않는다.
그래서 json은 별도 class를 사용해서 해결했고(org.json.*), 데이터의 정렬은 stack 구조를 이용하여 구현했다.

데이터 조회(jsp)

    JSONArray   jData = new JSONArray();
    HashMap subData;
...
    데이터 조회
...
       i = 0;
       while ( row.next() ) {
            subData = new HashMap();
            subData.put("id", row.getString(2));         // 키값을 입력한다.
            if ( i == 0 ) thisTermAmount = Double.valueOf(row.getString(13));
            for ( idx = 1;  idx <= 3; n++ ){
                  subData.put("field" + String.valueOf(idx), row.getString(idx));
             }
             subData.put("seq", row.getString(4));    // seq와 depth 필드가 필수다
             subData.put("depth", row.getString(5));
             subData.put("parent_id", row.getString(6));
             subData.put("leaf", true);
             jData.put(i, subData);
             i++;
        }
     if ( i > 0 ) 
             out.println(Utils.TreeJson(jData).toString());

배열을 그대로 이용하여서는 json으로 변환하기 힘들어 JSONArray와 HashMap()을 이용하여 데이터를 저장하여 이를 다시 재정렬하는 구조로 작성하였다.

정렬하는 부분(jsp)

import java.util.*;
import org.json.*;

public class Utils {

    Utils() {}

    public static JSONArray TreeJson(JSONArray jData)
    {
        int     len, m;
        Stack   s1 = new Stack();
        Stack   s2 = new Stack();
        JSONObject  j1, j2;

        try {
            j1 = jData.getJSONObject(0);
            s1.push(j1);
            for ( m = 1; m < jData.length(); m++) {
                j2 = jData.getJSONObject(m);
                if ( Integer.parseInt(j1.get("depth").toString()) <= Integer.parseInt(j2.get("depth").toString()) ) {
                     s1.push(j2);
                     j1 = j2;
                 }
                 else if ( Integer.parseInt(j1.get("depth").toString()) > Integer.parseInt(j2.get("depth").toString())  ) {
                    while (  Integer.parseInt((s1.peek()).get("depth").toString()) > Integer.parseInt(j2.get("depth").toString()) ) {
                        JSONArray children = new JSONArray();
                        int     lvl = Integer.parseInt((s1.peek()).get("depth").toString());
                        while ( Integer.parseInt((s1.peek()).get("depth").toString()) == lvl ) {
                            s2.push(s1.pop());
                        }
                        while(s2.size() > 0 ) {
                            children.put(s2.pop());
                        }
                        j1 = s1.pop();
                        j1.put("children", children);
                        j1.remove("leaf");
                        j1.put("leaf", false);
                        s1.push(j1);
                    }
                    s1.push(j2);
                    j1 = j2;
                }
            }

            while ( s1.size() > 0 && Integer.parseInt((s1.peek()).get("depth").toString()) > 1 ) {// clear stack
                JSONArray children = new JSONArray();
                int     lvl = Integer.parseInt((s1.peek()).get("depth").toString());
                while ( Integer.parseInt((s1.peek()).get("depth").toString()) == lvl ) {
                    s2.push(s1.pop());
                }
                while(s2.size() > 0 ) {
                    children.put(s2.pop());
                }
                j1 = s1.pop();
                j1.put("children", children);
                j1.remove("leaf");
                j1.put("leaf", false);
                s1.push(j1);
            }
        }
        catch( JSONException je) {
        }

        return new JSONArray(s1);
    }
}

java expert가 아니라 코드가 비효율적으로 구성되었을 수 있으나 일단 php에 없는 json함수와 tree 정렬 함수를 구현했다.

실행 결과는 tree 구조의 데이터를 json 형태로 보내게 된다.

실행 결과 예

{id:001,field0:root,parent_id:NULL,leaf:false,child:[{id:002,field0:'노드1',parent_id:001,leaf:true},{id:003,field0:'노드100',parent_id:001,leaf:true}]}

 

 

 

채권가격계산 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을 산출하면 된다.