diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0294819d2..1446a92aa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: sudo apt-get install libquantlib0-dev # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 @@ -49,7 +49,7 @@ jobs: # Publish built docs to gh-pages branch. - name: Checkout documentation branch if: matrix.python-version == '3.11' - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: gh-pages path: gh-pages diff --git a/quantlib/instruments/_assetswap.pxd b/quantlib/instruments/_assetswap.pxd new file mode 100644 index 000000000..6cc66d635 --- /dev/null +++ b/quantlib/instruments/_assetswap.pxd @@ -0,0 +1,38 @@ +from libcpp cimport bool +from quantlib.types cimport Real, Spread +from quantlib.time._schedule cimport Schedule +from ._bond cimport Bond +from quantlib.handle cimport shared_ptr +from quantlib.time._date cimport Date +from quantlib.time._daycounter cimport DayCounter +from .._cashflow cimport Leg +from ._swap cimport Swap +from quantlib.indexes._ibor_index cimport IborIndex + +cdef extern from "ql/instruments/assetswap.hpp" namespace "QuantLib" nogil: + cdef cppclass AssetSwap(Swap): + AssetSwap(bool payBondCoupon, + shared_ptr[Bond] bond, + Real bondCleanPrice, + const shared_ptr[IborIndex]& iborIndex, + Spread spread, + Schedule floatSchedule, + const DayCounter& floatingDayCounter, + bool parAssetSwap, # = true + Real gearing, # 1.0 + Real nonParRepayment, # = Null(), + Date dealMaturity) # = Date() + Spread fairSpread() except + + Real floatingLegBPS() except + + Real floatingLegNPV() except + + Real fairCleanPrice() except + + Real fairNonParRepayment() except + + + bool parSwap() + Spread spread() + Real cleanPrice() + Real nonParRepayment() + const shared_ptr[Bond]& bond() + bool payBondCoupon() + const Leg& bondLeg() + const Leg& floatingLeg() diff --git a/quantlib/instruments/_zerocouponswap.pxd b/quantlib/instruments/_zerocouponswap.pxd new file mode 100644 index 000000000..eaa9a7fe8 --- /dev/null +++ b/quantlib/instruments/_zerocouponswap.pxd @@ -0,0 +1,31 @@ +from quantlib.types cimport Natural, Rate, Real +from quantlib.handle cimport shared_ptr +from quantlib.time._calendar cimport BusinessDayConvention, Calendar +from quantlib.time._date cimport Date +from quantlib.time._daycounter cimport DayCounter +from quantlib.indexes._ibor_index cimport IborIndex +from .swap cimport Type +from ._swap cimport Swap + +cdef extern from "ql/instruments/zerocouponswap.hpp" namespace "QuantLib" nogil: + cdef cppclass ZeroCouponSwap(Swap): + ZeroCouponSwap(Type type, + Real baseNominal, + const Date& startDate, + const Date& maturityDate, + Real fixedPayment, + shared_ptr[IborIndex] iborIndex, + const Calendar& paymentCalendar, + BusinessDayConvention paymentConvention,# = Following, + Natural paymentDelay) # = 0 + + ZeroCouponSwap(Type type, + Real baseNominal, + const Date& startDate, + const Date& maturityDate, + Rate fixedRate, + const DayCounter& fixedDayCounter, + shared_ptr[IborIndex] iborIndex, + const Calendar& paymentCalendar, + BusinessDayConvention paymentConvention,# = Following, + Natural paymentDelay) # = 0) diff --git a/quantlib/instruments/api.py b/quantlib/instruments/api.py index 3643b82fd..728c883cd 100644 --- a/quantlib/instruments/api.py +++ b/quantlib/instruments/api.py @@ -1,3 +1,4 @@ +from .assetswap import AssetSwap from .bonds import FixedRateBond, ZeroCouponBond, FloatingRateBond from .bondforward import BondForward from .credit_default_swap import CreditDefaultSwap, PricingModel @@ -15,3 +16,4 @@ from .overnightindexfuture import OvernightIndexFuture from .overnightindexedswap import OvernightIndexedSwap from .make_ois import MakeOIS +from .zerocouponswap import ZeroCouponSwap diff --git a/quantlib/instruments/assetswap.pxd b/quantlib/instruments/assetswap.pxd new file mode 100644 index 000000000..d8ab6ba04 --- /dev/null +++ b/quantlib/instruments/assetswap.pxd @@ -0,0 +1,4 @@ +from .swap cimport Swap + +cdef class AssetSwap(Swap): + pass diff --git a/quantlib/instruments/assetswap.pyx b/quantlib/instruments/assetswap.pyx new file mode 100644 index 000000000..3037885e9 --- /dev/null +++ b/quantlib/instruments/assetswap.pyx @@ -0,0 +1,69 @@ +from cython.operator cimport dereference as deref +from libcpp cimport bool +from quantlib.types cimport Real, Spread +from quantlib.handle cimport shared_ptr, static_pointer_cast +from quantlib.indexes.ibor_index cimport IborIndex +from quantlib.indexes cimport _ibor_index as _ib +from quantlib.time.schedule cimport Schedule +from quantlib.time.date cimport Date +from quantlib.time.daycounter cimport DayCounter +from quantlib.utilities.null cimport Null +from quantlib.cashflow cimport Leg +from quantlib._cashflow cimport Leg as QlLeg +from . cimport _assetswap as _asw +from .bond cimport Bond +from ._bond cimport Bond as QlBond + +cdef class AssetSwap(Swap): + def __init__( + self, bool pay_bond_coupon, Bond bond, Real bond_clean_price, + IborIndex ibor_index, Spread spread, Schedule float_schedule=Schedule.__new__(Schedule), + DayCounter floating_day_counter=DayCounter(), bool par_asset_swap=True, + Real gearing=1.0, Real non_par_repayment=Null[Real](), Date deal_maturity=Date() + ): + self._thisptr.reset( + new _asw.AssetSwap(pay_bond_coupon, + static_pointer_cast[QlBond](bond._thisptr), + bond_clean_price, + static_pointer_cast[_ib.IborIndex](ibor_index._thisptr), + spread, + float_schedule._thisptr, + deref(floating_day_counter._thisptr), + par_asset_swap, + gearing, + non_par_repayment, + deal_maturity._thisptr + ) + ) + + @property + def fair_spread(self): + return (<_asw.AssetSwap*>self._thisptr.get()).fairSpread() + + @property + def floating_leg_BPS(self): + return (<_asw.AssetSwap*>self._thisptr.get()).floatingLegBPS() + + @property + def floating_leg_NPV(self): + return (<_asw.AssetSwap*>self._thisptr.get()).floatingLegNPV() + + @property + def fair_clean_price(self): + return (<_asw.AssetSwap*>self._thisptr.get()).fairCleanPrice() + + @property + def fair_non_par_repayment(self): + return (<_asw.AssetSwap*>self._thisptr.get()).fairNonParRepayment() + + @property + def bond_leg(self): + cdef Leg leg = Leg.__new__(Leg) + leg._thisptr = (<_asw.AssetSwap*>self._thisptr.get()).bondLeg() + return leg + + @property + def floating_leg(self): + cdef Leg leg = Leg.__new__(Leg) + leg._thisptr = (<_asw.AssetSwap*>self._thisptr.get()).floatingLeg() + return leg diff --git a/quantlib/instruments/zerocouponswap.pxd b/quantlib/instruments/zerocouponswap.pxd new file mode 100644 index 000000000..3df780634 --- /dev/null +++ b/quantlib/instruments/zerocouponswap.pxd @@ -0,0 +1,4 @@ +from .swap cimport Swap, Type + +cdef class ZeroCouponSwap(Swap): + pass diff --git a/quantlib/instruments/zerocouponswap.pyx b/quantlib/instruments/zerocouponswap.pyx new file mode 100644 index 000000000..4c01f8db6 --- /dev/null +++ b/quantlib/instruments/zerocouponswap.pyx @@ -0,0 +1,24 @@ +from quantlib.types cimport Real, Natural +from quantlib.handle cimport static_pointer_cast +from quantlib.time.date cimport Date +from quantlib.time.calendar cimport Calendar +from quantlib.time.businessdayconvention cimport BusinessDayConvention, Following +from quantlib.indexes.ibor_index cimport IborIndex +from quantlib.indexes cimport _ibor_index as _ib +from . cimport _zerocouponswap as _zcs + +cdef class ZeroCouponSwap(Swap): + def __init__(self, Type type, Real nominal, Date start_date, Date maturity, Real fixed_payment, IborIndex ibor_index, Calendar payment_calendar, BusinessDayConvention payment_convention=Following, Natural payment_delay=0): + self._thisptr.reset( + new _zcs.ZeroCouponSwap( + type, + nominal, + start_date._thisptr, + maturity._thisptr, + fixed_payment, + static_pointer_cast[_ib.IborIndex](ibor_index._thisptr), + payment_calendar._thisptr, + payment_convention, + payment_delay + ) + ) diff --git a/test/test_asian.py b/test/test_asian.py index 78ed69e5a..e4d64ce5d 100644 --- a/test/test_asian.py +++ b/test/test_asian.py @@ -22,6 +22,7 @@ from quantlib.quotes import SimpleQuote from quantlib.termstructures.volatility.api import BlackConstantVol +import contextlib import unittest from .utilities import flat_rate @@ -42,9 +43,11 @@ class AsianOptionTestCase(unittest.TestCase): """ def setUp(self): - self.settings = Settings() - + stack = contextlib.ExitStack() + self.settings = stack.enter_context(Settings()) + self.addCleanup(stack.close) + self.calendar = NullCalendar() self.today = Date(6, June, 2021) @@ -96,10 +99,9 @@ def setUp(self): self.payoff = PlainVanillaPayoff(self.option_type, self.strike) - def test_analytic_cont_geom_av_price(self): """ - "Testing analytic continuous geometric average-price Asians...") + Testing analytic continuous geometric average-price Asians data from "Option Pricing Formulas", Haug, pag.96-97 """ diff --git a/test/test_assetswap.py b/test/test_assetswap.py new file mode 100644 index 000000000..3cb2a8f84 --- /dev/null +++ b/test/test_assetswap.py @@ -0,0 +1,59 @@ +import unittest + +from quantlib.time.api import Date, Period, Annual, TARGET, Unadjusted, Schedule, DateGeneration, Actual365Fixed, Semiannual, ActualActual, Following +from quantlib.instruments.api import FixedRateBond, AssetSwap +from quantlib.pricingengines.api import DiscountingBondEngine, DiscountingSwapEngine +from quantlib.termstructures.yields.api import HandleYieldTermStructure +from quantlib.indexes.api import Euribor +from .utilities import flat_rate + +class TestMarketASWSpread(unittest.TestCase): + def setUp(self): + self.term_structure = HandleYieldTermStructure() + self.today = Date(24, 4, 2007) + self.ibor_index = Euribor(Period(Semiannual), self.term_structure) + self.term_structure.link_to(flat_rate(0.05, Actual365Fixed(), self.today)) + self.spread = 0.0 + self.face_amount = 100.0 + self.settlement_days = 2 + + def test_market_vs_par_asset_swap(self): + """Testing relationship between market asset swap and par asset swap...""" + + pay_fixed_rate = True + par_asset_swap = True + mkt_asset_swap = False + fixed_bond_schedule1 = Schedule.from_rule(Date(4, 1, 2005), + Date(4, 1, 2037), + Period(Annual), + TARGET(), + Unadjusted, Unadjusted, + DateGeneration.Backward, False) + fixed_bond1 = FixedRateBond(self.settlement_days, self.face_amount, fixed_bond_schedule1, + [0.04], ActualActual(ActualActual.ISDA), Following, + 100.0, Date(4, 1, 2005)) + bond_engine = DiscountingBondEngine(self.term_structure) + swap_engine = DiscountingSwapEngine(self.term_structure) + fixed_bond1.set_pricing_engine(bond_engine) + fixed_bond_mkt_price1 = 89.22 # market price observed on June 7th 2007 + fixed_bond_mkt_full_price1 = fixed_bond_mkt_price1 + fixed_bond1.accrued_amount() + fixed_bond_par_asset_swap1 = AssetSwap(pay_fixed_rate, + fixed_bond1, fixed_bond_mkt_price1, + self.ibor_index, + self.spread, + floating_day_counter=self.ibor_index.day_counter, + par_asset_swap=True) + fixed_bond_par_asset_swap1.set_pricing_engine(swap_engine) + fixed_bond_par_asset_swap_spread1 = fixed_bond_par_asset_swap1.fair_spread + fixed_bond_mkt_asset_swap1 = AssetSwap(pay_fixed_rate, + fixed_bond1, + fixed_bond_mkt_price1, + self.ibor_index, + self.spread, + floating_day_counter=self.ibor_index.day_counter, + par_asset_swap=False) + fixed_bond_mkt_asset_swap1.set_pricing_engine(swap_engine) + fixed_bond_mkt_asset_swap_spread1 = fixed_bond_mkt_asset_swap1.fair_spread + + self.assertAlmostEqual(fixed_bond_mkt_asset_swap_spread1, + 100 * fixed_bond_par_asset_swap_spread1 / fixed_bond_mkt_full_price1)