API ReferenceCredit API Reference

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, CDS

Or 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, CDS

See 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

NameTypeDefaultDescription
specstr or NoneNoneInstrument spec name
effectivedatetime.date or NoneNoneIssue or settlement date
terminationdate, str, or NoneNoneMaturity date or tenor string
couponfloat0.0Coupon rate (percentage, e.g., 5.0 for 5%)
frequencystr"s"Coupon payment frequency
settlement_daysint1Settlement lag in business days
ex_div_daysint0Ex-dividend days before coupon payment
calendarstr or NoneNoneNamed calendar
currencystr"USD"Currency code
conventionstr"actactisda"Day count convention for coupon accrual
accrued_conventionstr or NoneNoneSeparate day count for accrued interest (defaults to convention)
face_valuefloat100.0Face value of the bond
modifierstr"none"Business day adjustment rule
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup

See Conventions for accepted values for frequency, convention, and modifier.

Methods

Standard Methods

MethodReturnsDescription
.rate(curves, *, solver=None)floatPar rate against curves
.npv(curves, *, solver=None)floatNet present value
.cashflows(curves, *, solver=None)DataFramePeriod-level cashflow table
.spread(curves, *, solver=None)floatPar spread

Price and Yield

MethodReturnsDescription
.price(curves, *, solver=None, settlement=None)floatClean price
.dirty_price(curves, *, solver=None, settlement=None)floatDirty price (clean + accrued)
.accrued_interest(settlement)floatAccrued interest at settlement date
.ytm(clean_price, settlement, convention="periodic")floatYield to maturity from clean price
.price_from_ytm(ytm, settlement, convention="periodic")floatClean price from yield to maturity

YTM convention accepts "periodic", "annual", "semi_annual", or "continuous".

Risk Analytics

MethodReturnsDescription
.duration(curves, *, solver=None, metric="modified", settlement=None)floatModified or Macaulay duration
.convexity(curves, *, solver=None, settlement=None)floatPrice convexity
.dv01(curves, *, solver=None)floatDollar value of 1bp
.z_spread(curves, *, solver=None, price=None, settlement=None)floatZ-spread over curve
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None)floatAsset swap spread
.cs01(curves, *, solver=None)floatCredit spread sensitivity (1bp)
.price_yield_sensitivity(curves, *, solver=None)floatPrice-yield sensitivity
.analytic_delta(curves, *, solver=None)floatAnalytic 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 duration

Bill

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

NameTypeDefaultDescription
specstr or NoneNoneInstrument spec name
effectivedatetime.date or NoneNoneIssue date
terminationdate, str, or NoneNoneMaturity date or tenor string (e.g., "6M")
settlement_daysint1Settlement lag in business days
calendarstr or NoneNoneNamed calendar
currencystr"USD"Currency code
conventionstr"act360"Day count convention
face_valuefloat100.0Face value
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup

See Conventions for accepted values for convention.

Methods

MethodReturnsDescription
.rate(curves, *, solver=None)floatPar rate against curves
.npv(curves, *, solver=None)floatNet present value
.cashflows(curves, *, solver=None)DataFramePeriod-level cashflow table
.price(curves, *, solver=None)floatBill price
.dirty_price(curves, *, solver=None)floatDirty price
.ytm(clean_price, settlement)floatYield 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 price

Advanced 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

NameTypeDefaultDescription
specstr or NoneNoneInstrument spec name
effectivedatetime.date or NoneNoneIssue date
terminationdate, str, or NoneNoneMaturity date or tenor string
spreadfloat0.0Spread over floating rate (basis points)
frequencystr"q"Coupon payment frequency
settlement_daysint2Settlement lag in business days
ex_div_daysint0Ex-dividend days before coupon payment
calendarstr or NoneNoneNamed calendar
currencystr"USD"Currency code
conventionstr"act360"Day count convention
accrued_conventionstr or NoneNoneSeparate day count for accrued interest (defaults to convention)
face_valuefloat100.0Face value
modifierstr"mf"Business day adjustment rule
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup

See Conventions for accepted values for frequency, convention, and modifier.

Methods

MethodReturnsDescription
.rate(curves, *, solver=None)floatPar rate against curves
.npv(curves, *, solver=None)floatNet present value
.cashflows(curves, *, solver=None)DataFramePeriod-level cashflow table
.price(curves, *, solver=None)floatClean price
.dirty_price(curves, *, solver=None)floatDirty price
.analytic_delta(curves, *, solver=None)floatAnalytic 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 price

SubPeriodFRN

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

NameTypeDefaultDescription
effectivedatetime.date or NoneNoneIssue date
terminationdate, str, or NoneNoneMaturity date or tenor string
spreadfloat0.0Spread over floating rate (decimal, e.g., 0.001 = 10bp)
frequencystr"q"Coupon payment frequency
fixing_frequencystr"m"Sub-period fixing frequency (must be higher than frequency)
settlement_daysint2Settlement lag in business days
ex_div_daysint0Ex-dividend days before coupon payment
calendarstr or NoneNoneNamed calendar
currencystr"USD"Currency code
conventionstr"act360"Day count convention
accrued_conventionstr or NoneNoneSeparate day count for accrued interest (defaults to convention)
face_valuefloat100.0Face value
modifierstr"mf"Business day adjustment rule
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup
indexstr or NoneNoneFloating rate index name for fixing lookups

See Conventions for accepted values for frequency, convention, and modifier.

Methods

Standard Methods

MethodReturnsDescription
.rate(curves, *, solver=None)floatPar rate against curves
.npv(curves, *, solver=None)floatNet present value
.cashflows(curves, *, solver=None)DataFramePeriod-level cashflow table
.spread(curves, *, solver=None)floatPar spread

Price and Yield

MethodReturnsDescription
.price(curves, *, solver=None, settlement=None)floatClean price
.dirty_price(curves, *, solver=None, settlement=None)floatDirty price (clean + accrued)
.accrued_interest(settlement)floatAccrued interest at settlement date
.ytm(clean_price, settlement, convention="periodic")floatYield to maturity from clean price
.price_from_ytm(ytm, settlement, convention="periodic")floatClean price from yield to maturity

Risk Analytics

MethodReturnsDescription
.duration(curves, *, solver=None, metric="modified", settlement=None)floatModified or Macaulay duration
.convexity(curves, *, solver=None, settlement=None)floatPrice convexity
.dv01(curves, *, solver=None)floatDollar value of 1bp
.z_spread(curves, *, solver=None, price=None, settlement=None)floatZ-spread over curve
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None)floatAsset swap spread
.analytic_delta(curves, *, solver=None)floatAnalytic delta
.i_spread(settlement, swap_curve)floatInterpolated spread vs swap curve

SubPeriodFRN-Specific

MethodReturnsDescription
.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

NameTypeDefaultDescription
effectivedatetime.date or NoneNoneIssue date
terminationdate, str, or NoneNoneMaturity date or tenor string
issue_pricefloat100.0Original issue price (OID basis for accrued calculation)
frequencystr"s"Accrual frequency for OID calculation
settlement_daysint1Settlement lag in business days
calendarstr or NoneNoneNamed calendar
currencystr"USD"Currency code
conventionstr"actactisda"Day count convention
face_valuefloat100.0Face value at maturity
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup

See Conventions for accepted values for frequency and convention.

Methods

Standard Methods

MethodReturnsDescription
.rate(curves, *, solver=None)floatPar rate against curves
.npv(curves, *, solver=None)floatNet present value
.cashflows(curves, *, solver=None)DataFramePeriod-level cashflow table

Price and Yield

MethodReturnsDescription
.price(curves, *, solver=None, settlement=None)floatClean price
.dirty_price(curves, *, solver=None, settlement=None)floatDirty price (clean + OID accrued)
.accrued_interest(settlement)floatOID accrued interest at settlement date
.ytm(clean_price, settlement, convention="periodic")floatYield to maturity from clean price
.price_from_ytm(ytm, settlement, convention="periodic")floatClean price from yield to maturity

Risk Analytics

MethodReturnsDescription
.duration(curves, *, solver=None, metric="modified", settlement=None)floatModified or Macaulay duration
.convexity(curves, *, solver=None, settlement=None)floatPrice convexity
.z_spread(curves, *, solver=None, price=None, settlement=None)floatZ-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 zero

Advanced 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 curve

StepUpBond

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

NameTypeDefaultDescription
effectivedatetime.date or NoneNoneIssue date
terminationdate, str, or NoneNoneMaturity date or tenor string
coupon_rateslist[tuple[date, float]] or NoneNoneDated coupon rate schedule as [(date, rate), ...]
frequencystr"s"Coupon payment frequency
settlement_daysint1Settlement lag in business days
ex_div_daysint0Ex-dividend days before coupon payment
calendarstr or NoneNoneNamed calendar
currencystr"USD"Currency code
conventionstr"actactisda"Day count convention for coupon accrual
accrued_conventionstr or NoneNoneSeparate day count for accrued interest (defaults to convention)
face_valuefloat100.0Face value of the bond
modifierstr"none"Business day adjustment rule
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup

See Conventions for accepted values for frequency, convention, and modifier.

Methods

Standard Methods

MethodReturnsDescription
.rate(curves, *, solver=None)floatPar rate against curves
.npv(curves, *, solver=None)floatNet present value
.cashflows(curves, *, solver=None)DataFramePeriod-level cashflow table
.spread(curves, *, solver=None)floatPar spread

Price and Yield

MethodReturnsDescription
.price(curves, *, solver=None, settlement=None)floatClean price
.dirty_price(curves, *, solver=None, settlement=None)floatDirty price (clean + accrued)
.accrued_interest(settlement)floatAccrued interest at settlement date
.ytm(clean_price, settlement, convention="periodic")floatYield to maturity from clean price
.price_from_ytm(ytm, settlement, convention="periodic")floatClean price from yield to maturity

Risk Analytics

MethodReturnsDescription
.duration(curves, *, solver=None, metric="modified", settlement=None)floatModified or Macaulay duration
.convexity(curves, *, solver=None, settlement=None)floatPrice convexity
.dv01(curves, *, solver=None)floatDollar value of 1bp
.z_spread(curves, *, solver=None, price=None, settlement=None)floatZ-spread over curve
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None)floatAsset swap spread
.analytic_delta(curves, *, solver=None)floatAnalytic 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 bond

Advanced 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 curve

AmortizingBond

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

NameTypeDefaultDescription
effectivedatetime.date or NoneNoneIssue date
terminationdate, str, or NoneNoneMaturity date or tenor string
couponfloat0.0Coupon rate (percentage, e.g., 0.05 for 5%)
amortization_schedulelist[tuple[date, float]] or NoneNonePrincipal repayment schedule as [(date, amount), ...]
frequencystr"s"Coupon payment frequency
settlement_daysint1Settlement lag in business days
ex_div_daysint0Ex-dividend days before coupon payment
calendarstr or NoneNoneNamed calendar
currencystr"USD"Currency code
conventionstr"actactisda"Day count convention for coupon accrual
accrued_conventionstr or NoneNoneSeparate day count for accrued interest (defaults to convention)
face_valuefloat100.0Initial face value of the bond
modifierstr"none"Business day adjustment rule
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup

See Conventions for accepted values for frequency, convention, and modifier.

Methods

Standard Methods

MethodReturnsDescription
.rate(curves, *, solver=None)floatPar rate against curves
.npv(curves, *, solver=None)floatNet present value
.cashflows(curves, *, solver=None)DataFramePeriod-level cashflow table
.spread(curves, *, solver=None)floatPar spread

Price and Yield

MethodReturnsDescription
.price(curves, *, solver=None, settlement=None)floatClean price
.dirty_price(curves, *, solver=None, settlement=None)floatDirty price (clean + accrued)
.accrued_interest(settlement)floatAccrued interest at settlement date
.ytm(clean_price, settlement, convention="periodic")floatYield to maturity from clean price
.price_from_ytm(ytm, settlement, convention="periodic")floatClean price from yield to maturity

Risk Analytics

MethodReturnsDescription
.duration(curves, *, solver=None, metric="modified", settlement=None)floatModified or Macaulay duration
.convexity(curves, *, solver=None, settlement=None)floatPrice convexity
.dv01(curves, *, solver=None)floatDollar value of 1bp
.z_spread(curves, *, solver=None, price=None, settlement=None)floatZ-spread over curve
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None)floatAsset swap spread
.analytic_delta(curves, *, solver=None)floatAnalytic 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 amortization

Advanced 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 curve

PIKBond

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

NameTypeDefaultDescription
effectivedatetime.date or NoneNoneIssue date
terminationdate, str, or NoneNoneMaturity date or tenor string
couponfloat0.0Coupon rate (percentage, e.g., 0.06 for 6%)
pik_fractionfloat1.0Fraction of coupon paid in kind (1.0 = full PIK, 0.0 = all cash)
frequencystr"s"Coupon payment frequency
settlement_daysint1Settlement lag in business days
ex_div_daysint0Ex-dividend days before coupon payment
calendarstr or NoneNoneNamed calendar
currencystr"USD"Currency code
conventionstr"actactisda"Day count convention for coupon accrual
accrued_conventionstr or NoneNoneSeparate day count for accrued interest (defaults to convention)
face_valuefloat100.0Initial face value of the bond
modifierstr"none"Business day adjustment rule
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup

See Conventions for accepted values for frequency, convention, and modifier.

Methods

Standard Methods

MethodReturnsDescription
.rate(curves, *, solver=None)floatPar rate against curves
.npv(curves, *, solver=None)floatNet present value
.cashflows(curves, *, solver=None)DataFramePeriod-level cashflow table
.spread(curves, *, solver=None)floatPar spread

Price and Yield

MethodReturnsDescription
.price(curves, *, solver=None, settlement=None)floatClean price
.dirty_price(curves, *, solver=None, settlement=None)floatDirty price (clean + accrued)
.accrued_interest(settlement)floatAccrued interest at settlement date
.ytm(clean_price, settlement, convention="periodic")floatYield to maturity from clean price
.price_from_ytm(ytm, settlement, convention="periodic")floatClean price from yield to maturity

Risk Analytics

MethodReturnsDescription
.duration(curves, *, solver=None, metric="modified", settlement=None)floatModified or Macaulay duration
.convexity(curves, *, solver=None, settlement=None)floatPrice convexity
.dv01(curves, *, solver=None)floatDollar value of 1bp
.z_spread(curves, *, solver=None, price=None, settlement=None)floatZ-spread over curve
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None)floatAsset swap spread
.analytic_delta(curves, *, solver=None)floatAnalytic delta

PIK-Specific

MethodReturnsDescription
.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% discount

Advanced 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) > 0

CappedFloatRateNote

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

NameTypeDefaultDescription
effectivedatetime.date or NoneNoneIssue date
terminationdate, str, or NoneNoneMaturity date or tenor string
spreadfloat0.0Spread over floating rate (decimal)
frequencystr"q"Coupon payment frequency
fixing_frequencystr or NoneNoneSub-period fixing frequency (e.g., "m" for monthly within quarterly)
cap_ratefloat or NoneNoneCap strike rate (decimal). At least one of cap_rate/floor_rate required.
floor_ratefloat or NoneNoneFloor strike rate (decimal)
volfloat or NoneNoneVolatility for option pricing. Required -- no intrinsic fallback.
vol_modelstr"black76"Pricing model: "black76" or "bachelier"
settlement_daysint2Settlement lag in business days
ex_div_daysint0Ex-dividend days before coupon payment
calendarstr or NoneNoneNamed calendar
currencystr"USD"Currency code
conventionstr"act360"Day count convention
accrued_conventionstr or NoneNoneSeparate day count for accrued interest (defaults to convention)
face_valuefloat100.0Face value
modifierstr"mf"Business day adjustment rule
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup

See Conventions for accepted values for frequency, convention, and modifier.

Methods

Standard Methods

MethodReturnsDescription
.rate(curves, *, solver=None)floatPar rate against curves
.npv(curves, *, solver=None)floatNet present value
.cashflows(curves, *, solver=None)DataFramePeriod-level cashflow table
.spread(curves, *, solver=None)floatPar spread

Price and Yield

MethodReturnsDescription
.price(curves, *, solver=None, settlement=None)floatClean price
.dirty_price(curves, *, solver=None, settlement=None)floatDirty price (clean + accrued)
.accrued_interest(settlement)floatAccrued interest at settlement date
.ytm(clean_price, settlement, convention="periodic")floatYield to maturity from clean price
.price_from_ytm(ytm, settlement, convention="periodic")floatClean price from yield to maturity

Risk Analytics

MethodReturnsDescription
.duration(curves, *, solver=None, metric="modified", settlement=None)floatModified or Macaulay duration
.convexity(curves, *, solver=None, settlement=None)floatPrice convexity
.dv01(curves, *, solver=None)floatDollar value of 1bp
.z_spread(curves, *, solver=None, price=None, settlement=None)floatZ-spread over curve
.asw_spread(curves, *, solver=None, price=None, method="par_par", settlement=None)floatAsset swap spread
.analytic_delta(curves, *, solver=None)floatAnalytic delta

CappedFRN-Specific

MethodReturnsDescription
.sub_period_schedule()DataFrameSub-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 investor

Advanced 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

NameTypeDefaultDescription
bondbond instrumentrequiredUnderlying bond (FixedRateBond, FloatRateNote, StepUpBond, etc.). Positional.
asw_typestr"par"Asset swap type: "par" or "non_par"
dirty_pricefloat or NoneNoneDirty price of the bond (required for "non_par")
spreadfloat or NoneNoneFixed spread override
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup

Methods

MethodReturnsDescription
.rate(curves, *, solver=None)floatAsset swap spread
.npv(curves, *, solver=None)floatNet present value of the package
.cashflows(curves, *, solver=None)DataFramePeriod-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

NameTypeDefaultDescription
bondbond instrumentrequiredUnderlying bond (typically FixedRateBond). Positional.
call_schedulelist[tuple[date, float]] or NoneNoneCall dates and prices as [(date, strike_price), ...]
put_schedulelist[tuple[date, float]] or NoneNonePut dates and prices as [(date, strike_price), ...]
disc_curve_idstr or NoneNoneDiscount curve identifier for Solver lookup

Methods

Standard Methods (delegated to underlying bond)

MethodReturnsDescription
.rate(curves, *, solver=None)floatPar rate against curves
.npv(curves, *, solver=None)floatNet present value
.cashflows(curves, *, solver=None)DataFramePeriod-level cashflow table

Tree-Based Methods (Hull-White)

MethodReturnsDescription
.tree_price(curves, a, sigma, *, spread=0.0)floatOption-adjusted price via trinomial tree
.oas(curves, market_price, a, sigma)floatOption-adjusted spread from market price
.effective_duration(curves, a, sigma, *, oas=0.0, bump=0.0001)floatEffective duration (curve bump)
.effective_convexity(curves, a, sigma, *, oas=0.0, bump=0.0001)floatEffective convexity (curve bump)
.vega(curves, a, sigma, *, oas=0.0, bump=0.0001)floatVega (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

NameTypeDefaultDescription
specstr or NoneNoneInstrument spec name
effectivedatetime.date or NoneNoneProtection start date
terminationdate, str, or NoneNoneProtection end date or tenor string (e.g., "5Y")
frequencystr"q"Premium payment frequency
notionalfloat10_000_000.0Notional amount
recovery_ratefloat0.4Assumed recovery rate (decimal, e.g., 0.4 for 40%)
fixed_ratefloat100.0Fixed premium rate (basis points)
conventionstr"act360"Day count convention
currencystr"USD"Currency code
premium_accruedboolTrueWhether premium accrues to default date
modifierstr"mf"Business day adjustment rule
stubstr"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.

MethodReturnsDescription
.rate(disc_curve, credit_curve)floatPar spread (same as .spread)
.npv(disc_curve, credit_curve)floatNet present value
.spread(disc_curve, credit_curve)floatPar spread
.cashflows(disc_curve, credit_curve)DataFramePeriod-level cashflow table
.accrued(today=None)floatAccrued premium
.analytic_rec_risk(disc_curve, credit_curve)floatRecovery risk
.jtd(today=None)floatJump-to-default exposure
.ead()floatExposure at default
.upfront(disc_curve, credit_curve)floatUpfront payment amount
.to_upfront_spread(disc_curve, credit_curve)floatConvert 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 default


See Also

  • Credit Guides -- Bond analytics, CDS pricing, fitted curves, and spread analysis guides
  • Curves API -- CreditImpliedCurve, FittedBondCurve, SpreadCurve

On this page