Yesterday afternoon, I had a bit of time on my hands, and a report that someone desperately needed redone. The original author was a support engineer who was trying to learn Ruby and didn’t realize that Ruby hash-maps are case sensitive. In the interest of time, I corrected the issue by downcasing all the keys, and reran the script (which ran successfully, but inelegantly), but I looked at the problem, and once again, found a perfectly lovely use case for the language I love so much, F#.
First off, some of the basics.
open System // these may not be strictly necessary, but they do help describe // precisely what data I'm dealing with module UpperCaseString = type UpperCaseString = UpperCaseString of string let private upper (a:string) = a.ToUpper() let create (a:string) = if String.IsNullOrEmpty(a) then None else Some (UpperCaseString (upper a)) let value (UpperCaseString s) = s open UpperCaseString module FloatingDecimalBetweenZeroAndOne = type FloatingDecimalBetweenZeroAndOne = FloatingDecimalBetweenZeroAndOne of decimal let create (a:decimal) = if a > Decimal.One || a < Decimal.Zero then None else Some (FloatingDecimalBetweenZeroAndOne a) let value (FloatingDecimalBetweenZeroAndOne f) = f let make a = (create a |> Option.get) open FloatingDecimalBetweenZeroAndOne
Generally, I’ll do this for most business application coding nowadays. Creating a simple type that encapsulates correctness from the get-go (including basic validation) makes it so I don’t have to try to figure out weird results. I know the sorts of things I’m dealing with, and I deal with them from the beginning. Also, given the fundamental problem of the original script I had (the case sensitive hash-maps), ensuring I had a type that enforced upper-casing seemed worthwhile.
Next, the domain.
type Model = Model of UpperCaseString with static member Value (Model (UpperCaseString s)) = s type Ticker = Ticker of UpperCaseString with static member Value (Ticker (UpperCaseString s)) = s type Holding = { Ticker : Ticker; Allocation : FloatingDecimalBetweenZeroAndOne } type ModelAllocations = { Model : Model; Holdings : Holding list } type ModelWeight = { Model : Model; Weight : FloatingDecimalBetweenZeroAndOne } type SleevedHolding = { Ticker : Ticker; Model : Model; Allocation : FloatingDecimalBetweenZeroAndOne } with override x.ToString () = sprintf "%s,%s,%5M" (Ticker.Value x.Ticker) (Model.Value x.Model) (FloatingDecimalBetweenZeroAndOne.value x.Allocation) // This is the simplest description of what we're doing. // taking a list of holdings, a list of models and their holdings // a list of weights to apply to the model, and returning a set of 'sleeved' holdings. type SleeveProcess = Holding list -> ModelAllocations list -> ModelWeight list -> SleevedHolding list
The problem the original script was attempting to solve was to assign an appropriate ‘Model’ to a holding. A Model is, for sake of brevity, simply an identity (representing the “name” of a standard financial benchmark (like the S&P 500), and a Ticker an identity representing a stock ticker.
A Holding represents an individual stock in a of a portfolio, with the Allocation representing the percentage of the portfolio. ModelAllocations represent a model, and the list of holdings it has (what stocks are in the S&P 500, etc.) ModelWeight represents the approximate weight a portfolio is associated to a given model or benchmark.
E.G. S&P 500 -> 50% , and Russell 3000 -> 50%.
let makeModel str = UpperCaseString.create str |> Option.get |> Model let makeTicker str = UpperCaseString.create str |> Option.get |> Ticker let holding str all = { Ticker = makeTicker str; Allocation = make all } let makeSleeve t m a = { Ticker = t; Model = m; Allocation = a } let modelWeight str a = { Model = makeModel str; Weight = make a } let modelAllocation str h = { Model = makeModel str; Holdings = h }
One of the problems of heavy use of custom types is that you’ll typically want ‘shorthand’ functions for creating the types as necessary. It can be a pain, but difficult to explain results after the fact is worse.
Finally, the meat of it.
let sleeveHoldings holdings models weights = let findHolding t = models |> List.collect (fun n -> n.Holdings |> List.filter (fun n -> n.Ticker = t) |> List.map (fun x -> (n.Model, x.Allocation))) let sleeve h = let t = h.Ticker let m = findHolding t // a list of models that hold the security match m with | [] -> weights |> List.map (fun w -> let weightTimesAllocation = (value w.Weight * value h.Allocation) makeSleeve t w.Model (make weightTimesAllocation)) | [(x,_)] -> [makeSleeve t x h.Allocation] | xs -> let weightsMap = weights |> List.map (fun n -> (n.Model, value n.Weight)) |> Map.ofList let total = xs |> List.sumBy (fun (m, w) -> weightsMap.[m] * value (w)) xs |> List.map (fun (m, w) -> let weightTimesAllocationOverTotal = (value w * weightsMap.[m]) / total makeSleeve t m (make (weightTimesAllocationOverTotal * value h.Allocation))) holdings |> List.collect sleeve
First, we define a function to find the models with a given holding, and return that model and the allocation. Then we define a function to sleeve an individual holding. Finally, we call that function over the list of holdings passed in.
A simple set of tests:
let testHoldings = [ holding "A" 0.20m; holding "B" 0.20m; holding "C" 0.20m; holding "D" 0.20m; holding "E" 0.20m; ] let testWeights = [ modelWeight "MODEL1" 0.5m; modelWeight "MODEL2" 0.3m; modelWeight "MODEL3" 0.2m; ] let testModelAllocations = [ modelAllocation "MODEL1" [ holding "A" 1.0m; ]; modelAllocation "MODEL2" [ holding "A" 0.5m; holding "B" 0.3m; holding "D" 0.2m; ]; modelAllocation "MODEL3" [ holding "B" 0.5m; holding "C" 0.3m; holding "D" 0.2m; ] ] sleeveHoldings testHoldings testModelAllocations testWeights |> List.map (fun m -> m.ToString ()) val it : string list = ["A,MODEL1,0.1538461538461538461538461538"; "A,MODEL2,0.0461538461538461538461538462"; "B,MODEL2,0.0947368421052631578947368421"; "B,MODEL3,0.1052631578947368421052631579"; "C,MODEL3, 0.20"; "D,MODEL2,0.120"; "D,MODEL3,0.080"; "E,MODEL1,0.100"; "E,MODEL2,0.060"; "E,MODEL3,0.040"]
At the financial services firm I work for, this concept of attributing holdings is called ‘sleeving.’