|
Accurate modeling of cashflows is a cornerstone of any fixed income portfolio valuation and cashflow management platform. One of the essential ingredients in cashflow modeling is the generation of cashflow dates, and QuantLib.Schedule() function provides a flexible way to do this. However, its interface isn’t exactly straightforward. | |
The Challenge with QuantLib.ScheduleAccording to the SWIG-generated documentation, the QuantLib.Schedule() constructor accepts the following ten parameters:
While this constructor is functional, the parameters are quite cumbersome to work with and do not follow industry-standards especially around defining stubs and roll mechanisms My Take on a More Convenient ApproachWhile expanding portfolio valuation functionality in Kupala-Nich, I came up with a different interface for defining the schedule that provides a more intuitive way of defining cashflows and by extension swaps. Before looking at the new interface, let’s review the existing interface field by field.
The final four fields have a complex interconnection. These fields are not very intuitive and don’t match industry conventions I propose an alternative: replacing them with just two fields:
In fact, for the majority of swaps, there are only nine valid combinations of these values:
Here is the implementation of a function generates these four fields from rollType and stubType: get_roll_fields( roll_type: str, stub_type: str, start_date: ql.Date, maturity_date: ql.Date, roll_period: ql.Period, calendar: ql.Calendar, roll_conv, term_roll_conv) : first_roll_date = last_roll_date = ql.Date() end_of_month = False if roll_type == "IMM": if stub_type != "Front Short": raise ValueError("IMM roll_type requires stub_type='Front Short'") date_rule = ql.DateGeneration.TwentiethIMM elif roll_type == "EOM": end_of_month = True if stub_type.startswith("Front"): if not ql.Date.isEndOfMonth(maturity_date): raise ValueError("EOM roll_type with Front stub requires EOM maturity_date") date_rule = ql.DateGeneration.Backward elif stub_type.startswith("End"): if not ql.Date.isEndOfMonth(start_date): raise ValueError("EOM roll_type with End stub requires EOM start_date") date_rule = ql.DateGeneration.Forward else: raise ValueError(f"Unsupported stub_type for EOM: {stub_type}") else: # Standard roll types if stub_type.startswith("Front"): date_rule = ql.DateGeneration.Backward elif stub_type.startswith("End"): date_rule = ql.DateGeneration.Forward else: raise ValueError(f"Unsupported stub_type: {stub_type}") if "Long" in stub_type: s = list( ql.Schedule( start_date, maturity_date, roll_period, calendar, roll_conv, term_roll_conv, date_rule, end_of_month ) ) if len(s) >= 3: if stub_type.startswith("Front"): first_roll_date = s[2] elif stub_type.startswith("End"): last_roll_date = s[-3] else: raise ValueError("Schedule has too few dates for Long stub") return date_rule, end_of_month, first_roll_date, last_roll_date Schedule generation with Kupala-NichWith Kupala-Nich, I need to generate swap cashflows and by extension schedule based on user input in csv format. This format needs to be simple for basic usecaes but should scale to support all commonly traded swap flavors. To support this, the generate_schedule function has only two required parameters. template which defines the governing conventions for the swap being traded and maturityDate. The function also accepts seven optional arguments that fine grained control over cashflow schedule. Here is the implementation of this function. def generate_schedule(template, maturity_date, start_date = None, calendar = None, roll_period = None, roll_conv = None, term_roll_conv = None, roll_type = None, stub_type = None): start_date = template.get_and_validate_start_date(start_date) calendar = template.get_and_validate_calendar(calendar) roll_period = template.get_and_validate_roll_period(roll_period) roll_conv = template.get_and_validate_roll_conv(roll_conv) term_roll_conv= template.get_and_validate_term_roll_conv(term_roll_conv) roll_type = template.get_and_validate_roll_type(roll_type) stub_type = template.get_and_validate_stub_type(stub_type) date_rule, end_of_month, first_roll_date, last_roll_date = get_roll_fields( roll_type, stub_type, start_date, maturity_date, roll_period, calendar, roll_conv, term_roll_conv) return ql.Schedule(start_date, maturity_date, roll_period, calendar, roll_conv, term_roll_conv, date_rule, end_of_month, first_roll_date, last_roll_date) Conclusion
| |