Financial Analytics in R

Basic Financial Analytics


Author

Affiliation

N.F. Katzke

 

Published

July 21, 2024

Citation

Katzke, 2024


This practical gives you the tools to do basic portfolio analytics

Bonus: How to cap your portfolio:

Bonus: Creating Financial Statistics tables:

Homework: Due date 16 Aug 2024

You’ve been tasked to illustrate the impact that different scales of management fees (say 10, 25, 50, 100, 200, 250 and 300bps) have on investors’ overall cumulative returns.

Show the staggering impact of fees, by attempting to replicate the following figure (using fmxdat::Jalshtr)

Please create a github repo called Tyranny_of_Fees where you have a README html that shows your steps and functions.

Good luck!

(I’ll make the code available after you’ve attempted this):

library(tidyverse);library(lubridate)

Jalshtr <- 
  # First, make the return series monthly:
  fmxdat::Jalshtr %>% 
  mutate(YM = format(date, "%Y%B")) %>% group_by(YM) %>% 
  filter(date == last(date)) %>% arrange(date) %>% 
  ungroup() %>% 
  mutate(Returns = TRI / lag(TRI) - 1) %>% filter(date > first(date)) %>% select(-TRI, -YM)

Cum_Fee_Comparison <- function(Jalshtr, Fee = 50*1e-4, Start = ymd(20100101), 
                               # Added purely for figure adjustment:
                               Gap = 3, Lvlset = 5,
                               mnthfwd = 18){
  
  # Turn annual fee into monthly compounded:
  # Remember, we want to find x so that its compound is 10 bps, or: (1+x)^12 = 10*1e-4
  feeconverter <- function(x, Ann_Level) (1+x)^(1/Ann_Level)-1
  
  df_p <- 
  Jalshtr %>% filter(date > Start) %>% 
    mutate('Return - 10 bps' = Returns - feeconverter(10*1e-4, Ann_Level = 12)) %>% 
    mutate('Return - 50 bps' = Returns - feeconverter(50*1e-4, Ann_Level = 12)) %>% 
    mutate('Return - 100 bps' = Returns - feeconverter(100*1e-4, Ann_Level = 12)) %>% 
    mutate('Return - 200 bps' = Returns - feeconverter(200*1e-4, Ann_Level = 12)) %>% 
    mutate('Return - 250 bps' = Returns - feeconverter(250*1e-4, Ann_Level = 12)) %>% 
    mutate('Return - 350 bps' = Returns - feeconverter(350*1e-4, Ann_Level = 12)) %>% 
     rename(Gross = Returns) %>% 
    gather(Type, Rets, -date, -Gross) %>% 
    group_by(Type) %>% filter(date > first(date)) %>% 
    mutate(CP = cumprod(1+Rets)) %>% 
    mutate(Gross = cumprod(1+Gross))
  
  Ord <- c('Return - 10 bps', 'Return - 50 bps', 'Return - 100 bps', 'Return - 200 bps', 'Return - 250 bps', 'Return - 350 bps')
  
  Txt <- 
    df_p %>% filter(date == last(date)) %>% 
    mutate(date = date %m+% months(Gap)) %>% 
    mutate(Text = paste0(round(CP/Gross-1, 3)*100, "%")) %>% 
    # Order:
    mutate(Type = as.factor(Type)) %>%
    mutate(Type = forcats::fct_relevel(Type, Ord))
  
  Shock350 <- 
    df_p %>% filter(date == last(date)) %>% 
    mutate(date = date %m+% months(Gap)) %>% 
    filter(Type == 'Return - 350 bps') %>% pull(CP)
  Shock250 <- 
    df_p %>% filter(date == last(date)) %>% 
    mutate(date = date %m+% months(Gap)) %>% 
    filter(Type == 'Return - 250 bps') %>% pull(CP)
  Shock25 <- 
    df_p %>% filter(date == last(date)) %>% 
    mutate(date = date %m+% months(Gap)) %>% 
    filter(Type == 'Return - 10 bps') %>% pull(CP)
  
  Msg <- 
    df_p %>% ungroup() %>% filter(date > first(date) %m+% months(mnthfwd)) %>% slice(1) %>% 
    mutate(Lvl = Lvlset) %>% mutate(Msg = glue::glue("R1m invested in {format(Start, '%B %Y')}\n* 10bps: R{round(Shock25, 2)}m\n* 250bps: R{round(Shock250, 2)}m\n* 350bps: R{round(Shock350, 2)}m"))
  
g <-   
  df_p %>% 
    mutate(Type = as.factor(Type)) %>%
    mutate(Type = forcats::fct_relevel(Type, Ord)) %>% 
    fmxdat::plot_order_set( Column = "Type", Order = Ord) %>% 
  
    ggplot() + 
    geom_line(aes(date, Gross), color = "darkgreen", size = 1.5, alpha = 0.95) + 
    geom_line(aes(date, CP, color = Type), size = 1.2, alpha = 0.6) + 
    # ggrepel::geom_label_repel(data = Txt, aes(date, CP, label = Text ), hjust = 0, color = "darkred", alpha = 0.25, size = 3) + 
    geom_text(data = Txt, aes(date, CP, label = Text ), hjust = 0, color = "darkred", size = 3.3) +
    fmxdat::theme_fmx() +  
    geom_label(data = Msg, aes(date, Lvl, label = Msg), color = "darkgreen", alpha = 0.8, size = 4) + 
    labs(title = "Fee Impact on Cumulated Wealth", 
         subtitle = glue::glue("Base Return: FTSE JSE All Share Index | Start Date: {format(Start, '%B %Y')}"),
         x = "", y = "Cumulative Returns") + 
  
  scale_x_date(labels = scales::date_format("%Y"), date_breaks = "1 year") + 
  theme(axis.text.x=element_text(angle = 90, hjust = 1))
  

g

}

Impact from inception (2002)

To be uploaded…

Cum_Fee_Comparison(Jalshtr, Fee = 50*1e-4, Start = ymd(20020101), 
                               # Added purely for figure adjustment:
                               Gap = 3, Lvlset = 7,
                               mnthfwd = 18)

Impact since 2010

To be uploaded…

Cum_Fee_Comparison(Jalshtr, Fee = 50*1e-4, Start = ymd(20100101), 
                               # Added purely for figure adjustment:
                               Gap = 3, Lvlset = 3,
                               mnthfwd = 18)

Footnotes

    Citation

    For attribution, please cite this work as

    Katzke (2024, July 22). Financial Econometrics Course: Financial Analytics in R. Retrieved from https://www.fmx.nfkatzke.com/posts/2020-08-05-practical-1/

    BibTeX citation

    @misc{katzke2024financial,
      author = {Katzke, N.F.},
      title = {Financial Econometrics Course: Financial Analytics in R},
      url = {https://www.fmx.nfkatzke.com/posts/2020-08-05-practical-1/},
      year = {2024}
    }