From 1661b3e8cc7c62114fcf7a8420a0ddd70e1f5b34 Mon Sep 17 00:00:00 2001 From: gyx09212214-prog Date: Wed, 10 Jun 2026 00:55:22 +0800 Subject: [PATCH 1/2] Expose end-of-month scheduling on IborSwap --- financepy/products/rates/ibor_swap.py | 27 ++++++++-------- unit_tests/test_FinIborSwap.py | 44 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 unit_tests/test_FinIborSwap.py diff --git a/financepy/products/rates/ibor_swap.py b/financepy/products/rates/ibor_swap.py index e6acca2f4..2aa6c7c79 100644 --- a/financepy/products/rates/ibor_swap.py +++ b/financepy/products/rates/ibor_swap.py @@ -52,10 +52,11 @@ def __init__( float_spread: float = 0.0, float_freq_type: FrequencyTypes = FrequencyTypes.QUARTERLY, float_dc_type: DayCountTypes = DayCountTypes.THIRTY_E_360, - cal_type: CalendarTypes = CalendarTypes.WEEKEND, - bd_type: BusDayAdjustTypes = BusDayAdjustTypes.FOLLOWING, - dg_type: DateGenRuleTypes = DateGenRuleTypes.BACKWARD, - ): + cal_type: CalendarTypes = CalendarTypes.WEEKEND, + bd_type: BusDayAdjustTypes = BusDayAdjustTypes.FOLLOWING, + dg_type: DateGenRuleTypes = DateGenRuleTypes.BACKWARD, + end_of_month: bool = False, + ): """Create an interest rate swap contract giving the contract start date, its maturity, fixed cpn, fixed leg frequency, fixed leg day count convention and notional. The floating leg parameters have default @@ -97,10 +98,11 @@ def __init__( notional, principal, payment_lag, - cal_type, - bd_type, - dg_type, - ) + cal_type, + bd_type, + dg_type, + end_of_month, + ) self.float_leg = SwapFloatLeg( effective_dt, @@ -112,10 +114,11 @@ def __init__( notional, principal, payment_lag, - cal_type, - bd_type, - dg_type, - ) + cal_type, + bd_type, + dg_type, + end_of_month, + ) ########################################################################### diff --git a/unit_tests/test_FinIborSwap.py b/unit_tests/test_FinIborSwap.py new file mode 100644 index 000000000..adf697a9d --- /dev/null +++ b/unit_tests/test_FinIborSwap.py @@ -0,0 +1,44 @@ +from financepy.market.curves import InterpTypes +from financepy.products.rates.ibor_single_curve import IborSingleCurve +from financepy.products.rates.ibor_swap import IborSwap +from financepy.utils import ( + BusDayAdjustTypes, + Date, + DayCountTypes, + FrequencyTypes, + SwapTypes, +) + + +def test_ibor_swap_end_of_month_aligns_coupon_grid(): + valuation_date = Date(31, 5, 2023) + + swaps = [] + for tenor in ["6M", "1Y"]: + swaps.append( + IborSwap( + effective_dt=valuation_date, + term_dt_or_tenor=tenor, + fixed_leg_type=SwapTypes.PAY, + fixed_cpn=0.01, + fixed_freq_type=FrequencyTypes.QUARTERLY, + fixed_dc_type=DayCountTypes.ACT_360, + float_dc_type=DayCountTypes.ACT_360, + bd_type=BusDayAdjustTypes.NONE, + end_of_month=True, + ) + ) + + assert swaps[0].fixed_leg.payment_dts == [ + Date(31, 8, 2023), + Date(30, 11, 2023), + ] + assert swaps[1].fixed_leg.payment_dts[:2] == swaps[0].fixed_leg.payment_dts + + IborSingleCurve( + value_dt=valuation_date, + ibor_deposits=[], + ibor_fras=[], + ibor_swaps=swaps, + interp_type=InterpTypes.FLAT_FWD_RATES, + ) From 26f863c53391eef844e454fcfeb86d496509542e Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Jun 2026 09:04:52 +0800 Subject: [PATCH 2/2] Add IborSwap EOM schedule tests --- unit_tests/test_FinIborSwap.py | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/unit_tests/test_FinIborSwap.py b/unit_tests/test_FinIborSwap.py index adf697a9d..43da6be74 100644 --- a/unit_tests/test_FinIborSwap.py +++ b/unit_tests/test_FinIborSwap.py @@ -42,3 +42,53 @@ def test_ibor_swap_end_of_month_aligns_coupon_grid(): ibor_swaps=swaps, interp_type=InterpTypes.FLAT_FWD_RATES, ) + + +def test_ibor_swap_end_of_month_handles_non_eom_effective_date(): + swap = IborSwap( + effective_dt=Date(15, 5, 2023), + term_dt_or_tenor="1Y", + fixed_leg_type=SwapTypes.PAY, + fixed_cpn=0.01, + fixed_freq_type=FrequencyTypes.QUARTERLY, + fixed_dc_type=DayCountTypes.ACT_360, + float_dc_type=DayCountTypes.ACT_360, + bd_type=BusDayAdjustTypes.NONE, + end_of_month=True, + ) + + assert swap.fixed_leg.payment_dts == [ + Date(31, 5, 2023), + Date(31, 8, 2023), + Date(30, 11, 2023), + Date(29, 2, 2024), + Date(15, 5, 2024), + ] + + +def test_ibor_swap_end_of_month_applies_to_float_leg_schedule(): + swap = IborSwap( + effective_dt=Date(31, 5, 2023), + term_dt_or_tenor="1Y", + fixed_leg_type=SwapTypes.PAY, + fixed_cpn=0.01, + fixed_freq_type=FrequencyTypes.ANNUAL, + fixed_dc_type=DayCountTypes.ACT_360, + float_freq_type=FrequencyTypes.QUARTERLY, + float_dc_type=DayCountTypes.ACT_360, + bd_type=BusDayAdjustTypes.NONE, + end_of_month=True, + ) + + assert swap.float_leg.payment_dts == [ + Date(31, 8, 2023), + Date(30, 11, 2023), + Date(29, 2, 2024), + Date(31, 5, 2024), + ] + assert swap.float_leg.start_accrued_dts == [ + Date(31, 5, 2023), + Date(31, 8, 2023), + Date(30, 11, 2023), + Date(29, 2, 2024), + ]