Skip to content

US 30U/360

Bases: Convention

The US 30U/360 day count convention, a variant of US 30/360 (Bond Basis).

Computes the number of days in a period divided by 360, using the formula:

f = [(360 * (Y2 - Y1)) + (30 * (M2 - M1)) + (D2 - D1)] / 360

Differs from US 30/360 in February handling
  • February has 30 days if start or end date is Feb 28 (non-leap year) or Feb 29 (leap year).
  • Exception: In non-leap years, if start or end date is Feb 29, February has 29 days.
  • D1 = 30 if start day is 31.
  • D2 = 30 if end day is 31.

Used for specific financial calculations, aligning with tools like the HP12C calculator.

Parameters:

Name Type Description Default
use_post_dates bool

If True, uses cash flow post dates. Defaults to True.

True
include_non_financing_flows bool

If True, includes non-financing flows. Defaults to False.

False
use_xirr_method bool

If True, uses XIRR method with drawdown origin. Defaults to False.

False
Source code in curo/daycount/us_30u_360.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class US30U360(Convention):
    """
    The US 30U/360 day count convention, a variant of US 30/360 (Bond Basis).

    Computes the number of days in a period divided by 360, using the formula:

    `f = [(360 * (Y2 - Y1)) + (30 * (M2 - M1)) + (D2 - D1)] / 360`

    Note: Differs from US 30/360 in February handling
        - February has 30 days if start or end date is Feb 28 (non-leap year) or
            Feb 29 (leap year).
        - Exception: In non-leap years, if start or end date is Feb 29, February 
            has 29 days.
        - D1 = 30 if start day is 31.
        - D2 = 30 if end day is 31.

    Used for specific financial calculations, aligning with tools like the HP12C calculator.

    Args:
        use_post_dates (bool, optional): If True, uses cash flow post dates. Defaults to True.
        include_non_financing_flows (bool, optional): If True, includes non-financing flows.
            Defaults to False.
        use_xirr_method (bool, optional): If True, uses XIRR method with drawdown origin.
            Defaults to False.
    """
    def __init__(
        self,
        use_post_dates: bool = True,
        include_non_financing_flows: bool = False,
        use_xirr_method: bool = False
    ):
        super().__init__(
            use_post_dates=use_post_dates,
            include_non_financing_flows=include_non_financing_flows,
            use_xirr_method=use_xirr_method
        )

    @property
    def day_count_origin(self) -> DayCountOrigin:
        """
        Returns the day count origin based on the XIRR method.

        Returns:
            DayCountOrigin: NEIGHBOUR if use_xirr_method is False, DRAWDOWN otherwise.
        """
        return DayCountOrigin.DRAWDOWN if self.use_xirr_method else DayCountOrigin.NEIGHBOUR

    def _day_difference(self, start: pd.Timestamp, end: pd.Timestamp, d1: int, d2: int) -> int:
        """
        Computes the day difference (D2 - D1) with US 30U/360 February adjustments.

        Args:
            start (pd.Timestamp): Start date for month-end checks.
            end (pd.Timestamp): End date for month-end checks.
            d1 (int): Adjusted start day (30 if 31).
            d2 (int): Adjusted end day (30 if 31).

        Returns:
            int: Adjusted day difference.
        """
        is_d1_last_day = has_month_end_day(start)
        is_d2_last_day = has_month_end_day(end)

        if is_d1_last_day and is_d2_last_day:
            return d2 + (d1 - d2) - d1  # Simplifies to 0 for same-day month-ends
        if is_d1_last_day and start.month == 2 and d1 != d2:
            if not calendar.isleap(start.year) and d2 == 29:
                return d2 - (d1 + (29 - d1))
            return d2 - (d1 + (30 - d1))
        if is_d2_last_day and end.month == 2 and d2 != d1:
            if not calendar.isleap(end.year) and d1 == 29:
                return (d2 + (29 - d2)) - d1
            return (d2 + (30 - d2)) - d1
        return d2 - d1

    def compute_factor(self, start: pd.Timestamp, end: pd.Timestamp) -> DayCountFactor:
        """
        Computes the day count factor between two dates using the US 30U/360 convention.

        Args:
            start (pd.Timestamp): Start date of the period.
            end (pd.Timestamp): End date of the period.

        Returns:
            DayCountFactor: The day count factor with year fraction and operands.

        Raises:
            ValueError: If end is before start.
        """
        if end < start:
            raise ValueError("end must be after start")
        if end == start:
            return DayCountFactor(primary_period_fraction=0.0, discount_factor_log=["0"])

        # Adjust days: Set to 30 if 31
        d1 = 30 if start.day == 31 else start.day
        d2 = 30 if end.day == 31 else end.day

        # Compute day difference with February adjustments
        days_diff = self._day_difference(start, end, d1, d2)

        # Compute numerator: [360 * (Y2 - Y1)] + [30 * (M2 - M1)] + (D2 - D1)
        numerator = ((end.year - start.year) * 360) + ((end.month - start.month) * 30) + days_diff
        factor = numerator / 360

        # Format operands
        operand = str(int(factor)) if numerator % 360 == 0 else f"{numerator}/360"
        operand_log = [operand]

        return DayCountFactor(primary_period_fraction=factor, discount_factor_log=operand_log)

day_count_origin property

Returns the day count origin based on the XIRR method.

Returns:

Name Type Description
DayCountOrigin DayCountOrigin

NEIGHBOUR if use_xirr_method is False, DRAWDOWN otherwise.

compute_factor(start, end)

Computes the day count factor between two dates using the US 30U/360 convention.

Parameters:

Name Type Description Default
start Timestamp

Start date of the period.

required
end Timestamp

End date of the period.

required

Returns:

Name Type Description
DayCountFactor DayCountFactor

The day count factor with year fraction and operands.

Raises:

Type Description
ValueError

If end is before start.

Source code in curo/daycount/us_30u_360.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def compute_factor(self, start: pd.Timestamp, end: pd.Timestamp) -> DayCountFactor:
    """
    Computes the day count factor between two dates using the US 30U/360 convention.

    Args:
        start (pd.Timestamp): Start date of the period.
        end (pd.Timestamp): End date of the period.

    Returns:
        DayCountFactor: The day count factor with year fraction and operands.

    Raises:
        ValueError: If end is before start.
    """
    if end < start:
        raise ValueError("end must be after start")
    if end == start:
        return DayCountFactor(primary_period_fraction=0.0, discount_factor_log=["0"])

    # Adjust days: Set to 30 if 31
    d1 = 30 if start.day == 31 else start.day
    d2 = 30 if end.day == 31 else end.day

    # Compute day difference with February adjustments
    days_diff = self._day_difference(start, end, d1, d2)

    # Compute numerator: [360 * (Y2 - Y1)] + [30 * (M2 - M1)] + (D2 - D1)
    numerator = ((end.year - start.year) * 360) + ((end.month - start.month) * 30) + days_diff
    factor = numerator / 360

    # Format operands
    operand = str(int(factor)) if numerator % 360 == 0 else f"{numerator}/360"
    operand_log = [operand]

    return DayCountFactor(primary_period_fraction=factor, discount_factor_log=operand_log)