Module: Xirr::Bonds
- Defined in:
- lib/xirr/bonds.rb
Overview
Fixed income: pricing a bond, solving for its yield, and the standard risk metrics (Macaulay and modified duration, convexity).
A bond pays a coupon of coupon_rate a year, split across freq payments
(semiannual by default), and returns its face value at maturity, years
from now. Rates are quoted per year; ytm is the yield to maturity.
Settlement is assumed to fall on a coupon date, so prices are clean and the
number of coupon periods is whole.
The risk metrics don't depend on the face value (it cancels out), so they
omit it and lead with coupon_rate, unlike Bonds.price and Bonds.ytm.
Class Method Summary collapse
-
.convexity(coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) ⇒ Float
Convexity, in years² — the second-order sensitivity of a bond's price to yield.
-
.duration(coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) ⇒ Float
Macaulay duration — the present-value-weighted average time, in years, until a bond's cash flows are received.
-
.modified_duration(coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) ⇒ Float
Modified duration — Macaulay duration divided by +1 + ytm/freq+.
-
.price(face, coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) ⇒ Float
Price of a bond: the present value of its coupons and face value discounted at the yield
ytm. -
.ytm(face, coupon_rate, price, years, freq = 2, guess: 0.05, precision: Xirr.config.precision) ⇒ Float
Yield to maturity: the annual yield that discounts a bond's coupons and face value back to
price.
Class Method Details
.convexity(coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) ⇒ Float
Convexity, in years² — the second-order sensitivity of a bond's price to yield. Pairs with modified duration to refine a price-change estimate.
61 62 63 64 65 |
# File 'lib/xirr/bonds.rb', line 61 def convexity(coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) with_metric(coupon_rate, years, freq, precision) do |flows| convexity_value(flows, ytm.to_f / freq, freq) end end |
.duration(coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) ⇒ Float
Macaulay duration — the present-value-weighted average time, in years, until a bond's cash flows are received.
43 44 45 46 47 |
# File 'lib/xirr/bonds.rb', line 43 def duration(coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) with_metric(coupon_rate, years, freq, precision) do |flows| macaulay(flows, ytm.to_f / freq, freq) end end |
.modified_duration(coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) ⇒ Float
Modified duration — Macaulay duration divided by +1 + ytm/freq+. Estimates the percentage price change for a small change in yield.
52 53 54 55 56 |
# File 'lib/xirr/bonds.rb', line 52 def modified_duration(coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) with_metric(coupon_rate, years, freq, precision) do |flows| macaulay(flows, ytm.to_f / freq, freq) / (1 + ytm.to_f / freq) end end |
.price(face, coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) ⇒ Float
Price of a bond: the present value of its coupons and face value discounted
at the yield ytm. Prices at par when the coupon rate equals the yield.
21 22 23 24 25 |
# File 'lib/xirr/bonds.rb', line 21 def price(face, coupon_rate, ytm, years, freq = 2, precision: Xirr.config.precision) n = periods(years, freq) value = RtSafe.present_value(bond_flows(face, coupon_rate, n, freq), ytm.to_f / freq) round_value(value, precision) end |
.ytm(face, coupon_rate, price, years, freq = 2, guess: 0.05, precision: Xirr.config.precision) ⇒ Float
31 32 33 34 35 36 37 38 |
# File 'lib/xirr/bonds.rb', line 31 def ytm(face, coupon_rate, price, years, freq = 2, guess: 0.05, precision: Xirr.config.precision) n = periods(years, freq) flows = [[0.0, -price * 1.0]] + bond_flows(face, coupon_rate, n, freq) periodic = RtSafe.find(flows, guess: guess) raise ArgumentError, 'ytm did not converge' if periodic.nil? round_value(periodic * freq, precision) end |