import QuantLib as ql # ============================================================ # Global defaults (override via parameters if needed) # ============================================================ CALENDAR = ql.TARGET() BUSINESS_CONVENTION = ql.ModifiedFollowing DATE_GEN = ql.DateGeneration.Forward FIXED_DAYCOUNT = ql.Thirty360(ql.Thirty360.European) FLOAT_DAYCOUNT = ql.Actual360() # ============================================================ # Yield curve factory # ============================================================ def make_flat_curve( evaluation_date: ql.Date, rate: float, day_count: ql.DayCounter = ql.Actual365Fixed() ) -> ql.YieldTermStructureHandle: """ Flat yield curve factory. """ ql.Settings.instance().evaluationDate = evaluation_date curve = ql.FlatForward(evaluation_date, rate, day_count) return ql.YieldTermStructureHandle(curve) # ============================================================ # Index factory # ============================================================ def make_euribor_6m( curve_handle: ql.YieldTermStructureHandle ) -> ql.IborIndex: """ Euribor 6M index factory. """ return ql.Euribor6M(curve_handle) # ============================================================ # Schedule factory # ============================================================ def make_schedule( start: ql.Date, maturity: ql.Date, tenor: ql.Period, calendar: ql.Calendar = CALENDAR ) -> ql.Schedule: """ Generic schedule factory. """ return ql.Schedule( start, maturity, tenor, calendar, BUSINESS_CONVENTION, BUSINESS_CONVENTION, DATE_GEN, False ) # ============================================================ # Swap factory # ============================================================ def make_vanilla_swap( evaluation_date: ql.Date, curve_handle: ql.YieldTermStructureHandle, notional: float, fixed_rate: float, maturity_years: int, pay_fixed: bool = False, fixed_leg_freq: ql.Period = ql.Annual, float_leg_freq: ql.Period = ql.Semiannual ) -> ql.VanillaSwap: """ Vanilla fixed-for-float IRS factory. pay_fixed = True -> payer swap pay_fixed = False -> receiver swap """ ql.Settings.instance().evaluationDate = evaluation_date index = make_euribor_6m(curve_handle) start = CALENDAR.advance(evaluation_date, 2, ql.Days) maturity = CALENDAR.advance(start, maturity_years, ql.Years) fixed_schedule = make_schedule( start, maturity, ql.Period(fixed_leg_freq) ) float_schedule = make_schedule( start, maturity, ql.Period(float_leg_freq) ) swap_type = ( ql.VanillaSwap.Payer if pay_fixed else ql.VanillaSwap.Receiver ) swap = ql.VanillaSwap( swap_type, notional, fixed_schedule, fixed_rate, FIXED_DAYCOUNT, float_schedule, index, 0.0, FLOAT_DAYCOUNT ) swap.setPricingEngine( ql.DiscountingSwapEngine(curve_handle) ) return swap # ============================================================ # Swaption helper factory (for HW calibration) # ============================================================ def make_swaption_helpers( swaption_data: list, curve_handle: ql.YieldTermStructureHandle, index: ql.IborIndex, model: ql.HullWhite ) -> list: """ Create ATM swaption helpers. swaption_data = [(expiry, tenor, vol), ...] """ helpers = [] for expiry, tenor, vol in swaption_data: helper = ql.SwaptionHelper( expiry, tenor, ql.QuoteHandle(ql.SimpleQuote(vol)), index, index.tenor(), index.dayCounter(), index.dayCounter(), curve_handle ) helper.setPricingEngine( ql.JamshidianSwaptionEngine(model) ) helpers.append(helper) return helpers