Portfolio Management with R (2024)

We buy one unit of an asset at a price of 100 euro andwe sell it for 101. We have made a profit of 1 euro.

This simple case is frequent enough that we should makethe required computation simple as well. The PMwRpackage provides a function pl, which for this casemay be called as follows.

pl(price = c(100, 101), amount = c(1, -1))
P/L total 1average buy 100average sell 101cum. volume 2‘P/L total’ is in units of instrument;‘volume’ is sum of /absolute/ amounts.

Instead of a vectors price and amount, you couldalso have passed a journal to pl.

In principle, profit/loss (P/L) isstraightforward to compute. Let \(x\)be a vector of the absolute amounts traded, and let \(p\)be a vector of the prices at which we traded. ThenP/L is just the difference between what wereceived when selling and what we paid when buying.

\begin{equation} \label{eq:pl1}\sum x^{\scriptsize\mathrm{sell}}_i p^{\scriptsize\mathrm{sell}}_i - \sum x^{\scriptsize\mathrm{buy}}_i p^{\scriptsize \mathrm{buy}}_i\end{equation}

This can be simplified when we impose the conventionthat sold amounts are negative.

\begin{eqnarray} \label{eq:pl2}\mathrm{P/L} &=& -\sum_{x<0} x_i p_i - \sum_{x>0} x_i p_i \\ &=& -\sum x_i p_i\end{eqnarray}

The function pl also expects this convention: in thecode example we had \(x = [1, -1]'\).

There are several ways to perform this basic (orfundamental, rather) computation. Here are some, alongwith some timing results.

amount <- rep(c(-100, 100), 500)price <- rep(100, length(amount))library("rbenchmark")benchmark( ## variations amount %*% price, sum(amount*price), crossprod(amount, price), t(amount*price) %*% rep(1, length(amount)), ## matrix summing ## settings columns = c("test", "elapsed", "relative"), order = "relative", replications = 50000)
 test elapsed relative1 amount %*% price 0.126 1.0003 crossprod(amount, price) 0.138 1.0952 sum(amount * price) 0.172 1.3654 t(amount * price) %*% rep(1, length(amount)) 0.440 3.492

pl uses the straightforward sum(amount * price)variant; only when very long vectors are used, itswitches to crossprod.

pl also accepts an argument instrument: if it isavailable, pl computes and reports P/L for eachinstrument separately. As an example, suppose youtraded shares of two German companies, Adidas andCommerzbank. We collect the transactions in a journal.

J <- readOrg(text = "| instrument | amount | price ||-------------+--------+-------|| Adidas | 50 | 100 || Adidas | -50 | 102 || Commerzbank | 500 | 8 || Commerzbank | -500 | 7 |")J <- as.journal(J)J
 instrument amount price1 Adidas 50 1002 Adidas -50 1023 Commerzbank 500 84 Commerzbank -500 74 transactions

We now pass the journal directly to pl.

pl(J)
Adidas P/L total 100 average buy 100 average sell 102 cum. volume 100Commerzbank P/L total -500 average buy 8 average sell 7 cum. volume 1000‘P/L total’ is in units of instrument;‘volume’ is sum of /absolute/ amounts.

An aside: since the shares are denominated in the samecurrency (euro), total profit is the same even if wehad left out the instruments; however, average buyingand selling prices becomes less informative.

Financial instruments differ not only in thecurrencies in which they are denominated. Manyderivatives have multipliers, which you may alsospecify. Suppose you have traded FGBL (German Bundfutures) and FESX (EURO STOXX 50 futures).One point of the FGBL translates into 1000 euros; forthe FESX it is 10 euros.

J <- readOrg(text = "| instrument | amount | price ||-------------+--------+--------|| FGBL MAR 16 | 1 | 165.20 || FGBL MAR 16 | -1 | 165.37 || FGBL JUN 16 | 1 | 164.12 || FGBL JUN 16 | -1 | 164.13 || FESX JUN 16 | 5 | 2910 || FESX JUN 16 | -5 | 2905 |")J <- as.journal(J)futures_pl <- pl(J, multiplier = c("^FGBL" = 1000, "^FESX" = 10), multiplier.regexp = TRUE)futures_pl
FESX JUN 16 P/L total -250 average buy 2910 average sell 2905 cum. volume 10FGBL JUN 16 P/L total 10 average buy 164.12 average sell 164.13 cum. volume 2FGBL MAR 16 P/L total 170 average buy 165.2 average sell 165.37 cum. volume 2‘P/L total’ is in units of instrument;‘volume’ is sum of /absolute/ amounts.

Note that we used a named vector to pass themultipliers. Per default, the names of this vector needto exactly match the instruments' names. Settingmultiplier.regexp to TRUE causes the names of themultiplier vector to be interpreted as (Perl-style)regular expressions.

At this point, it may be helpful to describe how we canaccess the results of such P/L computations (other thanhaving them printed to the console, that is). Thefunction pl always returns a list of lists – onelist for each instrument.

str(futures_pl)
List of 3 $ FESX JUN 16:List of 6 ..$ pl : num -250 ..$ realised : logi NA ..$ unrealised: logi NA ..$ buy : num 2910 ..$ sell : num 2905 ..$ volume : num 10 $ FGBL JUN 16:List of 6 ..$ pl : num 10 ..$ realised : logi NA ..$ unrealised: logi NA ..$ buy : num 164 ..$ sell : num 164 ..$ volume : num 2 $ FGBL MAR 16:List of 6 ..$ pl : num 170 ..$ realised : logi NA ..$ unrealised: logi NA ..$ buy : num 165 ..$ sell : num 165 ..$ volume : num 2 - attr(*, "class")= chr "pl" - attr(*, "along.timestamp")= logi FALSE - attr(*, "instrument")= chr [1:3] "FESX JUN 16" "FGBL JUN 16" "FGBL MAR 16"

Each such list contains numeric vectors: `pl','realised', 'unrealised', 'buy', 'sell','volume'. There may also be an additional vector,timestamp, to be described later in Section PL overtime. The vectors'realised' and 'unrealised' will be NA unlessalong.timestamp is not FALSE, also described in SectionPL over time.Data can be extracted by standard methods.

unlist(futures_pl[["FESX JUN 16"]])
 pl realised unrealised buy sell volume-250 NA NA 2910 2905 10
unlist(lapply(futures_pl, `[[`, "volume"))
FESX JUN 16 FGBL JUN 16 FGBL MAR 16 10 2 2

You may prefer sapply(...) instead ofunlist(lapply(...)). Also, extracting the raw P/Lnumbers of each instrument is so common that you can saypl(pl(...)). So you could have written:

pl(pl(J, multiplier = c("FGBL" = 1000, "FESX" = 10), multiplier.regexp = TRUE))
FESX JUN 16 FGBL JUN 16 FGBL MAR 16 -250 10 170

It is often more convenient to have the data presentedas a table, which we can create with as.data.frame.

as.data.frame(futures_pl)
 pl buy sell volumeFESX JUN 16 -250 2910.00 2905.00 10FGBL JUN 16 10 164.12 164.13 2FGBL MAR 16 170 165.20 165.37 2

Or if you like ASCII tables, with toOrg.

toOrg(as.data.frame(futures_pl), row.names = "instrument")
| instrument | pl | buy | sell | volume ||-------------+------+--------+--------+--------|| FESX JUN 16 | -250 | 2910 | 2905 | 10 || FGBL JUN 16 | 10 | 164.12 | 164.13 | 2 || FGBL MAR 16 | 170 | 165.2 | 165.37 | 2 |

We can also use pl when there are open positions.The simplest example is a journal of just one trade.

pl(amount = 1, price = 100)
P/L total NAaverage buy 100average sell NaNcum. volume 1‘P/L total’ is in units of instrument;‘volume’ is sum of /absolute/ amounts.‘sum(amount)’ is not zero: specify ‘vprice’ to compute P/L.

There can be no P/L number since the position is notclosed. But the message that is showntells us what to do: we need to specify a price atwhich the open position is to be valued. This valuationprice is passed as argument vprice (v as invaluation).

pl(amount = 1, price = 100, vprice = 105)
P/L total 5average buy 100average sell 105cum. volume 1'P/L total' is in units of instrument;'volume' is sum of /absolute/ amounts. average sell includes 'vprice'

Note that average sell takes into account the valuationprice that we specified.6 But cum. volume has remained1 since only 1 unit was actually traded.

A common task is to compute P/L over a specifiedperiod of time such as one trading day. The procedure forsuch a case requires three ingredients:

  1. the initial position and its valuation prices,
  2. the trades during the period,
  3. the final position and its prices.

Suppose yesterday, at market close, we had the followingpositions.

yesterday_position <- c("FESX JUN 16" = -20, "FGBL JUN 16" = 10)yesterday_prices <- c("FESX JUN 16" = 2912, "FGBL JUN 16" = 164.23)

Note that, as with the multipliers above, we use namedvectors for both the position and the prices: the namesindicate the instruments.

Trading just ended, and we have done the followingtrades.

J
 instrument amount price1 FGBL MAR 16 1 165.202 FGBL MAR 16 -1 165.373 FGBL JUN 16 1 164.124 FGBL JUN 16 -1 164.135 FESX JUN 16 5 2910.006 FESX JUN 16 -5 2905.006 transactions

Now we pass the three ingredients – initial position,trades during the period, and valuation prices for thefinal, open positions – to pl.

pl(J, initial.position = yesterday_position, initial.price = yesterday_prices, vprice = c("FESX JUN 16" = 2902, "FGBL JUN 16" = 164.60), multiplier = c("FGBL" = 1000, "FESX" = 10), multiplier.regexp = TRUE)
FESX JUN 16 P/L total 1750 average buy 2903.6 average sell 2910.6 cum. volume 10FGBL JUN 16 P/L total 3710 average buy 164.22 average sell 164.56 cum. volume 2FGBL MAR 16 P/L total 170 average buy 165.2 average sell 165.37 cum. volume 2‘P/L total’ is in units of instrument;‘volume’ is sum of /absolute/ amounts. for FESX JUN 16: average buy includes ‘vprice’ for FGBL JUN 16: average sell includes ‘vprice’

An aside: we could have simulated this computation bycreating one journal of the initial position andanother journal (with reversed amount signs) for thefinal position, merging all three journals and thencomputing P/L.

Portfolio Management with R (2024)
Top Articles
Latest Posts
Article information

Author: Tyson Zemlak

Last Updated:

Views: 6823

Rating: 4.2 / 5 (43 voted)

Reviews: 90% of readers found this page helpful

Author information

Name: Tyson Zemlak

Birthday: 1992-03-17

Address: Apt. 662 96191 Quigley Dam, Kubview, MA 42013

Phone: +441678032891

Job: Community-Services Orchestrator

Hobby: Coffee roasting, Calligraphy, Metalworking, Fashion, Vehicle restoration, Shopping, Photography

Introduction: My name is Tyson Zemlak, I am a excited, light, sparkling, super, open, fair, magnificent person who loves writing and wants to share my knowledge and understanding with you.