(** [![Binder](img/badge-binder.svg)](https://mybinder.org/v2/gh/nhirschey/teaching/gh-pages?filepath=stockslongrun.ipynb)  [![Script](img/badge-script.svg)](/Teaching//stockslongrun.fsx)  [![Notebook](img/badge-notebook.svg)](/Teaching//stockslongrun.ipynb) # Stocks for the long run To explore working with returns, we're going to simulate return data and look at how our portfolio would grow. We will do analysis using relatively simple syntax common to many programming languages to make learning easier. *) #r "nuget: FSharp.Stats, 0.5.0" #r "nuget: Plotly.NET, 3.*" #r "nuget: Plotly.NET.Interactive, 3.*" open FSharp.Stats open Plotly.NET (** From 1/1871-1/2023, the US market return was annualized 7.5% with a 14% standard deviation (Robert Schiller data). *) let rnorm = Distributions.Continuous.Normal.Init 0.075 0.14 for i = 0 to 5 do printfn $"{rnorm.Sample()}"(* output: -0.04713500363917163 -0.10631365578089781 0.05753517879871741 -0.07849860985076322 -0.1481297214692095 -0.24806071516942002*) (** Put sampled values in a list. *) let returns = [ for i = 1 to 1000 do rnorm.Sample() ] (** Plot the distribution of returns. *) let hist = Chart.Histogram(returns) hist(* output:
*) let percentiles = [ for p in [0.0; 0.01; 0.05; 0.5; 0.95; 0.99; 1.0] do let pctl = Quantile.compute p returns p, pctl ](* output: val percentiles: (float * float) list = [(0.0, -0.3829766046); (0.01, -0.2493394921); (0.05, -0.1579542475); (0.5, 0.07277495078); (0.95, 0.2959389059); (0.99, 0.388052132); (1.0, 0.5194030419)]*) for (p, pctl) in percentiles do printfn $"percentile %.2f{p} is {pctl}"(* output: percentile 0.00 is -0.38297660460592675 percentile 0.01 is -0.24933949213419362 percentile 0.05 is -0.15795424750990175 percentile 0.50 is 0.07277495077614787 percentile 0.95 is 0.2959389059143977 percentile 0.99 is 0.38805213202301714 percentile 1.00 is 0.519403041929646*) (** A draw of 10-years of returns. *) let draw10y = [ for i = 1 to 10 do rnorm.Sample() ](* output: val draw10y: float list = [0.4727131934; 0.1613510637; 0.1437149688; 0.08306833005; -0.2342341369; 0.05660844679; -0.03931746301; 0.02412983575; 0.08168102008; -0.06920637651]*) (** Compute the cumulative product of the returns. *) let mutable cumprod = 1.0 for r in draw10y do cumprod <- cumprod * (1.0 + r) printfn $"r={round 3 r}" printfn $" cumprod={round 3 cumprod}"(* output: r=0.473 cumprod=1.473 r=0.161 cumprod=1.71 r=0.144 cumprod=1.956 r=0.083 cumprod=2.119 r=-0.234 cumprod=1.622 r=0.057 cumprod=1.714 r=-0.039 cumprod=1.647 r=0.024 cumprod=1.687 r=0.082 cumprod=1.824 r=-0.069 cumprod=1.698*) (** Let's plot the cumulative return. *) let draw10yCR = let mutable year = 0 let mutable cumprod = 1.0 [ for r in draw10y do cumprod <- cumprod * (1.0 + r) year <- year + 1 year, cumprod ](* output: val draw10yCR: (int * float) list = [(1, 1.472713193); (2, 1.710337034); (3, 1.956138067); (4, 2.11863119); (5, 1.622375442); (6, 1.714215595); (7, 1.646816987); (8, 1.686554411); (9, 1.824313895); (10, 1.698059741)]*) (** Functional version of the cumulative return calculation. *) ((0, 1.0), draw10y) ||> List.scan (fun (year, cumprod) r -> (year + 1, cumprod * (1.0 + r))) |> List.tail(* output: val it: (int * float) list = [(1, 1.472713193); (2, 1.710337034); (3, 1.956138067); (4, 2.11863119); (5, 1.622375442); (6, 1.714215595); (7, 1.646816987); (8, 1.686554411); (9, 1.824313895); (10, 1.698059741)]*) (** Plot the cumulative return. *) let draw10yCrPlot = Chart.Line(draw10yCR) draw10yCrPlot(* output:
*) (** > **Practice**: Sample 20 years of returns and plot the cumulative return. > *) // Answer here (** Let's simulate 1000 draws of 30 years of returns. *) let draw1k = [ for i = 1 to 1_000 do [ for y = 1 to 30 do rnorm.Sample() ] ] let draw1kCR = [ for life in draw1k do let mutable accRet = 1.0 let mutable year = 0 [ for r in life do accRet <- accRet * (1.0 + r) year <- year+1 year, accRet ] ] (** Look at a few observtions from the first draw. *) draw1kCR[0][0..5](* output: val it: (int * float) list = [(1, 0.9762929061); (2, 0.9619054462); (3, 1.039451857); (4, 1.0884101); (5, 1.149589257); (6, 1.585591653)]*) (** Plot the first draw *) Chart.Line(draw1kCR[0])(* output:
*) (** Plot the second draw *) Chart.Line(draw1kCR[1])(* output:
*) (** Plot the first and second together *) [ Chart.Line(draw1kCR[0]) Chart.Line(draw1kCR[1]) ] |> Chart.combine (* output:
*) (** Plot all 1000 draws together *) [ for x in draw1kCR do Chart.Line(x) ] |> Chart.combine |> Chart.withLayoutStyle(ShowLegend=false)(* output:
*) (** A more useful version may be to plot the values at the end of the last year. *) let terminalValues = [ for x in draw1kCR do let (yr, ret) = x[x.Length-1] ret ] terminalValues |> Chart.Histogram(* output:
*) (** What is the chance we lose money? *) let nLoseMoney = terminalValues |> List.filter (fun x -> x < 1.0) |> List.length |> float let chanceLose = nLoseMoney / (float terminalValues.Length) printfn $"chance lose money=%.3f{chanceLose}"(* output: chance lose money=0.003*) (** What is the chance we double our money? *) let nDoubleMoney = terminalValues |> List.filter (fun x -> x >= 2.0) |> List.length |> float let chanceDouble = nDoubleMoney / (float terminalValues.Length) printfn $"chance double money=%.3f{chanceDouble}"(* output: chance double money=0.954*) (** > **Practice**: What is the chance we earn at least a 10% compound rate of return per year? > *) // Answer here (** Now let's say we have 1m EUR and we want to live off of 50k EUR per year. What is the chance that we can do that? *) let expenses = 50_000.0 let initialWealth = 1_000_000.0 let wealthEvolution = [ for life in draw1k do let mutable wealth = initialWealth [ for r in life do // We'll take expenses out at the start of the year. if wealth > expenses then wealth <- (wealth - expenses) * (1.0 + r) else wealth <- 0.0 wealth ] ] let terminalWealth = [ for x in wealthEvolution do x[x.Length-1] ] (** What's the chance that we don't have enough money? *) let nBroke = terminalWealth |> List.filter (fun x -> x <= 0.0) |> List.length |> float let chanceBroke = nBroke / (float terminalWealth.Length) printfn $"chance broke=%.3f{chanceBroke}"(* output: chance broke=0.130*) (** > **Practice**: What is our chance of going broke if the market's standard deviation is 20% per year? > *) // Answer here (** > **Practice**: What is our chance of going broke if the market's return is 5% per year? > *) // Answer here