M ChangeLog => ChangeLog +5 -0
@@ 1,3 1,8 @@
+2024-07-28 Enrico Schumann <es@enricoschumann.net>
+
+ * R/unit_prices.R (unit_prices): add arguments
+ 'round.price' and 'round.units'
+
2024-07-24 Enrico Schumann <es@enricoschumann.net>
* R/unit_prices.R (unit_prices): refactor function
M DESCRIPTION => DESCRIPTION +1 -1
@@ 2,7 2,7 @@ Package: PMwR
Type: Package
Title: Portfolio Management with R
Version: 0.19-6
-Date: 2024-07-24
+Date: 2024-07-28
Maintainer: Enrico Schumann <es@enricoschumann.net>
Authors@R: person(given = "Enrico", family = "Schumann",
role = c("aut", "cre"),
M NEWS => NEWS +4 -3
@@ 1,12 1,13 @@
-v0.19-6 (2024-07-24; not released yet)
+v0.19-6 (2024-07-28; not released yet)
o 'unit_prices': rename argument 'initial.shares' to
- 'initial.units', and support its use
+ 'initial.units', and support its use. The function also
+ gains arguments 'round.price' and 'round.units'.
o 'returns' now issues a warning when there are NAs in
series and argument 'na.rm' is not explicitly set. This
warning will be removed when the default of 'na.rm' is
- set to TRUE in a not-too-distant future version.
+ set to FALSE in a not-too-distant future version.
o ....
M R/unit_prices.R => R/unit_prices.R +37 -13
@@ 4,7 4,9 @@
unit_prices <- function(NAV, cashflows,
initial.price,
initial.units = 0,
- cf.included = TRUE) {
+ cf.included = TRUE,
+ round.price = NULL,
+ round.units = NULL) {
if (inherits(NAV, "zoo"))
NAV <- data.frame(timestamp = index(NAV), NAV)
@@ 22,7 24,7 @@ unit_prices <- function(NAV, cashflows,
T <- sort(unique(cashflows[[1L]]))
price <- numeric(length(T))
- shares <- numeric(nrow(cashflows)) + NA
+ units <- numeric(nrow(cashflows)) + NA
S <- initial.units
t.NAV <- match(T, NAV[[1]])
@@ 31,6 33,25 @@ unit_prices <- function(NAV, cashflows,
paste(cashflows[[1]][is.na(t.NAV)], collapse = ", "))
}
+
+
+ ## rounding
+ do.round.price <- !is.null(round.price)
+ if (is.numeric(round.price)) {
+ fun_price <- function(x, amount = NULL, ...)
+ round(x, round.price)
+ } else
+ fun_price <- round.price
+
+ ## rounding
+ do.round.units <- !is.null(round.units)
+ if (is.numeric(round.units)) {
+ fun_units <- function(x, amount = NULL, ...)
+ round(x, round.units)
+ } else
+ fun_units <- round.units
+
+
if (missing(initial.price) && initial.units == 0)
initial.price <- 100
@@ 38,32 59,35 @@ unit_prices <- function(NAV, cashflows,
cf.t <- cashflows[[1L]] == t
scf <- sum(cashflows[[2]][cf.t])
if (t == T[1L] && initial.units == 0)
- ## FIXME if NAV.t[1] != cf.t[1] , should
- ## the initial unit-price be adjusted to
- ## reflect the performance of the NAV?
p <- initial.price
else
p <- (NAV[[2L]][t.NAV[t == T]] - cf.included*scf)/S
+ if (do.round.price)
+ p <- fun_price(p, cashflows[[2]][cf.t])
+
dS <- cashflows[[2]][cf.t]/p
- shares[cf.t] <- dS
+ if (do.round.units)
+ dS <- fun_units(dS, cashflows[[2]][cf.t])
+
+ units[cf.t] <- dS
price[t == T] <- p
S <- S + sum(dS)
}
- total.shares <- numeric(nrow(NAV))
- total.shares[t.NAV] <- tapply(shares, cashflows[[1L]], sum)
- total.shares[1L] <- total.shares[1L] + initial.units
- total.shares <- cumsum(total.shares)
+ total.units <- numeric(nrow(NAV))
+ total.units[t.NAV] <- tapply(units, cashflows[[1L]], sum)
+ total.units[1L] <- total.units[1L] + initial.units
+ total.units <- cumsum(total.units)
res <- NAV
colnames(res) <- c("timestamp", "NAV")
- p <- NAV[[2L]]/total.shares
+ p <- NAV[[2L]]/total.units
p[t.NAV] <- price
res <- cbind(res,
data.frame(price = p,
- units = total.shares,
+ units = total.units,
stringsAsFactors = FALSE))
colnames(cashflows) <- if (ncol(cashflows) == 3L)
@@ 71,6 95,6 @@ unit_prices <- function(NAV, cashflows,
else
c("timestamp", "cashflow")
attr(res, "transactions") <- cbind(cashflows,
- units = shares)
+ units = units)
res
}
M inst/tinytest/test_unit_prices.R => inst/tinytest/test_unit_prices.R +18 -0
@@ 166,3 166,21 @@ res <- unit_prices(NAV,
attr(res, "transactions")
expect_equivalent(res$price, c(20, 20, 202/200*20))
expect_equivalent(res$units, c(9, 10, 10))
+
+## -- rounding
+
+NAV <- data.frame(timestamp = 1,
+ NAV = 150)
+cf <- data.frame(timestamp = 1, amount = 55)
+res <- unit_prices(NAV = NAV, cashflows = cf,
+ initial.units = 100)
+attr(res, "transactions")
+
+res.r <- unit_prices(NAV = NAV, cashflows = cf,
+ initial.units = 100,
+ round.units = 4)
+attr(res.r, "transactions")
+new.units <- 55/(95/100)
+expect_equivalent(res.r$units, round(100+new.units, 4))
+expect_equivalent(attr(res.r, "transactions")$units, round(new.units, 4))
+expect_equivalent(attr(res , "transactions")$units, new.units)
M man/unit_prices.Rd => man/unit_prices.Rd +14 -4
@@ 8,10 8,10 @@
(units).
}
\usage{
-unit_prices(NAV,
- cashflows,
+unit_prices(NAV, cashflows,
initial.price, initial.units = 0,
- cf.included = TRUE)
+ cf.included = TRUE,
+ round.price = NULL, round.units = NULL)
}
\arguments{
\item{NAV}{%
@@ 39,6 39,14 @@ unit_prices(NAV,
default), it is assumed that the NAV series at the time
of the cashflow already includes the cashflow.
}
+ \item{round.price}{%
+ round unit prices: \code{NULL} (no rounding) or an
+ integer
+ }
+ \item{round.units}{%
+ round number of units: \code{NULL} (no rounding) or an
+ integer
+ }
}
\details{%
@@ 47,7 55,9 @@ unit_prices(NAV,
The function may be used to compute the returns for a
portfolio with external cashflows, i.e. what is usually
- called time-weighted returns.
+ called time-weighted returns. Note that 'cashflows' can
+ also comprise other positions that are added or removed
+ from the portfolio without affecting performance.
}
\value{%