Header menu logo Teaching

BinderScriptNotebook

Objectives

Loading Fama and French data

#r "nuget: FSharp.Data, 5.0.2"
#r "nuget: NovaSBE.Finance, 0.5.0"
#r "nuget: FSharp.Stats, 0.5.0"
#r "nuget: Plotly.NET, 3.*"
#r "nuget: Plotly.NET.Interactive, 3.*"

open System
open FSharp.Data
open FSharp.Stats
open Plotly.NET
open NovaSBE.Finance.French

let ff3 = 
    getFF3 Frequency.Daily 
    |> Array.toList

Faster code

Let's revisit our exponentially weighted volatility predicton from before.

This is the original, working with lists, with some minor changes for clarity.

type ReturnObs = { Date: DateTime; Return: float}

type VolatilityPrediction = 
    { /// First date the prediction is valid for
      Date: DateTime
      /// The volatility prediction
      PredictedVol: float}

let expRealizedVolList (width: int) (lambda: float) (data: list<ReturnObs>) =
    data
    // Descending gets us ordered t, t-1, t-2, ...
    |> List.sortByDescending (fun x -> x.Date)
    |> List.windowed (width + 1)
    |> List.map (fun window ->
        let dayToPredict = window[0]
        let trainingDays = window[1..]
        let mu = 
            trainingDays 
            |> List.map (fun x -> x.Return) 
            |> List.average
        let sd =
            [ for t = 1 to width do
                let w = (1.0 - lambda)*lambda**(float t - 1.0)
                w * (trainingDays[t-1].Return - mu)**2.0 ]
            |> List.sum
            |> sqrt

        { VolatilityPrediction.Date = dayToPredict.Date; PredictedVol = sd })
    |> List.rev

let's time it.

let rets = ff3 |> List.map (fun x -> { Date = x.Date; Return = x.MktRf})

#if FSX
#time "on"
#endif // FSX

let vList = expRealizedVolList 500 0.94 rets

That's kinda slow.

Part of the reason is that it's iterating through lists, and iterating through large lists can be slow.

Try it with array.

let xvArray (width: int) (lambda: float) (data: array<ReturnObs>) =
    data
    // Descending gets us ordered t, t-1, t-2, ...
    |> Array.sortByDescending (fun x -> x.Date)
    |> Array.windowed (width + 1)
    |> Array.map (fun window ->
        let dayToPredict = window[0]
        let trainingDays = window[1..]
        let mu = 
            trainingDays 
            |> Array.map (fun x -> x.Return) 
            |> Array.average
        let sd =
            [| for t = 1 to width do
                let w = (1.0 - lambda)*lambda**(float t - 1.0)
                w * (trainingDays[t-1].Return - mu)**2.0 |]
            |> Array.sum
            |> sqrt

        { VolatilityPrediction.Date = dayToPredict.Date; PredictedVol = sd })
    |> Array.rev

Data to test it.

let retsArray = rets |> List.toArray

Test it.

let vArray = xvArray 500 0.94 retsArray

That's somewhat faster.

We can be even faster by reducing allocations.

let xvArrayFewerAlloc (width: int) (lambda: float) (data: array<ReturnObs>) =
    data
    |> Array.sortByDescending (fun x -> x.Date)
    |> Array.windowed (width + 1)
    |> Array.map (fun window ->
        let mu = window[1..] |> Array.averageBy (fun x -> x.Return)
        let mutable acc = 0.0
        for t = 1 to width do 
            let w = (1.0 - lambda)*lambda**(float t - 1.0)
            acc <- acc + w * (window[t].Return - mu)**2.0
        { VolatilityPrediction.Date = window[0].Date; PredictedVol = sqrt acc })
    |> Array.rev

Test it.

let vFewerAlloc = xvArrayFewerAlloc 500 0.94 retsArray 

Even fewer allocations.

let xvArrayFewerAlloc2 (width: int) (lambda: float) (data: array<ReturnObs>) =
    let data = data |> Array.sortByDescending (fun x -> x.Date)
    data[..data.Length-1-width]
    |> Array.mapi (fun i x ->
        let mu = data[i+1..i+width] |> Array.averageBy (fun x -> x.Return)
        let mutable acc = 0.0
        for t = 1 to width do 
            let w = (1.0 - lambda)*lambda**(float t - 1.0)
            acc <- acc + w * (data[i+t].Return - mu)**2.0
        { VolatilityPrediction.Date = x.Date; PredictedVol = sqrt acc })
    |> Array.rev

let vFewerAlloc2 = xvArrayFewerAlloc2 500 0.94 retsArray 

Now make the fewer alloc version parallel.

let xvFewerAllocParallel (width: int) (lambda: float) (data: array<ReturnObs>) =
    data
    |> Array.sortByDescending (fun x -> x.Date)
    |> Array.windowed (width + 1)
    |> Array.Parallel.map (fun window ->
        let mu = window[1..] |> Array.averageBy (fun x -> x.Return)
        let mutable acc = 0.0
        for t = 1 to width do 
            let w = (1.0 - lambda)*lambda**(float t - 1.0)
            acc <- acc + w * (window[t].Return - mu)**2.0
        { VolatilityPrediction.Date = window[0].Date; PredictedVol = sqrt acc })
    |> Array.rev

Test it.

let vFewerParallel = xvFewerAllocParallel 500 0.94 retsArray

Now make the second fewer alloc version parallel.

let xvFewerAlloc2Parallel (width: int) (lambda: float) (data: array<ReturnObs>) =
    let data = data |> Array.sortByDescending (fun x -> x.Date)
    data[..data.Length-1-width]
    |> Array.Parallel.mapi (fun i x ->
        let mu = data[i+1..i+width] |> Array.averageBy (fun x -> x.Return)
        let mutable acc = 0.0
        for t = 1 to width do 
            let w = (1.0 - lambda)*lambda**(float t - 1.0)
            acc <- acc + w * (data[i+t].Return - mu)**2.0
        { VolatilityPrediction.Date = x.Date; PredictedVol = sqrt acc })
    |> Array.rev

Test it.

let vFewer2Parallel = xvFewerAlloc2Parallel 500 0.94 retsArray

Compare results.

(vList |> List.toArray) = vFewerAlloc2

[ vArray; vFewerAlloc; vFewerAlloc2; vFewerParallel; vFewer2Parallel]
|> List.forall (fun x -> x = (vList |> List.toArray ))

Return predictions

type ReturnPrediction = { Date: DateTime; PredictedReturn: float }

let avgReturnAccumulatorList (xs: list<ReturnObs>) =
    let mutable acc = 0.0
    [ for i=0 to (xs.Length-2) do
        acc <- acc + xs[i].Return
        let avgReturn = acc / float (i + 1)
        { Date = xs[i+1].Date; PredictedReturn = avgReturn }]

Test it.

let testData =
    [{ Date = DateTime(1999,1,1); Return = 1.0 }
     { Date = DateTime(1999,1,2); Return = 2.0 }
     { Date = DateTime(1999,1,3); Return = -3.0 } 
     { Date = DateTime(1999,1,4); Return = 0.0 }]

avgReturnAccumulatorList testData

For the whole dataset

let retPredictions = avgReturnAccumulatorList rets

Combining predictions

Now form portfolios based on these predictions.

type VolAndReturnPrediction = { Date: DateTime; PredictedVol: float; PredictedReturn: float }

let combinePredictions (predReturns: seq<ReturnPrediction>) (predVols: seq<VolatilityPrediction>) =
    let predVols = 
        predVols 
        |> Seq.map (fun x -> x.Date, x.PredictedVol) 
        |> Map
    [ for retObs in predReturns do 
        if predVols.ContainsKey retObs.Date then
            { Date = retObs.Date 
              PredictedVol = predVols[retObs.Date]
              PredictedReturn = retObs.PredictedReturn } ]

Let's see how it works

combinePredictions retPredictions[1000..1005] vFewer2Parallel

Now let's have a function that creates returns off of that.

let managedPortfolio gamma predVols predReturns (xs: list<ReturnObs>) =
    let preds = 
        combinePredictions predReturns predVols
        |> List.map (fun x -> x.Date, x)
        |> Map
    [ for x in xs do 
        if preds.ContainsKey x.Date then
            let pred = preds[x.Date]
            let w = pred.PredictedReturn / (gamma* pred.PredictedVol ** 2.0)
            { Date = x.Date
              Return = w * x.Return } ]

Let's see how it works

managedPortfolio 3.0 vFewer2Parallel retPredictions[1000..1005] rets

Doing it for the full sample.

let result = 
    managedPortfolio 3.0 vFewer2Parallel retPredictions rets

Now calculate mean-variance utility of the portfolio.

let avgReturn = result |> List.averageBy (fun x -> x.Return)
let varResult = result |> varBy (fun x -> x.Return)

(avgReturn - (3.0/2.0) * varResult)*252.0

Compare to just buy and hold

let managedMinDate = result |> List.map (fun x -> x.Date) |> List.min
let buyHoldPeriod = rets |> List.filter (fun x -> x.Date >= managedMinDate)
let avgBuyHold = buyHoldPeriod |> List.averageBy (fun x -> x.Return)
let varBuyHold = buyHoldPeriod |> varBy (fun x -> x.Return)

(avgBuyHold - (3.0/2.0) * varBuyHold)*252.0

Now our managed portfolio.

let avgManagedReturn = result |> List.averageBy (fun x -> x.Return)
let varManagedResult = result |> varBy (fun x -> x.Return)

(avgManagedReturn - (3.0/2.0) * varManagedResult)*252.0

Why the difference?

Try scaling managed to full sample variance.

let c = sqrt varBuyHold / sqrt varManagedResult
(c * avgManagedReturn - (3.0/2.0) * c ** 2.0 * varManagedResult)*252.0

Another way of seeing it.

let mvu gamma mu sigma =
    mu - 0.5 * gamma * sigma ** 2.0

mvu 3.0 (avgBuyHold * 252.0) (sqrt (varBuyHold * 252.0))

let w_star = avgBuyHold / (3.0 * varBuyHold)

mvu 3.0 (w_star * avgBuyHold * 252.0) (sqrt (w_star ** 2.0 * varBuyHold * 252.0))
namespace System
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data

--------------------
namespace Microsoft.FSharp.Data
namespace FSharp.Stats
namespace Plotly
namespace Plotly.NET
namespace NovaSBE
namespace NovaSBE.Finance
module French from NovaSBE.Finance
val ff3: FF3Obs list
val getFF3: frequency: Frequency -> FF3Obs array
type Frequency = | Daily | Monthly
union case Frequency.Daily: Frequency
Multiple items
type Array = new: unit -> Array static member geomspace: start: float * stop: float * num: int * ?IncludeEndpoint: bool -> float array static member linspace: start: float * stop: float * num: int * ?IncludeEndpoint: bool -> float array

--------------------
new: unit -> Array
val toList: array: 'T array -> 'T list
type ReturnObs = { Date: DateTime Return: float }
Multiple items
[<Struct>] type DateTime = new: year: int * month: int * day: int -> unit + 16 overloads member Add: value: TimeSpan -> DateTime member AddDays: value: float -> DateTime member AddHours: value: float -> DateTime member AddMicroseconds: value: float -> DateTime member AddMilliseconds: value: float -> DateTime member AddMinutes: value: float -> DateTime member AddMonths: months: int -> DateTime member AddSeconds: value: float -> DateTime member AddTicks: value: int64 -> DateTime ...
<summary>Represents an instant in time, typically expressed as a date and time of day.</summary>

--------------------
DateTime ()
   (+0 other overloads)
DateTime(ticks: int64) : DateTime
   (+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime(date: DateOnly, time: TimeOnly) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int) : DateTime
   (+0 other overloads)
DateTime(date: DateOnly, time: TimeOnly, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : DateTime
   (+0 other overloads)
Multiple items
val float: value: 'T -> float (requires member op_Explicit)

--------------------
type float = Double

--------------------
type float<'Measure> = float
type VolatilityPrediction = { Date: DateTime PredictedVol: float }
val expRealizedVolList: width: int -> lambda: float -> data: ReturnObs list -> VolatilityPrediction list
val width: int
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
val lambda: float
val data: ReturnObs list
type 'T list = List<'T>
Multiple items
module List from FSharp.Stats
<summary> Module to compute common statistical measure on list </summary>

--------------------
module List from Microsoft.FSharp.Collections

--------------------
type List = new: unit -> List static member geomspace: start: float * stop: float * num: int * ?IncludeEndpoint: bool -> float list static member linspace: start: float * stop: float * num: int * ?IncludeEndpoint: bool -> float list

--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...

--------------------
new: unit -> List
val sortByDescending: projection: ('T -> 'Key) -> list: 'T list -> 'T list (requires comparison)
val x: ReturnObs
ReturnObs.Date: DateTime
val windowed: windowSize: int -> list: 'T list -> 'T list list
val map: mapping: ('T -> 'U) -> list: 'T list -> 'U list
val window: ReturnObs list
val dayToPredict: ReturnObs
val trainingDays: ReturnObs list
val mu: float
ReturnObs.Return: float
val average: list: 'T list -> 'T (requires member (+) and member DivideByInt and member Zero)
val sd: float
val t: int
val w: float
val sum: list: 'T list -> 'T (requires member (+) and member Zero)
val sqrt: value: 'T -> 'U (requires member Sqrt)
val rev: list: 'T list -> 'T list
val rets: ReturnObs list
val x: FF3Obs
FF3Obs.Date: DateTime
FF3Obs.MktRf: float
val vList: VolatilityPrediction list
val xvArray: width: int -> lambda: float -> data: ReturnObs array -> VolatilityPrediction array
val data: ReturnObs array
type 'T array = 'T array
val sortByDescending: projection: ('T -> 'Key) -> array: 'T array -> 'T array (requires comparison)
val windowed: windowSize: int -> array: 'T array -> 'T array array
val map: mapping: ('T -> 'U) -> array: 'T array -> 'U array
val window: ReturnObs array
val trainingDays: ReturnObs array
val average: array: 'T array -> 'T (requires member (+) and member DivideByInt and member Zero)
val sum: array: 'T array -> 'T (requires member (+) and member Zero)
val rev: array: 'T array -> 'T array
val retsArray: ReturnObs array
val toArray: list: 'T list -> 'T array
val vArray: VolatilityPrediction array
val xvArrayFewerAlloc: width: int -> lambda: float -> data: ReturnObs array -> VolatilityPrediction array
val averageBy: projection: ('T -> 'U) -> array: 'T array -> 'U (requires member (+) and member DivideByInt and member Zero)
val mutable acc: float
val vFewerAlloc: VolatilityPrediction array
val xvArrayFewerAlloc2: width: int -> lambda: float -> data: ReturnObs array -> VolatilityPrediction array
property Array.Length: int with get
<summary>Gets the total number of elements in all the dimensions of the <see cref="T:System.Array" />.</summary>
<exception cref="T:System.OverflowException">The array is multidimensional and contains more than <see cref="F:System.Int32.MaxValue">Int32.MaxValue</see> elements.</exception>
<returns>The total number of elements in all the dimensions of the <see cref="T:System.Array" />; zero if there are no elements in the array.</returns>
val mapi: mapping: (int -> 'T -> 'U) -> array: 'T array -> 'U array
val i: int
val vFewerAlloc2: VolatilityPrediction array
val xvFewerAllocParallel: width: int -> lambda: float -> data: ReturnObs array -> VolatilityPrediction array
module Parallel from Microsoft.FSharp.Collections.ArrayModule
val vFewerParallel: VolatilityPrediction array
val xvFewerAlloc2Parallel: width: int -> lambda: float -> data: ReturnObs array -> VolatilityPrediction array
val vFewer2Parallel: VolatilityPrediction array
val forall: predicate: ('T -> bool) -> list: 'T list -> bool
val x: VolatilityPrediction array
type ReturnPrediction = { Date: DateTime PredictedReturn: float }
val avgReturnAccumulatorList: xs: ReturnObs list -> ReturnPrediction list
val xs: ReturnObs list
property List.Length: int with get
val avgReturn: float
val testData: ReturnObs list
val retPredictions: ReturnPrediction list
type VolAndReturnPrediction = { Date: DateTime PredictedVol: float PredictedReturn: float }
val combinePredictions: predReturns: ReturnPrediction seq -> predVols: VolatilityPrediction seq -> VolAndReturnPrediction list
val predReturns: ReturnPrediction seq
Multiple items
val seq: sequence: 'T seq -> 'T seq

--------------------
type 'T seq = Collections.Generic.IEnumerable<'T>
val predVols: VolatilityPrediction seq
val predVols: Map<DateTime,float>
Multiple items
module Seq from FSharp.Stats
<summary> Module to compute common statistical measure </summary>

--------------------
module Seq from Microsoft.FSharp.Collections

--------------------
type Seq = new: unit -> Seq static member geomspace: start: float * stop: float * num: int * ?IncludeEndpoint: bool -> float seq static member linspace: start: float * stop: float * num: int * ?IncludeEndpoint: bool -> float seq

--------------------
new: unit -> Seq
val map: mapping: ('T -> 'U) -> source: 'T seq -> 'U seq
val x: VolatilityPrediction
VolatilityPrediction.Date: DateTime
 First date the prediction is valid for
VolatilityPrediction.PredictedVol: float
 The volatility prediction
Multiple items
module Map from FSharp.Stats
<summary> Module to strore specialised computations on maps </summary>

--------------------
module Map from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> = interface IReadOnlyDictionary<'Key,'Value> interface IReadOnlyCollection<KeyValuePair<'Key,'Value>> interface IEnumerable interface IStructuralEquatable interface IComparable interface IEnumerable<KeyValuePair<'Key,'Value>> interface ICollection<KeyValuePair<'Key,'Value>> interface IDictionary<'Key,'Value> new: elements: ('Key * 'Value) seq -> Map<'Key,'Value> member Add: key: 'Key * value: 'Value -> Map<'Key,'Value> ...

--------------------
new: elements: ('Key * 'Value) seq -> Map<'Key,'Value>
val retObs: ReturnPrediction
member Map.ContainsKey: key: 'Key -> bool
ReturnPrediction.Date: DateTime
ReturnPrediction.PredictedReturn: float
val managedPortfolio: gamma: float -> predVols: VolatilityPrediction seq -> predReturns: ReturnPrediction seq -> xs: ReturnObs list -> ReturnObs list
val gamma: float
val preds: Map<DateTime,VolAndReturnPrediction>
val x: VolAndReturnPrediction
VolAndReturnPrediction.Date: DateTime
val pred: VolAndReturnPrediction
VolAndReturnPrediction.PredictedReturn: float
VolAndReturnPrediction.PredictedVol: float
val result: ReturnObs list
val averageBy: projection: ('T -> 'U) -> list: 'T list -> 'U (requires member (+) and member DivideByInt and member Zero)
val varResult: float
val varBy: f: ('T -> 'a) -> items: 'T seq -> 'U (requires member (-) and member Zero and member DivideByInt and member (+) and member ( * ) and member (+) and member (/))
<summary> Computes the sample variance (Bessel's correction by N-1) </summary>
<param name="f">A function applied to transform each element of the sequence.</param>
<param name="items">The input sequence.</param>
<remarks>Returns NaN if data is empty or if any entry is NaN.</remarks>
<returns>variance of a sample (Bessel's correction by N-1)</returns>
val managedMinDate: DateTime
val min: list: 'T list -> 'T (requires comparison)
val buyHoldPeriod: ReturnObs list
val filter: predicate: ('T -> bool) -> list: 'T list -> 'T list
val avgBuyHold: float
val varBuyHold: float
val avgManagedReturn: float
val varManagedResult: float
val c: float
val mvu: gamma: float -> mu: float -> sigma: float -> float
val sigma: float
val w_star: float

Type something to start searching.