Title: | Portfolio Management with R |
---|---|
Description: | Tools for the practical management of financial portfolios: backtesting investment and trading strategies, computing profit/loss and returns, analysing trades, handling lists of transactions, reporting, and more. The package provides a small set of reliable, efficient and convenient tools for processing and analysing trade/portfolio data. The manual provides all the details; it is available from <https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html>. Examples and descriptions of new features are provided at <https://enricoschumann.net/notes/PMwR/>. |
Authors: | Enrico Schumann [aut, cre] |
Maintainer: | Enrico Schumann <[email protected]> |
License: | GPL-3 |
Version: | 1.0-1 |
Built: | 2024-12-25 05:27:58 UTC |
Source: | https://gitlab.com/enricoschumann/pmwr |
Tools for the practical management of financial portfolios: backtesting investment and trading strategies, computing profit-and-loss and returns, analysing trades, reporting, and more.
PMwR provides a small set of reliable, efficient and convenient tools for processing and analysing trade/portfolio data. The Manual provides all the details; it is available from https://enricoschumann.net/PMwR/. Examples and descriptions of new features are provided at https://enricoschumann.net/notes/PMwR/.
Enrico Schumann <[email protected]>
The PMwR Manual, which explains all functionality:
Schumann, E. (2023) Portfolio Management with R.
https://enricoschumann.net/PMwR/
The closely-related NMOF package is described in:
Gilli, M., Maringer, D. and Schumann, E. (2019) Numerical Methods
and Optimization in Finance. 2nd edition. Elsevier.
doi:10.1016/C2017-0-01621-X
Schumann, E. (2023) Financial Optimisation with R (NMOF Manual). https://enricoschumann.net/NMOF.htm#NMOFmanual
Adjust a time series for dividends and splits.
div_adjust(x, t, div, backward = TRUE, additive = FALSE) split_adjust(x, t, ratio, backward = TRUE)
div_adjust(x, t, div, backward = TRUE, additive = FALSE) split_adjust(x, t, ratio, backward = TRUE)
x |
a numeric vector: the series to be adjusted |
t |
An integer vector, specifying the positions in
|
div |
A numeric vector, specifying the dividends (or
payments, cashflows). If necessary, recycled to
the length of |
ratio |
a numeric vector, specifying the split ratios. The ratio must be ‘American Style’: a 2-for-1 stock split, for example, corresponds to a ratio of 2. (In other countries, for instance Germany, a 2-for-1 stock split would be called a 1-for-1 split: you keep your shares and receive one new share per share that you own.) |
backward |
logical; see Details |
additive |
logical; see Details |
The function transforms into returns, and
with those returns specified in
calculated
as
in which is the price,
are dividends
and
is time. The adjusted
is
then reconstructed from those returns.
When additive
is TRUE
, dividends are
simply added back to the series; see Examples.
With backward
set to TRUE
, which is the
default, the final prices in the unadjusted series matches
the final prices in the adjusted series.
a numeric vector of length equal to length(x)
Enrico Schumann
Schumann, E. (2023) Portfolio Management with R. https://enricoschumann.net/PMwR/
Using div_adjust
for handling generic external cashflows:
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#returns-with-external-cashflows
x <- c(9.777, 10.04, 9.207, 9.406) div <- 0.7 t <- 3 div_adjust(x, t, div) div_adjust(x, t, div, FALSE) ## assume there were three splits: adjust shares outstanding shares <- c(100, 100, 200, 200, 1000, 1500) t <- c(3, 5, 6) ratio <- c(2, 5, 1.5) ### => invert ratio split_adjust(shares, t, 1/ratio) ## [1] 1500 1500 1500 1500 1500 1500 split_adjust(shares, t, 1/ratio, backward = FALSE) ## [1] 100 100 100 100 100 100 ## 'additive' ** FALSE ** (default setting) x <- c(100, 95, 100, 95, 100) div <- 5 t <- c(2, 4) div_adjust(x, t, div) ## 90.25 90.25 95.00 95.00 100.00 returns(div_adjust(x, t, div)) ## 0.00000000 0.05263158 0.00000000 0.05263158 ## ==> reflect _actual_ returns 100/95 - 1 ## 'additive' ** TRUE ** div_adjust(x, t, div, additive = TRUE) ## 90 90 95 95 100 returns(div_adjust(x, t, div, additive = TRUE)) ## 0.00000000 0.05555556 0.00000000 0.05263158 ## ==> reflect return 95/90 - 1
x <- c(9.777, 10.04, 9.207, 9.406) div <- 0.7 t <- 3 div_adjust(x, t, div) div_adjust(x, t, div, FALSE) ## assume there were three splits: adjust shares outstanding shares <- c(100, 100, 200, 200, 1000, 1500) t <- c(3, 5, 6) ratio <- c(2, 5, 1.5) ### => invert ratio split_adjust(shares, t, 1/ratio) ## [1] 1500 1500 1500 1500 1500 1500 split_adjust(shares, t, 1/ratio, backward = FALSE) ## [1] 100 100 100 100 100 100 ## 'additive' ** FALSE ** (default setting) x <- c(100, 95, 100, 95, 100) div <- 5 t <- c(2, 4) div_adjust(x, t, div) ## 90.25 90.25 95.00 95.00 100.00 returns(div_adjust(x, t, div)) ## 0.00000000 0.05263158 0.00000000 0.05263158 ## ==> reflect _actual_ returns 100/95 - 1 ## 'additive' ** TRUE ** div_adjust(x, t, div, additive = TRUE) ## 90 90 95 95 100 returns(div_adjust(x, t, div, additive = TRUE)) ## 0.00000000 0.05555556 0.00000000 0.05263158 ## ==> reflect return 95/90 - 1
Testing trading and investment strategies.
btest(prices, signal, do.signal = TRUE, do.rebalance = TRUE, print.info = NULL, b = 1, fraction = 1, initial.position = 0, initial.cash = 0, final.position = FALSE, cashflow = NULL, tc = 0, ..., add = FALSE, lag = 1, convert.weights = FALSE, trade.at.open = TRUE, tol = 1e-5, tol.p = NA, Globals = list(), prices0 = NULL, include.data = FALSE, include.timestamp = TRUE, timestamp, instrument, progressBar = FALSE, variations, variations.settings, replications)
btest(prices, signal, do.signal = TRUE, do.rebalance = TRUE, print.info = NULL, b = 1, fraction = 1, initial.position = 0, initial.cash = 0, final.position = FALSE, cashflow = NULL, tc = 0, ..., add = FALSE, lag = 1, convert.weights = FALSE, trade.at.open = TRUE, tol = 1e-5, tol.p = NA, Globals = list(), prices0 = NULL, include.data = FALSE, include.timestamp = TRUE, timestamp, instrument, progressBar = FALSE, variations, variations.settings, replications)
prices |
For a single asset, a matrix of prices with four
columns: open, high, low and close. For The series in Prices must be ordered by time (though the timestamps need not be provided). |
signal |
A function that evaluates to the position in units
of the instruments suggested by the trading
rule. If |
do.signal |
Logical or numeric vector, a function that
evaluates to When a logical vector, its length must match the
number of observations in prices:
If |
do.rebalance |
Same as |
print.info |
A function, called at the very end of each period,
i.e. after rebalancing. Can also be |
cashflow |
A function or |
b |
burn-in (an integer). Defaults to 1. This may also
be a length-one timestamp of the same class as
|
fraction |
amount of rebalancing to be done: a scalar between 0 and 1 |
initial.position |
a numeric vector: initial portfolio in units of instruments. If supplied, this will also be the initial suggested position. |
initial.cash |
a numeric vector of length 1. Defaults to 0. |
final.position |
logical |
tc |
transaction costs as a fraction of turnover (e.g.,
0.001 means 0.1%). May also be a function that
evaluates to such a fraction. More-complex
computations may be specified with
argument |
... |
other named arguments. All functions (signal, do.signal, do.rebalance, print.info, cashflow) will have access to these arguments. See Details for reserved argument names. |
add |
Default is |
lag |
default is 1 |
convert.weights |
Default is |
trade.at.open |
A logical vector of length one; default is |
tol |
A numeric vector of length one: only rebalance if
the maximum absolute suggested change for at least
one position is greater than |
tol.p |
A numeric vector of length one: only rebalance
those positions for which the relative suggested
change is greater than |
Globals |
A |
prices0 |
A numeric vector (default is |
include.data |
logical. If |
include.timestamp |
logical. If |
timestamp |
a vector of timestamps, along prices (optional; mainly used for print method and journal) |
instrument |
character vector of instrument names (optional; mainly used for print method and journal) |
progressBar |
logical: display |
variations |
a list. See Details. |
variations.settings |
a list. See Details. |
replications |
an integer. If set, the function returns a list of
|
The function provides infrastructure for testing
trading rules. Essentially, btest
does
accounting: keep track of transactions and positions,
value open positions, etc. The ingredients are price
time-series (single series or OHLC
bars), which need not be equally spaced; and several
functions that map these series and other pieces of
information into positions.
btest
worksbtest
runs a loop from b + 1
to
NROW(prices)
. In iteration t
, a
signal
can be computed based on information
from periods prior to t
. Trading then takes
place at the opening price of t
.
t time open high low close 1 HH:MM:SS <--\ 2 HH:MM:SS <-- - use information 3 HH:MM:SS _________________________ <--/ 4 HH:MM:SS X <- trade here 5 HH:MM:SS
For slow-to-compute signals this is reasonable if
there is a time lag between close and open. For
daily prices, for instance, signals could be
computed overnight. For higher frequencies, such as
every minute, the signal function should be fast to
compute. Alternatively, it may be better to use a
larger time offset (i.e. use a longer time lag) and
to trade at the close of t
by setting
argument trade.at.open
to FALSE
.
t time open high low close 1 HH:MM:SS <-- \ 2 HH:MM:SS <-- - use information 3 HH:MM:SS _________________________ <-- / 4 HH:MM:SS X <-- trade here 5 HH:MM:SS
If no OHLC bars are available, a single
series per asset (assumed to be close prices) can
be used. trade.at.open
will automaticall be
set to FALSE
.
The trade logic needs to be coded in the function
signal
. Arguments to that function must be
named and need to be passed with ...
.
Certain names are reserved and cannot be used as
arguments: Open
, High
, Low
,
Close
, Wealth
, Cash
,
Time
, Timestamp
, Portfolio
,
SuggestedPortfolio
, Globals
. Further
reserved names may be added in the future:
it is suggested to not start an argument
name with a capital letter.
The function signal
must evaluate to the
target position in units of the instruments. To
work with weights, set convert.weights
to
TRUE
, and btest
will translate the
weights into positions, based on the value of the
portfolio at t - 1
.
Within signal
(and also other function
arguments, such as do.signal
), you can
access data via special functions such as
Close
. These are automatically added as
arguments to signal
. Currently, the
following functions are available: Open
,
High
, Low
, Close
,
Wealth
, Cash
, Time
,
Timestamp
, Portfolio
,
SuggestedPortfolio
, Globals
.
Globals
is special: it is an
environment
, which can be used to
persistently store data during the run of
btest
. Use the argument Globals
to
add initial objects. See the Examples below and the
manual.
Additional functions may be added to btest
in the future. The names of those functions will
always be in title case. Hence, it is recommended
to not use argument names for signal
,
etc. that start with a capital letter.
btest
allows to run backtests in
parallel. See the examples at
https://enricoschumann.net/notes/parallel-backtests.html.
The argument variations.settings
is a list with the
following defaults:
method
character: supported are
"loop"
, "parallel"
(or "snow"
)
and "multicore"
load.balancing
logical
cores
numeric
A list with class attribute btest
. The list comprises:
position |
actual portfolio holdings |
suggested.position |
suggested holdings (aka target position) |
cash |
cash |
wealth |
time-series of total portfolio value (aka equity curve) |
cum.tc |
transaction costs |
journal |
|
initial.wealth |
initial wealth |
b |
burn-in |
final.position |
final position if |
Globals |
environment |
When include.timestamp
is TRUE
, the
timestamp is included. If no timestamp
was
specified, integers 1, 2, ...
are used
instead.
When include.data
is TRUE
, essentially
all information (prices, instrument, the
actual call
and functions signal
etc.)
are stored in the list as well.
Enrico Schumann [email protected]
Schumann, E. (2023) Portfolio Management with R.
https://enricoschumann.net/PMwR/;
in particular, see the chapter on backtesting:
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#backtesting
Schumann, E. (2018) Backtesting.
doi:10.2139/ssrn.3374195
## For more examples, please see the Manual ## https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html ## 1 - a simple rule timestamp <- structure(c(16679L, 16680L, 16681L, 16682L, 16685L, 16686L, 16687L, 16688L, 16689L, 16692L, 16693L), class = "Date") prices <- c(3182, 3205, 3272, 3185, 3201, 3236, 3272, 3224, 3194, 3188, 3213) data.frame(timestamp, prices) signal <- function() ## buy when last price is if (Close() < 3200) ## below 3200, else sell 1 else 0 ## (more precisely: build position of 1 ## when price < 3200, else reduce ## position to 0) solution <- btest(prices = prices, signal = signal) journal(solution) ## with Date timestamps solution <- btest(prices = prices, signal = signal, timestamp = timestamp) journal(solution) ## 2 - a simple MA model ## Not run: library("PMwR") library("NMOF") dax <- DAX[[1]] n <- 5 ma <- MA(dax, n, pad = NA) ma_strat <- function(ma) { if (Close() > ma[Time()]) 1 else 0 } plot(as.Date(row.names(DAX)), dax, type = "l", xlab = "", ylab = "DAX") lines(as.Date(row.names(DAX)), ma, type = "l") res <- btest(prices = dax, signal = ma_strat, b = n, ma = ma) par(mfrow = c(3, 1)) plot(as.Date(row.names(DAX)), dax, type = "l", xlab = "", ylab = "DAX") plot(as.Date(row.names(DAX)), res$wealth, type = "l", xlab = "", ylab = "Equity") plot(as.Date(row.names(DAX)), position(res), type = "s", xlab = "", ylab = "Position") ## End(Not run)
## For more examples, please see the Manual ## https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html ## 1 - a simple rule timestamp <- structure(c(16679L, 16680L, 16681L, 16682L, 16685L, 16686L, 16687L, 16688L, 16689L, 16692L, 16693L), class = "Date") prices <- c(3182, 3205, 3272, 3185, 3201, 3236, 3272, 3224, 3194, 3188, 3213) data.frame(timestamp, prices) signal <- function() ## buy when last price is if (Close() < 3200) ## below 3200, else sell 1 else 0 ## (more precisely: build position of 1 ## when price < 3200, else reduce ## position to 0) solution <- btest(prices = prices, signal = signal) journal(solution) ## with Date timestamps solution <- btest(prices = prices, signal = signal, timestamp = timestamp) journal(solution) ## 2 - a simple MA model ## Not run: library("PMwR") library("NMOF") dax <- DAX[[1]] n <- 5 ma <- MA(dax, n, pad = NA) ma_strat <- function(ma) { if (Close() > ma[Time()]) 1 else 0 } plot(as.Date(row.names(DAX)), dax, type = "l", xlab = "", ylab = "DAX") lines(as.Date(row.names(DAX)), ma, type = "l") res <- btest(prices = dax, signal = ma_strat, b = n, ma = ma) par(mfrow = c(3, 1)) plot(as.Date(row.names(DAX)), dax, type = "l", xlab = "", ylab = "DAX") plot(as.Date(row.names(DAX)), res$wealth, type = "l", xlab = "", ylab = "Equity") plot(as.Date(row.names(DAX)), position(res), type = "s", xlab = "", ylab = "Position") ## End(Not run)
Historical Prices of the DAX.
data("DAX")
data("DAX")
A data frame with 505 observations on the following variable:
DAX
a numeric vector
The DAX (Deutscher Aktienindex) is a stock-price index of the largest companies listed in Germany. The dataset comprises the close prices of the index for the years 2014 and 2015; dates are provided as rownames.
str(DAX) summary(DAX)
str(DAX) summary(DAX)
Compute drawdown statistics.
drawdowns(x, ...) ## Default S3 method: drawdowns(x, ...) ## S3 method for class 'zoo' drawdowns(x, ...)
drawdowns(x, ...) ## Default S3 method: drawdowns(x, ...) ## S3 method for class 'zoo' drawdowns(x, ...)
x |
a |
... |
additional arguments, to be passed to methods |
drawdowns
is a generic function. It computes drawdown
statistics: maximum; and time of peak, trough and recovery.
a data.frame
:
peak |
peak before drawdown |
trough |
lowest point |
recover |
new high or |
max |
the max drawdown |
Enrico Schumann
Gilli, M., Maringer, D. and Schumann, E. (2019) Numerical Methods and Optimization in Finance. 2nd edition. Elsevier. doi:10.1016/C2017-0-01621-X
Schumann, E. (2023) Portfolio Management with R.
https://enricoschumann.net/PMwR/;
in particular,
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#drawdowns-streaks
The actual computation of the drawdowns is done by function
drawdown
in package NMOF.
Series of uninterrupted up and down movements can be
computed with streaks
.
x <- c(100, 98) drawdowns(x) x <- c(100, 98, 102, 99) dd <- drawdowns(x) dd[order(dd$max, decreasing = TRUE), ]
x <- c(100, 98) drawdowns(x) x <- c(100, 98, 102, 99) dd <- drawdowns(x) dd[order(dd$max, decreasing = TRUE), ]
Generic function for retrieving and changing instrument information.
instrument(x, ...) instrument(x, ...) <- value
instrument(x, ...) instrument(x, ...) <- value
x |
an object |
... |
arguments passed to methods |
value |
an object |
Generic function: extract or, if meaningful, replace instrument information
when used to extract instrument, a character vector
Enrico Schumann
Schumann, E. (2023) Portfolio Management with R. https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html
jnl <- journal(instrument = "A", amount = 100, price = 1) instrument(jnl) instrument(jnl) <- "B"
jnl <- journal(instrument = "A", amount = 100, price = 1) instrument(jnl) instrument(jnl) <- "B"
Check whether a given ISIN or SEDOL is valid.
is_valid_ISIN(isin, NA.FALSE = FALSE) is_valid_SEDOL(SEDOL, NA.FALSE = FALSE)
is_valid_ISIN(isin, NA.FALSE = FALSE) is_valid_SEDOL(SEDOL, NA.FALSE = FALSE)
isin |
a character vector |
SEDOL |
a character vector |
NA.FALSE |
Checks a character vector of ISINs and
SEDOLs. The function returns TRUE
if the
ISIN/SEDOL is valid, else FALSE
.
Handling of NA
is defined by argument
NA.FALSE
.
International Securities Identification Numbers (ISINs): The test procedure in ISO 6166 does not differentiate between cases. Thus, ISINs are transformed to uppercase before being tested.
A named logical vector. For is_valid_SEDOL
, a
character vector is attached as an attribute note
.
Enrico Schumann
https://en.wikipedia.org/wiki/ISO_6166
https://en.wikipedia.org/wiki/SEDOL
https://anna-web.org/identifiers/
isin <- c("US0378331005", "AU0000XVGZA3", "DE000A0C3743", "not_an_isin") is_valid_ISIN(isin) is_valid_ISIN(c("US0378331005", "us0378331005")) ## case is ignored SEDOL <- c("0263494", "B1F3M59", "0263491", "A", NA) is_valid_SEDOL(SEDOL) ## 0263494 B1F3M59 0263491 A <NA> ## TRUE TRUE FALSE FALSE NA is_valid_SEDOL(SEDOL, NA.FALSE = TRUE) ## 0263494 B1F3M59 0263491 A <NA> ## TRUE TRUE FALSE FALSE FALSE
isin <- c("US0378331005", "AU0000XVGZA3", "DE000A0C3743", "not_an_isin") is_valid_ISIN(isin) is_valid_ISIN(c("US0378331005", "us0378331005")) ## case is ignored SEDOL <- c("0263494", "B1F3M59", "0263491", "A", NA) is_valid_SEDOL(SEDOL) ## 0263494 B1F3M59 0263491 A <NA> ## TRUE TRUE FALSE FALSE NA is_valid_SEDOL(SEDOL, NA.FALSE = TRUE) ## 0263494 B1F3M59 0263491 A <NA> ## TRUE TRUE FALSE FALSE FALSE
Create and manipulate a journal of financial transactions.
journal(amount, ...) ## Default S3 method: journal(amount, price, timestamp, instrument, id = NULL, account = NULL, ...) as.journal(x, ...) is.journal(x) ## S3 method for class 'journal' c(..., recursive = FALSE) ## S3 method for class 'journal' length(x) ## S3 method for class 'journal' aggregate(x, by, FUN, ...) ## S3 method for class 'journal' print(x, ..., width = getOption("width"), max.print = getOption("max.print"), exclude = NULL, include.only = NULL) ## S3 method for class 'journal' sort(x, decreasing = FALSE, by = "timestamp", ..., na.last = TRUE) ## S3 method for class 'journal' summary(object, by = "instrument", drop.zero = TRUE, na.rm = FALSE, ...) ## S3 method for class 'journal' subset(x, ...) ## S3 method for class 'journal' x[i, match.against = NULL, ignore.case = TRUE, perl = FALSE, fixed = FALSE, useBytes = FALSE, ..., invert = FALSE] ## S3 replacement method for class 'journal' x[i, match.against = NULL, ignore.case = TRUE, ..., invert = FALSE] <- value ## S3 method for class 'journal' as.data.frame(x, row.names = NULL, optional = FALSE, ...) ## S3 method for class 'journal' head(x, n = 6L, ..., by = "instrument") ## S3 method for class 'journal' tail(x, n = 6L, ..., by = "instrument")
journal(amount, ...) ## Default S3 method: journal(amount, price, timestamp, instrument, id = NULL, account = NULL, ...) as.journal(x, ...) is.journal(x) ## S3 method for class 'journal' c(..., recursive = FALSE) ## S3 method for class 'journal' length(x) ## S3 method for class 'journal' aggregate(x, by, FUN, ...) ## S3 method for class 'journal' print(x, ..., width = getOption("width"), max.print = getOption("max.print"), exclude = NULL, include.only = NULL) ## S3 method for class 'journal' sort(x, decreasing = FALSE, by = "timestamp", ..., na.last = TRUE) ## S3 method for class 'journal' summary(object, by = "instrument", drop.zero = TRUE, na.rm = FALSE, ...) ## S3 method for class 'journal' subset(x, ...) ## S3 method for class 'journal' x[i, match.against = NULL, ignore.case = TRUE, perl = FALSE, fixed = FALSE, useBytes = FALSE, ..., invert = FALSE] ## S3 replacement method for class 'journal' x[i, match.against = NULL, ignore.case = TRUE, ..., invert = FALSE] <- value ## S3 method for class 'journal' as.data.frame(x, row.names = NULL, optional = FALSE, ...) ## S3 method for class 'journal' head(x, n = 6L, ..., by = "instrument") ## S3 method for class 'journal' tail(x, n = 6L, ..., by = "instrument")
timestamp |
An atomic vector of mode numeric or character. Timestamps should typically be sortable. |
amount |
numeric |
price |
numeric |
instrument |
character or numeric (though typically character) |
id |
An atomic vector. Default is |
account |
An atomic vector. Default is |
... |
For For For For |
x |
a |
object |
a |
width |
integer. See |
decreasing |
passed to |
by |
|
na.rm |
logical |
drop.zero |
logical |
na.last |
arguments passed to sort |
max.print |
maximum number of transactions to print |
exclude |
character: fields that should not be printed |
include.only |
character: print only those fields. (Not supported yet.) |
row.names |
see |
optional |
see |
recursive |
ignored (see |
i |
integer, logical or character. The latter is interpreted as a
regexp (see |
n |
integer |
match.against |
character vector of field names. Default is |
ignore.case |
logical: passed to |
perl |
logical: passed to |
fixed |
logical: passed to |
useBytes |
logical: passed to |
invert |
logical. If |
FUN |
either a function that takes as input a journal and evaluates to a journal, or a list of named functions |
value |
a replacement value |
The journal
function creates a list of its arguments
and attaches a class attribute (‘journal
’).
It is a generic function; the default method creates a
journal from atomic vectors. The btest
method
extracts the journal from the results of a backtest; see
btest
.
as.journal
coerces an object to a journal and is
primarily used for creating a journal from a
data.frame
.
Calling as.journal
on an unnamed numeric vector
interprets the vector as amounts. If the vector is named,
these are interpreted as instruments; see Examples. Calling
as.journal
on a journal returns the journal itself.
journal
methods are available for several generic
functions, for instance:
all.equal
compare contents of two journals
aggregate
Splits a journal according to
by
, applies a function to every sub-journal and
recombines the results into a journal.
as.data.frame
Coerce journal to
data.frame
.
c
Combine several journals into one. Note
that the first argument to c.journal
must inherit
from journal
, or else the method dispatch will
fail. For empty journals, use journal()
(not
NULL
).
length
number of transactions in a journal;
it uses the length of amount
split
Splits a journal according to
f
, yielding a list of journals. Often used
interactively to have information per sub-journal
printed.
subset
evaluates an expression in an environment that can access all fields of the journal. The function is meant for interactive analysis; care is needed when it is used within other functions: see Examples and the Manual.
summary
provides summary statistics, such as number of trades and average buy/sell prices
toOrg
converts a journal to an Org table; package orgutils must be available
For journals that have a length, missing arguments will be
coded as NA
except for id
and
account
, which become NULL
. In
zero-length (i.e. ‘empty’) journals, all fields have
length 0. A zero-length journal is created, for instance,
by saying journal()
or when an zero-row
data.frame
is passed to as.journal
.
An object of class journal
, which is a list of atomic
vectors.
Enrico Schumann <[email protected]>
Schumann, E. (2023) Portfolio Management with R.
https://enricoschumann.net/R/packages/PMwR/;
in particular, see
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#journals
j <- journal(timestamp = 1:3, amount = c(1,2,3), price = 101:103, instrument = c("Stock A", "Stock A", "Stock B")) ## *** subset *** in functions ## this should work as expected ... t0 <- 2.5 subset(j, timestamp > t0) ## ... but here?! tradesAfterT <- function(j, t0) subset(j, timestamp > t0) tradesAfterT(j, 0) ## if really required tradesAfterT <- function(j, t0) { e <- substitute(timestamp > t0, list(t0 = t0)) do.call(subset, list(j, e)) } tradesAfterT(j, 0) ## ... or much simpler tradesAfterT <- function(j, t0) j[j$timestamp > t0] tradesAfterT(j, 0) ## *** aggregate *** ## several buys and sells on two days ## aim: find average buy/sell price per day j <- journal(timestamp = structure(c(15950, 15951, 15950, 15951, 15950, 15950, 15951, 15951, 15951, 15951), class = "Date"), amount = c(-3, -4, -3, -1, 3, -2, 1, 3, 5, 3), price = c(104, 102, 102, 110, 106, 104, 104, 106, 108, 107), instrument = c("B", "B", "A", "A", "B", "B", "A", "B", "A", "A")) by <- list(j$instrument, sign(j$amount), as.Date(j$timestamp)) fun <- function(x) { journal(timestamp = as.Date(x$timestamp[1]), amount = sum(x$amount), price = sum(x$amount*x$price)/sum(x$amount), instrument = x$instrument[1L]) } aggregate(j, by = by, FUN = fun) ## *** iterate over transactions in (previously defined) journal *** for (j in split(j, seq_along(j))) print(j) ## as.journal with numeric vector as.journal(1:3) ## amount ## 1 1 ## 2 2 ## 3 3 ## ## 3 transactions ## as.journal with *named* numeric vector x <- 1:3; names(x) <- LETTERS[1:3] as.journal(x) ## instrument amount ## 1 A 1 ## 2 B 2 ## 3 C 3 ## ## 3 transactions x <- 1:3; names(x) <- c("A", "B", "A") as.journal(x) ## instrument amount ## 1 A 1 ## 2 B 2 ## 3 A 3 ## ## 3 transactions
j <- journal(timestamp = 1:3, amount = c(1,2,3), price = 101:103, instrument = c("Stock A", "Stock A", "Stock B")) ## *** subset *** in functions ## this should work as expected ... t0 <- 2.5 subset(j, timestamp > t0) ## ... but here?! tradesAfterT <- function(j, t0) subset(j, timestamp > t0) tradesAfterT(j, 0) ## if really required tradesAfterT <- function(j, t0) { e <- substitute(timestamp > t0, list(t0 = t0)) do.call(subset, list(j, e)) } tradesAfterT(j, 0) ## ... or much simpler tradesAfterT <- function(j, t0) j[j$timestamp > t0] tradesAfterT(j, 0) ## *** aggregate *** ## several buys and sells on two days ## aim: find average buy/sell price per day j <- journal(timestamp = structure(c(15950, 15951, 15950, 15951, 15950, 15950, 15951, 15951, 15951, 15951), class = "Date"), amount = c(-3, -4, -3, -1, 3, -2, 1, 3, 5, 3), price = c(104, 102, 102, 110, 106, 104, 104, 106, 108, 107), instrument = c("B", "B", "A", "A", "B", "B", "A", "B", "A", "A")) by <- list(j$instrument, sign(j$amount), as.Date(j$timestamp)) fun <- function(x) { journal(timestamp = as.Date(x$timestamp[1]), amount = sum(x$amount), price = sum(x$amount*x$price)/sum(x$amount), instrument = x$instrument[1L]) } aggregate(j, by = by, FUN = fun) ## *** iterate over transactions in (previously defined) journal *** for (j in split(j, seq_along(j))) print(j) ## as.journal with numeric vector as.journal(1:3) ## amount ## 1 1 ## 2 2 ## 3 3 ## ## 3 transactions ## as.journal with *named* numeric vector x <- 1:3; names(x) <- LETTERS[1:3] as.journal(x) ## instrument amount ## 1 A 1 ## 2 B 2 ## 3 C 3 ## ## 3 transactions x <- 1:3; names(x) <- c("A", "B", "A") as.journal(x) ## instrument amount ## 1 A 1 ## 2 B 2 ## 3 A 3 ## ## 3 transactions
Create a net-asset-value (NAV) series.
NAVseries(NAV, timestamp, instrument = NULL, title = NULL, description = NULL, drop.NA = NULL) as.NAVseries(x, ...) ## S3 method for class 'NAVseries' print(x, ... , na.rm = FALSE) ## S3 method for class 'NAVseries' summary(object, ..., monthly.vol = TRUE, bm = NULL, monthly.te = TRUE, na.rm = FALSE, assume.daily = FALSE) ## S3 method for class 'NAVseries' plot(x, y, ..., xlab = "", ylab = "", type = "l") ## S3 method for class 'NAVseries' window(x, start = NULL, end = NULL, ...)
NAVseries(NAV, timestamp, instrument = NULL, title = NULL, description = NULL, drop.NA = NULL) as.NAVseries(x, ...) ## S3 method for class 'NAVseries' print(x, ... , na.rm = FALSE) ## S3 method for class 'NAVseries' summary(object, ..., monthly.vol = TRUE, bm = NULL, monthly.te = TRUE, na.rm = FALSE, assume.daily = FALSE) ## S3 method for class 'NAVseries' plot(x, y, ..., xlab = "", ylab = "", type = "l") ## S3 method for class 'NAVseries' window(x, start = NULL, end = NULL, ...)
NAV |
numeric |
timestamp |
|
instrument |
character |
title |
character |
description |
character |
x |
an |
object |
an |
... |
further arguments. For |
drop.NA |
logical. If |
bm |
an optional NAVseries. If |
monthly.vol |
if |
monthly.te |
if |
assume.daily |
logical |
na.rm |
logical |
y |
a second NAVseries to be plotted. Not supported yet. |
xlab |
character. See |
ylab |
character. See |
type |
character. See |
start |
same class as timestamp; |
end |
same class as timestamp; |
An NAV series is a numeric vector (the actual series) and
additional information, attached as attributes: timestamp,
instrument, title, description. Of these attributes,
timestamp is the most useful, as it is used for several
computations (e.g. when calling summary
) or
for plotting.
The ‘instrument’ is typically an internal label used to identify the series, such as a ticker; ‘title’ is a label, too, but is intended to be human-readable; ‘description’ finally should be human-readable as well, but may be longer than ‘title’.
The summary
method returns a list of the original
NAVseries plus various statistics, such as return per year
and volatility. The method may receive several NAV series
as input.
an NAVseries
: see Details.
an NAVseries
summary: a list of lists. If a
benchmark series is present, the summary has an
attribute bm
: an integer, specifying the
position of the benchmark.
The semantics of handling NAVseries are not stable
yet. Currently, objects of class NAVseries
are
univariate: you create a single NAVseries, summarise
it, plot it, and so one. In the future, at least some
of the methods will support the multi-variate case,
i.e. be able to handle several series at once.
Enrico Schumann <[email protected]>
Schumann, E. (2024) Portfolio Management with R.
https://enricoschumann.net/PMwR/; in particular, see
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#NAVseries
For handling external cashflows, see unit_prices
,
split_adjust
and div_adjust
.
summary(NAVseries(DAX[[1]], as.Date(row.names(DAX)), title = "DAX"))
summary(NAVseries(DAX[[1]], as.Date(row.names(DAX)), title = "DAX"))
Compute profit and (or) loss of financial transactions.
pl(amount, ... ) ## Default S3 method: pl(amount, price, timestamp = NULL, instrument = NULL, multiplier = 1, multiplier.regexp = FALSE, along.timestamp = FALSE, approx = FALSE, initial.position = NULL, initial.price = NULL, vprice = NULL, tol = 1e-10, do.warn = TRUE, do.sum = FALSE, pl.only = FALSE, footnotes = TRUE, ... ) ## S3 method for class 'journal' pl(amount, multiplier = 1, multiplier.regexp = FALSE, along.timestamp = FALSE, approx = FALSE, initial.position = NULL, initial.price = NULL, vprice = NULL, tol = 1e-10, do.warn = TRUE, ... ) ## S3 method for class 'pl' pl(amount, ... ) ## S3 method for class 'pl' print(x, ..., use.crayon = NULL, na.print = ".", footnotes = TRUE) ## S3 method for class 'pl' as.data.frame(x, ... ) .pl(amount, price, tol = 1e-10, do.warn = TRUE) .pl_stats(amount, price, tol = sqrt(.Machine$double.eps))
pl(amount, ... ) ## Default S3 method: pl(amount, price, timestamp = NULL, instrument = NULL, multiplier = 1, multiplier.regexp = FALSE, along.timestamp = FALSE, approx = FALSE, initial.position = NULL, initial.price = NULL, vprice = NULL, tol = 1e-10, do.warn = TRUE, do.sum = FALSE, pl.only = FALSE, footnotes = TRUE, ... ) ## S3 method for class 'journal' pl(amount, multiplier = 1, multiplier.regexp = FALSE, along.timestamp = FALSE, approx = FALSE, initial.position = NULL, initial.price = NULL, vprice = NULL, tol = 1e-10, do.warn = TRUE, ... ) ## S3 method for class 'pl' pl(amount, ... ) ## S3 method for class 'pl' print(x, ..., use.crayon = NULL, na.print = ".", footnotes = TRUE) ## S3 method for class 'pl' as.data.frame(x, ... ) .pl(amount, price, tol = 1e-10, do.warn = TRUE) .pl_stats(amount, price, tol = sqrt(.Machine$double.eps))
amount |
numeric or a |
price |
numeric |
instrument |
character or numeric (though typically character) |
timestamp |
An atomic vector of mode
|
along.timestamp |
logical; or a a vector of
timestamps. If the latter, |
initial.position |
a |
initial.price |
prices to evaluate initial position |
vprice |
valuation price; a numeric vector. With several instruments, the
prices must be named, e.g. |
multiplier |
numeric vector. When |
multiplier.regexp |
logical. If |
approx |
logical |
tol |
numeric: threshold to consider a position zero. |
x |
a |
... |
further argument |
use.crayon |
logical |
na.print |
character: how to print |
do.warn |
logical: issue warnings? |
do.sum |
logical: sum profit/loss across instruments? |
pl.only |
logical: if |
footnotes |
logical, with default |
Computes profit and/or loss and returns a list with
several statistics (see Section Value, below). To get only
the profit/loss numbers as a numeric vector, set argument
pl.only
to TRUE
.
pl
is a generic function: The default input is
vectors for amount, price, etc. Alternatively (and often
more conveniently), the function may also be called with a
journal
or a data.frame
as its
input. For data frames, columns must be named
amount
, price
, and so on, as in a
journal
.
pl
may be called in two ways: either to compute
total profit/loss from a list of trades, possibly
broken down by instrument
and account
; or to
compute profit/loss over time. The latter case
typically requires setting arguments
along.timestamp
and/or vprice
(see
Examples). Profit/loss over time is always computed with
time in ascending order: so if the timestamps in
along.timestamp
are not sorted, the function will
sort them (and vprice
as well).
Using vprice
: when along.timestamp
is
logical (FALSE
or TRUE
),
vprice
can be used to value an open
position. For a single asset, it should be a single
number; for several assets, it should be named
vector, with names indicating the instrument
.
When along.timestamp
is used to pass a
custom timestamp: for a single asset, vprice
must be a vector with the same length as
along.timestamp
; for several assets, it must
be a numeric matrix with dimension
length(along.timestamp)
times number of
assets.
.pl
and .pl_stats
are helper functions
that are called by pl
. .pl_stats
requires amount and price to be sorted in time, and
to be of length > 0.
To use package crayon – which is only sensible
in interactive use –, either explicitly set
use.crayon
to TRUE
or set an option
PMwR.use.crayon
to TRUE
.
For pl
, an object of class pl
, which is
a list of lists: one list for each instrument. Each
such list contains numeric vectors: pl
,
realised
, unrealised
, buy
,
sell
, volume
. If along.timestamp
is not FALSE
, a vector timestamp
is also present.
For .pl
, a numeric vector with four elements:
profit/loss in units of the instrument, sum of
absolute amounts, average buy price, average sell
price. For zero-length vector, the function evaluates to
c(0, 0, NaN, NaN)
.
For .pl_stats
, a list of two elements:
the average entry-price, and the realized profit/loss.
profit/loss in units of the instrument, sum of
absolute amounts, average buy price, average sell
price. For zero-length vector, the function evaluates to
c(0, 0, NaN, NaN)
.
Enrico Schumann <[email protected]>
Schumann, E. (2023) Portfolio Management with R. https://enricoschumann.net/PMwR/; in particular https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#profit-and-loss
J <- journal(timestamp = c( 1, 2, 3), amount = c( 1, 1, -2), price = c(100, 102, 101)) pl(J) pl(amount = c( 1, 1, -2), price = c(100, 102, 101)) ## without a 'journal' J <- journal(timestamp = c( 1, 2, 3, 1, 2, 3), amount = c( 1, 1, -2, 1, 1, -2), price = c(100, 102, 101, 100, 102, 105), instrument = c(rep("Bond A", 3), rep("Bond B", 3))) pl(J) ## Bond A ## P/L total 0 ## average buy 101 ## average sell 101 ## cum. volume 4 ## ## Bond B ## P/L total 8 ## average buy 101 ## average sell 105 ## cum. volume 4 ## ## 'P/L total' is in units of instrument; ## 'volume' is sum of /absolute/ amounts. as.data.frame(pl(J)) ## a single data.frame ## pl buy sell volume ## Bond A 0 101 101 4 ## Bond B 8 101 105 4 lapply(pl(J), as.data.frame) ## => a list of data.frames ## $`Bond A` ## pl realised unrealised buy sell volume ## 1 0 NA NA 101 101 4 ## ## $`Bond B` ## pl realised unrealised buy sell volume ## 1 8 NA NA 101 105 4 pl(pl(J)) ## P/L as a numeric vector ## Bond A Bond B ## 0 8 ## Example for 'vprice' instrument <- c(rep("Bond A", 2), rep("Bond B", 2)) amount <- c(1, -2, 2, -1) price <- c(100, 101, 100, 105) ## ... no p/l because positions not closed: pl(amount, price, instrument = instrument, do.warn = FALSE) ## ... but with vprice specified, p/l is computed: pl(amount, price, instrument = instrument, vprice = c("Bond A" = 103, "Bond B" = 100)) ### ... and is, except for volume, the same as here: instrument <- c(rep("Bond A", 3), rep("Bond B", 3)) amount <- c(1, -2, 1, 2, -1, -1) price <- c(100, 101, 103, 100, 105, 100) pl(amount, price, instrument = instrument) ## p/l over time: example for 'along.timestamp' and 'vprice' j <- journal(amount = c(1, -1), price = c(100, 101), timestamp = as.Date(c("2017-07-05", "2017-07-06"))) pl(j) pl(j, along.timestamp = TRUE) pl(j, along.timestamp = seq(from = as.Date("2017-07-04"), to = as.Date("2017-07-07"), by = "1 day"), vprice = 101:104) ## Example for 'multiplier' jnl <- read.table(text = "instrument, price, amount FGBL MAR 16, 165.20, 1 FGBL MAR 16, 165.37, -1 FGBL JUN 16, 164.12, 1 FGBL JUN 16, 164.13, -1 FESX JUN 16, 2910, 5 FESX JUN 16, 2905, -5", header = TRUE, stringsAsFactors = FALSE, sep = ",") jnl <- as.journal(jnl) pl(jnl, multiplier.regexp = TRUE, ## regexp matching is case sensitive multiplier = c("FGBL" = 1000, "FESX" = 10)) ## use package 'crayon' ## Not run: ## on Windows, you may also need 'options(crayon.enabled = TRUE)' options(PMwR.use.crayon = FALSE) pl(amount = c(1, -1), price = c(1, 2)) options(PMwR.use.crayon = TRUE) pl(amount = c(1, -1), price = c(1, 2)) ## End(Not run)
J <- journal(timestamp = c( 1, 2, 3), amount = c( 1, 1, -2), price = c(100, 102, 101)) pl(J) pl(amount = c( 1, 1, -2), price = c(100, 102, 101)) ## without a 'journal' J <- journal(timestamp = c( 1, 2, 3, 1, 2, 3), amount = c( 1, 1, -2, 1, 1, -2), price = c(100, 102, 101, 100, 102, 105), instrument = c(rep("Bond A", 3), rep("Bond B", 3))) pl(J) ## Bond A ## P/L total 0 ## average buy 101 ## average sell 101 ## cum. volume 4 ## ## Bond B ## P/L total 8 ## average buy 101 ## average sell 105 ## cum. volume 4 ## ## 'P/L total' is in units of instrument; ## 'volume' is sum of /absolute/ amounts. as.data.frame(pl(J)) ## a single data.frame ## pl buy sell volume ## Bond A 0 101 101 4 ## Bond B 8 101 105 4 lapply(pl(J), as.data.frame) ## => a list of data.frames ## $`Bond A` ## pl realised unrealised buy sell volume ## 1 0 NA NA 101 101 4 ## ## $`Bond B` ## pl realised unrealised buy sell volume ## 1 8 NA NA 101 105 4 pl(pl(J)) ## P/L as a numeric vector ## Bond A Bond B ## 0 8 ## Example for 'vprice' instrument <- c(rep("Bond A", 2), rep("Bond B", 2)) amount <- c(1, -2, 2, -1) price <- c(100, 101, 100, 105) ## ... no p/l because positions not closed: pl(amount, price, instrument = instrument, do.warn = FALSE) ## ... but with vprice specified, p/l is computed: pl(amount, price, instrument = instrument, vprice = c("Bond A" = 103, "Bond B" = 100)) ### ... and is, except for volume, the same as here: instrument <- c(rep("Bond A", 3), rep("Bond B", 3)) amount <- c(1, -2, 1, 2, -1, -1) price <- c(100, 101, 103, 100, 105, 100) pl(amount, price, instrument = instrument) ## p/l over time: example for 'along.timestamp' and 'vprice' j <- journal(amount = c(1, -1), price = c(100, 101), timestamp = as.Date(c("2017-07-05", "2017-07-06"))) pl(j) pl(j, along.timestamp = TRUE) pl(j, along.timestamp = seq(from = as.Date("2017-07-04"), to = as.Date("2017-07-07"), by = "1 day"), vprice = 101:104) ## Example for 'multiplier' jnl <- read.table(text = "instrument, price, amount FGBL MAR 16, 165.20, 1 FGBL MAR 16, 165.37, -1 FGBL JUN 16, 164.12, 1 FGBL JUN 16, 164.13, -1 FESX JUN 16, 2910, 5 FESX JUN 16, 2905, -5", header = TRUE, stringsAsFactors = FALSE, sep = ",") jnl <- as.journal(jnl) pl(jnl, multiplier.regexp = TRUE, ## regexp matching is case sensitive multiplier = c("FGBL" = 1000, "FESX" = 10)) ## use package 'crayon' ## Not run: ## on Windows, you may also need 'options(crayon.enabled = TRUE)' options(PMwR.use.crayon = FALSE) pl(amount = c(1, -1), price = c(1, 2)) options(PMwR.use.crayon = TRUE) pl(amount = c(1, -1), price = c(1, 2)) ## End(Not run)
Plot a time series after removing weekends and specific times of the day.
plot_trading_hours(x, t = NULL, interval = "5 min", labels = "hours", label.format = NULL, exclude.weekends = TRUE, holidays = NULL, fromHHMMSS = "000000", toHHMMSS = "240000", do.plot.axis = TRUE, ..., from = NULL, to = NULL, do.plot = TRUE, axis1.par = list())
plot_trading_hours(x, t = NULL, interval = "5 min", labels = "hours", label.format = NULL, exclude.weekends = TRUE, holidays = NULL, fromHHMMSS = "000000", toHHMMSS = "240000", do.plot.axis = TRUE, ..., from = NULL, to = NULL, do.plot = TRUE, axis1.par = list())
x |
A numeric vector. Can also be of class |
t |
A vector that inherits from class |
interval |
A character string like “num units”, in which |
labels |
A character vector of length one, determining the grid for
|
label.format |
See |
exclude.weekends |
logical: default is |
holidays |
a vector of class |
fromHHMMSS |
a character vector of length one in format “HHMMSS” |
toHHMMSS |
a character vector of length one in format “HHMMSS” |
do.plot.axis |
logical. Should |
... |
|
from |
POSIXct: start plot at (if not specified, plot starts at first data point) |
to |
POSIXct: end plot at (if not specified, plot ends at last data point) |
do.plot |
logical. Should anything be plotted? Default is |
axis1.par |
a list of named elements |
Plot a timeseries during specific times of day.
A list (invisibly if do.plot
is TRUE
):
list(t, x, axis.pos = pos, axis.labels, timegrid)
t |
positions |
x |
values |
axis.pos |
positions of x-tickmarks |
axis.labels |
labels at x-ticks |
timegrid |
a POSIXct vector |
map |
a function. See the manual (a link is under References). |
Enrico Schumann <[email protected]>
B.D. Ripley and K. Hornik. Date-Time Classes. R-News, 1(2):8–12, 2001.
E. Schumann (2023) Portfolio Management with R.
https://enricoschumann.net/PMwR/; in particular, see
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#plot-trading-hours
t <- as.POSIXct("2012-08-31 08:00:00") + 0:32400 x <- runif(length(t)) par(tck = 0.001, mgp = c(3,1,0.5), bty = "n") p <- plot_trading_hours(x, t, interval = "5 min", labels = "hours", xlab = "time", ylab = "random points", col = "blue") ## with ?lines t <- as.POSIXct("2012-08-31 10:00:00") + 0:9000 x <- seq(0, 1, length.out = 9001) lines(p$map(t)$t, x[p$map(t)$ix], pch = 19)
t <- as.POSIXct("2012-08-31 08:00:00") + 0:32400 x <- runif(length(t)) par(tck = 0.001, mgp = c(3,1,0.5), bty = "n") p <- plot_trading_hours(x, t, interval = "5 min", labels = "hours", xlab = "time", ylab = "random points", col = "blue") ## with ?lines t <- as.POSIXct("2012-08-31 10:00:00") + 0:9000 x <- seq(0, 1, length.out = 9001) lines(p$map(t)$t, x[p$map(t)$ix], pch = 19)
Use information on single trades to compute a position at a specific point in time.
position(amount, ...) ## Default S3 method: position(amount, timestamp, instrument, when, drop.zero = FALSE, account = NULL, use.names = NULL, ...) ## S3 method for class 'journal' position(amount, when, drop.zero = FALSE, use.account = FALSE, ...) ## S3 method for class 'position' print(x, ..., sep = ":")
position(amount, ...) ## Default S3 method: position(amount, timestamp, instrument, when, drop.zero = FALSE, account = NULL, use.names = NULL, ...) ## S3 method for class 'journal' position(amount, when, drop.zero = FALSE, use.account = FALSE, ...) ## S3 method for class 'position' print(x, ..., sep = ":")
when |
a timestamp or a vector of timestamps; alternatively, several keywords are supported. See Details. |
amount |
numeric or an object of class |
timestamp |
numeric or character: timestamps, must be sortable |
instrument |
character: symbols to identify different instruments |
account |
character: description of account. Ignored if |
use.account |
logical. If TRUE, positions are computed by account and instrument; otherwise by instrument only. |
use.names |
logical or |
drop.zero |
If logical, drop instruments that have a zero position;
default is |
x |
An object of type position. |
... |
arguments passed to |
sep |
A regular expression. Split instruments accordingly. Not implemented yet. |
position
computes positions for lists of trades.
position
is a generic function; most useful is the
method for journal
s.
The function checks if timestamp
is sorted (see
is.unsorted
) and sorts the journal by
timestamp
, if required. If there are (some) NA
values in timestamp
, but timestamp
is sorted
otherwise, the function will proceed (with a warning,
though).
The argument when
can also be specified as one of
several keywords: last
(or newest
or
latest
) provides the position at the latest
timestamp; first
(or oldest
) provides the
position at the earliest timestamp; all
provides the
positions at all timestamps in the journal. endofday
,
endofmonth
and endofyear
provide positions at
the end of all calendar days, months and years within the
timestamp range of the journal. The latter keywords can only
work if timestamp
can be coerced to
Date
.
An object of class position
, which is a numeric
matrix with instrument
and timestamp
attributes. Note that position
will never drop the
result's dim
attribute: it will always be a matrix of
size length(when)
times
length(unique(instrument))
, which may not be obvious
from the printed output. The rows of the matrix correspond
to timestamps; the columns correspond to instruments.
To extract the numeric position matrix, say
as.matrix(p)
.
Enrico Schumann
Schumann, E. (2023) Portfolio Management with R.
https://enricoschumann.net/R/packages/PMwR/;
in particular, see
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#computing-balances
journal
; internal computations are handled by
cumsum
and findInterval
position(amount = c(1, 1, -1, 3, -4), timestamp = 1:5, when = 4.9) ## using a journal J <- journal(timestamp = 1:5, amount = c(1, 1, -1, 3, -4)) position(J, when = 4.9) ## 'declaring' a position, using named amounts amount <- c(1, 1, 1) instrument <- c("A", "A", "B") position(amount = amount, instrument = instrument) ## .... or equivalently amount <- c(A = 2, B = 1) position(amount) ## ignore names of amount position(amount, use.names = FALSE)
position(amount = c(1, 1, -1, 3, -4), timestamp = 1:5, when = 4.9) ## using a journal J <- journal(timestamp = 1:5, amount = c(1, 1, -1, 3, -4)) position(J, when = 4.9) ## 'declaring' a position, using named amounts amount <- c(1, 1, 1) instrument <- c("A", "A", "B") position(amount = amount, instrument = instrument) ## .... or equivalently amount <- c(A = 2, B = 1) position(amount) ## ignore names of amount position(amount, use.names = FALSE)
Create price table
pricetable(price, ...)
pricetable(price, ...)
price |
a matrix |
... |
further arguments, passed to methods |
pricetable
is a helper function for extracting
prices of particular instrument at specified dates.
For this, it first creates a table that merges series
passed via ... and appends a class attribute. A [
method then allows to extract prices. Importantly, if
you ask for a subset of m rows and n columns, the
result will be a matrix of size m times n, even if
times or instruments are missing.
pricetable
is a generic function, currently
with methods for numeric vectors (including vectors
with a dim
, aka matrices) and for
zoo
objects.
a numeric matrix with class attribute pricetable
Enrico Schumann
Schumann, E. (2023) Portfolio Management with R. https://enricoschumann.net/PMwR/
## quickly creating a pricetable pricetable(1:3) pricetable(1:3, instrument = c("A", "B", "C")) ### ... and the same pricetable(c(A = 1, B = 2, C = 3)) ## subsetting examples m <- 3 n <- 2 price <- array(c(1:m, 1:m + 100), dim = c(m,n)) colnames(price) <- LETTERS[1:n] pt <- pricetable(price, timestamp = 1:m) ## A B ## 1 1 101 ## 2 2 102 ## 3 3 103 pt[ , "A"] ## A ## 1 1 ## 2 2 ## 3 3 pt[ , c("X", "A", "X")] ## X A X ## 1 NA 1 NA ## 2 NA 2 NA ## 3 NA 3 NA pt[ , c("X", "A", "X"), missing = 0] ## X A X ## 1 0 1 0 ## 2 0 2 0 ## 3 0 3 0 pt[c(0, 1.5, 4), , missing = "locf"] ## A B ## 0 NA NA ## 1.5 2 102 ## 4 3 103
## quickly creating a pricetable pricetable(1:3) pricetable(1:3, instrument = c("A", "B", "C")) ### ... and the same pricetable(c(A = 1, B = 2, C = 3)) ## subsetting examples m <- 3 n <- 2 price <- array(c(1:m, 1:m + 100), dim = c(m,n)) colnames(price) <- LETTERS[1:n] pt <- pricetable(price, timestamp = 1:m) ## A B ## 1 1 101 ## 2 2 102 ## 3 3 103 pt[ , "A"] ## A ## 1 1 ## 2 2 ## 3 3 pt[ , c("X", "A", "X")] ## X A X ## 1 NA 1 NA ## 2 NA 2 NA ## 3 NA 3 NA pt[ , c("X", "A", "X"), missing = 0] ## X A X ## 1 0 1 0 ## 2 0 2 0 ## 3 0 3 0 pt[c(0, 1.5, 4), , missing = "locf"] ## A B ## 0 NA NA ## 1.5 2 102 ## 4 3 103
Print treasury quotes with 1/32nds of points.
quote32(price, sep = "(-|'|:)", warn = TRUE) q32(price, sep = "(-|'|:)", warn = TRUE)
quote32(price, sep = "(-|'|:)", warn = TRUE) q32(price, sep = "(-|'|:)", warn = TRUE)
price |
numeric or character. See Details. |
sep |
character: a regular expression |
warn |
logical. Warn about rounding errors? |
The function is meant for pretty-printing of US treasury bond quotes; it provides no other functionality.
If price
is numeric
, it is interpreted as a quote in
decimal notation and ‘translated’ into a price quoted in
fractions of a point.
If price
is character
, it is interpreted as a quote in
fractional notation.
q32
is a short-hand for quote32
.
A numeric vector of class quote32
.
Enrico Schumann
CME Group (2020). Treasury Futures Price Rounding Conventions. https://www.cmegroup.com/education/articles-and-reports/treasury-futures-price-rounding-conventions.html
quote32(100 + 17/32 + 0.75/32) q32("100-172") q32("100-272") - q32("100-270") as.numeric(q32("100-272") - q32("100-270"))
quote32(100 + 17/32 + 0.75/32) q32("100-172") q32("100-272") - q32("100-270") as.numeric(q32("100-272") - q32("100-270"))
Return contribution of portfolio segments.
rc(R, weights, timestamp, segments = NULL, R.bm = NULL, weights.bm = NULL, method = "contribution", linking.method = NULL, allocation.minus.bm = TRUE, tol = sqrt(.Machine$double.eps))
rc(R, weights, timestamp, segments = NULL, R.bm = NULL, weights.bm = NULL, method = "contribution", linking.method = NULL, allocation.minus.bm = TRUE, tol = sqrt(.Machine$double.eps))
R |
returns: a numeric matrix |
weights |
the segment weights: a numeric matrix.
|
timestamp |
character or numeric |
segments |
character. If missing, column names of |
method |
a string; default is |
linking.method |
|
allocation.minus.bm |
logical |
tol |
numeric: weights whose absolute value is below
|
If portfolio returns are to be compared against benchmark returns, benchmark returns and weights must be supplied:
R.bm |
benchmark returns: a numeric matrix |
weights.bm |
the benchmark weights of segments: a numeric matrix.
|
The function computes segment contribution, potentially
over time. Returns and weights must be arranged in
matrices, with rows corresponding to time periods and
columns to portfolio segments. If weights
and
R
are atomic vectors, then they are interpreted as
cross-sectional weights/returns for a single period,
i.e. they are handled like row vectors.
Weights can be missing, in which case R
is assumed
to already comprise segment returns.
Note that the segment contributions need not come from asset classes; the computation works for any additive single-period decomposition of portfolio returns.
A list of two components:
period_contributions |
a data.frame of single-period contributions, sorted in time |
total_contributions |
a numeric vector |
Enrico Schumann
David R. Cariño (1999). Combining Attribution Effects Over Time. Journal of Performance Measurement. 3 (4), 5–14.
Jon A. Christopherson and David R. Cariño and Wayne E. Ferson (2009), Portfolio Performance Measurement and Benchmarking, McGraw-Hill.
Feibel, Bruce (2003), Investment Performance Measurement, Wiley.
Erik Valtonen (2002). Incremental Attribution with and without Notional Portfolios. Journal of Performance Measurement. 7 (1), 68–83.
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#return-contribution
weights <- rbind(c( 0.25, 0.75), c( 0.40, 0.60), c( 0.25, 0.75)) R <- rbind(c( 1 , 0), c( 2.5, -1.0), c(-2 , 0.5))/100 rc(R, weights, segment = c("equities", "bonds")) ## EXAMPLE of Christopherson et al., ch 19 weights <- cbind(stocks = c(0.5, 0.55), bonds = c(0.5, 0.45)) ## stocks bonds ## [1,] 0.50 0.50 ## [2,] 0.55 0.45 R <- cbind(stocks = c(.4, 0.1), bonds = c(.1, 0.2)) ## stocks bonds ## [1,] 0.4 0.1 ## [2,] 0.1 0.2 ## ==> contributions grow at portfolio rate-of-return rc(R, weights, linking.method = "geometric1") ## ==> contributions are made on top of current portfolio-value rc(R, weights, linking.method = "geometric0") ## ==> mixture rc(R, weights, linking.method = "geometric0.5") ## EXAMPLE from ## https://quant.stackexchange.com/questions/36520/ ## how-to-calculate-the-annual-contribution-of-a-fund-to-a-portfolio-of-funds/ ## 36530#36530 ## (unbreak the URL) weights <- rbind(c( 0.5, 0.5), c( 0.5, 0.5)) R <- rbind(c( 10, 0), c( 0 , 10))/100 rc(R, weights, segment = c("F1", "F2"), timestamp = 1:2, linking.method = "geometric1") ## ==> F1 contributed first, and so gets a higher total ## contribution rc(R, weights, segment = c("F1", "F2"), timestamp = 1:2, linking.method = "geometric0") ## ==> F2 contributed later, and so gets a higher total ## contribution because it started off a higher base ## value ## contribution for btest: ## run a portfolio 10% equities, 90% bonds P <- as.matrix(merge(DAX, REXP, by = "row.names")[, -1]) (bt <- btest(prices = list(P), signal = function() c(0.1, 0.9), convert.weights = TRUE, initial.cash = 100)) W <- bt$position*P/bt$wealth rc(returns(P)*W[-nrow(W), ])$total_contributions
weights <- rbind(c( 0.25, 0.75), c( 0.40, 0.60), c( 0.25, 0.75)) R <- rbind(c( 1 , 0), c( 2.5, -1.0), c(-2 , 0.5))/100 rc(R, weights, segment = c("equities", "bonds")) ## EXAMPLE of Christopherson et al., ch 19 weights <- cbind(stocks = c(0.5, 0.55), bonds = c(0.5, 0.45)) ## stocks bonds ## [1,] 0.50 0.50 ## [2,] 0.55 0.45 R <- cbind(stocks = c(.4, 0.1), bonds = c(.1, 0.2)) ## stocks bonds ## [1,] 0.4 0.1 ## [2,] 0.1 0.2 ## ==> contributions grow at portfolio rate-of-return rc(R, weights, linking.method = "geometric1") ## ==> contributions are made on top of current portfolio-value rc(R, weights, linking.method = "geometric0") ## ==> mixture rc(R, weights, linking.method = "geometric0.5") ## EXAMPLE from ## https://quant.stackexchange.com/questions/36520/ ## how-to-calculate-the-annual-contribution-of-a-fund-to-a-portfolio-of-funds/ ## 36530#36530 ## (unbreak the URL) weights <- rbind(c( 0.5, 0.5), c( 0.5, 0.5)) R <- rbind(c( 10, 0), c( 0 , 10))/100 rc(R, weights, segment = c("F1", "F2"), timestamp = 1:2, linking.method = "geometric1") ## ==> F1 contributed first, and so gets a higher total ## contribution rc(R, weights, segment = c("F1", "F2"), timestamp = 1:2, linking.method = "geometric0") ## ==> F2 contributed later, and so gets a higher total ## contribution because it started off a higher base ## value ## contribution for btest: ## run a portfolio 10% equities, 90% bonds P <- as.matrix(merge(DAX, REXP, by = "row.names")[, -1]) (bt <- btest(prices = list(P), signal = function() c(0.1, 0.9), convert.weights = TRUE, initial.cash = 100)) W <- bt$position*P/bt$wealth rc(returns(P)*W[-nrow(W), ])$total_contributions
Compute the differences between two portfolios.
rebalance(current, target, price, notional = NULL, multiplier = 1, truncate = TRUE, match.names = TRUE, fraction = 1, drop.zero = FALSE, current.weights = FALSE, target.weights = TRUE) ## S3 method for class 'rebalance' print(x, ..., drop.zero = TRUE) replace_weight(weights, ..., prefix = TRUE, sep = "::")
rebalance(current, target, price, notional = NULL, multiplier = 1, truncate = TRUE, match.names = TRUE, fraction = 1, drop.zero = FALSE, current.weights = FALSE, target.weights = TRUE) ## S3 method for class 'rebalance' print(x, ..., drop.zero = TRUE) replace_weight(weights, ..., prefix = TRUE, sep = "::")
current |
the current holdings: a (typically named) vector of position sizes;
can also be a |
target |
the target holdings: a (typically named) vector of weights;
can also be a |
price |
a numeric vector: the current prices; may be named |
notional |
a single number: the value of the portfolio; if missing,
replaced by |
multiplier |
numeric vector, possibly named |
truncate |
truncate computed positions? Default is |
match.names |
logical |
fraction |
numeric |
x |
an object of class |
... |
|
drop.zero |
logical: should instruments with no difference
between Note the different defaults for computing and printing. |
current.weights |
logical. If |
target.weights |
logical. If |
weights |
a numeric vector with named components |
sep |
character |
prefix |
logical |
The function computes the necessary trades to move from the
current
portfolio to a target
portfolio.
replace_weight
is a helper function to split
baskets into their components. All arguments passed
via ...
should be named vectors. If names are
not syntactically valid (see
make.names
), quote them. The passed
vectors themselves should be passed as named
arguments: see examples.
An object of class rebalance
, which is a
data.frame
:
instrument |
character, or |
price |
prices |
current |
current portfolio, in units of instrument |
target |
new portfolio, in units of instrument |
difference |
the difference between |
Attached to the data.frame are several attributes:
notional |
a single number |
match.names |
logical |
multiplier |
a numeric vector with as many elements as the resulting data.frame has rows |
Enrico Schumann
Schumann, E. (2023) Portfolio Management with R.
https://enricoschumann.net/R/packages/PMwR/;
in particular, see
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#rebalance
r <- rebalance(current = c(a = 100, b = 20), target = c(a = 0.2, c = 0.3), price = c(a = 1, b = 2, c = 3)) as.journal(r) ## replace_weight: the passed vectors must be named; ## 'basket_3' is ignored because not ## component of weights is named ## 'basket_3' replace_weight(c(basket_1 = 0.3, basket_2 = 0.7), basket_1 = c(a = 0.1, b = 0.4, c = .5), basket_2 = c(x = 0.1, y = 0.4, z = .5), basket_3 = c(X = 0.5, Z = 0.5), sep = "|")
r <- rebalance(current = c(a = 100, b = 20), target = c(a = 0.2, c = 0.3), price = c(a = 1, b = 2, c = 3)) as.journal(r) ## replace_weight: the passed vectors must be named; ## 'basket_3' is ignored because not ## component of weights is named ## 'basket_3' replace_weight(c(basket_1 = 0.3, basket_2 = 0.7), basket_1 = c(a = 0.1, b = 0.4, c = .5), basket_2 = c(x = 0.1, y = 0.4, z = .5), basket_3 = c(X = 0.5, Z = 0.5), sep = "|")
Convert prices into returns.
returns(x, ...) ## Default S3 method: returns(x, t = NULL, period = NULL, complete.first = TRUE, pad = NULL, position = NULL, weights = NULL, rebalance.when = NULL, lag = 1, na.rm = FALSE, ..., na.warn = FALSE) ## S3 method for class 'zoo' returns(x, period = NULL, complete.first = TRUE, pad = NULL, position = NULL, weights = NULL, rebalance.when = NULL, lag = 1, na.rm = FALSE, ...) ## S3 method for class 'p_returns' print(x, ..., year.rows = TRUE, month.names = NULL, zero.print = "0", plus = FALSE, digits = 1, na.print = NULL) ## S3 method for class 'p_returns' toLatex(object, ..., year.rows = TRUE, ytd = "YTD", month.names = NULL, eol = "\\\\", stand.alone = FALSE) ## S3 method for class 'p_returns' toHTML(x, ..., year.rows = TRUE, ytd = "YTD", month.names = NULL, stand.alone = TRUE, table.style = NULL, table.class = NULL, th.style = NULL, th.class = NULL, td.style = "text-align:right; padding:0.5em;", td.class = NULL, tr.style = NULL, tr.class = NULL, browse = FALSE) .returns(x, pad = NULL, lag)
returns(x, ...) ## Default S3 method: returns(x, t = NULL, period = NULL, complete.first = TRUE, pad = NULL, position = NULL, weights = NULL, rebalance.when = NULL, lag = 1, na.rm = FALSE, ..., na.warn = FALSE) ## S3 method for class 'zoo' returns(x, period = NULL, complete.first = TRUE, pad = NULL, position = NULL, weights = NULL, rebalance.when = NULL, lag = 1, na.rm = FALSE, ...) ## S3 method for class 'p_returns' print(x, ..., year.rows = TRUE, month.names = NULL, zero.print = "0", plus = FALSE, digits = 1, na.print = NULL) ## S3 method for class 'p_returns' toLatex(object, ..., year.rows = TRUE, ytd = "YTD", month.names = NULL, eol = "\\\\", stand.alone = FALSE) ## S3 method for class 'p_returns' toHTML(x, ..., year.rows = TRUE, ytd = "YTD", month.names = NULL, stand.alone = TRUE, table.style = NULL, table.class = NULL, th.style = NULL, th.class = NULL, td.style = "text-align:right; padding:0.5em;", td.class = NULL, tr.style = NULL, tr.class = NULL, browse = FALSE) .returns(x, pad = NULL, lag)
x |
for the default method, a For |
t |
timestamps. See arguments |
period |
Typically a string. Supported are All returns are computed as simple returns. They will
only be annualised with option |
complete.first |
logical. For holding-period returns such an monthly or yearly, should the first period (if incomplete) be used. |
pad |
either |
na.rm |
logical; see Details |
na.warn |
logical |
position |
either a numeric vector of the same length as the
number of assets (i.e. |
weights |
either a numeric vector of the same length as the
number of assets (i.e. |
rebalance.when |
a logical vector or a vector of integers indicating the
|
... |
further arguments to be passed to methods |
year.rows |
logical. If |
zero.print |
character. How to print zero values. |
na.print |
character. How to print |
plus |
logical. Add a ‘ |
lag |
The lag for computing returns. A positive integer,
defaults to one; ignored for time-weighted returns
or if |
object |
an object of class |
month.names |
character: names of months. Default is an
abbreviated month name as provided by the
locale. That may cause trouble, notably with
|
digits |
number of digits in table |
ytd |
header for YTD |
eol |
character |
stand.alone |
logical or character |
table.class |
character |
table.style |
character |
th.class |
character |
th.style |
character |
td.class |
character |
td.style |
character |
tr.class |
character |
tr.style |
character |
browse |
logical: open table in browser? |
returns
is a generic function. It computes
simple returns: current values divided by prior
values minus one. The default method works for
numeric vectors/matrices. The function
.returns
does the actual computations and may
be used when a ‘raw’ return computation is
needed.
When a timestamp is available, returns
can
compute returns for specific calendar periods. See
argument period
.
returns
may compute returns for a portfolio
specified in weights
or position
. The
portfolio is rebalanced at rebalance.when
; the
default is every period. Weights need not sum to
one. A zero-weight portfolio, or a portfolio that
never rebalances (e.g. with rebalance.when
set
to FALSE
), will result in a zero return.
rebalance.when
may either be logical,
integers or of the same class as a timestamp
(e.g. Date
).
Removing NA
s (by setting na.rm
to
TRUE
) is limited to the following types of
holding-period returns: ann
, total/itd
,
ytd
, mtd
. In each case, the first and
latest available finite values are used for computing
returns. For multivariate series x
, this can lead
to returns being computed for differing periods.
If called as returns(x)
: a numeric vector or
matrix, possibly with a class attribute (e.g. for a
zoo
series).
If called with a period
argument: an object of
class "p_returns"
(period returns), which is a
numeric vector of returns with attributes t
(timestamp) and period
. Main use is to have
methods that pretty-print such period returns; currently,
there are methods for toLatex
and
toHTML
.
In some cases, additional attributes may be attached:
when portfolio returns were computed (i.e. argument
weights
was specified), there are attributes
holdings
and contributions
. For
holding-period returns, there may be a logical attribute
is.annualised
, and an attribute from.to
,
which tells the start and end date of the holding
period.
Enrico Schumann <[email protected]>
Schumann, E. (2023) Portfolio Management with R.
https://enricoschumann.net/R/packages/PMwR/;
in particular, see
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#computing-returns
x <- 101:105 returns(x) returns(x, pad = NA) returns(x, pad = NA, lag = 2) ## monthly returns t <- seq(as.Date("2012-06-15"), as.Date("2012-12-31"), by = "1 day") x <- seq_along(t) + 1000 returns(x, t = t, period = "month") returns(x, t = t, period = "month", complete.first = FALSE) ### formatting print(returns(x, t = t, period = "month"), plus = TRUE, digits = 0) ## returns per year (annualised returns) returns(x, t = t, period = "ann") ## less than one year, not annualised returns(x, t = t, period = "ann!") ## less than one year, *but* annualised is.ann <- function(x) attr(x, "is.annualised") is.ann(returns(x, t = t, period = "ann")) ## FALSE is.ann(returns(x, t = t, period = "ann!")) ## TRUE ## with weights and fixed rebalancing times prices <- cbind(p1 = 101:105, p2 = rep(100, 5)) R <- returns(prices, weights = c(0.5, 0.5), rebalance.when = 1) ## ... => resulting weights h <- attr(R, "holdings") h*prices / rowSums(h*prices) ## p1 p2 ## [1,] 0.5000000 0.5000000 ## <== only initial weights are .5/.5 ## [2,] 0.5024631 0.4975369 ## [3,] 0.5049020 0.4950980 ## [4,] 0.5073171 0.4926829 ## [5,] 0.5097087 0.4902913
x <- 101:105 returns(x) returns(x, pad = NA) returns(x, pad = NA, lag = 2) ## monthly returns t <- seq(as.Date("2012-06-15"), as.Date("2012-12-31"), by = "1 day") x <- seq_along(t) + 1000 returns(x, t = t, period = "month") returns(x, t = t, period = "month", complete.first = FALSE) ### formatting print(returns(x, t = t, period = "month"), plus = TRUE, digits = 0) ## returns per year (annualised returns) returns(x, t = t, period = "ann") ## less than one year, not annualised returns(x, t = t, period = "ann!") ## less than one year, *but* annualised is.ann <- function(x) attr(x, "is.annualised") is.ann(returns(x, t = t, period = "ann")) ## FALSE is.ann(returns(x, t = t, period = "ann!")) ## TRUE ## with weights and fixed rebalancing times prices <- cbind(p1 = 101:105, p2 = rep(100, 5)) R <- returns(prices, weights = c(0.5, 0.5), rebalance.when = 1) ## ... => resulting weights h <- attr(R, "holdings") h*prices / rowSums(h*prices) ## p1 p2 ## [1,] 0.5000000 0.5000000 ## <== only initial weights are .5/.5 ## [2,] 0.5024631 0.4975369 ## [3,] 0.5049020 0.4950980 ## [4,] 0.5073171 0.4926829 ## [5,] 0.5097087 0.4902913
Historical Prices of the REXP.
data("REXP")
data("REXP")
A data frame with 502 observations on the following variable:
REXP
a numeric vector
Daily prices.
str(REXP)
str(REXP)
Scale time series so that they can be better compared.
scale1(x, ...) ## Default S3 method: scale1(x, ..., when = "first.complete", level = 1, centre = FALSE, scale = FALSE, geometric = TRUE, total.g = NULL) ## S3 method for class 'zoo' scale1(x, ..., when = "first.complete", level = 1, centre = FALSE, scale = FALSE, geometric = TRUE, inflate = NULL, total.g = NULL)
scale1(x, ...) ## Default S3 method: scale1(x, ..., when = "first.complete", level = 1, centre = FALSE, scale = FALSE, geometric = TRUE, total.g = NULL) ## S3 method for class 'zoo' scale1(x, ..., when = "first.complete", level = 1, centre = FALSE, scale = FALSE, geometric = TRUE, inflate = NULL, total.g = NULL)
x |
a time series |
when |
origin: for the default method, either a string or
numeric (integer). Allowed strings are
|
level |
numeric |
centre |
logical |
scale |
logical or numeric |
geometric |
logical: if |
inflate |
numeric: an annual rate at which the series is inflated (or deflated if negative) |
total.g |
numeric: to total growth rate (or total return) of a series |
... |
other arguments passed to methods |
This is a generic function, with methods for numeric
vectors and matrices, and zoo
objects.
An object of the same type as x
.
Enrico Schumann
Schumann, E. (2023) Portfolio Management with R.
https://enricoschumann.net/PMwR/;
in particular, see
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#scaling-series
scale1(cumprod(1 + c(0, rnorm(20, sd = 0.02))), level = 100)
scale1(cumprod(1 + c(0, rnorm(20, sd = 0.02))), level = 100)
Compute up and down streaks for time-series.
streaks(x, ...) ## Default S3 method: streaks(x, up = 0.2, down = -up, initial.state = NA, y = NULL, relative = TRUE, ...) ## S3 method for class 'zoo' streaks(x, up = 0.2, down = -up, initial.state = NA, y = NULL, relative = TRUE, ...) ## S3 method for class 'NAVseries' streaks(x, up = 0.2, down = -up, initial.state = NA, bm = NULL, relative = TRUE, ...)
streaks(x, ...) ## Default S3 method: streaks(x, up = 0.2, down = -up, initial.state = NA, y = NULL, relative = TRUE, ...) ## S3 method for class 'zoo' streaks(x, up = 0.2, down = -up, initial.state = NA, y = NULL, relative = TRUE, ...) ## S3 method for class 'NAVseries' streaks(x, up = 0.2, down = -up, initial.state = NA, bm = NULL, relative = TRUE, ...)
x |
a price series |
initial.state |
|
up |
a number, such as 0.1 (i.e. 10%) |
down |
a negative number, such as -0.1 (i.e. -10%) |
y |
another price series |
bm |
another price series. Mapped to ‘ |
relative |
logical |
... |
other arguments passed to methods |
streaks
is a generic function. It computes
series of uninterrupted up and down movements
(‘streaks’) in a price series. Uninterrupted
is meant in the sense that no countermovement of
down
(up
) percent or more occurs in up
(down) movements.
There are methods for numeric vectors, and
NAVseries
and zoo
objects.
The turning points (extreme points) are computed with the benefit of hindsight: the starting point (the low) of an up streak can only be determined once the streak is triggered, i.e. the up streak has already run its minimum amount. Vice versa for down streaks.
When ‘up
’ and ‘down
’ are
not equal, results may be inconsistent: in the
current implementation, streaks
alternates
between up and down streaks. Suppose up
is
large compared with down
, i.e. it takes long
to trigger up streaks, but they are easily
broken. Down streaks, on the other hand, are quickly
triggered but rarely broken. Now suppose that a down
streak is broken by an up streak: it may then well be
that the up streak would never have been counted as
such, because it was actually broken itself by
another down streak. The implementation for differing
values of ‘up
’ and ‘down
’
may change in the future.
A data.frame
:
start |
beginning of streak |
end |
end of streak |
state |
|
return , change
|
the return over the streak. If |
Enrico Schumann <[email protected]>
Schumann, E. (2023) Portfolio Management with R.
https://enricoschumann.net/PMwR/; in particular, see
https://enricoschumann.net/R/packages/PMwR/manual/PMwR.html#drawdowns-streaks
streaks(DAX[[1]], t = as.Date(row.names(DAX))) ## results <- streaks(x = <...>, y = <...>) ## ## ===> *arithmetic* excess returns ## x[results$end]/x[results$start] - ## y[results$end]/y[results$start] ## ===> *geometric* excess returns ## x[results$end]/x[results$start] / ## (y[results$end]/y[results$start]) - 1
streaks(DAX[[1]], t = as.Date(row.names(DAX))) ## results <- streaks(x = <...>, y = <...>) ## ## ===> *arithmetic* excess returns ## x[results$end]/x[results$start] - ## y[results$end]/y[results$start] ## ===> *geometric* excess returns ## x[results$end]/x[results$start] / ## (y[results$end]/y[results$start]) - 1
The toHTML
function is imported from package textutils.
Help is available at textutils::toHTML
.
Say library("textutils")
in your code to use the function.
Functions to help analyse trades (as opposed to profit-and-loss series)
scale_trades(amount, price, timestamp, aggregate = FALSE, fun = NULL, ...) split_trades(amount, price, timestamp, aggregate = FALSE, drop.zero = FALSE) limit(amount, price, timestamp, lim, tol = 1e-8) scale_to_unity(amount) close_on_first(amount) tw_exposure(amount, timestamp, start, end, abs.value = TRUE)
scale_trades(amount, price, timestamp, aggregate = FALSE, fun = NULL, ...) split_trades(amount, price, timestamp, aggregate = FALSE, drop.zero = FALSE) limit(amount, price, timestamp, lim, tol = 1e-8) scale_to_unity(amount) close_on_first(amount) tw_exposure(amount, timestamp, start, end, abs.value = TRUE)
amount |
notionals |
price |
a vector of prices |
timestamp |
a vector. |
aggregate |
|
fun |
a function |
lim |
a maximum absolute position size |
start |
optional time |
end |
optional time |
drop.zero |
logical. If |
abs.value |
logical. If |
... |
passed on to fun |
tol |
numeric |
scale_trades
takes a vector of notionals, prices and
scales all trades along the paths so that the maximum
exposure is 1.
The default fun
divides every element of a vector
n
by max(abs(cumsum(n)))
. If user-specified,
the function fun
needs to take a vector of notionals
(changes in position.)
split_trades
decomposes a trade list into single
trades, where a single trade comprises those trades from a
zero position to the next zero position. Note that the
trades must be sorted chronologically.
Either a list or a list-of-lists.
Enrico Schumann
n <- c(1,1,-3,-1,2) p <- 100 + 1:length(n) timestamp <- 1:length(n) scale_trades(n, p, timestamp) scale_trades(n, p, timestamp, TRUE) ## each _trade_ gets scaled split_trades(n, p, timestamp) split_trades(n, p, timestamp, TRUE) ## almost like the original series ## effect of 'drop.zero' P <- c(100, 99, 104, 103, 102, 105, 104) ## price series S <- c( 0, 1, 1, 0, 0, 1, 0) ## position to be held dS <- c(0, diff(S)) ## change in position ==> trades t <- seq_along(P) #### ==> 1) with all zero amounts split_trades(amount = dS, price = P, timestamp = t) #### ==> 2) without zero-amount trades split_trades(amount = dS, price = P, timestamp = t, drop.zero = TRUE) #### ==> 3) without all zero-amounts zero <- dS == 0 split_trades(amount = dS[!zero], price = P[!zero], timestamp = t[!zero])
n <- c(1,1,-3,-1,2) p <- 100 + 1:length(n) timestamp <- 1:length(n) scale_trades(n, p, timestamp) scale_trades(n, p, timestamp, TRUE) ## each _trade_ gets scaled split_trades(n, p, timestamp) split_trades(n, p, timestamp, TRUE) ## almost like the original series ## effect of 'drop.zero' P <- c(100, 99, 104, 103, 102, 105, 104) ## price series S <- c( 0, 1, 1, 0, 0, 1, 0) ## position to be held dS <- c(0, diff(S)) ## change in position ==> trades t <- seq_along(P) #### ==> 1) with all zero amounts split_trades(amount = dS, price = P, timestamp = t) #### ==> 2) without zero-amount trades split_trades(amount = dS, price = P, timestamp = t, drop.zero = TRUE) #### ==> 3) without all zero-amounts zero <- dS == 0 split_trades(amount = dS[!zero], price = P[!zero], timestamp = t[!zero])
Compute prices for a portfolio based on outstanding shares (units).
unit_prices(NAV, cashflows, initial.price, initial.units = 0, cf.included = TRUE, round.price = NULL, round.units = NULL)
unit_prices(NAV, cashflows, initial.price, initial.units = 0, cf.included = TRUE, round.price = NULL, round.units = NULL)
NAV |
a dataframe of two columns: timestamp and net asset value. There should be no duplicated timestamps. Column names are ignored; the function assumes timestamp is the first column, NAV the second. |
cashflows |
a data.frame of two or three columns: timestamp, cashflow and (optionally) an id or account. Column names are ignored; the function assumes timestamp is the first column, the external cashflows the second, and an account/id the third. |
initial.price |
initial price; ignored when |
initial.units |
number of outstanding units before first cashflow |
cf.included |
|
round.price |
round unit prices: |
round.units |
round number of units: |
This function is experimental, and its interface is not stable yet.
The function may be used to compute the returns for a portfolio with external cashflows, i.e. what is usually called time-weighted returns. Note that 'cashflows' can also comprise other positions that are added or removed from the portfolio without affecting performance.
A data.frame
with one row for each row in
NAV
:
timestamp |
the timestamp, as provided in argument
|
NAV |
total NAV, as provided in argument |
price |
NAV per unit |
units |
outstanding units (i.e. shares) after cashflows |
Attached as an attribute is a data.frame
transactions
, with as many rows as the provided
argument cashflows
, which provides the number of
units created/destroyed for each cashflow.
Enrico Schumann
Schumann, E. (2024) Portfolio Management with R.
https://enricoschumann.net/PMwR/
NAV <- data.frame(timestamp = seq(as.Date("2017-01-01"), as.Date("2017-01-10"), by = "1 day"), NAV = c(100:104, 205:209)) cf <- data.frame(timestamp = c(as.Date("2017-01-01"), as.Date("2017-01-06"), as.Date("2017-01-06")), cashflow = c(100, 50, 50), account = c("A", "A", "B")) (up <- unit_prices(NAV, cf, cf.included = TRUE)) ## timestamp NAV price units ## 1 2017-01-01 100 100.0000 1.000000 ## 2 2017-01-02 101 101.0000 1.000000 ## 3 2017-01-03 102 102.0000 1.000000 ## 4 2017-01-04 103 103.0000 1.000000 ## 5 2017-01-05 104 104.0000 1.000000 ## 6 2017-01-06 205 105.0000 1.952381 ## 7 2017-01-07 206 105.5122 1.952381 ## 8 2017-01-08 207 106.0244 1.952381 ## 9 2017-01-09 208 106.5366 1.952381 ## 10 2017-01-10 209 107.0488 1.952381 attr(up, "transactions") ## timestamp cashflow account units ## 1 2017-01-01 100 A 1.0000000 ## 2 2017-01-06 50 A 0.4761905 ## 3 2017-01-06 50 B 0.4761905
NAV <- data.frame(timestamp = seq(as.Date("2017-01-01"), as.Date("2017-01-10"), by = "1 day"), NAV = c(100:104, 205:209)) cf <- data.frame(timestamp = c(as.Date("2017-01-01"), as.Date("2017-01-06"), as.Date("2017-01-06")), cashflow = c(100, 50, 50), account = c("A", "A", "B")) (up <- unit_prices(NAV, cf, cf.included = TRUE)) ## timestamp NAV price units ## 1 2017-01-01 100 100.0000 1.000000 ## 2 2017-01-02 101 101.0000 1.000000 ## 3 2017-01-03 102 102.0000 1.000000 ## 4 2017-01-04 103 103.0000 1.000000 ## 5 2017-01-05 104 104.0000 1.000000 ## 6 2017-01-06 205 105.0000 1.952381 ## 7 2017-01-07 206 105.5122 1.952381 ## 8 2017-01-08 207 106.0244 1.952381 ## 9 2017-01-09 208 106.5366 1.952381 ## 10 2017-01-10 209 107.0488 1.952381 attr(up, "transactions") ## timestamp cashflow account units ## 1 2017-01-01 100 A 1.0000000 ## 2 2017-01-06 50 A 0.4761905 ## 3 2017-01-06 50 B 0.4761905
Valuation of financial objects: map an object into a quantity that is measured in a concrete (typically currency) unit.
valuation(x, ...) ## S3 method for class 'journal' valuation(x, multiplier = 1, cashflow = function(x, ...) x$amount * x$price, instrument = function(x, ...) "cash", flip.sign = TRUE, ...) ## S3 method for class 'position' valuation(x, vprice, multiplier = 1, do.sum = FALSE, price.unit, use.names = FALSE, verbose = TRUE, do.warn = TRUE, ...)
valuation(x, ...) ## S3 method for class 'journal' valuation(x, multiplier = 1, cashflow = function(x, ...) x$amount * x$price, instrument = function(x, ...) "cash", flip.sign = TRUE, ...) ## S3 method for class 'position' valuation(x, vprice, multiplier = 1, do.sum = FALSE, price.unit, use.names = FALSE, verbose = TRUE, do.warn = TRUE, ...)
x |
an object |
multiplier |
a numeric vector, typically with named elements |
cashflow |
either a numeric vector or a function that takes on argument (a journal) and transforms it into a numeric vector |
instrument |
either a character vector or a function that takes on argument (a journal) and transforms it into a character vector |
flip.sign |
logical. If |
vprice |
numeric: a matrix whose elements
correspond to those in |
do.sum |
logical: sum over positions |
use.names |
logical: use names of |
price.unit |
a named character vector. Not implemented. |
verbose |
logical |
do.warn |
logical |
... |
other arguments passed to methods |
This function is experimental, and the methods' interfaces are not stable yet.
valuation
is a generic function. Its
semantics suggest that an object (e.g. a financial
instrument or a position) is mapped into a concrete
quantity (such as an amount of some currency).
The journal
method transforms the
transactions in a journal into amounts of currency
(e.g, a sale of 100 shares of a company is
transformed into the value of these 100 shares).
The position
method takes a position
and returns the value (in currency units) of the
position.
depends on the object: for journals, a journal
Enrico Schumann <[email protected]>
Schumann, E. (2020) Portfolio Management with R. https://enricoschumann.net/R/packages/PMwR/
## valuing a JOURNAL j <- journal(amount = 10, price = 2) ## amount price ## 1 10 2 ## ## 1 transaction valuation(j, instrument = NA) ## amount price ## 1 -20 1 ## ## 1 transaction ## valuing a POSITION pos <- position(c(AMZN = -10, MSFT = 200)) ### contructing a price table: ### ==> P[i, j] must correspond to pos[i, j] P <- array(c(2200, 170), dim = c(1, 2)) colnames(P) <- instrument(pos) valuation(pos, vprice = P) ## AMZN MSFT ## [1,] -22000 34000 ### contructing a price table, alternative: ### a named vector ### ==> only works when there is only a single timestamp valuation(pos, vprice = c(MSFT = 170, AMZN = 2200)) all.equal(valuation(pos, vprice = P), valuation(pos, vprice = c(MSFT = 170, AMZN = 2200)))
## valuing a JOURNAL j <- journal(amount = 10, price = 2) ## amount price ## 1 10 2 ## ## 1 transaction valuation(j, instrument = NA) ## amount price ## 1 -20 1 ## ## 1 transaction ## valuing a POSITION pos <- position(c(AMZN = -10, MSFT = 200)) ### contructing a price table: ### ==> P[i, j] must correspond to pos[i, j] P <- array(c(2200, 170), dim = c(1, 2)) colnames(P) <- instrument(pos) valuation(pos, vprice = P) ## AMZN MSFT ## [1,] -22000 34000 ### contructing a price table, alternative: ### a named vector ### ==> only works when there is only a single timestamp valuation(pos, vprice = c(MSFT = 170, AMZN = 2200)) all.equal(valuation(pos, vprice = P), valuation(pos, vprice = c(MSFT = 170, AMZN = 2200)))