DayCounter 클래스
QuantLib Class | QuantLib-Python | QuantLib | 2025.04.20
1️⃣ DayCounter 클래스 소개
금융의 세계에서 하루의 차이는 수백만 원의 차이를 만들어낼 수 있습니다. 10억 원짜리 채권의 이자를 계산할 때, 일수를 364일로 계산하느냐 365일로 계산하느냐에 따라 받는 금액이 달라집니다. QuantLib의 DayCounter 클래스는 금융 시장에서 사용되는 다양한 일수 계산 규칙(Day Count Convention)을 정확하게 구현하는 핵심 도구입니다.
마치 요리에서 계량 단위가 중요하듯이(1컵이 미국식 240ml인지 한국식 200ml인지에 따라 결과가 달라지는 것처럼), 금융에서도 일수를 어떻게 세느냐에 따라 이자 금액이 달라집니다. DayCounter 클래스를 마스터하면 국제 금융 시장에서 통용되는 다양한 계산 규칙을 정확히 적용할 수 있게 됩니다.
2️⃣ 금융에서 일수 계산 규칙이 중요한 이유
금융 상품마다 이자를 계산하는 방식이 다릅니다. 같은 기간이라도 어떤 시장에서는 1년을 365일로 계산하고, 어떤 시장에서는 360일로 계산합니다. 또한 실제 경과 일수를 사용할지, 표준화된 월 일수(30일)를 사용할지도 상품마다 다릅니다.
금융에서 일수 계산 규칙이 중요한 이유
- 정확한 이자 계산: 일수 계산 방식에 따라 실제 받는 이자 금액이 달라집니다
- 시장 표준 준수: 각 시장과 상품 유형마다 정해진 일수 계산 규칙을 따라야 합니다
- 국제 거래 호환성: 글로벌 금융 거래에서는 표준화된 계산 방식이 필수입니다
- 가격 투명성: 동일한 계산 방식을 사용해야 공정한 가격 비교가 가능합니다
- 규제 준수: 금융 당국은 특정 상품에 대해 사용해야 할 일수 계산 방식을 규정합니다
예를 들어, 미국 국채는 Actual/Actual 방식을 사용하는 반면, 회사채는 30/360 방식을 주로 사용합니다. 이처럼 일수 계산 규칙은 금융 상품의 DNA와 같아서, 잘못 적용하면 완전히 다른 결과가 나옵니다.
3️⃣ DayCounter 객체 생성하기
QuantLib은 전 세계 금융 시장에서 사용되는 주요 일수 계산 규칙을 모두 지원합니다. 마치 다양한 측정 도구가 있는 것처럼, 상황에 맞는 DayCounter를 선택하여 사용할 수 있습니다.
✅ 주요 DayCounter 종류
가장 많이 사용되는 DayCounter들을 살펴보겠습니다:
import QuantLib as ql
# 1. Actual/365 Fixed - 실제 일수 / 365일
actual365 = ql.Actual365Fixed()
print(f"Actual/365 Fixed: {actual365.name()}")
# 2. Actual/360 - 실제 일수 / 360일 (머니마켓)
actual360 = ql.Actual360()
print(f"Actual/360: {actual360.name()}")
# 3. 30/360 - 각 월을 30일로 가정
thirty360 = ql.Thirty360(ql.Thirty360.USA)
print(f"30/360 (US): {thirty360.name()}")
# 4. Actual/Actual - 실제 일수 / 실제 연도 일수
actualactual = ql.ActualActual(ql.ActualActual.ISDA)
print(f"Actual/Actual (ISDA): {actualactual.name()}")
# 5. Business/252 - 영업일만 계산 (브라질 시장)
korea_cal = ql.SouthKorea()
business252 = ql.Business252(korea_cal)
print(f"Business/252: {business252.name()}")
# Actual/365 Fixed: Actual/365 (Fixed)
# Actual/360: Actual/360
# 30/360 (US): 30/360 (US)
# Actual/Actual (ISDA): Actual/Actual (ISDA)
# Business/252: Business/252(South-Korea exchange)✅ DayCounter별 사용 시장
각 DayCounter가 주로 사용되는 시장과 상품을 정리하면:
# 시장별 DayCounter 매핑
market_conventions = {
"미국 국채": ql.ActualActual(ql.ActualActual.ISDA),
"미국 회사채": ql.Thirty360(ql.Thirty360.USA),
"유럽 국채": ql.ActualActual(ql.ActualActual.ISDA),
"유로 회사채": ql.Thirty360(ql.Thirty360.European),
"머니마켓": ql.Actual360(),
"영국 국채": ql.ActualActual(ql.ActualActual.ISDA),
"일본 국채": ql.ActualActual(ql.ActualActual.ISDA),
"한국 국채": ql.ActualActual(ql.ActualActual.ISDA),
}
print("=== 시장별 표준 DayCounter ===")
for market, dc in market_conventions.items():
print(f"{market:12}: {dc.name()}")
# === 시장별 표준 DayCounter ===
# 미국 국채 : Actual/Actual (ISDA)
# 미국 회사채 : 30/360 (US)
# 유럽 국채 : Actual/Actual (ISDA)
# 유로 회사채 : 30/360 (Eurobond Basis)
# 머니마켓 : Actual/360
# 영국 국채 : Actual/Actual (ISDA)
# 일본 국채 : Actual/Actual (ISDA)
# 한국 국채 : Actual/Actual (ISDA)✅ DayCounter 기본 메서드
DayCounter의 핵심 기능들을 알아보겠습니다:
import QuantLib as ql
# 날짜 설정
start_date = ql.Date(15, 3, 2025)
end_date = ql.Date(15, 9, 2025) # 6개월 후
# 여러 DayCounter 비교
day_counters = [
("Actual/365", ql.Actual365Fixed()),
("Actual/360", ql.Actual360()),
("30/360", ql.Thirty360(ql.Thirty360.USA)),
]
print(f"기간: {start_date} ~ {end_date}")
print(f"실제 경과 일수: {end_date - start_date}일\n")
print("=== DayCounter별 계산 결과 ===")
for name, dc in day_counters:
# dayCount: 일수 계산
day_count = dc.dayCount(start_date, end_date)
# yearFraction: 연분율 계산 (가장 중요!)
year_frac = dc.yearFraction(start_date, end_date)
print(f"{name:12}: {day_count:3d}일, 연분율 {year_frac:.6f}")
# 기간: March 15th, 2025 ~ September 15th, 2025
# 실제 경과 일수: 184일
# === DayCounter별 계산 결과 ===
# Actual/365 : 184일, 연분율 0.504110
# Actual/360 : 184일, 연분율 0.511111
# 30/360 : 180일, 연분율 0.5000004️⃣ 실용 예제 1: 채권 이자 계산 비교
동일한 채권이라도 어떤 DayCounter를 사용하느냐에 따라 받는 이자가 달라집니다. 실제 채권 투자 상황에서 DayCounter별 이자 차이를 비교해보겠습니다.
import QuantLib as ql
# 채권 투자 시나리오
principal = 1000000000 # 액면가 10억 원
annual_rate = 0.05 # 연 5% 이자율
purchase_date = ql.Date(15, 1, 2025) # 매수일
sale_date = ql.Date(15, 7, 2025) # 매도일 (6개월 후)
print(f"채권 투자 시나리오")
print(f"액면가: {principal:,}원")
print(f"연 이자율: {annual_rate*100}%")
print(f"보유기간: {purchase_date} ~ {sale_date}")
print(f"실제 보유일수: {sale_date - purchase_date}일\n")
# 다양한 DayCounter로 이자 계산
day_counters = [
("Actual/365", ql.Actual365Fixed()),
("Actual/360", ql.Actual360()),
("30/360 (US)", ql.Thirty360(ql.Thirty360.USA)),
("Actual/Actual", ql.ActualActual(ql.ActualActual.ISDA)),
]
print("=" * 70)
print(f"{'DayCounter':20} {'일수':>6} {'연분율':>10} {'이자금액':>15} {'차이':>12}")
print("=" * 70)
# 기준 금액 (첫 번째 DayCounter 기준)
base_interest = None
for name, dc in day_counters:
# 일수 계산
day_count = dc.dayCount(purchase_date, sale_date)
# 연분율 계산 (핵심!)
year_fraction = dc.yearFraction(purchase_date, sale_date)
# 이자 계산
interest = principal * annual_rate * year_fraction
# 첫 번째를 기준으로 차이 계산
if base_interest is None:
base_interest = interest
diff_amount = 0
diff_pct = 0.0
else:
diff_amount = interest - base_interest
diff_pct = (interest / base_interest - 1) * 100
print(f"{name:20} {day_count:6d} {year_fraction:10.6f} "
f"{interest:>13,.0f}원 {diff_amount:>+10,.0f}원")
print("=" * 70)
# 극단적인 예: 1년 투자
print("\n\n극단적 예시: 1년 전체 투자 시")
one_year_start = ql.Date(1, 1, 2025)
one_year_end = ql.Date(31, 12, 2025)
print(f"보유기간: {one_year_start} ~ {one_year_end}")
print(f"실제 보유일수: {one_year_end - one_year_start}일\n")
print("=" * 70)
print(f"{'DayCounter':20} {'일수':>6} {'연분율':>10} {'이자금액':>15}")
print("=" * 70)
for name, dc in day_counters:
day_count = dc.dayCount(one_year_start, one_year_end)
year_fraction = dc.yearFraction(one_year_start, one_year_end)
interest = principal * annual_rate * year_fraction
print(f"{name:20} {day_count:6d} {year_fraction:10.6f} {interest:>13,.0f}원")
print("=" * 70)
# 채권 투자 시나리오
# 액면가: 1,000,000,000원
# 연 이자율: 5.0%
# 보유기간: January 15th, 2025 ~ July 15th, 2025
# 실제 보유일수: 181일
# ======================================================================
# DayCounter 일수 연분율 이자금액 차이
# ======================================================================
# Actual/365 181 0.495890 24,794,521원 +0원
# Actual/360 181 0.502778 25,138,889원 +344,368원
# 30/360 (US) 180 0.500000 25,000,000원 +205,479원
# Actual/Actual 181 0.495890 24,794,521원 +0원
# ======================================================================
# 극단적 예시: 1년 전체 투자 시
# 보유기간: January 1st, 2025 ~ December 31st, 2025
# 실제 보유일수: 364일
# ======================================================================
# DayCounter 일수 연분율 이자금액
# ======================================================================
# Actual/365 364 0.997260 49,863,014원
# Actual/360 364 1.011111 50,555,556원
# 30/360 (US) 360 1.000000 50,000,000원
# Actual/Actual 364 0.997260 49,863,014원
# ======================================================================이 예제는 DayCounter 선택이 실제 수익에 직접적인 영향을 미친다는 것을 명확하게 보여줍니다. 10억 원짜리 채권에서 6개월 동안 약 34만 원의 차이가 발생할 수 있으며, 이는 금액이 커질수록 더욱 큰 차이로 이어집니다.
5️⃣ 실용 예제 2: 복잡한 기간의 이자 계산
실무에서는 정확히 1년이나 6개월이 아닌, 불규칙한 기간의 이자를 계산해야 하는 경우가 많습니다. 특히 채권을 중도에 매매하거나 발행일이 특이한 경우, DayCounter의 정확한 계산이 더욱 중요해집니다.
import QuantLib as ql
# 복잡한 기간 설정 - 윤년 포함, 월말 처리
scenarios = [
{
"name": "일반적인 6개월",
"start": ql.Date(15, 3, 2025),
"end": ql.Date(15, 9, 2025)
},
{
"name": "윤년 포함 기간",
"start": ql.Date(29, 2, 2024), # 윤년 2월 29일
"end": ql.Date(28, 2, 2025)
},
{
"name": "월말 처리 케이스",
"start": ql.Date(31, 1, 2025),
"end": ql.Date(28, 2, 2025)
},
{
"name": "불규칙 기간",
"start": ql.Date(17, 5, 2025),
"end": ql.Date(23, 11, 2025)
}
]
# 투자 조건
principal = 500000000 # 5억 원
annual_rate = 0.045 # 연 4.5%
# 주요 DayCounter들
day_counters = [
("Actual/365", ql.Actual365Fixed()),
("Actual/360", ql.Actual360()),
("30/360", ql.Thirty360(ql.Thirty360.USA)),
("Act/Act", ql.ActualActual(ql.ActualActual.ISDA)),
]
for scenario in scenarios:
print(f"\n{'='*75}")
print(f"시나리오: {scenario['name']}")
print(f"기간: {scenario['start']} ~ {scenario['end']}")
print(f"실제 경과 일수: {scenario['end'] - scenario['start']}일")
print(f"{'='*75}")
print(f"{'DayCounter':15} {'계산일수':>8} {'연분율':>10} {'이자금액':>15}")
print(f"{'-'*75}")
for name, dc in day_counters:
day_count = dc.dayCount(scenario['start'], scenario['end'])
year_fraction = dc.yearFraction(scenario['start'], scenario['end'])
interest = principal * annual_rate * year_fraction
print(f"{name:15} {day_count:8d} {year_fraction:10.6f} {interest:>13,.0f}원")
# ============================= 추가: 분할 기간 계산 =============================
print("\n\n" + "="*75)
print("추가 분석: 연도를 넘어가는 기간의 DayCounter별 처리 방식")
print("="*75)
cross_year_start = ql.Date(1, 11, 2025)
cross_year_end = ql.Date(28, 2, 2026)
print(f"\n기간: {cross_year_start} ~ {cross_year_end}")
print(f"실제 경과 일수: {cross_year_end - cross_year_start}일")
# Actual/Actual은 연도별로 분할 계산
actualactual = ql.ActualActual(ql.ActualActual.ISDA)
year_end_2025 = ql.Date(31, 12, 2025)
# 2025년 부분
days_2025 = year_end_2025 - cross_year_start
frac_2025 = days_2025 / 365.0
# 2026년 부분
days_2026 = cross_year_end - year_end_2025
frac_2026 = days_2026 / 365.0
manual_calc = frac_2025 + frac_2026
auto_calc = actualactual.yearFraction(cross_year_start, cross_year_end)
print(f"\n=== Actual/Actual (ISDA) 상세 분석 ===")
print(f"2025년 부분: {days_2025}일 / 365 = {frac_2025:.6f}")
print(f"2026년 부분: {days_2026}일 / 365 = {frac_2026:.6f}")
print(f"수동 계산: {manual_calc:.6f}")
print(f"자동 계산: {auto_calc:.6f}")
print(f"일치 여부: {'✓ 일치' if abs(manual_calc - auto_calc) < 0.000001 else '✗ 불일치'}")
# ===========================================================================
# 시나리오: 일반적인 6개월
# 기간: March 15th, 2025 ~ September 15th, 2025
# 실제 경과 일수: 184일
# ===========================================================================
# DayCounter 계산일수 연분율 이자금액
# ---------------------------------------------------------------------------
# Actual/365 184 0.504110 11,342,466원
# Actual/360 184 0.511111 11,500,000원
# 30/360 180 0.500000 11,250,000원
# Act/Act 184 0.504110 11,342,466원
# ===========================================================================
# 시나리오: 윤년 포함 기간
# 기간: February 29th, 2024 ~ February 28th, 2025
# 실제 경과 일수: 365일
# ===========================================================================
# DayCounter 계산일수 연분율 이자금액
# ---------------------------------------------------------------------------
# Actual/365 365 1.000000 22,500,000원
# Actual/360 365 1.013889 22,812,500원
# 30/360 360 1.000000 22,500,000원
# Act/Act 365 0.999316 22,484,590원
# ===========================================================================
# 시나리오: 월말 처리 케이스
# 기간: January 31st, 2025 ~ February 28th, 2025
# 실제 경과 일수: 28일
# ===========================================================================
# DayCounter 계산일수 연분율 이자금액
# ---------------------------------------------------------------------------
# Actual/365 28 0.076712 1,726,027원
# Actual/360 28 0.077778 1,750,000원
# 30/360 30 0.083333 1,875,000원
# Act/Act 28 0.076712 1,726,027원
# ...
# ===========================================================================
# 추가 분석: 연도를 넘어가는 기간의 DayCounter별 처리 방식
# ===========================================================================
# 기간: November 1st, 2025 ~ February 28th, 2026
# 실제 경과 일수: 119일
# === Actual/Actual (ISDA) 상세 분석 ===
# 2025년 부분: 61일 / 365 = 0.167123
# 2026년 부분: 59일 / 365 = 0.161644
# 수동 계산: 0.326027
# 자동 계산: 0.326027
# 일치 여부: ✓ 일치이 예제는 DayCounter가 윤년, 월말, 연도 경계 등 복잡한 상황을 어떻게 처리하는지를 보여줍니다. 특히 Actual/Actual은 연도별로 나누어 계산하는 정교한 방식을 사용하며, 30/360은 월말을 표준화하는 독특한 처리 방식을 보여줍니다.
6️⃣ 다른 클래스와의 연계
DayCounter 클래스는 QuantLib의 거의 모든 금융 상품 클래스와 연동되어 정확한 이자 계산을 담당합니다. Date와 Calendar가 제공하는 정확한 날짜 정보를 받아 금융 계산의 핵심인 연분율을 산출합니다.
✅ Date와의 연계
DayCounter의 가장 기본적인 사용은 두 날짜 사이의 연분율을 계산하는 것입니다:
import QuantLib as ql
# 날짜 설정
start = ql.Date(1, 1, 2025)
end = ql.Date(30, 6, 2025)
# DayCounter 선택
dc = ql.ActualActual(ql.ActualActual.ISDA)
# 핵심 메서드들
day_count = dc.dayCount(start, end)
year_fraction = dc.yearFraction(start, end)
print(f"시작일: {start}")
print(f"종료일: {end}")
print(f"일수: {day_count}일")
print(f"연분율: {year_fraction:.6f}")
# 이자 계산에 활용
principal = 10000000
rate = 0.05
interest = principal * rate * year_fraction
print(f"\n10,000,000원 × 5% × {year_fraction:.6f} = {interest:,.0f}원")
# 시작일: January 1st, 2025
# 종료일: June 30th, 2025
# 일수: 180일
# 연분율: 0.493151
# 10,000,000원 × 5% × 0.493151 = 246,575원✅ Calendar와의 연계
일부 DayCounter는 영업일만 계산에 포함시킵니다:
# 영업일 기반 DayCounter
korea_cal = ql.SouthKorea()
business_dc = ql.Business252(korea_cal)
start_date = ql.Date(1, 3, 2025)
end_date = ql.Date(1, 6, 2025)
# 일반 DayCounter와 비교
actual365 = ql.Actual365Fixed()
print(f"기간: {start_date} ~ {end_date}\n")
# Actual/365 계산
actual_days = end_date - start_date
actual_frac = actual365.yearFraction(start_date, end_date)
# Business/252 계산
business_days = korea_cal.businessDaysBetween(start_date, end_date)
business_frac = business_dc.yearFraction(start_date, end_date)
print(f"=== Actual/365 Fixed ===")
print(f"일수: {actual_days}일")
print(f"연분율: {actual_frac:.6f}")
print(f"\n=== Business/252 (한국) ===")
print(f"영업일 수: {business_days}일")
print(f"연분율: {business_frac:.6f}")
print(f"\n차이: {(business_frac - actual_frac):.6f}")
# 기간: March 1st, 2025 ~ June 1st, 2025
# === Actual/365 Fixed ===
# 일수: 92일
# 연분율: 0.252055
# === Business/252 (한국) ===
# 영업일 수: 61일
# 연분율: 0.242063
# 차이: -0.009991✅ InterestRate 및 금융 상품과의 연계
모든 금융 상품은 DayCounter를 필수로 지정해야 합니다:
# InterestRate 객체 생성
rate = ql.InterestRate(
0.05, # 이자율 5%
ql.Actual365Fixed(), # DayCounter
ql.Compounded, # 복리
ql.Annual # 연간 복리
)
print(f"이자율: {rate}")
# 채권 생성 시 DayCounter 지정
calendar = ql.SouthKorea()
settlement_days = 2
face_value = 100
coupon_rate = 0.05
day_counter = ql.ActualActual(ql.ActualActual.ISDA) # DayCounter 지정
# 채권 스케줄 생성
issue_date = ql.Date(1, 1, 2025)
maturity_date = ql.Date(1, 1, 2030)
schedule = ql.Schedule(
issue_date,
maturity_date,
ql.Period(ql.Semiannual),
calendar,
ql.ModifiedFollowing,
ql.ModifiedFollowing,
ql.DateGeneration.Backward,
False
)
# 이자율: 5.000000 % Actual/365 (Fixed) Annual compounding7️⃣ 주의사항 및 실무 팁
DayCounter 사용 시 주의사항
시장 관행 확인 각 시장과 상품마다 표준으로 사용하는 DayCounter가 다릅니다. 임의로 선택하지 말고 시장 관행을 따라야 합니다.
계약서 확인 채권이나 스왑 계약서에 명시된 Day Count Convention을 정확히 확인하고 동일하게 적용해야 합니다.
소수점 정밀도 연분율 계산 결과는 부동소수점 연산이므로 극히 작은 오차가 발생할 수 있습니다. 금액이 클 경우 반올림 규칙을 명확히 해야 합니다.
30/360의 변형 30/360에는 여러 변형(US, European, Italian 등)이 있으며, 각각 월말 처리 방식이 다릅니다. 정확한 변형을 사용해야 합니다.
치명적 오류 방지
- 절대 혼용 금지: 동일한 상품 내에서 DayCounter를 중간에 바꾸면 안 됩니다
- 역산 시 주의: 이자 금액에서 원금을 역산할 때는 동일한 DayCounter를 사용해야 합니다
- 윤년 처리: 윤년이 포함된 기간은 반드시 테스트해보세요
실무 활용 팁
- 표준 매핑 테이블: 상품별 표준 DayCounter 매핑 테이블을 만들어 관리하세요
- 검증 로직: 계산 결과를 시장 데이터나 타 시스템과 비교 검증하는 로직을 추가하세요
- 문서화: 어떤 DayCounter를 사용했는지, 그 이유는 무엇인지 명확히 문서화하세요
- 테스트 케이스: 윤년, 월말, 연도 경계 등 특수한 경우에 대한 테스트 케이스를 준비하세요
8️⃣ 요약 및 다음 단계
DayCounter 클래스는 금융 계산의 정확성을 보장하는 핵심 도구로, 시장마다 다른 일수 계산 규칙을 정확히 구현합니다. 이번 섹션에서 학습한 내용을 정리하면:
- DayCounter 종류: Actual/365, Actual/360, 30/360, Actual/Actual, Business/252 등
- 연분율 계산: yearFraction() 메서드를 통한 정확한 기간 계산
- 실무 활용: 채권 이자 계산, 복잡한 기간 처리, 시장별 적용
- 다른 클래스 연계: Date, Calendar, InterestRate, 각종 금융 상품과의 통합
다음 단계에서는 Schedule 클래스를 학습하여 정기적인 현금흐름 일정을 자동으로 생성하는 방법을 배워보겠습니다. Schedule은 Date, Period, Calendar, DayCounter를 모두 활용하여 채권의 이자지급일, 스왑의 결제일 등 복잡한 일정을 체계적으로 관리하는 클래스입니다.