Credit Instruments
Bonds, credit derivatives, and structured products for pricing, risk, and spread analytics.
All types are available via flat import:
from vade import FixedRateBond, Bill, FloatRateNote, SubPeriodFRN
from vade import ZeroCouponBond, StepUpBond, AmortizingBond, PIKBond
from vade import CappedFloatRateNote, AssetSwap, CallableBond, CDSOr via product-path import:
from vade.instruments.credit import FixedRateBond, Bill, FloatRateNote, SubPeriodFRN
from vade.instruments.credit import ZeroCouponBond, StepUpBond, AmortizingBond, PIKBond
from vade.instruments.credit import CappedFloatRateNote, AssetSwap, CallableBond, CDSSee Conventions for all accepted string parameter values.
Contents: FixedRateBond | Bill | FloatRateNote | SubPeriodFRN | ZeroCouponBond | StepUpBond | AmortizingBond | PIKBond | CappedFloatRateNote | AssetSwap | CallableBond | CDS
FixedRateBond
Fixed Rate Bond with full analytics suite. Python wrapper composing FixedLeg with bond analytics.
See also: Bonds Guide for bond pricing, yield analysis, and risk metrics.
Constructor
FixedRateBond(
*,
spec=None,
effective=None,
termination=None,
coupon=0.0,
frequency="s",
settlement_days=1,
ex_div_days=0,
calendar=None,
currency="USD",
convention="actactisda",
accrued_convention=None,
face_value=100.0,
modifier="none",
disc_curve_id=None,
)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
spec | str or None | None | Instrument spec name |
effective | datetime.date or None | None | Issue or settlement date |
termination | date, str, or None | None | Maturity date or tenor string |
coupon | float | 0.0 | Coupon rate (percentage, e.g., 5.0 for 5%) |
frequency | str | "s" | Coupon payment frequency |
settlement_days | int | 1 | Settlement lag in business days |
ex_div_days | int | 0 | Ex-dividend days before coupon payment |
calendar | str or None | None | Named calendar |
currency | str | "USD" | Currency code |
convention | str | "actactisda" | Day count convention for coupon accrual |
accrued_convention | str or None | None | Separate day count for accrued interest (defaults to convention) |
face_value | float | 100.0 | Face value of the bond |
modifier | str | "none" | Business day adjustment rule |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
See Conventions for accepted values for frequency, convention, and modifier.
Methods
Standard Methods
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Par rate against curves |
.npv(curves, *, solver=None) | float | Net present value |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
.spread(curves, *, solver=None) | float | Par spread |
Price and Yield
| Method | Returns | Description |
|---|---|---|
.price(curves, *, solver=None, settlement=None) | float | Clean price |
.dirty_price(curves, *, solver=None, settlement=None) | float | Dirty price (clean + accrued) |
.accrued_interest(settlement) | float | Accrued interest at settlement date |
.ytm(clean_price, settlement, convention="periodic") | float | Yield to maturity from clean price |
.price_from_ytm(ytm, settlement, convention="periodic") | float | Clean price from yield to maturity |
YTM convention accepts "periodic", "annual", "semi_annual", or "continuous".
Risk Analytics
| Method | Returns | Description |
|---|---|---|
.duration(curves, *, solver=None, metric="modified", settlement=None) | float | Modified or Macaulay duration |
.convexity(curves, *, solver=None, settlement=None) | float | Price convexity |
.dv01(curves, *, solver=None) | float | Dollar value of 1bp |
.z_spread(curves, *, solver=None, price=None, settlement=None) | float | Z-spread over curve |
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None) | float | Asset swap spread |
.cs01(curves, *, solver=None) | float | Credit spread sensitivity (1bp) |
.price_yield_sensitivity(curves, *, solver=None) | float | Price-yield sensitivity |
.analytic_delta(curves, *, solver=None) | float | Analytic delta |
Duration metric accepts "modified" or "macaulay". ASW method accepts "par_par" or "proceeds".
Example
import datetime
from vade import FixedRateBond, DiscountCurve
nodes = {
datetime.date(2024, 1, 1): 1.0,
datetime.date(2025, 1, 1): 0.97,
datetime.date(2026, 1, 1): 0.94,
datetime.date(2027, 1, 1): 0.91,
}
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f")
bond = FixedRateBond(
effective=datetime.date(2024, 1, 1),
termination="2Y",
coupon=5.0,
frequency="s",
convention="actactisda",
)
bond.npv(curve) # net present value
bond.price(curve, settlement=datetime.date(2024, 1, 2)) # clean price
bond.dirty_price(curve, settlement=datetime.date(2024, 1, 2)) # dirty price
bond.accrued_interest(datetime.date(2024, 3, 1)) # accrued at settlement
bond.ytm(99.5, datetime.date(2024, 1, 2)) # yield to maturity
bond.duration(curve, metric="modified", settlement=datetime.date(2024, 1, 2)) # modified durationBill
Zero-coupon bill (Treasury bill). Python wrapper composing zero-coupon cashflow with bill analytics.
Constructor
Bill(
*,
spec=None,
effective=None,
termination=None,
settlement_days=1,
calendar=None,
currency="USD",
convention="act360",
face_value=100.0,
disc_curve_id=None,
)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
spec | str or None | None | Instrument spec name |
effective | datetime.date or None | None | Issue date |
termination | date, str, or None | None | Maturity date or tenor string (e.g., "6M") |
settlement_days | int | 1 | Settlement lag in business days |
calendar | str or None | None | Named calendar |
currency | str | "USD" | Currency code |
convention | str | "act360" | Day count convention |
face_value | float | 100.0 | Face value |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
See Conventions for accepted values for convention.
Methods
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Par rate against curves |
.npv(curves, *, solver=None) | float | Net present value |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
.price(curves, *, solver=None) | float | Bill price |
.dirty_price(curves, *, solver=None) | float | Dirty price |
.ytm(clean_price, settlement) | float | Yield to maturity from price |
Example
import datetime
from vade import Bill, DiscountCurve
nodes = {datetime.date(2024, 1, 1): 1.0, datetime.date(2024, 7, 1): 0.985, datetime.date(2025, 1, 1): 0.97}
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act360")
bill = Bill(
effective=datetime.date(2024, 1, 1),
termination="6M",
convention="act360",
)
bill.rate(curve) # implied discount rate
bill.price(curve) # bill price
bill.ytm(99.0, datetime.date(2024, 1, 2)) # yield from priceAdvanced Example
import datetime, math
from vade import Bill, DiscountCurve, LineCurve
# 6-month US Treasury Bill
bill = Bill(
effective=datetime.date(2024, 6, 15),
termination="6m",
convention="act360",
currency="usd",
)
# Flat 4% discount curve
base = datetime.date(2024, 6, 15)
nodes = {base: 1.0}
for y in range(1, 6):
nodes[datetime.date(2024 + y, 6, 15)] = math.exp(-0.04 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act360")
# Pricing
price = bill.price(curve)
assert 97.0 < float(price) < 100.0
dirty = bill.dirty_price(curve)
assert 97.0 < float(dirty) < 100.0
# NPV
npv = bill.npv(curve)
assert isinstance(float(npv), float)
# Cashflows -- single redemption row
cf = bill.cashflows(curve)
assert len(cf) >= 1
# Yield to maturity (from price and settlement date)
settlement = datetime.date(2024, 6, 16)
ytm = bill.ytm(float(price), settlement)
assert 0.02 < float(ytm) < 0.06
# Rate (discount rate)
rate = bill.rate(curve)
assert isinstance(float(rate), float)
# I-spread against a benchmark swap curve
benchmark = LineCurve({base: 0.035, datetime.date(2030, 6, 15): 0.04})
i_spr = bill.i_spread(settlement, benchmark)
assert isinstance(float(i_spr), float)FloatRateNote
Floating Rate Note (FRN). Python wrapper composing FloatLeg with FRN analytics.
Constructor
FloatRateNote(
*,
spec=None,
effective=None,
termination=None,
spread=0.0,
frequency="q",
settlement_days=2,
ex_div_days=0,
calendar=None,
currency="USD",
convention="act360",
accrued_convention=None,
face_value=100.0,
modifier="mf",
disc_curve_id=None,
)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
spec | str or None | None | Instrument spec name |
effective | datetime.date or None | None | Issue date |
termination | date, str, or None | None | Maturity date or tenor string |
spread | float | 0.0 | Spread over floating rate (basis points) |
frequency | str | "q" | Coupon payment frequency |
settlement_days | int | 2 | Settlement lag in business days |
ex_div_days | int | 0 | Ex-dividend days before coupon payment |
calendar | str or None | None | Named calendar |
currency | str | "USD" | Currency code |
convention | str | "act360" | Day count convention |
accrued_convention | str or None | None | Separate day count for accrued interest (defaults to convention) |
face_value | float | 100.0 | Face value |
modifier | str | "mf" | Business day adjustment rule |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
See Conventions for accepted values for frequency, convention, and modifier.
Methods
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Par rate against curves |
.npv(curves, *, solver=None) | float | Net present value |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
.price(curves, *, solver=None) | float | Clean price |
.dirty_price(curves, *, solver=None) | float | Dirty price |
.analytic_delta(curves, *, solver=None) | float | Analytic delta |
Example
import datetime
from vade import FloatRateNote, DiscountCurve
nodes = {datetime.date(2024, 1, 1): 1.0, datetime.date(2025, 1, 1): 0.97, datetime.date(2026, 1, 1): 0.94}
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act360")
frn = FloatRateNote(
effective=datetime.date(2024, 1, 1),
termination="1Y",
frequency="q",
spread=50.0,
convention="act360",
)
frn.rate(curve) # par spread
frn.npv(curve) # net present value
frn.price(curve) # clean priceSubPeriodFRN
A floating rate note where the coupon payment frequency differs from the fixing frequency. Each coupon period compounds multiple sub-period fixings: coupon = Product(1 + r_i * dcf_i) - 1 + spread. Python wrapper composing FloatLeg with sub-period compounding.
For example, a quarterly-pay / monthly-fix SubPeriodFRN has 4 coupon periods per year, but the floating rate within each coupon is compounded from ~3 monthly fixings. This is common in leveraged loan and CLO markets.
Constructor
SubPeriodFRN(
*,
effective=None,
termination=None,
spread=0.0,
frequency="q",
fixing_frequency="m",
settlement_days=2,
ex_div_days=0,
calendar=None,
currency="USD",
convention="act360",
accrued_convention=None,
face_value=100.0,
modifier="mf",
disc_curve_id=None,
index=None,
)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
effective | datetime.date or None | None | Issue date |
termination | date, str, or None | None | Maturity date or tenor string |
spread | float | 0.0 | Spread over floating rate (decimal, e.g., 0.001 = 10bp) |
frequency | str | "q" | Coupon payment frequency |
fixing_frequency | str | "m" | Sub-period fixing frequency (must be higher than frequency) |
settlement_days | int | 2 | Settlement lag in business days |
ex_div_days | int | 0 | Ex-dividend days before coupon payment |
calendar | str or None | None | Named calendar |
currency | str | "USD" | Currency code |
convention | str | "act360" | Day count convention |
accrued_convention | str or None | None | Separate day count for accrued interest (defaults to convention) |
face_value | float | 100.0 | Face value |
modifier | str | "mf" | Business day adjustment rule |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
index | str or None | None | Floating rate index name for fixing lookups |
See Conventions for accepted values for frequency, convention, and modifier.
Methods
Standard Methods
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Par rate against curves |
.npv(curves, *, solver=None) | float | Net present value |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
.spread(curves, *, solver=None) | float | Par spread |
Price and Yield
| Method | Returns | Description |
|---|---|---|
.price(curves, *, solver=None, settlement=None) | float | Clean price |
.dirty_price(curves, *, solver=None, settlement=None) | float | Dirty price (clean + accrued) |
.accrued_interest(settlement) | float | Accrued interest at settlement date |
.ytm(clean_price, settlement, convention="periodic") | float | Yield to maturity from clean price |
.price_from_ytm(ytm, settlement, convention="periodic") | float | Clean price from yield to maturity |
Risk Analytics
| Method | Returns | Description |
|---|---|---|
.duration(curves, *, solver=None, metric="modified", settlement=None) | float | Modified or Macaulay duration |
.convexity(curves, *, solver=None, settlement=None) | float | Price convexity |
.dv01(curves, *, solver=None) | float | Dollar value of 1bp |
.z_spread(curves, *, solver=None, price=None, settlement=None) | float | Z-spread over curve |
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None) | float | Asset swap spread |
.analytic_delta(curves, *, solver=None) | float | Analytic delta |
.i_spread(settlement, swap_curve) | float | Interpolated spread vs swap curve |
SubPeriodFRN-Specific
| Method | Returns | Description |
|---|---|---|
.sub_period_schedule() | list[tuple[date, date, date, list[tuple]]] | Nested schedule showing sub-period structure |
Each element of the outer list is a coupon period tuple: (accrual_start, accrual_end, payment_date, sub_periods). Each sub-period is a tuple: (start, end, dcf, fixing).
Example
import datetime
from vade import SubPeriodFRN, DiscountCurve
# Build a discount curve
nodes = {
datetime.date(2024, 1, 15): 1.0,
datetime.date(2025, 1, 15): 0.965,
datetime.date(2026, 1, 15): 0.93,
datetime.date(2030, 1, 15): 0.85,
}
curve = DiscountCurve(nodes, interpolation="log_linear")
# 2Y quarterly-pay / monthly-fix SubPeriodFRN with 10bp spread
frn = SubPeriodFRN(
effective=datetime.date(2024, 1, 15),
termination=datetime.date(2026, 1, 15),
spread=0.001,
frequency="q",
fixing_frequency="m",
convention="act360",
)
# Price using [discount_curve, forecast_curve] list
dp = frn.dirty_price(curves=[curve, curve])
assert isinstance(dp, float)
assert 90 < dp < 110 # near par
npv = frn.npv(curves=[curve, curve])
assert isinstance(npv, float)Advanced Example
import datetime
from vade import SubPeriodFRN, DiscountCurve
# Build curve with several nodes for accurate forward rates
nodes = {
datetime.date(2024, 1, 15): 1.0,
datetime.date(2024, 7, 15): 0.982,
datetime.date(2025, 1, 15): 0.965,
datetime.date(2025, 7, 15): 0.948,
datetime.date(2026, 1, 15): 0.93,
}
curve = DiscountCurve(nodes, interpolation="log_linear")
# 1Y SubPeriodFRN: quarterly coupon, monthly sub-period compounding
frn = SubPeriodFRN(
effective=datetime.date(2024, 1, 15),
termination=datetime.date(2025, 1, 15),
spread=0.0,
frequency="q",
fixing_frequency="m",
convention="act360",
)
# Inspect sub-period structure: 4 quarterly periods, each with ~3 monthly fixings
schedule = frn.sub_period_schedule()
assert len(schedule) == 4 # 4 quarterly coupon periods
for accrual_start, accrual_end, payment_date, sub_periods in schedule:
assert accrual_start < accrual_end # valid period
assert len(sub_periods) >= 2 # at least 2 sub-period fixings per quarter
for sub_start, sub_end, dcf, fixing in sub_periods:
assert sub_start < sub_end
assert 0 < dcf < 1 # day count fraction for a monthly period
# Pricing and analytics
dp = frn.dirty_price(curves=[curve, curve])
assert 90 < dp < 110 # near par for zero-spread FRN
npv = frn.npv(curves=[curve, curve])
assert isinstance(npv, float)
assert abs(npv - dp) < 1e-6 # NPV equals dirty price for FRN
ad = frn.analytic_delta(curves=[curve, curve])
assert isinstance(ad, float)
assert ad < 0 # negative delta (price falls as rates rise)ZeroCouponBond
Zero-coupon bond with OID (Original Issue Discount) accrued interest. Python wrapper composing zero-coupon cashflow with compound discounting.
Accrued interest uses the OID compound method: at any settlement date, accrued equals the amortized cost (issue price compounded at yield) minus the issue price, prorated within each semi-annual period.
Constructor
ZeroCouponBond(
*,
effective=None,
termination=None,
issue_price=100.0,
frequency="s",
settlement_days=1,
calendar=None,
currency="USD",
convention="actactisda",
face_value=100.0,
disc_curve_id=None,
)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
effective | datetime.date or None | None | Issue date |
termination | date, str, or None | None | Maturity date or tenor string |
issue_price | float | 100.0 | Original issue price (OID basis for accrued calculation) |
frequency | str | "s" | Accrual frequency for OID calculation |
settlement_days | int | 1 | Settlement lag in business days |
calendar | str or None | None | Named calendar |
currency | str | "USD" | Currency code |
convention | str | "actactisda" | Day count convention |
face_value | float | 100.0 | Face value at maturity |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
See Conventions for accepted values for frequency and convention.
Methods
Standard Methods
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Par rate against curves |
.npv(curves, *, solver=None) | float | Net present value |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
Price and Yield
| Method | Returns | Description |
|---|---|---|
.price(curves, *, solver=None, settlement=None) | float | Clean price |
.dirty_price(curves, *, solver=None, settlement=None) | float | Dirty price (clean + OID accrued) |
.accrued_interest(settlement) | float | OID accrued interest at settlement date |
.ytm(clean_price, settlement, convention="periodic") | float | Yield to maturity from clean price |
.price_from_ytm(ytm, settlement, convention="periodic") | float | Clean price from yield to maturity |
Risk Analytics
| Method | Returns | Description |
|---|---|---|
.duration(curves, *, solver=None, metric="modified", settlement=None) | float | Modified or Macaulay duration |
.convexity(curves, *, solver=None, settlement=None) | float | Price convexity |
.z_spread(curves, *, solver=None, price=None, settlement=None) | float | Z-spread over curve |
Example
import math
import datetime
from vade import ZeroCouponBond, DiscountCurve
# Flat 4% continuously compounded curve
base = datetime.date(2024, 1, 1)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 1, 1)] = math.exp(-0.04 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f")
zcb = ZeroCouponBond(
effective=datetime.date(2024, 1, 1),
termination=datetime.date(2029, 1, 1),
issue_price=80.0,
face_value=100.0,
frequency="s",
convention="actactisda",
)
settlement = datetime.date(2024, 1, 2)
price = zcb.price(curve, settlement=settlement)
assert 50.0 < price < 80.0 # deep discount zero-coupon bond
accrued = zcb.accrued_interest(settlement)
assert accrued >= 0.0 # OID accrued is non-negative
ytm = zcb.ytm(float(price), settlement)
assert 0.03 < ytm < 0.06 # yield in reasonable range
dur = zcb.duration(curve, metric="modified", settlement=settlement)
assert dur > 4.0 # near maturity duration for 5Y zeroAdvanced Example
import datetime, math
from vade import ZeroCouponBond, DiscountCurve
# 5-year zero coupon bond
zcb = ZeroCouponBond(
effective=datetime.date(2024, 1, 15),
termination="5y",
face_value=100.0,
frequency="s",
convention="act365f",
currency="usd",
)
# Flat 4% discount curve
base = datetime.date(2024, 1, 15)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 1, 15)] = math.exp(-0.04 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f")
settlement = datetime.date(2024, 1, 16)
# Pricing and yield
price = zcb.price(curve, settlement=settlement)
assert 75.0 < float(price) < 95.0
ytm = zcb.ytm(float(price), settlement)
assert 0.02 < float(ytm) < 0.06
# Price-from-YTM roundtrip
price_rt = zcb.price_from_ytm(ytm, settlement)
assert abs(float(price_rt) - float(price)) < 0.01
# OID accrued interest grows over time (requires issue_price < face_value)
zcb_oid = ZeroCouponBond(
effective=datetime.date(2024, 1, 15),
termination="5y",
issue_price=80.0,
face_value=100.0,
frequency="s",
convention="act365f",
currency="usd",
)
ai_early = zcb_oid.accrued_interest(datetime.date(2025, 1, 15))
ai_mid = zcb_oid.accrued_interest(datetime.date(2027, 1, 15))
assert float(ai_mid) > float(ai_early) # accrued grows over time
# Cashflows -- single redemption
cf = zcb.cashflows(curve)
assert len(cf) >= 1
# Risk analytics
dur = zcb.duration(curve, settlement=settlement)
assert 3.0 < float(dur) < 6.0
conv = zcb.convexity(curve, settlement=settlement)
assert float(conv) > 0
z_spr = zcb.z_spread(curve, price=float(price), settlement=settlement)
assert abs(float(z_spr)) < 0.01 # near zero when priced off same curveStepUpBond
Fixed-rate bond with coupon rates that change at specified dates. Python wrapper composing FixedLeg with dated rate resolution.
The coupon_rates parameter replaces the single coupon used by FixedRateBond, allowing step-up (or step-down) coupon schedules where the rate changes at predefined dates.
Constructor
StepUpBond(
*,
effective=None,
termination=None,
coupon_rates=None,
frequency="s",
settlement_days=1,
ex_div_days=0,
calendar=None,
currency="USD",
convention="actactisda",
accrued_convention=None,
face_value=100.0,
modifier="none",
disc_curve_id=None,
)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
effective | datetime.date or None | None | Issue date |
termination | date, str, or None | None | Maturity date or tenor string |
coupon_rates | list[tuple[date, float]] or None | None | Dated coupon rate schedule as [(date, rate), ...] |
frequency | str | "s" | Coupon payment frequency |
settlement_days | int | 1 | Settlement lag in business days |
ex_div_days | int | 0 | Ex-dividend days before coupon payment |
calendar | str or None | None | Named calendar |
currency | str | "USD" | Currency code |
convention | str | "actactisda" | Day count convention for coupon accrual |
accrued_convention | str or None | None | Separate day count for accrued interest (defaults to convention) |
face_value | float | 100.0 | Face value of the bond |
modifier | str | "none" | Business day adjustment rule |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
See Conventions for accepted values for frequency, convention, and modifier.
Methods
Standard Methods
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Par rate against curves |
.npv(curves, *, solver=None) | float | Net present value |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
.spread(curves, *, solver=None) | float | Par spread |
Price and Yield
| Method | Returns | Description |
|---|---|---|
.price(curves, *, solver=None, settlement=None) | float | Clean price |
.dirty_price(curves, *, solver=None, settlement=None) | float | Dirty price (clean + accrued) |
.accrued_interest(settlement) | float | Accrued interest at settlement date |
.ytm(clean_price, settlement, convention="periodic") | float | Yield to maturity from clean price |
.price_from_ytm(ytm, settlement, convention="periodic") | float | Clean price from yield to maturity |
Risk Analytics
| Method | Returns | Description |
|---|---|---|
.duration(curves, *, solver=None, metric="modified", settlement=None) | float | Modified or Macaulay duration |
.convexity(curves, *, solver=None, settlement=None) | float | Price convexity |
.dv01(curves, *, solver=None) | float | Dollar value of 1bp |
.z_spread(curves, *, solver=None, price=None, settlement=None) | float | Z-spread over curve |
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None) | float | Asset swap spread |
.analytic_delta(curves, *, solver=None) | float | Analytic delta |
Example
import math
import datetime
from vade import StepUpBond, DiscountCurve
# Flat 4% continuously compounded curve
base = datetime.date(2024, 1, 1)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 1, 1)] = math.exp(-0.04 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f")
bond = StepUpBond(
effective=datetime.date(2024, 1, 1),
termination=datetime.date(2029, 1, 1),
coupon_rates=[
(datetime.date(2024, 1, 1), 0.03), # 3% for first 3 years
(datetime.date(2027, 1, 1), 0.05), # steps up to 5%
],
frequency="s",
face_value=100.0,
convention="actactisda",
)
settlement = datetime.date(2024, 1, 2)
price = bond.price(curve, settlement=settlement)
assert 90.0 < price < 110.0 # near par
ytm = bond.ytm(float(price), settlement)
assert 0.03 < ytm < 0.06 # yield between step-up rates
dur = bond.duration(curve, metric="modified", settlement=settlement)
assert 3.0 < dur < 5.5 # reasonable duration for 5Y bondAdvanced Example
import datetime, math
from vade import StepUpBond, DiscountCurve
# 7-year step-up bond with 3 coupon steps
sub = StepUpBond(
effective=datetime.date(2024, 3, 15),
termination="7y",
coupon_rates=[
(datetime.date(2024, 3, 15), 0.03), # 3.0% initial
(datetime.date(2026, 3, 15), 0.035), # steps to 3.5% at year 2
(datetime.date(2028, 3, 15), 0.04), # steps to 4.0% at year 4
(datetime.date(2030, 3, 15), 0.045), # steps to 4.5% at year 6
],
frequency="s",
convention="act365f",
currency="usd",
)
# Flat 4% discount curve
base = datetime.date(2024, 3, 15)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 3, 15)] = math.exp(-0.04 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f")
# Pricing
price = sub.price(curve)
assert 90.0 < float(price) < 110.0
dirty = sub.dirty_price(curve)
assert 90.0 < float(dirty) < 115.0
# Yield to maturity
settlement = datetime.date(2024, 3, 16)
ytm = sub.ytm(float(price), settlement)
assert 0.02 < float(ytm) < 0.06
# Cashflows -- shows varying coupon amounts across steps
cf = sub.cashflows(curve)
assert len(cf) > 10 # semi-annual over 7 years
# Risk analytics
dur = sub.duration(curve)
assert 3.0 < float(dur) < 8.0
conv = sub.convexity(curve)
assert float(conv) > 0
dv01 = sub.dv01(curve)
assert float(dv01) > 0
z_spr = sub.z_spread(curve, price=float(price))
assert abs(float(z_spr)) < 0.01 # near zero when priced off same curveAmortizingBond
Fixed-rate bond with declining notional via scheduled principal repayments. Python wrapper composing FixedLeg with amortization schedule.
Principal repayment amounts are specified as absolute values. Coupon payments are computed on the remaining (reduced) notional after each principal repayment.
Constructor
AmortizingBond(
*,
effective=None,
termination=None,
coupon=0.0,
amortization_schedule=None,
frequency="s",
settlement_days=1,
ex_div_days=0,
calendar=None,
currency="USD",
convention="actactisda",
accrued_convention=None,
face_value=100.0,
modifier="none",
disc_curve_id=None,
)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
effective | datetime.date or None | None | Issue date |
termination | date, str, or None | None | Maturity date or tenor string |
coupon | float | 0.0 | Coupon rate (percentage, e.g., 0.05 for 5%) |
amortization_schedule | list[tuple[date, float]] or None | None | Principal repayment schedule as [(date, amount), ...] |
frequency | str | "s" | Coupon payment frequency |
settlement_days | int | 1 | Settlement lag in business days |
ex_div_days | int | 0 | Ex-dividend days before coupon payment |
calendar | str or None | None | Named calendar |
currency | str | "USD" | Currency code |
convention | str | "actactisda" | Day count convention for coupon accrual |
accrued_convention | str or None | None | Separate day count for accrued interest (defaults to convention) |
face_value | float | 100.0 | Initial face value of the bond |
modifier | str | "none" | Business day adjustment rule |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
See Conventions for accepted values for frequency, convention, and modifier.
Methods
Standard Methods
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Par rate against curves |
.npv(curves, *, solver=None) | float | Net present value |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
.spread(curves, *, solver=None) | float | Par spread |
Price and Yield
| Method | Returns | Description |
|---|---|---|
.price(curves, *, solver=None, settlement=None) | float | Clean price |
.dirty_price(curves, *, solver=None, settlement=None) | float | Dirty price (clean + accrued) |
.accrued_interest(settlement) | float | Accrued interest at settlement date |
.ytm(clean_price, settlement, convention="periodic") | float | Yield to maturity from clean price |
.price_from_ytm(ytm, settlement, convention="periodic") | float | Clean price from yield to maturity |
Risk Analytics
| Method | Returns | Description |
|---|---|---|
.duration(curves, *, solver=None, metric="modified", settlement=None) | float | Modified or Macaulay duration |
.convexity(curves, *, solver=None, settlement=None) | float | Price convexity |
.dv01(curves, *, solver=None) | float | Dollar value of 1bp |
.z_spread(curves, *, solver=None, price=None, settlement=None) | float | Z-spread over curve |
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None) | float | Asset swap spread |
.analytic_delta(curves, *, solver=None) | float | Analytic delta |
Example
import math
import datetime
from vade import AmortizingBond, DiscountCurve
# Flat 4% continuously compounded curve
base = datetime.date(2024, 1, 1)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 1, 1)] = math.exp(-0.04 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f")
bond = AmortizingBond(
effective=datetime.date(2024, 1, 1),
termination=datetime.date(2029, 1, 1),
coupon=0.05,
amortization_schedule=[
(datetime.date(2026, 1, 1), 25.0), # repay 25 at year 2
(datetime.date(2028, 1, 1), 25.0), # repay 25 at year 4
],
frequency="a",
face_value=100.0,
convention="actactisda",
)
settlement = datetime.date(2024, 1, 2)
price = bond.price(curve, settlement=settlement)
assert 95.0 < price < 115.0 # above par when coupon > yield
ytm = bond.ytm(float(price), settlement)
assert 0.03 < ytm < 0.06 # reasonable yield range
# Accrued interest reflects current notional
ai_early = bond.accrued_interest(datetime.date(2024, 7, 1))
ai_late = bond.accrued_interest(datetime.date(2026, 7, 1))
assert ai_early > ai_late # lower notional after amortizationAdvanced Example
import datetime, math
from vade import AmortizingBond, DiscountCurve
# 5-year amortizing bond: 25% principal at Y2 and Y4
ab = AmortizingBond(
effective=datetime.date(2024, 3, 15),
termination="5y",
coupon=0.05,
frequency="s",
convention="act365f",
currency="usd",
amortization_schedule=[
(datetime.date(2026, 3, 15), 25.0),
(datetime.date(2028, 3, 15), 25.0),
],
)
# Flat 4% discount curve
base = datetime.date(2024, 3, 15)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 3, 15)] = math.exp(-0.04 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f")
# Pricing and yield
settlement = datetime.date(2024, 3, 16)
price = ab.price(curve)
assert 100.0 < float(price) < 110.0 # above par, coupon > discount rate
ytm = ab.ytm(float(price), settlement)
assert 0.03 < float(ytm) < 0.06
# Cashflows -- inspect types: "Amortizing" and "Principal" rows present
cf = ab.cashflows(curve)
assert len(cf) > 5
cf_types = cf["Type"].to_list()
assert "Amortizing" in cf_types
assert "Principal" in cf_types # amortization creates principal repayment rows
# Accrued interest declines as notional amortizes
ai_before = ab.accrued_interest(datetime.date(2025, 6, 15)) # before any amort
ai_after = ab.accrued_interest(datetime.date(2026, 6, 15)) # after first 25% amort
assert float(ai_before) > float(ai_after) # lower notional -> lower accrued
# Risk analytics -- amortization reduces effective duration
dur = ab.duration(curve)
assert 2.0 < float(dur) < 5.0 # shorter than 5Y bullet
conv = ab.convexity(curve)
assert float(conv) > 0
dv01 = ab.dv01(curve)
assert float(dv01) > 0
z_spr = ab.z_spread(curve, price=float(price))
assert abs(float(z_spr)) < 0.01 # near zero when priced off same curvePIKBond
Payment-in-kind bond where accrued interest compounds into a growing notional. Python wrapper composing FixedLeg with PIK notional accretion.
With pik_fraction=1.0 (full PIK), no cash coupons are paid; all interest accrues to principal. With pik_fraction=0.5, half the coupon is paid in cash and half compounds into the notional. The notional_schedule() method returns the growing notional at each period boundary.
Constructor
PIKBond(
*,
effective=None,
termination=None,
coupon=0.0,
pik_fraction=1.0,
frequency="s",
settlement_days=1,
ex_div_days=0,
calendar=None,
currency="USD",
convention="actactisda",
accrued_convention=None,
face_value=100.0,
modifier="none",
disc_curve_id=None,
)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
effective | datetime.date or None | None | Issue date |
termination | date, str, or None | None | Maturity date or tenor string |
coupon | float | 0.0 | Coupon rate (percentage, e.g., 0.06 for 6%) |
pik_fraction | float | 1.0 | Fraction of coupon paid in kind (1.0 = full PIK, 0.0 = all cash) |
frequency | str | "s" | Coupon payment frequency |
settlement_days | int | 1 | Settlement lag in business days |
ex_div_days | int | 0 | Ex-dividend days before coupon payment |
calendar | str or None | None | Named calendar |
currency | str | "USD" | Currency code |
convention | str | "actactisda" | Day count convention for coupon accrual |
accrued_convention | str or None | None | Separate day count for accrued interest (defaults to convention) |
face_value | float | 100.0 | Initial face value of the bond |
modifier | str | "none" | Business day adjustment rule |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
See Conventions for accepted values for frequency, convention, and modifier.
Methods
Standard Methods
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Par rate against curves |
.npv(curves, *, solver=None) | float | Net present value |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
.spread(curves, *, solver=None) | float | Par spread |
Price and Yield
| Method | Returns | Description |
|---|---|---|
.price(curves, *, solver=None, settlement=None) | float | Clean price |
.dirty_price(curves, *, solver=None, settlement=None) | float | Dirty price (clean + accrued) |
.accrued_interest(settlement) | float | Accrued interest at settlement date |
.ytm(clean_price, settlement, convention="periodic") | float | Yield to maturity from clean price |
.price_from_ytm(ytm, settlement, convention="periodic") | float | Clean price from yield to maturity |
Risk Analytics
| Method | Returns | Description |
|---|---|---|
.duration(curves, *, solver=None, metric="modified", settlement=None) | float | Modified or Macaulay duration |
.convexity(curves, *, solver=None, settlement=None) | float | Price convexity |
.dv01(curves, *, solver=None) | float | Dollar value of 1bp |
.z_spread(curves, *, solver=None, price=None, settlement=None) | float | Z-spread over curve |
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None) | float | Asset swap spread |
.analytic_delta(curves, *, solver=None) | float | Analytic delta |
PIK-Specific
| Method | Returns | Description |
|---|---|---|
.notional_schedule() | list[tuple[date, float]] | Growing notional at each period boundary |
Example
import math
import datetime
from vade import PIKBond, DiscountCurve
# Flat 4% continuously compounded curve
base = datetime.date(2024, 1, 1)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 1, 1)] = math.exp(-0.04 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f")
bond = PIKBond(
effective=datetime.date(2024, 1, 1),
termination=datetime.date(2029, 1, 1),
coupon=0.06,
pik_fraction=1.0, # full PIK: all interest compounds
frequency="a",
face_value=100.0,
convention="actactisda",
)
# Full PIK means no cash coupon accrues
assert bond.accrued_interest(datetime.date(2024, 7, 1)) == 0.0
# Notional grows each period
schedule = bond.notional_schedule()
assert schedule[0][1] == 100.0 # initial face value
assert schedule[1][1] > 100.0 # notional grows after first period
price = bond.price(curve, settlement=datetime.date(2024, 1, 2))
assert 100.0 < price < 120.0 # above par for 6% PIK vs 4% discountAdvanced Example
import datetime, math
from vade import PIKBond, DiscountCurve
# Full PIK bond: 6% coupon, all interest capitalised
pik_full = PIKBond(
effective=datetime.date(2024, 1, 15),
termination="5y",
coupon=0.06,
pik_fraction=1.0,
frequency="a",
convention="act365f",
currency="usd",
)
# Partial PIK bond: same terms, 50% cash / 50% PIK
pik_partial = PIKBond(
effective=datetime.date(2024, 1, 15),
termination="5y",
coupon=0.06,
pik_fraction=0.5,
frequency="a",
convention="act365f",
currency="usd",
)
# Flat 4% discount curve
base = datetime.date(2024, 1, 15)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 1, 15)] = math.exp(-0.04 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f")
# notional_schedule() shows compounding growth for full PIK
ns = pik_full.notional_schedule()
assert len(ns) > 1
# First entry is par, subsequent entries grow as interest is capitalised
assert ns[0][1] == 100.0
assert ns[-1][1] > 100.0 # notional has grown
# Full PIK has zero accrued (all capitalised)
ai_full = pik_full.accrued_interest(datetime.date(2024, 7, 15))
assert float(ai_full) == 0.0
# Partial PIK has positive accrued (cash portion)
ai_partial = pik_partial.accrued_interest(datetime.date(2024, 7, 15))
assert float(ai_partial) > 0.0
# Full PIK price is higher (more future cashflows from compounding)
price_full = pik_full.price(curve)
price_partial = pik_partial.price(curve)
assert float(price_full) > float(price_partial)
# Cashflows
cf = pik_full.cashflows(curve)
assert len(cf) > 0
# Risk analytics
dur = pik_full.duration(curve)
assert 3.0 < float(dur) < 6.0
conv = pik_full.convexity(curve)
assert float(conv) > 0CappedFloatRateNote
Floating rate note with embedded cap and/or floor on the coupon rate. Python wrapper composing FloatLeg with Black-76 or Bachelier cap/floor pricing.
Requires vol (volatility) -- there is no intrinsic-only fallback. At least one of cap_rate or floor_rate must be specified. When both are set, the instrument is a collared FRN. Optionally supports sub-period compounding via fixing_frequency.
Constructor
CappedFloatRateNote(
*,
effective=None,
termination=None,
spread=0.0,
frequency="q",
fixing_frequency=None,
cap_rate=None,
floor_rate=None,
vol=None,
vol_model="black76",
settlement_days=2,
ex_div_days=0,
calendar=None,
currency="USD",
convention="act360",
accrued_convention=None,
face_value=100.0,
modifier="mf",
disc_curve_id=None,
)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
effective | datetime.date or None | None | Issue date |
termination | date, str, or None | None | Maturity date or tenor string |
spread | float | 0.0 | Spread over floating rate (decimal) |
frequency | str | "q" | Coupon payment frequency |
fixing_frequency | str or None | None | Sub-period fixing frequency (e.g., "m" for monthly within quarterly) |
cap_rate | float or None | None | Cap strike rate (decimal). At least one of cap_rate/floor_rate required. |
floor_rate | float or None | None | Floor strike rate (decimal) |
vol | float or None | None | Volatility for option pricing. Required -- no intrinsic fallback. |
vol_model | str | "black76" | Pricing model: "black76" or "bachelier" |
settlement_days | int | 2 | Settlement lag in business days |
ex_div_days | int | 0 | Ex-dividend days before coupon payment |
calendar | str or None | None | Named calendar |
currency | str | "USD" | Currency code |
convention | str | "act360" | Day count convention |
accrued_convention | str or None | None | Separate day count for accrued interest (defaults to convention) |
face_value | float | 100.0 | Face value |
modifier | str | "mf" | Business day adjustment rule |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
See Conventions for accepted values for frequency, convention, and modifier.
Methods
Standard Methods
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Par rate against curves |
.npv(curves, *, solver=None) | float | Net present value |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
.spread(curves, *, solver=None) | float | Par spread |
Price and Yield
| Method | Returns | Description |
|---|---|---|
.price(curves, *, solver=None, settlement=None) | float | Clean price |
.dirty_price(curves, *, solver=None, settlement=None) | float | Dirty price (clean + accrued) |
.accrued_interest(settlement) | float | Accrued interest at settlement date |
.ytm(clean_price, settlement, convention="periodic") | float | Yield to maturity from clean price |
.price_from_ytm(ytm, settlement, convention="periodic") | float | Clean price from yield to maturity |
Risk Analytics
| Method | Returns | Description |
|---|---|---|
.duration(curves, *, solver=None, metric="modified", settlement=None) | float | Modified or Macaulay duration |
.convexity(curves, *, solver=None, settlement=None) | float | Price convexity |
.dv01(curves, *, solver=None) | float | Dollar value of 1bp |
.z_spread(curves, *, solver=None, price=None, settlement=None) | float | Z-spread over curve |
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None) | float | Asset swap spread |
.analytic_delta(curves, *, solver=None) | float | Analytic delta |
CappedFRN-Specific
| Method | Returns | Description |
|---|---|---|
.sub_period_schedule() | DataFrame | Sub-period schedule when fixing_frequency set |
Example
import math
import datetime
from vade import CappedFloatRateNote, FloatRateNote, DiscountCurve
# Steep discount curve (rates ~5-6%)
base = datetime.date(2024, 1, 1)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 1, 1)] = math.exp(-0.055 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f")
# Uncapped FRN for comparison
frn = FloatRateNote(
effective=datetime.date(2024, 1, 1),
termination=datetime.date(2026, 1, 1),
spread=0.0,
frequency="q",
convention="act360",
)
frn_price = frn.dirty_price(curves=[curve, curve])
# Capped FRN with 2% cap (well below market rates)
capped = CappedFloatRateNote(
effective=datetime.date(2024, 1, 1),
termination=datetime.date(2026, 1, 1),
spread=0.0,
frequency="q",
cap_rate=0.02,
vol=0.20,
convention="act360",
)
capped_price = capped.dirty_price(curves=[curve, curve])
assert isinstance(capped_price, float)
assert capped_price < frn_price # cap reduces value to investorAdvanced Example
import datetime, math
from vade import CappedFloatRateNote, DiscountCurve
# Base discount/forecast curve (flat 4%)
base = datetime.date(2024, 6, 15)
nodes = {base: 1.0}
for y in range(1, 6):
nodes[datetime.date(2024 + y, 6, 15)] = math.exp(-0.04 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act360")
# Capped FRN: floating rate capped at 5%
capped = CappedFloatRateNote(
effective=datetime.date(2024, 6, 15),
termination="3y",
frequency="q",
convention="act360",
currency="usd",
cap_rate=0.05,
vol=0.50,
)
# Floored FRN: floating rate floored at 2%
floored = CappedFloatRateNote(
effective=datetime.date(2024, 6, 15),
termination="3y",
frequency="q",
convention="act360",
currency="usd",
floor_rate=0.02,
vol=0.50,
)
# Collared FRN: both cap and floor
collared = CappedFloatRateNote(
effective=datetime.date(2024, 6, 15),
termination="3y",
frequency="q",
convention="act360",
currency="usd",
cap_rate=0.05,
floor_rate=0.02,
vol=0.50,
)
# Pricing -- pass curves as list [discount, forecast] (required format)
price_capped = capped.dirty_price(curves=[curve, curve])
price_floored = floored.dirty_price(curves=[curve, curve])
price_collared = collared.dirty_price(curves=[curve, curve])
assert isinstance(float(price_capped), float)
assert isinstance(float(price_floored), float)
assert isinstance(float(price_collared), float)
# Floor adds value (guarantees minimum coupon)
# Cap reduces value (limits upside)
# So: capped < collared < floored
assert float(price_capped) < float(price_floored)
# Bachelier model comparison (vol_model="bachelier")
capped_bach = CappedFloatRateNote(
effective=datetime.date(2024, 6, 15),
termination="3y",
frequency="q",
convention="act360",
currency="usd",
cap_rate=0.05,
vol=0.01, # Bachelier vol in absolute terms
vol_model="bachelier",
)
price_bach = capped_bach.dirty_price(curves=[curve, curve])
assert isinstance(float(price_bach), float)
# Bachelier and Black-76 produce different prices
assert float(price_bach) != float(price_capped)
# Cashflows
cf = capped.cashflows(curves=[curve, curve])
assert len(cf) > 0
# Risk analytics -- near-zero for FRN is expected (resets to par)
dur = capped.duration(curves=[curve, curve])
assert isinstance(float(dur), float)AssetSwap
Asset swap decomposing a bond into a floating-rate equivalent. Python wrapper composing bond + floating leg.
Supports "par" (par-par) and "non_par" asset swap types. The par asset swap computes the spread over floating that makes the package NPV zero at par. The non-par variant requires a dirty_price input.
See also: Spread Analytics Guide for asset swap workflows and par vs non-par comparison.
Constructor
AssetSwap(bond, *, asw_type="par", dirty_price=None, spread=None, disc_curve_id=None)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
bond | bond instrument | required | Underlying bond (FixedRateBond, FloatRateNote, StepUpBond, etc.). Positional. |
asw_type | str | "par" | Asset swap type: "par" or "non_par" |
dirty_price | float or None | None | Dirty price of the bond (required for "non_par") |
spread | float or None | None | Fixed spread override |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
Methods
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Asset swap spread |
.npv(curves, *, solver=None) | float | Net present value of the package |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
Example
import math
import datetime
from vade import AssetSwap, FixedRateBond, DiscountCurve
# Flat 3% continuously compounded curve
base = datetime.date(2024, 1, 1)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 1, 1)] = math.exp(-0.03 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f", id="disc")
bond = FixedRateBond(
effective=datetime.date(2024, 1, 1),
termination="5Y",
coupon=0.04,
frequency="s",
face_value=100.0,
)
# Par asset swap
asw = AssetSwap(bond=bond, asw_type="par")
spread = asw.rate(curves=curve)
assert isinstance(spread, float)
assert -0.5 < spread < 0.5 # spread in reasonable range
# NPV is zero at fair spread (no fixed spread override)
npv = asw.npv(curves=curve)
assert abs(npv) < 1e-8
# Non-par asset swap with explicit dirty price
asw_np = AssetSwap(bond=bond, asw_type="non_par", dirty_price=102.5)
spread_np = asw_np.rate(curves=curve)
assert isinstance(spread_np, float)CallableBond
Bond with embedded call and/or put options, priced via Hull-White trinomial tree. Python wrapper composing bond + option schedule.
Tree-based methods require Hull-White model parameters: a (mean reversion speed) and sigma (short-rate volatility). Standard bond methods (rate, npv, cashflows) delegate to the underlying bond.
See also: Callable Bonds Guide for OAS analysis and effective duration workflows.
Constructor
CallableBond(bond, *, call_schedule=None, put_schedule=None, disc_curve_id=None)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
bond | bond instrument | required | Underlying bond (typically FixedRateBond). Positional. |
call_schedule | list[tuple[date, float]] or None | None | Call dates and prices as [(date, strike_price), ...] |
put_schedule | list[tuple[date, float]] or None | None | Put dates and prices as [(date, strike_price), ...] |
disc_curve_id | str or None | None | Discount curve identifier for Solver lookup |
Methods
Standard Methods (delegated to underlying bond)
| Method | Returns | Description |
|---|---|---|
.rate(curves, *, solver=None) | float | Par rate against curves |
.npv(curves, *, solver=None) | float | Net present value |
.cashflows(curves, *, solver=None) | DataFrame | Period-level cashflow table |
Tree-Based Methods (Hull-White)
| Method | Returns | Description |
|---|---|---|
.tree_price(curves, a, sigma, *, spread=0.0) | float | Option-adjusted price via trinomial tree |
.oas(curves, market_price, a, sigma) | float | Option-adjusted spread from market price |
.effective_duration(curves, a, sigma, *, oas=0.0, bump=0.0001) | float | Effective duration (curve bump) |
.effective_convexity(curves, a, sigma, *, oas=0.0, bump=0.0001) | float | Effective convexity (curve bump) |
.vega(curves, a, sigma, *, oas=0.0, bump=0.0001) | float | Vega (price sensitivity to volatility) |
Tree-based methods take a (mean reversion speed, e.g., 0.1) and sigma (short-rate vol, e.g., 0.01).
Example
import math
import datetime
from vade import CallableBond, FixedRateBond, DiscountCurve
# Flat 3% continuously compounded curve
base = datetime.date(2024, 1, 1)
nodes = {base: 1.0}
for y in range(1, 11):
nodes[datetime.date(2024 + y, 1, 1)] = math.exp(-0.03 * y)
curve = DiscountCurve(nodes, interpolation="log_linear", convention="act365f", id="disc")
bond = FixedRateBond(
effective=datetime.date(2024, 1, 1),
termination="5Y",
coupon=0.05,
frequency="a",
face_value=100.0,
)
cb = CallableBond(
bond=bond,
call_schedule=[
(datetime.date(2026, 1, 1), 100.0),
(datetime.date(2027, 1, 1), 100.0),
(datetime.date(2028, 1, 1), 100.0),
],
)
# Tree price with Hull-White parameters
tree_px = cb.tree_price(curves=curve, a=0.1, sigma=0.01)
assert 80.0 < tree_px < 120.0 # reasonable price range
# OAS: use tree_price as market price => OAS should be ~0
oas = cb.oas(curves=curve, market_price=tree_px, a=0.1, sigma=0.01)
assert abs(oas) < 1e-4 # OAS ~0 when market = model price
# Effective duration and convexity
eff_dur = cb.effective_duration(curves=curve, a=0.1, sigma=0.01)
assert eff_dur > 0 # positive duration
eff_cvx = cb.effective_convexity(curves=curve, a=0.1, sigma=0.01)
assert isinstance(eff_cvx, float)CDS
Credit Default Swap with hazard rate curve pricing. Rust-backed.
See also: Credit Curves & CDS Guide for CDS-based credit curve calibration.
Alias: CDS = CreditDefaultSwap
Constructor
CDS(
*,
spec=None,
effective=None,
termination=None,
frequency="q",
notional=10_000_000.0,
recovery_rate=0.4,
fixed_rate=100.0,
convention="act360",
currency="USD",
premium_accrued=True,
modifier="mf",
stub="shortfront",
)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
spec | str or None | None | Instrument spec name |
effective | datetime.date or None | None | Protection start date |
termination | date, str, or None | None | Protection end date or tenor string (e.g., "5Y") |
frequency | str | "q" | Premium payment frequency |
notional | float | 10_000_000.0 | Notional amount |
recovery_rate | float | 0.4 | Assumed recovery rate (decimal, e.g., 0.4 for 40%) |
fixed_rate | float | 100.0 | Fixed premium rate (basis points) |
convention | str | "act360" | Day count convention |
currency | str | "USD" | Currency code |
premium_accrued | bool | True | Whether premium accrues to default date |
modifier | str | "mf" | Business day adjustment rule |
stub | str | "shortfront" | Stub period preference |
See Conventions for accepted values for frequency, convention, modifier, and stub.
Methods
CDS methods take two separate curve arguments: a discount curve and a CreditImpliedCurve for survival probabilities.
| Method | Returns | Description |
|---|---|---|
.rate(disc_curve, credit_curve) | float | Par spread (same as .spread) |
.npv(disc_curve, credit_curve) | float | Net present value |
.spread(disc_curve, credit_curve) | float | Par spread |
.cashflows(disc_curve, credit_curve) | DataFrame | Period-level cashflow table |
.accrued(today=None) | float | Accrued premium |
.analytic_rec_risk(disc_curve, credit_curve) | float | Recovery risk |
.jtd(today=None) | float | Jump-to-default exposure |
.ead() | float | Exposure at default |
.upfront(disc_curve, credit_curve) | float | Upfront payment amount |
.to_upfront_spread(disc_curve, credit_curve) | float | Convert running to upfront spread |
Example
import datetime
from vade import CDS, DiscountCurve, CreditImpliedCurve
disc_nodes = {
datetime.date(2024, 1, 1): 1.0,
datetime.date(2025, 1, 1): 0.97,
datetime.date(2026, 1, 1): 0.94,
datetime.date(2029, 1, 1): 0.85,
}
disc_curve = DiscountCurve(disc_nodes, interpolation="log_linear", convention="act365f")
credit_nodes = {
datetime.date(2024, 1, 1): 1.0,
datetime.date(2025, 1, 1): 0.99,
datetime.date(2026, 1, 1): 0.98,
datetime.date(2029, 1, 1): 0.95,
}
credit_curve = CreditImpliedCurve(credit_nodes, convention="act365f", recovery_rate=0.4)
cds = CDS(
effective=datetime.date(2024, 1, 1),
termination="5Y",
fixed_rate=100.0,
frequency="q",
convention="act360",
recovery_rate=0.4,
)
cds.npv(disc_curve, credit_curve) # net present value
cds.spread(disc_curve, credit_curve) # par spread in basis points
cds.ead() # exposure at defaultSee Also
- Credit Guides -- Bond analytics, CDS pricing, fitted curves, and spread analysis guides
- Curves API -- CreditImpliedCurve, FittedBondCurve, SpreadCurve