Accrued coupon interest and clean/dirty bond prices
Source:R/accrued_interest.R
accrued_interest.RdComputes the coupon interest accrued between the previous coupon date and settlement, and optionally reconciles a quoted clean or dirty bond price. This is a day-count calculation, not a yield-based bond valuation.
Usage
accrued_interest(
.data = NULL,
face = NULL,
c = NULL,
settlement = NULL,
previous_coupon = NULL,
next_coupon = NULL,
k = 1L,
day_count = "act/act",
clean_price = NULL,
dirty_price = NULL,
tol = 1e-10
)Arguments
- .data
Optional data frame containing one bond per row. Required columns are
face,c,settlement,previous_coupon, andnext_coupon. Optional columns arek,day_count,clean_price, anddirty_price. Other columns are preserved. When.datais supplied,face,c, and the date arguments must come from its columns.- face
Positive face value of the bond. Numeric scalar or vector in direct-input mode; otherwise the corresponding column of
.data.- c
Nonnegative annual nominal coupon rate, as a proportion (0.06 for 6 percent). Numeric scalar or vector.
- settlement
Dateof settlement. The accrued-interest convention excludes this date from the elapsed interval.- previous_coupon
Dateof the immediately preceding coupon date.- next_coupon
Dateof the immediately following coupon date. Settlement must satisfyprevious_coupon <= settlement < next_coupon. At a coupon date, supply that date asprevious_couponto obtain zero accrued interest.- k
Number of regular coupon payments per year, a positive integer; defaults to 1. For data-frame input, the
kcolumn, if present, takes precedence over this argument.- day_count
Day-count convention, one of
"act/act","act/360","act/365", or"30e/360". The default is"act/act", defined as actual days divided by actual days in the coupon period, with annual coupon fraction1/k. For data-frame input, aday_countcolumn, if present, takes precedence over this argument."30e/360"denotes the European 30E/360 convention with both day-of-month values capped at 30.- clean_price
Optional quoted clean price, in the same currency units and for the same face value as
face.- dirty_price
Optional dirty price, on the same basis. If either price is provided, the other is determined by the clean/dirty price identity. If both are supplied, their consistency is checked.
- tol
Positive scalar tolerance for reconciling prices when both are supplied; finite and used as a scaled absolute tolerance. The bound is
tol * max(1, abs(clean_price), abs(dirty_price), abs(accrued_interest))for each row. The unit floor is part of this price-reconciliation rule; it is not a yield-solver tolerance.
Value
A tibble. For direct input, it contains the bond parameters,
settlement/coupon dates, the chosen convention, coupon_per_period,
days_elapsed, days_in_period, year_fraction, coupon_fraction,
accrued_interest, clean_price, and dirty_price. For data-frame
input, original columns are retained and the audit columns added.
When neither price is supplied, both price columns contain NA_real_.
Details
Let F be face value, c the nominal annual coupon rate, and k
the coupon frequency. The regular coupon is C = F * c / k.
For "act/act", with d actual elapsed calendar days and D actual
days in the full coupon period, the year fraction and accrued interest are
alpha = d / (k * D) and AI = F * c * alpha = C * d / D.
For "act/360" and "act/365", alpha equals actual elapsed calendar
days divided by 360 or 365 respectively; for "30e/360", it equals the
number of 30E/360 days divided by 360. These conventions accrue at the
annual coupon rate and therefore need not reach exactly one regular coupon
at the end of every actual calendar coupon period.
The price identity is dirty_price = clean_price + accrued_interest.
A price supplied to this function is not computed from a yield curve.
Calculations assume a fixed-rate bond with regular coupon periods, no
stubs, no ex-coupon adjustment, no withholding tax, no amortizing principal,
and no transaction costs. The caller supplies the correct bracketing coupon
dates: a full coupon schedule is not inferred.
All direct-input arguments of length greater than one must have the same
length; length-one values are recycled. Calendar dates must have class
Date, not bare character strings or numeric date serials.
Finite coupon and accrued amounts are recovered from overflowing intermediate
products when possible; nonrepresentable final amounts cause an error.
Examples
accrued_interest(
face = 1000, c = 0.06, k = 2,
previous_coupon = as.Date("2026-01-01"),
settlement = as.Date("2026-04-01"),
next_coupon = as.Date("2026-07-01"),
clean_price = 1005
)
#> # A tibble: 1 × 15
#> face c k settlement previous_coupon next_coupon day_count
#> <dbl> <dbl> <int> <date> <date> <date> <chr>
#> 1 1000 0.06 2 2026-04-01 2026-01-01 2026-07-01 act/act
#> # ℹ 8 more variables: coupon_per_period <dbl>, days_elapsed <dbl>,
#> # days_in_period <dbl>, year_fraction <dbl>, coupon_fraction <dbl>,
#> # accrued_interest <dbl>, clean_price <dbl>, dirty_price <dbl>
# The same position under two market day-count conventions.
accrued_interest(
face = 1000, c = 0.06, k = 2,
previous_coupon = as.Date("2026-01-01"),
settlement = as.Date("2026-04-01"),
next_coupon = as.Date("2026-07-01"),
day_count = "30e/360"
)
#> # A tibble: 1 × 15
#> face c k settlement previous_coupon next_coupon day_count
#> <dbl> <dbl> <int> <date> <date> <date> <chr>
#> 1 1000 0.06 2 2026-04-01 2026-01-01 2026-07-01 30e/360
#> # ℹ 8 more variables: coupon_per_period <dbl>, days_elapsed <dbl>,
#> # days_in_period <dbl>, year_fraction <dbl>, coupon_fraction <dbl>,
#> # accrued_interest <dbl>, clean_price <dbl>, dirty_price <dbl>
# Pipe-friendly portfolio input: each row is one bond position.
bonds <- tibble::tibble(
bond = c("A", "B"), face = c(1000, 500), c = c(0.06, 0.04),
k = c(2L, 2L),
previous_coupon = as.Date(c("2026-01-01", "2026-01-01")),
settlement = as.Date(c("2026-04-01", "2026-04-01")),
next_coupon = as.Date(c("2026-07-01", "2026-07-01"))
)
bonds |> accrued_interest()
#> # A tibble: 2 × 15
#> bond face c k previous_coupon settlement next_coupon
#> <chr> <dbl> <dbl> <int> <date> <date> <date>
#> 1 A 1000 0.06 2 2026-01-01 2026-04-01 2026-07-01
#> 2 B 500 0.04 2 2026-01-01 2026-04-01 2026-07-01
#> # ℹ 8 more variables: coupon_per_period <dbl>, days_elapsed <dbl>,
#> # days_in_period <dbl>, year_fraction <dbl>, coupon_fraction <dbl>,
#> # accrued_interest <dbl>, clean_price <dbl>, dirty_price <dbl>