Quant Puzzle #01

R-code
quant-finance
Author

Francois de Ryckel

Published

June 13, 2023

Modified

June 20, 2023

This is attempt to start a series of puzzles for quant. The source for these puzzles is this noteworthy substack on quant finance

Puzzle 1

An investment strategy has an annualized expected return of 3% and an annualized volatility of 10%. Assume returns are normally distributed and independent from one year to the next. After how many years will the mean annualized returns of a portfolio be positive with at least 91.3% probability? What about the compounded returns?

Our previous post on the random behavior of assets is a good start.

To calculate the mean annualized returns, we will use \[Ret = \frac {\sum_{i=1}^{n} R_i}{n}\]

  • \(R_i\) is the annual return for the year i
  • \(n\) is the number of year.

Solving using R

Using the mean annualized returns

library(dplyr)  # mutate(), tibble(), if_else(), group_by(), summarize()
library(purrr)  # pmap()
library(tidyr)  # unnest()

num_sim = 10000

# Create a function to get yearly return (both average and compounded)
create_simul_ret <- function(mu, sigma, num_year) { 
  df = tibble(year_num = 1:num_year, 
              ret = rnorm(num_year, mean = mu, sd = sigma)) |> 
    mutate(ann_ret = 1 + ret, 
           cum_ret = cumsum(ann_ret) / year_num,           # Function for mean annualized returns 
           compounded_ret = cumprod(ann_ret)^(1/year_num), # Function to get compounded returns
           posit_cum_ret = if_else(cum_ret > 1, 1, 0), 
           posit_comp_ret = if_else(compounded_ret > 1, 1, 0))
  return(df)
}

# To get all different random return numbers, I need to fetch the data from the row

df <- tibble(sim_num = 1:num_sim, mu = 0.03, sigma = 0.1, num_year = 30) |> 
  mutate(sim_ret = pmap(list(mu, sigma, num_year), create_simul_ret)) |> 
  unnest(cols = c(sim_ret)) |>      # large df with 30 rows/year per simulation
  group_by(year_num) |>             # group by year to calculate average returns
  summarize(avg_num_pos_ret = mean(posit_cum_ret), 
            avg_num_comp_pos_ret = mean(posit_comp_ret))
year_num avg_num_pos_ret avg_num_comp_pos_ret
1 0.6091 0.6091
2 0.6605 0.6475
3 0.6962 0.6759
4 0.7210 0.6948
5 0.7429 0.7126
6 0.7660 0.7332
7 0.7830 0.7481
8 0.7982 0.7581
9 0.8114 0.7733
10 0.8254 0.7828
11 0.8374 0.7974
12 0.8489 0.8077
13 0.8613 0.8188
14 0.8680 0.8257
15 0.8742 0.8334
16 0.8803 0.8410
17 0.8896 0.8484
18 0.8950 0.8540
19 0.8990 0.8601
20 0.9083 0.8662
21 0.9109 0.8700
22 0.9156 0.8758
23 0.9201 0.8809
24 0.9295 0.8872
25 0.9325 0.8939
26 0.9355 0.8997
27 0.9400 0.9036
28 0.9431 0.9062
29 0.9447 0.9110
30 0.9464 0.9136

Solving Using Python

import random
#Computes a moving average from a gaussian sample over n trials
def gauss_moving_average(mu,sigma,n):
    moving_average_list = []
    for i in range(n):
        if i == 0:
            moving_average_list.append(random.gauss(mu,sigma))
        else:
            moving_average_list.append((moving_average_list[i-1]*i+random.gauss(mu,sigma))/(i+1))
    return moving_average_list

#Outputs a list of guassian moving average time series
def run_sims(mu, sigma, n, years):
    sims = []
    for i in range(n):
        sims.append(gauss_moving_average(mu,sigma,years))
    return sims
#Outputs the percentage of positive entries in a column of a matrix
def percent_positive_column(matrix,column):
    n = len(matrix)
    percent_positive = 0
    for i in range(n):
        if matrix[i][column] > 0:
            percent_positive+=(1/n) 
    return percent_positive*100


#Runs the simulation and checks for our confidence level
matrix = run_sims(3,10,100000, 100)
for i in range(100):
    conf = percent_positive_column(matrix, i)
    print(conf)
    if  conf >= 91.3:
        print("Year",i+1, "has positive returns with probability", conf)
        break
61.93999999998159
66.66699999996007
70.11999999994435
72.92899999993158
75.1649999999214
77.24099999991195
79.06299999990367
80.68199999989629
81.95799999989049
83.17599999988494
84.27499999987994
85.2739999998754
86.20499999987116
87.1399999998669
87.92999999986331
88.63799999986009
89.30399999985706
90.13199999985329
90.65399999985091
91.18599999984849
91.68499999984621
Year 21 has positive returns with probability 91.68499999984621