국내 주식 휴장일 영업일 캘린더 클래스 만들기

반응형

코인 거래소는 24시간 365일 거래일이지만 국내 주식 시장은 예정된 이벤트(선거일, 대체공휴일 등) 등으로 토요일과 일요일 이외에도 휴장 하게 됩니다. 파이썬은 관련 모듈이 제공되어 즉시 사용가능 하지만 C/C++ 플랫폼에서의 사용을 위하여 주식시장 휴일 캘린더 클래스를 직접 구현해 보았습니다.

1. 휴장일 데이터 확보하기

휴장일 정보는 한국거래소 홈페이지의  KRX 시장 > 시장동향 > 증시일정 > 휴장일 경로에서 확인할 수 있고 엑셀 파일로 다운로드 가능 합니다.

2. 휴장일 데이터 가공 및 소스코드화

KRX에서 제공하는 휴장일 데이터의 메타데이터는 "일자 및 요일", "요일구분", "비고"로서 데이터 샘플은

"2017-01-27", "금요일", "설날"입니다. 프로그램이 휴장일 판단에 실제 필요한 부분은 "일자 및 요일"로서

DWORD 타입의 배열 및 맵으로 관리하기 위하여 엑셀 매크로를 활용하여 YYYYMMDD 형식으로 변환 후 헤더파일을 생성하였습니다. 다음과 같이 관리하면 휴장일이 추가/삭제되더라도 간단하게 변경사항 적용 가능합니다. 소스코드에 포함하는 휴장일 정보는 휴장일 설정파일이 없을 때 기본 설정파일을 생성하기 위한 용도이며 지정 경로에 설정파일이 존재하면 설정파일로부터 휴장일 정보를 읽습니다.

KRX 휴장일 원본데이터 형식

 

헤더파일, 설정파일 생성 매크로
엑셀 매크로로 휴장일 배열 데이터 생성
자동생성 헤더파일-휴장일 값 167개를 의미
휴장일 정보 배열 생성
휴장일 정보 설정 파일
휴장일 배열 소스코드-휴장일 추가 삭제시 소스코드 수정이 필요 없음

 

3. MarketCalendars 클래스

BOOL boIsSession() 의 프로토타입으로 휴장일이면 FALSE, 개장일이면 TRUE를 반환하며 파라미터로 CTime, SYSTEMTIME, DWORD dwYYYYMMDD, LPCTSTR strYYYYMMDD를 받을 수 있도록 구현하였습니다.

BOOL boNextOpenDay()은 오늘 날짜를 넣으면 다음 개장일을 입력 파라미터에 Overwrite 하여 반환합니다.

유의사항으로는 CTime과 SYSTEMTIME의 토요일, 일요일 값이 다릅니다.

진단 모듈등을 통한 설정파일 동적 reloading 기능을 위하여 생성자에서 초기화 하지 않고 별도의 Init, DeInit을 두었습니다.

3.1. CMarketCalendars.h

 

#pragma once

#include "auto-generated/_autogen_noedit_market_holiday_array_size_krx.h"

#define N_CALENDARS_LOW_LIMIT_DATE	(19800101)
#define N_CALENDARS_HIGH_LIMIT_DATE	(21001231)

#define CTIME_DAYOFWEEK_SUNDAY		(1)
#define CTIME_DAYOFWEEK_SATURDAY	(7)

#define SYSTEMTIME_DAYOFWEEK_SUNDAY		(0)
#define SYSTEMTIME_DAYOFWEEK_SATURDAY	(6)

namespace MARKET_CALENDARS
{
	typedef enum class _enMarketCat {
		XKRX,
		MAX
	} TEN_MARKET_CAT;

	typedef CMap<DWORD, DWORD, BOOL, BOOL> TMAP_HOLIDAY_YYYYMMDD;
	typedef struct _market_holiday {
		SRWLOCK srwlock;
		DWORD dwEntityCnt;
		TMAP_HOLIDAY_YYYYMMDD map;
		_market_holiday() {
			dwEntityCnt = 0;
			map.RemoveAll();
			InitializeSRWLock(&srwlock);
		}

		void EraseData(void) {
			dwEntityCnt = 0;
			map.RemoveAll();
		}
	} TST_MARKET_HOLIDAY, *PTST_MARKET_HOLIDAY;
}

using namespace MARKET_CALENDARS;
class CMarketCalendars
{
public:
	CMarketCalendars();
	~CMarketCalendars();

	void vInit(void);
	void vDeInit(void);

	void vLoadMarketHoliday(void);

public:
	BOOL boIsSession(TEN_MARKET_CAT enMarket, const DWORD dwYYYYMMDD);
	BOOL boIsSession(TEN_MARKET_CAT enMarket, CTime& t);
	BOOL boIsSession(TEN_MARKET_CAT enMarket, SYSTEMTIME& t);
	BOOL boIsSession(TEN_MARKET_CAT enMarket, LPCTSTR strYYYYMMDD);

	/* next_open(), return next open day */
	BOOL boNextOpenDay(CTime& t);
	BOOL boNextOpenDay(SYSTEMTIME& t);

	static const DWORD m_arrKRX_Holiday[N_DFLT_ENTITY_MARKET_HOLIDAY_KRX];

private:
	TST_MARKET_HOLIDAY _m_astHolidayMap[static_cast<UINT>(TEN_MARKET_CAT::MAX)];
};

 

3.2. void CMarketCalendars::vLoadMarketHoliday(void)

설정파일을 읽어서 맵에 적재하는 코드입니다. 정적 배열과 설정파일의 내용을 합집합으로 관리합니다.

void CMarketCalendars::vLoadMarketHoliday(void)
{
	/* Check market_holiday_xxx.csv file */
	CString cstrFilePath;
	CFileFind cffFindFile;
	CFile* pcfp = nullptr;
	BOOL boRet;
	PTST_MARKET_HOLIDAY p = nullptr;
	char chBuf[128];

	/* [START] Build KRX Holiday Query Map */
	p = &(_m_astHolidayMap[static_cast<UINT>(TEN_MARKET_CAT::XKRX)]);
	AcquireSRWLockExclusive(&(p->srwlock));
	p->EraseData();
	for (int i = 0; i < N_DFLT_ENTITY_MARKET_HOLIDAY_KRX; i++) {	/* KRX */
		p->map.SetAt(m_arrKRX_Holiday[i], TRUE);
		p->dwEntityCnt++;
	}
	ReleaseSRWLockExclusive(&(p->srwlock));


	cstrFilePath.Format(_T("C:\\noyecube\\market_holiday_krx.csv"));

	boRet = cffFindFile.FindFile(cstrFilePath);
	if (!boRet) {
		DPRINTF("[WARN] C:\\noyecube\\market_holiday_krx.csv Lookup Fail. New market_holiday_krx.csv created with default values.\n");
		pcfp = new CFile();
		boRet = pcfp->Open(cstrFilePath, CFile::modeWrite | CFile::modeCreate | CFile::modeNoTruncate | CFile::shareDenyNone);
		if (FALSE == boRet) {
			;	// do nothing
		}
		else {

			for (int i = 0; i < N_DFLT_ENTITY_MARKET_HOLIDAY_KRX; i++) {
				sprintf_s(chBuf, sizeof(chBuf), "%u\n", m_arrKRX_Holiday[i]);
				pcfp->Write(chBuf, 9);
			}

			pcfp->Flush();
			pcfp->Abort();
			delete pcfp;
			pcfp = nullptr;
		}
	}
	else {
		CStdioFile stdFp;
		DWORD dwTemp;
		boRet = stdFp.Open(cstrFilePath, CFile::modeRead | CFile::shareDenyNone | CFile::typeText);
		if (TRUE == boRet) {
			AcquireSRWLockExclusive(&(p->srwlock));
			while (stdFp.ReadString(chBuf, sizeof(chBuf) - 1)) {
				//DPRINTF("INFO %s", chBuf);
				dwTemp = (DWORD)_ttoi(chBuf);
				if ((dwTemp < N_CALENDARS_LOW_LIMIT_DATE) || (dwTemp > N_CALENDARS_HIGH_LIMIT_DATE)) {	/* Range filtering */
					continue;
				}
				DPRINTF("INFO %u\n", dwTemp);
				p->map.SetAt(dwTemp, TRUE);
			}
			ReleaseSRWLockExclusive(&(p->srwlock));
			stdFp.Close();
		}
	}
	/* [END] Build KRX Holiday Query Map */

	return;
}

 

3.3.  BOOL CMarketCalendars::boIsSession(TEN_MARKET_CAT enMarket, LPCTSTR strYYYYMMDD)

YYYYMMDD 형식의 문자열을 받으면 형식이 맞는지 길이를 체크 후 연도, 월, 일을 구한 뒤 CTime 객체를 생성하여 요일을 구합니다. 요일이 토요일, 일요일에 해당하면 즉시 FALSE를 반환하고 월화수목금에 해당하면 휴장일 맵을 체크 하여 휴장일 맵에 존재하는 날짜면 FALSE를 반환, 그렇지 않으면 TRUE를 반환합니다.

BOOL CMarketCalendars::boIsSession(TEN_MARKET_CAT enMarket, LPCTSTR strYYYYMMDD)
{
	DWORD dwTemp;
	BOOL boRet = FALSE;
	BOOL boRvalue;
	int iSizeOfString;
	int iDayofWeeek;
	PTST_MARKET_HOLIDAY p = nullptr;

	CString cstrTemp = strYYYYMMDD;
	cstrTemp.Trim();

	iSizeOfString = cstrTemp.GetLength() + 1;	/* count including '\0' + 1 */

	DPRINTF("[INFO] iSizeOfString (%d)\n", iSizeOfString);
	if (iSizeOfString != 9) {	/* Checking string format YYYYMMDD */
		return FALSE;
	}
	
	TCHAR tchBuf[9];
	_tcscpy_s(tchBuf, 9, cstrTemp);

	dwTemp = (DWORD)_ttoi(cstrTemp);
	DPRINTF("[INFO] dwTemp = %d\n", dwTemp);
	if ((dwTemp < N_CALENDARS_LOW_LIMIT_DATE) || (dwTemp > N_CALENDARS_HIGH_LIMIT_DATE)) {	/* Range filtering */
		return FALSE;
	}

	WORD wYear;
	WORD wMonth;
	WORD wDay;

	wDay = _ttoi(tchBuf + 6);
	tchBuf[6] = '\0';
	wMonth = _ttoi(tchBuf + 4);
	tchBuf[4] = '\0';
	wYear = _ttoi(tchBuf);
	
	DPRINTF("[INFO] boIsSession LPCTSTR [%d], [%d] [%d] [%d]\n", dwTemp, wYear, wMonth, wDay);

	CTime ct(wYear, wMonth, wDay, 9, 0, 0);
	DPRINTF("[INFO] boIsSession CTime::DayOfWeek (%d)\n", ct.GetDayOfWeek());

	switch (enMarket) {
	case TEN_MARKET_CAT::XKRX:
		/* Check SUNDAY OR SATURDAY */
		iDayofWeeek = ct.GetDayOfWeek();
		if ((CTIME_DAYOFWEEK_SATURDAY == iDayofWeeek) || (CTIME_DAYOFWEEK_SUNDAY == iDayofWeeek)) {
			return FALSE;
		}

		p = &(_m_astHolidayMap[static_cast<UINT>(TEN_MARKET_CAT::XKRX)]);

		AcquireSRWLockShared(&(p->srwlock));
		boRet = p->map.Lookup(dwTemp, boRvalue);
		ReleaseSRWLockShared(&(p->srwlock));
		if (TRUE == boRet) { /* Hit Holiday */
			return FALSE;
		}
		else {
			return TRUE;
		}
		break;
	default:
		break;
	}

	return boRet;
}

 

3.4. 샘플 데이터 결과 확인

호출 코드와 디버깅 메시지 결과입니다.

m_MkCalendars.vInit();	/* CMarketCalendars */
/* FIXME: Test Code */
LPSTR lpsz = _T("20200501");
if (TRUE == m_MkCalendars.boIsSession(TEN_MARKET_CAT::XKRX, lpsz)) {
	DPRINTF("[INFO] (%s) is Session Day\n", lpsz);
}
else {
	DPRINTF("[INFO] (%s) is HoliDay\n",
}

 

20200501 결과-금요일인데 휴장일로 판단

 

이상으로 C++ 환경에서 주식 휴장일 캘린더 클래스를 만들어 보았습니다.

반응형