Profit/loss and position valuations over time

In this note we'll look into how to compute the profit/loss and the value of a position over time. Suppose we traded in two stocks, AMZN and MSFT.

library("PMwR")
library("zoo")
j <- journal(amount = c(10, -10, 20, 5),
             instrument = c("MSFT", "MSFT", "MSFT", "AMZN"),
             price = c(178, 178.5, 180.7, 2315),
             timestamp = as.Date(c("2020-05-04", "2020-05-04",
                                   "2020-05-05", "2020-05-05")))
j
   instrument   timestamp  amount   price
1        MSFT  2020-05-04      10   178.0
2        MSFT  2020-05-04     -10   178.5
3        MSFT  2020-05-05      20   180.7
4        AMZN  2020-05-05       5  2315.0

4 transactions

We want to look at the profit/loss and position value for the week from May, 4th to May, 8th. We store these dates in a variable timestamp.

timestamp <- seq(from = as.Date("2020-05-04"),
                 to   = as.Date("2020-05-08"),
                 by   = "1 day")
timestamp
[1] "2020-05-04" "2020-05-05" "2020-05-06" "2020-05-07" "2020-05-08"

We'll need prices at which to value open position: we prepare them as zoo series.

MSFT <- structure(c(178.84, 180.76, 182.54, 183.6, 184.68),
                  .Dim = c(5L, 1L),
                  .Dimnames = list(NULL, "us5949181045"),
                  index = structure(
                    c(18386, 18387, 18388, 18389, 18390), class = "Date"),
                  class = "zoo")

AMZN <- structure(c(2286.04, 2315.99, 2317.8, 2351.26, 2367.61, 2379.61),
                  .Dim = c(6L, 1L),
                  .Dimnames = list(NULL, "us0231351067"),
                  index = structure(
                    c(18383, 18386, 18387, 18388, 18389, 18390), class = "Date"),
                  class = "zoo")
AMZN <- AMZN[-4L]
MSFT
AMZN
           us5949181045
2020-05-04       178.84
2020-05-05       180.76
2020-05-06       182.54
2020-05-07       183.60
2020-05-08       184.68

           us0231351067
2020-05-01      2286.04
2020-05-04      2315.99
2020-05-05      2317.80
2020-05-07      2367.61
2020-05-08      2379.61

Note that the two series are, on purpose, not synchronous, and that AMZN is missing one price.

Next, we create a matrix of prices.

P <- pricetable(AMZN, MSFT, instrument = c("AMZN", "MSFT"))
P <- P[timestamp, c("MSFT", "AMZN"), missing = "previous"]
P
             MSFT    AMZN
2020-05-04 178.84 2315.99
2020-05-05 180.76 2317.80
2020-05-06 182.54 2317.80
2020-05-07 183.60 2367.61
2020-05-08 184.68 2379.61

Note that subsetting a pricetable results in a numeric matrix.

str(P)
num [1:5, 1:2] 179 181 183 184 185 ...
- attr(*, "dimnames")=List of 2
 ..$ : chr [1:5] "2020-05-04" "2020-05-05" "2020-05-06" "2020-05-07" ...
 ..$ : chr [1:2] "MSFT" "AMZN"

Let us first look at profit/loss first: we only need to provide the journal j of the trades as created above, the timestamps at which to compute profit/loss, and the prices for valuing open positions.

pl(j, along.timestamp = timestamp, vprice = P)
AMZN 
  timestamp     2020-05-04 2020-05-05 2020-05-06 2020-05-07 2020-05-08
  P/L total           0.00      14.00      14.00     263.05     323.05
  __ realised            0          0          0          0          0
  __ unrealised       0.00      14.00      14.00     263.05     323.05
  average buy         2315
  average sell         NaN
  cum. volume            0          5          5          5          5

MSFT 
  timestamp     2020-05-04 2020-05-05 2020-05-06 2020-05-07 2020-05-08
  P/L total            5.0        6.2       41.8       63.0       84.6
  __ realised            5          5          5          5          5
  __ unrealised        0.0        1.2       36.8       58.0       79.6
  average buy        179.8
  average sell       178.5
  cum. volume           20         40         40         40         40

‘P/L total’ is in units of instrument;
‘volume’ is sum of /absolute/ amounts.

We can also look at the position values over time. Note that the call to valuation is similar to that of pl: we pass the position, computed at the timestamps defined above; the prices for valuing the open positions, and we also set the argument use.names to TRUE. This ensures that valuation will look at the column names of P and associate the prices there with the instruments specified in the journal j.

valuation(position(j, when = timestamp), vprice = P, use.names = TRUE)
               AMZN   MSFT
2020-05-04     0.00    0.0
2020-05-05 11589.00 3615.2
2020-05-06 11589.00 3650.8
2020-05-07 11838.05 3672.0
2020-05-08 11898.05 3693.6

attr(,"position")
           AMZN MSFT
2020-05-04    0    0
2020-05-05    5   20
2020-05-06    5   20
2020-05-07    5   20
2020-05-08    5   20

Remarks

In function valuation, the argument vprice had been called price.table prior to package version 0.15, but has been renamed because since that version it very much resembles vprice in function pl. There are two differences, explained below. For clarity, I'll write pl/vprice and valuation/vprice, to differentiate between the two arguments.

  1. pl/vprice must always be ordered in time. That is, if you want to compute profit/loss over time, the timestamps must be in ascending order. If the timestamps passed via along.timestamp are not ordered, they will get sorted in time, and pl/vprice will get appropriately sorted, too.

    For valuation, by contrast, prices need not be in temporal order; in fact, valuation does not care at all about timestamps. That should be intuitive: profit/loss is essentially a difference between values, so the order of the values matters, which is not the case for positions.

  2. There is an argument use.names in valuation, which controls whether (column) names of valuation/vprice are interpreted as instruments and hence elements of valuation/vprice are matched accordingly. Default for use.names is FALSE, so such information is not used and the elements in valuation/vprice must directly correspond to the elements in the position matrix.

    Keeping the default at FALSE means that the behaviour prior to package version 0.15 is kept (though this may change in a future release). This default is different from pl: names of pl/vprice are always interpreted as instruments. The reason is simple: when computing pl from a journal, the user typically does not control the order of instruments in the resulting pl. Instruments get sorted by the function, but the user is not expected to anticipate this ordering.

    This is different when valuing a position: a position is essentially a matrix in which each row corresponds to a valuation timestamp, and each column corresponds to an instrument. So the user knows the structure of the position and can prepare valuation/vprice accordingly.