Skip to content

📚 Helpful?

❤️ Support

DayCounter Class

QuantLib Class | QuantLib-Python | QuantLib | 2025.04.20

1️⃣ Introduction to DayCounter Class

In the world of finance, a difference of one day can create a difference of millions. When calculating interest on a 1 billion KRW bond, the amount you receive varies depending on whether you count 364 days or 365 days. The DayCounter class in QuantLib is a core tool that accurately implements various day count conventions used in financial markets.

Just as measurement units matter in cooking (like whether 1 cup means 240ml in US or 200ml in Korea affects the result), in finance, how you count days determines the interest amount. Mastering the DayCounter class enables you to accurately apply various calculation rules used in international financial markets.

2️⃣ Why Day Count Conventions Matter in Finance

Each financial instrument has a different method for calculating interest. For the same period, some markets count a year as 365 days while others count it as 360 days. Additionally, whether to use actual elapsed days or standardized month days (30 days) varies by instrument.

Why Day Count Conventions Are Important in Finance

  • Accurate Interest Calculation: The actual interest amount received varies depending on the day counting method
  • Market Standard Compliance: Each market and instrument type has established day count conventions that must be followed
  • International Transaction Compatibility: Standardized calculation methods are essential in global financial transactions
  • Price Transparency: Fair price comparison is only possible when using the same calculation method
  • Regulatory Compliance: Financial authorities specify which day count conventions must be used for certain instruments

For example, US Treasury bonds use the Actual/Actual method, while corporate bonds mainly use the 30/360 method. This shows that day count conventions are like the DNA of financial instruments - applying them incorrectly produces completely different results.

3️⃣ Creating DayCounter Objects

QuantLib supports all major day count conventions used in global financial markets. Like having various measurement tools, you can select and use the appropriate DayCounter for each situation.

✅ Main DayCounter Types

Let's look at the most commonly used DayCounters:

python
import QuantLib as ql

# 1. Actual/365 Fixed - Actual days / 365 days
actual365 = ql.Actual365Fixed()
print(f"Actual/365 Fixed: {actual365.name()}")

# 2. Actual/360 - Actual days / 360 days (Money Market)
actual360 = ql.Actual360()
print(f"Actual/360: {actual360.name()}")

# 3. 30/360 - Assumes each month has 30 days
thirty360 = ql.Thirty360(ql.Thirty360.USA)
print(f"30/360 (US): {thirty360.name()}")

# 4. Actual/Actual - Actual days / Actual year days
actualactual = ql.ActualActual(ql.ActualActual.ISDA)
print(f"Actual/Actual (ISDA): {actualactual.name()}")

# 5. Business/252 - Business days only (Brazilian market)
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 Usage by Market

Here's a summary of which markets and instruments primarily use each DayCounter:

python
# DayCounter mapping by market
market_conventions = {
    "US Treasury": ql.ActualActual(ql.ActualActual.ISDA),
    "US Corporate": ql.Thirty360(ql.Thirty360.USA),
    "EU Treasury": ql.ActualActual(ql.ActualActual.ISDA),
    "Euro Corporate": ql.Thirty360(ql.Thirty360.European),
    "Money Market": ql.Actual360(),
    "UK Gilts": ql.ActualActual(ql.ActualActual.ISDA),
    "Japan JGB": ql.ActualActual(ql.ActualActual.ISDA),
    "Korea KTB": ql.ActualActual(ql.ActualActual.ISDA),
}

print("=== Standard DayCounter by Market ===")
for market, dc in market_conventions.items():
    print(f"{market:15}: {dc.name()}")

# === Standard DayCounter by Market ===
# US Treasury    : Actual/Actual (ISDA)
# US Corporate   : 30/360 (US)
# EU Treasury    : Actual/Actual (ISDA)
# Euro Corporate : 30/360 (Eurobond Basis)
# Money Market   : Actual/360
# UK Gilts       : Actual/Actual (ISDA)
# Japan JGB      : Actual/Actual (ISDA)
# Korea KTB      : Actual/Actual (ISDA)

✅ Core DayCounter Methods

Let's explore the key functionalities of DayCounter:

python
import QuantLib as ql

# Set dates
start_date = ql.Date(15, 3, 2025)
end_date = ql.Date(15, 9, 2025)  # 6 months later

# Compare multiple DayCounters
day_counters = [
    ("Actual/365", ql.Actual365Fixed()),
    ("Actual/360", ql.Actual360()),
    ("30/360", ql.Thirty360(ql.Thirty360.USA)),
]

print(f"Period: {start_date} ~ {end_date}")
print(f"Actual elapsed days: {end_date - start_date} days\n")

print("=== Calculation Results by DayCounter ===")
for name, dc in day_counters:
    # dayCount: day counting
    day_count = dc.dayCount(start_date, end_date)

    # yearFraction: year fraction calculation (most important!)
    year_frac = dc.yearFraction(start_date, end_date)

    print(f"{name:12}: {day_count:3d} days, year fraction {year_frac:.6f}")

# Period: March 15th, 2025 ~ September 15th, 2025
# Actual elapsed days: 184 days

# === Calculation Results by DayCounter ===
# Actual/365  : 184 days, year fraction 0.504110
# Actual/360  : 184 days, year fraction 0.511111
# 30/360      : 180 days, year fraction 0.500000

4️⃣ Practical Example 1: Comparing Bond Interest Calculations

The interest you receive on the same bond varies depending on which DayCounter you use. Let's compare interest differences by DayCounter in an actual bond investment scenario.

python
import QuantLib as ql

# Bond investment scenario
principal = 1000000000  # Face value: 1 billion KRW
annual_rate = 0.05      # Annual rate: 5%
purchase_date = ql.Date(15, 1, 2025)   # Purchase date
sale_date = ql.Date(15, 7, 2025)       # Sale date (6 months later)

print(f"Bond Investment Scenario")
print(f"Face Value: {principal:,} KRW")
print(f"Annual Rate: {annual_rate*100}%")
print(f"Holding Period: {purchase_date} ~ {sale_date}")
print(f"Actual Holding Days: {sale_date - purchase_date} days\n")

# Calculate interest using various DayCounters
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("=" * 75)
print(f"{'DayCounter':20} {'Days':>6} {'Year Frac':>10} {'Interest':>15} {'Diff':>12}")
print("=" * 75)

# Base amount (first DayCounter as reference)
base_interest = None

for name, dc in day_counters:
    # Day counting
    day_count = dc.dayCount(purchase_date, sale_date)

    # Year fraction calculation (key!)
    year_fraction = dc.yearFraction(purchase_date, sale_date)

    # Interest calculation
    interest = principal * annual_rate * year_fraction

    # Calculate difference from first
    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} KRW {diff_amount:>+10,.0f} KRW")

print("=" * 75)

# Extreme example: 1-year investment
print("\n\nExtreme Example: Full Year Investment")
one_year_start = ql.Date(1, 1, 2025)
one_year_end = ql.Date(31, 12, 2025)

print(f"Holding Period: {one_year_start} ~ {one_year_end}")
print(f"Actual Holding Days: {one_year_end - one_year_start} days\n")

print("=" * 75)
print(f"{'DayCounter':20} {'Days':>6} {'Year Frac':>10} {'Interest':>15}")
print("=" * 75)

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} KRW")

print("=" * 75)


# Bond Investment Scenario
# Face Value: 1,000,000,000 KRW
# Annual Rate: 5.0%
# Holding Period: January 15th, 2025 ~ July 15th, 2025
# Actual Holding Days: 181 days

# ===========================================================================
# DayCounter             Days  Year Frac        Interest         Diff
# ===========================================================================
# Actual/365              181   0.495890    24,794,521 KRW         +0 KRW
# Actual/360              181   0.502778    25,138,889 KRW   +344,368 KRW
# 30/360 (US)             180   0.500000    25,000,000 KRW   +205,479 KRW
# Actual/Actual           181   0.495890    24,794,521 KRW         +0 KRW
# ===========================================================================


# Extreme Example: Full Year Investment
# Holding Period: January 1st, 2025 ~ December 31st, 2025
# Actual Holding Days: 364 days

# ===========================================================================
# DayCounter             Days  Year Frac        Interest
# ===========================================================================
# Actual/365              364   0.997260    49,863,014 KRW
# Actual/360              364   1.011111    50,555,556 KRW
# 30/360 (US)             360   1.000000    50,000,000 KRW
# Actual/Actual           364   0.997260    49,863,014 KRW
# ===========================================================================

This example clearly shows that DayCounter selection directly impacts actual returns. For a 1 billion KRW bond, a difference of about 340,000 KRW can occur over 6 months, and this difference grows larger as the amount increases.

5️⃣ Practical Example 2: Interest Calculation for Complex Periods

In practice, we often need to calculate interest for irregular periods, not exactly 1 year or 6 months. Especially when bonds are traded mid-term or have unusual issue dates, accurate DayCounter calculation becomes even more critical.

python
import QuantLib as ql

# Complex period settings - including leap year, month-end handling
scenarios = [
    {
        "name": "Regular 6 Months",
        "start": ql.Date(15, 3, 2025),
        "end": ql.Date(15, 9, 2025)
    },
    {
        "name": "Including Leap Year",
        "start": ql.Date(29, 2, 2024),  # Leap year Feb 29
        "end": ql.Date(28, 2, 2025)
    },
    {
        "name": "Month-End Case",
        "start": ql.Date(31, 1, 2025),
        "end": ql.Date(28, 2, 2025)
    },
    {
        "name": "Irregular Period",
        "start": ql.Date(17, 5, 2025),
        "end": ql.Date(23, 11, 2025)
    }
]

# Investment conditions
principal = 500000000  # 500 million KRW
annual_rate = 0.045    # Annual 4.5%

# Main DayCounters
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: {scenario['name']}")
    print(f"Period: {scenario['start']} ~ {scenario['end']}")
    print(f"Actual Elapsed Days: {scenario['end'] - scenario['start']} days")
    print(f"{'='*75}")
    print(f"{'DayCounter':15} {'Calc Days':>10} {'Year Frac':>10} {'Interest':>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:10d} {year_fraction:10.6f} {interest:>13,.0f} KRW")


# ============================= Additional: Split Period Calculation =============================
print("\n\n" + "="*75)
print("Additional Analysis: DayCounter Handling for Year-Crossing Periods")
print("="*75)

cross_year_start = ql.Date(1, 11, 2025)
cross_year_end = ql.Date(28, 2, 2026)

print(f"\nPeriod: {cross_year_start} ~ {cross_year_end}")
print(f"Actual Elapsed Days: {cross_year_end - cross_year_start} days")

# Actual/Actual splits calculation by year
actualactual = ql.ActualActual(ql.ActualActual.ISDA)
year_end_2025 = ql.Date(31, 12, 2025)

# 2025 portion
days_2025 = year_end_2025 - cross_year_start
frac_2025 = days_2025 / 365.0

# 2026 portion
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) Detailed Analysis ===")
print(f"2025 portion: {days_2025} days / 365 = {frac_2025:.6f}")
print(f"2026 portion: {days_2026} days / 365 = {frac_2026:.6f}")
print(f"Manual calculation: {manual_calc:.6f}")
print(f"Automatic calculation: {auto_calc:.6f}")
print(f"Match: {'✓ Match' if abs(manual_calc - auto_calc) < 0.000001 else '✗ Mismatch'}")


# ===========================================================================
# Scenario: Regular 6 Months
# Period: March 15th, 2025 ~ September 15th, 2025
# Actual Elapsed Days: 184 days
# ===========================================================================
# DayCounter       Calc Days  Year Frac        Interest
# ---------------------------------------------------------------------------
# Actual/365             184   0.504110     11,342,466 KRW
# Actual/360             184   0.511111     11,500,000 KRW
# 30/360                 180   0.500000     11,250,000 KRW
# Act/Act                184   0.504110     11,342,466 KRW

# ===========================================================================
# Scenario: Including Leap Year
# Period: February 29th, 2024 ~ February 28th, 2025
# Actual Elapsed Days: 365 days
# ===========================================================================
# DayCounter       Calc Days  Year Frac        Interest
# ---------------------------------------------------------------------------
# Actual/365             365   1.000000     22,500,000 KRW
# Actual/360             365   1.013889     22,812,500 KRW
# 30/360                 360   1.000000     22,500,000 KRW
# Act/Act                365   0.999316     22,484,590 KRW

# ===========================================================================
# Scenario: Month-End Case
# Period: January 31st, 2025 ~ February 28th, 2025
# Actual Elapsed Days: 28 days
# ===========================================================================
# DayCounter       Calc Days  Year Frac        Interest
# ---------------------------------------------------------------------------
# Actual/365              28   0.076712      1,726,027 KRW
# Actual/360              28   0.077778      1,750,000 KRW
# 30/360                  30   0.083333      1,875,000 KRW
# Act/Act                 28   0.076712      1,726,027 KRW
# ...

# ===========================================================================
# Additional Analysis: DayCounter Handling for Year-Crossing Periods
# ===========================================================================

# Period: November 1st, 2025 ~ February 28th, 2026
# Actual Elapsed Days: 119 days

# === Actual/Actual (ISDA) Detailed Analysis ===
# 2025 portion: 61 days / 365 = 0.167123
# 2026 portion: 59 days / 365 = 0.161644
# Manual calculation: 0.326027
# Automatic calculation: 0.326027
# Match: ✓ Match

This example demonstrates how DayCounter handles complex situations like leap years, month-ends, and year boundaries. Particularly, Actual/Actual uses a sophisticated method of splitting calculations by year, while 30/360 shows a unique approach to standardizing month-ends.

6️⃣ Integration with Other Classes

The DayCounter class integrates with almost all QuantLib financial instrument classes to handle accurate interest calculations. It receives precise date information from Date and Calendar to calculate year fractions, which are core to financial calculations.

✅ Integration with Date

The most basic use of DayCounter is calculating the year fraction between two dates:

python
import QuantLib as ql

# Set dates
start = ql.Date(1, 1, 2025)
end = ql.Date(30, 6, 2025)

# Select DayCounter
dc = ql.ActualActual(ql.ActualActual.ISDA)

# Core methods
day_count = dc.dayCount(start, end)
year_fraction = dc.yearFraction(start, end)

print(f"Start Date: {start}")
print(f"End Date: {end}")
print(f"Days: {day_count} days")
print(f"Year Fraction: {year_fraction:.6f}")

# Apply to interest calculation
principal = 10000000
rate = 0.05
interest = principal * rate * year_fraction
print(f"\n10,000,000 KRW × 5% × {year_fraction:.6f} = {interest:,.0f} KRW")

# Start Date: January 1st, 2025
# End Date: June 30th, 2025
# Days: 180 days
# Year Fraction: 0.493151

# 10,000,000 KRW × 5% × 0.493151 = 246,575 KRW

✅ Integration with Calendar

Some DayCounters only include business days in calculations:

python
# Business day-based 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)

# Compare with regular DayCounter
actual365 = ql.Actual365Fixed()

print(f"Period: {start_date} ~ {end_date}\n")

# Actual/365 calculation
actual_days = end_date - start_date
actual_frac = actual365.yearFraction(start_date, end_date)

# Business/252 calculation
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"Days: {actual_days} days")
print(f"Year Fraction: {actual_frac:.6f}")

print(f"\n=== Business/252 (Korea) ===")
print(f"Business Days: {business_days} days")
print(f"Year Fraction: {business_frac:.6f}")

print(f"\nDifference: {(business_frac - actual_frac):.6f}")

# Period: March 1st, 2025 ~ June 1st, 2025

# === Actual/365 Fixed ===
# Days: 92 days
# Year Fraction: 0.252055

# === Business/252 (Korea) ===
# Business Days: 61 days
# Year Fraction: 0.242063

# Difference: -0.009991

✅ Integration with InterestRate and Financial Instruments

All financial instruments must specify a DayCounter:

python
# Create InterestRate object
rate = ql.InterestRate(
    0.05,                                    # 5% rate
    ql.Actual365Fixed(),                     # DayCounter
    ql.Compounded,                           # Compounding
    ql.Annual                                # Annual compounding
)

print(f"Interest Rate: {rate}")

# Specify DayCounter when creating bonds
calendar = ql.SouthKorea()
settlement_days = 2
face_value = 100
coupon_rate = 0.05
day_counter = ql.ActualActual(ql.ActualActual.ISDA)  # DayCounter specification

# Create bond schedule
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
)

# Interest Rate: 5.000000 % Actual/365 (Fixed) Annual compounding

7️⃣ Precautions and Practical Tips

Precautions When Using DayCounter

  • Check Market Conventions Each market and instrument type has a standard DayCounter. Don't choose arbitrarily - follow market conventions.

  • Verify Contracts Check the Day Count Convention specified in bond or swap contracts and apply it exactly the same.

  • Decimal Precision Year fraction calculation results involve floating-point operations and may have very small errors. For large amounts, rounding rules must be clearly defined.

  • 30/360 Variations 30/360 has several variations (US, European, Italian, etc.), each with different month-end handling. Use the exact variation required.

Preventing Critical Errors

  • Never Mix: Do not change DayCounters mid-way within the same instrument
  • Caution in Reverse Calculation: When reverse-calculating principal from interest amount, use the same DayCounter
  • Leap Year Handling: Always test periods that include leap years

Practical Tips

  • Standard Mapping Table: Create and maintain a mapping table of standard DayCounters by instrument type
  • Validation Logic: Add logic to compare and validate calculation results with market data or other systems
  • Documentation: Clearly document which DayCounter was used and why
  • Test Cases: Prepare test cases for special situations like leap years, month-ends, and year boundaries

8️⃣ Summary and Next Steps

The DayCounter class is a core tool that ensures accuracy in financial calculations, precisely implementing different day count conventions across markets. To summarize what we learned in this section:

  • DayCounter Types: Actual/365, Actual/360, 30/360, Actual/Actual, Business/252, etc.
  • Year Fraction Calculation: Accurate period calculation using the yearFraction() method
  • Practical Applications: Bond interest calculation, complex period handling, market-specific applications
  • Integration with Other Classes: Integration with Date, Calendar, InterestRate, and various financial instruments

In the next step, we will learn the Schedule class to automatically generate regular cash flow schedules. Schedule utilizes Date, Period, Calendar, and DayCounter to systematically manage complex schedules such as bond coupon payment dates and swap settlement dates.

Additional Learning Resources

Made by haun with ❤️