Factor Black-Litterman Prior
PortfolioOptimisers.FactorBlackLittermanPrior — Type
struct FactorBlackLittermanPrior{__T_pe, __T_f_mp, __T_mp, __T_re, __T_ve, __T_views, __T_sets, __T_views_conf, __T_w, __T_rf, __T_l, __T_tau, __T_rsd, __T_cache} <: AbstractLowOrderPriorEstimator_FFactor Black-Litterman prior estimator for asset returns.
FactorBlackLittermanPrior is a low order prior estimator that computes the mean and covariance of asset returns using a factor-based Black-Litterman model. It combines an asset prior estimator, matrix post-processing for factors and assets, regression and variance estimators, user or algorithmic views, asset sets, view confidences, weights, risk-free rate, leverage, blending parameter tau, and a residual variance flag. This estimator supports both direct and constraint-based views, flexible confidence specification, and matrix processing, and incorporates factor regression and residual adjustment for posterior inference.
Fields
pe: Prior estimator.
f_mp: Factor matrix processing estimator.
mp: Matrix processing estimator.
re: Regression estimator.
ve: Variance estimator.
views: Views estimator or result.
sets: Universe sets. The time-series factor axis,sets.dict[sets.tfkey], is what this estimator reads: it is the universe the views are written in, and it must name the columns ofFin order. The asset axis is required byUniverseSetsand is what a view slices — the factor entries come back fromport_opt_viewuntouched.
views_conf: Views confidence estimator or result.
w: Optional equilibrium weights vectorassets × 1. Ifnothing, equal weights are used.
rf: Risk-free rate. The Black-Litterman update blends the prior mean against the view returns, so it runs on the total-return scale those are written on. A mean taken from a wrapped prior estimator is on that scale already; an equilibrium mean is a bare risk premium, and the rate converts it before the update. A member with no equilibrium branch has nothing to convert and adds the rate to the posterior asset expected returns instead. It is added exactly once either way, and the wrapped prior estimators are left alone, so a risk-free rate one of them applied internally stays where it is.
l: Risk aversion parameter.
tau: Blending parameter controlling the weight given to the prior relative to the views.
rsd: Whether to include residual variance in the posterior covariance.
cache: Optional partial-fit state. It isnothinguntilpartial_fit!writes one, and the estimator's read-out verb reads it when the caller gives no data matrix. Each propagation channel does one thing with it:factorycarries it unchanged, because a factory call resolves configuration rather than the sample;port_opt_viewslices it to the selected assets by index copy, so the viewed estimator answers over those assets alone; andobs_weights_viewdrops it, because no slice of a state exists on the observation axis. A family whose state has no exact asset slice drops it on both axes and names the reason.
Constructors
FactorBlackLittermanPrior(; pe::AbstractLowOrderPriorEstimator_A_AF = EmpiricalPrior(), f_mp::AbstractMatrixProcessingEstimator = MatrixProcessing(), mp::AbstractMatrixProcessingEstimator = MatrixProcessing(), re::AbstractTimeSeriesRegressionEstimator = StepwiseRegression(), ve::AbstractVarianceEstimator = SimpleVariance(), views::Lc_BLV, sets::Option{<:UniverseSets} = nothing, views_conf::Option{<:Num_VecNum} = nothing, w::Option{<:VecNum} = nothing, rf::Number = 0.0, l::Option{<:Number} = nothing, tau::Option{<:Number} = nothing, rsd::Bool = true, cache::Option{<:AbstractPartialFitState} = nothing) -> FactorBlackLittermanPriorKeywords correspond to the struct's fields.
Composition: what this estimator forwards
This estimator lifts a factor-axis prior onto the asset axis, reconstructing X as F * transpose(M) .+ transpose(b), so it builds its carrier directly rather than forwarding one along its own axis; each field still follows the pattern of a posterior field replacing its prior counterpart while a structural field forwards whole. It is the member of the Black-Litterman family whose factor block is modified rather than passed through — the views land on the factor distribution, and the assets are its projection.
- The factor block
fpris the posterior factor distribution, processed byf_mp, withcholdropped because the posterior covariance supersedes the one it factorises. Itswand that weighting's diagnostics forward untouched. muandsigmaare that block projected through the loadings, so the returned carrier is internally consistent:mu == rr.M * fpr.mu + rr.bholds by construction, whateverrfis, because the rate is insidefpr.muwhere it is present at all.sigmaoptionally gains a residual correction whenrsdistrue.wis the factor prior's, and is over the right axis: this estimator wraps only a factor prior, andposterior_Xhas exactlyF's rows, so it is the only weighting in existence.
Its siblings differ: BayesianBlackLittermanPrior also satisfies the identity exactly, while BlackLittermanPrior and AugmentedBlackLittermanPrior do not — see their warnings.
The views are written on the factor axis
views resolves against sets.dict[sets.tfkey] — the axis UniverseSets declares for factors — because that is the distribution they update. The asset axis is still required (every UniverseSets carries one) and is what port_opt_view slices; the factor entries come back untouched, which is why this field is @vprop rather than exempted by hand.
sets.dict[sets.tfkey] must name the columns of F in order; factor_universe checks it, and reports the factor axis rather than the asset one when it is missing or the wrong length.
Validation
- If
viewsis aLinearConstraintEstimator,!isnothing(sets). - If
views_confis notnothing,views_confis validated withassert_bl_views_conf. - If
tauis notnothing,tau > 0.
w is not validated here. Its length is a property of the returns matrix, which the constructor never sees, so a wrong length surfaces at prior as a DimensionMismatch out of equilibrium_mu and only when l is set. The constructor also accepts an empty w, where the sibling AugmentedBlackLittermanPrior refuses one.
Propagated parameters
When factory is called on this type, the following @fprop-tagged fields are automatically propagated:
pe: Recursively updated viafactory.re: Recursively updated viafactory.ve: Recursively updated viafactory.
View parameters
When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:
re: Recursively viewed viaport_opt_view.ve: Recursively viewed viaport_opt_view.sets: Sliced to the selected indices viaport_opt_view.w: Sliced to the selected indices viaport_opt_view.
Examples
julia> FactorBlackLittermanPrior(; sets = UniverseSets(; dict = Dict("nx" => ["A", "B", "C"], "nf" => ["F1", "F2"])), views = LinearConstraintEstimator(; val = ["F1 == 0.03", "F2 == 0.04"]))FactorBlackLittermanPrior pe ┼ EmpiricalPrior │ ce ┼ PortfolioOptimisersCovariance │ │ ce ┼ Covariance │ │ │ me ┼ SimpleExpectedReturns │ │ │ │ w ┴ nothing │ │ │ ce ┼ GeneralCovariance │ │ │ │ ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true) │ │ │ │ w ┴ nothing │ │ │ alg ┼ FullMoment() │ │ │ w ┴ nothing │ │ mp ┼ MatrixProcessing │ │ │ pdm ┼ Posdef │ │ │ │ alg ┼ UnionAll: NearestCorrelationMatrix.Newton │ │ │ │ kwargs ┴ @NamedTuple{}: NamedTuple() │ │ │ dn ┼ nothing │ │ │ dt ┼ nothing │ │ │ alg ┼ nothing │ │ │ order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg) │ me ┼ SimpleExpectedReturns │ │ w ┴ nothing │ horizon ┼ nothing │ fill_limit ┴ nothing f_mp ┼ MatrixProcessing │ pdm ┼ Posdef │ │ alg ┼ UnionAll: NearestCorrelationMatrix.Newton │ │ kwargs ┴ @NamedTuple{}: NamedTuple() │ dn ┼ nothing │ dt ┼ nothing │ alg ┼ nothing │ order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg) mp ┼ MatrixProcessing │ pdm ┼ Posdef │ │ alg ┼ UnionAll: NearestCorrelationMatrix.Newton │ │ kwargs ┴ @NamedTuple{}: NamedTuple() │ dn ┼ nothing │ dt ┼ nothing │ alg ┼ nothing │ order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg) re ┼ StepwiseRegression │ crit ┼ PValue │ │ t ┴ Float64: 0.05 │ alg ┼ ForwardSelection() │ tgt ┼ LinearModel │ │ kwargs ┴ @NamedTuple{}: NamedTuple() ve ┼ SimpleVariance │ me ┼ SimpleExpectedReturns │ │ w ┴ nothing │ w ┼ nothing │ corrected ┴ Bool: true views ┼ LinearConstraintEstimator │ val ┼ Vector{String}: ["F1 == 0.03", "F2 == 0.04"] │ key ┴ nothing sets ┼ UniverseSets │ xkey ┼ String: "nx" │ uxkey ┼ String: "ux" │ tfkey ┼ String: "nf" │ utfkey ┼ String: "uf" │ cfkey ┼ String: "ncf" │ ucfkey ┼ String: "ucf" │ nikey ┼ String: "ni" │ dict ┴ Dict{String, Vector{String}}: Dict("nf" => ["F1", "F2"], "nx" => ["A", "B", "C"]) views_conf ┼ nothing w ┼ nothing rf ┼ Float64: 0.0 l ┼ nothing tau ┼ nothing rsd ┴ Bool: trueThe incremental fit
This prior has no exact incremental recursion, so it takes the online step by refitting from a sample buffer: Online seeds cache, partial_fit! appends each observation to it verbatim, and the one-argument prior runs this estimator's own batch verb over the rows the buffer kept. The answer is therefore exactly a batch fit over those rows, and a max_history on the wrapper windows the whole fit.
cache travels the three propagation channels as every partial-fit state does: factory carries it unchanged, port_opt_view slices it to the selected assets, and obs_weights_view drops it, because no slice of a state exists on the observation axis. It is not rendered, because a running buffer is not the configuration a reader looks the type up for.
Related
AbstractLowOrderPriorEstimator_FEmpiricalPriorBlackLittermanViewsUniverseSetsLowOrderPriorpriorfactoryport_opt_view
References
- [29] F. Black and R. Litterman. Global portfolio optimization. Financial Analysts Journal 48, 28–43 (1992).
- [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 5.1, Equations 5.13 to 5.15, over the factor axis, and Section 4.1, Equations 4.2 and 4.3, for the lift onto the assets.
PortfolioOptimisers.prior — Function
prior(pe::FactorBlackLittermanPrior, X::MatNum, F::MatNum,
pnl::Option{<:AssetPanel} = nothing; dims::Int = 1,
strict::Bool = false, kwargs...)Compute factor Black-Litterman prior moments for asset returns.
prior estimates the mean and covariance of asset returns using the factor-based Black-Litterman model, combining an asset prior estimator, matrix post-processing for factors and assets, regression and variance estimators, user or algorithmic views, asset sets, view confidences, weights, risk-free rate, leverage, blending parameter tau, and a residual variance flag. This method supports both direct and constraint-based views, flexible confidence specification, and matrix processing, and incorporates factor regression and residual adjustment for posterior inference.
When pe.tau is nothing the blending parameter is 1/T, where T is the number of observations of the oriented X. The mean handed to the update is a total return over the factors, which is the scale the view returns in Q are written on, and the factor block is reported on that scale. pe.rf reaches the update on the pe.l branch alone, because that is the only branch whose prior mean is a risk premium rather than a total return. The closed form below states the shift it leaves there. Where pe.l is nothing nothing reads the field, so it does not reach the answer.
Mathematical definition
Black-Litterman views are applied directly to the factor space, updating factor moments $(\boldsymbol{\Pi}_f, \mathbf{\Sigma}_f)$ via the standard BL equations, then asset posteriors are reconstructed through the loadings matrix:
\[\begin{align} \hat{\boldsymbol{\mu}} &= \mathbf{M} \hat{\boldsymbol{\mu}}_{f,BL} + \boldsymbol{b}\,. \end{align}\]
\[\begin{align} \hat{\mathbf{\Sigma}} &= \mathbf{M} \hat{\mathbf{\Sigma}}_{f,BL} \mathbf{M}^\intercal + \mathbf{\Sigma}_\varepsilon\,. \end{align}\]
Where:
- $N$, $K$, $K_v$, $T$: The number of assets, of factors, of views, and of observations.
- $\hat{\boldsymbol{\mu}}$: $N \times 1$ posterior asset mean vector,
pr.mu. - $\hat{\mathbf{\Sigma}}$: $N \times N$ posterior asset covariance matrix,
pr.sigma. - $\boldsymbol{\Pi}_f$, $\mathbf{\Sigma}_f$: $K \times 1$ and $K \times K$ prior factor moments, from
pe.pefit onF. - $\hat{\boldsymbol{\mu}}_{f,BL}$: $K \times 1$ Black-Litterman posterior factor mean,
pr.fpr.mu. - $\hat{\mathbf{\Sigma}}_{f,BL}$: $K \times K$ Black-Litterman posterior factor covariance,
pr.fpr.sigma. - $\mathbf{M}$: $N \times K$ factor loadings matrix,
pr.rr.M. - $\boldsymbol{b}$: $N \times 1$ regression intercept vector,
pr.rr.b. - $\mathbf{P}$: $K_v \times K$ views matrix, over the factor axis.
- $\mathbf{\Omega}$: $K_v \times K_v$ view uncertainty matrix, from
calc_omegaandbl_preroll. - $\mathbf{\Sigma}_\varepsilon$: $N \times N$ diagonal residual variance matrix, zero when
rsd = false. - $\tau$: Scaling parameter,
1/Tby default. - $r_{f}$: Risk-free rate, added once by
apply_rfto the equilibrium factor mean. It is therefore inside $\hat{\boldsymbol{\mu}}_{f,BL}$ wherepe.lis set, and absent wherepe.lisnothing.
The factor moments are the ordinary Black-Litterman posterior of vanilla_posteriors, computed over the factor axis: $\mathbf{P}$ has $K$ columns, and $\mathbf{\Sigma}_f$ is the factor prior's covariance. That is literal, not an analogy: running vanilla_posteriors by hand on the factor prior and the bl_preroll triple reproduces pr.fpr.mu and pr.fpr.sigma, and the two lifted forms above reproduce pr.mu and pr.sigma on both rsd branches. The chol this lift returns still factorises the covariance it is returned with, so the residual block reaches the factor and the covariance together.
The rate reaches the answer through the factors, and only on one branch. Where pe.l is set the equilibrium mean is a risk premium, so apply_rf converts it to a total factor return before the update. The blend and the lift then carry that whole factor mean, the rate with it. Writing $\mathbf{G} = \tau\mathbf{\Sigma}_f\mathbf{P}^\intercal(\mathbf{P}\tau\mathbf{\Sigma}_f\mathbf{P}^\intercal + \mathbf{\Omega})^{-1}$ for the update gain and $\mathbf{1}$ for the vector of ones, the answer moves against the same estimator at $r_f = 0$ by:
\[\begin{align} \Delta\hat{\boldsymbol{\mu}} &= r_{f}\mathbf{M}\left(\mathbf{I} - \mathbf{G}\mathbf{P}\right)\mathbf{1}\,. \end{align}\]
The shift is linear in $r_f$ and depends on the views through $\mathbf{G}$. It is [0.489, 0.831, 0.868, 0.183, 0.973] per unit of $r_f$ on the sample above, matching the closed form to 1e-16 and agreeing between rf = 0.03 and rf = 0.06. Where pe.l is nothing the prior mean is the factor prior's own, a total return already, so nothing reads pe.rf and the same two fits differ by 0.0 in every entry.
Algorithm
- Orient
XandFwithdims_oriented, toobservations × assetsandobservations × factors. - When
pe.viewsresolves names, check the declared factor axis against the width ofFwithfactor_universe. A precomputedBlackLittermanViewsresolves no name, so step 6 checks its width instead. - Reduce
Xto the assets it can be fitted over withcoverage_reduction, underpnl, giving the mask andXi. This member wraps a factor prior, so there is no asset-side prior result to read an Investable Mask off and the gap is read out of the returns themselves. - Fit the wrapped prior
pe.peonFalone, givingf_prior, and readprior_muandprior_sigmaoff it. The wrapped estimator is bounded over the asset axis, but the matrix it is handed here is the factor one. - Regress
XionFwithfactor_reconstructionunderpe.re, giving the regression resultrrand the reconstructed returnsposterior_X. - Assemble the views and their uncertainty with
bl_preroll, overprior_sigmaandsize(Xi, 1)observations, givingblp. The axis is:tfkey, so no view row is ever dropped for a departed asset and no ledger is kept. - Put the prior mean on the total-return scale the views are written on, giving
prior_total_mu. Whenpe.lis set this is the equilibrium mean ofequilibrium_mu, a risk premium, pluspe.rfbyapply_rf, overpe.wsliced to the reduced axis byinvestable_weights_view; otherwise it isprior_mu, which is on that scale already. - Run the master equations with
bl_posteriors, giving the posterior factor pair. When no view row survived it hands backprior_total_muand the factor prior covariance instead, and step 10 lifts those exactly as it lifts a posterior pair. - Process the posterior factor covariance in place with
matrix_processing!, underpe.f_mpandF. - Lift the posterior factor pair onto the reduced assets with
factor_lift, givingmu,sigma,cholandesigma. This is the liftFactorPriorapplies; only the factor moments handed to it differ. It adds the residual block whenpe.rsdistrue, and processessigmaunderpe.mp. - Write
esigmaonto theesigmafield ofrr. Underpe.rsd = truethe field holds the residual variances the lift measured, and underpe.rsd = falseit holdsnothing, because the lift added no residual block. - Forward the factor block with
forward_prior, replacingmuandsigmaby the posterior factor pair and droppingchol. It is not expanded: the reduction never touched the factor axis. - Announce the departures once with
announce_bl_departures, naming them withinvestable_universe_names. - Write every asset-axis block back onto the full universe: the moment pair with
expand_moment, the reconstruction withexpand_columnsand the regression withexpand_regression.cholis dropped instead of expanded, because aNaNframe has no factorisation. - Build the carrier directly, taking
wand its diagnostics fromf_priorand carrying noZ.
Arguments
pe: Factor Black-Litterman prior estimator.X: Asset returns matrix (observations × assets).F: Factor matrix (observations × factors).pnl: OptionalAssetPanel, the panel the carrier held. A wrapping prior forwards it unchanged, so that it can compose an estimator that is fitted on a panel. An estimator that reads no panel ignores it. The prior this estimator nests is fitted on the factors, whose axis no panel describes, so the panel stops here.dims: Dimension along which to perform the computation.strict: Iftrue, enforce strict validation of views and sets. Default isfalse.kwargs...: Additional keyword arguments passed to underlying estimators and matrix processing.
Validation
dims in (1, 2).- If
pe.viewsis aLinearConstraintEstimator,haskey(pe.sets.dict, pe.sets.tfkey)andlength(pe.sets.dict[pe.sets.tfkey]) == size(F, 2), both viafactor_universe.
pe.w has no named check. When pe.l is set, a pe.w whose length is not size(X, 2) raises a bare DimensionMismatch from the multiplication inside equilibrium_mu. When pe.l is nothing, pe.w is never read.
Returns
pr::LowOrderPrior: Result object carrying the reconstructed asset returns, the posterior asset mean vector, the posterior asset covariance matrix, its Cholesky factor, the factor prior's observation weighting and diagnostics, the regression result, and a factor blockfprholding the posterior factor moments, on the total-return scale the update ran on.fpr.cholisnothing, and noZis carried.
Related
References
- [5]
- D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
- [29]
- F. Black and R. Litterman. Global portfolio optimization. Financial Analysts Journal 48, 28–43 (1992).