rblncr | The portfolio model specification
This blog post is part 2 of my follow-along-as-I-build-it series on building an R package called rblncr. The content in this post is somewhat duplicated in the README of that repo.
The current tagline is “Declarative portfolio management with R.” You can read about the motivation behind my work in part 1 of the series.
Today I’m going to take you through the Portfolio Model specification. Whenever I see the term ‘specification’ I get a bit twitchy so let me break it down for you.
At a super high level, want to be able to tell our code what we want our portfolio to look like. The way we deliver this information in a standardized structure so that there are no errors in transmission. The way we achieve this is to specify the structure this information package should take, and then to include validation steps in the transmission procedures of these models. If we do a good job, the specification should be flexible enough to achieve a wide range of use cases without being too verbose and cluttered. Then, we need tooling to work with these models in a safe manner.
An example portfolio model
I have settled on yaml
as the file storage format for portfolio model objects. Here is an example of a portfolio model in yaml:
name: port2
description: create from function
cash:
percent: 10.0
tolerance: 0.0
assets:
- symbol: AAPL
percent: 80.5
tolerance: 2.0
- symbol: GOOG
percent: 9.5
tolerance: 2.0
created_at: 2022-10-19T10:08:50
updated_at: 2022-10-19T10:08:50
Well that’s not so bad! In fact, it’s kind of… obvious. I will just say here that simplicity is deceptive, and this took me like four hours of angst to settle on.
We give our portfolio a name, a description, a desired cash allocation, desired asset allocations, and we keep track of when the portfolio was created and when it was updated.
Under the cash and assets sections, we specify what percentage of the portfolio we want to be allocated to that asset. The asset section can have as many entries as you need - it could be hundreds if you have a complex portfolio strategy.
The tolerance value tells us by what percentage our actual holding values can drift from our targets without triggering a rebalance. So, with percent = 30
and tolerance = 10
, your actual holding can vary by 10% of 30% (ie between 27.27% and 33%) without triggering a rebalance.
Setting appropriate tolerances is something of an art, as it depends on the size of your portfolio, the number of allocations, and trade-offs between trade churn (which is expensive) and appropriate exposure.
I think that the specification is simple enough that you could actually email the file to a human stock broker and they could probably rebalance your portfolio for you with no further input!
The sharper readers may notice that we do not specify any rebalancing constraints or schedules. One could, for example, add a section called rebalance_schedule
where you could write monthly
, daily
, quarterly
or whatever. But I actually think this confuses the essential elements of a portfolio model from the implementation details of portfolio rebalancing runs.
The portfolio model, to my mind, specifies your desired state. Your current holdings describe your current state. The generated order book describes what needs to happen to get your current state to your desired state. The rebalancing schedule (AKA cron
) - yet to be built - will tell us when we should perform this rebalancing.
Manipulating Portfolio Models
Ok, we have a specification. How to we use it? Here is a short demo of how to use the functions in the linked repo to create, save, read and modify portfolio models. Pretty much copy-masted from the README.
Let’s create a cash
list and asset_weights
data frame. The structure of these is important - if you use different object types or column names the validator will fail!
cash <- list(percent = 10, tolerance = 2)
asset_weights <- data.frame(symbol = c("AAPL","GOOG"), percent = c(80.5,9.5), tolerance = c(2,2))
Let’s use them to create a sample portfolio.
sample_portfolio <- create_portfolio_model("sample_portfolio",
"create from function",
cash = cash,
asset_weights = asset_weights)
Here’s the actual structure of the object:
> str(sample_portfolio)
> str(sample_portfolio)
List of 6
$ name : chr "sample_portfolio"
$ description: chr "create from function"
$ cash :List of 2
..$ percent : num 10
..$ tolerance: num 0
$ assets :'data.frame': 2 obs. of 3 variables:
..$ symbol : chr [1:2] "AAPL" "GOOG"
..$ percent : num [1:2] 80.5 9.5
..$ tolerance: num [1:2] 2 2
$ created_at : chr "2022-10-19T10:08:50"
$ updated_at : chr "2022-10-19T10:08:50"
We can save the model to yaml, and we can open that file in a text editor to verify that it looks like the example above.
save_portfolio_model(sample_portfolio, "inst/extdata/sample_portfolio.yaml")
We can load a model from yaml
loaded_model <- load_portfolio_model("inst/extdata/sample_portfolio.yaml")
And we can check that it is a valid portfolio model object.
validate_portfolio_model(loaded_model)
We can change a model by either manually editing the yaml file, or by updating an element of the list.
modified_model <- update_portfolio_model(loaded_model, "name", "port3")
Note that the name
and updated_at
elements have changed.
> str(modified_model)
List of 6
$ name : chr "port3"
$ description: chr "create from function"
$ cash :List of 2
..$ percent : num 10
..$ tolerance: num 0
$ assets :'data.frame': 2 obs. of 3 variables:
..$ symbol : chr [1:2] "AAPL" "GOOG"
..$ percent : num [1:2] 80.5 9.5
..$ tolerance: num [1:2] 2 2
$ created_at : chr "2022-10-19T10:08:50"
$ updated_at : chr "2022-10-19T10:20:00"
We can then write that updated model to the same location (if, say, we want to trigger a git commit based workflow) or to a new location.
Wrap Up
Well, that’s it! Now I’m going to work on connecting to a broker, and obtaining current portfolio positions. I’ve chosen Alpaca as the broker to develop against, so I’m off to read their API docs.