Skip to content
13

API introduction

This section explains PortfolioOptimisers.jl API in detail. The pages are organised in exactly the same way as the src folder itself. This means there should be a 1 to 1 correspondence between documentation and source files[1].

Design philosophy

There are three overarching design choices in PortfolioOptimisers.jl:

1. Well-defined type hierarchies

  • Easily and quickly add new features by sticking to defined interfaces.

2. Strongly typed immutable structs

  • All types are concrete and known at instantiation.

  • Constants can be propagated if necessary.

  • There is always a single immutable source of truth for every process.

  • If needed, modifying values must be done via interface functions, which simplifies finding and fixing bugs. If the interface for modification is not provided the code will throw a missing method exception.

3. Compositional design

  • PortfolioOptimisers.jl is a toolkit whose components can interact in complex, deeply nested ways.

  • Separation of concerns lets us subdivide logical components into isolated, self-contained units. Leading to easier and fearless development and testing.

  • Extensive and judicious data validation checks are performed at the earliest possible moment–-mostly at variable instantiation–-to ensure correctness.

  • Turtles all the way down. Structures can be used, reused, and nested in many ways. This allows for efficient data reuse and arbitrary complexity.

Design goals

This philosophy has three primary goals:

1. Maintainability and expandability

  • The only way to break existing functionality should be by modifying APIs.

  • Adding functionality should be a case of subtyping existing abstract types and implementing the correct interfaces.

  • Avoid leaking side effects to other components unless completely necessary. An example of this is entropy pooling requiring the use of a vector of observation weights which must be taken into account in different, largely unrelated places.

2. Correctness and robustness

  • Each subunit should perform its own data validation as early as possible unless it absolutely needs downstream data.

3. Performance

  • Types and constants are always fully known at inference time.

  • Immutability ensures smaller structs live in the stack.

Features

This section is under active development so any [<name>]-(@ref) lacks docstrings.

Preprocessing

Matrix processing

Regression models

Factor prior models and implied volatility use regression in their estimation, which return a Regression object.

Regression targets

Regression types

Moment estimation

Expected returns

Overloads Statistics.mean.

Variance and standard deviation

Overloads Statistics.var and Statistics.std.

  • Optionally weighted variance with custom expected returns estimator SimpleVariance

Covariance and correlation

Overloads Statistics.cov and Statistics.cor.

Coskewness

Implements coskewness.

  • Coskewness and spectral decomposition of the negative coskewness with custom expected returns estimator and matrix processing pipeline Coskewness

Cokurtosis

Implements cokurtosis.

  • Cokurtosis with custom expected returns estimator and matrix processing pipeline Cokurtosis

Distance matrices

Implements distance and cor_and_dist.

  • First order distance estimator with custom distance algorithm, and optional exponent Distance

  • Second order distance estimator with custom pairwise distance algorithm from Distances.jl, custom distance algorithm, and optional exponent DistanceDistance

The distance estimators are used together with various distance matrix algorithms.

Phylogeny

PortfolioOptimisers.jl can make use of asset relationships to perform optimisations, define constraints, and compute relatedness characteristics of portfolios.

Clustering

Phylogeny constraints and clustering optimisations make use of clustering algorithms via ClustersEstimator, Clusters, and clusterise. Most clustering algorithms come from Clustering.jl.

Hierarchical
Non-hierarchical

Non-hierarchical clustering algorithms are incompatible with hierarchical clustering optimisations, but they can be used for phylogeny constraints and NestedClustered optimisations.

Networks

Adjacency matrices

Adjacency matrices encode asset relationships either with clustering or graph theory via phylogeny_matrix and PhylogenyResult.

Centrality and phylogeny measures

Optimisation constraints

Non clustering optimisers support a wide range of constraints, while naive and clustering optimisers only support weight bounds. Furthermore, entropy pooling prior supports a variety of views constraints. It is therefore important to provide users with the ability to generate constraints manually and/or programmatically. We therefore provide a wide, robust, and extensible range of types such as AbstractEstimatorValueAlgorithm and UniformValues, and functions that make this easy, fast, and safe.

Constraints can be defined via their estimators or directly by their result types. Some using estimators need to map key-value pairs to the asset universe, this is done by defining the assets and asset groups in AssetSets. Internally, PortfolioOptimisers.jl uses all the information and calls group_to_val!, and replace_group_by_assets to produce the appropriate arrays.

Prior statistics

Many optimisations and constraints use prior statistics computed via prior.

Uncertainty sets

In order to make optimisations more robust to noise and measurement error, it is possible to define uncertainty sets on the expected returns and covariance. These can be used in optimisations which use either of these two quantities. These are implemented via ucs, mu_ucs, and sigma_ucs.

PortfolioOptimisers.jl implements two types of uncertainty sets.

It also implements various estimators for the uncertainty sets, the following two can generate box and ellipsoidal sets.

The following estimator can only generate box sets.

Turnover

The turnover is defined as the element-wise absolute difference between the vector of current weights and a vector of benchmark weights. It can be used as a constraint, method for fee calculation, and risk measure. These are all implemented using turnover_constraints, TurnoverEstimator, and Turnover.

Fees

Fees are a non-negligible aspect of active investing. As such PortfolioOptimiser.jl has the ability to account for them in all optimisations but the naive ones. They can also be used to adjust expected returns calculations via calc_fees and calc_asset_fees.

  • Fees FeesEstimator and Fees
    • Proportional long

    • Proportional short

    • Fixed long

    • Fixed short

    • Turnover

Portfolio returns and drawdowns

Various risk measures and analyses require the computation of simple and cumulative portfolio returns and drawdowns both in aggregate and per-asset. These are computed by calc_net_returns, calc_net_asset_returns, cumulative_returns, drawdowns.

Tracking

It is often useful to create portfolios that track the performance of an index, indicator, or another portfolio.

The error can be computed using different algorithms using norm_tracking.

It is also possible to track the error in with risk measures RiskTrackingError using WeightsTracking, which allows for two approaches.

Risk measures

PortfolioOptimisers.jl provides a wide range of risk measures. These are broadly categorised into two types based on the type of optimisations that support them.

Risk measures for traditional optimisation

These are all subtypes of RiskMeasure, and are supported by all optimisation estimators.

Risk measures for hierarchical optimisation

These are all subtypes of HierarchicalRiskMeasure, and are only supported by hierarchical optimisation estimators.

Non-optimisation risk measures

These risk measures are unsuitable for optimisation because they can return negative values. However, they can be used for performance metrics.

Performance metrics

Portfolio optimisation

Optimisations are implemented via optimise. Optimisations consume an estimator and return a result.

Naive

These return a NaiveOptimisationResult.

Naive optimisation features

Traditional

These optimisations are implemented as JuMP problems and make use of JuMPOptimiser, which encodes all supported constraints.

Objective function optimisations

These optimisations support a variety of objective functions.

Risk budgeting optimisations

These optimisations attempt to achieve weight values according to a risk budget vector. This vector can be provided on a per asset or per factor basis.

Traditional optimisation features

Clustering optimisation

Clustering optimisations make use of asset relationships to either minimise the risk exposure by breaking the asset universe into subsets which are hierarchically or individually optimised.

Hierarchical clustering optimisation

These optimisations minimise risk by hierarchically splitting the asset universe into subsets, computing the risk of each subset, and combining them according to their hierarchy.

Hierarchical clustering optimisation features
Schur complementary optimisation

Schur complementary hierarchical risk parity provides a bridge between mean variance optimisation and hierarchical risk parity by using an interpolation parameter. It converges to hierarchical risk parity, and approximates mean variance by adjusting this parameter. It uses the Schur complement to adjust the weights of a portfolio according to how much more useful information is gained by assigning more weight to a group of assets.

Schur complementary optimisation features
Nested clusters optimisation

Nested clustered optimisation breaks the asset universe of size N into C smaller subsets and treats every subset as an individual portfolio. The weights assigned to each asset are placed in an N × C matrix. In each column, non-zero values correspond to assets assigned to that subset, this means that assets only contribute to the column (and therefore synthetic asset) corresponding to their assigned subset. In other words, each row of the matrix contains a single non-zero value and each row contains as many non-zero values as there are assets in that subset.

From here there are two options:

  1. Compute the returns matrix of the synthetic assets directly by multiplying the original T × N matrix by the N × C matrix of asset weights to produce a T × C matrix of predicted returns, where T is the number of observations.

  2. For each subset perform a cross validation prediction, yielding a vector of returns for that subset. These vectors are then horizontally concatenated into a Y × C matrix of cross-validation predicted returns, where Y ≤ T because the cross validation may not use the full history.

This matrix of predicted returns is then used by the outer optimisation estimator to generate an optimisation of the synthetic assets. This produces a C × 1 vector, essentially optimising a portfolio of asset clusters. The final weights are the product of the original N × C matrix of asset weights per cluster by the C × 1 vector of optimal synthetic asset weights to produce the final N × 1 vector of asset weights.

Nested clusters optimisation features

Ensemble optimisation

This works similarly to the Nested Clustered estimator, only instead of breaking the asset universe into subsets, a list of inner estimators is provided. The procedure is then exactly the same as the nested clusters optimisation, only instead of an N × C matrix of asset weights where each column corresponds to a subset of assets, each column corresponds to a completely independent and isolated inner estimator, which also means there is no enforced sparsity pattern on this matrix.

Ensemble optimisation features

Subset resampling optimisation

This optimiser takes ideas from MultipleRandomised cross validation to randomly sample the asset universe and optimise each sample individually using a given optimiser. The final asset weights are the average weight per asset across all samples, if an asset does not appear in a sample, it is taken to be zero.

Subset resampling optimisation features

Finite allocation optimisation

Unlike all other estimators, finite allocation does not yield an "optimal" value, but rather the optimal attainable solution based on a finite amount of capital. They use the result of other estimations, the latest prices, and a cash amount.

Cross validation

Plotting

Visualising the results is quite a useful way of summarising the portfolio characteristics or evolution. To this extent we provide a few plotting functions with more to come.


  1. Except for a few cases, most of which are convenience function overloads. This means some links do not go to the exact method definition. Other than hard-coding links to specific lines of code, which is fragile, I haven't found an easy solution. ↩︎