Finance.Bonds (finance v1.5.0)

Copy Markdown View Source

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

These functions assume settlement falls on a coupon date, so the price is "clean" (no accrued interest) and the number of coupon periods is whole. Arbitrary settlement dates and day-count conventions are a later feature.

The risk metrics — duration/4, modified_duration/4, convexity/4 — don't depend on the face value (it cancels out), so they leave it off and lead with coupon_rate, unlike price/5 and ytm/5 which lead with face.

Summary

Functions

Convexity, in years² — the second-order sensitivity of a bond's price to yield. Pairs with modified duration to refine the price-change estimate.

Same as convexity/4, but returns the value directly and raises ArgumentError on error.

Macaulay duration — the present-value-weighted average time, in years, until a bond's cash flows are received. A plain measure of interest-rate sensitivity.

Same as duration/4, but returns the value directly and raises ArgumentError on error.

Modified duration — Macaulay duration divided by 1 + ytm/freq. It estimates the percentage price change for a small change in yield.

Prices a bond: the present value of its coupons and face value, discounted at the yield ytm.

Same as price/5, but returns the value directly and raises ArgumentError on error.

Solves for a bond's yield to maturity — the annual yield that discounts its coupons and face value back to price. The inverse of price/5.

Same as ytm/5, but returns the yield directly and raises ArgumentError on error.

Types

error()

@type error() :: Finance.error()

option()

@type option() :: Finance.option()

Functions

convexity(coupon_rate, ytm, years, freq \\ 2, opts \\ [])

@spec convexity(number(), number(), number(), pos_integer(), [option()]) ::
  {:ok, float()} | {:error, error()}

Convexity, in years² — the second-order sensitivity of a bond's price to yield. Pairs with modified duration to refine the price-change estimate.

iex> Finance.Bonds.convexity(0.0, 0.05, 10, 1)
{:ok, 99.773243}

iex> Finance.Bonds.convexity(0.06, 0.06, 3, 1)
{:ok, 9.891032}

convexity!(coupon_rate, ytm, years, freq \\ 2, opts \\ [])

@spec convexity!(number(), number(), number(), pos_integer(), [option()]) :: float()

Same as convexity/4, but returns the value directly and raises ArgumentError on error.

duration(coupon_rate, ytm, years, freq \\ 2, opts \\ [])

@spec duration(number(), number(), number(), pos_integer(), [option()]) ::
  {:ok, float()} | {:error, error()}

Macaulay duration — the present-value-weighted average time, in years, until a bond's cash flows are received. A plain measure of interest-rate sensitivity.

The face value cancels out, so it isn't a parameter (see the module note).

iex> Finance.Bonds.duration(0.0, 0.05, 10, 1)
{:ok, 10.0}

iex> Finance.Bonds.duration(0.06, 0.06, 3, 1)
{:ok, 2.833393}

duration!(coupon_rate, ytm, years, freq \\ 2, opts \\ [])

@spec duration!(number(), number(), number(), pos_integer(), [option()]) :: float()

Same as duration/4, but returns the value directly and raises ArgumentError on error.

modified_duration(coupon_rate, ytm, years, freq \\ 2, opts \\ [])

@spec modified_duration(number(), number(), number(), pos_integer(), [option()]) ::
  {:ok, float()} | {:error, error()}

Modified duration — Macaulay duration divided by 1 + ytm/freq. It estimates the percentage price change for a small change in yield.

iex> Finance.Bonds.modified_duration(0.0, 0.05, 10, 1)
{:ok, 9.52381}

iex> Finance.Bonds.modified_duration(0.06, 0.06, 3, 1)
{:ok, 2.673012}

modified_duration!(coupon_rate, ytm, years, freq \\ 2, opts \\ [])

@spec modified_duration!(number(), number(), number(), pos_integer(), [option()]) ::
  float()

Same as modified_duration/4, but returns the value directly and raises ArgumentError on error.

price(face, coupon_rate, ytm, years, freq \\ 2, opts \\ [])

@spec price(number(), number(), number(), number(), pos_integer(), [option()]) ::
  {:ok, float()} | {:error, error()}

Prices a bond: the present value of its coupons and face value, discounted at the yield ytm.

When the coupon rate equals the yield, the bond prices at par.

iex> Finance.Bonds.price(100, 0.05, 0.05, 10)
{:ok, 100.0}

iex> Finance.Bonds.price(1000, 0.08, 0.10, 10)
{:ok, 875.377897}

price!(face, coupon_rate, ytm, years, freq \\ 2, opts \\ [])

@spec price!(number(), number(), number(), number(), pos_integer(), [option()]) ::
  float()

Same as price/5, but returns the value directly and raises ArgumentError on error.

ytm(face, coupon_rate, price, years, freq \\ 2, opts \\ [])

@spec ytm(number(), number(), number(), number(), pos_integer(), [option()]) ::
  {:ok, float()} | {:error, error()}

Solves for a bond's yield to maturity — the annual yield that discounts its coupons and face value back to price. The inverse of price/5.

There is no closed form, so this reuses the same solver as Finance.CashFlow.irr/1 and takes the same options; it returns {:error, :did_not_converge} if no yield can be found.

iex> Finance.Bonds.ytm(1000, 0.08, 875.377897, 10)
{:ok, 0.1}

ytm!(face, coupon_rate, price, years, freq \\ 2, opts \\ [])

@spec ytm!(number(), number(), number(), number(), pos_integer(), [option()]) ::
  float()

Same as ytm/5, but returns the yield directly and raises ArgumentError on error.