From 88001167aa1270f078eb10d6651495334a52ce7a Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Thu, 11 Jul 2024 13:55:13 -0400 Subject: [PATCH 1/6] allow to build OvernightLeg --- quantlib/_cashflow.pxd | 6 +- quantlib/cashflows/_coupon.pxd | 2 +- quantlib/cashflows/_floating_rate_coupon.pxd | 2 +- .../cashflows/_overnight_indexed_coupon.pxd | 20 ++++++- .../cashflows/overnight_indexed_coupon.pxd | 3 +- .../cashflows/overnight_indexed_coupon.pyx | 55 ++++++++++++++++++- 6 files changed, 79 insertions(+), 9 deletions(-) diff --git a/quantlib/_cashflow.pxd b/quantlib/_cashflow.pxd index 8756c0dc1..d8a6ef165 100644 --- a/quantlib/_cashflow.pxd +++ b/quantlib/_cashflow.pxd @@ -5,19 +5,19 @@ from libcpp.vector cimport vector from quantlib.handle cimport shared_ptr, optional from quantlib.time._date cimport Date -cdef extern from 'ql/event.hpp' namespace 'QuantLib': +cdef extern from 'ql/event.hpp' namespace 'QuantLib' nogil: cdef cppclass Event: Date date() bool hasOccurred(Date& refDate, optional[bool] includeRefDate) -cdef extern from 'ql/cashflow.hpp' namespace 'QuantLib': +cdef extern from 'ql/cashflow.hpp' namespace 'QuantLib' nogil: cdef cppclass CashFlow(Event): Real amount() except + ctypedef vector[shared_ptr[CashFlow]] Leg -cdef extern from 'ql/cashflows/simplecashflow.hpp' namespace 'QuantLib': +cdef extern from 'ql/cashflows/simplecashflow.hpp' namespace 'QuantLib' nogil: cdef cppclass SimpleCashFlow(CashFlow): SimpleCashFlow(Real amount, Date& date) diff --git a/quantlib/cashflows/_coupon.pxd b/quantlib/cashflows/_coupon.pxd index 2ed9121d9..25e81a9bf 100644 --- a/quantlib/cashflows/_coupon.pxd +++ b/quantlib/cashflows/_coupon.pxd @@ -5,7 +5,7 @@ from quantlib.time._daycounter cimport DayCounter from quantlib._cashflow cimport CashFlow from quantlib._interest_rate cimport InterestRate -cdef extern from 'ql/cashflows/coupon.hpp' namespace 'QuantLib': +cdef extern from 'ql/cashflows/coupon.hpp' namespace 'QuantLib' nogil: cdef cppclass Coupon(CashFlow): Coupon(const Date& paymentDate, Real nominal, diff --git a/quantlib/cashflows/_floating_rate_coupon.pxd b/quantlib/cashflows/_floating_rate_coupon.pxd index bce1eaa34..edb4773d5 100644 --- a/quantlib/cashflows/_floating_rate_coupon.pxd +++ b/quantlib/cashflows/_floating_rate_coupon.pxd @@ -9,7 +9,7 @@ from quantlib.cashflows._coupon cimport Coupon from ._coupon_pricer cimport FloatingRateCouponPricer from quantlib.indexes._interest_rate_index cimport InterestRateIndex -cdef extern from 'ql/cashflows/floatingratecoupon.hpp' namespace 'QuantLib': +cdef extern from 'ql/cashflows/floatingratecoupon.hpp' namespace 'QuantLib' nogil: cdef cppclass FloatingRateCoupon(Coupon): FloatingRateCoupon(const Date& paymentDate, Real nominal, diff --git a/quantlib/cashflows/_overnight_indexed_coupon.pxd b/quantlib/cashflows/_overnight_indexed_coupon.pxd index dc9a5c14b..0bc7c1a88 100644 --- a/quantlib/cashflows/_overnight_indexed_coupon.pxd +++ b/quantlib/cashflows/_overnight_indexed_coupon.pxd @@ -1,15 +1,19 @@ -from quantlib.types cimport Natural, Rate, Real, Spread, Time +from quantlib.types cimport Integer, Natural, Rate, Real, Spread, Time from libcpp cimport bool from libcpp.vector cimport vector from quantlib.handle cimport shared_ptr from quantlib.time._date cimport Date from quantlib.time._daycounter cimport DayCounter +from quantlib.time._schedule cimport Schedule +from quantlib.time._calendar cimport Calendar +from quantlib.time.businessdayconvention cimport BusinessDayConvention + from quantlib.indexes._ibor_index cimport OvernightIndex from ._floating_rate_coupon cimport FloatingRateCoupon from .rateaveraging cimport RateAveraging -cdef extern from 'ql/cashflows/overnightindexedcoupon.hpp' namespace 'QuantLib': +cdef extern from 'ql/cashflows/overnightindexedcoupon.hpp' namespace 'QuantLib' nogil: cdef cppclass OvernightIndexedCoupon(FloatingRateCoupon): OvernightIndexedCoupon(const Date& paymentDate, Real nominal, @@ -34,3 +38,15 @@ cdef extern from 'ql/cashflows/overnightindexedcoupon.hpp' namespace 'QuantLib': RateAveraging averagingMethod() Natural lockoutDays() bool applyObservationShift() + + cdef cppclass OvernightLeg: + OvernightLeg(Schedule schedule, shared_ptr[OvernightIndex] overnightIndex) + OvernightLeg& withNotionals(Real notional) + OvernightLeg& withPaymentDayCounter(DayCounter dc) + OvernightLeg& withPaymentAdjustment(BusinessDayConvention) + OvernightLeg& withPaymentCalendar(Calendar) + OvernightLeg& withPaymentLag(Integer Lag) + OvernightLeg& withSpreads(Real spread) + OvernightLeg& withObservationShift(bool) + OvernightLeg& withLookbackDays(Natural) + OvernightLeg& withLockoutDays(Natural) diff --git a/quantlib/cashflows/overnight_indexed_coupon.pxd b/quantlib/cashflows/overnight_indexed_coupon.pxd index 28fd90a63..49584b408 100644 --- a/quantlib/cashflows/overnight_indexed_coupon.pxd +++ b/quantlib/cashflows/overnight_indexed_coupon.pxd @@ -1,8 +1,9 @@ from .floating_rate_coupon cimport FloatingRateCoupon from quantlib.cashflow cimport Leg +from . cimport _overnight_indexed_coupon as _oic cdef class OvernightIndexedCoupon(FloatingRateCoupon): pass cdef class OvernightLeg(Leg): - pass + cdef _oic.OvernightLeg* leg diff --git a/quantlib/cashflows/overnight_indexed_coupon.pyx b/quantlib/cashflows/overnight_indexed_coupon.pyx index 858f06c7c..523456d2f 100644 --- a/quantlib/cashflows/overnight_indexed_coupon.pyx +++ b/quantlib/cashflows/overnight_indexed_coupon.pyx @@ -1,11 +1,14 @@ -from quantlib.types cimport Natural, Real, Spread +from quantlib.types cimport Integer, Natural, Real, Spread from libcpp cimport bool from libcpp.vector cimport vector from cython.operator cimport dereference as deref, preincrement as preinc from quantlib.handle cimport make_shared, shared_ptr, static_pointer_cast +from quantlib.time.businessdayconvention cimport BusinessDayConvention from quantlib.time.date cimport Date, date_from_qldate from quantlib.time._date cimport Date as QlDate +from quantlib.time.calendar cimport Calendar +from quantlib.time.schedule cimport Schedule from quantlib.time.daycounter cimport DayCounter from quantlib.indexes.ibor_index cimport OvernightIndex from quantlib.utilities.null cimport Null @@ -13,6 +16,7 @@ cimport quantlib.indexes._ibor_index as _ii cimport quantlib._cashflow as _cf from .rateaveraging cimport RateAveraging from . cimport _overnight_indexed_coupon as _oic +from .._cashflow cimport Leg as QlLeg cdef class OvernightIndexedCoupon(FloatingRateCoupon): @@ -89,3 +93,52 @@ cdef class OvernightLeg(Leg): oic._thisptr = deref(it) yield oic preinc(it) + + def __init__(self, Schedule schedule, OvernightIndex index): + self.leg = new _oic.OvernightLeg(schedule._thisptr, + static_pointer_cast[_ii.OvernightIndex](index._thisptr)) + + def __dealloc__(self): + if self.leg is not NULL: + del self.leg + self.leg = NULL + + def with_notionals(self, Real notional): + self.leg.withNotionals(notional) + return self + + def with_payment_day_counter(self, DayCounter dc): + self.leg.withPaymentDayCounter(deref(dc._thisptr)) + return self + + def with_payment_adjustment(self, BusinessDayConvention bdc): + self.withPaymentAdjustment(bdc) + return self + + def with_payment_calendar(self, Calendar cal): + self.leg.withPaymentCalendar(cal._thisptr) + return self + + def with_spreads(self, Spread spread): + self.leg.withSpreads(spread) + return self + + def with_observation_shift(self, bool apply_observation_shift=True): + self.leg.withObservationShift(apply_observation_shift) + return self + + def with_lookback_days(self, Natural lookback_days): + self.leg.withLookbackDays(lookback_days) + return self + + def with_lockout_days(self, Natural lockout_days): + self.leg.withLockoutDays(lockout_days) + return self + + def with_payment_lag(self, Integer lag): + self.leg.withPaymentLag(lag) + return self + + def __call__(self): + self._thisptr = deref(self.leg) + return self From 4b921fdc88be25dfbaf233cbffbc7ef594b1601f Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Fri, 7 Feb 2025 16:26:17 -0500 Subject: [PATCH 2/6] allow to build non standard swaps --- quantlib/_instrument.pxd | 2 +- quantlib/instruments/_swap.pxd | 11 +++++------ quantlib/instruments/swap.pyx | 32 ++++---------------------------- 3 files changed, 10 insertions(+), 35 deletions(-) diff --git a/quantlib/_instrument.pxd b/quantlib/_instrument.pxd index cf94572fe..d28985071 100644 --- a/quantlib/_instrument.pxd +++ b/quantlib/_instrument.pxd @@ -6,7 +6,7 @@ from quantlib.time._date cimport Date from libcpp.string cimport string from libcpp cimport bool -cdef extern from 'ql/instrument.hpp' namespace 'QuantLib': +cdef extern from 'ql/instrument.hpp' namespace 'QuantLib' nogil: cdef cppclass Instrument: Instrument() bool isExpired() diff --git a/quantlib/instruments/_swap.pxd b/quantlib/instruments/_swap.pxd index 4465cf5a2..7040cf66e 100644 --- a/quantlib/instruments/_swap.pxd +++ b/quantlib/instruments/_swap.pxd @@ -7,8 +7,7 @@ FOR A PARTICULAR PURPOSE. See the license for more details. """ -include '../types.pxi' - +from quantlib.types cimport DiscountFactor, Real, Size from libcpp.vector cimport vector from libcpp cimport bool @@ -22,11 +21,11 @@ from quantlib._cashflow cimport Leg cdef extern from 'ql/instruments/swap.hpp' namespace 'QuantLib' nogil: cdef cppclass Swap(Instrument): - ## Swap(Leg& firstLeg, - ## Leg& secondLeg) + Swap(Leg& firstLeg, + Leg& secondLeg) - ## Swap(vector[Leg]& legs, - ## vector[bool]& payer) + Swap(vector[Leg]& legs, + vector[bool]& payer) bool isExpired() Size numberOfLegs() Date startDate() diff --git a/quantlib/instruments/swap.pyx b/quantlib/instruments/swap.pyx index 070b7f312..7eb23a31a 100644 --- a/quantlib/instruments/swap.pyx +++ b/quantlib/instruments/swap.pyx @@ -27,35 +27,11 @@ cdef class Swap(Instrument): Payer = Type.Payer Receiver = Type.Receiver - def __init__(self): - raise NotImplementedError('Generic swap not yet implemented. \ - Please use child classes.') + def __init__(self, Leg first_leg, Leg second_leg): + """ The cash flows belonging to the first leg are paid; + the ones belonging to the second leg are received""" - ## def __init__(self, Leg firstLeg, - ## Leg secondLeg): - - ## cdef _cf.Leg* leg1 = firstLeg._thisptr.get() - ## cdef _cf.Leg* leg2 = secondLeg._thisptr.get() - - ## self._thisptr = new shared_ptr[_instrument.Instrument](\ - ## new _swap.Swap(deref(leg1), - ## deref(leg2))) - - - ## def __init__(self, vector[Leg] legs, - ## vector[bool] payer): - - ## cdef vector[_cf.Leg]* _legs = new vector[_cf.Leg](len(legs)) - ## for l in legs: - ## _legs.push_back(l) - - ## cdef vector[bool]* _payer = new vector[bool](len(payer)) - ## for p in payer: - ## _payer.push_back(p) - - ## self._thisptr = new shared_ptr[_instrument.Instrument](\ - ## new _swap.Swap(_legs, payer) - ## ) + self._thisptr.reset(new _swap.Swap(first_leg._thisptr, second_leg._thisptr)) property start_date: def __get__(self): From 5ecdb668741a4bac165b0b6522495b7839c521a9 Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Mon, 10 Feb 2025 15:06:44 -0500 Subject: [PATCH 3/6] add various legs to the api --- quantlib/cashflows/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quantlib/cashflows/api.py b/quantlib/cashflows/api.py index 6772683e4..a554a163d 100644 --- a/quantlib/cashflows/api.py +++ b/quantlib/cashflows/api.py @@ -1,4 +1,5 @@ -from .overnight_indexed_coupon import OvernightIndexedCoupon +from .overnight_indexed_coupon import OvernightIndexedCoupon, OvernightLeg from .cms_coupon import CmsCoupon from .ibor_coupon import IborCoupon -from .fixed_rate_coupon import FixedRateCoupon +from .fixed_rate_coupon import FixedRateCoupon, FixedRateLeg +from ..cashflow import SimpleCashFlow, Leg From ec9a894f8fa8d27e2985e80f487d7f0791205755 Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Mon, 10 Feb 2025 15:09:24 -0500 Subject: [PATCH 4/6] use move --- quantlib/cashflows/_overnight_indexed_coupon.pxd | 9 +++++++++ quantlib/cashflows/fixed_rate_coupon.pyx | 4 +++- quantlib/cashflows/overnight_indexed_coupon.pyx | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/quantlib/cashflows/_overnight_indexed_coupon.pxd b/quantlib/cashflows/_overnight_indexed_coupon.pxd index 0bc7c1a88..3b8b0a8cd 100644 --- a/quantlib/cashflows/_overnight_indexed_coupon.pxd +++ b/quantlib/cashflows/_overnight_indexed_coupon.pxd @@ -12,6 +12,7 @@ from quantlib.time.businessdayconvention cimport BusinessDayConvention from quantlib.indexes._ibor_index cimport OvernightIndex from ._floating_rate_coupon cimport FloatingRateCoupon from .rateaveraging cimport RateAveraging +from .._cashflow cimport Leg cdef extern from 'ql/cashflows/overnightindexedcoupon.hpp' namespace 'QuantLib' nogil: cdef cppclass OvernightIndexedCoupon(FloatingRateCoupon): @@ -50,3 +51,11 @@ cdef extern from 'ql/cashflows/overnightindexedcoupon.hpp' namespace 'QuantLib' OvernightLeg& withObservationShift(bool) OvernightLeg& withLookbackDays(Natural) OvernightLeg& withLockoutDays(Natural) + Leg operator() const + +cdef extern from 'ql/cashflows/overnightindexedcoupon.hpp': + # this allows to declare the cast operator as raising exceptions + """ + #define to_leg(x) static_cast(x) + """ + cdef Leg to_leg(OvernightLeg) except + diff --git a/quantlib/cashflows/fixed_rate_coupon.pyx b/quantlib/cashflows/fixed_rate_coupon.pyx index 522b1d76a..f1d8a114e 100644 --- a/quantlib/cashflows/fixed_rate_coupon.pyx +++ b/quantlib/cashflows/fixed_rate_coupon.pyx @@ -1,6 +1,7 @@ from quantlib.types cimport Rate, Real from cython.operator cimport dereference as deref, preincrement as preinc from libcpp.vector cimport vector +from libcpp.utility cimport move from quantlib.compounding cimport Compounding from quantlib.handle cimport shared_ptr from quantlib.time.businessdayconvention cimport BusinessDayConvention @@ -11,6 +12,7 @@ from quantlib.time.daycounter cimport DayCounter from quantlib.time.schedule cimport Schedule cimport quantlib._cashflow as _cf from quantlib.interest_rate cimport InterestRate +from .._cashflow cimport Leg as QlLeg cimport quantlib._interest_rate as _ir cdef class FixedRateCoupon(Coupon): @@ -60,7 +62,7 @@ cdef class FixedRateLeg(Leg): return self def __call__(self): - self._thisptr = _frc.to_leg(deref(self.frl)) + self._thisptr = move[QlLeg](_frc.to_leg(deref(self.frl))) return self def __iter__(self): diff --git a/quantlib/cashflows/overnight_indexed_coupon.pyx b/quantlib/cashflows/overnight_indexed_coupon.pyx index 523456d2f..5c427d4c1 100644 --- a/quantlib/cashflows/overnight_indexed_coupon.pyx +++ b/quantlib/cashflows/overnight_indexed_coupon.pyx @@ -2,6 +2,7 @@ from quantlib.types cimport Integer, Natural, Real, Spread from libcpp cimport bool from libcpp.vector cimport vector +from libcpp.utility cimport move from cython.operator cimport dereference as deref, preincrement as preinc from quantlib.handle cimport make_shared, shared_ptr, static_pointer_cast from quantlib.time.businessdayconvention cimport BusinessDayConvention @@ -140,5 +141,5 @@ cdef class OvernightLeg(Leg): return self def __call__(self): - self._thisptr = deref(self.leg) + self._thisptr = move[QlLeg](_oic.to_leg(deref(self.leg))) return self From 8f10b6bdfa864b03344e3d7779872461ffca8cd7 Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Wed, 5 Mar 2025 22:00:49 -0500 Subject: [PATCH 5/6] MakeOIS: add more methods --- quantlib/instruments/_make_ois.pxd | 16 ++++++++++++++++ quantlib/instruments/make_ois.pyx | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/quantlib/instruments/_make_ois.pxd b/quantlib/instruments/_make_ois.pxd index cc979a5bc..61d7a3c1a 100644 --- a/quantlib/instruments/_make_ois.pxd +++ b/quantlib/instruments/_make_ois.pxd @@ -31,13 +31,29 @@ cdef extern from 'ql/instruments/makeois.hpp' namespace 'QuantLib': MakeOIS& withEffectiveDate(const Date&) MakeOIS& withTerminationDate(const Date&) MakeOIS& withRule(DateGeneration r) + MakeOIS& withFixedLegRule(DateGeneration r) + MakeOIS& withOvernightLegRule(DateGeneration r) MakeOIS& withPaymentFrequency(Frequency f) + MakeOIS& withFixedLegPaymentFrequency(Frequency f) + MakeOIS& withOvernightLegPaymentFrequency(Frequency f) MakeOIS& withPaymentAdjustment(BusinessDayConvention convention) MakeOIS& withPaymentLag(Natural lag) MakeOIS& withPaymentCalendar(const Calendar& cal) + MakeOIS& withCalendar(const Calendar& cal) + MakeOIS& withFixedLegCalendar(const Calendar& cal) + MakeOIS& withOvernightLegCalendar(const Calendar& cal) + MakeOIS& withConvention(BusinessDayConvention bdc) + MakeOIS& withFixedLegConvention(BusinessDayConvention bdc) + MakeOIS& withOvernightLegConvention(BusinessDayConvention bdc) + MakeOIS& withTerminationDateConvention(BusinessDayConvention bdc) + MakeOIS& withFixedLegTerminationDateConvention(BusinessDayConvention bdc) + MakeOIS& withOvernightLegTerminationDateConvention(BusinessDayConvention bdc) MakeOIS& withEndOfMonth(bool flag) # = true); + MakeOIS& withFixedLegEndOfMonth(bool flag) # = true); + MakeOIS& withOvernightLegEndOfMonth(bool flag) # = true); + MakeOIS& withFixedLegDayCount(const DayCounter& dc) diff --git a/quantlib/instruments/make_ois.pyx b/quantlib/instruments/make_ois.pyx index 9fbc461fe..81e36c174 100644 --- a/quantlib/instruments/make_ois.pyx +++ b/quantlib/instruments/make_ois.pyx @@ -74,6 +74,14 @@ cdef class MakeOIS: self._thisptr.withPaymentFrequency(f) return self + def with_fixed_leg_payment_frequency(self, Frequency f): + self._thisptr.withFixedLegPaymentFrequency(f) + return self + + def with_overnight_leg_payment_frequency(self, Frequency f): + self._thisptr.withOvernightLegPaymentFrequency(f) + return self + def with_payment_adjustment(self, BusinessDayConvention convention): self._thisptr.withPaymentAdjustment(convention) return self From be2ebc1e4702644e18e0f8d596ba22fb2d0e26c9 Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Thu, 6 Mar 2025 12:39:22 -0500 Subject: [PATCH 6/6] try to make code more efficient --- quantlib/instruments/_swap.pxd | 1 + quantlib/instruments/swap.pyx | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/quantlib/instruments/_swap.pxd b/quantlib/instruments/_swap.pxd index 7040cf66e..2644cb69d 100644 --- a/quantlib/instruments/_swap.pxd +++ b/quantlib/instruments/_swap.pxd @@ -36,3 +36,4 @@ cdef extern from 'ql/instruments/swap.hpp' namespace 'QuantLib' nogil: DiscountFactor endDiscounts(Size j) except + DiscountFactor npvDateDiscount() except + Leg& leg(Size j) except + + const vector[Leg]& legs() diff --git a/quantlib/instruments/swap.pyx b/quantlib/instruments/swap.pyx index 7eb23a31a..3d2eaa2aa 100644 --- a/quantlib/instruments/swap.pyx +++ b/quantlib/instruments/swap.pyx @@ -11,9 +11,11 @@ from quantlib.types cimport Size from quantlib.cashflow cimport Leg cimport quantlib.time._date as _date from quantlib.time.date cimport date_from_qldate +from quantlib._cashflow cimport Leg as QlLeg + from . cimport _swap -cdef inline _swap.Swap* get_swap(Swap swap): +cdef inline _swap.Swap* get_swap(Swap swap) noexcept: """ Utility function to extract a properly casted Swap pointer out of the internal _thisptr attribute of the Instrument base class. """ @@ -60,10 +62,18 @@ cdef class Swap(Instrument): def leg(self, int i): cdef Leg leg = Leg.__new__(Leg) - leg._thisptr = get_swap(self).leg(i) + cdef _swap.Swap* swap = <_swap.Swap*>self._thisptr.get() + if 0 <= i < swap.numberOfLegs(): + leg._thisptr = swap.legs()[i] + else: + raise IndexError(f"leg #{i} doesn't exist") return leg def __getitem__(self, int i): cdef Leg leg = Leg.__new__(Leg) - leg._thisptr = get_swap(self).leg(i) + cdef _swap.Swap* swap = <_swap.Swap*>self._thisptr.get() + if 0 <= i < swap.numberOfLegs(): + leg._thisptr = swap.legs()[i] + else: + raise IndexError(f"leg #{i} doesn't exist") return leg