KRX의 휴장일 데이터를 기반으로 Python pandas의 AbstractHolidayCalendar와 CustomBusinessDay 사용하기
작성일Edited onDisqus:
0. Introduction
Python을 사용해서 금융 시계열 분석을 하다보면, datetime과 관련한 처리가 까다로운 경우가 많습니다. 대표적으로, 휴일 및 공휴일 등을 계산하는 경우가 있습니다. Python pandas 라이브러리에 business day 개념이 있기는 하지만 (기본적으로는) 주말을 제외한 공휴일을 제대로 고려하지 못합니다.
조금 찾아보니, pandas에서 이러한 문제를 관리할 수 있는 방법이 있었습니다. 본 포스팅은 해당 내용을 공부하며 정리한 글 입니다. 글의 내용 중 잘못 된 부분이 있는 경우, 피드백을 주시면 감사하겠습니다. :)
1. pandas의 AbstractHolidayCalendar
1.1. pandas의 Holiday를 사용하여 정의하기
pandas의 AbstractHolidayCalendar를 사용하여, 사용자가 공휴일을 직접 명시할 수 있습니다. 해당 클래스를 상속하는 클래스를 정의하고, 공휴일에 대한 정보를 담고 있는 클래스 변수 rules를 override 하면 됩니다. 각 공휴일은 pandas의 Holiday 클래스를 이용하여 아래와 같이 입력해줍니다.
1 2 3 4 5 6 7
from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday
Holiday 클래스의 경우 offset 혹은 observance 인자를 통해 조금 더 상세한 설정이 가능합니다.
offset은 명시된 일자와 실제 공휴일 간의 일자 차이를 의미합니다. 주로 pandas.DateOffset과 함께 사용하여 “셋째 주 월요일”과 같은 식의 공휴일을 지정하는데 많이 사용됩니다.
observance는 날짜로 주어진 공휴일에 대한 일종의 후처리 로직입니다. 주어진 날짜가 주말일 경우 공휴일을 다음주 월요일로 미룰지(next_monday), 주말 직전 금요일로 당길지(previous_friday) 등을 설정할 수 있습니다. pandas에서 기본적으로 제공되는 함수들을 사용하거나, 직접 정의한 함수를 사용할 수 있습니다.
실제 사용 예시를 확인하시면 이해가 쉽습니다. 아래는 pandas에서 기본적으로 제공되는 미국 공휴일 캘린더 클래스(USFederalHolidayCalendar)입니다. (pandas 1.1 기준으로, pandas.tseries.holiday 모듈에서 구현을 찾으실 수 있습니다.)
from pandas.tseries.offsets import DateOffset from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday, nearest_workday from dateutil.relativedelta import MO, TU, WE, TH, FR, SA, SU
classUSFederalHolidayCalendar(AbstractHolidayCalendar): rules = [ Holiday( "New Years Day", month=1, day=1, observance=nearest_workday # 1월 1일이 토요일인 경우 직전 금요일을, # 일요일인 경우 이후 월요일을 공휴일로 사용 ), Holiday( "Martin Luther King Jr. Day", start_date=datetime(1986, 1, 1), month=1, day=1, offset=DateOffset(weekday=MO(3)) # 1986년 이후 1월의 셋째 주 월요일을 공휴일로 사용 ), Holiday( "Presidents Day", month=2, day=1, offset=DateOffset(weekday=MO(3)) # 2월 셋째 주 월요일을 공휴일로 사용 ), Holiday( "Memorial Day", month=5, day=31, offset=DateOffset(weekday=MO(-1)) # 5월 마지막 월요일을 공휴일로 사용 ), # 이하 동일 Holiday("July 4th", month=7, day=4, observance=nearest_workday), Holiday("Labor Day", month=9, day=1, offset=DateOffset(weekday=MO(1))), Holiday("Columbus Day", month=10, day=1, offset=DateOffset(weekday=MO(2))), Holiday("Veterans Day", month=11, day=11, observance=nearest_workday), Holiday("Thanksgiving", month=11, day=1, offset=DateOffset(weekday=TH(4))), Holiday("Christmas", month=12, day=25, observance=nearest_workday), ]
1.2. DB 정보를 사용하여 정의하기
위와 같은 방법의 경우, 비주기적인 공휴일이 발생했을 때 소스코드를 수정해 주어야 합니다. 뿐만 아니라 음력으로 공휴일을 세는 경우가 많은 한국의 경우 위의 방법을 사용하기가 애매합니다. 만약 공휴일 혹은 휴장일 파일 혹은 데이터베이스를 구축해 놓은 경우, 위의 코드를 응용하여 데이터베이스의 최신 내용을 기반으로 하는 캘린더를 정의할 수 있습니다.
아래는 DB에서 가져온 공유일 데이터 holidays를 사용하여 공휴일 캘린더를 정의하는 예시입니다.
datetime.date(2020, 10, 6) + TDay - TDay # Timestamp('2020-10-06 00:00:00') # 주어진 일자가 영업일인 경우, 해당 일자 반환 datetime.date(2020, 10, 1) + TDay - TDay # Timestamp('2020-09-29 00:00:00') # 주어진 일자가 영업일이 아닐 경우, # 해당 일자에서 가장 가까운 *이전* 영업일 datetime.date(2020, 10, 1) - TDay + TDay # Timestamp('2020-10-05 00:00:00') # 주어진 일자가 영업일이 아닐 경우, # 해당 일자에서 가장 가까운 *이후* 영업일
pd.date_range('2020-10-01', '2020-10-31', freq=TDay) pd.bdate_range('2020-10-01', '2020-10-31', freq=TDay) """ 공휴일을 제외한 기간 내 영업일 DatetimeIndex(['2020-10-05', '2020-10-06', '2020-10-07', '2020-10-08', '2020-10-12', '2020-10-13', '2020-10-14', '2020-10-15', '2020-10-16', '2020-10-19', '2020-10-20', '2020-10-21', '2020-10-22', '2020-10-23', '2020-10-26', '2020-10-27', '2020-10-28', '2020-10-29', '2020-10-30'], dtype='datetime64[ns]', freq='C') # freq='C'는 'CustomBusinessDay'를 의미 """
np.busday_count( '2020-10-01', '2020-10-31', weekmask=TDay.weekmask, holidays=TDay.holidays ) """ 공휴일을 제외한 두 일자 간 영업일수 19 """
TDay에 의해 변경된 결과값의 타입이 pandas._libs.tslibs.timestamps.Timestamp인 점을 인지하고 있어야 합니다. datetime.date 인스턴스가 들어가야 하는 위치에 Timestamp 인스턴스가 들어가는 경우, 가끔씩 type mismatch에 의한 에러가 발생할 수 있습니다. (예를 들면, 'Timestamp' object has no attribute '...') 이를 해결하기 위해서는, Timestamp의 to_pydatetime() method를 아래와 같이 사용하면 됩니다.