\(r_t\) measures the rate of return from \(t-1\) to \(t\). It is the percentage change in price given by:
\[ r_t = \frac{P_t-P_{t-1}}{P_{t-1}} = \frac{P_t}{P_{t-1}}-1 \]
One commonly used equality is
\[ P_t = P_{t-1} (1+r_t) \]
\(z_t\) measures the log return, also referred to as continuously compounded return. It is the first difference of the natural logarithm of prices.
\[ z_t = \ln P_t - \ln P_{t-1} = \ln \frac{P_t}{P_{t-1}} \]
The conversion between log return and simple return:
\[ \begin{aligned} \color{red}{z_t} &\color{red}{= \ln (1+r_t) }\\ r_t &= e^{z_t} -1 \end{aligned} \]
Q: Why is \(z_t\) continuously compounded return?
A: This is related to the constant \(e\), Euler number.
The discovery of the constant itself is credited to Jacob Bernoulli, who attempted to find the value of the following expression:
\[ e = \lim_{n\to\infty} \left( 1 + \frac{1}{n} \right)^n \]
If you put \(\$100\) in a bank with an annual interest rate of \(10\%\) and a yearly compounding period. What you get in a year can be expressed as follows:
\[ D_t = D_0 \left(1+\frac{R}{n}\right)^{nt} = 100\times \left(1+\frac{10\%}{1}\right)^{1\times1} = 110 \] Note that
\(\quad\) \(D_0\) is the initial deposit,
\(\quad\) \(D_t\) is the value of the deposit at time \(t\),
\(\quad\) \(R\) is the annual percentage rate (APR),
\(\quad\) \(n\) is the number of compounding periods in one year,
\(\quad\) \(t\) is the number of years from \(D_0\) to \(D_t\).
(For a bank deposit account, the quoted interest rate often refers to as “simple interest” which ignores compounding. For example, an interest rate of \(5\%\) payable every six months will be quoted as a simple interest of \(10\%\) per annum in the market.)
Q: What if we compound semi-annually?
A: That is when \(e\) equals 2.
\[ D_t = 100\times(1+\frac{10\%}{2})^{2\times1} = 110.25 \]
What if we compound monthly? \(\rightarrow\) \(n=12\)
\[ D_t = 100\times(1+\frac{10\%}{12})^{12\times1} = 110.47 \]
What if we compound daily? \(\rightarrow\) \(n=365\)
\[ D_t = 100\times(1+\frac{10\%}{365})^{365\times1} = 110.52 \]
\(\left(1+\frac{R}{n}\right)^{nt}\) can be rewritten as \(\left(1+\frac{1}{\frac{n}{R}}\right)^{\frac{n}{R} \cdot R \cdot t}\). We have
\[ \left(1+\frac{1}{\frac{n}{R}}\right)^{\frac{n}{R} \cdot R \cdot t} \to e^{Rt} \] as \(\frac{n}{R}\to \infty\).
Under continuous compounding,
\[ \color{red} {D_t = D_0\, e^{Rt}} . \]
R <- 0.1 # annual interest rate
t <- 1 # total time period
n <- 2 # compound frequency in one year
100 * (1+R/n)^{n*t}
## [1] 110.25
n <- 12
100 * (1+R/n)^{n*t}
## [1] 110.4713
n <- 365
100 * (1+R/n)^{n*t}
## [1] 110.5156
100 * exp(R*t)
## [1] 110.5171
Note that
\(D_t\) under continuous compounding is always larger than those of under fixed compounding frequencies. The higher the frequency, the larger the end value \(D_t\) is. This is due to the earnings from “interest-on-interest”.
\(\left(1+\frac{R}{n}\right)^{n}-1\) is the effective interest rate. This is the interest rate you get as a proportion to the amount you put in and it depends on the frequency of compounding.
Continuously compounded interest rate is the effective interest rate when compounded continuously \((n\to\infty).\)
Proof. 1 Show \(\ln (1+x) \approx x\) as \(x\to 0\).
Let \(f(x)=\ln (1+x)\), its first-order Taylor expansion at \(x\) close to \(0\) is:
\[ \begin{aligned} f(x) \approx f(0) + f^\prime(0)(x-0) \end{aligned} \]
The first derivative of \(f(x)\) is
\[ f^\prime(x) = \frac{1}{1+x}. \]
Hence
\[ \begin{aligned} f(x) &\approx f(0) + f^\prime(0)(x-0) \\ &= \ln(1) + 1\cdot x \\ &= x. \end{aligned} \]
\(\square\)
From prices to returns.
f_name <- "data/Titlon_equity_price_2014-2023_daily.csv"
titlon_data <- read_csv(f_name)
## group by ISIN, calculate returns
titlon_group <- titlon_data %>% group_by(ISIN)
groups <- titlon_group %>% group_split()
group_key <- titlon_group %>%
group_keys() %>%
mutate(id = row_number())
group_key
## # A tibble: 501 × 2
## ISIN id
## <chr> <int>
## 1 AU000000CSS3 1
## 2 AU0000057408 2
## 3 BMG0451H1170 3
## 4 BMG0670A1099 4
## 5 BMG067231032 5
## 6 BMG0702P1086 6
## 7 BMG1466R1732 7
## 8 BMG1466R2078 8
## 9 BMG173841013 9
## 10 BMG1738J1247 10
## # ℹ 491 more rows
# subset companies with more than 3 years' data
isin_vec <- titlon_group %>% tally() %>%
filter(n>(252*3)) %>%
pull(ISIN)
id_vec <- sapply(isin_vec, function(isin) which(group_key$ISIN==isin))
# data for one equity
i <- 4
groups[[id_vec[i]]]
## # A tibble: 951 × 46
## Date `Internal code` SecurityId CompanyId Symbol ISIN Name
## <date> <dbl> <dbl> <dbl> <chr> <chr> <chr>
## 1 2020-02-19 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 2 2020-02-20 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 3 2020-02-21 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 4 2020-02-24 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 5 2020-02-25 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 6 2020-02-26 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 7 2020-02-27 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 8 2020-02-28 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 9 2020-03-02 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 10 2020-03-03 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## # ℹ 941 more rows
## # ℹ 39 more variables: BestBidPrice <dbl>, BestAskPrice <dbl>, Open <dbl>,
## # High <dbl>, Low <dbl>, Close <dbl>, OfficialNumberOfTrades <dbl>,
## # OfficialVolume <dbl>, Price <dbl>, AdjustedPrice <dbl>, Dividends <dbl>,
## # LDividends <dbl>, CorpAdj <dbl>, DividendAdj <dbl>, Currency <chr>,
## # NumberOfShares <dbl>, Exchange <chr>, NOKPerForex <dbl>, mktcap <dbl>,
## # OSEBXmktshare_prevmnth <dbl>, OSEBXAlpha_prevmnth <dbl>, …
groups[[id_vec[i]]] %>% tail()
## # A tibble: 6 × 46
## Date `Internal code` SecurityId CompanyId Symbol ISIN Name
## <date> <dbl> <dbl> <dbl> <chr> <chr> <chr>
## 1 2023-11-17 2015535 1305295 12748 BWE BMG0702P1086 BW ENERGY…
## 2 2023-11-20 2015535 1305295 12748 BWE BMG0702P1086 BW ENERGY…
## 3 2023-11-21 2015535 1305295 12748 BWE BMG0702P1086 BW ENERGY…
## 4 2023-11-22 2015535 1305295 12748 BWE BMG0702P1086 BW ENERGY…
## 5 2023-11-23 2015535 1305295 12748 BWE BMG0702P1086 BW ENERGY…
## 6 2023-11-24 2015535 1305295 12748 BWE BMG0702P1086 BW ENERGY…
## # ℹ 39 more variables: BestBidPrice <dbl>, BestAskPrice <dbl>, Open <dbl>,
## # High <dbl>, Low <dbl>, Close <dbl>, OfficialNumberOfTrades <dbl>,
## # OfficialVolume <dbl>, Price <dbl>, AdjustedPrice <dbl>, Dividends <dbl>,
## # LDividends <dbl>, CorpAdj <dbl>, DividendAdj <dbl>, Currency <chr>,
## # NumberOfShares <dbl>, Exchange <chr>, NOKPerForex <dbl>, mktcap <dbl>,
## # OSEBXmktshare_prevmnth <dbl>, OSEBXAlpha_prevmnth <dbl>,
## # OSEBXBeta_prevmnth <dbl>, SMB <dbl>, HML <dbl>, LIQ <dbl>, MOM <dbl>, …
the_group <- groups[[id_vec[i]]]
the_group
## # A tibble: 951 × 46
## Date `Internal code` SecurityId CompanyId Symbol ISIN Name
## <date> <dbl> <dbl> <dbl> <chr> <chr> <chr>
## 1 2020-02-19 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 2 2020-02-20 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 3 2020-02-21 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 4 2020-02-24 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 5 2020-02-25 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 6 2020-02-26 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 7 2020-02-27 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 8 2020-02-28 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 9 2020-03-02 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## 10 2020-03-03 2015535 1305295 12748 BWE BMG0702P1086 BW Energ…
## # ℹ 941 more rows
## # ℹ 39 more variables: BestBidPrice <dbl>, BestAskPrice <dbl>, Open <dbl>,
## # High <dbl>, Low <dbl>, Close <dbl>, OfficialNumberOfTrades <dbl>,
## # OfficialVolume <dbl>, Price <dbl>, AdjustedPrice <dbl>, Dividends <dbl>,
## # LDividends <dbl>, CorpAdj <dbl>, DividendAdj <dbl>, Currency <chr>,
## # NumberOfShares <dbl>, Exchange <chr>, NOKPerForex <dbl>, mktcap <dbl>,
## # OSEBXmktshare_prevmnth <dbl>, OSEBXAlpha_prevmnth <dbl>, …
ticker <- the_group$Symbol[1]
ticker
## [1] "BWE"
the_group$Name[1]
## [1] "BW Energy Limited"
# convert to xts
library(quantmod)
the_group_xts <- xts(the_group[,c("AdjustedPrice")], order.by=the_group$Date)
# the_group_xts <- xts(the_group[,c("Price")], order.by=the_group$Date)
the_group_xts %>% str()
## An xts object on 2020-02-19 / 2023-11-24 containing:
## Data: double [951, 1]
## Columns: AdjustedPrice
## Index: Date [951] (TZ: "UTC")
the_group_xts
## AdjustedPrice
## 2020-02-19 24.150
## 2020-02-20 24.525
## 2020-02-21 24.370
## 2020-02-24 21.800
## 2020-02-25 20.355
## 2020-02-26 19.742
## 2020-02-27 17.350
## 2020-02-28 17.028
## 2020-03-02 17.294
## 2020-03-03 19.550
## ...
## 2023-11-13 29.300
## 2023-11-14 28.850
## 2023-11-15 28.750
## 2023-11-16 26.100
## 2023-11-17 25.850
## 2023-11-20 25.550
## 2023-11-21 24.500
## 2023-11-22 24.000
## 2023-11-23 24.100
## 2023-11-24 24.250
# from daily to monthly
prices_monthly <- the_group_xts %>% to.monthly(indexAt = "last", OHLC=FALSE)
prices_monthly %>% head(20)
## AdjustedPrice
## 2020-02-28 17.028
## 2020-03-31 9.070
## 2020-04-30 12.740
## 2020-05-29 14.554
## 2020-06-30 17.168
## 2020-07-31 17.170
## 2020-08-31 20.500
## 2020-09-30 16.750
## 2020-10-30 14.800
## 2020-11-30 20.920
## 2020-12-30 27.600
## 2021-01-29 24.180
## 2021-02-26 25.160
## 2021-03-31 26.880
## 2021-04-30 27.750
## 2021-05-31 23.950
## 2021-06-30 26.200
## 2021-07-30 26.750
## 2021-08-31 27.950
## 2021-09-30 27.900
prices_monthly %>% tail(20)
## AdjustedPrice
## 2022-04-29 27.50
## 2022-05-31 28.84
## 2022-06-30 25.56
## 2022-07-29 27.42
## 2022-08-31 24.82
## 2022-09-30 21.62
## 2022-10-31 26.30
## 2022-11-30 27.06
## 2022-12-30 25.14
## 2023-01-31 27.90
## 2023-02-28 29.24
## 2023-03-31 26.92
## 2023-04-28 28.60
## 2023-05-31 27.25
## 2023-06-30 25.80
## 2023-07-31 30.00
## 2023-08-31 25.65
## 2023-09-29 27.35
## 2023-10-31 27.90
## 2023-11-24 24.25
# price plot
plot(prices_monthly, main = sprintf("Monthly Price: %s", ticker))
# calculate monthly return by hand
simple_ret <- (diff(prices_monthly)/lag(prices_monthly)) %>% setNames("simple_return") # simple return
log_ret <- diff(log(prices_monthly)) %>% setNames("log_return") # log return, aka, continuously compounded return
merge(prices_monthly, simple_ret, log_ret)
## AdjustedPrice simple_return log_return
## 2020-02-28 17.028 NA NA
## 2020-03-31 9.070 -0.4673478976 -0.629886784
## 2020-04-30 12.740 0.4046306505 0.339774386
## 2020-05-29 14.554 0.1423861852 0.133119220
## 2020-06-30 17.168 0.1796069809 0.165181316
## 2020-07-31 17.170 0.0001164958 0.000116489
## 2020-08-31 20.500 0.1939429237 0.177261211
## 2020-09-30 16.750 -0.1829268293 -0.202026628
## 2020-10-30 14.800 -0.1164179104 -0.123771078
## 2020-11-30 20.920 0.4135135135 0.346078458
## 2020-12-30 27.600 0.3193116635 0.277110134
## 2021-01-29 24.180 -0.1239130435 -0.132289928
## 2021-02-26 25.160 0.0405293631 0.039729587
## 2021-03-31 26.880 0.0683624801 0.066127084
## 2021-04-30 27.750 0.0323660714 0.031853325
## 2021-05-31 23.950 -0.1369369369 -0.147267516
## 2021-06-30 26.200 0.0939457203 0.089791087
## 2021-07-30 26.750 0.0209923664 0.020775063
## 2021-08-31 27.950 0.0448598131 0.043882726
## 2021-09-30 27.900 -0.0017889088 -0.001790511
## 2021-10-29 27.550 -0.0125448029 -0.012624153
## 2021-11-30 21.100 -0.2341197822 -0.266729495
## 2021-12-30 20.100 -0.0473933649 -0.048553225
## 2022-01-31 22.900 0.1393034826 0.130417095
## 2022-02-28 23.300 0.0174672489 0.017316450
## 2022-03-31 27.000 0.1587982833 0.147383505
## 2022-04-29 27.500 0.0185185185 0.018349139
## 2022-05-31 28.840 0.0487272727 0.047577308
## 2022-06-30 25.560 -0.1137309293 -0.120734683
## 2022-07-29 27.420 0.0727699531 0.070244045
## 2022-08-31 24.820 -0.0948212983 -0.099622894
## 2022-09-30 21.620 -0.1289282836 -0.138030968
## 2022-10-31 26.300 0.2164662350 0.195950127
## 2022-11-30 27.060 0.0288973384 0.028487684
## 2022-12-30 25.140 -0.0709534368 -0.073596420
## 2023-01-31 27.900 0.1097852029 0.104166486
## 2023-02-28 29.240 0.0480286738 0.046910946
## 2023-03-31 26.920 -0.0793433653 -0.082668130
## 2023-04-28 28.600 0.0624071322 0.060537213
## 2023-05-31 27.250 -0.0472027972 -0.048353197
## 2023-06-30 25.800 -0.0532110092 -0.054679029
## 2023-07-31 30.000 0.1627906977 0.150822890
## 2023-08-31 25.650 -0.1450000000 -0.156653810
## 2023-09-29 27.350 0.0662768031 0.064172957
## 2023-10-31 27.900 0.0201096892 0.019910160
## 2023-11-24 24.250 -0.1308243728 -0.140210071
# calculate monthly return using `PerformanceAnalytics::Return.calculate`
library(PerformanceAnalytics)
merge(Return.calculate(prices_monthly, method = "discrete"),
Return.calculate(prices_monthly, method = "log") ) %>%
fortify() %>%
setNames(c("Date", "Simple Ret", "Log Ret")) %>%
knitr::kable(digits = 5, escape=F, caption="Using `PerformanceAnalytics::Return.calculate`") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = F, latex_options="scale_down") %>%
scroll_box(height = "500px")
Date | Simple Ret | Log Ret |
---|---|---|
2020-02-28 | ||
2020-03-31 | -0.46735 | -0.62989 |
2020-04-30 | 0.40463 | 0.33977 |
2020-05-29 | 0.14239 | 0.13312 |
2020-06-30 | 0.17961 | 0.16518 |
2020-07-31 | 0.00012 | 0.00012 |
2020-08-31 | 0.19394 | 0.17726 |
2020-09-30 | -0.18293 | -0.20203 |
2020-10-30 | -0.11642 | -0.12377 |
2020-11-30 | 0.41351 | 0.34608 |
2020-12-30 | 0.31931 | 0.27711 |
2021-01-29 | -0.12391 | -0.13229 |
2021-02-26 | 0.04053 | 0.03973 |
2021-03-31 | 0.06836 | 0.06613 |
2021-04-30 | 0.03237 | 0.03185 |
2021-05-31 | -0.13694 | -0.14727 |
2021-06-30 | 0.09395 | 0.08979 |
2021-07-30 | 0.02099 | 0.02078 |
2021-08-31 | 0.04486 | 0.04388 |
2021-09-30 | -0.00179 | -0.00179 |
2021-10-29 | -0.01254 | -0.01262 |
2021-11-30 | -0.23412 | -0.26673 |
2021-12-30 | -0.04739 | -0.04855 |
2022-01-31 | 0.13930 | 0.13042 |
2022-02-28 | 0.01747 | 0.01732 |
2022-03-31 | 0.15880 | 0.14738 |
2022-04-29 | 0.01852 | 0.01835 |
2022-05-31 | 0.04873 | 0.04758 |
2022-06-30 | -0.11373 | -0.12073 |
2022-07-29 | 0.07277 | 0.07024 |
2022-08-31 | -0.09482 | -0.09962 |
2022-09-30 | -0.12893 | -0.13803 |
2022-10-31 | 0.21647 | 0.19595 |
2022-11-30 | 0.02890 | 0.02849 |
2022-12-30 | -0.07095 | -0.07360 |
2023-01-31 | 0.10979 | 0.10417 |
2023-02-28 | 0.04803 | 0.04691 |
2023-03-31 | -0.07934 | -0.08267 |
2023-04-28 | 0.06241 | 0.06054 |
2023-05-31 | -0.04720 | -0.04835 |
2023-06-30 | -0.05321 | -0.05468 |
2023-07-31 | 0.16279 | 0.15082 |
2023-08-31 | -0.14500 | -0.15665 |
2023-09-29 | 0.06628 | 0.06417 |
2023-10-31 | 0.02011 | 0.01991 |
2023-11-24 | -0.13082 | -0.14021 |
For simple return, the cumulative return from time \(0\) to time \(T\) is given by:
\[ r_{0:T} = \Pi_{t=1}^T (1+r_t) -1. \]
For log return
\[ z_{0:T} = \sum_{t=1}^T z_t \]
Note that we have to back out the simple return from the log return
\[ r_{0:T} = \exp(z_{0:T}) -1 \]
return_monthly_simple <- Return.calculate(prices_monthly, method = "discrete")
return_monthly_simple[1,] <- 0
cumulative_returns_simple <- cumprod(1 + return_monthly_simple) - 1
# plot price, monthly ret, and cumu ret
par(mfrow=c(3,1))
plot(prices_monthly, main = sprintf("Monthly adjPrice: %s", ticker))
plot(return_monthly_simple,
main = sprintf("Monthly Return: %s", ticker))
plot(cumulative_returns_simple,
main = sprintf("Monthly Cumulative Return: %s", ticker))
return_monthly_log <- Return.calculate(prices_monthly, method = "log")
return_monthly_log[1,] <- 0
cumulative_returns_log <- exp(cumsum(return_monthly_log)) - 1 # back out the simple return
par(mfrow=c(3,1))
plot(prices_monthly, main = sprintf("Monthly adjPrice: %s", ticker))
plot(return_monthly_log,
main = sprintf("Monthly Return: %s", ticker))
plot(cumulative_returns_log,
main = sprintf("Monthly Cumulative Return: %s", ticker))
The two types act very differently when it comes to aggregation. Each has an advantage over the other:
simple returns aggregate across assets
The simple return of a portfolio is the weighted sum of the simple returns of the constituents of the portfolio.
log returns aggregate across time
The log return for a time period is the sum of the log returns of partitions of the time period. For example, the log return for a year is the sum of the log returns of the days within the year.
Multiperiod returns are the generalized case of cumulative return by allowing any holding period \(k\le T\).
The \(k\)-period simple return from time \(t-k\) to \(t\) is given by
\[ r_{t}(k) = \frac{P_t-P_{t-k}}{P_{t-k}}. \]
It can be expressed as in terms of one-period returns as follows:
\[ \begin{aligned} r_t(k) &= \prod_{j=0}^{k-1}(1+r_{t-j}) -1 \\ &= (1+r_t)(1+r_{t-1})\cdots (1+r_{t-k+1})-1 . \end{aligned} \]
We always like to talk in terms of annual performance as people like to know how much they can expect to make a year in percentage terms. That is why in most of the fund reports, you will find a standard metric called annualized returns. It is also known as the Compound Annual Growth Rate (CAGR) or the Geometric Annual Return.
Annualized return under simple returns \(r_t^A\) is given by:
\[ \begin{split} (1+r_t^A)^k &= 1+r_t(k) \\ r_t^A &= \big(1+r_t(k) \big)^{\frac{1}{k}}-1 = \left[\prod_{j=1}^{k-1}(1+r_{t-j})\right]^{\frac{1}{k}}-1 . \end{split} \]
The \(k\)-period log return is given by
\[ z_t(k) = \ln \frac{P_t}{P_{t-k}}. \]
It is the sum of the \(k\) one-period log returns:
\[ z_t(k) = \sum_{j=0}^{k-1} z_{t-j} = z_t + z_{t-1} + \cdots + z_{t-k+1} \]
Annualized return under continuously compounding \(z_t^A\) is given by:
\[ \begin{aligned} k\,z_t^A &= z_t(k) \\ z_t^A &= \frac{1}{k}z_t(k) = \frac{1}{k} \sum_{j=0}^{k-1} z_{t-j}, \end{aligned} \] which is the average one-period log returns.
To summarize in one table
Simple returns | Log returns | Back out simple returns | |
---|---|---|---|
Single period | \(r_t=\frac{P_t}{P_{t-1}}-1\) | \(z_t=\ln \frac{P_t}{P_{t-1}}\) | \(r_t = \exp(z_t)-1\) |
Multiperiod | \(r_t(k) = \prod_{j=0}^{k-1}(1+r_{t-j}) -1\) | \(z_t(k) = \sum_{j=0}^{k-1} z_{t-j}\) | \(r_t(k) = \exp\big(z_t(k)\big)-1\) |
Annualized | \(r_t^A = \left[\prod_{j=0}^{k-1}(1+r_{t-j})\right]^{\frac{1}{k}}-1\) | \(z_t^A = \frac{1}{k} \sum_{j=0}^{k-1} z_{t-j}\) | \(r_t^A = \exp(z_t^A)-1\) |
Note that the annualized return here assumes the single period returns are annual returns.
In case of higher frequency data than annual, i.e., daily, weekly and monthly, we have to compound by the number of periods in a year.
daily single period: \(r_t^A = \left[\prod_{j=0}^{k-1}(1+r_{t-j})\right]^{\frac{1}{k}\cdot \color{red}{252}}-1\) for simple return, \(z_t^A = \frac{252}{k} \sum_{j=0}^{k-1} z_{t-j}\) for log return.
weekly \(r_t^A = \left[\prod_{j=0}^{k-1}(1+r_{t-j})\right]^{\frac{1}{k}\cdot \color{red}{52}}-1\) for simple return, \(z_t^A = \frac{52}{k} \sum_{j=0}^{k-1} z_{t-j}\) for log return.
monthly \(r_t^A = \left[\prod_{j=0}^{k-1}(1+r_{t-j})\right]^{\frac{1}{k}\cdot \color{red}{12}}-1\) for simple return, \(z_t^A = \frac{12}{k} \sum_{j=0}^{k-1} z_{t-j}\) for log return.
quarterly \(r_t^A = \left[\prod_{j=0}^{k-1}(1+r_{t-j})\right]^{\frac{1}{k}\cdot \color{red}{4}}-1\) for simple return, \(z_t^A = \frac{4}{k} \sum_{j=0}^{k-1} z_{t-j}\) for log return.
Consider a buy-and-hold portfolio invested in \(k\) different assets. The value at time \(t\) is
\[ V_t = \sum_{i=1}^k n_i P_{i,t} \] where \(n_i\) is the number of shares invested in asset \(i\).
Buy-and-hold portfolio: Portfolio weights from the initial portfolio are allowed to change over time as prices of the underlying assets change over time. In this case no rebalancing of the portfolio is done. \(n_i\) is constant for each asset \(i\) over the holding period. The weight increases if an asset’s price increases, and decreases otherwise. This strategy is passive and does not trade further except for the initial asset allocation.
Another trading strategy is rebalancing (monthly). This is to buy and trade at the end of each rebalance period such that your portfolio aligns with your target allocation. Regularly rebalancing a portfolio ensures that the investor maintains the desired risk and return characteristics. For instance, if the weight of a particular asset class has increased significantly due to strong performance, rebalancing involves selling a portion of that asset and reinvesting the proceeds in other assets to restore the desired portfolio weight. This is a rebalanced portfolio.
The simple one-period return of the portfolio is a weighted average of the returns of component stocks.
\[ r_{p,t} = \frac{V_t}{V_{t-1}}-1 = \sum_{i=1}^k w_{i,t} r_{i,t} \]
This result is useful. We can use the property to get the expected return and variance of the portfolio as:
\[ \begin{aligned} E[r_{p,t}] &= \sum_{i=1}^k w_{i,t} E[r_{i,t}] \\ \text{Var}[r_{p,t}] &= \sum_{i=1}^k\sum_{j=1}^k w_{i,t}\,w_{j,t}\,\text{Cov}(r_{i,t}, r_{j,t}) \end{aligned} \]
Proof. 2 Show \(r_{p,t} = \sum_{i=1}^k w_i r_{i,t}\).
\[ \begin{aligned} r_{p,t} &= \frac{V_t}{V_{t-1}} -1 \\ &= \frac{\sum_{i=1}^k n_iP_{i,t}}{\sum_{j=1}^k n_jP_{j,t-1}} -1 \\ &= \sum_{i=1}^k \frac{n_iP_{i,t-1}}{\sum_{j=1}^k n_jP_{j,t-1}} \cdot \frac{P_{i,t}}{P_{i,t-1}} - 1 \qquad \text{(multiply and divide by } P_{i, t-1} )\\ &= \sum_i w_{i,t} (r_{i,t}+1) -1 \\ &= \sum_i w_{i,t} r_{i,t} \qquad (w_{i,t} \text{ sums to 1}) \end{aligned} \] where \(w_{i,t}= \frac{n_iP_{i,t-1}}{\sum_{j=1}^k n_jP_{j,t-1}}\) is the weight of asset \(i\) at time \(t-1\).
\(\square\)
Note that an asset’s weight in month \(t\), \(w_{i,t}\), is decided by the ratio of the value of the asset to the portfolio value at the beginning of the period, i.e., at time \(t-1\).
This cross-sectional additivity does not apply to log returns. In stead we have:
\[ \begin{aligned} z_{p,t} &= \ln\,\left(\frac{V_t}{V_{t-1}}\right) \\ &= \ln \, \left(\frac{\sum_{i=1}^k n_iP_{i,t}}{\sum_{j=1}^k n_jP_{j,t-1}} \right) \\ &= \ln \, \left(\sum_{i=1}^k\frac{ n_iP_{i,t-1}}{\sum_{j=1}^k n_jP_{j,t-1}} \cdot \frac{P_{i,t}}{P_{i,t-1}} \right) \\ &= \ln \, \left(\sum_{i=1}^k w_{i,t} \frac{P_{i,t}}{P_{i,t-1}} \right) \\ &= \ln \, \left(\sum_{i=1}^k w_{i,t} \exp(z_{i,t}) \right) . \end{aligned} \] The log return of the portfolio is not a linear function for the log returns of the components.
Because the continuously compounded portfolio return is not a weighted average of the individual asset continuously compounded returns, the analysis of portfolios is typically performed using simple returns and not continuously compounded returns.
On the other hand, the log returns are additive when we consider the time series of returns:
\[ z_{p,t}(k) = \sum_{j=0}^{k-1} z_{p,t-j}. \] Given the expected values and the covariances of the subperiod returns, it is then easy to compute the expected value and the variance of the full period return.
In contrast, the time series additivity does not apply to simple returns.
\[ r_{p, t}(k) = \prod_{j=0}^{k-1} (1+r_{p,t-j}) -1 \]
We use a portfolio consisting of 2 assets as an example.
Loop through assets to calculate monthly returns from daily price data.
# calculate monthly return
data_return <- titlon_group %>%
group_modify( ~{
xts(.$AdjustedPrice, order.by = .$Date) %>%
to.monthly(indexAt="last", OHLC=FALSE) %>%
Return.calculate() %>%
data.frame() %>%
rownames_to_column(var="Date")
}) %>% ungroup()
colnames(data_return)[3] <- "Return_monthly"
data_return
## # A tibble: 30,351 × 3
## ISIN Date Return_monthly
## <chr> <chr> <dbl>
## 1 AU000000CSS3 2021-05-31 NA
## 2 AU000000CSS3 2021-06-30 -0.0147
## 3 AU000000CSS3 2021-07-29 0.0746
## 4 AU000000CSS3 2021-08-30 0.208
## 5 AU000000CSS3 2021-09-30 -0.195
## 6 AU000000CSS3 2021-10-29 0.114
## 7 AU000000CSS3 2021-11-29 -0.000128
## 8 AU000000CSS3 2021-12-29 -0.0640
## 9 AU000000CSS3 2022-01-24 -0.0193
## 10 AU000000CSS3 2022-02-28 0.00349
## # ℹ 30,341 more rows
## equity identifiers, ensure one-to-one mapping
select <- dplyr::select
unique_id <- titlon_data %>%
distinct(ISIN, .keep_all = TRUE) %>%
select(all_of(c("SecurityId", "CompanyId", "Symbol", "ISIN",
"Name", "Sector")))
unique_id
## # A tibble: 501 × 6
## SecurityId CompanyId Symbol ISIN Name Sector
## <dbl> <dbl> <chr> <chr> <chr> <chr>
## 1 1304857 12720 2020 BMG9156K1018 2020 Bulkers Industrials
## 2 1301972 12440 FIVEPG DK0060945467 5th Planet Games Consumer Discret…
## 3 NA NA AASB NO0010672181 Aasen Sparebank Financials
## 4 6085 2017 ABG NO0003021909 ABG Sundal Collier Financials
## 5 1301198 12348 ABL NO0010715394 ABL GROUP Industrials
## 6 1304655 12701 ADE NO0010844038 Adevinta Consumer Discret…
## 7 1304652 12701 ADEA NO0010843998 Adevinta ser. A Consumer Discret…
## 8 NA NA ADS CY0108052115 ADS Crude Carriers Industrials
## 9 1251504 11273 AEGA NO0010626559 Aega Financials
## 10 NA NA AEGA NO0012958539 AEGA Financials
## # ℹ 491 more rows
## add company info
data_return <- data_return %>%
left_join(unique_id, by="ISIN") %>%
select("ISIN", "Date",
"Symbol", "Name", "Sector",
"Return_monthly")
## subset complete cases
start_date <- ymd("2015-01-01")
end_date <- ymd("2022-12-31")
data_return <- data_return %>%
mutate(Date = ymd(Date)) %>%
filter(between(Date, start_date, end_date))
ISIN_vec <- data_return %>% group_by(ISIN) %>%
tally(sort=TRUE) %>%
filter(n==96) %>%
pull(ISIN)
ISIN_vec %>% length() # 142 complete cases
## [1] 142
data_return <- data_return %>% filter(ISIN %in% ISIN_vec)
data_return
## # A tibble: 13,632 × 6
## ISIN Date Symbol Name Sector Return_monthly
## <chr> <date> <chr> <chr> <chr> <dbl>
## 1 BMG0451H1170 2015-01-30 ARCHER Archer Energy -0.240
## 2 BMG0451H1170 2015-02-27 ARCHER Archer Energy -0.228
## 3 BMG0451H1170 2015-03-31 ARCHER Archer Energy 0.00844
## 4 BMG0451H1170 2015-04-30 ARCHER Archer Energy 0.167
## 5 BMG0451H1170 2015-05-29 ARCHER Archer Energy -0.0394
## 6 BMG0451H1170 2015-06-30 ARCHER Archer Energy 0.00746
## 7 BMG0451H1170 2015-07-31 ARCHER Archer Energy -0.215
## 8 BMG0451H1170 2015-08-31 ARCHER Archer Energy -0.401
## 9 BMG0451H1170 2015-09-30 ARCHER Archer Energy -0.130
## 10 BMG0451H1170 2015-10-30 ARCHER Archer Energy -0.0769
## # ℹ 13,622 more rows
Get sample data for two assets and create an equally-weighted portfolio of them.
k <- 2 # number of asset
sample_data <- data_return %>%
filter(ISIN %in% c("BMG0451H1170", "NO0003079709"))
sample_data
## # A tibble: 192 × 6
## ISIN Date Symbol Name Sector Return_monthly
## <chr> <date> <chr> <chr> <chr> <dbl>
## 1 BMG0451H1170 2015-01-30 ARCHER Archer Energy -0.240
## 2 BMG0451H1170 2015-02-27 ARCHER Archer Energy -0.228
## 3 BMG0451H1170 2015-03-31 ARCHER Archer Energy 0.00844
## 4 BMG0451H1170 2015-04-30 ARCHER Archer Energy 0.167
## 5 BMG0451H1170 2015-05-29 ARCHER Archer Energy -0.0394
## 6 BMG0451H1170 2015-06-30 ARCHER Archer Energy 0.00746
## 7 BMG0451H1170 2015-07-31 ARCHER Archer Energy -0.215
## 8 BMG0451H1170 2015-08-31 ARCHER Archer Energy -0.401
## 9 BMG0451H1170 2015-09-30 ARCHER Archer Energy -0.130
## 10 BMG0451H1170 2015-10-30 ARCHER Archer Energy -0.0769
## # ℹ 182 more rows
ret_mat <- sample_data %>% pull(Return_monthly) %>%
matrix(ncol=k)
ret_mat %>% dim()
## [1] 96 2
wts <- rep(1/k, k)
wts
## [1] 0.5 0.5
Calculate portfolio return manually (most concise). It is straightforward to calculate rebalanced, as the begin of period weights are always the same. But it is a bit cumbersome to calculate buy-and-hold as we need to update the weight for each period.
Here shows an example of rebalancing monthly.
# option 1, matrix representation
ptf_ret <- ret_mat %*% matrix(wts, ncol=1)
names(ptf_ret) <- "port_ret_manual"
ptf_ret %>% head(10)
## [,1]
## [1,] -0.122990026
## [2,] -0.043001570
## [3,] 0.022356632
## [4,] 0.247278035
## [5,] 0.003363661
## [6,] -0.036708697
## [7,] -0.007408891
## [8,] -0.253805031
## [9,] -0.061228133
## [10,] 0.081909129
Alternatively, we can use PerformanceAnalytics
package. It provides many functions convenient for performance evaluation. We need to convert the data to xts
before providing it as the input for PerformanceAnalytics
functions.
## using PerformanceAnalytics::Return.portfolio
sample_data <- sample_data %>% # standardize date
mutate(yrmon=as.yearmon(Date))
sample_data <- sample_data %>%
mutate(Date=as.Date(yrmon, frac=1))
return_xts <- sample_data %>% pull(Return_monthly) %>%
matrix(ncol=k) %>%
xts(order.by = sample_data$Date %>% unique())
colnames(return_xts) <- sample_data$Symbol %>% unique()
return_xts %>% str()
## An xts object on 2015-01-31 / 2022-12-31 containing:
## Data: double [96, 2]
## Columns: ARCHER, KIT
## Index: Date [96] (TZ: "UTC")
return_xts %>%
data.frame %>%
knitr::kable(digits = 5, caption = "Component asset monthly returns") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = F, latex_options="scale_down") %>%
scroll_box(height = "500px")
ARCHER | KIT | |
---|---|---|
2015-01-31 | -0.24010 | -0.00588 |
2015-02-28 | -0.22801 | 0.14201 |
2015-03-31 | 0.00844 | 0.03627 |
2015-04-30 | 0.16736 | 0.32719 |
2015-05-31 | -0.03943 | 0.04615 |
2015-06-30 | 0.00746 | -0.08088 |
2015-07-31 | -0.21481 | 0.20000 |
2015-08-31 | -0.40094 | -0.10667 |
2015-09-30 | -0.12992 | 0.00746 |
2015-10-31 | -0.07692 | 0.24074 |
2015-11-30 | -0.16176 | 0.06567 |
2015-12-31 | -0.27485 | 0.09244 |
2016-01-31 | -0.27419 | 0.05128 |
2016-02-29 | -0.40667 | -0.02683 |
2016-03-31 | 0.80899 | 0.08772 |
2016-04-30 | 0.46998 | 0.07126 |
2016-05-31 | -0.09296 | 0.00899 |
2016-06-30 | -0.16149 | -0.01114 |
2016-07-31 | -0.11111 | 0.26126 |
2016-08-31 | -0.17292 | -0.03036 |
2016-09-30 | 0.20907 | 0.01289 |
2016-10-31 | 0.40000 | 0.01273 |
2016-11-30 | -0.04167 | 0.08618 |
2016-12-31 | 0.95652 | -0.00331 |
2017-01-31 | 0.30159 | 0.10448 |
2017-02-28 | -0.29878 | 0.08108 |
2017-03-31 | 0.18261 | -0.09444 |
2017-04-30 | -0.04412 | 0.09618 |
2017-05-31 | -0.12692 | 0.04499 |
2017-06-30 | -0.13833 | 0.10000 |
2017-07-31 | 0.18098 | 0.11742 |
2017-08-31 | -0.14545 | -0.07684 |
2017-09-30 | 0.21581 | -0.01469 |
2017-10-31 | -0.18833 | -0.11553 |
2017-11-30 | -0.13860 | 0.01124 |
2017-12-31 | 0.19785 | -0.02917 |
2018-01-31 | 0.04876 | 0.18026 |
2018-02-28 | -0.12334 | 0.03636 |
2018-03-31 | -0.12662 | 0.01754 |
2018-04-30 | 0.16481 | 0.10216 |
2018-05-31 | 0.15319 | 0.07301 |
2018-06-30 | -0.01292 | 0.01134 |
2018-07-31 | -0.14486 | -0.03466 |
2018-08-31 | -0.21749 | 0.03485 |
2018-09-30 | 0.06145 | -0.00306 |
2018-10-31 | -0.07895 | -0.12590 |
2018-11-30 | -0.12286 | 0.04684 |
2018-12-31 | -0.29235 | -0.02685 |
2019-01-31 | 0.08631 | -0.01149 |
2019-02-28 | 0.17797 | 0.03488 |
2019-03-31 | -0.11151 | -0.02247 |
2019-04-30 | 0.12348 | 0.10345 |
2019-05-31 | -0.28649 | -0.04996 |
2019-06-30 | 0.14394 | 0.05263 |
2019-07-31 | 0.02980 | 0.11087 |
2019-08-31 | -0.10825 | -0.07045 |
2019-09-30 | -0.05529 | -0.02526 |
2019-10-31 | -0.11832 | 0.00864 |
2019-11-30 | -0.16883 | 0.00321 |
2019-12-31 | 0.10417 | 0.17395 |
2020-01-31 | -0.09119 | -0.07454 |
2020-02-29 | -0.01557 | 0.03144 |
2020-03-31 | -0.36801 | -0.18286 |
2020-04-30 | -0.07119 | 0.25874 |
2020-05-31 | 0.23653 | 0.08889 |
2020-06-30 | 0.04116 | 0.00510 |
2020-07-31 | -0.02791 | 0.26903 |
2020-08-31 | 0.13636 | 0.09066 |
2020-09-30 | -0.10947 | 0.11736 |
2020-10-31 | 0.01182 | -0.03241 |
2020-11-30 | 0.44626 | -0.04593 |
2020-12-31 | -0.00646 | 0.03899 |
2021-01-31 | 0.36098 | -0.10265 |
2021-02-28 | -0.06810 | 0.04551 |
2021-03-31 | 0.11923 | 0.29411 |
2021-04-30 | 0.19817 | -0.07088 |
2021-05-31 | -0.13193 | 0.08707 |
2021-06-30 | 0.02423 | -0.09108 |
2021-07-31 | -0.05699 | -0.02115 |
2021-08-31 | -0.09920 | -0.01440 |
2021-09-30 | 0.24051 | -0.05219 |
2021-10-31 | -0.01020 | 0.00906 |
2021-11-30 | -0.21237 | 0.07016 |
2021-12-31 | 0.00000 | 0.22789 |
2022-01-31 | 0.11257 | -0.04025 |
2022-02-28 | -0.23294 | -0.08168 |
2022-03-31 | 0.01227 | -0.03125 |
2022-04-30 | -0.10606 | -0.06995 |
2022-05-31 | 0.36441 | 0.02489 |
2022-06-30 | -0.13043 | -0.07392 |
2022-07-31 | 0.01429 | 0.18871 |
2022-08-31 | 0.08310 | -0.03837 |
2022-09-30 | -0.14174 | -0.06135 |
2022-10-31 | 0.11515 | 0.23805 |
2022-11-30 | 0.00136 | 0.01073 |
2022-12-31 | -0.06649 | 0.19108 |
Return.portfolio
can do buy-and-hold and rebalanced strategies rather easily.
We do not need to calculate the weight on our own in case of buy-and-hold.
# buy and hold ptf
ptf_bh <- Return.portfolio(R = return_xts, weights = wts, verbose = TRUE)
# rebalanced ptf
ptf_rebal <- Return.portfolio(R = return_xts, weights = wts, rebalance_on="month", verbose = TRUE)
Plot the portfolio return time series.
plot_data <- ptf_bh$returns %>%
merge(ptf_rebal$returns) %>%
setNames(c("buy-and-hold", "rebalance"))
plot(plot_data, multi.panel=TRUE, main="Portfolio monthly return")
By setting verbose = TRUE
, it allows us to check intermediary calculations, such as contributions, weight for each asset.
Here we check the end of period weight.
# plot end of period weights
eop_weight_bh <- ptf_bh$EOP.Weight
eop_weight_rebal <- ptf_rebal$EOP.Weight
par(mfrow = c(2, 1), mar = c(2, 4, 2, 2))
plot.xts(eop_weight_bh$KIT)
plot.xts(eop_weight_rebal$KIT)
We see that the weight of KIT
basically skyrockets and dominates the portfolio, taking up more than \(99\%\) of the portfolio.
This is due to its strong performance.
Now we check the relative performance of the two assets.
## check relative performance by calculating the equity curve
equity_curve_xts <- return_xts %>%
apply(2, function(col) cumprod(1 + col))
equity_curve_xts <- xts(equity_curve_xts, index(return_xts))
colnames(equity_curve_xts) <- colnames(return_xts)
plot(equity_curve_xts, multi.panel=TRUE, yaxis.same=FALSE)
Q: what does the begin of period weight look like?
A: a horizontal line at \(y=50\%\).
bop_weight_rebal <- ptf_rebal$BOP.Weight
plot(bop_weight_rebal$KIT)
Excess return and Sharpe ratio.
## excess return
f_name <- "data/NO_Rf-OSEBX_2015-2023_monthly.csv"
rfr <- read_csv(f_name)
rfr <- rfr %>% mutate(yrmon=as.yearmon(Date))
ptf_ret3 <- ptf_rebal$returns %>%
data.frame(row.names = index(ptf_bh$returns)) %>%
as_tibble(rownames="Date") %>%
mutate(yrmon=as.yearmon(Date))
bind_cols(ptf_ret, ptf_ret3) # cross validate hand and computer calculations
## # A tibble: 96 × 4
## ...1 Date portfolio.returns yrmon
## <dbl> <chr> <dbl> <yearmon>
## 1 -0.123 2015-01-31 -0.123 Jan 2015
## 2 -0.0430 2015-02-28 -0.0430 Feb 2015
## 3 0.0224 2015-03-31 0.0224 Mar 2015
## 4 0.247 2015-04-30 0.247 Apr 2015
## 5 0.00336 2015-05-31 0.00336 May 2015
## 6 -0.0367 2015-06-30 -0.0367 Jun 2015
## 7 -0.00741 2015-07-31 -0.00741 Jul 2015
## 8 -0.254 2015-08-31 -0.254 Aug 2015
## 9 -0.0612 2015-09-30 -0.0612 Sep 2015
## 10 0.0819 2015-10-31 0.0819 Oct 2015
## # ℹ 86 more rows
Merge with risk free rate.
colnames(ptf_ret3)[2] <- "port_ret"
ptf_ret3 <- ptf_ret3 %>%
left_join(rfr[,-1], by="yrmon") %>%
mutate(excess_ret = port_ret-rf_1month)
ptf_ret3
## # A tibble: 96 × 7
## Date port_ret yrmon mkt_return_monthly rf_1month rf_annualized excess_ret
## <chr> <dbl> <yea> <dbl> <dbl> <dbl> <dbl>
## 1 2015-01… -0.123 Jan … 0.0350 0.0012 0.0145 -0.124
## 2 2015-02… -0.0430 Feb … 0.0326 0.00113 0.0136 -0.0441
## 3 2015-03… 0.0224 Mar … 0.00578 0.00127 0.0153 0.0211
## 4 2015-04… 0.247 Apr … 0.0326 0.00123 0.0149 0.246
## 5 2015-05… 0.00336 May … 0.00988 0.00117 0.0141 0.00219
## 6 2015-06… -0.0367 Jun … -0.0257 0.00102 0.0123 -0.0377
## 7 2015-07… -0.00741 Jul … 0.0156 0.00097 0.0117 -0.00838
## 8 2015-08… -0.254 Aug … -0.0702 0.00095 0.0115 -0.255
## 9 2015-09… -0.0612 Sep … -0.0207 0.00083 0.0100 -0.0621
## 10 2015-10… 0.0819 Oct … 0.0575 0.0008 0.00964 0.0811
## # ℹ 86 more rows
Monthly mean and volatility
\[ \text{Arithmetic } \bar{r}_{p, 0:T} = \frac{\sum_{t=1}^T r_{p,t}}{T} \]
\[ \text{Geometric } \bar{r}_{p, 0:T} = \left[\prod_{t=1}^T (1+r_{p,t}) \right] ^{\frac{1}{T}}-1 \] Written as \(\bar{r}_p\) in short, representing the arithmetic/geometric mean return of the portfolio from time \(0\) to time \(T\).
Arithmetic average sometimes cannot precisely reflect historical gains and losses. Suppose the original capital of \(\$100\) is invested over a two-month period with 10% return in the first month and \(10\%\) loss in the second month.
The arithmetic average return is \((10\% - 10\%)/2=0\%\), which implies that there is no change in the \(\$100\) invested. However, the actual gain/loss for the investment grows from \(\$100\) to \(\$110\) in the first month, and drops 10% from \(\$110\) to \(\$99\) in the second month, indicating an actual loss of \(\$1\).
The geometric return is \((1.1\times0.9)^{0.5}-1\approx -0.5\%\), indicating a loss of \(-0.5\%\) per month. The geometric return that incorporates the compounding effect of growth is a better indication of historical performance.
Geometric mean is smaller than or equal to arithmetic mean and the equality holds if and only if \(r_{p,1}=r_{p,2}=\cdots=r_{p,T}\). This can be proven by using Jensen’s inequality that says, for any concave function \(g(x)\),
\[ E[g(x)] \le g(E[x]). \] And \(\ln(x)\) is a concave function.
# arithmetic mean monthly returns
mean(ptf_ret3$port_ret)
## [1] 0.01681626
# geometric mean
mean.geometric(ptf_ret3$port_ret)
## [1] 0.009155608
prod(1+ptf_ret3$port_ret)^(1/96)-1
## [1] 0.009155608
# mean excess return
mean(ptf_ret3$excess_ret)
## [1] 0.01599762
mean.geometric(ptf_ret3$excess_ret)
## [1] 0.008326127
# standard deviation
sd(ptf_ret3$port_ret)
## [1] 0.1278693
Portfolio return and standard deviation are usually published as annualized.
\[ r_{p}^A = \frac{12}{T}\sum_{t=1}^T r_{p,t} = 12\times \bar{r}_p \]
\[ r_{p}^A = \left[\prod_{t=1}^T (1+r_{p,t}) \right]^{\frac{12}{T}}-1 = (1+\bar{r}_p)^{12}-1 \]
\[ \begin{aligned} \sigma_{p}^A &= \sqrt{12} \sigma_p \\ \sigma_p^2 &= \frac{1}{T-1} \sum_{t=1}^T (r_{p,t}-\bar{r}_p)^2 \end{aligned} \]
It is possible to use Return.annualized
, StdDev.annualized
from the PerformanceAnalytics
package to get the annualized statistics, but we need to convert the portfolio return as an xts
object.
# convert to xts
ptf_xts <- xts(ptf_ret3[,-c(1,3)], order.by=ymd(ptf_ret3$Date))
ptf_xts %>%
data.frame %>%
knitr::kable(digits = 5, caption = "Portfolio return in `xts`") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = F, latex_options="scale_down") %>%
scroll_box(height = "500px")
port_ret | mkt_return_monthly | rf_1month | rf_annualized | excess_ret | |
---|---|---|---|---|---|
2015-01-31 | -0.12299 | 0.03498 | 0.00120 | 0.01450 | -0.12419 |
2015-02-28 | -0.04300 | 0.03262 | 0.00113 | 0.01364 | -0.04413 |
2015-03-31 | 0.02236 | 0.00578 | 0.00127 | 0.01535 | 0.02109 |
2015-04-30 | 0.24728 | 0.03256 | 0.00123 | 0.01486 | 0.24605 |
2015-05-31 | 0.00336 | 0.00988 | 0.00117 | 0.01413 | 0.00219 |
2015-06-30 | -0.03671 | -0.02566 | 0.00102 | 0.01231 | -0.03773 |
2015-07-31 | -0.00741 | 0.01561 | 0.00097 | 0.01170 | -0.00838 |
2015-08-31 | -0.25381 | -0.07016 | 0.00095 | 0.01146 | -0.25476 |
2015-09-30 | -0.06123 | -0.02072 | 0.00083 | 0.01001 | -0.06206 |
2015-10-31 | 0.08191 | 0.05749 | 0.00080 | 0.00964 | 0.08111 |
2015-11-30 | -0.04805 | 0.02198 | 0.00108 | 0.01304 | -0.04913 |
2015-12-31 | -0.09121 | -0.02942 | 0.00087 | 0.01049 | -0.09208 |
2016-01-31 | -0.11146 | -0.08083 | 0.00085 | 0.01025 | -0.11231 |
2016-02-29 | -0.21675 | 0.02063 | 0.00080 | 0.00964 | -0.21755 |
2016-03-31 | 0.44835 | 0.00917 | 0.00070 | 0.00843 | 0.44765 |
2016-04-30 | 0.27062 | 0.04938 | 0.00069 | 0.00831 | 0.26993 |
2016-05-31 | -0.04198 | 0.01819 | 0.00068 | 0.00819 | -0.04266 |
2016-06-30 | -0.08631 | -0.02341 | 0.00073 | 0.00880 | -0.08704 |
2016-07-31 | 0.07508 | 0.01621 | 0.00068 | 0.00819 | 0.07440 |
2016-08-31 | -0.10164 | 0.01028 | 0.00073 | 0.00880 | -0.10237 |
2016-09-30 | 0.11098 | 0.00608 | 0.00082 | 0.00988 | 0.11016 |
2016-10-31 | 0.20636 | 0.02491 | 0.00078 | 0.00940 | 0.20558 |
2016-11-30 | 0.02225 | 0.02888 | 0.00107 | 0.01292 | 0.02118 |
2016-12-31 | 0.47661 | 0.04148 | 0.00087 | 0.01049 | 0.47574 |
2017-01-31 | 0.20303 | 0.01353 | 0.00059 | 0.00710 | 0.20244 |
2017-02-28 | -0.10885 | -0.00411 | 0.00076 | 0.00916 | -0.10961 |
2017-03-31 | 0.04408 | -0.00351 | 0.00068 | 0.00819 | 0.04340 |
2017-04-30 | 0.02603 | 0.01426 | 0.00069 | 0.00831 | 0.02534 |
2017-05-31 | -0.04096 | 0.01818 | 0.00072 | 0.00867 | -0.04168 |
2017-06-30 | -0.01916 | -0.01656 | 0.00060 | 0.00722 | -0.01976 |
2017-07-31 | 0.14920 | 0.04857 | 0.00056 | 0.00674 | 0.14864 |
2017-08-31 | -0.11115 | 0.01005 | 0.00057 | 0.00686 | -0.11172 |
2017-09-30 | 0.10056 | 0.05842 | 0.00056 | 0.00674 | 0.10000 |
2017-10-31 | -0.15193 | 0.03047 | 0.00052 | 0.00626 | -0.15245 |
2017-11-30 | -0.06368 | -0.01254 | 0.00056 | 0.00674 | -0.06424 |
2017-12-31 | 0.08434 | 0.02211 | 0.00056 | 0.00674 | 0.08378 |
2018-01-31 | 0.11451 | -0.00422 | 0.00063 | 0.00759 | 0.11388 |
2018-02-28 | -0.04349 | 0.01080 | 0.00078 | 0.00940 | -0.04427 |
2018-03-31 | -0.05454 | -0.01763 | 0.00079 | 0.00952 | -0.05533 |
2018-04-30 | 0.13348 | 0.06785 | 0.00076 | 0.00916 | 0.13272 |
2018-05-31 | 0.11310 | 0.01809 | 0.00068 | 0.00819 | 0.11242 |
2018-06-30 | -0.00079 | 0.00413 | 0.00064 | 0.00771 | -0.00143 |
2018-07-31 | -0.08976 | 0.01963 | 0.00065 | 0.00783 | -0.09041 |
2018-08-31 | -0.09132 | 0.01148 | 0.00068 | 0.00819 | -0.09200 |
2018-09-30 | 0.02920 | 0.03482 | 0.00082 | 0.00988 | 0.02838 |
2018-10-31 | -0.10242 | -0.05180 | 0.00082 | 0.00988 | -0.10324 |
2018-11-30 | -0.03801 | -0.03224 | 0.00095 | 0.01146 | -0.03896 |
2018-12-31 | -0.15960 | -0.07145 | 0.00089 | 0.01073 | -0.16049 |
2019-01-31 | 0.03741 | 0.04484 | 0.00084 | 0.01013 | 0.03657 |
2019-02-28 | 0.10642 | 0.03588 | 0.00086 | 0.01037 | 0.10556 |
2019-03-31 | -0.06699 | -0.00251 | 0.00104 | 0.01255 | -0.06803 |
2019-04-30 | 0.11346 | 0.02062 | 0.00103 | 0.01243 | 0.11243 |
2019-05-31 | -0.16822 | -0.03272 | 0.00105 | 0.01267 | -0.16927 |
2019-06-30 | 0.09829 | 0.01472 | 0.00114 | 0.01377 | 0.09715 |
2019-07-31 | 0.07034 | -0.00635 | 0.00114 | 0.01377 | 0.06920 |
2019-08-31 | -0.08935 | 0.00250 | 0.00117 | 0.01413 | -0.09052 |
2019-09-30 | -0.04028 | 0.02939 | 0.00130 | 0.01571 | -0.04158 |
2019-10-31 | -0.05484 | 0.01291 | 0.00140 | 0.01693 | -0.05624 |
2019-11-30 | -0.08281 | 0.00490 | 0.00143 | 0.01730 | -0.08424 |
2019-12-31 | 0.13906 | 0.03213 | 0.00141 | 0.01705 | 0.13765 |
2020-01-31 | -0.08287 | -0.01894 | 0.00137 | 0.01656 | -0.08424 |
2020-02-29 | 0.00793 | -0.09143 | 0.00135 | 0.01632 | 0.00658 |
2020-03-31 | -0.27544 | -0.14830 | 0.00052 | 0.00626 | -0.27596 |
2020-04-30 | 0.09378 | 0.09614 | 0.00023 | 0.00276 | 0.09355 |
2020-05-31 | 0.16271 | 0.02794 | 0.00008 | 0.00096 | 0.16263 |
2020-06-30 | 0.02313 | -0.00195 | 0.00018 | 0.00216 | 0.02295 |
2020-07-31 | 0.12056 | 0.03900 | 0.00014 | 0.00168 | 0.12042 |
2020-08-31 | 0.11351 | 0.03998 | 0.00008 | 0.00096 | 0.11343 |
2020-09-30 | 0.00395 | -0.00369 | 0.00020 | 0.00240 | 0.00375 |
2020-10-31 | -0.01029 | -0.05168 | 0.00036 | 0.00433 | -0.01065 |
2020-11-30 | 0.20017 | 0.14601 | 0.00023 | 0.00276 | 0.19994 |
2020-12-31 | 0.01627 | 0.04684 | 0.00029 | 0.00349 | 0.01598 |
2021-01-31 | 0.12916 | -0.00726 | 0.00026 | 0.00312 | 0.12890 |
2021-02-28 | -0.01129 | 0.04155 | 0.00026 | 0.00312 | -0.01155 |
2021-03-31 | 0.20667 | 0.05143 | 0.00020 | 0.00240 | 0.20647 |
2021-04-30 | 0.06364 | 0.01661 | 0.00017 | 0.00204 | 0.06347 |
2021-05-31 | -0.02243 | 0.03382 | 0.00015 | 0.00180 | -0.02258 |
2021-06-30 | -0.03342 | 0.00728 | 0.00011 | 0.00132 | -0.03353 |
2021-07-31 | -0.03907 | 0.01188 | 0.00012 | 0.00144 | -0.03919 |
2021-08-31 | -0.05680 | 0.00668 | 0.00017 | 0.00204 | -0.05697 |
2021-09-30 | 0.09416 | 0.01879 | 0.00037 | 0.00445 | 0.09379 |
2021-10-31 | -0.00057 | 0.02532 | 0.00040 | 0.00481 | -0.00097 |
2021-11-30 | -0.07111 | -0.00965 | 0.00057 | 0.00686 | -0.07168 |
2021-12-31 | 0.11394 | 0.01707 | 0.00067 | 0.00807 | 0.11327 |
2022-01-31 | 0.03616 | -0.02212 | 0.00073 | 0.00880 | 0.03543 |
2022-02-28 | -0.15731 | 0.02325 | 0.00074 | 0.00892 | -0.15805 |
2022-03-31 | -0.00949 | 0.04923 | 0.00087 | 0.01049 | -0.01036 |
2022-04-30 | -0.08801 | -0.01707 | 0.00081 | 0.00976 | -0.08882 |
2022-05-31 | 0.19465 | 0.03837 | 0.00083 | 0.01001 | 0.19382 |
2022-06-30 | -0.10218 | -0.09097 | 0.00114 | 0.01377 | -0.10332 |
2022-07-31 | 0.10150 | 0.07084 | 0.00135 | 0.01632 | 0.10015 |
2022-08-31 | 0.02237 | -0.00360 | 0.00169 | 0.02047 | 0.02068 |
2022-09-30 | -0.10155 | -0.11683 | 0.00225 | 0.02734 | -0.10380 |
2022-10-31 | 0.17660 | 0.06589 | 0.00228 | 0.02771 | 0.17432 |
2022-11-30 | 0.00604 | 0.03855 | 0.00270 | 0.03289 | 0.00334 |
2022-12-31 | 0.06230 | -0.02602 | 0.00253 | 0.03079 | 0.05977 |
# Compute the annualized arithmetic mean
Return.annualized(ptf_xts$port_ret, geometric = FALSE)
## port_ret
## Annualized Return 0.2017952
mean(ptf_ret3$port_ret)*12
## [1] 0.2017952
# Compute the annualized geometric mean
Return.annualized(ptf_xts$port_ret, geometric = TRUE)
## port_ret
## Annualized Return 0.1155721
prod(1+ptf_ret3$port_ret)^(12/96)-1
## [1] 0.1155721
# Compute the annualized standard deviation
StdDev.annualized(ptf_xts$port_ret)
## port_ret
## Annualized Standard Deviation 0.4429521
sd(ptf_ret3$port_ret)*sqrt(12)
## [1] 0.4429521
Histogram of portfolio returns.
# histogram to check normality
chart.Histogram(ptf_xts$port_ret, methods = c("add.density", "add.normal"))
Skewness \(\mu_3\) is the third (normalized) moment about the mean.
It measures the asymmetry of the distribution, with symmetric distribution having \(\mu_3=0\).
\[ \mu_3 = \frac{1}{T} \frac{\sum_{t=1}^T (r_{p,t}-\bar{r}_p)^3}{\sigma_p^3} \]
# skewness
skewness(ptf_xts$port_ret)
## [1] 0.7779903
Kurtosis \(K_p\) is the fourth (normalized) moment about the mean.
It measures the tail behavior in comparison with the normal distribution.
\[ \mu_4 = \frac{1}{T} \frac{\sum_{t=1}^T (r_{p,t}-\bar{r}_p)^4}{\sigma_p^4} \]
The kurtosis for any normal distribution is three. For this reason, we subtract three from \(\mu_4\) to get the “excess kurtosis”.
\(t\)-distribution has higher kurtosis than normal distributions.
set.seed(125)
rnorm(1000) %>% moments::kurtosis()
## [1] 3.037806
rt(1000, df=1) %>% moments::kurtosis()
## [1] 503.3928
rt(1000, df=2) %>% moments::kurtosis()
## [1] 61.25994
rt(1000, df=10) %>% moments::kurtosis()
## [1] 4.303517
rt(1000, df=30) %>% moments::kurtosis()
## [1] 3.646411
Distinguish from standard deviation/variance.
Standard deviation and kurtosis are both measures of the variability of a distribution, but they are not directly related.
Two distributions with identical means and standard deviations can have very different shapes, and kurtosis is one of the measures of that difference.
It looks at how much of the ‘weight’ of the distribution (recall that the total weight, or the area under the curve, is 1) is sitting in the tails as opposed to the middle of the distribution.
PerformanceAnalytics::kurtosis(x)
returns excess kurtosis by default.
Need to specify method="moment"
in order to get the original kurtosis.
moments::kurtosis(x)
returns the original kurtosis. Need to be compared with 3 on your own.
# kurtosis
kurtosis(ptf_xts$port_ret)
## [1] 1.69699
# summary statistic
returns_statistics <- table.Stats(ptf_xts$port_ret)
returns_statistics
## port_ret
## Observations 96.0000
## NAs 0.0000
## Minimum -0.2754
## Quartile 1 -0.0680
## Median 0.0014
## Arithmetic Mean 0.0168
## Geometric Mean 0.0092
## Quartile 3 0.1008
## Maximum 0.4766
## SE Mean 0.0131
## LCL Mean (0.95) -0.0091
## UCL Mean (0.95) 0.0427
## Variance 0.0164
## Stdev 0.1279
## Skewness 0.7780
## Kurtosis 1.6970
While the Sharpe ratio measures the excess return as the difference between the return of the portfolio and the risk-free rate, the information ratio, on the other hand, measures the portfolio performance against a comparable benchmark rather than the risk-free rate.
The portfolio return in excess of the return of the benchmark is known as the active return, and the variability of the active return is known as the active risk or tracking error of the portfolio.
The information ratio is the active return per unit of active risk.
\[ \begin{aligned} \text{Information Ratio} &= \text{IR}_p = \frac{\bar{r}_p-\bar{r}_m}{\sigma_{p-m}} \\ \text{Annualized Informatione Ratio} &= \text{IR}_p^A = \frac{\bar{r}_p-\bar{r}_m}{\sigma_{p-m}} \times \sqrt{12} \end{aligned} \] where
Sharpe and Information ratios use standard deviation as the measure of portfolio risk. An alternative risk measure is the beta coefficient based on the CAPM model.
Recall that the beta coefficient can be estimated using the follow regression
\[ r_{i,\color{red}{t}} - r_{f,\color{red}{t}} = \alpha_i + \beta_i (r_{m,\color{red}{t}}-r_{f,\color{red}{t}}) + \varepsilon_{i,\color{red}{t}} . \] The OLS \(\beta_i\) estimate can be expressed as
\[ \hat{\beta}_i = \frac{\text{Cov}(R_{m}^e, R_{i}^e)}{\text{Var}(R_{i}^e)} \] where
Treynor ratio is a variant of Sharpe ratio. It substitute the standard deviation in Sharpe ratio with the beta coefficient.
The Treynor ratio is computed as
\[ \text{Treynor Ratio}_p = \frac{\bar{r}_p-\bar{r}_f}{\beta_p} \] Note that
the only relevant risk in the computation of the Treynor ratio is systematic risk. It assumes that the portfolio is fully diversified. Appropriate for diversified equity funds, the element of unsystematic risk would be negligible.
Sharpe ratio assumes that the relevant risk is total risk (systematic and unsystematic risk), and it measures excess return per unit of total risk. It is appropriate for the portfolio that is less diversified.
Jensen’s alpha also assumes that the relevant risk is systematic risk.
Jensen’s alpha measures the average return on the portfolio in excess of what predicted by the CAPM, given the portfilio’s beta and the average market return.
\[ \alpha_p = \bar{r}_p - \left[\bar{r}_f + \beta_p(\bar{r}_m-\bar{r}_f)\right] \]
Exercise
Given the following information about the return on a portfolio, the market index and risk free returns.
Year | Portfolio | Market index | Risk free rate |
1 | 14% | 12% | 7% |
2 | 10 | 7 | 7.5 |
3 | 19 | 20 | 7.7 |
4 | -8 | -2 | 7.5 |
5 | 23 | 12 | 8.5 |
6 | 28 | 23 | 8 |
7 | 20 | 17 | 7.3 |
8 | 14 | 20 | 7 |
9 | -9 | -5 | 7.5 |
10 | 19 | 16 | 8 |
Average | 13% | 12% | 7.6% |
Standard deviation | 12.39% | 9.43% | 0.47% |
Cov(\(r_p\), \(r_m\)) | 0.0107 |
\[ \beta_p = \frac{\text{Cov}(r_p, r_m)}{\text{Var}(r_m)} = \frac{0.0107}{9.43\%^2} = 1.203 \]
\[ S_p = \frac{\bar{r}_p-\bar{r}_f}{\sigma_p} = \frac{(13-7.6)\%}{12.39\%} = 0.436 \]
\[ T_p = \frac{\bar{r}_p-\bar{r}_f}{\beta_p} = \frac{(13-7.6)\%}{1.203} = 0.0449 \]
\[ \alpha_p = \bar{r}_p - \left[\bar{r}_f + \beta_p(\bar{r}_m-\bar{r}_f)\right] = 13\% - 7.6\% - 1.203\times (12-7.6)\% = 0.107\% \]
The M-squared (\(M^2\)) measure is first introduced by Franco Modigliani. \(M^2\) provides a direct comparison between the leverage-adjusted portfolio and the market portfolio.
A managed portfolio \(p\) is mixed with a position in the risk free asset to make the “leverage-adjusted” (or “adjusted” in short) portfolio have the same volatility as the market. Suppose the managed portfolio p has a total variability equal to \(1.5\times \sigma_m\). The “adjusted” portfolio \(p^*\) is found by investing a weight \(w\) in \(p\) and a weight \((1 − w)\) in the risk free asset, such that the portfolio has the same standard deviation as the market:
\[ \begin{aligned} r_{p^*} &= w\, r_p + (1-w)\, r_f \\ \sigma_{p^*} &= w\sigma_p + (1-w)\sigma_{r_f} = w\sigma_p + (1-w)\cdot 0 = w\sigma_p \end{aligned} \] Let
\[ \sigma_{p^*} = \sigma_m , \] we have
\[ w\sigma_p = \sigma_m \Rightarrow w=\frac{\sigma_m}{\sigma_p} = \frac{\sigma_m}{1.5\sigma_m} = \frac{2}{3} \] By investing two thirds in \(p\) and one third in the risk free asset \(r_f\), achieve the same volatility as the market.
Since \(P^∗\) and \(m\) have the same volatility, we may compare their returns simply by calculating the difference:
\[ M_p^2 = r_{p^*} - r_m . \]
When the return distribution is asymmetric (skewed), investors use additional risk measures that focus on describing the potential losses.
The Semi-Deviation is the calculation of the variability of returns below the mean return. \[ \begin{aligned} \text{Semi } \sigma_p &= \sqrt{\frac{1}{n}\textstyle \sum_{t=1}^T \big[\min(r_{p,t}-\bar{r}_p, 0)\big]^2 } \\ \text{Annualized Semi } \sigma_p^A &= \sqrt{\frac{1}{n}\textstyle \sum_{t=1}^T \big[\min(r_{p,t}-\bar{r}_p, 0)\big]^2 } \times \sqrt{12} \end{aligned} \] where \(n\) is either the number of observations of the entire series or the number of observations in the subset of the series falling below the average.
Value-at-Risk (VaR) measures the potential loss in value of a risky asset or portfolio over a defined period for a given confidence interval. It corresponds to a probability \(p\), which is a confidence level.
A \(p\) VaR means that the probability of a loss greater than VaR is (at most) \((p)\) while the probability of a loss less than VaR is (at least) \(1-p\).
For example, a one-day \(5\%\) VaR of \(\$1\) million implies the portfolio has a \(5\%\) chance that the value of the asset drops more than \(\$1\) million over one day. In other words, there is a \(95\%\) probability that the asset makes a profit or lose less than \(\$1\) million over a day.
Different conventions are used for \(p\). You can tell by the magnitude whether it refers to a confidence level or a significance level. For instance, sometimes you see \(95\%\) VaR (confidence level) and \(5\%\) VaR (significance level).
To obtain a sample estimate of \(5\%\) VaR, we sort the observations from high to low. The VaR is the return at the \(5\)th percentile of the empirical sample distribution.
More formally, \[ \text{VaR}(\alpha) = -F^{-1}(\alpha) \] where \(F(\cdot)\) is the cdf of the portfolio return \(r_p\).
The expected shortfall (ES) is the expected value of the loss, given the loss is greater than the VaR.
\[ \text{ES}(\alpha) = E[r_p \vert r_p \leq F^{-1}(\alpha)] \] - VaR is the most optimistic measure of worst-case scenarios as it takes the smallest loss of all these cases, while ES informs the magnitudes of potential losses given that the loss is greater than the VaR.
## downside risk measures
# Calculate the SemiDeviation
SemiDeviation(ptf_xts$port_ret)
## port_ret
## Semi-Deviation 0.08114918
sd(ptf_xts$port_ret)
## [1] 0.1278693
# Calculate the value at risk, p is the confidence level
VaR(ptf_xts$port_ret, p=.95) # 95% VaR
## port_ret
## VaR -0.1584788
VaR(ptf_xts$port_ret, p=.99) # 99% VaR
## port_ret
## VaR -0.2278215
# Calculate the expected shortfall
ES(ptf_xts$port_ret, p=.95)
## port_ret
## ES -0.1913056
ES(ptf_xts$port_ret, p=.99)
## port_ret
## ES -0.2278215
The other popular downside risk estimate, the maximum drawdown (MDD) of the portfolio, measures the largest loss from peak to trough over the examination period.
The maximum drawdown of the portfolio indicates the maximum possible loss investors have ever experienced over the examination period.
\[ MDD = \frac{LP-PV}{PV} \]
# Table of drawdowns
maxDrawdown(ptf_xts$port_ret)
## [1] 0.5840139
table.Drawdowns(ptf_xts$port_ret)
## From Trough To Depth Length To Trough Recovery
## 1 2018-06-30 2020-03-31 2021-03-31 -0.5840 34 22 12
## 2 2015-06-30 2016-02-29 2016-12-31 -0.5637 19 9 10
## 3 2022-02-28 2022-04-30 2022-12-31 -0.2388 11 3 8
## 4 2017-08-31 2017-11-30 2018-05-31 -0.2232 10 4 6
## 5 2015-01-31 2015-02-28 2015-04-30 -0.1607 4 2 2
# Plot of drawdowns
chart.Drawdown(ptf_xts$port_ret)
## rolling annualized mean and volatility
chart.RollingPerformance(R = ptf_xts$port_ret, width = 12, FUN = "Return.annualized")
chart.RollingPerformance(R = ptf_xts$port_ret, width = 12, FUN = "StdDev.annualized")
chart.RollingPerformance(R = ptf_xts$port_ret, width = 12, FUN = "SharpeRatio.annualized", Rf=ptf_xts$excess_ret)