This document covers QuantLib's bond instruments, cash flows, and fixed income analytics.
- Bond Base Class
- ZeroCouponBond
- FixedRateBond
- FloatingRateBond
- CallableBond and PuttableBond
- ConvertibleBond
- Cash Flow Classes
- Interest Rate Swaps
- Bond Pricing Engines
- Usage Examples
Inherits from: Instrument → LazyObject → Observable
All bond instruments inherit from the Bond base class, which provides common functionality for pricing, yield calculations, and cash flow analysis.
# Constructor with explicit cash flows
Bond(
settlementDays, # Natural: settlement delay in days
calendar, # Calendar: payment calendar
faceAmount, # Real: face/notional amount
maturityDate, # Date: bond maturity
issueDate=Date(), # Date: issue date (optional)
cashflows=Leg() # Leg: vector of cash flows (optional)
)
# Constructor with just cash flows
Bond(
settlementDays, # Natural: settlement delay in days
calendar, # Calendar: payment calendar
issueDate=Date(), # Date: issue date (optional)
coupons=Leg() # Leg: coupon cash flows
)bond = ... # Any Bond subclass
# Clean and dirty prices
bond.cleanPrice() # Market price without accrued interest
bond.dirtyPrice() # Price including accrued interest
bond.accruedAmount() # Accrued interest at settlement
bond.accruedAmount(settlement_date) # Accrued at specific date
# Set pricing engine for valuation
bond.setPricingEngine(pricing_engine)
bond.NPV() # Net present value# Yield to maturity
ytm = bond.yield_(
day_counter=ql.ActualActual(),
compounding=ql.Compounded,
frequency=ql.Semiannual,
accuracy=1e-8,
max_evaluations=100,
guess=0.05
)
# Yield from price
ytm_from_price = bond.yield_(
clean_price=99.5,
day_counter=ql.ActualActual(),
compounding=ql.Compounded,
frequency=ql.Semiannual
)# Duration and convexity
bond.duration() # Modified duration
bond.duration(ytm) # Duration at specific yield
bond.convexity() # Convexity
bond.convexity(ytm) # Convexity at specific yield
# From yield and settlement date
bond.duration(ytm, day_counter, compounding, frequency, settlement_date)
bond.convexity(ytm, day_counter, compounding, frequency, settlement_date)bond.settlementDate() # Settlement date for current evaluation date
bond.settlementDate(trade_date) # Settlement for specific trade date
bond.cashflows() # Leg of cash flows
bond.maturityDate() # Bond maturity
bond.issueDate() # Issue date
bond.faceAmount() # Face/par value
bond.calendar() # Bond calendar
bond.settlementDays() # Settlement periodPure discount bond with single payment at maturity.
ZeroCouponBond(
settlementDays,
calendar,
faceAmount,
maturityDate,
convention=ql.Following,
redemption=100.0,
issueDate=None
)
# Example
zero_bond = ql.ZeroCouponBond(
settlementDays=3,
calendar=ql.TARGET(),
faceAmount=100.0,
maturityDate=ql.Date(15, 6, 2030),
convention=ql.Following,
redemption=100.0,
issueDate=ql.Date(15, 6, 2023)
)Inherits from: Bond → Instrument → LazyObject → Observable
Bond with fixed coupon payments throughout its life.
Inheritance Details (click to expand)
- From
Bond:cleanPrice(),dirtyPrice(),bondYield(),accruedAmount(),duration(),convexity(),settlementDate(),maturityDate(),cashflows(), etc. - From
Instrument:NPV(),setPricingEngine(),isExpired(),errorEstimate() - From
LazyObject:calculate(),freeze(),unfreeze() - From
Observable: Observer pattern support for automatic recalculation
# Primary constructor - only first 5 parameters are required
FixedRateBond(
settlementDays, # Integer: REQUIRED - settlement period in days
faceAmount, # Real: REQUIRED - face/notional amount
schedule, # Schedule: REQUIRED - payment schedule
coupons, # List[Rate]: REQUIRED - coupon rates (can vary by period)
paymentDayCounter, # DayCounter: REQUIRED - day count for coupon accrual
paymentConvention=ql.Following, # BusinessDayConvention: payment adjustment (default: Following)
redemption=100.0, # Real: redemption amount at maturity (default: 100.0)
issueDate=ql.Date(), # Date: bond issue date (default: null Date)
paymentCalendar=ql.Calendar(), # Calendar: payment calendar (default: null, uses schedule calendar)
exCouponPeriod=ql.Period(), # Period: ex-coupon period (default: no ex-coupon)
exCouponCalendar=ql.Calendar(), # Calendar: ex-coupon calendar (default: null)
exCouponConvention=ql.Unadjusted, # BusinessDayConvention: ex-coupon adjustment (default: Unadjusted)
exCouponEndOfMonth=False # Boolean: ex-coupon end-of-month rule (default: False)
)
# Minimal constructor example - only required parameters
FixedRateBond(
settlementDays=2, # Integer: settlement days
faceAmount=100.0, # Real: face amount
schedule=my_schedule, # Schedule: payment schedule (see Schedule class)
coupons=[0.04], # List[Rate]: coupon rates
paymentDayCounter=ql.ActualActual() # DayCounter: day count convention
)
# Full constructor example - all parameters specified
FixedRateBond(
settlementDays=3, # Integer: settlement days
faceAmount=1000.0, # Real: face amount
schedule=my_schedule, # Schedule: payment schedule
coupons=[0.045], # List[Rate]: coupon rates
paymentDayCounter=ql.ActualActual(ql.ActualActual.Bond), # DayCounter: day count convention
paymentConvention=ql.ModifiedFollowing, # BusinessDayConvention: payment adjustment
redemption=105.0, # Real: premium redemption
issueDate=ql.Date(15, 6, 2023), # Date: issue date
paymentCalendar=ql.TARGET(), # Calendar: payment calendar
exCouponPeriod=ql.Period("5D"), # Period: ex-coupon period
exCouponCalendar=ql.TARGET(), # Calendar: ex-coupon calendar
exCouponConvention=ql.Following, # BusinessDayConvention: ex-coupon adjustment
exCouponEndOfMonth=True # Boolean: ex-coupon end-of-month rule
)Related Classes: Schedule, DayCounter, Calendar, BusinessDayConvention
bond = ql.FixedRateBond(...)
# Pricing methods (require pricing engine - see Bond Pricing Engines section)
bond.cleanPrice() # Real: market price without accrued interest
bond.dirtyPrice() # Real: price including accrued interest
bond.accruedAmount() # Real: accrued interest at settlement
bond.NPV() # Real: net present value (requires setPricingEngine())
# Yield calculations
bond.bondYield( # InterestRate: yield to maturity
price, # Real: clean price
dayCounter, # DayCounter: yield day count convention
compounding, # Compounding: Simple|Compounded|Continuous
frequency # Frequency: Annual|Semiannual|Quarterly|etc
)
# Alternative yield calculation (from current market price)
bond.bondYield(
dayCounter, # DayCounter: yield calculation day count
compounding, # Compounding: compounding convention
frequency # Frequency: compounding frequency
)
# Risk metrics
bond.duration() # Real: modified duration (from current price)
bond.convexity() # Real: convexity (from current price)
bond.duration(ytm, dayCounter, compounding, frequency) # Real: duration at specific yield
bond.convexity(ytm, dayCounter, compounding, frequency) # Real: convexity at specific yield
# Bond properties
bond.settlementDate() # Date: settlement date for current evaluation date
bond.settlementDate(trade_date) # Date: settlement date for specific trade date
bond.maturityDate() # Date: bond maturity
bond.issueDate() # Date: bond issue date
bond.faceAmount() # Real: face/par value
bond.cashflows() # Leg: vector of cash flows (see Cash Flow Classes)
bond.notional(date=ql.Date()) # Real: notional at specific date
bond.calendar() # Calendar: bond's payment calendar
# Cash flow analysis
bond.nextCashFlowDate(date=ql.Date()) # Date: next coupon date after given date
bond.previousCashFlowDate(date=ql.Date()) # Date: previous coupon date before given date
bond.nextCashFlowAmount(date=ql.Date()) # Real: next coupon amount
bond.previousCashFlowAmount(date=ql.Date()) # Real: previous coupon amountSee Also: InterestRate, Compounding, Frequency, Cash Flow Classes, Bond Pricing Engines
# Additional methods specific to FixedRateBond
bond.frequency() # Frequency: coupon payment frequency
bond.dayCounter() # DayCounter: coupon day counterimport QuantLib as ql
# Setup
ql.Settings.instance().evaluationDate = ql.Date(15, 6, 2023)
# Create payment schedule
schedule = ql.MakeSchedule(
effectiveDate=ql.Date(15, 6, 2023),
terminationDate=ql.Date(15, 6, 2028),
tenor=ql.Period("6M"), # Semi-annual payments
calendar=ql.TARGET(),
convention=ql.ModifiedFollowing
)
# Create bond
fixed_bond = ql.FixedRateBond(
settlementDays=3,
faceAmount=100.0,
schedule=schedule,
coupons=[0.04], # 4% annual coupon
paymentDayCounter=ql.ActualActual(ql.ActualActual.Bond), # REQUIRED parameter
paymentConvention=ql.ModifiedFollowing,
redemption=100.0
)
# Set pricing engine and get price
yield_curve = ql.FlatForward(2, ql.TARGET(), 0.03, ql.Actual365Fixed())
engine = ql.DiscountingBondEngine(ql.YieldTermStructureHandle(yield_curve))
fixed_bond.setPricingEngine(engine)
price = fixed_bond.cleanPrice()
print(f"Bond price: {price:.3f}")Bonds with changing coupon rates over time.
# Different coupon rates for different periods
coupons = [0.03, 0.035, 0.04, 0.045] # Stepping up each year
stepup_bond = ql.FixedRateBond(
settlementDays=3,
faceAmount=100.0,
schedule=schedule,
coupons=coupons,
paymentDayCounter=ql.Thirty360(),
paymentConvention=ql.ModifiedFollowing
)Bond with variable coupon payments linked to a reference rate.
FloatingRateBond(
settlementDays, # Size: settlement period in days
faceAmount, # Real: face/notional amount
schedule, # Schedule: payment schedule
index, # IborIndex: reference index (e.g., LIBOR)
paymentDayCounter, # DayCounter: REQUIRED day count for payments
paymentConvention=ql.Following, # Business day convention
fixingDays=None, # Size: fixing days (None = index default)
gearings=[], # List[Real]: multipliers for index rate
spreads=[], # List[Spread]: spreads over index
caps=[], # List[Rate]: rate caps
floors=[], # List[Rate]: rate floors
inArrears=False, # Boolean: in-arrears fixing
redemption=100.0, # Real: redemption amount
issueDate=ql.Date() # Date: issue date
)
# Example - Floating rate note linked to 6M EURIBOR
import QuantLib as ql
euribor6m = ql.Euribor6M()
floating_schedule = ql.MakeSchedule(
effectiveDate=ql.Date(15, 6, 2023),
terminationDate=ql.Date(15, 6, 2028),
tenor=ql.Period("6M"),
calendar=ql.TARGET()
)
frn = ql.FloatingRateBond(
settlementDays=2,
faceAmount=100.0,
schedule=floating_schedule,
index=euribor6m,
paymentDayCounter=ql.Actual360(), # REQUIRED parameter
paymentConvention=ql.ModifiedFollowing,
fixingDays=2,
gearings=[1.0], # 1x EURIBOR
spreads=[0.002], # +20bp spread
caps=[], # No cap
floors=[0.0], # 0% floor
inArrears=False
)Bonds with embedded options.
# Callable bond
CallableFixedRateBond(
settlementDays,
faceAmount,
schedule,
coupons,
accrualDayCounter,
paymentConvention,
redemption,
issueDate,
callability # CallabilitySchedule
)
# Puttable bond
PuttableFixedRateBond(
settlementDays,
faceAmount,
schedule,
coupons,
accrualDayCounter,
paymentConvention,
redemption,
issueDate,
putability # CallabilitySchedule (put rights)
)
# Example with call schedule
call_dates = [ql.Date(15, 6, 2025), ql.Date(15, 6, 2026), ql.Date(15, 6, 2027)]
call_prices = [102.0, 101.0, 100.5]
call_schedule = ql.CallabilitySchedule()
for date, price in zip(call_dates, call_prices):
call_schedule.append(
ql.Callability(
price=ql.BondPrice(price, ql.BondPrice.Clean),
type_=ql.Callability.Call,
date=date
)
)
callable_bond = ql.CallableFixedRateBond(
settlementDays=3,
faceAmount=100.0,
schedule=schedule,
coupons=[0.045],
accrualDayCounter=ql.Thirty360(),
paymentConvention=ql.ModifiedFollowing,
redemption=100.0,
issueDate=ql.Date(15, 6, 2023),
callability=call_schedule
)Bonds convertible to equity.
# Requires more complex setup with equity process
convertible = ql.ConvertibleFixedCouponBond(
exercise=exercise_schedule,
conversionRatio=conversion_ratio,
dividends=[],
callability=call_schedule,
creditSpread=credit_spread_handle,
issueDate=issue_date,
settlementDays=settlement_days,
dayCounter=day_counter,
schedule=coupon_schedule,
coupons=coupon_rates
)Cash flows represent individual payments from bonds and other instruments.
Basic cash flow with fixed amount and date.
SimpleCashFlow(amount, date)
# Example
redemption = ql.SimpleCashFlow(100.0, ql.Date(15, 6, 2028))Base class for interest payments.
# All coupons have these methods
coupon.amount() # Coupon amount
coupon.date() # Payment date
coupon.accrualStartDate() # Accrual period start
coupon.accrualEndDate() # Accrual period end
coupon.accrualPeriod() # Accrual period length
coupon.accrualDays() # Days in accrual period
coupon.dayCounter() # Day count convention
coupon.rate() # Coupon rate
coupon.nominal() # Notional amountFixed rate interest payment.
FixedRateCoupon(
paymentDate,
nominal,
rate,
dayCounter,
accrualStartDate,
accrualEndDate,
refPeriodStart=None,
refPeriodEnd=None
)
# Example
coupon = ql.FixedRateCoupon(
paymentDate=ql.Date(15, 12, 2023),
nominal=100.0,
rate=0.04,
dayCounter=ql.ActualActual(),
accrualStartDate=ql.Date(15, 6, 2023),
accrualEndDate=ql.Date(15, 12, 2023)
)
coupon_amount = coupon.amount() # Calculate coupon paymentFloating rate coupon linked to IBOR index.
IborCoupon(
paymentDate,
nominal,
startDate,
endDate,
fixingDays,
index, # IborIndex
gearing=1.0,
spread=0.0,
refPeriodStart=None,
refPeriodEnd=None,
dayCounter=None,
isInArrears=False
)
# Example
euribor3m = ql.Euribor3M()
ibor_coupon = ql.IborCoupon(
paymentDate=ql.Date(15, 9, 2023),
nominal=100.0,
startDate=ql.Date(15, 6, 2023),
endDate=ql.Date(15, 9, 2023),
fixingDays=2,
index=euribor3m,
gearing=1.0,
spread=0.001 # 10bp spread
)Floating coupon with cap and/or floor.
CappedFlooredCoupon(
underlying_coupon, # IborCoupon or other floating coupon
cap=None, # Maximum rate
floor=None # Minimum rate
)
# Example - LIBOR coupon with 5% cap and 1% floor
underlying = ql.IborCoupon(...)
capped_coupon = ql.CappedFlooredCoupon(underlying, cap=0.05, floor=0.01)Coupon linked to Constant Maturity Swap rate.
CmsCoupon(
paymentDate,
nominal,
startDate,
endDate,
fixingDays,
swapIndex, # SwapIndex
gearing=1.0,
spread=0.0,
refPeriodStart=None,
refPeriodEnd=None,
dayCounter=None,
isInArrears=False
)Collection of cash flows forming one side of a swap or bond.
# Access cash flows
leg = bond.cashflows() # Returns Leg object
# Iterate through cash flows
for i, cf in enumerate(leg):
print(f"Cash flow {i}: {cf.amount()} on {cf.date()}")
# Check if it's a coupon
if hasattr(cf, 'rate'):
coupon = ql.as_coupon(cf) # Cast to coupon
print(f" Rate: {coupon.rate():.4f}")
# Leg properties
leg.size() # Number of cash flows
npv = ql.CashFlows.npv(leg, yield_curve, settlement_date)Utility functions for analyzing cash flow streams.
# NPV calculations
npv = ql.CashFlows.npv(cashflows, discount_curve, settlement_date)
npv_yield = ql.CashFlows.npv(cashflows, yield_rate, day_counter, compounding, frequency, settlement_date)
# BPS (basis point sensitivity)
bps = ql.CashFlows.bps(cashflows, yield_curve, settlement_date)
# Duration and convexity
duration = ql.CashFlows.duration(cashflows, yield_rate, day_counter, compounding, frequency, settlement_date, ql.Duration.Modified)
convexity = ql.CashFlows.convexity(cashflows, yield_rate, day_counter, compounding, frequency, settlement_date)
# Yield calculation
yield_rate = ql.CashFlows.yield_(cashflows, present_value, day_counter, compounding, frequency, settlement_date)
# Z-spread calculation
z_spread = ql.CashFlows.zSpread(cashflows, present_value, discount_curve, day_counter, compounding, frequency, settlement_date)Interest rate swaps exchange fixed and floating rate cash flows.
Standard fixed-for-floating interest rate swap.
VanillaSwap(
type_, # ql.VanillaSwap.Payer or ql.VanillaSwap.Receiver
nominal,
fixedSchedule, # Fixed leg payment schedule
fixedRate,
fixedDayCounter,
floatSchedule, # Floating leg payment schedule
index, # Floating rate index
spread, # Spread over index
floatingDayCounter,
paymentConvention=ql.Following
)
# Example - 5-year USD swap, pay 3% fixed vs 3M LIBOR
fixed_schedule = ql.MakeSchedule(
effectiveDate=ql.Date(15, 6, 2023),
terminationDate=ql.Date(15, 6, 2028),
tenor=ql.Period("1Y"),
calendar=ql.UnitedStates(),
convention=ql.ModifiedFollowing
)
float_schedule = ql.MakeSchedule(
effectiveDate=ql.Date(15, 6, 2023),
terminationDate=ql.Date(15, 6, 2028),
tenor=ql.Period("3M"),
calendar=ql.UnitedStates(),
convention=ql.ModifiedFollowing
)
usd_libor_3m = ql.USDLibor(ql.Period("3M"))
swap = ql.VanillaSwap(
type_=ql.VanillaSwap.Payer, # Pay fixed, receive floating
nominal=1000000.0,
fixedSchedule=fixed_schedule,
fixedRate=0.03,
fixedDayCounter=ql.Thirty360(),
floatSchedule=float_schedule,
index=usd_libor_3m,
spread=0.0,
floatingDayCounter=ql.Actual360()
)
# Price the swap
discount_curve_handle = ql.YieldTermStructureHandle(discount_curve)
swap_engine = ql.DiscountingSwapEngine(discount_curve_handle)
swap.setPricingEngine(swap_engine)
swap_npv = swap.NPV()
fair_rate = swap.fairRate() # Par fixed rate
fair_spread = swap.fairSpread() # Par floating spread# Swap legs
fixed_leg = swap.fixedLeg()
float_leg = swap.floatingLeg()
# Individual leg NPVs
fixed_leg_npv = swap.fixedLegNPV()
float_leg_npv = swap.floatingLegNPV()
# BPS (01 sensitivity)
fixed_leg_bps = swap.fixedLegBPS()
float_leg_bps = swap.floatingLegBPS()
# Start and maturity dates
swap.startDate()
swap.maturityDate()Simplified swap construction.
swap = ql.MakeVanillaSwap(
swapTenor=ql.Period("5Y"),
index=usd_libor_3m,
fixedRate=0.03,
forwardStart=ql.Period("0D")
).withDiscountingTermStructure(discount_curve_handle)
# Or receive fixed
receive_swap = ql.MakeVanillaSwap(
swapTenor=ql.Period("10Y"),
index=euribor_6m,
fixedRate=0.025,
forwardStart=ql.Period("1Y")
).receiveFixed().withDiscountingTermStructure(discount_curve_handle)Swap against overnight index (e.g., SOFR, EONIA).
OvernightIndexedSwap(
type_,
nominal,
schedule, # Payment schedule (typically annual)
fixedRate,
fixedDC,
overnightIndex, # e.g., SOFR, EONIA
spread=0.0,
paymentLag=0,
paymentAdjustment=ql.Following,
paymentCalendar=None,
telescopicValueDates=False
)
# Example - 2-year SOFR OIS
sofr = ql.Sofr()
ois_schedule = ql.MakeSchedule(
effectiveDate=ql.Date(15, 6, 2023),
terminationDate=ql.Date(15, 6, 2025),
tenor=ql.Period("1Y"),
calendar=ql.UnitedStates()
)
ois = ql.OvernightIndexedSwap(
type_=ql.OvernightIndexedSwap.Payer,
nominal=1000000.0,
schedule=ois_schedule,
fixedRate=0.025,
fixedDC=ql.Actual360(),
overnightIndex=sofr,
spread=0.0
)Standard bond pricing using discount curve.
engine = ql.DiscountingBondEngine(discount_curve_handle)
bond.setPricingEngine(engine)
bond_npv = bond.NPV()For callable/puttable bonds using interest rate trees.
# Hull-White model
hw_model = ql.HullWhite(yield_curve_handle)
# Tree engine
tree_engine = ql.TreeCallableFixedRateBondEngine(
model=hw_model,
size=100, # Tree size
discount_curve=discount_curve_handle
)
callable_bond.setPricingEngine(tree_engine)
bond_price = callable_bond.cleanPrice()Black model for callable bonds.
black_engine = ql.BlackCallableFixedRateBondEngine(
fwdYieldVol=vol_handle,
discountCurve=discount_curve_handle
)import QuantLib as ql
# Setup - ALWAYS set evaluation date first
ql.Settings.instance().evaluationDate = ql.Date(15, 6, 2023)
calendar = ql.TARGET()
# Create 5-year corporate bond with 4.5% coupon
schedule = ql.MakeSchedule(
effectiveDate=ql.Date(15, 6, 2023),
terminationDate=ql.Date(15, 6, 2028),
tenor=ql.Period("6M"),
calendar=calendar,
convention=ql.ModifiedFollowing
)
corporate_bond = ql.FixedRateBond(
settlementDays=3,
faceAmount=100.0,
schedule=schedule,
coupons=[0.045],
accrualDayCounter=ql.ActualActual(ql.ActualActual.Bond)
)
# Create discount curve (Treasury + credit spread)
treasury_curve = ql.FlatForward(ql.Date(15, 6, 2023), 0.03, ql.Actual360())
credit_spread = 0.015 # 150bp credit spread
corporate_curve = ql.ZeroSpreadedTermStructure(
ql.YieldTermStructureHandle(treasury_curve),
ql.QuoteHandle(ql.SimpleQuote(credit_spread))
)
# Price the bond
engine = ql.DiscountingBondEngine(ql.YieldTermStructureHandle(corporate_curve))
corporate_bond.setPricingEngine(engine)
# Analytics
clean_price = corporate_bond.cleanPrice()
ytm = corporate_bond.yield_(ql.ActualActual(), ql.Compounded, ql.Semiannual)
duration = corporate_bond.duration()
convexity = corporate_bond.convexity()
print(f"Clean Price: {clean_price:.3f}")
print(f"Yield to Maturity: {ytm.rate():.4f}")
print(f"Modified Duration: {duration:.3f}")
print(f"Convexity: {convexity:.3f}")# Create FRN linked to 3M EURIBOR
euribor3m = ql.Euribor3M()
frn_schedule = ql.MakeSchedule(
effectiveDate=ql.Date(15, 6, 2023),
terminationDate=ql.Date(15, 6, 2026),
tenor=ql.Period("3M"),
calendar=ql.TARGET()
)
frn = ql.FloatingRateBond(
settlementDays=2,
faceAmount=100.0,
schedule=frn_schedule,
index=euribor3m,
accrualDayCounter=ql.Actual360(),
fixingDays=2,
gearings=[1.0],
spreads=[0.0025], # 25bp spread
caps=[0.06], # 6% cap
floors=[0.0] # 0% floor
)
# Set up curve with forward rates
euribor_curve = ql.FlatForward(ql.Date(15, 6, 2023), 0.035, ql.Actual360())
euribor3m.addFixing(ql.Date(13, 6, 2023), 0.034) # Current fixing
# Link index to curve
euribor_handle = ql.YieldTermStructureHandle(euribor_curve)
euribor3m = ql.Euribor3M(euribor_handle)
# Price FRN
discount_curve = ql.FlatForward(ql.Date(15, 6, 2023), 0.032, ql.Actual360())
frn_engine = ql.DiscountingBondEngine(ql.YieldTermStructureHandle(discount_curve))
frn.setPricingEngine(frn_engine)
frn_price = frn.cleanPrice()
print(f"FRN Clean Price: {frn_price:.3f}")
# Analyze individual coupons
for i, cf in enumerate(frn.cashflows()):
if hasattr(cf, 'rate'):
coupon = ql.as_coupon(cf)
if hasattr(coupon, 'index'): # Floating coupon
ibor_coupon = ql.as_ibor_coupon(coupon)
print(f"Coupon {i}: {ibor_coupon.rate():.4f} on {cf.date()}")# 10-year USD IRS: Pay 3.5% fixed vs 3M LIBOR
usd_libor_3m = ql.USDLibor(ql.Period("3M"))
swap = ql.MakeVanillaSwap(
swapTenor=ql.Period("10Y"),
index=usd_libor_3m,
fixedRate=0.035,
forwardStart=ql.Period("0D")
)
# Set up curves
discount_curve = ql.FlatForward(ql.Date(15, 6, 2023), 0.04, ql.Actual360())
forecast_curve = ql.FlatForward(ql.Date(15, 6, 2023), 0.038, ql.Actual360())
# Link index to forecast curve
usd_libor_3m = ql.USDLibor(ql.Period("3M"), ql.YieldTermStructureHandle(forecast_curve))
# Price swap
swap_engine = ql.DiscountingSwapEngine(ql.YieldTermStructureHandle(discount_curve))
swap.setPricingEngine(swap_engine)
# Analytics
swap_npv = swap.NPV()
fair_rate = swap.fairRate()
fixed_leg_npv = swap.fixedLegNPV()
float_leg_npv = swap.floatingLegNPV()
print(f"Swap NPV: ${swap_npv:,.2f}")
print(f"Fair Fixed Rate: {fair_rate:.4f}")
print(f"Fixed Leg NPV: ${fixed_leg_npv:,.2f}")
print(f"Floating Leg NPV: ${float_leg_npv:,.2f}")# Create portfolio of bonds
bonds = []
# 2-year Treasury
treasury_2y = ql.FixedRateBond(
settlementDays=1,
faceAmount=100.0,
schedule=ql.MakeSchedule(
effectiveDate=ql.Date(15, 6, 2023),
terminationDate=ql.Date(15, 6, 2025),
tenor=ql.Period("6M"),
calendar=ql.UnitedStates()
),
coupons=[0.025],
accrualDayCounter=ql.ActualActual()
)
# 10-year Treasury
treasury_10y = ql.FixedRateBond(
settlementDays=1,
faceAmount=100.0,
schedule=ql.MakeSchedule(
effectiveDate=ql.Date(15, 6, 2023),
terminationDate=ql.Date(15, 6, 2033),
tenor=ql.Period("6M"),
calendar=ql.UnitedStates()
),
coupons=[0.04],
accrualDayCounter=ql.ActualActual()
)
bonds = [treasury_2y, treasury_10y]
weights = [0.4, 0.6] # Portfolio weights
# Price bonds
treasury_curve = ql.FlatForward(ql.Date(15, 6, 2023), 0.035, ql.Actual360())
engine = ql.DiscountingBondEngine(ql.YieldTermStructureHandle(treasury_curve))
portfolio_value = 0.0
portfolio_duration = 0.0
portfolio_convexity = 0.0
for bond, weight in zip(bonds, weights):
bond.setPricingEngine(engine)
bond_price = bond.cleanPrice()
bond_duration = bond.duration()
bond_convexity = bond.convexity()
portfolio_value += weight * bond_price
portfolio_duration += weight * bond_duration * bond_price
portfolio_convexity += weight * bond_convexity * bond_price
# Weight by market value
portfolio_duration /= portfolio_value
portfolio_convexity /= portfolio_value
print(f"Portfolio Price: {portfolio_value:.3f}")
print(f"Portfolio Duration: {portfolio_duration:.3f}")
print(f"Portfolio Convexity: {portfolio_convexity:.3f}")