Documentation
v1.0.0 · Fixed Income · Java 21+ · Apache 2.0
Bonds
Bond pricing, yield inversion, duration, and curve construction — all built around the immutable Bond record. Every operation is expressed as an interface so implementations can be swapped without changing calling code.
Immutable representation of a fixed-coupon bond. Being a Java record, equality is field-based — two Bond instances with identical parameters are equal and can be used as map keys.
| Field | Type | Description |
|---|---|---|
| faceValue | double | Principal amount (e.g. 1000). Must be > 0. |
| annualRate | double | Annual coupon rate, decimal (e.g. 0.06 for 6%). Must be ≥ 0. |
| maturityYears | double | Time to maturity in years. Must be > 0 and coherent with couponFrequency. |
| couponFrequency | Frequency | Payment frequency: ANNUALLY, SEMI_ANNUALLY, QUARTERLY, MONTHLY, DAILY. |
faceValue × annualRate / periodsPerYear.
// 6% annual coupon rate, semi-annual payments, 5-year maturity
Bond bond = new Bond(1000.0, 0.06, 5.0, Frequency.SEMI_ANNUALLY);
bond.getCouponPayment(); // → 30.0 (1000 × 0.06 / 2)
bond.getCashflows(); // → {0.5=30.0, 1.0=30.0, ..., 5.0=1030.0}
Prices a bond by discounting all cash flows at a single Yield to Maturity. The compounding convention for the yield is supplied explicitly and may differ from the bond's coupon frequency.
$CF(t)$ — cash flow at time $t$. $DF(y, t)$ — discount factor from the compounding strategy at yield $y$.
// 6% annual coupon rate, semi-annual payments, 5-year maturity
Bond bond = new Bond(1000.0, 0.06, 5.0, Frequency.SEMI_ANNUALLY);
CompoundingStrategy cs = new DiscreteCompoundingStrategy(2); // semi-annual
BondPricer pricer = new YieldBondPricer(0.07, cs);
double price = pricer.price(bond); // → 958.42
Prices a bond by discounting each cash flow at its own zero (spot) rate, interpolated from a provided curve.
$r(t)$ — zero rate at tenor $t$, obtained by interpolating the supplied curve. Each cash flow is discounted at its own tenor rate.
// 6% annual coupon rate, semi-annual payments, 5-year maturity
Bond bond = new Bond(1000.0, 0.06, 5.0, Frequency.SEMI_ANNUALLY);
Map<Double, Double> curve = Map.of(
0.5, 0.050, 1.0, 0.055,
2.0, 0.060, 5.0, 0.065);
BondPricer pricer = new ZeroCouponBondRateBondPricer(
curve,
new LinearInterpolationStrategy(),
new ContinuousCompoundingStrategy());
double price = pricer.price(bond); // → 976.57
Inverts the pricing formula numerically to find the YTM that makes the theoretical price equal to the observed market price. The solver algorithm is injected as a strategy — use Bisection, Newton-Raphson, or Secant interchangeably.
Cash flows are captured once before the solver loop to avoid recomputation across iterations.
// 6% annual coupon rate, semi-annual payments, 5-year maturity
Bond bond = new Bond(1000.0, 0.06, 5.0, Frequency.SEMI_ANNUALLY);
BondYieldCalculator calc = new RootFindingBondYieldCalculator(
new DiscreteCompoundingStrategy(2),
new NewtonRaphsonSolver());
double ytm = calc.yieldToMaturity(bond, 958.42);
// → 0.0700 (7.00%)
// Any RootSolver works — swap without changing the rest of the code
BondYieldCalculator calc2 = new RootFindingBondYieldCalculator(
new DiscreteCompoundingStrategy(2),
new BisectionSolver());
Computes Macaulay duration, modified duration, and DV01 from a yield and compounding convention. All three measures share the same weighted cash flow schedule, ensuring internal consistency.
Time-weighted average of discounted cash flows, in years.
Percentage price change per unit parallel shift in yield. The conversion is delegated to the compounding strategy, so the formula is automatically correct for both conventions.
Absolute price change for a 1 basis point (0.01%) increase in yield. Same currency unit as the bond price.
// 6% annual coupon rate, semi-annual payments, 5-year maturity
Bond bond = new Bond(1000.0, 0.06, 5.0, Frequency.SEMI_ANNUALLY);
CompoundingStrategy cs = new DiscreteCompoundingStrategy(2);
BondDurationCalculator calc = new YieldBondDurationCalculator(0.07, cs);
double price = 958.42;
double dmac = calc.macaulayDuration(bond, price); // → 4.38 years
double dmod = calc.modifiedDuration(bond, price); // → 4.23
double dv01 = calc.dv01(bond, price); // → 0.0405
Bootstraps a zero-coupon (spot rate) curve from coupon bonds sorted in ascending maturity order. At each step, all intermediate cash flows are discounted at already-derived spot rates, and the unknown rate at the bond's maturity is solved analytically.
Applied sequentially for each bond in ascending maturity order. The initial zero curve provides seed rates for the shortest tenors. The result is an unmodifiable NavigableMap.
BootstrappingStrategy boot = new SpotRateCurveBootstrappingStrategy(
Map.of(0.5, 0.04), // seed: 6-month spot rate
new ContinuousCompoundingStrategy());
NavigableMap<Double, Double> curve = boot.bootstrapFromParBonds(
List.of(
new Bond(1000, 0.04, 1.0, Frequency.SEMI_ANNUALLY),
new Bond(1000, 0.05, 2.0, Frequency.SEMI_ANNUALLY),
new Bond(1000, 0.055, 3.0, Frequency.SEMI_ANNUALLY)),
new LinearInterpolationStrategy());
// → {0.5=0.04, 1.0=0.0396, 2.0=0.0497, 3.0=0.0548}
// Seed curve: known short-end spot rates
TreeMap<Double, Double> zeroCurve = new TreeMap<>(
Map.of(0.5, 0.1238, 1.0, 0.1165));
Bond bond1 = new Bond(100, 0.08, 1.5, Frequency.SEMI_ANNUALLY);
Bond bond2 = new Bond(100, 0.10, 2.0, Frequency.SEMI_ANNUALLY);
Map<Bond, Double> marketPrices = Map.of(bond1, 94.84, bond2, 97.12);
BootstrappingStrategy boot = new SpotRateCurveBootstrappingStrategy(
zeroCurve, new ContinuousCompoundingStrategy());
Map<Double, Double> curve = boot.bootstrapFromMarketPrices(
List.of(bond1, bond2), marketPrices, new LinearInterpolationStrategy());
// → {0.5=0.1238, 1.0=0.1165, 1.5=0.1150, 2.0=0.1130}
Rates
Compounding strategies, compound interest, and rate conversion utilities. All compounding strategies share a common CompoundingStrategy interface and are interchangeable across the library.
Implements continuous compounding. Used throughout the library wherever a compounding convention is required. Under continuous compounding, modified duration equals Macaulay duration (no adjustment needed).
CompoundingStrategy cs = new ContinuousCompoundingStrategy();
cs.discountFactor(0.05, 2.0); // → e^(-0.10) = 0.9048
cs.accumulationFactor(0.05, 2.0); // → e^( 0.10) = 1.1052
cs.futureValue(1000, 0.05, 2.0); // → 1105.17
cs.rateFromDiscountFactor(0.9048, 2.0); // → 0.0500
cs.forwardRate(0.04, 1.0, 0.05, 2.0); // → 0.06
Implements discrete compounding with a configurable number of periods per year $m$. Common conventions: semi-annual ($m=2$) for bonds, annual ($m=1$) for general finance, monthly ($m=12$) for consumer products.
CompoundingStrategy semi = new DiscreteCompoundingStrategy(2);
semi.discountFactor(0.06, 5.0); // → (1.03)^(-10) = 0.7441
semi.futureValue(1000, 0.06, 5.0); // → 1343.92
semi.rateFromDiscountFactor(0.7441, 5.0); // → 0.0600
semi.forwardRate(0.04, 1.0, 0.05, 2.0); // → 0.06
Calculates the period-by-period growth of an investment under compound interest. Supports an initial principal, regular periodic contributions (ordinary annuity), and independent compounding and contribution frequencies.
$m$ — compounding periods per year. $r$ — annual rate. $C_n$ — contribution aggregated for period $n$ (added after interest, ordinary annuity convention). When contribution and compounding frequencies differ, $C_n$ is scaled proportionally.
| Field | Type | Description |
|---|---|---|
| deposits | List<Double> | Contribution added each period (index 0 = initial investment). |
| interests | List<Double> | Interest earned each compounding period. |
| totalDeposit | List<Double> | Cumulative principal deposited through each period. |
| accuredInterest | List<Double> | Cumulative interest earned through each period. |
| balance | List<Double> | Total account balance at the end of each period. |
CompoundInterestCalculator calc = new CompoundInterestCalculator();
CompoundInterestResult res = calc.calculate(
10_000, // initial investment
200, // $200 monthly contribution
Frequency.MONTHLY, // contribution frequency
5.0, // 5-year horizon
0.06, // 6% annual rate
Frequency.MONTHLY); // monthly compounding
double finalBalance = res.balance().getLast(); // → ~27,442
int periods = res.balance().size(); // → 61 (period 0 + 60 monthly periods)
Static utility for converting interest rates between compounding conventions. All conversions are derived from the principle of equated accumulation factors — two rates are equivalent if they produce the same future value over the same period.
// Semi-annual 5% → equivalent continuous rate
double rc = RateConverter.discreteToContinuous(0.05, Frequency.SEMI_ANNUALLY);
// → 0.04938 (4.938%)
// Continuous → quarterly discrete
double rq = RateConverter.continuousToDiscrete(rc, Frequency.QUARTERLY);
// → 0.04969 (4.969%)
// Semi-annual 5% → monthly discrete
double rm = RateConverter.convertDiscreteRates(
0.05, Frequency.SEMI_ANNUALLY, Frequency.MONTHLY);
// → 0.04949 (4.949%)
Numerical Solvers
Three root-finding strategies implement the RootSolver interface. All are injected into RootFindingBondYieldCalculator and are interchangeable without changing calling code.
| Solver | Convergence | Requires | Best for |
|---|---|---|---|
| BisectionSolver | Linear — halves interval each step | Sign change in [a, b] | Guaranteed convergence, robust fallback |
| NewtonRaphsonSolver | Quadratic near root | Single initial guess | Fast convergence when guess is close |
| SecantSolver | Superlinear (~1.618×) | Two distinct initial guesses | No bracket needed, faster than bisection |
Guaranteed convergence by halving a bracketing interval at each step. Requires a sign change in the interval: f(a) × f(b) < 0. Slower than Newton-Raphson but will always converge if a root exists in the bracket.
Quadratic convergence near the root. The derivative is approximated numerically using a symmetric finite difference with step $h = 10^{-6}$. Fastest of the three for well-conditioned problems; may diverge far from the root.
Superlinear convergence (~1.618×). Approximates the derivative using the slope of the chord through the two most recent iterates. No bracketing interval required — needs two distinct initial guesses.
Interpolation
The InterpolationStrategy interface is used by ZeroCouponBondRateBondPricer and SpotRateCurveBootstrappingStrategy to look up rates at arbitrary maturities between known curve points.
Linearly interpolates between the two nearest known data points. Falls back to flat extrapolation (nearest edge value) when the requested point lies outside the data range.
Outside range: $y = y_{\min}$ if $x < x_{\min}$ (left flat extrapolation), $y = y_{\max}$ if $x > x_{\max}$ (right flat extrapolation).
NavigableMap<Double, Double> curve = new TreeMap<>(Map.of(
1.0, 0.04,
2.0, 0.05,
5.0, 0.06));
InterpolationStrategy interp = new LinearInterpolationStrategy();
interp.interpolate(curve, 1.5); // → 0.045 (midpoint between 1yr and 2yr)
interp.interpolate(curve, 3.5); // → 0.055 (midpoint between 2yr and 5yr)
interp.interpolate(curve, 8.0); // → 0.060 (flat extrapolation past 5yr)
interp.interpolate(curve, 0.5); // → 0.040 (flat extrapolation before 1yr)